diff --git a/internal/webhooks/vspheremachine.go b/internal/webhooks/vspheremachine.go index 420df4733d..f183e9a10e 100644 --- a/internal/webhooks/vspheremachine.go +++ b/internal/webhooks/vspheremachine.go @@ -92,6 +92,17 @@ func (webhook *VSphereMachineWebhook) ValidateCreate(_ context.Context, raw runt allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "guestSoftPowerOffTimeout"), spec.GuestSoftPowerOffTimeout, "should be greater than 0")) } } + for i, device := range spec.PciDevices { + if device.VGPUProfile == "" { + if device.DeviceID == nil || device.VendorID == nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "template", "spec", "pciDevices", fmt.Sprintf("%d", i)), device, "should have both deviceId and vendorId set")) + } + } else { + if device.DeviceID != nil || device.VendorID != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "template", "spec", "pciDevices", fmt.Sprintf("%d", i)), device, "should have either deviceId + vendorId or vgpuProfile")) + } + } + } return nil, AggregateObjErrors(obj.GroupVersionKind().GroupKind(), obj.Name, allErrs) } diff --git a/internal/webhooks/vspheremachine_test.go b/internal/webhooks/vspheremachine_test.go index b5ef77df7f..8c1fa7857e 100644 --- a/internal/webhooks/vspheremachine_test.go +++ b/internal/webhooks/vspheremachine_test.go @@ -48,52 +48,86 @@ func TestVSphereMachine_ValidateCreate(t *testing.T) { }{ { name: "preferredAPIServerCIDR set on creation ", - vsphereMachine: createVSphereMachine("foo.com", nil, "192.168.0.1/32", []string{}, infrav1.VirtualMachinePowerOpModeTrySoft, nil), + vsphereMachine: createVSphereMachine("foo.com", nil, "192.168.0.1/32", []string{}, infrav1.VirtualMachinePowerOpModeTrySoft, nil, nil), wantErr: true, }, { name: "IPs are not in CIDR format", - vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil), + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil, nil), wantErr: true, }, { name: "IPs are not valid IPs in CIDR format", - vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"/32", "192.168.0.644/33"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil), + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"/32", "192.168.0.644/33"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil, nil), wantErr: true, }, { name: "guestSoftPowerOffTimeout should not be set with powerOffMode set to hard", - vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeHard, &metav1.Duration{Duration: infrav1.GuestSoftPowerOffDefaultTimeout}), + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeHard, &metav1.Duration{Duration: infrav1.GuestSoftPowerOffDefaultTimeout}, nil), wantErr: true, }, { name: "guestSoftPowerOffTimeout should not be set with powerOffMode set to soft", - vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeSoft, &metav1.Duration{Duration: infrav1.GuestSoftPowerOffDefaultTimeout}), + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeSoft, &metav1.Duration{Duration: infrav1.GuestSoftPowerOffDefaultTimeout}, nil), wantErr: true, }, { name: "guestSoftPowerOffTimeout should not be negative", - vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeHard, &metav1.Duration{Duration: -1234}), + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeHard, &metav1.Duration{Duration: -1234}, nil), + wantErr: true, + }, + + { + name: "empty pciDevice", + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil, []infrav1.PCIDeviceSpec{{VGPUProfile: ""}}), + wantErr: true, + }, + { + name: "incorrect pciDevice", + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil, []infrav1.PCIDeviceSpec{{VGPUProfile: "vgpu", DeviceID: new(int32)}}), + wantErr: true, + }, + { + name: "incorrect pciDevice", + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil, []infrav1.PCIDeviceSpec{{VGPUProfile: "vgpu", DeviceID: new(int32), VendorID: new(int32)}}), wantErr: true, }, + { + name: "incomplete pciDevice", + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil, []infrav1.PCIDeviceSpec{{DeviceID: new(int32)}}), + wantErr: true, + }, + { + name: "incomplete pciDevice", + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil, []infrav1.PCIDeviceSpec{{VendorID: new(int32)}}), + wantErr: true, + }, + { + name: "successful VSphereMachine creation with PCI device", + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil, []infrav1.PCIDeviceSpec{{DeviceID: new(int32), VendorID: new(int32)}}), + }, + { + name: "successful VSphereMachine creation with vgpu", + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil, []infrav1.PCIDeviceSpec{{VGPUProfile: "vgpu"}}), + }, { name: "successful VSphereMachine creation", - vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil), + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil, nil), wantErr: false, }, { name: "successful VSphereMachine creation with powerOffMode set to hard", - vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeHard, nil), + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeHard, nil, nil), wantErr: false, }, { name: "successful VSphereMachine creation with powerOffMode set to soft", - vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil), + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil, nil), wantErr: false, }, { name: "successful VSphereMachine creation with powerOffMode set to trySoft and non-default timeout", - vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, &metav1.Duration{Duration: 1234}), + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, &metav1.Duration{Duration: 1234}, nil), wantErr: false, }, } @@ -121,50 +155,56 @@ func TestVSphereMachine_ValidateUpdate(t *testing.T) { }{ { name: "ProviderID can be updated", - oldVSphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil), - vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil), + oldVSphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil, nil), + vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil, nil), wantErr: false, }, { name: "updating ips can be done", - oldVSphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil), - vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32", "192.168.0.10/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil), + oldVSphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil, nil), + vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32", "192.168.0.10/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil, nil), wantErr: false, }, { name: "updating non-existing IP with invalid ips can not be done", - oldVSphereMachine: createVSphereMachine("foo.com", nil, "", nil, infrav1.VirtualMachinePowerOpModeSoft, nil), - vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"/32", "192.168.0.10/33"}, infrav1.VirtualMachinePowerOpModeSoft, nil), + oldVSphereMachine: createVSphereMachine("foo.com", nil, "", nil, infrav1.VirtualMachinePowerOpModeSoft, nil, nil), + vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"/32", "192.168.0.10/33"}, infrav1.VirtualMachinePowerOpModeSoft, nil, nil), wantErr: true, }, { name: "updating existing IP with invalid ips can not be done", - oldVSphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil), - vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"/32", "192.168.0.10/33"}, infrav1.VirtualMachinePowerOpModeSoft, nil), + oldVSphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil, nil), + vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"/32", "192.168.0.10/33"}, infrav1.VirtualMachinePowerOpModeSoft, nil, nil), wantErr: true, }, { name: "updating server cannot be done", - oldVSphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil), - vsphereMachine: createVSphereMachine("bar.com", &someProviderID, "", []string{"192.168.0.1/32", "192.168.0.10/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil), + oldVSphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil, nil), + vsphereMachine: createVSphereMachine("bar.com", &someProviderID, "", []string{"192.168.0.1/32", "192.168.0.10/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil, nil), + wantErr: true, + }, + { + name: "updating pci devices cannot be done", + oldVSphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil, []infrav1.PCIDeviceSpec{{VGPUProfile: "vgpu"}}), + vsphereMachine: createVSphereMachine("foo.com", nil, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil, []infrav1.PCIDeviceSpec{{VGPUProfile: "new-vgpu"}}), wantErr: true, }, { name: "powerOffMode cannot be updated when new powerOffMode is not valid", - oldVSphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil), - vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeHard, &metav1.Duration{Duration: infrav1.GuestSoftPowerOffDefaultTimeout}), + oldVSphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, nil, nil), + vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeHard, &metav1.Duration{Duration: infrav1.GuestSoftPowerOffDefaultTimeout}, nil), wantErr: true, }, { name: "powerOffMode can be updated to hard", - oldVSphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, &metav1.Duration{Duration: infrav1.GuestSoftPowerOffDefaultTimeout}), - vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeHard, nil), + oldVSphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, &metav1.Duration{Duration: infrav1.GuestSoftPowerOffDefaultTimeout}, nil), + vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeHard, nil, nil), wantErr: false, }, { name: "powerOffMode can be updated to soft", - oldVSphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, &metav1.Duration{Duration: infrav1.GuestSoftPowerOffDefaultTimeout}), - vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil), + oldVSphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeTrySoft, &metav1.Duration{Duration: infrav1.GuestSoftPowerOffDefaultTimeout}, nil), + vsphereMachine: createVSphereMachine("foo.com", &someProviderID, "", []string{"192.168.0.1/32"}, infrav1.VirtualMachinePowerOpModeSoft, nil, nil), wantErr: false, }, } @@ -181,7 +221,7 @@ func TestVSphereMachine_ValidateUpdate(t *testing.T) { } } -func createVSphereMachine(server string, providerID *string, preferredAPIServerCIDR string, ips []string, powerOffMode infrav1.VirtualMachinePowerOpMode, guestSoftPowerOffTimeout *metav1.Duration) *infrav1.VSphereMachine { +func createVSphereMachine(server string, providerID *string, preferredAPIServerCIDR string, ips []string, powerOffMode infrav1.VirtualMachinePowerOpMode, guestSoftPowerOffTimeout *metav1.Duration, pciDevices []infrav1.PCIDeviceSpec) *infrav1.VSphereMachine { VSphereMachine := &infrav1.VSphereMachine{ Spec: infrav1.VSphereMachineSpec{ VirtualMachineCloneSpec: infrav1.VirtualMachineCloneSpec{ @@ -190,6 +230,7 @@ func createVSphereMachine(server string, providerID *string, preferredAPIServerC PreferredAPIServerCIDR: preferredAPIServerCIDR, Devices: []infrav1.NetworkDeviceSpec{}, }, + PciDevices: pciDevices, }, ProviderID: providerID, PowerOffMode: powerOffMode, diff --git a/internal/webhooks/vspheremachinetemplate.go b/internal/webhooks/vspheremachinetemplate.go index e72aeb5c5e..2497249c83 100644 --- a/internal/webhooks/vspheremachinetemplate.go +++ b/internal/webhooks/vspheremachinetemplate.go @@ -85,10 +85,14 @@ func (webhook *VSphereMachineTemplateWebhook) ValidateCreate(_ context.Context, } } for i, device := range spec.PciDevices { - hasVGPU := device.VGPUProfile != "" - hasPCI := device.DeviceID != nil && device.VendorID != nil - if (hasPCI && hasVGPU) || (!hasPCI && !hasVGPU) { - allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "template", "spec", "pciDevices", fmt.Sprintf("%d, i)), device, "should have either deviceID + vendorID or vgpuProfile")) + if device.VGPUProfile == "" { + if device.DeviceID == nil || device.VendorID == nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "template", "spec", "pciDevices", fmt.Sprintf("%d", i)), device, "should have both deviceId and vendorId set")) + } + } else { + if device.DeviceID != nil || device.VendorID != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "template", "spec", "pciDevices", fmt.Sprintf("%d", i)), device, "should have either deviceId + vendorId or vgpuProfile")) + } } } return nil, AggregateObjErrors(obj.GroupVersionKind().GroupKind(), obj.Name, allErrs) diff --git a/internal/webhooks/vspheremachinetemplate_test.go b/internal/webhooks/vspheremachinetemplate_test.go index 20f6eddacf..d1a0714645 100644 --- a/internal/webhooks/vspheremachinetemplate_test.go +++ b/internal/webhooks/vspheremachinetemplate_test.go @@ -37,37 +37,70 @@ func TestVSphereMachineTemplate_ValidateCreate(t *testing.T) { }{ { name: "preferredAPIServerCIDR set on creation ", - vsphereMachine: createVSphereMachineTemplate("foo.com", "", nil, "192.168.0.1/32", []string{}), + vsphereMachine: createVSphereMachineTemplate("foo.com", "", nil, "192.168.0.1/32", []string{}, nil), wantErr: true, }, { name: "ProviderID set on creation", - vsphereMachine: createVSphereMachineTemplate("foo.com", "", &someProviderID, "", []string{}), + vsphereMachine: createVSphereMachineTemplate("foo.com", "", &someProviderID, "", []string{}, nil), wantErr: true, }, { name: "IPs are not in CIDR format", - vsphereMachine: createVSphereMachineTemplate("foo.com", "", nil, "", []string{"192.168.0.1/32", "192.168.0.3"}), + vsphereMachine: createVSphereMachineTemplate("foo.com", "", nil, "", []string{"192.168.0.1/32", "192.168.0.3"}, nil), wantErr: true, }, { name: "successful VSphereMachine creation", - vsphereMachine: createVSphereMachineTemplate("foo.com", "", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}), + vsphereMachine: createVSphereMachineTemplate("foo.com", "", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, nil), wantErr: true, }, { name: "incomplete hardware version", - vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}), + vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, nil), wantErr: true, }, { name: "incorrect hardware version", - vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-0", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}), + vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-0", nil, "", []string{"192.168.0.1/32", "192.168.0.3/32"}, nil), wantErr: true, }, + { + name: "empty pciDevice", + vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-17", nil, "", []string{}, []infrav1.PCIDeviceSpec{{VGPUProfile: ""}}), + wantErr: true, + }, + { + name: "incorrect pciDevice", + vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-17", nil, "", []string{}, []infrav1.PCIDeviceSpec{{VGPUProfile: "vgpu", DeviceID: new(int32)}}), + wantErr: true, + }, + { + name: "incorrect pciDevice", + vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-17", nil, "", []string{}, []infrav1.PCIDeviceSpec{{VGPUProfile: "vgpu", DeviceID: new(int32), VendorID: new(int32)}}), + wantErr: true, + }, + { + name: "incomplete pciDevice", + vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-17", nil, "", []string{}, []infrav1.PCIDeviceSpec{{DeviceID: new(int32)}}), + wantErr: true, + }, + { + name: "incomplete pciDevice", + vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-17", nil, "", []string{}, []infrav1.PCIDeviceSpec{{VendorID: new(int32)}}), + wantErr: true, + }, + { + name: "successful VSphereMachine creation with PCI device", + vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-17", nil, "", []string{}, []infrav1.PCIDeviceSpec{{DeviceID: new(int32), VendorID: new(int32)}}), + }, + { + name: "successful VSphereMachine creation with vgpu", + vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-17", nil, "", []string{}, []infrav1.PCIDeviceSpec{{VGPUProfile: "vgpu"}}), + }, { name: "successful VSphereMachine creation with hardware version set", - vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-17", nil, "", []string{}), + vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-17", nil, "", []string{}, nil), }, } for _, tc := range tests { @@ -94,36 +127,43 @@ func TestVSphereMachineTemplate_ValidateUpdate(t *testing.T) { }{ { name: "ProviderID cannot be updated", - oldVSphereMachine: createVSphereMachineTemplate("foo.com", "", nil, "", []string{"192.168.0.1/32"}), - vsphereMachine: createVSphereMachineTemplate("foo.com", "", &someProviderID, "", []string{"192.168.0.1/32"}), + oldVSphereMachine: createVSphereMachineTemplate("foo.com", "", nil, "", []string{"192.168.0.1/32"}, nil), + vsphereMachine: createVSphereMachineTemplate("foo.com", "", &someProviderID, "", []string{"192.168.0.1/32"}, nil), req: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{DryRun: ptr.To(false)}}, wantErr: true, }, { name: "ip addresses cannot be updated", - oldVSphereMachine: createVSphereMachineTemplate("foo.com", "", nil, "", []string{"192.168.0.1/32"}), - vsphereMachine: createVSphereMachineTemplate("foo.com", "", &someProviderID, "", []string{"192.168.0.1/32", "192.168.0.10/32"}), + oldVSphereMachine: createVSphereMachineTemplate("foo.com", "", nil, "", []string{"192.168.0.1/32"}, nil), + vsphereMachine: createVSphereMachineTemplate("foo.com", "", &someProviderID, "", []string{"192.168.0.1/32", "192.168.0.10/32"}, nil), req: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{DryRun: ptr.To(false)}}, wantErr: true, }, { name: "server cannot be updated", - oldVSphereMachine: createVSphereMachineTemplate("foo.com", "", nil, "", []string{"192.168.0.1/32"}), - vsphereMachine: createVSphereMachineTemplate("baz.com", "", &someProviderID, "", []string{"192.168.0.1/32", "192.168.0.10/32"}), + oldVSphereMachine: createVSphereMachineTemplate("foo.com", "", nil, "", []string{"192.168.0.1/32"}, nil), + vsphereMachine: createVSphereMachineTemplate("baz.com", "", &someProviderID, "", []string{"192.168.0.1/32", "192.168.0.10/32"}, nil), req: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{DryRun: ptr.To(false)}}, wantErr: true, }, { name: "hardware version cannot be updated", - oldVSphereMachine: createVSphereMachineTemplate("foo.com", "vmx-16", nil, "", []string{"192.168.0.1/32"}), - vsphereMachine: createVSphereMachineTemplate("baz.com", "vmx-17", nil, "", []string{"192.168.0.1/32"}), + oldVSphereMachine: createVSphereMachineTemplate("foo.com", "vmx-16", nil, "", []string{"192.168.0.1/32"}, nil), + vsphereMachine: createVSphereMachineTemplate("baz.com", "vmx-17", nil, "", []string{"192.168.0.1/32"}, nil), + req: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{DryRun: ptr.To(false)}}, + wantErr: true, + }, + { + name: "pci devices cannot be updated", + oldVSphereMachine: createVSphereMachineTemplate("foo.com", "vmx-16", nil, "", []string{"192.168.0.1/32"}, []infrav1.PCIDeviceSpec{{VGPUProfile: "vgpu"}}), + vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-16", nil, "", []string{"192.168.0.1/32"}, []infrav1.PCIDeviceSpec{{VGPUProfile: "new-vgpu"}}), req: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{DryRun: ptr.To(false)}}, wantErr: true, }, { name: "with hardware version set and not updated", - oldVSphereMachine: createVSphereMachineTemplate("foo.com", "vmx-16", nil, "", []string{"192.168.0.1/32"}), - vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-16", nil, "", []string{"192.168.0.1/32"}), + oldVSphereMachine: createVSphereMachineTemplate("foo.com", "vmx-16", nil, "", []string{"192.168.0.1/32"}, nil), + vsphereMachine: createVSphereMachineTemplate("foo.com", "vmx-16", nil, "", []string{"192.168.0.1/32"}, nil), req: &admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{DryRun: ptr.To(false)}}, wantErr: false, // explicitly calling out that this is a valid scenario. }, @@ -145,7 +185,7 @@ func TestVSphereMachineTemplate_ValidateUpdate(t *testing.T) { } } -func createVSphereMachineTemplate(server, hwVersion string, providerID *string, preferredAPIServerCIDR string, ips []string) *infrav1.VSphereMachineTemplate { +func createVSphereMachineTemplate(server, hwVersion string, providerID *string, preferredAPIServerCIDR string, ips []string, pciDevices []infrav1.PCIDeviceSpec) *infrav1.VSphereMachineTemplate { vsphereMachineTemplate := &infrav1.VSphereMachineTemplate{ Spec: infrav1.VSphereMachineTemplateSpec{ Template: infrav1.VSphereMachineTemplateResource{ @@ -158,6 +198,7 @@ func createVSphereMachineTemplate(server, hwVersion string, providerID *string, Devices: []infrav1.NetworkDeviceSpec{}, }, HardwareVersion: hwVersion, + PciDevices: pciDevices, }, }, }, diff --git a/pkg/services/govmomi/pci/device.go b/pkg/services/govmomi/pci/device.go index a6e1a1132b..eff285859d 100644 --- a/pkg/services/govmomi/pci/device.go +++ b/pkg/services/govmomi/pci/device.go @@ -77,20 +77,19 @@ func ConstructDeviceSpecs(pciDeviceSpecs []infrav1.PCIDeviceSpec) []types.BaseVi } func createBackingInfo(spec infrav1.PCIDeviceSpec) types.BaseVirtualDeviceBackingInfo { - if spec.DeviceID != nil && spec.VendorID != nil { - return &types.VirtualPCIPassthroughDynamicBackingInfo{ - AllowedDevice: []types.VirtualPCIPassthroughAllowedDevice{ - { - VendorId: *spec.VendorID, - DeviceId: *spec.DeviceID, - }, - }, - CustomLabel: spec.CustomLabel, + if spec.VGPUProfile != "" { + return &types.VirtualPCIPassthroughVmiopBackingInfo{ + Vgpu: spec.VGPUProfile, } } - - return &types.VirtualPCIPassthroughVmiopBackingInfo{ - Vgpu: spec.VGPUProfile, + return &types.VirtualPCIPassthroughDynamicBackingInfo{ + AllowedDevice: []types.VirtualPCIPassthroughAllowedDevice{ + { + VendorId: *spec.VendorID, + DeviceId: *spec.DeviceID, + }, + }, + CustomLabel: spec.CustomLabel, } }