Skip to content

Commit 7349d1c

Browse files
WVerlaekroboquat
authored andcommitted
[ws-manager-mk2] Timeout controller tests
1 parent dda547c commit 7349d1c

File tree

5 files changed

+369
-57
lines changed

5 files changed

+369
-57
lines changed

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func updateWorkspaceStatus(ctx context.Context, workspace *workspacev1.Workspace
4040
workspace.Status.Phase = workspacev1.WorkspacePhasePending
4141
}
4242

43-
if workspace.Status.Phase != workspacev1.WorkspacePhasePending {
43+
if isDisposalFinished(workspace) {
4444
workspace.Status.Phase = workspacev1.WorkspacePhaseStopped
4545
}
4646
return nil
@@ -124,11 +124,7 @@ func updateWorkspaceStatus(ctx context.Context, workspace *workspacev1.Workspace
124124
workspace.Status.Phase = workspacev1.WorkspacePhaseStopping
125125

126126
if controllerutil.ContainsFinalizer(pod, workspacev1.GitpodFinalizerName) {
127-
if wsk8s.ConditionPresentAndTrue(workspace.Status.Conditions, string(workspacev1.WorkspaceConditionBackupComplete)) ||
128-
wsk8s.ConditionPresentAndTrue(workspace.Status.Conditions, string(workspacev1.WorkspaceConditionBackupFailure)) ||
129-
wsk8s.ConditionPresentAndTrue(workspace.Status.Conditions, string(workspacev1.WorkspaceConditionAborted)) ||
130-
wsk8s.ConditionWithStatusAndReason(workspace.Status.Conditions, string(workspacev1.WorkspaceConditionContentReady), false, "InitializationFailure") {
131-
127+
if isDisposalFinished(workspace) {
132128
workspace.Status.Phase = workspacev1.WorkspacePhaseStopped
133129
}
134130

@@ -191,6 +187,13 @@ func updateWorkspaceStatus(ctx context.Context, workspace *workspacev1.Workspace
191187
return nil
192188
}
193189

190+
func isDisposalFinished(ws *workspacev1.Workspace) bool {
191+
return wsk8s.ConditionPresentAndTrue(ws.Status.Conditions, string(workspacev1.WorkspaceConditionBackupComplete)) ||
192+
wsk8s.ConditionPresentAndTrue(ws.Status.Conditions, string(workspacev1.WorkspaceConditionBackupFailure)) ||
193+
wsk8s.ConditionPresentAndTrue(ws.Status.Conditions, string(workspacev1.WorkspaceConditionAborted)) ||
194+
wsk8s.ConditionWithStatusAndReason(ws.Status.Conditions, string(workspacev1.WorkspaceConditionContentReady), false, "InitializationFailure")
195+
}
196+
194197
// extractFailure returns a pod failure reason and possibly a phase. If phase is nil then
195198
// one should extract the phase themselves. If the pod has not failed, this function returns "", nil.
196199
func extractFailure(ws *workspacev1.Workspace, pod *corev1.Pod) (string, *workspacev1.WorkspacePhase) {

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

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"sigs.k8s.io/controller-runtime/pkg/log/zap"
2121
"sigs.k8s.io/controller-runtime/pkg/metrics"
2222

23+
"github.com/gitpod-io/gitpod/common-go/util"
24+
"github.com/gitpod-io/gitpod/ws-manager-mk2/pkg/activity"
2325
"github.com/gitpod-io/gitpod/ws-manager/api/config"
2426
workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"
2527
//+kubebuilder:scaffold:imports
@@ -28,6 +30,12 @@ import (
2830
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
2931
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
3032

33+
const (
34+
timeout = time.Second * 20
35+
duration = time.Second * 2
36+
interval = time.Millisecond * 250
37+
)
38+
3139
// var cfg *rest.Config
3240
var k8sClient client.Client
3341
var testEnv *envtest.Environment
@@ -39,8 +47,9 @@ func TestAPIs(t *testing.T) {
3947
}
4048

4149
var (
42-
ctx context.Context
43-
cancel context.CancelFunc
50+
ctx context.Context
51+
cancel context.CancelFunc
52+
wsActivity *activity.WorkspaceActivity
4453
)
4554

4655
var _ = BeforeSuite(func() {
@@ -91,21 +100,15 @@ var _ = BeforeSuite(func() {
91100
})
92101
Expect(err).ToNot(HaveOccurred())
93102

94-
wsReconciler, err := NewWorkspaceReconciler(k8sManager.GetClient(), k8sManager.GetScheme(), &config.Configuration{
95-
Namespace: "default",
96-
SeccompProfile: "default.json",
97-
WorkspaceClasses: map[string]*config.WorkspaceClass{
98-
"default": {
99-
Name: "default",
100-
},
101-
},
102-
WorkspaceURLTemplate: "{{ .ID }}-{{ .Prefix }}-{{ .Host }}",
103-
GitpodHostURL: "gitpod.io",
104-
}, metrics.Registry)
105-
103+
conf := newTestConfig()
104+
wsReconciler, err := NewWorkspaceReconciler(k8sManager.GetClient(), k8sManager.GetScheme(), &conf, metrics.Registry)
106105
Expect(err).ToNot(HaveOccurred())
107-
err = wsReconciler.SetupWithManager(k8sManager)
106+
Expect(wsReconciler.SetupWithManager(k8sManager)).To(Succeed())
107+
108+
wsActivity = &activity.WorkspaceActivity{}
109+
timeoutReconciler, err := NewTimeoutReconciler(k8sManager.GetClient(), conf, wsActivity)
108110
Expect(err).ToNot(HaveOccurred())
111+
Expect(timeoutReconciler.SetupWithManager(k8sManager)).To(Succeed())
109112

110113
ctx, cancel = context.WithCancel(context.Background())
111114

@@ -117,6 +120,32 @@ var _ = BeforeSuite(func() {
117120

118121
})
119122

123+
func newTestConfig() config.Configuration {
124+
return config.Configuration{
125+
GitpodHostURL: "gitpod.io",
126+
HeartbeatInterval: util.Duration(30 * time.Second),
127+
Namespace: "default",
128+
SeccompProfile: "default.json",
129+
Timeouts: config.WorkspaceTimeoutConfiguration{
130+
AfterClose: util.Duration(1 * time.Minute),
131+
Initialization: util.Duration(30 * time.Minute),
132+
TotalStartup: util.Duration(45 * time.Minute),
133+
RegularWorkspace: util.Duration(60 * time.Minute),
134+
MaxLifetime: util.Duration(36 * time.Hour),
135+
HeadlessWorkspace: util.Duration(90 * time.Minute),
136+
Stopping: util.Duration(60 * time.Minute),
137+
ContentFinalization: util.Duration(55 * time.Minute),
138+
Interrupted: util.Duration(5 * time.Minute),
139+
},
140+
WorkspaceClasses: map[string]*config.WorkspaceClass{
141+
"default": {
142+
Name: "default",
143+
},
144+
},
145+
WorkspaceURLTemplate: "{{ .ID }}-{{ .Prefix }}-{{ .Host }}",
146+
}
147+
}
148+
120149
var _ = AfterSuite(func() {
121150
cancel()
122151
By("tearing down the test environment")

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

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111

1212
apierrors "k8s.io/apimachinery/pkg/api/errors"
1313
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/apimachinery/pkg/types"
15+
"k8s.io/client-go/util/retry"
1416
ctrl "sigs.k8s.io/controller-runtime"
1517
"sigs.k8s.io/controller-runtime/pkg/client"
1618
"sigs.k8s.io/controller-runtime/pkg/controller"
@@ -24,6 +26,9 @@ import (
2426
)
2527

2628
func NewTimeoutReconciler(c client.Client, cfg config.Configuration, activity *wsactivity.WorkspaceActivity) (*TimeoutReconciler, error) {
29+
if cfg.HeartbeatInterval == 0 {
30+
return nil, fmt.Errorf("invalid heartbeat interval, must not be 0")
31+
}
2732
reconcileInterval := time.Duration(cfg.HeartbeatInterval)
2833
// Reconcile interval is half the heartbeat interval to catch timed out workspaces in time.
2934
// See https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem why we need this.
@@ -92,18 +97,26 @@ func (r *TimeoutReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re
9297

9398
// Workspace timed out, set Timeout condition.
9499
log.Info("Workspace timed out", "reason", timedout)
95-
workspace.Status.Conditions = wsk8s.AddUniqueCondition(workspace.Status.Conditions, metav1.Condition{
96-
Type: string(workspacev1.WorkspaceConditionTimeout),
97-
Status: metav1.ConditionTrue,
98-
LastTransitionTime: metav1.Now(),
99-
Reason: "TimedOut",
100-
Message: timedout,
101-
})
102-
103-
if err = r.Client.Status().Update(ctx, &workspace); err != nil {
100+
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
101+
err := r.Get(ctx, types.NamespacedName{Name: workspace.Name, Namespace: workspace.Namespace}, &workspace)
102+
if err != nil {
103+
return err
104+
}
105+
106+
workspace.Status.Conditions = wsk8s.AddUniqueCondition(workspace.Status.Conditions, metav1.Condition{
107+
Type: string(workspacev1.WorkspaceConditionTimeout),
108+
Status: metav1.ConditionTrue,
109+
LastTransitionTime: metav1.Now(),
110+
Reason: "TimedOut",
111+
Message: timedout,
112+
})
113+
114+
return r.Status().Update(ctx, &workspace)
115+
}); err != nil {
104116
log.Error(err, "Failed to update workspace status with Timeout condition")
105117
return ctrl.Result{}, err
106118
}
119+
107120
return ctrl.Result{}, nil
108121
}
109122

0 commit comments

Comments
 (0)