Skip to content

Commit 86ae602

Browse files
committed
Allow forced detachment of a host from Ironic
Adds Force as DetachedAnnotationArgument to API. Detach in all states if force is set to true. Add force argument to provisioner's Detach and Delete functions. Signed-off-by: CrystalChun <cchun@redhat.com>
1 parent baa561f commit 86ae602

9 files changed

Lines changed: 49 additions & 19 deletions

File tree

apis/metal3.io/v1alpha1/baremetalhost_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,11 @@ const (
685685
type DetachedAnnotationArguments struct {
686686
// DeleteAction indicates the desired delete logic when the detached annotation is present
687687
DeleteAction DetachedDeleteAction `json:"deleteAction,omitempty"`
688+
689+
// Force indicates if detaching should be forced regardless of the host's state
690+
// +optional
691+
// +kubebuilder:default:=false
692+
Force bool `json:"force,omitempty"`
688693
}
689694

690695
// Match compares the saved status information with the name and

internal/controller/metal3.io/baremetalhost_controller.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ func (r *BareMetalHostReconciler) actionPowerOffBeforeDeleting(ctx context.Conte
539539
}
540540

541541
// Manage deletion of the host.
542-
func (r *BareMetalHostReconciler) actionDeleting(ctx context.Context, prov provisioner.Provisioner, info *reconcileInfo) actionResult {
542+
func (r *BareMetalHostReconciler) actionDeleting(ctx context.Context, prov provisioner.Provisioner, info *reconcileInfo, force bool) actionResult {
543543
info.log.Info(
544544
"marked to be deleted",
545545
"timestamp", info.host.DeletionTimestamp,
@@ -551,7 +551,7 @@ func (r *BareMetalHostReconciler) actionDeleting(ctx context.Context, prov provi
551551
return deleteComplete{}
552552
}
553553

554-
provResult, err := prov.Delete(ctx)
554+
provResult, err := prov.Delete(ctx, force)
555555
if err != nil {
556556
return actionError{fmt.Errorf("failed to delete: %w", err)}
557557
}
@@ -617,8 +617,8 @@ func hasCustomDeploy(host *metal3api.BareMetalHost) bool {
617617
}
618618

619619
// detachHost() detaches the host from the Provisioner.
620-
func (r *BareMetalHostReconciler) detachHost(ctx context.Context, prov provisioner.Provisioner, info *reconcileInfo) actionResult {
621-
provResult, err := prov.Detach(ctx)
620+
func (r *BareMetalHostReconciler) detachHost(ctx context.Context, prov provisioner.Provisioner, info *reconcileInfo, force bool) actionResult {
621+
provResult, err := prov.Detach(ctx, force)
622622
if err != nil {
623623
return actionError{fmt.Errorf("failed to detach: %w", err)}
624624
}

internal/controller/metal3.io/host_state_machine.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,14 @@ func (hsm *hostStateMachine) checkDetachedHost(ctx context.Context, info *reconc
317317
// Only allow detaching hosts in Provisioned/ExternallyProvisioned/Ready/Available states
318318
switch info.host.Status.Provisioning.State {
319319
case metal3api.StateProvisioned, metal3api.StateExternallyProvisioned, metal3api.StateReady, metal3api.StateAvailable:
320-
return hsm.Reconciler.detachHost(ctx, hsm.Provisioner, info)
320+
return hsm.Reconciler.detachHost(ctx, hsm.Provisioner, info, false)
321321
default:
322-
info.log.Info("host cannot be detached yet, waiting for the current operation to finish", "provisioningState", info.host.Status.Provisioning.State)
322+
info.log.Info("host not in allowed detaching state, checking for force annotation")
323+
if hasForceDetachAnnotation(hsm.Host) {
324+
info.log.Info("forcing detach of host", "host", info.host.Name, "annotation", hsm.Host.GetAnnotations()[metal3api.DetachedAnnotation])
325+
return hsm.Reconciler.detachHost(hsm.Provisioner, info, true)
326+
}
327+
info.log.Info("host cannot be detached yet, waiting for the current operation to finish", "provisioningState", info.host.Status.Provisioning.State, "annotation", hsm.Host.GetAnnotations()[metal3api.DetachedAnnotation])
323328
}
324329
}
325330
if info.host.Status.ErrorType == metal3api.DetachError {
@@ -340,6 +345,20 @@ func (hsm *hostStateMachine) checkDetachedHost(ctx context.Context, info *reconc
340345
return nil
341346
}
342347

348+
func hasForceDetachAnnotation(host *metal3api.BareMetalHost) bool {
349+
annotations := host.GetAnnotations()
350+
if annotations != nil {
351+
if val, ok := annotations[metal3api.DetachedAnnotation]; ok {
352+
args := metal3api.DetachedAnnotationArguments{}
353+
if err := json.Unmarshal([]byte(val), &args); err != nil {
354+
return false
355+
}
356+
return args.Force
357+
}
358+
}
359+
return false
360+
}
361+
343362
func (hsm *hostStateMachine) ensureRegistered(ctx context.Context, info *reconcileInfo) (result actionResult) {
344363
if !hsm.haveCreds {
345364
// If we are in the process of deletion (which may start with
@@ -632,5 +651,5 @@ func (hsm *hostStateMachine) handlePoweringOffBeforeDelete(ctx context.Context,
632651
}
633652

634653
func (hsm *hostStateMachine) handleDeleting(ctx context.Context, info *reconcileInfo) actionResult {
635-
return hsm.Reconciler.actionDeleting(ctx, hsm.Provisioner, info)
654+
return hsm.Reconciler.actionDeleting(ctx, hsm.Provisioner, info, false)
636655
}

internal/controller/metal3.io/host_state_machine_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,11 +1379,11 @@ func (m *mockProvisioner) Deprovision(_ context.Context, _ bool, _ metal3api.Aut
13791379
return m.getNextResultByMethod("Deprovision"), err
13801380
}
13811381

1382-
func (m *mockProvisioner) Delete(context.Context) (result provisioner.Result, err error) {
1382+
func (m *mockProvisioner) Delete(_ context.Context, _ bool) (result provisioner.Result, err error) {
13831383
return m.getNextResultByMethod("Delete"), err
13841384
}
13851385

1386-
func (m *mockProvisioner) Detach(context.Context) (result provisioner.Result, err error) {
1386+
func (m *mockProvisioner) Detach(_ context.Context, _ bool) (result provisioner.Result, err error) {
13871387
res := m.getNextResultByMethod("Detach")
13881388
return res, err
13891389
}

pkg/provisioner/demo/demo.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ func (p *demoProvisioner) Deprovision(_ context.Context, _ bool, _ metal3api.Aut
277277
// Delete removes the host from the provisioning system. It may be
278278
// called multiple times, and should return true for its dirty flag
279279
// until the deprovisioning operation is completed.
280-
func (p *demoProvisioner) Delete(_ context.Context) (result provisioner.Result, err error) {
280+
func (p *demoProvisioner) Delete(_ context.Context, _ bool) (result provisioner.Result, err error) {
281281
p.log.Info("deleting host")
282282
return result, nil
283283
}
@@ -287,7 +287,7 @@ func (p *demoProvisioner) Delete(_ context.Context) (result provisioner.Result,
287287
// for the target system. It may be called multiple times,
288288
// and should return true for its dirty flag until the
289289
// deletion operation is completed.
290-
func (p *demoProvisioner) Detach(_ context.Context) (result provisioner.Result, err error) {
290+
func (p *demoProvisioner) Detach(_ context.Context, _ bool) (result provisioner.Result, err error) {
291291
p.log.Info("detaching host")
292292
return result, nil
293293
}

pkg/provisioner/fixture/fixture.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ func (p *fixtureProvisioner) Deprovision(_ context.Context, _ bool, _ metal3api.
331331
// Delete removes the host from the provisioning system. It may be
332332
// called multiple times, and should return true for its dirty flag
333333
// until the deprovisioning operation is completed.
334-
func (p *fixtureProvisioner) Delete(_ context.Context) (result provisioner.Result, err error) {
334+
func (p *fixtureProvisioner) Delete(_ context.Context, _ bool) (result provisioner.Result, err error) {
335335
p.log.Info("deleting host")
336336

337337
if !p.state.Deleted {
@@ -349,8 +349,8 @@ func (p *fixtureProvisioner) Delete(_ context.Context) (result provisioner.Resul
349349
// for the target system. It may be called multiple times,
350350
// and should return true for its dirty flag until the
351351
// deletion operation is completed.
352-
func (p *fixtureProvisioner) Detach(ctx context.Context) (result provisioner.Result, err error) {
353-
return p.Delete(ctx)
352+
func (p *fixtureProvisioner) Detach(ctx context.Context, force bool) (result provisioner.Result, err error) {
353+
return p.Delete(ctx, force)
354354
}
355355

356356
// PowerOn ensures the server is powered on independently of any image

pkg/provisioner/ironic/delete_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,15 @@ func deleteTest(t *testing.T, detach bool) {
217217

218218
var result provisioner.Result
219219
if detach {
220+
<<<<<<< HEAD
220221
result, err = prov.Detach(t.Context())
221222
} else {
222223
result, err = prov.Delete(t.Context())
224+
=======
225+
result, err = prov.Detach(false)
226+
} else {
227+
result, err = prov.Delete(false)
228+
>>>>>>> 33f438dc (Allow forced detachment of a host from Ironic)
223229
}
224230

225231
assert.Equal(t, tc.expectedDirty, result.Dirty)

pkg/provisioner/ironic/ironic.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,7 +1510,7 @@ func (p *ironicProvisioner) Deprovision(ctx context.Context, restartOnFailure bo
15101510
// Delete removes the host from the provisioning system. It may be
15111511
// called multiple times, and should return true for its dirty flag
15121512
// until the deprovisioning operation is completed.
1513-
func (p *ironicProvisioner) Delete(ctx context.Context) (result provisioner.Result, err error) {
1513+
func (p *ironicProvisioner) Delete(ctx context.Context, _ bool) (result provisioner.Result, err error) {
15141514
ironicNode, err := p.getNode(ctx)
15151515
if err != nil {
15161516
if errors.Is(err, provisioner.ErrNeedsRegistration) {
@@ -1592,10 +1592,10 @@ func (p *ironicProvisioner) Delete(ctx context.Context) (result provisioner.Resu
15921592
// for the target system. It may be called multiple times,
15931593
// and should return true for its dirty flag until the
15941594
// deletion operation is completed.
1595-
func (p *ironicProvisioner) Detach(ctx context.Context) (result provisioner.Result, err error) {
1595+
func (p *ironicProvisioner) Detach(ctx context.Context, force bool) (result provisioner.Result, err error) {
15961596
// Currently the same behavior as Delete()
15971597
p.log.Info("removing the node for detachment", "node", p.nodeID)
1598-
return p.Delete(ctx)
1598+
return p.Delete(ctx, force)
15991599
}
16001600

16011601
// softPowerOffUnsupportedError is returned when the BMC does not

pkg/provisioner/provisioner.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,14 @@ type Provisioner interface {
182182
// Delete removes the host from the provisioning system. It may be
183183
// called multiple times, and should return true for its dirty
184184
// flag until the deletion operation is completed.
185-
Delete(ctx context.Context) (result Result, err error)
185+
Delete(ctx context.Context, force bool) (result Result, err error)
186186

187187
// Detach removes the host from the provisioning system.
188188
// Similar to Delete, but ensures non-interruptive behavior
189189
// for the target system. It may be called multiple times,
190190
// and should return true for its dirty flag until the
191191
// deletion operation is completed.
192-
Detach(ctx context.Context) (result Result, err error)
192+
Detach(ctx context.Context, force bool) (result Result, err error)
193193

194194
// PowerOn ensures the server is powered on independently of any image
195195
// provisioning operation.

0 commit comments

Comments
 (0)