From 884c175d80eede6a2d521b79505e65eb27865bfd 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. Signed-off-by: Ondrej Blazek --- api/v1alpha5/zz_generated.conversion.go | 3 + api/v1alpha6/openstackcluster_conversion.go | 45 +++- api/v1alpha6/zz_generated.conversion.go | 48 ++-- api/v1alpha7/openstackcluster_conversion.go | 24 ++ api/v1alpha7/zz_generated.conversion.go | 68 +++--- api/v1beta1/types.go | 17 ++ api/v1beta1/zz_generated.deepcopy.go | 17 ++ ...re.cluster.x-k8s.io_openstackclusters.yaml | 216 ++++++++++++++++++ ...er.x-k8s.io_openstackclustertemplates.yaml | 174 ++++++++++++++ controllers/openstackcluster_controller.go | 57 +++++ docs/book/src/api/v1beta1/api.md | 51 +++++ go.mod | 6 +- go.sum | 12 +- .../services/loadbalancer/loadbalancer.go | 42 +++- .../loadbalancer/loadbalancer_test.go | 50 +++- 15 files changed, 747 insertions(+), 83 deletions(-) diff --git a/api/v1alpha5/zz_generated.conversion.go b/api/v1alpha5/zz_generated.conversion.go index cc9d3ff4cc..999c555fb4 100644 --- a/api/v1alpha5/zz_generated.conversion.go +++ b/api/v1alpha5/zz_generated.conversion.go @@ -456,6 +456,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 } @@ -597,6 +599,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/openstackcluster_conversion.go b/api/v1alpha6/openstackcluster_conversion.go index d9c7070f66..16375c2c07 100644 --- a/api/v1alpha6/openstackcluster_conversion.go +++ b/api/v1alpha6/openstackcluster_conversion.go @@ -210,6 +210,13 @@ func restorev1beta1ClusterSpec(previous *infrav1.OpenStackClusterSpec, dst *infr dst.APIServerLoadBalancer.Enabled = previous.APIServerLoadBalancer.Enabled } optional.RestoreString(&previous.APIServerLoadBalancer.Provider, &dst.APIServerLoadBalancer.Provider) + + if previous.APIServerLoadBalancer.Network != nil { + dst.APIServerLoadBalancer.Network = previous.APIServerLoadBalancer.Network + } + if previous.APIServerLoadBalancer.Subnets != nil { + dst.APIServerLoadBalancer.Subnets = previous.APIServerLoadBalancer.Subnets + } } if dst.APIServerLoadBalancer.IsZero() { dst.APIServerLoadBalancer = previous.APIServerLoadBalancer @@ -294,6 +301,18 @@ func Convert_v1alpha6_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(in *O 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 { + return autoConvert_v1alpha6_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(in, out, s) +} + func Convert_v1beta1_OpenStackClusterSpec_To_v1alpha6_OpenStackClusterSpec(in *infrav1.OpenStackClusterSpec, out *OpenStackClusterSpec, s apiconversion.Scope) error { err := autoConvert_v1beta1_OpenStackClusterSpec_To_v1alpha6_OpenStackClusterSpec(in, out, s) if err != nil { @@ -375,6 +394,10 @@ func restorev1beta1ClusterStatus(previous *infrav1.OpenStackClusterStatus, dst * dst.BastionSecurityGroup = previous.BastionSecurityGroup restorev1beta1BastionStatus(previous.Bastion, dst.Bastion) + + if previous.APIServerLoadBalancer != nil { + dst.APIServerLoadBalancer = previous.APIServerLoadBalancer + } } func Convert_v1beta1_OpenStackClusterStatus_To_v1alpha6_OpenStackClusterStatus(in *infrav1.OpenStackClusterStatus, out *OpenStackClusterStatus, s apiconversion.Scope) error { @@ -390,7 +413,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 @@ -405,7 +437,16 @@ 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 diff --git a/api/v1alpha6/zz_generated.conversion.go b/api/v1alpha6/zz_generated.conversion.go index 046dfa05fe..93dfea061a 100644 --- a/api/v1alpha6/zz_generated.conversion.go +++ b/api/v1alpha6/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_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 { @@ -86,11 +76,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 { @@ -246,6 +231,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 { @@ -341,6 +331,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 { @@ -351,6 +346,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 { @@ -461,11 +461,6 @@ 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 { if err := v1.Convert_Pointer_bool_To_bool(&in.Enabled, &out.Enabled, s); err != nil { return err @@ -475,14 +470,11 @@ func autoConvert_v1beta1_APIServerLoadBalancer_To_v1alpha6_APIServerLoadBalancer if err := optional.Convert_optional_String_To_string(&in.Provider, &out.Provider, s); err != nil { return err } + // 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 { @@ -622,14 +614,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/openstackcluster_conversion.go b/api/v1alpha7/openstackcluster_conversion.go index 09d641f5ab..f273d98760 100644 --- a/api/v1alpha7/openstackcluster_conversion.go +++ b/api/v1alpha7/openstackcluster_conversion.go @@ -216,6 +216,14 @@ func restorev1beta1ClusterSpec(previous *infrav1.OpenStackClusterSpec, dst *infr dst.APIServerLoadBalancer.Enabled = previous.APIServerLoadBalancer.Enabled } optional.RestoreString(&previous.APIServerLoadBalancer.Provider, &dst.APIServerLoadBalancer.Provider) + + if previous.APIServerLoadBalancer.Network != nil { + dst.APIServerLoadBalancer.Network = previous.APIServerLoadBalancer.Network + } + + if previous.APIServerLoadBalancer.Subnets != nil { + dst.APIServerLoadBalancer.Subnets = previous.APIServerLoadBalancer.Subnets + } } if dst.APIServerLoadBalancer.IsZero() { dst.APIServerLoadBalancer = previous.APIServerLoadBalancer @@ -300,6 +308,18 @@ func Convert_v1alpha7_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(in *O 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 { + return autoConvert_v1alpha7_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(in, out, s) +} + func Convert_v1beta1_OpenStackClusterSpec_To_v1alpha7_OpenStackClusterSpec(in *infrav1.OpenStackClusterSpec, out *OpenStackClusterSpec, s apiconversion.Scope) error { err := autoConvert_v1beta1_OpenStackClusterSpec_To_v1alpha7_OpenStackClusterSpec(in, out, s) if err != nil { @@ -362,6 +382,10 @@ func restorev1beta1ClusterStatus(previous *infrav1.OpenStackClusterStatus, dst * } restorev1beta1BastionStatus(previous.Bastion, dst.Bastion) + + if previous.APIServerLoadBalancer != nil { + dst.APIServerLoadBalancer = previous.APIServerLoadBalancer + } } func Convert_v1beta1_OpenStackClusterStatus_To_v1alpha7_OpenStackClusterStatus(in *infrav1.OpenStackClusterStatus, out *OpenStackClusterStatus, s apiconversion.Scope) error { diff --git a/api/v1alpha7/zz_generated.conversion.go b/api/v1alpha7/zz_generated.conversion.go index ca0e36a024..407c862a75 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 { @@ -316,6 +301,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 { @@ -391,6 +381,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 { @@ -401,6 +396,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 { @@ -496,11 +496,6 @@ 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 { if err := v1.Convert_Pointer_bool_To_bool(&in.Enabled, &out.Enabled, s); err != nil { return err @@ -510,14 +505,11 @@ func autoConvert_v1beta1_APIServerLoadBalancer_To_v1alpha7_APIServerLoadBalancer if err := optional.Convert_optional_String_To_string(&in.Provider, &out.Provider, s); err != nil { return err } + // 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 @@ -786,14 +778,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 @@ -1075,7 +1063,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 @@ -1128,7 +1124,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 9bf90d826d..b141d2731b 100644 --- a/api/v1beta1/types.go +++ b/api/v1beta1/types.go @@ -632,6 +632,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 @@ -802,6 +808,17 @@ type APIServerLoadBalancer struct { // specified. // +optional Provider optional.String `json:"provider,omitempty"` + + // Network defines which network should the load balancer be allocated on. + //+optional + Network *NetworkParam `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. + // Only the first element is taken into account. + // +optional + // +listType=atomic + // kubebuilder:validation:MaxLength:=2 + Subnets []SubnetParam `json:"subnets,omitempty"` } func (s *APIServerLoadBalancer) IsZero() bool { diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index c75e06db9e..c1a66427d4 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -50,6 +50,18 @@ func (in *APIServerLoadBalancer) DeepCopyInto(out *APIServerLoadBalancer) { *out = new(string) **out = **in } + if in.Network != nil { + in, out := &in.Network, &out.Network + *out = new(NetworkParam) + (*in).DeepCopyInto(*out) + } + if in.Subnets != nil { + in, out := &in.Subnets, &out.Subnets + *out = make([]SubnetParam, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIServerLoadBalancer. @@ -372,6 +384,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 6211a5157b..37e4f575e3 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml @@ -4893,12 +4893,185 @@ spec: API server loadbalancer, omit the APIServerLoadBalancer field in the cluster spec instead. type: boolean + network: + description: Network defines which network should the load balancer + be allocated on. + maxProperties: 1 + minProperties: 1 + properties: + filter: + description: Filter specifies a filter to select an OpenStack + network. If provided, cannot be empty. + minProperties: 1 + properties: + description: + 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 + id: + description: ID is the ID of the network to use. If ID is + provided, the other filters cannot be provided. Must be + in UUID format. + format: uuid + type: string + type: object provider: description: |- Provider specifies name of a specific Octavia provider to use for the API load balancer. The Octavia default will be used if it is not specified. type: string + subnets: + description: |- + 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. + Only the first element is taken into account. + kubebuilder:validation:MaxLength:=2 + items: + description: SubnetParam specifies an OpenStack subnet to use. + It may be specified by either ID or filter, but not both. + maxProperties: 1 + minProperties: 1 + properties: + filter: + description: Filter specifies a filter to select the subnet. + It must match exactly one subnet. + minProperties: 1 + properties: + cidr: + type: string + description: + type: string + gatewayIP: + 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 + id: + description: ID is the uuid of the subnet. It will not be + validated. + format: uuid + type: string + type: object + type: array + x-kubernetes-list-type: atomic required: - enabled type: object @@ -6355,6 +6528,49 @@ spec: type: string ip: type: string + loadBalancerNetwork: + description: |- + 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. + 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 ccc9da38b0..cebc2b2a04 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclustertemplates.yaml @@ -2317,12 +2317,186 @@ spec: API server loadbalancer, omit the APIServerLoadBalancer field in the cluster spec instead. type: boolean + network: + description: Network defines which network should the + load balancer be allocated on. + maxProperties: 1 + minProperties: 1 + properties: + filter: + description: Filter specifies a filter to select an + OpenStack network. If provided, cannot be empty. + minProperties: 1 + properties: + description: + 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 + id: + description: ID is the ID of the network to use. If + ID is provided, the other filters cannot be provided. + Must be in UUID format. + format: uuid + type: string + type: object provider: description: |- Provider specifies name of a specific Octavia provider to use for the API load balancer. The Octavia default will be used if it is not specified. type: string + subnets: + description: |- + 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. + Only the first element is taken into account. + kubebuilder:validation:MaxLength:=2 + items: + description: SubnetParam specifies an OpenStack subnet + to use. It may be specified by either ID or filter, + but not both. + maxProperties: 1 + minProperties: 1 + properties: + filter: + description: Filter specifies a filter to select + the subnet. It must match exactly one subnet. + minProperties: 1 + properties: + cidr: + type: string + description: + type: string + gatewayIP: + 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 + id: + description: ID is the uuid of the subnet. It will + not be validated. + format: uuid + type: string + type: object + type: array + x-kubernetes-list-type: atomic required: - enabled type: object diff --git a/controllers/openstackcluster_controller.go b/controllers/openstackcluster_controller.go index 3823b6ec3a..56831c714a 100644 --- a/controllers/openstackcluster_controller.go +++ b/controllers/openstackcluster_controller.go @@ -619,6 +619,57 @@ func bastionHashHasChanged(computeHash string, clusterAnnotations map[string]str return latestHash != computeHash } +func resolveLoadBalancerNetwork(openStackCluster *infrav1.OpenStackCluster, networkingService *networking.Service) error { + lbSpec := openStackCluster.Spec.APIServerLoadBalancer + if lbSpec.IsEnabled() { + lbStatus := openStackCluster.Status.APIServerLoadBalancer + if lbStatus == nil { + lbStatus = &infrav1.LoadBalancer{} + openStackCluster.Status.APIServerLoadBalancer = lbStatus + } + + lbNetStatus := lbStatus.LoadBalancerNetwork + if lbNetStatus == nil { + lbNetStatus = &infrav1.NetworkStatusWithSubnets{ + NetworkStatus: infrav1.NetworkStatus{}, + } + } + + if lbSpec.Network != nil { + lbNet, err := networkingService.GetNetworkByParam(lbSpec.Network) + if err != nil { + handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to find loadbalancer network: %w", err)) + return fmt.Errorf("failed to find network: %w", err) + } + + lbNetStatus.Name = lbNet.Name + lbNetStatus.ID = lbNet.ID + lbNetStatus.Tags = lbNet.Tags + + // Filter out only relevant subnets specified by the spec + lbNetStatus.Subnets = []infrav1.Subnet{} + for _, s := range lbSpec.Subnets { + matchFound := false + for _, subnetID := range lbNet.Subnets { + if s.ID != nil && subnetID == *s.ID { + matchFound = true + lbNetStatus.Subnets = append( + lbNetStatus.Subnets, infrav1.Subnet{ + ID: *s.ID, + }) + } + } + if !matchFound { + handleUpdateOSCError(openStackCluster, fmt.Errorf("no subnet match was found in the specified network (specified subnet: %v, available subnets: %v)", s, lbNet.Subnets)) + return fmt.Errorf("no subnet match was found in the specified network (specified subnet: %v, available subnets: %v)", s, lbNet.Subnets) + } + } + } + } + + return nil +} + func reconcileNetworkComponents(scope *scope.WithLogger, cluster *clusterv1.Cluster, openStackCluster *infrav1.OpenStackCluster) error { clusterResourceName := names.ClusterResourceName(cluster) @@ -647,6 +698,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 = resolveLoadBalancerNetwork(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, clusterResourceName) 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 51eccf2f6c..087efc5310 100644 --- a/docs/book/src/api/v1beta1/api.md +++ b/docs/book/src/api/v1beta1/api.md @@ -902,6 +902,37 @@ API load balancer. The Octavia default will be used if it is not specified.

+ + +network
+ + +NetworkParam + + + + +(Optional) +

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

+ + + + +subnets
+ + +[]SubnetParam + + + + +(Optional) +

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. +Only the first element is taken into account. +kubebuilder:validation:MaxLength:=2

+ +

AdditionalBlockDevice @@ -1750,6 +1781,23 @@ string (Optional) + + +loadBalancerNetwork
+ + +NetworkStatusWithSubnets + + + + +(Optional) +

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.

+ +

MachineResources @@ -1905,6 +1953,7 @@ FilterByNeutronTags

(Appears on: +APIServerLoadBalancer, OpenStackClusterSpec, PortOpts)

@@ -2002,6 +2051,7 @@ string

(Appears on: +LoadBalancer, OpenStackClusterStatus)

@@ -5044,6 +5094,7 @@ FilterByNeutronTags

(Appears on: +APIServerLoadBalancer, ExternalRouterIPParam, FixedIP, OpenStackClusterSpec) diff --git a/go.mod b/go.mod index c2bfc1a442..b5bd1a1173 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( 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..4f2d074054 100644 --- a/go.sum +++ b/go.sum @@ -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 2de72c81d6..cd309221b4 100644 --- a/pkg/cloud/services/loadbalancer/loadbalancer.go +++ b/pkg/cloud/services/loadbalancer/loadbalancer.go @@ -270,9 +270,34 @@ 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) + if openStackCluster.Status.APIServerLoadBalancer == nil { + return nil, fmt.Errorf("apiserver loadbalancer network is not yet available in OpenStackCluster.Status") + } + + lbNetwork := openStackCluster.Status.APIServerLoadBalancer.LoadBalancerNetwork + if lbNetwork == nil { + lbNetwork = &infrav1.NetworkStatusWithSubnets{} + openStackCluster.Status.APIServerLoadBalancer.LoadBalancerNetwork = lbNetwork + } + + var vipNetworkID, vipSubnetID string + if lbNetwork.ID != "" { + vipNetworkID = lbNetwork.ID + } + + if len(lbNetwork.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 = lbNetwork.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 { @@ -300,11 +325,12 @@ func (s *Service) getOrCreateAPILoadBalancer(openStackCluster *infrav1.OpenStack } lbCreateOpts := loadbalancers.CreateOpts{ - Name: loadBalancerName, - VipSubnetID: subnetID, - Description: names.GetDescription(clusterResourceName), - Provider: lbProvider, - Tags: openStackCluster.Spec.Tags, + Name: loadBalancerName, + VipSubnetID: vipSubnetID, + VipNetworkID: vipNetworkID, + Description: names.GetDescription(clusterResourceName), + Provider: lbProvider, + Tags: openStackCluster.Spec.Tags, } if vipAddress != nil { lbCreateOpts.VipAddress = *vipAddress diff --git a/pkg/cloud/services/loadbalancer/loadbalancer_test.go b/pkg/cloud/services/loadbalancer/loadbalancer_test.go index ce46235d82..3768884adb 100644 --- a/pkg/cloud/services/loadbalancer/loadbalancer_test.go +++ b/pkg/cloud/services/loadbalancer/loadbalancer_test.go @@ -475,17 +475,63 @@ func Test_getOrCreateAPILoadBalancer(t *testing.T) { {ID: "aaaaaaaa-bbbb-cccc-dddd-333333333333"}, }, }, + APIServerLoadBalancer: &infrav1.LoadBalancer{ + LoadBalancerNetwork: nil, + }, }, }, 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", + 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", }, }, }