diff --git a/components/server/ee/src/prebuilds/prebuild-manager.ts b/components/server/ee/src/prebuilds/prebuild-manager.ts index fe6c8e0b96c6f3..80a4345dd96999 100644 --- a/components/server/ee/src/prebuilds/prebuild-manager.ts +++ b/components/server/ee/src/prebuilds/prebuild-manager.ts @@ -34,6 +34,7 @@ import * as opentracing from "opentracing"; import { StopWorkspacePolicy } from "@gitpod/ws-manager/lib"; import { error } from "console"; import { IncrementalPrebuildsService } from "./incremental-prebuilds-service"; +import { PrebuildRateLimiterConfig } from "../../../src/workspace/prebuild-rate-limiter"; export class WorkspaceRunningError extends Error { constructor(msg: string, public instance: WorkspaceInstance) { @@ -49,9 +50,6 @@ export interface StartPrebuildParams { forcePrebuild?: boolean; } -const PREBUILD_LIMITER_WINDOW_SECONDS = 60; -const PREBUILD_LIMITER_DEFAULT_LIMIT = 50; - @injectable() export class PrebuildManager { @inject(TracedWorkspaceDB) protected readonly workspaceDB: DBWithTracing; @@ -389,36 +387,24 @@ export class PrebuildManager { } private async shouldRateLimitPrebuild(span: opentracing.Span, cloneURL: string): Promise { - const windowStart = secondsBefore(new Date().toISOString(), PREBUILD_LIMITER_WINDOW_SECONDS); + const rateLimit = PrebuildRateLimiterConfig.getConfigForCloneURL(this.config.prebuildLimiter, cloneURL); + + const windowStart = secondsBefore(new Date().toISOString(), rateLimit.period); const unabortedCount = await this.workspaceDB .trace({ span }) .countUnabortedPrebuildsSince(cloneURL, new Date(windowStart)); - const limit = this.getPrebuildRateLimitForCloneURL(cloneURL); - if (unabortedCount >= limit) { - log.debug("Prebuild exceeds rate limit", { limit, unabortedPrebuildsCount: unabortedCount, cloneURL }); + if (unabortedCount >= rateLimit.limit) { + log.debug("Prebuild exceeds rate limit", { + ...rateLimit, + unabortedPrebuildsCount: unabortedCount, + cloneURL, + }); return true; } return false; } - private getPrebuildRateLimitForCloneURL(cloneURL: string): number { - // First we use any explicit overrides for a given cloneURL - let limit = this.config.prebuildLimiter[cloneURL]; - if (limit > 0) { - return limit; - } - - // Find if there is a default value set under the '*' key - limit = this.config.prebuildLimiter["*"]; - if (limit > 0) { - return limit; - } - - // Last resort default - return PREBUILD_LIMITER_DEFAULT_LIMIT; - } - private async shouldSkipInactiveProject(project: Project): Promise { return await this.projectService.isProjectConsideredInactive(project.id); } diff --git a/components/server/src/config.ts b/components/server/src/config.ts index 68a547faa65f76..e1a7e069857ae5 100644 --- a/components/server/src/config.ts +++ b/components/server/src/config.ts @@ -17,6 +17,7 @@ import * as yaml from "js-yaml"; import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; import { filePathTelepresenceAware } from "@gitpod/gitpod-protocol/lib/env"; import { WorkspaceClasses, WorkspaceClassesConfig } from "./workspace/workspace-classes"; +import { PrebuildRateLimiters } from "./workspace/prebuild-rate-limiter"; export const Config = Symbol("Config"); export type Config = Omit< @@ -200,10 +201,10 @@ export interface ConfigSerialized { enablePayment?: boolean; /** - * Number of prebuilds that can be started in the last 1 minute. + * Number of prebuilds that can be started in a given time period. * Key '*' specifies the default rate limit for a cloneURL, unless overriden by a specific cloneURL. */ - prebuildLimiter: { [cloneURL: string]: number } & { "*": number }; + prebuildLimiter: PrebuildRateLimiters; /** * If a numeric value interpreted as days is set, repositories not beeing opened with Gitpod are diff --git a/components/server/src/workspace/prebuild-rate-limiter.ts b/components/server/src/workspace/prebuild-rate-limiter.ts new file mode 100644 index 00000000000000..531f7b763e86aa --- /dev/null +++ b/components/server/src/workspace/prebuild-rate-limiter.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +export type PrebuildRateLimiters = { [cloneURL: string]: PrebuildRateLimiterConfig } & { + "*": PrebuildRateLimiterConfig; +}; + +export type PrebuildRateLimiterConfig = { + // maximum number of requests per period + limit: number; + + // time period which the limit is enforce against in seconds + period: number; +}; + +export namespace PrebuildRateLimiterConfig { + const DEFAULT_CONFIG: PrebuildRateLimiterConfig = { + limit: 50, + period: 50, + }; + + export function getConfigForCloneURL( + rateLimiters: PrebuildRateLimiters, + cloneURL: string, + ): PrebuildRateLimiterConfig { + // First we use any explicit overrides for a given cloneURL + let config = rateLimiters[cloneURL]; + if (config) { + return config; + } + + // Find if there is a default value set under the '*' key + config = rateLimiters["*"]; + if (config) { + return config; + } + + // Last resort default + return DEFAULT_CONFIG; + } +} diff --git a/install/installer/cmd/testdata/render/aws-setup/output.golden b/install/installer/cmd/testdata/render/aws-setup/output.golden index 62e6e5371f0ff6..a4bab5eb6ecdf8 100644 --- a/install/installer/cmd/testdata/render/aws-setup/output.golden +++ b/install/installer/cmd/testdata/render/aws-setup/output.golden @@ -4853,7 +4853,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -9254,7 +9257,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: d6614af994531fe5c31ddff93c3f5c9ae4eacae9dc0bcb278d23cd44d0bc4b41 + gitpod.io/checksum_config: b32c163fe13c4dd3dbabb7512d3fea34e61192c7b703a882669b339922630979 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/cmd/testdata/render/azure-setup/output.golden b/install/installer/cmd/testdata/render/azure-setup/output.golden index a4263268b76445..6dd9f8a504461b 100644 --- a/install/installer/cmd/testdata/render/azure-setup/output.golden +++ b/install/installer/cmd/testdata/render/azure-setup/output.golden @@ -4716,7 +4716,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -9105,7 +9108,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: d6614af994531fe5c31ddff93c3f5c9ae4eacae9dc0bcb278d23cd44d0bc4b41 + gitpod.io/checksum_config: b32c163fe13c4dd3dbabb7512d3fea34e61192c7b703a882669b339922630979 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/cmd/testdata/render/customization/output.golden b/install/installer/cmd/testdata/render/customization/output.golden index df479624da9a1f..4a4ce880feee56 100644 --- a/install/installer/cmd/testdata/render/customization/output.golden +++ b/install/installer/cmd/testdata/render/customization/output.golden @@ -5692,7 +5692,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -10716,7 +10719,7 @@ spec: metadata: annotations: gitpod.io: hello - gitpod.io/checksum_config: 671903bbf173feb891de94d4380d0f3360b0e8b4c4b3aeac6d49b7544f5ee697 + gitpod.io/checksum_config: b963c6af8760581ce8807544183b759ea2e40fc1a23232d1fc60df13e817c937 hello: world creationTimestamp: null labels: diff --git a/install/installer/cmd/testdata/render/external-registry/output.golden b/install/installer/cmd/testdata/render/external-registry/output.golden index dab48f73e7255e..ba6142ccba8b75 100644 --- a/install/installer/cmd/testdata/render/external-registry/output.golden +++ b/install/installer/cmd/testdata/render/external-registry/output.golden @@ -4903,7 +4903,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -9531,7 +9534,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: a94779de77b43b080faf67667aa017f25fc963c43412f8438c49ace3a7b7af2a + gitpod.io/checksum_config: a369165008e0cfac09c1b83051c9c686c06acf116907f80e46cf73b105f55719 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/cmd/testdata/render/gcp-setup/output.golden b/install/installer/cmd/testdata/render/gcp-setup/output.golden index 990f1290832a91..a8cc03f75027a0 100644 --- a/install/installer/cmd/testdata/render/gcp-setup/output.golden +++ b/install/installer/cmd/testdata/render/gcp-setup/output.golden @@ -4677,7 +4677,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -9022,7 +9025,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: fce060cf17c7827eebde2eb3483be41a293d2ad20607890b0e23bded75346d93 + gitpod.io/checksum_config: 67d25bb8e71032ee17ede85e68359e908eb6b74de606d59d9a0ba9168bfccc38 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/cmd/testdata/render/http-proxy/output.golden b/install/installer/cmd/testdata/render/http-proxy/output.golden index 3a250c0773a6ce..4fdddef0141a11 100644 --- a/install/installer/cmd/testdata/render/http-proxy/output.golden +++ b/install/installer/cmd/testdata/render/http-proxy/output.golden @@ -5126,7 +5126,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -11072,7 +11075,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: a94779de77b43b080faf67667aa017f25fc963c43412f8438c49ace3a7b7af2a + gitpod.io/checksum_config: a369165008e0cfac09c1b83051c9c686c06acf116907f80e46cf73b105f55719 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/cmd/testdata/render/insecure-s3-setup/output.golden b/install/installer/cmd/testdata/render/insecure-s3-setup/output.golden index 7eca4d6cfdd2f7..9c5191fd0bd2c5 100644 --- a/install/installer/cmd/testdata/render/insecure-s3-setup/output.golden +++ b/install/installer/cmd/testdata/render/insecure-s3-setup/output.golden @@ -5037,7 +5037,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -9677,7 +9680,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: a94779de77b43b080faf67667aa017f25fc963c43412f8438c49ace3a7b7af2a + gitpod.io/checksum_config: a369165008e0cfac09c1b83051c9c686c06acf116907f80e46cf73b105f55719 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/cmd/testdata/render/kind-meta/output.golden b/install/installer/cmd/testdata/render/kind-meta/output.golden index 5a72d7b3984ff3..2166d24a9d3547 100644 --- a/install/installer/cmd/testdata/render/kind-meta/output.golden +++ b/install/installer/cmd/testdata/render/kind-meta/output.golden @@ -4003,7 +4003,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -7111,7 +7114,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: a94779de77b43b080faf67667aa017f25fc963c43412f8438c49ace3a7b7af2a + gitpod.io/checksum_config: a369165008e0cfac09c1b83051c9c686c06acf116907f80e46cf73b105f55719 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/cmd/testdata/render/kind-webapp/output.golden b/install/installer/cmd/testdata/render/kind-webapp/output.golden index 1b0ac12151e2c1..a73007725c9050 100644 --- a/install/installer/cmd/testdata/render/kind-webapp/output.golden +++ b/install/installer/cmd/testdata/render/kind-webapp/output.golden @@ -2288,7 +2288,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -4315,7 +4318,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: a94779de77b43b080faf67667aa017f25fc963c43412f8438c49ace3a7b7af2a + gitpod.io/checksum_config: a369165008e0cfac09c1b83051c9c686c06acf116907f80e46cf73b105f55719 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/cmd/testdata/render/minimal/output.golden b/install/installer/cmd/testdata/render/minimal/output.golden index 009f16a60edb26..8f8dda65926a92 100644 --- a/install/installer/cmd/testdata/render/minimal/output.golden +++ b/install/installer/cmd/testdata/render/minimal/output.golden @@ -5123,7 +5123,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -9906,7 +9909,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: a94779de77b43b080faf67667aa017f25fc963c43412f8438c49ace3a7b7af2a + gitpod.io/checksum_config: a369165008e0cfac09c1b83051c9c686c06acf116907f80e46cf73b105f55719 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/cmd/testdata/render/shortname/output.golden b/install/installer/cmd/testdata/render/shortname/output.golden index b4d32d01891934..481b14ea82a499 100644 --- a/install/installer/cmd/testdata/render/shortname/output.golden +++ b/install/installer/cmd/testdata/render/shortname/output.golden @@ -5123,7 +5123,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -9906,7 +9909,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: fb70098b8891802e254f67cb2cab3ff40389429d959e77d3733a7eeba60399e5 + gitpod.io/checksum_config: 547d4ea1c8405743d18271c02b7eab2469c1eee1888ef5ceaeaff41fe4946333 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/cmd/testdata/render/statefulset-customization/output.golden b/install/installer/cmd/testdata/render/statefulset-customization/output.golden index 36e09373efed61..b6d76af47a1bd9 100644 --- a/install/installer/cmd/testdata/render/statefulset-customization/output.golden +++ b/install/installer/cmd/testdata/render/statefulset-customization/output.golden @@ -5135,7 +5135,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -9918,7 +9921,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: a94779de77b43b080faf67667aa017f25fc963c43412f8438c49ace3a7b7af2a + gitpod.io/checksum_config: a369165008e0cfac09c1b83051c9c686c06acf116907f80e46cf73b105f55719 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/cmd/testdata/render/use-pod-security-policies/output.golden b/install/installer/cmd/testdata/render/use-pod-security-policies/output.golden index 7acc39b36d711c..1a8f616448a273 100644 --- a/install/installer/cmd/testdata/render/use-pod-security-policies/output.golden +++ b/install/installer/cmd/testdata/render/use-pod-security-policies/output.golden @@ -5456,7 +5456,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -10350,7 +10353,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: a94779de77b43b080faf67667aa017f25fc963c43412f8438c49ace3a7b7af2a + gitpod.io/checksum_config: a369165008e0cfac09c1b83051c9c686c06acf116907f80e46cf73b105f55719 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/cmd/testdata/render/vsxproxy-pvc/output.golden b/install/installer/cmd/testdata/render/vsxproxy-pvc/output.golden index c3306bf94bfbd9..ba2a2de0894afa 100644 --- a/install/installer/cmd/testdata/render/vsxproxy-pvc/output.golden +++ b/install/installer/cmd/testdata/render/vsxproxy-pvc/output.golden @@ -5125,7 +5125,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -9896,7 +9899,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: a94779de77b43b080faf67667aa017f25fc963c43412f8438c49ace3a7b7af2a + gitpod.io/checksum_config: a369165008e0cfac09c1b83051c9c686c06acf116907f80e46cf73b105f55719 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/cmd/testdata/render/workspace-requests-limits/output.golden b/install/installer/cmd/testdata/render/workspace-requests-limits/output.golden index 41341284f3a7c1..a5af06f8ab9b19 100644 --- a/install/installer/cmd/testdata/render/workspace-requests-limits/output.golden +++ b/install/installer/cmd/testdata/render/workspace-requests-limits/output.golden @@ -5126,7 +5126,10 @@ data: "resources": null }, "prebuildLimiter": { - "*": 50 + "*": { + "limit": 100, + "period": 600 + } }, "workspaceClasses": [ { @@ -9909,7 +9912,7 @@ spec: template: metadata: annotations: - gitpod.io/checksum_config: a94779de77b43b080faf67667aa017f25fc963c43412f8438c49ace3a7b7af2a + gitpod.io/checksum_config: a369165008e0cfac09c1b83051c9c686c06acf116907f80e46cf73b105f55719 creationTimestamp: null labels: app: gitpod diff --git a/install/installer/pkg/components/server/configmap.go b/install/installer/pkg/components/server/configmap.go index 10157718f1a947..43f70a00637286 100644 --- a/install/installer/pkg/components/server/configmap.go +++ b/install/installer/pkg/components/server/configmap.go @@ -260,9 +260,12 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { ChargebeeProviderOptionsFile: fmt.Sprintf("%s/providerOptions", chargebeeMountPath), StripeSecretsFile: fmt.Sprintf("%s/apikeys", stripeSecretMountPath), InsecureNoDomain: false, - PrebuildLimiter: map[string]int{ + PrebuildLimiter: PrebuildRateLimiters{ // default limit for all cloneURLs - "*": 50, + "*": PrebuildRateLimiterConfig{ + Limit: 100, + Period: 600, + }, }, WorkspaceClasses: workspaceClasses, InactivityPeriodForReposInDays: inactivityPeriodForReposInDays, diff --git a/install/installer/pkg/components/server/types.go b/install/installer/pkg/components/server/types.go index ba39f1f76bc140..b22352d7695065 100644 --- a/install/installer/pkg/components/server/types.go +++ b/install/installer/pkg/components/server/types.go @@ -53,9 +53,9 @@ type ConfigSerialized struct { CodeSync CodeSync `json:"codeSync"` // PrebuildLimiter defines the number of prebuilds allowed for each cloneURL in a given 1 minute interval // Key of "*" defines the default limit, unless there exists a cloneURL in the map which overrides it. - PrebuildLimiter map[string]int `json:"prebuildLimiter"` - WorkspaceClasses []WorkspaceClass `json:"workspaceClasses"` - InactivityPeriodForReposInDays int `json:"inactivityPeriodForReposInDays"` + PrebuildLimiter PrebuildRateLimiters `json:"prebuildLimiter"` + WorkspaceClasses []WorkspaceClass `json:"workspaceClasses"` + InactivityPeriodForReposInDays int `json:"inactivityPeriodForReposInDays"` } type CodeSyncResources struct { RevLimit int32 `json:"revLimit"` @@ -157,3 +157,10 @@ type WorkspaceClassCategory string const ( GeneralPurpose WorkspaceClassCategory = "GENERAL PURPOSE" ) + +type PrebuildRateLimiters = map[string]PrebuildRateLimiterConfig + +type PrebuildRateLimiterConfig struct { + Limit uint32 `json:"limit"` + Period uint32 `json:"period"` +} diff --git a/install/installer/pkg/components/slowserver/configmap.go b/install/installer/pkg/components/slowserver/configmap.go index 63ae60c962c2e2..604d13b24a3a45 100644 --- a/install/installer/pkg/components/slowserver/configmap.go +++ b/install/installer/pkg/components/slowserver/configmap.go @@ -242,9 +242,12 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) { StripeSecretsFile: fmt.Sprintf("%s/apikeys", stripeSecretMountPath), StripeConfigFile: fmt.Sprintf("%s/config", stripeConfigMountPath), InsecureNoDomain: false, - PrebuildLimiter: map[string]int{ + PrebuildLimiter: server.PrebuildRateLimiters{ // default limit for all cloneURLs - "*": 50, + "*": server.PrebuildRateLimiterConfig{ + Limit: 100, + Period: 600, + }, }, WorkspaceClasses: workspaceClasses, InactivityPeriodForReposInDays: inactivityPeriodForReposInDays,