Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ func Convert_v1beta1_OpenStackClusterStatus_To_v1beta2_OpenStackClusterStatus(in
out.Network = (*infrav1.NetworkStatusWithSubnets)(unsafe.Pointer(in.Network))
out.ExternalNetwork = (*infrav1.NetworkStatus)(unsafe.Pointer(in.ExternalNetwork))
out.Router = (*infrav1.Router)(unsafe.Pointer(in.Router))
out.APIServerLoadBalancer = (*infrav1.LoadBalancer)(unsafe.Pointer(in.APIServerLoadBalancer))
out.APIServerManagedLoadBalancer = (*infrav1.LoadBalancer)(unsafe.Pointer(in.APIServerLoadBalancer))
out.ControlPlaneSecurityGroup = (*infrav1.SecurityGroupStatus)(unsafe.Pointer(in.ControlPlaneSecurityGroup))
out.WorkerSecurityGroup = (*infrav1.SecurityGroupStatus)(unsafe.Pointer(in.WorkerSecurityGroup))
out.BastionSecurityGroup = (*infrav1.SecurityGroupStatus)(unsafe.Pointer(in.BastionSecurityGroup))
Expand Down Expand Up @@ -219,7 +219,7 @@ func Convert_v1beta2_OpenStackClusterStatus_To_v1beta1_OpenStackClusterStatus(in
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))
out.APIServerLoadBalancer = (*LoadBalancer)(unsafe.Pointer(in.APIServerManagedLoadBalancer))
out.ControlPlaneSecurityGroup = (*SecurityGroupStatus)(unsafe.Pointer(in.ControlPlaneSecurityGroup))
out.WorkerSecurityGroup = (*SecurityGroupStatus)(unsafe.Pointer(in.WorkerSecurityGroup))
out.BastionSecurityGroup = (*SecurityGroupStatus)(unsafe.Pointer(in.BastionSecurityGroup))
Expand Down Expand Up @@ -397,6 +397,25 @@ func Convert_v1beta1_OpenStackClusterSpec_To_v1beta2_OpenStackClusterSpec(
}
}

// Consolidate the flat v1beta1 APIServer fields into the new APIServer struct.
if in.APIServerLoadBalancer != nil ||
in.DisableAPIServerFloatingIP != nil ||
in.APIServerFloatingIP != nil ||
in.APIServerFixedIP != nil ||
in.APIServerPort != nil {
out.APIServer = &infrav1.APIServer{
DisableFloatingIP: in.DisableAPIServerFloatingIP,
FloatingIP: in.APIServerFloatingIP,
FixedIP: in.APIServerFixedIP,
Port: in.APIServerPort,
}
// APIServerLoadBalancer is structurally identical between versions,
// so an unsafe cast is safe here (same field layout).
if in.APIServerLoadBalancer != nil {
out.APIServer.ManagedLoadBalancer = (*infrav1.APIServerLoadBalancer)(unsafe.Pointer(in.APIServerLoadBalancer))
}
}

return nil
}

Expand Down Expand Up @@ -427,6 +446,18 @@ func Convert_v1beta2_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(
}
}

// Expand the v1beta2 APIServer struct back into the flat v1beta1 fields.
if in.APIServer != nil {
out.DisableAPIServerFloatingIP = in.APIServer.DisableFloatingIP
out.APIServerFloatingIP = in.APIServer.FloatingIP
out.APIServerFixedIP = in.APIServer.FixedIP
out.APIServerPort = in.APIServer.Port

if in.APIServer.ManagedLoadBalancer != nil {
out.APIServerLoadBalancer = (*APIServerLoadBalancer)(unsafe.Pointer(in.APIServer.ManagedLoadBalancer))
}
}

return nil
}

Expand Down
215 changes: 215 additions & 0 deletions api/v1beta1/conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1216,3 +1216,218 @@ func TestOpenStackCluster_RoundTrip_ManagedSecurityGroups_ClusterNodesRules(t *t
})
}
}

func TestOpenStackCluster_RoundTrip_APIServer(t *testing.T) {
floatingIP := optional.String(ptr.To("203.0.113.10"))
fixedIP := optional.String(ptr.To("10.0.0.5"))
port := optional.UInt16(ptr.To(uint16(6443)))
disable := optional.Bool(ptr.To(true))

tests := []struct {
name string
in OpenStackCluster
}{
{
name: "all fields set",
in: OpenStackCluster{
Spec: OpenStackClusterSpec{
APIServerFloatingIP: floatingIP,
APIServerFixedIP: fixedIP,
APIServerPort: port,
DisableAPIServerFloatingIP: disable,
APIServerLoadBalancer: &APIServerLoadBalancer{
Enabled: ptr.To(true),
},
},
},
},
{
name: "only floatingIP set",
in: OpenStackCluster{
Spec: OpenStackClusterSpec{
APIServerFloatingIP: floatingIP,
},
},
},
{
name: "only fixedIP set",
in: OpenStackCluster{
Spec: OpenStackClusterSpec{
APIServerFixedIP: fixedIP,
},
},
},
{
name: "only port set",
in: OpenStackCluster{
Spec: OpenStackClusterSpec{
APIServerPort: port,
},
},
},
{
name: "only disableFloatingIP set",
in: OpenStackCluster{
Spec: OpenStackClusterSpec{
DisableAPIServerFloatingIP: disable,
},
},
},
{
name: "only loadBalancer set",
in: OpenStackCluster{
Spec: OpenStackClusterSpec{
APIServerLoadBalancer: &APIServerLoadBalancer{
Enabled: ptr.To(true),
},
},
},
},
{
name: "loadBalancer disabled explicitly",
in: OpenStackCluster{
Spec: OpenStackClusterSpec{
APIServerLoadBalancer: &APIServerLoadBalancer{
Enabled: ptr.To(false),
},
},
},
},
{
name: "disableFloatingIP with fixedIP (no-LB VIP case)",
in: OpenStackCluster{
Spec: OpenStackClusterSpec{
DisableAPIServerFloatingIP: disable,
APIServerFixedIP: fixedIP,
},
},
},
{
name: "no APIServer fields set — APIServer stays nil",
in: OpenStackCluster{},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

hub := &infrav1.OpenStackCluster{}
g.Expect(tt.in.ConvertTo(hub)).To(Succeed())

src := tt.in.Spec

// --- Verify intermediate v1beta2 state ---
allNil := src.APIServerFloatingIP == nil &&
src.APIServerFixedIP == nil &&
src.APIServerPort == nil &&
src.DisableAPIServerFloatingIP == nil &&
src.APIServerLoadBalancer == nil

if allNil {
g.Expect(hub.Spec.APIServer).To(BeNil(), "APIServer should be nil when no source fields are set")
} else {
g.Expect(hub.Spec.APIServer).NotTo(BeNil(), "APIServer should be non-nil when at least one source field is set")
g.Expect(hub.Spec.APIServer.FloatingIP).To(Equal(src.APIServerFloatingIP))
g.Expect(hub.Spec.APIServer.FixedIP).To(Equal(src.APIServerFixedIP))
g.Expect(hub.Spec.APIServer.Port).To(Equal(src.APIServerPort))
g.Expect(hub.Spec.APIServer.DisableFloatingIP).To(Equal(src.DisableAPIServerFloatingIP))

if src.APIServerLoadBalancer == nil {
g.Expect(hub.Spec.APIServer.ManagedLoadBalancer).To(BeNil())
} else {
g.Expect(hub.Spec.APIServer.ManagedLoadBalancer).NotTo(BeNil())
g.Expect(hub.Spec.APIServer.ManagedLoadBalancer.Enabled).To(Equal(src.APIServerLoadBalancer.Enabled))
}
}

// --- Verify full round-trip back to v1beta1 ---
restored := &OpenStackCluster{}
g.Expect(restored.ConvertFrom(hub)).To(Succeed())

g.Expect(restored.Spec.APIServerFloatingIP).To(Equal(src.APIServerFloatingIP))
g.Expect(restored.Spec.APIServerFixedIP).To(Equal(src.APIServerFixedIP))
g.Expect(restored.Spec.APIServerPort).To(Equal(src.APIServerPort))
g.Expect(restored.Spec.DisableAPIServerFloatingIP).To(Equal(src.DisableAPIServerFloatingIP))
g.Expect(restored.Spec.APIServerLoadBalancer).To(Equal(src.APIServerLoadBalancer))
})
}
}

func TestOpenStackClusterStatusAPIServerLoadBalancerConversion(t *testing.T) {
g := NewWithT(t)

src := &OpenStackCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
Namespace: "default",
},
Spec: OpenStackClusterSpec{
IdentityRef: OpenStackIdentityReference{
Name: "cloud-config",
CloudName: "openstack",
},
},
Status: OpenStackClusterStatus{
APIServerLoadBalancer: &LoadBalancer{
Name: "test-lb",
ID: "lb-id-123",
IP: "192.168.1.100",
InternalIP: "10.0.0.10",
},
},
}

// Convert to v1beta2
dst := &infrav1.OpenStackCluster{}
g.Expect(src.ConvertTo(dst)).To(Succeed())

// Verify field was mapped to the renamed destination field
g.Expect(dst.Status.APIServerManagedLoadBalancer).NotTo(BeNil())
g.Expect(dst.Status.APIServerManagedLoadBalancer.Name).To(Equal("test-lb"))
g.Expect(dst.Status.APIServerManagedLoadBalancer.ID).To(Equal("lb-id-123"))
g.Expect(dst.Status.APIServerManagedLoadBalancer.IP).To(Equal("192.168.1.100"))
g.Expect(dst.Status.APIServerManagedLoadBalancer.InternalIP).To(Equal("10.0.0.10"))

// Convert back
restored := &OpenStackCluster{}
g.Expect(restored.ConvertFrom(dst)).To(Succeed())

// Verify round-trip
g.Expect(restored.Status.APIServerLoadBalancer).NotTo(BeNil())
g.Expect(restored.Status.APIServerLoadBalancer).To(Equal(src.Status.APIServerLoadBalancer))
}

func TestOpenStackClusterStatusAPIServerLoadBalancerNilConversion(t *testing.T) {
g := NewWithT(t)

src := &OpenStackCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
Namespace: "default",
},
Spec: OpenStackClusterSpec{
IdentityRef: OpenStackIdentityReference{
Name: "cloud-config",
CloudName: "openstack",
},
},
Status: OpenStackClusterStatus{
APIServerLoadBalancer: nil,
},
}

// Convert to v1beta2
dst := &infrav1.OpenStackCluster{}
g.Expect(src.ConvertTo(dst)).To(Succeed())

// Verify nil is preserved and does not bleed into renamed field
g.Expect(dst.Status.APIServerManagedLoadBalancer).To(BeNil())

// Convert back
restored := &OpenStackCluster{}
g.Expect(restored.ConvertFrom(dst)).To(Succeed())

// Verify round-trip preserves nil
g.Expect(restored.Status.APIServerLoadBalancer).To(BeNil())
}
20 changes: 8 additions & 12 deletions api/v1beta1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading