diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index a048db573f..0f7d007062 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -79,6 +79,7 @@ model Runtime { enum ApplicationState { Running Stopped + Restarting } enum ApplicationPhase { diff --git a/server/src/application/dto/create-application.dto.ts b/server/src/application/dto/create-application.dto.ts index 831f0a467f..7b48665df1 100644 --- a/server/src/application/dto/create-application.dto.ts +++ b/server/src/application/dto/create-application.dto.ts @@ -2,6 +2,11 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' import { ApplicationState } from '@prisma/client' import { IsEnum, IsNotEmpty, Length } from 'class-validator' +enum CreateApplicationState { + Running = 'Running', + Stopped = 'Stopped', +} + export class CreateApplicationDto { @ApiProperty({ required: true }) @Length(1, 64) @@ -9,11 +14,11 @@ export class CreateApplicationDto { name: string @ApiPropertyOptional({ - default: ApplicationState.Running, - enum: ApplicationState, + default: CreateApplicationState.Running, + enum: CreateApplicationState, }) @IsNotEmpty() - @IsEnum(ApplicationState) + @IsEnum(CreateApplicationState) state: ApplicationState @ApiProperty() diff --git a/server/src/application/dto/update-application.dto.ts b/server/src/application/dto/update-application.dto.ts index c03c60f5b8..1224c4ebb5 100644 --- a/server/src/application/dto/update-application.dto.ts +++ b/server/src/application/dto/update-application.dto.ts @@ -2,7 +2,11 @@ import { ApiPropertyOptional } from '@nestjs/swagger' import { ApplicationState } from '@prisma/client' import { IsIn } from 'class-validator' -const STATES = ['Running', 'Stopped'] +const STATES = [ + ApplicationState.Running, + ApplicationState.Stopped, + ApplicationState.Restarting, +] export class UpdateApplicationDto { /** * Application name diff --git a/server/src/instance/instance-task.service.ts b/server/src/instance/instance-task.service.ts index 28a1f08824..6ed3ebe31c 100644 --- a/server/src/instance/instance-task.service.ts +++ b/server/src/instance/instance-task.service.ts @@ -20,6 +20,11 @@ export class InstanceTaskService { private readonly prisma: PrismaService, ) {} + /** + * State `Running` with phase `Created` or `Stopped` - create instance + * + * -> Phase `Starting` + */ @Cron(CronExpression.EVERY_SECOND) async handlePreparedStart() { const apps = await this.prisma.application.findMany({ @@ -57,6 +62,11 @@ export class InstanceTaskService { } } + /** + * Phase `Starting` - waiting for instance to be available + * + * -> Phase `Started` + */ @Cron(CronExpression.EVERY_SECOND) async handleStarting() { const apps = await this.prisma.application.findMany({ @@ -78,6 +88,12 @@ export class InstanceTaskService { if (!instance.service) continue + // if state is `Restarting`, update state to `Running` with phase `Started` + let toState = app.state + if (app.state === ApplicationState.Restarting) { + toState = ApplicationState.Running + } + // update application state await this.prisma.application.updateMany({ where: { @@ -85,6 +101,7 @@ export class InstanceTaskService { phase: ApplicationPhase.Starting, }, data: { + state: toState, phase: ApplicationPhase.Started, }, }) @@ -95,6 +112,11 @@ export class InstanceTaskService { } } + /** + * State `Stopped` with phase `Started` - remove instance + * + * -> Phase `Stopping` + */ @Cron(CronExpression.EVERY_SECOND) async handlePreparedStop() { const apps = await this.prisma.application.findMany({ @@ -128,6 +150,11 @@ export class InstanceTaskService { } } + /** + * Phase `Stopping` - waiting for deployment to be removed. + * + * -> Phase `Stopped` + */ @Cron(CronExpression.EVERY_SECOND) async handleStopping() { const apps = await this.prisma.application.findMany({ @@ -159,4 +186,84 @@ export class InstanceTaskService { } } } + + /** + * State `Restarting` with phase `Started` - remove instance + * + * -> Phase `Stopping` + */ + @Cron(CronExpression.EVERY_SECOND) + async handlePreparedRestart() { + const apps = await this.prisma.application.findMany({ + where: { + state: ApplicationState.Restarting, + phase: ApplicationPhase.Started, + }, + take: 5, + }) + + for (const app of apps) { + try { + const appid = app.appid + await this.instanceService.remove(appid) + + await this.prisma.application.updateMany({ + where: { + appid: app.appid, + state: ApplicationState.Restarting, + phase: ApplicationPhase.Started, + }, + data: { + phase: ApplicationPhase.Stopping, + }, + }) + + this.logger.debug( + `Application ${app.appid} updated to phase stopping for restart`, + ) + } catch (error) { + this.logger.error(error) + } + } + } + + /** + * State `Restarting` with phase `Stopped` - create instance + * + * -> Phase `Starting` + */ + @Cron(CronExpression.EVERY_SECOND) + async handleRestarting() { + const apps = await this.prisma.application.findMany({ + where: { + state: ApplicationState.Restarting, + phase: ApplicationPhase.Stopped, + }, + take: 5, + }) + + for (const app of apps) { + try { + const appid = app.appid + await this.instanceService.create(appid) + + await this.prisma.application.updateMany({ + where: { + appid: app.appid, + state: ApplicationState.Restarting, + phase: ApplicationPhase.Stopped, + }, + data: { + phase: ApplicationPhase.Starting, + }, + }) + + this.logger.debug( + `Application ${app.appid} updated to phase starting for restart`, + ) + } catch (error) { + this.logger.error(error) + } + } + } }