Skip to content

Commit 3f601cb

Browse files
weizhouapachesureshanaparti
authored andcommitted
vTPM: support KVM and VMware (apache#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 <[email protected]> * Update plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java Co-authored-by: Suresh Kumar Anaparti <[email protected]> * vTPM: remove template details for guest CPU mode/model * UI: boot vm from ISO into UEFI/SECURE mode --------- Co-authored-by: Suresh Kumar Anaparti <[email protected]>
1 parent 439ee92 commit 3f601cb

File tree

12 files changed

+260
-3
lines changed

12 files changed

+260
-3
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,13 @@ public interface VmDetailConstants {
104104
String VMWARE_HOST_NAME = String.format("%s-host", VMWARE_TO_KVM_PREFIX);
105105
String VMWARE_DISK = String.format("%s-disk", VMWARE_TO_KVM_PREFIX);
106106
String VMWARE_MAC_ADDRESSES = String.format("%s-mac-addresses", VMWARE_TO_KVM_PREFIX);
107+
108+
// TPM
109+
String VIRTUAL_TPM_ENABLED = "virtual.tpm.enabled";
110+
String VIRTUAL_TPM_MODEL = "virtual.tpm.model";
111+
String VIRTUAL_TPM_VERSION = "virtual.tpm.version";
112+
113+
// CPU mode and model, ADMIN only
114+
String GUEST_CPU_MODE = "guest.cpu.mode";
115+
String GUEST_CPU_MODEL = "guest.cpu.model";
107116
}

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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@
172172
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SoundDef;
173173
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.TPMDef;
174174
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.TermPolicy;
175+
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.TpmDef;
175176
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.VideoDef;
176177
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.VideoDef2;
177178
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef;
@@ -2917,11 +2918,17 @@ protected DevicesDef createDevicesDef(VirtualMachineTO vmTO, GuestDef guest, int
29172918
devices.addDevice(createVideoDef2(vmTO));
29182919
devices.addDevice(createConsoleDef());
29192920
devices.addDevice(createGraphicDef(vmTO));
2921+
29202922
if (!isGuestS390x()) {
29212923
devices.addDevice(createTabletInputDef());
29222924
}
29232925
devices.addDevice(createSoundDef(vmTO));
29242926

2927+
TpmDef tpmDef = createTpmDef(vmTO);
2928+
if (tpmDef != null) {
2929+
devices.addDevice(tpmDef);
2930+
}
2931+
29252932
if (isGuestAarch64()) {
29262933
createArm64UsbDef(devices);
29272934
}
@@ -3159,8 +3166,11 @@ public int calculateCpuShares(VirtualMachineTO vmTO) {
31593166

31603167
private CpuModeDef createCpuModeDef(VirtualMachineTO vmTO, int vcpus) {
31613168
final CpuModeDef cmd = new CpuModeDef();
3162-
cmd.setMode(guestCpuMode);
3163-
cmd.setModel(guestCpuModel);
3169+
Map<String, String> details = vmTO.getDetails();
3170+
String cpuMode = MapUtils.isNotEmpty(details) && details.get(VmDetailConstants.GUEST_CPU_MODE) != null ? details.get(VmDetailConstants.GUEST_CPU_MODE) : guestCpuMode;
3171+
String cpuModel = MapUtils.isNotEmpty(details) && details.get(VmDetailConstants.GUEST_CPU_MODEL) != null ? details.get(VmDetailConstants.GUEST_CPU_MODEL) : guestCpuModel;
3172+
cmd.setMode(cpuMode);
3173+
cmd.setModel(cpuModel);
31643174
if (VirtualMachine.Type.User.equals(vmTO.getType())) {
31653175
cmd.setFeatures(cpuFeatures);
31663176
}
@@ -3169,6 +3179,19 @@ private CpuModeDef createCpuModeDef(VirtualMachineTO vmTO, int vcpus) {
31693179
return cmd;
31703180
}
31713181

3182+
protected TpmDef createTpmDef(VirtualMachineTO vmTO) {
3183+
Map<String, String> details = vmTO.getDetails();
3184+
if (MapUtils.isEmpty(details)) {
3185+
return null;
3186+
}
3187+
String tpmModel = details.get(VmDetailConstants.VIRTUAL_TPM_MODEL);
3188+
if (tpmModel == null) {
3189+
return null;
3190+
}
3191+
String tpmVersion = details.get(VmDetailConstants.VIRTUAL_TPM_VERSION);
3192+
return new TpmDef(tpmModel, tpmVersion);
3193+
}
3194+
31723195
private void configureGuestIfUefiEnabled(boolean isSecureBoot, String bootMode, GuestDef guest) {
31733196
setGuestLoader(bootMode, SECURE, guest, GuestDef.GUEST_LOADER_SECURE);
31743197
setGuestLoader(bootMode, LEGACY, guest, GuestDef.GUEST_LOADER_LEGACY);

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

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.cloud.agent.properties.AgentPropertiesFileHandler;
2121
import java.io.File;
2222
import java.util.ArrayList;
23+
import java.util.Arrays;
2324
import java.util.HashMap;
2425
import java.util.List;
2526
import java.util.Map;
@@ -2473,6 +2474,82 @@ public String toString() {
24732474
}
24742475
}
24752476

2477+
public static class TpmDef {
2478+
enum TpmModel {
2479+
TIS("tpm-tis"), // TPM Interface Specification (TIS)
2480+
CRB("tpm-crb"); // Command-Response Buffer (CRB)
2481+
2482+
final String model;
2483+
2484+
TpmModel(String model) {
2485+
this.model = model;
2486+
}
2487+
2488+
@Override
2489+
public String toString() {
2490+
return model;
2491+
}
2492+
}
2493+
2494+
enum TpmVersion {
2495+
V1_2("1.2"), // 1.2
2496+
V2_0("2.0"); // 2.0. Default version. The CRB model is only supported with version 2.0.
2497+
2498+
final String version;
2499+
2500+
TpmVersion(String version) {
2501+
this.version = version;
2502+
}
2503+
2504+
@Override
2505+
public String toString() {
2506+
return version;
2507+
}
2508+
}
2509+
2510+
private TpmModel model;
2511+
private TpmVersion version = TpmVersion.V2_0;
2512+
2513+
public TpmDef(TpmModel model, TpmVersion version) {
2514+
this.model = model;
2515+
if (version != null) {
2516+
this.version = version;
2517+
}
2518+
}
2519+
2520+
public TpmDef(String model, String version) {
2521+
this.model = Arrays.stream(TpmModel.values())
2522+
.filter(tpmModel -> tpmModel.toString().equals(model))
2523+
.findFirst()
2524+
.orElse(null);
2525+
if (version != null) {
2526+
this.version = Arrays.stream(TpmVersion.values())
2527+
.filter(tpmVersion -> tpmVersion.toString().equals(version))
2528+
.findFirst()
2529+
.orElse(this.version);;
2530+
}
2531+
}
2532+
2533+
public TpmModel getModel() {
2534+
return model;
2535+
}
2536+
2537+
public TpmVersion getVersion() {
2538+
return version;
2539+
}
2540+
2541+
@Override
2542+
public String toString() {
2543+
StringBuilder tpmBuidler = new StringBuilder();
2544+
if (model != null) {
2545+
tpmBuidler.append("<tpm model='").append(model).append("'>\n");
2546+
tpmBuidler.append("<backend type='emulator' version='").append(version).append("'/>\n");
2547+
tpmBuidler.append("</tpm>\n");
2548+
}
2549+
return tpmBuidler.toString();
2550+
}
2551+
}
2552+
24762553
public void setHvsType(String hvs) {
24772554
_hvsType = hvs;
24782555
}

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

Lines changed: 24 additions & 0 deletions
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

Lines changed: 11 additions & 0 deletions
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

Lines changed: 57 additions & 0 deletions
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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
import com.vmware.vim25.FileInfo;
5151
import com.vmware.vim25.HostDatastoreBrowserSearchResults;
5252
import com.vmware.vim25.HostDatastoreBrowserSearchSpec;
53+
import com.vmware.vim25.VirtualTPM;
54+
import org.apache.cloudstack.api.ApiConstants;
5355
import org.apache.cloudstack.storage.command.CopyCommand;
5456
import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsAnswer;
5557
import org.apache.cloudstack.storage.command.browser.ListDataStoreObjectsCommand;
@@ -835,4 +837,41 @@ public void testExecuteWithEmptyPath() throws Exception {
835837
assertEquals(Collections.singletonList(1L), answer.getSizes());
836838
assertEquals(Collections.singletonList(date.getTime()), answer.getLastModified());
837839
}
840+
841+
@Test
842+
public void testAddVirtualTPMDevice() throws Exception {
843+
VirtualMachineMO vmMo = Mockito.mock(VirtualMachineMO.class);
844+
VirtualMachineTO vmSpec = Mockito.mock(VirtualMachineTO.class);
845+
VirtualMachineConfigSpec vmConfigSpec = Mockito.mock(VirtualMachineConfigSpec.class);
846+
Map<String, String> details = new HashMap<>();
847+
details.put(ApiConstants.BootType.UEFI.toString(), "SECURE");
848+
details.put(VmDetailConstants.VIRTUAL_TPM_ENABLED, "true");
849+
when(vmSpec.getDetails()).thenReturn(details);
850+
when(vmMo.getAllDeviceList()).thenReturn(new ArrayList<>());
851+
List<VirtualDeviceConfigSpec> deviceChanges = Mockito.mock(List.class);
852+
when(vmConfigSpec.getDeviceChange()).thenReturn(deviceChanges);
853+
854+
vmwareResource.configureVirtualTPM(vmMo, vmSpec, vmConfigSpec);
855+
Mockito.verify(vmwareResource, Mockito.times(1)).addVirtualTPMDevice(vmConfigSpec);
856+
Mockito.verify(deviceChanges, Mockito.times(1)).add(any(VirtualDeviceConfigSpec.class));
857+
}
858+
859+
@Test
860+
public void testRemoveVirtualTPMDevice() throws Exception {
861+
VirtualMachineMO vmMo = Mockito.mock(VirtualMachineMO.class);
862+
VirtualMachineTO vmSpec = Mockito.mock(VirtualMachineTO.class);
863+
VirtualMachineConfigSpec vmConfigSpec = Mockito.mock(VirtualMachineConfigSpec.class);
864+
Map<String, String> details = new HashMap<>();
865+
details.put(ApiConstants.BootType.UEFI.toString(), "SECURE");
866+
details.put(VmDetailConstants.VIRTUAL_TPM_ENABLED, "false");
867+
when(vmSpec.getDetails()).thenReturn(details);
868+
VirtualTPM tpm = new VirtualTPM();
869+
when(vmMo.getAllDeviceList()).thenReturn(List.of(tpm));
870+
List<VirtualDeviceConfigSpec> deviceChanges = Mockito.mock(List.class);
871+
when(vmConfigSpec.getDeviceChange()).thenReturn(deviceChanges);
872+
873+
vmwareResource.configureVirtualTPM(vmMo, vmSpec, vmConfigSpec);
874+
Mockito.verify(vmwareResource, Mockito.times(1)).removeVirtualTPMDevice(vmConfigSpec, tpm);
875+
Mockito.verify(deviceChanges, Mockito.times(1)).add(any(VirtualDeviceConfigSpec.class));
876+
}
838877
}

0 commit comments

Comments
 (0)