diff --git a/api/v1alpha5/conversion.go b/api/v1alpha5/conversion.go index 9fb404b9e7..cd66e62bd3 100644 --- a/api/v1alpha5/conversion.go +++ b/api/v1alpha5/conversion.go @@ -205,7 +205,7 @@ func Convert_v1beta1_OpenStackClusterSpec_To_v1alpha5_OpenStackClusterSpec(in *i return err } - if in.ExternalNetwork.ID != "" { + if in.ExternalNetwork != nil && in.ExternalNetwork.ID != "" { out.ExternalNetworkID = in.ExternalNetwork.ID } @@ -227,6 +227,12 @@ func Convert_v1beta1_OpenStackClusterSpec_To_v1alpha5_OpenStackClusterSpec(in *i out.AllowAllInClusterTraffic = in.ManagedSecurityGroups.AllowAllInClusterTraffic } + if in.APIServerLoadBalancer != nil { + if err := Convert_v1beta1_APIServerLoadBalancer_To_v1alpha5_APIServerLoadBalancer(in.APIServerLoadBalancer, &out.APIServerLoadBalancer, s); err != nil { + return err + } + } + out.CloudName = in.IdentityRef.CloudName out.IdentityRef = &OpenStackIdentityReference{Name: in.IdentityRef.Name} @@ -240,7 +246,7 @@ func Convert_v1alpha5_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(in *O } if in.ExternalNetworkID != "" { - out.ExternalNetwork = infrav1.NetworkFilter{ + out.ExternalNetwork = &infrav1.NetworkFilter{ ID: in.ExternalNetworkID, } } @@ -273,11 +279,30 @@ func Convert_v1alpha5_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(in *O } } + if in.APIServerLoadBalancer.Enabled { + out.APIServerLoadBalancer = &infrav1.APIServerLoadBalancer{} + if err := Convert_v1alpha5_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(&in.APIServerLoadBalancer, out.APIServerLoadBalancer, s); err != nil { + return err + } + } + out.IdentityRef.CloudName = in.CloudName if in.IdentityRef != nil { out.IdentityRef.Name = in.IdentityRef.Name } + // The generated conversion function converts "" to &"" which is not what we want + if in.APIServerFloatingIP == "" { + out.APIServerFloatingIP = nil + } + if in.APIServerFixedIP == "" { + out.APIServerFixedIP = nil + } + + if in.APIServerPort != 0 { + out.APIServerPort = pointer.Int(in.APIServerPort) + } + return nil } diff --git a/api/v1alpha5/conversion_test.go b/api/v1alpha5/conversion_test.go index 1ecbf00a13..72b3c62851 100644 --- a/api/v1alpha5/conversion_test.go +++ b/api/v1alpha5/conversion_test.go @@ -40,7 +40,7 @@ func TestConvertFrom(t *testing.T) { want ctrlconversion.Convertible }{ { - name: "conversion must have conversion-data annotation", + name: "cluster conversion must have conversion-data annotation", spoke: &OpenStackCluster{}, hub: &infrav1.OpenStackCluster{ Spec: infrav1.OpenStackClusterSpec{}, @@ -51,13 +51,13 @@ func TestConvertFrom(t *testing.T) { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - "cluster.x-k8s.io/conversion-data": "{\"spec\":{\"apiServerLoadBalancer\":{},\"controlPlaneEndpoint\":{\"host\":\"\",\"port\":0},\"disableAPIServerFloatingIP\":false,\"disableExternalNetwork\":false,\"externalNetwork\":{},\"identityRef\":{\"cloudName\":\"\",\"name\":\"\"},\"network\":{}},\"status\":{\"ready\":false}}", + "cluster.x-k8s.io/conversion-data": "{\"spec\":{\"identityRef\":{\"cloudName\":\"\",\"name\":\"\"}},\"status\":{\"ready\":false}}", }, }, }, }, { - name: "conversion must have conversion-data annotation", + name: "cluster template conversion must have conversion-data annotation", spoke: &OpenStackClusterTemplate{}, hub: &infrav1.OpenStackClusterTemplate{ Spec: infrav1.OpenStackClusterTemplateSpec{}, @@ -72,13 +72,13 @@ func TestConvertFrom(t *testing.T) { }, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - "cluster.x-k8s.io/conversion-data": "{\"spec\":{\"template\":{\"spec\":{\"apiServerLoadBalancer\":{},\"controlPlaneEndpoint\":{\"host\":\"\",\"port\":0},\"disableAPIServerFloatingIP\":false,\"disableExternalNetwork\":false,\"externalNetwork\":{},\"identityRef\":{\"cloudName\":\"\",\"name\":\"\"},\"network\":{}}}}}", + "cluster.x-k8s.io/conversion-data": "{\"spec\":{\"template\":{\"spec\":{\"identityRef\":{\"cloudName\":\"\",\"name\":\"\"}}}}}", }, }, }, }, { - name: "conversion must have conversion-data annotation", + name: "machine conversion must have conversion-data annotation", spoke: &OpenStackMachine{}, hub: &infrav1.OpenStackMachine{ Spec: infrav1.OpenStackMachineSpec{}, @@ -93,7 +93,7 @@ func TestConvertFrom(t *testing.T) { }, }, { - name: "conversion must have conversion-data annotation", + name: "machine template conversion must have conversion-data annotation", spoke: &OpenStackMachineTemplate{}, hub: &infrav1.OpenStackMachineTemplate{ Spec: infrav1.OpenStackMachineTemplateSpec{}, diff --git a/api/v1alpha5/zz_generated.conversion.go b/api/v1alpha5/zz_generated.conversion.go index 646ce0a938..3b4f8e916c 100644 --- a/api/v1alpha5/zz_generated.conversion.go +++ b/api/v1alpha5/zz_generated.conversion.go @@ -659,9 +659,7 @@ func Convert_v1beta1_OpenStackClusterList_To_v1alpha5_OpenStackClusterList(in *v func autoConvert_v1alpha5_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(in *OpenStackClusterSpec, out *v1beta1.OpenStackClusterSpec, s conversion.Scope) error { // WARNING: in.CloudName requires manual conversion: does not exist in peer-type // WARNING: in.NodeCIDR requires manual conversion: does not exist in peer-type - if err := Convert_v1alpha5_NetworkFilter_To_v1beta1_NetworkFilter(&in.Network, &out.Network, s); err != nil { - return err - } + // WARNING: in.Network requires manual conversion: inconvertible types (sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha5.NetworkFilter vs *sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.NetworkFilter) // WARNING: in.Subnet requires manual conversion: does not exist in peer-type // WARNING: in.DNSNameservers requires manual conversion: does not exist in peer-type if in.ExternalRouterIPs != nil { @@ -676,18 +674,26 @@ func autoConvert_v1alpha5_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(i out.ExternalRouterIPs = nil } // WARNING: in.ExternalNetworkID requires manual conversion: does not exist in peer-type - if err := Convert_v1alpha5_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(&in.APIServerLoadBalancer, &out.APIServerLoadBalancer, s); err != nil { + // WARNING: in.APIServerLoadBalancer requires manual conversion: inconvertible types (sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha5.APIServerLoadBalancer vs *sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.APIServerLoadBalancer) + if err := optional.Convert_bool_To_optional_Bool(&in.DisableAPIServerFloatingIP, &out.DisableAPIServerFloatingIP, s); err != nil { + return err + } + if err := optional.Convert_string_To_optional_String(&in.APIServerFloatingIP, &out.APIServerFloatingIP, s); err != nil { + return err + } + if err := optional.Convert_string_To_optional_String(&in.APIServerFixedIP, &out.APIServerFixedIP, s); err != nil { + return err + } + if err := optional.Convert_int_To_optional_Int(&in.APIServerPort, &out.APIServerPort, s); err != nil { return err } - out.DisableAPIServerFloatingIP = in.DisableAPIServerFloatingIP - out.APIServerFloatingIP = in.APIServerFloatingIP - out.APIServerFixedIP = in.APIServerFixedIP - out.APIServerPort = in.APIServerPort // WARNING: in.ManagedSecurityGroups requires manual conversion: inconvertible types (bool vs *sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ManagedSecurityGroups) // WARNING: in.AllowAllInClusterTraffic requires manual conversion: does not exist in peer-type - out.DisablePortSecurity = in.DisablePortSecurity + if err := optional.Convert_bool_To_optional_Bool(&in.DisablePortSecurity, &out.DisablePortSecurity, s); err != nil { + return err + } out.Tags = *(*[]string)(unsafe.Pointer(&in.Tags)) - out.ControlPlaneEndpoint = in.ControlPlaneEndpoint + // WARNING: in.ControlPlaneEndpoint requires manual conversion: inconvertible types (sigs.k8s.io/cluster-api/api/v1beta1.APIEndpoint vs *sigs.k8s.io/cluster-api/api/v1beta1.APIEndpoint) out.ControlPlaneAvailabilityZones = *(*[]string)(unsafe.Pointer(&in.ControlPlaneAvailabilityZones)) if in.Bastion != nil { in, out := &in.Bastion, &out.Bastion @@ -705,9 +711,7 @@ func autoConvert_v1alpha5_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(i func autoConvert_v1beta1_OpenStackClusterSpec_To_v1alpha5_OpenStackClusterSpec(in *v1beta1.OpenStackClusterSpec, out *OpenStackClusterSpec, s conversion.Scope) error { // WARNING: in.ManagedSubnets requires manual conversion: does not exist in peer-type // WARNING: in.Router requires manual conversion: does not exist in peer-type - if err := Convert_v1beta1_NetworkFilter_To_v1alpha5_NetworkFilter(&in.Network, &out.Network, s); err != nil { - return err - } + // WARNING: in.Network requires manual conversion: inconvertible types (*sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.NetworkFilter vs sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha5.NetworkFilter) // WARNING: in.Subnets requires manual conversion: does not exist in peer-type // WARNING: in.NetworkMTU requires manual conversion: does not exist in peer-type if in.ExternalRouterIPs != nil { @@ -723,17 +727,25 @@ func autoConvert_v1beta1_OpenStackClusterSpec_To_v1alpha5_OpenStackClusterSpec(i } // WARNING: in.ExternalNetwork requires manual conversion: does not exist in peer-type // WARNING: in.DisableExternalNetwork requires manual conversion: does not exist in peer-type - if err := Convert_v1beta1_APIServerLoadBalancer_To_v1alpha5_APIServerLoadBalancer(&in.APIServerLoadBalancer, &out.APIServerLoadBalancer, s); err != nil { + // WARNING: in.APIServerLoadBalancer requires manual conversion: inconvertible types (*sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.APIServerLoadBalancer vs sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha5.APIServerLoadBalancer) + if err := optional.Convert_optional_Bool_To_bool(&in.DisableAPIServerFloatingIP, &out.DisableAPIServerFloatingIP, s); err != nil { + return err + } + if err := optional.Convert_optional_String_To_string(&in.APIServerFloatingIP, &out.APIServerFloatingIP, s); err != nil { + return err + } + if err := optional.Convert_optional_String_To_string(&in.APIServerFixedIP, &out.APIServerFixedIP, s); err != nil { + return err + } + if err := optional.Convert_optional_Int_To_int(&in.APIServerPort, &out.APIServerPort, s); err != nil { return err } - out.DisableAPIServerFloatingIP = in.DisableAPIServerFloatingIP - out.APIServerFloatingIP = in.APIServerFloatingIP - out.APIServerFixedIP = in.APIServerFixedIP - out.APIServerPort = in.APIServerPort // WARNING: in.ManagedSecurityGroups requires manual conversion: inconvertible types (*sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ManagedSecurityGroups vs bool) - out.DisablePortSecurity = in.DisablePortSecurity + if err := optional.Convert_optional_Bool_To_bool(&in.DisablePortSecurity, &out.DisablePortSecurity, s); err != nil { + return err + } out.Tags = *(*[]string)(unsafe.Pointer(&in.Tags)) - out.ControlPlaneEndpoint = in.ControlPlaneEndpoint + // WARNING: in.ControlPlaneEndpoint requires manual conversion: inconvertible types (*sigs.k8s.io/cluster-api/api/v1beta1.APIEndpoint vs sigs.k8s.io/cluster-api/api/v1beta1.APIEndpoint) out.ControlPlaneAvailabilityZones = *(*[]string)(unsafe.Pointer(&in.ControlPlaneAvailabilityZones)) // WARNING: in.ControlPlaneOmitAvailabilityZone requires manual conversion: does not exist in peer-type if in.Bastion != nil { diff --git a/api/v1alpha6/openstackcluster_conversion.go b/api/v1alpha6/openstackcluster_conversion.go index b8aac5aa5a..b1ecec275e 100644 --- a/api/v1alpha6/openstackcluster_conversion.go +++ b/api/v1alpha6/openstackcluster_conversion.go @@ -20,10 +20,12 @@ import ( "reflect" apiconversion "k8s.io/apimachinery/pkg/conversion" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ctrlconversion "sigs.k8s.io/controller-runtime/pkg/conversion" infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1" "sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/conversion" + optional "sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/optional" ) var _ ctrlconversion.Convertible = &OpenStackCluster{} @@ -162,14 +164,24 @@ func restorev1alpha6ClusterSpec(previous *OpenStackClusterSpec, dst *OpenStackCl func restorev1beta1ClusterSpec(previous *infrav1.OpenStackClusterSpec, dst *infrav1.OpenStackClusterSpec) { // Bastion is restored separately + if dst.Network.IsEmpty() { + dst.Network = previous.Network + } + // Restore all fields except ID, which should have been copied over in conversion - dst.ExternalNetwork.Name = previous.ExternalNetwork.Name - dst.ExternalNetwork.Description = previous.ExternalNetwork.Description - dst.ExternalNetwork.ProjectID = previous.ExternalNetwork.ProjectID - dst.ExternalNetwork.Tags = previous.ExternalNetwork.Tags - dst.ExternalNetwork.TagsAny = previous.ExternalNetwork.TagsAny - dst.ExternalNetwork.NotTags = previous.ExternalNetwork.NotTags - dst.ExternalNetwork.NotTagsAny = previous.ExternalNetwork.NotTagsAny + if previous.ExternalNetwork != nil { + if dst.ExternalNetwork == nil { + dst.ExternalNetwork = &infrav1.NetworkFilter{} + } + + dst.ExternalNetwork.Name = previous.ExternalNetwork.Name + dst.ExternalNetwork.Description = previous.ExternalNetwork.Description + dst.ExternalNetwork.ProjectID = previous.ExternalNetwork.ProjectID + dst.ExternalNetwork.Tags = previous.ExternalNetwork.Tags + dst.ExternalNetwork.TagsAny = previous.ExternalNetwork.TagsAny + dst.ExternalNetwork.NotTags = previous.ExternalNetwork.NotTags + dst.ExternalNetwork.NotTagsAny = previous.ExternalNetwork.NotTagsAny + } // Restore fields not present in v1alpha6 dst.Router = previous.Router @@ -185,6 +197,21 @@ func restorev1beta1ClusterSpec(previous *infrav1.OpenStackClusterSpec, dst *infr if previous.ManagedSecurityGroups != nil { dst.ManagedSecurityGroups.AllNodesSecurityGroupRules = previous.ManagedSecurityGroups.AllNodesSecurityGroupRules } + + if dst.APIServerLoadBalancer.IsZero() { + dst.APIServerLoadBalancer = previous.APIServerLoadBalancer + } + + if dst.ControlPlaneEndpoint == nil || *dst.ControlPlaneEndpoint == (clusterv1.APIEndpoint{}) { + dst.ControlPlaneEndpoint = previous.ControlPlaneEndpoint + } + + optional.RestoreString(&previous.APIServerFloatingIP, &dst.APIServerFloatingIP) + optional.RestoreString(&previous.APIServerFixedIP, &dst.APIServerFixedIP) + optional.RestoreInt(&previous.APIServerPort, &dst.APIServerPort) + optional.RestoreBool(&previous.DisableAPIServerFloatingIP, &dst.DisableAPIServerFloatingIP) + optional.RestoreBool(&previous.ControlPlaneOmitAvailabilityZone, &dst.ControlPlaneOmitAvailabilityZone) + optional.RestoreBool(&previous.DisablePortSecurity, &dst.DisablePortSecurity) } func Convert_v1alpha6_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(in *OpenStackClusterSpec, out *infrav1.OpenStackClusterSpec, s apiconversion.Scope) error { @@ -193,8 +220,15 @@ func Convert_v1alpha6_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(in *O return err } + if in.Network != (NetworkFilter{}) { + out.Network = &infrav1.NetworkFilter{} + if err := Convert_v1alpha6_NetworkFilter_To_v1beta1_NetworkFilter(&in.Network, out.Network, s); err != nil { + return err + } + } + if in.ExternalNetworkID != "" { - out.ExternalNetwork = infrav1.NetworkFilter{ + out.ExternalNetwork = &infrav1.NetworkFilter{ ID: in.ExternalNetworkID, } } @@ -227,11 +261,23 @@ func Convert_v1alpha6_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(in *O } } + if in.ControlPlaneEndpoint != (clusterv1.APIEndpoint{}) { + out.ControlPlaneEndpoint = &in.ControlPlaneEndpoint + } + out.IdentityRef.CloudName = in.CloudName if in.IdentityRef != nil { out.IdentityRef.Name = in.IdentityRef.Name } + apiServerLoadBalancer := &infrav1.APIServerLoadBalancer{} + if err := Convert_v1alpha6_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(&in.APIServerLoadBalancer, apiServerLoadBalancer, s); err != nil { + return err + } + if !apiServerLoadBalancer.IsZero() { + out.APIServerLoadBalancer = apiServerLoadBalancer + } + return nil } @@ -241,7 +287,13 @@ func Convert_v1beta1_OpenStackClusterSpec_To_v1alpha6_OpenStackClusterSpec(in *i return err } - if in.ExternalNetwork.ID != "" { + if in.Network != nil { + if err := Convert_v1beta1_NetworkFilter_To_v1alpha6_NetworkFilter(in.Network, &out.Network, s); err != nil { + return err + } + } + + if in.ExternalNetwork != nil && in.ExternalNetwork.ID != "" { out.ExternalNetworkID = in.ExternalNetwork.ID } @@ -261,9 +313,19 @@ func Convert_v1beta1_OpenStackClusterSpec_To_v1alpha6_OpenStackClusterSpec(in *i out.AllowAllInClusterTraffic = in.ManagedSecurityGroups.AllowAllInClusterTraffic } + if in.ControlPlaneEndpoint != nil { + out.ControlPlaneEndpoint = *in.ControlPlaneEndpoint + } + out.CloudName = in.IdentityRef.CloudName out.IdentityRef = &OpenStackIdentityReference{Name: in.IdentityRef.Name} + if in.APIServerLoadBalancer != nil { + if err := Convert_v1beta1_APIServerLoadBalancer_To_v1alpha6_APIServerLoadBalancer(in.APIServerLoadBalancer, &out.APIServerLoadBalancer, s); err != nil { + return err + } + } + return nil } diff --git a/api/v1alpha6/zz_generated.conversion.go b/api/v1alpha6/zz_generated.conversion.go index dafa54171b..b0ea770cdd 100644 --- a/api/v1alpha6/zz_generated.conversion.go +++ b/api/v1alpha6/zz_generated.conversion.go @@ -685,9 +685,7 @@ func Convert_v1beta1_OpenStackClusterList_To_v1alpha6_OpenStackClusterList(in *v func autoConvert_v1alpha6_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(in *OpenStackClusterSpec, out *v1beta1.OpenStackClusterSpec, s conversion.Scope) error { // WARNING: in.CloudName requires manual conversion: does not exist in peer-type // WARNING: in.NodeCIDR requires manual conversion: does not exist in peer-type - if err := Convert_v1alpha6_NetworkFilter_To_v1beta1_NetworkFilter(&in.Network, &out.Network, s); err != nil { - return err - } + // WARNING: in.Network requires manual conversion: inconvertible types (sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha6.NetworkFilter vs *sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.NetworkFilter) // WARNING: in.Subnet requires manual conversion: does not exist in peer-type // WARNING: in.DNSNameservers requires manual conversion: does not exist in peer-type if in.ExternalRouterIPs != nil { @@ -702,20 +700,30 @@ func autoConvert_v1alpha6_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(i out.ExternalRouterIPs = nil } // WARNING: in.ExternalNetworkID requires manual conversion: does not exist in peer-type - if err := Convert_v1alpha6_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(&in.APIServerLoadBalancer, &out.APIServerLoadBalancer, s); err != nil { + // WARNING: in.APIServerLoadBalancer requires manual conversion: inconvertible types (sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha6.APIServerLoadBalancer vs *sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.APIServerLoadBalancer) + if err := optional.Convert_bool_To_optional_Bool(&in.DisableAPIServerFloatingIP, &out.DisableAPIServerFloatingIP, s); err != nil { + return err + } + if err := optional.Convert_string_To_optional_String(&in.APIServerFloatingIP, &out.APIServerFloatingIP, s); err != nil { + return err + } + if err := optional.Convert_string_To_optional_String(&in.APIServerFixedIP, &out.APIServerFixedIP, s); err != nil { + return err + } + if err := optional.Convert_int_To_optional_Int(&in.APIServerPort, &out.APIServerPort, s); err != nil { return err } - out.DisableAPIServerFloatingIP = in.DisableAPIServerFloatingIP - out.APIServerFloatingIP = in.APIServerFloatingIP - out.APIServerFixedIP = in.APIServerFixedIP - out.APIServerPort = in.APIServerPort // WARNING: in.ManagedSecurityGroups requires manual conversion: inconvertible types (bool vs *sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ManagedSecurityGroups) // WARNING: in.AllowAllInClusterTraffic requires manual conversion: does not exist in peer-type - out.DisablePortSecurity = in.DisablePortSecurity + if err := optional.Convert_bool_To_optional_Bool(&in.DisablePortSecurity, &out.DisablePortSecurity, s); err != nil { + return err + } out.Tags = *(*[]string)(unsafe.Pointer(&in.Tags)) - out.ControlPlaneEndpoint = in.ControlPlaneEndpoint + // WARNING: in.ControlPlaneEndpoint requires manual conversion: inconvertible types (sigs.k8s.io/cluster-api/api/v1beta1.APIEndpoint vs *sigs.k8s.io/cluster-api/api/v1beta1.APIEndpoint) out.ControlPlaneAvailabilityZones = *(*[]string)(unsafe.Pointer(&in.ControlPlaneAvailabilityZones)) - out.ControlPlaneOmitAvailabilityZone = in.ControlPlaneOmitAvailabilityZone + if err := optional.Convert_bool_To_optional_Bool(&in.ControlPlaneOmitAvailabilityZone, &out.ControlPlaneOmitAvailabilityZone, s); err != nil { + return err + } if in.Bastion != nil { in, out := &in.Bastion, &out.Bastion *out = new(v1beta1.Bastion) @@ -732,9 +740,7 @@ func autoConvert_v1alpha6_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(i func autoConvert_v1beta1_OpenStackClusterSpec_To_v1alpha6_OpenStackClusterSpec(in *v1beta1.OpenStackClusterSpec, out *OpenStackClusterSpec, s conversion.Scope) error { // WARNING: in.ManagedSubnets requires manual conversion: does not exist in peer-type // WARNING: in.Router requires manual conversion: does not exist in peer-type - if err := Convert_v1beta1_NetworkFilter_To_v1alpha6_NetworkFilter(&in.Network, &out.Network, s); err != nil { - return err - } + // WARNING: in.Network requires manual conversion: inconvertible types (*sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.NetworkFilter vs sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha6.NetworkFilter) // WARNING: in.Subnets requires manual conversion: does not exist in peer-type // WARNING: in.NetworkMTU requires manual conversion: does not exist in peer-type if in.ExternalRouterIPs != nil { @@ -750,19 +756,29 @@ func autoConvert_v1beta1_OpenStackClusterSpec_To_v1alpha6_OpenStackClusterSpec(i } // WARNING: in.ExternalNetwork requires manual conversion: does not exist in peer-type // WARNING: in.DisableExternalNetwork requires manual conversion: does not exist in peer-type - if err := Convert_v1beta1_APIServerLoadBalancer_To_v1alpha6_APIServerLoadBalancer(&in.APIServerLoadBalancer, &out.APIServerLoadBalancer, s); err != nil { + // WARNING: in.APIServerLoadBalancer requires manual conversion: inconvertible types (*sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.APIServerLoadBalancer vs sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha6.APIServerLoadBalancer) + if err := optional.Convert_optional_Bool_To_bool(&in.DisableAPIServerFloatingIP, &out.DisableAPIServerFloatingIP, s); err != nil { + return err + } + if err := optional.Convert_optional_String_To_string(&in.APIServerFloatingIP, &out.APIServerFloatingIP, s); err != nil { + return err + } + if err := optional.Convert_optional_String_To_string(&in.APIServerFixedIP, &out.APIServerFixedIP, s); err != nil { + return err + } + if err := optional.Convert_optional_Int_To_int(&in.APIServerPort, &out.APIServerPort, s); err != nil { return err } - out.DisableAPIServerFloatingIP = in.DisableAPIServerFloatingIP - out.APIServerFloatingIP = in.APIServerFloatingIP - out.APIServerFixedIP = in.APIServerFixedIP - out.APIServerPort = in.APIServerPort // WARNING: in.ManagedSecurityGroups requires manual conversion: inconvertible types (*sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ManagedSecurityGroups vs bool) - out.DisablePortSecurity = in.DisablePortSecurity + if err := optional.Convert_optional_Bool_To_bool(&in.DisablePortSecurity, &out.DisablePortSecurity, s); err != nil { + return err + } out.Tags = *(*[]string)(unsafe.Pointer(&in.Tags)) - out.ControlPlaneEndpoint = in.ControlPlaneEndpoint + // WARNING: in.ControlPlaneEndpoint requires manual conversion: inconvertible types (*sigs.k8s.io/cluster-api/api/v1beta1.APIEndpoint vs sigs.k8s.io/cluster-api/api/v1beta1.APIEndpoint) out.ControlPlaneAvailabilityZones = *(*[]string)(unsafe.Pointer(&in.ControlPlaneAvailabilityZones)) - out.ControlPlaneOmitAvailabilityZone = in.ControlPlaneOmitAvailabilityZone + if err := optional.Convert_optional_Bool_To_bool(&in.ControlPlaneOmitAvailabilityZone, &out.ControlPlaneOmitAvailabilityZone, s); err != nil { + return err + } if in.Bastion != nil { in, out := &in.Bastion, &out.Bastion *out = new(Bastion) diff --git a/api/v1alpha7/openstackcluster_conversion.go b/api/v1alpha7/openstackcluster_conversion.go index 5761f11f8c..c8b5e29507 100644 --- a/api/v1alpha7/openstackcluster_conversion.go +++ b/api/v1alpha7/openstackcluster_conversion.go @@ -18,10 +18,12 @@ package v1alpha7 import ( apiconversion "k8s.io/apimachinery/pkg/conversion" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ctrlconversion "sigs.k8s.io/controller-runtime/pkg/conversion" infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1" "sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/conversion" + "sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/optional" ) var _ ctrlconversion.Convertible = &OpenStackCluster{} @@ -167,14 +169,24 @@ func restorev1alpha7ClusterSpec(previous *OpenStackClusterSpec, dst *OpenStackCl func restorev1beta1ClusterSpec(previous *infrav1.OpenStackClusterSpec, dst *infrav1.OpenStackClusterSpec) { // Bastion is restored separately + if dst.Network.IsEmpty() { + dst.Network = previous.Network + } + // Restore all fields except ID, which should have been copied over in conversion - dst.ExternalNetwork.Name = previous.ExternalNetwork.Name - dst.ExternalNetwork.Description = previous.ExternalNetwork.Description - dst.ExternalNetwork.ProjectID = previous.ExternalNetwork.ProjectID - dst.ExternalNetwork.Tags = previous.ExternalNetwork.Tags - dst.ExternalNetwork.TagsAny = previous.ExternalNetwork.TagsAny - dst.ExternalNetwork.NotTags = previous.ExternalNetwork.NotTags - dst.ExternalNetwork.NotTagsAny = previous.ExternalNetwork.NotTagsAny + if previous.ExternalNetwork != nil { + if dst.ExternalNetwork == nil { + dst.ExternalNetwork = &infrav1.NetworkFilter{} + } + + dst.ExternalNetwork.Name = previous.ExternalNetwork.Name + dst.ExternalNetwork.Description = previous.ExternalNetwork.Description + dst.ExternalNetwork.ProjectID = previous.ExternalNetwork.ProjectID + dst.ExternalNetwork.Tags = previous.ExternalNetwork.Tags + dst.ExternalNetwork.TagsAny = previous.ExternalNetwork.TagsAny + dst.ExternalNetwork.NotTags = previous.ExternalNetwork.NotTags + dst.ExternalNetwork.NotTagsAny = previous.ExternalNetwork.NotTagsAny + } dst.DisableExternalNetwork = previous.DisableExternalNetwork @@ -187,6 +199,21 @@ func restorev1beta1ClusterSpec(previous *infrav1.OpenStackClusterSpec, dst *infr if previous.ManagedSecurityGroups != nil { dst.ManagedSecurityGroups.AllNodesSecurityGroupRules = previous.ManagedSecurityGroups.AllNodesSecurityGroupRules } + + if dst.APIServerLoadBalancer.IsZero() { + dst.APIServerLoadBalancer = previous.APIServerLoadBalancer + } + + if dst.ControlPlaneEndpoint == nil || *dst.ControlPlaneEndpoint == (clusterv1.APIEndpoint{}) { + dst.ControlPlaneEndpoint = previous.ControlPlaneEndpoint + } + + optional.RestoreString(&previous.APIServerFloatingIP, &dst.APIServerFloatingIP) + optional.RestoreString(&previous.APIServerFixedIP, &dst.APIServerFixedIP) + optional.RestoreInt(&previous.APIServerPort, &dst.APIServerPort) + optional.RestoreBool(&previous.DisableAPIServerFloatingIP, &dst.DisableAPIServerFloatingIP) + optional.RestoreBool(&previous.ControlPlaneOmitAvailabilityZone, &dst.ControlPlaneOmitAvailabilityZone) + optional.RestoreBool(&previous.DisablePortSecurity, &dst.DisablePortSecurity) } func Convert_v1alpha7_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(in *OpenStackClusterSpec, out *infrav1.OpenStackClusterSpec, s apiconversion.Scope) error { @@ -195,8 +222,15 @@ func Convert_v1alpha7_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(in *O return err } + if in.Network != (NetworkFilter{}) { + out.Network = &infrav1.NetworkFilter{} + if err := Convert_v1alpha7_NetworkFilter_To_v1beta1_NetworkFilter(&in.Network, out.Network, s); err != nil { + return err + } + } + if in.ExternalNetworkID != "" { - out.ExternalNetwork = infrav1.NetworkFilter{ + out.ExternalNetwork = &infrav1.NetworkFilter{ ID: in.ExternalNetworkID, } } @@ -229,11 +263,23 @@ func Convert_v1alpha7_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(in *O } } + if in.ControlPlaneEndpoint != (clusterv1.APIEndpoint{}) { + out.ControlPlaneEndpoint = &in.ControlPlaneEndpoint + } + out.IdentityRef.CloudName = in.CloudName if in.IdentityRef != nil { out.IdentityRef.Name = in.IdentityRef.Name } + apiServerLoadBalancer := &infrav1.APIServerLoadBalancer{} + if err := Convert_v1alpha7_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(&in.APIServerLoadBalancer, apiServerLoadBalancer, s); err != nil { + return err + } + if !apiServerLoadBalancer.IsZero() { + out.APIServerLoadBalancer = apiServerLoadBalancer + } + return nil } @@ -243,7 +289,13 @@ func Convert_v1beta1_OpenStackClusterSpec_To_v1alpha7_OpenStackClusterSpec(in *i return err } - if in.ExternalNetwork.ID != "" { + if in.Network != nil { + if err := Convert_v1beta1_NetworkFilter_To_v1alpha7_NetworkFilter(in.Network, &out.Network, s); err != nil { + return err + } + } + + if in.ExternalNetwork != nil && in.ExternalNetwork.ID != "" { out.ExternalNetworkID = in.ExternalNetwork.ID } @@ -263,9 +315,19 @@ func Convert_v1beta1_OpenStackClusterSpec_To_v1alpha7_OpenStackClusterSpec(in *i out.AllowAllInClusterTraffic = in.ManagedSecurityGroups.AllowAllInClusterTraffic } + if in.ControlPlaneEndpoint != nil { + out.ControlPlaneEndpoint = *in.ControlPlaneEndpoint + } + out.CloudName = in.IdentityRef.CloudName out.IdentityRef = &OpenStackIdentityReference{Name: in.IdentityRef.Name} + if in.APIServerLoadBalancer != nil { + if err := Convert_v1beta1_APIServerLoadBalancer_To_v1alpha7_APIServerLoadBalancer(in.APIServerLoadBalancer, &out.APIServerLoadBalancer, s); err != nil { + return err + } + } + return nil } diff --git a/api/v1alpha7/types_conversion.go b/api/v1alpha7/types_conversion.go index f18f77269d..7b966d1320 100644 --- a/api/v1alpha7/types_conversion.go +++ b/api/v1alpha7/types_conversion.go @@ -21,6 +21,7 @@ import ( "k8s.io/utils/pointer" infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1" + "sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/optional" ) /* SecurityGroupFilter */ @@ -200,17 +201,9 @@ func restorev1alpha7Port(previous *PortOpts, dst *PortOpts) { } func restorev1beta1Port(previous *infrav1.PortOpts, dst *infrav1.PortOpts) { - if dst.NameSuffix == nil || *dst.NameSuffix == "" { - dst.NameSuffix = previous.NameSuffix - } - - if dst.Description == nil || *dst.Description == "" { - dst.Description = previous.Description - } - - if dst.MACAddress == nil || *dst.MACAddress == "" { - dst.MACAddress = previous.MACAddress - } + optional.RestoreString(&previous.NameSuffix, &dst.NameSuffix) + optional.RestoreString(&previous.Description, &dst.Description) + optional.RestoreString(&previous.MACAddress, &dst.MACAddress) if len(dst.FixedIPs) == len(previous.FixedIPs) { for j := range dst.FixedIPs { @@ -234,13 +227,8 @@ func restorev1beta1Port(previous *infrav1.PortOpts, dst *infrav1.PortOpts) { } } - if dst.HostID == nil || *dst.HostID == "" { - dst.HostID = previous.HostID - } - - if dst.VNICType == nil || *dst.VNICType == "" { - dst.VNICType = previous.VNICType - } + optional.RestoreString(&previous.HostID, &dst.HostID) + optional.RestoreString(&previous.VNICType, &dst.VNICType) if dst.Profile == nil && previous.Profile != nil { dst.Profile = &infrav1.BindingProfile{} diff --git a/api/v1alpha7/zz_generated.conversion.go b/api/v1alpha7/zz_generated.conversion.go index 2cbd5b0184..3ce6bde5ca 100644 --- a/api/v1alpha7/zz_generated.conversion.go +++ b/api/v1alpha7/zz_generated.conversion.go @@ -899,11 +899,11 @@ func autoConvert_v1alpha7_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(i } else { out.Router = nil } - if err := Convert_v1alpha7_NetworkFilter_To_v1beta1_NetworkFilter(&in.Network, &out.Network, s); err != nil { + // WARNING: in.Network requires manual conversion: inconvertible types (sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7.NetworkFilter vs *sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.NetworkFilter) + // WARNING: in.Subnet requires manual conversion: does not exist in peer-type + if err := optional.Convert_int_To_optional_Int(&in.NetworkMTU, &out.NetworkMTU, s); err != nil { return err } - // WARNING: in.Subnet requires manual conversion: does not exist in peer-type - out.NetworkMTU = in.NetworkMTU // WARNING: in.DNSNameservers requires manual conversion: does not exist in peer-type if in.ExternalRouterIPs != nil { in, out := &in.ExternalRouterIPs, &out.ExternalRouterIPs @@ -917,20 +917,30 @@ func autoConvert_v1alpha7_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(i out.ExternalRouterIPs = nil } // WARNING: in.ExternalNetworkID requires manual conversion: does not exist in peer-type - if err := Convert_v1alpha7_APIServerLoadBalancer_To_v1beta1_APIServerLoadBalancer(&in.APIServerLoadBalancer, &out.APIServerLoadBalancer, s); err != nil { + // WARNING: in.APIServerLoadBalancer requires manual conversion: inconvertible types (sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7.APIServerLoadBalancer vs *sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.APIServerLoadBalancer) + if err := optional.Convert_bool_To_optional_Bool(&in.DisableAPIServerFloatingIP, &out.DisableAPIServerFloatingIP, s); err != nil { + return err + } + if err := optional.Convert_string_To_optional_String(&in.APIServerFloatingIP, &out.APIServerFloatingIP, s); err != nil { + return err + } + if err := optional.Convert_string_To_optional_String(&in.APIServerFixedIP, &out.APIServerFixedIP, s); err != nil { + return err + } + if err := optional.Convert_int_To_optional_Int(&in.APIServerPort, &out.APIServerPort, s); err != nil { return err } - out.DisableAPIServerFloatingIP = in.DisableAPIServerFloatingIP - out.APIServerFloatingIP = in.APIServerFloatingIP - out.APIServerFixedIP = in.APIServerFixedIP - out.APIServerPort = in.APIServerPort // WARNING: in.ManagedSecurityGroups requires manual conversion: inconvertible types (bool vs *sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ManagedSecurityGroups) // WARNING: in.AllowAllInClusterTraffic requires manual conversion: does not exist in peer-type - out.DisablePortSecurity = in.DisablePortSecurity + if err := optional.Convert_bool_To_optional_Bool(&in.DisablePortSecurity, &out.DisablePortSecurity, s); err != nil { + return err + } out.Tags = *(*[]string)(unsafe.Pointer(&in.Tags)) - out.ControlPlaneEndpoint = in.ControlPlaneEndpoint + // WARNING: in.ControlPlaneEndpoint requires manual conversion: inconvertible types (sigs.k8s.io/cluster-api/api/v1beta1.APIEndpoint vs *sigs.k8s.io/cluster-api/api/v1beta1.APIEndpoint) out.ControlPlaneAvailabilityZones = *(*[]string)(unsafe.Pointer(&in.ControlPlaneAvailabilityZones)) - out.ControlPlaneOmitAvailabilityZone = in.ControlPlaneOmitAvailabilityZone + if err := optional.Convert_bool_To_optional_Bool(&in.ControlPlaneOmitAvailabilityZone, &out.ControlPlaneOmitAvailabilityZone, s); err != nil { + return err + } if in.Bastion != nil { in, out := &in.Bastion, &out.Bastion *out = new(v1beta1.Bastion) @@ -955,11 +965,11 @@ func autoConvert_v1beta1_OpenStackClusterSpec_To_v1alpha7_OpenStackClusterSpec(i } else { out.Router = nil } - if err := Convert_v1beta1_NetworkFilter_To_v1alpha7_NetworkFilter(&in.Network, &out.Network, s); err != nil { + // WARNING: in.Network requires manual conversion: inconvertible types (*sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.NetworkFilter vs sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7.NetworkFilter) + // WARNING: in.Subnets requires manual conversion: does not exist in peer-type + if err := optional.Convert_optional_Int_To_int(&in.NetworkMTU, &out.NetworkMTU, s); err != nil { return err } - // WARNING: in.Subnets requires manual conversion: does not exist in peer-type - out.NetworkMTU = in.NetworkMTU if in.ExternalRouterIPs != nil { in, out := &in.ExternalRouterIPs, &out.ExternalRouterIPs *out = make([]ExternalRouterIPParam, len(*in)) @@ -973,19 +983,29 @@ func autoConvert_v1beta1_OpenStackClusterSpec_To_v1alpha7_OpenStackClusterSpec(i } // WARNING: in.ExternalNetwork requires manual conversion: does not exist in peer-type // WARNING: in.DisableExternalNetwork requires manual conversion: does not exist in peer-type - if err := Convert_v1beta1_APIServerLoadBalancer_To_v1alpha7_APIServerLoadBalancer(&in.APIServerLoadBalancer, &out.APIServerLoadBalancer, s); err != nil { + // WARNING: in.APIServerLoadBalancer requires manual conversion: inconvertible types (*sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.APIServerLoadBalancer vs sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7.APIServerLoadBalancer) + if err := optional.Convert_optional_Bool_To_bool(&in.DisableAPIServerFloatingIP, &out.DisableAPIServerFloatingIP, s); err != nil { + return err + } + if err := optional.Convert_optional_String_To_string(&in.APIServerFloatingIP, &out.APIServerFloatingIP, s); err != nil { + return err + } + if err := optional.Convert_optional_String_To_string(&in.APIServerFixedIP, &out.APIServerFixedIP, s); err != nil { + return err + } + if err := optional.Convert_optional_Int_To_int(&in.APIServerPort, &out.APIServerPort, s); err != nil { return err } - out.DisableAPIServerFloatingIP = in.DisableAPIServerFloatingIP - out.APIServerFloatingIP = in.APIServerFloatingIP - out.APIServerFixedIP = in.APIServerFixedIP - out.APIServerPort = in.APIServerPort // WARNING: in.ManagedSecurityGroups requires manual conversion: inconvertible types (*sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1.ManagedSecurityGroups vs bool) - out.DisablePortSecurity = in.DisablePortSecurity + if err := optional.Convert_optional_Bool_To_bool(&in.DisablePortSecurity, &out.DisablePortSecurity, s); err != nil { + return err + } out.Tags = *(*[]string)(unsafe.Pointer(&in.Tags)) - out.ControlPlaneEndpoint = in.ControlPlaneEndpoint + // WARNING: in.ControlPlaneEndpoint requires manual conversion: inconvertible types (*sigs.k8s.io/cluster-api/api/v1beta1.APIEndpoint vs sigs.k8s.io/cluster-api/api/v1beta1.APIEndpoint) out.ControlPlaneAvailabilityZones = *(*[]string)(unsafe.Pointer(&in.ControlPlaneAvailabilityZones)) - out.ControlPlaneOmitAvailabilityZone = in.ControlPlaneOmitAvailabilityZone + if err := optional.Convert_optional_Bool_To_bool(&in.ControlPlaneOmitAvailabilityZone, &out.ControlPlaneOmitAvailabilityZone, s); err != nil { + return err + } if in.Bastion != nil { in, out := &in.Bastion, &out.Bastion *out = new(Bastion) diff --git a/api/v1beta1/openstackcluster_types.go b/api/v1beta1/openstackcluster_types.go index 3a3fe803c0..2aeb112c85 100644 --- a/api/v1beta1/openstackcluster_types.go +++ b/api/v1beta1/openstackcluster_types.go @@ -20,6 +20,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" capierrors "sigs.k8s.io/cluster-api/errors" + + "sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/optional" ) const ( @@ -34,6 +36,8 @@ type OpenStackClusterSpec struct { // subnets with the defined CIDR, and a router connected to these subnets. Currently only one IPv4 // subnet is supported. If you leave this empty, no network will be created. // +kubebuilder:validation:MaxItems=1 + // +listType=atomic + // +optional ManagedSubnets []SubnetSpec `json:"managedSubnets,omitempty"` // Router specifies an existing router to be used if ManagedSubnets are @@ -43,7 +47,8 @@ type OpenStackClusterSpec struct { // Network specifies an existing network to use if no ManagedSubnets // are specified. - Network NetworkFilter `json:"network,omitempty"` + // +optional + Network *NetworkFilter `json:"network,omitempty"` // Subnets specifies existing subnets to use if not ManagedSubnets are // specified. All subnets must be in the network specified by Network. @@ -51,6 +56,7 @@ type OpenStackClusterSpec struct { // all subnets in Network will be used. If 2 subnets are specified, one // must be IPv4 and the other IPv6. // +kubebuilder:validation:MaxItems=2 + // +listType=atomic // +optional Subnets []SubnetFilter `json:"subnets,omitempty"` @@ -59,26 +65,39 @@ type OpenStackClusterSpec struct { // If left empty, the network will have the default MTU defined in Openstack network service. // To use this field, the Openstack installation requires the net-mtu neutron API extension. // +optional - NetworkMTU int `json:"networkMTU,omitempty"` + NetworkMTU optional.Int `json:"networkMTU,omitempty"` // ExternalRouterIPs is an array of externalIPs on the respective subnets. // This is necessary if the router needs a fixed ip in a specific subnet. + // +listType=atomic + // +optional ExternalRouterIPs []ExternalRouterIPParam `json:"externalRouterIPs,omitempty"` // ExternalNetwork is the OpenStack Network to be used to get public internet to the VMs. + // This option is ignored if DisableExternalNetwork is set to true. + // + // If ExternalNetwork is defined it must refer to exactly one external network. + // + // If ExternalNetwork is not defined or is empty the controller will use any + // existing external network as long as there is only one. It is an + // error if ExternalNetwork is not defined and there are multiple + // external networks unless DisableExternalNetwork is also set. + // + // If ExternalNetwork is not defined and there are no external networks + // the controller will proceed as though DisableExternalNetwork was set. // +optional - ExternalNetwork NetworkFilter `json:"externalNetwork,omitempty"` + ExternalNetwork *NetworkFilter `json:"externalNetwork,omitempty"` - // DisableExternalNetwork determines whether or not to attempt to connect the cluster + // DisableExternalNetwork specifies whether or not to attempt to connect the cluster // to an external network. This allows for the creation of clusters when connecting // to an external network is not possible or desirable, e.g. if using a provider network. // +optional - DisableExternalNetwork bool `json:"disableExternalNetwork"` + DisableExternalNetwork optional.Bool `json:"disableExternalNetwork,omitempty"` // APIServerLoadBalancer configures the optional LoadBalancer for the APIServer. // It must be activated by setting `enabled: true`. // +optional - APIServerLoadBalancer APIServerLoadBalancer `json:"apiServerLoadBalancer,omitempty"` + APIServerLoadBalancer *APIServerLoadBalancer `json:"apiServerLoadBalancer,omitempty"` // DisableAPIServerFloatingIP determines whether or not to attempt to attach a floating // IP to the API server. This allows for the creation of clusters when attaching a floating @@ -93,13 +112,14 @@ type OpenStackClusterSpec struct { // configuration to manage the VIP on the control plane machines, which falls outside of // the scope of this controller. // +optional - DisableAPIServerFloatingIP bool `json:"disableAPIServerFloatingIP"` + DisableAPIServerFloatingIP optional.Bool `json:"disableAPIServerFloatingIP,omitempty"` // APIServerFloatingIP is the floatingIP which will be associated with the API server. // The floatingIP will be created if it does not already exist. // If not specified, a new floatingIP is allocated. // This field is not used if DisableAPIServerFloatingIP is set to true. - APIServerFloatingIP string `json:"apiServerFloatingIP,omitempty"` + // +optional + APIServerFloatingIP optional.String `json:"apiServerFloatingIP,omitempty"` // APIServerFixedIP is the fixed IP which will be associated with the API server. // In the case where the API server has a floating IP but not a managed load balancer, @@ -109,11 +129,13 @@ type OpenStackClusterSpec struct { // If a managed load balancer is not used AND the API server floating IP is disabled, // this field MUST be specified and should correspond to a pre-allocated port that // holds the fixed IP to be used as a VIP. - APIServerFixedIP string `json:"apiServerFixedIP,omitempty"` + // +optional + APIServerFixedIP optional.String `json:"apiServerFixedIP,omitempty"` // APIServerPort is the port on which the listener on the APIServer // will be created - APIServerPort int `json:"apiServerPort,omitempty"` + // +optional + APIServerPort optional.Int `json:"apiServerPort,omitempty"` // ManagedSecurityGroups determines whether OpenStack security groups for the cluster // will be managed by the OpenStack provider or whether pre-existing security groups will @@ -127,23 +149,35 @@ type OpenStackClusterSpec struct { // DisablePortSecurity disables the port security of the network created for the // Kubernetes cluster, which also disables SecurityGroups - DisablePortSecurity bool `json:"disablePortSecurity,omitempty"` + // +optional + DisablePortSecurity optional.Bool `json:"disablePortSecurity,omitempty"` - // Tags for all resources in cluster + // Tags to set on all resources in cluster which support tags // +listType=set + // +optional Tags []string `json:"tags,omitempty"` // ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. + // It is normally populated automatically by the OpenStackCluster + // controller during cluster provisioning. If it is set on creation the + // control plane endpoint will use the values set here in preference to + // values set elsewhere. + // ControlPlaneEndpoint cannot be modified after ControlPlaneEndpoint.Host has been set. // +optional - ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint"` + ControlPlaneEndpoint *clusterv1.APIEndpoint `json:"controlPlaneEndpoint,omitempty"` - // ControlPlaneAvailabilityZones is the az to deploy control plane to + // ControlPlaneAvailabilityZones is the set of availability zones which + // control plane machines may be deployed to. // +listType=set + // +optional ControlPlaneAvailabilityZones []string `json:"controlPlaneAvailabilityZones,omitempty"` - // Indicates whether to omit the az for control plane nodes, allowing the Nova scheduler - // to make a decision on which az to use based on other scheduling constraints - ControlPlaneOmitAvailabilityZone bool `json:"controlPlaneOmitAvailabilityZone,omitempty"` + // ControlPlaneOmitAvailabilityZone causes availability zone to be + // omitted when creating control plane nodes, allowing the Nova + // scheduler to make a decision on which availability zone to use based + // on other scheduling constraints + // +optional + ControlPlaneOmitAvailabilityZone optional.Bool `json:"controlPlaneOmitAvailabilityZone,omitempty"` // Bastion is the OpenStack instance to login the nodes // @@ -167,31 +201,42 @@ type OpenStackClusterStatus struct { Ready bool `json:"ready"` // Network contains information about the created OpenStack Network. + // +optional Network *NetworkStatusWithSubnets `json:"network,omitempty"` - // externalNetwork contains information about the external network used for default ingress and egress traffic. + // ExternalNetwork contains information about the external network used for default ingress and egress traffic. + // +optional ExternalNetwork *NetworkStatus `json:"externalNetwork,omitempty"` // Router describes the default cluster router + // +optional Router *Router `json:"router,omitempty"` // APIServerLoadBalancer describes the api server load balancer if one exists + // +optional APIServerLoadBalancer *LoadBalancer `json:"apiServerLoadBalancer,omitempty"` // FailureDomains represent OpenStack availability zones FailureDomains clusterv1.FailureDomains `json:"failureDomains,omitempty"` - // ControlPlaneSecurityGroups contains all the information about the OpenStack - // Security Group that needs to be applied to control plane nodes. - // TODO: Maybe instead of two properties, we add a property to the group? + // ControlPlaneSecurityGroup contains the information about the + // OpenStack Security Group that needs to be applied to control plane + // nodes. + // +optional ControlPlaneSecurityGroup *SecurityGroupStatus `json:"controlPlaneSecurityGroup,omitempty"` - // WorkerSecurityGroup contains all the information about the OpenStack Security - // Group that needs to be applied to worker nodes. + // WorkerSecurityGroup contains the information about the OpenStack + // Security Group that needs to be applied to worker nodes. + // +optional WorkerSecurityGroup *SecurityGroupStatus `json:"workerSecurityGroup,omitempty"` + // BastionSecurityGroup contains the information about the OpenStack + // Security Group that needs to be applied to worker nodes. + // +optional BastionSecurityGroup *SecurityGroupStatus `json:"bastionSecurityGroup,omitempty"` + // Bastion contains the information about the deployed bastion host + // +optional Bastion *BastionStatus `json:"bastion,omitempty"` // FailureReason will be set in the event that there is a terminal problem diff --git a/api/v1beta1/types.go b/api/v1beta1/types.go index 4b31458b4b..b242a9585d 100644 --- a/api/v1beta1/types.go +++ b/api/v1beta1/types.go @@ -94,6 +94,9 @@ type NetworkFilter struct { } func (networkFilter *NetworkFilter) IsEmpty() bool { + if networkFilter == nil { + return true + } return networkFilter.Name == "" && networkFilter.Description == "" && networkFilter.ProjectID == "" && @@ -612,6 +615,14 @@ type APIServerLoadBalancer struct { Provider string `json:"provider,omitempty"` } +func (s *APIServerLoadBalancer) IsZero() bool { + return s == nil || (!s.Enabled && len(s.AdditionalPorts) == 0 && len(s.AllowedCIDRs) == 0 && s.Provider == "") +} + +func (s *APIServerLoadBalancer) IsEnabled() bool { + return s != nil && s.Enabled +} + // ReferencedMachineResources contains resolved references to resources required by the machine. type ReferencedMachineResources struct { // ServerGroupID is the ID of the server group the machine should be added to and is calculated based on ServerGroupFilter. diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index cd5c538ffa..3e41b4104d 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -492,7 +492,11 @@ func (in *OpenStackClusterSpec) DeepCopyInto(out *OpenStackClusterSpec) { *out = new(RouterFilter) (*in).DeepCopyInto(*out) } - in.Network.DeepCopyInto(&out.Network) + 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)) @@ -500,6 +504,11 @@ func (in *OpenStackClusterSpec) DeepCopyInto(out *OpenStackClusterSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.NetworkMTU != nil { + in, out := &in.NetworkMTU, &out.NetworkMTU + *out = new(int) + **out = **in + } if in.ExternalRouterIPs != nil { in, out := &in.ExternalRouterIPs, &out.ExternalRouterIPs *out = make([]ExternalRouterIPParam, len(*in)) @@ -507,24 +516,71 @@ func (in *OpenStackClusterSpec) DeepCopyInto(out *OpenStackClusterSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - in.ExternalNetwork.DeepCopyInto(&out.ExternalNetwork) - in.APIServerLoadBalancer.DeepCopyInto(&out.APIServerLoadBalancer) + if in.ExternalNetwork != nil { + in, out := &in.ExternalNetwork, &out.ExternalNetwork + *out = new(NetworkFilter) + (*in).DeepCopyInto(*out) + } + if in.DisableExternalNetwork != nil { + in, out := &in.DisableExternalNetwork, &out.DisableExternalNetwork + *out = new(bool) + **out = **in + } + if in.APIServerLoadBalancer != nil { + in, out := &in.APIServerLoadBalancer, &out.APIServerLoadBalancer + *out = new(APIServerLoadBalancer) + (*in).DeepCopyInto(*out) + } + if in.DisableAPIServerFloatingIP != nil { + in, out := &in.DisableAPIServerFloatingIP, &out.DisableAPIServerFloatingIP + *out = new(bool) + **out = **in + } + if in.APIServerFloatingIP != nil { + in, out := &in.APIServerFloatingIP, &out.APIServerFloatingIP + *out = new(string) + **out = **in + } + if in.APIServerFixedIP != nil { + in, out := &in.APIServerFixedIP, &out.APIServerFixedIP + *out = new(string) + **out = **in + } + if in.APIServerPort != nil { + in, out := &in.APIServerPort, &out.APIServerPort + *out = new(int) + **out = **in + } if in.ManagedSecurityGroups != nil { in, out := &in.ManagedSecurityGroups, &out.ManagedSecurityGroups *out = new(ManagedSecurityGroups) (*in).DeepCopyInto(*out) } + if in.DisablePortSecurity != nil { + in, out := &in.DisablePortSecurity, &out.DisablePortSecurity + *out = new(bool) + **out = **in + } if in.Tags != nil { in, out := &in.Tags, &out.Tags *out = make([]string, len(*in)) copy(*out, *in) } - out.ControlPlaneEndpoint = in.ControlPlaneEndpoint + if in.ControlPlaneEndpoint != nil { + in, out := &in.ControlPlaneEndpoint, &out.ControlPlaneEndpoint + *out = new(apiv1beta1.APIEndpoint) + **out = **in + } if in.ControlPlaneAvailabilityZones != nil { in, out := &in.ControlPlaneAvailabilityZones, &out.ControlPlaneAvailabilityZones *out = make([]string, len(*in)) copy(*out, *in) } + if in.ControlPlaneOmitAvailabilityZone != nil { + in, out := &in.ControlPlaneOmitAvailabilityZone, &out.ControlPlaneOmitAvailabilityZone + *out = new(bool) + **out = **in + } if in.Bastion != nil { in, out := &in.Bastion, &out.Bastion *out = new(Bastion) 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 afe2b5c726..ff569270af 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml @@ -5529,15 +5529,21 @@ spec: type: object type: object controlPlaneAvailabilityZones: - description: ControlPlaneAvailabilityZones is the az to deploy control - plane to + description: |- + ControlPlaneAvailabilityZones is the set of availability zones which + control plane machines may be deployed to. items: type: string type: array x-kubernetes-list-type: set controlPlaneEndpoint: - description: ControlPlaneEndpoint represents the endpoint used to - communicate with the control plane. + description: |- + ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. + It is normally populated automatically by the OpenStackCluster + controller during cluster provisioning. If it is set on creation the + control plane endpoint will use the values set here in preference to + values set elsewhere. + ControlPlaneEndpoint cannot be modified after ControlPlaneEndpoint.Host has been set. properties: host: description: The hostname on which the API server is serving. @@ -5552,8 +5558,10 @@ spec: type: object controlPlaneOmitAvailabilityZone: description: |- - Indicates whether to omit the az for control plane nodes, allowing the Nova scheduler - to make a decision on which az to use based on other scheduling constraints + ControlPlaneOmitAvailabilityZone causes availability zone to be + omitted when creating control plane nodes, allowing the Nova + scheduler to make a decision on which availability zone to use based + on other scheduling constraints type: boolean disableAPIServerFloatingIP: description: |- @@ -5572,7 +5580,7 @@ spec: type: boolean disableExternalNetwork: description: |- - DisableExternalNetwork determines whether or not to attempt to connect the cluster + DisableExternalNetwork specifies whether or not to attempt to connect the cluster to an external network. This allows for the creation of clusters when connecting to an external network is not possible or desirable, e.g. if using a provider network. type: boolean @@ -5582,8 +5590,22 @@ spec: Kubernetes cluster, which also disables SecurityGroups type: boolean externalNetwork: - description: ExternalNetwork is the OpenStack Network to be used to - get public internet to the VMs. + description: |- + ExternalNetwork is the OpenStack Network to be used to get public internet to the VMs. + This option is ignored if DisableExternalNetwork is set to true. + + + If ExternalNetwork is defined it must refer to exactly one external network. + + + If ExternalNetwork is not defined or is empty the controller will use any + existing external network as long as there is only one. It is an + error if ExternalNetwork is not defined and there are multiple + external networks unless DisableExternalNetwork is also set. + + + If ExternalNetwork is not defined and there are no external networks + the controller will proceed as though DisableExternalNetwork was set. properties: description: type: string @@ -5736,6 +5758,7 @@ spec: - subnet type: object type: array + x-kubernetes-list-type: atomic identityRef: description: |- IdentityRef is a reference to a secret holding OpenStack credentials @@ -5893,6 +5916,7 @@ spec: type: object maxItems: 1 type: array + x-kubernetes-list-type: atomic network: description: |- Network specifies an existing network to use if no ManagedSubnets @@ -6117,8 +6141,10 @@ spec: type: object maxItems: 2 type: array + x-kubernetes-list-type: atomic tags: - description: Tags for all resources in cluster + description: Tags to set on all resources in cluster which support + tags items: type: string type: array @@ -6156,6 +6182,8 @@ spec: - name type: object bastion: + description: Bastion contains the information about the deployed bastion + host properties: dependentResources: properties: @@ -6569,8 +6597,8 @@ spec: type: object bastionSecurityGroup: description: |- - SecurityGroupStatus represents the basic information of the associated - OpenStack Neutron Security Group. + BastionSecurityGroup contains the information about the OpenStack + Security Group that needs to be applied to worker nodes. properties: id: description: id of the security group @@ -6636,9 +6664,9 @@ spec: type: object controlPlaneSecurityGroup: description: |- - ControlPlaneSecurityGroups contains all the information about the OpenStack - Security Group that needs to be applied to control plane nodes. - TODO: Maybe instead of two properties, we add a property to the group? + ControlPlaneSecurityGroup contains the information about the + OpenStack Security Group that needs to be applied to control plane + nodes. properties: id: description: id of the security group @@ -6703,7 +6731,7 @@ spec: - name type: object externalNetwork: - description: externalNetwork contains information about the external + description: ExternalNetwork contains information about the external network used for default ingress and egress traffic. properties: id: @@ -6844,8 +6872,8 @@ spec: type: object workerSecurityGroup: description: |- - WorkerSecurityGroup contains all the information about the OpenStack Security - Group that needs to be applied to worker nodes. + WorkerSecurityGroup contains the information about the OpenStack + Security Group that needs to be applied to worker nodes. properties: id: description: id of the security group 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 550887a8a6..cefd394be1 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclustertemplates.yaml @@ -2961,15 +2961,21 @@ spec: type: object type: object controlPlaneAvailabilityZones: - description: ControlPlaneAvailabilityZones is the az to deploy - control plane to + description: |- + ControlPlaneAvailabilityZones is the set of availability zones which + control plane machines may be deployed to. items: type: string type: array x-kubernetes-list-type: set controlPlaneEndpoint: - description: ControlPlaneEndpoint represents the endpoint - used to communicate with the control plane. + description: |- + ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. + It is normally populated automatically by the OpenStackCluster + controller during cluster provisioning. If it is set on creation the + control plane endpoint will use the values set here in preference to + values set elsewhere. + ControlPlaneEndpoint cannot be modified after ControlPlaneEndpoint.Host has been set. properties: host: description: The hostname on which the API server is serving. @@ -2984,8 +2990,10 @@ spec: type: object controlPlaneOmitAvailabilityZone: description: |- - Indicates whether to omit the az for control plane nodes, allowing the Nova scheduler - to make a decision on which az to use based on other scheduling constraints + ControlPlaneOmitAvailabilityZone causes availability zone to be + omitted when creating control plane nodes, allowing the Nova + scheduler to make a decision on which availability zone to use based + on other scheduling constraints type: boolean disableAPIServerFloatingIP: description: |- @@ -3004,7 +3012,7 @@ spec: type: boolean disableExternalNetwork: description: |- - DisableExternalNetwork determines whether or not to attempt to connect the cluster + DisableExternalNetwork specifies whether or not to attempt to connect the cluster to an external network. This allows for the creation of clusters when connecting to an external network is not possible or desirable, e.g. if using a provider network. type: boolean @@ -3014,8 +3022,22 @@ spec: Kubernetes cluster, which also disables SecurityGroups type: boolean externalNetwork: - description: ExternalNetwork is the OpenStack Network to be - used to get public internet to the VMs. + description: |- + ExternalNetwork is the OpenStack Network to be used to get public internet to the VMs. + This option is ignored if DisableExternalNetwork is set to true. + + + If ExternalNetwork is defined it must refer to exactly one external network. + + + If ExternalNetwork is not defined or is empty the controller will use any + existing external network as long as there is only one. It is an + error if ExternalNetwork is not defined and there are multiple + external networks unless DisableExternalNetwork is also set. + + + If ExternalNetwork is not defined and there are no external networks + the controller will proceed as though DisableExternalNetwork was set. properties: description: type: string @@ -3168,6 +3190,7 @@ spec: - subnet type: object type: array + x-kubernetes-list-type: atomic identityRef: description: |- IdentityRef is a reference to a secret holding OpenStack credentials @@ -3327,6 +3350,7 @@ spec: type: object maxItems: 1 type: array + x-kubernetes-list-type: atomic network: description: |- Network specifies an existing network to use if no ManagedSubnets @@ -3551,8 +3575,10 @@ spec: type: object maxItems: 2 type: array + x-kubernetes-list-type: atomic tags: - description: Tags for all resources in cluster + description: Tags to set on all resources in cluster which + support tags items: type: string type: array diff --git a/controllers/openstackcluster_controller.go b/controllers/openstackcluster_controller.go index eea38455f2..0ce97f12d5 100644 --- a/controllers/openstackcluster_controller.go +++ b/controllers/openstackcluster_controller.go @@ -184,7 +184,7 @@ func (r *OpenStackClusterReconciler) reconcileDelete(ctx context.Context, scope clusterName := fmt.Sprintf("%s-%s", cluster.Namespace, cluster.Name) - if openStackCluster.Spec.APIServerLoadBalancer.Enabled { + if openStackCluster.Spec.APIServerLoadBalancer != nil && openStackCluster.Spec.APIServerLoadBalancer.Enabled { loadBalancerService, err := loadbalancer.NewService(scope) if err != nil { return reconcile.Result{}, err @@ -348,7 +348,7 @@ func reconcileNormal(scope *scope.WithLogger, cluster *clusterv1.Cluster, openSt openStackCluster.Status.FailureDomains = make(clusterv1.FailureDomains) for _, az := range availabilityZones { // By default, the AZ is used or not used for control plane nodes depending on the flag - found := !openStackCluster.Spec.ControlPlaneOmitAvailabilityZone + found := !pointer.BoolDeref(openStackCluster.Spec.ControlPlaneOmitAvailabilityZone, false) // If explicit AZs for control plane nodes are given, they override the value if len(openStackCluster.Spec.ControlPlaneAvailabilityZones) > 0 { found = contains(openStackCluster.Spec.ControlPlaneAvailabilityZones, az.ZoneName) @@ -464,10 +464,14 @@ func reconcileBastion(scope *scope.WithLogger, cluster *clusterv1.Cluster, openS return ctrl.Result{}, nil } - floatingIP := openStackCluster.Spec.Bastion.FloatingIP - if openStackCluster.Status.Bastion.FloatingIP != "" { + var floatingIP *string + switch { + case openStackCluster.Status.Bastion.FloatingIP != "": // Some floating IP has already been created for this bastion, make sure we re-use it - floatingIP = openStackCluster.Status.Bastion.FloatingIP + floatingIP = &openStackCluster.Status.Bastion.FloatingIP + case openStackCluster.Spec.Bastion.FloatingIP != "": + // Use floating IP from the spec + floatingIP = &openStackCluster.Spec.Bastion.FloatingIP } // Check if there is an existing floating IP attached to bastion, in case where FloatingIP would not yet have been stored in cluster status fp, err = networkingService.GetOrCreateFloatingIP(openStackCluster, openStackCluster, clusterName, floatingIP) @@ -634,7 +638,7 @@ func reconcilePreExistingNetworkComponents(scope *scope.WithLogger, networkingSe } if !openStackCluster.Spec.Network.IsEmpty() { - netOpts := filterconvert.NetworkFilterToListOpts(&openStackCluster.Spec.Network) + netOpts := filterconvert.NetworkFilterToListOpts(openStackCluster.Spec.Network) networkList, err := networkingService.GetNetworksByFilter(&netOpts) if err != nil { handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to find network: %w", err)) @@ -718,7 +722,7 @@ func reconcileControlPlaneEndpoint(scope *scope.WithLogger, networkingService *n // API server load balancer is enabled. Create an Octavia load balancer. // Note that we reconcile the load balancer even if the control plane // endpoint is already set. - case openStackCluster.Spec.APIServerLoadBalancer.Enabled: + case openStackCluster.Spec.APIServerLoadBalancer != nil && openStackCluster.Spec.APIServerLoadBalancer.Enabled: loadBalancerService, err := loadbalancer.NewService(scope) if err != nil { return err @@ -743,12 +747,12 @@ func reconcileControlPlaneEndpoint(scope *scope.WithLogger, networkingService *n // Control plane endpoint is already set // Note that checking this here means that we don't re-execute any of // the branches below if the control plane endpoint is already set. - case openStackCluster.Spec.ControlPlaneEndpoint.IsValid(): + case openStackCluster.Spec.ControlPlaneEndpoint != nil && openStackCluster.Spec.ControlPlaneEndpoint.IsValid(): host = openStackCluster.Spec.ControlPlaneEndpoint.Host // API server load balancer is disabled, but floating IP is not. Create // a floating IP to be attached directly to a control plane host. - case !openStackCluster.Spec.DisableAPIServerFloatingIP: + case !pointer.BoolDeref(openStackCluster.Spec.DisableAPIServerFloatingIP, false): fp, err := networkingService.GetOrCreateFloatingIP(openStackCluster, openStackCluster, clusterName, openStackCluster.Spec.APIServerFloatingIP) if err != nil { handleUpdateOSCError(openStackCluster, fmt.Errorf("floating IP cannot be got or created: %w", err)) @@ -760,8 +764,8 @@ func reconcileControlPlaneEndpoint(scope *scope.WithLogger, networkingService *n // plane floating IP. In this case we configure APIServerFixedIP as the // control plane endpoint and leave it to the user to configure load // balancing. - case openStackCluster.Spec.APIServerFixedIP != "": - host = openStackCluster.Spec.APIServerFixedIP + case openStackCluster.Spec.APIServerFixedIP != nil: + host = *openStackCluster.Spec.APIServerFixedIP // Control plane endpoint is not set, and none can be created default: @@ -770,7 +774,7 @@ func reconcileControlPlaneEndpoint(scope *scope.WithLogger, networkingService *n return err } - openStackCluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{ + openStackCluster.Spec.ControlPlaneEndpoint = &clusterv1.APIEndpoint{ Host: host, Port: int32(apiServerPort), } @@ -781,10 +785,10 @@ func reconcileControlPlaneEndpoint(scope *scope.WithLogger, networkingService *n // getAPIServerPort returns the port to use for the API server based on the cluster spec. func getAPIServerPort(openStackCluster *infrav1.OpenStackCluster) int { switch { - case openStackCluster.Spec.ControlPlaneEndpoint.IsValid(): + case openStackCluster.Spec.ControlPlaneEndpoint != nil && openStackCluster.Spec.ControlPlaneEndpoint.IsValid(): return int(openStackCluster.Spec.ControlPlaneEndpoint.Port) - case openStackCluster.Spec.APIServerPort != 0: - return openStackCluster.Spec.APIServerPort + case openStackCluster.Spec.APIServerPort != nil: + return *openStackCluster.Spec.APIServerPort } return 6443 } diff --git a/controllers/openstackcluster_controller_test.go b/controllers/openstackcluster_controller_test.go index 971d0308d2..9688ffb968 100644 --- a/controllers/openstackcluster_controller_test.go +++ b/controllers/openstackcluster_controller_test.go @@ -34,6 +34,7 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/test/framework" "sigs.k8s.io/cluster-api/util/annotations" @@ -519,12 +520,12 @@ var _ = Describe("OpenStackCluster controller", func() { Bastion: &infrav1.Bastion{ Enabled: true, }, - DisableAPIServerFloatingIP: true, - APIServerFixedIP: "10.0.0.1", - ExternalNetwork: infrav1.NetworkFilter{ + DisableAPIServerFloatingIP: pointer.Bool(true), + APIServerFixedIP: pointer.String("10.0.0.1"), + ExternalNetwork: &infrav1.NetworkFilter{ ID: externalNetworkID, }, - Network: infrav1.NetworkFilter{ + Network: &infrav1.NetworkFilter{ ID: clusterNetworkID, }, } @@ -556,6 +557,7 @@ var _ = Describe("OpenStackCluster controller", func() { ListOptsBuilder: networks.ListOpts{ ID: externalNetworkID, }, + External: pointer.Bool(true), }).Return([]networks.Network{ { ID: externalNetworkID, @@ -598,12 +600,12 @@ var _ = Describe("OpenStackCluster controller", func() { Bastion: &infrav1.Bastion{ Enabled: true, }, - DisableAPIServerFloatingIP: true, - APIServerFixedIP: "10.0.0.1", - ExternalNetwork: infrav1.NetworkFilter{ + DisableAPIServerFloatingIP: pointer.Bool(true), + APIServerFixedIP: pointer.String("10.0.0.1"), + ExternalNetwork: &infrav1.NetworkFilter{ ID: externalNetworkID, }, - Network: infrav1.NetworkFilter{ + Network: &infrav1.NetworkFilter{ ID: clusterNetworkID, }, Subnets: []infrav1.SubnetFilter{ @@ -639,6 +641,7 @@ var _ = Describe("OpenStackCluster controller", func() { ListOptsBuilder: networks.ListOpts{ ID: externalNetworkID, }, + External: pointer.Bool(true), }).Return([]networks.Network{ { ID: externalNetworkID, @@ -679,9 +682,9 @@ var _ = Describe("OpenStackCluster controller", func() { testCluster.SetName("subnet-filtering") testCluster.Spec = infrav1.OpenStackClusterSpec{ - DisableAPIServerFloatingIP: true, - APIServerFixedIP: "10.0.0.1", - DisableExternalNetwork: true, + DisableAPIServerFloatingIP: pointer.Bool(true), + APIServerFixedIP: pointer.String("10.0.0.1"), + DisableExternalNetwork: pointer.Bool(true), Subnets: []infrav1.SubnetFilter{ {ID: clusterSubnetID}, }, @@ -762,7 +765,7 @@ func Test_getAPIServerPort(t *testing.T) { name: "with a control plane endpoint", openStackCluster: &infrav1.OpenStackCluster{ Spec: infrav1.OpenStackClusterSpec{ - ControlPlaneEndpoint: clusterv1.APIEndpoint{ + ControlPlaneEndpoint: &clusterv1.APIEndpoint{ Host: "192.168.0.1", Port: 6444, }, @@ -774,7 +777,7 @@ func Test_getAPIServerPort(t *testing.T) { name: "with API server port", openStackCluster: &infrav1.OpenStackCluster{ Spec: infrav1.OpenStackClusterSpec{ - APIServerPort: 6445, + APIServerPort: pointer.Int(6445), }, }, want: 6445, diff --git a/controllers/openstackmachine_controller.go b/controllers/openstackmachine_controller.go index 2a296bcfed..0195b007ee 100644 --- a/controllers/openstackmachine_controller.go +++ b/controllers/openstackmachine_controller.go @@ -256,7 +256,7 @@ func (r *OpenStackMachineReconciler) reconcileDelete(scope *scope.WithLogger, cl return ctrl.Result{}, err } - if openStackCluster.Spec.APIServerLoadBalancer.Enabled { + if openStackCluster.Spec.APIServerLoadBalancer.IsEnabled() { loadBalancerService, err := loadbalancer.NewService(scope) if err != nil { return ctrl.Result{}, err @@ -278,7 +278,7 @@ func (r *OpenStackMachineReconciler) reconcileDelete(scope *scope.WithLogger, cl } else if instanceStatus, err = computeService.GetInstanceStatusByName(openStackMachine, openStackMachine.Name); err != nil { return ctrl.Result{}, err } - if !openStackCluster.Spec.APIServerLoadBalancer.Enabled && util.IsControlPlaneMachine(machine) && openStackCluster.Spec.APIServerFloatingIP == "" { + if !openStackCluster.Spec.APIServerLoadBalancer.IsEnabled() && util.IsControlPlaneMachine(machine) && openStackCluster.Spec.APIServerFloatingIP == nil { if instanceStatus != nil { instanceNS, err := instanceStatus.NetworkStatus() if err != nil { @@ -452,21 +452,24 @@ func (r *OpenStackMachineReconciler) reconcileNormal(ctx context.Context, scope return ctrl.Result{}, nil } - if openStackCluster.Spec.APIServerLoadBalancer.Enabled { + if openStackCluster.Spec.APIServerLoadBalancer.IsEnabled() { err = r.reconcileLoadBalancerMember(scope, openStackCluster, openStackMachine, instanceNS, clusterName) if err != nil { conditions.MarkFalse(openStackMachine, infrav1.APIServerIngressReadyCondition, infrav1.LoadBalancerMemberErrorReason, clusterv1.ConditionSeverityError, "Reconciling load balancer member failed: %v", err) return ctrl.Result{}, fmt.Errorf("reconcile load balancer member: %w", err) } - } else if !openStackCluster.Spec.DisableAPIServerFloatingIP { - floatingIPAddress := openStackCluster.Spec.ControlPlaneEndpoint.Host - if openStackCluster.Spec.APIServerFloatingIP != "" { + } else if !pointer.BoolDeref(openStackCluster.Spec.DisableAPIServerFloatingIP, false) { + var floatingIPAddress *string + switch { + case openStackCluster.Spec.ControlPlaneEndpoint != nil && openStackCluster.Spec.ControlPlaneEndpoint.IsValid(): + floatingIPAddress = &openStackCluster.Spec.ControlPlaneEndpoint.Host + case openStackCluster.Spec.APIServerFloatingIP != nil: floatingIPAddress = openStackCluster.Spec.APIServerFloatingIP } fp, err := networkingService.GetOrCreateFloatingIP(openStackMachine, openStackCluster, clusterName, floatingIPAddress) if err != nil { conditions.MarkFalse(openStackMachine, infrav1.APIServerIngressReadyCondition, infrav1.FloatingIPErrorReason, clusterv1.ConditionSeverityError, "Floating IP cannot be obtained or created: %v", err) - return ctrl.Result{}, fmt.Errorf("get or create floating IP %q: %w", floatingIPAddress, err) + return ctrl.Result{}, fmt.Errorf("get or create floating IP %v: %w", floatingIPAddress, err) } port, err := computeService.GetManagementPort(openStackCluster, instanceStatus) if err != nil { diff --git a/docs/book/src/api/v1beta1/api.md b/docs/book/src/api/v1beta1/api.md index 311b73e3cf..7995b52c2a 100644 --- a/docs/book/src/api/v1beta1/api.md +++ b/docs/book/src/api/v1beta1/api.md @@ -77,6 +77,7 @@ OpenStackClusterSpec
ManagedSubnets describe OpenStack Subnets to be created. Cluster actuator will create a network, subnets with the defined CIDR, and a router connected to these subnets. Currently only one IPv4 subnet is supported. If you leave this empty, no network will be created.
@@ -107,6 +108,7 @@ NetworkFilterNetwork specifies an existing network to use if no ManagedSubnets are specified.
ExternalRouterIPs is an array of externalIPs on the respective subnets. This is necessary if the router needs a fixed ip in a specific subnet.
ExternalNetwork is the OpenStack Network to be used to get public internet to the VMs.
+ExternalNetwork is the OpenStack Network to be used to get public internet to the VMs. +This option is ignored if DisableExternalNetwork is set to true.
+If ExternalNetwork is defined it must refer to exactly one external network.
+If ExternalNetwork is not defined or is empty the controller will use any +existing external network as long as there is only one. It is an +error if ExternalNetwork is not defined and there are multiple +external networks unless DisableExternalNetwork is also set.
+If ExternalNetwork is not defined and there are no external networks +the controller will proceed as though DisableExternalNetwork was set.
DisableExternalNetwork determines whether or not to attempt to connect the cluster +
DisableExternalNetwork specifies whether or not to attempt to connect the cluster to an external network. This allows for the creation of clusters when connecting to an external network is not possible or desirable, e.g. if using a provider network.
APIServerFloatingIP is the floatingIP which will be associated with the API server. The floatingIP will be created if it does not already exist. If not specified, a new floatingIP is allocated. @@ -246,6 +258,7 @@ string
APIServerFixedIP is the fixed IP which will be associated with the API server. In the case where the API server has a floating IP but not a managed load balancer, this field is not used. @@ -264,6 +277,7 @@ int
APIServerPort is the port on which the listener on the APIServer will be created
DisablePortSecurity disables the port security of the network created for the Kubernetes cluster, which also disables SecurityGroups
Tags for all resources in cluster
+(Optional) +Tags to set on all resources in cluster which support tags
ControlPlaneEndpoint represents the endpoint used to communicate with the control plane.
+ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. +It is normally populated automatically by the OpenStackCluster +controller during cluster provisioning. If it is set on creation the +control plane endpoint will use the values set here in preference to +values set elsewhere. +ControlPlaneEndpoint cannot be modified after ControlPlaneEndpoint.Host has been set.
ControlPlaneAvailabilityZones is the az to deploy control plane to
+(Optional) +ControlPlaneAvailabilityZones is the set of availability zones which +control plane machines may be deployed to.
Indicates whether to omit the az for control plane nodes, allowing the Nova scheduler -to make a decision on which az to use based on other scheduling constraints
+(Optional) +ControlPlaneOmitAvailabilityZone causes availability zone to be +omitted when creating control plane nodes, allowing the Nova +scheduler to make a decision on which availability zone to use based +on other scheduling constraints
ManagedSubnets describe OpenStack Subnets to be created. Cluster actuator will create a network, subnets with the defined CIDR, and a router connected to these subnets. Currently only one IPv4 subnet is supported. If you leave this empty, no network will be created.
@@ -1981,6 +2008,7 @@ NetworkFilterNetwork specifies an existing network to use if no ManagedSubnets are specified.
ExternalRouterIPs is an array of externalIPs on the respective subnets. This is necessary if the router needs a fixed ip in a specific subnet.
ExternalNetwork is the OpenStack Network to be used to get public internet to the VMs.
+ExternalNetwork is the OpenStack Network to be used to get public internet to the VMs. +This option is ignored if DisableExternalNetwork is set to true.
+If ExternalNetwork is defined it must refer to exactly one external network.
+If ExternalNetwork is not defined or is empty the controller will use any +existing external network as long as there is only one. It is an +error if ExternalNetwork is not defined and there are multiple +external networks unless DisableExternalNetwork is also set.
+If ExternalNetwork is not defined and there are no external networks +the controller will proceed as though DisableExternalNetwork was set.
DisableExternalNetwork determines whether or not to attempt to connect the cluster +
DisableExternalNetwork specifies whether or not to attempt to connect the cluster to an external network. This allows for the creation of clusters when connecting to an external network is not possible or desirable, e.g. if using a provider network.
APIServerFloatingIP is the floatingIP which will be associated with the API server. The floatingIP will be created if it does not already exist. If not specified, a new floatingIP is allocated. @@ -2120,6 +2158,7 @@ string
APIServerFixedIP is the fixed IP which will be associated with the API server. In the case where the API server has a floating IP but not a managed load balancer, this field is not used. @@ -2138,6 +2177,7 @@ int
APIServerPort is the port on which the listener on the APIServer will be created
DisablePortSecurity disables the port security of the network created for the Kubernetes cluster, which also disables SecurityGroups
Tags for all resources in cluster
+(Optional) +Tags to set on all resources in cluster which support tags
ControlPlaneEndpoint represents the endpoint used to communicate with the control plane.
+ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. +It is normally populated automatically by the OpenStackCluster +controller during cluster provisioning. If it is set on creation the +control plane endpoint will use the values set here in preference to +values set elsewhere. +ControlPlaneEndpoint cannot be modified after ControlPlaneEndpoint.Host has been set.
ControlPlaneAvailabilityZones is the az to deploy control plane to
+(Optional) +ControlPlaneAvailabilityZones is the set of availability zones which +control plane machines may be deployed to.
Indicates whether to omit the az for control plane nodes, allowing the Nova scheduler -to make a decision on which az to use based on other scheduling constraints
+(Optional) +ControlPlaneOmitAvailabilityZone causes availability zone to be +omitted when creating control plane nodes, allowing the Nova +scheduler to make a decision on which availability zone to use based +on other scheduling constraints
Network contains information about the created OpenStack Network.
externalNetwork contains information about the external network used for default ingress and egress traffic.
+(Optional) +ExternalNetwork contains information about the external network used for default ingress and egress traffic.
Router describes the default cluster router
APIServerLoadBalancer describes the api server load balancer if one exists
ControlPlaneSecurityGroups contains all the information about the OpenStack -Security Group that needs to be applied to control plane nodes. -TODO: Maybe instead of two properties, we add a property to the group?
+(Optional) +ControlPlaneSecurityGroup contains the information about the +OpenStack Security Group that needs to be applied to control plane +nodes.
WorkerSecurityGroup contains all the information about the OpenStack Security -Group that needs to be applied to worker nodes.
+(Optional) +WorkerSecurityGroup contains the information about the OpenStack +Security Group that needs to be applied to worker nodes.
BastionSecurityGroup contains the information about the OpenStack +Security Group that needs to be applied to worker nodes.
Bastion contains the information about the deployed bastion host
ManagedSubnets describe OpenStack Subnets to be created. Cluster actuator will create a network, subnets with the defined CIDR, and a router connected to these subnets. Currently only one IPv4 subnet is supported. If you leave this empty, no network will be created.
@@ -2526,6 +2590,7 @@ NetworkFilterNetwork specifies an existing network to use if no ManagedSubnets are specified.
ExternalRouterIPs is an array of externalIPs on the respective subnets. This is necessary if the router needs a fixed ip in a specific subnet.
ExternalNetwork is the OpenStack Network to be used to get public internet to the VMs.
+ExternalNetwork is the OpenStack Network to be used to get public internet to the VMs. +This option is ignored if DisableExternalNetwork is set to true.
+If ExternalNetwork is defined it must refer to exactly one external network.
+If ExternalNetwork is not defined or is empty the controller will use any +existing external network as long as there is only one. It is an +error if ExternalNetwork is not defined and there are multiple +external networks unless DisableExternalNetwork is also set.
+If ExternalNetwork is not defined and there are no external networks +the controller will proceed as though DisableExternalNetwork was set.
DisableExternalNetwork determines whether or not to attempt to connect the cluster +
DisableExternalNetwork specifies whether or not to attempt to connect the cluster to an external network. This allows for the creation of clusters when connecting to an external network is not possible or desirable, e.g. if using a provider network.
APIServerFloatingIP is the floatingIP which will be associated with the API server. The floatingIP will be created if it does not already exist. If not specified, a new floatingIP is allocated. @@ -2665,6 +2740,7 @@ string
APIServerFixedIP is the fixed IP which will be associated with the API server. In the case where the API server has a floating IP but not a managed load balancer, this field is not used. @@ -2683,6 +2759,7 @@ int
APIServerPort is the port on which the listener on the APIServer will be created
DisablePortSecurity disables the port security of the network created for the Kubernetes cluster, which also disables SecurityGroups
Tags for all resources in cluster
+(Optional) +Tags to set on all resources in cluster which support tags
ControlPlaneEndpoint represents the endpoint used to communicate with the control plane.
+ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. +It is normally populated automatically by the OpenStackCluster +controller during cluster provisioning. If it is set on creation the +control plane endpoint will use the values set here in preference to +values set elsewhere. +ControlPlaneEndpoint cannot be modified after ControlPlaneEndpoint.Host has been set.
ControlPlaneAvailabilityZones is the az to deploy control plane to
+(Optional) +ControlPlaneAvailabilityZones is the set of availability zones which +control plane machines may be deployed to.
Indicates whether to omit the az for control plane nodes, allowing the Nova scheduler -to make a decision on which az to use based on other scheduling constraints
+(Optional) +ControlPlaneOmitAvailabilityZone causes availability zone to be +omitted when creating control plane nodes, allowing the Nova +scheduler to make a decision on which availability zone to use based +on other scheduling constraints