Skip to content

Commit

Permalink
Add inspect.metal3.io/hardwaredetails annotation
Browse files Browse the repository at this point in the history
As described in metal3-io/metal3-docs#155

This enables addition of only the hardware part of the status,
unlike the status annotation which can only be set on the first
reconcile, since it allows all fields of the status to be modified.
  • Loading branch information
Steven Hardy committed Mar 4, 2021
1 parent 84bde45 commit 12a9989
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 0 deletions.
59 changes: 59 additions & 0 deletions controllers/metal3.io/baremetalhost_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const (
provisionerNotReadyRetryDelay = time.Second * 30
rebootAnnotationPrefix = "reboot.metal3.io"
inspectAnnotationPrefix = "inspect.metal3.io"
hardwareDetailsAnnotation = inspectAnnotationPrefix + "/hardwaredetails"
)

// BareMetalHostReconciler reconciles a BareMetalHost object
Expand Down Expand Up @@ -154,6 +155,14 @@ func (r *BareMetalHostReconciler) Reconcile(request ctrl.Request) (result ctrl.R
}
}

// Consume hardwaredetails from annotation if present
hwdUpdated, err := r.updateHardwareDetails(request, host)
if err != nil {
return ctrl.Result{}, errors.Wrap(err, "Could not update Hardware Details")
} else if hwdUpdated {
return ctrl.Result{Requeue: true}, nil
}

// NOTE(dhellmann): Handle a few steps outside of the phase
// structure because they require extra data lookup (like the
// credential checks) or have to be done "first" (like delete
Expand Down Expand Up @@ -259,6 +268,42 @@ func (r *BareMetalHostReconciler) Reconcile(request ctrl.Request) (result ctrl.R
return
}

// Consume inspect.metal3.io/hardwaredetails when either
// inspect.metal3.io=disabled or there are no existing HardwareDetails
func (r *BareMetalHostReconciler) updateHardwareDetails(request ctrl.Request, host *metal3v1alpha1.BareMetalHost) (bool, error) {
updated := false
if host.Status.HardwareDetails == nil || inspectionDisabled(host) {
objHardwareDetails, err := r.getHardwareDetailsFromAnnotation(host)
if err != nil {
return updated, errors.Wrap(err, "Error getting HardwareDetails from annotation")
}
if objHardwareDetails != nil {
r.publishEvent(request, host.NewEvent("UpdateHardwareDetails", "Setting HardwareDetails from annotation"))
host.Status.HardwareDetails = objHardwareDetails
err = r.saveHostStatus(host)
if err != nil {
return updated, errors.Wrap(err, "Could not update hardwaredetails from annotation")
}
updated = true
}
}
// We either just processed the annotation, or the status is already set
// so we remove it
annotations := host.GetAnnotations()
if _, present := annotations[hardwareDetailsAnnotation]; present {
// In the case where the value was not just consumed, generate an event
if updated != true {
r.publishEvent(request, host.NewEvent("RemoveAnnotation", "HardwareDetails annotation ignored, status already set and inspection is not disabled"))
}
delete(host.Annotations, hardwareDetailsAnnotation)
err := r.Update(context.TODO(), host)
if err != nil {
return updated, errors.Wrap(err, "Could not update removing hardwaredetails annotation")
}
}
return updated, nil
}

func logResult(info *reconcileInfo, result ctrl.Result) {
if result.Requeue || result.RequeueAfter != 0 ||
!utils.StringInList(info.host.Finalizers,
Expand Down Expand Up @@ -949,6 +994,20 @@ func (r *BareMetalHostReconciler) getHostStatusFromAnnotation(host *metal3v1alph
return objStatus, nil
}

// extract host from HardwareDetails annotation
func (r *BareMetalHostReconciler) getHardwareDetailsFromAnnotation(host *metal3v1alpha1.BareMetalHost) (*metal3v1alpha1.HardwareDetails, error) {
annotations := host.GetAnnotations()
if annotations[hardwareDetailsAnnotation] == "" {
return nil, nil
}
content := []byte(annotations[hardwareDetailsAnnotation])
objHardwareDetails := &metal3v1alpha1.HardwareDetails{}
if err := json.Unmarshal(content, objHardwareDetails); err != nil {
return nil, err
}
return objHardwareDetails, nil
}

func (r *BareMetalHostReconciler) setErrorCondition(request ctrl.Request, host *metal3v1alpha1.BareMetalHost, errType metal3v1alpha1.ErrorType, message string) (err error) {
reqLogger := r.Log.WithValues("baremetalhost", request.NamespacedName)

Expand Down
77 changes: 77 additions & 0 deletions controllers/metal3.io/baremetalhost_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
namespace string = "test-namespace"
defaultSecretName string = "bmc-creds-valid" // #nosec
statusAnnotation string = `{"operationalStatus":"OK","lastUpdated":"2020-04-15T15:00:50Z","hardwareProfile":"StatusProfile","hardware":{"systemVendor":{"manufacturer":"QEMU","productName":"Standard PC (Q35 + ICH9, 2009)","serialNumber":""},"firmware":{"bios":{"date":"","vendor":"","version":""}},"ramMebibytes":4096,"nics":[{"name":"eth0","model":"0x1af4 0x0001","mac":"00:b7:8b:bb:3d:f6","ip":"172.22.0.64","speedGbps":0,"vlanId":0,"pxe":true},{"name":"eth1","model":"0x1af4 0x0001","mac":"00:b7:8b:bb:3d:f8","ip":"192.168.111.20","speedGbps":0,"vlanId":0,"pxe":false}],"storage":[{"name":"/dev/sda","rotational":true,"sizeBytes":53687091200,"vendor":"QEMU","model":"QEMU HARDDISK","serialNumber":"drive-scsi0-0-0-0","hctl":"6:0:0:0"}],"cpu":{"arch":"x86_64","model":"Intel Xeon E3-12xx v2 (IvyBridge)","clockMegahertz":2494.224,"flags":["aes","apic","arat","avx","clflush","cmov","constant_tsc","cx16","cx8","de","eagerfpu","ept","erms","f16c","flexpriority","fpu","fsgsbase","fxsr","hypervisor","lahf_lm","lm","mca","mce","mmx","msr","mtrr","nopl","nx","pae","pat","pclmulqdq","pge","pni","popcnt","pse","pse36","rdrand","rdtscp","rep_good","sep","smep","sse","sse2","sse4_1","sse4_2","ssse3","syscall","tpr_shadow","tsc","tsc_adjust","tsc_deadline_timer","vme","vmx","vnmi","vpid","x2apic","xsave","xsaveopt","xtopology"],"count":4},"hostname":"node-0"},"provisioning":{"state":"provisioned","ID":"8a0ede17-7b87-44ac-9293-5b7d50b94b08","image":{"url":"bar","checksum":""}},"goodCredentials":{"credentials":{"name":"node-0-bmc-secret","namespace":"metal3"},"credentialsVersion":"879"},"triedCredentials":{"credentials":{"name":"node-0-bmc-secret","namespace":"metal3"},"credentialsVersion":"879"},"errorMessage":"","poweredOn":true,"operationHistory":{"register":{"start":"2020-04-15T12:06:26Z","end":"2020-04-15T12:07:12Z"},"inspect":{"start":"2020-04-15T12:07:12Z","end":"2020-04-15T12:09:29Z"},"provision":{"start":null,"end":null},"deprovision":{"start":null,"end":null}}}`
hwdAnnotation string = `{"systemVendor":{"manufacturer":"QEMU","productName":"Standard PC (Q35 + ICH9, 2009)","serialNumber":""},"firmware":{"bios":{"date":"","vendor":"","version":""}},"ramMebibytes":4096,"nics":[{"name":"eth0","model":"0x1af4 0x0001","mac":"00:b7:8b:bb:3d:f6","ip":"172.22.0.64","speedGbps":0,"vlanId":0,"pxe":true}],"storage":[{"name":"/dev/sda","rotational":true,"sizeBytes":53687091200,"vendor":"QEMU","model":"QEMU HARDDISK","serialNumber":"drive-scsi0-0-0-0","hctl":"6:0:0:0"}],"cpu":{"arch":"x86_64","model":"Intel Xeon E3-12xx v2 (IvyBridge)","clockMegahertz":2494.224,"flags":["foo"],"count":4},"hostname":"hwdAnnotation-0"}`
)

func newSecret(name string, data map[string]string) *corev1.Secret {
Expand Down Expand Up @@ -192,6 +193,82 @@ func waitForProvisioningState(t *testing.T, r *BareMetalHostReconciler, host *me
)
}

// TestHardwareDetails_EmptyStatus ensures that hardware details in
// the status field are populated when the hardwaredetails annotation
// is present and no existing HarwareDetails are present
func TestHardwareDetails_EmptyStatus(t *testing.T) {
host := newDefaultHost(t)
host.Annotations = map[string]string{
hardwareDetailsAnnotation: hwdAnnotation,
}

r := newTestReconciler(host)

tryReconcile(t, r, host,
func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
_, found := host.Annotations[hardwareDetailsAnnotation]
if host.Status.HardwareDetails != nil && host.Status.HardwareDetails.Hostname == "hwdAnnotation-0" && !found {
return true
}
return false
},
)
}

// TestHardwareDetails_StatusPresent ensures that hardware details in
// the hardwaredetails annotation is ignored with existing Status
func TestHardwareDetails_StatusPresent(t *testing.T) {
host := newDefaultHost(t)
host.Annotations = map[string]string{
hardwareDetailsAnnotation: hwdAnnotation,
}
time := metav1.Now()
host.Status.LastUpdated = &time
hwd := metal3v1alpha1.HardwareDetails{}
hwd.Hostname = "existinghost"
host.Status.HardwareDetails = &hwd

r := newTestReconciler(host)

tryReconcile(t, r, host,
func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
_, found := host.Annotations[hardwareDetailsAnnotation]
if host.Status.HardwareDetails != nil && host.Status.HardwareDetails.Hostname == "existinghost" && !found {
return true
}
return false
},
)
}

// TestHardwareDetails_StatusPresentInspectDisabled ensures that
// hardware details in the hardwaredetails annotation are consumed
// even when existing status exists, when inspection is disabled
func TestHardwareDetails_StatusPresentInspectDisabled(t *testing.T) {
host := newDefaultHost(t)
host.Annotations = map[string]string{
inspectAnnotationPrefix: "disabled",
hardwareDetailsAnnotation: hwdAnnotation,
}
time := metav1.Now()
host.Status.LastUpdated = &time
hwd := metal3v1alpha1.HardwareDetails{}
hwd.Hostname = "existinghost"
host.Status.HardwareDetails = &hwd

r := newTestReconciler(host)

tryReconcile(t, r, host,
func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
_, found := host.Annotations[hardwareDetailsAnnotation]
if host.Status.HardwareDetails != nil && host.Status.HardwareDetails.Hostname == "hwdAnnotation-0" && !found {
return true
}
return false
},
)
}

// TestStatusAnnotation_EmptyStatus ensures that status is manually populated
// when status annotation is present and status field is empty.
func TestStatusAnnotation_EmptyStatus(t *testing.T) {
Expand Down

0 comments on commit 12a9989

Please sign in to comment.