From 9d77b1c48525be9a0becb26d0818b2156adfa2fc Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Tue, 17 Jun 2025 12:37:08 +0530 Subject: [PATCH] Add multiple nics support to nodes --- api/v1beta1/zz_generated.conversion.go | 1 + api/v1beta2/cloudstackmachine_conversion.go | 33 +++++++++++++- api/v1beta2/zz_generated.conversion.go | 40 ++++++++++++----- api/v1beta3/cloudstackmachine_types.go | 16 +++++++ api/v1beta3/zz_generated.deepcopy.go | 20 +++++++++ ...e.cluster.x-k8s.io_cloudstackmachines.yaml | 19 ++++++++ ...r.x-k8s.io_cloudstackmachinetemplates.yaml | 21 +++++++++ pkg/cloud/instance.go | 44 ++++++++++++++++++- 8 files changed, 179 insertions(+), 15 deletions(-) diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index 51eb2885..229c534b 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -650,6 +650,7 @@ func autoConvert_v1beta3_CloudStackMachineSpec_To_v1beta1_CloudStackMachineSpec( if err := Convert_v1beta3_CloudStackResourceDiskOffering_To_v1beta1_CloudStackResourceDiskOffering(&in.DiskOffering, &out.DiskOffering, s); err != nil { return err } + // WARNING: in.Networks requires manual conversion: does not exist in peer-type out.SSHKey = in.SSHKey out.Details = *(*map[string]string)(unsafe.Pointer(&in.Details)) out.AffinityGroupIDs = *(*[]string)(unsafe.Pointer(&in.AffinityGroupIDs)) diff --git a/api/v1beta2/cloudstackmachine_conversion.go b/api/v1beta2/cloudstackmachine_conversion.go index 43225d52..48ec39a9 100644 --- a/api/v1beta2/cloudstackmachine_conversion.go +++ b/api/v1beta2/cloudstackmachine_conversion.go @@ -17,16 +17,45 @@ limitations under the License. package v1beta2 import ( + machineryconversion "k8s.io/apimachinery/pkg/conversion" "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3" + utilconversion "sigs.k8s.io/cluster-api/util/conversion" "sigs.k8s.io/controller-runtime/pkg/conversion" ) func (src *CloudStackMachine) ConvertTo(dstRaw conversion.Hub) error { // nolint dst := dstRaw.(*v1beta3.CloudStackMachine) - return Convert_v1beta2_CloudStackMachine_To_v1beta3_CloudStackMachine(src, dst, nil) + if err := Convert_v1beta2_CloudStackMachine_To_v1beta3_CloudStackMachine(src, dst, nil); err != nil { + return err + } + + // Manually restore data + restored := &v1beta3.CloudStackMachine{} + if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { + return err + } + + if len(restored.Spec.Networks) > 0 { + dst.Spec.Networks = restored.Spec.Networks + } + + return nil } func (dst *CloudStackMachine) ConvertFrom(srcRaw conversion.Hub) error { // nolint src := srcRaw.(*v1beta3.CloudStackMachine) - return Convert_v1beta3_CloudStackMachine_To_v1beta2_CloudStackMachine(src, dst, nil) + if err := Convert_v1beta3_CloudStackMachine_To_v1beta2_CloudStackMachine(src, dst, nil); err != nil { + return err + } + + // Preserve Hub data on down-conversion, including Networks field + err := utilconversion.MarshalData(src, dst) + return err +} + +// Convert_v1beta3_CloudStackMachineSpec_To_v1beta2_CloudStackMachineSpec handles the conversion from v1beta3 to v1beta2, +// ignoring the Networks field that doesn't exist in v1beta2 +func Convert_v1beta3_CloudStackMachineSpec_To_v1beta2_CloudStackMachineSpec(in *v1beta3.CloudStackMachineSpec, out *CloudStackMachineSpec, s machineryconversion.Scope) error { // nolint + // Use the auto-generated conversion function, which will handle all fields except Networks + return autoConvert_v1beta3_CloudStackMachineSpec_To_v1beta2_CloudStackMachineSpec(in, out, s) } diff --git a/api/v1beta2/zz_generated.conversion.go b/api/v1beta2/zz_generated.conversion.go index 38e70b6d..137ef317 100644 --- a/api/v1beta2/zz_generated.conversion.go +++ b/api/v1beta2/zz_generated.conversion.go @@ -198,11 +198,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta3.CloudStackMachineSpec)(nil), (*CloudStackMachineSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta3_CloudStackMachineSpec_To_v1beta2_CloudStackMachineSpec(a.(*v1beta3.CloudStackMachineSpec), b.(*CloudStackMachineSpec), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*CloudStackMachineStateChecker)(nil), (*v1beta3.CloudStackMachineStateChecker)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_CloudStackMachineStateChecker_To_v1beta3_CloudStackMachineStateChecker(a.(*CloudStackMachineStateChecker), b.(*v1beta3.CloudStackMachineStateChecker), scope) }); err != nil { @@ -358,6 +353,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta3.CloudStackMachineSpec)(nil), (*CloudStackMachineSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta3_CloudStackMachineSpec_To_v1beta2_CloudStackMachineSpec(a.(*v1beta3.CloudStackMachineSpec), b.(*CloudStackMachineSpec), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta3.CloudStackMachineTemplateSpec)(nil), (*CloudStackMachineTemplateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta3_CloudStackMachineTemplateSpec_To_v1beta2_CloudStackMachineTemplateSpec(a.(*v1beta3.CloudStackMachineTemplateSpec), b.(*CloudStackMachineTemplateSpec), scope) }); err != nil { @@ -876,7 +876,17 @@ func Convert_v1beta3_CloudStackMachine_To_v1beta2_CloudStackMachine(in *v1beta3. func autoConvert_v1beta2_CloudStackMachineList_To_v1beta3_CloudStackMachineList(in *CloudStackMachineList, out *v1beta3.CloudStackMachineList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]v1beta3.CloudStackMachine)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1beta3.CloudStackMachine, len(*in)) + for i := range *in { + if err := Convert_v1beta2_CloudStackMachine_To_v1beta3_CloudStackMachine(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -887,7 +897,17 @@ func Convert_v1beta2_CloudStackMachineList_To_v1beta3_CloudStackMachineList(in * func autoConvert_v1beta3_CloudStackMachineList_To_v1beta2_CloudStackMachineList(in *v1beta3.CloudStackMachineList, out *CloudStackMachineList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]CloudStackMachine)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CloudStackMachine, len(*in)) + for i := range *in { + if err := Convert_v1beta3_CloudStackMachine_To_v1beta2_CloudStackMachine(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -938,6 +958,7 @@ func autoConvert_v1beta3_CloudStackMachineSpec_To_v1beta2_CloudStackMachineSpec( if err := Convert_v1beta3_CloudStackResourceDiskOffering_To_v1beta2_CloudStackResourceDiskOffering(&in.DiskOffering, &out.DiskOffering, s); err != nil { return err } + // WARNING: in.Networks requires manual conversion: does not exist in peer-type out.SSHKey = in.SSHKey out.Details = *(*map[string]string)(unsafe.Pointer(&in.Details)) out.AffinityGroupIDs = *(*[]string)(unsafe.Pointer(&in.AffinityGroupIDs)) @@ -949,11 +970,6 @@ func autoConvert_v1beta3_CloudStackMachineSpec_To_v1beta2_CloudStackMachineSpec( return nil } -// Convert_v1beta3_CloudStackMachineSpec_To_v1beta2_CloudStackMachineSpec is an autogenerated conversion function. -func Convert_v1beta3_CloudStackMachineSpec_To_v1beta2_CloudStackMachineSpec(in *v1beta3.CloudStackMachineSpec, out *CloudStackMachineSpec, s conversion.Scope) error { - return autoConvert_v1beta3_CloudStackMachineSpec_To_v1beta2_CloudStackMachineSpec(in, out, s) -} - func autoConvert_v1beta2_CloudStackMachineStateChecker_To_v1beta3_CloudStackMachineStateChecker(in *CloudStackMachineStateChecker, out *v1beta3.CloudStackMachineStateChecker, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1beta2_CloudStackMachineStateCheckerSpec_To_v1beta3_CloudStackMachineStateCheckerSpec(&in.Spec, &out.Spec, s); err != nil { diff --git a/api/v1beta3/cloudstackmachine_types.go b/api/v1beta3/cloudstackmachine_types.go index a725495a..34946f26 100644 --- a/api/v1beta3/cloudstackmachine_types.go +++ b/api/v1beta3/cloudstackmachine_types.go @@ -32,6 +32,17 @@ const ( NoAffinity = "no" ) +type NetworkSpec struct { + // CloudStack Network Name (required to resolve ID) + Name string `json:"name"` + + // Optional IP in the network + IP string `json:"ip,omitempty"` + + // Optional Network ID (overrides Name if set) + ID string `json:"id,omitempty"` +} + // CloudStackMachineSpec defines the desired state of CloudStackMachine type CloudStackMachineSpec struct { // Name. @@ -55,6 +66,11 @@ type CloudStackMachineSpec struct { // +optional DiskOffering CloudStackResourceDiskOffering `json:"diskOffering,omitempty"` + // The list of networks (overrides zone.network) + // +optional + // In CloudStackMachineSpec + Networks []NetworkSpec `json:"networks,omitempty"` + // CloudStack ssh key to use. // +optional SSHKey string `json:"sshKey"` diff --git a/api/v1beta3/zz_generated.deepcopy.go b/api/v1beta3/zz_generated.deepcopy.go index 7ad73718..54c04c69 100644 --- a/api/v1beta3/zz_generated.deepcopy.go +++ b/api/v1beta3/zz_generated.deepcopy.go @@ -480,6 +480,11 @@ func (in *CloudStackMachineSpec) DeepCopyInto(out *CloudStackMachineSpec) { out.Offering = in.Offering out.Template = in.Template out.DiskOffering = in.DiskOffering + if in.Networks != nil { + in, out := &in.Networks, &out.Networks + *out = make([]NetworkSpec, len(*in)) + copy(*out, *in) + } if in.Details != nil { in, out := &in.Details, &out.Details *out = make(map[string]string, len(*in)) @@ -797,6 +802,21 @@ 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 *NetworkSpec) DeepCopyInto(out *NetworkSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkSpec. +func (in *NetworkSpec) DeepCopy() *NetworkSpec { + if in == nil { + return nil + } + out := new(NetworkSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VPC) DeepCopyInto(out *VPC) { *out = *in diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackmachines.yaml index 8ef94154..3c957df4 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackmachines.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackmachines.yaml @@ -659,6 +659,25 @@ spec: name: description: Name. type: string + networks: + description: |- + The list of networks (overrides zone.network) + In CloudStackMachineSpec + items: + properties: + id: + description: Optional Network ID (overrides Name if set) + type: string + ip: + description: Optional IP in the network + type: string + name: + description: CloudStack Network Name (required to resolve ID) + type: string + required: + - name + type: object + type: array offering: description: CloudStack compute offering. properties: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackmachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackmachinetemplates.yaml index 290defac..5e23cd36 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackmachinetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackmachinetemplates.yaml @@ -594,6 +594,27 @@ spec: name: description: Name. type: string + networks: + description: |- + The list of networks (overrides zone.network) + In CloudStackMachineSpec + items: + properties: + id: + description: Optional Network ID (overrides Name if + set) + type: string + ip: + description: Optional IP in the network + type: string + name: + description: CloudStack Network Name (required to resolve + ID) + type: string + required: + - name + type: object + type: array offering: description: CloudStack compute offering. properties: diff --git a/pkg/cloud/instance.go b/pkg/cloud/instance.go index 8ebe7137..3da2705c 100644 --- a/pkg/cloud/instance.go +++ b/pkg/cloud/instance.go @@ -302,6 +302,38 @@ func (c *client) CheckLimits( return nil } +func (c *client) resolveNetworkIDByName(name string) (string, error) { + net, count, err := c.cs.Network.GetNetworkByName(name, cloudstack.WithProject(c.user.Project.ID)) + if err != nil { + return "", errors.Wrapf(err, "failed to look up network %q", name) + } + if count != 1 { + return "", errors.Errorf("expected 1 network named %q, but got %d", name, count) + } + return net.Id, nil +} + +func (c *client) buildIPToNetworkList(csMachine *infrav1.CloudStackMachine) ([]map[string]string, error) { + ipToNetworkList := []map[string]string{} + + for _, net := range csMachine.Spec.Networks { + networkID := net.ID + if networkID == "" { + var err error + networkID, err = c.resolveNetworkIDByName(net.Name) + if err != nil { + return nil, err + } + } + entry := map[string]string{"networkid": networkID} + if net.IP != "" { + entry["ip"] = net.IP + } + ipToNetworkList = append(ipToNetworkList, entry) + } + return ipToNetworkList, nil +} + // DeployVM will create a VM instance, // and sets the infrastructure machine spec and status accordingly. func (c *client) DeployVM( @@ -322,7 +354,17 @@ func (c *client) DeployVM( } p := c.cs.VirtualMachine.NewDeployVirtualMachineParams(offering.Id, templateID, fd.Spec.Zone.ID) - p.SetNetworkids([]string{fd.Spec.Zone.Network.ID}) + + if len(csMachine.Spec.Networks) == 0 && fd.Spec.Zone.Network.ID != "" { + p.SetNetworkids([]string{fd.Spec.Zone.Network.ID}) + } else { + ipToNetworkList, err := c.buildIPToNetworkList(csMachine) + if err != nil { + return err + } + p.SetIptonetworklist(ipToNetworkList) + } + setIfNotEmpty(csMachine.Name, p.SetName) setIfNotEmpty(capiMachine.Name, p.SetDisplayname) setIfNotEmpty(diskOfferingID, p.SetDiskofferingid)