Skip to content

Commit 287cb5a

Browse files
committed
[bridge] Introduce AppClusterWorkspaceInstanceController
1 parent 6d7d538 commit 287cb5a

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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 { WorkspaceDB } from "@gitpod/gitpod-db/lib/workspace-db";
8+
import { Disposable, DisposableCollection } from "@gitpod/gitpod-protocol";
9+
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
10+
import { repeat } from "@gitpod/gitpod-protocol/lib/util/repeat";
11+
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
12+
import { inject, injectable } from "inversify";
13+
import { Configuration } from "./config";
14+
import { WorkspaceInstanceController } from "./workspace-instance-controller";
15+
16+
/**
17+
* The WorkspaceInstance lifecycle is split between application clusters and workspace clusters on the transition from
18+
* pending/building -> starting (cmp. WorkspacePhases here:
19+
* https://github.com/gitpod-io/gitpod/blob/008ea3fadc89d4817cf3effc8a5b30eaf469fb1c/components/gitpod-protocol/src/workspace-instance.ts#L111).
20+
*
21+
* Before the transition, WorkspaceInstances belong to the respective app cluster, denoted by "instance.region === 'eu02'", for exmaple.
22+
* After a WorkspaceInstance has been moved over to a workspace cluster, that moved "ownership" is reflected in said field.
23+
* We maintain a constant connection (called "bridge") to all workspace clusters to be able to keep reality (workspace
24+
* side) in sync with what we have in our DB/forward to clients.
25+
*
26+
* This class is meant to take the same responsibility for all WorkspaceInstances that have not (yet) been passed over
27+
* to a workspace cluster for whatever reason. Here's a list of examples, prefixed by phase:
28+
* - "preparing": failed cleanup after failed call to wsManager.StartWorkspace
29+
* - "building": failed cleanup after failed image-build (which is still controlled by the application cluster,
30+
* although that might change in the future)
31+
*/
32+
@injectable()
33+
export class AppClusterWorkspaceInstancesController implements Disposable {
34+
@inject(Configuration) protected readonly config: Configuration;
35+
36+
@inject(WorkspaceDB) protected readonly workspaceDb: WorkspaceDB;
37+
38+
@inject(WorkspaceInstanceController) protected readonly workspaceInstanceController: WorkspaceInstanceController;
39+
40+
protected readonly dispoables = new DisposableCollection();
41+
42+
public async start() {
43+
const disposable = repeat(
44+
async () => this.controlAppClusterManagedWorkspaceInstances(),
45+
this.config.controllerIntervalSeconds * 1000,
46+
);
47+
this.dispoables.push(disposable);
48+
}
49+
50+
protected async controlAppClusterManagedWorkspaceInstances() {
51+
const installation = this.config.installation;
52+
53+
const span = TraceContext.startSpan("controlAppClusterManagedWorkspaceInstances");
54+
const ctx = { span };
55+
try {
56+
log.info("Controlling app cluster instances", { installation });
57+
58+
const notStoppedInstances = await this.workspaceDb.findRunningInstancesWithWorkspaces(
59+
installation,
60+
undefined,
61+
false,
62+
);
63+
await this.workspaceInstanceController.controlNotStoppedAppClusterManagedInstanceTimeouts(
64+
ctx,
65+
notStoppedInstances,
66+
installation,
67+
);
68+
69+
log.info("Done controlling app cluster instances", {
70+
installation,
71+
instancesCount: notStoppedInstances.length,
72+
});
73+
} catch (err) {
74+
log.error("Error controlling app cluster instances", err, {
75+
installation,
76+
});
77+
TraceContext.setError(ctx, err);
78+
} finally {
79+
span.finish();
80+
}
81+
}
82+
83+
public dispose() {
84+
this.dispoables.dispose();
85+
}
86+
}

components/ws-manager-bridge/src/container-module.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { Client } from "@gitpod/gitpod-protocol/lib/experiments/types";
3939
import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server";
4040
import { ClusterSyncService } from "./cluster-sync-service";
4141
import { WorkspaceInstanceController, WorkspaceInstanceControllerImpl } from "./workspace-instance-controller";
42+
import { AppClusterWorkspaceInstancesController } from "./app-cluster-instance-controller";
4243

4344
export const containerModule = new ContainerModule((bind) => {
4445
bind(MessagebusConfiguration).toSelf().inSingletonScope();
@@ -94,4 +95,6 @@ export const containerModule = new ContainerModule((bind) => {
9495
bind(Client).toDynamicValue(getExperimentsClientForBackend).inSingletonScope();
9596

9697
bind(WorkspaceInstanceController).to(WorkspaceInstanceControllerImpl).inTransientScope();
98+
99+
bind(AppClusterWorkspaceInstancesController).toSelf().inSingletonScope();
97100
});

components/ws-manager-bridge/src/main.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { TracingManager } from "@gitpod/gitpod-protocol/lib/util/tracing";
1515
import { ClusterServiceServer } from "./cluster-service-server";
1616
import { BridgeController } from "./bridge-controller";
1717
import { ClusterSyncService } from "./cluster-sync-service";
18+
import { AppClusterWorkspaceInstancesController } from "./app-cluster-instance-controller";
1819

1920
log.enableJSONLogging("ws-manager-bridge", undefined, LogrusLogLevel.getFromEnv());
2021

@@ -52,6 +53,11 @@ export const start = async (container: Container) => {
5253
const clusterSyncService = container.get<ClusterSyncService>(ClusterSyncService);
5354
clusterSyncService.start();
5455

56+
const appClusterInstanceController = container.get<AppClusterWorkspaceInstancesController>(
57+
AppClusterWorkspaceInstancesController,
58+
);
59+
appClusterInstanceController.start();
60+
5561
process.on("SIGTERM", async () => {
5662
log.info("SIGTERM received, stopping");
5763
bridgeController.dispose();
@@ -64,6 +70,7 @@ export const start = async (container: Container) => {
6470
});
6571
}
6672
clusterServiceServer.stop().then(() => log.info("gRPC shutdown completed"));
73+
appClusterInstanceController.dispose();
6774
});
6875
log.info("ws-manager-bridge is up and running");
6976
await new Promise((rs, rj) => {});

0 commit comments

Comments
 (0)