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