Skip to content

Commit 00229da

Browse files
WVerlaekroboquat
authored andcommitted
[ws-manager-mk2] Add finalizer on workspace, handle deletion
1 parent 8fcec5e commit 00229da

File tree

6 files changed

+75
-16
lines changed

6 files changed

+75
-16
lines changed

components/ws-manager-api/go/crd/v1/workspace_types.go

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ import (
99
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1010
)
1111

12+
const (
13+
// GitpodFinalizerName is the name of the finalizer we use on workspaces and their pods.
14+
GitpodFinalizerName = "gitpod.io/finalizer"
15+
)
16+
1217
// WorkspaceSpec defines the desired state of Workspace
1318
type WorkspaceSpec struct {
1419
// +kubebuilder:validation:Required

components/ws-manager-mk2/controllers/create.go

+1-4
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@ const (
4545
// TODO(furisto): remove this label once we have moved ws-daemon to a controller setup
4646
instanceIDLabel = "gitpod.io/instanceID"
4747

48-
// gitpodPodFinalizerName is the name of the finalizer we use on pods
49-
gitpodPodFinalizerName = "gitpod.io/finalizer"
50-
5148
// Grace time until the process in the workspace is properly completed
5249
// e.g. dockerd in the workspace may take some time to clean up the overlay directory.
5350
gracePeriod = 3 * time.Minute
@@ -411,7 +408,7 @@ func createDefiniteWorkspacePod(sctx *startWorkspaceContext) (*corev1.Pod, error
411408
Namespace: sctx.Config.Namespace,
412409
Labels: labels,
413410
Annotations: annotations,
414-
Finalizers: []string{gitpodPodFinalizerName},
411+
Finalizers: []string{workspacev1.GitpodFinalizerName},
415412
},
416413
Spec: corev1.PodSpec{
417414
Hostname: sctx.Workspace.Spec.Ownership.WorkspaceID,

components/ws-manager-mk2/controllers/status.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ func updateWorkspaceStatus(ctx context.Context, workspace *workspacev1.Workspace
121121
case isPodBeingDeleted(pod):
122122
workspace.Status.Phase = workspacev1.WorkspacePhaseStopping
123123

124-
if controllerutil.ContainsFinalizer(pod, gitpodPodFinalizerName) {
124+
if controllerutil.ContainsFinalizer(pod, workspacev1.GitpodFinalizerName) {
125125
if wsk8s.ConditionPresentAndTrue(workspace.Status.Conditions, string(workspacev1.WorkspaceConditionBackupComplete)) ||
126126
wsk8s.ConditionPresentAndTrue(workspace.Status.Conditions, string(workspacev1.WorkspaceConditionBackupFailure)) ||
127127
wsk8s.ConditionPresentAndTrue(workspace.Status.Conditions, string(workspacev1.WorkspaceConditionAborted)) ||

components/ws-manager-mk2/controllers/workspace_controller.go

+23-5
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,18 @@ func (r *WorkspaceReconciler) actOnStatus(ctx context.Context, workspace *worksp
162162
r.metrics.rememberWorkspace(workspace)
163163

164164
case workspace.Status.Phase == workspacev1.WorkspacePhaseStopped:
165-
err := r.Client.Delete(ctx, workspace)
166-
if err != nil {
167-
return ctrl.Result{Requeue: true}, err
165+
// Done stopping workspace - remove finalizer.
166+
if controllerutil.ContainsFinalizer(workspace, workspacev1.GitpodFinalizerName) {
167+
controllerutil.RemoveFinalizer(workspace, workspacev1.GitpodFinalizerName)
168+
if err := r.Update(ctx, workspace); err != nil {
169+
return ctrl.Result{}, client.IgnoreNotFound(err)
170+
}
168171
}
172+
173+
// Workspace might have already been in a deleting state,
174+
// but not guaranteed, so try deleting anyway.
175+
err := r.Client.Delete(ctx, workspace)
176+
return ctrl.Result{}, client.IgnoreNotFound(err)
169177
}
170178

171179
return ctrl.Result{}, nil
@@ -223,10 +231,20 @@ func (r *WorkspaceReconciler) actOnStatus(ctx context.Context, workspace *worksp
223231
return ctrl.Result{Requeue: true}, err
224232
}
225233

234+
case isWorkspaceBeingDeleted(workspace) && !isPodBeingDeleted(pod):
235+
// Workspace was requested to be deleted, propagate by deleting the Pod.
236+
// The Pod deletion will then trigger workspace disposal steps.
237+
err := r.Client.Delete(ctx, pod)
238+
if errors.IsNotFound(err) {
239+
// pod is gone - nothing to do here
240+
} else {
241+
return ctrl.Result{Requeue: true}, err
242+
}
243+
226244
// we've disposed already - try to remove the finalizer and call it a day
227245
case workspace.Status.Phase == workspacev1.WorkspacePhaseStopped:
228-
hadFinalizer := controllerutil.ContainsFinalizer(pod, gitpodPodFinalizerName)
229-
controllerutil.RemoveFinalizer(pod, gitpodPodFinalizerName)
246+
hadFinalizer := controllerutil.ContainsFinalizer(pod, workspacev1.GitpodFinalizerName)
247+
controllerutil.RemoveFinalizer(pod, workspacev1.GitpodFinalizerName)
230248
if err := r.Client.Update(ctx, pod); err != nil {
231249
return ctrl.Result{}, fmt.Errorf("failed to remove gitpod finalizer: %w", err)
232250
}

components/ws-manager-mk2/controllers/workspace_controller_test.go

+43-6
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ var _ = Describe("WorkspaceController", func() {
3939
ws := newWorkspace(uuid.NewString(), "default")
4040
pod := createWorkspaceExpectPod(ws)
4141

42-
Expect(controllerutil.ContainsFinalizer(pod, gitpodPodFinalizerName)).To(BeTrue())
42+
Expect(controllerutil.ContainsFinalizer(pod, workspacev1.GitpodFinalizerName)).To(BeTrue())
4343

4444
By("controller updating the pod starts value")
4545
Eventually(func() (int, error) {
@@ -156,6 +156,19 @@ var _ = Describe("WorkspaceController", func() {
156156
// Expect cleanup without a backup.
157157
expectWorkspaceCleanup(ws, pod)
158158
})
159+
160+
It("deleting workspace resource should gracefully clean up", func() {
161+
ws := newWorkspace(uuid.NewString(), "default")
162+
pod := createWorkspaceExpectPod(ws)
163+
164+
Expect(k8sClient.Delete(ctx, ws)).To(Succeed())
165+
166+
expectPhaseEventually(ws, workspacev1.WorkspacePhaseStopping)
167+
168+
expectFinalizerAndMarkBackupCompleted(ws, pod)
169+
170+
expectWorkspaceCleanup(ws, pod)
171+
})
159172
})
160173

161174
Context("with headless workspaces", func() {
@@ -250,6 +263,14 @@ func createWorkspaceExpectPod(ws *workspacev1.Workspace) *corev1.Pod {
250263
return pod
251264
}
252265

266+
func expectPhaseEventually(ws *workspacev1.Workspace, phase workspacev1.WorkspacePhase) {
267+
By(fmt.Sprintf("controller transition workspace phase to %s", phase))
268+
Eventually(func(g Gomega) {
269+
g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: ws.Name, Namespace: ws.Namespace}, ws)).To(Succeed())
270+
g.Expect(ws.Status.Phase).To(Equal(phase))
271+
}, timeout, interval).Should(Succeed())
272+
}
273+
253274
func expectConditionEventually(ws *workspacev1.Workspace, tpe string, status string, reason string) {
254275
By(fmt.Sprintf("controller setting workspace condition %s to %s", tpe, status))
255276
Eventually(func(g Gomega) {
@@ -306,7 +327,7 @@ func expectFinalizerAndMarkBackupCompleted(ws *workspacev1.Workspace, pod *corev
306327
if err := k8sClient.Get(ctx, types.NamespacedName{Name: pod.GetName(), Namespace: pod.GetNamespace()}, pod); err != nil {
307328
return false, err
308329
}
309-
return controllerutil.ContainsFinalizer(pod, gitpodPodFinalizerName), nil
330+
return controllerutil.ContainsFinalizer(pod, workspacev1.GitpodFinalizerName), nil
310331
}, duration, interval).Should(BeTrue(), "missing gitpod finalizer on pod, expected one to wait for backup to succeed")
311332

312333
By("signalling backup completed")
@@ -328,7 +349,7 @@ func expectFinalizerAndMarkBackupFailed(ws *workspacev1.Workspace, pod *corev1.P
328349
if err := k8sClient.Get(ctx, types.NamespacedName{Name: pod.GetName(), Namespace: pod.GetNamespace()}, pod); err != nil {
329350
return false, err
330351
}
331-
return controllerutil.ContainsFinalizer(pod, gitpodPodFinalizerName), nil
352+
return controllerutil.ContainsFinalizer(pod, workspacev1.GitpodFinalizerName), nil
332353
}, duration, interval).Should(BeTrue(), "missing gitpod finalizer on pod, expected one to wait for backup to succeed")
333354

334355
By("signalling backup completed")
@@ -347,7 +368,8 @@ func expectWorkspaceCleanup(ws *workspacev1.Workspace, pod *corev1.Pod) {
347368
Eventually(func() (int, error) {
348369
if err := k8sClient.Get(ctx, types.NamespacedName{Name: pod.GetName(), Namespace: pod.GetNamespace()}, pod); err != nil {
349370
if errors.IsNotFound(err) {
350-
// Pod got deleted, because finalizers got removed.
371+
// Race: finalizers got removed causing pod to get deleted before we could check.
372+
// This is what we want though.
351373
return 0, nil
352374
}
353375
return 0, err
@@ -361,6 +383,20 @@ func expectWorkspaceCleanup(ws *workspacev1.Workspace, pod *corev1.Pod) {
361383
return checkNotFound(pod)
362384
}, timeout, interval).Should(Succeed(), "pod did not go away")
363385

386+
By("controller removing workspace finalizers")
387+
Eventually(func() (int, error) {
388+
if err := k8sClient.Get(ctx, types.NamespacedName{Name: ws.GetName(), Namespace: ws.GetNamespace()}, ws); err != nil {
389+
if errors.IsNotFound(err) {
390+
// Race: finalizers got removed causing workspace to get deleted before we could check.
391+
// This is what we want though.
392+
return 0, nil
393+
}
394+
return 0, err
395+
}
396+
return len(ws.ObjectMeta.Finalizers), nil
397+
398+
}, timeout, interval).Should(Equal(0), "workspace finalizers did not go away")
399+
364400
By("cleaning up the workspace resource")
365401
Eventually(func() error {
366402
return checkNotFound(ws)
@@ -395,8 +431,9 @@ func newWorkspace(name, namespace string) *workspacev1.Workspace {
395431
Kind: "Workspace",
396432
},
397433
ObjectMeta: metav1.ObjectMeta{
398-
Name: name,
399-
Namespace: namespace,
434+
Name: name,
435+
Namespace: namespace,
436+
Finalizers: []string{workspacev1.GitpodFinalizerName},
400437
},
401438
Spec: workspacev1.WorkspaceSpec{
402439
Ownership: workspacev1.Ownership{

components/ws-manager-mk2/service/manager.go

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
"k8s.io/client-go/util/retry"
4444
"k8s.io/utils/pointer"
4545
"sigs.k8s.io/controller-runtime/pkg/client"
46+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
4647
)
4748

4849
const (
@@ -232,6 +233,7 @@ func (wsm *WorkspaceManagerServer) StartWorkspace(ctx context.Context, req *wsma
232233
Ports: ports,
233234
},
234235
}
236+
controllerutil.AddFinalizer(&ws, workspacev1.GitpodFinalizerName)
235237

236238
wsm.metrics.recordWorkspaceStart(&ws)
237239
err = wsm.Client.Create(ctx, &ws)

0 commit comments

Comments
 (0)