Path: blob/main/components/ws-manager-bridge/src/prebuild-updater.ts
2498 views
/**1* Copyright (c) 2022 Gitpod GmbH. All rights reserved.2* Licensed under the GNU Affero General Public License (AGPL).3* See License.AGPL.txt in the project root for license information.4*/56import { inject, injectable } from "inversify";7import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";8import { WorkspaceStatus, WorkspaceType } from "@gitpod/ws-manager/lib";9import { HeadlessWorkspaceEventType, WorkspaceInstance } from "@gitpod/gitpod-protocol";10import { log, LogContext } from "@gitpod/gitpod-protocol/lib/util/logging";11import { PrebuildStateMapper } from "./prebuild-state-mapper";12import { DBWithTracing, TracedWorkspaceDB } from "@gitpod/gitpod-db/lib/traced-db";13import { WorkspaceDB } from "@gitpod/gitpod-db/lib/workspace-db";14import { Metrics } from "./metrics";15import { filterStatus } from "./bridge";16import { RedisPublisher } from "@gitpod/gitpod-db/lib";1718@injectable()19export class PrebuildUpdater {20constructor(21@inject(PrebuildStateMapper) private readonly prebuildStateMapper: PrebuildStateMapper,22@inject(TracedWorkspaceDB) private readonly workspaceDB: DBWithTracing<WorkspaceDB>,23@inject(Metrics) private readonly prometheusExporter: Metrics,24@inject(RedisPublisher) private readonly publisher: RedisPublisher,25) {}2627async updatePrebuiltWorkspace(ctx: TraceContext, userId: string, status: WorkspaceStatus.AsObject) {28if (status.spec && status.spec.type !== WorkspaceType.PREBUILD) {29return;30}3132const instanceId = status.id!;33const workspaceId = status.metadata!.metaId!;34const logCtx: LogContext = { instanceId, workspaceId, userId };3536log.info(logCtx, "Handling prebuild workspace update.", filterStatus(status));3738const span = TraceContext.startSpan("updatePrebuiltWorkspace", ctx);39try {40const prebuild = await this.workspaceDB.trace({ span }).findPrebuildByWorkspaceID(status.metadata!.metaId!);41if (!prebuild) {42log.warn(logCtx, "Headless workspace without prebuild");43TraceContext.setError({ span }, new Error("headless workspace without prebuild"));44return;45}46span.setTag("updatePrebuiltWorkspace.prebuildId", prebuild.id);47span.setTag("updatePrebuiltWorkspace.workspaceInstance.statusVersion", status.statusVersion);48log.info(logCtx, "Found prebuild record in database.", prebuild);4950// prebuild.statusVersion = 0 is the default value in the DB, these shouldn't be counted as stale in our metrics51if (prebuild.statusVersion > 0 && prebuild.statusVersion >= status.statusVersion) {52// We've gotten an event which is younger than one we've already processed. We shouldn't process the stale one.53span.setTag("updatePrebuiltWorkspace.staleEvent", true);54this.prometheusExporter.recordStalePrebuildEvent();55log.info(logCtx, "Stale prebuild event received, skipping.");56return;57}58prebuild.statusVersion = status.statusVersion;5960const update = await this.prebuildStateMapper.mapWorkspaceStatusToPrebuild(status);61const terminatingStates = ["available", "timeout", "aborted", "failed"];62if (update) {63const updatedPrebuild = {64...prebuild,65...update.update,66};6768span.setTag("updatePrebuildWorkspace.prebuild.state", updatedPrebuild.state);69span.setTag("updatePrebuildWorkspace.prebuild.error", updatedPrebuild.error);7071// Here we make sure that we increment the counter only when:72// the state changes (we can receive multiple events with the same state)73if (74updatedPrebuild.state &&75terminatingStates.includes(updatedPrebuild.state) &&76updatedPrebuild.state !== prebuild.state77) {78this.prometheusExporter.increasePrebuildsCompletedCounter(updatedPrebuild.state);79}8081await this.workspaceDB.trace({ span }).storePrebuiltWorkspace(updatedPrebuild);8283// notify updates84// headless update85if (!HeadlessWorkspaceEventType.isRunning(update.type)) {86await this.publisher.publishHeadlessUpdate({87type: update.type,88workspaceID: workspaceId,89});90}9192// prebuild info93const info = (await this.workspaceDB.trace({ span }).findPrebuildInfos([updatedPrebuild.id]))[0];94if (info) {95await this.publisher.publishPrebuildUpdate({96projectID: prebuild.projectId || "",97prebuildID: updatedPrebuild.id,98status: updatedPrebuild.state,99workspaceID: workspaceId,100organizationID: info.teamId,101});102}103}104} catch (e) {105TraceContext.setError({ span }, e);106throw e;107} finally {108span.finish();109}110}111112async stopPrebuildInstance(ctx: TraceContext, instance: WorkspaceInstance): Promise<void> {113const span = TraceContext.startSpan("stopPrebuildInstance", ctx);114115const prebuild = await this.workspaceDB.trace({}).findPrebuildByWorkspaceID(instance.workspaceId);116if (prebuild) {117// this is a prebuild - set it to aborted118prebuild.state = "aborted";119await this.workspaceDB.trace({}).storePrebuiltWorkspace(prebuild);120121{122// notify about prebuild updated123const info = (await this.workspaceDB.trace({ span }).findPrebuildInfos([prebuild.id]))[0];124if (info) {125await this.publisher.publishPrebuildUpdate({126projectID: prebuild.projectId || "",127prebuildID: prebuild.id,128status: prebuild.state,129workspaceID: instance.workspaceId,130organizationID: info.teamId,131});132}133}134}135}136}137138139