Skip to content

Commit eea205c

Browse files
Merge pull request #365 from damdo/machine-status-conversion-and-syncing
OCPCLOUD-2995: Machine status conversion and syncing
2 parents 4a81309 + 737e205 commit eea205c

File tree

11 files changed

+1779
-176
lines changed

11 files changed

+1779
-176
lines changed

pkg/controllers/machinesetsync/machineset_sync_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1072,7 +1072,7 @@ func setChangedMAPIMachineSetStatusFields(existingMAPIMachineSet, convertedMAPIM
10721072
existingMAPIMachineSet.Status.ErrorMessage = convertedMAPIMachineSet.Status.ErrorMessage
10731073

10741074
for i := range convertedMAPIMachineSet.Status.Conditions {
1075-
existingMAPIMachineSet.Status.Conditions = util.SetMAPIMachineSetCondition(existingMAPIMachineSet.Status.Conditions, &convertedMAPIMachineSet.Status.Conditions[i])
1075+
existingMAPIMachineSet.Status.Conditions = util.SetMAPICondition(existingMAPIMachineSet.Status.Conditions, &convertedMAPIMachineSet.Status.Conditions[i])
10761076
}
10771077
}
10781078

pkg/controllers/machinesync/machine_sync_controller.go

Lines changed: 396 additions & 131 deletions
Large diffs are not rendered by default.

pkg/controllers/machinesync/machine_sync_controller_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ var _ = Describe("With a running MachineSync Reconciler", func() {
127127
// reference it on the CAPI Machine
128128
capaMachineBuilder = awsv1resourcebuilder.AWSMachine().
129129
WithNamespace(capiNamespace.GetName()).
130-
WithName("machine-template")
130+
WithName("foo")
131131

132132
mapiMachineSetBuilder = machinev1resourcebuilder.MachineSet().
133133
WithNamespace(mapiNamespace.GetName()).
@@ -138,7 +138,7 @@ var _ = Describe("With a running MachineSync Reconciler", func() {
138138
// reference it on the CAPI MachineSet
139139
capaMachineTemplateBuilder := awsv1resourcebuilder.AWSMachineTemplate().
140140
WithNamespace(capiNamespace.GetName()).
141-
WithName("machine-template")
141+
WithName("foo")
142142

143143
capaMachineTemplate := capaMachineTemplateBuilder.Build()
144144

@@ -168,7 +168,7 @@ var _ = Describe("With a running MachineSync Reconciler", func() {
168168

169169
mapiMachineBuilder = machinev1resourcebuilder.Machine().
170170
WithNamespace(mapiNamespace.GetName()).
171-
WithGenerateName("foo").
171+
WithName("foo").
172172
WithProviderSpecBuilder(machinev1resourcebuilder.AWSProviderSpec().WithLoadBalancers(nil))
173173

174174
capiMachineBuilder = clusterv1resourcebuilder.Machine().
@@ -333,11 +333,11 @@ var _ = Describe("With a running MachineSync Reconciler", func() {
333333
BeforeEach(func() {
334334

335335
By("Creating the MAPI machine")
336-
mapiMachine = mapiMachineBuilder.WithName("test-machine").Build()
336+
mapiMachine = mapiMachineBuilder.Build()
337337
Eventually(k8sClient.Create(ctx, mapiMachine)).Should(Succeed(), "mapi machine should be able to be created")
338338

339339
By("Creating the CAPI Machine")
340-
capiMachine = capiMachineBuilder.WithName("test-machine").Build()
340+
capiMachine = capiMachineBuilder.Build()
341341
Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed(), "capi machine should be able to be created")
342342

343343
By("Setting the MAPI machine AuthoritativeAPI to Cluster API")
@@ -514,21 +514,21 @@ var _ = Describe("With a running MachineSync Reconciler", func() {
514514
Context("when the MAPI machine does not exist and the CAPI machine does", func() {
515515
Context("and there is no CAPI machineSet owning the machine", func() {
516516
BeforeEach(func() {
517-
capiMachine = capiMachineBuilder.WithName("test-machine-no-machineset").Build()
517+
By("Creating the CAPI machine")
518+
capiMachine = capiMachineBuilder.Build()
518519
Eventually(k8sClient.Create(ctx, capiMachine)).Should(Succeed(), "capi machine should be able to be created")
519520

520-
capaMachine = capaMachineBuilder.WithName("test-machine-no-machineset").WithOwnerReferences([]metav1.OwnerReference{
521-
{
521+
By("Updating the CAPA machine adding the CAPI machine as an owner")
522+
Eventually(k.Update(capaMachine, func() {
523+
capaMachine.OwnerReferences = append(capaMachine.OwnerReferences, metav1.OwnerReference{
522524
Kind: machineKind,
523525
APIVersion: clusterv1.GroupVersion.String(),
524526
Name: capiMachine.Name,
525527
UID: capiMachine.UID,
526528
BlockOwnerDeletion: ptr.To(true),
527529
Controller: ptr.To(false),
528-
},
529-
}).Build()
530-
531-
Eventually(k8sClient.Create(ctx, capaMachine)).Should(Succeed(), "capa machine should be able to be created")
530+
})
531+
})).Should(Succeed(), "capa machine should be able to be updated")
532532
})
533533

534534
It("should not make any changes to the CAPI machine", func() {

pkg/conversion/capi2mapi/machine.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ import (
2121

2222
mapiv1beta1 "github.com/openshift/api/machine/v1beta1"
2323

24+
corev1 "k8s.io/api/core/v1"
2425
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2526
"k8s.io/apimachinery/pkg/util/validation/field"
27+
"k8s.io/utils/ptr"
2628
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
29+
capierrors "sigs.k8s.io/cluster-api/errors"
2730
)
2831

2932
const (
@@ -36,6 +39,11 @@ func fromCAPIMachineToMAPIMachine(capiMachine *clusterv1.Machine) (*mapiv1beta1.
3639

3740
lifecycleHooks, capiMachineNonHookAnnotations := convertCAPILifecycleHookAnnotationsToMAPILifecycleHooksAndAnnotations(capiMachine.Annotations)
3841

42+
mapiMachineStatus, machineStatusErrs := convertCAPIMachineStatusToMAPI(capiMachine.Status)
43+
if len(machineStatusErrs) > 0 {
44+
errs = append(errs, machineStatusErrs...)
45+
}
46+
3947
mapiMachine := &mapiv1beta1.Machine{
4048
ObjectMeta: metav1.ObjectMeta{
4149
Name: capiMachine.Name,
@@ -55,6 +63,7 @@ func fromCAPIMachineToMAPIMachine(capiMachine *clusterv1.Machine) (*mapiv1beta1.
5563
// ProviderSpec: // ProviderSpec MUST NOT be populated here. It is added later by higher level fuctions.
5664
// Taints: // TODO(OCPCLOUD-2861): Taint propagation from Machines to Nodes is not yet implemented in CAPI.
5765
},
66+
Status: mapiMachineStatus,
5867
}
5968

6069
// Unused fields - Below this line are fields not used from the CAPI Machine.
@@ -99,6 +108,114 @@ func fromCAPIMachineToMAPIMachine(capiMachine *clusterv1.Machine) (*mapiv1beta1.
99108
return mapiMachine, nil
100109
}
101110

111+
// convertCAPIMachineStatusToMAPI converts a CAPI MachineStatus to MAPI format.
112+
func convertCAPIMachineStatusToMAPI(capiStatus clusterv1.MachineStatus) (mapiv1beta1.MachineStatus, field.ErrorList) {
113+
errs := field.ErrorList{}
114+
115+
addresses, addressesErr := convertCAPIMachineAddressesToMAPI(capiStatus.Addresses)
116+
if addressesErr != nil {
117+
errs = append(errs, addressesErr...)
118+
}
119+
120+
mapiStatus := mapiv1beta1.MachineStatus{
121+
NodeRef: capiStatus.NodeRef,
122+
LastUpdated: capiStatus.LastUpdated,
123+
// Conditions: // TODO(OCPCLOUD-3193): Add MAPI conditions when they are implemented.
124+
ErrorReason: convertCAPIMachineFailureReasonToMAPIErrorReason(capiStatus.FailureReason),
125+
ErrorMessage: convertCAPIMachineFailureMessageToMAPIErrorMessage(capiStatus.FailureMessage),
126+
Phase: convertCAPIMachinePhaseToMAPI(capiStatus.Phase),
127+
Addresses: addresses,
128+
129+
// LastOperation // this is MAPI-specific and not used in CAPI.
130+
// ObservedGeneration: // We don't set the observed generation at this stage as it is handled by the machineSync controller.
131+
// AuthoritativeAPI: // Ignore, this field as it is not present in CAPI.
132+
// SynchronizedGeneration: // Ignore, this field as it is not present in CAPI.
133+
}
134+
135+
// unused fields from CAPI MachineStatus
136+
// - NodeInfo: not present on the MAPI Machine status.
137+
// - CertificatesExpiryDate: not present on the MAPI Machine status.
138+
// - BootstrapReady: this is derived and not stored directly in MAPI.
139+
// - InfrastructureReady: this is derived and not stored directly in MAPI.
140+
// - Deletion: not present on the MAPI Machine status.
141+
// - V1Beta2: for now we use the V1Beta1 status fields to obtain the status of the MAPI Machine.
142+
143+
return mapiStatus, errs
144+
}
145+
146+
// convertCAPIMachineAddressesToMAPI converts CAPI machine addresses to MAPI format.
147+
func convertCAPIMachineAddressesToMAPI(capiAddresses clusterv1.MachineAddresses) ([]corev1.NodeAddress, field.ErrorList) {
148+
if capiAddresses == nil {
149+
return nil, nil
150+
}
151+
152+
errs := field.ErrorList{}
153+
mapiAddresses := make([]corev1.NodeAddress, 0, len(capiAddresses))
154+
155+
// Addresses are slightly different between MAPI/CAPI.
156+
for _, addr := range capiAddresses {
157+
switch addr.Type {
158+
case clusterv1.MachineHostName:
159+
mapiAddresses = append(mapiAddresses, corev1.NodeAddress{Type: corev1.NodeHostName, Address: addr.Address})
160+
case clusterv1.MachineExternalIP:
161+
mapiAddresses = append(mapiAddresses, corev1.NodeAddress{Type: corev1.NodeExternalIP, Address: addr.Address})
162+
case clusterv1.MachineInternalIP:
163+
mapiAddresses = append(mapiAddresses, corev1.NodeAddress{Type: corev1.NodeInternalIP, Address: addr.Address})
164+
case clusterv1.MachineExternalDNS:
165+
mapiAddresses = append(mapiAddresses, corev1.NodeAddress{Type: corev1.NodeExternalDNS, Address: addr.Address})
166+
case clusterv1.MachineInternalDNS:
167+
mapiAddresses = append(mapiAddresses, corev1.NodeAddress{Type: corev1.NodeInternalDNS, Address: addr.Address})
168+
default:
169+
errs = append(errs, field.Invalid(field.NewPath("status", "addresses"), string(addr.Type), string(addr.Type)+" unrecognized address type"))
170+
}
171+
}
172+
173+
return mapiAddresses, errs
174+
}
175+
176+
// convertCAPIMachinePhaseToMAPI converts CAPI machine phase to MAPI format.
177+
func convertCAPIMachinePhaseToMAPI(capiPhase string) *string {
178+
// Phase is slightly different between MAPI/CAPI.
179+
// In CAPI can be one of: Pending;Provisioning;Provisioned;Running;Deleting;Deleted;Failed;Unknown
180+
// In MAPI can be one of: Provisioning;Provisioned;Running;Deleting;Failed (missing Pending,Deleted,Unknown)
181+
switch capiPhase {
182+
case "":
183+
return nil // Empty is equivalent to nil in MAPI.
184+
case "Pending":
185+
return nil // Pending is not supported in MAPI but is is a very early state so we don't need to represent it.
186+
case "Deleted":
187+
return ptr.To("Deleting") // Deleted is not supported in MAPI but we can stay in Deleting until the machine is fully removed.
188+
case "Unknown":
189+
return nil // Unknown is not supported in MAPI but we can set it to nil until we know more.
190+
case "Provisioning", "Provisioned", "Running", "Deleting", "Failed":
191+
return &capiPhase // This is a supported phase so we can represent it in MAPI.
192+
default:
193+
return nil // This is an unknown phase so we can't represent it in MAPI.
194+
}
195+
}
196+
197+
// convertCAPIMachineFailureReasonToMAPIErrorReason converts CAPI MachineStatusError to MAPI MachineStatusError.
198+
func convertCAPIMachineFailureReasonToMAPIErrorReason(capiFailureReason *capierrors.MachineStatusError) *mapiv1beta1.MachineStatusError {
199+
if capiFailureReason == nil {
200+
return nil
201+
}
202+
203+
mapiErrorReason := mapiv1beta1.MachineStatusError(*capiFailureReason)
204+
205+
return &mapiErrorReason
206+
}
207+
208+
// convertCAPIMachineFailureMessageToMAPIErrorMessage converts CAPI MachineStatusError to MAPI MachineStatusError.
209+
func convertCAPIMachineFailureMessageToMAPIErrorMessage(capiFailureMessage *string) *string {
210+
if capiFailureMessage == nil {
211+
return nil
212+
}
213+
214+
mapiErrorMessage := *capiFailureMessage
215+
216+
return &mapiErrorMessage
217+
}
218+
102219
const (
103220
// Note the trailing slash here is important when we are trimming the prefix.
104221
capiPreDrainAnnotationPrefix = clusterv1.PreDrainDeleteHookAnnotationPrefix + "/"

pkg/conversion/capi2mapi/machine_test.go

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ import (
2525
capabuilder "github.com/openshift/cluster-api-actuator-pkg/testutils/resourcebuilder/cluster-api/infrastructure/v1beta2"
2626
"github.com/openshift/cluster-capi-operator/pkg/conversion/test/matchers"
2727
"github.com/openshift/cluster-capi-operator/pkg/util"
28-
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
29-
28+
corev1 "k8s.io/api/core/v1"
3029
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3130
"k8s.io/utils/ptr"
31+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
32+
capierrors "sigs.k8s.io/cluster-api/errors"
3233
)
3334

3435
var _ = Describe("capi2mapi Machine conversion", func() {
@@ -97,3 +98,72 @@ var _ = Describe("capi2mapi Machine conversion", func() {
9798
}),
9899
)
99100
})
101+
102+
var _ = Describe("capi2mapi Machine Status Conversion", func() {
103+
Context("when converting CAPI Machine status to MAPI", func() {
104+
It("should set all MAPI Machine status fields and conditions to the expected values", func() {
105+
// Set CAPI machine status fields
106+
nodeRef := &corev1.ObjectReference{
107+
Kind: "Node",
108+
Name: "test-node",
109+
Namespace: "",
110+
}
111+
lastUpdated := &metav1.Time{Time: time.Now()}
112+
condition := clusterv1.Condition{
113+
Type: "Available", Status: corev1.ConditionTrue,
114+
Severity: clusterv1.ConditionSeverityNone,
115+
Reason: "MachineAvailable", Message: "Machine is available",
116+
}
117+
118+
capiMachine := capibuilder.Machine().
119+
WithName("test-machine").
120+
WithNamespace("test-namespace").
121+
WithNodeRef(nodeRef).
122+
WithLastUpdated(lastUpdated).
123+
WithAddresses(clusterv1.MachineAddresses{
124+
{Type: clusterv1.MachineAddressType(corev1.NodeInternalIP), Address: "10.0.0.1"},
125+
{Type: clusterv1.MachineAddressType(corev1.NodeExternalIP), Address: "203.0.113.1"},
126+
}).
127+
WithPhase("Running").
128+
WithFailureReason(ptr.To(capierrors.MachineStatusError("InvalidConfiguration"))).
129+
WithFailureMessage(ptr.To(string("Test failure message"))).
130+
WithConditions([]clusterv1.Condition{condition}).
131+
Build()
132+
133+
mapiStatus, errs := convertCAPIMachineStatusToMAPI(capiMachine.Status)
134+
Expect(errs).To(BeEmpty())
135+
136+
Expect(mapiStatus.NodeRef).To(Equal(nodeRef))
137+
Expect(mapiStatus.LastUpdated).To(Equal(lastUpdated))
138+
Expect(mapiStatus.Addresses).To(ConsistOf(
139+
SatisfyAll(HaveField("Type", corev1.NodeInternalIP), HaveField("Address", "10.0.0.1")),
140+
SatisfyAll(HaveField("Type", corev1.NodeExternalIP), HaveField("Address", "203.0.113.1")),
141+
))
142+
143+
Expect(mapiStatus.Phase).To(HaveValue(BeEquivalentTo(mapiv1beta1.PhaseRunning)))
144+
Expect(mapiStatus.ErrorReason).To(HaveValue(BeEquivalentTo(mapiv1beta1.MachineStatusError("InvalidConfiguration"))))
145+
Expect(mapiStatus.ErrorMessage).To(HaveValue(BeEquivalentTo("Test failure message")))
146+
147+
// We do not convert these conditions to MAPI conditions as they are not a 1:1 mapping conversion between CAPI and MAPI.
148+
Expect(mapiStatus.Conditions).To(BeNil())
149+
})
150+
151+
It("should set all MAPI Machine status fields to empty when CAPI MachineStatus is empty", func() {
152+
capiMachine := capibuilder.Machine().
153+
WithName("test-machine").
154+
WithNamespace("test-namespace").
155+
Build()
156+
157+
mapiStatus, errs := convertCAPIMachineStatusToMAPI(capiMachine.Status)
158+
Expect(errs).To(BeEmpty())
159+
160+
Expect(mapiStatus.NodeRef).To(BeNil())
161+
Expect(mapiStatus.LastUpdated).To(BeNil())
162+
Expect(mapiStatus.Addresses).To(BeEmpty())
163+
Expect(mapiStatus.Phase).To(BeNil())
164+
Expect(mapiStatus.ErrorReason).To(BeNil())
165+
Expect(mapiStatus.ErrorMessage).To(BeNil())
166+
Expect(mapiStatus.Conditions).To(BeEmpty())
167+
})
168+
})
169+
})

pkg/conversion/capi2mapi/machineset.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ func convertCAPIMachineSetStatusToMAPI(capiStatus clusterv1.MachineSetStatus) ma
7676
// ObservedGeneration: // We don't set the observed generation at this stage as it is handled by the machineSetSync controller.
7777
// AuthoritativeAPI: // Ignore, this field as it is not present in CAPI.
7878
// SynchronizedGeneration: // Ignore, this field as it is not present in CAPI.
79-
Conditions: convertCAPIConditionsToMAPI(capiStatus.Conditions),
79+
80+
// The only two conditions normally used for MAPI MachineSets are Paused and Synchronized.
81+
// We do not convert these conditions to MAPI conditions as they are managed directly by the machineSet sync and migration controllers.
82+
Conditions: nil,
8083
}
8184

8285
// Convert FailureReason/FailureMessage to ErrorReason/ErrorMessage
@@ -100,10 +103,3 @@ func convertCAPIFailureReasonToMAPIErrorReason(capiFailureReason capierrors.Mach
100103
mapiErrorReason := mapiv1beta1.MachineSetStatusError(capiFailureReason)
101104
return &mapiErrorReason
102105
}
103-
104-
// convertCAPIConditionsToMAPI converts CAPI conditions to MAPI conditions.
105-
func convertCAPIConditionsToMAPI(capiConditions clusterv1.Conditions) []mapiv1beta1.Condition {
106-
// The only two conditions normally used for MAPI MachineSets are Paused and Synchronized.
107-
// We do not convert these conditions to MAPI conditions as they are managed directly by the machineSet sync and migration controllers.
108-
return nil
109-
}

0 commit comments

Comments
 (0)