Skip to content

Commit 8d05a11

Browse files
committed
[server] Cleanup around EntitlementService and BillingMode
1 parent 959bdcd commit 8d05a11

File tree

6 files changed

+96
-134
lines changed

6 files changed

+96
-134
lines changed

components/server/src/billing/billing-mode.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import { inject, injectable } from "inversify";
88

9-
import { User } from "@gitpod/gitpod-protocol";
109
import { Config } from "../config";
1110
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
1211
import { CostCenter_BillingStrategy } from "@gitpod/usage-api/lib/usage/v1/usage.pb";
@@ -22,7 +21,7 @@ export class BillingModes {
2221
@inject(UsageService) private readonly usageService: UsageService,
2322
) {}
2423

25-
public async getBillingMode(userId: string, organizationId: string, now: Date): Promise<BillingMode> {
24+
public async getBillingMode(userId: string, organizationId: string): Promise<BillingMode> {
2625
if (!this.config.enablePayment) {
2726
// Payment is not enabled. E.g. Dedicated
2827
return { mode: "none" };
@@ -33,7 +32,7 @@ export class BillingModes {
3332
return { mode: "usage-based", paid };
3433
}
3534

36-
async getBillingModeForUser(user: User, now: Date): Promise<BillingMode> {
35+
async getBillingModeForUser(): Promise<BillingMode> {
3736
if (!this.config.enablePayment) {
3837
// Payment is not enabled. E.g. Self-Hosted.
3938
return { mode: "none" };

components/server/src/billing/entitlement-service-ubp.ts

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66

77
import { TeamDB } from "@gitpod/gitpod-db/lib";
88
import {
9-
BillingTier,
10-
Team,
11-
User,
129
WorkspaceInstance,
1310
WorkspaceTimeoutDuration,
1411
WORKSPACE_TIMEOUT_DEFAULT_LONG,
1512
WORKSPACE_TIMEOUT_DEFAULT_SHORT,
1613
WORKSPACE_LIFETIME_LONG,
1714
WORKSPACE_LIFETIME_SHORT,
15+
User,
16+
BillingTier,
17+
Team,
1818
} from "@gitpod/gitpod-protocol";
1919
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
2020
import { inject, injectable } from "inversify";
@@ -38,11 +38,10 @@ export class EntitlementServiceUBP implements EntitlementService {
3838
async mayStartWorkspace(
3939
user: User,
4040
organizationId: string,
41-
date: Date,
4241
runningInstances: Promise<WorkspaceInstance[]>,
4342
): Promise<MayStartWorkspaceResult> {
4443
const hasHitParallelWorkspaceLimit = async (): Promise<HitParallelWorkspaceLimit | undefined> => {
45-
const max = await this.getMaxParallelWorkspaces(user, date);
44+
const max = await this.getMaxParallelWorkspaces(user.id, organizationId);
4645
const current = (await runningInstances).filter((i) => i.status.phase !== "preparing").length;
4746
if (current >= max) {
4847
return {
@@ -54,7 +53,7 @@ export class EntitlementServiceUBP implements EntitlementService {
5453
}
5554
};
5655
const [usageLimitReachedOnCostCenter, hitParallelWorkspaceLimit] = await Promise.all([
57-
this.checkUsageLimitReached(user, organizationId, date),
56+
this.checkUsageLimitReached(user.id, organizationId),
5857
hasHitParallelWorkspaceLimit(),
5958
]);
6059
return {
@@ -63,69 +62,63 @@ export class EntitlementServiceUBP implements EntitlementService {
6362
};
6463
}
6564

66-
private async checkUsageLimitReached(
67-
user: User,
68-
organizationId: string,
69-
date: Date,
70-
): Promise<AttributionId | undefined> {
71-
const result = await this.usageService.checkUsageLimitReached(user.id, organizationId);
65+
private async checkUsageLimitReached(userId: string, organizationId: string): Promise<AttributionId | undefined> {
66+
const result = await this.usageService.checkUsageLimitReached(userId, organizationId);
7267
if (result.reached) {
7368
return result.attributionId;
7469
}
7570
return undefined;
7671
}
7772

78-
private async getMaxParallelWorkspaces(user: User, date: Date): Promise<number> {
79-
if (await this.hasPaidSubscription(user, date)) {
73+
private async getMaxParallelWorkspaces(userId: string, organizationId: string): Promise<number> {
74+
if (await this.hasPaidSubscription(userId, organizationId)) {
8075
return MAX_PARALLEL_WORKSPACES_PAID;
8176
} else {
8277
return MAX_PARALLEL_WORKSPACES_FREE;
8378
}
8479
}
8580

86-
async maySetTimeout(user: User, date: Date): Promise<boolean> {
87-
return this.hasPaidSubscription(user, date);
81+
async maySetTimeout(userId: string, organizationId?: string): Promise<boolean> {
82+
return this.hasPaidSubscription(userId, organizationId);
8883
}
8984

90-
async getDefaultWorkspaceTimeout(user: User, date: Date): Promise<WorkspaceTimeoutDuration> {
91-
if (await this.hasPaidSubscription(user, date)) {
85+
async getDefaultWorkspaceTimeout(userId: string, organizationId: string): Promise<WorkspaceTimeoutDuration> {
86+
if (await this.hasPaidSubscription(userId, organizationId)) {
9287
return WORKSPACE_TIMEOUT_DEFAULT_LONG;
9388
} else {
9489
return WORKSPACE_TIMEOUT_DEFAULT_SHORT;
9590
}
9691
}
9792

98-
async getDefaultWorkspaceLifetime(user: User, date: Date): Promise<WorkspaceTimeoutDuration> {
99-
if (await this.hasPaidSubscription(user, date)) {
93+
async getDefaultWorkspaceLifetime(userId: string, organizationId: string): Promise<WorkspaceTimeoutDuration> {
94+
if (await this.hasPaidSubscription(userId, organizationId)) {
10095
return WORKSPACE_LIFETIME_LONG;
10196
} else {
10297
return WORKSPACE_LIFETIME_SHORT;
10398
}
10499
}
105100

106-
/**
107-
* DEPRECATED: With usage-based billing, users can choose exactly how many resources they want to get.
108-
* Thus, we no longer need to "force" extra resources via the `userGetsMoreResources` mechanism.
109-
*/
110-
async userGetsMoreResources(user: User, date: Date = new Date()): Promise<boolean> {
111-
return false;
112-
}
113-
114101
/**
115102
* Returns true if network connections should be limited
116103
* @param user
117104
*/
118-
async limitNetworkConnections(user: User, date: Date): Promise<boolean> {
105+
async limitNetworkConnections(userId: string, organizationId: string): Promise<boolean> {
119106
// gpl: Because with the current payment handling (pay-after-use) having a "paid" plan is not a good enough classifier for trushworthyness atm.
120107
// We're looking into improving this, but for the meantime we limit network connections for everybody to reduce the impact of abuse.
121108
return true;
122109
}
123110

124-
private async hasPaidSubscription(user: User, date: Date): Promise<boolean> {
111+
private async hasPaidSubscription(userId: string, organizationId?: string): Promise<boolean> {
112+
if (organizationId) {
113+
// This is the "stricter", more correct version: We only allow privileges on the Organization that is paying for it
114+
const { billingStrategy } = await this.usageService.getCostCenter(userId, organizationId);
115+
return billingStrategy === CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE;
116+
}
117+
// This is the old behavior, stemming from our transition to PAYF, where our API did-/doesn't pass organizationId, yet
125118
// Member of paid team?
126-
const teams = await this.teamDB.findTeamsByUser(user.id);
119+
const teams = await this.teamDB.findTeamsByUser(userId);
127120
const isTeamSubscribedPromises = teams.map(async (team: Team) => {
128-
const { billingStrategy } = await this.usageService.getCostCenter(user.id, team.id);
121+
const { billingStrategy } = await this.usageService.getCostCenter(userId, team.id);
129122
return billingStrategy === CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE;
130123
});
131124
// Return the first truthy promise, or false if all the promises were falsy.
@@ -147,8 +140,8 @@ export class EntitlementServiceUBP implements EntitlementService {
147140
});
148141
}
149142

150-
async getBillingTier(user: User): Promise<BillingTier> {
151-
const hasPaidPlan = await this.hasPaidSubscription(user, new Date());
143+
async getBillingTier(userId: string, organizationId: string): Promise<BillingTier> {
144+
const hasPaidPlan = await this.hasPaidSubscription(userId, organizationId);
152145
return hasPaidPlan ? "paid" : "free";
153146
}
154147
}

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

Lines changed: 40 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -47,49 +47,44 @@ export interface EntitlementService {
4747
mayStartWorkspace(
4848
user: User,
4949
organizationId: string,
50-
date: Date,
5150
runningInstances: Promise<WorkspaceInstance[]>,
5251
): Promise<MayStartWorkspaceResult>;
5352

5453
/**
5554
* A user may set the workspace timeout if they have a professional subscription
56-
* @param user
57-
* @param date The date for which we want to know whether the user is allowed to set a timeout (depends on active subscription)
55+
* @param userId
56+
* @param organizationId
5857
*/
59-
maySetTimeout(user: User, date: Date): Promise<boolean>;
58+
maySetTimeout(userId: string, organizationId?: string): Promise<boolean>;
6059

6160
/**
6261
* Returns the default workspace timeout for the given user at a given point in time
63-
* @param user
64-
* @param date The date for which we want to know the default workspace timeout (depends on active subscription)
62+
* @param userId
63+
* @param organizationId
6564
*/
66-
getDefaultWorkspaceTimeout(user: User, date: Date): Promise<WorkspaceTimeoutDuration>;
65+
getDefaultWorkspaceTimeout(userId: string, organizationId: string): Promise<WorkspaceTimeoutDuration>;
6766

6867
/**
6968
* Returns the default workspace lifetime for the given user at a given point in time
70-
* @param user
71-
* @param date The date for which we want to know the default workspace timeout (depends on active subscription)
72-
*/
73-
getDefaultWorkspaceLifetime(user: User, date: Date): Promise<WorkspaceTimeoutDuration>;
74-
75-
/**
76-
* Returns true if the user ought to land on a workspace cluster that provides more resources
77-
* compared to the default case.
69+
* @param userId
70+
* @param organizationId
7871
*/
79-
userGetsMoreResources(user: User): Promise<boolean>;
72+
getDefaultWorkspaceLifetime(userId: string, organizationId: string): Promise<WorkspaceTimeoutDuration>;
8073

8174
/**
8275
* Returns true if network connections should be limited
83-
* @param user
76+
* @param userId
77+
* @param organizationId
8478
*/
85-
limitNetworkConnections(user: User, date: Date): Promise<boolean>;
79+
limitNetworkConnections(userId: string, organizationId: string): Promise<boolean>;
8680

8781
/**
88-
* Returns BillingTier of this particular user
82+
* Returns BillingTier of this organization
8983
*
90-
* @param user
84+
* @param userId
85+
* @param organizationId
9186
*/
92-
getBillingTier(user: User): Promise<BillingTier>;
87+
getBillingTier(userId: string, organizationId: string): Promise<BillingTier>;
9388
}
9489

9590
/**
@@ -107,7 +102,6 @@ export class EntitlementServiceImpl implements EntitlementService {
107102
async mayStartWorkspace(
108103
user: User,
109104
organizationId: string,
110-
date: Date = new Date(),
111105
runningInstances: Promise<WorkspaceInstance[]>,
112106
): Promise<MayStartWorkspaceResult> {
113107
try {
@@ -117,13 +111,13 @@ export class EntitlementServiceImpl implements EntitlementService {
117111
needsVerification: true,
118112
};
119113
}
120-
const billingMode = await this.billingModes.getBillingModeForUser(user, date);
114+
const billingMode = await this.billingModes.getBillingMode(user.id, organizationId);
121115
switch (billingMode.mode) {
122116
case "none":
123117
// if payment is not enabled users can start as many parallel workspaces as they want
124118
return {};
125119
case "usage-based":
126-
return this.ubp.mayStartWorkspace(user, organizationId, date, runningInstances);
120+
return this.ubp.mayStartWorkspace(user, organizationId, runningInstances);
127121
default:
128122
throw new Error("Unsupported billing mode: " + (billingMode as any).mode); // safety net
129123
}
@@ -133,104 +127,87 @@ export class EntitlementServiceImpl implements EntitlementService {
133127
}
134128
}
135129

136-
async maySetTimeout(user: User, date: Date = new Date()): Promise<boolean> {
130+
async maySetTimeout(userId: string, organizationId?: string): Promise<boolean> {
137131
try {
138-
const billingMode = await this.billingModes.getBillingModeForUser(user, date);
132+
const billingMode = await this.billingModes.getBillingModeForUser();
139133
switch (billingMode.mode) {
140134
case "none":
141135
// when payment is disabled users can do everything
142136
return true;
143137
case "usage-based":
144-
return this.ubp.maySetTimeout(user, date);
138+
return this.ubp.maySetTimeout(userId, organizationId);
145139
}
146140
} catch (err) {
147-
log.error({ userId: user.id }, "EntitlementService error: maySetTimeout", err);
141+
log.error({ userId }, "EntitlementService error: maySetTimeout", err);
148142
return true;
149143
}
150144
}
151145

152-
async getDefaultWorkspaceTimeout(user: User, date: Date = new Date()): Promise<WorkspaceTimeoutDuration> {
146+
async getDefaultWorkspaceTimeout(userId: string, organizationId: string): Promise<WorkspaceTimeoutDuration> {
153147
try {
154-
const billingMode = await this.billingModes.getBillingModeForUser(user, date);
148+
const billingMode = await this.billingModes.getBillingMode(userId, organizationId);
155149
switch (billingMode.mode) {
156150
case "none":
157151
return WORKSPACE_TIMEOUT_DEFAULT_LONG;
158152
case "usage-based":
159-
return this.ubp.getDefaultWorkspaceTimeout(user, date);
153+
return this.ubp.getDefaultWorkspaceTimeout(userId, organizationId);
160154
}
161155
} catch (err) {
162-
log.error({ userId: user.id }, "EntitlementService error: getDefaultWorkspaceTimeout", err);
156+
log.error({ userId }, "EntitlementService error: getDefaultWorkspaceTimeout", err);
163157
return WORKSPACE_TIMEOUT_DEFAULT_LONG;
164158
}
165159
}
166160

167-
async getDefaultWorkspaceLifetime(user: User, date: Date = new Date()): Promise<WorkspaceTimeoutDuration> {
161+
async getDefaultWorkspaceLifetime(userId: string, organizationId: string): Promise<WorkspaceTimeoutDuration> {
168162
try {
169-
const billingMode = await this.billingModes.getBillingModeForUser(user, date);
163+
const billingMode = await this.billingModes.getBillingMode(userId, organizationId);
170164
switch (billingMode.mode) {
171165
case "none":
172166
return WORKSPACE_LIFETIME_LONG;
173167
case "usage-based":
174-
return this.ubp.getDefaultWorkspaceLifetime(user, date);
168+
return this.ubp.getDefaultWorkspaceLifetime(userId, organizationId);
175169
}
176170
} catch (err) {
177-
log.error({ userId: user.id }, "EntitlementService error: getDefaultWorkspaceLifetime", err);
171+
log.error({ userId }, "EntitlementService error: getDefaultWorkspaceLifetime", err);
178172
return WORKSPACE_LIFETIME_LONG;
179173
}
180174
}
181175

182-
async userGetsMoreResources(user: User, date: Date = new Date()): Promise<boolean> {
183-
try {
184-
const billingMode = await this.billingModes.getBillingModeForUser(user, date);
185-
switch (billingMode.mode) {
186-
case "none":
187-
// TODO(gpl) Not sure this makes sense, but it's the way it was before
188-
return false;
189-
case "usage-based":
190-
return this.ubp.userGetsMoreResources(user);
191-
}
192-
} catch (err) {
193-
log.error({ userId: user.id }, "EntitlementService error: userGetsMoreResources", err);
194-
return true;
195-
}
196-
}
197-
198176
/**
199177
* Returns true if network connections should be limited
200178
* @param user
201179
*/
202-
async limitNetworkConnections(user: User, date: Date): Promise<boolean> {
180+
async limitNetworkConnections(userId: string, organizationId: string): Promise<boolean> {
203181
try {
204-
const billingMode = await this.billingModes.getBillingModeForUser(user, date);
182+
const billingMode = await this.billingModes.getBillingMode(userId, organizationId);
205183
switch (billingMode.mode) {
206184
case "none":
207185
return false;
208186
case "usage-based":
209-
return this.ubp.limitNetworkConnections(user, date);
187+
return this.ubp.limitNetworkConnections(userId, organizationId);
210188
}
211189
} catch (err) {
212-
log.error({ userId: user.id }, "EntitlementService error: limitNetworkConnections", err);
190+
log.error({ userId }, "EntitlementService error: limitNetworkConnections", err);
213191
return false;
214192
}
215193
}
216194

217195
/**
218196
* Returns true if network connections should be limited
219-
* @param user
197+
* @param userId
198+
* @param organizationId
220199
*/
221-
async getBillingTier(user: User): Promise<BillingTier> {
200+
async getBillingTier(userId: string, organizationId: string): Promise<BillingTier> {
222201
try {
223-
const now = new Date();
224-
const billingMode = await this.billingModes.getBillingModeForUser(user, now);
202+
const billingMode = await this.billingModes.getBillingMode(userId, organizationId);
225203
switch (billingMode.mode) {
226204
case "none":
227-
// TODO(gpl) Is this true? Cross-check this whole interface with Self-Hosted before next release!
228205
return "paid";
229206
case "usage-based":
230-
return this.ubp.getBillingTier(user);
207+
return billingMode.paid ? "paid" : "free";
231208
}
232209
} catch (err) {
233-
log.error({ userId: user.id }, "EntitlementService error: getBillingTier", err);
210+
log.error({ userId }, "EntitlementService error: getBillingTier", err);
234211
return "paid";
235212
}
236213
}

components/server/src/prebuilds/prebuild-manager.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -293,12 +293,7 @@ export class PrebuildManager {
293293
protected async checkUsageLimitReached(user: User, organizationId: string): Promise<void> {
294294
let result: MayStartWorkspaceResult = {};
295295
try {
296-
result = await this.entitlementService.mayStartWorkspace(
297-
user,
298-
organizationId,
299-
new Date(),
300-
Promise.resolve([]),
301-
);
296+
result = await this.entitlementService.mayStartWorkspace(user, organizationId, Promise.resolve([]));
302297
} catch (err) {
303298
log.error({ userId: user.id }, "EntitlementService.mayStartWorkspace error", err);
304299
return; // we don't want to block workspace starts because of internal errors

0 commit comments

Comments
 (0)