From cd7ab5b0ea47b9c6fc5734e13f8616b39dff3359 Mon Sep 17 00:00:00 2001 From: Ondrej Blazek Date: Mon, 4 Mar 2024 15:01:26 +0100 Subject: [PATCH] feat: add configurable loadbalancer network Previously when loadbalacer was created it used the same network/subnet as the control plane nodes for the VIP. This was not always the right assumption as some users might want to be able to customize this according to their env. This commit fixes the above by adding two fields into OpenStackClusterSpec/Status two fields `network` and `subnets` under `APIServerLoadBalancer` so that user can define which network/subnet to use for allocation of the loadbalancer. Fixes: #1809 Signed-off-by: Ondrej Blazek --- api/v1alpha5/zz_generated.conversion.go | 3 + api/v1alpha6/conversion.go | 51 ++++- api/v1alpha6/zz_generated.conversion.go | 48 ++--- api/v1alpha7/conversion.go | 27 +++ api/v1alpha7/zz_generated.conversion.go | 68 ++++--- api/v1beta1/types.go | 15 ++ api/v1beta1/zz_generated.deepcopy.go | 17 ++ ...re.cluster.x-k8s.io_openstackclusters.yaml | 187 ++++++++++++++++++ ...er.x-k8s.io_openstackclustertemplates.yaml | 146 ++++++++++++++ controllers/openstackcluster_controller.go | 60 ++++++ docs/book/src/api/v1beta1/api.md | 47 +++++ go.mod | 8 +- go.sum | 16 +- .../services/loadbalancer/loadbalancer.go | 36 +++- .../loadbalancer/loadbalancer_test.go | 47 ++++- 15 files changed, 689 insertions(+), 87 deletions(-) diff --git a/api/v1alpha5/zz_generated.conversion.go b/api/v1alpha5/zz_generated.conversion.go index 646ce0a938..f5e27362b7 100644 --- a/api/v1alpha5/zz_generated.conversion.go +++ b/api/v1alpha5/zz_generated.conversion.go @@ -421,6 +421,8 @@ func autoConvert_v1beta1_APIServerLoadBalancer_To_v1alpha5_APIServerLoadBalancer out.AdditionalPorts = *(*[]int)(unsafe.Pointer(&in.AdditionalPorts)) out.AllowedCIDRs = *(*[]string)(unsafe.Pointer(&in.AllowedCIDRs)) // WARNING: in.Provider requires manual conversion: does not exist in peer-type + // WARNING: in.Network requires manual conversion: does not exist in peer-type + // WARNING: in.Subnets requires manual conversion: does not exist in peer-type return nil } @@ -558,6 +560,7 @@ func autoConvert_v1beta1_LoadBalancer_To_v1alpha5_LoadBalancer(in *v1beta1.LoadB out.InternalIP = in.InternalIP out.AllowedCIDRs = *(*[]string)(unsafe.Pointer(&in.AllowedCIDRs)) // WARNING: in.Tags requires manual conversion: does not exist in peer-type + // WARNING: in.LoadBalancerNetwork requires manual conversion: does not exist in peer-type return nil } diff --git a/api/v1alpha6/conversion.go b/api/v1alpha6/conversion.go index dbdcde2687..ff55a771fb 100644 --- a/api/v1alpha6/conversion.go +++ b/api/v1alpha6/conversion.go @@ -197,6 +197,10 @@ func restorev1beta1ClusterStatus(previous *infrav1.OpenStackClusterStatus, dst * if previous.Bastion != nil && previous.Bastion.DependentResources.PortsStatus != nil { dst.Bastion.DependentResources.PortsStatus = previous.Bastion.DependentResources.PortsStatus } + + if previous.APIServerLoadBalancer != nil && previous.APIServerLoadBalancer.LoadBalancerNetwork != nil { + dst.APIServerLoadBalancer.LoadBalancerNetwork = previous.APIServerLoadBalancer.LoadBalancerNetwork + } } func restorev1alpha6ClusterSpec(previous *OpenStackClusterSpec, dst *OpenStackClusterSpec) { @@ -312,6 +316,11 @@ var v1beta1OpenStackClusterRestorer = conversion.RestorerFor[*infrav1.OpenStackC return &c.Spec.ManagedSubnets }, ), + "apiServerLoadBalancer": conversion.UnconditionalFieldRestorer( + func(c *infrav1.OpenStackCluster) *infrav1.APIServerLoadBalancer { + return &c.Spec.APIServerLoadBalancer + }, + ), } func (r *OpenStackCluster) ConvertTo(dstRaw ctrlconversion.Hub) error { @@ -407,6 +416,11 @@ var v1beta1OpenStackClusterTemplateRestorer = conversion.RestorerFor[*infrav1.Op return &c.Spec.Template.Spec.ManagedSubnets }, ), + "apiServerLoadBalancer": conversion.UnconditionalFieldRestorer( + func(c *infrav1.OpenStackClusterTemplate) *infrav1.APIServerLoadBalancer { + return &c.Spec.Template.Spec.APIServerLoadBalancer + }, + ), } func (r *OpenStackClusterTemplate) ConvertTo(dstRaw ctrlconversion.Hub) error { @@ -1014,7 +1028,16 @@ func Convert_v1beta1_OpenStackClusterStatus_To_v1alpha6_OpenStackClusterStatus(i } out.Network.Router = (*Router)(in.Router) - out.Network.APIServerLoadBalancer = (*LoadBalancer)(in.APIServerLoadBalancer) + if in.APIServerLoadBalancer != nil { + out.Network.APIServerLoadBalancer = &LoadBalancer{ + Name: in.APIServerLoadBalancer.Name, + ID: in.APIServerLoadBalancer.ID, + IP: in.APIServerLoadBalancer.IP, + InternalIP: in.APIServerLoadBalancer.InternalIP, + AllowedCIDRs: in.APIServerLoadBalancer.AllowedCIDRs, + Tags: in.APIServerLoadBalancer.Tags, + } + } } return nil @@ -1029,12 +1052,36 @@ func Convert_v1alpha6_OpenStackClusterStatus_To_v1beta1_OpenStackClusterStatus(i // Router and APIServerLoadBalancer have been moved out of Network in v1beta1 if in.Network != nil { out.Router = (*infrav1.Router)(in.Network.Router) - out.APIServerLoadBalancer = (*infrav1.LoadBalancer)(in.Network.APIServerLoadBalancer) + if in.Network.APIServerLoadBalancer != nil { + out.APIServerLoadBalancer = &infrav1.LoadBalancer{ + Name: in.Network.APIServerLoadBalancer.Name, + ID: in.Network.APIServerLoadBalancer.ID, + IP: in.Network.APIServerLoadBalancer.IP, + InternalIP: in.Network.APIServerLoadBalancer.InternalIP, + AllowedCIDRs: in.Network.APIServerLoadBalancer.AllowedCIDRs, + Tags: in.Network.APIServerLoadBalancer.Tags, + } + } } return nil } +func Convert_v1beta1_APIServerLoadBalancer_To_v1alpha6_APIServerLoadBalancer(in *infrav1.APIServerLoadBalancer, out *APIServerLoadBalancer, s apiconversion.Scope) error { + return autoConvert_v1beta1_APIServerLoadBalancer_To_v1alpha6_APIServerLoadBalancer(in, out, s) +} + +func Convert_v1beta1_LoadBalancer_To_v1alpha6_LoadBalancer(in *infrav1.LoadBalancer, out *LoadBalancer, s apiconversion.Scope) error { + return autoConvert_v1beta1_LoadBalancer_To_v1alpha6_LoadBalancer(in, out, s) +} + +func Convert_v1alpha6_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(in *APIServerLoadBalancer, out *infrav1.APIServerLoadBalancer, s apiconversion.Scope) error { + err := autoConvert_v1alpha6_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(in, out, s) + out.Subnets = nil + out.Network = nil + return err +} + func Convert_v1beta1_OpenStackMachineSpec_To_v1alpha6_OpenStackMachineSpec(in *infrav1.OpenStackMachineSpec, out *OpenStackMachineSpec, s apiconversion.Scope) error { err := autoConvert_v1beta1_OpenStackMachineSpec_To_v1alpha6_OpenStackMachineSpec(in, out, s) if err != nil { diff --git a/api/v1alpha6/zz_generated.conversion.go b/api/v1alpha6/zz_generated.conversion.go index dafa54171b..af64367719 100644 --- a/api/v1alpha6/zz_generated.conversion.go +++ b/api/v1alpha6/zz_generated.conversion.go @@ -40,16 +40,6 @@ func init() { // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*APIServerLoadBalancer)(nil), (*v1beta1.APIServerLoadBalancer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha6_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(a.(*APIServerLoadBalancer), b.(*v1beta1.APIServerLoadBalancer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*v1beta1.APIServerLoadBalancer)(nil), (*APIServerLoadBalancer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_APIServerLoadBalancer_To_v1alpha6_APIServerLoadBalancer(a.(*v1beta1.APIServerLoadBalancer), b.(*APIServerLoadBalancer), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*AddressPair)(nil), (*v1beta1.AddressPair)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha6_AddressPair_To_v1beta1_AddressPair(a.(*AddressPair), b.(*v1beta1.AddressPair), scope) }); err != nil { @@ -85,11 +75,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.LoadBalancer)(nil), (*LoadBalancer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_LoadBalancer_To_v1alpha6_LoadBalancer(a.(*v1beta1.LoadBalancer), b.(*LoadBalancer), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*OpenStackCluster)(nil), (*v1beta1.OpenStackCluster)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha6_OpenStackCluster_To_v1beta1_OpenStackCluster(a.(*OpenStackCluster), b.(*v1beta1.OpenStackCluster), scope) }); err != nil { @@ -255,6 +240,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*APIServerLoadBalancer)(nil), (*v1beta1.APIServerLoadBalancer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha6_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(a.(*APIServerLoadBalancer), b.(*v1beta1.APIServerLoadBalancer), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*Bastion)(nil), (*v1beta1.Bastion)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha6_Bastion_To_v1beta1_Bastion(a.(*Bastion), b.(*v1beta1.Bastion), scope) }); err != nil { @@ -330,6 +320,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.APIServerLoadBalancer)(nil), (*APIServerLoadBalancer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_APIServerLoadBalancer_To_v1alpha6_APIServerLoadBalancer(a.(*v1beta1.APIServerLoadBalancer), b.(*APIServerLoadBalancer), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta1.BastionStatus)(nil), (*Instance)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_BastionStatus_To_v1alpha6_Instance(a.(*v1beta1.BastionStatus), b.(*Instance), scope) }); err != nil { @@ -340,6 +335,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.LoadBalancer)(nil), (*LoadBalancer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_LoadBalancer_To_v1alpha6_LoadBalancer(a.(*v1beta1.LoadBalancer), b.(*LoadBalancer), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta1.NetworkFilter)(nil), (*NetworkFilter)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_NetworkFilter_To_v1alpha6_NetworkFilter(a.(*v1beta1.NetworkFilter), b.(*NetworkFilter), scope) }); err != nil { @@ -426,24 +426,16 @@ func autoConvert_v1alpha6_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer return nil } -// Convert_v1alpha6_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer is an autogenerated conversion function. -func Convert_v1alpha6_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(in *APIServerLoadBalancer, out *v1beta1.APIServerLoadBalancer, s conversion.Scope) error { - return autoConvert_v1alpha6_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(in, out, s) -} - func autoConvert_v1beta1_APIServerLoadBalancer_To_v1alpha6_APIServerLoadBalancer(in *v1beta1.APIServerLoadBalancer, out *APIServerLoadBalancer, s conversion.Scope) error { out.Enabled = in.Enabled out.AdditionalPorts = *(*[]int)(unsafe.Pointer(&in.AdditionalPorts)) out.AllowedCIDRs = *(*[]string)(unsafe.Pointer(&in.AllowedCIDRs)) out.Provider = in.Provider + // WARNING: in.Network requires manual conversion: does not exist in peer-type + // WARNING: in.Subnets requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_APIServerLoadBalancer_To_v1alpha6_APIServerLoadBalancer is an autogenerated conversion function. -func Convert_v1beta1_APIServerLoadBalancer_To_v1alpha6_APIServerLoadBalancer(in *v1beta1.APIServerLoadBalancer, out *APIServerLoadBalancer, s conversion.Scope) error { - return autoConvert_v1beta1_APIServerLoadBalancer_To_v1alpha6_APIServerLoadBalancer(in, out, s) -} - func autoConvert_v1alpha6_AddressPair_To_v1beta1_AddressPair(in *AddressPair, out *v1beta1.AddressPair, s conversion.Scope) error { out.IPAddress = in.IPAddress if err := optional.Convert_string_To_optional_String(&in.MACAddress, &out.MACAddress, s); err != nil { @@ -579,14 +571,10 @@ func autoConvert_v1beta1_LoadBalancer_To_v1alpha6_LoadBalancer(in *v1beta1.LoadB out.InternalIP = in.InternalIP out.AllowedCIDRs = *(*[]string)(unsafe.Pointer(&in.AllowedCIDRs)) out.Tags = *(*[]string)(unsafe.Pointer(&in.Tags)) + // WARNING: in.LoadBalancerNetwork requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_LoadBalancer_To_v1alpha6_LoadBalancer is an autogenerated conversion function. -func Convert_v1beta1_LoadBalancer_To_v1alpha6_LoadBalancer(in *v1beta1.LoadBalancer, out *LoadBalancer, s conversion.Scope) error { - return autoConvert_v1beta1_LoadBalancer_To_v1alpha6_LoadBalancer(in, out, s) -} - func autoConvert_v1alpha6_NetworkFilter_To_v1beta1_NetworkFilter(in *NetworkFilter, out *v1beta1.NetworkFilter, s conversion.Scope) error { out.Name = in.Name out.Description = in.Description diff --git a/api/v1alpha7/conversion.go b/api/v1alpha7/conversion.go index ed6c3f487f..3e7e9176b7 100644 --- a/api/v1alpha7/conversion.go +++ b/api/v1alpha7/conversion.go @@ -128,6 +128,10 @@ func restorev1beta1ClusterStatus(previous *infrav1.OpenStackClusterStatus, dst * if previous.Bastion != nil && previous.Bastion.DependentResources.PortsStatus != nil { dst.Bastion.DependentResources.PortsStatus = previous.Bastion.DependentResources.PortsStatus } + + if previous.APIServerLoadBalancer != nil && previous.APIServerLoadBalancer.LoadBalancerNetwork != nil { + dst.APIServerLoadBalancer.LoadBalancerNetwork = previous.APIServerLoadBalancer.LoadBalancerNetwork + } } var v1beta1OpenStackClusterRestorer = conversion.RestorerFor[*infrav1.OpenStackCluster]{ @@ -401,6 +405,14 @@ func restorev1beta1ClusterSpec(previous *infrav1.OpenStackClusterSpec, dst *infr if previous.ManagedSecurityGroups != nil { dst.ManagedSecurityGroups.AllNodesSecurityGroupRules = previous.ManagedSecurityGroups.AllNodesSecurityGroupRules } + + if previous.APIServerLoadBalancer.Network != nil { + dst.APIServerLoadBalancer.Network = previous.APIServerLoadBalancer.Network + } + + if previous.APIServerLoadBalancer.Subnets != nil { + dst.APIServerLoadBalancer.Subnets = previous.APIServerLoadBalancer.Subnets + } } func (r *OpenStackCluster) ConvertTo(dstRaw ctrlconversion.Hub) error { @@ -988,3 +1000,18 @@ func Convert_v1beta1_NetworkFilter_To_v1alpha7_NetworkFilter(in *infrav1.Network infrav1.ConvertAllTagsFrom(&in.FilterByNeutronTags, &out.Tags, &out.TagsAny, &out.NotTags, &out.NotTagsAny) return nil } + +func Convert_v1beta1_LoadBalancer_To_v1alpha7_LoadBalancer(in *infrav1.LoadBalancer, out *LoadBalancer, s apiconversion.Scope) error { + return autoConvert_v1beta1_LoadBalancer_To_v1alpha7_LoadBalancer(in, out, s) +} + +func Convert_v1beta1_APIServerLoadBalancer_To_v1alpha7_APIServerLoadBalancer(in *infrav1.APIServerLoadBalancer, out *APIServerLoadBalancer, s apiconversion.Scope) error { + return autoConvert_v1beta1_APIServerLoadBalancer_To_v1alpha7_APIServerLoadBalancer(in, out, s) +} + +func Convert_v1alpha7_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(in *APIServerLoadBalancer, out *infrav1.APIServerLoadBalancer, s apiconversion.Scope) error { + err := autoConvert_v1alpha7_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(in, out, s) + out.Subnets = nil + out.Network = nil + return err +} diff --git a/api/v1alpha7/zz_generated.conversion.go b/api/v1alpha7/zz_generated.conversion.go index 2cbd5b0184..7c01bc392e 100644 --- a/api/v1alpha7/zz_generated.conversion.go +++ b/api/v1alpha7/zz_generated.conversion.go @@ -41,16 +41,6 @@ func init() { // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*APIServerLoadBalancer)(nil), (*v1beta1.APIServerLoadBalancer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha7_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(a.(*APIServerLoadBalancer), b.(*v1beta1.APIServerLoadBalancer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*v1beta1.APIServerLoadBalancer)(nil), (*APIServerLoadBalancer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_APIServerLoadBalancer_To_v1alpha7_APIServerLoadBalancer(a.(*v1beta1.APIServerLoadBalancer), b.(*APIServerLoadBalancer), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*AdditionalBlockDevice)(nil), (*v1beta1.AdditionalBlockDevice)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha7_AdditionalBlockDevice_To_v1beta1_AdditionalBlockDevice(a.(*AdditionalBlockDevice), b.(*v1beta1.AdditionalBlockDevice), scope) }); err != nil { @@ -131,11 +121,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.LoadBalancer)(nil), (*LoadBalancer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_LoadBalancer_To_v1alpha7_LoadBalancer(a.(*v1beta1.LoadBalancer), b.(*LoadBalancer), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*NetworkStatus)(nil), (*v1beta1.NetworkStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha7_NetworkStatus_To_v1beta1_NetworkStatus(a.(*NetworkStatus), b.(*v1beta1.NetworkStatus), scope) }); err != nil { @@ -326,6 +311,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*APIServerLoadBalancer)(nil), (*v1beta1.APIServerLoadBalancer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha7_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(a.(*APIServerLoadBalancer), b.(*v1beta1.APIServerLoadBalancer), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*Bastion)(nil), (*v1beta1.Bastion)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha7_Bastion_To_v1beta1_Bastion(a.(*Bastion), b.(*v1beta1.Bastion), scope) }); err != nil { @@ -376,6 +366,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.APIServerLoadBalancer)(nil), (*APIServerLoadBalancer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_APIServerLoadBalancer_To_v1alpha7_APIServerLoadBalancer(a.(*v1beta1.APIServerLoadBalancer), b.(*APIServerLoadBalancer), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta1.BastionStatus)(nil), (*BastionStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_BastionStatus_To_v1alpha7_BastionStatus(a.(*v1beta1.BastionStatus), b.(*BastionStatus), scope) }); err != nil { @@ -386,6 +381,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.LoadBalancer)(nil), (*LoadBalancer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_LoadBalancer_To_v1alpha7_LoadBalancer(a.(*v1beta1.LoadBalancer), b.(*LoadBalancer), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta1.NetworkFilter)(nil), (*NetworkFilter)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_NetworkFilter_To_v1alpha7_NetworkFilter(a.(*v1beta1.NetworkFilter), b.(*NetworkFilter), scope) }); err != nil { @@ -452,24 +452,16 @@ func autoConvert_v1alpha7_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer return nil } -// Convert_v1alpha7_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer is an autogenerated conversion function. -func Convert_v1alpha7_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(in *APIServerLoadBalancer, out *v1beta1.APIServerLoadBalancer, s conversion.Scope) error { - return autoConvert_v1alpha7_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(in, out, s) -} - func autoConvert_v1beta1_APIServerLoadBalancer_To_v1alpha7_APIServerLoadBalancer(in *v1beta1.APIServerLoadBalancer, out *APIServerLoadBalancer, s conversion.Scope) error { out.Enabled = in.Enabled out.AdditionalPorts = *(*[]int)(unsafe.Pointer(&in.AdditionalPorts)) out.AllowedCIDRs = *(*[]string)(unsafe.Pointer(&in.AllowedCIDRs)) out.Provider = in.Provider + // WARNING: in.Network requires manual conversion: does not exist in peer-type + // WARNING: in.Subnets requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_APIServerLoadBalancer_To_v1alpha7_APIServerLoadBalancer is an autogenerated conversion function. -func Convert_v1beta1_APIServerLoadBalancer_To_v1alpha7_APIServerLoadBalancer(in *v1beta1.APIServerLoadBalancer, out *APIServerLoadBalancer, s conversion.Scope) error { - return autoConvert_v1beta1_APIServerLoadBalancer_To_v1alpha7_APIServerLoadBalancer(in, out, s) -} - func autoConvert_v1alpha7_AdditionalBlockDevice_To_v1beta1_AdditionalBlockDevice(in *AdditionalBlockDevice, out *v1beta1.AdditionalBlockDevice, s conversion.Scope) error { out.Name = in.Name out.SizeGiB = in.SizeGiB @@ -734,14 +726,10 @@ func autoConvert_v1beta1_LoadBalancer_To_v1alpha7_LoadBalancer(in *v1beta1.LoadB out.InternalIP = in.InternalIP out.AllowedCIDRs = *(*[]string)(unsafe.Pointer(&in.AllowedCIDRs)) out.Tags = *(*[]string)(unsafe.Pointer(&in.Tags)) + // WARNING: in.LoadBalancerNetwork requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_LoadBalancer_To_v1alpha7_LoadBalancer is an autogenerated conversion function. -func Convert_v1beta1_LoadBalancer_To_v1alpha7_LoadBalancer(in *v1beta1.LoadBalancer, out *LoadBalancer, s conversion.Scope) error { - return autoConvert_v1beta1_LoadBalancer_To_v1alpha7_LoadBalancer(in, out, s) -} - func autoConvert_v1alpha7_NetworkFilter_To_v1beta1_NetworkFilter(in *NetworkFilter, out *v1beta1.NetworkFilter, s conversion.Scope) error { out.Name = in.Name out.Description = in.Description @@ -1004,7 +992,15 @@ func autoConvert_v1alpha7_OpenStackClusterStatus_To_v1beta1_OpenStackClusterStat out.Network = (*v1beta1.NetworkStatusWithSubnets)(unsafe.Pointer(in.Network)) out.ExternalNetwork = (*v1beta1.NetworkStatus)(unsafe.Pointer(in.ExternalNetwork)) out.Router = (*v1beta1.Router)(unsafe.Pointer(in.Router)) - out.APIServerLoadBalancer = (*v1beta1.LoadBalancer)(unsafe.Pointer(in.APIServerLoadBalancer)) + if in.APIServerLoadBalancer != nil { + in, out := &in.APIServerLoadBalancer, &out.APIServerLoadBalancer + *out = new(v1beta1.LoadBalancer) + if err := Convert_v1alpha7_LoadBalancer_To_v1beta1_LoadBalancer(*in, *out, s); err != nil { + return err + } + } else { + out.APIServerLoadBalancer = nil + } out.FailureDomains = *(*apiv1beta1.FailureDomains)(unsafe.Pointer(&in.FailureDomains)) if in.ControlPlaneSecurityGroup != nil { in, out := &in.ControlPlaneSecurityGroup, &out.ControlPlaneSecurityGroup @@ -1057,7 +1053,15 @@ func autoConvert_v1beta1_OpenStackClusterStatus_To_v1alpha7_OpenStackClusterStat out.Network = (*NetworkStatusWithSubnets)(unsafe.Pointer(in.Network)) out.ExternalNetwork = (*NetworkStatus)(unsafe.Pointer(in.ExternalNetwork)) out.Router = (*Router)(unsafe.Pointer(in.Router)) - out.APIServerLoadBalancer = (*LoadBalancer)(unsafe.Pointer(in.APIServerLoadBalancer)) + if in.APIServerLoadBalancer != nil { + in, out := &in.APIServerLoadBalancer, &out.APIServerLoadBalancer + *out = new(LoadBalancer) + if err := Convert_v1beta1_LoadBalancer_To_v1alpha7_LoadBalancer(*in, *out, s); err != nil { + return err + } + } else { + out.APIServerLoadBalancer = nil + } out.FailureDomains = *(*apiv1beta1.FailureDomains)(unsafe.Pointer(&in.FailureDomains)) if in.ControlPlaneSecurityGroup != nil { in, out := &in.ControlPlaneSecurityGroup, &out.ControlPlaneSecurityGroup diff --git a/api/v1beta1/types.go b/api/v1beta1/types.go index 8be6ecc5e4..bdd1acf48a 100644 --- a/api/v1beta1/types.go +++ b/api/v1beta1/types.go @@ -413,6 +413,12 @@ type LoadBalancer struct { AllowedCIDRs []string `json:"allowedCIDRs,omitempty"` //+optional Tags []string `json:"tags,omitempty"` + // LoadBalancerNetwork contains information about network and/or subnets which the + // loadbalancer is allocated on. + // If subnets are specified within the LoadBalancerNetwork currently only the first + // subnet in the list is taken into account. + // +optional + LoadBalancerNetwork *NetworkStatusWithSubnets `json:"loadBalancerNetwork,omitempty"` } // SecurityGroupStatus represents the basic information of the associated @@ -599,6 +605,15 @@ type APIServerLoadBalancer struct { AllowedCIDRs []string `json:"allowedCidrs,omitempty"` // Octavia Provider Used to create load balancer Provider string `json:"provider,omitempty"` + // Network defines which network should the load balancer be allocated on. + //+optional + Network *NetworkFilter `json:"network,omitempty"` + // Subnets define which subnets should the load balancer be allocated on. + // It is expected that subnets are located on the network specified in this resource. + // +optional + // +listType=atomic + // kubebuilder:validation:MaxLength:=2 + Subnets []SubnetFilter `json:"subnets,omitempty"` } // ReferencedMachineResources contains resolved references to resources required by the machine. diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index cd5c538ffa..55a8ba97fc 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -40,6 +40,18 @@ func (in *APIServerLoadBalancer) DeepCopyInto(out *APIServerLoadBalancer) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Network != nil { + in, out := &in.Network, &out.Network + *out = new(NetworkFilter) + (*in).DeepCopyInto(*out) + } + if in.Subnets != nil { + in, out := &in.Subnets, &out.Subnets + *out = make([]SubnetFilter, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIServerLoadBalancer. @@ -325,6 +337,11 @@ func (in *LoadBalancer) DeepCopyInto(out *LoadBalancer) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.LoadBalancerNetwork != nil { + in, out := &in.LoadBalancerNetwork, &out.LoadBalancerNetwork + *out = new(NetworkStatusWithSubnets) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancer. diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml index 85b3b178e4..cb1c1b9145 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml @@ -4884,9 +4884,155 @@ spec: description: Enabled defines whether a load balancer should be created. type: boolean + network: + description: Network defines which network should the load balancer + be allocated on. + properties: + description: + type: string + id: + type: string + name: + type: string + notTags: + description: |- + NotTags is a list of tags to filter by. If specified, resources which + contain all of the given tags will be excluded from the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + notTagsAny: + description: |- + NotTagsAny is a list of tags to filter by. If specified, resources + which contain any of the given tags will be excluded from the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + projectId: + type: string + tags: + description: |- + Tags is a list of tags to filter by. If specified, the resource must + have all of the tags specified to be included in the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + tagsAny: + description: |- + TagsAny is a list of tags to filter by. If specified, the resource + must have at least one of the tags specified to be included in the + result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + type: object provider: description: Octavia Provider Used to create load balancer type: string + subnets: + description: |- + Subnets define which subnets should the load balancer be allocated on. + kubebuilder:validation:MaxLength:=2 + items: + properties: + cidr: + type: string + description: + type: string + gateway_ip: + type: string + id: + type: string + ipVersion: + type: integer + ipv6AddressMode: + type: string + ipv6RaMode: + type: string + name: + type: string + notTags: + description: |- + NotTags is a list of tags to filter by. If specified, resources which + contain all of the given tags will be excluded from the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + notTagsAny: + description: |- + NotTagsAny is a list of tags to filter by. If specified, resources + which contain any of the given tags will be excluded from the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + projectId: + type: string + tags: + description: |- + Tags is a list of tags to filter by. If specified, the resource must + have all of the tags specified to be included in the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + tagsAny: + description: |- + TagsAny is a list of tags to filter by. If specified, the resource + must have at least one of the tags specified to be included in the + result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + type: object + type: array + x-kubernetes-list-type: atomic type: object apiServerPort: description: |- @@ -6143,6 +6289,47 @@ spec: type: string ip: type: string + loadBalancerNetwork: + description: |- + LoadBalancerNetwork contains information about network and/or subnets which the + loadbalancer is allocated on. + properties: + id: + type: string + name: + type: string + subnets: + description: Subnets is a list of subnets associated with + the default cluster network. Machines which use the default + cluster network will get an address from all of these subnets. + items: + description: Subnet represents basic information about the + associated OpenStack Neutron Subnet. + properties: + cidr: + type: string + id: + type: string + name: + type: string + tags: + items: + type: string + type: array + required: + - cidr + - id + - name + type: object + type: array + tags: + items: + type: string + type: array + required: + - id + - name + type: object name: type: string tags: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclustertemplates.yaml index d4c396ef94..a763a11726 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclustertemplates.yaml @@ -2308,9 +2308,155 @@ spec: description: Enabled defines whether a load balancer should be created. type: boolean + network: + description: Network defines which network should the + load balancer be allocated on. + properties: + description: + type: string + id: + type: string + name: + type: string + notTags: + description: |- + NotTags is a list of tags to filter by. If specified, resources which + contain all of the given tags will be excluded from the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + notTagsAny: + description: |- + NotTagsAny is a list of tags to filter by. If specified, resources + which contain any of the given tags will be excluded from the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + projectId: + type: string + tags: + description: |- + Tags is a list of tags to filter by. If specified, the resource must + have all of the tags specified to be included in the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + tagsAny: + description: |- + TagsAny is a list of tags to filter by. If specified, the resource + must have at least one of the tags specified to be included in the + result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + type: object provider: description: Octavia Provider Used to create load balancer type: string + subnets: + description: |- + Subnets define which subnets should the load balancer be allocated on. + kubebuilder:validation:MaxLength:=2 + items: + properties: + cidr: + type: string + description: + type: string + gateway_ip: + type: string + id: + type: string + ipVersion: + type: integer + ipv6AddressMode: + type: string + ipv6RaMode: + type: string + name: + type: string + notTags: + description: |- + NotTags is a list of tags to filter by. If specified, resources which + contain all of the given tags will be excluded from the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + notTagsAny: + description: |- + NotTagsAny is a list of tags to filter by. If specified, resources + which contain any of the given tags will be excluded from the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + projectId: + type: string + tags: + description: |- + Tags is a list of tags to filter by. If specified, the resource must + have all of the tags specified to be included in the result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + tagsAny: + description: |- + TagsAny is a list of tags to filter by. If specified, the resource + must have at least one of the tags specified to be included in the + result. + items: + description: |- + NeutronTag represents a tag on a Neutron resource. + It may not be empty and may not contain commas. + minLength: 1 + pattern: ^[^,]+$ + type: string + type: array + x-kubernetes-list-type: set + type: object + type: array + x-kubernetes-list-type: atomic type: object apiServerPort: description: |- diff --git a/controllers/openstackcluster_controller.go b/controllers/openstackcluster_controller.go index 944155f100..a4bb7c0e63 100644 --- a/controllers/openstackcluster_controller.go +++ b/controllers/openstackcluster_controller.go @@ -585,6 +585,60 @@ func bastionHashHasChanged(computeHash string, clusterAnnotations map[string]str return latestHash != computeHash } +func reconcileLoadBalancerNetwork(openStackCluster *infrav1.OpenStackCluster, networkingService *networking.Service) error { + if openStackCluster.Spec.APIServerLoadBalancer.Network != nil { + lbNetOpts := openStackCluster.Spec.APIServerLoadBalancer.Network.ToListOpt() + lbNetList, err := networkingService.GetNetworksByFilter(&lbNetOpts) + if err != nil { + handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to find loadbalancer network: %w", err)) + return fmt.Errorf("failed to find network: %w", err) + } + if len(lbNetList) == 0 { + handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to find loadbalancer network")) + return fmt.Errorf("failed to find any network") + } + if len(lbNetList) > 1 { + handleUpdateOSCError(openStackCluster, fmt.Errorf("found multiple loadbalancer networks (result: %v)", lbNetList)) + return fmt.Errorf("found multiple loadbalancer networks (result: %v)", lbNetList) + } + + // Filter out only relevant subnets specified by the spec + if openStackCluster.Status.APIServerLoadBalancer == nil { + openStackCluster.Status.APIServerLoadBalancer = &infrav1.LoadBalancer{ + LoadBalancerNetwork: &infrav1.NetworkStatusWithSubnets{ + NetworkStatus: infrav1.NetworkStatus{ + Name: lbNetList[0].Name, + ID: lbNetList[0].ID, + Tags: lbNetList[0].Tags, + }, + }, + } + } + + openStackCluster.Status.APIServerLoadBalancer.LoadBalancerNetwork.Subnets = []infrav1.Subnet{} + for _, s := range openStackCluster.Spec.APIServerLoadBalancer.Subnets { + matchFound := false + for _, subnetID := range lbNetList[0].Subnets { + if subnetID == s.ID { + matchFound = true + openStackCluster.Status.APIServerLoadBalancer.LoadBalancerNetwork.Subnets = append( + openStackCluster.Status.APIServerLoadBalancer.LoadBalancerNetwork.Subnets, infrav1.Subnet{ + ID: s.ID, + Name: s.Name, + CIDR: s.CIDR, + }) + } + } + if !matchFound { + handleUpdateOSCError(openStackCluster, fmt.Errorf("no subnet match was found in the specified network (specified subnet: %v, available subnets: %v)", s, lbNetList[0].Subnets)) + return fmt.Errorf("no subnet match was found in the specified network (specified subnet: %v, available subnets: %v)", s, lbNetList[0].Subnets) + } + } + } + + return nil +} + func reconcileNetworkComponents(scope *scope.WithLogger, cluster *clusterv1.Cluster, openStackCluster *infrav1.OpenStackCluster) error { clusterName := fmt.Sprintf("%s-%s", cluster.Namespace, cluster.Name) @@ -613,6 +667,12 @@ func reconcileNetworkComponents(scope *scope.WithLogger, cluster *clusterv1.Clus return fmt.Errorf("failed to reconcile network: ManagedSubnets only supports one element, %d provided", len(openStackCluster.Spec.ManagedSubnets)) } + err = reconcileLoadBalancerNetwork(openStackCluster, networkingService) + if err != nil { + handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile loadbalancer network: %w", err)) + return fmt.Errorf("failed to reconcile loadbalancer network: %w", err) + } + err = networkingService.ReconcileSecurityGroups(openStackCluster, clusterName) if err != nil { handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile security groups: %w", err)) diff --git a/docs/book/src/api/v1beta1/api.md b/docs/book/src/api/v1beta1/api.md index 9dc7667830..09a646f8ec 100644 --- a/docs/book/src/api/v1beta1/api.md +++ b/docs/book/src/api/v1beta1/api.md @@ -862,6 +862,35 @@ string

Octavia Provider Used to create load balancer

+ + +network
+ + +NetworkFilter + + + + +(Optional) +

Network defines which network should the load balancer be allocated on.

+ + + + +subnets
+ + +[]SubnetFilter + + + + +(Optional) +

Subnets define which subnets should the load balancer be allocated on. +kubebuilder:validation:MaxLength:=2

+ +

AdditionalBlockDevice @@ -1687,6 +1716,21 @@ string (Optional) + + +loadBalancerNetwork
+ + +NetworkStatusWithSubnets + + + + +(Optional) +

LoadBalancerNetwork contains information about network and/or subnets which the +loadbalancer is allocated on.

+ +

ManagedSecurityGroupName @@ -1745,6 +1789,7 @@ bool

(Appears on: +APIServerLoadBalancer, OpenStackClusterSpec, PortOpts)

@@ -1870,6 +1915,7 @@ string

(Appears on: +LoadBalancer, OpenStackClusterStatus)

@@ -4558,6 +4604,7 @@ string

(Appears on: +APIServerLoadBalancer, ExternalRouterIPParam, FixedIP, OpenStackClusterSpec) diff --git a/go.mod b/go.mod index 50ebe0a6a8..4da2f804a2 100644 --- a/go.mod +++ b/go.mod @@ -8,14 +8,14 @@ require ( github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 github.com/google/gofuzz v1.2.0 - github.com/gophercloud/gophercloud v1.7.0 + github.com/gophercloud/gophercloud v1.11.0 github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-version v1.4.0 github.com/onsi/ginkgo/v2 v2.15.0 github.com/onsi/gomega v1.30.0 github.com/prometheus/client_golang v1.17.0 github.com/spf13/pflag v1.0.5 - golang.org/x/crypto v0.16.0 + golang.org/x/crypto v0.18.0 golang.org/x/text v0.14.0 gopkg.in/ini.v1 v1.67.0 k8s.io/api v0.28.4 @@ -139,8 +139,8 @@ require ( golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.14.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.16.1 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 3da2fa64fd..310f8aaa69 100644 --- a/go.sum +++ b/go.sum @@ -259,8 +259,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= -github.com/gophercloud/gophercloud v1.7.0 h1:fyJGKh0LBvIZKLvBWvQdIgkaV5yTM3Jh9EYUh+UNCAs= -github.com/gophercloud/gophercloud v1.7.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= +github.com/gophercloud/gophercloud v1.11.0 h1:ls0O747DIq1D8SUHc7r2vI8BFbMLeLFuENaAIfEx7OM= +github.com/gophercloud/gophercloud v1.11.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 h1:sH7xkTfYzxIEgzq1tDHIMKRh1vThOEOGNsettdEeLbE= github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56/go.mod h1:VSalo4adEk+3sNkmVJLnhHoOyOYYS8sTWLG4mv5BKto= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= @@ -491,8 +491,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -640,13 +640,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/pkg/cloud/services/loadbalancer/loadbalancer.go b/pkg/cloud/services/loadbalancer/loadbalancer.go index 38dda5ecb6..e9c4ff4b1c 100644 --- a/pkg/cloud/services/loadbalancer/loadbalancer.go +++ b/pkg/cloud/services/loadbalancer/loadbalancer.go @@ -264,9 +264,26 @@ func (s *Service) getOrCreateAPILoadBalancer(openStackCluster *infrav1.OpenStack return nil, fmt.Errorf("network is not yet available in OpenStackCluster.Status") } - // Create the VIP on the first cluster subnet - subnetID := openStackCluster.Status.Network.Subnets[0].ID - s.scope.Logger().Info("Creating load balancer in subnet", "subnetID", subnetID, "name", loadBalancerName) + var vipNetworkID, vipSubnetID string + if openStackCluster.Status.APIServerLoadBalancer != nil { + if openStackCluster.Status.APIServerLoadBalancer.LoadBalancerNetwork != nil { + if openStackCluster.Status.APIServerLoadBalancer.LoadBalancerNetwork.ID != "" { + vipNetworkID = openStackCluster.Status.APIServerLoadBalancer.LoadBalancerNetwork.ID + } + if len(openStackCluster.Status.APIServerLoadBalancer.LoadBalancerNetwork.Subnets) > 0 { + // Currently only the first subnet is taken into account. + // This can be fixed as soon as we switch over to gophercloud release that + // contains AdditionalVips field. + vipSubnetID = openStackCluster.Status.APIServerLoadBalancer.LoadBalancerNetwork.Subnets[0].ID + } + } + } + if vipNetworkID == "" && vipSubnetID == "" { + // keep the default and create the VIP on the first cluster subnet + vipSubnetID = openStackCluster.Status.Network.Subnets[0].ID + } + + s.scope.Logger().Info("Creating load balancer in subnet", "subnetID", vipSubnetID, "name", loadBalancerName) providers, err := s.loadbalancerClient.ListLoadBalancerProviders() if err != nil { @@ -294,12 +311,13 @@ func (s *Service) getOrCreateAPILoadBalancer(openStackCluster *infrav1.OpenStack } lbCreateOpts := loadbalancers.CreateOpts{ - Name: loadBalancerName, - VipSubnetID: subnetID, - VipAddress: vipAddress, - Description: names.GetDescription(clusterName), - Provider: lbProvider, - Tags: openStackCluster.Spec.Tags, + Name: loadBalancerName, + VipSubnetID: vipSubnetID, + VipNetworkID: vipNetworkID, + VipAddress: vipAddress, + Description: names.GetDescription(clusterName), + Provider: lbProvider, + Tags: openStackCluster.Spec.Tags, } lb, err = s.loadbalancerClient.CreateLoadBalancer(lbCreateOpts) if err != nil { diff --git a/pkg/cloud/services/loadbalancer/loadbalancer_test.go b/pkg/cloud/services/loadbalancer/loadbalancer_test.go index f264a83415..11ce9dbede 100644 --- a/pkg/cloud/services/loadbalancer/loadbalancer_test.go +++ b/pkg/cloud/services/loadbalancer/loadbalancer_test.go @@ -475,11 +475,54 @@ func Test_getOrCreateAPILoadBalancer(t *testing.T) { m.ListLoadBalancers(gomock.Any()).Return([]loadbalancers.LoadBalancer{}, nil) m.ListLoadBalancerProviders().Return(octaviaProviders, nil) m.CreateLoadBalancer(gomock.Any()).Return(&loadbalancers.LoadBalancer{ - ID: "AAAAA", + ID: "AAAAA", + VipSubnetID: "aaaaaaaa-bbbb-cccc-dddd-222222222222", }, nil) }, want: &loadbalancers.LoadBalancer{ - ID: "AAAAA", + ID: "AAAAA", + VipSubnetID: "aaaaaaaa-bbbb-cccc-dddd-222222222222", + }, + }, + { + name: "loadbalancer on a specific network created", + openStackCluster: &infrav1.OpenStackCluster{ + Status: infrav1.OpenStackClusterStatus{ + Network: &infrav1.NetworkStatusWithSubnets{ + Subnets: []infrav1.Subnet{ + {ID: "aaaaaaaa-bbbb-cccc-dddd-222222222222"}, + }, + }, + APIServerLoadBalancer: &infrav1.LoadBalancer{ + LoadBalancerNetwork: &infrav1.NetworkStatusWithSubnets{ + NetworkStatus: infrav1.NetworkStatus{ + Name: "VIPNET", + ID: "VIPNET", + }, + Subnets: []infrav1.Subnet{ + { + Name: "vip-subnet", + CIDR: "10.0.0.0/24", + ID: "VIPSUBNET", + }, + }, + }, + }, + }, + }, + expectLoadBalancer: func(m *mock.MockLbClientMockRecorder) { + m.ListLoadBalancers(gomock.Any()).Return([]loadbalancers.LoadBalancer{}, nil) + m.ListLoadBalancerProviders().Return(octaviaProviders, nil) + m.CreateLoadBalancer(gomock.Any()).Return(&loadbalancers.LoadBalancer{ + ID: "AAAAA", + VipSubnetID: "VIPSUBNET", + VipNetworkID: "VIPNET", + }, nil) + }, + want: &loadbalancers.LoadBalancer{ + ID: "AAAAA", + VipSubnetID: "VIPSUBNET", + VipNetworkID: "VIPNET", }, }, }