From 6f31a70becde22e4ad6e43728275eca8aafc5065 Mon Sep 17 00:00:00 2001 From: Bryan Venteicher Date: Thu, 18 Jul 2024 13:42:38 -0500 Subject: [PATCH] resize: VM resize of just CPU and Memory Add support for resize of just CPU & memory. For ease, this was just made a part of the existing pre-power on reconfigure flow, while the full resize has its own path that will eventually replace the former. --- .../vsphere/session/session_vm_update.go | 32 +- pkg/providers/vsphere/vmprovider_vm.go | 2 +- .../vsphere/vmprovider_vm_resize_test.go | 332 +++++++++--------- pkg/util/resize/configspec.go | 15 + pkg/util/resize/configspec_test.go | 39 ++ 5 files changed, 245 insertions(+), 175 deletions(-) diff --git a/pkg/providers/vsphere/session/session_vm_update.go b/pkg/providers/vsphere/session/session_vm_update.go index eb4802bff..4358f33cc 100644 --- a/pkg/providers/vsphere/session/session_vm_update.go +++ b/pkg/providers/vsphere/session/session_vm_update.go @@ -540,6 +540,10 @@ func updateConfigSpec( UpdateConfigSpecFirmware(config, configSpec, vmCtx.VM) UpdateConfigSpecGuestID(config, configSpec, vmCtx.VM.Spec.GuestID) + if pkgcfg.FromContext(vmCtx).Features.VMResizeCPUMemory { + UpdateHardwareConfigSpec(config, configSpec, &vmClassSpec) + } + return configSpec } @@ -604,8 +608,8 @@ func (s *Session) prePowerOnVMReconfigure( var configSpec *vimtypes.VirtualMachineConfigSpec var err error - vmResizeEnabled := pkgcfg.FromContext(vmCtx).Features.VMResize - if vmResizeEnabled { + features := pkgcfg.FromContext(vmCtx).Features + if features.VMResize { configSpec, err = s.prePowerOnVMResizeConfigSpec(vmCtx, config, updateArgs) } else { configSpec, err = s.prePowerOnVMConfigSpec(vmCtx, config, updateArgs) @@ -631,15 +635,15 @@ func (s *Session) prePowerOnVMReconfigure( vmCtx.Logger.Error(err, "pre power on reconfigure failed") return err } + } - if vmResizeEnabled { - vmopv1util.MustSetLastResizedAnnotation(vmCtx.VM, updateArgs.VMClass) + if features.VMResize || features.VMResizeCPUMemory { + vmopv1util.MustSetLastResizedAnnotation(vmCtx.VM, updateArgs.VMClass) - vmCtx.VM.Status.Class = &vmopv1common.LocalObjectRef{ - APIVersion: vmopv1.GroupVersion.String(), - Kind: "VirtualMachineClass", - Name: updateArgs.VMClass.Name, - } + vmCtx.VM.Status.Class = &vmopv1common.LocalObjectRef{ + APIVersion: vmopv1.GroupVersion.String(), + Kind: "VirtualMachineClass", + Name: updateArgs.VMClass.Name, } } @@ -919,12 +923,14 @@ func (s *Session) resizeVMWhenPoweredStateOff( if resizeArgs.VMClass != nil { needsResize = vmopv1util.ResizeNeeded(*vmCtx.VM, *resizeArgs.VMClass) if needsResize { - cs, err := resize.CreateResizeConfigSpec(vmCtx, *moVM.Config, resizeArgs.ConfigSpec) + if pkgcfg.FromContext(vmCtx).Features.VMResize { + configSpec, err = resize.CreateResizeConfigSpec(vmCtx, *moVM.Config, resizeArgs.ConfigSpec) + } else { + configSpec, err = resize.CreateResizeCPUMemoryConfigSpec(vmCtx, *moVM.Config, resizeArgs.ConfigSpec) + } if err != nil { return false, err } - - configSpec = cs } } @@ -1022,7 +1028,7 @@ func (s *Session) updateVMDesiredPowerStateOff( refetchProps = true } - if pkgcfg.FromContext(vmCtx).Features.VMResize { + if f := pkgcfg.FromContext(vmCtx).Features; f.VMResize || f.VMResizeCPUMemory { refetchProps, err = s.resizeVMWhenPoweredStateOff( vmCtx, vcVM, diff --git a/pkg/providers/vsphere/vmprovider_vm.go b/pkg/providers/vsphere/vmprovider_vm.go index 041abb01d..50bf7b19e 100644 --- a/pkg/providers/vsphere/vmprovider_vm.go +++ b/pkg/providers/vsphere/vmprovider_vm.go @@ -824,7 +824,7 @@ func (vs *vSphereVMProvider) vmCreateGetPrereqs( if !vmopv1util.IsClasslessVM(*vmCtx.VM) { // Only set VM Class field for non-synthesized classes. - if pkgcfg.FromContext(vmCtx).Features.VMResize { + if f := pkgcfg.FromContext(vmCtx).Features; f.VMResize || f.VMResizeCPUMemory { vmopv1util.MustSetLastResizedAnnotation(vmCtx.VM, createArgs.VMClass) } vmCtx.VM.Status.Class = &common.LocalObjectRef{ diff --git a/pkg/providers/vsphere/vmprovider_vm_resize_test.go b/pkg/providers/vsphere/vmprovider_vm_resize_test.go index 2837e1d3c..c9e61bd7d 100644 --- a/pkg/providers/vsphere/vmprovider_vm_resize_test.go +++ b/pkg/providers/vsphere/vmprovider_vm_resize_test.go @@ -37,7 +37,6 @@ func vmResizeTests() { BeforeEach(func() { testConfig = builder.VCSimTestConfig{ WithContentLibrary: true, - WithVMResize: true, WithNetworkEnv: builder.NetworkEnvNamed, } }) @@ -117,236 +116,247 @@ func vmResizeTests() { ExpectWithOffset(1, vm.Status.Class.Name).To(Equal(class.Name)) } - Context("Resize VM", func() { + DescribeTableSubtree("Resize VM", + func(fullResize bool) { - var ( - vm *vmopv1.VirtualMachine - vmClass *vmopv1.VirtualMachineClass - configSpec vimtypes.VirtualMachineConfigSpec - ) + var ( + vm *vmopv1.VirtualMachine + vmClass *vmopv1.VirtualMachineClass + configSpec vimtypes.VirtualMachineConfigSpec + ) - BeforeEach(func() { - vm = builder.DummyBasicVirtualMachine("test-vm", "") - - configSpec = vimtypes.VirtualMachineConfigSpec{} - configSpec.NumCPUs = 1 - configSpec.MemoryMB = 512 - }) - - JustBeforeEach(func() { - vmClass = createVMClass(configSpec, "initial-class") - - clusterVMImage := &vmopv1.ClusterVirtualMachineImage{} - Expect(ctx.Client.Get(ctx, client.ObjectKey{Name: ctx.ContentLibraryImageName}, clusterVMImage)).To(Succeed()) - - vm.Namespace = nsInfo.Namespace - vm.Spec.ClassName = vmClass.Name - vm.Spec.ImageName = clusterVMImage.Name - vm.Spec.Image.Kind = cvmiKind - vm.Spec.Image.Name = clusterVMImage.Name - vm.Spec.PowerState = vmopv1.VirtualMachinePowerStateOff - vm.Spec.StorageClass = ctx.StorageClassName + BeforeEach(func() { + vm = builder.DummyBasicVirtualMachine("test-vm", "") - _, err := createOrUpdateAndGetVcVM(ctx, vm) - Expect(err).ToNot(HaveOccurred()) - }) + if fullResize { + testConfig.WithVMResize = true + } else { + testConfig.WithVMResizeCPUMemory = true + } - Context("NumCPUs", func() { - BeforeEach(func() { - configSpec.NumCPUs = 2 + configSpec = vimtypes.VirtualMachineConfigSpec{} + configSpec.NumCPUs = 1 + configSpec.MemoryMB = 512 }) - It("Resizes", func() { - cs := configSpec - cs.NumCPUs = 42 - newVMClass := createVMClass(cs) - vm.Spec.ClassName = newVMClass.Name + JustBeforeEach(func() { + vmClass = createVMClass(configSpec, "initial-class") - vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) - Expect(err).ToNot(HaveOccurred()) + clusterVMImage := &vmopv1.ClusterVirtualMachineImage{} + Expect(ctx.Client.Get(ctx, client.ObjectKey{Name: ctx.ContentLibraryImageName}, clusterVMImage)).To(Succeed()) - var o mo.VirtualMachine - Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) - Expect(o.Config.Hardware.NumCPU).To(BeEquivalentTo(42)) + vm.Namespace = nsInfo.Namespace + vm.Spec.ClassName = vmClass.Name + vm.Spec.ImageName = clusterVMImage.Name + vm.Spec.Image.Kind = cvmiKind + vm.Spec.Image.Name = clusterVMImage.Name + vm.Spec.PowerState = vmopv1.VirtualMachinePowerStateOff + vm.Spec.StorageClass = ctx.StorageClassName - assertExpectedResizedClassFields(vm, newVMClass) + _, err := createOrUpdateAndGetVcVM(ctx, vm) + Expect(err).ToNot(HaveOccurred()) }) - }) - Context("MemoryMB", func() { - BeforeEach(func() { - configSpec.MemoryMB = 1024 - }) + Context("NumCPUs", func() { + BeforeEach(func() { + configSpec.NumCPUs = 2 + }) - It("Resizes", func() { - cs := configSpec - cs.MemoryMB = 8192 - newVMClass := createVMClass(cs) - vm.Spec.ClassName = newVMClass.Name + It("Resizes", func() { + cs := configSpec + cs.NumCPUs = 42 + newVMClass := createVMClass(cs) + vm.Spec.ClassName = newVMClass.Name - vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) - Expect(err).ToNot(HaveOccurred()) + vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) + Expect(err).ToNot(HaveOccurred()) - var o mo.VirtualMachine - Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) - Expect(o.Config.Hardware.MemoryMB).To(BeEquivalentTo(8192)) + var o mo.VirtualMachine + Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) + Expect(o.Config.Hardware.NumCPU).To(BeEquivalentTo(42)) - assertExpectedResizedClassFields(vm, newVMClass) + assertExpectedResizedClassFields(vm, newVMClass) + }) }) - }) - Context("Powering On VM", func() { - BeforeEach(func() { - configSpec.NumCPUs = 2 - configSpec.MemoryMB = 1024 - }) + Context("MemoryMB", func() { + BeforeEach(func() { + configSpec.MemoryMB = 1024 + }) - It("Resizes", func() { - cs := configSpec - cs.NumCPUs = 42 - cs.MemoryMB = 8192 - newVMClass := createVMClass(cs) - vm.Spec.ClassName = newVMClass.Name + It("Resizes", func() { + cs := configSpec + cs.MemoryMB = 8192 + newVMClass := createVMClass(cs) + vm.Spec.ClassName = newVMClass.Name - vm.Spec.PowerState = vmopv1.VirtualMachinePowerStateOn - vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) - Expect(err).ToNot(HaveOccurred()) + vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) + Expect(err).ToNot(HaveOccurred()) - var o mo.VirtualMachine - Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) - Expect(o.Summary.Runtime.PowerState).To(Equal(vimtypes.VirtualMachinePowerStatePoweredOn)) - Expect(o.Config.Hardware.NumCPU).To(BeEquivalentTo(42)) - Expect(o.Config.Hardware.MemoryMB).To(BeEquivalentTo(8192)) + var o mo.VirtualMachine + Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) + Expect(o.Config.Hardware.MemoryMB).To(BeEquivalentTo(8192)) - assertExpectedResizedClassFields(vm, newVMClass) + assertExpectedResizedClassFields(vm, newVMClass) + }) }) - }) - Context("Same Class Resize Annotation", func() { - BeforeEach(func() { - configSpec.MemoryMB = 1024 - }) + Context("Powering On VM", func() { + BeforeEach(func() { + configSpec.NumCPUs = 2 + configSpec.MemoryMB = 1024 + }) - It("Resizes", func() { - cs := configSpec - cs.MemoryMB = 8192 - updateVMClass(vmClass, cs) + It("Resizes", func() { + cs := configSpec + cs.NumCPUs = 42 + cs.MemoryMB = 8192 + newVMClass := createVMClass(cs) + vm.Spec.ClassName = newVMClass.Name - By("Does not resize without annotation", func() { + vm.Spec.PowerState = vmopv1.VirtualMachinePowerStateOn vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) Expect(err).ToNot(HaveOccurred()) var o mo.VirtualMachine Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) - Expect(o.Config.Hardware.MemoryMB).To(BeEquivalentTo(1024)) - }) + Expect(o.Summary.Runtime.PowerState).To(Equal(vimtypes.VirtualMachinePowerStatePoweredOn)) + Expect(o.Config.Hardware.NumCPU).To(BeEquivalentTo(42)) + Expect(o.Config.Hardware.MemoryMB).To(BeEquivalentTo(8192)) - vm.Annotations[vmopv1.VirtualMachineSameVMClassResizeAnnotation] = "" - vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) - Expect(err).ToNot(HaveOccurred()) + assertExpectedResizedClassFields(vm, newVMClass) + }) + }) - var o mo.VirtualMachine - Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) - Expect(o.Config.Hardware.MemoryMB).To(BeEquivalentTo(8192)) + Context("Same Class Resize Annotation", func() { + BeforeEach(func() { + configSpec.MemoryMB = 1024 + }) - assertExpectedResizedClassFields(vm, vmClass) - }) + It("Resizes", func() { + cs := configSpec + cs.MemoryMB = 8192 + updateVMClass(vmClass, cs) - It("Resizes brownfield VM", func() { - cs := configSpec - cs.MemoryMB = 8192 - updateVMClass(vmClass, cs) + By("Does not resize without annotation", func() { + vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) + Expect(err).ToNot(HaveOccurred()) - // Remove annotation so the VM appears to be from before this feature. - Expect(vm.Annotations).To(HaveKey(vmopv1util.LastResizedAnnotationKey)) - delete(vm.Annotations, vmopv1util.LastResizedAnnotationKey) + var o mo.VirtualMachine + Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) + Expect(o.Config.Hardware.MemoryMB).To(BeEquivalentTo(1024)) + }) - By("Does not resize without same class annotation", func() { + vm.Annotations[vmopv1.VirtualMachineSameVMClassResizeAnnotation] = "" vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) Expect(err).ToNot(HaveOccurred()) var o mo.VirtualMachine Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) - Expect(o.Config.Hardware.MemoryMB).To(BeEquivalentTo(1024)) + Expect(o.Config.Hardware.MemoryMB).To(BeEquivalentTo(8192)) - Expect(vm.Annotations).ToNot(HaveKey(vmopv1util.LastResizedAnnotationKey)) + assertExpectedResizedClassFields(vm, vmClass) }) - vm.Annotations[vmopv1.VirtualMachineSameVMClassResizeAnnotation] = "" - vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) - Expect(err).ToNot(HaveOccurred()) + It("Resizes brownfield VM", func() { + cs := configSpec + cs.MemoryMB = 8192 + updateVMClass(vmClass, cs) - var o mo.VirtualMachine - Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) - Expect(o.Config.Hardware.MemoryMB).To(BeEquivalentTo(8192)) + // Remove annotation so the VM appears to be from before this feature. + Expect(vm.Annotations).To(HaveKey(vmopv1util.LastResizedAnnotationKey)) + delete(vm.Annotations, vmopv1util.LastResizedAnnotationKey) - assertExpectedResizedClassFields(vm, vmClass) - }) - }) + By("Does not resize without same class annotation", func() { + vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) + Expect(err).ToNot(HaveOccurred()) - Context("Devops Overrides", func() { - Context("ChangeBlockTracking", func() { - It("Overrides", func() { - vm.Spec.Advanced = &vmopv1.VirtualMachineAdvancedSpec{ - ChangeBlockTracking: vimtypes.NewBool(true), - } + var o mo.VirtualMachine + Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) + Expect(o.Config.Hardware.MemoryMB).To(BeEquivalentTo(1024)) + Expect(vm.Annotations).ToNot(HaveKey(vmopv1util.LastResizedAnnotationKey)) + }) + + vm.Annotations[vmopv1.VirtualMachineSameVMClassResizeAnnotation] = "" vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) Expect(err).ToNot(HaveOccurred()) var o mo.VirtualMachine Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) - Expect(o.Config.ChangeTrackingEnabled).To(HaveValue(BeTrue())) + Expect(o.Config.Hardware.MemoryMB).To(BeEquivalentTo(8192)) assertExpectedResizedClassFields(vm, vmClass) }) }) - Context("VM Class does not exist", func() { - BeforeEach(func() { - configSpec.ChangeTrackingEnabled = vimtypes.NewBool(false) + Context("Devops Overrides", func() { + Context("ChangeBlockTracking", func() { + It("Overrides", func() { + vm.Spec.Advanced = &vmopv1.VirtualMachineAdvancedSpec{ + ChangeBlockTracking: vimtypes.NewBool(true), + } + + vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) + Expect(err).ToNot(HaveOccurred()) + + var o mo.VirtualMachine + Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) + Expect(o.Config.ChangeTrackingEnabled).To(HaveValue(BeTrue())) + + assertExpectedResizedClassFields(vm, vmClass) + }) }) - It("Still applies overrides", func() { - Expect(ctx.Client.Delete(ctx, vmClass)).To(Succeed()) + Context("VM Class does not exist", func() { + BeforeEach(func() { + configSpec.ChangeTrackingEnabled = vimtypes.NewBool(false) + }) - vm.Spec.Advanced = &vmopv1.VirtualMachineAdvancedSpec{ - ChangeBlockTracking: vimtypes.NewBool(true), - } + It("Still applies overrides", func() { + Expect(ctx.Client.Delete(ctx, vmClass)).To(Succeed()) - vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) - Expect(err).ToNot(HaveOccurred()) + vm.Spec.Advanced = &vmopv1.VirtualMachineAdvancedSpec{ + ChangeBlockTracking: vimtypes.NewBool(true), + } - var o mo.VirtualMachine - Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) - Expect(o.Config.ChangeTrackingEnabled).To(HaveValue(BeTrue())) + vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) + Expect(err).ToNot(HaveOccurred()) - // BMV: TBD exactly what we should do in this case. - // Expect(vm.Status.Class).To(BeNil()) - }) - }) + var o mo.VirtualMachine + Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) + Expect(o.Config.ChangeTrackingEnabled).To(HaveValue(BeTrue())) - Context("VM Classless VMs", func() { - BeforeEach(func() { - configSpec.ChangeTrackingEnabled = vimtypes.NewBool(false) + // BMV: TBD exactly what we should do in this case. + // Expect(vm.Status.Class).To(BeNil()) + }) }) - It("Still applies overrides", func() { - vm.Spec.ClassName = "" - vm.Spec.Advanced = &vmopv1.VirtualMachineAdvancedSpec{ - ChangeBlockTracking: vimtypes.NewBool(true), - } + Context("VM Classless VMs", func() { + BeforeEach(func() { + configSpec.ChangeTrackingEnabled = vimtypes.NewBool(false) + }) - vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) - Expect(err).ToNot(HaveOccurred()) + It("Still applies overrides", func() { + vm.Spec.ClassName = "" + vm.Spec.Advanced = &vmopv1.VirtualMachineAdvancedSpec{ + ChangeBlockTracking: vimtypes.NewBool(true), + } - var o mo.VirtualMachine - Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) - Expect(o.Config.ChangeTrackingEnabled).To(HaveValue(BeTrue())) + vcVM, err := createOrUpdateAndGetVcVM(ctx, vm) + Expect(err).ToNot(HaveOccurred()) + + var o mo.VirtualMachine + Expect(vcVM.Properties(ctx, vcVM.Reference(), nil, &o)).To(Succeed()) + Expect(o.Config.ChangeTrackingEnabled).To(HaveValue(BeTrue())) - Expect(vm.Status.Class).To(BeNil()) + Expect(vm.Status.Class).To(BeNil()) + }) }) }) - }) - }) + }, + + Entry("Full", true), + Entry("CPU & Memory", false), + ) } diff --git a/pkg/util/resize/configspec.go b/pkg/util/resize/configspec.go index 62eff2a9e..8a993cce1 100644 --- a/pkg/util/resize/configspec.go +++ b/pkg/util/resize/configspec.go @@ -52,6 +52,21 @@ func CreateResizeConfigSpec( return outCS, nil } +// CreateResizeCPUMemoryConfigSpec takes the current VM CPU and Memory state in the ConfigInfo and +// compares it to the desired state in the ConfigSpec, returning a ConfigSpec with any required +// changes to drive the desired state. +func CreateResizeCPUMemoryConfigSpec( + _ context.Context, + ci vimtypes.VirtualMachineConfigInfo, + cs vimtypes.VirtualMachineConfigSpec) (vimtypes.VirtualMachineConfigSpec, error) { + + outCS := vimtypes.VirtualMachineConfigSpec{} + cmp(ci.Hardware.NumCPU, cs.NumCPUs, &outCS.NumCPUs) + cmp(int64(ci.Hardware.MemoryMB), cs.MemoryMB, &outCS.MemoryMB) + + return outCS, nil +} + // compareAnnotation compares the ConfigInfo.Annotation. func compareAnnotation( ci vimtypes.VirtualMachineConfigInfo, diff --git a/pkg/util/resize/configspec_test.go b/pkg/util/resize/configspec_test.go index df0e1a390..1336a5c3c 100644 --- a/pkg/util/resize/configspec_test.go +++ b/pkg/util/resize/configspec_test.go @@ -883,3 +883,42 @@ var _ = Describe("CreateResizeConfigSpec", func() { ConfigSpec{}), ) }) + +var _ = Describe("CreateResizeCPUMemoryConfigSpec", func() { + + ctx := context.Background() + + DescribeTable("ConfigInfo", + func( + ci vimtypes.VirtualMachineConfigInfo, + cs, expectedCS vimtypes.VirtualMachineConfigSpec) { + + actualCS, err := resize.CreateResizeCPUMemoryConfigSpec(ctx, ci, cs) + Expect(err).ToNot(HaveOccurred()) + Expect(reflect.DeepEqual(actualCS, expectedCS)).To(BeTrue(), cmp.Diff(actualCS, expectedCS)) + }, + + Entry("Empty needs no updating", + ConfigInfo{}, + ConfigSpec{}, + ConfigSpec{}), + + Entry("NumCPUs needs updating", + ConfigInfo{Hardware: vimtypes.VirtualHardware{NumCPU: 2}}, + ConfigSpec{NumCPUs: 4}, + ConfigSpec{NumCPUs: 4}), + Entry("NumCpus does not need updating", + ConfigInfo{Hardware: vimtypes.VirtualHardware{NumCPU: 4}}, + ConfigSpec{NumCPUs: 4}, + ConfigSpec{}), + + Entry("MemoryMB needs updating", + ConfigInfo{Hardware: vimtypes.VirtualHardware{MemoryMB: 512}}, + ConfigSpec{MemoryMB: 1024}, + ConfigSpec{MemoryMB: 1024}), + Entry("MemoryMB does not need updating", + ConfigInfo{Hardware: vimtypes.VirtualHardware{MemoryMB: 1024}}, + ConfigSpec{MemoryMB: 1024}, + ConfigSpec{}), + ) +})