Skip to content

Commit

Permalink
[usage] added costcenter nextBillingTime
Browse files Browse the repository at this point in the history
  • Loading branch information
svenefftinge committed Sep 13, 2022
1 parent 865dffa commit ab2cfcb
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* 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.
*/

import { MigrationInterface, QueryRunner } from "typeorm";
import { columnExists } from "./helper/helper";

const D_B_COST_CENTER = "d_b_cost_center";
const COL_NEXT_BILLING_TIME = "nextBillingTime";

export class CostCenterNextBillingTime1663055856941 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
if (!(await columnExists(queryRunner, D_B_COST_CENTER, COL_NEXT_BILLING_TIME))) {
await queryRunner.query(
`ALTER TABLE ${D_B_COST_CENTER} ADD COLUMN ${COL_NEXT_BILLING_TIME} varchar(30) NOT NULL, ALGORITHM=INPLACE, LOCK=NONE `,
);
await queryRunner.query(
`ALTER TABLE ${D_B_COST_CENTER} ADD INDEX(${COL_NEXT_BILLING_TIME}), ALGORITHM=INPLACE, LOCK=NONE `,
);
}
}

public async down(queryRunner: QueryRunner): Promise<void> {}
}
104 changes: 59 additions & 45 deletions components/usage-api/go/v1/usage.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion components/usage-api/typescript/src/usage/v1/usage.pb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ export interface CostCenter {
attributionId: string;
spendingLimit: number;
billingStrategy: CostCenter_BillingStrategy;
/** next_billing_time specifies when the next billing cycle happens. Only set when billing strategy is 'other'. This property is readonly. */
nextBillingTime: Date | undefined;
}

export enum CostCenter_BillingStrategy {
Expand Down Expand Up @@ -961,7 +963,12 @@ export const GetCostCenterResponse = {
};

function createBaseCostCenter(): CostCenter {
return { attributionId: "", spendingLimit: 0, billingStrategy: CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE };
return {
attributionId: "",
spendingLimit: 0,
billingStrategy: CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE,
nextBillingTime: undefined,
};
}

export const CostCenter = {
Expand All @@ -975,6 +982,9 @@ export const CostCenter = {
if (message.billingStrategy !== CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE) {
writer.uint32(24).int32(costCenter_BillingStrategyToNumber(message.billingStrategy));
}
if (message.nextBillingTime !== undefined) {
Timestamp.encode(toTimestamp(message.nextBillingTime), writer.uint32(34).fork()).ldelim();
}
return writer;
},

Expand All @@ -994,6 +1004,9 @@ export const CostCenter = {
case 3:
message.billingStrategy = costCenter_BillingStrategyFromJSON(reader.int32());
break;
case 4:
message.nextBillingTime = fromTimestamp(Timestamp.decode(reader, reader.uint32()));
break;
default:
reader.skipType(tag & 7);
break;
Expand All @@ -1009,6 +1022,7 @@ export const CostCenter = {
billingStrategy: isSet(object.billingStrategy)
? costCenter_BillingStrategyFromJSON(object.billingStrategy)
: CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE,
nextBillingTime: isSet(object.nextBillingTime) ? fromJsonTimestamp(object.nextBillingTime) : undefined,
};
},

Expand All @@ -1018,6 +1032,7 @@ export const CostCenter = {
message.spendingLimit !== undefined && (obj.spendingLimit = Math.round(message.spendingLimit));
message.billingStrategy !== undefined &&
(obj.billingStrategy = costCenter_BillingStrategyToJSON(message.billingStrategy));
message.nextBillingTime !== undefined && (obj.nextBillingTime = message.nextBillingTime.toISOString());
return obj;
},

Expand All @@ -1026,6 +1041,7 @@ export const CostCenter = {
message.attributionId = object.attributionId ?? "";
message.spendingLimit = object.spendingLimit ?? 0;
message.billingStrategy = object.billingStrategy ?? CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE;
message.nextBillingTime = object.nextBillingTime ?? undefined;
return message;
},
};
Expand Down
3 changes: 3 additions & 0 deletions components/usage-api/usage/v1/usage.proto
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,7 @@ message CostCenter {
BILLING_STRATEGY_OTHER = 1;
}
BillingStrategy billing_strategy = 3;

// next_billing_time specifies when the next billing cycle happens. Only set when billing strategy is 'other'. This property is readonly.
google.protobuf.Timestamp next_billing_time = 4;
}
2 changes: 2 additions & 0 deletions components/usage/config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"controllerSchedule": "1h",
"defaultSpendingLimitForUsers": 5000,
"defaultSpendingLimitForTeams": 0,
"creditsPerMinuteByWorkspaceClass": {
"default": 0.1666666667,
"gitpodio-internal-xl": 0.3333333333
Expand Down
45 changes: 42 additions & 3 deletions components/usage/pkg/apiv1/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,17 @@ import (

var _ v1.UsageServiceServer = (*UsageService)(nil)

type UsageConfig struct {
DefaultSpendingLimitForUsers int32 `json:"defaultSpendingLimitForUsers,omitempty"`

DefaultSpendingLimitForTeams int32 `json:"defaultSpendingLimitForTeams,omitempty"`
}

type UsageService struct {
conn *gorm.DB
nowFunc func() time.Time
pricer *WorkspacePricer
cfg UsageConfig

v1.UnimplementedUsageServiceServer
}
Expand Down Expand Up @@ -164,12 +171,30 @@ func (s *UsageService) GetCostCenter(ctx context.Context, in *v1.GetCostCenterRe
return nil, status.Errorf(codes.InvalidArgument, "Failed to parse attribution ID: %s", err.Error())
}

logger := log.WithField("attributionId", in.AttributionId)

result, err := db.GetCostCenter(ctx, s.conn, db.AttributionID(attributionIdReq))
if err != nil {
if errors.Is(err, db.CostCenterNotFound) {
return nil, status.Errorf(codes.NotFound, "Cost center not found: %s", err.Error())
logger.Info("No existing cost center. Creating one.")
defaultSpendingLimit := s.cfg.DefaultSpendingLimitForUsers
if !attributionId.IsUser() {
defaultSpendingLimit = s.cfg.DefaultSpendingLimitForTeams
}
result = &db.CostCenter{
ID: attributionId,
CreationTime: db.NewVarcharTime(time.Now()),
BillingStrategy: db.CostCenter_Other,
SpendingLimit: defaultSpendingLimit,
NextBillingTime: db.NewVarcharTime(time.Now().AddDate(0, 1, 0)),
}
result, err = db.SaveCostCenter(ctx, s.conn, result)
if err != nil {
return nil, status.Errorf(codes.Internal, "Failed to store new cost center in DB: %s", err.Error())
}
} else {
return nil, status.Errorf(codes.Internal, "Failed to get cost center %s from DB: %s", in.AttributionId, err.Error())
}
return nil, status.Errorf(codes.Internal, "Failed to get cost center %s from DB: %s", in.AttributionId, err.Error())
}

billingStrategy := v1.CostCenter_BILLING_STRATEGY_OTHER
Expand All @@ -182,6 +207,7 @@ func (s *UsageService) GetCostCenter(ctx context.Context, in *v1.GetCostCenterRe
AttributionId: string(attributionId),
SpendingLimit: result.SpendingLimit,
BillingStrategy: billingStrategy,
NextBillingTime: timestamppb.New(result.NextBillingTime.Time()),
},
}, nil
}
Expand All @@ -201,10 +227,22 @@ func (s *UsageService) SetCostCenter(ctx context.Context, in *v1.SetCostCenterRe
billingStrategy = db.CostCenter_Stripe
}

costCenter, err := s.GetCostCenter(ctx, &v1.GetCostCenterRequest{
AttributionId: string(attributionID),
})
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "Couldn't obtain cost center: %s", err.Error())
}
var nextBillingTime db.VarcharTime
if costCenter.GetCostCenter().GetNextBillingTime() != nil {
nextBillingTime = db.NewVarcharTime(costCenter.GetCostCenter().GetNextBillingTime().AsTime())
}

_, err = db.SaveCostCenter(ctx, s.conn, &db.CostCenter{
ID: attributionID,
SpendingLimit: in.CostCenter.SpendingLimit,
BillingStrategy: billingStrategy,
NextBillingTime: nextBillingTime,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "Failed to save cost center %s: %s", attributionID, err.Error())
Expand Down Expand Up @@ -394,9 +432,10 @@ func dedupeWorkspaceInstancesForUsage(instances []db.WorkspaceInstanceForUsage)
return set
}

func NewUsageService(conn *gorm.DB, pricer *WorkspacePricer) *UsageService {
func NewUsageService(conn *gorm.DB, pricer *WorkspacePricer, cfg UsageConfig) *UsageService {
return &UsageService{
conn: conn,
cfg: cfg,
nowFunc: func() time.Time {
return time.Now().UTC()
},
Expand Down
Loading

0 comments on commit ab2cfcb

Please sign in to comment.