Skip to content

Commit f885b04

Browse files
committed
Group API server fields under spec.apiServer
Consolidate the various flat API server fields (apiServerFloatingIP, apiServerFixedIP, apiServerPort, disableAPIServerFloatingIP, and apiServerLoadBalancer) into a single structured spec.apiServer object, with apiServerLoadBalancer renamed to managedLoadBalancer within it. Includes conversion from v1beta1 and updated webhook validation.
1 parent f5508c1 commit f885b04

36 files changed

Lines changed: 1570 additions & 1251 deletions

api/v1beta1/conversion.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,25 @@ func Convert_v1beta1_OpenStackClusterSpec_To_v1beta2_OpenStackClusterSpec(
397397
}
398398
}
399399

400+
// Consolidate the flat v1beta1 APIServer fields into the new APIServer struct.
401+
if in.APIServerLoadBalancer != nil ||
402+
in.DisableAPIServerFloatingIP != nil ||
403+
in.APIServerFloatingIP != nil ||
404+
in.APIServerFixedIP != nil ||
405+
in.APIServerPort != nil {
406+
out.APIServer = &infrav1.APIServer{
407+
DisableFloatingIP: in.DisableAPIServerFloatingIP,
408+
FloatingIP: in.APIServerFloatingIP,
409+
FixedIP: in.APIServerFixedIP,
410+
Port: in.APIServerPort,
411+
}
412+
// APIServerLoadBalancer is structurally identical between versions,
413+
// so an unsafe cast is safe here (same field layout).
414+
if in.APIServerLoadBalancer != nil {
415+
out.APIServer.ManagedLoadBalancer = (*infrav1.APIServerLoadBalancer)(unsafe.Pointer(in.APIServerLoadBalancer))
416+
}
417+
}
418+
400419
return nil
401420
}
402421

@@ -427,6 +446,18 @@ func Convert_v1beta2_OpenStackClusterSpec_To_v1beta1_OpenStackClusterSpec(
427446
}
428447
}
429448

449+
// Expand the v1beta2 APIServer struct back into the flat v1beta1 fields.
450+
if in.APIServer != nil {
451+
out.DisableAPIServerFloatingIP = in.APIServer.DisableFloatingIP
452+
out.APIServerFloatingIP = in.APIServer.FloatingIP
453+
out.APIServerFixedIP = in.APIServer.FixedIP
454+
out.APIServerPort = in.APIServer.Port
455+
456+
if in.APIServer.ManagedLoadBalancer != nil {
457+
out.APIServerLoadBalancer = (*APIServerLoadBalancer)(unsafe.Pointer(in.APIServer.ManagedLoadBalancer))
458+
}
459+
}
460+
430461
return nil
431462
}
432463

api/v1beta1/conversion_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,3 +1216,140 @@ func TestOpenStackCluster_RoundTrip_ManagedSecurityGroups_ClusterNodesRules(t *t
12161216
})
12171217
}
12181218
}
1219+
1220+
func TestOpenStackCluster_RoundTrip_APIServer(t *testing.T) {
1221+
floatingIP := optional.String(ptr.To("203.0.113.10"))
1222+
fixedIP := optional.String(ptr.To("10.0.0.5"))
1223+
port := optional.UInt16(ptr.To(uint16(6443)))
1224+
disable := optional.Bool(ptr.To(true))
1225+
1226+
tests := []struct {
1227+
name string
1228+
in OpenStackCluster
1229+
}{
1230+
{
1231+
name: "all fields set",
1232+
in: OpenStackCluster{
1233+
Spec: OpenStackClusterSpec{
1234+
APIServerFloatingIP: floatingIP,
1235+
APIServerFixedIP: fixedIP,
1236+
APIServerPort: port,
1237+
DisableAPIServerFloatingIP: disable,
1238+
APIServerLoadBalancer: &APIServerLoadBalancer{
1239+
Enabled: ptr.To(true),
1240+
},
1241+
},
1242+
},
1243+
},
1244+
{
1245+
name: "only floatingIP set",
1246+
in: OpenStackCluster{
1247+
Spec: OpenStackClusterSpec{
1248+
APIServerFloatingIP: floatingIP,
1249+
},
1250+
},
1251+
},
1252+
{
1253+
name: "only fixedIP set",
1254+
in: OpenStackCluster{
1255+
Spec: OpenStackClusterSpec{
1256+
APIServerFixedIP: fixedIP,
1257+
},
1258+
},
1259+
},
1260+
{
1261+
name: "only port set",
1262+
in: OpenStackCluster{
1263+
Spec: OpenStackClusterSpec{
1264+
APIServerPort: port,
1265+
},
1266+
},
1267+
},
1268+
{
1269+
name: "only disableFloatingIP set",
1270+
in: OpenStackCluster{
1271+
Spec: OpenStackClusterSpec{
1272+
DisableAPIServerFloatingIP: disable,
1273+
},
1274+
},
1275+
},
1276+
{
1277+
name: "only loadBalancer set",
1278+
in: OpenStackCluster{
1279+
Spec: OpenStackClusterSpec{
1280+
APIServerLoadBalancer: &APIServerLoadBalancer{
1281+
Enabled: ptr.To(true),
1282+
},
1283+
},
1284+
},
1285+
},
1286+
{
1287+
name: "loadBalancer disabled explicitly",
1288+
in: OpenStackCluster{
1289+
Spec: OpenStackClusterSpec{
1290+
APIServerLoadBalancer: &APIServerLoadBalancer{
1291+
Enabled: ptr.To(false),
1292+
},
1293+
},
1294+
},
1295+
},
1296+
{
1297+
name: "disableFloatingIP with fixedIP (no-LB VIP case)",
1298+
in: OpenStackCluster{
1299+
Spec: OpenStackClusterSpec{
1300+
DisableAPIServerFloatingIP: disable,
1301+
APIServerFixedIP: fixedIP,
1302+
},
1303+
},
1304+
},
1305+
{
1306+
name: "no APIServer fields set — APIServer stays nil",
1307+
in: OpenStackCluster{},
1308+
},
1309+
}
1310+
1311+
for _, tt := range tests {
1312+
t.Run(tt.name, func(t *testing.T) {
1313+
g := NewWithT(t)
1314+
1315+
hub := &infrav1.OpenStackCluster{}
1316+
g.Expect(tt.in.ConvertTo(hub)).To(Succeed())
1317+
1318+
src := tt.in.Spec
1319+
1320+
// --- Verify intermediate v1beta2 state ---
1321+
allNil := src.APIServerFloatingIP == nil &&
1322+
src.APIServerFixedIP == nil &&
1323+
src.APIServerPort == nil &&
1324+
src.DisableAPIServerFloatingIP == nil &&
1325+
src.APIServerLoadBalancer == nil
1326+
1327+
if allNil {
1328+
g.Expect(hub.Spec.APIServer).To(BeNil(), "APIServer should be nil when no source fields are set")
1329+
} else {
1330+
g.Expect(hub.Spec.APIServer).NotTo(BeNil(), "APIServer should be non-nil when at least one source field is set")
1331+
g.Expect(hub.Spec.APIServer.FloatingIP).To(Equal(src.APIServerFloatingIP))
1332+
g.Expect(hub.Spec.APIServer.FixedIP).To(Equal(src.APIServerFixedIP))
1333+
g.Expect(hub.Spec.APIServer.Port).To(Equal(src.APIServerPort))
1334+
g.Expect(hub.Spec.APIServer.DisableFloatingIP).To(Equal(src.DisableAPIServerFloatingIP))
1335+
1336+
if src.APIServerLoadBalancer == nil {
1337+
g.Expect(hub.Spec.APIServer.ManagedLoadBalancer).To(BeNil())
1338+
} else {
1339+
g.Expect(hub.Spec.APIServer.ManagedLoadBalancer).NotTo(BeNil())
1340+
g.Expect(hub.Spec.APIServer.ManagedLoadBalancer.Enabled).To(Equal(src.APIServerLoadBalancer.Enabled))
1341+
}
1342+
}
1343+
1344+
// --- Verify full round-trip back to v1beta1 ---
1345+
restored := &OpenStackCluster{}
1346+
g.Expect(restored.ConvertFrom(hub)).To(Succeed())
1347+
1348+
g.Expect(restored.Spec.APIServerFloatingIP).To(Equal(src.APIServerFloatingIP))
1349+
g.Expect(restored.Spec.APIServerFixedIP).To(Equal(src.APIServerFixedIP))
1350+
g.Expect(restored.Spec.APIServerPort).To(Equal(src.APIServerPort))
1351+
g.Expect(restored.Spec.DisableAPIServerFloatingIP).To(Equal(src.DisableAPIServerFloatingIP))
1352+
g.Expect(restored.Spec.APIServerLoadBalancer).To(Equal(src.APIServerLoadBalancer))
1353+
})
1354+
}
1355+
}

api/v1beta1/zz_generated.conversion.go

Lines changed: 6 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta2/openstackcluster_types.go

Lines changed: 74 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const (
3131

3232
// OpenStackClusterSpec defines the desired state of OpenStackCluster.
3333
// +kubebuilder:validation:XValidation:rule="has(self.disableExternalNetwork) && self.disableExternalNetwork ? !has(self.bastion) || !has(self.bastion.floatingIP) : true",message="bastion floating IP cannot be set when disableExternalNetwork is true"
34-
// +kubebuilder:validation:XValidation:rule="has(self.disableExternalNetwork) && self.disableExternalNetwork ? has(self.disableAPIServerFloatingIP) && self.disableAPIServerFloatingIP : true",message="disableAPIServerFloatingIP cannot be false when disableExternalNetwork is true"
34+
// +kubebuilder:validation:XValidation:rule="has(self.disableExternalNetwork) && self.disableExternalNetwork ? has(self.apiServer) && has(self.apiServer.disableFloatingIP) && self.apiServer.disableFloatingIP : true",message="apiServer.disableFloatingIP cannot be false when disableExternalNetwork is true"
3535
type OpenStackClusterSpec struct {
3636
// managedSubnets describe OpenStack Subnets to be created. Cluster actuator will create a network,
3737
// subnets with the defined CIDR, and a router connected to these subnets. Currently only one IPv4
@@ -94,48 +94,10 @@ type OpenStackClusterSpec struct {
9494
// +optional
9595
DisableExternalNetwork optional.Bool `json:"disableExternalNetwork,omitempty"`
9696

97-
// apiServerLoadBalancer configures the optional LoadBalancer for the APIServer.
98-
// If not specified, no load balancer will be created for the API server.
97+
// apiServer configures the API server endpoint and its associated
98+
// load balancer and floating IP.
9999
// +optional
100-
APIServerLoadBalancer *APIServerLoadBalancer `json:"apiServerLoadBalancer,omitempty"`
101-
102-
// disableAPIServerFloatingIP determines whether or not to attempt to attach a floating
103-
// IP to the API server. This allows for the creation of clusters when attaching a floating
104-
// IP to the API server (and hence, in many cases, exposing the API server to the internet)
105-
// is not possible or desirable, e.g. if using a shared VLAN for communication between
106-
// management and workload clusters or when the management cluster is inside the
107-
// project network.
108-
// This option requires that the API server use a VIP on the cluster network so that the
109-
// underlying machines can change without changing ControlPlaneEndpoint.Host.
110-
// When using a managed load balancer, this VIP will be managed automatically.
111-
// If not using a managed load balancer, cluster configuration will fail without additional
112-
// configuration to manage the VIP on the control plane machines, which falls outside of
113-
// the scope of this controller.
114-
// +optional
115-
DisableAPIServerFloatingIP optional.Bool `json:"disableAPIServerFloatingIP,omitempty"`
116-
117-
// apiServerFloatingIP is the floatingIP which will be associated with the API server.
118-
// The floatingIP will be created if it does not already exist.
119-
// If not specified, a new floatingIP is allocated.
120-
// This field is not used if DisableAPIServerFloatingIP is set to true.
121-
// +optional
122-
APIServerFloatingIP optional.String `json:"apiServerFloatingIP,omitempty"`
123-
124-
// apiServerFixedIP is the fixed IP which will be associated with the API server.
125-
// In the case where the API server has a floating IP but not a managed load balancer,
126-
// this field is not used.
127-
// If a managed load balancer is used and this field is not specified, a fixed IP will
128-
// be dynamically allocated for the load balancer.
129-
// If a managed load balancer is not used AND the API server floating IP is disabled,
130-
// this field MUST be specified and should correspond to a pre-allocated port that
131-
// holds the fixed IP to be used as a VIP.
132-
// +optional
133-
APIServerFixedIP optional.String `json:"apiServerFixedIP,omitempty"`
134-
135-
// apiServerPort is the port on which the listener on the APIServer
136-
// will be created. If specified, it must be an integer between 0 and 65535.
137-
// +optional
138-
APIServerPort optional.UInt16 `json:"apiServerPort,omitempty"`
100+
APIServer *APIServer `json:"apiServer,omitempty"`
139101

140102
// managedSecurityGroups determines whether OpenStack security groups for the cluster
141103
// will be managed by the OpenStack provider or whether pre-existing security groups will
@@ -189,6 +151,41 @@ type OpenStackClusterSpec struct {
189151
IdentityRef OpenStackIdentityReference `json:"identityRef"`
190152
}
191153

154+
type APIServer struct {
155+
// port is the port on which the API server listener will be created.
156+
// If specified, it must be an integer between 0 and 65535.
157+
// +optional
158+
Port optional.UInt16 `json:"port,omitempty"`
159+
160+
// fixedIP is the fixed IP which will be associated with the API server.
161+
// In the case where the API server has a floating IP but not a managed
162+
// load balancer, this field is not used.
163+
// If a managed load balancer is used and this field is not specified, a
164+
// fixed IP will be dynamically allocated for the load balancer.
165+
// If a managed load balancer is not used AND the floating IP is disabled,
166+
// this field MUST be specified and should correspond to a pre-allocated
167+
// port that holds the fixed IP to be used as a VIP.
168+
// +optional
169+
FixedIP optional.String `json:"fixedIP,omitempty"`
170+
171+
// floatingIP is the floating IP which will be associated with the API server.
172+
// The floating IP will be created if it does not already exist.
173+
// If not specified, a new floating IP is allocated.
174+
// This field is not used if DisableFloatingIP is set to true.
175+
// +optional
176+
FloatingIP optional.String `json:"floatingIP,omitempty"`
177+
178+
// disableFloatingIP determines whether or not to attempt to attach a
179+
// floating IP to the API server.
180+
// +optional
181+
DisableFloatingIP optional.Bool `json:"disableFloatingIP,omitempty"`
182+
183+
// managedLoadBalancer configures the optional LoadBalancer for the API server.
184+
// If not specified, no load balancer will be created.
185+
// +optional
186+
ManagedLoadBalancer *APIServerLoadBalancer `json:"managedLoadBalancer,omitempty"`
187+
}
188+
192189
// ClusterInitialization represents the initialization status of the cluster.
193190
type ClusterInitialization struct {
194191
// provisioned is set to true when the initial provisioning of the cluster infrastructure is completed.
@@ -363,3 +360,38 @@ func (c *OpenStackCluster) GetIdentityRef() (*string, *OpenStackIdentityReferenc
363360
func init() {
364361
objectTypes = append(objectTypes, &OpenStackCluster{}, &OpenStackClusterList{})
365362
}
363+
364+
func (a *APIServer) GetManagedLoadBalancer() *APIServerLoadBalancer {
365+
if a == nil {
366+
return nil
367+
}
368+
return a.ManagedLoadBalancer
369+
}
370+
371+
func (a *APIServer) GetDisableFloatingIP() *bool {
372+
if a == nil {
373+
return nil
374+
}
375+
return a.DisableFloatingIP
376+
}
377+
378+
func (a *APIServer) GetFloatingIP() *string {
379+
if a == nil {
380+
return nil
381+
}
382+
return a.FloatingIP
383+
}
384+
385+
func (a *APIServer) GetFixedIP() *string {
386+
if a == nil {
387+
return nil
388+
}
389+
return a.FixedIP
390+
}
391+
392+
func (a *APIServer) GetPort() *uint16 {
393+
if a == nil {
394+
return nil
395+
}
396+
return a.Port
397+
}

0 commit comments

Comments
 (0)