Skip to content

Commit 5126d8f

Browse files
committed
Implement status sync and conversion for AWSMachines and provider status
1 parent 69085f0 commit 5126d8f

File tree

15 files changed

+1224
-265
lines changed

15 files changed

+1224
-265
lines changed

pkg/controllers/machinesetsync/machineset_sync_controller.go

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ import (
5151
openstackv1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
5252
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
5353
"sigs.k8s.io/cluster-api/util/annotations"
54-
"sigs.k8s.io/cluster-api/util/conditions"
55-
conditionsv1beta2 "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
5654
ctrl "sigs.k8s.io/controller-runtime"
5755
"sigs.k8s.io/controller-runtime/pkg/builder"
5856
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -1000,26 +998,13 @@ func (r *MachineSetSyncReconciler) ensureMAPIMachineSetStatusUpdated(ctx context
1000998
func setChangedCAPIMachineSetStatusFields(existingCAPIMachineSet, convertedCAPIMachineSet *clusterv1.MachineSet) {
1001999
// convertedCAPIMachine holds the computed and desired status changes converted from the source MAPI machine, so apply them to the existing existingCAPIMachine.
10021000
// Merge the v1beta1 conditions.
1003-
for _, condition := range convertedCAPIMachineSet.Status.Conditions {
1004-
conditions.Set(existingCAPIMachineSet, &condition)
1005-
}
1001+
util.EnsureCAPIConditions(existingCAPIMachineSet, convertedCAPIMachineSet)
10061002

10071003
// Copy them back to the convertedCAPIMachine.
10081004
convertedCAPIMachineSet.Status.Conditions = existingCAPIMachineSet.Status.Conditions
10091005

10101006
// Merge the v1beta2 conditions.
1011-
if convertedCAPIMachineSet.Status.V1Beta2 != nil {
1012-
if existingCAPIMachineSet.Status.V1Beta2 == nil {
1013-
existingCAPIMachineSet.Status.V1Beta2 = &clusterv1.MachineSetV1Beta2Status{}
1014-
}
1015-
1016-
for _, condition := range convertedCAPIMachineSet.Status.V1Beta2.Conditions {
1017-
conditionsv1beta2.Set(existingCAPIMachineSet, condition)
1018-
}
1019-
1020-
// Copy them back to the convertedCAPIMachine.
1021-
convertedCAPIMachineSet.Status.V1Beta2.Conditions = existingCAPIMachineSet.Status.V1Beta2.Conditions
1022-
}
1007+
util.EnsureCAPIV1Beta2Conditions(existingCAPIMachineSet, convertedCAPIMachineSet)
10231008

10241009
// Finally overwrite the entire existingCAPIMachine status with the convertedCAPIMachine status.
10251010
existingCAPIMachineSet.Status = convertedCAPIMachineSet.Status
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
"fmt"
21+
22+
configv1 "github.com/openshift/api/config/v1"
23+
mapiv1beta1 "github.com/openshift/api/machine/v1beta1"
24+
"github.com/openshift/cluster-capi-operator/pkg/conversion/capi2mapi"
25+
"github.com/openshift/cluster-capi-operator/pkg/conversion/mapi2capi"
26+
"github.com/openshift/cluster-capi-operator/pkg/util"
27+
)
28+
29+
// setChangedMAPIMachineProviderStatusFields unmarshals the existing and converted ProviderStatus, copies over the fields and marshals it back to the existingMAPIMachine.
30+
func setChangedMAPIMachineProviderStatusFields(platform configv1.PlatformType, existingMAPIMachine, convertedMAPIMachine *mapiv1beta1.Machine) error {
31+
var newProviderStatus interface{}
32+
33+
switch platform {
34+
case configv1.AWSPlatformType:
35+
existingStatus, err := mapi2capi.AWSProviderStatusFromRawExtension(existingMAPIMachine.Status.ProviderStatus)
36+
if err != nil {
37+
return fmt.Errorf("unable to convert RawExtension to AWS ProviderStatus: %w", err)
38+
}
39+
40+
convertedStatus, err := mapi2capi.AWSProviderStatusFromRawExtension(convertedMAPIMachine.Status.ProviderStatus)
41+
if err != nil {
42+
return fmt.Errorf("unable to convert RawExtension to AWS ProviderStatus: %w", err)
43+
}
44+
45+
for i := range convertedStatus.Conditions {
46+
existingStatus.Conditions = util.SetMAPIProviderCondition(existingStatus.Conditions, &convertedStatus.Conditions[i])
47+
}
48+
49+
convertedStatus.Conditions = existingStatus.Conditions
50+
51+
newProviderStatus = convertedStatus
52+
case configv1.OpenStackPlatformType:
53+
// TODO(openstack): implement
54+
return nil
55+
case configv1.PowerVSPlatformType:
56+
// TODO(powervs): implement
57+
return nil
58+
}
59+
60+
rawExtension, err := capi2mapi.RawExtensionFromInterface(newProviderStatus)
61+
if err != nil {
62+
return fmt.Errorf("unable to convert ProviderStatus to RawExtension: %w", err)
63+
}
64+
65+
existingMAPIMachine.Status.ProviderStatus = rawExtension
66+
67+
return nil
68+
}

pkg/controllers/machinesync/machine_sync_controller.go

Lines changed: 21 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,9 @@ import (
4747
"k8s.io/client-go/tools/record"
4848
"k8s.io/utils/ptr"
4949
awsv1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
50-
ibmpowervsv1 "sigs.k8s.io/cluster-api-provider-ibmcloud/api/v1beta2"
5150
openstackv1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
5251
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
5352
"sigs.k8s.io/cluster-api/util/annotations"
54-
"sigs.k8s.io/cluster-api/util/conditions"
55-
conditionsv1beta2 "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
5653
"sigs.k8s.io/cluster-api/util/labels/format"
5754
ctrl "sigs.k8s.io/controller-runtime"
5855
"sigs.k8s.io/controller-runtime/pkg/builder"
@@ -93,6 +90,9 @@ const (
9390
)
9491

9592
var (
93+
// errAssertingInfrasMachineClientObject is returned when we encounter an issue asserting a runtime.Object into a client.Object.
94+
errAssertingInfrasMachineClientObject = errors.New("error asserting the Cluster API Infrastructure Machine to client.Object")
95+
9696
// errAssertingCAPIAWSMachine is returned when we encounter an issue asserting a client.Object into an AWSMachine.
9797
errAssertingCAPIAWSMachine = errors.New("error asserting the Cluster API AWSMachine object")
9898

@@ -622,114 +622,6 @@ func (r *MachineSyncReconciler) convertCAPIToMAPIMachine(capiMachine *clusterv1.
622622
}
623623
}
624624

625-
// createOrUpdateCAPIInfraMachine creates a CAPI infra machine from a MAPI machine, or updates if it exists and it is out of date.
626-
//
627-
//nolint:funlen,unparam
628-
func (r *MachineSyncReconciler) createOrUpdateCAPIInfraMachine(ctx context.Context, mapiMachine *mapiv1beta1.Machine, infraMachine client.Object, newCAPIInfraMachine client.Object) (ctrl.Result, bool, error) {
629-
logger := logf.FromContext(ctx)
630-
// This variable tracks whether or not we are still progressing
631-
// towards syncronizing the MAPI machine with the CAPI infra machine.
632-
// It is then passed up the stack so the syncronized condition can be set accordingly.
633-
syncronizationIsProgressing := false
634-
635-
alreadyExists := false
636-
637-
//nolint: nestif
638-
if util.IsNilObject(infraMachine) {
639-
if err := r.Create(ctx, newCAPIInfraMachine); err != nil && !apierrors.IsAlreadyExists(err) {
640-
logger.Error(err, "Failed to create Cluster API infra machine")
641-
createErr := fmt.Errorf("failed to create Cluster API infra machine: %w", err)
642-
643-
if condErr := r.applySynchronizedConditionWithPatch(ctx, mapiMachine, corev1.ConditionFalse, reasonFailedToCreateCAPIInfraMachine, createErr.Error(), nil); condErr != nil {
644-
return ctrl.Result{}, syncronizationIsProgressing, utilerrors.NewAggregate([]error{createErr, condErr})
645-
}
646-
647-
return ctrl.Result{}, syncronizationIsProgressing, createErr
648-
} else if apierrors.IsAlreadyExists(err) {
649-
// this handles the case where the CAPI Machine is not present, so we can't resolve the
650-
// infraMachine ref from it - but the InfraMachine exists. (e.g a user deletes the CAPI machine manually).
651-
// This would lead to the call to fetchCAPIInfraResources returning nil for the infraMachine.
652-
alreadyExists = true
653-
} else {
654-
logger.Info("Successfully created Cluster API infra machine")
655-
656-
return ctrl.Result{}, syncronizationIsProgressing, nil
657-
}
658-
}
659-
660-
if alreadyExists {
661-
if err := r.Get(ctx, client.ObjectKeyFromObject(newCAPIInfraMachine), infraMachine); err != nil {
662-
logger.Error(err, "Failed to get Cluster API infra machine")
663-
getErr := fmt.Errorf("failed to get Cluster API infra machine: %w", err)
664-
665-
if condErr := r.applySynchronizedConditionWithPatch(ctx, mapiMachine, corev1.ConditionFalse, reasonFailedToGetCAPIInfraResources, getErr.Error(), nil); condErr != nil {
666-
return ctrl.Result{}, syncronizationIsProgressing, utilerrors.NewAggregate([]error{getErr, condErr})
667-
}
668-
669-
return ctrl.Result{}, syncronizationIsProgressing, getErr
670-
}
671-
}
672-
673-
capiInfraMachinesDiff, err := compareCAPIInfraMachines(r.Platform, infraMachine, newCAPIInfraMachine)
674-
if err != nil {
675-
logger.Error(err, "Failed to check Cluster API infra machine diff")
676-
updateErr := fmt.Errorf("failed to check Cluster API infra machine diff: %w", err)
677-
678-
if condErr := r.applySynchronizedConditionWithPatch(
679-
ctx, mapiMachine, corev1.ConditionFalse, reasonFailedToUpdateCAPIInfraMachine, updateErr.Error(), nil); condErr != nil {
680-
return ctrl.Result{}, syncronizationIsProgressing, utilerrors.NewAggregate([]error{updateErr, condErr})
681-
}
682-
683-
return ctrl.Result{}, syncronizationIsProgressing, updateErr
684-
}
685-
686-
if len(capiInfraMachinesDiff) == 0 {
687-
logger.Info("No changes detected in Cluster API infra machine")
688-
return ctrl.Result{}, syncronizationIsProgressing, nil
689-
}
690-
691-
logger.Info("Deleting the corresponding Cluster API infra machine as it is out of date, it will be recreated", "diff", fmt.Sprintf("%+v", capiInfraMachinesDiff))
692-
693-
if err := r.Delete(ctx, infraMachine); err != nil {
694-
logger.Error(err, "Failed to delete Cluster API infra machine")
695-
696-
deleteErr := fmt.Errorf("failed to delete Cluster API infra machine: %w", err)
697-
698-
if condErr := r.applySynchronizedConditionWithPatch(
699-
ctx, mapiMachine, corev1.ConditionFalse, reasonFailedToUpdateCAPIInfraMachine, deleteErr.Error(), nil); condErr != nil {
700-
return ctrl.Result{}, syncronizationIsProgressing, utilerrors.NewAggregate([]error{deleteErr, condErr})
701-
}
702-
703-
return ctrl.Result{}, syncronizationIsProgressing, deleteErr
704-
}
705-
706-
// Remove finalizers from the deleting CAPI infraMachine, it is not authoritative.
707-
infraMachine.SetFinalizers(nil)
708-
709-
if err := r.Update(ctx, infraMachine); err != nil {
710-
logger.Error(err, "Failed to remove finalizer for deleting Cluster API infra machine")
711-
712-
deleteErr := fmt.Errorf("failed to remove finalizer for deleting Cluster API infra machine: %w", err)
713-
714-
if condErr := r.applySynchronizedConditionWithPatch(
715-
ctx, mapiMachine, corev1.ConditionFalse, reasonFailedToUpdateCAPIInfraMachine, deleteErr.Error(), nil); condErr != nil {
716-
return ctrl.Result{}, syncronizationIsProgressing, utilerrors.NewAggregate([]error{deleteErr, condErr})
717-
}
718-
719-
return ctrl.Result{}, syncronizationIsProgressing, deleteErr
720-
}
721-
722-
// The outdated outdated CAPI infra machine has been deleted.
723-
// We will try and recreate an up-to-date one later.
724-
logger.Info("Successfully deleted outdated Cluster API infra machine")
725-
726-
// Set the syncronized as progressing to signal the caller
727-
// we are still progressing and aren't fully synced yet.
728-
syncronizationIsProgressing = true
729-
730-
return ctrl.Result{}, syncronizationIsProgressing, nil
731-
}
732-
733625
// ensureCAPIMachine creates a new CAPI machine if one doesn't exist.
734626
func (r *MachineSyncReconciler) ensureCAPIMachine(ctx context.Context, sourceMAPIMachine *mapiv1beta1.Machine, existingCAPIMachine, convertedCAPIMachine *clusterv1.Machine) (*clusterv1.Machine, error) {
735627
// If there is an existing CAPI machine, no need to create one.
@@ -1410,79 +1302,6 @@ func compareMAPIMachines(a, b *mapiv1beta1.Machine) (map[string]any, error) {
14101302
return diff, nil
14111303
}
14121304

1413-
// compareCAPIInfraMachines compares CAPI infra machines a and b, and returns a list of differences, or none if there are none.
1414-
//
1415-
//nolint:funlen
1416-
func compareCAPIInfraMachines(platform configv1.PlatformType, infraMachine1, infraMachine2 client.Object) (map[string]any, error) {
1417-
switch platform {
1418-
case configv1.AWSPlatformType:
1419-
typedInfraMachine1, ok := infraMachine1.(*awsv1.AWSMachine)
1420-
if !ok {
1421-
return nil, errAssertingCAPIAWSMachine
1422-
}
1423-
1424-
typedinfraMachine2, ok := infraMachine2.(*awsv1.AWSMachine)
1425-
if !ok {
1426-
return nil, errAssertingCAPIAWSMachine
1427-
}
1428-
1429-
diff := make(map[string]any)
1430-
if diffSpec := deep.Equal(typedInfraMachine1.Spec, typedinfraMachine2.Spec); len(diffSpec) > 0 {
1431-
diff[".spec"] = diffSpec
1432-
}
1433-
1434-
if diffMetadata := util.ObjectMetaEqual(typedInfraMachine1.ObjectMeta, typedinfraMachine2.ObjectMeta); len(diffMetadata) > 0 {
1435-
diff[".metadata"] = diffMetadata
1436-
}
1437-
1438-
return diff, nil
1439-
case configv1.OpenStackPlatformType:
1440-
typedInfraMachine1, ok := infraMachine1.(*openstackv1.OpenStackMachine)
1441-
if !ok {
1442-
return nil, errAssertingCAPIOpenStackMachine
1443-
}
1444-
1445-
typedinfraMachine2, ok := infraMachine2.(*openstackv1.OpenStackMachine)
1446-
if !ok {
1447-
return nil, errAssertingCAPIOpenStackMachine
1448-
}
1449-
1450-
diff := make(map[string]any)
1451-
if diffSpec := deep.Equal(typedInfraMachine1.Spec, typedinfraMachine2.Spec); len(diffSpec) > 0 {
1452-
diff[".spec"] = diffSpec
1453-
}
1454-
1455-
if diffMetadata := util.ObjectMetaEqual(typedInfraMachine1.ObjectMeta, typedinfraMachine2.ObjectMeta); len(diffMetadata) > 0 {
1456-
diff[".metadata"] = diffMetadata
1457-
}
1458-
1459-
return diff, nil
1460-
case configv1.PowerVSPlatformType:
1461-
typedInfraMachine1, ok := infraMachine1.(*ibmpowervsv1.IBMPowerVSMachine)
1462-
if !ok {
1463-
return nil, errAssertingCAPIIBMPowerVSMachine
1464-
}
1465-
1466-
typedinfraMachine2, ok := infraMachine2.(*ibmpowervsv1.IBMPowerVSMachine)
1467-
if !ok {
1468-
return nil, errAssertingCAPIIBMPowerVSMachine
1469-
}
1470-
1471-
diff := make(map[string]any)
1472-
if diffSpec := deep.Equal(typedInfraMachine1.Spec, typedinfraMachine2.Spec); len(diffSpec) > 0 {
1473-
diff[".spec"] = diffSpec
1474-
}
1475-
1476-
if diffMetadata := util.ObjectMetaEqual(typedInfraMachine1.ObjectMeta, typedinfraMachine2.ObjectMeta); len(diffMetadata) > 0 {
1477-
diff[".metadata"] = diffMetadata
1478-
}
1479-
1480-
return diff, nil
1481-
default:
1482-
return nil, fmt.Errorf("%w: %s", errPlatformNotSupported, platform)
1483-
}
1484-
}
1485-
14861305
// ensureCAPIMachineStatusUpdated updates the CAPI machine status if changes are detected and conditions are met.
14871306
func (r *MachineSyncReconciler) ensureCAPIMachineStatusUpdated(ctx context.Context, mapiMachine *mapiv1beta1.Machine, existingCAPIMachine, convertedCAPIMachine, updatedOrCreatedCAPIMachine *clusterv1.Machine, capiMachinesDiff map[string]any, specUpdated bool) (bool, error) {
14881307
logger := logf.FromContext(ctx)
@@ -1580,6 +1399,10 @@ func (r *MachineSyncReconciler) ensureMAPIMachineStatusUpdated(ctx context.Conte
15801399
// Set the changed MAPI machine status fields from the converted MAPI machine.
15811400
setChangedMAPIMachineStatusFields(existingMAPIMachine, convertedMAPIMachine)
15821401

1402+
if err := setChangedMAPIMachineProviderStatusFields(r.Platform, existingMAPIMachine, convertedMAPIMachine); err != nil {
1403+
return false, fmt.Errorf("failed to set provider status fields: %w", err)
1404+
}
1405+
15831406
// Here we would've updated the observed generation to match the updated source API object generation.
15841407
// MAPI Machine does not have the observed generation field.
15851408

@@ -1638,26 +1461,10 @@ func (r *MachineSyncReconciler) ensureMAPIMachineSpecUpdated(ctx context.Context
16381461
func setChangedCAPIMachineStatusFields(existingCAPIMachine, convertedCAPIMachine *clusterv1.Machine) {
16391462
// convertedCAPIMachine holds the computed and desired status changes converted from the source MAPI machine, so apply them to the existing existingCAPIMachine.
16401463
// Merge the v1beta1 conditions.
1641-
for i := range convertedCAPIMachine.Status.Conditions {
1642-
conditions.Set(existingCAPIMachine, &convertedCAPIMachine.Status.Conditions[i])
1643-
}
1644-
1645-
// Copy them back to the convertedCAPIMachine.
1646-
convertedCAPIMachine.Status.Conditions = existingCAPIMachine.Status.Conditions
1464+
util.EnsureCAPIConditions(existingCAPIMachine, convertedCAPIMachine)
16471465

16481466
// Merge the v1beta2 conditions.
1649-
if convertedCAPIMachine.Status.V1Beta2 != nil {
1650-
if existingCAPIMachine.Status.V1Beta2 == nil {
1651-
existingCAPIMachine.Status.V1Beta2 = &clusterv1.MachineV1Beta2Status{}
1652-
}
1653-
1654-
for i := range convertedCAPIMachine.Status.V1Beta2.Conditions {
1655-
conditionsv1beta2.Set(existingCAPIMachine, convertedCAPIMachine.Status.V1Beta2.Conditions[i])
1656-
}
1657-
1658-
// Copy them back to the convertedCAPIMachine.
1659-
convertedCAPIMachine.Status.V1Beta2.Conditions = existingCAPIMachine.Status.V1Beta2.Conditions
1660-
}
1467+
util.EnsureCAPIV1Beta2Conditions(existingCAPIMachine, convertedCAPIMachine)
16611468

16621469
// Finally overwrite the entire existingCAPIMachine status with the convertedCAPIMachine status.
16631470
existingCAPIMachine.Status = convertedCAPIMachine.Status
@@ -1701,9 +1508,18 @@ func (r *MachineSyncReconciler) applySynchronizedConditionWithPatch(ctx context.
17011508

17021509
// hasSpecOrMetadataOrProviderSpecChanges checks if there are spec, metadata, or provider spec changes in the diff.
17031510
func hasSpecOrMetadataOrProviderSpecChanges(diff map[string]any) bool {
1704-
_, hasSpec := diff[".spec"]
1705-
_, hasMetadata := diff[".metadata"]
17061511
_, hasProviderSpec := diff[".providerSpec"]
1512+
return hasSpecChanges(diff) || hasMetadataChanges(diff) || hasProviderSpec
1513+
}
1514+
1515+
// hasSpecChanges checks if there are spec changes in the diff.
1516+
func hasSpecChanges(diff map[string]any) bool {
1517+
_, hasSpec := diff[".spec"]
1518+
return hasSpec
1519+
}
17071520

1708-
return hasSpec || hasMetadata || hasProviderSpec
1521+
// hasSpecChanges checks if there are spec changes in the diff.
1522+
func hasMetadataChanges(diff map[string]any) bool {
1523+
_, hasSpec := diff[".metadata"]
1524+
return hasSpec
17091525
}

0 commit comments

Comments
 (0)