Skip to content

Commit be0ddb6

Browse files
committed
Fix MAPI->CAPI conversion not adding ControlPlane label
1 parent 4b37064 commit be0ddb6

File tree

3 files changed

+139
-1
lines changed

3 files changed

+139
-1
lines changed

pkg/controllers/machinesync/aws_test.go

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package machinesync
1818

1919
import (
2020
"context"
21+
"encoding/json"
2122

2223
. "github.com/onsi/ginkgo/v2"
2324
. "github.com/onsi/gomega"
@@ -91,7 +92,7 @@ var _ = Describe("AWS load balancer validation during MAPI->CAPI conversion", fu
9192
Expect(k8sClient.Create(ctx, capiNamespace)).To(Succeed())
9293

9394
infrastructureName = "cluster-aws-lb"
94-
awsClusterBuilder = awsv1resourcebuilder.AWSCluster().WithNamespace(capiNamespace.GetName()).WithName(infrastructureName)
95+
awsClusterBuilder = awsv1resourcebuilder.AWSCluster().WithNamespace(capiNamespace.GetName()).WithName(infrastructureName).WithRegion("us-east-1")
9596

9697
// Create CAPI Cluster that all tests will use
9798
capiCluster := clusterv1resourcebuilder.Cluster().WithNamespace(capiNamespace.GetName()).WithName(infrastructureName).WithInfrastructureRef(&corev1.ObjectReference{
@@ -291,6 +292,86 @@ var _ = Describe("AWS load balancer validation during MAPI->CAPI conversion", fu
291292
capiMachine := clusterv1resourcebuilder.Machine().WithNamespace(capiNamespace.GetName()).WithName(mapiMachine.GetName()).Build()
292293
Eventually(k8sClient.Get(ctx, client.ObjectKeyFromObject(capiMachine), capiMachine), timeout).Should(Succeed())
293294
})
295+
296+
It("should preserve load balancers when toggling authoritativeAPI MAPI -> CAPI -> MAPI", func() {
297+
By("Creating AWSCluster with load balancers")
298+
loadBalancerSpec := &awsv1.AWSLoadBalancerSpec{
299+
Name: ptr.To("cluster-int"),
300+
LoadBalancerType: awsv1.LoadBalancerTypeNLB,
301+
}
302+
secondaryLoadBalancerSpec := &awsv1.AWSLoadBalancerSpec{
303+
Name: ptr.To("cluster-ext"),
304+
LoadBalancerType: awsv1.LoadBalancerTypeNLB,
305+
}
306+
awsCluster := awsClusterBuilder.WithControlPlaneLoadBalancer(loadBalancerSpec).WithSecondaryControlPlaneLoadBalancer(secondaryLoadBalancerSpec).Build()
307+
Expect(k8sClient.Create(ctx, awsCluster)).To(Succeed())
308+
309+
lbRefs := []mapiv1beta1.LoadBalancerReference{
310+
{Name: "cluster-int", Type: mapiv1beta1.NetworkLoadBalancerType},
311+
{Name: "cluster-ext", Type: mapiv1beta1.NetworkLoadBalancerType},
312+
}
313+
314+
By("Creating MAPI master machine with load balancers and authoritativeAPI: MachineAPI")
315+
mapiMachine := machinev1resourcebuilder.Machine().
316+
WithNamespace(mapiNamespace.GetName()).
317+
WithGenerateName("master-").
318+
AsMaster().
319+
WithProviderSpecBuilder(machinev1resourcebuilder.AWSProviderSpec().WithLoadBalancers(lbRefs)).
320+
Build()
321+
Expect(k8sClient.Create(ctx, mapiMachine)).To(Succeed())
322+
Eventually(k.UpdateStatus(mapiMachine, func() { mapiMachine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMachineAPI })).Should(Succeed())
323+
324+
By("Verifying MAPI -> CAPI sync is successful")
325+
Eventually(k.Object(mapiMachine), timeout).Should(
326+
HaveField("Status.Conditions", ContainElement(
327+
SatisfyAll(
328+
HaveField("Type", Equal(consts.SynchronizedCondition)),
329+
HaveField("Status", Equal(corev1.ConditionTrue)),
330+
HaveField("Reason", Equal("ResourceSynchronized")),
331+
HaveField("Message", Equal("Successfully synchronized MAPI Machine to CAPI")),
332+
))),
333+
)
334+
335+
By("Capturing original providerSpec")
336+
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(mapiMachine), mapiMachine)).To(Succeed())
337+
originalProviderSpec := mapiMachine.Spec.ProviderSpec.Value.DeepCopy()
338+
339+
By("Switching authoritativeAPI to ClusterAPI")
340+
transitionToAuthoritativeAPI(ctx, k8sClient, k, mapiMachine, mapiv1beta1.MachineAuthorityClusterAPI, timeout)
341+
342+
By("Verifying CAPI -> MAPI sync is successful")
343+
Eventually(k.Object(mapiMachine), timeout).Should(
344+
HaveField("Status.Conditions", ContainElement(
345+
SatisfyAll(
346+
HaveField("Type", Equal(consts.SynchronizedCondition)),
347+
HaveField("Status", Equal(corev1.ConditionTrue)),
348+
HaveField("Reason", Equal("ResourceSynchronized")),
349+
HaveField("Message", Equal("Successfully synchronized CAPI Machine to MAPI")),
350+
))),
351+
)
352+
353+
By("Switching authoritativeAPI back to MachineAPI")
354+
transitionToAuthoritativeAPI(ctx, k8sClient, k, mapiMachine, mapiv1beta1.MachineAuthorityMachineAPI, timeout)
355+
356+
By("Verifying MAPI -> CAPI sync is successful")
357+
Eventually(k.Object(mapiMachine), timeout).Should(
358+
HaveField("Status.Conditions", ContainElement(
359+
SatisfyAll(
360+
HaveField("Type", Equal(consts.SynchronizedCondition)),
361+
HaveField("Status", Equal(corev1.ConditionTrue)),
362+
HaveField("Reason", Equal("ResourceSynchronized")),
363+
HaveField("Message", Equal("Successfully synchronized MAPI Machine to CAPI")),
364+
))),
365+
)
366+
367+
By("Verifying load balancers are unchanged after round trip")
368+
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(mapiMachine), mapiMachine)).To(Succeed())
369+
370+
var originalSpec, finalSpec mapiv1beta1.AWSMachineProviderConfig
371+
Expect(json.Unmarshal(originalProviderSpec.Raw, &originalSpec)).To(Succeed())
372+
Expect(json.Unmarshal(mapiMachine.Spec.ProviderSpec.Value.Raw, &finalSpec)).To(Succeed())
373+
Expect(finalSpec.LoadBalancers).To(Equal(originalSpec.LoadBalancers), "load balancers should not change after round trip")
374+
})
294375
})
295376

296377
var _ = Describe("validateLoadBalancerReferencesAgainstExpected", func() {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
Copyright 2025 Red Hat, Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package machinesync
18+
19+
import (
20+
"context"
21+
22+
. "github.com/onsi/gomega"
23+
mapiv1beta1 "github.com/openshift/api/machine/v1beta1"
24+
"sigs.k8s.io/controller-runtime/pkg/client"
25+
"sigs.k8s.io/controller-runtime/pkg/envtest/komega"
26+
)
27+
28+
// transitionToAuthoritativeAPI transitions the AuthoritativeAPI of a machine to the target API.
29+
// It sets spec.AuthoritativeAPI to the target API and then transitions status.AuthoritativeAPI through the Migrating state to the target API.
30+
// This simulates the migration controller behavior for unit tests.
31+
func transitionToAuthoritativeAPI(ctx context.Context, k8sClient client.Client, k komega.Komega, machine *mapiv1beta1.Machine, targetAPI mapiv1beta1.MachineAuthority, timeout interface{}) {
32+
if machine.Spec.AuthoritativeAPI == targetAPI && machine.Status.AuthoritativeAPI == targetAPI {
33+
return
34+
}
35+
36+
if machine.Spec.AuthoritativeAPI != targetAPI {
37+
Eventually(k.Update(machine, func() {
38+
machine.Spec.AuthoritativeAPI = targetAPI
39+
}), timeout).Should(Succeed(), "Failed to update Machine spec.AuthoritativeAPI to %s", targetAPI)
40+
}
41+
42+
// AuthoritativeAPI must transition through Migrating state
43+
if machine.Status.AuthoritativeAPI != mapiv1beta1.MachineAuthorityMigrating {
44+
Eventually(k.UpdateStatus(machine, func() {
45+
machine.Status.AuthoritativeAPI = mapiv1beta1.MachineAuthorityMigrating
46+
}), timeout).Should(Succeed(), "Failed to transition Machine status.AuthoritativeAPI to Migrating")
47+
}
48+
49+
Eventually(k.UpdateStatus(machine, func() {
50+
machine.Status.AuthoritativeAPI = targetAPI
51+
}), timeout).Should(Succeed(), "Failed to transition Machine status.AuthoritativeAPI to %s", targetAPI)
52+
}

pkg/conversion/mapi2capi/util.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ func convertMAPILabelsToCAPI(mapiLabels map[string]string) map[string]string {
5555
capiLabelKey, capiLabelVal := transformFunc(mapiLabelVal)
5656
capiLabels[capiLabelKey] = capiLabelVal
5757

58+
// If the machine role is "master", also add the control-plane label.
59+
if mapiLabelKey == "machine.openshift.io/cluster-api-machine-role" && mapiLabelVal == "master" {
60+
capiLabels[clusterv1.MachineControlPlaneLabel] = ""
61+
}
62+
5863
continue
5964
}
6065

0 commit comments

Comments
 (0)