Skip to content

Commit 4ba0ca2

Browse files
svenefftingesagor999
authored andcommitted
[server] snapshots for PVC workspaces
1 parent c2c423c commit 4ba0ca2

File tree

3 files changed

+79
-24
lines changed

3 files changed

+79
-24
lines changed

components/server/ee/src/workspace/gitpod-server-impl.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ import {
5151
} from "@gitpod/gitpod-protocol";
5252
import { ResponseError } from "vscode-jsonrpc";
5353
import {
54-
TakeSnapshotRequest,
5554
AdmissionLevel,
5655
ControlAdmissionRequest,
5756
StopWorkspacePolicy,
@@ -530,18 +529,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
530529
}
531530
await this.guardAccess({ kind: "workspaceInstance", subject: instance, workspace }, "get");
532531

533-
const client = await this.workspaceManagerClientProvider.get(
534-
instance.region,
535-
this.config.installationShortname,
536-
);
537-
const request = new TakeSnapshotRequest();
538-
request.setId(instance.id);
539-
request.setReturnImmediately(true);
540-
541-
// this triggers the snapshots, but returns early! cmp. waitForSnapshot to wait for it's completion
542-
const resp = await client.takeSnapshot(ctx, request);
543-
544-
const snapshot = await this.snapshotService.createSnapshot(options, resp.getUrl());
532+
const snapshot = await this.snapshotService.createSnapshot(ctx, instance);
545533

546534
// to be backwards compatible during rollout, we require new clients to explicitly pass "dontWait: true"
547535
const waitOpts = { workspaceOwner: workspace.ownerId, snapshot };

components/server/ee/src/workspace/snapshot-service.ts

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@
77
import { inject, injectable } from "inversify";
88
import { v4 as uuidv4 } from "uuid";
99
import { WorkspaceDB } from "@gitpod/gitpod-db/lib";
10-
import { Disposable, GitpodServer, Snapshot } from "@gitpod/gitpod-protocol";
10+
import { Disposable, Snapshot, WorkspaceInstance } from "@gitpod/gitpod-protocol";
1111
import { StorageClient } from "../../../src/storage/storage-client";
1212
import { ConsensusLeaderQorum } from "../../../src/consensus/consensus-leader-quorum";
1313
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
1414
import { repeat } from "@gitpod/gitpod-protocol/lib/util/repeat";
15+
import { WorkspaceManagerClientProvider } from "@gitpod/ws-manager/lib/client-provider";
16+
import { GetVolumeSnapshotRequest, TakeSnapshotRequest } from "@gitpod/ws-manager/lib";
17+
import { Config } from "../../../src/config";
18+
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
1519

1620
const SNAPSHOT_TIMEOUT_SECONDS = 60 * 30;
1721
const SNAPSHOT_POLL_INTERVAL_SECONDS = 5;
@@ -31,6 +35,9 @@ export class SnapshotService {
3135
@inject(WorkspaceDB) protected readonly workspaceDb: WorkspaceDB;
3236
@inject(StorageClient) protected readonly storageClient: StorageClient;
3337
@inject(ConsensusLeaderQorum) protected readonly leaderQuorum: ConsensusLeaderQorum;
38+
@inject(WorkspaceManagerClientProvider)
39+
protected readonly workspaceManagerClientProvider: WorkspaceManagerClientProvider;
40+
@inject(Config) protected readonly config: Config;
3441

3542
protected readonly runningSnapshots: Map<string, Promise<void>> = new Map();
3643

@@ -74,14 +81,24 @@ export class SnapshotService {
7481
}
7582
}
7683

77-
public async createSnapshot(options: GitpodServer.TakeSnapshotOptions, snapshotUrl: string): Promise<Snapshot> {
84+
public async createSnapshot(ctx: TraceContext, instance: WorkspaceInstance): Promise<Snapshot> {
85+
const client = await this.workspaceManagerClientProvider.get(
86+
instance.region,
87+
this.config.installationShortname,
88+
);
89+
const request = new TakeSnapshotRequest();
90+
request.setId(instance.id);
91+
request.setReturnImmediately(true);
92+
93+
// this triggers the snapshots, but returns early! cmp. waitForSnapshot to wait for it's completion
94+
const resp = await client.takeSnapshot(ctx, request);
7895
const id = uuidv4();
7996
return await this.workspaceDb.storeSnapshot({
8097
id,
8198
creationTime: new Date().toISOString(),
8299
state: "pending",
83-
bucketId: snapshotUrl,
84-
originalWorkspaceId: options.workspaceId,
100+
bucketId: resp.getUrl(),
101+
originalWorkspaceId: instance.workspaceId,
85102
});
86103
}
87104

@@ -112,6 +129,24 @@ export class SnapshotService {
112129

113130
const { id: snapshotId, bucketId, originalWorkspaceId, creationTime } = opts.snapshot;
114131
const start = new Date(creationTime).getTime();
132+
const workspace = await this.workspaceDb.findWorkspaceAndInstance(originalWorkspaceId);
133+
if (!workspace) {
134+
const message = `Couldn't find original workspace for snapshot.`;
135+
await this.workspaceDb.updateSnapshot({
136+
id: snapshotId,
137+
state: "error",
138+
message,
139+
});
140+
throw new Error(message);
141+
}
142+
const client = await this.workspaceManagerClientProvider.get(
143+
workspace.region,
144+
this.config.installationShortname,
145+
);
146+
const req = new GetVolumeSnapshotRequest();
147+
req.setId(workspace.instanceId);
148+
149+
const isPVC = workspace?.config._featureFlags?.some((f) => f === "persistent_volume_claim");
115150
while (start + SNAPSHOT_TIMEOUT_SECONDS * 1000 > Date.now()) {
116151
await new Promise((resolve) => setTimeout(resolve, SNAPSHOT_POLL_INTERVAL_SECONDS * 1000));
117152

@@ -126,13 +161,19 @@ export class SnapshotService {
126161
if (snapshot.state === "error") {
127162
throw new Error(`snapshot error: ${snapshot.message}`);
128163
}
164+
let exists = false;
165+
if (isPVC) {
166+
const response = await client.getVolumeSnapshot({}, req);
167+
exists = response.getReady();
168+
} else {
169+
// pending: check if the snapshot is there
170+
exists = await this.storageClient.workspaceSnapshotExists(
171+
opts.workspaceOwner,
172+
originalWorkspaceId,
173+
bucketId,
174+
);
175+
}
129176

130-
// pending: check if the snapshot is there
131-
const exists = await this.storageClient.workspaceSnapshotExists(
132-
opts.workspaceOwner,
133-
originalWorkspaceId,
134-
bucketId,
135-
);
136177
if (exists) {
137178
await this.workspaceDb.updateSnapshot({
138179
id: snapshotId,

components/ws-manager-api/typescript/src/promisified-client.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import {
3030
TakeSnapshotResponse,
3131
UpdateSSHKeyRequest,
3232
UpdateSSHKeyResponse,
33+
GetVolumeSnapshotResponse,
34+
GetVolumeSnapshotRequest,
3335
} from "./core_pb";
3436
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
3537
import * as opentracing from "opentracing";
@@ -120,7 +122,7 @@ export class PromisifiedWorkspaceManagerClient implements Disposable {
120122
{
121123
// Important!!!!: client timeout must be higher than ws-manager to be able to process any error
122124
// https://github.com/gitpod-io/gitpod/blob/main/components/ws-manager/pkg/manager/manager.go#L171
123-
deadline: new Date(new Date().getTime() + 60000*11),
125+
deadline: new Date(new Date().getTime() + 60000 * 11),
124126
interceptors: this.interceptor,
125127
},
126128
(err, resp) => {
@@ -306,6 +308,30 @@ export class PromisifiedWorkspaceManagerClient implements Disposable {
306308
);
307309
}
308310

311+
public getVolumeSnapshot(ctx: TraceContext, request: GetVolumeSnapshotRequest): Promise<GetVolumeSnapshotResponse> {
312+
return this.retryIfUnavailable(
313+
(attempt: number) =>
314+
new Promise<GetVolumeSnapshotResponse>((resolve, reject) => {
315+
const span = TraceContext.startSpan(`/ws-manager/getVolumeSnapshot`, ctx);
316+
span.log({ attempt });
317+
this.client.getVolumeSnapshot(
318+
request,
319+
withTracing({ span }),
320+
this.getDefaultUnaryOptions(),
321+
(err, resp) => {
322+
span.finish();
323+
if (err) {
324+
TraceContext.setError(ctx, err);
325+
reject(err);
326+
} else {
327+
resolve(resp);
328+
}
329+
},
330+
);
331+
}),
332+
);
333+
}
334+
309335
public controlAdmission(ctx: TraceContext, request: ControlAdmissionRequest): Promise<ControlAdmissionResponse> {
310336
// we do not use the default options here as takeSnapshot can take a very long time - much longer than the default deadline allows
311337
return this.retryIfUnavailable(

0 commit comments

Comments
 (0)