Skip to content

Commit

Permalink
feat(server): autoscaling for application (#1250)
Browse files Browse the repository at this point in the history
* feat(server): autoscaling for application

* refactor(server): make targetCPUUtilizationPercentage and targetMemoryUtilizationPercentage optional in autoscaling

* refactor(server): reapply hpa immediately when config is changed

* refactor(server): move autoscaling to application resource bundle

* fix(server): add validation for CreateAutoscalingDto
  • Loading branch information
0fatal authored Jun 16, 2023
1 parent 589d0ff commit 698e136
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 33 deletions.
18 changes: 0 additions & 18 deletions runtimes/nodejs/package-lock.json

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

13 changes: 5 additions & 8 deletions server/package-lock.json

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

1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@nestjs/schedule": "^2.1.0",
"@nestjs/swagger": "^6.1.3",
"@nestjs/throttler": "^3.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"compression": "^1.7.4",
"cron-validate": "^1.4.5",
Expand Down
10 changes: 10 additions & 0 deletions server/src/application/application.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ export class ApplicationController {
@ApiResponseObject(ApplicationWithRelations)
@Post()
async create(@Req() req: IRequest, @Body() dto: CreateApplicationDto) {
const error = dto.autoscaling.validate()
if (error) {
return ResponseUtil.error(error)
}

const user = req.user

// check regionId exists
Expand Down Expand Up @@ -274,6 +279,11 @@ export class ApplicationController {
@Body() dto: UpdateApplicationBundleDto,
@Req() req: IRequest,
) {
const error = dto.autoscaling.validate()
if (error) {
return ResponseUtil.error(error)
}

const app = await this.application.findOne(appid)
const user = req.user
const regionId = app.regionId
Expand Down
17 changes: 16 additions & 1 deletion server/src/application/application.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export class ApplicationService {
{
appid,
resource: this.buildBundleResource(dto),
autoscaling: this.buildAutoscalingConfig(dto),
isTrialTier: isTrialTier,
createdAt: new Date(),
updatedAt: new Date(),
Expand Down Expand Up @@ -292,11 +293,13 @@ export class ApplicationService {
) {
const db = SystemDatabase.db
const resource = this.buildBundleResource(dto)
const autoscaling = this.buildAutoscalingConfig(dto)

const res = await db
.collection<ApplicationBundle>('ApplicationBundle')
.findOneAndUpdate(
{ appid },
{ $set: { resource, updatedAt: new Date(), isTrialTier } },
{ $set: { resource, autoscaling, updatedAt: new Date(), isTrialTier } },
{
projection: {
'bundle.resource.requestCPU': 0,
Expand Down Expand Up @@ -386,4 +389,16 @@ export class ApplicationService {

return resource
}

private buildAutoscalingConfig(dto: UpdateApplicationBundleDto) {
const autoscaling = {
enable: false,
minReplicas: 1,
maxReplicas: 5,
targetCPUUtilizationPercentage: null,
targetMemoryUtilizationPercentage: null,
...dto.autoscaling,
}
return autoscaling
}
}
70 changes: 70 additions & 0 deletions server/src/application/dto/create-autoscaling.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
import {
IsBoolean,
IsInt,
IsNotEmpty,
IsOptional,
Max,
Min,
ValidateIf,
} from 'class-validator'

export class CreateAutoscalingDto {
@ApiProperty({ default: false })
@IsNotEmpty()
@IsBoolean()
enable: boolean

@ApiProperty({ default: 1 })
@IsNotEmpty()
@IsInt()
@Min(1)
@Max(19)
@ValidateIf(({ enable }) => enable)
minReplicas: number

@ApiProperty({ default: 5 })
@IsNotEmpty()
@IsInt()
@Min(2)
@Max(20)
@ValidateIf(({ enable }) => enable)
maxReplicas: number

@ApiPropertyOptional({ default: 50 })
@IsOptional()
@IsInt()
@Min(0)
@Max(100)
@ValidateIf(({ enable }) => enable)
targetCPUUtilizationPercentage?: number

@ApiPropertyOptional({ default: 50 })
@IsOptional()
@IsInt()
@Min(0)
@Max(100)
@ValidateIf(({ enable }) => enable)
targetMemoryUtilizationPercentage?: number

validate() {
if (this.enable) {
if (this.maxReplicas <= this.minReplicas) {
return 'Max replicas must be smaller than min replicas.'
}
if (
!this.targetCPUUtilizationPercentage &&
!this.targetMemoryUtilizationPercentage
) {
return 'Either targetCPUUtilizationPercentage or targetMemoryUtilizationPercentage must be specified.'
}
if (
this.targetCPUUtilizationPercentage &&
this.targetMemoryUtilizationPercentage
) {
return 'TargetCPUUtilizationPercentage and TargetMemoryUtilizationPercentage cannot be specified simultaneously.'
}
}
return null
}
}
16 changes: 15 additions & 1 deletion server/src/application/dto/update-application.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
import { IsIn, IsInt, IsNotEmpty, IsString, Length } from 'class-validator'
import {
IsIn,
IsInt,
IsNotEmpty,
IsString,
Length,
ValidateNested,
} from 'class-validator'
import { ApplicationState } from '../entities/application'
import { CreateAutoscalingDto } from './create-autoscaling.dto'
import { Type } from 'class-transformer'

const STATES = [
ApplicationState.Running,
Expand Down Expand Up @@ -64,4 +73,9 @@ export class UpdateApplicationBundleDto {
@IsNotEmpty()
@IsInt()
storageCapacity: number

@ApiProperty({ type: CreateAutoscalingDto })
@ValidateNested()
@Type(() => CreateAutoscalingDto)
autoscaling: CreateAutoscalingDto
}
4 changes: 4 additions & 0 deletions server/src/application/entities/application-bundle.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
import { ObjectId } from 'mongodb'
import { Autoscaling } from './application-configuration'

export class ApplicationBundleResource {
@ApiProperty({ example: 500 })
Expand Down Expand Up @@ -53,6 +54,9 @@ export class ApplicationBundle {
@ApiProperty()
resource: ApplicationBundleResource

@ApiProperty()
autoscaling: Autoscaling

@ApiPropertyOptional()
isTrialTier?: boolean

Expand Down
17 changes: 17 additions & 0 deletions server/src/application/entities/application-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ export class EnvironmentVariable {
value: string
}

export class Autoscaling {
@ApiProperty()
enable: boolean

@ApiProperty()
minReplicas: number

@ApiProperty()
maxReplicas: number

@ApiProperty()
targetCPUUtilizationPercentage?: number

@ApiProperty()
targetMemoryUtilizationPercentage?: number
}

export class ApplicationConfiguration {
@ApiProperty({ type: String })
_id?: ObjectId
Expand Down
Loading

0 comments on commit 698e136

Please sign in to comment.