Repository: metal3-io/baremetal-operator
Pull request #837 (2021-03-29 14:25:16)

Stop passing entire BareMetalHost to Provisioner
Until now we have passed (a copy of) the entire BareMetalHost data structure to the Provisioner that is created for each reconcile. This enabled the Provisioner to use any information contained in the CR at any time. On the downside, this enabled the Provisioner to use any information contained in the CR at any time. This makes it difficult to reason about what the provisioner is doing and what information it requires (an architectural pattern known as a "big ball of mud". This will prove untenable if more provisioner implementations are to be added.

This PR alters the provisioning methods so that only the data reasonably required by each operation is passed to the corresponding function as an explicit argument. Only information that is used to identify the host is passed to the provisioner (and is thus implicitly available on every provisioner call):

* A copy of the CR metadata (including the resource name and namespace, and also labels and annotations in case those are needed to trigger custom behaviour in some provisioner)
* The BMC address
* The BMC credentials
* Whether to disable SSL host validation in communicating with the BMC
* The PXE MAC address
* The Provisioner ID (as returned from ValidateManagementAccess())

Method-specific data in arguments are passed in structures so that the interface can continue to evolve without mass changes of function signatures. While some data is passed that is not strictly required by the ironic provisioner because it is foreseeable that other provisioners may need to make use of it, it is also foreseeable that future provisioners will have unforeseeable requirements for more information (certainly the ironic provisioner makes use of data in some places that might be considered unexpected). Therefore future evolution is expected, but by making it explicit we can allow the controller and the BMH CRD to evolve separately from the provisioner.

metal3-io-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/metal3-io/baremetal-operator/pull/837#" title="Author self-approved">zaneb</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=metal3-io%2Fbaremetal-operator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/metal3-io/baremetal-operator/blob/master/OWNERS)~~ [zaneb]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
metal3-io-bot: @zaneb: The following test **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
golint | 108330bb7e0ad22965f028c9d1b509ff709ef8eb | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/837/golint/1376541050021089282) | `/test golint`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
metal3-io-bot: @zaneb: The following test **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
govet | 108330bb7e0ad22965f028c9d1b509ff709ef8eb | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/837/govet/1376541050021089285) | `/test govet`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
zaneb: /test-integration

Patch:
diff --git a/apis/metal3.io/v1alpha1/baremetalhost_types.go b/apis/metal3.io/v1alpha1/baremetalhost_types.go
index 8d494928..86ad9bd7 100644
--- a/apis/metal3.io/v1alpha1/baremetalhost_types.go
+++ b/apis/metal3.io/v1alpha1/baremetalhost_types.go
@@ -913,11 +913,6 @@ func (host *BareMetalHost) OperationMetricForState(operation ProvisioningState)
 	return
 }
 
-// GetImageChecksum returns the hash value and its algo.
-func (host *BareMetalHost) GetImageChecksum() (string, string, bool) {
-	return host.Spec.Image.GetChecksum()
-}
-
 // GetChecksum method returns the checksum of an image
 func (image *Image) GetChecksum() (checksum, checksumType string, ok bool) {
 	if image == nil {
diff --git a/controllers/metal3.io/baremetalhost_controller.go b/controllers/metal3.io/baremetalhost_controller.go
index 4e6afed5..7611d7d3 100644
--- a/controllers/metal3.io/baremetalhost_controller.go
+++ b/controllers/metal3.io/baremetalhost_controller.go
@@ -214,7 +214,8 @@ func (r *BareMetalHostReconciler) Reconcile(ctx context.Context, request ctrl.Re
 		request:        request,
 		bmcCredsSecret: bmcCredsSecret,
 	}
-	prov, err := r.ProvisionerFactory(*host.DeepCopy(), *bmcCreds, info.publishEvent)
+
+	prov, err := r.ProvisionerFactory(provisioner.BuildHostData(*host, *bmcCreds), info.publishEvent)
 	if err != nil {
 		return ctrl.Result{}, errors.Wrap(err, "failed to create provisioner")
 	}
@@ -522,6 +523,22 @@ func (r *BareMetalHostReconciler) actionUnmanaged(prov provisioner.Provisioner,
 	return actionContinue{unmanagedRetryDelay}
 }
 
+// getCurrentImage() returns the current image that has been or is being
+// provisioned.
+func getCurrentImage(host *metal3v1alpha1.BareMetalHost) *metal3v1alpha1.Image {
+	// If an image is currently provisioned, return it
+	if host.Status.Provisioning.Image.URL != "" {
+		return host.Status.Provisioning.Image.DeepCopy()
+	}
+
+	// If we are in the process of provisioning an image, return that image
+	if host.Status.Provisioning.State == metal3v1alpha1.StateProvisioning &&
+		host.Spec.Image != nil && host.Spec.Image.URL != "" {
+		return host.Spec.Image.DeepCopy()
+	}
+	return nil
+}
+
 // Test the credentials by connecting to the management controller.
 func (r *BareMetalHostReconciler) registerHost(prov provisioner.Provisioner, info *reconcileInfo) actionResult {
 	info.log.Info("registering and validating access to management controller",
@@ -536,7 +553,15 @@ func (r *BareMetalHostReconciler) registerHost(prov provisioner.Provisioner, inf
 		dirty = true
 	}
 
-	provResult, provID, err := prov.ValidateManagementAccess(credsChanged, info.host.Status.ErrorType == metal3v1alpha1.RegistrationError)
+	provResult, provID, err := prov.ValidateManagementAccess(
+		provisioner.ManagementAccessData{
+			BootMode:              info.host.Status.Provisioning.BootMode,
+			AutomatedCleaningMode: info.host.Spec.AutomatedCleaningMode,
+			State:                 info.host.Status.Provisioning.State,
+			CurrentImage:          getCurrentImage(info.host),
+		},
+		credsChanged,
+		info.host.Status.ErrorType == metal3v1alpha1.RegistrationError)
 	if err != nil {
 		noManagementAccess.Inc()
 		return actionError{errors.Wrap(err, "failed to validate BMC access")}
@@ -604,7 +629,12 @@ func (r *BareMetalHostReconciler) actionInspecting(prov provisioner.Provisioner,
 	info.log.Info("inspecting hardware")
 
 	refresh := hasInspectAnnotation(info.host)
-	provResult, details, err := prov.InspectHardware(info.host.Status.ErrorType == metal3v1alpha1.InspectionError, refresh)
+	provResult, details, err := prov.InspectHardware(
+		provisioner.InspectData{
+			BootMode: info.host.Status.Provisioning.BootMode,
+		},
+		info.host.Status.ErrorType == metal3v1alpha1.InspectionError,
+		refresh)
 	if err != nil {
 		return actionError{errors.Wrap(err, "hardware inspection failed")}
 	}
@@ -689,8 +719,12 @@ func (r *BareMetalHostReconciler) actionPreparing(prov provisioner.Provisioner,
 		return actionError{errors.Wrap(err, "Could not save the host provisioning settings")}
 	}
 
+	prepareData := provisioner.PrepareData{
+		RAIDConfig:      info.host.Status.Provisioning.RAID.DeepCopy(),
+		RootDeviceHints: info.host.Status.Provisioning.RootDeviceHints.DeepCopy(),
+	}
 	// Do prepare(manual clean).
-	provResult, started, err := prov.Prepare(dirty)
+	provResult, started, err := prov.Prepare(prepareData, dirty)
 	if err != nil {
 		return actionError{errors.Wrap(err, "error preparing host")}
 	}
@@ -726,6 +760,13 @@ func (r *BareMetalHostReconciler) actionProvisioning(prov provisioner.Provisione
 	}
 	info.log.Info("provisioning")
 
+	hwProf, err := hardware.GetProfile(info.host.HardwareProfile())
+	if err != nil {
+		return actionError{errors.Wrap(err,
+			fmt.Sprintf("could not start provisioning with bad hardware profile %s",
+				info.host.HardwareProfile()))}
+	}
+
 	if clearRebootAnnotations(info.host) {
 		if err := r.Update(context.TODO(), info.host); err != nil {
 			return actionError{errors.Wrap(err, "failed to remove reboot annotations from host")}
@@ -733,7 +774,13 @@ func (r *BareMetalHostReconciler) actionProvisioning(prov provisioner.Provisione
 		return actionContinue{}
 	}
 
-	provResult, err := prov.Provision(hostConf)
+	provResult, err := prov.Provision(provisioner.ProvisionData{
+		Image:           *info.host.Spec.Image.DeepCopy(),
+		HostConfig:      hostConf,
+		BootMode:        info.host.Status.Provisioning.BootMode,
+		HardwareProfile: hwProf,
+		RootDeviceHints: info.host.Status.Provisioning.RootDeviceHints.DeepCopy(),
+	})
 	if err != nil {
 		return actionError{errors.Wrap(err, "failed to provision")}
 	}
@@ -777,7 +824,9 @@ func (r *BareMetalHostReconciler) actionDeprovisioning(prov provisioner.Provisio
 	if info.host.Status.Provisioning.Image.URL != "" {
 		// Adopt the host in case it has been re-registered during the
 		// deprovisioning process before it completed
-		provResult, err := prov.Adopt(info.host.Status.ErrorType == metal3v1alpha1.ProvisionedRegistrationError)
+		provResult, err := prov.Adopt(
+			provisioner.AdoptData{State: info.host.Status.Provisioning.State},
+			info.host.Status.ErrorType == metal3v1alpha1.ProvisionedRegistrationError)
 		if err != nil {
 			return actionError{err}
 		}
@@ -923,7 +972,9 @@ func (r *BareMetalHostReconciler) manageHostPower(prov provisioner.Provisioner,
 // action. We use the Adopt() API to make sure that the provisioner is aware of
 // the provisioning details. Then we monitor its power status.
 func (r *BareMetalHostReconciler) actionManageSteadyState(prov provisioner.Provisioner, info *reconcileInfo) actionResult {
-	provResult, err := prov.Adopt(info.host.Status.ErrorType == metal3v1alpha1.ProvisionedRegistrationError)
+	provResult, err := prov.Adopt(
+		provisioner.AdoptData{State: info.host.Status.Provisioning.State},
+		info.host.Status.ErrorType == metal3v1alpha1.ProvisionedRegistrationError)
 	if err != nil {
 		return actionError{err}
 	}
diff --git a/controllers/metal3.io/host_state_machine.go b/controllers/metal3.io/host_state_machine.go
index 37a4f4ba..8476212d 100644
--- a/controllers/metal3.io/host_state_machine.go
+++ b/controllers/metal3.io/host_state_machine.go
@@ -122,7 +122,8 @@ func (hsm *hostStateMachine) updateHostStateFrom(initialState metal3v1alpha1.Pro
 		// the API. That means we can safely update any status fields
 		// along with the state.
 		switch hsm.NextState {
-		case metal3v1alpha1.StateInspecting,
+		case metal3v1alpha1.StateRegistering,
+			metal3v1alpha1.StateInspecting,
 			metal3v1alpha1.StateProvisioning:
 			// TODO: When the user-selectable profile field is
 			// removed, move saveHostProvisioningSettings() from the
diff --git a/pkg/provisioner/demo/demo.go b/pkg/provisioner/demo/demo.go
index 475137a7..96b1f8ab 100644
--- a/pkg/provisioner/demo/demo.go
+++ b/pkg/provisioner/demo/demo.go
@@ -3,6 +3,8 @@ package demo
 import (
 	"time"
 
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
 	"github.com/go-logr/logr"
 	logz "sigs.k8s.io/controller-runtime/pkg/log/zap"
 
@@ -51,8 +53,10 @@ const (
 // Provisioner implements the provisioning.Provisioner interface
 // and uses Ironic to manage the host.
 type demoProvisioner struct {
-	// the host to be managed by this provisioner
-	host metal3v1alpha1.BareMetalHost
+	// the object metadata of the BareMetalHost resource
+	objectMeta metav1.ObjectMeta
+	// the provisioning ID for this host
+	provID string
 	// the bmc credentials
 	bmcCreds bmc.Credentials
 	// a logger configured for this host
@@ -61,13 +65,14 @@ type demoProvisioner struct {
 	publisher provisioner.EventPublisher
 }
 
-// New returns a new Ironic Provisioner
-func New(host metal3v1alpha1.BareMetalHost, bmcCreds bmc.Credentials, publisher provisioner.EventPublisher) (provisioner.Provisioner, error) {
+// New returns a new demo Provisioner
+func New(hostData provisioner.HostData, publisher provisioner.EventPublisher) (provisioner.Provisioner, error) {
 	p := &demoProvisioner{
-		host:      host,
-		bmcCreds:  bmcCreds,
-		log:       log.WithValues("host", host.Name),
-		publisher: publisher,
+		objectMeta: hostData.ObjectMeta,
+		provID:     hostData.ProvisionerID,
+		bmcCreds:   hostData.BMCCredentials,
+		log:        log.WithValues("host", hostData.ObjectMeta.Name),
+		publisher:  publisher,
 	}
 	return p, nil
 }
@@ -78,10 +83,10 @@ func (m *demoProvisioner) HasProvisioningCapacity() (result bool, err error) {
 
 // ValidateManagementAccess tests the connection information for the
 // host to verify that the location and credentials work.
-func (p *demoProvisioner) ValidateManagementAccess(credentialsChanged, force bool) (result provisioner.Result, provID string, err error) {
+func (p *demoProvisioner) ValidateManagementAccess(data provisioner.ManagementAccessData, credentialsChanged, force bool) (result provisioner.Result, provID string, err error) {
 	p.log.Info("testing management access")
 
-	hostName := p.host.ObjectMeta.Name
+	hostName := p.objectMeta.Name
 
 	switch hostName {
 
@@ -97,10 +102,9 @@ func (p *demoProvisioner) ValidateManagementAccess(credentialsChanged, force boo
 		result.RequeueAfter = time.Second * 5
 
 	default:
-		if p.host.Status.Provisioning.ID == "" {
-			provID = p.host.ObjectMeta.Name
-			p.log.Info("setting provisioning id",
-				"provisioningID", p.host.Status.Provisioning.ID)
+		if p.provID == "" {
+			provID = p.objectMeta.Name
+			p.log.Info("setting provisioning id", "provisioningID", provID)
 			result.Dirty = true
 		}
 	}
@@ -112,10 +116,8 @@ func (p *demoProvisioner) ValidateManagementAccess(credentialsChanged, force boo
 // details of devices discovered on the hardware. It may be called
 // multiple times, and should return true for its dirty flag until the
 // inspection is completed.
-func (p *demoProvisioner) InspectHardware(force, refresh bool) (result provisioner.Result, details *metal3v1alpha1.HardwareDetails, err error) {
-	p.log.Info("inspecting hardware", "status", p.host.OperationalStatus())
-
-	hostName := p.host.ObjectMeta.Name
+func (p *demoProvisioner) InspectHardware(data provisioner.InspectData, force, refresh bool) (result provisioner.Result, details *metal3v1alpha1.HardwareDetails, err error) {
+	hostName := p.objectMeta.Name
 
 	if hostName == InspectingHost {
 		// set dirty so we don't allow the host to progress past this
@@ -125,58 +127,51 @@ func (p *demoProvisioner) InspectHardware(force, refresh bool) (result provision
 		return
 	}
 
-	// The inspection is ongoing. We'll need to check the demo
-	// status for the server here until it is ready for us to get the
-	// inspection details. Simulate that for now by creating the
-	// hardware details struct as part of a second pass.
-	if p.host.Status.HardwareDetails == nil {
-		p.log.Info("continuing inspection by setting details")
-		details =
-			&metal3v1alpha1.HardwareDetails{
-				RAMMebibytes: 128 * 1024,
-				NIC: []metal3v1alpha1.NIC{
-					{
-						Name:      "nic-1",
-						Model:     "virt-io",
-						MAC:       "some:mac:address",
-						IP:        "192.168.100.1",
-						SpeedGbps: 1,
-						PXE:       true,
-					},
-					{
-						Name:      "nic-2",
-						Model:     "e1000",
-						MAC:       "some:other:mac:address",
-						IP:        "192.168.100.2",
-						SpeedGbps: 1,
-						PXE:       false,
-					},
+	p.log.Info("continuing inspection by setting details")
+	details =
+		&metal3v1alpha1.HardwareDetails{
+			RAMMebibytes: 128 * 1024,
+			NIC: []metal3v1alpha1.NIC{
+				{
+					Name:      "nic-1",
+					Model:     "virt-io",
+					MAC:       "some:mac:address",
+					IP:        "192.168.100.1",
+					SpeedGbps: 1,
+					PXE:       true,
 				},
-				Storage: []metal3v1alpha1.Storage{
-					{
-						Name:       "disk-1 (boot)",
-						Rotational: false,
-						SizeBytes:  metal3v1alpha1.TebiByte * 93,
-						Model:      "Dell CFJ61",
-					},
-					{
-						Name:       "disk-2",
-						Rotational: false,
-						SizeBytes:  metal3v1alpha1.TebiByte * 93,
-						Model:      "Dell CFJ61",
-					},
+				{
+					Name:      "nic-2",
+					Model:     "e1000",
+					MAC:       "some:other:mac:address",
+					IP:        "192.168.100.2",
+					SpeedGbps: 1,
+					PXE:       false,
 				},
-				CPU: metal3v1alpha1.CPU{
-					Arch:           "x86_64",
-					Model:          "Core 2 Duo",
-					ClockMegahertz: 3.0 * metal3v1alpha1.GigaHertz,
-					Flags:          []string{"lm", "hypervisor", "vmx"},
-					Count:          1,
+			},
+			Storage: []metal3v1alpha1.Storage{
+				{
+					Name:       "disk-1 (boot)",
+					Rotational: false,
+					SizeBytes:  metal3v1alpha1.TebiByte * 93,
+					Model:      "Dell CFJ61",
 				},
-			}
-		p.publisher("InspectionComplete", "Hardware inspection completed")
-		p.host.SetOperationalStatus(metal3v1alpha1.OperationalStatusOK)
-	}
+				{
+					Name:       "disk-2",
+					Rotational: false,
+					SizeBytes:  metal3v1alpha1.TebiByte * 93,
+					Model:      "Dell CFJ61",
+				},
+			},
+			CPU: metal3v1alpha1.CPU{
+				Arch:           "x86_64",
+				Model:          "Core 2 Duo",
+				ClockMegahertz: 3.0 * metal3v1alpha1.GigaHertz,
+				Flags:          []string{"lm", "hypervisor", "vmx"},
+				Count:          1,
+			},
+		}
+	p.publisher("InspectionComplete", "Hardware inspection completed")
 
 	return
 }
@@ -191,9 +186,8 @@ func (p *demoProvisioner) UpdateHardwareState() (hwState provisioner.HardwareSta
 }
 
 // Prepare remove existing configuration and set new configuration
-func (p *demoProvisioner) Prepare(unprepared bool) (result provisioner.Result, started bool, err error) {
-	hostName := p.host.ObjectMeta.Name
-	p.log.Info("provisioning image to host", "state", p.host.Status.Provisioning.State)
+func (p *demoProvisioner) Prepare(data provisioner.PrepareData, unprepared bool) (result provisioner.Result, started bool, err error) {
+	hostName := p.objectMeta.Name
 
 	switch hostName {
 
@@ -213,8 +207,9 @@ func (p *demoProvisioner) Prepare(unprepared bool) (result provisioner.Result, s
 	return result, false, nil
 }
 
-// Adopt allows an externally-provisioned server to be adopted.
-func (p *demoProvisioner) Adopt(force bool) (result provisioner.Result, err error) {
+// Adopt notifies the provisioner that the state machine believes the host
+// to be currently provisioned, and that it should be managed as such.
+func (p *demoProvisioner) Adopt(data provisioner.AdoptData, force bool) (result provisioner.Result, err error) {
 	p.log.Info("adopting host")
 	result.Dirty = false
 	return
@@ -223,10 +218,10 @@ func (p *demoProvisioner) Adopt(force bool) (result provisioner.Result, err erro
 // Provision writes the image from the host spec to the host. It may
 // be called multiple times, and should return true for its dirty flag
 // until the deprovisioning operation is completed.
-func (p *demoProvisioner) Provision(hostConf provisioner.HostConfigData) (result provisioner.Result, err error) {
+func (p *demoProvisioner) Provision(data provisioner.ProvisionData) (result provisioner.Result, err error) {
 
-	hostName := p.host.ObjectMeta.Name
-	p.log.Info("provisioning image to host", "state", p.host.Status.Provisioning.State)
+	hostName := p.objectMeta.Name
+	p.log.Info("provisioning image to host")
 
 	switch hostName {
 
@@ -251,7 +246,7 @@ func (p *demoProvisioner) Provision(hostConf provisioner.HostConfigData) (result
 // deprovisioning operation is completed.
 func (p *demoProvisioner) Deprovision(force bool) (result provisioner.Result, err error) {
 
-	hostName := p.host.ObjectMeta.Name
+	hostName := p.objectMeta.Name
 	switch hostName {
 	default:
 		return result, nil
@@ -297,7 +292,7 @@ func (p *demoProvisioner) Delete() (result provisioner.Result, err error) {
 // provisioning operation.
 func (p *demoProvisioner) PowerOn() (result provisioner.Result, err error) {
 
-	hostName := p.host.ObjectMeta.Name
+	hostName := p.objectMeta.Name
 	switch hostName {
 	default:
 		return result, nil
@@ -318,7 +313,7 @@ func (p *demoProvisioner) PowerOn() (result provisioner.Result, err error) {
 // provisioning operation.
 func (p *demoProvisioner) PowerOff(rebootMode metal3v1alpha1.RebootMode) (result provisioner.Result, err error) {
 
-	hostName := p.host.ObjectMeta.Name
+	hostName := p.objectMeta.Name
 	switch hostName {
 	default:
 		return result, nil
diff --git a/pkg/provisioner/fixture/fixture.go b/pkg/provisioner/fixture/fixture.go
index 41b2b967..75f3e8fd 100644
--- a/pkg/provisioner/fixture/fixture.go
+++ b/pkg/provisioner/fixture/fixture.go
@@ -45,8 +45,8 @@ func (cd *fixtureHostConfigData) MetaData() (string, error) {
 // fixtureProvisioner implements the provisioning.fixtureProvisioner interface
 // and uses Ironic to manage the host.
 type fixtureProvisioner struct {
-	// the host to be managed by this provisioner
-	host metal3v1alpha1.BareMetalHost
+	// the provisioning ID for this host
+	provID string
 	// the bmc credentials
 	bmcCreds bmc.Credentials
 	// a logger configured for this host
@@ -71,12 +71,12 @@ type Fixture struct {
 	poweredOn bool
 }
 
-// New returns a new Ironic FixtureProvisioner
-func (f *Fixture) New(host metal3v1alpha1.BareMetalHost, bmcCreds bmc.Credentials, publisher provisioner.EventPublisher) (provisioner.Provisioner, error) {
+// New returns a new Fixture Provisioner
+func (f *Fixture) New(hostData provisioner.HostData, publisher provisioner.EventPublisher) (provisioner.Provisioner, error) {
 	p := &fixtureProvisioner{
-		host:      *host.DeepCopy(),
-		bmcCreds:  bmcCreds,
-		log:       log.WithValues("host", host.Name),
+		provID:    hostData.ProvisionerID,
+		bmcCreds:  hostData.BMCCredentials,
+		log:       log.WithValues("host", hostData.ObjectMeta.Name),
 		publisher: publisher,
 		state:     f,
 	}
@@ -89,11 +89,11 @@ func (p *fixtureProvisioner) HasProvisioningCapacity() (result bool, err error)
 
 // ValidateManagementAccess tests the connection information for the
 // host to verify that the location and credentials work.
-func (p *fixtureProvisioner) ValidateManagementAccess(credentialsChanged, force bool) (result provisioner.Result, provID string, err error) {
+func (p *fixtureProvisioner) ValidateManagementAccess(data provisioner.ManagementAccessData, credentialsChanged, force bool) (result provisioner.Result, provID string, err error) {
 	p.log.Info("testing management access")
 
 	// Fill in the ID of the host in the provisioning system
-	if p.host.Status.Provisioning.ID == "" {
+	if p.provID == "" {
 		provID = "temporary-fake-id"
 		result.Dirty = true
 		result.RequeueAfter = time.Second * 5
@@ -108,60 +108,56 @@ func (p *fixtureProvisioner) ValidateManagementAccess(credentialsChanged, force
 // details of devices discovered on the hardware. It may be called
 // multiple times, and should return true for its dirty flag until the
 // inspection is completed.
-func (p *fixtureProvisioner) InspectHardware(force, refresh bool) (result provisioner.Result, details *metal3v1alpha1.HardwareDetails, err error) {
-	p.log.Info("inspecting hardware", "status", p.host.OperationalStatus())
-
+func (p *fixtureProvisioner) InspectHardware(data provisioner.InspectData, force, refresh bool) (result provisioner.Result, details *metal3v1alpha1.HardwareDetails, err error) {
 	// The inspection is ongoing. We'll need to check the fixture
 	// status for the server here until it is ready for us to get the
 	// inspection details. Simulate that for now by creating the
 	// hardware details struct as part of a second pass.
-	if p.host.Status.HardwareDetails == nil {
-		p.log.Info("continuing inspection by setting details")
-		details =
-			&metal3v1alpha1.HardwareDetails{
-				RAMMebibytes: 128 * 1024,
-				NIC: []metal3v1alpha1.NIC{
-					{
-						Name:      "nic-1",
-						Model:     "virt-io",
-						MAC:       "some:mac:address",
-						IP:        "192.168.100.1",
-						SpeedGbps: 1,
-						PXE:       true,
-					},
-					{
-						Name:      "nic-2",
-						Model:     "e1000",
-						MAC:       "some:other:mac:address",
-						IP:        "192.168.100.2",
-						SpeedGbps: 1,
-						PXE:       false,
-					},
+	p.log.Info("continuing inspection by setting details")
+	details =
+		&metal3v1alpha1.HardwareDetails{
+			RAMMebibytes: 128 * 1024,
+			NIC: []metal3v1alpha1.NIC{
+				{
+					Name:      "nic-1",
+					Model:     "virt-io",
+					MAC:       "some:mac:address",
+					IP:        "192.168.100.1",
+					SpeedGbps: 1,
+					PXE:       true,
 				},
-				Storage: []metal3v1alpha1.Storage{
-					{
-						Name:       "disk-1 (boot)",
-						Rotational: false,
-						SizeBytes:  metal3v1alpha1.TebiByte * 93,
-						Model:      "Dell CFJ61",
-					},
-					{
-						Name:       "disk-2",
-						Rotational: false,
-						SizeBytes:  metal3v1alpha1.TebiByte * 93,
-						Model:      "Dell CFJ61",
-					},
+				{
+					Name:      "nic-2",
+					Model:     "e1000",
+					MAC:       "some:other:mac:address",
+					IP:        "192.168.100.2",
+					SpeedGbps: 1,
+					PXE:       false,
 				},
-				CPU: metal3v1alpha1.CPU{
-					Arch:           "x86_64",
-					Model:          "FancyPants CPU",
-					ClockMegahertz: 3.0 * metal3v1alpha1.GigaHertz,
-					Flags:          []string{"fpu", "hypervisor", "sse", "vmx"},
-					Count:          1,
+			},
+			Storage: []metal3v1alpha1.Storage{
+				{
+					Name:       "disk-1 (boot)",
+					Rotational: false,
+					SizeBytes:  metal3v1alpha1.TebiByte * 93,
+					Model:      "Dell CFJ61",
 				},
-			}
-		p.publisher("InspectionComplete", "Hardware inspection completed")
-	}
+				{
+					Name:       "disk-2",
+					Rotational: false,
+					SizeBytes:  metal3v1alpha1.TebiByte * 93,
+					Model:      "Dell CFJ61",
+				},
+			},
+			CPU: metal3v1alpha1.CPU{
+				Arch:           "x86_64",
+				Model:          "FancyPants CPU",
+				ClockMegahertz: 3.0 * metal3v1alpha1.GigaHertz,
+				Flags:          []string{"fpu", "hypervisor", "sse", "vmx"},
+				Count:          1,
+			},
+		}
+	p.publisher("InspectionComplete", "Hardware inspection completed")
 
 	return
 }
@@ -171,23 +167,22 @@ func (p *fixtureProvisioner) InspectHardware(force, refresh bool) (result provis
 // is expected to do this in the least expensive way possible, such as
 // reading from a cache.
 func (p *fixtureProvisioner) UpdateHardwareState() (hwState provisioner.HardwareState, err error) {
-	if !p.host.NeedsProvisioning() {
-		hwState.PoweredOn = &p.state.poweredOn
-		p.log.Info("updating hardware state")
-	}
+	hwState.PoweredOn = &p.state.poweredOn
+	p.log.Info("updating hardware state")
 	return
 }
 
 // Prepare remove existing configuration and set new configuration
-func (p *fixtureProvisioner) Prepare(unprepared bool) (result provisioner.Result, started bool, err error) {
+func (p *fixtureProvisioner) Prepare(data provisioner.PrepareData, unprepared bool) (result provisioner.Result, started bool, err error) {
 	p.log.Info("preparing host")
 	return
 }
 
-// Adopt allows an externally-provisioned server to be adopted.
-func (p *fixtureProvisioner) Adopt(force bool) (result provisioner.Result, err error) {
+// Adopt notifies the provisioner that the state machine believes the host
+// to be currently provisioned, and that it should be managed as such.
+func (p *fixtureProvisioner) Adopt(data provisioner.AdoptData, force bool) (result provisioner.Result, err error) {
 	p.log.Info("adopting host")
-	if p.host.Spec.ExternallyProvisioned && !p.state.adopted {
+	if !p.state.adopted {
 		p.state.adopted = true
 		result.Dirty = true
 		result.RequeueAfter = provisionRequeueDelay
@@ -198,14 +193,13 @@ func (p *fixtureProvisioner) Adopt(force bool) (result provisioner.Result, err e
 // Provision writes the image from the host spec to the host. It may
 // be called multiple times, and should return true for its dirty flag
 // until the deprovisioning operation is completed.
-func (p *fixtureProvisioner) Provision(hostConf provisioner.HostConfigData) (result provisioner.Result, err error) {
-	p.log.Info("provisioning image to host",
-		"state", p.host.Status.Provisioning.State)
+func (p *fixtureProvisioner) Provision(data provisioner.ProvisionData) (result provisioner.Result, err error) {
+	p.log.Info("provisioning image to host")
 
 	if p.state.image.URL == "" {
 		p.publisher("ProvisioningComplete", "Image provisioning completed")
 		p.log.Info("moving to done")
-		p.state.image = *p.host.Spec.Image
+		p.state.image = data.Image
 		result.Dirty = true
 		result.RequeueAfter = provisionRequeueDelay
 	}
diff --git a/pkg/provisioner/ironic/errors.go b/pkg/provisioner/ironic/errors.go
index 389aa6af..c3384bcd 100644
--- a/pkg/provisioner/ironic/errors.go
+++ b/pkg/provisioner/ironic/errors.go
@@ -1,38 +1,28 @@
 package ironic
 
-import (
-	"fmt"
-)
-
 // SoftPowerOffUnsupportedError is returned when the BMC does not
 // support soft power off.
 type SoftPowerOffUnsupportedError struct {
-	Address string
 }
 
 func (e SoftPowerOffUnsupportedError) Error() string {
-	return fmt.Sprintf("Soft power off is unsupported on BMC %s",
-		e.Address)
+	return "soft power off is unsupported on BMC"
 }
 
 // SoftPowerOffFailed is returned when the soft power off command
 // finishes with failure.
 type SoftPowerOffFailed struct {
-	Address string
 }
 
 func (e SoftPowerOffFailed) Error() string {
-	return fmt.Sprintf("Soft power off has failed on BMC %s",
-		e.Address)
+	return "Soft power off has failed on BMC"
 }
 
 // HostLockedError is returned when the BMC host is
 // locked.
 type HostLockedError struct {
-	Address string
 }
 
 func (e HostLockedError) Error() string {
-	return fmt.Sprintf("BMC %s host is locked",
-		e.Address)
+	return "BMC host is locked"
 }
diff --git a/pkg/provisioner/ironic/ironic.go b/pkg/provisioner/ironic/ironic.go
index e1d0f612..fb210e52 100644
--- a/pkg/provisioner/ironic/ironic.go
+++ b/pkg/provisioner/ironic/ironic.go
@@ -7,6 +7,8 @@ import (
 	"strings"
 	"time"
 
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
 	"github.com/go-logr/logr"
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes"
@@ -18,7 +20,6 @@ import (
 
 	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 	"github.com/metal3-io/baremetal-operator/pkg/bmc"
-	"github.com/metal3-io/baremetal-operator/pkg/hardware"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/clients"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/devicehints"
@@ -131,12 +132,18 @@ func init() {
 // Provisioner implements the provisioning.Provisioner interface
 // and uses Ironic to manage the host.
 type ironicProvisioner struct {
-	// the host to be managed by this provisioner
-	host metal3v1alpha1.BareMetalHost
-	// a shorter path to the provisioning status data structure
-	status *metal3v1alpha1.ProvisionStatus
+	// the object metadata of the BareMetalHost resource
+	objectMeta metav1.ObjectMeta
+	// the UUID of the node in Ironic
+	nodeID string
+	// the address of the BMC
+	bmcAddress string
+	// whether to disable SSL certificate verification
+	disableCertVerification bool
 	// credentials to log in to the BMC
 	bmcCreds bmc.Credentials
+	// the MAC address of the PXE boot interface
+	bootMACAddress string
 	// a client for talking to ironic
 	client *gophercloud.ServiceClient
 	// a client for talking to ironic-inspector
@@ -165,6 +172,8 @@ func LogStartup() {
 // A private function to construct an ironicProvisioner (rather than a
 // Provisioner interface) in a consistent way for tests.
 func newProvisionerWithSettings(host metal3v1alpha1.BareMetalHost, bmcCreds bmc.Credentials, publisher provisioner.EventPublisher, ironicURL string, ironicAuthSettings clients.AuthConfig, inspectorURL string, inspectorAuthSettings clients.AuthConfig) (*ironicProvisioner, error) {
+	hostData := provisioner.BuildHostData(host, bmcCreds)
+
 	tlsConf := clients.TLSConfig{
 		TrustedCAFile:      ironicTrustedCAFile,
 		InsecureSkipVerify: ironicInsecure,
@@ -179,31 +188,37 @@ func newProvisionerWithSettings(host metal3v1alpha1.BareMetalHost, bmcCreds bmc.
 		return nil, err
 	}
 
-	return newProvisionerWithIronicClients(host, bmcCreds, publisher,
+	return newProvisionerWithIronicClients(hostData, publisher,
 		clientIronic, clientInspector)
 }
 
-func newProvisionerWithIronicClients(host metal3v1alpha1.BareMetalHost, bmcCreds bmc.Credentials, publisher provisioner.EventPublisher, clientIronic *gophercloud.ServiceClient, clientInspector *gophercloud.ServiceClient) (*ironicProvisioner, error) {
+func newProvisionerWithIronicClients(hostData provisioner.HostData, publisher provisioner.EventPublisher, clientIronic *gophercloud.ServiceClient, clientInspector *gophercloud.ServiceClient) (*ironicProvisioner, error) {
 	// Ensure we have a microversion high enough to get the features
 	// we need.
 	clientIronic.Microversion = "1.56"
+
+	provisionerLogger := log.WithValues("host", ironicNodeName(hostData.ObjectMeta))
+
 	p := &ironicProvisioner{
-		host:      host,
-		status:    &(host.Status.Provisioning),
-		bmcCreds:  bmcCreds,
-		client:    clientIronic,
-		inspector: clientInspector,
-		publisher: publisher,
+		objectMeta:              hostData.ObjectMeta,
+		nodeID:                  hostData.ProvisionerID,
+		bmcCreds:                hostData.BMCCredentials,
+		bmcAddress:              hostData.BMCAddress,
+		disableCertVerification: hostData.DisableCertificateVerification,
+		bootMACAddress:          hostData.BootMACAddress,
+		client:                  clientIronic,
+		inspector:               clientInspector,
+		log:                     provisionerLogger,
+		debugLog:                provisionerLogger.V(1),
+		publisher:               publisher,
 	}
-	p.log = log.WithValues("host", p.ironicNodeNameFromHost())
-	p.debugLog = p.log.V(1)
 
 	return p, nil
 }
 
 // New returns a new Ironic Provisioner using the global configuration
 // for finding the Ironic services.
-func New(host metal3v1alpha1.BareMetalHost, bmcCreds bmc.Credentials, publisher provisioner.EventPublisher) (provisioner.Provisioner, error) {
+func New(hostData provisioner.HostData, publisher provisioner.EventPublisher) (provisioner.Provisioner, error) {
 	var err error
 	if clientIronicSingleton == nil || clientInspectorSingleton == nil {
 		tlsConf := clients.TLSConfig{
@@ -222,12 +237,12 @@ func New(host metal3v1alpha1.BareMetalHost, bmcCreds bmc.Credentials, publisher
 			return nil, err
 		}
 	}
-	return newProvisionerWithIronicClients(host, bmcCreds, publisher,
+	return newProvisionerWithIronicClients(hostData, publisher,
 		clientIronicSingleton, clientInspectorSingleton)
 }
 
 func (p *ironicProvisioner) bmcAccess() (bmc.AccessDetails, error) {
-	bmcAccess, err := bmc.NewAccessDetails(p.host.Spec.BMC.Address, p.host.Spec.BMC.DisableCertificateVerification)
+	bmcAccess, err := bmc.NewAccessDetails(p.bmcAddress, p.disableCertVerification)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to parse BMC address information")
 	}
@@ -286,30 +301,38 @@ func (p *ironicProvisioner) listAllPorts(address string) ([]ports.Port, error) {
 
 }
 
+func (p *ironicProvisioner) getNode() (*nodes.Node, error) {
+	if p.nodeID == "" {
+		return nil, provisioner.NeedsRegistration
+	}
+
+	ironicNode, err := nodes.Get(p.client, p.nodeID).Extract()
+	switch err.(type) {
+	case nil:
+		p.debugLog.Info("found existing node by ID")
+		return ironicNode, nil
+	case gophercloud.ErrDefault404:
+		// Look by ID failed, trying to lookup by hostname in case it was
+		// previously created
+		return nil, provisioner.NeedsRegistration
+	default:
+		return nil, errors.Wrap(err,
+			fmt.Sprintf("failed to find node by ID %s", p.nodeID))
+	}
+}
+
 // Look for an existing registration for the host in Ironic.
-func (p *ironicProvisioner) findExistingHost() (ironicNode *nodes.Node, err error) {
+func (p *ironicProvisioner) findExistingHost(bootMACAddress string) (ironicNode *nodes.Node, err error) {
 	// Try to load the node by UUID
-	if p.status.ID != "" {
-		// Look for the node to see if it exists (maybe Ironic was
-		// restarted)
-		ironicNode, err = nodes.Get(p.client, p.status.ID).Extract()
-		switch err.(type) {
-		case nil:
-			p.debugLog.Info("found existing node by ID")
-			return ironicNode, nil
-		case gophercloud.ErrDefault404:
-			// Look by ID failed, trying to lookup by hostname in case it was
-			// previously created
-		default:
-			return nil, errors.Wrap(err,
-				fmt.Sprintf("failed to find node by ID %s", p.status.ID))
-		}
+	ironicNode, err = p.getNode()
+	if !errors.Is(err, provisioner.NeedsRegistration) {
+		return
 	}
 
 	// Try to load the node by name
-	nodeSearchList := []string{p.ironicNodeNameFromHost()}
-	if !strings.Contains(p.host.Name, nameSeparator) {
-		nodeSearchList = append(nodeSearchList, p.host.Name)
+	nodeSearchList := []string{ironicNodeName(p.objectMeta)}
+	if !strings.Contains(p.objectMeta.Name, nameSeparator) {
+		nodeSearchList = append(nodeSearchList, p.objectMeta.Name)
 	}
 
 	for _, nodeName := range nodeSearchList {
@@ -329,11 +352,11 @@ func (p *ironicProvisioner) findExistingHost() (ironicNode *nodes.Node, err erro
 	}
 
 	// Try to load the node by port address
-	p.log.Info("looking for existing node by MAC", "MAC", p.host.Spec.BootMACAddress)
-	allPorts, err := p.listAllPorts(p.host.Spec.BootMACAddress)
+	p.log.Info("looking for existing node by MAC", "MAC", bootMACAddress)
+	allPorts, err := p.listAllPorts(bootMACAddress)
 
 	if err != nil {
-		p.log.Info("failed to find an existing port with address", "MAC", p.host.Spec.BootMACAddress)
+		p.log.Info("failed to find an existing port with address", "MAC", bootMACAddress)
 		return nil, nil
 	}
 
@@ -346,7 +369,7 @@ func (p *ironicProvisioner) findExistingHost() (ironicNode *nodes.Node, err erro
 
 			// If the node has a name, this means we didn't find it above.
 			if ironicNode.Name != "" {
-				return nil, NewMacAddressConflictError(p.host.Spec.BootMACAddress, ironicNode.Name)
+				return nil, NewMacAddressConflictError(bootMACAddress, ironicNode.Name)
 			}
 
 			return ironicNode, nil
@@ -358,11 +381,12 @@ func (p *ironicProvisioner) findExistingHost() (ironicNode *nodes.Node, err erro
 				fmt.Sprintf("port exists but failed to find linked node by ID %s", nodeUUID))
 		}
 	} else {
-		p.log.Info("port with address doesn't exist", "MAC", p.host.Spec.BootMACAddress)
+		p.log.Info("port with address doesn't exist", "MAC", bootMACAddress)
 	}
 
+	// Either the node was never created or the Ironic database has
+	// been dropped.
 	return nil, nil
-
 }
 
 // ValidateManagementAccess registers the host with the provisioning
@@ -371,7 +395,7 @@ func (p *ironicProvisioner) findExistingHost() (ironicNode *nodes.Node, err erro
 //
 // FIXME(dhellmann): We should rename this method to describe what it
 // actually does.
-func (p *ironicProvisioner) ValidateManagementAccess(credentialsChanged, force bool) (result provisioner.Result, provID string, err error) {
+func (p *ironicProvisioner) ValidateManagementAccess(data provisioner.ManagementAccessData, credentialsChanged, force bool) (result provisioner.Result, provID string, err error) {
 	bmcAccess, err := p.bmcAccess()
 	if err != nil {
 		result, err = operationFailed(err.Error())
@@ -383,7 +407,7 @@ func (p *ironicProvisioner) ValidateManagementAccess(credentialsChanged, force b
 
 	p.debugLog.Info("validating management access")
 
-	ironicNode, err = p.findExistingHost()
+	ironicNode, err = p.findExistingHost(p.bootMACAddress)
 	if err != nil {
 		switch err.(type) {
 		case macAddressConflictError:
@@ -396,7 +420,7 @@ func (p *ironicProvisioner) ValidateManagementAccess(credentialsChanged, force b
 
 	// Some BMC types require a MAC address, so ensure we have one
 	// when we need it. If not, place the host in an error state.
-	if bmcAccess.NeedsMAC() && p.host.Spec.BootMACAddress == "" {
+	if bmcAccess.NeedsMAC() && p.bootMACAddress == "" {
 		msg := fmt.Sprintf("BMC driver %s requires a BootMACAddress value", bmcAccess.Type())
 		p.log.Info(msg)
 		result, err = operationFailed(msg)
@@ -415,7 +439,7 @@ func (p *ironicProvisioner) ValidateManagementAccess(credentialsChanged, force b
 	if ironicNode == nil {
 		p.log.Info("registering host in ironic")
 
-		if p.host.Spec.BootMode == metal3v1alpha1.UEFISecureBoot && !bmcAccess.SupportsSecureBoot() {
+		if data.BootMode == metal3v1alpha1.UEFISecureBoot && !bmcAccess.SupportsSecureBoot() {
 			msg := fmt.Sprintf("BMC driver %s does not support secure boot", bmcAccess.Type())
 			p.log.Info(msg)
 			result, err = operationFailed(msg)
@@ -427,16 +451,16 @@ func (p *ironicProvisioner) ValidateManagementAccess(credentialsChanged, force b
 			nodes.CreateOpts{
 				Driver:              bmcAccess.Driver(),
 				BootInterface:       bmcAccess.BootInterface(),
-				Name:                p.host.Name,
+				Name:                p.objectMeta.Name,
 				DriverInfo:          driverInfo,
-				DeployInterface:     p.deployInterface(),
+				DeployInterface:     p.deployInterface(data.CurrentImage),
 				InspectInterface:    "inspector",
 				ManagementInterface: bmcAccess.ManagementInterface(),
 				PowerInterface:      bmcAccess.PowerInterface(),
 				RAIDInterface:       bmcAccess.RAIDInterface(),
 				VendorInterface:     bmcAccess.VendorInterface(),
 				Properties: map[string]interface{}{
-					"capabilities": bootModeCapabilities[p.host.Status.Provisioning.BootMode],
+					"capabilities": bootModeCapabilities[data.BootMode],
 				},
 			}).Extract()
 		// FIXME(dhellmann): Handle 409 and 503? errors here.
@@ -452,15 +476,15 @@ func (p *ironicProvisioner) ValidateManagementAccess(credentialsChanged, force b
 
 		// If we know the MAC, create a port. Otherwise we will have
 		// to do this after we run the introspection step.
-		if p.host.Spec.BootMACAddress != "" {
+		if p.bootMACAddress != "" {
 			enable := true
 			p.log.Info("creating port for node in ironic", "MAC",
-				p.host.Spec.BootMACAddress)
+				p.bootMACAddress)
 			_, err = ports.Create(
 				p.client,
 				ports.CreateOpts{
 					NodeUUID:   ironicNode.UUID,
-					Address:    p.host.Spec.BootMACAddress,
+					Address:    p.bootMACAddress,
 					PXEEnabled: &enable,
 				}).Extract()
 			if err != nil {
@@ -469,20 +493,8 @@ func (p *ironicProvisioner) ValidateManagementAccess(credentialsChanged, force b
 			}
 		}
 
-		// If there is an image to be provisioned, or an image has
-		// previously been provisioned, include those details. Either
-		// case may mean we are re-adopting a host that was already
-		// known but removed/lost because the pod restarted.
-		var imageData *metal3v1alpha1.Image
-		switch {
-		case p.host.Status.Provisioning.Image.URL != "":
-			imageData = &p.host.Status.Provisioning.Image
-		case p.host.Spec.Image != nil && p.host.Spec.Image.URL != "":
-			imageData = p.host.Spec.Image
-		}
-
-		if imageData != nil {
-			updatesImage, optsErr := p.getImageUpdateOptsForNode(ironicNode, imageData)
+		if data.CurrentImage != nil {
+			updatesImage, optsErr := p.getImageUpdateOptsForNode(ironicNode, data.CurrentImage, data.BootMode)
 			if optsErr != nil {
 				result, err = transientError(errors.Wrap(optsErr, "Could not get Image options for node"))
 				return
@@ -499,13 +511,13 @@ func (p *ironicProvisioner) ValidateManagementAccess(credentialsChanged, force b
 		// if there are differences.
 		provID = ironicNode.UUID
 
-		if ironicNode.Name != p.ironicNodeNameFromHost() {
+		if ironicNode.Name != ironicNodeName(p.objectMeta) {
 			updates = append(
 				updates,
 				nodes.UpdateOperation{
 					Op:    nodes.ReplaceOp,
 					Path:  "/name",
-					Value: p.ironicNodeNameFromHost(),
+					Value: ironicNodeName(p.objectMeta),
 				},
 			)
 		}
@@ -526,12 +538,14 @@ func (p *ironicProvisioner) ValidateManagementAccess(credentialsChanged, force b
 			// below.
 		}
 	}
-	if ironicNode.AutomatedClean == nil || (p.host.Spec.AutomatedCleaningMode == "disabled" && *ironicNode.AutomatedClean != false) || (p.host.Spec.AutomatedCleaningMode == "enabled" && *ironicNode.AutomatedClean == false) {
+	if ironicNode.AutomatedClean == nil ||
+		(data.AutomatedCleaningMode == metal3v1alpha1.CleaningModeDisabled && *ironicNode.AutomatedClean) ||
+		(data.AutomatedCleaningMode == metal3v1alpha1.CleaningModeMetadata && !*ironicNode.AutomatedClean) {
 		p.log.Info("setting automated cleaning mode to",
 			"ID", ironicNode.UUID,
-			"mode", p.host.Spec.AutomatedCleaningMode)
+			"mode", data.AutomatedCleaningMode)
 
-		value := p.host.Spec.AutomatedCleaningMode != metal3v1alpha1.CleaningModeDisabled
+		value := data.AutomatedCleaningMode != metal3v1alpha1.CleaningModeDisabled
 		updates = append(
 			updates,
 			nodes.UpdateOperation{
@@ -647,16 +661,12 @@ func (p *ironicProvisioner) changeNodeProvisionState(ironicNode *nodes.Node, opt
 // details of devices discovered on the hardware. It may be called
 // multiple times, and should return true for its dirty flag until the
 // inspection is completed.
-func (p *ironicProvisioner) InspectHardware(force, refresh bool) (result provisioner.Result, details *metal3v1alpha1.HardwareDetails, err error) {
-	p.log.Info("inspecting hardware", "status", p.host.OperationalStatus())
+func (p *ironicProvisioner) InspectHardware(data provisioner.InspectData, force, refresh bool) (result provisioner.Result, details *metal3v1alpha1.HardwareDetails, err error) {
+	p.log.Info("inspecting hardware")
 
-	ironicNode, err := p.findExistingHost()
+	ironicNode, err := p.getNode()
 	if err != nil {
-		result, err = transientError(errors.Wrap(err, "failed to find existing host"))
-		return
-	}
-	if ironicNode == nil {
-		result, err = transientError(provisioner.NeedsRegistration)
+		result, err = transientError(err)
 		return
 	}
 
@@ -681,7 +691,7 @@ func (p *ironicProvisioner) InspectHardware(force, refresh bool) (result provisi
 				fallthrough
 			default:
 				p.log.Info("updating boot mode before hardware inspection")
-				op, value := buildCapabilitiesValue(ironicNode, p.host.Status.Provisioning.BootMode)
+				op, value := buildCapabilitiesValue(ironicNode, data.BootMode)
 				updates := nodes.UpdateOpts{
 					nodes.UpdateOperation{
 						Op:    op,
@@ -729,15 +739,15 @@ func (p *ironicProvisioner) InspectHardware(force, refresh bool) (result provisi
 
 	// Introspection is done
 	p.log.Info("getting hardware details from inspection")
-	introData := introspection.GetIntrospectionData(p.inspector, ironicNode.UUID)
-	data, err := introData.Extract()
+	response := introspection.GetIntrospectionData(p.inspector, ironicNode.UUID)
+	introData, err := response.Extract()
 	if err != nil {
 		result, err = transientError(errors.Wrap(err, "failed to retrieve hardware introspection data"))
 		return
 	}
-	p.log.Info("received introspection data", "data", introData.Body)
+	p.log.Info("received introspection data", "data", response.Body)
 
-	details = hardwaredetails.GetHardwareDetails(data)
+	details = hardwaredetails.GetHardwareDetails(introData)
 	p.publisher("InspectionComplete", "Hardware inspection completed")
 	result, err = operationComplete()
 	return
@@ -750,13 +760,8 @@ func (p *ironicProvisioner) InspectHardware(force, refresh bool) (result provisi
 func (p *ironicProvisioner) UpdateHardwareState() (hwState provisioner.HardwareState, err error) {
 	p.debugLog.Info("updating hardware state")
 
-	ironicNode, err := p.findExistingHost()
+	ironicNode, err := p.getNode()
 	if err != nil {
-		err = errors.Wrap(err, "failed to find existing host")
-		return
-	}
-	if ironicNode == nil {
-		err = provisioner.NeedsRegistration
 		return
 	}
 
@@ -933,7 +938,7 @@ func (p *ironicProvisioner) setDirectDeployUpdateOptsForNode(ironicNode *nodes.N
 	return updates, nil
 }
 
-func (p *ironicProvisioner) getImageUpdateOptsForNode(ironicNode *nodes.Node, imageData *metal3v1alpha1.Image) (updates nodes.UpdateOpts, err error) {
+func (p *ironicProvisioner) getImageUpdateOptsForNode(ironicNode *nodes.Node, imageData *metal3v1alpha1.Image, bootMode metal3v1alpha1.BootMode) (updates nodes.UpdateOpts, err error) {
 	// instance_uuid
 	p.log.Info("setting instance_uuid")
 	updates = append(
@@ -941,7 +946,7 @@ func (p *ironicProvisioner) getImageUpdateOptsForNode(ironicNode *nodes.Node, im
 		nodes.UpdateOperation{
 			Op:    nodes.ReplaceOp,
 			Path:  "/instance_uuid",
-			Value: string(p.host.ObjectMeta.UID),
+			Value: string(p.objectMeta.UID),
 		},
 	)
 
@@ -949,7 +954,7 @@ func (p *ironicProvisioner) getImageUpdateOptsForNode(ironicNode *nodes.Node, im
 	// also put it to properties for consistency, although it's not
 	// strictly required in our case).
 
-	if p.host.Spec.BootMode == metal3v1alpha1.UEFISecureBoot {
+	if bootMode == metal3v1alpha1.UEFISecureBoot {
 		updates = append(updates, nodes.UpdateOperation{
 			Op:   nodes.AddOp,
 			Path: "/instance_info/capabilities",
@@ -977,17 +982,8 @@ func (p *ironicProvisioner) getImageUpdateOptsForNode(ironicNode *nodes.Node, im
 	return p.setDirectDeployUpdateOptsForNode(ironicNode, imageData, updates)
 }
 
-func (p *ironicProvisioner) getUpdateOptsForNode(ironicNode *nodes.Node) (updates nodes.UpdateOpts, err error) {
-
-	hwProf, err := hardware.GetProfile(p.host.HardwareProfile())
-
-	if err != nil {
-		return updates, errors.Wrap(err,
-			fmt.Sprintf("Could not start provisioning with bad hardware profile %s",
-				p.host.HardwareProfile()))
-	}
-
-	imageOpts, err := p.getImageUpdateOptsForNode(ironicNode, p.host.Spec.Image)
+func (p *ironicProvisioner) getUpdateOptsForNode(ironicNode *nodes.Node, data provisioner.ProvisionData) (updates nodes.UpdateOpts, err error) {
+	imageOpts, err := p.getImageUpdateOptsForNode(ironicNode, &data.Image, data.BootMode)
 	if err != nil {
 		return updates, errors.Wrap(err, "Could not get Image options for node")
 	}
@@ -1012,7 +1008,7 @@ func (p *ironicProvisioner) getUpdateOptsForNode(ironicNode *nodes.Node) (update
 	//
 	// If the user has provided explicit root device hints, they take
 	// precedence. Otherwise use the values from the hardware profile.
-	hints := devicehints.MakeHintMap(p.host.Status.Provisioning.RootDeviceHints)
+	hints := devicehints.MakeHintMap(data.RootDeviceHints)
 	p.log.Info("using root device", "hints", hints)
 	updates = append(
 		updates,
@@ -1039,7 +1035,7 @@ func (p *ironicProvisioner) getUpdateOptsForNode(ironicNode *nodes.Node) (update
 		nodes.UpdateOperation{
 			Op:    op,
 			Path:  "/properties/cpu_arch",
-			Value: hwProf.CPUArch,
+			Value: data.HardwareProfile.CPUArch,
 		},
 	)
 
@@ -1056,12 +1052,12 @@ func (p *ironicProvisioner) getUpdateOptsForNode(ironicNode *nodes.Node) (update
 		nodes.UpdateOperation{
 			Op:    op,
 			Path:  "/properties/local_gb",
-			Value: hwProf.LocalGB,
+			Value: data.HardwareProfile.LocalGB,
 		},
 	)
 
 	// boot_mode
-	op, value := buildCapabilitiesValue(ironicNode, p.host.Status.Provisioning.BootMode)
+	op, value := buildCapabilitiesValue(ironicNode, data.BootMode)
 	updates = append(
 		updates,
 		nodes.UpdateOperation{
@@ -1111,11 +1107,11 @@ func buildCapabilitiesValue(ironicNode *nodes.Node, bootMode metal3v1alpha1.Boot
 	return
 }
 
-func (p *ironicProvisioner) setUpForProvisioning(ironicNode *nodes.Node, hostConf provisioner.HostConfigData) (result provisioner.Result, err error) {
+func (p *ironicProvisioner) setUpForProvisioning(ironicNode *nodes.Node, data provisioner.ProvisionData) (result provisioner.Result, err error) {
 
 	p.log.Info("starting provisioning", "node properties", ironicNode.Properties)
 
-	updates, err := p.getUpdateOptsForNode(ironicNode)
+	updates, err := p.getUpdateOptsForNode(ironicNode, data)
 	if err != nil {
 		return transientError(errors.Wrap(err, "failed to update opts for node"))
 	}
@@ -1153,27 +1149,24 @@ func (p *ironicProvisioner) setUpForProvisioning(ironicNode *nodes.Node, hostCon
 		"deploy step", ironicNode.DeployStep,
 	)
 	p.publisher("ProvisioningStarted",
-		fmt.Sprintf("Image provisioning started for %s", p.host.Spec.Image.URL))
+		fmt.Sprintf("Image provisioning started for %s", data.Image.URL))
 	return
 }
 
-func (p *ironicProvisioner) deployInterface() (result string) {
+func (p *ironicProvisioner) deployInterface(image *metal3v1alpha1.Image) (result string) {
 	result = "direct"
-	if p.host.Spec.Image != nil && p.host.Spec.Image.DiskFormat != nil && *p.host.Spec.Image.DiskFormat == "live-iso" {
+	if image != nil && image.DiskFormat != nil && *image.DiskFormat == "live-iso" {
 		result = "ramdisk"
 	}
 	return result
 }
 
-// Adopt allows an externally-provisioned server to be adopted by Ironic.
-func (p *ironicProvisioner) Adopt(force bool) (result provisioner.Result, err error) {
-	var ironicNode *nodes.Node
-
-	if ironicNode, err = p.findExistingHost(); err != nil {
-		return transientError(errors.Wrap(err, "could not find host to adpot"))
-	}
-	if ironicNode == nil {
-		return transientError(provisioner.NeedsRegistration)
+// Adopt notifies the provisioner that the state machine believes the host
+// to be currently provisioned, and that it should be managed as such.
+func (p *ironicProvisioner) Adopt(data provisioner.AdoptData, force bool) (result provisioner.Result, err error) {
+	ironicNode, err := p.getNode()
+	if err != nil {
+		return transientError(err)
 	}
 
 	switch nodes.ProvisionState(ironicNode.ProvisionState) {
@@ -1183,7 +1176,7 @@ func (p *ironicProvisioner) Adopt(force bool) (result provisioner.Result, err er
 	case nodes.Manageable:
 		_, hasImageSource := ironicNode.InstanceInfo["image_source"]
 		_, hasBootISO := ironicNode.InstanceInfo["boot_iso"]
-		if p.status.State == metal3v1alpha1.StateDeprovisioning &&
+		if data.State == metal3v1alpha1.StateDeprovisioning &&
 			!(hasImageSource || hasBootISO) {
 			// If we got here after a fresh registration and image data is
 			// available, it should have been added to the node during
@@ -1219,18 +1212,18 @@ func (p *ironicProvisioner) Adopt(force bool) (result provisioner.Result, err er
 	return operationComplete()
 }
 
-func (p *ironicProvisioner) ironicHasSameImage(ironicNode *nodes.Node) (sameImage bool) {
+func (p *ironicProvisioner) ironicHasSameImage(ironicNode *nodes.Node, image metal3v1alpha1.Image) (sameImage bool) {
 	// To make it easier to test if ironic is configured with
 	// the same image we are trying to provision to the host.
-	if p.host.Spec.Image != nil && p.host.Spec.Image.DiskFormat != nil && *p.host.Spec.Image.DiskFormat == "live-iso" {
-		sameImage = (ironicNode.InstanceInfo["boot_iso"] == p.host.Spec.Image.URL)
+	if image.DiskFormat != nil && *image.DiskFormat == "live-iso" {
+		sameImage = (ironicNode.InstanceInfo["boot_iso"] == image.URL)
 		p.log.Info("checking image settings",
 			"boot_iso", ironicNode.InstanceInfo["boot_iso"],
 			"same", sameImage,
 			"provisionState", ironicNode.ProvisionState)
 	} else {
-		checksum, checksumType, _ := p.host.GetImageChecksum()
-		sameImage = (ironicNode.InstanceInfo["image_source"] == p.host.Spec.Image.URL &&
+		checksum, checksumType, _ := image.GetChecksum()
+		sameImage = (ironicNode.InstanceInfo["image_source"] == image.URL &&
 			ironicNode.InstanceInfo["image_os_hash_algo"] == checksumType &&
 			ironicNode.InstanceInfo["image_os_hash_value"] == checksum)
 		p.log.Info("checking image settings",
@@ -1243,11 +1236,11 @@ func (p *ironicProvisioner) ironicHasSameImage(ironicNode *nodes.Node) (sameImag
 	return sameImage
 }
 
-func (p *ironicProvisioner) buildManualCleaningSteps(bmcAccess bmc.AccessDetails) (cleanSteps []nodes.CleanStep, err error) {
+func (p *ironicProvisioner) buildManualCleaningSteps(bmcAccess bmc.AccessDetails, data provisioner.PrepareData) (cleanSteps []nodes.CleanStep, err error) {
 	// Build raid clean steps
 	if bmcAccess.RAIDInterface() != "no-raid" {
-		cleanSteps = append(cleanSteps, BuildRAIDCleanSteps(p.host.Status.Provisioning.RAID)...)
-	} else if p.host.Status.Provisioning.RAID != nil {
+		cleanSteps = append(cleanSteps, BuildRAIDCleanSteps(data.RAIDConfig)...)
+	} else if data.RAIDConfig != nil {
 		return nil, fmt.Errorf("RAID settings are defined, but the node's driver %s does not support RAID", bmcAccess.Driver())
 	}
 
@@ -1256,10 +1249,10 @@ func (p *ironicProvisioner) buildManualCleaningSteps(bmcAccess bmc.AccessDetails
 	return
 }
 
-func (p *ironicProvisioner) startManualCleaning(bmcAccess bmc.AccessDetails, ironicNode *nodes.Node) (success bool, result provisioner.Result, err error) {
+func (p *ironicProvisioner) startManualCleaning(bmcAccess bmc.AccessDetails, ironicNode *nodes.Node, data provisioner.PrepareData) (success bool, result provisioner.Result, err error) {
 	if bmcAccess.RAIDInterface() != "no-raid" {
 		// Set raid configuration
-		err = setTargetRAIDCfg(p, ironicNode)
+		err = setTargetRAIDCfg(p, ironicNode, data)
 		if err != nil {
 			result, err = transientError(err)
 			return
@@ -1267,7 +1260,7 @@ func (p *ironicProvisioner) startManualCleaning(bmcAccess bmc.AccessDetails, iro
 	}
 
 	// Build manual clean steps
-	cleanSteps, err := p.buildManualCleaningSteps(bmcAccess)
+	cleanSteps, err := p.buildManualCleaningSteps(bmcAccess, data)
 	if err != nil {
 		result, err = operationFailed(err.Error())
 		return
@@ -1290,27 +1283,23 @@ func (p *ironicProvisioner) startManualCleaning(bmcAccess bmc.AccessDetails, iro
 
 // Prepare remove existing configuration and set new configuration.
 // If `started` is true,  it means that we successfully executed `tryChangeNodeProvisionState`.
-func (p *ironicProvisioner) Prepare(unprepared bool) (result provisioner.Result, started bool, err error) {
+func (p *ironicProvisioner) Prepare(data provisioner.PrepareData, unprepared bool) (result provisioner.Result, started bool, err error) {
 	bmcAccess, err := p.bmcAccess()
 	if err != nil {
 		result, err = transientError(err)
 		return
 	}
 
-	var ironicNode *nodes.Node
-	if ironicNode, err = p.findExistingHost(); err != nil {
-		result, err = transientError(errors.Wrap(err, "could not find host to clean"))
-		return
-	}
-	if ironicNode == nil {
-		result, err = transientError(provisioner.NeedsRegistration)
+	ironicNode, err := p.getNode()
+	if err != nil {
+		result, err = transientError(err)
 		return
 	}
 
 	switch nodes.ProvisionState(ironicNode.ProvisionState) {
 	case nodes.Available:
 		var cleanSteps []nodes.CleanStep
-		cleanSteps, err = p.buildManualCleaningSteps(bmcAccess)
+		cleanSteps, err = p.buildManualCleaningSteps(bmcAccess, data)
 		if err != nil {
 			result, err = operationFailed(err.Error())
 			return
@@ -1326,7 +1315,7 @@ func (p *ironicProvisioner) Prepare(unprepared bool) (result provisioner.Result,
 
 	case nodes.Manageable:
 		if unprepared {
-			started, result, err = p.startManualCleaning(bmcAccess, ironicNode)
+			started, result, err = p.startManualCleaning(bmcAccess, ironicNode, data)
 			return
 		}
 		// Manual clean finished
@@ -1365,19 +1354,15 @@ func (p *ironicProvisioner) Prepare(unprepared bool) (result provisioner.Result,
 // Provision writes the image from the host spec to the host. It may
 // be called multiple times, and should return true for its dirty flag
 // until the deprovisioning operation is completed.
-func (p *ironicProvisioner) Provision(hostConf provisioner.HostConfigData) (result provisioner.Result, err error) {
-	var ironicNode *nodes.Node
-
-	if ironicNode, err = p.findExistingHost(); err != nil {
-		return transientError(errors.Wrap(err, "could not find host to receive image"))
-	}
-	if ironicNode == nil {
-		return transientError(provisioner.NeedsRegistration)
+func (p *ironicProvisioner) Provision(data provisioner.ProvisionData) (result provisioner.Result, err error) {
+	ironicNode, err := p.getNode()
+	if err != nil {
+		return transientError(err)
 	}
 
 	p.log.Info("provisioning image to host", "state", ironicNode.ProvisionState)
 
-	ironicHasSameImage := p.ironicHasSameImage(ironicNode)
+	ironicHasSameImage := p.ironicHasSameImage(ironicNode, data.Image)
 
 	// Ironic has the settings it needs, see if it finds any issues
 	// with them.
@@ -1400,7 +1385,7 @@ func (p *ironicProvisioner) Provision(hostConf provisioner.HostConfigData) (resu
 				ironicNode.LastError))
 		}
 		p.log.Info("recovering from previous failure")
-		if provResult, err := p.setUpForProvisioning(ironicNode, hostConf); err != nil || provResult.Dirty || provResult.ErrorMessage != "" {
+		if provResult, err := p.setUpForProvisioning(ironicNode, data); err != nil || provResult.Dirty || provResult.ErrorMessage != "" {
 			return provResult, err
 		}
 
@@ -1422,7 +1407,7 @@ func (p *ironicProvisioner) Provision(hostConf provisioner.HostConfigData) (resu
 		)
 
 	case nodes.Available:
-		if provResult, err := p.setUpForProvisioning(ironicNode, hostConf); err != nil || provResult.Dirty || provResult.ErrorMessage != "" {
+		if provResult, err := p.setUpForProvisioning(ironicNode, data); err != nil || provResult.Dirty || provResult.ErrorMessage != "" {
 			return provResult, err
 		}
 
@@ -1431,13 +1416,13 @@ func (p *ironicProvisioner) Provision(hostConf provisioner.HostConfigData) (resu
 		p.log.Info("making host active")
 
 		// Retrieve cloud-init user data
-		userData, err := hostConf.UserData()
+		userData, err := data.HostConfig.UserData()
 		if err != nil {
 			return transientError(errors.Wrap(err, "could not retrieve user data"))
 		}
 
 		// Retrieve cloud-init network_data.json. Default value is empty
-		networkDataRaw, err := hostConf.NetworkData()
+		networkDataRaw, err := data.HostConfig.NetworkData()
 		if err != nil {
 			return transientError(errors.Wrap(err, "could not retrieve network data"))
 		}
@@ -1448,14 +1433,14 @@ func (p *ironicProvisioner) Provision(hostConf provisioner.HostConfigData) (resu
 
 		// Retrieve cloud-init meta_data.json with falback to default
 		metaData := map[string]interface{}{
-			"uuid":             string(p.host.ObjectMeta.UID),
-			"metal3-namespace": p.host.ObjectMeta.Namespace,
-			"metal3-name":      p.host.ObjectMeta.Name,
-			"local-hostname":   p.host.ObjectMeta.Name,
-			"local_hostname":   p.host.ObjectMeta.Name,
-			"name":             p.host.ObjectMeta.Name,
+			"uuid":             string(p.objectMeta.UID),
+			"metal3-namespace": p.objectMeta.Namespace,
+			"metal3-name":      p.objectMeta.Name,
+			"local-hostname":   p.objectMeta.Name,
+			"local_hostname":   p.objectMeta.Name,
+			"name":             p.objectMeta.Name,
 		}
-		metaDataRaw, err := hostConf.MetaData()
+		metaDataRaw, err := data.HostConfig.MetaData()
 		if err != nil {
 			return transientError(errors.Wrap(err, "could not retrieve metadata"))
 		}
@@ -1491,7 +1476,7 @@ func (p *ironicProvisioner) Provision(hostConf provisioner.HostConfigData) (resu
 	case nodes.Active:
 		// provisioning is done
 		p.publisher("ProvisioningComplete",
-			fmt.Sprintf("Image provisioning completed for %s", p.host.Spec.Image.URL))
+			fmt.Sprintf("Image provisioning completed for %s", data.Image.URL))
 		p.log.Info("finished provisioning")
 		return operationComplete()
 
@@ -1533,12 +1518,9 @@ func (p *ironicProvisioner) setMaintenanceFlag(ironicNode *nodes.Node, value boo
 func (p *ironicProvisioner) Deprovision(force bool) (result provisioner.Result, err error) {
 	p.log.Info("deprovisioning")
 
-	ironicNode, err := p.findExistingHost()
+	ironicNode, err := p.getNode()
 	if err != nil {
-		return transientError(errors.Wrap(err, "failed to find existing host"))
-	}
-	if ironicNode == nil {
-		return transientError(provisioner.NeedsRegistration)
+		return transientError(err)
 	}
 
 	p.log.Info("deprovisioning host",
@@ -1627,14 +1609,13 @@ func (p *ironicProvisioner) Deprovision(force bool) (result provisioner.Result,
 // called multiple times, and should return true for its dirty flag
 // until the deprovisioning operation is completed.
 func (p *ironicProvisioner) Delete() (result provisioner.Result, err error) {
-
-	ironicNode, err := p.findExistingHost()
+	ironicNode, err := p.getNode()
 	if err != nil {
-		return transientError(errors.Wrap(err, "failed to find existing host"))
-	}
-	if ironicNode == nil {
-		p.log.Info("no node found, already deleted")
-		return operationComplete()
+		if errors.Is(err, provisioner.NeedsRegistration) {
+			p.log.Info("no node found, already deleted")
+			return operationComplete()
+		}
+		return transientError(err)
 	}
 
 	p.log.Info("deleting host",
@@ -1716,11 +1697,11 @@ func (p *ironicProvisioner) changePower(ironicNode *nodes.Node, target nodes.Tar
 	case gophercloud.ErrDefault409:
 		p.log.Info("host is locked, trying again after delay", "delay", powerRequeueDelay)
 		result, _ = retryAfterDelay(powerRequeueDelay)
-		return result, HostLockedError{Address: p.host.Spec.BMC.Address}
+		return result, HostLockedError{}
 	case gophercloud.ErrDefault400:
 		// Error 400 Bad Request means target power state is not supported by vendor driver
 		p.log.Info("power change error", "message", changeResult.Err)
-		return result, SoftPowerOffUnsupportedError{Address: p.host.Spec.BMC.Address}
+		return result, SoftPowerOffUnsupportedError{}
 	default:
 		p.log.Info("power change error", "message", changeResult.Err)
 		return transientError(errors.Wrap(changeResult.Err, "failed to change power state"))
@@ -1732,13 +1713,12 @@ func (p *ironicProvisioner) changePower(ironicNode *nodes.Node, target nodes.Tar
 func (p *ironicProvisioner) PowerOn() (result provisioner.Result, err error) {
 	p.log.Info("ensuring host is powered on")
 
-	ironicNode, err := p.findExistingHost()
+	ironicNode, err := p.getNode()
 	if err != nil {
-		return transientError(errors.Wrap(err, "failed to find existing host"))
+		return transientError(err)
 	}
 
 	p.log.Info("checking current state",
-		"current", p.host.Status.PoweredOn,
 		"target", ironicNode.TargetPowerState)
 
 	if ironicNode.PowerState != powerOn {
@@ -1788,9 +1768,9 @@ func (p *ironicProvisioner) PowerOff(rebootMode metal3v1alpha1.RebootMode) (resu
 func (p *ironicProvisioner) hardPowerOff() (result provisioner.Result, err error) {
 	p.log.Info("ensuring host is powered off by \"hard power off\" command")
 
-	ironicNode, err := p.findExistingHost()
+	ironicNode, err := p.getNode()
 	if err != nil {
-		return transientError(errors.Wrap(err, "failed to find existing host"))
+		return transientError(err)
 	}
 
 	if ironicNode.PowerState != powerOff {
@@ -1817,9 +1797,9 @@ func (p *ironicProvisioner) hardPowerOff() (result provisioner.Result, err error
 func (p *ironicProvisioner) softPowerOff() (result provisioner.Result, err error) {
 	p.log.Info("ensuring host is powered off by \"soft power off\" command")
 
-	ironicNode, err := p.findExistingHost()
+	ironicNode, err := p.getNode()
 	if err != nil {
-		return transientError(errors.Wrap(err, "failed to find existing host"))
+		return transientError(err)
 	}
 
 	if ironicNode.PowerState != powerOff {
@@ -1832,7 +1812,7 @@ func (p *ironicProvisioner) softPowerOff() (result provisioner.Result, err error
 		// If the target state is unset while the last error is set,
 		// then the last execution of soft power off has failed.
 		if targetState == "" && ironicNode.LastError != "" {
-			return result, SoftPowerOffFailed{Address: p.host.Spec.BMC.Address}
+			return result, SoftPowerOffFailed{}
 		}
 		result, err = p.changePower(ironicNode, nodes.SoftPowerOff)
 		if err != nil {
@@ -1844,8 +1824,8 @@ func (p *ironicProvisioner) softPowerOff() (result provisioner.Result, err error
 	return result, nil
 }
 
-func (p *ironicProvisioner) ironicNodeNameFromHost() string {
-	return p.host.Namespace + nameSeparator + p.host.Name
+func ironicNodeName(objMeta metav1.ObjectMeta) string {
+	return objMeta.Namespace + nameSeparator + objMeta.Name
 }
 
 // IsReady checks if the provisioning backend is available
@@ -1865,7 +1845,7 @@ func (p *ironicProvisioner) HasProvisioningCapacity() (result bool, err error) {
 	}
 
 	// If the current host is already under processing then let's skip the test
-	if _, ok := hosts[p.ironicNodeNameFromHost()]; ok {
+	if _, ok := hosts[ironicNodeName(p.objectMeta)]; ok {
 		return true, nil
 	}
 
diff --git a/pkg/provisioner/ironic/raid.go b/pkg/provisioner/ironic/raid.go
index 2acefeb7..75127f0c 100644
--- a/pkg/provisioner/ironic/raid.go
+++ b/pkg/provisioner/ironic/raid.go
@@ -6,23 +6,24 @@ import (
 	"github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes"
 
 	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
+	"github.com/metal3-io/baremetal-operator/pkg/provisioner"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/devicehints"
 
 	"github.com/pkg/errors"
 )
 
 // setTargetRAIDCfg set the RAID settings to the ironic Node for RAID configuration steps
-func setTargetRAIDCfg(p *ironicProvisioner, ironicNode *nodes.Node) (err error) {
+func setTargetRAIDCfg(p *ironicProvisioner, ironicNode *nodes.Node, data provisioner.PrepareData) (err error) {
 	var logicalDisks []nodes.LogicalDisk
 
 	// Build target for RAID configuration steps
-	logicalDisks, err = BuildTargetRAIDCfg(p.host.Status.Provisioning.RAID)
+	logicalDisks, err = BuildTargetRAIDCfg(data.RAIDConfig)
 	if len(logicalDisks) == 0 || err != nil {
 		return
 	}
 
 	// set root volume
-	if p.host.Status.Provisioning.RootDeviceHints == nil {
+	if data.RootDeviceHints == nil {
 		logicalDisks[0].IsRootVolume = new(bool)
 		*logicalDisks[0].IsRootVolume = true
 	} else {
diff --git a/pkg/provisioner/provisioner.go b/pkg/provisioner/provisioner.go
index a32c9757..5422208d 100644
--- a/pkg/provisioner/provisioner.go
+++ b/pkg/provisioner/provisioner.go
@@ -4,8 +4,11 @@ import (
 	"errors"
 	"time"
 
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
 	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 	"github.com/metal3-io/baremetal-operator/pkg/bmc"
+	"github.com/metal3-io/baremetal-operator/pkg/hardware"
 )
 
 /*
@@ -16,8 +19,28 @@ Package provisioning defines the API for talking to the provisioning backend.
 // with provisioning.
 type EventPublisher func(reason, message string)
 
+type HostData struct {
+	ObjectMeta                     metav1.ObjectMeta
+	BMCAddress                     string
+	BMCCredentials                 bmc.Credentials
+	DisableCertificateVerification bool
+	BootMACAddress                 string
+	ProvisionerID                  string
+}
+
+func BuildHostData(host metal3v1alpha1.BareMetalHost, bmcCreds bmc.Credentials) HostData {
+	return HostData{
+		ObjectMeta:                     *host.ObjectMeta.DeepCopy(),
+		BMCAddress:                     host.Spec.BMC.Address,
+		BMCCredentials:                 bmcCreds,
+		DisableCertificateVerification: host.Spec.BMC.DisableCertificateVerification,
+		BootMACAddress:                 host.Spec.BootMACAddress,
+		ProvisionerID:                  host.Status.Provisioning.ID,
+	}
+}
+
 // Factory is the interface for creating new Provisioner objects.
-type Factory func(host metal3v1alpha1.BareMetalHost, bmcCreds bmc.Credentials, publish EventPublisher) (Provisioner, error)
+type Factory func(hostData HostData, publish EventPublisher) (Provisioner, error)
 
 // HostConfigData retrieves host configuration data
 type HostConfigData interface {
@@ -34,6 +57,34 @@ type HostConfigData interface {
 	MetaData() (string, error)
 }
 
+type ManagementAccessData struct {
+	BootMode              metal3v1alpha1.BootMode
+	AutomatedCleaningMode metal3v1alpha1.AutomatedCleaningMode
+	State                 metal3v1alpha1.ProvisioningState
+	CurrentImage          *metal3v1alpha1.Image
+}
+
+type AdoptData struct {
+	State metal3v1alpha1.ProvisioningState
+}
+
+type InspectData struct {
+	BootMode metal3v1alpha1.BootMode
+}
+
+type PrepareData struct {
+	RAIDConfig      *metal3v1alpha1.RAIDConfig
+	RootDeviceHints *metal3v1alpha1.RootDeviceHints
+}
+
+type ProvisionData struct {
+	Image           metal3v1alpha1.Image
+	HostConfig      HostConfigData
+	BootMode        metal3v1alpha1.BootMode
+	HardwareProfile hardware.Profile
+	RootDeviceHints *metal3v1alpha1.RootDeviceHints
+}
+
 // Provisioner holds the state information for talking to the
 // provisioning backend.
 type Provisioner interface {
@@ -43,13 +94,13 @@ type Provisioner interface {
 	// of credentials it has are different from the credentials it has
 	// previously been using, without implying that either set of
 	// credentials is correct.
-	ValidateManagementAccess(credentialsChanged, force bool) (result Result, provID string, err error)
+	ValidateManagementAccess(data ManagementAccessData, credentialsChanged, force bool) (result Result, provID string, err error)
 
 	// InspectHardware updates the HardwareDetails field of the host with
 	// details of devices discovered on the hardware. It may be called
 	// multiple times, and should return true for its dirty flag until the
 	// inspection is completed.
-	InspectHardware(force, refresh bool) (result Result, details *metal3v1alpha1.HardwareDetails, err error)
+	InspectHardware(data InspectData, force, refresh bool) (result Result, details *metal3v1alpha1.HardwareDetails, err error)
 
 	// UpdateHardwareState fetches the latest hardware state of the
 	// server and updates the HardwareDetails field of the host with
@@ -59,15 +110,15 @@ type Provisioner interface {
 
 	// Adopt brings an externally-provisioned host under management by
 	// the provisioner.
-	Adopt(force bool) (result Result, err error)
+	Adopt(data AdoptData, force bool) (result Result, err error)
 
 	// Prepare remove existing configuration and set new configuration
-	Prepare(unprepared bool) (result Result, started bool, err error)
+	Prepare(data PrepareData, unprepared bool) (result Result, started bool, err error)
 
 	// Provision writes the image from the host spec to the host. It
 	// may be called multiple times, and should return true for its
 	// dirty flag until the deprovisioning operation is completed.
-	Provision(configData HostConfigData) (result Result, err error)
+	Provision(data ProvisionData) (result Result, err error)
 
 	// Deprovision removes the host from the image. It may be called
 	// multiple times, and should return true for its dirty flag until


Test patch:
diff --git a/apis/metal3.io/v1alpha1/baremetalhost_types_test.go b/apis/metal3.io/v1alpha1/baremetalhost_types_test.go
index 5a005d5d..8d499c86 100644
--- a/apis/metal3.io/v1alpha1/baremetalhost_types_test.go
+++ b/apis/metal3.io/v1alpha1/baremetalhost_types_test.go
@@ -443,7 +443,7 @@ func TestGetImageChecksum(t *testing.T) {
 		},
 	} {
 		t.Run(tc.Scenario, func(t *testing.T) {
-			_, _, actual := tc.Host.GetImageChecksum()
+			_, _, actual := tc.Host.Spec.Image.GetChecksum()
 			if actual != tc.Expected {
 				t.Errorf("expected %v but got %v", tc.Expected, actual)
 			}
diff --git a/controllers/metal3.io/baremetalhost_controller_test.go b/controllers/metal3.io/baremetalhost_controller_test.go
index 8ae29f4d..ebaef87c 100644
--- a/controllers/metal3.io/baremetalhost_controller_test.go
+++ b/controllers/metal3.io/baremetalhost_controller_test.go
@@ -1472,6 +1472,94 @@ func TestErrorCountIncrementsAlways(t *testing.T) {
 	}
 }
 
+func TestGetImageReady(t *testing.T) {
+	host := metal3v1alpha1.BareMetalHost{
+		Spec: metal3v1alpha1.BareMetalHostSpec{
+			Image: &metal3v1alpha1.Image{
+				URL: "http://example.test/image",
+			},
+		},
+		Status: metal3v1alpha1.BareMetalHostStatus{
+			Provisioning: metal3v1alpha1.ProvisionStatus{
+				State: metal3v1alpha1.StateReady,
+			},
+		},
+	}
+
+	img := getCurrentImage(&host)
+
+	assert.Nil(t, img)
+}
+
+func TestGetImageProvisioning(t *testing.T) {
+	host := metal3v1alpha1.BareMetalHost{
+		Spec: metal3v1alpha1.BareMetalHostSpec{
+			Image: &metal3v1alpha1.Image{
+				URL: "http://example.test/image",
+			},
+		},
+		Status: metal3v1alpha1.BareMetalHostStatus{
+			Provisioning: metal3v1alpha1.ProvisionStatus{
+				State: metal3v1alpha1.StateProvisioning,
+			},
+		},
+	}
+
+	img := getCurrentImage(&host)
+
+	assert.NotNil(t, img)
+	assert.NotSame(t, host.Spec.Image, img)
+	assert.Exactly(t, *host.Spec.Image, *img)
+}
+
+func TestGetImageProvisioned(t *testing.T) {
+	host := metal3v1alpha1.BareMetalHost{
+		Spec: metal3v1alpha1.BareMetalHostSpec{
+			Image: &metal3v1alpha1.Image{
+				URL: "http://example.test/image2",
+			},
+		},
+		Status: metal3v1alpha1.BareMetalHostStatus{
+			Provisioning: metal3v1alpha1.ProvisionStatus{
+				State: metal3v1alpha1.StateProvisioned,
+				Image: metal3v1alpha1.Image{
+					URL: "http://example.test/image",
+				},
+			},
+		},
+	}
+
+	img := getCurrentImage(&host)
+
+	assert.NotNil(t, img)
+	assert.NotSame(t, &host.Status.Provisioning.Image, img)
+	assert.Exactly(t, host.Status.Provisioning.Image, *img)
+}
+
+func TestGetImageDeprovisioning(t *testing.T) {
+	host := metal3v1alpha1.BareMetalHost{
+		Spec: metal3v1alpha1.BareMetalHostSpec{
+			Image: &metal3v1alpha1.Image{
+				URL: "http://example.test/image2",
+			},
+		},
+		Status: metal3v1alpha1.BareMetalHostStatus{
+			Provisioning: metal3v1alpha1.ProvisionStatus{
+				State: metal3v1alpha1.StateDeprovisioning,
+				Image: metal3v1alpha1.Image{
+					URL: "http://example.test/image",
+				},
+			},
+		},
+	}
+
+	img := getCurrentImage(&host)
+
+	assert.NotNil(t, img)
+	assert.NotSame(t, &host.Status.Provisioning.Image, img)
+	assert.Exactly(t, host.Status.Provisioning.Image, *img)
+}
+
 func TestUpdateRAID(t *testing.T) {
 	host := metal3v1alpha1.BareMetalHost{
 		Spec: metal3v1alpha1.BareMetalHostSpec{
diff --git a/controllers/metal3.io/host_state_machine_test.go b/controllers/metal3.io/host_state_machine_test.go
index 298dddd5..e6ff6f1c 100644
--- a/controllers/metal3.io/host_state_machine_test.go
+++ b/controllers/metal3.io/host_state_machine_test.go
@@ -11,6 +11,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	"github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
+	"github.com/metal3-io/baremetal-operator/pkg/hardware"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner"
 	promutil "github.com/prometheus/client_golang/prometheus/testutil"
 	corev1 "k8s.io/api/core/v1"
@@ -20,7 +21,7 @@ import (
 
 func testStateMachine(host *metal3v1alpha1.BareMetalHost) *hostStateMachine {
 	r := newTestReconciler()
-	p, _ := r.ProvisionerFactory(*host.DeepCopy(), bmc.Credentials{},
+	p, _ := r.ProvisionerFactory(provisioner.BuildHostData(*host, bmc.Credentials{}),
 		func(reason, message string) {})
 	return newHostStateMachine(host, r, p, true)
 }
@@ -489,6 +490,7 @@ func host(state metal3v1alpha1.ProvisioningState) *hostBuilder {
 				RootDeviceHints: &v1alpha1.RootDeviceHints{},
 			},
 			Status: metal3v1alpha1.BareMetalHostStatus{
+				HardwareProfile: hardware.DefaultProfileName,
 				Provisioning: metal3v1alpha1.ProvisionStatus{
 					State:    state,
 					BootMode: v1alpha1.DefaultBootMode,
@@ -614,11 +616,11 @@ func (m *mockProvisioner) setNextError(methodName, msg string) {
 	}
 }
 
-func (m *mockProvisioner) ValidateManagementAccess(credentialsChanged, force bool) (result provisioner.Result, provID string, err error) {
+func (m *mockProvisioner) ValidateManagementAccess(data provisioner.ManagementAccessData, credentialsChanged, force bool) (result provisioner.Result, provID string, err error) {
 	return m.getNextResultByMethod("ValidateManagementAccess"), "", err
 }
 
-func (m *mockProvisioner) InspectHardware(force, refresh bool) (result provisioner.Result, details *metal3v1alpha1.HardwareDetails, err error) {
+func (m *mockProvisioner) InspectHardware(data provisioner.InspectData, force, refresh bool) (result provisioner.Result, details *metal3v1alpha1.HardwareDetails, err error) {
 	details = &metal3v1alpha1.HardwareDetails{}
 	return m.getNextResultByMethod("InspectHardware"), details, err
 }
@@ -627,15 +629,15 @@ func (m *mockProvisioner) UpdateHardwareState() (hwState provisioner.HardwareSta
 	return
 }
 
-func (m *mockProvisioner) Prepare(unprepared bool) (result provisioner.Result, started bool, err error) {
+func (m *mockProvisioner) Prepare(data provisioner.PrepareData, unprepared bool) (result provisioner.Result, started bool, err error) {
 	return m.getNextResultByMethod("Prepare"), m.nextResults["Prepare"].Dirty, err
 }
 
-func (m *mockProvisioner) Adopt(force bool) (result provisioner.Result, err error) {
+func (m *mockProvisioner) Adopt(data provisioner.AdoptData, force bool) (result provisioner.Result, err error) {
 	return m.getNextResultByMethod("Adopt"), err
 }
 
-func (m *mockProvisioner) Provision(configData provisioner.HostConfigData) (result provisioner.Result, err error) {
+func (m *mockProvisioner) Provision(data provisioner.ProvisionData) (result provisioner.Result, err error) {
 	return m.getNextResultByMethod("Provision"), err
 }
 
diff --git a/pkg/provisioner/ironic/adopt_test.go b/pkg/provisioner/ironic/adopt_test.go
index e47fb9fd..5edd50b5 100644
--- a/pkg/provisioner/ironic/adopt_test.go
+++ b/pkg/provisioner/ironic/adopt_test.go
@@ -9,6 +9,7 @@ import (
 	"github.com/stretchr/testify/assert"
 
 	"github.com/metal3-io/baremetal-operator/pkg/bmc"
+	"github.com/metal3-io/baremetal-operator/pkg/provisioner"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/clients"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/testserver"
 )
@@ -102,6 +103,7 @@ func TestAdopt(t *testing.T) {
 			defer inspector.Stop()
 
 			host := makeHost()
+			host.Status.Provisioning.ID = nodeUUID
 			publisher := func(reason, message string) {}
 			auth := clients.AuthConfig{Type: clients.NoAuth}
 			prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, publisher,
@@ -111,8 +113,8 @@ func TestAdopt(t *testing.T) {
 				t.Fatalf("could not create provisioner: %s", err)
 			}
 
-			prov.status.ID = nodeUUID
-			result, err := prov.Adopt(tc.force)
+			adoptData := provisioner.AdoptData{State: host.Status.Provisioning.State}
+			result, err := prov.Adopt(adoptData, tc.force)
 
 			assert.Equal(t, tc.expectedDirty, result.Dirty)
 			assert.Equal(t, time.Second*time.Duration(tc.expectedRequestAfter), result.RequeueAfter)
diff --git a/pkg/provisioner/ironic/delete_test.go b/pkg/provisioner/ironic/delete_test.go
index 6230964f..c456dd34 100644
--- a/pkg/provisioner/ironic/delete_test.go
+++ b/pkg/provisioner/ironic/delete_test.go
@@ -82,7 +82,7 @@ func TestDelete(t *testing.T) {
 			hostName: "worker-0",
 			ironic:   testserver.NewIronic(t).Ready().NodeError(nodeUUID, http.StatusGatewayTimeout),
 
-			expectedError: "failed to find existing host: failed to find node by ID 33ce8659-7400-4c68-9535-d10766f07a58: Expected HTTP response code \\[200\\].*",
+			expectedError: "failed to find node by ID 33ce8659-7400-4c68-9535-d10766f07a58: Expected HTTP response code \\[200\\].*",
 		},
 		{
 			name:   "not-ironic-node",
diff --git a/pkg/provisioner/ironic/findhost_test.go b/pkg/provisioner/ironic/findhost_test.go
index e8b21af6..87726d13 100644
--- a/pkg/provisioner/ironic/findhost_test.go
+++ b/pkg/provisioner/ironic/findhost_test.go
@@ -70,7 +70,7 @@ func TestFindExistingHost(t *testing.T) {
 				t.Fatalf("could not create provisioner: %s", err)
 			}
 
-			node, err := prov.findExistingHost()
+			node, err := prov.findExistingHost("")
 			t.Logf("requests: %s", tc.ironic.Requests)
 			if err != nil {
 				t.Fatalf("could not look up host: %s", err)
diff --git a/pkg/provisioner/ironic/inspecthardware_test.go b/pkg/provisioner/ironic/inspecthardware_test.go
index f391dc9a..06372d15 100644
--- a/pkg/provisioner/ironic/inspecthardware_test.go
+++ b/pkg/provisioner/ironic/inspecthardware_test.go
@@ -6,9 +6,12 @@ import (
 	"time"
 
 	"github.com/metal3-io/baremetal-operator/pkg/bmc"
+	"github.com/metal3-io/baremetal-operator/pkg/provisioner"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/clients"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/testserver"
 
+	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
+
 	"github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes"
 	"github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection"
 	"github.com/stretchr/testify/assert"
@@ -192,6 +195,7 @@ func TestInspectHardware(t *testing.T) {
 			}
 
 			host := makeHost()
+			host.Status.Provisioning.ID = nodeUUID
 			publishedMsg := ""
 			publisher := func(reason, message string) {
 				publishedMsg = reason + " " + message
@@ -204,8 +208,9 @@ func TestInspectHardware(t *testing.T) {
 				t.Fatalf("could not create provisioner: %s", err)
 			}
 
-			prov.status.ID = nodeUUID
-			result, details, err := prov.InspectHardware(tc.force, false)
+			result, details, err := prov.InspectHardware(
+				provisioner.InspectData{BootMode: metal3v1alpha1.DefaultBootMode},
+				tc.force, false)
 
 			assert.Equal(t, tc.expectedDirty, result.Dirty)
 			assert.Equal(t, time.Second*time.Duration(tc.expectedRequestAfter), result.RequeueAfter)
diff --git a/pkg/provisioner/ironic/ironic_test.go b/pkg/provisioner/ironic/ironic_test.go
index a3561d31..49e97c5b 100644
--- a/pkg/provisioner/ironic/ironic_test.go
+++ b/pkg/provisioner/ironic/ironic_test.go
@@ -10,6 +10,7 @@ import (
 
 	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 	"github.com/metal3-io/baremetal-operator/pkg/bmc"
+	"github.com/metal3-io/baremetal-operator/pkg/provisioner"
 
 	// We don't use this package directly here, but need it imported
 	// so it registers its test fixture with the other BMC access
@@ -91,7 +92,7 @@ func TestNewNoBMCDetails(t *testing.T) {
 	host := makeHost()
 	host.Spec.BMC = metal3v1alpha1.BMCDetails{}
 
-	prov, err := New(host, bmc.Credentials{}, nullEventPublisher)
+	prov, err := New(provisioner.BuildHostData(host, bmc.Credentials{}), nullEventPublisher)
 	assert.Equal(t, nil, err)
 	assert.NotEqual(t, nil, prov)
 }
diff --git a/pkg/provisioner/ironic/power_test.go b/pkg/provisioner/ironic/power_test.go
index 02ff3694..2db33570 100644
--- a/pkg/provisioner/ironic/power_test.go
+++ b/pkg/provisioner/ironic/power_test.go
@@ -91,6 +91,7 @@ func TestPowerOn(t *testing.T) {
 			defer inspector.Stop()
 
 			host := makeHost()
+			host.Status.Provisioning.ID = nodeUUID
 			publisher := func(reason, message string) {}
 			auth := clients.AuthConfig{Type: clients.NoAuth}
 			prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, publisher,
@@ -100,7 +101,6 @@ func TestPowerOn(t *testing.T) {
 				t.Fatalf("could not create provisioner: %s", err)
 			}
 
-			prov.status.ID = nodeUUID
 			result, err := prov.PowerOn()
 
 			assert.Equal(t, tc.expectedDirty, result.Dirty)
@@ -203,6 +203,7 @@ func TestPowerOff(t *testing.T) {
 			defer inspector.Stop()
 
 			host := makeHost()
+			host.Status.Provisioning.ID = nodeUUID
 			publisher := func(reason, message string) {}
 			auth := clients.AuthConfig{Type: clients.NoAuth}
 			prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, publisher,
@@ -212,7 +213,6 @@ func TestPowerOff(t *testing.T) {
 				t.Fatalf("could not create provisioner: %s", err)
 			}
 
-			prov.status.ID = nodeUUID
 			// We pass the RebootMode type here to define the reboot action
 			result, err := prov.PowerOff(tc.rebootMode)
 
diff --git a/pkg/provisioner/ironic/prepare_test.go b/pkg/provisioner/ironic/prepare_test.go
index 1ec188ae..3633b558 100644
--- a/pkg/provisioner/ironic/prepare_test.go
+++ b/pkg/provisioner/ironic/prepare_test.go
@@ -10,6 +10,7 @@ import (
 
 	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 	"github.com/metal3-io/baremetal-operator/pkg/bmc"
+	"github.com/metal3-io/baremetal-operator/pkg/provisioner"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/clients"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/testserver"
 )
@@ -120,9 +121,11 @@ func TestPrepare(t *testing.T) {
 			defer inspector.Stop()
 
 			host := makeHost()
+			host.Status.Provisioning.ID = nodeUUID
+			prepData := provisioner.PrepareData{}
 			if tc.existRaidConfig {
 				host.Spec.BMC.Address = "irmc://test.bmc/"
-				host.Status.Provisioning.RAID = &metal3v1alpha1.RAIDConfig{
+				prepData.RAIDConfig = &metal3v1alpha1.RAIDConfig{
 					HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
 						{
 							Name:  "root",
@@ -145,8 +148,7 @@ func TestPrepare(t *testing.T) {
 				t.Fatalf("could not create provisioner: %s", err)
 			}
 
-			prov.status.ID = nodeUUID
-			result, started, err := prov.Prepare(tc.unprepared)
+			result, started, err := prov.Prepare(prepData, tc.unprepared)
 
 			assert.Equal(t, tc.expectedStarted, started)
 			assert.Equal(t, tc.expectedDirty, result.Dirty)
diff --git a/pkg/provisioner/ironic/provision_test.go b/pkg/provisioner/ironic/provision_test.go
index 96186599..b0581e74 100644
--- a/pkg/provisioner/ironic/provision_test.go
+++ b/pkg/provisioner/ironic/provision_test.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 	"github.com/metal3-io/baremetal-operator/pkg/bmc"
+	"github.com/metal3-io/baremetal-operator/pkg/provisioner"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/fixture"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/clients"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/testserver"
@@ -98,6 +99,7 @@ func TestProvision(t *testing.T) {
 			defer inspector.Stop()
 
 			host := makeHost()
+			host.Status.Provisioning.ID = nodeUUID
 			publisher := func(reason, message string) {}
 			auth := clients.AuthConfig{Type: clients.NoAuth}
 			prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, publisher,
@@ -107,8 +109,10 @@ func TestProvision(t *testing.T) {
 				t.Fatalf("could not create provisioner: %s", err)
 			}
 
-			prov.status.ID = nodeUUID
-			result, err := prov.Provision(fixture.NewHostConfigData("testUserData", "test: NetworkData", "test: Meta"))
+			result, err := prov.Provision(provisioner.ProvisionData{
+				HostConfig: fixture.NewHostConfigData("testUserData", "test: NetworkData", "test: Meta"),
+				BootMode:   v1alpha1.DefaultBootMode,
+			})
 
 			assert.Equal(t, tc.expectedDirty, result.Dirty)
 			assert.Equal(t, time.Second*time.Duration(tc.expectedRequestAfter), result.RequeueAfter)
@@ -232,6 +236,7 @@ func TestDeprovision(t *testing.T) {
 			defer inspector.Stop()
 
 			host := makeHost()
+			host.Status.Provisioning.ID = nodeUUID
 			publisher := func(reason, message string) {}
 			auth := clients.AuthConfig{Type: clients.NoAuth}
 			prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, publisher,
@@ -241,7 +246,6 @@ func TestDeprovision(t *testing.T) {
 				t.Fatalf("could not create provisioner: %s", err)
 			}
 
-			prov.status.ID = nodeUUID
 			result, err := prov.Deprovision(false)
 
 			assert.Equal(t, tc.expectedDirty, result.Dirty)
@@ -373,6 +377,7 @@ func TestIronicHasSameImage(t *testing.T) {
 				host.Spec.Image.Checksum = tc.hostChecksum
 				host.Spec.Image.ChecksumType = tc.hostChecksumType
 			}
+			host.Status.Provisioning.ID = nodeUUID
 			publisher := func(reason, message string) {}
 			auth := clients.AuthConfig{Type: clients.NoAuth}
 			prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, publisher,
@@ -382,8 +387,7 @@ func TestIronicHasSameImage(t *testing.T) {
 				t.Fatalf("could not create provisioner: %s", err)
 			}
 
-			prov.status.ID = nodeUUID
-			sameImage := prov.ironicHasSameImage(&tc.node)
+			sameImage := prov.ironicHasSameImage(&tc.node, *host.Spec.Image)
 			assert.Equal(t, tc.expected, sameImage)
 		})
 	}
diff --git a/pkg/provisioner/ironic/updatehardwarestate_test.go b/pkg/provisioner/ironic/updatehardwarestate_test.go
index 691a2f0a..8aac4457 100644
--- a/pkg/provisioner/ironic/updatehardwarestate_test.go
+++ b/pkg/provisioner/ironic/updatehardwarestate_test.go
@@ -82,7 +82,7 @@ func TestUpdateHardwareState(t *testing.T) {
 			hostName: "worker-0",
 			ironic:   testserver.NewIronic(t).Ready().NodeError(nodeUUID, http.StatusGatewayTimeout),
 
-			expectedError: "failed to find existing host: failed to find node by ID 33ce8659-7400-4c68-9535-d10766f07a58: Expected HTTP response code \\[200\\].*",
+			expectedError: "failed to find node by ID 33ce8659-7400-4c68-9535-d10766f07a58: Expected HTTP response code \\[200\\].*",
 
 			expectUnreadablePower: true,
 		},
@@ -90,9 +90,9 @@ func TestUpdateHardwareState(t *testing.T) {
 			name: "node-not-found-by-name",
 
 			hostName: "worker-0",
-			ironic:   testserver.NewIronic(t).Ready().NoNode(nodeUUID).NodeError("myns.myhost", http.StatusGatewayTimeout),
+			ironic:   testserver.NewIronic(t).Ready().NoNode(nodeUUID).NodeError("myns"+nameSeparator+"myhost", http.StatusGatewayTimeout),
 
-			expectedError: "failed to find existing host: failed to find node by name myns.worker-0: EOF",
+			expectedError: "Host not registered",
 
 			expectUnreadablePower: true,
 		},
diff --git a/pkg/provisioner/ironic/updateopts_test.go b/pkg/provisioner/ironic/updateopts_test.go
index 56f67563..57c119bd 100644
--- a/pkg/provisioner/ironic/updateopts_test.go
+++ b/pkg/provisioner/ironic/updateopts_test.go
@@ -12,6 +12,8 @@ import (
 
 	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 	"github.com/metal3-io/baremetal-operator/pkg/bmc"
+	"github.com/metal3-io/baremetal-operator/pkg/hardware"
+	"github.com/metal3-io/baremetal-operator/pkg/provisioner"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/clients"
 )
 
@@ -20,7 +22,8 @@ func TestGetUpdateOptsForNodeWithRootHints(t *testing.T) {
 	eventPublisher := func(reason, message string) {}
 	auth := clients.AuthConfig{Type: clients.NoAuth}
 
-	prov, err := newProvisionerWithSettings(makeHost(), bmc.Credentials{}, eventPublisher,
+	host := makeHost()
+	prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, eventPublisher,
 		"https://ironic.test", auth, "https://ironic.test", auth,
 	)
 	if err != nil {
@@ -28,7 +31,12 @@ func TestGetUpdateOptsForNodeWithRootHints(t *testing.T) {
 	}
 	ironicNode := &nodes.Node{}
 
-	patches, err := prov.getUpdateOptsForNode(ironicNode)
+	provData := provisioner.ProvisionData{
+		Image:           *host.Spec.Image,
+		BootMode:        metal3v1alpha1.DefaultBootMode,
+		RootDeviceHints: host.Status.Provisioning.RootDeviceHints,
+	}
+	patches, err := prov.getUpdateOptsForNode(ironicNode, provData)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -121,7 +129,13 @@ func TestGetUpdateOptsForNodeVirtual(t *testing.T) {
 	}
 	ironicNode := &nodes.Node{}
 
-	patches, err := prov.getUpdateOptsForNode(ironicNode)
+	hwProf, _ := hardware.GetProfile("libvirt")
+	provData := provisioner.ProvisionData{
+		Image:           *host.Spec.Image,
+		BootMode:        metal3v1alpha1.DefaultBootMode,
+		HardwareProfile: hwProf,
+	}
+	patches, err := prov.getUpdateOptsForNode(ironicNode, provData)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -225,7 +239,13 @@ func TestGetUpdateOptsForNodeDell(t *testing.T) {
 	}
 	ironicNode := &nodes.Node{}
 
-	patches, err := prov.getUpdateOptsForNode(ironicNode)
+	hwProf, _ := hardware.GetProfile("dell")
+	provData := provisioner.ProvisionData{
+		Image:           *host.Spec.Image,
+		BootMode:        metal3v1alpha1.DefaultBootMode,
+		HardwareProfile: hwProf,
+	}
+	patches, err := prov.getUpdateOptsForNode(ironicNode, provData)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -287,7 +307,8 @@ func TestGetUpdateOptsForNodeLiveIso(t *testing.T) {
 	eventPublisher := func(reason, message string) {}
 	auth := clients.AuthConfig{Type: clients.NoAuth}
 
-	prov, err := newProvisionerWithSettings(makeHostLiveIso(), bmc.Credentials{}, eventPublisher,
+	host := makeHostLiveIso()
+	prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, eventPublisher,
 		"https://ironic.test", auth, "https://ironic.test", auth,
 	)
 	if err != nil {
@@ -295,7 +316,11 @@ func TestGetUpdateOptsForNodeLiveIso(t *testing.T) {
 	}
 	ironicNode := &nodes.Node{}
 
-	patches, err := prov.getUpdateOptsForNode(ironicNode)
+	provData := provisioner.ProvisionData{
+		Image:    *host.Spec.Image,
+		BootMode: metal3v1alpha1.DefaultBootMode,
+	}
+	patches, err := prov.getUpdateOptsForNode(ironicNode, provData)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -348,7 +373,8 @@ func TestGetUpdateOptsForNodeImageToLiveIso(t *testing.T) {
 	eventPublisher := func(reason, message string) {}
 	auth := clients.AuthConfig{Type: clients.NoAuth}
 
-	prov, err := newProvisionerWithSettings(makeHostLiveIso(), bmc.Credentials{}, eventPublisher,
+	host := makeHostLiveIso()
+	prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, eventPublisher,
 		"https://ironic.test", auth, "https://ironic.test", auth,
 	)
 	if err != nil {
@@ -362,7 +388,11 @@ func TestGetUpdateOptsForNodeImageToLiveIso(t *testing.T) {
 		},
 	}
 
-	patches, err := prov.getUpdateOptsForNode(ironicNode)
+	provData := provisioner.ProvisionData{
+		Image:    *host.Spec.Image,
+		BootMode: metal3v1alpha1.DefaultBootMode,
+	}
+	patches, err := prov.getUpdateOptsForNode(ironicNode, provData)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -440,7 +470,11 @@ func TestGetUpdateOptsForNodeLiveIsoToImage(t *testing.T) {
 		},
 	}
 
-	patches, err := prov.getUpdateOptsForNode(ironicNode)
+	provData := provisioner.ProvisionData{
+		Image:    *host.Spec.Image,
+		BootMode: metal3v1alpha1.DefaultBootMode,
+	}
+	patches, err := prov.getUpdateOptsForNode(ironicNode, provData)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -519,7 +553,6 @@ func TestGetUpdateOptsForNodeSecureBoot(t *testing.T) {
 			},
 			Online:          true,
 			HardwareProfile: "unknown",
-			BootMode:        metal3v1alpha1.UEFISecureBoot,
 		},
 		Status: metal3v1alpha1.BareMetalHostStatus{
 			HardwareProfile: "libvirt",
@@ -540,7 +573,13 @@ func TestGetUpdateOptsForNodeSecureBoot(t *testing.T) {
 	}
 	ironicNode := &nodes.Node{}
 
-	patches, err := prov.getUpdateOptsForNode(ironicNode)
+	hwProf, _ := hardware.GetProfile("libvirt")
+	provData := provisioner.ProvisionData{
+		Image:           *host.Spec.Image,
+		BootMode:        metal3v1alpha1.UEFISecureBoot,
+		HardwareProfile: hwProf,
+	}
+	patches, err := prov.getUpdateOptsForNode(ironicNode, provData)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/pkg/provisioner/ironic/validatemanagementaccess_test.go b/pkg/provisioner/ironic/validatemanagementaccess_test.go
index 1712f660..c43a581a 100644
--- a/pkg/provisioner/ironic/validatemanagementaccess_test.go
+++ b/pkg/provisioner/ironic/validatemanagementaccess_test.go
@@ -10,6 +10,7 @@ import (
 
 	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 	"github.com/metal3-io/baremetal-operator/pkg/bmc"
+	"github.com/metal3-io/baremetal-operator/pkg/provisioner"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/clients"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/testserver"
 )
@@ -34,7 +35,7 @@ func TestValidateManagementAccessNoMAC(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	result, _, err := prov.ValidateManagementAccess(false, false)
+	result, _, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{}, false, false)
 	if err != nil {
 		t.Fatalf("error from ValidateManagementAccess: %s", err)
 	}
@@ -66,7 +67,7 @@ func TestValidateManagementAccessMACOptional(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	result, _, err := prov.ValidateManagementAccess(false, false)
+	result, _, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{}, false, false)
 	if err != nil {
 		t.Fatalf("error from ValidateManagementAccess: %s", err)
 	}
@@ -100,7 +101,7 @@ func TestValidateManagementAccessCreateNodeNoImage(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	result, provID, err := prov.ValidateManagementAccess(false, false)
+	result, provID, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{}, false, false)
 	if err != nil {
 		t.Fatalf("error from ValidateManagementAccess: %s", err)
 	}
@@ -136,7 +137,7 @@ func TestValidateManagementAccessCreateWithImage(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	result, provID, err := prov.ValidateManagementAccess(false, false)
+	result, provID, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{CurrentImage: host.Spec.Image.DeepCopy()}, false, false)
 	if err != nil {
 		t.Fatalf("error from ValidateManagementAccess: %s", err)
 	}
@@ -174,7 +175,7 @@ func TestValidateManagementAccessCreateWithLiveIso(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	result, provID, err := prov.ValidateManagementAccess(false, false)
+	result, provID, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{CurrentImage: host.Spec.Image.DeepCopy()}, false, false)
 	if err != nil {
 		t.Fatalf("error from ValidateManagementAccess: %s", err)
 	}
@@ -186,89 +187,6 @@ func TestValidateManagementAccessCreateWithLiveIso(t *testing.T) {
 	assert.Contains(t, updates, host.Spec.Image.URL)
 }
 
-func TestValidateManagementAccessCreateNodeImageSpecOrStatus(t *testing.T) {
-	cases := []struct {
-		name        string
-		specImage   *metal3v1alpha1.Image
-		statusImage metal3v1alpha1.Image
-		expected    string
-	}{
-		{
-			name: "image-from-spec",
-			specImage: &metal3v1alpha1.Image{
-				URL:      "image-from-spec",
-				Checksum: "image-checksum",
-			},
-			statusImage: metal3v1alpha1.Image{},
-			expected:    "image-from-spec",
-		},
-		{
-			name:      "image-from-status",
-			specImage: nil,
-			statusImage: metal3v1alpha1.Image{
-				URL:      "image-from-status",
-				Checksum: "image-checksum",
-			},
-			expected: "image-from-status",
-		},
-		{
-			name: "image-from-both",
-			specImage: &metal3v1alpha1.Image{
-				URL:      "image-from-spec",
-				Checksum: "image-checksum",
-			},
-			statusImage: metal3v1alpha1.Image{
-				URL:      "image-from-status",
-				Checksum: "image-checksum",
-			},
-			expected: "image-from-status",
-		},
-	}
-
-	for _, tc := range cases {
-		t.Run(tc.name, func(t *testing.T) {
-			host := makeHost()
-			host.Spec.BootMACAddress = ""
-			host.Spec.Image = tc.specImage
-			host.Status.Provisioning.ID = "" // so we don't lookup by uuid
-			host.Status.Provisioning.Image = tc.statusImage
-
-			var createdNode *nodes.Node
-
-			createCallback := func(node nodes.Node) {
-				createdNode = &node
-			}
-
-			ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).NoNode(host.Namespace + nameSeparator + host.Name).NoNode(host.Name)
-			ironic.NodeUpdate(nodes.Node{UUID: "node-0"})
-			ironic.Start()
-			defer ironic.Stop()
-
-			auth := clients.AuthConfig{Type: clients.NoAuth}
-			prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, nullEventPublisher,
-				ironic.Endpoint(), auth, testserver.NewInspector(t).Endpoint(), auth,
-			)
-			if err != nil {
-				t.Fatalf("could not create provisioner: %s", err)
-			}
-
-			result, provID, err := prov.ValidateManagementAccess(false, false)
-			if err != nil {
-				t.Fatalf("error from ValidateManagementAccess: %s", err)
-			}
-			assert.Equal(t, "", result.ErrorMessage)
-			assert.NotEqual(t, "", createdNode.UUID)
-			updates := ironic.GetLastNodeUpdateRequestFor(provID)
-			assert.NotEqual(t, 0, len(updates))
-			for _, u := range updates {
-				if u.Path == "/instance_info/image_source" {
-					assert.Equal(t, u.Value, tc.expected)
-				}
-			}
-		})
-	}
-}
-
 func TestValidateManagementAccessExistingNode(t *testing.T) {
 	// Create a host without a bootMACAddress and with a BMC that
 	// does not require one.
@@ -298,7 +216,7 @@ func TestValidateManagementAccessExistingNode(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	result, provID, err := prov.ValidateManagementAccess(false, false)
+	result, provID, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{}, false, false)
 	if err != nil {
 		t.Fatalf("error from ValidateManagementAccess: %s", err)
 	}
@@ -335,7 +253,7 @@ func TestValidateManagementAccessExistingNodeNameUpdate(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	result, _, err := prov.ValidateManagementAccess(false, false)
+	result, _, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{}, false, false)
 	if err != nil {
 		t.Fatalf("error from ValidateManagementAccess: %s", err)
 	}
@@ -400,7 +318,7 @@ func TestValidateManagementAccessExistingNodeContinue(t *testing.T) {
 				t.Fatalf("could not create provisioner: %s", err)
 			}
 
-			result, _, err := prov.ValidateManagementAccess(false, false)
+			result, _, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{}, false, false)
 			if err != nil {
 				t.Fatalf("error from ValidateManagementAccess: %s", err)
 			}
@@ -447,7 +365,7 @@ func TestValidateManagementAccessExistingNodeWaiting(t *testing.T) {
 				t.Fatalf("could not create provisioner: %s", err)
 			}
 
-			result, _, err := prov.ValidateManagementAccess(false, false)
+			result, _, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{}, false, false)
 			if err != nil {
 				t.Fatalf("error from ValidateManagementAccess: %s", err)
 			}
@@ -489,7 +407,7 @@ func TestValidateManagementAccessNewCredentials(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	result, provID, err := prov.ValidateManagementAccess(true, false)
+	result, provID, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{}, true, false)
 	if err != nil {
 		t.Fatalf("error from ValidateManagementAccess: %s", err)
 	}
@@ -537,7 +455,7 @@ func TestValidateManagementAccessLinkExistingIronicNodeByMAC(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	result, provID, err := prov.ValidateManagementAccess(false, false)
+	result, provID, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{}, false, false)
 	if err != nil {
 		t.Fatalf("error from ValidateManagementAccess: %s", err)
 	}
@@ -581,7 +499,7 @@ func TestValidateManagementAccessExistingPortWithWrongUUID(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	_, _, err = prov.ValidateManagementAccess(false, false)
+	_, _, err = prov.ValidateManagementAccess(provisioner.ManagementAccessData{}, false, false)
 	assert.EqualError(t, err, "failed to find existing host: port exists but linked node doesn't random-wrong-id: Resource not found")
 }
 
@@ -624,7 +542,7 @@ func TestValidateManagementAccessExistingPortButHasName(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	res, _, err := prov.ValidateManagementAccess(false, false)
+	res, _, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{}, false, false)
 	assert.Nil(t, err)
 	assert.Equal(t, res.ErrorMessage, "MAC address 11:11:11:11:11:11 conflicts with existing node wrong-name")
 }
@@ -652,8 +570,7 @@ func TestValidateManagementAccessAddTwoHostsWithSameMAC(t *testing.T) {
 	defer ironic.Stop()
 
 	host := makeHost()
-
-	// This value is differen than the port that actually exists
+	// This value is different than the port that actually exists
 	host.Spec.BootMACAddress = "22:22:22:22:22:22"
 	host.Status.Provisioning.ID = "" // so we don't lookup by uuid
 
@@ -665,7 +582,8 @@ func TestValidateManagementAccessAddTwoHostsWithSameMAC(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	result, provID, err := prov.ValidateManagementAccess(false, false)
+	// MAC address value is different than the port that actually exists
+	result, provID, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{}, false, false)
 	if err != nil {
 		t.Fatalf("error from ValidateManagementAccess: %s", err)
 	}
@@ -677,7 +595,6 @@ func TestValidateManagementAccessUnsupportedSecureBoot(t *testing.T) {
 	// Create a host without a bootMACAddress and with a BMC that
 	// requires one.
 	host := makeHost()
-	host.Spec.BootMode = metal3v1alpha1.UEFISecureBoot
 	host.Status.Provisioning.ID = "" // so we don't lookup by uuid
 
 	ironic := testserver.NewIronic(t).Ready().NoNode("myns" + nameSeparator + host.Name).NoNode(host.Name)
@@ -692,7 +609,7 @@ func TestValidateManagementAccessUnsupportedSecureBoot(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	result, _, err := prov.ValidateManagementAccess(false, false)
+	result, _, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{BootMode: metal3v1alpha1.UEFISecureBoot}, false, false)
 	if err != nil {
 		t.Fatalf("error from ValidateManagementAccess: %s", err)
 	}
@@ -715,7 +632,7 @@ func TestValidateManagementAccessNoBMCDetails(t *testing.T) {
 		t.Fatalf("could not create provisioner: %s", err)
 	}
 
-	result, _, err := prov.ValidateManagementAccess(false, false)
+	result, _, err := prov.ValidateManagementAccess(provisioner.ManagementAccessData{}, false, false)
 	if err != nil {
 		t.Fatalf("error from ValidateManagementAccess: %s", err)
 	}