func TestRebootWithSuffixlessAnnotation(t *testing.T) {
	host := newDefaultHost(t)
	host.Annotations = make(map[string]string)
	host.Annotations[rebootAnnotationPrefix] = ""
	host.Status.PoweredOn = true
	host.Status.Provisioning.State = metal3v1alpha1.StateProvisioned
	host.Spec.Online = true
	host.Spec.Image = &metal3v1alpha1.Image{URL: "foo", Checksum: "123"}
	host.Spec.Image.URL = "foo"
	host.Status.Provisioning.Image.URL = "foo"
	r := newTestReconciler(host)
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			if host.Status.PoweredOn {
				return false
			}
			return true
		},
	)
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			if _, exists := host.Annotations[rebootAnnotationPrefix]; exists {
				return false
			}
			return true
		},
	)
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			if !host.Status.PoweredOn {
				return false
			}
			return true
		},
	)
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			if !host.Status.PoweredOn {
				return false
			}
			return true
		},
	)
}
TestRebootWithSuffixlessAnnotation tests full reboot cycle with suffixless
annotation which doesn't wait for annotation removal before power on
package controllers
import (
	"context"
	goctx "context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"testing"
	"github.com/metal3-io/baremetal-operator/pkg/ironic/bmc"
	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/event"
	logf "sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
	"github.com/metal3-io/baremetal-operator/pkg/provisioner/fixture"
	"github.com/metal3-io/baremetal-operator/pkg/utils"
)
const (
	namespace         string = "test-namespace"
	defaultSecretName string = "bmc-creds-valid"  
	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 {
	secretData := make(map[string][]byte)
	for k, v := range data {
		secretData[k] = []byte(base64.StdEncoding.EncodeToString([]byte(v)))
	}
	secret := &corev1.Secret{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Secret",
			APIVersion: "v1",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
		},
		Data: secretData,
	}
	return secret
}
func newBMCCredsSecret(name, username, password string) *corev1.Secret {
	return newSecret(name, map[string]string{"username": username, "password": password})
}
func newHost(name string, spec *metal3v1alpha1.BareMetalHostSpec) *metal3v1alpha1.BareMetalHost {
	return &metal3v1alpha1.BareMetalHost{
		TypeMeta: metav1.TypeMeta{
			Kind:       "BareMetalHost",
			APIVersion: "metal3.io/v1alpha1",
		}, ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
		},
		Spec: *spec,
	}
}
func newDefaultNamedHost(name string, t *testing.T) *metal3v1alpha1.BareMetalHost {
	spec := &metal3v1alpha1.BareMetalHostSpec{
		BMC: metal3v1alpha1.BMCDetails{
			Address:         "ipmi://192.168.122.1:6233",
			CredentialsName: defaultSecretName,
		},
		HardwareProfile: "libvirt",
		RootDeviceHints: &metal3v1alpha1.RootDeviceHints{
			DeviceName:         "userd_devicename",
			HCTL:               "1:2:3:4",
			Model:              "userd_model",
			Vendor:             "userd_vendor",
			SerialNumber:       "userd_serial",
			MinSizeGigabytes:   40,
			WWN:                "userd_wwn",
			WWNWithExtension:   "userd_with_extension",
			WWNVendorExtension: "userd_vendor_extension",
		},
	}
	t.Logf("newNamedHost(%s)", name)
	return newHost(name, spec)
}
func newDefaultHost(t *testing.T) *metal3v1alpha1.BareMetalHost {
	return newDefaultNamedHost(t.Name(), t)
}
func newTestReconcilerWithFixture(fix *fixture.Fixture, initObjs ...runtime.Object) *BareMetalHostReconciler {
	c := fakeclient.NewFakeClient(initObjs...)
	bmcSecret := newBMCCredsSecret(defaultSecretName, "User", "Pass")
	c.Create(goctx.TODO(), bmcSecret)
	return &BareMetalHostReconciler{
		Client:             c,
		ProvisionerFactory: fix,
		Log:                ctrl.Log.WithName("controllers").WithName("BareMetalHost"),
		APIReader:          c,
	}
}
func newTestReconciler(initObjs ...runtime.Object) *BareMetalHostReconciler {
	fix := fixture.Fixture{}
	return newTestReconcilerWithFixture(&fix, initObjs...)
}
type DoneFunc func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool
func newRequest(host *metal3v1alpha1.BareMetalHost) ctrl.Request {
	namespacedName := types.NamespacedName{
		Namespace: host.ObjectMeta.Namespace,
		Name:      host.ObjectMeta.Name,
	}
	return ctrl.Request{NamespacedName: namespacedName}
}
func tryReconcile(t *testing.T, r *BareMetalHostReconciler, host *metal3v1alpha1.BareMetalHost, isDone DoneFunc) {
	request := newRequest(host)
	for i := 0; ; i++ {
		t.Logf("tryReconcile: top of loop %d", i)
		if i >= 25 {
			t.Fatal(fmt.Errorf("Exceeded 25 iterations"))
		}
		result, err := r.Reconcile(context.Background(), request)
		if err != nil {
			t.Fatal(err)
			break
		}
		updatedHost := &metal3v1alpha1.BareMetalHost{}
		if err = r.Get(goctx.TODO(), request.NamespacedName, updatedHost); k8serrors.IsNotFound(err) {
			host = nil
		} else {
			updatedHost.DeepCopyInto(host)
		}
		if isDone(host, result) {
			t.Logf("tryReconcile: loop done %d", i)
			break
		}
		t.Logf("tryReconcile: loop bottom %d result=%v", i, result)
		if !result.Requeue && result.RequeueAfter == 0 {
			t.Fatal(fmt.Errorf("Ended reconcile at iteration %d without test condition being true", i))
			break
		}
	}
}
func waitForStatus(t *testing.T, r *BareMetalHostReconciler, host *metal3v1alpha1.BareMetalHost, desiredStatus metal3v1alpha1.OperationalStatus) {
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			state := host.OperationalStatus()
			t.Logf("Waiting for status %s: %s", desiredStatus, state)
			return state == desiredStatus
		},
	)
}
func waitForError(t *testing.T, r *BareMetalHostReconciler, host *metal3v1alpha1.BareMetalHost) {
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			t.Logf("Waiting for error: %q", host.Status.ErrorMessage)
			return host.Status.ErrorMessage != ""
		},
	)
}
func waitForNoError(t *testing.T, r *BareMetalHostReconciler, host *metal3v1alpha1.BareMetalHost) {
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			t.Logf("Waiting for no error message: %q", host.Status.ErrorMessage)
			return host.Status.ErrorMessage == ""
		},
	)
}
func waitForProvisioningState(t *testing.T, r *BareMetalHostReconciler, host *metal3v1alpha1.BareMetalHost, desiredState metal3v1alpha1.ProvisioningState) {
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			t.Logf("Waiting for state %q have state %q", desiredState, host.Status.Provisioning.State)
			return host.Status.Provisioning.State == desiredState
		},
	)
}
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
		},
	)
}
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
		},
	)
}
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
		},
	)
}
func TestHardwareDetails_Invalid(t *testing.T) {
	host := newDefaultHost(t)
	badAnnotation := fmt.Sprintf("{\"hardware\": %s}", hwdAnnotation)
	host.Annotations = map[string]string{
		inspectAnnotationPrefix:   "disabled",
		hardwareDetailsAnnotation: badAnnotation,
	}
	time := metav1.Now()
	host.Status.LastUpdated = &time
	hwd := metal3v1alpha1.HardwareDetails{}
	hwd.Hostname = "existinghost"
	host.Status.HardwareDetails = &hwd
	r := newTestReconciler(host)
	request := newRequest(host)
	_, err := r.Reconcile(context.Background(), request)
	expectedErr := "json: unknown field"
	assert.Contains(t, err.Error(), expectedErr)
}
func TestStatusAnnotation_EmptyStatus(t *testing.T) {
	host := newDefaultHost(t)
	host.Annotations = map[string]string{
		metal3v1alpha1.StatusAnnotation: statusAnnotation,
	}
	host.Spec.Online = true
	host.Spec.Image = &metal3v1alpha1.Image{URL: "foo", Checksum: "123"}
	r := newTestReconciler(host)
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			if host.Status.HardwareProfile == "StatusProfile" && host.Status.Provisioning.Image.URL == "bar" {
				return true
			}
			return false
		},
	)
}
func TestStatusAnnotation_StatusPresent(t *testing.T) {
	host := newDefaultHost(t)
	host.Annotations = map[string]string{
		metal3v1alpha1.StatusAnnotation: statusAnnotation,
	}
	host.Spec.Online = true
	time := metav1.Now()
	host.Status.LastUpdated = &time
	host.Status.Provisioning.Image = metal3v1alpha1.Image{URL: "foo", Checksum: "123"}
	r := newTestReconciler(host)
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			_, found := host.Annotations[metal3v1alpha1.StatusAnnotation]
			if host.Status.HardwareProfile != "StatusProfile" && host.Status.Provisioning.Image.URL == "foo" && !found {
				return true
			}
			return false
		},
	)
}
func TestStatusAnnotation_Partial(t *testing.T) {
	unpackedStatus, err := unmarshalStatusAnnotation([]byte(statusAnnotation))
	if err != nil {
		t.Fatal(err)
		return
	}
	unpackedStatus.LastUpdated = nil
	packedStatus, err := json.Marshal(unpackedStatus)
	if err != nil {
		t.Fatal(err)
		return
	}
	host := newDefaultHost(t)
	host.Annotations = map[string]string{
		metal3v1alpha1.StatusAnnotation: string(packedStatus),
	}
	host.Spec.Online = true
	host.Spec.Image = &metal3v1alpha1.Image{URL: "foo", Checksum: "123"}
	r := newTestReconciler(host)
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			if host.Status.HardwareProfile == "StatusProfile" && host.Status.Provisioning.Image.URL == "bar" {
				return true
			}
			return false
		},
	)
}
func TestPause(t *testing.T) {
	host := newDefaultHost(t)
	host.Annotations = map[string]string{
		metal3v1alpha1.PausedAnnotation: "true",
	}
	r := newTestReconciler(host)
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			t.Logf("requeue: %v  finalizers: %v", result.Requeue, host.Finalizers)
			if !result.Requeue && len(host.Finalizers) == 0 {
				return true
			}
			return false
		},
	)
}
func TestInspectDisabled(t *testing.T) {
	host := newDefaultHost(t)
	host.Annotations = map[string]string{
		inspectAnnotationPrefix: "disabled",
	}
	r := newTestReconciler(host)
	waitForProvisioningState(t, r, host, metal3v1alpha1.StateMatchProfile)
	assert.Nil(t, host.Status.HardwareDetails)
}
func TestInspectEnabled(t *testing.T) {
	host := newDefaultHost(t)
	r := newTestReconciler(host)
	waitForProvisioningState(t, r, host, metal3v1alpha1.StateMatchProfile)
	assert.NotNil(t, host.Status.HardwareDetails)
}
func TestAddFinalizers(t *testing.T) {
	host := newDefaultHost(t)
	r := newTestReconciler(host)
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			t.Logf("finalizers: %v", host.ObjectMeta.Finalizers)
			if utils.StringInList(host.ObjectMeta.Finalizers, metal3v1alpha1.BareMetalHostFinalizer) {
				return true
			}
			return false
		},
	)
}
func TestSetLastUpdated(t *testing.T) {
	host := newDefaultHost(t)
	r := newTestReconciler(host)
	tryReconcile(t, r, host,
		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
			t.Logf("LastUpdated: %v", host.Status.LastUpdated)
			if !host.Status.LastUpdated.IsZero() {
				return true
			}
			return false
		},
	)
}
func TestInspectionDisabledAnnotation(t *testing.T) {
	host := newDefaultHost(t)
	host.Annotations = make(map[string]string)
	assert.False(t, inspectionDisabled(host))
	host.Annotations[inspectAnnotationPrefix] = "disabled"
	assert.True(t, inspectionDisabled(host))
}
func makeReconcileInfo(host *metal3v1alpha1.BareMetalHost) *reconcileInfo {
	return &reconcileInfo{
		log:  logf.Log.WithName("controllers").WithName("BareMetalHost").WithName("baremetal_controller"),
		host: host,
	}
}
func TestHasRebootAnnotation(t *testing.T) {
	host := newDefaultHost(t)
	info := makeReconcileInfo(host)
	host.Annotations = make(map[string]string)
	hasReboot, rebootMode := hasRebootAnnotation(info)
	assert.False(t, hasReboot)
	assert.Equal(t, metal3v1alpha1.RebootModeSoft, rebootMode)
	host.Annotations = make(map[string]string)
	suffixedAnnotation := rebootAnnotationPrefix + "/foo"
	host.Annotations[suffixedAnnotation] = ""
	hasReboot, rebootMode = hasRebootAnnotation(info)
	assert.True(t, hasReboot)
	assert.Equal(t, metal3v1alpha1.RebootModeSoft, rebootMode)
	delete(host.Annotations, suffixedAnnotation)
	host.Annotations[rebootAnnotationPrefix] = ""
	hasReboot, rebootMode = hasRebootAnnotation(info)
	assert.True(t, hasReboot)
	assert.Equal(t, metal3v1alpha1.RebootModeSoft, rebootMode)
	host.Annotations[suffixedAnnotation] = ""
	hasReboot, rebootMode = hasRebootAnnotation(info)
	assert.True(t, hasReboot)
	assert.Equal(t, metal3v1alpha1.RebootModeSoft, rebootMode)
	host.Annotations[suffixedAnnotation+"bar"] = ""
	hasReboot, rebootMode = hasRebootAnnotation(info)
	assert.True(t, hasReboot)
	assert.Equal(t, metal3v1alpha1.RebootModeSoft, rebootMode)
}
func TestHasHardRebootAnnotation(t *testing.T) {
	host := newDefaultHost(t)
	info := makeReconcileInfo(host)
	host.Annotations = make(map[string]string)
	hasReboot, rebootMode := hasRebootAnnotation(info)
	assert.False(t, hasReboot)
	assert.Equal(t, metal3v1alpha1.RebootModeSoft, rebootMode)
	host.Annotations = make(map[string]string)
	suffixedAnnotation := rebootAnnotationPrefix + "/foo/"
	host.Annotations[suffixedAnnotation] = "{\"mode\": \"hard\"}"
	hasReboot, rebootMode = hasRebootAnnotation(info)
	assert.True(t, hasReboot)
	assert.Equal(t, metal3v1alpha1.RebootModeHard, rebootMode)
	delete(host.Annotations, suffixedAnnotation)
	host.Annotations[rebootAnnotationPrefix] = "{\"mode\": \"soft\"}"
	hasReboot, rebootMode = hasRebootAnnotation(info)
	assert.True(t, hasReboot)
	assert.Equal(t, metal3v1alpha1.RebootModeSoft, rebootMode)
	delete(host.Annotations, suffixedAnnotation)
	host.Annotations[rebootAnnotationPrefix] = "{\"bad\"= \"json\"]"
	hasReboot, rebootMode = hasRebootAnnotation(info)
	assert.True(t, hasReboot)
	assert.Equal(t, metal3v1alpha1.RebootModeSoft, rebootMode)
	host.Annotations[suffixedAnnotation] = ""
	hasReboot, rebootMode = hasRebootAnnotation(info)
	assert.True(t, hasReboot)
	assert.Equal(t, metal3v1alpha1.RebootModeSoft, rebootMode)
	host.Annotations[suffixedAnnotation+"bar"] = "{\"mode\": \"hard\"}"
	hasReboot, rebootMode = hasRebootAnnotation(info)
	assert.True(t, hasReboot)
	assert.Equal(t, metal3v1alpha1.RebootModeHard, rebootMode)
}