From ee9e7bbf2a21a6f089f30c86fe0a21aa5ca33693 Mon Sep 17 00:00:00 2001 From: "Cornelius A. Ludmann" Date: Mon, 4 Apr 2022 14:49:04 +0000 Subject: [PATCH] [installer] Allow to set default workspace timeout --- .../gitpod-protocol/src/gitpod-service.ts | 11 ++++++- .../server/ee/src/user/eligibility-service.ts | 6 ++-- components/server/ee/src/user/user-service.ts | 33 +++++++++++++++++-- .../ee/src/workspace/gitpod-server-impl.ts | 13 +++++--- components/server/src/config.ts | 2 ++ components/server/src/user/user-service.ts | 29 ++++++++++++++-- .../server/src/workspace/workspace-starter.ts | 2 +- install/installer/example-config.yaml | 1 + .../pkg/components/server/configmap.go | 2 ++ .../installer/pkg/components/server/types.go | 7 +++- .../pkg/components/ws-manager/configmap.go | 9 +++-- install/installer/pkg/config/v1/config.go | 16 +++++++++ 12 files changed, 113 insertions(+), 18 deletions(-) diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts index af280e8fc5938b..5fc7dd37d3928e 100644 --- a/components/gitpod-protocol/src/gitpod-service.ts +++ b/components/gitpod-protocol/src/gitpod-service.ts @@ -321,7 +321,16 @@ export interface ClientHeaderFields { clientRegion?: string; } -export const WorkspaceTimeoutValues = ["30m", "60m", "180m"] as const; +export const WORKSPACE_TIMEOUT_DEFAULT_SHORT = "short"; +export const WORKSPACE_TIMEOUT_DEFAULT_LONG = "long"; +export const WORKSPACE_TIMEOUT_EXTENDED = "extended"; +export const WORKSPACE_TIMEOUT_EXTENDED_ALT = "180m"; // for backwards compatibility since the IDE uses this +export const WorkspaceTimeoutValues = [ + WORKSPACE_TIMEOUT_DEFAULT_SHORT, + WORKSPACE_TIMEOUT_DEFAULT_LONG, + WORKSPACE_TIMEOUT_EXTENDED, + WORKSPACE_TIMEOUT_EXTENDED_ALT, +] as const; export const createServiceMock = function ( methods: Partial>, diff --git a/components/server/ee/src/user/eligibility-service.ts b/components/server/ee/src/user/eligibility-service.ts index 532d81d258aba0..c6e411bd09bb81 100644 --- a/components/server/ee/src/user/eligibility-service.ts +++ b/components/server/ee/src/user/eligibility-service.ts @@ -7,7 +7,7 @@ import { inject, injectable } from "inversify"; import { TeamSubscriptionDB, UserDB } from "@gitpod/gitpod-db/lib"; import { TokenProvider } from "../../../src/user/token-provider"; -import { User, WorkspaceTimeoutDuration, WorkspaceInstance } from "@gitpod/gitpod-protocol"; +import { User, WorkspaceTimeoutDuration, WorkspaceInstance, WORKSPACE_TIMEOUT_DEFAULT_LONG, WORKSPACE_TIMEOUT_DEFAULT_SHORT } from "@gitpod/gitpod-protocol"; import { RemainingHours } from "@gitpod/gitpod-protocol/lib/accounting-protocol"; import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; import { Plans, MAX_PARALLEL_WORKSPACES } from "@gitpod/gitpod-protocol/lib/plans"; @@ -251,9 +251,9 @@ export class EligibilityService { */ async getDefaultWorkspaceTimeout(user: User, date: Date = new Date()): Promise { if (await this.maySetTimeout(user, date)) { - return "60m"; + return WORKSPACE_TIMEOUT_DEFAULT_LONG; } else { - return "30m"; + return WORKSPACE_TIMEOUT_DEFAULT_SHORT; } } diff --git a/components/server/ee/src/user/user-service.ts b/components/server/ee/src/user/user-service.ts index f0896037754469..6c12c6cea95127 100644 --- a/components/server/ee/src/user/user-service.ts +++ b/components/server/ee/src/user/user-service.ts @@ -5,7 +5,7 @@ */ import { UserService, CheckSignUpParams, CheckTermsParams } from "../../../src/user/user-service"; -import { User, WorkspaceTimeoutDuration } from "@gitpod/gitpod-protocol"; +import { User, WorkspaceTimeoutDuration, WORKSPACE_TIMEOUT_EXTENDED, WORKSPACE_TIMEOUT_EXTENDED_ALT, WORKSPACE_TIMEOUT_DEFAULT_LONG, WORKSPACE_TIMEOUT_DEFAULT_SHORT } from "@gitpod/gitpod-protocol"; import { inject } from "inversify"; import { LicenseEvaluator } from "@gitpod/licensor/lib"; import { Feature } from "@gitpod/licensor/lib/api"; @@ -14,6 +14,7 @@ import { EligibilityService } from "./eligibility-service"; import { SubscriptionService } from "@gitpod/gitpod-payment-endpoint/lib/accounting"; import { OssAllowListDB } from "@gitpod/gitpod-db/lib/oss-allowlist-db"; import { HostContextProvider } from "../../../src/auth/host-context-provider"; +import { Config } from "../../../src/config"; export class UserServiceEE extends UserService { @inject(LicenseEvaluator) protected readonly licenseEvaluator: LicenseEvaluator; @@ -21,6 +22,7 @@ export class UserServiceEE extends UserService { @inject(SubscriptionService) protected readonly subscriptionService: SubscriptionService; @inject(OssAllowListDB) protected readonly OssAllowListDb: OssAllowListDB; @inject(HostContextProvider) protected readonly hostContextProvider: HostContextProvider; + @inject(Config) protected readonly config: Config; async getDefaultWorkspaceTimeout(user: User, date: Date): Promise { if (this.config.enablePayment) { @@ -32,10 +34,35 @@ export class UserServiceEE extends UserService { // the self-hosted case if (!this.licenseEvaluator.isEnabled(Feature.FeatureSetTimeout, userCount)) { - return "30m"; + return WORKSPACE_TIMEOUT_DEFAULT_SHORT; } - return "60m"; + return WORKSPACE_TIMEOUT_DEFAULT_LONG; + } + + public workspaceTimeoutToDuration(timeout: WorkspaceTimeoutDuration): string { + switch (timeout) { + case WORKSPACE_TIMEOUT_DEFAULT_SHORT: + return "30m"; + case WORKSPACE_TIMEOUT_DEFAULT_LONG: + return this.config.workspaceDefaults.timeoutDefault || "60m"; + case WORKSPACE_TIMEOUT_EXTENDED: + case WORKSPACE_TIMEOUT_EXTENDED_ALT: + return this.config.workspaceDefaults.timeoutExtended || "180m"; + } + } + + public durationToWorkspaceTimeout(duration: string): WorkspaceTimeoutDuration { + switch (duration) { + case "30m": + return WORKSPACE_TIMEOUT_DEFAULT_SHORT; + case this.config.workspaceDefaults.timeoutDefault || "60m": + return WORKSPACE_TIMEOUT_DEFAULT_LONG; + case this.config.workspaceDefaults.timeoutExtended || "180m": + return WORKSPACE_TIMEOUT_EXTENDED_ALT; + default: + return WORKSPACE_TIMEOUT_DEFAULT_SHORT; + } } async userGetsMoreResources(user: User): Promise { diff --git a/components/server/ee/src/workspace/gitpod-server-impl.ts b/components/server/ee/src/workspace/gitpod-server-impl.ts index 8303ac344b12d7..6d46644431073b 100644 --- a/components/server/ee/src/workspace/gitpod-server-impl.ts +++ b/components/server/ee/src/workspace/gitpod-server-impl.ts @@ -45,6 +45,7 @@ import { Workspace, FindPrebuildsParams, TeamMemberRole, + WORKSPACE_TIMEOUT_DEFAULT_SHORT, } from "@gitpod/gitpod-protocol"; import { ResponseError } from "vscode-jsonrpc"; import { @@ -77,7 +78,7 @@ import { import { Plans } from "@gitpod/gitpod-protocol/lib/plans"; import * as pThrottle from "p-throttle"; import { formatDate } from "@gitpod/gitpod-protocol/lib/util/date-time"; -import { FindUserByIdentityStrResult } from "../../../src/user/user-service"; +import { FindUserByIdentityStrResult, UserService } from "../../../src/user/user-service"; import { Accounting, AccountService, @@ -133,6 +134,8 @@ export class GitpodServerEEImpl extends GitpodServerImpl { @inject(UserCounter) protected readonly userCounter: UserCounter; + @inject(UserService) protected readonly userService: UserService; + initialize( client: GitpodClient | undefined, user: User | undefined, @@ -312,7 +315,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl { instancesWithReset.map((i) => { const req = new SetTimeoutRequest(); req.setId(i.id); - req.setDuration(defaultTimeout); + req.setDuration(this.userService.workspaceTimeoutToDuration(defaultTimeout)); return client.setTimeout(ctx, req); }), @@ -320,7 +323,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl { const req = new SetTimeoutRequest(); req.setId(runningInstance.id); - req.setDuration(duration); + req.setDuration(this.userService.workspaceTimeoutToDuration(duration)); await client.setTimeout(ctx, req); return { @@ -343,7 +346,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl { const runningInstance = await this.workspaceDb.trace(ctx).findRunningInstance(workspaceId); if (!runningInstance) { log.warn({ userId: user.id, workspaceId }, "Can only get keep-alive for running workspaces"); - return { duration: "30m", canChange }; + return { duration: WORKSPACE_TIMEOUT_DEFAULT_SHORT, canChange }; } await this.guardAccess({ kind: "workspaceInstance", subject: runningInstance, workspace: workspace }, "get"); @@ -352,7 +355,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl { const client = await this.workspaceManagerClientProvider.get(runningInstance.region); const desc = await client.describeWorkspace(ctx, req); - const duration = desc.getStatus()!.getSpec()!.getTimeout() as WorkspaceTimeoutDuration; + const duration = this.userService.durationToWorkspaceTimeout(desc.getStatus()!.getSpec()!.getTimeout()); return { duration, canChange }; } diff --git a/components/server/src/config.ts b/components/server/src/config.ts index 011ee984c7b2e9..06b2186c6035a9 100644 --- a/components/server/src/config.ts +++ b/components/server/src/config.ts @@ -29,6 +29,8 @@ export interface WorkspaceDefaults { workspaceImage: string; previewFeatureFlags: NamedWorkspaceFeatureFlag[]; defaultFeatureFlags: NamedWorkspaceFeatureFlag[]; + timeoutDefault?: string; + timeoutExtended?: string; } export interface WorkspaceGarbageCollection { diff --git a/components/server/src/user/user-service.ts b/components/server/src/user/user-service.ts index 87b1cab2d998d4..aa527c4299d552 100644 --- a/components/server/src/user/user-service.ts +++ b/components/server/src/user/user-service.ts @@ -5,7 +5,7 @@ */ import { injectable, inject } from "inversify"; -import { User, Identity, WorkspaceTimeoutDuration, UserEnvVarValue, Token } from "@gitpod/gitpod-protocol"; +import { User, Identity, WorkspaceTimeoutDuration, UserEnvVarValue, Token, WORKSPACE_TIMEOUT_DEFAULT_SHORT, WORKSPACE_TIMEOUT_DEFAULT_LONG, WORKSPACE_TIMEOUT_EXTENDED, WORKSPACE_TIMEOUT_EXTENDED_ALT } from "@gitpod/gitpod-protocol"; import { TermsAcceptanceDB, UserDB } from "@gitpod/gitpod-db/lib"; import { HostContextProvider } from "../auth/host-context-provider"; import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; @@ -155,7 +155,32 @@ export class UserService { * @param date The date for which we want to know the default workspace timeout */ async getDefaultWorkspaceTimeout(user: User, date: Date = new Date()): Promise { - return "30m"; + return WORKSPACE_TIMEOUT_DEFAULT_SHORT; + } + + public workspaceTimeoutToDuration(timeout: WorkspaceTimeoutDuration): string { + switch (timeout) { + case WORKSPACE_TIMEOUT_DEFAULT_SHORT: + return "30m"; + case WORKSPACE_TIMEOUT_DEFAULT_LONG: + return "60m"; + case WORKSPACE_TIMEOUT_EXTENDED: + case WORKSPACE_TIMEOUT_EXTENDED_ALT: + return "180m"; + } + } + + public durationToWorkspaceTimeout(duration: string): WorkspaceTimeoutDuration { + switch (duration) { + case "30m": + return WORKSPACE_TIMEOUT_DEFAULT_SHORT; + case "60m": + return WORKSPACE_TIMEOUT_DEFAULT_LONG; + case "180m": + return WORKSPACE_TIMEOUT_EXTENDED_ALT; + default: + return WORKSPACE_TIMEOUT_DEFAULT_SHORT; + } } /** diff --git a/components/server/src/workspace/workspace-starter.ts b/components/server/src/workspace/workspace-starter.ts index 61e7e1039702cf..a855bf1207a1d8 100644 --- a/components/server/src/workspace/workspace-starter.ts +++ b/components/server/src/workspace/workspace-starter.ts @@ -1191,7 +1191,7 @@ export class WorkspaceStarter { spec.setWorkspaceLocation(workspace.config.workspaceLocation || spec.getCheckoutLocation()); spec.setFeatureFlagsList(this.toWorkspaceFeatureFlags(featureFlags)); if (workspace.type === "regular") { - spec.setTimeout(await userTimeoutPromise); + spec.setTimeout(this.userService.workspaceTimeoutToDuration(await userTimeoutPromise)); } spec.setAdmission(admissionLevel); return spec; diff --git a/install/installer/example-config.yaml b/install/installer/example-config.yaml index c55a0416a7aaec..c0e404c5462958 100644 --- a/install/installer/example-config.yaml +++ b/install/installer/example-config.yaml @@ -23,6 +23,7 @@ openVSX: url: https://open-vsx.org repository: eu.gcr.io/gitpod-core-dev/build workspace: + maxLifetime: 36h0m0s resources: requests: cpu: "1" diff --git a/install/installer/pkg/components/server/configmap.go b/install/installer/pkg/components/server/configmap.go index 5be3dafa27316c..50770abd3aa75f 100644 --- a/install/installer/pkg/components/server/configmap.go +++ b/install/installer/pkg/components/server/configmap.go @@ -40,6 +40,8 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { WorkspaceImage: common.ImageName(common.ThirdPartyContainerRepo(ctx.Config.Repository, ""), workspace.DefaultWorkspaceImage, workspace.DefaultWorkspaceImageVersion), PreviewFeatureFlags: []NamedWorkspaceFeatureFlag{}, DefaultFeatureFlags: []NamedWorkspaceFeatureFlag{}, + TimeoutDefault: ctx.Config.Workspace.TimeoutDefault, + TimeoutExtended: ctx.Config.Workspace.TimeoutExtended, }, Session: Session{ MaxAgeMs: 259200000, diff --git a/install/installer/pkg/components/server/types.go b/install/installer/pkg/components/server/types.go index 8bcba3a17ffccc..e829852990a64c 100644 --- a/install/installer/pkg/components/server/types.go +++ b/install/installer/pkg/components/server/types.go @@ -4,7 +4,10 @@ package server -import "github.com/gitpod-io/gitpod/installer/pkg/config/v1" +import ( + "github.com/gitpod-io/gitpod/common-go/util" + "github.com/gitpod-io/gitpod/installer/pkg/config/v1" +) // These types are from TypeScript files @@ -120,6 +123,8 @@ type WorkspaceDefaults struct { WorkspaceImage string `json:"workspaceImage"` PreviewFeatureFlags []NamedWorkspaceFeatureFlag `json:"previewFeatureFlags"` DefaultFeatureFlags []NamedWorkspaceFeatureFlag `json:"defaultFeatureFlags"` + TimeoutDefault *util.Duration `json:"timeoutDefault,omitempty"` + TimeoutExtended *util.Duration `json:"timeoutExtended,omitempty"` } type NamedWorkspaceFeatureFlag string diff --git a/install/installer/pkg/components/ws-manager/configmap.go b/install/installer/pkg/components/ws-manager/configmap.go index beb0ca6a6188c3..a7b28551471b2f 100644 --- a/install/installer/pkg/components/ws-manager/configmap.go +++ b/install/installer/pkg/components/ws-manager/configmap.go @@ -38,6 +38,11 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { return (&q).String() } + timeoutAfterClose := util.Duration(2 * time.Minute) + if ctx.Config.Workspace.TimeoutAfterClose != nil { + timeoutAfterClose = *ctx.Config.Workspace.TimeoutAfterClose + } + wsmcfg := config.ServiceConfiguration{ Manager: config.Configuration{ Namespace: ctx.Namespace, @@ -81,11 +86,11 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { WorkspaceHostPath: wsdaemon.HostWorkingArea, WorkspacePodTemplate: templatesCfg, Timeouts: config.WorkspaceTimeoutConfiguration{ - AfterClose: util.Duration(2 * time.Minute), + AfterClose: timeoutAfterClose, HeadlessWorkspace: util.Duration(1 * time.Hour), Initialization: util.Duration(30 * time.Minute), RegularWorkspace: util.Duration(30 * time.Minute), - MaxLifetime: util.Duration(36 * time.Hour), + MaxLifetime: ctx.Config.Workspace.MaxLifetime, TotalStartup: util.Duration(1 * time.Hour), ContentFinalization: util.Duration(1 * time.Hour), Stopping: util.Duration(1 * time.Hour), diff --git a/install/installer/pkg/config/v1/config.go b/install/installer/pkg/config/v1/config.go index 090eee253cb9b6..5b1c1711b554f6 100644 --- a/install/installer/pkg/config/v1/config.go +++ b/install/installer/pkg/config/v1/config.go @@ -5,6 +5,9 @@ package config import ( + "time" + + "github.com/gitpod-io/gitpod/common-go/util" "github.com/gitpod-io/gitpod/installer/pkg/config" "github.com/gitpod-io/gitpod/installer/pkg/config/v1/experimental" "github.com/gitpod-io/gitpod/ws-daemon/pkg/cpulimit" @@ -53,6 +56,7 @@ func (v version) Defaults(in interface{}) error { cfg.Workspace.Runtime.FSShiftMethod = FSShiftFuseFS cfg.Workspace.Runtime.ContainerDSocket = "/run/containerd/containerd.sock" cfg.Workspace.Runtime.ContainerDRuntimeDir = "/var/lib/containerd/io.containerd.runtime.v2.task/k8s.io" + cfg.Workspace.MaxLifetime = util.Duration(36 * time.Hour) cfg.OpenVSX.URL = "https://open-vsx.org" cfg.DisableDefinitelyGP = false @@ -224,6 +228,18 @@ type Workspace struct { Runtime WorkspaceRuntime `json:"runtime" validate:"required"` Resources Resources `json:"resources" validate:"required"` Templates *WorkspaceTemplates `json:"templates,omitempty"` + + // MaxLifetime is the maximum time a workspace is allowed to run. After that, the workspace times out despite activity + MaxLifetime util.Duration `json:"maxLifetime" validate:"required"` + + // TimeoutDefault is the default timeout of a regular workspace + TimeoutDefault *util.Duration `json:"timeoutDefault,omitempty"` + + // TimeoutExtended is the workspace timeout that a user can extend to for one workspace + TimeoutExtended *util.Duration `json:"timeoutExtended,omitempty"` + + // TimeoutAfterClose is the time a workspace timed out after it has been closed (“closed” means that it does not get a heartbeat from an IDE anymore) + TimeoutAfterClose *util.Duration `json:"timeoutAfterClose,omitempty"` } type OpenVSX struct {