From 3e6887b40c46e8ce14a7ea34e4b22f0a38c83d58 Mon Sep 17 00:00:00 2001 From: Gero Posmyk-Leinemann Date: Fri, 10 Feb 2023 15:43:28 +0000 Subject: [PATCH 1/2] [server] EntitlementService: remove unnecessary dependency --- .../server/ee/src/billing/entitlement-service-license.ts | 2 +- components/server/ee/src/billing/entitlement-service-ubp.ts | 4 ++-- components/server/src/billing/entitlement-service.ts | 3 ++- components/server/src/user/user-service.ts | 5 ++++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/components/server/ee/src/billing/entitlement-service-license.ts b/components/server/ee/src/billing/entitlement-service-license.ts index 458db0ca398933..5c5da2f18de8bf 100644 --- a/components/server/ee/src/billing/entitlement-service-license.ts +++ b/components/server/ee/src/billing/entitlement-service-license.ts @@ -28,7 +28,7 @@ export class EntitlementServiceLicense implements EntitlementService { async mayStartWorkspace( user: User, - workspace: Workspace, + workspace: Pick, date: Date, runningInstances: Promise, ): Promise { diff --git a/components/server/ee/src/billing/entitlement-service-ubp.ts b/components/server/ee/src/billing/entitlement-service-ubp.ts index f136390fe0d7ac..a0109a2fac6ce9 100644 --- a/components/server/ee/src/billing/entitlement-service-ubp.ts +++ b/components/server/ee/src/billing/entitlement-service-ubp.ts @@ -47,7 +47,7 @@ export class EntitlementServiceUBP implements EntitlementService { async mayStartWorkspace( user: User, - workspace: Workspace, + workspace: Pick, date: Date, runningInstances: Promise, ): Promise { @@ -75,7 +75,7 @@ export class EntitlementServiceUBP implements EntitlementService { protected async checkUsageLimitReached( user: User, - workspace: Workspace, + workspace: Pick, date: Date, ): Promise { const result = await this.userService.checkUsageLimitReached(user, workspace); diff --git a/components/server/src/billing/entitlement-service.ts b/components/server/src/billing/entitlement-service.ts index 6628fae52d0a4d..691f2370b03578 100644 --- a/components/server/src/billing/entitlement-service.ts +++ b/components/server/src/billing/entitlement-service.ts @@ -17,6 +17,7 @@ import { injectable } from "inversify"; export interface MayStartWorkspaceResult { hitParallelWorkspaceLimit?: HitParallelWorkspaceLimit; + //** Out of Chargebee credits? */ oufOfCredits?: boolean; needsVerification?: boolean; @@ -42,7 +43,7 @@ export interface EntitlementService { */ mayStartWorkspace( user: User, - workspace: Workspace, + workspace: Pick, date: Date, runningInstances: Promise, ): Promise; diff --git a/components/server/src/user/user-service.ts b/components/server/src/user/user-service.ts index 2b0ac78cb86d1d..50770c68ba49eb 100644 --- a/components/server/src/user/user-service.ts +++ b/components/server/src/user/user-service.ts @@ -244,7 +244,10 @@ export class UserService { * @param workspace - optional, in which case the default billing account will be checked * @returns */ - async checkUsageLimitReached(user: User, workspace?: Workspace): Promise { + async checkUsageLimitReached( + user: User, + workspace?: Pick, + ): Promise { const attributionId = await this.getWorkspaceUsageAttributionId(user, workspace?.projectId); const creditBalance = await this.usageService.getCurrentBalance(attributionId); const currentInvoiceCredits = creditBalance.usedCredits; From 7027ed4c24f82b6a3c11f5f4a99fe3cc19d9fec7 Mon Sep 17 00:00:00 2001 From: Gero Posmyk-Leinemann Date: Fri, 10 Feb 2023 15:53:17 +0000 Subject: [PATCH 2/2] [server] Don't start a prebuild if the usage limit got reached --- .../ee/src/prebuilds/prebuild-manager.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/components/server/ee/src/prebuilds/prebuild-manager.ts b/components/server/ee/src/prebuilds/prebuild-manager.ts index b4dce898379185..d62c6fc96e4aa2 100644 --- a/components/server/ee/src/prebuilds/prebuild-manager.ts +++ b/components/server/ee/src/prebuilds/prebuild-manager.ts @@ -38,6 +38,7 @@ import { PrebuildRateLimiterConfig } from "../../../src/workspace/prebuild-rate- import { ResponseError } from "vscode-ws-jsonrpc"; import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; import { UserService } from "../../../src/user/user-service"; +import { EntitlementService, MayStartWorkspaceResult } from "../../../src/billing/entitlement-service"; export class WorkspaceRunningError extends Error { constructor(msg: string, public instance: WorkspaceInstance) { @@ -65,6 +66,7 @@ export class PrebuildManager { @inject(IncrementalPrebuildsService) protected readonly incrementalPrebuildsService: IncrementalPrebuildsService; @inject(UserService) protected readonly userService: UserService; @inject(TeamDB) protected readonly teamDB: TeamDB; + @inject(EntitlementService) protected readonly entitlementService: EntitlementService; async abortPrebuildsForBranch(ctx: TraceContext, project: Project, user: User, branch: string): Promise { const span = TraceContext.startSpan("abortPrebuildsForBranch", ctx); @@ -137,6 +139,7 @@ export class PrebuildManager { `Running prebuilds without a project is no longer supported. Please add '${cloneURL}' as a project in a team.`, ); } + await this.checkUsageLimitReached(user, project); // throws if true const config = await this.fetchConfig({ span }, user, context); @@ -293,6 +296,26 @@ export class PrebuildManager { } } + protected async checkUsageLimitReached(user: User, project: Project): Promise { + let result: MayStartWorkspaceResult = {}; + try { + result = await this.entitlementService.mayStartWorkspace( + user, + { projectId: project.id }, + new Date(), + Promise.resolve([]), + ); + } catch (err) { + log.error({ userId: user.id }, "EntitlementSerivce.mayStartWorkspace error", err); + return; // we don't want to block workspace starts because of internal errors + } + if (!!result.usageLimitReachedOnCostCenter) { + throw new ResponseError(ErrorCodes.PAYMENT_SPENDING_LIMIT_REACHED, "Increase usage limit and try again.", { + attributionId: result.usageLimitReachedOnCostCenter, + }); + } + } + async retriggerPrebuild( ctx: TraceContext, user: User,