diff --git a/api/v1alpha3/machine_types.go b/api/v1alpha3/machine_types.go index 32fe0dc431df..97bd17378a2d 100644 --- a/api/v1alpha3/machine_types.go +++ b/api/v1alpha3/machine_types.go @@ -37,6 +37,18 @@ const ( // MachineDeploymentLabelName is the label set on machines if they're controlled by MachineDeployment MachineDeploymentLabelName = "cluster.x-k8s.io/deployment-name" + + // PreDrainDeleteHookAnnotationPrefix annotation specifies the prefix we + // search each annotation for during the pre-drain.delete lifecycle hook + // to pause reconciliation of deletion. These hooks will prevent removal of + // draining the associated node until all are removed. + PreDrainDeleteHookAnnotationPrefix = "pre-drain.delete.hook.machine.cluster.x-k8s.io" + + // PreTerminateDeleteHookAnnotationPrefix annotation specifies the prefix we + // search each annotation for during the pre-terminate.delete lifecycle hook + // to pause reconciliation of deletion. These hooks will prevent removal of + // an instance from an infrastructure provider until all are removed. + PreTerminateDeleteHookAnnotationPrefix = "pre-terminate.delete.hook.machine.cluster.x-k8s.io" ) // ANCHOR: MachineSpec diff --git a/controllers/machine_controller.go b/controllers/machine_controller.go index 1ef2c0954216..49cae3578c49 100644 --- a/controllers/machine_controller.go +++ b/controllers/machine_controller.go @@ -270,6 +270,11 @@ func (r *MachineReconciler) reconcileDelete(ctx context.Context, cluster *cluste } if isDeleteNodeAllowed { + // pre-drain.delete lifecycle hook + // Return early without error, will requeue if/when the hook owner removes the annotation. + if annotations.HasWithPrefix(clusterv1.PreDrainDeleteHookAnnotationPrefix, m.ObjectMeta.Annotations) { + return ctrl.Result{}, nil + } // Drain node before deletion. if _, exists := m.ObjectMeta.Annotations[clusterv1.ExcludeNodeDrainingAnnotation]; !exists { logger.Info("Draining node", "node", m.Status.NodeRef.Name) @@ -281,6 +286,12 @@ func (r *MachineReconciler) reconcileDelete(ctx context.Context, cluster *cluste } } + // pre-term.delete lifecycle hook + // Return early without error, will requeue if/when the hook owner removes the annotation. + if annotations.HasWithPrefix(clusterv1.PreTerminateDeleteHookAnnotationPrefix, m.ObjectMeta.Annotations) { + return ctrl.Result{}, nil + } + if ok, err := r.reconcileDeleteExternal(ctx, m); !ok || err != nil { // Return early and don't remove the finalizer if we got an error or // the external reconciliation deletion isn't ready. diff --git a/docs/proposals/20200602-machine-deletion-phase-hooks.md b/docs/proposals/20200602-machine-deletion-phase-hooks.md index 6aa9438b8c50..279ad7e4038a 100644 --- a/docs/proposals/20200602-machine-deletion-phase-hooks.md +++ b/docs/proposals/20200602-machine-deletion-phase-hooks.md @@ -8,8 +8,8 @@ reviewers: - "@detiber" - "@ncdc" creation-date: 2020-06-02 -last-updated: 2020-06-02 -status: implementable +last-updated: 2020-08-07 +status: implemented --- # Machine Deletion Phase Hooks diff --git a/util/annotations/paused.go b/util/annotations/helpers.go similarity index 86% rename from util/annotations/paused.go rename to util/annotations/helpers.go index acb817a05d12..fbccc929f01e 100644 --- a/util/annotations/paused.go +++ b/util/annotations/helpers.go @@ -17,6 +17,8 @@ limitations under the License. package annotations import ( + "strings" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" ) @@ -38,3 +40,12 @@ func HasPausedAnnotation(o metav1.Object) bool { _, ok := annotations[clusterv1.PausedAnnotation] return ok } + +func HasWithPrefix(prefix string, annotations map[string]string) bool { + for key := range annotations { + if strings.HasPrefix(key, prefix) { + return true + } + } + return false +}