Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import javax.naming.ConfigurationException;
import javax.xml.parsers.ParserConfigurationException;

import com.xensource.xenapi.VTPM;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageAnswer;
import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageCommand;
Expand Down Expand Up @@ -5826,4 +5827,82 @@ public void destroyVm(VM vm, Connection connection, boolean forced) throws XenAP
public void destroyVm(VM vm, Connection connection) throws XenAPIException, XmlRpcException {
destroyVm(vm, connection, false);
}

/**
* Configure vTPM (Virtual Trusted Platform Module) support for a VM.
* vTPM provides a virtual TPM 2.0 device for VMs, enabling features like Secure Boot and disk encryption.
*
* Requirements:
* - XenServer/XCP-ng 8.3 (and above)
* - UEFI Secure Boot enabled
* - VM in halted state
*
* @param conn XenServer connection
* @param vm The VM to configure
* @param vmSpec VM specification containing vTPM settings
*/
public void configureVTPM(Connection conn, VM vm, VirtualMachineTO vmSpec) throws XenAPIException, XmlRpcException {
if (vmSpec == null || vmSpec.getDetails() == null) {
return;
}

String vtpmEnabled = vmSpec.getDetails().getOrDefault(VmDetailConstants.VIRTUAL_TPM_ENABLED, null);

final Map<String, String> platform = vm.getPlatform(conn);
if (platform != null) {
final String guestRequiresVtpm = platform.get("vtpm");
if (guestRequiresVtpm != null && Boolean.parseBoolean(guestRequiresVtpm) && !Boolean.parseBoolean(vtpmEnabled)) {
logger.warn("Guest OS requires vTPM by default, even if VM details doesn't have the setting: {}", vmSpec.getName());
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a grammatical error in this log message. "doesn't" should be "don't" because "VM details" is plural (referring to the details map/collection). The correct phrasing should be: "Guest OS requires vTPM by default, even if VM details don't have the setting"

Suggested change
logger.warn("Guest OS requires vTPM by default, even if VM details doesn't have the setting: {}", vmSpec.getName());
logger.warn("Guest OS requires vTPM by default, even if VM details don't have the setting: {}", vmSpec.getName());

Copilot uses AI. Check for mistakes.
return;
}
}

if (!Boolean.parseBoolean(vtpmEnabled)) {
return;
}

String bootMode = StringUtils.defaultIfEmpty(vmSpec.getDetails().get(ApiConstants.BootType.UEFI.toString()), null);
String bootType = (bootMode == null) ? ApiConstants.BootType.BIOS.toString() : ApiConstants.BootType.UEFI.toString();

if (!ApiConstants.BootType.UEFI.toString().equals(bootType)) {
Comment on lines +5865 to +5867
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for determining boot type is incorrect. If bootMode is null, bootType is set to BIOS, but if bootMode has any non-empty value, bootType is unconditionally set to UEFI. This means any non-null value in the UEFI key will be treated as UEFI mode, regardless of the actual value. The logic should check the value of bootMode to determine if it's truly UEFI or not. Consider checking if the bootMode value equals "UEFI" or similar logic to properly determine the boot type.

Suggested change
String bootType = (bootMode == null) ? ApiConstants.BootType.BIOS.toString() : ApiConstants.BootType.UEFI.toString();
if (!ApiConstants.BootType.UEFI.toString().equals(bootType)) {
if (!ApiConstants.BootType.UEFI.toString().equalsIgnoreCase(bootMode)) {

Copilot uses AI. Check for mistakes.
logger.warn("vTPM requires UEFI boot mode. Skipping vTPM configuration for VM: {}", vmSpec.getName());
return;
}

try {
Set<VTPM> existingVtpms = vm.getVTPMs(conn);
if (!existingVtpms.isEmpty()) {
logger.debug("vTPM already exists for VM: {}", vmSpec.getName());
return;
}

// Creates vTPM using: xe vtpm-create vm-uuid=<uuid>
String vmUuid = vm.getUuid(conn);
String result = callHostPlugin(conn, "vmops", "create_vtpm", "vm_uuid", vmUuid);

if (result == null || result.isEmpty() || result.startsWith("ERROR:") || result.startsWith("EXCEPTION:")) {
throw new CloudRuntimeException("Failed to create vTPM, result: " + result);
}

logger.info("Successfully created vTPM {} for VM: {}", result.trim(), vmSpec.getName());
} catch (Exception e) {
logger.warn("Failed to configure vTPM for VM: {}, continuing without vTPM", vmSpec.getName(), e);
}
}

public boolean isVTPMSupported(Connection conn, Host host) {
try {
Host.Record hostRecord = host.getRecord(conn);
String productVersion = hostRecord.softwareVersion.get("product_version");
if (productVersion == null) {
return false;
}
ComparableVersion currentVersion = new ComparableVersion(productVersion);
ComparableVersion minVersion = new ComparableVersion("8.2.0");
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The minimum version check is inconsistent with the PR description and documentation. The PR title and description state support for XenServer 8.4 / XCP-ng 8.3/8.4, and the documentation comment at line 5836 states "XenServer/XCP-ng 8.3 (and above)", but this code checks for version 8.2.0 or higher. This version mismatch could enable vTPM on versions that don't properly support it. The minimum version should be updated to "8.3.0" to match the stated requirements.

Copilot uses AI. Check for mistakes.
return currentVersion.compareTo(minVersion) >= 0;
} catch (Exception e) {
logger.warn("Failed to check vTPM support on host", e);
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,20 @@ public Answer execute(final ReadyCommand command, final CitrixResourceBase citri
final Set<VM> vms = host.getResidentVMs(conn);
citrixResourceBase.destroyPatchVbd(conn, vms);

} catch (final Exception e) {
logger.warn("Unable to destroy CD-ROM device for system VMs", e);
}

try {
final Host host = Host.getByUuid(conn, citrixResourceBase.getHost().getUuid());
final Host.Record hr = host.getRecord(conn);
if (isUefiSupported(CitrixHelper.getProductVersion(hr))) {
hostDetails.put(com.cloud.host.Host.HOST_UEFI_ENABLE, Boolean.TRUE.toString());
}
} catch (final Exception e) {
} catch (Exception e) {
logger.warn("Unable to get UEFI support info", e);
}

try {
final boolean result = citrixResourceBase.cleanupHaltedVms(conn);
if (!result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ public Answer execute(final StartCommand command, final CitrixResourceBase citri
citrixResourceBase.createVGPU(conn, command, vm, gpuDevice);
}

try {
if (citrixResourceBase.isVTPMSupported(conn, host)) {
citrixResourceBase.configureVTPM(conn, vm, vmSpec);
}
} catch (Exception e) {
logger.warn("Failed to configure vTPM for VM " + vmName + ", continuing without vTPM", e);
}

Host.Record record = host.getRecord(conn);
String xenBrand = record.softwareVersion.get("product_brand");
String xenVersion = record.softwareVersion.get("product_version");
Expand Down
40 changes: 39 additions & 1 deletion scripts/vm/hypervisor/xenserver/xenserver84/vmops
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,43 @@ def network_rules(session, args):
except:
logging.exception("Failed to network rule!")

@echo
def create_vtpm(session, args):
util.SMlog("create_vtpm called with args: %s" % str(args))

try:
vm_uuid = args.get('vm_uuid')
if not vm_uuid:
return "ERROR: vm_uuid parameter is required"

# Check if vTPM already exists for this VM
cmd = ['xe', 'vtpm-list', 'vm-uuid=' + vm_uuid, '--minimal']
result = util.pread2(cmd)
existing_vtpms = result.strip()

if existing_vtpms:
util.SMlog("vTPM already exists for VM %s: %s" % (vm_uuid, existing_vtpms))
return existing_vtpms.split(',')[0]

cmd = ['xe', 'vtpm-create', 'vm-uuid=' + vm_uuid]
result = util.pread2(cmd)
vtpm_uuid = result.strip()

if vtpm_uuid:
util.SMlog("Successfully created vTPM %s for VM %s" % (vtpm_uuid, vm_uuid))
return vtpm_uuid
else:
return "ERROR: Failed to create vTPM, empty result"

except CommandException as e:
error_msg = "xe command failed: %s" % str(e)
util.SMlog("ERROR: %s" % error_msg)
return "ERROR: " + error_msg
except Exception as e:
error_msg = str(e)
util.SMlog("ERROR: %s" % error_msg)
return "ERROR: " + error_msg

if __name__ == "__main__":
XenAPIPlugin.dispatch({"pingtest": pingtest, "setup_iscsi":setup_iscsi,
"preparemigration": preparemigration,
Expand All @@ -1604,4 +1641,5 @@ if __name__ == "__main__":
"createFileInDomr":createFileInDomr,
"kill_copy_process":kill_copy_process,
"secureCopyToHost":secureCopyToHost,
"runPatchScriptInDomr": runPatchScriptInDomr})
"runPatchScriptInDomr": runPatchScriptInDomr,
"create_vtpm": create_vtpm})
Original file line number Diff line number Diff line change
Expand Up @@ -5400,6 +5400,10 @@ private void fillVMOrTemplateDetailOptions(final Map<String, List<String>> optio
options.put(VmDetailConstants.RAM_RESERVATION, Collections.emptyList());
options.put(VmDetailConstants.VIRTUAL_TPM_ENABLED, Arrays.asList("true", "false"));
}

if (HypervisorType.XenServer.equals(hypervisorType)) {
options.put(VmDetailConstants.VIRTUAL_TPM_ENABLED, Arrays.asList("true", "false"));
}
}

@Override
Expand Down
Loading