-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[server] Cleanup around EntitlementService and BillingMode #18429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,15 +6,15 @@ | |
|
||
import { TeamDB } from "@gitpod/gitpod-db/lib"; | ||
import { | ||
BillingTier, | ||
Team, | ||
User, | ||
WorkspaceInstance, | ||
WorkspaceTimeoutDuration, | ||
WORKSPACE_TIMEOUT_DEFAULT_LONG, | ||
WORKSPACE_TIMEOUT_DEFAULT_SHORT, | ||
WORKSPACE_LIFETIME_LONG, | ||
WORKSPACE_LIFETIME_SHORT, | ||
User, | ||
BillingTier, | ||
Team, | ||
} from "@gitpod/gitpod-protocol"; | ||
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution"; | ||
import { inject, injectable } from "inversify"; | ||
|
@@ -38,11 +38,10 @@ export class EntitlementServiceUBP implements EntitlementService { | |
async mayStartWorkspace( | ||
user: User, | ||
organizationId: string, | ||
date: Date, | ||
runningInstances: Promise<WorkspaceInstance[]>, | ||
): Promise<MayStartWorkspaceResult> { | ||
const hasHitParallelWorkspaceLimit = async (): Promise<HitParallelWorkspaceLimit | undefined> => { | ||
const max = await this.getMaxParallelWorkspaces(user, date); | ||
const max = await this.getMaxParallelWorkspaces(user.id, organizationId); | ||
const current = (await runningInstances).filter((i) => i.status.phase !== "preparing").length; | ||
if (current >= max) { | ||
return { | ||
|
@@ -54,7 +53,7 @@ export class EntitlementServiceUBP implements EntitlementService { | |
} | ||
}; | ||
const [usageLimitReachedOnCostCenter, hitParallelWorkspaceLimit] = await Promise.all([ | ||
this.checkUsageLimitReached(user, organizationId, date), | ||
this.checkUsageLimitReached(user.id, organizationId), | ||
hasHitParallelWorkspaceLimit(), | ||
]); | ||
return { | ||
|
@@ -63,69 +62,63 @@ export class EntitlementServiceUBP implements EntitlementService { | |
}; | ||
} | ||
|
||
private async checkUsageLimitReached( | ||
user: User, | ||
organizationId: string, | ||
date: Date, | ||
): Promise<AttributionId | undefined> { | ||
const result = await this.usageService.checkUsageLimitReached(user.id, organizationId); | ||
private async checkUsageLimitReached(userId: string, organizationId: string): Promise<AttributionId | undefined> { | ||
const result = await this.usageService.checkUsageLimitReached(userId, organizationId); | ||
if (result.reached) { | ||
return result.attributionId; | ||
} | ||
return undefined; | ||
} | ||
|
||
private async getMaxParallelWorkspaces(user: User, date: Date): Promise<number> { | ||
if (await this.hasPaidSubscription(user, date)) { | ||
private async getMaxParallelWorkspaces(userId: string, organizationId: string): Promise<number> { | ||
if (await this.hasPaidSubscription(userId, organizationId)) { | ||
return MAX_PARALLEL_WORKSPACES_PAID; | ||
} else { | ||
return MAX_PARALLEL_WORKSPACES_FREE; | ||
} | ||
} | ||
|
||
async maySetTimeout(user: User, date: Date): Promise<boolean> { | ||
return this.hasPaidSubscription(user, date); | ||
async maySetTimeout(userId: string, organizationId?: string): Promise<boolean> { | ||
return this.hasPaidSubscription(userId, organizationId); | ||
} | ||
|
||
async getDefaultWorkspaceTimeout(user: User, date: Date): Promise<WorkspaceTimeoutDuration> { | ||
if (await this.hasPaidSubscription(user, date)) { | ||
async getDefaultWorkspaceTimeout(userId: string, organizationId: string): Promise<WorkspaceTimeoutDuration> { | ||
if (await this.hasPaidSubscription(userId, organizationId)) { | ||
return WORKSPACE_TIMEOUT_DEFAULT_LONG; | ||
} else { | ||
return WORKSPACE_TIMEOUT_DEFAULT_SHORT; | ||
} | ||
} | ||
|
||
async getDefaultWorkspaceLifetime(user: User, date: Date): Promise<WorkspaceTimeoutDuration> { | ||
if (await this.hasPaidSubscription(user, date)) { | ||
async getDefaultWorkspaceLifetime(userId: string, organizationId: string): Promise<WorkspaceTimeoutDuration> { | ||
if (await this.hasPaidSubscription(userId, organizationId)) { | ||
return WORKSPACE_LIFETIME_LONG; | ||
} else { | ||
return WORKSPACE_LIFETIME_SHORT; | ||
} | ||
} | ||
|
||
/** | ||
* DEPRECATED: With usage-based billing, users can choose exactly how many resources they want to get. | ||
* Thus, we no longer need to "force" extra resources via the `userGetsMoreResources` mechanism. | ||
*/ | ||
async userGetsMoreResources(user: User, date: Date = new Date()): Promise<boolean> { | ||
return false; | ||
} | ||
|
||
/** | ||
* Returns true if network connections should be limited | ||
* @param user | ||
*/ | ||
async limitNetworkConnections(user: User, date: Date): Promise<boolean> { | ||
async limitNetworkConnections(userId: string, organizationId: string): Promise<boolean> { | ||
// gpl: Because with the current payment handling (pay-after-use) having a "paid" plan is not a good enough classifier for trushworthyness atm. | ||
// We're looking into improving this, but for the meantime we limit network connections for everybody to reduce the impact of abuse. | ||
return true; | ||
} | ||
|
||
private async hasPaidSubscription(user: User, date: Date): Promise<boolean> { | ||
private async hasPaidSubscription(userId: string, organizationId?: string): Promise<boolean> { | ||
if (organizationId) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is good and we should even aim for making the organization mandatory, but it is a breaking change. For instance, users who were able to change timeouts on workspaces running on their free org (but have another paid org) can now no longer do this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exactly. But sharpening that behavior is sth we should have done all along, right after the migration to Stripe. |
||
// This is the "stricter", more correct version: We only allow privileges on the Organization that is paying for it | ||
const { billingStrategy } = await this.usageService.getCostCenter(userId, organizationId); | ||
return billingStrategy === CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE; | ||
} | ||
// This is the old behavior, stemming from our transition to PAYF, where our API did-/doesn't pass organizationId, yet | ||
// Member of paid team? | ||
const teams = await this.teamDB.findTeamsByUser(user.id); | ||
const teams = await this.teamDB.findTeamsByUser(userId); | ||
const isTeamSubscribedPromises = teams.map(async (team: Team) => { | ||
const { billingStrategy } = await this.usageService.getCostCenter(user.id, team.id); | ||
const { billingStrategy } = await this.usageService.getCostCenter(userId, team.id); | ||
return billingStrategy === CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE; | ||
}); | ||
// Return the first truthy promise, or false if all the promises were falsy. | ||
|
@@ -147,8 +140,8 @@ export class EntitlementServiceUBP implements EntitlementService { | |
}); | ||
} | ||
|
||
async getBillingTier(user: User): Promise<BillingTier> { | ||
const hasPaidPlan = await this.hasPaidSubscription(user, new Date()); | ||
async getBillingTier(userId: string, organizationId: string): Promise<BillingTier> { | ||
const hasPaidPlan = await this.hasPaidSubscription(userId, organizationId); | ||
return hasPaidPlan ? "paid" : "free"; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getBillingModeForUser
without any user context looks pretty odd from the outside. Do we need this method at all?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a comment, and explicitly marked it as deprecated now. All we really need to do is add orgnaizationId to
api.maySetTimeout
andapi.updateWorkspaceTimeoutSetting
, then we can remove it entirely.