diff --git a/pkg/controller/vsphere/reconciler.go b/pkg/controller/vsphere/reconciler.go index 74240b3b73..3f96b749d8 100644 --- a/pkg/controller/vsphere/reconciler.go +++ b/pkg/controller/vsphere/reconciler.go @@ -1015,6 +1015,23 @@ func clone(s *machineScope) (string, error) { deviceSpecs = append(deviceSpecs, networkDevices...) + // TODO: Replace this placeholder with actual provider spec field check + // When VSphereMachineProviderSpec.VirtualTPM field is added, replace the condition below: + // if s.providerSpec.VirtualTPM != nil && s.providerSpec.VirtualTPM.Enabled { + // For now, this is a placeholder that hardcodes adding a TPM device. + enableVirtualTPM := true + + if enableVirtualTPM { + // Add VirtualTPM device if needed (TODO: placeholder for future provider spec field) + tpmDeviceSpec, err := addVirtualTPMDevice(s, devices) + if err != nil { + return "", fmt.Errorf("error getting VirtualTPM specs: %w", err) + } + if tpmDeviceSpec != nil { + deviceSpecs = append(deviceSpecs, tpmDeviceSpec) + } + } + extraConfig := []types.BaseOptionValue{} extraConfig = append(extraConfig, IgnitionConfig(userData)...) @@ -1040,20 +1057,24 @@ func clone(s *machineScope) (string, error) { } } + // Create the VM configuration spec + vmConfigSpec := &types.VirtualMachineConfigSpec{ + Annotation: s.machine.GetName(), + // Assign the clone's InstanceUUID the value of the Kubernetes Machine + // object's UID. This allows lookup of the cloned VM prior to knowing + // the VM's UUID. + InstanceUuid: string(s.machine.UID), + Flags: newVMFlagInfo(), + ExtraConfig: extraConfig, + DeviceChange: deviceSpecs, + NumCPUs: numCPUs, + NumCoresPerSocket: numCoresPerSocket, + MemoryMB: s.providerSpec.MemoryMiB, + Firmware: getVMFirmware(enableVirtualTPM), + } + spec := types.VirtualMachineCloneSpec{ - Config: &types.VirtualMachineConfigSpec{ - Annotation: s.machine.GetName(), - // Assign the clone's InstanceUUID the value of the Kubernetes Machine - // object's UID. This allows lookup of the cloned VM prior to knowing - // the VM's UUID. - InstanceUuid: string(s.machine.UID), - Flags: newVMFlagInfo(), - ExtraConfig: extraConfig, - DeviceChange: deviceSpecs, - NumCPUs: numCPUs, - NumCoresPerSocket: numCoresPerSocket, - MemoryMB: s.providerSpec.MemoryMiB, - }, + Config: vmConfigSpec, Location: types.VirtualMachineRelocateSpec{ Datastore: types.NewReference(datastore.Reference()), Folder: types.NewReference(folder.Reference()), @@ -1417,6 +1438,45 @@ func getNetworkDevices(s *machineScope, resourcepool *object.ResourcePool, devic return networkDevices, nil } +// addVirtualTPMDevice creates VirtualTPM device specifications for the VM. +// This function is prepared for future integration with a VirtualTPM field in VSphereMachineProviderSpec. +func addVirtualTPMDevice(s *machineScope, devices object.VirtualDeviceList) (types.BaseVirtualDeviceConfigSpec, error) { + var baseVirtualDeviceConfigSpec types.BaseVirtualDeviceConfigSpec + // Check if TPM already exists in template + existingTPMs := devices.SelectByType((*types.VirtualTPM)(nil)) + if len(existingTPMs) > 0 { + klog.V(3).Infof("VirtualTPM already exists in template, skipping addition") + return baseVirtualDeviceConfigSpec, nil + } + + // Create new VirtualTPM device + tpmDevice := &types.VirtualTPM{ + VirtualDevice: types.VirtualDevice{ + Key: devices.NewKey(), + // Note: VirtualTPM doesn't require backing info or controller key + // as it's a standalone security device + }, + } + + baseVirtualDeviceConfigSpec = &types.VirtualDeviceConfigSpec{ + Device: tpmDevice, + Operation: types.VirtualDeviceConfigSpecOperationAdd, + } + + klog.V(2).Infof("Adding VirtualTPM device to VM %s", s.machine.GetName()) + + return baseVirtualDeviceConfigSpec, nil +} + +// getVMFirmware returns the VM firmware type based on certain factors. +func getVMFirmware(enableVirtualTPM bool) string { + if enableVirtualTPM { + // VirtualTPM requires EFI firmware. + return string(types.GuestOsDescriptorFirmwareTypeEfi) + } + return string(types.GuestOsDescriptorFirmwareTypeBios) +} + func newVMFlagInfo() *types.VirtualMachineFlagInfo { diskUUIDEnabled := true return &types.VirtualMachineFlagInfo{ diff --git a/pkg/controller/vsphere/reconciler_test.go b/pkg/controller/vsphere/reconciler_test.go index b60252f583..d70a8db166 100644 --- a/pkg/controller/vsphere/reconciler_test.go +++ b/pkg/controller/vsphere/reconciler_test.go @@ -1419,6 +1419,109 @@ func TestCreateDataDisks(t *testing.T) { } } +func TestGetVMFirmware(t *testing.T) { + // TODO +} + +func TestGetVirtualTPMDevices(t *testing.T) { + model, session, server := initSimulator(t) + t.Cleanup(model.Remove) + t.Cleanup(server.Close) + vm := simulator.Map.Any("VirtualMachine").(*simulator.VirtualMachine) + machine := object.NewVirtualMachine(session.Client.Client, vm.Reference()) + + deviceList, err := machine.Device(context.TODO()) + if err != nil { + t.Fatalf("Failed to obtain vm devices: %v", err) + } + + getMachineScope := func() *machineScope { + gates, _ := testutils.NewDefaultMutableFeatureGate() + return &machineScope{ + Context: context.TODO(), + machine: &machinev1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-tpm", + Namespace: "test", + Labels: map[string]string{ + machinev1.MachineClusterIDLabel: "CLUSTERID", + }, + }, + }, + providerSpec: &machinev1.VSphereMachineProviderSpec{}, + session: session, + providerStatus: &machinev1.VSphereMachineProviderStatus{}, + featureGates: gates, + } + } + + testCases := []struct { + name string + devices object.VirtualDeviceList + expectedDeviceCount int + expectedError string + }{ + { + name: "No existing TPM devices", + devices: deviceList, + expectedDeviceCount: 1, // TPM is enabled by default now + }, + { + name: "Empty device list", + devices: object.VirtualDeviceList{}, + expectedDeviceCount: 1, // TPM is enabled by default now + }, + } + + for _, test := range testCases { + tc := test + t.Run(tc.name, func(t *testing.T) { + scope := getMachineScope() + + // Call the function under test + tpmDeviceSpec, err := addVirtualTPMDevice(scope, tc.devices) + + // Check for expected errors + if tc.expectedError != "" { + if err == nil || err.Error() != tc.expectedError { + t.Fatalf("Expected error '%s', got: %v", tc.expectedError, err) + } + return + } + + // Check for unexpected errors + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Check device count - if we expect a device, tpmDeviceSpec should not be nil + if tc.expectedDeviceCount > 0 && tpmDeviceSpec == nil { + t.Fatalf("Expected %d TPM devices, got nil device spec", tc.expectedDeviceCount) + } + if tc.expectedDeviceCount == 0 && tpmDeviceSpec != nil { + t.Fatalf("Expected %d TPM devices, got non-nil device spec", tc.expectedDeviceCount) + } + + // If devices were created, validate their structure + if tpmDeviceSpec != nil { + spec := tpmDeviceSpec.(*types.VirtualDeviceConfigSpec) + if spec.Operation != types.VirtualDeviceConfigSpecOperationAdd { + t.Fatalf("Expected operation to be Add, got %v", spec.Operation) + } + + tpmDevice, ok := spec.Device.(*types.VirtualTPM) + if !ok { + t.Fatalf("Expected device to be VirtualTPM, got %T", spec.Device) + } + + if tpmDevice.Key == 0 { + t.Fatalf("Expected TPM device to have a non-zero key") + } + } + }) + } +} + func createAdditionalDisks(devices object.VirtualDeviceList, controller types.BaseVirtualController, numOfDisks int) object.VirtualDeviceList { deviceList := devices disks := devices.SelectByType((*types.VirtualDisk)(nil))