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