Path: blob/main/components/ws-manager-bridge/src/bridge-controller.ts
2498 views
/**1* Copyright (c) 2021 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, interfaces } from "inversify";7import { WorkspaceClusterInfo, WorkspaceManagerBridge, WorkspaceManagerBridgeFactory } from "./bridge";8import { Configuration } from "./config";9import { WorkspaceManagerClientProvider } from "@gitpod/ws-manager/lib/client-provider";10import { WorkspaceManagerClientProviderSource } from "@gitpod/ws-manager/lib/client-provider-source";11import { log } from "@gitpod/gitpod-protocol/lib/util/logging";12import { TLSConfig, WorkspaceClusterWoTLS } from "@gitpod/gitpod-protocol/lib/workspace-cluster";13import { WorkspaceCluster } from "@gitpod/gitpod-protocol/lib/workspace-cluster";14import { Queue } from "@gitpod/gitpod-protocol";15import { defaultGRPCOptions } from "@gitpod/gitpod-protocol/lib/util/grpc";16import * as grpc from "@grpc/grpc-js";17import { Metrics } from "./metrics";18import { TrustedValue } from "@gitpod/gitpod-protocol/lib/util/scrubbing";1920@injectable()21export class BridgeController {22constructor(23@inject(Configuration) private readonly config: Configuration,24@inject(WorkspaceManagerBridgeFactory)25private readonly bridgeFactory: interfaces.Factory<WorkspaceManagerBridge>,26@inject(WorkspaceManagerClientProvider) private readonly clientProvider: WorkspaceManagerClientProvider,27@inject(Metrics) private readonly metrics: Metrics,28) {}2930private readonly bridges: Map<string, WorkspaceManagerBridge> = new Map();31private readonly reconcileQueue: Queue = new Queue();32private reconcileTimer: NodeJS.Timeout | undefined = undefined;3334public async start() {35const scheduleReconcile = async () => {36try {37await this.reconcile();38} catch (err) {39log.error("error reconciling WorkspaceCluster", err);40} finally {41this.reconcileTimer = setTimeout(42scheduleReconcile,43this.config.wsClusterDBReconcileIntervalSeconds * 1000,44);45}46};47await scheduleReconcile();48}4950/**51* Triggers a reconcile run52*/53public async runReconcileNow() {54await this.reconcile();55}5657private async reconcile() {58return this.reconcileQueue.enqueue(async () => {59const allClusters = await this.getAllWorkspaceClusters();60log.info("reconciling clusters...", {61allClusters: new TrustedValue(Array.from(allClusters.keys())),62bridges: new TrustedValue(Array.from(this.bridges.keys())),63});64const toDelete: string[] = [];65try {66for (const [name, bridge] of this.bridges) {67const cluster = allClusters.get(name);68if (!cluster) {69log.info("reconcile: cluster not present anymore, stopping", { name });70bridge.stop();71toDelete.push(name);72} else {73log.debug("reconcile: cluster already present, doing nothing", { name });74allClusters.delete(name);75}76}77} finally {78for (const del of toDelete) {79this.bridges.delete(del);80}81}8283this.metrics.updateClusterMetrics(Array.from(allClusters).map(([_, c]) => c));84for (const [name, newCluster] of allClusters) {85log.info("reconcile: create bridge for new cluster", { name });86const bridge = await this.createAndStartBridge(newCluster);87this.bridges.set(newCluster.name, bridge);88}89log.info("done reconciling.", {90newClusters: new TrustedValue(Array.from(allClusters.keys())),91bridges: new TrustedValue(Array.from(this.bridges.keys())),92});93});94}9596private async createAndStartBridge(cluster: WorkspaceClusterInfo): Promise<WorkspaceManagerBridge> {97const bridge = this.bridgeFactory() as WorkspaceManagerBridge;98const grpcOptions: grpc.ClientOptions = {99...defaultGRPCOptions,100};101const clientProvider = async () => {102return this.clientProvider.get(cluster.name, grpcOptions);103};104bridge.start(cluster, clientProvider);105return bridge;106}107108protected async getAllWorkspaceClusters(): Promise<Map<string, WorkspaceClusterWoTLS>> {109const allInfos = await this.clientProvider.getAllWorkspaceClusters();110const result: Map<string, WorkspaceClusterWoTLS> = new Map();111for (const cluster of allInfos) {112result.set(cluster.name, cluster);113}114return result;115}116117public async dispose() {118await this.reconcileQueue.enqueue(async () => {119// running in reconcileQueue to make sure we're not in the process of reconciling atm (and re-scheduling)120if (this.reconcileTimer !== undefined) {121clearTimeout(this.reconcileTimer);122this.reconcileTimer = undefined;123}124});125126for (const [_, bridge] of this.bridges) {127bridge.stop();128}129}130}131132@injectable()133export class WorkspaceManagerClientProviderConfigSource implements WorkspaceManagerClientProviderSource {134constructor(@inject(Configuration) private readonly config: Configuration) {}135136public async getWorkspaceCluster(name: string): Promise<WorkspaceCluster | undefined> {137return this.clusters.find((m) => m.name === name);138}139140public async getAllWorkspaceClusters(): Promise<WorkspaceClusterWoTLS[]> {141return this.clusters;142}143144private get clusters(): WorkspaceCluster[] {145return this.config.staticBridges.map((c) => {146if (!c.tls) {147return c;148}149150return {151...c,152tls: {153ca: TLSConfig.loadFromBase64File(c.tls.ca),154crt: TLSConfig.loadFromBase64File(c.tls.crt),155key: TLSConfig.loadFromBase64File(c.tls.key),156},157};158});159}160}161162163