Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion apis/metal3.io/v1alpha1/baremetalhost_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ const (
// InspectionError is an error condition occurring when an attempt to
// obtain hardware details from the Host fails.
InspectionError ErrorType = "inspection error"
// PreparationError is an error condition occurring when do
// cleaning steps failed.
PreparationError ErrorType = "preparation error"
// ProvisioningError is an error condition occuring when the controller
// fails to provision or deprovision the Host.
ProvisioningError ErrorType = "provisioning error"
Expand Down Expand Up @@ -162,6 +165,9 @@ const (
// against known hardware profiles
StateMatchProfile ProvisioningState = "match profile"

// StatePreparing means we are removing existing configuration and set new configuration to the host
StatePreparing ProvisioningState = "preparing"

// StateReady means the host can be consumed
StateReady ProvisioningState = "ready"

Expand Down Expand Up @@ -530,7 +536,7 @@ type BareMetalHostStatus struct {

// ErrorType indicates the type of failure encountered when the
// OperationalStatus is OperationalStatusError
// +kubebuilder:validation:Enum=provisioned registration error;registration error;inspection error;provisioning error;power management error
// +kubebuilder:validation:Enum=provisioned registration error;registration error;inspection error;preparation error;provisioning error;power management error
ErrorType ErrorType `json:"errorType,omitempty"`

// LastUpdated identifies when this status was last observed.
Expand Down
1 change: 1 addition & 0 deletions config/crd/bases/metal3.io_baremetalhosts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ spec:
- provisioned registration error
- registration error
- inspection error
- preparation error
- provisioning error
- power management error
type: string
Expand Down
1 change: 1 addition & 0 deletions config/render/capm3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ spec:
- provisioned registration error
- registration error
- inspection error
- preparation error
- provisioning error
- power management error
type: string
Expand Down
45 changes: 44 additions & 1 deletion controllers/metal3.io/baremetalhost_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,9 @@ func (r *BareMetalHostReconciler) registerHost(prov provisioner.Provisioner, inf
if provID != "" && info.host.Status.Provisioning.ID != provID {
info.log.Info("setting provisioning id", "ID", provID)
info.host.Status.Provisioning.ID = provID
if info.host.Status.Provisioning.State == metal3v1alpha1.StatePreparing {
clearHostProvisioningSettings(info.host)
}
dirty = true
}

Expand Down Expand Up @@ -564,6 +567,44 @@ func (r *BareMetalHostReconciler) actionMatchProfile(prov provisioner.Provisione
return actionComplete{}
}

func (r *BareMetalHostReconciler) actionPreparing(prov provisioner.Provisioner, info *reconcileInfo) actionResult {
info.log.Info("preparing")

// Save provisioning settings.
provisioningSettings := info.host.Status.Provisioning.DeepCopy()
dirty, err := saveHostProvisioningSettings(info.host)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Hellcatlk, just to clarify, IIUC @zaneb 's comment then saveHostProvisioningSettings will contains the necessary logic to trigger the manual cleaning in the Prepare. Right now in this method just the Provisioning.RootDeviceHints are checked. If it is expected to be enriched in future for the related RAID section, then probably I'd find more clear to add comment for that within such method

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct. But note that this function already existed, and this is the right thing to do regardless of whether RAID is added.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I was thinking to something more like the // TODO: in the buildManualCleaningSteps

if err != nil {
return actionError{errors.Wrap(err, "Could not save the host provisioning settings")}
}

// Do prepare(manual clean).
provResult, started, err := prov.Prepare(dirty)
if err != nil {
return actionError{errors.Wrap(err, "error preparing host")}
}

if provResult.ErrorMessage != "" {
info.log.Info("handling cleaning error in controller")
Comment thread
zaneb marked this conversation as resolved.
clearHostProvisioningSettings(info.host)
return recordActionFailure(info, metal3v1alpha1.PreparationError, provResult.ErrorMessage)
}

if provResult.Dirty {
result := actionContinue{provResult.RequeueAfter}
if clearError(info.host) || (dirty && started) {
// If clearError return true, but started is false, restore provisioningSettings.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is not really clear to me, can you please elaborate it a little bit?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just describing how the if statement below can come to be true.

if dirty && !started {
info.host.Status.Provisioning = *provisioningSettings
Comment thread
zaneb marked this conversation as resolved.
Comment thread
zaneb marked this conversation as resolved.
}
return actionUpdate{result}
}
return result
}

Comment thread
zaneb marked this conversation as resolved.
clearError(info.host)
return actionComplete{}
}

// Start/continue provisioning if we need to.
func (r *BareMetalHostReconciler) actionProvisioning(prov provisioner.Provisioner, info *reconcileInfo) actionResult {
hostConf := &hostConfigData{
Expand Down Expand Up @@ -796,7 +837,9 @@ func (r *BareMetalHostReconciler) actionManageReady(prov provisioner.Provisioner
return actionError{errors.Wrap(err, "Could not save the host provisioning settings")}
}
if dirty {
info.log.Info("updating host provisioning settings")
info.log.Info("Host provisioning settings have been updated, go back to Preparing state")
clearHostProvisioningSettings(info.host)
return actionUpdate{}
Comment thread
zaneb marked this conversation as resolved.
}
clearError(info.host)
return actionComplete{}
Expand Down
4 changes: 2 additions & 2 deletions controllers/metal3.io/baremetalhost_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -923,12 +923,12 @@ func TestExternallyProvisionedTransitions(t *testing.T) {
waitForProvisioningState(t, r, host, metal3v1alpha1.StateInspecting)
})

t.Run("ready to externally provisioned", func(t *testing.T) {
t.Run("preparing to externally provisioned", func(t *testing.T) {
host := newDefaultHost(t)
host.Spec.Online = true
r := newTestReconciler(host)

waitForProvisioningState(t, r, host, metal3v1alpha1.StateReady)
waitForProvisioningState(t, r, host, metal3v1alpha1.StatePreparing)

host.Spec.ExternallyProvisioned = true
err := r.Update(goctx.TODO(), host)
Expand Down
42 changes: 42 additions & 0 deletions controllers/metal3.io/demo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,48 @@ func TestDemoInspecting(t *testing.T) {
)
}

func TestDemoPreparing(t *testing.T) {
host := newDefaultNamedHost(demo.PreparingHost, t)
host.Spec.Image = &metal3v1alpha1.Image{
URL: "a-url",
Checksum: "a-checksum",
}
host.Spec.Online = true
r := newDemoReconciler(host)

tryReconcile(t, r, host,
func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
t.Logf("Status: %q State: %q ErrorMessage: %q",
host.OperationalStatus(),
host.Status.Provisioning.State,
host.Status.ErrorMessage,
)
return host.Status.Provisioning.State == metal3v1alpha1.StatePreparing
},
)
}

func TestDemoPreparingError(t *testing.T) {
host := newDefaultNamedHost(demo.PreparingErrorHost, t)
host.Spec.Image = &metal3v1alpha1.Image{
URL: "a-url",
Checksum: "a-checksum",
}
host.Spec.Online = true
r := newDemoReconciler(host)

tryReconcile(t, r, host,
func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
t.Logf("Status: %q State: %q ErrorMessage: %q",
host.OperationalStatus(),
host.Status.Provisioning.State,
host.Status.ErrorMessage,
)
return host.Status.Provisioning.State == metal3v1alpha1.StatePreparing
},
)
}

// TestDemoReady tests that a host with the right name reports
// that it is ready to be provisioned
func TestDemoReady(t *testing.T) {
Expand Down
19 changes: 16 additions & 3 deletions controllers/metal3.io/host_state_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func (hsm *hostStateMachine) handlers() map[metal3v1alpha1.ProvisioningState]sta
metal3v1alpha1.StateExternallyProvisioned: hsm.handleExternallyProvisioned,
metal3v1alpha1.StateMatchProfile: hsm.handleMatchProfile,
metal3v1alpha1.StateAvailable: hsm.handleReady,
metal3v1alpha1.StatePreparing: hsm.handlePreparing,
metal3v1alpha1.StateReady: hsm.handleReady,
metal3v1alpha1.StateProvisioning: hsm.handleProvisioning,
metal3v1alpha1.StateProvisioned: hsm.handleProvisioned,
Expand Down Expand Up @@ -311,7 +312,7 @@ func (hsm *hostStateMachine) handleInspecting(info *reconcileInfo) actionResult
func (hsm *hostStateMachine) handleMatchProfile(info *reconcileInfo) actionResult {
actResult := hsm.Reconciler.actionMatchProfile(hsm.Provisioner, info)
if _, complete := actResult.(actionComplete); complete {
hsm.NextState = metal3v1alpha1.StateReady
hsm.NextState = metal3v1alpha1.StatePreparing
hsm.Host.Status.ErrorCount = 0
}
return actResult
Expand All @@ -329,20 +330,32 @@ func (hsm *hostStateMachine) handleExternallyProvisioned(info *reconcileInfo) ac
case hsm.Host.NeedsHardwareProfile():
hsm.NextState = metal3v1alpha1.StateMatchProfile
default:
hsm.NextState = metal3v1alpha1.StateReady
hsm.NextState = metal3v1alpha1.StatePreparing
}
return actionComplete{}
}

func (hsm *hostStateMachine) handlePreparing(info *reconcileInfo) actionResult {
actResult := hsm.Reconciler.actionPreparing(hsm.Provisioner, info)
if _, complete := actResult.(actionComplete); complete {
hsm.Host.Status.ErrorCount = 0
hsm.NextState = metal3v1alpha1.StateReady
}
Comment thread
zaneb marked this conversation as resolved.
return actResult
}

func (hsm *hostStateMachine) handleReady(info *reconcileInfo) actionResult {
if hsm.Host.Spec.ExternallyProvisioned {
hsm.NextState = metal3v1alpha1.StateExternallyProvisioned
clearHostProvisioningSettings(info.host)
return actionComplete{}
}

// ErrorCount is cleared when appropriate inside actionManageReady
actResult := hsm.Reconciler.actionManageReady(hsm.Provisioner, info)
if _, complete := actResult.(actionComplete); complete {
if _, update := actResult.(actionUpdate); update {
hsm.NextState = metal3v1alpha1.StatePreparing
} else if _, complete := actResult.(actionComplete); complete {
hsm.NextState = metal3v1alpha1.StateProvisioning
}
return actResult
Expand Down
20 changes: 17 additions & 3 deletions controllers/metal3.io/host_state_machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestProvisioningCapacity(t *testing.T) {
},
{
Scenario: "transition-to-provisioning-delayed",
Host: host(metal3v1alpha1.StateReady).build(),
Host: host(metal3v1alpha1.StateReady).SaveHostProvisioningSettings().build(),
HasProvisioningCapacity: false,

ExpectedProvisioningState: metal3v1alpha1.StateReady,
Expand All @@ -61,7 +61,7 @@ func TestProvisioningCapacity(t *testing.T) {
},
{
Scenario: "transition-to-provisioning-ok",
Host: host(metal3v1alpha1.StateReady).build(),
Host: host(metal3v1alpha1.StateReady).SaveHostProvisioningSettings().build(),
HasProvisioningCapacity: true,

ExpectedProvisioningState: metal3v1alpha1.StateProvisioning,
Expand Down Expand Up @@ -379,8 +379,13 @@ func TestErrorCountClearedOnStateTransition(t *testing.T) {
TargetState: metal3v1alpha1.StateMatchProfile,
},
{
Scenario: "matchprofile-to-ready",
Scenario: "matchprofile-to-preparing",
Host: host(metal3v1alpha1.StateMatchProfile).build(),
TargetState: metal3v1alpha1.StatePreparing,
},
{
Scenario: "preparing-to-ready",
Host: host(metal3v1alpha1.StatePreparing).build(),
TargetState: metal3v1alpha1.StateReady,
},
{
Expand Down Expand Up @@ -504,6 +509,11 @@ func (hb *hostBuilder) build() *metal3v1alpha1.BareMetalHost {
return &hb.BareMetalHost
}

func (hb *hostBuilder) SaveHostProvisioningSettings() *hostBuilder {
saveHostProvisioningSettings(&hb.BareMetalHost)
return hb
}

func (hb *hostBuilder) SetTriedCredentials() *hostBuilder {
hb.Status.TriedCredentials = hb.Status.GoodCredentials
return hb
Expand Down Expand Up @@ -617,6 +627,10 @@ func (m *mockProvisioner) UpdateHardwareState() (hwState provisioner.HardwareSta
return
}

func (m *mockProvisioner) Prepare(unprepared bool) (result provisioner.Result, started bool, err error) {
return m.getNextResultByMethod("Prepare"), m.nextResults["Prepare"].Dirty, err
}

func (m *mockProvisioner) Adopt(force bool) (result provisioner.Result, err error) {
return m.getNextResultByMethod("Adopt"), err
}
Expand Down
14 changes: 10 additions & 4 deletions docs/BaremetalHost_ProvisioningState.dot
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,32 @@ digraph BaremetalHost {

ExternallyProvisioned -> Inspecting [label="!externallyProvisioned && NeedsHardwareInspection()"]
ExternallyProvisioned -> MatchProfile [label="!externallyProvisioned && NeedsHardwareProfile()"]
ExternallyProvisioned -> Ready [label="!externallyProvisioned"]
ExternallyProvisioned -> Preparing [label="!externallyProvisioned"]
Ready -> ExternallyProvisioned [label="externallyProvisioned"]

Inspecting -> MatchProfile [label="done"]
Inspecting -> Deleting3 [label="!DeletionTimestamp.IsZero()"]

Deleting3 [shape=point]

MatchProfile -> Ready [label="done"]
MatchProfile -> Preparing [label="done"]
MatchProfile -> Deleting4 [label="!DeletionTimestamp.IsZero()"]

Deleting4 [shape=point]

Deleting5 [shape=point]

Preparing -> Ready [label="done"]
Preparing -> Deleting6 [label="!DeletionTimestamp.IsZero()"]
Comment thread
zaneb marked this conversation as resolved.

Deleting6 [shape=point]

Ready [shape=doublecircle]
Ready -> Provisioning [label="NeedsProvisioning()"]
Ready -> Deleting6 [label="!DeletionTimestamp.IsZero()"]
Ready -> Preparing [label="saveHostProvisioningSettings()"]
Ready -> Deleting7 [label="!DeletionTimestamp.IsZero()"]

Deleting6 [shape=point]
Deleting7 [shape=point]

Provisioning -> Provisioned [label=done]
Provisioning -> Deprovisioning [label="failed || !DeletionTimestamp.IsZero()"]
Expand Down
Binary file modified docs/BaremetalHost_ProvisioningState.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions docs/baremetalhost-states.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ will stay in the Inspecting state until this process is completed.
A host in the Match Profile state is being matched against a hardware
profile.

## Preparing

When setting up RAID, BIOS and other similar configurations,
the host will be in Preparing state. For ironic provisioner,
we build and set up manual clean steps in Preparing state.

## Ready

A host in the Ready state is available to be provisioned.
Expand Down
29 changes: 29 additions & 0 deletions pkg/provisioner/demo/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ const (
// InspectingHost is a host that is having its hardware scanned.
InspectingHost string = "demo-inspecting"

// PreparingErrorHost is a host that started preparing but failed.
PreparingErrorHost string = "demo-preparing-error"

// PreparingHost is a host that is in the middle of preparing.
PreparingHost string = "demo-preparing"

// ValidationErrorHost is a host that started provisioning but
// failed validation.
ValidationErrorHost string = "demo-validation-error"
Expand Down Expand Up @@ -184,6 +190,29 @@ func (p *demoProvisioner) UpdateHardwareState() (hwState provisioner.HardwareSta
return
}

// Prepare remove existing configuration and set new configuration
func (p *demoProvisioner) Prepare(unprepared bool) (result provisioner.Result, started bool, err error) {
hostName := p.host.ObjectMeta.Name
p.log.Info("provisioning image to host", "state", p.host.Status.Provisioning.State)

switch hostName {

case PreparingErrorHost:
p.log.Info("preparing error host")
result.ErrorMessage = "preparing failed"

case PreparingHost:
p.log.Info("preparing host")
result.Dirty = true
result.RequeueAfter = time.Second * 5

default:
p.log.Info("finished preparing")
}

return result, false, nil
}

// Adopt allows an externally-provisioned server to be adopted.
func (p *demoProvisioner) Adopt(force bool) (result provisioner.Result, err error) {
p.log.Info("adopting host")
Expand Down
5 changes: 5 additions & 0 deletions pkg/provisioner/empty/empty.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ func (p *emptyProvisioner) Adopt(force bool) (provisioner.Result, error) {
return provisioner.Result{}, nil
}

// Prepare remove existing configuration and set new configuration
func (p *emptyProvisioner) Prepare(unprepared bool) (result provisioner.Result, started bool, err error) {
return provisioner.Result{}, false, nil
}

// Provision writes the image from the host spec to the host. It may
// be called multiple times, and should return true for its dirty flag
// until the deprovisioning operation is completed.
Expand Down
Loading