Skip to content

Commit cbd30c0

Browse files
committed
[server] Broadcast prebuild updates from redis
1 parent 11787e1 commit cbd30c0

File tree

4 files changed

+101
-12
lines changed

4 files changed

+101
-12
lines changed

components/gitpod-protocol/src/redis.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,21 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7+
import { PrebuiltWorkspaceState } from "./protocol";
8+
79
export const WorkspaceInstanceUpdatesChannel = "chan:workspace-instances";
10+
export const PrebuildUpdatesChannel = "chan:prebuilds";
811

912
export type RedisWorkspaceInstanceUpdate = {
1013
ownerID: string;
1114
instanceID: string;
1215
workspaceID: string;
1316
};
17+
18+
export type RedisPrebuildUpdate = {
19+
status: PrebuiltWorkspaceState;
20+
prebuildID: string;
21+
workspaceID: string;
22+
instanceID: string;
23+
projectID: string;
24+
};

components/server/src/messaging/redis-subscriber.ts

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import { inject, injectable } from "inversify";
1414
import {
1515
Disposable,
1616
DisposableCollection,
17+
PrebuildUpdatesChannel,
18+
PrebuildWithStatus,
19+
RedisPrebuildUpdate,
1720
RedisWorkspaceInstanceUpdate,
1821
WorkspaceInstanceUpdatesChannel,
1922
} from "@gitpod/gitpod-protocol";
@@ -35,11 +38,12 @@ export class RedisSubscriber implements LocalMessageBroker {
3538
) {}
3639

3740
protected workspaceInstanceUpdateListeners: Map<string, WorkspaceInstanceUpdateListener[]> = new Map();
41+
protected prebuildUpdateListeners: Map<string, PrebuildUpdateListener[]> = new Map();
3842

3943
protected readonly disposables = new DisposableCollection();
4044

4145
async start(): Promise<void> {
42-
const channels = [WorkspaceInstanceUpdatesChannel];
46+
const channels = [WorkspaceInstanceUpdatesChannel, PrebuildUpdatesChannel];
4347

4448
for (const chan of channels) {
4549
await this.redis.subscribe(chan);
@@ -65,17 +69,28 @@ export class RedisSubscriber implements LocalMessageBroker {
6569
private async onMessage(channel: string, message: string): Promise<void> {
6670
switch (channel) {
6771
case WorkspaceInstanceUpdatesChannel:
68-
const enabled = await this.isRedisPubSubByTypeEnabled("workspace-instance");
69-
if (!enabled) {
72+
const wsInstanceTypeEnabled = await this.isRedisPubSubByTypeEnabled("workspace-instance");
73+
if (!wsInstanceTypeEnabled) {
7074
log.debug("[redis] Redis workspace instance update is disabled through feature flag", {
7175
channel,
7276
message,
7377
});
7478
return;
7579
}
7680

77-
const parsed = JSON.parse(message) as RedisWorkspaceInstanceUpdate;
78-
return this.onInstanceUpdate(parsed);
81+
return this.onInstanceUpdate(JSON.parse(message) as RedisWorkspaceInstanceUpdate);
82+
83+
case PrebuildUpdatesChannel:
84+
const prebuildTypeEnabled = await this.isRedisPubSubByTypeEnabled("prebuild");
85+
if (!prebuildTypeEnabled) {
86+
log.debug("[redis] Redis prebuild update is disabled through feature flag", {
87+
channel,
88+
message,
89+
});
90+
return;
91+
}
92+
return this.onPrebuildUpdate(JSON.parse(message) as RedisPrebuildUpdate);
93+
7994
default:
8095
throw new Error(`Redis Pub/Sub received message on unknown channel: ${channel}`);
8196
}
@@ -112,13 +127,48 @@ export class RedisSubscriber implements LocalMessageBroker {
112127
}
113128
}
114129

130+
private async onPrebuildUpdate(update: RedisPrebuildUpdate): Promise<void> {
131+
log.debug("[redis] Received prebuild update", { update });
132+
133+
if (!update.projectID || !update.prebuildID || !update.status) {
134+
return;
135+
}
136+
137+
const listeners = this.prebuildUpdateListeners.get(update.projectID) || [];
138+
if (listeners.length === 0) {
139+
return;
140+
}
141+
142+
const ctx = {};
143+
const info = (await this.workspaceDB.findPrebuildInfos([update.prebuildID]))[0];
144+
if (!info) {
145+
return;
146+
}
147+
148+
const prebuildWithStatus: PrebuildWithStatus = {
149+
info: info,
150+
status: update.status,
151+
};
152+
153+
for (const l of listeners) {
154+
try {
155+
l(ctx, prebuildWithStatus);
156+
} catch (err) {
157+
log.error(
158+
"Failed to broadcast workspace instance update.",
159+
{ projectId: update.projectID, instanceId: update.instanceID, workspaceId: update.workspaceID },
160+
err,
161+
);
162+
}
163+
}
164+
}
165+
115166
async stop() {
116167
this.disposables.dispose();
117168
}
118169

119170
listenForPrebuildUpdates(projectId: string, listener: PrebuildUpdateListener): Disposable {
120-
// TODO: not implemented
121-
return Disposable.create(() => {});
171+
return this.doRegister(projectId, listener, this.prebuildUpdateListeners);
122172
}
123173

124174
listenForPrebuildUpdatableEvents(listener: HeadlessWorkspaceEventListener): Disposable {

components/ws-manager-bridge/src/prebuild-updater.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,13 @@ export class PrebuildUpdater {
9595
const info = (await this.workspaceDB.trace({ span }).findPrebuildInfos([updatedPrebuild.id]))[0];
9696
if (info) {
9797
await this.messagebus.notifyOnPrebuildUpdate({ info, status: updatedPrebuild.state });
98-
await this.publisher.publishPrebuildUpdate();
98+
await this.publisher.publishPrebuildUpdate({
99+
instanceID: instanceId,
100+
projectID: prebuild.projectId || "",
101+
prebuildID: updatedPrebuild.id,
102+
status: updatedPrebuild.state,
103+
workspaceID: workspaceId,
104+
});
99105
}
100106
}
101107
} catch (e) {
@@ -120,7 +126,13 @@ export class PrebuildUpdater {
120126
const info = (await this.workspaceDB.trace({ span }).findPrebuildInfos([prebuild.id]))[0];
121127
if (info) {
122128
await this.messagebus.notifyOnPrebuildUpdate({ info, status: prebuild.state });
123-
await this.publisher.publishPrebuildUpdate();
129+
await this.publisher.publishPrebuildUpdate({
130+
instanceID: instance.id,
131+
projectID: prebuild.projectId || "",
132+
prebuildID: prebuild.id,
133+
status: prebuild.state,
134+
workspaceID: instance.workspaceId,
135+
});
124136
}
125137
}
126138
}

components/ws-manager-bridge/src/redis/publisher.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ import { inject, injectable } from "inversify";
88
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
99
import { Metrics } from "../metrics";
1010
import { RedisClient } from "./client";
11-
import { RedisWorkspaceInstanceUpdate, WorkspaceInstanceUpdatesChannel } from "@gitpod/gitpod-protocol";
11+
import {
12+
PrebuildUpdatesChannel,
13+
RedisPrebuildUpdate,
14+
RedisWorkspaceInstanceUpdate,
15+
WorkspaceInstanceUpdatesChannel,
16+
} from "@gitpod/gitpod-protocol";
1217

1318
@injectable()
1419
export class RedisPublisher {
@@ -17,9 +22,20 @@ export class RedisPublisher {
1722
@inject(Metrics) private readonly metrics: Metrics,
1823
) {}
1924

20-
async publishPrebuildUpdate(): Promise<void> {
25+
async publishPrebuildUpdate(update: RedisPrebuildUpdate): Promise<void> {
2126
log.debug("[redis] Publish prebuild udpate invoked.");
22-
this.metrics.reportUpdatePublished("prebuild");
27+
28+
let err: Error | undefined;
29+
try {
30+
const serialized = JSON.stringify(update);
31+
await this.client.get().publish(PrebuildUpdatesChannel, serialized);
32+
log.debug("[redis] Succesfully published prebuild update.", update);
33+
} catch (e) {
34+
err = e;
35+
log.error("[redis] Failed to publish prebuild update.", e, update);
36+
} finally {
37+
this.metrics.reportUpdatePublished("prebuild", err);
38+
}
2339
}
2440

2541
async publishInstanceUpdate(update: RedisWorkspaceInstanceUpdate): Promise<void> {

0 commit comments

Comments
 (0)