Skip to content
Draft
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
86 changes: 73 additions & 13 deletions pkg/controller/vsphere/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)...)
Expand All @@ -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()),
Expand Down Expand Up @@ -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{
Expand Down
103 changes: 103 additions & 0 deletions pkg/controller/vsphere/reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down