From f6731ea943f8f805053cca9490d675e8385070cc Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Tue, 18 May 2021 13:27:09 +0000 Subject: [PATCH] [server][ws-manager] Allow setting customTimeoutAnnotation for headless workspace pods --- .../server/src/workspace/workspace-starter.ts | 4 +- components/ws-manager/pkg/manager/status.go | 16 +- .../timeout_prebuild_customTimeout.golden | 1 + .../timeout_prebuild_customTimeout.json | 303 ++++++++++++++++++ 4 files changed, 316 insertions(+), 8 deletions(-) create mode 100644 components/ws-manager/pkg/manager/testdata/timeout_prebuild_customTimeout.golden create mode 100644 components/ws-manager/pkg/manager/testdata/timeout_prebuild_customTimeout.json diff --git a/components/server/src/workspace/workspace-starter.ts b/components/server/src/workspace/workspace-starter.ts index 04d2fdec650e23..1dea0953403d5c 100644 --- a/components/server/src/workspace/workspace-starter.ts +++ b/components/server/src/workspace/workspace-starter.ts @@ -633,7 +633,9 @@ export class WorkspaceStarter { spec.setWorkspaceImage(instance.workspaceImage); spec.setWorkspaceLocation(workspace.config.workspaceLocation || spec.getCheckoutLocation()); spec.setFeatureFlagsList(this.toWorkspaceFeatureFlags(featureFlags)); - spec.setTimeout(await userTimeoutPromise); + if (workspace.type === 'regular') { + spec.setTimeout(await userTimeoutPromise); + } spec.setAdmission(admissionLevel); return spec; } diff --git a/components/ws-manager/pkg/manager/status.go b/components/ws-manager/pkg/manager/status.go index 7ff487479641c9..cfb2be5e918ba5 100644 --- a/components/ws-manager/pkg/manager/status.go +++ b/components/ws-manager/pkg/manager/status.go @@ -731,24 +731,26 @@ func (m *Manager) isWorkspaceTimedOut(wso workspaceObjects) (reason string, err return decide(start, m.Config.Timeouts.TotalStartup, activity) case api.WorkspacePhase_RUNNING: + timeout := m.Config.Timeouts.RegularWorkspace + activity := activityNone if wso.IsWorkspaceHeadless() { - return decide(start, m.Config.Timeouts.HeadlessWorkspace, activityRunningHeadless) + timeout = m.Config.Timeouts.HeadlessWorkspace + lastActivity = &start + activity = activityRunningHeadless } else if lastActivity == nil { // the workspace is up and running, but the user has never produced any activity return decide(start, m.Config.Timeouts.TotalStartup, activityNone) } else if isClosed { return decide(*lastActivity, m.Config.Timeouts.AfterClose, activityClosed) } - timeout := m.Config.Timeouts.RegularWorkspace if ctv, ok := wso.Pod.Annotations[customTimeoutAnnotation]; ok { - if ct, err := time.ParseDuration(ctv); err != nil { - log.WithError(err).WithField("customTimeout", ctv).WithFields(wsk8s.GetOWIFromObject(&wso.Pod.ObjectMeta)).Warn("pod had custom timeout annotation set, but could not parse its value. Defaulting to ws-manager config.") - timeout = m.Config.Timeouts.RegularWorkspace - } else { + if ct, err := time.ParseDuration(ctv); err == nil { timeout = util.Duration(ct) + } else { + log.WithError(err).WithField("customTimeout", ctv).WithFields(wsk8s.GetOWIFromObject(&wso.Pod.ObjectMeta)).Warn("pod had custom timeout annotation set, but could not parse its value. Defaulting to ws-manager config.") } } - return decide(*lastActivity, timeout, activityNone) + return decide(*lastActivity, timeout, activity) case api.WorkspacePhase_INTERRUPTED: if lastActivity == nil { diff --git a/components/ws-manager/pkg/manager/testdata/timeout_prebuild_customTimeout.golden b/components/ws-manager/pkg/manager/testdata/timeout_prebuild_customTimeout.golden new file mode 100644 index 00000000000000..9e26dfeeb6e641 --- /dev/null +++ b/components/ws-manager/pkg/manager/testdata/timeout_prebuild_customTimeout.golden @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/components/ws-manager/pkg/manager/testdata/timeout_prebuild_customTimeout.json b/components/ws-manager/pkg/manager/testdata/timeout_prebuild_customTimeout.json new file mode 100644 index 00000000000000..7435e98a6d1256 --- /dev/null +++ b/components/ws-manager/pkg/manager/testdata/timeout_prebuild_customTimeout.json @@ -0,0 +1,303 @@ +{ + "creationDelta": "100m", + "wso": { + "pod": { + "metadata": { + "name": "ws-foobas", + "namespace": "default", + "selfLink": "/api/v1/namespaces/default/pods/ws-foobas", + "uid": "486e5f88-4354-11e9-aee4-080027861af1", + "resourceVersion": "64956", + "creationTimestamp": "2019-03-10T16:48:08Z", + "labels": { + "gpwsman": "true", + "headless": "true", + "owner": "foobar", + "metaID": "metameta", + "workspaceID": "foobas", + "workspaceType": "prebuild" + }, + "annotations": { + "gitpod/id": "foobas", + "gitpod/ready": "true", + "gitpod/servicePrefix": "foobas", + "gitpod/url": "http://10.0.0.114:8082", + "gitpod/customTimeout": "2h", + "prometheus.io/path": "/metrics", + "prometheus.io/port": "23000", + "prometheus.io/scrape": "true" + } + }, + "spec": { + "volumes": [ + { + "name": "vol-this-workspace", + "hostPath": { + "path": "/tmp/workspaces/foobas", + "type": "DirectoryOrCreate" + } + }, + { + "name": "vol-this-theia", + "hostPath": { + "path": "/tmp/theia/theia-xyz", + "type": "Directory" + } + }, + { + "name": "vol-sync-tmp", + "hostPath": { + "path": "/tmp/workspaces/sync-tmp", + "type": "DirectoryOrCreate" + } + }, + { + "name": "default-token-6qnvx", + "secret": { + "secretName": "default-token-6qnvx", + "defaultMode": 420 + } + } + ], + "containers": [ + { + "name": "workspace", + "image": "nginx:latest", + "ports": [ + { + "containerPort": 23000, + "protocol": "TCP" + } + ], + "env": [ + { + "name": "THEIA_WORKSPACE_ROOT", + "value": "/workspace" + }, + { + "name": "GITPOD_THEIA_PORT", + "value": "23000" + }, + { + "name": "GITPOD_HOST", + "value": "gitpod.io" + }, + { + "name": "GITPOD_INTERVAL", + "value": "30" + }, + { + "name": "GITPOD_WSSYNC_APITOKEN", + "value": "c17a7eaf-e5de-4e9d-815a-7919379e2bf8" + }, + { + "name": "GITPOD_WSSYNC_APIPORT", + "value": "44444" + }, + { + "name": "GITPOD_REPO_ROOT", + "value": "/workspace" + }, + { + "name": "GITPOD_CLI_APITOKEN", + "value": "690516e2-c416-4a28-ba74-e36f125922aa" + }, + { + "name": "GITPOD_WORKSPACE_ID", + "value": "foobas" + }, + { + "name": "GITPOD_GIT_USER_NAME", + "value": "usernameGoesHere" + }, + { + "name": "GITPOD_GIT_USER_EMAIL", + "value": "some@user.com" + } + ], + "resources": { + "limits": { + "cpu": "100m", + "memory": "100Mi" + }, + "requests": { + "cpu": "100m", + "memory": "100Mi" + } + }, + "volumeMounts": [ + { + "name": "vol-this-workspace", + "mountPath": "/workspace" + }, + { + "name": "vol-this-theia", + "readOnly": true, + "mountPath": "/theia" + }, + { + "name": "default-token-6qnvx", + "readOnly": true, + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" + } + ], + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "imagePullPolicy": "Always" + } + ], + "restartPolicy": "Always", + "terminationGracePeriodSeconds": 30, + "dnsPolicy": "ClusterFirst", + "serviceAccountName": "default", + "serviceAccount": "default", + "nodeName": "minikube", + "securityContext": {}, + "schedulerName": "default-scheduler", + "tolerations": [ + { + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "effect": "NoExecute", + "tolerationSeconds": 300 + }, + { + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "effect": "NoExecute", + "tolerationSeconds": 300 + } + ] + }, + "status": { + "phase": "Running", + "conditions": [ + { + "type": "Initialized", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2019-03-10T16:48:08Z" + }, + { + "type": "Ready", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2019-03-10T16:48:13Z" + }, + { + "type": "PodScheduled", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2019-03-10T16:48:08Z" + } + ], + "hostIP": "10.0.2.15", + "podIP": "172.17.0.5", + "startTime": "2019-03-10T16:48:08Z", + "containerStatuses": [ + { + "name": "sync", + "state": { + "running": { + "startedAt": "2019-03-10T16:48:13Z" + } + }, + "lastState": {}, + "ready": true, + "restartCount": 0, + "image": "csweichel/noop:latest", + "imageID": "docker-pullable://csweichel/noop@sha256:aaa6b993f4c853fac7101aa7fc087926f829004e62cbce6e1852e5a3aac87c52", + "containerID": "docker://9961f75ea72f36bb0ba1e42b3b2da98eb44a9dc12e7c7e8edfb52512b3b04016" + }, + { + "name": "workspace", + "state": { + "running": { + "startedAt": "2019-03-10T16:48:12Z" + } + }, + "lastState": {}, + "ready": true, + "restartCount": 0, + "image": "nginx:latest", + "imageID": "docker-pullable://nginx@sha256:98efe605f61725fd817ea69521b0eeb32bef007af0e3d0aeb6258c6e6fe7fc1a", + "containerID": "docker://e7080b843a47db414d6c94cfda7f657b99d8aa5bbf7c9c118ec98c0eefb6c0df" + } + ], + "qosClass": "Guaranteed" + } + }, + "theiaService": { + "metadata": { + "name": "foobas-theia", + "namespace": "default", + "selfLink": "/api/v1/namespaces/default/services/foobas-theia", + "uid": "48687212-4354-11e9-aee4-080027861af1", + "resourceVersion": "64923", + "creationTimestamp": "2019-03-10T16:48:08Z", + "labels": { + "gpwsman": "true", + "headless": "false", + "owner": "foobar", + "metaID": "metameta", + "workspaceID": "foobas" + } + }, + "spec": { + "ports": [ + { + "name": "theia", + "protocol": "TCP", + "port": 23000, + "targetPort": 23000 + } + ], + "selector": { + "gpwsman": "true", + "headless": "false", + "owner": "foobar", + "workspaceID": "foobas" + }, + "clusterIP": "10.103.194.121", + "type": "ClusterIP", + "sessionAffinity": "None" + }, + "status": { + "loadBalancer": {} + } + }, + "portsService": { + "metadata": { + "name": "foobas-ports", + "namespace": "default", + "selfLink": "/api/v1/namespaces/default/services/foobas-ports", + "uid": "486cb304-4354-11e9-aee4-080027861af1", + "resourceVersion": "64926", + "creationTimestamp": "2019-03-10T16:48:08Z", + "labels": { + "gpwsman": "true", + "workspaceID": "foobas" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 8080, + "targetPort": 8080 + } + ], + "selector": { + "gpwsman": "true", + "workspaceID": "foobas" + }, + "clusterIP": "10.110.184.222", + "type": "ClusterIP", + "sessionAffinity": "None" + }, + "status": { + "loadBalancer": {} + } + } + } +} \ No newline at end of file