diff --git a/apis/v1alpha3/topology_conversion.go b/apis/v1alpha3/topology_conversion.go new file mode 100644 index 0000000000..6560e2cc37 --- /dev/null +++ b/apis/v1alpha3/topology_conversion.go @@ -0,0 +1,32 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha3 + +import ( + conversion "k8s.io/apimachinery/pkg/conversion" + v1beta1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1" +) + +func Convert_v1beta1_Topology_To_v1alpha3_Topology(in *v1beta1.Topology, out *Topology, s conversion.Scope) error { + if len(in.NetworkConfigurations) > 0 { + networks := make([]string, len(in.NetworkConfigurations)) + for i := range in.NetworkConfigurations { + networks[i] = in.NetworkConfigurations[i].NetworkName + } + } + return nil +} diff --git a/apis/v1alpha3/zz_generated.conversion.go b/apis/v1alpha3/zz_generated.conversion.go index 87e728ee2a..fa4ec96357 100644 --- a/apis/v1alpha3/zz_generated.conversion.go +++ b/apis/v1alpha3/zz_generated.conversion.go @@ -150,11 +150,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.Topology)(nil), (*Topology)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_Topology_To_v1alpha3_Topology(a.(*v1beta1.Topology), b.(*Topology), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*VSphereCluster)(nil), (*v1beta1.VSphereCluster)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha3_VSphereCluster_To_v1beta1_VSphereCluster(a.(*VSphereCluster), b.(*v1beta1.VSphereCluster), scope) }); err != nil { @@ -450,6 +445,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.Topology)(nil), (*Topology)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_Topology_To_v1alpha3_Topology(a.(*v1beta1.Topology), b.(*Topology), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta1.VSphereClusterSpec)(nil), (*VSphereClusterSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_VSphereClusterSpec_To_v1alpha3_VSphereClusterSpec(a.(*v1beta1.VSphereClusterSpec), b.(*VSphereClusterSpec), scope) }); err != nil { @@ -784,15 +784,11 @@ func autoConvert_v1beta1_Topology_To_v1alpha3_Topology(in *v1beta1.Topology, out out.ComputeCluster = (*string)(unsafe.Pointer(in.ComputeCluster)) out.Hosts = (*FailureDomainHosts)(unsafe.Pointer(in.Hosts)) out.Networks = *(*[]string)(unsafe.Pointer(&in.Networks)) + // WARNING: in.NetworkConfigurations requires manual conversion: does not exist in peer-type out.Datastore = in.Datastore return nil } -// Convert_v1beta1_Topology_To_v1alpha3_Topology is an autogenerated conversion function. -func Convert_v1beta1_Topology_To_v1alpha3_Topology(in *v1beta1.Topology, out *Topology, s conversion.Scope) error { - return autoConvert_v1beta1_Topology_To_v1alpha3_Topology(in, out, s) -} - func autoConvert_v1alpha3_VSphereCluster_To_v1beta1_VSphereCluster(in *VSphereCluster, out *v1beta1.VSphereCluster, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1alpha3_VSphereClusterSpec_To_v1beta1_VSphereClusterSpec(&in.Spec, &out.Spec, s); err != nil { @@ -1144,7 +1140,17 @@ func Convert_v1beta1_VSphereFailureDomain_To_v1alpha3_VSphereFailureDomain(in *v func autoConvert_v1alpha3_VSphereFailureDomainList_To_v1beta1_VSphereFailureDomainList(in *VSphereFailureDomainList, out *v1beta1.VSphereFailureDomainList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]v1beta1.VSphereFailureDomain)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1beta1.VSphereFailureDomain, len(*in)) + for i := range *in { + if err := Convert_v1alpha3_VSphereFailureDomain_To_v1beta1_VSphereFailureDomain(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -1155,7 +1161,17 @@ func Convert_v1alpha3_VSphereFailureDomainList_To_v1beta1_VSphereFailureDomainLi func autoConvert_v1beta1_VSphereFailureDomainList_To_v1alpha3_VSphereFailureDomainList(in *v1beta1.VSphereFailureDomainList, out *VSphereFailureDomainList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]VSphereFailureDomain)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VSphereFailureDomain, len(*in)) + for i := range *in { + if err := Convert_v1beta1_VSphereFailureDomain_To_v1alpha3_VSphereFailureDomain(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } diff --git a/apis/v1alpha4/topology_conversion.go b/apis/v1alpha4/topology_conversion.go new file mode 100644 index 0000000000..6244e57bcc --- /dev/null +++ b/apis/v1alpha4/topology_conversion.go @@ -0,0 +1,32 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha4 + +import ( + conversion "k8s.io/apimachinery/pkg/conversion" + v1beta1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1" +) + +func Convert_v1beta1_Topology_To_v1alpha4_Topology(in *v1beta1.Topology, out *Topology, s conversion.Scope) error { + if len(in.NetworkConfigurations) > 0 { + networks := make([]string, len(in.NetworkConfigurations)) + for i := range in.NetworkConfigurations { + networks[i] = in.NetworkConfigurations[i].NetworkName + } + } + return nil +} diff --git a/apis/v1alpha4/zz_generated.conversion.go b/apis/v1alpha4/zz_generated.conversion.go index a401c23933..b1f72ac734 100644 --- a/apis/v1alpha4/zz_generated.conversion.go +++ b/apis/v1alpha4/zz_generated.conversion.go @@ -150,11 +150,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.Topology)(nil), (*Topology)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_Topology_To_v1alpha4_Topology(a.(*v1beta1.Topology), b.(*Topology), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*VSphereCluster)(nil), (*v1beta1.VSphereCluster)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha4_VSphereCluster_To_v1beta1_VSphereCluster(a.(*VSphereCluster), b.(*v1beta1.VSphereCluster), scope) }); err != nil { @@ -490,6 +485,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.Topology)(nil), (*Topology)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_Topology_To_v1alpha4_Topology(a.(*v1beta1.Topology), b.(*Topology), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta1.VSphereClusterSpec)(nil), (*VSphereClusterSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_VSphereClusterSpec_To_v1alpha4_VSphereClusterSpec(a.(*v1beta1.VSphereClusterSpec), b.(*VSphereClusterSpec), scope) }); err != nil { @@ -824,15 +824,11 @@ func autoConvert_v1beta1_Topology_To_v1alpha4_Topology(in *v1beta1.Topology, out out.ComputeCluster = (*string)(unsafe.Pointer(in.ComputeCluster)) out.Hosts = (*FailureDomainHosts)(unsafe.Pointer(in.Hosts)) out.Networks = *(*[]string)(unsafe.Pointer(&in.Networks)) + // WARNING: in.NetworkConfigurations requires manual conversion: does not exist in peer-type out.Datastore = in.Datastore return nil } -// Convert_v1beta1_Topology_To_v1alpha4_Topology is an autogenerated conversion function. -func Convert_v1beta1_Topology_To_v1alpha4_Topology(in *v1beta1.Topology, out *Topology, s conversion.Scope) error { - return autoConvert_v1beta1_Topology_To_v1alpha4_Topology(in, out, s) -} - func autoConvert_v1alpha4_VSphereCluster_To_v1beta1_VSphereCluster(in *VSphereCluster, out *v1beta1.VSphereCluster, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1alpha4_VSphereClusterSpec_To_v1beta1_VSphereClusterSpec(&in.Spec, &out.Spec, s); err != nil { @@ -1302,7 +1298,17 @@ func Convert_v1beta1_VSphereFailureDomain_To_v1alpha4_VSphereFailureDomain(in *v func autoConvert_v1alpha4_VSphereFailureDomainList_To_v1beta1_VSphereFailureDomainList(in *VSphereFailureDomainList, out *v1beta1.VSphereFailureDomainList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]v1beta1.VSphereFailureDomain)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1beta1.VSphereFailureDomain, len(*in)) + for i := range *in { + if err := Convert_v1alpha4_VSphereFailureDomain_To_v1beta1_VSphereFailureDomain(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -1313,7 +1319,17 @@ func Convert_v1alpha4_VSphereFailureDomainList_To_v1beta1_VSphereFailureDomainLi func autoConvert_v1beta1_VSphereFailureDomainList_To_v1alpha4_VSphereFailureDomainList(in *v1beta1.VSphereFailureDomainList, out *VSphereFailureDomainList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]VSphereFailureDomain)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VSphereFailureDomain, len(*in)) + for i := range *in { + if err := Convert_v1beta1_VSphereFailureDomain_To_v1alpha4_VSphereFailureDomain(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } diff --git a/apis/v1beta1/vspherefailuredomain_types.go b/apis/v1beta1/vspherefailuredomain_types.go index 4151fe3926..697e4c4dcb 100644 --- a/apis/v1beta1/vspherefailuredomain_types.go +++ b/apis/v1beta1/vspherefailuredomain_types.go @@ -18,6 +18,7 @@ limitations under the License. package v1beta1 import ( + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -74,6 +75,10 @@ type Topology struct { // +optional Networks []string `json:"networks,omitempty"` + // NetworkConfigurations is a list with new network configurations within this failure domain + // +optional + NetworkConfigurations []NetworkConfiguration `json:"networkConfigs,omitempty"` + // Datastore is the name or inventory path of the datastore in which the // virtual machine is created/located. // +optional @@ -88,6 +93,56 @@ type FailureDomainHosts struct { HostGroupName string `json:"hostGroupName"` } +// NetworkConfiguration defines a network configuration that should be used when consuming +// a failure domain. +type NetworkConfiguration struct { + // NetworkName is the network name for this machine's VM. + NetworkName string `json:"name,omitempty"` + + // DHCP4 is a flag that indicates whether or not to use DHCP for IPv4 + // +optional + DHCP4 *bool `json:"dhcp4,omitempty"` + + // DHCP6 is a flag that indicates whether or not to use DHCP for IPv6 + // +optional + DHCP6 *bool `json:"dhcp6,omitempty"` + + // Nameservers is a list of IPv4 and/or IPv6 addresses used as DNS + // nameservers. + // Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf). + // +optional + Nameservers []string `json:"nameservers,omitempty"` + + // SearchDomains is a list of search domains used when resolving IP + // addresses with DNS. + // +optional + SearchDomains []string `json:"searchDomains,omitempty"` + + // DHCP4Overrides allows for the control over several DHCP behaviors. + // Overrides will only be applied when the corresponding DHCP flag is set. + // Only configured values will be sent, omitted values will default to + // distribution defaults. + // Dependent on support in the network stack for your distribution. + // For more information see the netplan reference (https://netplan.io/reference#dhcp-overrides) + // +optional + DHCP4Overrides *DHCPOverrides `json:"dhcp4Overrides,omitempty"` + + // DHCP6Overrides allows for the control over several DHCP behaviors. + // Overrides will only be applied when the corresponding DHCP flag is set. + // Only configured values will be sent, omitted values will default to + // distribution defaults. + // Dependent on support in the network stack for your distribution. + // For more information see the netplan reference (https://netplan.io/reference#dhcp-overrides) + // +optional + DHCP6Overrides *DHCPOverrides `json:"dhcp6Overrides,omitempty"` + + // AddressesFromPools is a list of IPAddressPools that should be assigned + // to IPAddressClaims. The machine's cloud-init metadata will be populated + // with IPAddresses fulfilled by an IPAM provider. + // +optional + AddressesFromPools []corev1.TypedLocalObjectReference `json:"addressesFromPools,omitempty"` +} + // +kubebuilder:object:root=true // +kubebuilder:storageversion // +kubebuilder:resource:path=vspherefailuredomains,scope=Cluster,categories=cluster-api diff --git a/apis/v1beta1/vspherefailuredomain_webhook.go b/apis/v1beta1/vspherefailuredomain_webhook.go index 5812aa5bcc..de6f30d58f 100644 --- a/apis/v1beta1/vspherefailuredomain_webhook.go +++ b/apis/v1beta1/vspherefailuredomain_webhook.go @@ -64,6 +64,10 @@ func (r *VSphereFailureDomain) ValidateCreate() error { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "Topology", "ComputeCluster"), fmt.Sprintf("cannot be nil if zone's Failure Domain type is %s", r.Spec.Zone.Type))) } + if len(r.Spec.Topology.NetworkConfigurations) != 0 && len(r.Spec.Topology.Networks) != 0 { + allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "Topology", "Networks"), "cannot be set if spec.Topology.NetworkConfigs is already set")) + } + return aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs) } diff --git a/apis/v1beta1/vspherefailuredomain_webhook_test.go b/apis/v1beta1/vspherefailuredomain_webhook_test.go index df9457d613..0d0d210f9f 100644 --- a/apis/v1beta1/vspherefailuredomain_webhook_test.go +++ b/apis/v1beta1/vspherefailuredomain_webhook_test.go @@ -23,16 +23,58 @@ import ( "k8s.io/utils/pointer" ) -//nolint func TestVsphereFailureDomain_Default(t *testing.T) { - g := NewWithT(t) - m := &VSphereFailureDomain{ - Spec: VSphereFailureDomainSpec{}, + tests := []struct { + name string + spec VSphereFailureDomainSpec + expectedSpec VSphereFailureDomainSpec + }{ + { + name: "when autoconfigure is not set", + spec: VSphereFailureDomainSpec{ + Zone: FailureDomain{ + AutoConfigure: nil, + }, + Region: FailureDomain{ + AutoConfigure: nil, + }, + }, + expectedSpec: VSphereFailureDomainSpec{ + Zone: FailureDomain{ + AutoConfigure: pointer.Bool(false), + }, + Region: FailureDomain{ + AutoConfigure: pointer.Bool(false), + }, + }, + }, + { + name: "when autoconfigure is set just on one field", + spec: VSphereFailureDomainSpec{ + Region: FailureDomain{ + AutoConfigure: pointer.Bool(true), + }, + }, + expectedSpec: VSphereFailureDomainSpec{ + Zone: FailureDomain{ + AutoConfigure: pointer.Bool(false), + }, + Region: FailureDomain{ + AutoConfigure: pointer.Bool(true), + }, + }, + }, } - m.Default() - g.Expect(*m.Spec.Zone.AutoConfigure).To(BeFalse()) - g.Expect(*m.Spec.Region.AutoConfigure).To(BeFalse()) + g := NewWithT(t) + + for _, test := range tests { + m := &VSphereFailureDomain{ + Spec: test.spec, + } + m.Default() + g.Expect(m.Spec).To(Equal(test.expectedSpec)) + } } func TestVSphereFailureDomain_ValidateCreate(t *testing.T) { @@ -40,7 +82,7 @@ func TestVSphereFailureDomain_ValidateCreate(t *testing.T) { tests := []struct { name string - errExpected *bool + errExpected bool failureDomain VSphereFailureDomain }{ { @@ -53,6 +95,7 @@ func TestVSphereFailureDomain_ValidateCreate(t *testing.T) { AutoConfigure: pointer.Bool(true), }, }}, + errExpected: true, }, { name: "hostGroup failureDomain set but compute Cluster is empty", @@ -66,6 +109,7 @@ func TestVSphereFailureDomain_ValidateCreate(t *testing.T) { }, }, }}, + errExpected: true, }, { name: "type of zone failure domain is Hostgroup but topology's hostgroup is not set", @@ -86,6 +130,7 @@ func TestVSphereFailureDomain_ValidateCreate(t *testing.T) { Hosts: nil, }, }}, + errExpected: true, }, { name: "type of region failure domain is Compute Cluster but topology is not set", @@ -109,6 +154,7 @@ func TestVSphereFailureDomain_ValidateCreate(t *testing.T) { }, }, }}, + errExpected: true, }, { name: "type of zone failure domain is Compute Cluster but topology is not set", @@ -128,6 +174,83 @@ func TestVSphereFailureDomain_ValidateCreate(t *testing.T) { ComputeCluster: nil, }, }}, + errExpected: true, + }, + { + name: "valid failure domain configuration should not cause an error", + failureDomain: VSphereFailureDomain{Spec: VSphereFailureDomainSpec{ + Region: FailureDomain{ + Name: "foo", + Type: DatacenterFailureDomain, + TagCategory: "k8s-bar", + }, + Zone: FailureDomain{ + Name: "foo", + Type: ComputeClusterFailureDomain, + TagCategory: "k8s-bar", + }, + Topology: Topology{ + Datacenter: "/blah", + ComputeCluster: pointer.String("Cluster1"), + Networks: []string{"network-a", "network-b"}, + }, + }}, + errExpected: false, + }, + { + name: "valid failure domain configuration with new NetworkConfig should not cause an error", + failureDomain: VSphereFailureDomain{Spec: VSphereFailureDomainSpec{ + Region: FailureDomain{ + Name: "foo", + Type: DatacenterFailureDomain, + TagCategory: "k8s-bar", + }, + Zone: FailureDomain{ + Name: "foo", + Type: ComputeClusterFailureDomain, + TagCategory: "k8s-bar", + }, + Topology: Topology{ + Datacenter: "/blah", + ComputeCluster: pointer.String("Cluster1"), + NetworkConfigurations: []NetworkConfiguration{ + { + NetworkName: "network-a", + DHCP4: pointer.Bool(true), + DHCP6: pointer.Bool(false), + }, + }, + }, + }}, + errExpected: false, + }, + { + name: "failure domain configuration with new NetworkConfig and networks should cause an error", + failureDomain: VSphereFailureDomain{Spec: VSphereFailureDomainSpec{ + Region: FailureDomain{ + Name: "foo", + Type: DatacenterFailureDomain, + TagCategory: "k8s-bar", + }, + Zone: FailureDomain{ + Name: "foo", + Type: ComputeClusterFailureDomain, + TagCategory: "k8s-bar", + }, + Topology: Topology{ + Datacenter: "/blah", + ComputeCluster: pointer.String("Cluster1"), + Networks: []string{"network-a", "network-b"}, + NetworkConfigurations: []NetworkConfiguration{ + { + NetworkName: "network-a", + DHCP4: pointer.Bool(true), + DHCP6: pointer.Bool(false), + }, + }, + }, + }}, + errExpected: true, }, } @@ -136,7 +259,7 @@ func TestVSphereFailureDomain_ValidateCreate(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { err := tt.failureDomain.ValidateCreate() - if tt.errExpected == nil || !*tt.errExpected { + if tt.errExpected { g.Expect(err).To(HaveOccurred()) } else { g.Expect(err).NotTo(HaveOccurred()) diff --git a/apis/v1beta1/zz_generated.deepcopy.go b/apis/v1beta1/zz_generated.deepcopy.go index 0b1928995f..a545521e98 100644 --- a/apis/v1beta1/zz_generated.deepcopy.go +++ b/apis/v1beta1/zz_generated.deepcopy.go @@ -195,6 +195,58 @@ func (in *Network) DeepCopy() *Network { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkConfiguration) DeepCopyInto(out *NetworkConfiguration) { + *out = *in + if in.DHCP4 != nil { + in, out := &in.DHCP4, &out.DHCP4 + *out = new(bool) + **out = **in + } + if in.DHCP6 != nil { + in, out := &in.DHCP6, &out.DHCP6 + *out = new(bool) + **out = **in + } + if in.Nameservers != nil { + in, out := &in.Nameservers, &out.Nameservers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SearchDomains != nil { + in, out := &in.SearchDomains, &out.SearchDomains + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.DHCP4Overrides != nil { + in, out := &in.DHCP4Overrides, &out.DHCP4Overrides + *out = new(DHCPOverrides) + (*in).DeepCopyInto(*out) + } + if in.DHCP6Overrides != nil { + in, out := &in.DHCP6Overrides, &out.DHCP6Overrides + *out = new(DHCPOverrides) + (*in).DeepCopyInto(*out) + } + if in.AddressesFromPools != nil { + in, out := &in.AddressesFromPools, &out.AddressesFromPools + *out = make([]v1.TypedLocalObjectReference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkConfiguration. +func (in *NetworkConfiguration) DeepCopy() *NetworkConfiguration { + if in == nil { + return nil + } + out := new(NetworkConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkDeviceSpec) DeepCopyInto(out *NetworkDeviceSpec) { *out = *in @@ -392,6 +444,13 @@ func (in *Topology) DeepCopyInto(out *Topology) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.NetworkConfigurations != nil { + in, out := &in.NetworkConfigurations, &out.NetworkConfigurations + *out = make([]NetworkConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Topology. diff --git a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vsphereclusters.yaml b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vsphereclusters.yaml index 8d2d9546cf..0f8160c319 100644 --- a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vsphereclusters.yaml +++ b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vsphereclusters.yaml @@ -321,7 +321,6 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object - x-kubernetes-map-type: atomic server: description: Server is the address of the vSphere endpoint. type: string diff --git a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspherefailuredomains.yaml b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspherefailuredomains.yaml index 4e894a5c2b..081b2dad59 100644 --- a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspherefailuredomains.yaml +++ b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspherefailuredomains.yaml @@ -334,6 +334,174 @@ spec: - hostGroupName - vmGroupName type: object + networkConfigs: + description: NetworkConfigurations is a list with new network + configurations within this failure domain + items: + description: NetworkConfiguration defines a network configuration + that should be used when consuming a failure domain. + properties: + addressesFromPools: + description: AddressesFromPools is a list of IPAddressPools + that should be assigned to IPAddressClaims. The machine's + cloud-init metadata will be populated with IPAddresses + fulfilled by an IPAM provider. + items: + description: TypedLocalObjectReference contains enough + information to let you locate the typed referenced object + inside the same namespace. + properties: + apiGroup: + description: APIGroup is the group for the resource + being referenced. If APIGroup is not specified, + the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + dhcp4: + description: DHCP4 is a flag that indicates whether or not + to use DHCP for IPv4 + type: boolean + dhcp4Overrides: + description: DHCP4Overrides allows for the control over + several DHCP behaviors. Overrides will only be applied + when the corresponding DHCP flag is set. Only configured + values will be sent, omitted values will default to distribution + defaults. Dependent on support in the network stack for + your distribution. For more information see the netplan + reference (https://netplan.io/reference#dhcp-overrides) + properties: + hostname: + description: Hostname is the name which will be sent + to the DHCP server instead of the machine's hostname. + type: string + routeMetric: + description: RouteMetric is used to prioritize routes + for devices. A lower metric for an interface will + have a higher priority. + type: integer + sendHostname: + description: SendHostname when `true`, the hostname + of the machine will be sent to the DHCP server. + type: boolean + useDNS: + description: UseDNS when `true`, the DNS servers in + the DHCP server will be used and take precedence. + type: boolean + useDomains: + description: UseDomains can take the values `true`, + `false`, or `route`. When `true`, the domain name + from the DHCP server will be used as the DNS search + domain for this device. When `route`, the domain name + from the DHCP response will be used for routing DNS + only, not for searching. + type: string + useHostname: + description: UseHostname when `true`, the hostname from + the DHCP server will be set as the transient hostname + of the machine. + type: boolean + useMTU: + description: UseMTU when `true`, the MTU from the DHCP + server will be set as the MTU of the device. + type: boolean + useNTP: + description: UseNTP when `true`, the NTP servers from + the DHCP server will be used by systemd-timesyncd + and take precedence. + type: boolean + useRoutes: + description: UseRoutes when `true`, the routes from + the DHCP server will be installed in the routing table. + type: string + type: object + dhcp6: + description: DHCP6 is a flag that indicates whether or not + to use DHCP for IPv6 + type: boolean + dhcp6Overrides: + description: DHCP6Overrides allows for the control over + several DHCP behaviors. Overrides will only be applied + when the corresponding DHCP flag is set. Only configured + values will be sent, omitted values will default to distribution + defaults. Dependent on support in the network stack for + your distribution. For more information see the netplan + reference (https://netplan.io/reference#dhcp-overrides) + properties: + hostname: + description: Hostname is the name which will be sent + to the DHCP server instead of the machine's hostname. + type: string + routeMetric: + description: RouteMetric is used to prioritize routes + for devices. A lower metric for an interface will + have a higher priority. + type: integer + sendHostname: + description: SendHostname when `true`, the hostname + of the machine will be sent to the DHCP server. + type: boolean + useDNS: + description: UseDNS when `true`, the DNS servers in + the DHCP server will be used and take precedence. + type: boolean + useDomains: + description: UseDomains can take the values `true`, + `false`, or `route`. When `true`, the domain name + from the DHCP server will be used as the DNS search + domain for this device. When `route`, the domain name + from the DHCP response will be used for routing DNS + only, not for searching. + type: string + useHostname: + description: UseHostname when `true`, the hostname from + the DHCP server will be set as the transient hostname + of the machine. + type: boolean + useMTU: + description: UseMTU when `true`, the MTU from the DHCP + server will be set as the MTU of the device. + type: boolean + useNTP: + description: UseNTP when `true`, the NTP servers from + the DHCP server will be used by systemd-timesyncd + and take precedence. + type: boolean + useRoutes: + description: UseRoutes when `true`, the routes from + the DHCP server will be installed in the routing table. + type: string + type: object + name: + description: NetworkName is the network name for this machine's + VM. + type: string + nameservers: + description: Nameservers is a list of IPv4 and/or IPv6 addresses + used as DNS nameservers. Please note that Linux allows + only three nameservers (https://linux.die.net/man/5/resolv.conf). + items: + type: string + type: array + searchDomains: + description: SearchDomains is a list of search domains used + when resolving IP addresses with DNS. + items: + type: string + type: array + type: object + type: array networks: description: Networks is the list of networks within this failure domain diff --git a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheremachines.yaml b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheremachines.yaml index 919a85312c..27e72fe0c8 100644 --- a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheremachines.yaml +++ b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheremachines.yaml @@ -943,10 +943,10 @@ spec: description: Name is the name of resource being referenced type: string required: - - apiGroup - kind - name type: object + x-kubernetes-map-type: atomic type: array deviceName: description: DeviceName may be used to explicitly assign diff --git a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheremachinetemplates.yaml b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheremachinetemplates.yaml index 2172061e52..f4b06af3bb 100644 --- a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheremachinetemplates.yaml +++ b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspheremachinetemplates.yaml @@ -832,10 +832,10 @@ spec: being referenced type: string required: - - apiGroup - kind - name type: object + x-kubernetes-map-type: atomic type: array deviceName: description: DeviceName may be used to explicitly diff --git a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspherevms.yaml b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspherevms.yaml index ed8ec68c54..2fb38a4d7f 100644 --- a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspherevms.yaml +++ b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspherevms.yaml @@ -80,7 +80,6 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object - x-kubernetes-map-type: atomic cloneMode: description: CloneMode specifies the type of clone operation. The LinkedClone mode is only support for templates that have at least @@ -909,6 +908,7 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + x-kubernetes-map-type: atomic cloneMode: description: CloneMode specifies the type of clone operation. The LinkedClone mode is only support for templates that have at least @@ -990,10 +990,10 @@ spec: description: Name is the name of resource being referenced type: string required: - - apiGroup - kind - name type: object + x-kubernetes-map-type: atomic type: array deviceName: description: DeviceName may be used to explicitly assign diff --git a/controllers/vspheredeploymentzone_controller_domain.go b/controllers/vspheredeploymentzone_controller_domain.go index 5437825b63..44a53044c2 100644 --- a/controllers/vspheredeploymentzone_controller_domain.go +++ b/controllers/vspheredeploymentzone_controller_domain.go @@ -84,6 +84,16 @@ func (r vsphereDeploymentZoneReconciler) reconcileTopology(ctx *context.VSphereD } } + for _, networkConfig := range topology.NetworkConfigurations { + if networkConfig.NetworkName == "" { + continue + } + if _, err := ctx.AuthSession.Finder.Network(ctx, networkConfig.NetworkName); err != nil { + conditions.MarkFalse(ctx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.NetworkNotFoundReason, clusterv1.ConditionSeverityError, "network %s is misconfigured", networkConfig.NetworkName) + return errors.Wrapf(err, "unable to find network %s", networkConfig.NetworkName) + } + } + if hostPlacementInfo := topology.Hosts; hostPlacementInfo != nil { rule, err := cluster.VerifyAffinityRule(ctx, *topology.ComputeCluster, hostPlacementInfo.HostGroupName, hostPlacementInfo.VMGroupName) switch { diff --git a/controllers/vspheredeploymentzone_controller_test.go b/controllers/vspheredeploymentzone_controller_test.go index baf7ed1918..22564052b2 100644 --- a/controllers/vspheredeploymentzone_controller_test.go +++ b/controllers/vspheredeploymentzone_controller_test.go @@ -408,6 +408,144 @@ func TestVSphereDeploymentZone_Reconcile(t *testing.T) { g.Expect(ownerRefs[0].Kind).To(Equal("VSphereDeploymentZone")) }) + t.Run("should fail to reconcile a deployment zone with wrong networkconfig", func(t *testing.T) { + g := NewWithT(t) + + vsphereFailureDomain := &infrav1.VSphereFailureDomain{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "blah-fd-", + }, + Spec: infrav1.VSphereFailureDomainSpec{ + Region: infrav1.FailureDomain{ + Name: "k8s-region-west", + Type: infrav1.DatacenterFailureDomain, + TagCategory: "k8s-region", + AutoConfigure: pointer.Bool(false), + }, + Zone: infrav1.FailureDomain{ + Name: "k8s-zone-west-1", + Type: infrav1.ComputeClusterFailureDomain, + TagCategory: "k8s-zone", + AutoConfigure: pointer.Bool(false), + }, + Topology: infrav1.Topology{ + Datacenter: "DC0", + ComputeCluster: pointer.String("DC0_C0"), + Datastore: "LocalDS_0", + NetworkConfigurations: []infrav1.NetworkConfiguration{ + { + NetworkName: "VM Networkxpto", + DHCP4: pointer.Bool(true), + }, + }, + }, + }, + } + g.Expect(testEnv.Create(ctx, vsphereFailureDomain)).To(Succeed()) + + vsphereDeploymentZone := &infrav1.VSphereDeploymentZone{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "blah-", + }, + Spec: infrav1.VSphereDeploymentZoneSpec{ + Server: simr.ServerURL().Host, + FailureDomain: vsphereFailureDomain.Name, + ControlPlane: pointer.Bool(true), + PlacementConstraint: infrav1.PlacementConstraint{ + ResourcePool: "DC0_C0_RP1", + Folder: "/", + }, + }} + g.Expect(testEnv.Create(ctx, vsphereDeploymentZone)).To(Succeed()) + + deploymentZoneKey := client.ObjectKey{Name: vsphereDeploymentZone.Name} + g.Eventually(func() bool { + if err := testEnv.Get(ctx, deploymentZoneKey, vsphereDeploymentZone); err != nil { + return false + } + return conditions.IsFalse(vsphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition) && + conditions.GetReason(vsphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition) == infrav1.NetworkNotFoundReason + }, timeout).Should(BeTrue()) + }) + + t.Run("should create a deployment zone & failure domain with networkconfig", func(t *testing.T) { + g := NewWithT(t) + + vsphereFailureDomain := &infrav1.VSphereFailureDomain{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "blah-fd-", + }, + Spec: infrav1.VSphereFailureDomainSpec{ + Region: infrav1.FailureDomain{ + Name: "k8s-region-west", + Type: infrav1.DatacenterFailureDomain, + TagCategory: "k8s-region", + AutoConfigure: pointer.Bool(false), + }, + Zone: infrav1.FailureDomain{ + Name: "k8s-zone-west-1", + Type: infrav1.ComputeClusterFailureDomain, + TagCategory: "k8s-zone", + AutoConfigure: pointer.Bool(false), + }, + Topology: infrav1.Topology{ + Datacenter: "DC0", + ComputeCluster: pointer.String("DC0_C0"), + Datastore: "LocalDS_0", + NetworkConfigurations: []infrav1.NetworkConfiguration{ + { + NetworkName: "VM Network", + DHCP4: pointer.Bool(true), + }, + }, + }, + }, + } + g.Expect(testEnv.Create(ctx, vsphereFailureDomain)).To(Succeed()) + + vsphereDeploymentZone := &infrav1.VSphereDeploymentZone{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "blah-", + }, + Spec: infrav1.VSphereDeploymentZoneSpec{ + Server: simr.ServerURL().Host, + FailureDomain: vsphereFailureDomain.Name, + ControlPlane: pointer.Bool(true), + PlacementConstraint: infrav1.PlacementConstraint{ + ResourcePool: "DC0_C0_RP1", + Folder: "/", + }, + }} + g.Expect(testEnv.Create(ctx, vsphereDeploymentZone)).To(Succeed()) + + defer func(do ...client.Object) { + g.Expect(testEnv.Cleanup(ctx, do...)).To(Succeed()) + }(vsphereDeploymentZone, vsphereFailureDomain) + + g.Eventually(func() bool { + if err := testEnv.Get(ctx, client.ObjectKeyFromObject(vsphereDeploymentZone), vsphereDeploymentZone); err != nil { + return false + } + return len(vsphereDeploymentZone.Finalizers) > 0 + }, timeout).Should(BeTrue()) + + g.Eventually(func() bool { + if err := testEnv.Get(ctx, client.ObjectKeyFromObject(vsphereDeploymentZone), vsphereDeploymentZone); err != nil { + return false + } + return conditions.IsTrue(vsphereDeploymentZone, infrav1.VCenterAvailableCondition) && + conditions.IsTrue(vsphereDeploymentZone, infrav1.PlacementConstraintMetCondition) && + conditions.IsTrue(vsphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition) + }, timeout).Should(BeTrue()) + + By("sets the owner ref on the vsphereFailureDomain object") + g.Expect(testEnv.Get(ctx, client.ObjectKeyFromObject(vsphereFailureDomain), vsphereFailureDomain)).To(Succeed()) + ownerRefs := vsphereFailureDomain.GetOwnerReferences() + g.Expect(ownerRefs).To(HaveLen(1)) + g.Expect(ownerRefs[0].Name).To(Equal(vsphereDeploymentZone.Name)) + g.Expect(ownerRefs[0].Kind).To(Equal("VSphereDeploymentZone")) + }) + By("deleting deployment zone") t.Run("it should delete associated failure domain", func(t *testing.T) { g := NewWithT(t) diff --git a/pkg/services/vimmachine.go b/pkg/services/vimmachine.go index 93679cee51..92b65284d2 100644 --- a/pkg/services/vimmachine.go +++ b/pkg/services/vimmachine.go @@ -496,8 +496,18 @@ func (v *VimMachineService) generateOverrideFunc(ctx *context.VIMMachineContext) if vsphereFailureDomain.Spec.Topology.Datastore != "" { vm.Spec.Datastore = vsphereFailureDomain.Spec.Topology.Datastore } - if len(vsphereFailureDomain.Spec.Topology.Networks) > 0 { - vm.Spec.Network.Devices = overrideNetworkDeviceSpecs(vm.Spec.Network.Devices, vsphereFailureDomain.Spec.Topology.Networks) + + networkConfigs := vsphereFailureDomain.Spec.Topology.NetworkConfigurations + // Convert the deprecated vsphereFailureDomain.Spec.Topology to networkConfigs if necessary. + if len(networkConfigs) == 0 && len(vsphereFailureDomain.Spec.Topology.Networks) > 0 { + networkConfigs = make([]infrav1.NetworkConfiguration, len(vsphereFailureDomain.Spec.Topology.Networks)) + for i := range vsphereFailureDomain.Spec.Topology.Networks { + networkConfigs[i].NetworkName = vsphereFailureDomain.Spec.Topology.Networks[i] + } + } + + if len(networkConfigs) > 0 { + vm.Spec.Network.Devices = overrideNetworkDeviceSpecs(vm.Spec.Network.Devices, networkConfigs) } } return overrideWithFailureDomainFunc, true @@ -507,7 +517,7 @@ func (v *VimMachineService) generateOverrideFunc(ctx *context.VIMMachineContext) // The substitution is done based on the order in which the network devices have been defined. // // In case there are more network definitions than the number of network devices specified, the definitions are appended to the list. -func overrideNetworkDeviceSpecs(deviceSpecs []infrav1.NetworkDeviceSpec, networks []string) []infrav1.NetworkDeviceSpec { +func overrideNetworkDeviceSpecs(deviceSpecs []infrav1.NetworkDeviceSpec, networks []infrav1.NetworkConfiguration) []infrav1.NetworkDeviceSpec { index, length := 0, len(networks) devices := make([]infrav1.NetworkDeviceSpec, 0, integer.IntMax(length, len(deviceSpecs))) @@ -516,16 +526,54 @@ func overrideNetworkDeviceSpecs(deviceSpecs []infrav1.NetworkDeviceSpec, network vmNetworkDeviceSpec := deviceSpecs[i] if i < length { index++ - vmNetworkDeviceSpec.NetworkName = networks[i] + mergeFailureDomainNetSpecToNetworkDeviceSpec(networks[i], &vmNetworkDeviceSpec) } devices = append(devices, vmNetworkDeviceSpec) } // append the remaining network definitions to the VM spec for ; index < length; index++ { - devices = append(devices, infrav1.NetworkDeviceSpec{ - NetworkName: networks[index], - }) + newNetwork := infrav1.NetworkDeviceSpec{} + mergeFailureDomainNetSpecToNetworkDeviceSpec(networks[index], &newNetwork) + devices = append(devices, newNetwork) } return devices } + +func mergeFailureDomainNetSpecToNetworkDeviceSpec(fd infrav1.NetworkConfiguration, vmNetworkDeviceSpec *infrav1.NetworkDeviceSpec) { + // We don't convert if destination is null + if vmNetworkDeviceSpec == nil { + return + } + if fd.NetworkName != "" { + vmNetworkDeviceSpec.NetworkName = fd.NetworkName + } + if fd.DHCP4 != nil { + vmNetworkDeviceSpec.DHCP4 = *fd.DHCP4 + } + if fd.DHCP6 != nil { + vmNetworkDeviceSpec.DHCP6 = *fd.DHCP6 + } + if len(fd.Nameservers) > 0 { + vmNetworkDeviceSpec.Nameservers = make([]string, len(fd.Nameservers)) + copy(vmNetworkDeviceSpec.Nameservers, fd.Nameservers) + } + + if len(fd.SearchDomains) > 0 { + vmNetworkDeviceSpec.SearchDomains = make([]string, len(fd.SearchDomains)) + copy(vmNetworkDeviceSpec.SearchDomains, fd.SearchDomains) + } + + if fd.DHCP4Overrides != nil { + vmNetworkDeviceSpec.DHCP4Overrides = fd.DHCP4Overrides.DeepCopy() + } + + if fd.DHCP6Overrides != nil { + vmNetworkDeviceSpec.DHCP6Overrides = fd.DHCP6Overrides.DeepCopy() + } + + if len(fd.AddressesFromPools) > 0 { + vmNetworkDeviceSpec.AddressesFromPools = make([]corev1.TypedLocalObjectReference, len(fd.AddressesFromPools)) + copy(vmNetworkDeviceSpec.AddressesFromPools, fd.AddressesFromPools) + } +} diff --git a/pkg/services/vimmachine_test.go b/pkg/services/vimmachine_test.go index 7f4abf3f22..d04fc8ad4c 100644 --- a/pkg/services/vimmachine_test.go +++ b/pkg/services/vimmachine_test.go @@ -59,6 +59,33 @@ var _ = Describe("VimMachineService_GenerateOverrideFunc", func() { }, } } + + failureDomainWithNetConfig := func(suffix string, addOldNetwork bool) *infrav1.VSphereFailureDomain { + var networks []string + if addOldNetwork { + networks = append(networks, fmt.Sprintf("nw-%s", suffix), "another-nw") + } + return &infrav1.VSphereFailureDomain{ + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("fd-%s", suffix)}, + Spec: infrav1.VSphereFailureDomainSpec{ + Topology: infrav1.Topology{ + Datacenter: fmt.Sprintf("dc-%s", suffix), + Datastore: fmt.Sprintf("ds-%s", suffix), + Networks: networks, + NetworkConfigurations: []infrav1.NetworkConfiguration{ + { + NetworkName: fmt.Sprintf("newnw-%s", suffix), + DHCP4: pointer.Bool(false), + }, + { + NetworkName: "another-new-nw", + Nameservers: []string{"10.10.10.10", "10.10.20.20"}, + }, + }, + }, + }, + } + } var ( controllerCtx *context.ControllerContext machineCtx *context.VIMMachineContext @@ -66,7 +93,8 @@ var _ = Describe("VimMachineService_GenerateOverrideFunc", func() { ) BeforeEach(func() { - controllerCtx = fake.NewControllerContext(fake.NewControllerManagerContext(deplZone("one"), deplZone("two"), failureDomain("one"), failureDomain("two"))) + controllerCtx = fake.NewControllerContext(fake.NewControllerManagerContext(deplZone("one"), deplZone("two"), deplZone("three"), deplZone("four"), + failureDomain("one"), failureDomain("two"), failureDomainWithNetConfig("three", false), failureDomainWithNetConfig("four", true))) machineCtx = fake.NewMachineContext(fake.NewClusterContext(controllerCtx)) vimMachineService = &VimMachineService{} }) @@ -183,7 +211,125 @@ var _ = Describe("VimMachineService_GenerateOverrideFunc", func() { Expect(devices[2].NetworkName).To(Equal("baz")) }) }) + + Context("with only network config specified in the topology", func() { + BeforeEach(func() { + machineCtx.Machine.Spec.FailureDomain = pointer.String("zone-three") + }) + It("overrides the n/w configs from the networks list of the topology", func() { + By("For equal number of networks") + vm := &infrav1.VSphereVM{ + Spec: infrav1.VSphereVMSpec{ + VirtualMachineCloneSpec: infrav1.VirtualMachineCloneSpec{ + Network: infrav1.NetworkSpec{Devices: []infrav1.NetworkDeviceSpec{{NetworkName: "foo", DHCP4: true}, {NetworkName: "bar", DHCP6: true, Nameservers: []string{"10.50.50.10"}}}}, + }, + }, + } + + overrideFunc, ok := vimMachineService.generateOverrideFunc(machineCtx) + Expect(ok).To(BeTrue()) + + overrideFunc(vm) + + devices := vm.Spec.Network.Devices + Expect(devices).To(HaveLen(2)) + Expect(devices[0].NetworkName).To(Equal("newnw-three")) + Expect(devices[0].DHCP4).To(BeFalse()) + Expect(devices[0].DHCP6).To(BeFalse()) + + Expect(devices[1].NetworkName).To(Equal("another-new-nw")) + Expect(devices[1].DHCP4).To(BeFalse()) + Expect(devices[1].DHCP6).To(BeTrue()) + Expect(devices[1].Nameservers).To(HaveLen(2)) + Expect(devices[1].Nameservers).To(Equal([]string{"10.10.10.10", "10.10.20.20"})) + + }) + + It("appends the n/w names present in the networks list of the topology", func() { + By("With number of devices in VMSpec < number of networks in the placement constraint") + vm := &infrav1.VSphereVM{ + Spec: infrav1.VSphereVMSpec{ + VirtualMachineCloneSpec: infrav1.VirtualMachineCloneSpec{ + Network: infrav1.NetworkSpec{Devices: []infrav1.NetworkDeviceSpec{{NetworkName: "foo", DHCP4: false}}}, + }, + }, + } + + overrideFunc, ok := vimMachineService.generateOverrideFunc(machineCtx) + Expect(ok).To(BeTrue()) + + overrideFunc(vm) + + devices := vm.Spec.Network.Devices + Expect(devices).To(HaveLen(2)) + Expect(devices[0].NetworkName).To(Equal("newnw-three")) + Expect(devices[0].DHCP4).To(BeFalse()) + Expect(devices[0].DHCP6).To(BeFalse()) + Expect(devices[1].NetworkName).To(Equal("another-new-nw")) + }) + + It("only overrides the n/w names present in the networks list of the topology", func() { + By("With number of devices in VMSpec > number of networks in the placement constraint") + vm := &infrav1.VSphereVM{ + Spec: infrav1.VSphereVMSpec{ + VirtualMachineCloneSpec: infrav1.VirtualMachineCloneSpec{ + Network: infrav1.NetworkSpec{Devices: []infrav1.NetworkDeviceSpec{{NetworkName: "foo", DHCP4: true}, {NetworkName: "bar", DHCP6: true}, {NetworkName: "baz", DHCP6: false}}}, + }, + }, + } + + overrideFunc, ok := vimMachineService.generateOverrideFunc(machineCtx) + Expect(ok).To(BeTrue()) + + overrideFunc(vm) + + devices := vm.Spec.Network.Devices + Expect(devices).To(HaveLen(3)) + Expect(devices[0].NetworkName).To(Equal("newnw-three")) + Expect(devices[0].DHCP4).To(BeFalse()) + + Expect(devices[1].NetworkName).To(Equal("another-new-nw")) + Expect(devices[1].DHCP6).To(BeTrue()) + + Expect(devices[2].NetworkName).To(Equal("baz")) + }) + }) + + Context("with network config and networks specified in the topology", func() { + BeforeEach(func() { + machineCtx.Machine.Spec.FailureDomain = pointer.String("zone-four") + }) + It("overrides the n/w configs using the networkconfig and discarding networks", func() { + By("For equal number of networks") + vm := &infrav1.VSphereVM{ + Spec: infrav1.VSphereVMSpec{ + VirtualMachineCloneSpec: infrav1.VirtualMachineCloneSpec{ + Network: infrav1.NetworkSpec{Devices: []infrav1.NetworkDeviceSpec{{NetworkName: "foo", DHCP4: true}, {NetworkName: "bar", DHCP6: true, Nameservers: []string{"10.50.50.10"}}}}, + }, + }, + } + + overrideFunc, ok := vimMachineService.generateOverrideFunc(machineCtx) + Expect(ok).To(BeTrue()) + + overrideFunc(vm) + + devices := vm.Spec.Network.Devices + Expect(devices).To(HaveLen(2)) + Expect(devices[0].NetworkName).To(Equal("newnw-four")) + Expect(devices[0].DHCP4).To(BeFalse()) + Expect(devices[0].DHCP6).To(BeFalse()) + + Expect(devices[1].NetworkName).To(Equal("another-new-nw")) + Expect(devices[1].DHCP4).To(BeFalse()) + Expect(devices[1].DHCP6).To(BeTrue()) + Expect(devices[1].Nameservers).To(HaveLen(2)) + Expect(devices[1].Nameservers).To(Equal([]string{"10.10.10.10", "10.10.20.20"})) + + }) + }) }) + }) var _ = Describe("VimMachineService_GetHostInfo", func() {