From 0ccd6b0aa82d18513908658b41efc1eb50172aeb Mon Sep 17 00:00:00 2001 From: Bryan Venteicher Date: Wed, 13 Dec 2023 16:05:25 -0600 Subject: [PATCH 1/2] Add tests for InterfaceSpec Routes missed in ef5ee4c0 --- .../vsphere2/network/network_test.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/pkg/vmprovider/providers/vsphere2/network/network_test.go b/pkg/vmprovider/providers/vsphere2/network/network_test.go index 526d1990d..69e7ccc74 100644 --- a/pkg/vmprovider/providers/vsphere2/network/network_test.go +++ b/pkg/vmprovider/providers/vsphere2/network/network_test.go @@ -125,10 +125,17 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", func() { "172.42.1.100/24", "fd1a:6c85:79fe:7c98:0000:0000:0000:000f/56", }, - Gateway4: "172.42.1.1", - Gateway6: "fd1a:6c85:79fe:7c98:0000:0000:0000:0001", - MTU: pointer.Int64(9000), - Nameservers: []string{"9.9.9.9"}, + Gateway4: "172.42.1.1", + Gateway6: "fd1a:6c85:79fe:7c98:0000:0000:0000:0001", + MTU: pointer.Int64(9000), + Nameservers: []string{"9.9.9.9"}, + Routes: []vmopv1.VirtualMachineNetworkRouteSpec{ + { + To: "10.10.10.10", + Via: "5.5.5.5", + Metric: 42, + }, + }, SearchDomains: []string{"vmware.com"}, }, } @@ -166,6 +173,10 @@ var _ = Describe("CreateAndWaitForNetworkInterfaces", func() { Expect(result.MTU).To(BeEquivalentTo(9000)) Expect(result.Nameservers).To(ConsistOf("9.9.9.9")) Expect(result.SearchDomains).To(ConsistOf("vmware.com")) + Expect(result.Routes).To(HaveLen(1)) + Expect(result.Routes[0].To).To(Equal("10.10.10.10")) + Expect(result.Routes[0].Via).To(Equal("5.5.5.5")) + Expect(result.Routes[0].Metric).To(BeEquivalentTo(42)) }) }) }) From 8b0ed544dffa4ccd7067a52f8381df27ac14ab8d Mon Sep 17 00:00:00 2001 From: Bryan Venteicher Date: Wed, 13 Dec 2023 12:38:20 -0600 Subject: [PATCH 2/2] Add VirtualMachine hub-spoke-hub and spoke-hub-spoke tests Start with a nearly fully populated v1a2 VM and ensure that the VM resulting from conversion is the same. We can keep adding these explicit conversion tests to ensure that fields are converted as we expect instead of always depending on the fuzzer. Add typical v1a1 TKG VM and ensure spoke-hub-spoke conversion works. Fix a few conversion issues around the Network and Advanced fields. Always restore the full VM Status from the annotations. Our controller should be the only one updating the Status, and an update from the conversion will cause our controllers to reconcile and reevaluate the Status. --- api/v1alpha1/conversion_test.go | 25 +- api/v1alpha1/virtualmachine_conversion.go | 79 ++-- .../virtualmachine_conversion_test.go | 386 ++++++++++++++++++ 3 files changed, 451 insertions(+), 39 deletions(-) create mode 100644 api/v1alpha1/virtualmachine_conversion_test.go diff --git a/api/v1alpha1/conversion_test.go b/api/v1alpha1/conversion_test.go index e6698a98e..7ba0708fb 100644 --- a/api/v1alpha1/conversion_test.go +++ b/api/v1alpha1/conversion_test.go @@ -85,25 +85,30 @@ func TestFuzzyConversion(t *testing.T) { } func overrideVirtualMachineFieldsFuncs(codecs runtimeserializer.CodecFactory) []interface{} { - // TODO: The changes from v1a1 to v1a2 is quite large so several parts of the input objects are - // defaulted out until we start to marshall the object in the annotations for down conversions - // and back. return []interface{}{ func(vmSpec *v1alpha1.VirtualMachineSpec, c fuzz.Continue) { c.Fuzz(vmSpec) - for i := range vmSpec.Volumes { - // Not present in v1a2. - vmSpec.Volumes[i].VsphereVolume = nil + var volumes []v1alpha1.VirtualMachineVolume + for _, vol := range vmSpec.Volumes { + // vSphere volumes are gone in v1a2 so skip those. + if vol.VsphereVolume == nil { + volumes = append(volumes, vol) + } } + vmSpec.Volumes = volumes - if vmSpec.AdvancedOptions != nil { - if provOpts := vmSpec.AdvancedOptions.DefaultVolumeProvisioningOptions; provOpts != nil { + if opts := vmSpec.AdvancedOptions; opts != nil { + if provOpts := opts.DefaultVolumeProvisioningOptions; provOpts != nil { if provOpts.ThinProvisioned != nil { // Both cannot be set. provOpts.EagerZeroed = nil } } + + if opts.ChangeBlockTracking != nil && !*opts.ChangeBlockTracking { + opts.ChangeBlockTracking = nil + } } // This is effectively deprecated. @@ -226,3 +231,7 @@ func overrideConditionsObservedGeneration(conditions []metav1.Condition) { conditions[i].ObservedGeneration = 0 } } + +func ptrOf[T any](v T) *T { + return &v +} diff --git a/api/v1alpha1/virtualmachine_conversion.go b/api/v1alpha1/virtualmachine_conversion.go index a626dac8f..77bdbcb51 100644 --- a/api/v1alpha1/virtualmachine_conversion.go +++ b/api/v1alpha1/virtualmachine_conversion.go @@ -225,6 +225,9 @@ func convert_v1alpha2_BootstrapSpec_To_v1alpha1_VmMetadata( out.Transport = VirtualMachineMetadataExtraConfigTransport case "user-data": out.Transport = VirtualMachineMetadataCloudInitTransport + default: + // Best approx we can do. + out.Transport = VirtualMachineMetadataCloudInitTransport } } else if cloudInit.CloudConfig != nil { out.Transport = VirtualMachineMetadataCloudInitTransport @@ -358,34 +361,39 @@ func convert_v1alpha2_ReadinessProbeSpec_To_v1alpha1_Probe(in *v1alpha2.VirtualM } func convert_v1alpha1_VirtualMachineAdvancedOptions_To_v1alpha2_VirtualMachineAdvancedSpec( - in *VirtualMachineAdvancedOptions) *v1alpha2.VirtualMachineAdvancedSpec { + in *VirtualMachineAdvancedOptions, inVolumes []VirtualMachineVolume) *v1alpha2.VirtualMachineAdvancedSpec { + + bootDiskCapacity := convert_v1alpha1_VsphereVolumes_To_v1alpha2_BootDiskCapacity(inVolumes) - if in == nil || apiequality.Semantic.DeepEqual(*in, VirtualMachineAdvancedOptions{}) { + if (in == nil || apiequality.Semantic.DeepEqual(*in, VirtualMachineAdvancedOptions{})) && bootDiskCapacity == nil { return nil } out := v1alpha2.VirtualMachineAdvancedSpec{} - - if opts := in.DefaultVolumeProvisioningOptions; opts != nil { - if opts.ThinProvisioned != nil { - if *opts.ThinProvisioned { - out.DefaultVolumeProvisioningMode = v1alpha2.VirtualMachineVolumeProvisioningModeThin - } else { - out.DefaultVolumeProvisioningMode = v1alpha2.VirtualMachineVolumeProvisioningModeThick + out.BootDiskCapacity = bootDiskCapacity + + if in != nil { + if opts := in.DefaultVolumeProvisioningOptions; opts != nil { + if opts.ThinProvisioned != nil { + if *opts.ThinProvisioned { + out.DefaultVolumeProvisioningMode = v1alpha2.VirtualMachineVolumeProvisioningModeThin + } else { + out.DefaultVolumeProvisioningMode = v1alpha2.VirtualMachineVolumeProvisioningModeThick + } + } else if opts.EagerZeroed != nil && *opts.EagerZeroed { + out.DefaultVolumeProvisioningMode = v1alpha2.VirtualMachineVolumeProvisioningModeThickEagerZero } - } else if opts.EagerZeroed != nil && *opts.EagerZeroed { - out.DefaultVolumeProvisioningMode = v1alpha2.VirtualMachineVolumeProvisioningModeThickEagerZero } - } - if in.ChangeBlockTracking != nil { - out.ChangeBlockTracking = *in.ChangeBlockTracking + if in.ChangeBlockTracking != nil { + out.ChangeBlockTracking = *in.ChangeBlockTracking + } } return &out } -func convert_v1alpha1_VsphereVolumes_To_v1alpah2_BootDiskCapacity(volumes []VirtualMachineVolume) *resource.Quantity { +func convert_v1alpha1_VsphereVolumes_To_v1alpha2_BootDiskCapacity(volumes []VirtualMachineVolume) *resource.Quantity { // The v1a1 VsphereVolume was never a great API as you had to know the DeviceKey upfront; at the time our // API was private - only used by CAPW - and predates the "VM Service" VMs; In v1a2, we only support resizing // the boot disk via an explicit field. As good as we can here, map v1a1 volume into the v1a2 specific field. @@ -441,7 +449,7 @@ func convert_v1alpha2_VirtualMachineAdvancedSpec_To_v1alpha1_VirtualMachineAdvan } func convert_v1alpha2_BootDiskCapacity_To_v1alpha1_VirtualMachineVolume(capacity *resource.Quantity) *VirtualMachineVolume { - if capacity == nil || capacity.IsZero() { + if capacity == nil { return nil } @@ -577,10 +585,7 @@ func Convert_v1alpha1_VirtualMachineSpec_To_v1alpha2_VirtualMachineSpec( } out.ReadinessProbe = convert_v1alpha1_Probe_To_v1alpha2_ReadinessProbeSpec(in.ReadinessProbe) - out.Advanced = convert_v1alpha1_VirtualMachineAdvancedOptions_To_v1alpha2_VirtualMachineAdvancedSpec(in.AdvancedOptions) - if out.Advanced != nil { - out.Advanced.BootDiskCapacity = convert_v1alpha1_VsphereVolumes_To_v1alpah2_BootDiskCapacity(in.Volumes) - } + out.Advanced = convert_v1alpha1_VirtualMachineAdvancedOptions_To_v1alpha2_VirtualMachineAdvancedSpec(in.AdvancedOptions, in.Volumes) if in.ResourcePolicyName != "" { if out.Reserved == nil { @@ -610,6 +615,7 @@ func Convert_v1alpha2_VirtualMachineSpec_To_v1alpha1_VirtualMachineSpec( out.VmMetadata = convert_v1alpha2_BootstrapSpec_To_v1alpha1_VmMetadata(in.Bootstrap) if in.Network != nil { + out.NetworkInterfaces = make([]VirtualMachineNetworkInterface, 0, len(in.Network.Interfaces)) for _, networkInterfaceSpec := range in.Network.Interfaces { networkInterface := convert_v1alpha2_NetworkInterfaceSpec_To_v1alpha1_NetworkInterface(networkInterfaceSpec) out.NetworkInterfaces = append(out.NetworkInterfaces, networkInterface) @@ -821,16 +827,20 @@ func restore_v1alpha2_VirtualMachineBootstrapSpec( } } -func restore_v1alpha2_VirtualMachineNetwork( +func restore_v1alpha2_VirtualMachineNetworkSpec( dst, src *v1alpha2.VirtualMachine) { - dstNetwork := dst.Spec.Network srcNetwork := src.Spec.Network - - if dstNetwork == nil || srcNetwork == nil { + if srcNetwork == nil || (srcNetwork.HostName == "" && !srcNetwork.Disabled && len(srcNetwork.Interfaces) == 0) { + // Nothing to restore. return } + if dst.Spec.Network == nil { + dst.Spec.Network = &v1alpha2.VirtualMachineNetworkSpec{} + } + dstNetwork := dst.Spec.Network + dstNetwork.HostName = srcNetwork.HostName dstNetwork.Disabled = srcNetwork.Disabled @@ -859,6 +869,17 @@ func restore_v1alpha2_VirtualMachineNetwork( } } +func restore_v1alpha2_VirtualMachineReadinessProbeSpec( + dst, src *v1alpha2.VirtualMachine) { + + if src.Spec.ReadinessProbe != nil { + if dst.Spec.ReadinessProbe == nil { + dst.Spec.ReadinessProbe = &v1alpha2.VirtualMachineReadinessProbeSpec{} + } + dst.Spec.ReadinessProbe.GuestInfo = src.Spec.ReadinessProbe.GuestInfo + } +} + // ConvertTo converts this VirtualMachine to the Hub version. func (src *VirtualMachine) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1alpha2.VirtualMachine) @@ -873,14 +894,10 @@ func (src *VirtualMachine) ConvertTo(dstRaw conversion.Hub) error { } restore_v1alpha2_VirtualMachineBootstrapSpec(dst, restored) - restore_v1alpha2_VirtualMachineNetwork(dst, restored) + restore_v1alpha2_VirtualMachineNetworkSpec(dst, restored) + restore_v1alpha2_VirtualMachineReadinessProbeSpec(dst, restored) - if restored.Spec.ReadinessProbe != nil { - if dst.Spec.ReadinessProbe == nil { - dst.Spec.ReadinessProbe = &v1alpha2.VirtualMachineReadinessProbeSpec{} - } - dst.Spec.ReadinessProbe.GuestInfo = restored.Spec.ReadinessProbe.GuestInfo - } + dst.Status = restored.Status return nil } diff --git a/api/v1alpha1/virtualmachine_conversion_test.go b/api/v1alpha1/virtualmachine_conversion_test.go new file mode 100644 index 000000000..56ebd54eb --- /dev/null +++ b/api/v1alpha1/virtualmachine_conversion_test.go @@ -0,0 +1,386 @@ +// Copyright (c) 2023 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package v1alpha1_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/conversion" + + "github.com/vmware-tanzu/vm-operator/api/utilconversion" + "github.com/vmware-tanzu/vm-operator/api/v1alpha1" + nextver "github.com/vmware-tanzu/vm-operator/api/v1alpha2" + nextver_cloudinit "github.com/vmware-tanzu/vm-operator/api/v1alpha2/cloudinit" + nextver_common "github.com/vmware-tanzu/vm-operator/api/v1alpha2/common" + nextver_sysprep "github.com/vmware-tanzu/vm-operator/api/v1alpha2/sysprep" +) + +func TestVirtualMachineConversion(t *testing.T) { + g := NewWithT(t) + + hubSpokeHub := func(hub conversion.Hub, spoke conversion.Convertible) { + hubBefore := hub.DeepCopyObject().(conversion.Hub) + + // First convert hub to spoke + dstCopy := spoke.DeepCopyObject().(conversion.Convertible) + g.Expect(dstCopy.ConvertFrom(hubBefore)).To(Succeed()) + + // Convert spoke back to hub and check if the resulting hub is equal to the hub before the round trip + hubAfter := hub.DeepCopyObject().(conversion.Hub) + g.Expect(dstCopy.ConvertTo(hubAfter)).To(Succeed()) + + g.Expect(apiequality.Semantic.DeepEqual(hubBefore, hubAfter)).To(BeTrue(), cmp.Diff(hubBefore, hubAfter)) + } + + spokeHubSpoke := func(spoke conversion.Convertible, hub conversion.Hub) { + spokeBefore := spoke.DeepCopyObject().(conversion.Convertible) + + // First convert spoke to hub + hubCopy := hub.DeepCopyObject().(conversion.Hub) + g.Expect(spokeBefore.ConvertTo(hubCopy)).To(Succeed()) + + // Convert hub back to spoke and check if the resulting spoke is equal to the spoke before the round trip + spokeAfter := spoke.DeepCopyObject().(conversion.Convertible) + g.Expect(spokeAfter.ConvertFrom(hubCopy)).To(Succeed()) + + metaAfter := spokeAfter.(metav1.Object) + delete(metaAfter.GetAnnotations(), utilconversion.AnnotationKey) + + g.Expect(apiequality.Semantic.DeepEqual(spokeBefore, spokeAfter)).To(BeTrue(), cmp.Diff(spokeBefore, spokeAfter)) + } + + t.Run("VirtualMachine hub-spoke-hub", func(t *testing.T) { + hub := nextver.VirtualMachine{ + Spec: nextver.VirtualMachineSpec{ + ImageName: "my-name", + ClassName: "my-class", + StorageClass: "my-storage-class", + Bootstrap: &nextver.VirtualMachineBootstrapSpec{ + CloudInit: &nextver.VirtualMachineBootstrapCloudInitSpec{ + RawCloudConfig: &nextver_common.SecretKeySelector{ + Name: "cloud-init-secret", + Key: "user-data", + }, + SSHAuthorizedKeys: []string{"my-ssh-key"}, + }, + }, + Network: &nextver.VirtualMachineNetworkSpec{ + HostName: "my-test-vm", + Disabled: true, + Interfaces: []nextver.VirtualMachineNetworkInterfaceSpec{ + { + Name: "vds-interface", + Network: nextver_common.PartialObjectRef{ + TypeMeta: metav1.TypeMeta{ + Kind: "Network", + APIVersion: "netoperator.vmware.com/v1alpha1", + }, + Name: "primary", + }, + }, + { + Name: "ncp-interface", + Network: nextver_common.PartialObjectRef{ + TypeMeta: metav1.TypeMeta{ + Kind: "VirtualNetwork", + APIVersion: "vmware.com/v1alpha1", + }, + Name: "segment1", + }, + }, + { + Name: "my-interface", + Network: nextver_common.PartialObjectRef{ + TypeMeta: metav1.TypeMeta{ + Kind: "Network", + APIVersion: "netoperator.vmware.com/v1alpha1", + }, + Name: "secondary", + }, + Addresses: []string{"1.1.1.11", "2.2.2.22"}, + DHCP4: true, + DHCP6: true, + Gateway4: "1.1.1.1", + Gateway6: "2.2.2.2", + MTU: ptrOf[int64](9000), + Nameservers: []string{"9.9.9.9"}, + Routes: []nextver.VirtualMachineNetworkRouteSpec{ + { + To: "3.3.3.3", + Via: "1.1.1.111", + Metric: 42, + }, + }, + SearchDomains: []string{"vmware.com"}, + }, + }, + }, + PowerState: nextver.VirtualMachinePowerStateOff, + PowerOffMode: nextver.VirtualMachinePowerOpModeHard, + SuspendMode: nextver.VirtualMachinePowerOpModeTrySoft, + NextRestartTime: "tomorrow", + RestartMode: nextver.VirtualMachinePowerOpModeSoft, + Volumes: []nextver.VirtualMachineVolume{ + { + Name: "my-volume", + VirtualMachineVolumeSource: nextver.VirtualMachineVolumeSource{ + PersistentVolumeClaim: ptrOf(nextver.PersistentVolumeClaimVolumeSource{ + PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "my-claim", + }, + }), + }, + }, + { + Name: "my-volume-2", + VirtualMachineVolumeSource: nextver.VirtualMachineVolumeSource{ + PersistentVolumeClaim: ptrOf(nextver.PersistentVolumeClaimVolumeSource{ + PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "my-claim-2", + }, + InstanceVolumeClaim: &nextver.InstanceVolumeClaimVolumeSource{ + StorageClass: "instance-storage-class", + Size: resource.MustParse("2048k"), + }, + }), + }, + }, + }, + ReadinessProbe: &nextver.VirtualMachineReadinessProbeSpec{ + TCPSocket: &nextver.TCPSocketAction{ + Host: "some-host", + Port: intstr.FromString("https"), + }, + GuestHeartbeat: &nextver.GuestHeartbeatAction{ + ThresholdStatus: nextver.RedHeartbeatStatus, + }, + GuestInfo: []nextver.GuestInfoAction{ + { + Key: "guest-key", + Value: "guest-value", + }, + }, + TimeoutSeconds: 100, + PeriodSeconds: 200, + }, + Advanced: &nextver.VirtualMachineAdvancedSpec{ + BootDiskCapacity: ptrOf(resource.MustParse("1024k")), + DefaultVolumeProvisioningMode: nextver.VirtualMachineVolumeProvisioningModeThickEagerZero, + ChangeBlockTracking: true, + }, + Reserved: &nextver.VirtualMachineReservedSpec{ + ResourcePolicyName: "my-resource-policy", + }, + MinHardwareVersion: 42, + }, + } + + hubSpokeHub(&hub, &v1alpha1.VirtualMachine{}) + }) + + t.Run("VirtualMachine hub-spoke-hub with inlined CloudInit", func(t *testing.T) { + hub := nextver.VirtualMachine{ + Spec: nextver.VirtualMachineSpec{ + Bootstrap: &nextver.VirtualMachineBootstrapSpec{ + CloudInit: &nextver.VirtualMachineBootstrapCloudInitSpec{ + CloudConfig: &nextver_cloudinit.CloudConfig{ + Timezone: "my-tz", + Users: []nextver_cloudinit.User{ + { + Name: "not-root", + }, + }, + }, + SSHAuthorizedKeys: []string{"my-ssh-key"}, + }, + }, + }, + } + + hubSpokeHub(&hub, &v1alpha1.VirtualMachine{}) + }) + + t.Run("VirtualMachine hub-spoke-hub with inlined Sysprep", func(t *testing.T) { + hub := nextver.VirtualMachine{ + Spec: nextver.VirtualMachineSpec{ + Bootstrap: &nextver.VirtualMachineBootstrapSpec{ + Sysprep: &nextver.VirtualMachineBootstrapSysprepSpec{ + Sysprep: &nextver_sysprep.Sysprep{ + GUIRunOnce: nextver_sysprep.GUIRunOnce{ + Commands: []string{"echo", "hello"}, + }, + GUIUnattended: nextver_sysprep.GUIUnattended{ + AutoLogon: true, + }, + Identification: nextver_sysprep.Identification{ + DomainAdmin: "my-admin", + }, + UserData: nextver_sysprep.UserData{ + FullName: "vmware", + }, + }, + }, + }, + }, + } + + hubSpokeHub(&hub, &v1alpha1.VirtualMachine{}) + }) + + t.Run("VirtualMachine hub-spoke-hub with LinuxPrep and vAppConfig", func(t *testing.T) { + hub := nextver.VirtualMachine{ + Spec: nextver.VirtualMachineSpec{ + Bootstrap: &nextver.VirtualMachineBootstrapSpec{ + LinuxPrep: &nextver.VirtualMachineBootstrapLinuxPrepSpec{ + HardwareClockIsUTC: true, + TimeZone: "my-tz", + }, + VAppConfig: &nextver.VirtualMachineBootstrapVAppConfigSpec{ + Properties: []nextver_common.KeyValueOrSecretKeySelectorPair{ + { + Key: "my-key", + Value: nextver_common.ValueOrSecretKeySelector{ + Value: ptrOf("my-value"), + }, + }, + }, + RawProperties: "my-secret", + }, + }, + }, + } + + hubSpokeHub(&hub, &v1alpha1.VirtualMachine{}) + }) + + t.Run("VirtualMachine hub-spoke-hub with vAppConfig", func(t *testing.T) { + hub := nextver.VirtualMachine{ + Spec: nextver.VirtualMachineSpec{ + Bootstrap: &nextver.VirtualMachineBootstrapSpec{ + VAppConfig: &nextver.VirtualMachineBootstrapVAppConfigSpec{ + Properties: []nextver_common.KeyValueOrSecretKeySelectorPair{ + { + Key: "my-key", + Value: nextver_common.ValueOrSecretKeySelector{ + Value: ptrOf("my-value"), + }, + }, + }, + RawProperties: "my-secret", + }, + }, + }, + } + + hubSpokeHub(&hub, &v1alpha1.VirtualMachine{}) + }) + + t.Run("VirtualMachine hub-spoke Status", func(t *testing.T) { + hub := nextver.VirtualMachine{ + Status: nextver.VirtualMachineStatus{ + Network: &nextver.VirtualMachineNetworkStatus{ + PrimaryIP4: "192.168.1.10", + Interfaces: []nextver.VirtualMachineNetworkInterfaceStatus{ + { + Name: "eth0", + IP: nextver.VirtualMachineNetworkInterfaceIPStatus{ + Addresses: []nextver.VirtualMachineNetworkInterfaceIPAddrStatus{ + { + Address: "my-ip", + }, + }, + MACAddr: "my-mac", + }, + }, + }, + }, + Conditions: []metav1.Condition{ + { + Type: nextver.VirtualMachineConditionCreated, + Status: metav1.ConditionTrue, + }, + }, + }, + } + + spoke := &v1alpha1.VirtualMachine{} + g.Expect(spoke.ConvertFrom(&hub)).To(Succeed()) + + g.Expect(spoke.Status.VmIp).To(Equal("192.168.1.10")) + g.Expect(spoke.Status.NetworkInterfaces[0].MacAddress).To(Equal("my-mac")) + g.Expect(spoke.Status.NetworkInterfaces[0].IpAddresses).To(Equal([]string{"my-ip"})) + g.Expect(spoke.Status.Phase).To(Equal(v1alpha1.Created)) + }) + + t.Run("VirtualMachine spoke-hub-spoke with TKG CP", func(t *testing.T) { + spoke := v1alpha1.VirtualMachine{ + Spec: v1alpha1.VirtualMachineSpec{ + ClassName: "best-effort-small", + ImageName: "vmi-d5973af773e94c1d8", + NetworkInterfaces: []v1alpha1.VirtualMachineNetworkInterface{ + { + NetworkName: "primary", + NetworkType: "vsphere-distributed", + }, + }, + PowerOffMode: v1alpha1.VirtualMachinePowerOpModeHard, + PowerState: v1alpha1.VirtualMachinePoweredOn, + ReadinessProbe: &v1alpha1.Probe{ + TCPSocket: &v1alpha1.TCPSocketAction{ + Port: intstr.FromInt(6443), + Host: "10.1.1.10", + }, + }, + ResourcePolicyName: "tkg1", + RestartMode: v1alpha1.VirtualMachinePowerOpModeHard, + StorageClass: "wcpglobal-storage-profile", + SuspendMode: v1alpha1.VirtualMachinePowerOpModeHard, + VmMetadata: &v1alpha1.VirtualMachineMetadata{ + SecretName: "tkg1-pg4ct-mvqgx", + Transport: v1alpha1.VirtualMachineMetadataCloudInitTransport, + }, + Volumes: []v1alpha1.VirtualMachineVolume{ + { + Name: "my-volume", + PersistentVolumeClaim: &v1alpha1.PersistentVolumeClaimVolumeSource{ + PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "my-claim", + }, + }, + }, + }, + }, + Status: v1alpha1.VirtualMachineStatus{ + Phase: v1alpha1.Unknown, + }, + } + + spokeHubSpoke(&spoke, &nextver.VirtualMachine{}) + }) + + t.Run("VirtualMachine spoke-hub-spoke with CloudInit EC", func(t *testing.T) { + // This transport is really old and probably never used. + spoke := v1alpha1.VirtualMachine{ + Spec: v1alpha1.VirtualMachineSpec{ + VmMetadata: &v1alpha1.VirtualMachineMetadata{ + Transport: v1alpha1.VirtualMachineMetadataExtraConfigTransport, + SecretName: "my-secret", + }, + }, + Status: v1alpha1.VirtualMachineStatus{ + Phase: v1alpha1.Unknown, + }, + } + + spokeHubSpoke(&spoke, &nextver.VirtualMachine{}) + }) +}