Skip to content

Commit 52371b9

Browse files
Merge pull request #2754 from dtantsur/test-hwdata
✨ Allow recovering hardware details from existing HardwareData
2 parents 903c22e + dbf58b5 commit 52371b9

File tree

3 files changed

+160
-6
lines changed

3 files changed

+160
-6
lines changed

internal/controller/metal3.io/baremetalhost_controller.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -274,17 +274,31 @@ func (r *BareMetalHostReconciler) Reconcile(ctx context.Context, request ctrl.Re
274274
func (r *BareMetalHostReconciler) updateHardwareDetails(ctx context.Context, request ctrl.Request, host *metal3api.BareMetalHost) (bool, error) {
275275
updated := false
276276
if host.Status.HardwareDetails == nil || host.InspectionDisabled() {
277-
objHardwareDetails, err := r.getHardwareDetailsFromAnnotation(host)
278-
if err != nil {
279-
return updated, fmt.Errorf("error parsing HardwareDetails from annotation: %w", err)
277+
hardwareData := &metal3api.HardwareData{}
278+
hardwareDataKey := client.ObjectKey{
279+
Name: host.Name,
280+
Namespace: host.Namespace,
281+
}
282+
err := r.Client.Get(ctx, hardwareDataKey, hardwareData)
283+
if err != nil && !k8serrors.IsNotFound(err) {
284+
return updated, fmt.Errorf("error loading HardwareData: %w", err)
280285
}
281-
if objHardwareDetails != nil {
286+
objHardwareDetails := hardwareData.Spec.HardwareDetails
287+
288+
if objHardwareDetails == nil {
289+
objHardwareDetails, err = r.getHardwareDetailsFromAnnotation(host)
290+
if err != nil {
291+
return updated, fmt.Errorf("error parsing HardwareDetails from annotation: %w", err)
292+
}
293+
}
294+
295+
if objHardwareDetails != nil && !reflect.DeepEqual(host.Status.HardwareDetails, objHardwareDetails) {
282296
host.Status.HardwareDetails = objHardwareDetails
283297
err = r.saveHostStatus(ctx, host)
284298
if err != nil {
285-
return updated, fmt.Errorf("could not update hardwaredetails from annotation: %w", err)
299+
return updated, fmt.Errorf("could not update hardwaredetails from existing hardware data or annotation: %w", err)
286300
}
287-
r.publishEvent(ctx, request, host.NewEvent("UpdateHardwareDetails", "Set HardwareDetails from annotation"))
301+
r.publishEvent(ctx, request, host.NewEvent("UpdateHardwareDetails", "Set HardwareDetails from hardware data or annotation"))
288302
updated = true
289303
}
290304
}

internal/controller/metal3.io/baremetalhost_controller_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,36 @@ func TestHardwareDetails_EmptyStatus(t *testing.T) {
274274
)
275275
}
276276

277+
// TestHardwareDetails_FromHardwareData ensures that hardware details in
278+
// the status field are populated when the HardwareData object
279+
// is present and no existing HarwareDetails are present.
280+
func TestHardwareDetails_FromHardwareData(t *testing.T) {
281+
host := newDefaultHost(t)
282+
hwdata := &metal3api.HardwareData{
283+
ObjectMeta: metav1.ObjectMeta{
284+
Name: host.Name,
285+
Namespace: host.Namespace,
286+
},
287+
Spec: metal3api.HardwareDataSpec{
288+
HardwareDetails: &metal3api.HardwareDetails{},
289+
},
290+
}
291+
err := json.Unmarshal([]byte(hwdAnnotation), hwdata.Spec.HardwareDetails)
292+
require.NoError(t, err)
293+
294+
r := newTestReconciler(t, host, hwdata)
295+
296+
tryReconcile(t, r, host,
297+
func(host *metal3api.BareMetalHost, result reconcile.Result) bool {
298+
_, found := host.Annotations[metal3api.HardwareDetailsAnnotation]
299+
if host.Status.HardwareDetails != nil && host.Status.HardwareDetails.Hostname == "hwdAnnotation-0" && !found {
300+
return true
301+
}
302+
return false
303+
},
304+
)
305+
}
306+
277307
// TestHardwareDetails_StatusPresent ensures that hardware details in
278308
// the hardwaredetails annotation is ignored with existing Status.
279309
func TestHardwareDetails_StatusPresent(t *testing.T) {
@@ -328,6 +358,43 @@ func TestHardwareDetails_StatusPresentInspectDisabled(t *testing.T) {
328358
)
329359
}
330360

361+
// TestHardwareDetails_StatusPresentInspectDisabled ensures that
362+
// hardware details in the HardwareData object are consumed
363+
// even when existing status exists, when inspection is disabled.
364+
func TestHardwareDetails_HardwareDataStatusPresentInspectDisabled(t *testing.T) {
365+
host := newDefaultHost(t)
366+
host.Spec.InspectionMode = metal3api.InspectionModeDisabled
367+
time := metav1.Now()
368+
host.Status.LastUpdated = &time
369+
hwd := metal3api.HardwareDetails{}
370+
hwd.Hostname = "existinghost"
371+
host.Status.HardwareDetails = &hwd
372+
373+
hwdata := &metal3api.HardwareData{
374+
ObjectMeta: metav1.ObjectMeta{
375+
Name: host.Name,
376+
Namespace: host.Namespace,
377+
},
378+
Spec: metal3api.HardwareDataSpec{
379+
HardwareDetails: &metal3api.HardwareDetails{},
380+
},
381+
}
382+
err := json.Unmarshal([]byte(hwdAnnotation), hwdata.Spec.HardwareDetails)
383+
require.NoError(t, err)
384+
385+
r := newTestReconciler(t, host, hwdata)
386+
387+
tryReconcile(t, r, host,
388+
func(host *metal3api.BareMetalHost, result reconcile.Result) bool {
389+
_, found := host.Annotations[metal3api.HardwareDetailsAnnotation]
390+
if host.Status.HardwareDetails != nil && host.Status.HardwareDetails.Hostname == "hwdAnnotation-0" && !found {
391+
return true
392+
}
393+
return false
394+
},
395+
)
396+
}
397+
331398
// TestHardwareDetails_Invalid
332399
// Tests scenario where the hardwaredetails value is invalid.
333400
func TestHardwareDetails_Invalid(t *testing.T) {

test/e2e/external_inspection_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,79 @@ var _ = Describe("External Inspection", Label("required", "external-inspection")
249249
}, e2eConfig.GetIntervals(specName, "wait-bmh-deleted")...)
250250
})
251251

252+
It("should skip inspection and become available when HardwareData exists and BMH has inspection disabled", func() {
253+
By("Creating a secret with BMH credentials")
254+
bmcCredentialsData := map[string]string{
255+
"username": bmc.User,
256+
"password": bmc.Password,
257+
}
258+
CreateSecret(ctx, clusterProxy.GetClient(), namespace.Name, secretName, bmcCredentialsData)
259+
260+
By("pre-creating a hardware data")
261+
hwdata := metal3api.HardwareData{
262+
ObjectMeta: metav1.ObjectMeta{
263+
Name: specName + "-inspect",
264+
Namespace: namespace.Name,
265+
},
266+
Spec: metal3api.HardwareDataSpec{
267+
HardwareDetails: &metal3api.HardwareDetails{},
268+
},
269+
}
270+
err := json.Unmarshal([]byte(hardwareDetails), hwdata.Spec.HardwareDetails)
271+
Expect(err).NotTo(HaveOccurred())
272+
err = clusterProxy.GetClient().Create(ctx, &hwdata)
273+
Expect(err).NotTo(HaveOccurred())
274+
275+
By("creating a BMH with inspection disabled and hardware details added")
276+
bmh := metal3api.BareMetalHost{
277+
ObjectMeta: metav1.ObjectMeta{
278+
Name: specName + "-inspect",
279+
Namespace: namespace.Name,
280+
},
281+
Spec: metal3api.BareMetalHostSpec{
282+
BMC: metal3api.BMCDetails{
283+
Address: bmc.Address,
284+
CredentialsName: "bmc-credentials",
285+
DisableCertificateVerification: bmc.DisableCertificateVerification,
286+
},
287+
BootMode: metal3api.Legacy,
288+
BootMACAddress: bmc.BootMacAddress,
289+
InspectionMode: metal3api.InspectionModeDisabled,
290+
},
291+
}
292+
err = clusterProxy.GetClient().Create(ctx, &bmh)
293+
Expect(err).NotTo(HaveOccurred())
294+
295+
By("waiting for the BMH to become available")
296+
WaitForBmhInProvisioningState(ctx, WaitForBmhInProvisioningStateInput{
297+
Client: clusterProxy.GetClient(),
298+
Bmh: bmh,
299+
State: metal3api.StateAvailable,
300+
UndesiredStates: []metal3api.ProvisioningState{metal3api.StateInspecting},
301+
}, e2eConfig.GetIntervals(specName, "wait-available")...)
302+
303+
By("checking that the BMH was not inspected")
304+
key := types.NamespacedName{Namespace: bmh.Namespace, Name: bmh.Name}
305+
Expect(clusterProxy.GetClient().Get(ctx, key, &bmh)).To(Succeed())
306+
Expect(bmh.Status.OperationHistory.Inspect.Start.IsZero()).To(BeTrue())
307+
308+
By("checking that the hardware details match what was in hardware data")
309+
hwStatusJSON, err := json.Marshal(bmh.Status.HardwareDetails)
310+
Expect(err).NotTo(HaveOccurred())
311+
Expect(hwStatusJSON).To(MatchJSON(hardwareDetails))
312+
313+
By("Delete BMH")
314+
err = clusterProxy.GetClient().Delete(ctx, &bmh)
315+
Expect(err).NotTo(HaveOccurred())
316+
317+
By("Waiting for the BMH to be deleted")
318+
WaitForBmhDeleted(ctx, WaitForBmhDeletedInput{
319+
Client: clusterProxy.GetClient(),
320+
BmhName: bmh.Name,
321+
Namespace: bmh.Namespace,
322+
}, e2eConfig.GetIntervals(specName, "wait-bmh-deleted")...)
323+
})
324+
252325
AfterEach(func() {
253326
DumpResources(ctx, e2eConfig, clusterProxy, path.Join(artifactFolder, specName))
254327
if !skipCleanup {

0 commit comments

Comments
 (0)