Repository: metal3-io/baremetal-operator
Pull request #908 (2021-06-08 07:37:47)

Avoid always do delete_configuration clean step
Signed-off-by: Zou Yu <zouy.fnst@cn.fujitsu.com>

When I test on a server(irmc) without a RAID controller, this will cause an error.
I am very sorry for ignored this situation in the previous code.

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

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **dtantsur** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @dtantsur` in a comment when ready.

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

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

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

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":["dtantsur"]} -->
Hellcatlk: /cc @andfasano @zaneb 
Hellcatlk: /assign @dtantsur
metal3-io-bot: @Hellcatlk: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | e34342206af4a1389d56139377e4e519ecc98e2b | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/908/unit/1402167967931699200) | `/test unit`

<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: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please ask for approval from **dtantsur** after the PR has been reviewed.

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

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

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

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":["dtantsur"]} -->
Hellcatlk: /test-integration
dtantsur: /approve

Could you update the design to reflect this change?
zaneb: I fear this will prevent the user from disabling RAID once it has been set up (even deleting the BareMetalHost and starting again won't help; you'd have to do it manually in the BMC).

Specifically, I think it relies on an assumption that there is a distinction between a missing RAID config and a RAID config where both the Hardware and Software volume lists are empty. However, given that all 3 of these are marked `omitempty` I strongly suspect that will not be the case (i.e. if no volumes are specified then the RAID config will be nil). If this is not merely an assumption and you have verified that there is a difference, please confirm.

I myself have attempted to solve the problem of extra manual cleans occurring because of the delete_configuration step (before I was aware that it is breaking irmc hosts that do not support RAID), but I came to the conclusion that there was very little that could be done about it while remaining correct. For example, if a new Host is added with an empty RAID config, I think we do still want to clear it just in case RAID was previously enabled before we enrolled that Host. For reference, here is the patch and my notes: https://github.com/zaneb/baremetal-operator/commit/8b5b575e3beb9d23221262c1f755c35b34ee559a

Obviously the fact that this is breaking some hosts gives new urgency to the problem. But I'm not sure that we can do the right thing without some indication either from ironic or from the user that the host does or does not support RAID.
Hellcatlk: > Specifically, I think it relies on an assumption that there is a distinction between a missing RAID config and a RAID config where both the Hardware and Software volume lists are empty. However, given that all 3 of these are marked omitempty I strongly suspect that will not be the case (i.e. if no volumes are specified then the RAID config will be nil). If this is not merely an assumption and you have verified that there is a difference, please confirm.

@zaneb If the raid field does not exist or is empty, the value of host.Spec.RAID is:
`2021-06-08T22:08:09.642-0400    INFO    controllers.BareMetalHost       DEBUG     {"baremetalhost": "metal3/example-baremetalhost", "raid": null}`

If the raid field isn't empty, eg:
```yaml
spec:
  raid:
    hardwareRAIDVolumes:
```
The value of host.Spec.RAID is:
`2021-06-08T22:08:34.936-0400    INFO    controllers.BareMetalHost       DEBUG     {"baremetalhost": "metal3/example-baremetalhost", "raid": {}}`
zaneb: OK, thanks for checking that. So we're relying on a very subtle distinction that:

```
spec:
  raid:
```

means use whatever RAID settings happen to exist, but

```
spec:
  raid:
    hardwareRAIDVolumes:
```

means clear any existing hardware volumes.

As subtle as that is, I don't think we have any choice. On deprovisioning we set the RAID config in the status to nil, which is equivalent to 'keep as is', so that's the right thing in terms of deciding whether we retrigger Preparing again or not. (i.e. if the Spec does not tell us to leave it as is, we will trigger Preparing and delete the existing configuration.)


Second question: it appears that under this scheme both hardware and software RAID follow the same rules with respect to `delete_configuration`. It follows that users will be unable to use software RAID on hosts that don't have a hardware RAID controller - which naively I would have thought was the point of software RAID.

It seems to me that what we actually want is to do is to `delete_configuration` when we have:

```
spec:
  raid:
    hardwareRAIDVolumes:
```

but `erase_devices_metadata` the rest of the time, including when we have:

```
spec:
  raid:
    softwareRAIDVolumes:
```

but we don't currently have a way of telling the cases in those two examples apart. Perhaps getting rid of `omitempty` and adding a `// +optional` comment instead might give us the right behaviour (giving us either nil or an empty slice) without changing the CRD schema? Or possibly the user would still have to go even further and put `hardwareRaidVolumes: []`.

(Also, while it could make sense to keep the externally configured hardware RAID settings, IMHO it never makes sense to rely on existing software RAID configuration because you can't know how those might have been trashed by a previous installation. I'd have to think more about how this could be managed.)
Hellcatlk: > but we don't currently have a way of telling the cases in those two examples apart. Perhaps getting rid of `omitempty` and adding a `// +optional` comment instead might give us the right behaviour (giving us either nil or an empty slice) without changing the CRD schema? Or possibly the user would still have to go even further and put `hardwareRaidVolumes: []`.

I used `// +optional` instead of `omitempty` 

For
```yaml
spec:
  raid:
```
It's value is `"raid": null`

For
```yaml
spec:
  raid:
    hardwareRAIDVolume:
```
It's value is `"raid": {"hardwareRAIDVolumes":null,"softwareRAIDVolumes":null}`

Hellcatlk: @zaneb If you assign a value to hardwareRAIDVolumes in the following way: 
```yaml
spec:
  raid:
    hardwareRAIDVolumes: []
```
We can distinguish them.
2021-06-09T00:00:57.300-0400    INFO    controllers.BareMetalHost       DEBUG   {"baremetalhost": "metal3/example-baremetalhost", "raid": {}}
2021-06-09T00:00:57.300-0400    INFO    controllers.BareMetalHost       DEBUG   {"baremetalhost": "metal3/example-baremetalhost", "hardware": []}
2021-06-09T00:00:57.300-0400    INFO    controllers.BareMetalHost       DEBUG   {"baremetalhost": "metal3/example-baremetalhost", "software": null}
elfosardo: /test-integration
Hellcatlk: /test-integration
Hellcatlk: The reason for the failure of test-integration seems to be `Invalid CAPM3 version : v1alpha5. Not in : v1alpha4`
furkatgofurov7: /test-integration
furkatgofurov7: > The reason for the failure of test-integration seems to be `Invalid CAPM3 version : v1alpha5. Not in : v1alpha4`

@Hellcatlk hi! We have been working on some changes in CI and this PR was affected, sorry. We also notified about it over the [slack](https://kubernetes.slack.com/archives/CHD49TLE7/p1623234051111900). I re-triggered it again, since fix for the problem has already landed, hopefully should be fine this time. 
Hellcatlk: @zaneb Although I do not think that PR is the best solution to this problem, but this is the simplest solution I could find currently. And besides that, we seem to have no choice, unless we can know whether a server has a RAID controller through `ironic`.

In addition, I will update the design document after this PR is merged to make it consistent with the implementation. 
zaneb: I started looking at the other things we'll likely need to do to make sure we don't end up in the wrong state: #910 (incomplete and I haven't bothered fixing the unit tests yet).

In the meantime I think this is probably a reasonable way forward to allow us to fix the breakage and keep working. It looks like we need some way for the user to distinguish between "clear all existing HW RAID config" and "use existing hardware RAID config". We should document it as recommending you pass an empty list to get the former, and nothing to get the latter.
Hellcatlk: >In the meantime I think this is probably a reasonable way forward to allow us to fix the breakage and keep working. It looks like we need some way for the user to distinguish between "clear all existing HW RAID config" and "use existing hardware RAID config". We should document it as recommending you pass an empty list to get the former, and nothing to get the latter.

You mean, only the following three situations will clear the RAID configuration?
```yaml
spec:
  # Clear hardware RAID configuration
  raid:
    hardwareRAIDVolumes: []
```

```yaml
spec:
  # Clear hardware RAID configuration
  raid:
     hardwareRAIDVolumes: []
     softwareRAIDVolumes: []
``` 

```yaml
spec:
  # Clear softwareRAID configuration
  raid:
     softwareRAIDVolumes: []
```

Hellcatlk: @zaneb I am sorry but I have to say that the most urgent thing right now is to catch up with OCP4.8, so can we just take this as shor-term fix for now? If disagree, I can open a new PR to just change the irmc raid interface to "no-raid". WDYH?
Hellcatlk: /test-integration
zaneb: > Do you mean that only the following three situations will clear the RAID configuration?

I mean we should document it that way (even if it's not true that those are the _only_ ways to clear it) so that we have the flexibility to redefine what

```
spec:
  # ¯\_(ツ)_/¯
  raid:
    hardwareRAIDVolumes:
```

means.
metal3-io-bot: @Hellcatlk: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
markdownlint | 6442d3098f829b1f26fdd13ac696485c519bbf48 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/908/markdownlint/1403168722901798912) | `/test markdownlint`

<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 -->
Hellcatlk: > 
> 
> > Do you mean that only the following three situations will clear the RAID configuration?
> 
> I mean we should document it that way (even if it's not true that those are the _only_ ways to clear it) so that we have the flexibility to redefine what
> 
> ```
> spec:
>   # ¯\_(ツ)_/¯
>   raid:
>     hardwareRAIDVolumes:
> ```
> 
> means.

Thanks, I add description to api.md.
Hellcatlk: @zaneb I updated some logic, and now software RAID is always deleted. 
metal3-io-bot: @Hellcatlk: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | 5eb85ad979e6d3dd7c3ca8532b901712beb1812f | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/908/unit/1409804792682778624) | `/test unit`

<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: @Hellcatlk: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | 1335099ed657425c1edfdbe9b089366e6c7eccb4 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/908/unit/1410172608061640704) | `/test unit`

<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: @Hellcatlk: PR needs rebase.

<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.
</details>

Patch:
diff --git a/PROJECT b/PROJECT
index e546544e..1031f087 100644
--- a/PROJECT
+++ b/PROJECT
@@ -4,10 +4,4 @@ resources:
 - group: metal3.io
   kind: BareMetalHost
   version: v1alpha1
-- group: metal3.io
-  kind: HostFirmwareSettings
-  version: v1alpha1
-- group: metal3.io
-  kind: FirmwareSchema
-  version: v1alpha1
 version: "2"
diff --git a/apis/go.sum b/apis/go.sum
index 04c07372..b0c7635f 100644
--- a/apis/go.sum
+++ b/apis/go.sum
@@ -584,7 +584,6 @@ k8s.io/apiextensions-apiserver v0.19.2/go.mod h1:EYNjpqIAvNZe+svXVx9j4uBaVhTB4C9
 k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
 k8s.io/apimachinery v0.20.2 h1:hFx6Sbt1oG0n6DZ+g4bFt5f6BoMkOjKWsQFu077M3Vg=
 k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
-k8s.io/apimachinery v0.21.2 h1:vezUc/BHqWlQDnZ+XkrpXSmnANSLbpnlpwo0Lhk0gpc=
 k8s.io/apiserver v0.19.2/go.mod h1:FreAq0bJ2vtZFj9Ago/X0oNGC51GfubKK/ViOKfVAOA=
 k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA=
 k8s.io/code-generator v0.19.2/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk=
diff --git a/apis/metal3.io/v1alpha1/baremetalhost_types.go b/apis/metal3.io/v1alpha1/baremetalhost_types.go
index f9e7b09a..c60de869 100644
--- a/apis/metal3.io/v1alpha1/baremetalhost_types.go
+++ b/apis/metal3.io/v1alpha1/baremetalhost_types.go
@@ -276,7 +276,9 @@ type SoftwareRAIDVolume struct {
 // RAIDConfig contains the configuration that are required to config RAID in Bare Metal server
 type RAIDConfig struct {
 	// The list of logical disks for hardware RAID, if rootDeviceHints isn't used, first volume is root volume.
-	HardwareRAIDVolumes []HardwareRAIDVolume `json:"hardwareRAIDVolumes,omitempty"`
+	// You can set the value of this field to `[]` to clear all the hardware RAID configurations.
+	// +optional
+	HardwareRAIDVolumes []HardwareRAIDVolume `json:"hardwareRAIDVolumes"`
 
 	// The list of logical disks for software RAID, if rootDeviceHints isn't used, first volume is root volume.
 	// If HardwareRAIDVolumes is set this item will be invalid.
@@ -285,8 +287,10 @@ type RAIDConfig struct {
 	// If there are two, the first one has to be a RAID-1, while the RAID level for the second one can be 0, 1, or 1+0.
 	// As the first RAID device will be the deployment device,
 	// enforcing a RAID-1 reduces the risk of ending up with a non-booting node in case of a disk failure.
+	// Software RAID will always be deleted.
 	// +kubebuilder:validation:MaxItems=2
-	SoftwareRAIDVolumes []SoftwareRAIDVolume `json:"softwareRAIDVolumes,omitempty"`
+	// +optional
+	SoftwareRAIDVolumes []SoftwareRAIDVolume `json:"softwareRAIDVolumes"`
 }
 
 // FirmwareConfig contains the configuration that you want to configure BIOS settings in Bare metal server
diff --git a/apis/metal3.io/v1alpha1/firmwareschema_types.go b/apis/metal3.io/v1alpha1/firmwareschema_types.go
deleted file mode 100644
index 58b0247b..00000000
--- a/apis/metal3.io/v1alpha1/firmwareschema_types.go
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
-
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package v1alpha1
-
-import (
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/intstr"
-)
-
-// Additional data describing the firmware setting
-type SettingSchema struct {
-
-	// The type of setting.
-	// +kubebuilder:validation:Enum=Enumeration;String;Integer;Boolean;Password
-	AttributeType string `json:"attribute_type,omitempty"`
-
-	// The allowable value for an Enumeration type setting.
-	AllowableValues []string `json:"allowable_values,omitempty"`
-
-	// The lowest value for an Integer type setting.
-	LowerBound *int `json:"lower_bound,omitempty"`
-
-	// The highest value for an Integer type setting.
-	UpperBound *int `json:"upper_bound,omitempty"`
-
-	// Minimum length for a String type setting.
-	MinLength *int `json:"min_length,omitempty"`
-
-	// Maximum length for a String type setting.
-	MaxLength *int `json:"max_length,omitempty"`
-
-	// Whether or not this setting is read only.
-	ReadOnly *bool `json:"read_only,omitempty"`
-
-	// Whether or not a reset is required after changing this setting.
-	ResetRequired *bool `json:"reset_required,omitempty"`
-
-	// Whether or not this setting's value is unique to this node, e.g.
-	// a serial number.
-	Unique *bool `json:"unique,omitempty"`
-}
-
-// FirmwareSchemaSpec defines the desired state of FirmwareSchema
-type FirmwareSchemaSpec struct {
-
-	// The hardware vendor associated with this schema
-	// +optional
-	HardwareVendor string `json:"hardwareVendor,omitempty"`
-
-	// The hardware model associated with this schema
-	// +optional
-	HardwareModel string `json:"hardwareModel,omitempty"`
-
-	// Map of firmware name to schema
-	Schema map[string]SettingSchema `json:"schema" required:"true"`
-}
-
-//+kubebuilder:object:root=true
-
-// FirmwareSchema is the Schema for the firmwareschemas API
-type FirmwareSchema struct {
-	metav1.TypeMeta   `json:",inline"`
-	metav1.ObjectMeta `json:"metadata,omitempty"`
-
-	Spec FirmwareSchemaSpec `json:"spec,omitempty"`
-}
-
-// Check whether the setting's name and value is valid using the schema
-func (host *FirmwareSchema) CheckSettingIsValid(name string, value intstr.IntOrString, schemas map[string]SettingSchema) bool {
-
-	schema, ok := schemas[name]
-	if !ok {
-		// The setting must exist in the status
-		return false
-	}
-
-	if schema.ReadOnly != nil && *schema.ReadOnly == true {
-		return false
-	}
-
-	// Check if valid based on type
-	switch schema.AttributeType {
-	case "Enumeration":
-		for _, av := range schema.AllowableValues {
-			if value.String() == av {
-				return true
-			}
-		}
-		return false
-
-	case "Integer":
-		if schema.LowerBound == nil || schema.UpperBound == nil {
-			// return true if no settings to check validity
-			return true
-		}
-		return (value.IntValue() >= *schema.LowerBound && value.IntValue() <= *schema.UpperBound)
-
-	case "String":
-		if schema.MinLength == nil || schema.MaxLength == nil {
-			// return true if no settings to check validity
-			return true
-		}
-		return (len(value.String()) >= *schema.MinLength && len(value.String()) <= *schema.MaxLength)
-
-	case "Boolean":
-		return (value.String() == "true" || value.String() == "false")
-
-	case "Password":
-		// Prevent sets of password types
-		return false
-
-	case "":
-		// allow the set as BIOS registry fields may not have been available
-		return true
-
-	default:
-		// Unexpected attribute type
-		return false
-	}
-}
-
-//+kubebuilder:object:root=true
-
-// FirmwareSchemaList contains a list of FirmwareSchema
-type FirmwareSchemaList struct {
-	metav1.TypeMeta `json:",inline"`
-	metav1.ListMeta `json:"metadata,omitempty"`
-	Items           []FirmwareSchema `json:"items"`
-}
-
-func init() {
-	SchemeBuilder.Register(&FirmwareSchema{}, &FirmwareSchemaList{})
-}
diff --git a/apis/metal3.io/v1alpha1/hostfirmwaresettings_types.go b/apis/metal3.io/v1alpha1/hostfirmwaresettings_types.go
deleted file mode 100644
index 1d955899..00000000
--- a/apis/metal3.io/v1alpha1/hostfirmwaresettings_types.go
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
-
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package v1alpha1
-
-import (
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/intstr"
-)
-
-// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.
-
-type SettingsMap map[string]string
-type DesiredSettingsMap map[string]intstr.IntOrString
-
-type SchemaReference struct {
-	// `namespace` is the namespace of the where the schema is stored.
-	Namespace string `json:"namespace"`
-	// `name` is the reference to the schema.
-	Name string `json:"name"`
-}
-
-// HostFirmwareSettingsSpec defines the desired state of HostFirmwareSettings
-type HostFirmwareSettingsSpec struct {
-
-	// Settings are the desired firmware settings stored as name/value pairs.
-	// This will be populated with the actual firmware settings and only
-	// contain the settings that can be modified (i.e. not ReadOnly), to
-	// facilitate making changes.
-	// +patchStrategy=merge
-	Settings DesiredSettingsMap `json:"settings" required:"true"`
-}
-
-// HostFirmwareSettingsStatus defines the observed state of HostFirmwareSettings
-type HostFirmwareSettingsStatus struct {
-	// FirmwareSchema is a reference to the Schema used to describe each
-	// FirmwareSetting. By default, this will be a Schema in the same
-	// Namespace as the settings but it can be overwritten in the Spec
-	FirmwareSchema *SchemaReference `json:"schema,omitempty"`
-
-	// Settings are the actual firmware settings stored as name/value pairs
-	Settings SettingsMap `json:"settings" required:"true"`
-}
-
-//+kubebuilder:object:root=true
-//+kubebuilder:subresource:status
-
-// HostFirmwareSettings is the Schema for the hostfirmwaresettings API
-type HostFirmwareSettings struct {
-	metav1.TypeMeta   `json:",inline"`
-	metav1.ObjectMeta `json:"metadata,omitempty"`
-
-	Spec   HostFirmwareSettingsSpec   `json:"spec,omitempty"`
-	Status HostFirmwareSettingsStatus `json:"status,omitempty"`
-}
-
-//+kubebuilder:object:root=true
-
-// HostFirmwareSettingsList contains a list of HostFirmwareSettings
-type HostFirmwareSettingsList struct {
-	metav1.TypeMeta `json:",inline"`
-	metav1.ListMeta `json:"metadata,omitempty"`
-	Items           []HostFirmwareSettings `json:"items"`
-}
-
-func init() {
-	SchemeBuilder.Register(&HostFirmwareSettings{}, &HostFirmwareSettingsList{})
-}
diff --git a/apis/metal3.io/v1alpha1/zz_generated.deepcopy.go b/apis/metal3.io/v1alpha1/zz_generated.deepcopy.go
index 44ab715e..c0d9bbf8 100644
--- a/apis/metal3.io/v1alpha1/zz_generated.deepcopy.go
+++ b/apis/metal3.io/v1alpha1/zz_generated.deepcopy.go
@@ -265,27 +265,6 @@ func (in *CustomDeploy) DeepCopy() *CustomDeploy {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in DesiredSettingsMap) DeepCopyInto(out *DesiredSettingsMap) {
-	{
-		in := &in
-		*out = make(DesiredSettingsMap, len(*in))
-		for key, val := range *in {
-			(*out)[key] = val
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DesiredSettingsMap.
-func (in DesiredSettingsMap) DeepCopy() DesiredSettingsMap {
-	if in == nil {
-		return nil
-	}
-	out := new(DesiredSettingsMap)
-	in.DeepCopyInto(out)
-	return *out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Firmware) DeepCopyInto(out *Firmware) {
 	*out = *in
@@ -332,86 +311,6 @@ func (in *FirmwareConfig) DeepCopy() *FirmwareConfig {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *FirmwareSchema) DeepCopyInto(out *FirmwareSchema) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
-	in.Spec.DeepCopyInto(&out.Spec)
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirmwareSchema.
-func (in *FirmwareSchema) DeepCopy() *FirmwareSchema {
-	if in == nil {
-		return nil
-	}
-	out := new(FirmwareSchema)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *FirmwareSchema) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *FirmwareSchemaList) DeepCopyInto(out *FirmwareSchemaList) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ListMeta.DeepCopyInto(&out.ListMeta)
-	if in.Items != nil {
-		in, out := &in.Items, &out.Items
-		*out = make([]FirmwareSchema, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirmwareSchemaList.
-func (in *FirmwareSchemaList) DeepCopy() *FirmwareSchemaList {
-	if in == nil {
-		return nil
-	}
-	out := new(FirmwareSchemaList)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *FirmwareSchemaList) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *FirmwareSchemaSpec) DeepCopyInto(out *FirmwareSchemaSpec) {
-	*out = *in
-	if in.Schema != nil {
-		in, out := &in.Schema, &out.Schema
-		*out = make(map[string]SettingSchema, len(*in))
-		for key, val := range *in {
-			(*out)[key] = *val.DeepCopy()
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirmwareSchemaSpec.
-func (in *FirmwareSchemaSpec) DeepCopy() *FirmwareSchemaSpec {
-	if in == nil {
-		return nil
-	}
-	out := new(FirmwareSchemaSpec)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *HardwareDetails) DeepCopyInto(out *HardwareDetails) {
 	*out = *in
@@ -487,114 +386,6 @@ func (in *HardwareSystemVendor) DeepCopy() *HardwareSystemVendor {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *HostFirmwareSettings) DeepCopyInto(out *HostFirmwareSettings) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
-	in.Spec.DeepCopyInto(&out.Spec)
-	in.Status.DeepCopyInto(&out.Status)
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostFirmwareSettings.
-func (in *HostFirmwareSettings) DeepCopy() *HostFirmwareSettings {
-	if in == nil {
-		return nil
-	}
-	out := new(HostFirmwareSettings)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *HostFirmwareSettings) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *HostFirmwareSettingsList) DeepCopyInto(out *HostFirmwareSettingsList) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ListMeta.DeepCopyInto(&out.ListMeta)
-	if in.Items != nil {
-		in, out := &in.Items, &out.Items
-		*out = make([]HostFirmwareSettings, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostFirmwareSettingsList.
-func (in *HostFirmwareSettingsList) DeepCopy() *HostFirmwareSettingsList {
-	if in == nil {
-		return nil
-	}
-	out := new(HostFirmwareSettingsList)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *HostFirmwareSettingsList) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *HostFirmwareSettingsSpec) DeepCopyInto(out *HostFirmwareSettingsSpec) {
-	*out = *in
-	if in.Settings != nil {
-		in, out := &in.Settings, &out.Settings
-		*out = make(DesiredSettingsMap, len(*in))
-		for key, val := range *in {
-			(*out)[key] = val
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostFirmwareSettingsSpec.
-func (in *HostFirmwareSettingsSpec) DeepCopy() *HostFirmwareSettingsSpec {
-	if in == nil {
-		return nil
-	}
-	out := new(HostFirmwareSettingsSpec)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *HostFirmwareSettingsStatus) DeepCopyInto(out *HostFirmwareSettingsStatus) {
-	*out = *in
-	if in.FirmwareSchema != nil {
-		in, out := &in.FirmwareSchema, &out.FirmwareSchema
-		*out = new(SchemaReference)
-		**out = **in
-	}
-	if in.Settings != nil {
-		in, out := &in.Settings, &out.Settings
-		*out = make(SettingsMap, len(*in))
-		for key, val := range *in {
-			(*out)[key] = val
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostFirmwareSettingsStatus.
-func (in *HostFirmwareSettingsStatus) DeepCopy() *HostFirmwareSettingsStatus {
-	if in == nil {
-		return nil
-	}
-	out := new(HostFirmwareSettingsStatus)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Image) DeepCopyInto(out *Image) {
 	*out = *in
@@ -771,97 +562,6 @@ func (in *RootDeviceHints) DeepCopy() *RootDeviceHints {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *SchemaReference) DeepCopyInto(out *SchemaReference) {
-	*out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchemaReference.
-func (in *SchemaReference) DeepCopy() *SchemaReference {
-	if in == nil {
-		return nil
-	}
-	out := new(SchemaReference)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *SettingSchema) DeepCopyInto(out *SettingSchema) {
-	*out = *in
-	if in.AllowableValues != nil {
-		in, out := &in.AllowableValues, &out.AllowableValues
-		*out = make([]string, len(*in))
-		copy(*out, *in)
-	}
-	if in.LowerBound != nil {
-		in, out := &in.LowerBound, &out.LowerBound
-		*out = new(int)
-		**out = **in
-	}
-	if in.UpperBound != nil {
-		in, out := &in.UpperBound, &out.UpperBound
-		*out = new(int)
-		**out = **in
-	}
-	if in.MinLength != nil {
-		in, out := &in.MinLength, &out.MinLength
-		*out = new(int)
-		**out = **in
-	}
-	if in.MaxLength != nil {
-		in, out := &in.MaxLength, &out.MaxLength
-		*out = new(int)
-		**out = **in
-	}
-	if in.ReadOnly != nil {
-		in, out := &in.ReadOnly, &out.ReadOnly
-		*out = new(bool)
-		**out = **in
-	}
-	if in.ResetRequired != nil {
-		in, out := &in.ResetRequired, &out.ResetRequired
-		*out = new(bool)
-		**out = **in
-	}
-	if in.Unique != nil {
-		in, out := &in.Unique, &out.Unique
-		*out = new(bool)
-		**out = **in
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SettingSchema.
-func (in *SettingSchema) DeepCopy() *SettingSchema {
-	if in == nil {
-		return nil
-	}
-	out := new(SettingSchema)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in SettingsMap) DeepCopyInto(out *SettingsMap) {
-	{
-		in := &in
-		*out = make(SettingsMap, len(*in))
-		for key, val := range *in {
-			(*out)[key] = val
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SettingsMap.
-func (in SettingsMap) DeepCopy() SettingsMap {
-	if in == nil {
-		return nil
-	}
-	out := new(SettingsMap)
-	in.DeepCopyInto(out)
-	return *out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *SoftwareRAIDVolume) DeepCopyInto(out *SoftwareRAIDVolume) {
 	*out = *in
diff --git a/config/crd/bases/metal3.io_baremetalhosts.yaml b/config/crd/bases/metal3.io_baremetalhosts.yaml
index c1f8b0c8..277ab918 100644
--- a/config/crd/bases/metal3.io_baremetalhosts.yaml
+++ b/config/crd/bases/metal3.io_baremetalhosts.yaml
@@ -273,7 +273,8 @@ spec:
                 properties:
                   hardwareRAIDVolumes:
                     description: The list of logical disks for hardware RAID, if rootDeviceHints
-                      isn't used, first volume is root volume.
+                      isn't used, first volume is root volume. You can set the value
+                      of this field to `[]` to clear all the hardware RAID configurations.
                     items:
                       description: HardwareRAIDVolume defines the desired configuration
                         of volume in hardware RAID
@@ -325,7 +326,8 @@ spec:
                       has to be a RAID-1, while the RAID level for the second one
                       can be 0, 1, or 1+0. As the first RAID device will be the deployment
                       device, enforcing a RAID-1 reduces the risk of ending up with
-                      a non-booting node in case of a disk failure.
+                      a non-booting node in case of a disk failure. Software RAID
+                      will always be deleted.
                     items:
                       description: SoftwareRAIDVolume defines the desired configuration
                         of volume in software RAID
@@ -858,6 +860,8 @@ spec:
                       hardwareRAIDVolumes:
                         description: The list of logical disks for hardware RAID,
                           if rootDeviceHints isn't used, first volume is root volume.
+                          You can set the value of this field to `[]` to clear all
+                          the hardware RAID configurations.
                         items:
                           description: HardwareRAIDVolume defines the desired configuration
                             of volume in hardware RAID
@@ -910,7 +914,8 @@ spec:
                           RAID-1, while the RAID level for the second one can be 0,
                           1, or 1+0. As the first RAID device will be the deployment
                           device, enforcing a RAID-1 reduces the risk of ending up
-                          with a non-booting node in case of a disk failure.
+                          with a non-booting node in case of a disk failure. Software
+                          RAID will always be deleted.
                         items:
                           description: SoftwareRAIDVolume defines the desired configuration
                             of volume in software RAID
diff --git a/config/crd/bases/metal3.io_firmwareschemas.yaml b/config/crd/bases/metal3.io_firmwareschemas.yaml
deleted file mode 100644
index 02030c87..00000000
--- a/config/crd/bases/metal3.io_firmwareschemas.yaml
+++ /dev/null
@@ -1,100 +0,0 @@
-
----
-apiVersion: apiextensions.k8s.io/v1
-kind: CustomResourceDefinition
-metadata:
-  annotations:
-    controller-gen.kubebuilder.io/version: v0.4.1
-  creationTimestamp: null
-  name: firmwareschemas.metal3.io
-spec:
-  group: metal3.io
-  names:
-    kind: FirmwareSchema
-    listKind: FirmwareSchemaList
-    plural: firmwareschemas
-    singular: firmwareschema
-  scope: Namespaced
-  versions:
-  - name: v1alpha1
-    schema:
-      openAPIV3Schema:
-        description: FirmwareSchema is the Schema for the firmwareschemas API
-        properties:
-          apiVersion:
-            description: 'APIVersion defines the versioned schema of this representation
-              of an object. Servers should convert recognized schemas to the latest
-              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
-            type: string
-          kind:
-            description: 'Kind is a string value representing the REST resource this
-              object represents. Servers may infer this from the endpoint the client
-              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
-            type: string
-          metadata:
-            type: object
-          spec:
-            description: FirmwareSchemaSpec defines the desired state of FirmwareSchema
-            properties:
-              hardwareModel:
-                description: The hardware model associated with this schema
-                type: string
-              hardwareVendor:
-                description: The hardware vendor associated with this schema
-                type: string
-              schema:
-                additionalProperties:
-                  description: Additional data describing the firmware setting
-                  properties:
-                    allowable_values:
-                      description: The allowable value for an Enumeration type setting.
-                      items:
-                        type: string
-                      type: array
-                    attribute_type:
-                      description: The type of setting.
-                      enum:
-                      - Enumeration
-                      - String
-                      - Integer
-                      - Boolean
-                      - Password
-                      type: string
-                    lower_bound:
-                      description: The lowest value for an Integer type setting.
-                      type: integer
-                    max_length:
-                      description: Maximum length for a String type setting.
-                      type: integer
-                    min_length:
-                      description: Minimum length for a String type setting.
-                      type: integer
-                    read_only:
-                      description: Whether or not this setting is read only.
-                      type: boolean
-                    reset_required:
-                      description: Whether or not a reset is required after changing
-                        this setting.
-                      type: boolean
-                    unique:
-                      description: Whether or not this setting's value is unique to
-                        this node, e.g. a serial number.
-                      type: boolean
-                    upper_bound:
-                      description: The highest value for an Integer type setting.
-                      type: integer
-                  type: object
-                description: Map of firmware name to schema
-                type: object
-            required:
-            - schema
-            type: object
-        type: object
-    served: true
-    storage: true
-status:
-  acceptedNames:
-    kind: ""
-    plural: ""
-  conditions: []
-  storedVersions: []
diff --git a/config/crd/bases/metal3.io_hostfirmwaresettings.yaml b/config/crd/bases/metal3.io_hostfirmwaresettings.yaml
deleted file mode 100644
index e8dea176..00000000
--- a/config/crd/bases/metal3.io_hostfirmwaresettings.yaml
+++ /dev/null
@@ -1,93 +0,0 @@
-
----
-apiVersion: apiextensions.k8s.io/v1
-kind: CustomResourceDefinition
-metadata:
-  annotations:
-    controller-gen.kubebuilder.io/version: v0.4.1
-  creationTimestamp: null
-  name: hostfirmwaresettings.metal3.io
-spec:
-  group: metal3.io
-  names:
-    kind: HostFirmwareSettings
-    listKind: HostFirmwareSettingsList
-    plural: hostfirmwaresettings
-    singular: hostfirmwaresettings
-  scope: Namespaced
-  versions:
-  - name: v1alpha1
-    schema:
-      openAPIV3Schema:
-        description: HostFirmwareSettings is the Schema for the hostfirmwaresettings
-          API
-        properties:
-          apiVersion:
-            description: 'APIVersion defines the versioned schema of this representation
-              of an object. Servers should convert recognized schemas to the latest
-              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
-            type: string
-          kind:
-            description: 'Kind is a string value representing the REST resource this
-              object represents. Servers may infer this from the endpoint the client
-              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
-            type: string
-          metadata:
-            type: object
-          spec:
-            description: HostFirmwareSettingsSpec defines the desired state of HostFirmwareSettings
-            properties:
-              settings:
-                additionalProperties:
-                  anyOf:
-                  - type: integer
-                  - type: string
-                  x-kubernetes-int-or-string: true
-                description: Settings are the desired firmware settings stored as
-                  name/value pairs. This will be populated with the actual firmware
-                  settings and only contain the settings that can be modified (i.e.
-                  not ReadOnly), to facilitate making changes.
-                type: object
-            required:
-            - settings
-            type: object
-          status:
-            description: HostFirmwareSettingsStatus defines the observed state of
-              HostFirmwareSettings
-            properties:
-              schema:
-                description: FirmwareSchema is a reference to the Schema used to describe
-                  each FirmwareSetting. By default, this will be a Schema in the same
-                  Namespace as the settings but it can be overwritten in the Spec
-                properties:
-                  name:
-                    description: '`name` is the reference to the schema.'
-                    type: string
-                  namespace:
-                    description: '`namespace` is the namespace of the where the schema
-                      is stored.'
-                    type: string
-                required:
-                - name
-                - namespace
-                type: object
-              settings:
-                additionalProperties:
-                  type: string
-                description: Settings are the actual firmware settings stored as name/value
-                  pairs
-                type: object
-            required:
-            - settings
-            type: object
-        type: object
-    served: true
-    storage: true
-    subresources:
-      status: {}
-status:
-  acceptedNames:
-    kind: ""
-    plural: ""
-  conditions: []
-  storedVersions: []
diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml
index 29db00a2..d6d78cc5 100644
--- a/config/crd/kustomization.yaml
+++ b/config/crd/kustomization.yaml
@@ -3,23 +3,17 @@
 # It should be run by config/default
 resources:
 - bases/metal3.io_baremetalhosts.yaml
-- bases/metal3.io_hostfirmwaresettings.yaml
-- bases/metal3.io_firmwareschemas.yaml
 # +kubebuilder:scaffold:crdkustomizeresource
 
 patchesStrategicMerge:
 # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
 # patches here are for enabling the conversion webhook for each CRD
 #- patches/webhook_in_baremetalhosts.yaml
-#- patches/webhook_in_hostfirmwaresettings.yaml
-#- patches/webhook_in_firmwareschemas.yaml
 # +kubebuilder:scaffold:crdkustomizewebhookpatch
 
 # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
 # patches here are for enabling the CA injection for each CRD
 #- patches/cainjection_in_baremetalhosts.yaml
-#- patches/cainjection_in_hostfirmwaresettings.yaml
-#- patches/cainjection_in_firmwareschemas.yaml
 # +kubebuilder:scaffold:crdkustomizecainjectionpatch
 
 # the following config is for teaching kustomize how to do kustomization for CRDs.
diff --git a/config/crd/patches/cainjection_in_firmwareschemas.yaml b/config/crd/patches/cainjection_in_firmwareschemas.yaml
deleted file mode 100644
index dd50dd1a..00000000
--- a/config/crd/patches/cainjection_in_firmwareschemas.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-# The following patch adds a directive for certmanager to inject CA into the CRD
-# CRD conversion requires k8s 1.13 or later.
-apiVersion: apiextensions.k8s.io/v1beta1
-kind: CustomResourceDefinition
-metadata:
-  annotations:
-    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
-  name: firmwareschemas.metal3.io
diff --git a/config/crd/patches/cainjection_in_hostfirmwaresettings.yaml b/config/crd/patches/cainjection_in_hostfirmwaresettings.yaml
deleted file mode 100644
index 4d81f6e4..00000000
--- a/config/crd/patches/cainjection_in_hostfirmwaresettings.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-# The following patch adds a directive for certmanager to inject CA into the CRD
-# CRD conversion requires k8s 1.13 or later.
-apiVersion: apiextensions.k8s.io/v1beta1
-kind: CustomResourceDefinition
-metadata:
-  annotations:
-    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
-  name: hostfirmwaresettings.metal3.io
diff --git a/config/crd/patches/webhook_in_firmwareschemas.yaml b/config/crd/patches/webhook_in_firmwareschemas.yaml
deleted file mode 100644
index f65cf2fb..00000000
--- a/config/crd/patches/webhook_in_firmwareschemas.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-# The following patch enables conversion webhook for CRD
-# CRD conversion requires k8s 1.13 or later.
-apiVersion: apiextensions.k8s.io/v1beta1
-kind: CustomResourceDefinition
-metadata:
-  name: firmwareschemas.metal3.io
-spec:
-  conversion:
-    strategy: Webhook
-    webhookClientConfig:
-      # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
-      # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
-      caBundle: Cg==
-      service:
-        namespace: system
-        name: webhook-service
-        path: /convert
diff --git a/config/crd/patches/webhook_in_hostfirmwaresettings.yaml b/config/crd/patches/webhook_in_hostfirmwaresettings.yaml
deleted file mode 100644
index 2e32829d..00000000
--- a/config/crd/patches/webhook_in_hostfirmwaresettings.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-# The following patch enables conversion webhook for CRD
-# CRD conversion requires k8s 1.13 or later.
-apiVersion: apiextensions.k8s.io/v1beta1
-kind: CustomResourceDefinition
-metadata:
-  name: hostfirmwaresettings.metal3.io
-spec:
-  conversion:
-    strategy: Webhook
-    webhookClientConfig:
-      # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
-      # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
-      caBundle: Cg==
-      service:
-        namespace: system
-        name: webhook-service
-        path: /convert
diff --git a/config/rbac/firmwareschema_editor_role.yaml b/config/rbac/firmwareschema_editor_role.yaml
deleted file mode 100644
index be313f63..00000000
--- a/config/rbac/firmwareschema_editor_role.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-# permissions for end users to edit firmwareschemas.
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: firmwareschema-editor-role
-rules:
-- apiGroups:
-  - metal3.io
-  resources:
-  - firmwareschemas
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - metal3.io
-  resources:
-  - firmwareschemas/status
-  verbs:
-  - get
diff --git a/config/rbac/firmwareschema_viewer_role.yaml b/config/rbac/firmwareschema_viewer_role.yaml
deleted file mode 100644
index 90691038..00000000
--- a/config/rbac/firmwareschema_viewer_role.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-# permissions for end users to view firmwareschemas.
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: firmwareschema-viewer-role
-rules:
-- apiGroups:
-  - metal3.io
-  resources:
-  - firmwareschemas
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - metal3.io
-  resources:
-  - firmwareschemas/status
-  verbs:
-  - get
diff --git a/config/rbac/hostfirmwaresettings_editor_role.yaml b/config/rbac/hostfirmwaresettings_editor_role.yaml
deleted file mode 100644
index 64b1d5da..00000000
--- a/config/rbac/hostfirmwaresettings_editor_role.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-# permissions for end users to edit hostfirmwaresettings.
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: hostfirmwaresettings-editor-role
-rules:
-- apiGroups:
-  - metal3.io
-  resources:
-  - hostfirmwaresettings
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - metal3.io
-  resources:
-  - hostfirmwaresettings/status
-  verbs:
-  - get
diff --git a/config/rbac/hostfirmwaresettings_viewer_role.yaml b/config/rbac/hostfirmwaresettings_viewer_role.yaml
deleted file mode 100644
index 5235eb6b..00000000
--- a/config/rbac/hostfirmwaresettings_viewer_role.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-# permissions for end users to view hostfirmwaresettings.
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: hostfirmwaresettings-viewer-role
-rules:
-- apiGroups:
-  - metal3.io
-  resources:
-  - hostfirmwaresettings
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - metal3.io
-  resources:
-  - hostfirmwaresettings/status
-  verbs:
-  - get
diff --git a/config/render/capm3.yaml b/config/render/capm3.yaml
index 85bb20db..d344303b 100644
--- a/config/render/capm3.yaml
+++ b/config/render/capm3.yaml
@@ -271,7 +271,8 @@ spec:
                 properties:
                   hardwareRAIDVolumes:
                     description: The list of logical disks for hardware RAID, if rootDeviceHints
-                      isn't used, first volume is root volume.
+                      isn't used, first volume is root volume. You can set the value
+                      of this field to `[]` to clear all the hardware RAID configurations.
                     items:
                       description: HardwareRAIDVolume defines the desired configuration
                         of volume in hardware RAID
@@ -323,7 +324,8 @@ spec:
                       has to be a RAID-1, while the RAID level for the second one
                       can be 0, 1, or 1+0. As the first RAID device will be the deployment
                       device, enforcing a RAID-1 reduces the risk of ending up with
-                      a non-booting node in case of a disk failure.
+                      a non-booting node in case of a disk failure. Software RAID
+                      will always be deleted.
                     items:
                       description: SoftwareRAIDVolume defines the desired configuration
                         of volume in software RAID
@@ -856,6 +858,8 @@ spec:
                       hardwareRAIDVolumes:
                         description: The list of logical disks for hardware RAID,
                           if rootDeviceHints isn't used, first volume is root volume.
+                          You can set the value of this field to `[]` to clear all
+                          the hardware RAID configurations.
                         items:
                           description: HardwareRAIDVolume defines the desired configuration
                             of volume in hardware RAID
@@ -908,7 +912,8 @@ spec:
                           RAID-1, while the RAID level for the second one can be 0,
                           1, or 1+0. As the first RAID device will be the deployment
                           device, enforcing a RAID-1 reduces the risk of ending up
-                          with a non-booting node in case of a disk failure.
+                          with a non-booting node in case of a disk failure. Software
+                          RAID will always be deleted.
                         items:
                           description: SoftwareRAIDVolume defines the desired configuration
                             of volume in software RAID
@@ -1078,197 +1083,6 @@ status:
   conditions: []
   storedVersions: []
 ---
-apiVersion: apiextensions.k8s.io/v1
-kind: CustomResourceDefinition
-metadata:
-  annotations:
-    controller-gen.kubebuilder.io/version: v0.4.1
-  creationTimestamp: null
-  name: firmwareschemas.metal3.io
-spec:
-  group: metal3.io
-  names:
-    kind: FirmwareSchema
-    listKind: FirmwareSchemaList
-    plural: firmwareschemas
-    singular: firmwareschema
-  scope: Namespaced
-  versions:
-  - name: v1alpha1
-    schema:
-      openAPIV3Schema:
-        description: FirmwareSchema is the Schema for the firmwareschemas API
-        properties:
-          apiVersion:
-            description: 'APIVersion defines the versioned schema of this representation
-              of an object. Servers should convert recognized schemas to the latest
-              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
-            type: string
-          kind:
-            description: 'Kind is a string value representing the REST resource this
-              object represents. Servers may infer this from the endpoint the client
-              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
-            type: string
-          metadata:
-            type: object
-          spec:
-            description: FirmwareSchemaSpec defines the desired state of FirmwareSchema
-            properties:
-              hardwareModel:
-                description: The hardware model associated with this schema
-                type: string
-              hardwareVendor:
-                description: The hardware vendor associated with this schema
-                type: string
-              schema:
-                additionalProperties:
-                  description: Additional data describing the firmware setting
-                  properties:
-                    allowable_values:
-                      description: The allowable value for an Enumeration type setting.
-                      items:
-                        type: string
-                      type: array
-                    attribute_type:
-                      description: The type of setting.
-                      enum:
-                      - Enumeration
-                      - String
-                      - Integer
-                      - Boolean
-                      - Password
-                      type: string
-                    lower_bound:
-                      description: The lowest value for an Integer type setting.
-                      type: integer
-                    max_length:
-                      description: Maximum length for a String type setting.
-                      type: integer
-                    min_length:
-                      description: Minimum length for a String type setting.
-                      type: integer
-                    read_only:
-                      description: Whether or not this setting is read only.
-                      type: boolean
-                    reset_required:
-                      description: Whether or not a reset is required after changing
-                        this setting.
-                      type: boolean
-                    unique:
-                      description: Whether or not this setting's value is unique to
-                        this node, e.g. a serial number.
-                      type: boolean
-                    upper_bound:
-                      description: The highest value for an Integer type setting.
-                      type: integer
-                  type: object
-                description: Map of firmware name to schema
-                type: object
-            required:
-            - schema
-            type: object
-        type: object
-    served: true
-    storage: true
-status:
-  acceptedNames:
-    kind: ""
-    plural: ""
-  conditions: []
-  storedVersions: []
----
-apiVersion: apiextensions.k8s.io/v1
-kind: CustomResourceDefinition
-metadata:
-  annotations:
-    controller-gen.kubebuilder.io/version: v0.4.1
-  creationTimestamp: null
-  name: hostfirmwaresettings.metal3.io
-spec:
-  group: metal3.io
-  names:
-    kind: HostFirmwareSettings
-    listKind: HostFirmwareSettingsList
-    plural: hostfirmwaresettings
-    singular: hostfirmwaresettings
-  scope: Namespaced
-  versions:
-  - name: v1alpha1
-    schema:
-      openAPIV3Schema:
-        description: HostFirmwareSettings is the Schema for the hostfirmwaresettings
-          API
-        properties:
-          apiVersion:
-            description: 'APIVersion defines the versioned schema of this representation
-              of an object. Servers should convert recognized schemas to the latest
-              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
-            type: string
-          kind:
-            description: 'Kind is a string value representing the REST resource this
-              object represents. Servers may infer this from the endpoint the client
-              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
-            type: string
-          metadata:
-            type: object
-          spec:
-            description: HostFirmwareSettingsSpec defines the desired state of HostFirmwareSettings
-            properties:
-              settings:
-                additionalProperties:
-                  anyOf:
-                  - type: integer
-                  - type: string
-                  x-kubernetes-int-or-string: true
-                description: Settings are the desired firmware settings stored as
-                  name/value pairs. This will be populated with the actual firmware
-                  settings and only contain the settings that can be modified (i.e.
-                  not ReadOnly), to facilitate making changes.
-                type: object
-            required:
-            - settings
-            type: object
-          status:
-            description: HostFirmwareSettingsStatus defines the observed state of
-              HostFirmwareSettings
-            properties:
-              schema:
-                description: FirmwareSchema is a reference to the Schema used to describe
-                  each FirmwareSetting. By default, this will be a Schema in the same
-                  Namespace as the settings but it can be overwritten in the Spec
-                properties:
-                  name:
-                    description: '`name` is the reference to the schema.'
-                    type: string
-                  namespace:
-                    description: '`namespace` is the namespace of the where the schema
-                      is stored.'
-                    type: string
-                required:
-                - name
-                - namespace
-                type: object
-              settings:
-                additionalProperties:
-                  type: string
-                description: Settings are the actual firmware settings stored as name/value
-                  pairs
-                type: object
-            required:
-            - settings
-            type: object
-        type: object
-    served: true
-    storage: true
-    subresources:
-      status: {}
-status:
-  acceptedNames:
-    kind: ""
-    plural: ""
-  conditions: []
-  storedVersions: []
----
 apiVersion: rbac.authorization.k8s.io/v1
 kind: Role
 metadata:
diff --git a/config/samples/metal3.io_v1alpha1_firmwareschema.yaml b/config/samples/metal3.io_v1alpha1_firmwareschema.yaml
deleted file mode 100644
index f5777284..00000000
--- a/config/samples/metal3.io_v1alpha1_firmwareschema.yaml
+++ /dev/null
@@ -1,34 +0,0 @@
-apiVersion: metal3.io/v1alpha1
-kind: FirmwareSchema
-metadata:
-  name: firmwareschema-sample
-spec:
-status:
-  referenceCount: 2
-  hardwareVendor: "VendorA"
-  hardwareModel: "ModelT"
-  schema:
-    - "ProcVirtualization":
-         attribute_type: "Enumeration"
-         allowable_values:
-           - "Enabled"
-           - "Disabled"
-         read_only: false
-    - "ProcCPUCores":
-         attribute_type: "Integer"
-         lower_bound: 1
-         read_only: true
-         upper_bound: 100
-    - "SRIOV":
-         allowable_values:
-           - "Enabled"
-           - "Disabled"
-    - "SystemModelName":
-         attribute_type: "String"
-         max_length: 40
-         min_length: 0
-         read_only: true
-    - "SerialNumber":
-         attribute_type: "String"
-         max_length: 16
-         min_length: 0
diff --git a/config/samples/metal3.io_v1alpha1_hostfirmwaresettings.yaml b/config/samples/metal3.io_v1alpha1_hostfirmwaresettings.yaml
deleted file mode 100644
index 268335bf..00000000
--- a/config/samples/metal3.io_v1alpha1_hostfirmwaresettings.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-apiVersion: metal3.io/v1alpha1
-kind: HostFirmwareSettings
-metadata:
-  name: hostfirmwaresettings-sample
-spec:
-  settings:
-    - "ProcVirtualization": "Enabled"
-    - "SRIOV": "Enabled"
-    - "NetworkBootRetryCount": "20"
-status:
-  settings:
-    - "ProcVirtualization": "Enabled"
-    - "ProcCPUCores": "100"
-    - "SRIOV": "Enabled"
-    - "SystemModelName":  "QYZ12345"
-    - "SerialNumber": "22654891"
-    - "NetworkBootRetryCount": "10"
diff --git a/config/tls/kustomization.yaml b/config/tls/kustomization.yaml
index cdafae35..0af5f2e1 100644
--- a/config/tls/kustomization.yaml
+++ b/config/tls/kustomization.yaml
@@ -5,5 +5,10 @@ resources:
 - ../default
 - ../namespace
 
+secretGenerator:
+  - name: ironic-cacert
+    files:
+    - crt=ca.crt
+
 patchesStrategicMerge:
 - tls_ca_patch.yaml
diff --git a/controllers/metal3.io/baremetalhost_controller.go b/controllers/metal3.io/baremetalhost_controller.go
index 30c06ab5..8f5e4f3a 100644
--- a/controllers/metal3.io/baremetalhost_controller.go
+++ b/controllers/metal3.io/baremetalhost_controller.go
@@ -775,12 +775,18 @@ func (r *BareMetalHostReconciler) actionPreparing(prov provisioner.Provisioner,
 	}
 
 	prepareData := provisioner.PrepareData{
-		RAIDConfig:      newStatus.Provisioning.RAID.DeepCopy(),
-		RootDeviceHints: newStatus.Provisioning.RootDeviceHints.DeepCopy(),
-		FirmwareConfig:  newStatus.Provisioning.Firmware.DeepCopy(),
+		TargetRAIDConfig: newStatus.Provisioning.RAID.DeepCopy(),
+		ActualRAIDConfig: info.host.Status.Provisioning.RAID.DeepCopy(),
+		RootDeviceHints:  newStatus.Provisioning.RootDeviceHints.DeepCopy(),
+		FirmwareConfig:   newStatus.Provisioning.Firmware.DeepCopy(),
+	}
+	// When manual cleaning fails, we think that the existed RAID configuration
+	// is invalid and needs to be reconfigured.
+	if info.host.Status.ErrorType == metal3v1alpha1.PreparationError {
+		prepareData.ActualRAIDConfig = nil
+		dirty = true
 	}
-	provResult, started, err := prov.Prepare(prepareData,
-		dirty || info.host.Status.ErrorType == metal3v1alpha1.PreparationError)
+	provResult, started, err := prov.Prepare(prepareData, dirty)
 	if err != nil {
 		return actionError{errors.Wrap(err, "error preparing host")}
 	}
@@ -890,7 +896,10 @@ func (r *BareMetalHostReconciler) actionProvisioning(prov provisioner.Provisione
 // fields of a host.
 func clearHostProvisioningSettings(host *metal3v1alpha1.BareMetalHost) {
 	host.Status.Provisioning.RootDeviceHints = nil
-	host.Status.Provisioning.RAID = nil
+	// Keep `HardwareRAIDVolumes` to avoid configuring the same hardware RAID repeatedly
+	if host.Status.Provisioning.RAID != nil {
+		host.Status.Provisioning.RAID.SoftwareRAIDVolumes = nil
+	}
 	host.Status.Provisioning.Firmware = nil
 }
 
@@ -1005,12 +1014,9 @@ func (r *BareMetalHostReconciler) manageHostPower(prov provisioner.Provisioner,
 		"reboot process", desiredPowerOnState != info.host.Spec.Online)
 
 	if desiredPowerOnState {
-		provResult, err = prov.PowerOn(info.host.Status.ErrorType == metal3v1alpha1.PowerManagementError)
+		provResult, err = prov.PowerOn()
 	} else {
-		if info.host.Status.ErrorCount > 0 {
-			desiredRebootMode = metal3v1alpha1.RebootModeHard
-		}
-		provResult, err = prov.PowerOff(desiredRebootMode, info.host.Status.ErrorType == metal3v1alpha1.PowerManagementError)
+		provResult, err = prov.PowerOff(desiredRebootMode)
 	}
 	if err != nil {
 		return actionError{errors.Wrap(err, "failed to manage power state of host")}
@@ -1115,46 +1121,29 @@ func saveHostProvisioningSettings(host *metal3v1alpha1.BareMetalHost) (dirty boo
 	}
 
 	// Copy RAID settings
-	if host.Spec.RAID != host.Status.Provisioning.RAID {
-		// If RAID settings is nil, remove saved settings,
-		// else check hardware RAID and software RAID.
-		if host.Spec.RAID == nil {
-			host.Status.Provisioning.RAID = nil
-			dirty = true
-		} else {
-			if host.Status.Provisioning.RAID == nil {
-				host.Status.Provisioning.RAID = &metal3v1alpha1.RAIDConfig{}
-				dirty = true
-			}
-			// If HardwareRAIDVolumes isn't nil, we will ignore SoftwareRAIDVolumes.
-			if len(host.Spec.RAID.HardwareRAIDVolumes) != 0 {
-				// If software RAID has been saved, remove it.
-				if len(host.Status.Provisioning.RAID.SoftwareRAIDVolumes) != 0 {
-					host.Status.Provisioning.RAID.SoftwareRAIDVolumes = nil
-				}
-				// Compare hardware RAID settings
-				if !reflect.DeepEqual(host.Spec.RAID.HardwareRAIDVolumes, host.Status.Provisioning.RAID.HardwareRAIDVolumes) {
-					host.Status.Provisioning.RAID.HardwareRAIDVolumes = host.Spec.RAID.HardwareRAIDVolumes
-					dirty = true
-				}
-			} else {
-				// If hardware RAID has been saved, remove it.
-				if len(host.Status.Provisioning.RAID.HardwareRAIDVolumes) != 0 {
-					host.Status.Provisioning.RAID.HardwareRAIDVolumes = nil
-					dirty = true
-				}
-				// Compare software RAID settings
-				if !reflect.DeepEqual(host.Spec.RAID.SoftwareRAIDVolumes, host.Status.Provisioning.RAID.SoftwareRAIDVolumes) {
-					host.Status.Provisioning.RAID.SoftwareRAIDVolumes = host.Spec.RAID.SoftwareRAIDVolumes
-					dirty = true
-				}
-			}
+	specRAID := host.Spec.RAID
+	// If RAID configure is nil or empty, means that we need to keep the current hardware RAID configuration
+	// or clear current software RAID configuration
+	if specRAID == nil || reflect.DeepEqual(specRAID, &metal3v1alpha1.RAIDConfig{}) {
+		// Set the default value of RAID configure:
+		// {
+		//     HardwareRAIDVolumes: nil or Status.Provisioning.RAID.HardwareRAIDVolumes(not empty),
+		//     SoftwareRAIDVolume: [],
+		// }
+		specRAID = &metal3v1alpha1.RAIDConfig{}
+		if host.Status.Provisioning.RAID != nil && len(host.Status.Provisioning.RAID.HardwareRAIDVolumes) != 0 {
+			specRAID.HardwareRAIDVolumes = host.Status.Provisioning.RAID.HardwareRAIDVolumes
 		}
+		specRAID.SoftwareRAIDVolumes = []metal3v1alpha1.SoftwareRAIDVolume{}
+	}
+	if !reflect.DeepEqual(host.Status.Provisioning.RAID, specRAID) {
+		host.Status.Provisioning.RAID = specRAID
+		dirty = true
 	}
 
 	// Copy BIOS settings
 	if !reflect.DeepEqual(host.Status.Provisioning.Firmware, host.Spec.Firmware) {
-		host.Status.Provisioning.Firmware = host.Spec.Firmware.DeepCopy()
+		host.Status.Provisioning.Firmware = host.Spec.Firmware
 		dirty = true
 	}
 
diff --git a/docs/api.md b/docs/api.md
index 8fe035ee..a8f138c3 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -185,7 +185,35 @@ The sub-fields are:
     GiB. If unspecified or set to 0, the maximum capacity of disk will be
     used for logical disk.
 
-**NOTE:** Currently the 'raid' field is only supported by ilo5/idrac.
+If you do not set the RAID field, we will keep the current RAID configuration.
+
+You can set the `hardwareRAIDVolume` as an empty slice to clear the hardware
+RAID configuration, for example:
+
+```yaml
+spec:
+   raid:
+     hardwareRAIDVolume: []
+```
+
+**NOTE:** Currently the 'raid' field is only supported by ilo5/idrac/irmc.
+
+**NOTE:** Software RAID will always be deleted.
+
+**NOTE:** If you got following error message:
+
+1. raid settings are defined, but the node's driver %s does not support RAID.
+2. node's driver %s does not support hardware RAID.
+3. node's driver %s does not support software RAID.
+
+You can solve it by:
+
+1. Keep raid field is nil.
+2. Keep hardwareRAIDVolumes field is nil.
+3. Keep softwareRAIDVolumes field is nil.
+
+If the error message you get isn't included the above, you may need to check
+whether the BM has a RAID controller and keep the raid field blank.
 
 #### firmware
 
diff --git a/ironic-deployment/basic-auth/default/auth.yaml b/ironic-deployment/basic-auth/default/auth.yaml
index 98550cff..356ea2f6 100644
--- a/ironic-deployment/basic-auth/default/auth.yaml
+++ b/ironic-deployment/basic-auth/default/auth.yaml
@@ -1,7 +1,7 @@
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: ${NAMEPREFIX}-ironic
+  name: capm3-ironic
 spec:
   template:
     spec:
diff --git a/ironic-deployment/basic-auth/default/kustomization.yaml b/ironic-deployment/basic-auth/default/kustomization.yaml
index 1e6cceba..36370e8a 100644
--- a/ironic-deployment/basic-auth/default/kustomization.yaml
+++ b/ironic-deployment/basic-auth/default/kustomization.yaml
@@ -1,6 +1,6 @@
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: ${NAMEPREFIX}-system
+namespace: capm3-system
 resources:
 - ../../default
 
diff --git a/ironic-deployment/basic-auth/keepalived/auth.yaml b/ironic-deployment/basic-auth/keepalived/auth.yaml
index 98550cff..356ea2f6 100644
--- a/ironic-deployment/basic-auth/keepalived/auth.yaml
+++ b/ironic-deployment/basic-auth/keepalived/auth.yaml
@@ -1,7 +1,7 @@
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: ${NAMEPREFIX}-ironic
+  name: capm3-ironic
 spec:
   template:
     spec:
diff --git a/ironic-deployment/basic-auth/keepalived/kustomization.yaml b/ironic-deployment/basic-auth/keepalived/kustomization.yaml
index 34061bde..c2514726 100644
--- a/ironic-deployment/basic-auth/keepalived/kustomization.yaml
+++ b/ironic-deployment/basic-auth/keepalived/kustomization.yaml
@@ -1,6 +1,6 @@
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: ${NAMEPREFIX}-system
+namespace: capm3-system
 resources:
 - ../../keepalived
 
diff --git a/ironic-deployment/basic-auth/tls/default/auth.yaml b/ironic-deployment/basic-auth/tls/default/auth.yaml
index 98550cff..356ea2f6 100644
--- a/ironic-deployment/basic-auth/tls/default/auth.yaml
+++ b/ironic-deployment/basic-auth/tls/default/auth.yaml
@@ -1,7 +1,7 @@
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: ${NAMEPREFIX}-ironic
+  name: capm3-ironic
 spec:
   template:
     spec:
diff --git a/ironic-deployment/basic-auth/tls/default/kustomization.yaml b/ironic-deployment/basic-auth/tls/default/kustomization.yaml
index a750b9ac..ff711f7e 100644
--- a/ironic-deployment/basic-auth/tls/default/kustomization.yaml
+++ b/ironic-deployment/basic-auth/tls/default/kustomization.yaml
@@ -1,6 +1,6 @@
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: ${NAMEPREFIX}-system
+namespace: capm3-system
 resources:
 - ../../../tls/default
 
diff --git a/ironic-deployment/basic-auth/tls/keepalived/auth.yaml b/ironic-deployment/basic-auth/tls/keepalived/auth.yaml
index 98550cff..356ea2f6 100644
--- a/ironic-deployment/basic-auth/tls/keepalived/auth.yaml
+++ b/ironic-deployment/basic-auth/tls/keepalived/auth.yaml
@@ -1,7 +1,7 @@
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: ${NAMEPREFIX}-ironic
+  name: capm3-ironic
 spec:
   template:
     spec:
diff --git a/ironic-deployment/basic-auth/tls/keepalived/kustomization.yaml b/ironic-deployment/basic-auth/tls/keepalived/kustomization.yaml
index fa1188c9..06751477 100644
--- a/ironic-deployment/basic-auth/tls/keepalived/kustomization.yaml
+++ b/ironic-deployment/basic-auth/tls/keepalived/kustomization.yaml
@@ -1,6 +1,6 @@
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: ${NAMEPREFIX}-system
+namespace: capm3-system
 resources:
 - ../../../tls/keepalived
 
diff --git a/ironic-deployment/certmanager/certificate.yaml b/ironic-deployment/certmanager/certificate.yaml
index 6a9cea6b..476cd135 100644
--- a/ironic-deployment/certmanager/certificate.yaml
+++ b/ironic-deployment/certmanager/certificate.yaml
@@ -2,7 +2,7 @@ apiVersion: cert-manager.io/v1
 kind: Issuer
 metadata:
   name: selfsigned-issuer
-  namespace: ${NAMEPREFIX}-system
+  namespace: capm3-system
 spec:
   selfSigned: {}
 ---
@@ -10,7 +10,7 @@ apiVersion: cert-manager.io/v1
 kind: Certificate
 metadata:
   name: ironic-cacert
-  namespace: ${NAMEPREFIX}-system
+  namespace: capm3-system
 spec:
   isCA: true
   ipAddresses:
@@ -24,7 +24,7 @@ apiVersion: cert-manager.io/v1
 kind: Issuer
 metadata:
   name: ca-issuer
-  namespace: ${NAMEPREFIX}-system
+  namespace: capm3-system
 spec:
   ca:
     secretName: ironic-cacert
@@ -33,7 +33,7 @@ apiVersion: cert-manager.io/v1
 kind: Certificate
 metadata:
   name: ironic-cert
-  namespace: ${NAMEPREFIX}-system
+  namespace: capm3-system
 spec:
   ipAddresses:
   - IRONIC_HOST_IP
@@ -46,7 +46,7 @@ apiVersion: cert-manager.io/v1
 kind: Certificate
 metadata:
   name: ironic-inspector-cert
-  namespace: ${NAMEPREFIX}-system
+  namespace: capm3-system
 spec:
   ipAddresses:
   - IRONIC_HOST_IP
@@ -59,7 +59,7 @@ apiVersion: cert-manager.io/v1
 kind: Certificate
 metadata:
   name: mariadb-cert
-  namespace: ${NAMEPREFIX}-system
+  namespace: capm3-system
 spec:
   ipAddresses:
   - MARIADB_HOST_IP
diff --git a/ironic-deployment/default/kustomization.yaml b/ironic-deployment/default/kustomization.yaml
index 48c0842f..b0f162b5 100644
--- a/ironic-deployment/default/kustomization.yaml
+++ b/ironic-deployment/default/kustomization.yaml
@@ -1,6 +1,6 @@
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: ${NAMEPREFIX}-system
+namespace: capm3-system
 resources:
 - ../ironic
 configMapGenerator:
diff --git a/ironic-deployment/ironic/ironic.yaml b/ironic-deployment/ironic/ironic.yaml
index e6cc3651..788646a9 100644
--- a/ironic-deployment/ironic/ironic.yaml
+++ b/ironic-deployment/ironic/ironic.yaml
@@ -1,7 +1,7 @@
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: ${NAMEPREFIX}-ironic
+  name: capm3-ironic
 spec:
   replicas: 1
   strategy:
@@ -10,11 +10,11 @@ spec:
     type: Recreate
   selector:
     matchLabels:
-      name: ${NAMEPREFIX}-ironic
+      name: capm3-ironic
   template:
     metadata:
       labels:
-        name: ${NAMEPREFIX}-ironic
+        name: capm3-ironic
     spec:
       hostNetwork: true
       containers:
diff --git a/ironic-deployment/keepalived/keepalived_patch.yaml b/ironic-deployment/keepalived/keepalived_patch.yaml
index 95552903..23614ac9 100644
--- a/ironic-deployment/keepalived/keepalived_patch.yaml
+++ b/ironic-deployment/keepalived/keepalived_patch.yaml
@@ -1,7 +1,7 @@
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: ${NAMEPREFIX}-ironic
+  name: capm3-ironic
 spec:
   template:
     spec:
diff --git a/ironic-deployment/keepalived/kustomization.yaml b/ironic-deployment/keepalived/kustomization.yaml
index 847439b1..daedb24e 100644
--- a/ironic-deployment/keepalived/kustomization.yaml
+++ b/ironic-deployment/keepalived/kustomization.yaml
@@ -1,6 +1,6 @@
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: ${NAMEPREFIX}-system
+namespace: capm3-system
 resources:
 - ../../config/namespace
 - ../ironic
diff --git a/ironic-deployment/tls/default/kustomization.yaml b/ironic-deployment/tls/default/kustomization.yaml
index 44ac055f..a84fe668 100644
--- a/ironic-deployment/tls/default/kustomization.yaml
+++ b/ironic-deployment/tls/default/kustomization.yaml
@@ -1,6 +1,6 @@
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: ${NAMEPREFIX}-system
+namespace: capm3-system
 resources:
 - ../../default
 - ../../certmanager
diff --git a/ironic-deployment/tls/default/tls.yaml b/ironic-deployment/tls/default/tls.yaml
index 4a0bf9c1..107ec414 100644
--- a/ironic-deployment/tls/default/tls.yaml
+++ b/ironic-deployment/tls/default/tls.yaml
@@ -1,7 +1,7 @@
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: ${NAMEPREFIX}-ironic
+  name: capm3-ironic
 spec:
   template:
     spec:
diff --git a/ironic-deployment/tls/keepalived/kustomization.yaml b/ironic-deployment/tls/keepalived/kustomization.yaml
index ce39a6d1..0ff81618 100644
--- a/ironic-deployment/tls/keepalived/kustomization.yaml
+++ b/ironic-deployment/tls/keepalived/kustomization.yaml
@@ -1,6 +1,6 @@
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: ${NAMEPREFIX}-system
+namespace: capm3-system
 resources:
 - ../../keepalived
 - ../../certmanager
diff --git a/ironic-deployment/tls/keepalived/tls.yaml b/ironic-deployment/tls/keepalived/tls.yaml
index 818d3436..718ec054 100644
--- a/ironic-deployment/tls/keepalived/tls.yaml
+++ b/ironic-deployment/tls/keepalived/tls.yaml
@@ -1,7 +1,7 @@
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: ${NAMEPREFIX}-ironic
+  name: capm3-ironic
 spec:
   template:
     spec:
diff --git a/pkg/bmc/irmc.go b/pkg/bmc/irmc.go
index 7fa9ed11..f6c45c9b 100644
--- a/pkg/bmc/irmc.go
+++ b/pkg/bmc/irmc.go
@@ -83,7 +83,7 @@ func (a *iRMCAccessDetails) PowerInterface() string {
 }
 
 func (a *iRMCAccessDetails) RAIDInterface() string {
-	return "no-raid"
+	return "irmc"
 }
 
 func (a *iRMCAccessDetails) VendorInterface() string {
@@ -99,6 +99,10 @@ func (a *iRMCAccessDetails) BuildBIOSSettings(firmwareConfig *metal3v1alpha1.Fir
 		return nil, nil
 	}
 
+	if firmwareConfig == nil {
+		return nil, nil
+	}
+
 	var value string
 
 	if firmwareConfig.VirtualizationEnabled != nil {
diff --git a/pkg/provisioner/demo/demo.go b/pkg/provisioner/demo/demo.go
index be79a6f4..00741b33 100644
--- a/pkg/provisioner/demo/demo.go
+++ b/pkg/provisioner/demo/demo.go
@@ -305,7 +305,7 @@ func (p *demoProvisioner) Detach() (result provisioner.Result, err error) {
 
 // PowerOn ensures the server is powered on independently of any image
 // provisioning operation.
-func (p *demoProvisioner) PowerOn(force bool) (result provisioner.Result, err error) {
+func (p *demoProvisioner) PowerOn() (result provisioner.Result, err error) {
 
 	hostName := p.objectMeta.Name
 	switch hostName {
@@ -326,7 +326,7 @@ func (p *demoProvisioner) PowerOn(force bool) (result provisioner.Result, err er
 
 // PowerOff ensures the server is powered off independently of any image
 // provisioning operation.
-func (p *demoProvisioner) PowerOff(rebootMode metal3v1alpha1.RebootMode, force bool) (result provisioner.Result, err error) {
+func (p *demoProvisioner) PowerOff(rebootMode metal3v1alpha1.RebootMode) (result provisioner.Result, err error) {
 
 	hostName := p.objectMeta.Name
 	switch hostName {
diff --git a/pkg/provisioner/fixture/fixture.go b/pkg/provisioner/fixture/fixture.go
index b3eee1cf..ab0b2e21 100644
--- a/pkg/provisioner/fixture/fixture.go
+++ b/pkg/provisioner/fixture/fixture.go
@@ -291,7 +291,7 @@ func (p *fixtureProvisioner) Detach() (result provisioner.Result, err error) {
 
 // PowerOn ensures the server is powered on independently of any image
 // provisioning operation.
-func (p *fixtureProvisioner) PowerOn(force bool) (result provisioner.Result, err error) {
+func (p *fixtureProvisioner) PowerOn() (result provisioner.Result, err error) {
 	p.log.Info("ensuring host is powered on")
 
 	if !p.state.poweredOn {
@@ -307,7 +307,7 @@ func (p *fixtureProvisioner) PowerOn(force bool) (result provisioner.Result, err
 
 // PowerOff ensures the server is powered off independently of any image
 // provisioning operation.
-func (p *fixtureProvisioner) PowerOff(rebootMode metal3v1alpha1.RebootMode, force bool) (result provisioner.Result, err error) {
+func (p *fixtureProvisioner) PowerOff(rebootMode metal3v1alpha1.RebootMode) (result provisioner.Result, err error) {
 	p.log.Info("ensuring host is powered off")
 
 	if p.state.poweredOn {
diff --git a/pkg/provisioner/ironic/errors.go b/pkg/provisioner/ironic/errors.go
index 8a69c0cb..c3384bcd 100644
--- a/pkg/provisioner/ironic/errors.go
+++ b/pkg/provisioner/ironic/errors.go
@@ -9,6 +9,15 @@ func (e SoftPowerOffUnsupportedError) Error() string {
 	return "soft power off is unsupported on BMC"
 }
 
+// SoftPowerOffFailed is returned when the soft power off command
+// finishes with failure.
+type SoftPowerOffFailed struct {
+}
+
+func (e SoftPowerOffFailed) Error() string {
+	return "Soft power off has failed on BMC"
+}
+
 // HostLockedError is returned when the BMC host is
 // locked.
 type HostLockedError struct {
diff --git a/pkg/provisioner/ironic/ironic.go b/pkg/provisioner/ironic/ironic.go
index eb42929c..95294c15 100644
--- a/pkg/provisioner/ironic/ironic.go
+++ b/pkg/provisioner/ironic/ironic.go
@@ -980,11 +980,11 @@ func (p *ironicProvisioner) ironicHasSameImage(ironicNode *nodes.Node, image met
 
 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(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())
+	raidCleanSteps, err := BuildRAIDCleanSteps(bmcAccess.RAIDInterface(), data.TargetRAIDConfig, data.ActualRAIDConfig)
+	if err != nil {
+		return nil, err
 	}
+	cleanSteps = append(cleanSteps, raidCleanSteps...)
 
 	// Build bios clean steps
 	settings, err := bmcAccess.BuildBIOSSettings(data.FirmwareConfig)
@@ -1010,13 +1010,10 @@ func (p *ironicProvisioner) buildManualCleaningSteps(bmcAccess bmc.AccessDetails
 }
 
 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, data)
-		if err != nil {
-			result, err = transientError(err)
-			return
-		}
+	// Set raid configuration
+	result, err = setTargetRAIDCfg(p, bmcAccess.RAIDInterface(), ironicNode, data)
+	if result.Dirty || result.ErrorMessage != "" || err != nil {
+		return
 	}
 
 	// Build manual clean steps
@@ -1028,7 +1025,7 @@ func (p *ironicProvisioner) startManualCleaning(bmcAccess bmc.AccessDetails, iro
 
 	// Start manual clean
 	if len(cleanSteps) != 0 {
-		p.log.Info("remove existing configuration and set new configuration", "steps", cleanSteps)
+		p.log.Info("remove existing configuration and set new configuration", "clean steps", cleanSteps)
 		return p.tryChangeNodeProvisionState(
 			ironicNode,
 			nodes.ProvisionStateOpts{
@@ -1066,6 +1063,7 @@ func (p *ironicProvisioner) Prepare(data provisioner.PrepareData, unprepared boo
 				return
 			}
 			if len(cleanSteps) != 0 {
+				p.log.Info("the node needs to be reconfigured", "clean steps", cleanSteps)
 				result, err = p.changeNodeProvisionState(
 					ironicNode,
 					nodes.ProvisionStateOpts{Target: nodes.TargetManage},
@@ -1519,7 +1517,7 @@ func (p *ironicProvisioner) changePower(ironicNode *nodes.Node, target nodes.Tar
 
 // PowerOn ensures the server is powered on independently of any image
 // provisioning operation.
-func (p *ironicProvisioner) PowerOn(force bool) (result provisioner.Result, err error) {
+func (p *ironicProvisioner) PowerOn() (result provisioner.Result, err error) {
 	p.log.Info("ensuring host is powered on")
 
 	ironicNode, err := p.getNode()
@@ -1535,39 +1533,46 @@ func (p *ironicProvisioner) PowerOn(force bool) (result provisioner.Result, err
 			p.log.Info("waiting for power status to change")
 			return operationContinuing(powerRequeueDelay)
 		}
-		if ironicNode.LastError != "" && !force {
-			p.log.Info("PowerOn operation failed", "msg", ironicNode.LastError)
-			return operationFailed(fmt.Sprintf("PowerOn operation failed: %s",
-				ironicNode.LastError))
-		}
-		if result, err = p.changePower(ironicNode, nodes.PowerOn); err == nil {
-			p.publisher("PowerOn", "Host powered on")
-			return result, nil
-		}
+		result, err = p.changePower(ironicNode, nodes.PowerOn)
 		switch err.(type) {
+		case nil:
 		case HostLockedError:
-			return retryAfterDelay(powerRequeueDelay)
 		default:
-			return transientError(errors.Wrap(err, "failed to PowerOn node"))
+			return transientError(errors.Wrap(err, "failed to power on host"))
 		}
+		p.publisher("PowerOn", "Host powered on")
 	}
+
 	return result, nil
 }
 
 // PowerOff ensures the server is powered off independently of any image
 // provisioning operation.
-func (p *ironicProvisioner) PowerOff(rebootMode metal3v1alpha1.RebootMode, force bool) (result provisioner.Result, err error) {
+func (p *ironicProvisioner) PowerOff(rebootMode metal3v1alpha1.RebootMode) (result provisioner.Result, err error) {
 	p.log.Info(fmt.Sprintf("ensuring host is powered off (mode: %s)", rebootMode))
 
-	if rebootMode == metal3v1alpha1.RebootModeSoft {
-		return p.softPowerOff()
+	if rebootMode == metal3v1alpha1.RebootModeHard {
+		result, err = p.hardPowerOff()
+	} else {
+		result, err = p.softPowerOff()
 	}
-	// Reboot mode is hard or force flag is set
-	return p.hardPowerOff(force)
+	if err != nil {
+		switch err.(type) {
+		// In case of soft power off is unsupported or has failed,
+		// we activate hard power off.
+		case SoftPowerOffUnsupportedError, SoftPowerOffFailed:
+			return p.hardPowerOff()
+		case HostLockedError:
+			return retryAfterDelay(powerRequeueDelay)
+		default:
+			return transientError(err)
+		}
+	}
+	return result, nil
 }
 
 // hardPowerOff sends 'power off' request to BM node and waits for the result
-func (p *ironicProvisioner) hardPowerOff(force bool) (result provisioner.Result, err error) {
+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.getNode()
@@ -1576,22 +1581,13 @@ func (p *ironicProvisioner) hardPowerOff(force bool) (result provisioner.Result,
 	}
 
 	if ironicNode.PowerState != powerOff {
-		if ironicNode.LastError != "" && !force {
-			p.log.Info("hard power off error", "msg", ironicNode.LastError)
-			return operationFailed(ironicNode.LastError)
-		}
 		if ironicNode.TargetPowerState == powerOff {
 			p.log.Info("waiting for power status to change")
 			return operationContinuing(powerRequeueDelay)
 		}
 		result, err = p.changePower(ironicNode, nodes.PowerOff)
 		if err != nil {
-			switch err.(type) {
-			case HostLockedError:
-				return retryAfterDelay(powerRequeueDelay)
-			default:
-				return transientError(errors.Wrap(err, "failed to power off host"))
-			}
+			return transientError(errors.Wrap(err, "failed to power off host"))
 		}
 		p.publisher("PowerOff", "Host powered off")
 		return result, err
@@ -1623,17 +1619,11 @@ 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 != "" {
-			p.log.Info("soft power off error", "msg", ironicNode.LastError)
-			return operationFailed(ironicNode.LastError)
+			return result, SoftPowerOffFailed{}
 		}
 		result, err = p.changePower(ironicNode, nodes.SoftPowerOff)
 		if err != nil {
-			switch err.(type) {
-			case HostLockedError:
-				return retryAfterDelay(powerRequeueDelay)
-			default:
-				return transientError(errors.Wrap(err, "failed to power off host"))
-			}
+			return transientError(err)
 		}
 		p.publisher("PowerOff", "Host soft powered off")
 	}
diff --git a/pkg/provisioner/ironic/raid.go b/pkg/provisioner/ironic/raid.go
index 75127f0c..5daadbe5 100644
--- a/pkg/provisioner/ironic/raid.go
+++ b/pkg/provisioner/ironic/raid.go
@@ -2,6 +2,7 @@ package ironic
 
 import (
 	"fmt"
+	"reflect"
 
 	"github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes"
 
@@ -12,14 +13,27 @@ import (
 	"github.com/pkg/errors"
 )
 
+const (
+	noRAIDInterface       string = "no-raid"
+	softwareRAIDInterface string = "agent"
+)
+
 // setTargetRAIDCfg set the RAID settings to the ironic Node for RAID configuration steps
-func setTargetRAIDCfg(p *ironicProvisioner, ironicNode *nodes.Node, data provisioner.PrepareData) (err error) {
+func setTargetRAIDCfg(p *ironicProvisioner, raidInterface string, ironicNode *nodes.Node, data provisioner.PrepareData) (provisioner.Result, error) {
+	err := checkRAIDConfigure(raidInterface, data.TargetRAIDConfig)
+	if err != nil {
+		return operationFailed(err.Error())
+	}
+
 	var logicalDisks []nodes.LogicalDisk
 
 	// Build target for RAID configuration steps
-	logicalDisks, err = BuildTargetRAIDCfg(data.RAIDConfig)
-	if len(logicalDisks) == 0 || err != nil {
-		return
+	logicalDisks, err = BuildTargetRAIDCfg(data.TargetRAIDConfig)
+	if err != nil {
+		return operationFailed(err.Error())
+	}
+	if len(logicalDisks) == 0 {
+		return provisioner.Result{}, nil
 	}
 
 	// set root volume
@@ -31,11 +45,15 @@ func setTargetRAIDCfg(p *ironicProvisioner, ironicNode *nodes.Node, data provisi
 	}
 
 	// Set target for RAID configuration steps
-	return nodes.SetRAIDConfig(
+	err = nodes.SetRAIDConfig(
 		p.client,
 		ironicNode.UUID,
 		nodes.RAIDConfigOpts{LogicalDisks: logicalDisks},
 	).ExtractErr()
+	if err != nil {
+		return transientError(err)
+	}
+	return provisioner.Result{}, nil
 }
 
 // BuildTargetRAIDCfg build RAID logical disks, this method doesn't set the root volume
@@ -138,7 +156,71 @@ func buildTargetSoftwareRAIDCfg(volumes []metal3v1alpha1.SoftwareRAIDVolume) (lo
 }
 
 // BuildRAIDCleanSteps build the clean steps for RAID configuration from BaremetalHost spec
-func BuildRAIDCleanSteps(raid *metal3v1alpha1.RAIDConfig) (cleanSteps []nodes.CleanStep) {
+func BuildRAIDCleanSteps(raidInterface string, target *metal3v1alpha1.RAIDConfig, actual *metal3v1alpha1.RAIDConfig) (cleanSteps []nodes.CleanStep, err error) {
+	err = checkRAIDConfigure(raidInterface, target)
+	if err != nil {
+		return nil, err
+	}
+
+	// No RAID
+	if raidInterface == noRAIDInterface {
+		return
+	}
+
+	// Software RAID
+	if raidInterface == softwareRAIDInterface {
+		// Ignore HardwareRAIDVolumes
+		if target != nil {
+			target.HardwareRAIDVolumes = nil
+		}
+		if actual != nil {
+			actual.HardwareRAIDVolumes = nil
+		}
+		if reflect.DeepEqual(target, actual) {
+			return
+		}
+
+		cleanSteps = append(
+			cleanSteps,
+			[]nodes.CleanStep{
+				{
+					Interface: "raid",
+					Step:      "delete_configuration",
+				},
+				{
+					Interface: "deploy",
+					Step:      "erase_devices_metadata",
+				},
+			}...,
+		)
+
+		// If software raid configuration is empty, only need to clear old configuration
+		if target == nil || len(target.SoftwareRAIDVolumes) == 0 {
+			return
+		}
+
+		cleanSteps = append(
+			cleanSteps,
+			nodes.CleanStep{
+				Interface: "raid",
+				Step:      "create_configuration",
+			},
+		)
+		return
+	}
+
+	// Hardware RAID
+	// Ignore SoftwareRAIDVolumes
+	if target != nil {
+		target.SoftwareRAIDVolumes = nil
+	}
+	if actual != nil {
+		actual.SoftwareRAIDVolumes = nil
+	}
+	if reflect.DeepEqual(target, actual) {
+		return
+	}
+
 	// Add ‘delete_configuration’ before ‘create_configuration’ to make sure
 	// that only the desired logical disks exist in the system after manual cleaning.
 	cleanSteps = append(
@@ -148,19 +230,12 @@ func BuildRAIDCleanSteps(raid *metal3v1alpha1.RAIDConfig) (cleanSteps []nodes.Cl
 			Step:      "delete_configuration",
 		},
 	)
-	// If not configure raid, only need to clear old configuration
-	if raid == nil || (len(raid.HardwareRAIDVolumes) == 0 && len(raid.SoftwareRAIDVolumes) == 0) {
+
+	// If hardware raid configuration is empty, only need to clear old configuration
+	if target == nil || len(target.HardwareRAIDVolumes) == 0 {
 		return
 	}
-	if len(raid.HardwareRAIDVolumes) == 0 && len(raid.SoftwareRAIDVolumes) != 0 {
-		cleanSteps = append(
-			cleanSteps,
-			nodes.CleanStep{
-				Interface: "deploy",
-				Step:      "erase_devices_metadata",
-			},
-		)
-	}
+
 	// ‘create_configuration’ doesn’t remove existing disks. It is recommended
 	// that only the desired logical disks exist in the system after manual cleaning.
 	cleanSteps = append(
@@ -172,3 +247,21 @@ func BuildRAIDCleanSteps(raid *metal3v1alpha1.RAIDConfig) (cleanSteps []nodes.Cl
 	)
 	return
 }
+
+func checkRAIDConfigure(raidInterface string, raid *metal3v1alpha1.RAIDConfig) error {
+	switch raidInterface {
+	case noRAIDInterface:
+		if raid != nil && (len(raid.HardwareRAIDVolumes) != 0 || len(raid.SoftwareRAIDVolumes) != 0) {
+			return fmt.Errorf("raid settings are defined, but the node's driver %s does not support RAID", raidInterface)
+		}
+	case softwareRAIDInterface:
+		if raid != nil && len(raid.HardwareRAIDVolumes) != 0 {
+			return fmt.Errorf("node's driver %s does not support hardware RAID", raidInterface)
+		}
+	default:
+		if raid != nil && len(raid.HardwareRAIDVolumes) == 0 && len(raid.SoftwareRAIDVolumes) != 0 {
+			return fmt.Errorf("node's driver %s does not support software RAID", raidInterface)
+		}
+	}
+	return nil
+}
diff --git a/pkg/provisioner/provisioner.go b/pkg/provisioner/provisioner.go
index a579e44c..f23d308a 100644
--- a/pkg/provisioner/provisioner.go
+++ b/pkg/provisioner/provisioner.go
@@ -76,9 +76,10 @@ type InspectData struct {
 }
 
 type PrepareData struct {
-	RAIDConfig      *metal3v1alpha1.RAIDConfig
-	RootDeviceHints *metal3v1alpha1.RootDeviceHints
-	FirmwareConfig  *metal3v1alpha1.FirmwareConfig
+	TargetRAIDConfig *metal3v1alpha1.RAIDConfig
+	ActualRAIDConfig *metal3v1alpha1.RAIDConfig
+	RootDeviceHints  *metal3v1alpha1.RootDeviceHints
+	FirmwareConfig   *metal3v1alpha1.FirmwareConfig
 }
 
 type ProvisionData struct {
@@ -144,12 +145,12 @@ type Provisioner interface {
 
 	// PowerOn ensures the server is powered on independently of any image
 	// provisioning operation.
-	PowerOn(force bool) (result Result, err error)
+	PowerOn() (result Result, err error)
 
 	// PowerOff ensures the server is powered off independently of any image
 	// provisioning operation. The boolean argument may be used to specify
 	// if a hard reboot (force power off) is required - true if so.
-	PowerOff(rebootMode metal3v1alpha1.RebootMode, force bool) (result Result, err error)
+	PowerOff(rebootMode metal3v1alpha1.RebootMode) (result Result, err error)
 
 	// IsReady checks if the provisioning backend is available to accept
 	// all the incoming requests.
diff --git a/tools/deploy.sh b/tools/deploy.sh
index 9f9bb3e1..d7fbd8be 100755
--- a/tools/deploy.sh
+++ b/tools/deploy.sh
@@ -22,34 +22,9 @@ MARIADB_HOST_IP="${MARIADB_HOST_IP:-"127.0.0.1"}"
 KUBECTL_ARGS="${KUBECTL_ARGS:-""}"
 KUSTOMIZE="go run sigs.k8s.io/kustomize/kustomize/v3"
 RESTART_CONTAINER_CERTIFICATE_UPDATED=${RESTART_CONTAINER_CERTIFICATE_UPDATED:-"false"}
-export NAMEPREFIX=${NAMEPREFIX:-"capm3"}
 
 SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )"
 
-IRONIC_DEPLOY_FILES="${SCRIPTDIR}/ironic-deployment/basic-auth/default/auth.yaml \
-	${SCRIPTDIR}/ironic-deployment/basic-auth/default/kustomization.yaml \
-	${SCRIPTDIR}/ironic-deployment/basic-auth/keepalived/auth.yaml \
-	${SCRIPTDIR}/ironic-deployment/basic-auth/keepalived/kustomization.yaml \
-	${SCRIPTDIR}/ironic-deployment/basic-auth/tls/default/auth.yaml \
-	${SCRIPTDIR}/ironic-deployment/basic-auth/tls/default/kustomization.yaml \
-	${SCRIPTDIR}/ironic-deployment/basic-auth/tls/keepalived/auth.yaml \
-	${SCRIPTDIR}/ironic-deployment/basic-auth/tls/keepalived/kustomization.yaml \
-	${SCRIPTDIR}/ironic-deployment/certmanager/certificate.yaml \
-	${SCRIPTDIR}/ironic-deployment/default/kustomization.yaml \
-	${SCRIPTDIR}/ironic-deployment/ironic/ironic.yaml \
-	${SCRIPTDIR}/ironic-deployment/keepalived/keepalived_patch.yaml \
-	${SCRIPTDIR}/ironic-deployment/keepalived/kustomization.yaml \
-	${SCRIPTDIR}/ironic-deployment/tls/default/kustomization.yaml \
-	${SCRIPTDIR}/ironic-deployment/tls/default/tls.yaml \
-	${SCRIPTDIR}/ironic-deployment/tls/keepalived/kustomization.yaml \
-	${SCRIPTDIR}/ironic-deployment/tls/keepalived/tls.yaml"
-
-for DEPLOY_FILE in ${IRONIC_DEPLOY_FILES}; do
-  cp "$DEPLOY_FILE" "$DEPLOY_FILE".bak
-  # shellcheck disable=SC2094
-  envsubst <"$DEPLOY_FILE".bak> "$DEPLOY_FILE"
-done
-
 if [ "${DEPLOY_BASIC_AUTH}" == "true" ]; then
     BMO_SCENARIO="${SCRIPTDIR}/config/basic-auth"
     IRONIC_SCENARIO="${SCRIPTDIR}/ironic-deployment/basic-auth"
@@ -169,11 +144,6 @@ if [ "${DEPLOY_IRONIC}" == "true" ]; then
     popd
 fi
 
-# Move back the original IRONIC_DEPLOY_FILES
- for DEPLOY_FILE in ${IRONIC_DEPLOY_FILES}; do
-    mv "$DEPLOY_FILE".bak "$DEPLOY_FILE"
- done
-
 if [ "${DEPLOY_BASIC_AUTH}" == "true" ]; then
     if [ "${DEPLOY_BMO}" == "true" ]; then
         rm "${BMO_SCENARIO}/ironic-username"
diff --git a/tools/run_local_ironic.sh b/tools/run_local_ironic.sh
index 7642c364..4c97f2c1 100755
--- a/tools/run_local_ironic.sh
+++ b/tools/run_local_ironic.sh
@@ -18,7 +18,7 @@ PROVISIONING_INTERFACE="${PROVISIONING_INTERFACE:-"ironicendpoint"}"
 CLUSTER_DHCP_RANGE="${CLUSTER_DHCP_RANGE:-"172.22.0.10,172.22.0.100"}"
 IRONIC_KERNEL_PARAMS="${IRONIC_KERNEL_PARAMS:-"console=ttyS0"}"
 IRONIC_BOOT_ISO_SOURCE="${IRONIC_BOOT_ISO_SOURCE:-"local"}"
-export NAMEPREFIX=${NAMEPREFIX:-"capm3"}
+
 
 IRONIC_CACERT_FILE="${IRONIC_CACERT_FILE:-}"
 IRONIC_CERT_FILE="${IRONIC_CERT_FILE:-}"
@@ -93,7 +93,7 @@ data:
 kind: Secret
 metadata:
    name: ironic-cacert
-   namespace: ${NAMEPREFIX}-system
+   namespace: capm3-system
 type: Opaque
 EOF
 fi


Test patch:
diff --git a/apis/metal3.io/v1alpha1/hostfirmwaresettings_types_test.go b/apis/metal3.io/v1alpha1/hostfirmwaresettings_types_test.go
deleted file mode 100644
index 6d743f1a..00000000
--- a/apis/metal3.io/v1alpha1/hostfirmwaresettings_types_test.go
+++ /dev/null
@@ -1,137 +0,0 @@
-package v1alpha1
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/intstr"
-)
-
-func TestCheckSettingIsValid(t *testing.T) {
-
-	lower_bound := 1
-	upper_bound := 20
-	min_length := 1
-	max_length := 16
-	read_only := true
-
-	fwSchema := &FirmwareSchema{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "myfwschema",
-			Namespace: "myns",
-		},
-		Spec: FirmwareSchemaSpec{
-			Schema: make(map[string]SettingSchema),
-		},
-	}
-
-	fwSchema.Spec.Schema["AssetTag"] = SettingSchema{AttributeType: "String",
-		MinLength: &min_length, MaxLength: &max_length}
-	fwSchema.Spec.Schema["ProcVirtualization"] = SettingSchema{AttributeType: "Enumeration",
-		AllowableValues: []string{"Enabled", "Disabled"}}
-	fwSchema.Spec.Schema["NetworkBootRetryCount"] = SettingSchema{AttributeType: "Integer",
-		LowerBound: &lower_bound, UpperBound: &upper_bound}
-	fwSchema.Spec.Schema["SerialNumber"] = SettingSchema{AttributeType: "String",
-		MinLength: &min_length, MaxLength: &max_length, ReadOnly: &read_only}
-	fwSchema.Spec.Schema["QuietBoot"] = SettingSchema{AttributeType: "Boolean"}
-	fwSchema.Spec.Schema["SriovEnable"] = SettingSchema{} // fields intentionally excluded
-
-	for _, tc := range []struct {
-		Scenario string
-		Name     string
-		Value    intstr.IntOrString
-		Expected bool
-	}{
-		{
-			Scenario: "StringTypePass",
-			Name:     "AssetTag",
-			Value:    intstr.FromString("NewServer"),
-			Expected: true,
-		},
-		{
-			Scenario: "StringTypeFailUpper",
-			Name:     "AssetTag",
-			Value:    intstr.FromString("NewServerPutInServiceIn2021"),
-			Expected: false,
-		},
-		{
-			Scenario: "StringTypeFailLower",
-			Name:     "AssetTag",
-			Value:    intstr.FromString(""),
-			Expected: false,
-		},
-		{
-			Scenario: "EnumerationTypePass",
-			Name:     "ProcVirtualization",
-			Value:    intstr.FromString("Disabled"),
-			Expected: true,
-		},
-		{
-			Scenario: "EnumerationTypeFail",
-			Name:     "ProcVirtualization",
-			Value:    intstr.FromString("Foo"),
-			Expected: false,
-		},
-		{
-			Scenario: "IntegerTypePassAsString",
-			Name:     "NetworkBootRetryCount",
-			Value:    intstr.FromString("10"),
-			Expected: true,
-		},
-		{
-			Scenario: "IntegerTypePassAsInt",
-			Name:     "NetworkBootRetryCount",
-			Value:    intstr.FromInt(10),
-			Expected: true,
-		},
-		{
-			Scenario: "IntegerTypeFailUpper",
-			Name:     "NetworkBootRetryCount",
-			Value:    intstr.FromString("42"),
-			Expected: false,
-		},
-		{
-			Scenario: "IntegerTypeFailLower",
-			Name:     "NetworkBootRetryCount",
-			Value:    intstr.FromInt(0),
-			Expected: false,
-		},
-		{
-			Scenario: "BooleanTypePass",
-			Name:     "QuietBoot",
-			Value:    intstr.FromString("true"),
-			Expected: true,
-		},
-		{
-			Scenario: "BooleanTypeFail",
-			Name:     "QuietBoot",
-			Value:    intstr.FromString("Enabled"),
-			Expected: false,
-		},
-		{
-			Scenario: "ReadOnlyTypeFail",
-			Name:     "SerialNumber",
-			Value:    intstr.FromString("42"),
-			Expected: false,
-		},
-		{
-			Scenario: "MissingEnumerationField",
-			Name:     "SriovEnable",
-			Value:    intstr.FromString("Disabled"),
-			Expected: true,
-		},
-		{
-			Scenario: "UnknownSettingFail",
-			Name:     "IceCream",
-			Value:    intstr.FromString("Vanilla"),
-			Expected: false,
-		},
-	} {
-		t.Run(tc.Scenario, func(t *testing.T) {
-			actual := fwSchema.CheckSettingIsValid(tc.Name, tc.Value, fwSchema.Spec.Schema)
-			assert.Equal(t, tc.Expected, actual)
-		})
-	}
-}
diff --git a/controllers/metal3.io/baremetalhost_controller_test.go b/controllers/metal3.io/baremetalhost_controller_test.go
index 09a9ae07..6d0c2e06 100644
--- a/controllers/metal3.io/baremetalhost_controller_test.go
+++ b/controllers/metal3.io/baremetalhost_controller_test.go
@@ -80,6 +80,18 @@ func newDefaultNamedHost(name string, t *testing.T) *metal3v1alpha1.BareMetalHos
 			Address:         "ipmi://192.168.122.1:6233",
 			CredentialsName: defaultSecretName,
 		},
+		HardwareProfile: "libvirt",
+		RootDeviceHints: &metal3v1alpha1.RootDeviceHints{
+			DeviceName:         "userd_devicename",
+			HCTL:               "1:2:3:4",
+			Model:              "userd_model",
+			Vendor:             "userd_vendor",
+			SerialNumber:       "userd_serial",
+			MinSizeGigabytes:   40,
+			WWN:                "userd_wwn",
+			WWNWithExtension:   "userd_with_extension",
+			WWNVendorExtension: "userd_vendor_extension",
+		},
 	}
 	t.Logf("newNamedHost(%s)", name)
 	return newHost(name, spec)
@@ -1747,16 +1759,6 @@ func TestUpdateRAID(t *testing.T) {
 				WWNWithExtension:   "userd_with_extension",
 				WWNVendorExtension: "userd_vendor_extension",
 			},
-			RAID: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Name: "root",
-					},
-					{
-						Name: "v1",
-					},
-				},
-			},
 		},
 		Status: metal3v1alpha1.BareMetalHostStatus{
 			Provisioning: metal3v1alpha1.ProvisionStatus{
@@ -1774,119 +1776,39 @@ func TestUpdateRAID(t *testing.T) {
 			},
 		},
 	}
+
 	cases := []struct {
-		name       string
-		specRAID   *metal3v1alpha1.RAIDConfig
-		statusRAID *metal3v1alpha1.RAIDConfig
-		dirty      bool
-		expected   *metal3v1alpha1.RAIDConfig
+		name     string
+		raid     *metal3v1alpha1.RAIDConfig
+		dirty    bool
+		expected *metal3v1alpha1.RAIDConfig
 	}{
 		{
-			name:       "not configured, not saved",
-			specRAID:   nil,
-			statusRAID: nil,
-			dirty:      false,
-		},
-		{
-			name:       "not configured, not saved",
-			specRAID:   &metal3v1alpha1.RAIDConfig{},
-			statusRAID: &metal3v1alpha1.RAIDConfig{},
-			dirty:      false,
-			expected:   &metal3v1alpha1.RAIDConfig{},
-		},
-		{
-			name:       "not configured, not saved",
-			specRAID:   &metal3v1alpha1.RAIDConfig{},
-			statusRAID: nil,
-			dirty:      true,
-			expected:   &metal3v1alpha1.RAIDConfig{},
-		},
-		{
-			name:       "not configured, not saved",
-			specRAID:   nil,
-			statusRAID: &metal3v1alpha1.RAIDConfig{},
-			dirty:      true,
-			expected:   nil,
-		},
-		{
-			name: "HardwareRAIDVolumes configured, not saved",
-			specRAID: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-			statusRAID: nil,
-			dirty:      true,
-			expected: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-		},
-		{
-			name: "SoftwareRAIDVolumes configured, not saved",
-			specRAID: &metal3v1alpha1.RAIDConfig{
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-			statusRAID: nil,
-			dirty:      true,
+			name:  "keep current hardware RAID, clear current software RAID",
+			raid:  nil,
+			dirty: true,
 			expected: &metal3v1alpha1.RAIDConfig{
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
+				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{},
 			},
 		},
 		{
-			name: "both configured, not saved",
-			specRAID: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-			statusRAID: nil,
-			dirty:      true,
+			name:  "keep current hardware RAID, clear current software RAID",
+			raid:  &metal3v1alpha1.RAIDConfig{},
+			dirty: false,
 			expected: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
+				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{},
 			},
 		},
 		{
-			name: "HardwareRAIDVolumes configured, HardwareRAIDVolumes saved",
-			specRAID: &metal3v1alpha1.RAIDConfig{
+			name: "Configure hardwareRAIDVolumes",
+			raid: &metal3v1alpha1.RAIDConfig{
 				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
 					{
 						Level: "1",
 					},
 				},
 			},
-			statusRAID: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-			dirty: false,
+			dirty: true,
 			expected: &metal3v1alpha1.RAIDConfig{
 				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
 					{
@@ -1896,15 +1818,13 @@ func TestUpdateRAID(t *testing.T) {
 			},
 		},
 		{
-			name: "HardwareRAIDVolumes configured, SoftwareRAIDVolumes saved",
-			specRAID: &metal3v1alpha1.RAIDConfig{
+			name: "Configure hardwareRAIDVolumes",
+			raid: &metal3v1alpha1.RAIDConfig{
 				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
 					{
 						Level: "1",
 					},
 				},
-			},
-			statusRAID: &metal3v1alpha1.RAIDConfig{
 				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
 					{
 						Level: "1",
@@ -1918,103 +1838,38 @@ func TestUpdateRAID(t *testing.T) {
 						Level: "1",
 					},
 				},
-			},
-		},
-		{
-			name: "HardwareRAIDVolumes configured, both saved",
-			specRAID: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-			statusRAID: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
 				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
 					{
 						Level: "1",
 					},
 				},
 			},
-			dirty: false,
-			expected: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
 		},
 		{
-			name: "SoftwareRAIDVolumes configured, HardwareRAIDVolumes saved",
-			specRAID: &metal3v1alpha1.RAIDConfig{
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-			statusRAID: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
+			name: "Clear hardwareRAIDVolumes",
+			raid: &metal3v1alpha1.RAIDConfig{
+				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{},
 			},
 			dirty: true,
 			expected: &metal3v1alpha1.RAIDConfig{
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
+				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{},
 			},
 		},
 		{
-			name: "SoftwareRAIDVolumes configured, SoftwareRAIDVolumes saved",
-			specRAID: &metal3v1alpha1.RAIDConfig{
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-			statusRAID: &metal3v1alpha1.RAIDConfig{
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
+			name: "Clear hardwareRAIDVolumes",
+			raid: &metal3v1alpha1.RAIDConfig{
+				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{},
+				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{},
 			},
-			dirty: false,
+			dirty: true,
 			expected: &metal3v1alpha1.RAIDConfig{
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
+				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{},
+				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{},
 			},
 		},
 		{
-			name: "SoftwareRAIDVolumes configured, both saved",
-			specRAID: &metal3v1alpha1.RAIDConfig{
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-			statusRAID: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
+			name: "Configure SoftwareRAIDVolumes",
+			raid: &metal3v1alpha1.RAIDConfig{
 				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
 					{
 						Level: "1",
@@ -2031,108 +1886,24 @@ func TestUpdateRAID(t *testing.T) {
 			},
 		},
 		{
-			name: "both configured, HardwareRAIDVolumes saved",
-			specRAID: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-			statusRAID: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-			dirty: false,
-			expected: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-		},
-		{
-			name: "both configured, SoftwareRAIDVolumes saved",
-			specRAID: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-			statusRAID: &metal3v1alpha1.RAIDConfig{
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
+			name: "Clear softwareRAIDVolumes",
+			raid: &metal3v1alpha1.RAIDConfig{
+				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{},
 			},
 			dirty: true,
 			expected: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-		},
-		{
-			name: "both configured, both saved",
-			specRAID: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-			statusRAID: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
-			},
-			dirty: false,
-			expected: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
-					{
-						Level: "1",
-					},
-				},
+				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{},
 			},
 		},
 	}
 	for _, c := range cases {
 		t.Run(c.name, func(t *testing.T) {
-			host.Spec.RAID = c.specRAID
-			host.Status.Provisioning.RAID = c.statusRAID
+			host.Spec.RAID = c.raid
 			dirty, _ := saveHostProvisioningSettings(&host)
 			assert.Equal(t, c.dirty, dirty)
 			assert.Equal(t, c.expected, host.Status.Provisioning.RAID)
+			dirty, _, _ = getHostProvisioningSettings(&host)
+			assert.Equal(t, false, dirty)
 		})
 	}
 }
diff --git a/controllers/metal3.io/host_state_machine_test.go b/controllers/metal3.io/host_state_machine_test.go
index 4982fbd7..b76118ea 100644
--- a/controllers/metal3.io/host_state_machine_test.go
+++ b/controllers/metal3.io/host_state_machine_test.go
@@ -1003,6 +1003,9 @@ func host(state metal3v1alpha1.ProvisioningState) *hostBuilder {
 					Image: v1alpha1.Image{
 						URL: "", //needs provisioning
 					},
+					RAID: &metal3v1alpha1.RAIDConfig{
+						SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{},
+					},
 				},
 				GoodCredentials:   creds,
 				TriedCredentials:  creds,
@@ -1172,11 +1175,11 @@ func (m *mockProvisioner) Detach() (result provisioner.Result, err error) {
 	return res, err
 }
 
-func (m *mockProvisioner) PowerOn(force bool) (result provisioner.Result, err error) {
+func (m *mockProvisioner) PowerOn() (result provisioner.Result, err error) {
 	return m.getNextResultByMethod("PowerOn"), err
 }
 
-func (m *mockProvisioner) PowerOff(rebootMode metal3v1alpha1.RebootMode, force bool) (result provisioner.Result, err error) {
+func (m *mockProvisioner) PowerOff(rebootMode metal3v1alpha1.RebootMode) (result provisioner.Result, err error) {
 	return m.getNextResultByMethod("PowerOff"), err
 }
 
diff --git a/pkg/provisioner/ironic/power_test.go b/pkg/provisioner/ironic/power_test.go
index 0a899d5f..2db33570 100644
--- a/pkg/provisioner/ironic/power_test.go
+++ b/pkg/provisioner/ironic/power_test.go
@@ -20,13 +20,11 @@ func TestPowerOn(t *testing.T) {
 	nodeUUID := "33ce8659-7400-4c68-9535-d10766f07a58"
 	cases := []struct {
 		name   string
-		force  bool
 		ironic *testserver.IronicMock
 
 		expectedDirty        bool
 		expectedError        bool
 		expectedRequestAfter int
-		expectedErrorResult  bool
 	}{
 		{
 			name: "node-already-power-on",
@@ -77,30 +75,6 @@ func TestPowerOn(t *testing.T) {
 			expectedRequestAfter: 10,
 			expectedDirty:        true,
 		},
-		{
-			name: "power-on with LastError",
-			ironic: testserver.NewIronic(t).Ready().Node(nodes.Node{
-				PowerState:       powerOff,
-				TargetPowerState: powerOff,
-				UUID:             nodeUUID,
-				LastError:        "power on failed",
-			}),
-			expectedRequestAfter: 0,
-			expectedDirty:        false,
-			expectedErrorResult:  true,
-		},
-		{
-			name:  "power-on with LastError",
-			force: true,
-			ironic: testserver.NewIronic(t).Ready().Node(nodes.Node{
-				PowerState:       powerOff,
-				TargetPowerState: powerOff,
-				UUID:             nodeUUID,
-				LastError:        "power on failed",
-			}),
-			expectedError:       true,
-			expectedErrorResult: false,
-		},
 	}
 
 	for _, tc := range cases {
@@ -127,7 +101,7 @@ func TestPowerOn(t *testing.T) {
 				t.Fatalf("could not create provisioner: %s", err)
 			}
 
-			result, err := prov.PowerOn(tc.force)
+			result, err := prov.PowerOn()
 
 			assert.Equal(t, tc.expectedDirty, result.Dirty)
 			assert.Equal(t, time.Second*time.Duration(tc.expectedRequestAfter), result.RequeueAfter)
@@ -136,9 +110,6 @@ func TestPowerOn(t *testing.T) {
 			} else {
 				assert.Error(t, err)
 			}
-			if tc.expectedErrorResult {
-				assert.Contains(t, result.ErrorMessage, "PowerOn operation failed")
-			}
 		})
 	}
 }
@@ -149,12 +120,10 @@ func TestPowerOff(t *testing.T) {
 	cases := []struct {
 		name   string
 		ironic *testserver.IronicMock
-		force  bool
 
 		expectedDirty        bool
 		expectedError        bool
 		expectedRequestAfter int
-		expectedErrorResult  bool
 		rebootMode           metal3v1alpha1.RebootMode
 	}{
 		{
@@ -218,44 +187,6 @@ func TestPowerOff(t *testing.T) {
 			expectedRequestAfter: 10,
 			expectedDirty:        true,
 		},
-		{
-			name: "power-off soft with force",
-			ironic: testserver.NewIronic(t).WithDefaultResponses().Node(nodes.Node{
-				PowerState:           powerOn,
-				TargetPowerState:     powerOn,
-				TargetProvisionState: "",
-				UUID:                 nodeUUID,
-			}),
-			rebootMode:    metal3v1alpha1.RebootModeSoft,
-			force:         true,
-			expectedDirty: true,
-		},
-		{
-			name: "power-off hard with LastError",
-			ironic: testserver.NewIronic(t).WithDefaultResponses().Node(nodes.Node{
-				PowerState:           powerOn,
-				TargetPowerState:     powerOn,
-				TargetProvisionState: "",
-				UUID:                 nodeUUID,
-				LastError:            "hard power off failed",
-			}),
-			rebootMode:          metal3v1alpha1.RebootModeHard,
-			expectedDirty:       false,
-			expectedErrorResult: true,
-		},
-		{
-			name: "power-off hard with force",
-			ironic: testserver.NewIronic(t).WithDefaultResponses().Node(nodes.Node{
-				PowerState:           powerOn,
-				TargetPowerState:     powerOn,
-				TargetProvisionState: "",
-				UUID:                 nodeUUID,
-				LastError:            "hard power off failed",
-			}),
-			rebootMode:    metal3v1alpha1.RebootModeHard,
-			force:         true,
-			expectedDirty: true,
-		},
 	}
 
 	for _, tc := range cases {
@@ -283,7 +214,7 @@ func TestPowerOff(t *testing.T) {
 			}
 
 			// We pass the RebootMode type here to define the reboot action
-			result, err := prov.PowerOff(tc.rebootMode, tc.force)
+			result, err := prov.PowerOff(tc.rebootMode)
 
 			assert.Equal(t, tc.expectedDirty, result.Dirty)
 			assert.Equal(t, time.Second*time.Duration(tc.expectedRequestAfter), result.RequeueAfter)
@@ -292,10 +223,6 @@ func TestPowerOff(t *testing.T) {
 			} else {
 				assert.Error(t, err)
 			}
-			if tc.expectedErrorResult {
-				assert.Contains(t, result.ErrorMessage, "hard power off failed")
-			}
-
 		})
 	}
 }
diff --git a/pkg/provisioner/ironic/prepare_test.go b/pkg/provisioner/ironic/prepare_test.go
index 78d6b05c..c9834e1f 100644
--- a/pkg/provisioner/ironic/prepare_test.go
+++ b/pkg/provisioner/ironic/prepare_test.go
@@ -181,7 +181,7 @@ func TestPrepare(t *testing.T) {
 			prepData := provisioner.PrepareData{}
 			if tc.existRaidConfig {
 				host.Spec.BMC.Address = "raid-test://test.bmc/"
-				prepData.RAIDConfig = &metal3v1alpha1.RAIDConfig{
+				prepData.TargetRAIDConfig = &metal3v1alpha1.RAIDConfig{
 					HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
 						{
 							Name:  "root",
diff --git a/pkg/provisioner/ironic/raid_test.go b/pkg/provisioner/ironic/raid_test.go
index 0b07a4e1..c73651a2 100644
--- a/pkg/provisioner/ironic/raid_test.go
+++ b/pkg/provisioner/ironic/raid_test.go
@@ -191,30 +191,26 @@ func TestBuildTargetRAIDCfg(t *testing.T) {
 func TestBuildRAIDCleanSteps(t *testing.T) {
 	cases := []struct {
 		name          string
-		raid          *metal3v1alpha1.RAIDConfig
+		raidInterface string
+		target        *metal3v1alpha1.RAIDConfig
+		actual        *metal3v1alpha1.RAIDConfig
 		expected      []nodes.CleanStep
-		expectedError string
+		expectedError bool
 	}{
 		{
-			name: "hardware raid",
-			raid: &metal3v1alpha1.RAIDConfig{
+			name:          "configure hardware RAID",
+			raidInterface: "irmc",
+			target: &metal3v1alpha1.RAIDConfig{
 				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
 					{
 						Name:  "root",
 						Level: "1",
 					},
-					{
-						Name:  "v1",
-						Level: "1",
-					},
 				},
 				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
 					{
 						Level: "1",
 					},
-					{
-						Level: "1",
-					},
 				},
 			},
 			expected: []nodes.CleanStep{
@@ -229,12 +225,56 @@ func TestBuildRAIDCleanSteps(t *testing.T) {
 			},
 		},
 		{
-			name: "software raid",
-			raid: &metal3v1alpha1.RAIDConfig{
+			name:          "have same hardware RAID",
+			raidInterface: "irmc",
+			target: &metal3v1alpha1.RAIDConfig{
+				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
+					{
+						Name:  "root",
+						Level: "1",
+					},
+				},
 				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
 					{
 						Level: "1",
 					},
+				},
+			},
+			actual: &metal3v1alpha1.RAIDConfig{
+				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
+					{
+						Name:  "root",
+						Level: "1",
+					},
+				},
+			},
+		},
+		{
+			name:          "clear hardware RAID",
+			raidInterface: "irmc",
+			target: &metal3v1alpha1.RAIDConfig{
+				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{},
+			},
+			actual: &metal3v1alpha1.RAIDConfig{
+				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
+					{
+						Name:  "root",
+						Level: "1",
+					},
+				},
+			},
+			expected: []nodes.CleanStep{
+				{
+					Interface: "raid",
+					Step:      "delete_configuration",
+				},
+			},
+		},
+		{
+			name:          "configure software RAID",
+			raidInterface: "agent",
+			target: &metal3v1alpha1.RAIDConfig{
+				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
 					{
 						Level: "1",
 					},
@@ -256,49 +296,153 @@ func TestBuildRAIDCleanSteps(t *testing.T) {
 			},
 		},
 		{
-			name: "raid is nil",
-			raid: nil,
-			expected: []nodes.CleanStep{
-				{
-					Interface: "raid",
-					Step:      "delete_configuration",
+			name:          "have same software RAID",
+			raidInterface: "agent",
+			target: &metal3v1alpha1.RAIDConfig{
+				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
+					{
+						Level: "1",
+					},
 				},
 			},
-		},
-		{
-			name: "volumes is nil",
-			raid: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: nil,
-				SoftwareRAIDVolumes: nil,
-			},
-			expected: []nodes.CleanStep{
-				{
-					Interface: "raid",
-					Step:      "delete_configuration",
+			actual: &metal3v1alpha1.RAIDConfig{
+				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
+					{
+						Level: "1",
+					},
 				},
 			},
 		},
 		{
-			name: "volumes is empty",
-			raid: &metal3v1alpha1.RAIDConfig{
-				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{},
+			name:          "clear software RAID",
+			raidInterface: "agent",
+			target: &metal3v1alpha1.RAIDConfig{
 				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{},
 			},
+			actual: &metal3v1alpha1.RAIDConfig{
+				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
+					{
+						Level: "1",
+					},
+				},
+			},
 			expected: []nodes.CleanStep{
 				{
 					Interface: "raid",
 					Step:      "delete_configuration",
 				},
+				{
+					Interface: "deploy",
+					Step:      "erase_devices_metadata",
+				},
 			},
 		},
 	}
 
 	for _, c := range cases {
 		t.Run(c.name, func(t *testing.T) {
-			step := BuildRAIDCleanSteps(c.raid)
+			step, err := BuildRAIDCleanSteps(c.raidInterface, c.target, c.actual)
 			if !reflect.DeepEqual(c.expected, step) {
 				t.Errorf("expected: %v, got: %v", c.expected, step)
 			}
+			if (err != nil) != c.expectedError {
+				t.Errorf("got unexpected error: %v", err)
+			}
+		})
+	}
+}
+
+func TestCheckRAIDConfigure(t *testing.T) {
+	cases := []struct {
+		raidInterface string
+		RAID          *metal3v1alpha1.RAIDConfig
+		expectedError bool
+	}{
+		{
+			raidInterface: "no-raid",
+		},
+		{
+			raidInterface: "no-raid",
+			RAID:          &metal3v1alpha1.RAIDConfig{},
+		},
+		{
+			raidInterface: "no-raid",
+			RAID: &metal3v1alpha1.RAIDConfig{
+				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
+					{
+						Name:  "root",
+						Level: "1",
+					},
+				},
+			},
+			expectedError: true,
+		},
+		{
+			raidInterface: "agent",
+		},
+		{
+			raidInterface: "agent",
+			RAID:          &metal3v1alpha1.RAIDConfig{},
+		},
+		{
+			raidInterface: "agent",
+			RAID: &metal3v1alpha1.RAIDConfig{
+				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
+					{
+						Name:  "root",
+						Level: "1",
+					},
+				},
+			},
+			expectedError: true,
+		},
+		{
+			raidInterface: "agent",
+			RAID: &metal3v1alpha1.RAIDConfig{
+				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
+					{
+						Level: "1",
+					},
+				},
+			},
+		},
+		{
+			raidInterface: "hardware",
+		},
+		{
+			raidInterface: "hardware",
+			RAID:          &metal3v1alpha1.RAIDConfig{},
+		},
+		{
+			raidInterface: "hardware",
+			RAID: &metal3v1alpha1.RAIDConfig{
+				HardwareRAIDVolumes: []metal3v1alpha1.HardwareRAIDVolume{
+					{
+						Name:  "root",
+						Level: "1",
+					},
+				},
+			},
+		},
+		{
+			raidInterface: "hardware",
+			RAID: &metal3v1alpha1.RAIDConfig{
+				SoftwareRAIDVolumes: []metal3v1alpha1.SoftwareRAIDVolume{
+					{
+						Level: "1",
+					},
+				},
+			},
+			expectedError: true,
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.raidInterface, func(t *testing.T) {
+			err := checkRAIDConfigure(c.raidInterface, c.RAID)
+			if (err != nil) != c.expectedError {
+				t.Errorf("Got unexpected error: %v", err)
+			}
 		})
 	}
 }