Skip to content

Commit 5d28e66

Browse files
vTPM: support KVM and VMware (#10543)
* KVM: add Virtual TPM model and version * KVM: add admin-only VM setting GUEST.CPU.MODE and GUEST.CPU.MODEL * VMware: add vTPM * vTPM: do not set Key due to 'Cannot add multiple devices using the same device key..' * vTPM: add unit test testTpmModel * engine/schema: remove user vm details for guest CPU mode/model * vTPM: extra methods as Daan's requests * vTPM: add unit tests in VmwareResourceTest * vTPM: update unit tests in VmwareResourceTest * vTPM: add unit test in LibvirtComputingResourceTest * vTPM: use the default TPM version if an invalid version is passed * vTPM: requires UEFI on vmware and do nothing if it is not enabled/disabled * vTPM: let uses to add UEFI on vmware * Update plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com> * Update plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com> * vTPM: remove template details for guest CPU mode/model * UI: boot vm from ISO into UEFI/SECURE mode --------- Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com>
1 parent 12c077d commit 5d28e66

File tree

12 files changed

+259
-3
lines changed

12 files changed

+259
-3
lines changed

api/src/main/java/com/cloud/vm/VmDetailConstants.java

+9
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,13 @@ public interface VmDetailConstants {
101101
String VMWARE_HOST_NAME = String.format("%s-host", VMWARE_TO_KVM_PREFIX);
102102
String VMWARE_DISK = String.format("%s-disk", VMWARE_TO_KVM_PREFIX);
103103
String VMWARE_MAC_ADDRESSES = String.format("%s-mac-addresses", VMWARE_TO_KVM_PREFIX);
104+
105+
// TPM
106+
String VIRTUAL_TPM_ENABLED = "virtual.tpm.enabled";
107+
String VIRTUAL_TPM_MODEL = "virtual.tpm.model";
108+
String VIRTUAL_TPM_VERSION = "virtual.tpm.version";
109+
110+
// CPU mode and model, ADMIN only
111+
String GUEST_CPU_MODE = "guest.cpu.mode";
112+
String GUEST_CPU_MODEL = "guest.cpu.model";
104113
}

api/src/main/java/org/apache/cloudstack/query/QueryService.java

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// under the License.
1717
package org.apache.cloudstack.query;
1818

19+
import java.util.Arrays;
1920
import java.util.List;
2021

2122
import org.apache.cloudstack.affinity.AffinityGroupResponse;
@@ -97,13 +98,16 @@
9798
import org.apache.cloudstack.framework.config.ConfigKey;
9899

99100
import com.cloud.exception.PermissionDeniedException;
101+
import com.cloud.vm.VmDetailConstants;
100102

101103
/**
102104
* Service used for list api query.
103105
*
104106
*/
105107
public interface QueryService {
106108

109+
List<String> RootAdminOnlyVmSettings = Arrays.asList(VmDetailConstants.GUEST_CPU_MODE, VmDetailConstants.GUEST_CPU_MODEL);
110+
107111
// Config keys
108112
ConfigKey<Boolean> AllowUserViewDestroyedVM = new ConfigKey<>("Advanced", Boolean.class, "allow.user.view.destroyed.vm", "false",
109113
"Determines whether users can view their destroyed or expunging vm ", true, ConfigKey.Scope.Account);

engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
-- Schema upgrade from 4.20.0.0 to 4.20.1.0
2020
--;
2121

22+
-- Delete user vm details for guest CPU mode/model which are root admin only
23+
DELETE FROM `cloud`.`user_vm_details` WHERE `name` IN ('guest.cpu.mode','guest.cpu.model');
24+
25+
-- Delete template details for guest CPU mode/model which are root admin only
26+
DELETE FROM `cloud`.`vm_template_details` WHERE `name` IN ('guest.cpu.mode','guest.cpu.model');
27+
2228
-- Add column api_key_access to user and account tables
2329
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.user', 'api_key_access', 'boolean DEFAULT NULL COMMENT "is api key access allowed for the user" AFTER `secret_key`');
2430
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.account', 'api_key_access', 'boolean DEFAULT NULL COMMENT "is api key access allowed for the account" ');

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java

+24-2
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SCSIDef;
167167
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SerialDef;
168168
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.TermPolicy;
169+
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.TpmDef;
169170
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.VideoDef;
170171
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef;
171172
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogAction;
@@ -2660,6 +2661,11 @@ protected DevicesDef createDevicesDef(VirtualMachineTO vmTO, GuestDef guest, int
26602661
devices.addDevice(createGraphicDef(vmTO));
26612662
devices.addDevice(createTabletInputDef());
26622663

2664+
TpmDef tpmDef = createTpmDef(vmTO);
2665+
if (tpmDef != null) {
2666+
devices.addDevice(tpmDef);
2667+
}
2668+
26632669
if (isGuestAarch64()) {
26642670
createArm64UsbDef(devices);
26652671
}
@@ -2840,8 +2846,11 @@ public int calculateCpuShares(VirtualMachineTO vmTO) {
28402846

28412847
private CpuModeDef createCpuModeDef(VirtualMachineTO vmTO, int vcpus) {
28422848
final CpuModeDef cmd = new CpuModeDef();
2843-
cmd.setMode(guestCpuMode);
2844-
cmd.setModel(guestCpuModel);
2849+
Map<String, String> details = vmTO.getDetails();
2850+
String cpuMode = MapUtils.isNotEmpty(details) && details.get(VmDetailConstants.GUEST_CPU_MODE) != null ? details.get(VmDetailConstants.GUEST_CPU_MODE) : guestCpuMode;
2851+
String cpuModel = MapUtils.isNotEmpty(details) && details.get(VmDetailConstants.GUEST_CPU_MODEL) != null ? details.get(VmDetailConstants.GUEST_CPU_MODEL) : guestCpuModel;
2852+
cmd.setMode(cpuMode);
2853+
cmd.setModel(cpuModel);
28452854
if (VirtualMachine.Type.User.equals(vmTO.getType())) {
28462855
cmd.setFeatures(cpuFeatures);
28472856
}
@@ -2850,6 +2859,19 @@ private CpuModeDef createCpuModeDef(VirtualMachineTO vmTO, int vcpus) {
28502859
return cmd;
28512860
}
28522861

2862+
protected TpmDef createTpmDef(VirtualMachineTO vmTO) {
2863+
Map<String, String> details = vmTO.getDetails();
2864+
if (MapUtils.isEmpty(details)) {
2865+
return null;
2866+
}
2867+
String tpmModel = details.get(VmDetailConstants.VIRTUAL_TPM_MODEL);
2868+
if (tpmModel == null) {
2869+
return null;
2870+
}
2871+
String tpmVersion = details.get(VmDetailConstants.VIRTUAL_TPM_VERSION);
2872+
return new TpmDef(tpmModel, tpmVersion);
2873+
}
2874+
28532875
private void configureGuestIfUefiEnabled(boolean isSecureBoot, String bootMode, GuestDef guest) {
28542876
setGuestLoader(bootMode, SECURE, guest, GuestDef.GUEST_LOADER_SECURE);
28552877
setGuestLoader(bootMode, LEGACY, guest, GuestDef.GUEST_LOADER_LEGACY);

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java

+77
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.File;
2020
import java.util.ArrayList;
21+
import java.util.Arrays;
2122
import java.util.HashMap;
2223
import java.util.List;
2324
import java.util.Map;
@@ -2358,6 +2359,82 @@ public String toString() {
23582359
}
23592360
}
23602361

2362+
public static class TpmDef {
2363+
enum TpmModel {
2364+
TIS("tpm-tis"), // TPM Interface Specification (TIS)
2365+
CRB("tpm-crb"); // Command-Response Buffer (CRB)
2366+
2367+
final String model;
2368+
2369+
TpmModel(String model) {
2370+
this.model = model;
2371+
}
2372+
2373+
@Override
2374+
public String toString() {
2375+
return model;
2376+
}
2377+
}
2378+
2379+
enum TpmVersion {
2380+
V1_2("1.2"), // 1.2
2381+
V2_0("2.0"); // 2.0. Default version. The CRB model is only supported with version 2.0.
2382+
2383+
final String version;
2384+
2385+
TpmVersion(String version) {
2386+
this.version = version;
2387+
}
2388+
2389+
@Override
2390+
public String toString() {
2391+
return version;
2392+
}
2393+
}
2394+
2395+
private TpmModel model;
2396+
private TpmVersion version = TpmVersion.V2_0;
2397+
2398+
public TpmDef(TpmModel model, TpmVersion version) {
2399+
this.model = model;
2400+
if (version != null) {
2401+
this.version = version;
2402+
}
2403+
}
2404+
2405+
public TpmDef(String model, String version) {
2406+
this.model = Arrays.stream(TpmModel.values())
2407+
.filter(tpmModel -> tpmModel.toString().equals(model))
2408+
.findFirst()
2409+
.orElse(null);
2410+
if (version != null) {
2411+
this.version = Arrays.stream(TpmVersion.values())
2412+
.filter(tpmVersion -> tpmVersion.toString().equals(version))
2413+
.findFirst()
2414+
.orElse(this.version);;
2415+
}
2416+
}
2417+
2418+
public TpmModel getModel() {
2419+
return model;
2420+
}
2421+
2422+
public TpmVersion getVersion() {
2423+
return version;
2424+
}
2425+
2426+
@Override
2427+
public String toString() {
2428+
StringBuilder tpmBuidler = new StringBuilder();
2429+
if (model != null) {
2430+
tpmBuidler.append("<tpm model='").append(model).append("'>\n");
2431+
tpmBuidler.append("<backend type='emulator' version='").append(version).append("'/>\n");
2432+
tpmBuidler.append("</tpm>\n");
2433+
}
2434+
return tpmBuidler.toString();
2435+
}
2436+
}
2437+
23612438
public void setHvsType(String hvs) {
23622439
_hvsType = hvs;
23632440
}

plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java

+24
Original file line numberDiff line numberDiff line change
@@ -6541,4 +6541,28 @@ public void testGetDiskModelFromVMDetailVirtioBlk() {
65416541
DiskDef.DiskBus diskBus = libvirtComputingResourceSpy.getDiskModelFromVMDetail(virtualMachineTO);
65426542
assertEquals(DiskDef.DiskBus.VIRTIOBLK, diskBus);
65436543
}
6544+
6545+
@Test
6546+
public void testCreateTpmDef() {
6547+
VirtualMachineTO virtualMachineTO = Mockito.mock(VirtualMachineTO.class);
6548+
Map<String, String> details = new HashMap<>();
6549+
details.put(VmDetailConstants.VIRTUAL_TPM_MODEL, "tpm-tis");
6550+
details.put(VmDetailConstants.VIRTUAL_TPM_VERSION, "2.0");
6551+
Mockito.when(virtualMachineTO.getDetails()).thenReturn(details);
6552+
LibvirtVMDef.TpmDef tpmDef = libvirtComputingResourceSpy.createTpmDef(virtualMachineTO);
6553+
assertEquals(LibvirtVMDef.TpmDef.TpmModel.TIS, tpmDef.getModel());
6554+
assertEquals(LibvirtVMDef.TpmDef.TpmVersion.V2_0, tpmDef.getVersion());
6555+
}
6556+
6557+
@Test
6558+
public void testCreateTpmDefWithInvalidVersion() {
6559+
VirtualMachineTO virtualMachineTO = Mockito.mock(VirtualMachineTO.class);
6560+
Map<String, String> details = new HashMap<>();
6561+
details.put(VmDetailConstants.VIRTUAL_TPM_MODEL, "tpm-crb");
6562+
details.put(VmDetailConstants.VIRTUAL_TPM_VERSION, "3.0");
6563+
Mockito.when(virtualMachineTO.getDetails()).thenReturn(details);
6564+
LibvirtVMDef.TpmDef tpmDef = libvirtComputingResourceSpy.createTpmDef(virtualMachineTO);
6565+
assertEquals(LibvirtVMDef.TpmDef.TpmModel.CRB, tpmDef.getModel());
6566+
assertEquals(LibvirtVMDef.TpmDef.TpmVersion.V2_0, tpmDef.getVersion());
6567+
}
65446568
}

plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java

+11
Original file line numberDiff line numberDiff line change
@@ -566,9 +566,20 @@ public void testTopology() {
566566
assertEquals("<cpu><topology sockets='4' cores='2' threads='1' /></cpu>", cpuModeDef.toString());
567567
}
568568

569+
@Test
569570
public void testTopologyNoInfo() {
570571
LibvirtVMDef.CpuModeDef cpuModeDef = new LibvirtVMDef.CpuModeDef();
571572
cpuModeDef.setTopology(-1, -1, 4);
572573
assertEquals("<cpu></cpu>", cpuModeDef.toString());
573574
}
575+
576+
@Test
577+
public void testTpmModel() {
578+
LibvirtVMDef.TpmDef tpmDef = new LibvirtVMDef.TpmDef("tpm-tis", "2.0");
579+
assertEquals(LibvirtVMDef.TpmDef.TpmModel.TIS, tpmDef.getModel());
580+
assertEquals(LibvirtVMDef.TpmDef.TpmVersion.V2_0, tpmDef.getVersion());
581+
assertEquals("<tpm model='tpm-tis'>\n" +
582+
"<backend type='emulator' version='2.0'/>\n" +
583+
"</tpm>\n", tpmDef.toString());
584+
}
574585
}

plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java

+57
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@
5151

5252
import com.cloud.capacity.CapacityManager;
5353
import com.cloud.hypervisor.vmware.mo.HostDatastoreBrowserMO;
54+
import com.vmware.vim25.Description;
5455
import com.vmware.vim25.FileInfo;
5556
import com.vmware.vim25.FileQueryFlags;
5657
import com.vmware.vim25.FolderFileInfo;
5758
import com.vmware.vim25.HostDatastoreBrowserSearchResults;
5859
import com.vmware.vim25.HostDatastoreBrowserSearchSpec;
5960
import com.vmware.vim25.VirtualCdromIsoBackingInfo;
6061
import com.vmware.vim25.VirtualMachineConfigSummary;
62+
import com.vmware.vim25.VirtualTPM;
6163
import org.apache.cloudstack.api.ApiConstants;
6264
import org.apache.cloudstack.backup.PrepareForBackupRestorationCommand;
6365
import org.apache.cloudstack.storage.command.CopyCommand;
@@ -2597,12 +2599,16 @@ protected StartAnswer execute(StartCommand cmd) {
25972599

25982600
setBootOptions(vmSpec, bootMode, vmConfigSpec);
25992601

2602+
// Config vTPM
2603+
configureVirtualTPM(vmMo, vmSpec, vmConfigSpec);
2604+
26002605
if (StringUtils.isNotEmpty(vmStoragePolicyId)) {
26012606
vmConfigSpec.getVmProfile().add(vmProfileSpec);
26022607
if (logger.isTraceEnabled()) {
26032608
logger.trace(String.format("Configuring the VM %s with storage policy: %s", vmInternalCSName, vmStoragePolicyId));
26042609
}
26052610
}
2611+
26062612
//
26072613
// Configure VM
26082614
//
@@ -3203,6 +3209,57 @@ protected void configureSpecVideoCardNewVRamSize(VirtualMachineVideoCard videoCa
32033209
vmConfigSpec.getDeviceChange().add(arrayVideoCardConfigSpecs);
32043210
}
32053211

3212+
/**
3213+
* Add or Remove virtual TPM module
3214+
*
3215+
* @param vmMo virtual machine mo
3216+
* @param vmSpec virtual machine specs
3217+
* @param vmConfigSpec virtual machine config spec
3218+
* @throws Exception exception
3219+
*/
3220+
protected void configureVirtualTPM(VirtualMachineMO vmMo, VirtualMachineTO vmSpec, VirtualMachineConfigSpec vmConfigSpec) throws Exception {
3221+
String virtualTPMEnabled = vmSpec.getDetails().getOrDefault(VmDetailConstants.VIRTUAL_TPM_ENABLED, null);
3222+
if (Boolean.parseBoolean(virtualTPMEnabled)) {
3223+
for (VirtualDevice device : vmMo.getAllDeviceList()) {
3224+
if (device instanceof VirtualTPM) {
3225+
logger.debug(String.format("Virtual TPM device has already been added to VM %s, returning", vmMo.getVmName()));
3226+
return;
3227+
}
3228+
}
3229+
logger.debug(String.format("Adding Virtual TPM device to the VM %s", vmMo.getVmName()));
3230+
addVirtualTPMDevice(vmConfigSpec);
3231+
} else if (virtualTPMEnabled == null) {
3232+
logger.debug(String.format("Virtual TPM device is neither enabled nor disabled for VM %s, skipping", vmMo.getVmName()));
3233+
} else {
3234+
logger.debug(String.format("Virtual TPM device is disabled for VM %s", vmMo.getVmName()));
3235+
for (VirtualDevice device : vmMo.getAllDeviceList()) {
3236+
if (device instanceof VirtualTPM) {
3237+
logger.debug(String.format("Removing Virtual TPM device from VM %s as it is disabled", vmMo.getVmName()));
3238+
removeVirtualTPMDevice(vmConfigSpec, (VirtualTPM) device);
3239+
}
3240+
}
3241+
}
3242+
}
3243+
3244+
protected void addVirtualTPMDevice(VirtualMachineConfigSpec vmConfigSpec) {
3245+
Description description = new Description();
3246+
description.setSummary("Trusted Platform Module");
3247+
description.setLabel("Trusted Platform Module");
3248+
VirtualTPM virtualTPM = new VirtualTPM();
3249+
virtualTPM.setDeviceInfo(description);
3250+
VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();
3251+
deviceConfigSpec.setDevice(virtualTPM);
3252+
deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD);
3253+
vmConfigSpec.getDeviceChange().add(deviceConfigSpec);
3254+
}
3255+
3256+
protected void removeVirtualTPMDevice(VirtualMachineConfigSpec vmConfigSpec, VirtualTPM virtualTPM) {
3257+
VirtualDeviceConfigSpec virtualDeviceConfigSpec = new VirtualDeviceConfigSpec();
3258+
virtualDeviceConfigSpec.setDevice(virtualTPM);
3259+
virtualDeviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.REMOVE);
3260+
vmConfigSpec.getDeviceChange().add(virtualDeviceConfigSpec);
3261+
}
3262+
32063263
private void tearDownVm(VirtualMachineMO vmMo) throws Exception {
32073264

32083265
if (vmMo == null)

plugins/hypervisors/vmware/src/test/java/com/cloud/hypervisor/vmware/resource/VmwareResourceTest.java

+39
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
import com.vmware.vim25.FileInfo;
4949
import com.vmware.vim25.HostDatastoreBrowserSearchResults;
5050
import com.vmware.vim25.HostDatastoreBrowserSearchSpec;
51+
import com.vmware.vim25.VirtualTPM;
52+
import org.apache.cloudstack.api.ApiConstants;
5153
import org.apache.cloudstack.storage.command.CopyCommand;
5254
import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer;
5355
import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
@@ -842,4 +844,41 @@ public void testExecuteWithEmptyPath() throws Exception {
842844
assertEquals(Collections.singletonList(1L), answer.getSizes());
843845
assertEquals(Collections.singletonList(date.getTime()), answer.getLastModified());
844846
}
847+
848+
@Test
849+
public void testAddVirtualTPMDevice() throws Exception {
850+
VirtualMachineMO vmMo = Mockito.mock(VirtualMachineMO.class);
851+
VirtualMachineTO vmSpec = Mockito.mock(VirtualMachineTO.class);
852+
VirtualMachineConfigSpec vmConfigSpec = Mockito.mock(VirtualMachineConfigSpec.class);
853+
Map<String, String> details = new HashMap<>();
854+
details.put(ApiConstants.BootType.UEFI.toString(), "SECURE");
855+
details.put(VmDetailConstants.VIRTUAL_TPM_ENABLED, "true");
856+
when(vmSpec.getDetails()).thenReturn(details);
857+
when(vmMo.getAllDeviceList()).thenReturn(new ArrayList<>());
858+
List<VirtualDeviceConfigSpec> deviceChanges = Mockito.mock(List.class);
859+
when(vmConfigSpec.getDeviceChange()).thenReturn(deviceChanges);
860+
861+
vmwareResource.configureVirtualTPM(vmMo, vmSpec, vmConfigSpec);
862+
Mockito.verify(vmwareResource, Mockito.times(1)).addVirtualTPMDevice(vmConfigSpec);
863+
Mockito.verify(deviceChanges, Mockito.times(1)).add(any(VirtualDeviceConfigSpec.class));
864+
}
865+
866+
@Test
867+
public void testRemoveVirtualTPMDevice() throws Exception {
868+
VirtualMachineMO vmMo = Mockito.mock(VirtualMachineMO.class);
869+
VirtualMachineTO vmSpec = Mockito.mock(VirtualMachineTO.class);
870+
VirtualMachineConfigSpec vmConfigSpec = Mockito.mock(VirtualMachineConfigSpec.class);
871+
Map<String, String> details = new HashMap<>();
872+
details.put(ApiConstants.BootType.UEFI.toString(), "SECURE");
873+
details.put(VmDetailConstants.VIRTUAL_TPM_ENABLED, "false");
874+
when(vmSpec.getDetails()).thenReturn(details);
875+
VirtualTPM tpm = new VirtualTPM();
876+
when(vmMo.getAllDeviceList()).thenReturn(List.of(tpm));
877+
List<VirtualDeviceConfigSpec> deviceChanges = Mockito.mock(List.class);
878+
when(vmConfigSpec.getDeviceChange()).thenReturn(deviceChanges);
879+
880+
vmwareResource.configureVirtualTPM(vmMo, vmSpec, vmConfigSpec);
881+
Mockito.verify(vmwareResource, Mockito.times(1)).removeVirtualTPMDevice(vmConfigSpec, tpm);
882+
Mockito.verify(deviceChanges, Mockito.times(1)).add(any(VirtualDeviceConfigSpec.class));
883+
}
845884
}

0 commit comments

Comments
 (0)