Skip to content

Commit 8a7136a

Browse files
committed
WIP: test
1 parent b1e5d07 commit 8a7136a

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

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

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,119 @@ var _ = Describe("WorkspaceController", func() {
389389
})
390390
})
391391

392+
It("pod rejection should result in a retry", func() {
393+
ws := newWorkspace(uuid.NewString(), "default")
394+
395+
// ### prepare block start
396+
By("creating workspace")
397+
// Simulate pod getting scheduled to a node.
398+
var node corev1.Node
399+
node.Name = uuid.NewString()
400+
Expect(k8sClient.Create(ctx, &node)).To(Succeed())
401+
// Manually create the workspace pod with the node name.
402+
// We can't update the pod with the node name, as this operation
403+
// is only allowed for the scheduler. So as a hack, we manually
404+
// create the workspace's pod.
405+
pod := &corev1.Pod{
406+
ObjectMeta: metav1.ObjectMeta{
407+
Name: fmt.Sprintf("ws-%s", ws.Name),
408+
Namespace: ws.Namespace,
409+
Finalizers: []string{workspacev1.GitpodFinalizerName},
410+
Labels: map[string]string{
411+
wsk8s.WorkspaceManagedByLabel: constants.ManagedBy,
412+
},
413+
},
414+
Spec: corev1.PodSpec{
415+
NodeName: node.Name,
416+
Containers: []corev1.Container{{
417+
Name: "workspace",
418+
Image: "someimage",
419+
}},
420+
},
421+
}
422+
423+
Expect(k8sClient.Create(ctx, pod)).To(Succeed())
424+
pod = createWorkspaceExpectPod(ws)
425+
updateObjWithRetries(k8sClient, pod, false, func(pod *corev1.Pod) {
426+
Expect(ctrl.SetControllerReference(ws, pod, k8sClient.Scheme())).To(Succeed())
427+
})
428+
// Wait until controller has reconciled at least once (by waiting for the runtime status to get updated).
429+
// This is necessary for the metrics to get recorded correctly. If we don't wait, the first reconciliation
430+
// might be once the Pod is already in a running state, and hence the metric state might not record e.g. content
431+
// restore.
432+
// This is only necessary because we manually created the pod, normally the Pod creation is the controller's
433+
// first reconciliation which ensures the metrics are recorded from the workspace's initial state.
434+
435+
Eventually(func(g Gomega) {
436+
g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: ws.Name, Namespace: ws.Namespace}, ws)).To(Succeed())
437+
g.Expect(ws.Status.Runtime).ToNot(BeNil())
438+
g.Expect(ws.Status.Runtime.PodName).To(Equal(pod.Name))
439+
}, timeout, interval).Should(Succeed())
440+
441+
// Await "deployed" condition, and check we are good
442+
expectConditionEventually(ws, string(workspacev1.WorkspaceConditionDeployed), metav1.ConditionTrue, "")
443+
Eventually(func(g Gomega) {
444+
g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: ws.Name, Namespace: ws.Namespace}, ws)).To(Succeed())
445+
g.Expect(ws.Status.PodStarts).To(Equal(1))
446+
g.Expect(ws.Status.PodRecreated).To(Equal(0))
447+
}, timeout, interval).Should(Succeed())
448+
449+
// ### prepare block end
450+
451+
// ### trigger block start
452+
// Make pod be rejected 🪄
453+
By("rejecting pod")
454+
rejectPod(pod)
455+
456+
expectConditionEventually(ws, string(workspacev1.WorkspaceConditionPodRejected), metav1.ConditionFalse, "")
457+
458+
By("await pod deleted")
459+
Eventually(func(g Gomega) {
460+
g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, pod)).To(MatchError(ContainSubstring("not found")))
461+
}, timeout, interval).Should(Succeed())
462+
463+
By("await pod recreation")
464+
Eventually(func(g Gomega) {
465+
g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: ws.Name, Namespace: ws.Namespace}, ws)).To(Succeed())
466+
g.Expect(ws.Status.PodRecreated).To(Equal(1))
467+
g.Expect(ws.Status.Phase).To(Equal(workspacev1.WorkspacePhasePending))
468+
}, timeout, interval).Should(Succeed())
469+
// ### trigger block end
470+
471+
// ### retry block start
472+
// Transition Pod to pending, and expect workspace to reach Creating phase.
473+
// This should also cause create time metrics to be recorded.
474+
updateObjWithRetries(k8sClient, pod, true, func(pod *corev1.Pod) {
475+
pod.Status.Phase = corev1.PodPending
476+
pod.Status.ContainerStatuses = []corev1.ContainerStatus{{
477+
State: corev1.ContainerState{
478+
Waiting: &corev1.ContainerStateWaiting{
479+
Reason: "ContainerCreating",
480+
},
481+
},
482+
Name: "workspace",
483+
}}
484+
})
485+
486+
expectPhaseEventually(ws, workspacev1.WorkspacePhaseCreating)
487+
488+
// Transition Pod to running, and expect workspace to reach Running phase.
489+
// This should also cause e.g. startup time metrics to be recorded.
490+
updateObjWithRetries(k8sClient, pod, true, func(pod *corev1.Pod) {
491+
pod.Status.Phase = corev1.PodRunning
492+
pod.Status.ContainerStatuses = []corev1.ContainerStatus{{
493+
Name: "workspace",
494+
Ready: true,
495+
}}
496+
})
497+
498+
updateObjWithRetries(k8sClient, ws, true, func(ws *workspacev1.Workspace) {
499+
ws.Status.SetCondition(workspacev1.NewWorkspaceConditionContentReady(metav1.ConditionTrue, workspacev1.ReasonInitializationSuccess, ""))
500+
})
501+
502+
expectPhaseEventually(ws, workspacev1.WorkspacePhaseRunning)
503+
// ### retry block end
504+
})
392505
})
393506

394507
Context("with headless workspaces", func() {
@@ -634,6 +747,16 @@ func requestStop(ws *workspacev1.Workspace) {
634747
})
635748
}
636749

750+
func rejectPod(pod *corev1.Pod) {
751+
GinkgoHelper()
752+
By("adding pod rejected condition")
753+
updateObjWithRetries(k8sClient, pod, true, func(pod *corev1.Pod) {
754+
pod.Status.Phase = corev1.PodFailed
755+
pod.Status.Reason = "NodeAffinity"
756+
pod.Status.Message = "Pod was rejected"
757+
})
758+
}
759+
637760
func markReady(ws *workspacev1.Workspace) {
638761
GinkgoHelper()
639762
By("adding content ready condition")

0 commit comments

Comments
 (0)