Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bridge] Introduce AppClusterWorkspaceInstanceController #13831

Merged
merged 1 commit into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License-AGPL.txt in the project root for license information.
*/

import { WorkspaceDB } from "@gitpod/gitpod-db/lib/workspace-db";
import { Disposable, DisposableCollection } from "@gitpod/gitpod-protocol";
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
import { repeat } from "@gitpod/gitpod-protocol/lib/util/repeat";
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
import { inject, injectable } from "inversify";
import { Configuration } from "./config";
import { WorkspaceInstanceController } from "./workspace-instance-controller";

/**
* The WorkspaceInstance lifecycle is split between application clusters and workspace clusters on the transition from
* pending/building -> starting (cmp. WorkspacePhases here:
* https://github.com/gitpod-io/gitpod/blob/008ea3fadc89d4817cf3effc8a5b30eaf469fb1c/components/gitpod-protocol/src/workspace-instance.ts#L111).
*
* Before the transition, WorkspaceInstances belong to the respective app cluster, denoted by "instance.region === 'eu02'", for exmaple.
* After a WorkspaceInstance has been moved over to a workspace cluster, that moved "ownership" is reflected in said field.
* We maintain a constant connection (called "bridge") to all workspace clusters to be able to keep reality (workspace
* side) in sync with what we have in our DB/forward to clients.
*
* This class is meant to take the same responsibility for all WorkspaceInstances that have not (yet) been passed over
* to a workspace cluster for whatever reason. Here's a list of examples, prefixed by phase:
* - "preparing": failed cleanup after failed call to wsManager.StartWorkspace
* - "building": failed cleanup after failed image-build (which is still controlled by the application cluster,
* although that might change in the future)
*/
@injectable()
export class AppClusterWorkspaceInstancesController implements Disposable {
@inject(Configuration) protected readonly config: Configuration;

@inject(WorkspaceDB) protected readonly workspaceDb: WorkspaceDB;

@inject(WorkspaceInstanceController) protected readonly workspaceInstanceController: WorkspaceInstanceController;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we eventually move this instance onto this controller also? Seems we've got layers here which may not be adding immediate value.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we could! It's basically and artifact of splitting the PRs, and not adjusting after the rebase 😆


protected readonly dispoables = new DisposableCollection();

public async start() {
const disposable = repeat(
async () => this.controlAppClusterManagedWorkspaceInstances(),
this.config.controllerIntervalSeconds * 1000,
);
this.dispoables.push(disposable);
}

protected async controlAppClusterManagedWorkspaceInstances() {
const appClusterInstallation = this.config.installation;

const span = TraceContext.startSpan("controlAppClusterManagedWorkspaceInstances");
const ctx = { span };
try {
log.info("Controlling app cluster instances", { installation: appClusterInstallation });

const notStoppedInstances = await this.workspaceDb.findRunningInstancesWithWorkspaces(
appClusterInstallation,
undefined,
false,
);
await this.workspaceInstanceController.controlNotStoppedAppClusterManagedInstanceTimeouts(
ctx,
notStoppedInstances,
appClusterInstallation,
);

log.info("Done controlling app cluster instances", {
installation: appClusterInstallation,
instancesCount: notStoppedInstances.length,
});
Comment on lines +56 to +72
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. This would be the perfect place to put any metrics which report duration & outcome. Shame we don't have metrics configured for ws-manager-bridge

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could "cheat" and attach the duration of the run (and any outcome) to the log fields.

} catch (err) {
log.error("Error controlling app cluster instances", err, {
installation: appClusterInstallation,
});
TraceContext.setError(ctx, err);
} finally {
span.finish();
}
}

public dispose() {
this.dispoables.dispose();
}
}
3 changes: 3 additions & 0 deletions components/ws-manager-bridge/src/container-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { Client } from "@gitpod/gitpod-protocol/lib/experiments/types";
import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server";
import { ClusterSyncService } from "./cluster-sync-service";
import { WorkspaceInstanceController, WorkspaceInstanceControllerImpl } from "./workspace-instance-controller";
import { AppClusterWorkspaceInstancesController } from "./app-cluster-instance-controller";

export const containerModule = new ContainerModule((bind) => {
bind(MessagebusConfiguration).toSelf().inSingletonScope();
Expand Down Expand Up @@ -95,4 +96,6 @@ export const containerModule = new ContainerModule((bind) => {

// transient to make sure we're creating a separate instance every time we ask for it
bind(WorkspaceInstanceController).to(WorkspaceInstanceControllerImpl).inTransientScope();

bind(AppClusterWorkspaceInstancesController).toSelf().inSingletonScope();
});
7 changes: 7 additions & 0 deletions components/ws-manager-bridge/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { TracingManager } from "@gitpod/gitpod-protocol/lib/util/tracing";
import { ClusterServiceServer } from "./cluster-service-server";
import { BridgeController } from "./bridge-controller";
import { ClusterSyncService } from "./cluster-sync-service";
import { AppClusterWorkspaceInstancesController } from "./app-cluster-instance-controller";

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

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

const appClusterInstanceController = container.get<AppClusterWorkspaceInstancesController>(
AppClusterWorkspaceInstancesController,
);
appClusterInstanceController.start();

process.on("SIGTERM", async () => {
log.info("SIGTERM received, stopping");
bridgeController.dispose();
Expand All @@ -64,6 +70,7 @@ export const start = async (container: Container) => {
});
}
clusterServiceServer.stop().then(() => log.info("gRPC shutdown completed"));
appClusterInstanceController.dispose();
});
log.info("ws-manager-bridge is up and running");
await new Promise((rs, rj) => {});
Expand Down