Skip to content

Commit 92550df

Browse files
Merge pull request #2670 from dtantsur/inspectionmode
✨ Introduce InspectionMode field
2 parents d5a0ef1 + f098914 commit 92550df

File tree

10 files changed

+282
-36
lines changed

10 files changed

+282
-36
lines changed

apis/metal3.io/v1alpha1/baremetalhost_types.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,22 @@ const (
6767
// InspectAnnotationValueDisabled is a constant string="disabled"
6868
// This is particularly useful to check if inspect annotation is disabled
6969
// inspect.metal3.io=disabled.
70+
//
71+
// Deprecated: use InspectionMode instead.
7072
InspectAnnotationValueDisabled = "disabled"
7173
)
7274

75+
// InspectionMode represents the mode of host inspection.
76+
type InspectionMode string
77+
78+
const (
79+
// InspectionModeDisabled disables host inspection.
80+
InspectionModeDisabled InspectionMode = "disabled"
81+
82+
// InspectionModeAgent runs standard agent-based inspection.
83+
InspectionModeAgent InspectionMode = "agent"
84+
)
85+
7386
// RootDeviceHints holds the hints for specifying the storage location
7487
// for the root filesystem for the image.
7588
type RootDeviceHints struct {
@@ -485,6 +498,13 @@ type BareMetalHostSpec struct {
485498
// instead, a reboot will be used in place of power on/off
486499
// +optional
487500
DisablePowerOff bool `json:"disablePowerOff,omitempty"`
501+
502+
// Specifies the mode for host inspection.
503+
// "disabled" - no inspection will be performed
504+
// "agent" - normal agent-based inspection will run
505+
// +optional
506+
// +kubebuilder:validation:Enum=disabled;agent
507+
InspectionMode InspectionMode `json:"inspectionMode,omitempty"`
488508
}
489509

490510
// AutomatedCleaningMode is the interface to enable/disable automated cleaning
@@ -974,6 +994,19 @@ func (host *BareMetalHost) CredentialsKey() types.NamespacedName {
974994
}
975995
}
976996

997+
// InspectionDisabled returns true if inspection is disabled via either
998+
// the inspect.metal3.io annotation or the inspectionMode field.
999+
func (host *BareMetalHost) InspectionDisabled() bool {
1000+
// Check the new InspectionMode field first
1001+
if host.Spec.InspectionMode == InspectionModeDisabled {
1002+
return true
1003+
}
1004+
1005+
// Fall back to the legacy annotation for backward compatibility
1006+
annotations := host.GetAnnotations()
1007+
return annotations[InspectAnnotationPrefix] == InspectAnnotationValueDisabled
1008+
}
1009+
9771010
// NeedsHardwareInspection looks at the state of the host to determine
9781011
// if hardware inspection should be run.
9791012
func (host *BareMetalHost) NeedsHardwareInspection() bool {
@@ -987,6 +1020,11 @@ func (host *BareMetalHost) NeedsHardwareInspection() bool {
9871020
// this host, because we don't want to reboot it.
9881021
return false
9891022
}
1023+
if host.InspectionDisabled() {
1024+
// Never perform inspection if it's explicitly disabled
1025+
return false
1026+
}
1027+
// FIXME(dtantsur): the HardwareDetails field is deprecated.
9901028
return host.Status.HardwareDetails == nil
9911029
}
9921030

apis/metal3.io/v1alpha1/baremetalhost_types_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,3 +665,55 @@ func TestHasBMCDetails(t *testing.T) {
665665
})
666666
}
667667
}
668+
669+
func TestInspectionDisabled(t *testing.T) {
670+
for _, tc := range []struct {
671+
Scenario string
672+
InspectionMode InspectionMode
673+
Annotations map[string]string
674+
Expected bool
675+
}{
676+
{
677+
Scenario: "default",
678+
},
679+
{
680+
Scenario: "annotation",
681+
Annotations: map[string]string{
682+
InspectAnnotationPrefix: "disabled",
683+
},
684+
Expected: true,
685+
},
686+
{
687+
Scenario: "empty annotation value",
688+
Annotations: map[string]string{
689+
InspectAnnotationPrefix: "",
690+
},
691+
},
692+
{
693+
Scenario: "InspectionMode",
694+
InspectionMode: InspectionModeDisabled,
695+
Expected: true,
696+
},
697+
{
698+
Scenario: "both",
699+
Annotations: map[string]string{
700+
InspectAnnotationPrefix: "disabled",
701+
},
702+
InspectionMode: InspectionModeDisabled,
703+
Expected: true,
704+
},
705+
} {
706+
t.Run(tc.Scenario, func(t *testing.T) {
707+
host := BareMetalHost{
708+
ObjectMeta: metav1.ObjectMeta{
709+
Annotations: tc.Annotations,
710+
},
711+
Spec: BareMetalHostSpec{
712+
InspectionMode: tc.InspectionMode,
713+
},
714+
}
715+
actual := host.InspectionDisabled()
716+
assert.Equal(t, tc.Expected, actual)
717+
})
718+
}
719+
}

config/base/crds/bases/metal3.io_baremetalhosts.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,15 @@ spec:
288288
required:
289289
- url
290290
type: object
291+
inspectionMode:
292+
description: |-
293+
Specifies the mode for host inspection.
294+
"disabled" - no inspection will be performed
295+
"agent" - normal agent-based inspection will run
296+
enum:
297+
- disabled
298+
- agent
299+
type: string
291300
metaData:
292301
description: |-
293302
MetaData holds the reference to the Secret containing host metadata

config/render/capm3.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,15 @@ spec:
288288
required:
289289
- url
290290
type: object
291+
inspectionMode:
292+
description: |-
293+
Specifies the mode for host inspection.
294+
"disabled" - no inspection will be performed
295+
"agent" - normal agent-based inspection will run
296+
enum:
297+
- disabled
298+
- agent
299+
type: string
291300
metaData:
292301
description: |-
293302
MetaData holds the reference to the Secret containing host metadata

internal/controller/metal3.io/baremetalhost_controller.go

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ func (r *BareMetalHostReconciler) Reconcile(ctx context.Context, request ctrl.Re
269269
// inspect.metal3.io=disabled or there are no existing HardwareDetails.
270270
func (r *BareMetalHostReconciler) updateHardwareDetails(ctx context.Context, request ctrl.Request, host *metal3api.BareMetalHost) (bool, error) {
271271
updated := false
272-
if host.Status.HardwareDetails == nil || inspectionDisabled(host) {
272+
if host.Status.HardwareDetails == nil || host.InspectionDisabled() {
273273
objHardwareDetails, err := r.getHardwareDetailsFromAnnotation(host)
274274
if err != nil {
275275
return updated, fmt.Errorf("error parsing HardwareDetails from annotation: %w", err)
@@ -446,16 +446,9 @@ func clearRebootAnnotations(host *metal3api.BareMetalHost) (dirty bool) {
446446
return
447447
}
448448

449-
// inspectionDisabled checks for existence of inspect.metal3.io=disabled
450-
// which means we don't inspect even in Inspecting state.
451-
func inspectionDisabled(host *metal3api.BareMetalHost) bool {
452-
annotations := host.GetAnnotations()
453-
return annotations[metal3api.InspectAnnotationPrefix] == metal3api.InspectAnnotationValueDisabled
454-
}
455-
456-
// hasInspectAnnotation checks for existence of inspect.metal3.io annotation
457-
// and returns true if it exist.
458-
func hasInspectAnnotation(host *metal3api.BareMetalHost) bool {
449+
// inspectionRefreshRequested checks for existence of inspect.metal3.io
450+
// annotation and returns true if it exist.
451+
func inspectionRefreshRequested(host *metal3api.BareMetalHost) bool {
459452
annotations := host.GetAnnotations()
460453
if annotations != nil {
461454
if expect, ok := annotations[metal3api.InspectAnnotationPrefix]; ok && expect != metal3api.InspectAnnotationValueDisabled {
@@ -974,15 +967,15 @@ func updateRootDeviceHints(host *metal3api.BareMetalHost, info *reconcileInfo) (
974967
func (r *BareMetalHostReconciler) actionInspecting(prov provisioner.Provisioner, info *reconcileInfo) actionResult {
975968
info.log.Info("inspecting hardware")
976969

977-
if inspectionDisabled(info.host) {
978-
info.log.Info("inspection disabled by annotation")
979-
info.publishEvent("InspectionSkipped", "disabled by annotation")
970+
if info.host.InspectionDisabled() {
971+
info.log.Info("inspection disabled by user")
972+
info.publishEvent("InspectionSkipped", "disabled by user")
980973
return actionComplete{}
981974
}
982975

983976
info.log.Info("inspecting hardware")
984977

985-
refresh := hasInspectAnnotation(info.host)
978+
refresh := inspectionRefreshRequested(info.host)
986979
forceReboot, _ := hasRebootAnnotation(info, true)
987980

988981
provResult, started, details, err := prov.InspectHardware(
@@ -1004,7 +997,7 @@ func (r *BareMetalHostReconciler) actionInspecting(prov provisioner.Provisioner,
1004997
dirty := false
1005998

1006999
// Delete inspect annotation if exists
1007-
if hasInspectAnnotation(info.host) {
1000+
if inspectionRefreshRequested(info.host) {
10081001
delete(info.host.Annotations, metal3api.InspectAnnotationPrefix)
10091002
dirty = true
10101003
}

internal/controller/metal3.io/baremetalhost_controller_test.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -592,16 +592,6 @@ func TestSetLastUpdated(t *testing.T) {
592592
)
593593
}
594594

595-
func TestInspectionDisabledAnnotation(t *testing.T) {
596-
host := newDefaultHost(t)
597-
host.Annotations = make(map[string]string)
598-
599-
assert.False(t, inspectionDisabled(host))
600-
601-
host.Annotations[metal3api.InspectAnnotationPrefix] = "disabled"
602-
assert.True(t, inspectionDisabled(host))
603-
}
604-
605595
func makeReconcileInfo(host *metal3api.BareMetalHost) *reconcileInfo {
606596
return &reconcileInfo{
607597
log: logf.Log.WithName("controllers").WithName("BareMetalHost").WithName("baremetal_controller"),

internal/controller/metal3.io/host_state_machine.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ func (hsm *hostStateMachine) handleRegistering(_ *reconcileInfo) actionResult {
408408
// if the credentials change and the Host must be re-registered.
409409
if hsm.Host.Spec.ExternallyProvisioned {
410410
hsm.NextState = metal3api.StateExternallyProvisioned
411-
} else if inspectionDisabled(hsm.Host) {
411+
} else if hsm.Host.InspectionDisabled() {
412412
hsm.NextState = metal3api.StatePreparing
413413
} else {
414414
hsm.NextState = metal3api.StateInspecting
@@ -439,8 +439,7 @@ func (hsm *hostStateMachine) handleExternallyProvisioned(info *reconcileInfo) ac
439439
return hsm.Reconciler.actionManageSteadyState(hsm.Provisioner, info)
440440
}
441441

442-
// TODO(dtantsur): move this logic inside NeedsHardwareInspection?
443-
if hsm.Host.NeedsHardwareInspection() && !inspectionDisabled(hsm.Host) {
442+
if hsm.Host.NeedsHardwareInspection() {
444443
hsm.NextState = metal3api.StateInspecting
445444
} else {
446445
hsm.NextState = metal3api.StatePreparing
@@ -464,7 +463,7 @@ func (hsm *hostStateMachine) handleAvailable(info *reconcileInfo) actionResult {
464463
return actionComplete{}
465464
}
466465

467-
if hasInspectAnnotation(hsm.Host) {
466+
if inspectionRefreshRequested(hsm.Host) {
468467
hsm.NextState = metal3api.StateInspecting
469468
return actionComplete{}
470469
}

internal/webhooks/metal3.io/v1alpha1/baremetalhost_validation.go

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ func (webhook *BareMetalHost) validateHost(host *metal3api.BareMetalHost) []erro
8282
errs = append(errs, annotationErrors...)
8383
}
8484

85+
if err := validateInspectionMode(host); err != nil {
86+
errs = append(errs, err)
87+
}
88+
8589
if err := validatePowerStatus(host); err != nil {
8690
errs = append(errs, err)
8791
}
@@ -219,8 +223,7 @@ func validateAnnotations(host *metal3api.BareMetalHost) []error {
219223
case annotation == metal3api.InspectAnnotationPrefix:
220224
err = validateInspectAnnotation(value)
221225
case annotation == metal3api.HardwareDetailsAnnotation:
222-
inspect := host.Annotations[metal3api.InspectAnnotationPrefix]
223-
err = validateHwdDetailsAnnotation(value, inspect)
226+
err = validateHwdDetailsAnnotation(value, host.InspectionDisabled())
224227
default:
225228
err = nil
226229
}
@@ -298,13 +301,14 @@ func checkStatusAnnotation(bmhStatus *metal3api.BareMetalHostStatus) error {
298301
return nil
299302
}
300303

301-
func validateHwdDetailsAnnotation(hwdDetAnnotation string, inspect string) error {
304+
func validateHwdDetailsAnnotation(hwdDetAnnotation string, inspectionDisabled bool) error {
302305
if hwdDetAnnotation == "" {
303306
return nil
304307
}
305308

306-
if inspect != "disabled" {
307-
return errors.New("when hardware details are provided, the inspect.metal3.io annotation must be set to disabled")
309+
// Check both the annotation and the new field for disabled inspection
310+
if !inspectionDisabled {
311+
return errors.New("when hardware details are provided, inspection must be disabled (either via inspect.metal3.io annotation or inspectionMode field)")
308312
}
309313

310314
objHwdDet := &metal3api.HardwareDetails{}
@@ -380,6 +384,31 @@ func (webhook *BareMetalHost) validateCrossNamespaceSecretReferences(host *metal
380384
return errs
381385
}
382386

387+
func validateInspectionMode(host *metal3api.BareMetalHost) error {
388+
inspectAnnotation := host.Annotations[metal3api.InspectAnnotationPrefix]
389+
390+
// Check for contradicting values when both are set
391+
if inspectAnnotation != "" && host.Spec.InspectionMode != "" {
392+
// Annotation logic: "disabled" means disabled, anything else means enabled
393+
annotationDisabled := inspectAnnotation == metal3api.InspectAnnotationValueDisabled
394+
fieldDisabled := host.Spec.InspectionMode == metal3api.InspectionModeDisabled
395+
396+
if annotationDisabled != fieldDisabled {
397+
return errors.New("inspect.metal3.io annotation and inspectionMode field have contradicting values")
398+
}
399+
}
400+
401+
// If InspectionMode is set, it must be a valid value (kubebuilder validation handles this for the API,
402+
// but we validate here for safety)
403+
if host.Spec.InspectionMode != "" &&
404+
host.Spec.InspectionMode != metal3api.InspectionModeDisabled &&
405+
host.Spec.InspectionMode != metal3api.InspectionModeAgent {
406+
return fmt.Errorf("invalid inspectionMode value: %s, allowed values are 'disabled' or 'agent'", host.Spec.InspectionMode)
407+
}
408+
409+
return nil
410+
}
411+
383412
func validatePowerStatus(host *metal3api.BareMetalHost) error {
384413
if host.Spec.DisablePowerOff && !host.Spec.Online {
385414
return errors.New("node can't simultaneously have online set to false and have power off disabled")

0 commit comments

Comments
 (0)