Skip to content

Commit 7025b03

Browse files
committed
[server] Separate EntitlementServiceLicense from EntitlementServiceChargebee
1 parent 6390f20 commit 7025b03

File tree

8 files changed

+122
-90
lines changed

8 files changed

+122
-90
lines changed

components/server/ee/src/billing/entitlement-service-chargebee.ts

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,11 @@ export class EntitlementServiceChargebee implements EntitlementService {
3131
@inject(TeamDB) protected readonly teamDb: TeamDB;
3232
@inject(TeamSubscription2DB) protected readonly teamSubscription2Db: TeamSubscription2DB;
3333

34-
/**
35-
* Whether a user is allowed to start a workspace
36-
* !!! This is executed on the hot path of workspace startup, be careful with async when changing !!!
37-
* @param user
38-
* @param date now
39-
* @param runningInstances
40-
*/
4134
async mayStartWorkspace(
4235
user: User,
4336
date: Date,
4437
runningInstances: Promise<WorkspaceInstance[]>,
4538
): Promise<MayStartWorkspaceResult> {
46-
if (!this.config.enablePayment) {
47-
return { enoughCredits: true };
48-
}
49-
5039
const hasHitParallelWorkspaceLimit = async (): Promise<HitParallelWorkspaceLimit | undefined> => {
5140
const max = await this.getMaxParallelWorkspaces(user);
5241
const instances = (await runningInstances).filter((i) => i.status.phase !== "preparing");
@@ -77,11 +66,6 @@ export class EntitlementServiceChargebee implements EntitlementService {
7766
* @param date The date for which we want to know whether the user is allowed to set a timeout (depends on active subscription)
7867
*/
7968
protected async getMaxParallelWorkspaces(user: User, date: Date = new Date()): Promise<number> {
80-
// if payment is not enabled users can start as many parallel workspaces as they want
81-
if (!this.config.enablePayment) {
82-
return MAX_PARALLEL_WORKSPACES;
83-
}
84-
8569
const subscriptions = await this.subscriptionService.getNotYetCancelledSubscriptions(user, date.toISOString());
8670
return subscriptions.map((s) => Plans.getParallelWorkspacesById(s.planId)).reduce((p, v) => Math.max(p, v));
8771
}
@@ -126,17 +110,7 @@ export class EntitlementServiceChargebee implements EntitlementService {
126110
return cachedStatement.remainingHours - maxPossibleUsage;
127111
}
128112

129-
/**
130-
* A user may set the workspace timeout if they have a professional subscription
131-
* @param user
132-
* @param date The date for which we want to know whether the user is allowed to set a timeout (depends on active subscription)
133-
*/
134113
async maySetTimeout(user: User, date: Date = new Date()): Promise<boolean> {
135-
if (!this.config.enablePayment) {
136-
// when payment is disabled users can do everything
137-
return true;
138-
}
139-
140114
const subscriptions = await this.subscriptionService.getNotYetCancelledSubscriptions(user, date.toISOString());
141115
const eligblePlans = [
142116
Plans.PROFESSIONAL_EUR,
@@ -152,11 +126,6 @@ export class EntitlementServiceChargebee implements EntitlementService {
152126
return subscriptions.filter((s) => eligblePlans.includes(s.planId!)).length > 0;
153127
}
154128

155-
/**
156-
* Returns the default workspace timeout for the given user at a given point in time
157-
* @param user
158-
* @param date The date for which we want to know the default workspace timeout (depends on active subscription)
159-
*/
160129
async getDefaultWorkspaceTimeout(user: User, date: Date = new Date()): Promise<WorkspaceTimeoutDuration> {
161130
if (await this.maySetTimeout(user, date)) {
162131
return WORKSPACE_TIMEOUT_DEFAULT_LONG;
@@ -170,11 +139,6 @@ export class EntitlementServiceChargebee implements EntitlementService {
170139
* compared to the default case.
171140
*/
172141
async userGetsMoreResources(user: User): Promise<boolean> {
173-
if (!this.config.enablePayment) {
174-
// when payment is disabled users can do everything
175-
return true;
176-
}
177-
178142
const subscriptions = await this.subscriptionService.getNotYetCancelledSubscriptions(
179143
user,
180144
new Date().toISOString(),
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { UserDB } from "@gitpod/gitpod-db/lib";
8+
import {
9+
User,
10+
WorkspaceInstance,
11+
WorkspaceTimeoutDuration,
12+
WORKSPACE_TIMEOUT_DEFAULT_LONG,
13+
WORKSPACE_TIMEOUT_DEFAULT_SHORT,
14+
} from "@gitpod/gitpod-protocol";
15+
import { LicenseEvaluator } from "@gitpod/licensor/lib";
16+
import { Feature } from "@gitpod/licensor/lib/api";
17+
import { inject, injectable } from "inversify";
18+
import { EntitlementService } from "../../../src/billing/entitlement-service";
19+
import { Config } from "../../../src/config";
20+
import { MayStartWorkspaceResult } from "../user/eligibility-service";
21+
22+
@injectable()
23+
export class EntitlementServiceLicense implements EntitlementService {
24+
@inject(Config) protected readonly config: Config;
25+
@inject(UserDB) protected readonly userDb: UserDB;
26+
@inject(LicenseEvaluator) protected readonly licenseEvaluator: LicenseEvaluator;
27+
28+
async mayStartWorkspace(
29+
user: User,
30+
date: Date,
31+
runningInstances: Promise<WorkspaceInstance[]>,
32+
): Promise<MayStartWorkspaceResult> {
33+
// if payment is not enabled users can start as many parallel workspaces as they want
34+
return { enoughCredits: true };
35+
}
36+
37+
async maySetTimeout(user: User, date: Date): Promise<boolean> {
38+
// when payment is disabled users can do everything
39+
return true;
40+
}
41+
42+
async getDefaultWorkspaceTimeout(user: User, date: Date): Promise<WorkspaceTimeoutDuration> {
43+
const userCount = await this.userDb.getUserCount(true);
44+
45+
// the self-hosted case
46+
if (!this.licenseEvaluator.isEnabled(Feature.FeatureSetTimeout, userCount)) {
47+
return WORKSPACE_TIMEOUT_DEFAULT_SHORT;
48+
}
49+
50+
return WORKSPACE_TIMEOUT_DEFAULT_LONG;
51+
}
52+
53+
async userGetsMoreResources(user: User): Promise<boolean> {
54+
// TODO(gpl) Not sure this makes sense, but it's the way it was before
55+
return false;
56+
}
57+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { User, WorkspaceInstance, WorkspaceTimeoutDuration } from "@gitpod/gitpod-protocol";
8+
import { inject } from "inversify";
9+
import { EntitlementService } from "../../../src/billing/entitlement-service";
10+
import { Config } from "../../../src/config";
11+
import { MayStartWorkspaceResult } from "../user/eligibility-service";
12+
import { EntitlementServiceChargebee } from "./entitlement-service-chargebee";
13+
import { EntitlementServiceLicense } from "./entitlement-service-license";
14+
15+
/**
16+
* The default implementation for the Enterprise Edition (EE). It decides based on config which ruleset to choose for each call.
17+
*/
18+
export class EntitlementServiceImpl implements EntitlementService {
19+
@inject(Config) protected readonly config: Config;
20+
@inject(EntitlementServiceChargebee) protected readonly etsChargebee: EntitlementServiceChargebee;
21+
@inject(EntitlementServiceLicense) protected readonly etsLicense: EntitlementServiceLicense;
22+
23+
async mayStartWorkspace(
24+
user: User,
25+
date: Date,
26+
runningInstances: Promise<WorkspaceInstance[]>,
27+
): Promise<MayStartWorkspaceResult> {
28+
if (!this.config.enablePayment) {
29+
return await this.etsLicense.mayStartWorkspace(user, date, runningInstances);
30+
}
31+
return await this.etsChargebee.mayStartWorkspace(user, date, runningInstances);
32+
}
33+
34+
async maySetTimeout(user: User, date: Date): Promise<boolean> {
35+
if (!this.config.enablePayment) {
36+
return await this.etsLicense.maySetTimeout(user, date);
37+
}
38+
return await this.etsChargebee.maySetTimeout(user, date);
39+
}
40+
41+
async getDefaultWorkspaceTimeout(user: User, date: Date): Promise<WorkspaceTimeoutDuration> {
42+
if (!this.config.enablePayment) {
43+
return await this.etsLicense.getDefaultWorkspaceTimeout(user, date);
44+
}
45+
return await this.etsChargebee.getDefaultWorkspaceTimeout(user, date);
46+
}
47+
48+
async userGetsMoreResources(user: User): Promise<boolean> {
49+
if (!this.config.enablePayment) {
50+
return await this.etsLicense.userGetsMoreResources(user);
51+
}
52+
return await this.etsChargebee.userGetsMoreResources(user);
53+
}
54+
}

components/server/ee/src/container-module.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ import { BitbucketServerApp } from "./prebuilds/bitbucket-server-app";
6464
import { EntitlementService } from "../../src/billing/entitlement-service";
6565
import { EntitlementServiceChargebee } from "./billing/entitlement-service-chargebee";
6666
import { BillingModes, BillingModesImpl } from "./billing/billing-mode";
67+
import { EntitlementServiceLicense } from "./billing/entitlement-service-license";
68+
import { EntitlementServiceImpl } from "./billing/entitlement-service";
6769

6870
export const productionEEContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
6971
rebind(Server).to(ServerEE).inSingletonScope();
@@ -127,6 +129,8 @@ export const productionEEContainerModule = new ContainerModule((bind, unbind, is
127129
bind(StripeService).toSelf().inSingletonScope();
128130

129131
bind(EntitlementServiceChargebee).toSelf().inSingletonScope();
130-
rebind(EntitlementService).to(EntitlementServiceChargebee).inSingletonScope();
132+
bind(EntitlementServiceLicense).toSelf().inSingletonScope();
133+
bind(EntitlementServiceImpl).toSelf().inSingletonScope();
134+
rebind(EntitlementService).to(EntitlementServiceImpl).inSingletonScope();
131135
bind(BillingModes).to(BillingModesImpl).inSingletonScope();
132136
});

components/server/ee/src/user/user-service.ts

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
} from "@gitpod/gitpod-protocol";
1616
import { inject } from "inversify";
1717
import { LicenseEvaluator } from "@gitpod/licensor/lib";
18-
import { Feature } from "@gitpod/licensor/lib/api";
1918
import { AuthException } from "../../../src/auth/errors";
2019
import { SubscriptionService } from "@gitpod/gitpod-payment-endpoint/lib/accounting";
2120
import { OssAllowListDB } from "@gitpod/gitpod-db/lib/oss-allowlist-db";
@@ -31,23 +30,6 @@ export class UserServiceEE extends UserService {
3130
@inject(HostContextProvider) protected readonly hostContextProvider: HostContextProvider;
3231
@inject(Config) protected readonly config: Config;
3332

34-
// TODO(gpl) Needs to fold into EntitlementService
35-
async getDefaultWorkspaceTimeout(user: User, date: Date): Promise<WorkspaceTimeoutDuration> {
36-
if (this.config.enablePayment) {
37-
// the SaaS case
38-
return this.entitlementService.getDefaultWorkspaceTimeout(user, date);
39-
}
40-
41-
const userCount = await this.userDb.getUserCount(true);
42-
43-
// the self-hosted case
44-
if (!this.licenseEvaluator.isEnabled(Feature.FeatureSetTimeout, userCount)) {
45-
return WORKSPACE_TIMEOUT_DEFAULT_SHORT;
46-
}
47-
48-
return WORKSPACE_TIMEOUT_DEFAULT_LONG;
49-
}
50-
5133
public workspaceTimeoutToDuration(timeout: WorkspaceTimeoutDuration): string {
5234
switch (timeout) {
5335
case WORKSPACE_TIMEOUT_DEFAULT_SHORT:
@@ -73,15 +55,6 @@ export class UserServiceEE extends UserService {
7355
}
7456
}
7557

76-
// TODO(gpl) Needs to fold into EntitlementService
77-
async userGetsMoreResources(user: User): Promise<boolean> {
78-
if (this.config.enablePayment) {
79-
return this.entitlementService.userGetsMoreResources(user);
80-
}
81-
82-
return false;
83-
}
84-
8558
async checkSignUp(params: CheckSignUpParams) {
8659
// todo@at: check if we need an optimization for SaaS here. used to be a no-op there.
8760

components/server/ee/src/workspace/gitpod-server-impl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
331331

332332
// if any other running instance has a custom timeout other than the user's default, we'll reset that timeout
333333
const client = await this.workspaceManagerClientProvider.get(runningInstance.region);
334-
const defaultTimeout = await this.userService.getDefaultWorkspaceTimeout(user);
334+
const defaultTimeout = await this.entitlementService.getDefaultWorkspaceTimeout(user, new Date());
335335
const instancesWithReset = runningInstances.filter(
336336
(i) => i.workspaceId !== workspaceId && i.status.timeout !== defaultTimeout && i.status.phase === "running",
337337
);

components/server/src/user/user-service.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -166,16 +166,6 @@ export class UserService {
166166
}
167167
}
168168

169-
// TODO(gpl) Needs to fold into EntitlementService
170-
/**
171-
* Returns the default workspace timeout for the given user at a given point in time
172-
* @param user
173-
* @param date The date for which we want to know the default workspace timeout
174-
*/
175-
async getDefaultWorkspaceTimeout(user: User, date: Date = new Date()): Promise<WorkspaceTimeoutDuration> {
176-
return WORKSPACE_TIMEOUT_DEFAULT_SHORT;
177-
}
178-
179169
public workspaceTimeoutToDuration(timeout: WorkspaceTimeoutDuration): string {
180170
switch (timeout) {
181171
case WORKSPACE_TIMEOUT_DEFAULT_SHORT:
@@ -201,18 +191,6 @@ export class UserService {
201191
}
202192
}
203193

204-
// TODO(gpl) Needs to fold into EntitlementService
205-
/**
206-
* Returns true if the user ought land in a cluster which offers more resources than
207-
* the default.
208-
*
209-
* @param user user to check for
210-
* @returns
211-
*/
212-
async userGetsMoreResources(user: User): Promise<boolean> {
213-
return false;
214-
}
215-
216194
/**
217195
* Identifies the team or user to which a workspace instance's running time should be attributed to
218196
* (e.g. for usage analytics or billing purposes).

components/server/src/workspace/workspace-starter.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ import { IDEService } from "../ide-service";
118118
import { WorkspaceClusterImagebuilderClientProvider } from "./workspace-cluster-imagebuilder-client-provider";
119119
import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server";
120120
import { WorkspaceClasses } from "./workspace-classes";
121+
import { EntitlementService } from "../billing/entitlement-service";
121122

122123
export interface StartWorkspaceOptions {
123124
rethrow?: boolean;
@@ -211,6 +212,7 @@ export class WorkspaceStarter {
211212
@inject(ContextParser) protected contextParser: ContextParser;
212213
@inject(BlockedRepositoryDB) protected readonly blockedRepositoryDB: BlockedRepositoryDB;
213214
@inject(TeamDB) protected readonly teamDB: TeamDB;
215+
@inject(EntitlementService) protected readonly entitlementService: EntitlementService;
214216

215217
public async startWorkspace(
216218
ctx: TraceContext,
@@ -792,7 +794,7 @@ export class WorkspaceStarter {
792794

793795
if (!workspaceClass) {
794796
workspaceClass = WorkspaceClasses.getDefaultId(this.config.workspaceClasses);
795-
if (await this.userService.userGetsMoreResources(user)) {
797+
if (await this.entitlementService.userGetsMoreResources(user)) {
796798
workspaceClass = WorkspaceClasses.getMoreResourcesIdOrDefault(this.config.workspaceClasses);
797799
}
798800
}
@@ -1388,7 +1390,7 @@ export class WorkspaceStarter {
13881390
lastValidWorkspaceInstanceId,
13891391
volumeSnapshots !== undefined,
13901392
);
1391-
const userTimeoutPromise = this.userService.getDefaultWorkspaceTimeout(user);
1393+
const userTimeoutPromise = this.entitlementService.getDefaultWorkspaceTimeout(user, new Date());
13921394

13931395
let featureFlags = instance.configuration!.featureFlags || [];
13941396
if (volumeSnapshots !== undefined) {
@@ -1409,7 +1411,7 @@ export class WorkspaceStarter {
14091411
if (!classesEnabled) {
14101412
// This is branch is not relevant once we roll out WorkspaceClasses, so we don't try to integrate these old classes into our model
14111413
workspaceClass = "default";
1412-
if (await this.userService.userGetsMoreResources(user)) {
1414+
if (await this.entitlementService.userGetsMoreResources(user)) {
14131415
workspaceClass = "gitpodio-internal-xl";
14141416
}
14151417
} else {

0 commit comments

Comments
 (0)