Skip to content

Commit 66b7cee

Browse files
committed
tie snapshot volumes to workspace instance id
1 parent bc37221 commit 66b7cee

File tree

8 files changed

+55
-36
lines changed

8 files changed

+55
-36
lines changed

components/gitpod-db/src/typeorm/entity/db-volume-snapshot.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Transformer } from "../transformer";
1313
@Entity()
1414
@Index("ind_dbsync", ["creationTime"]) // DBSync
1515
export class DBVolumeSnapshot implements VolumeSnapshot {
16-
@PrimaryColumn(TypeORM.UUID_COLUMN_TYPE)
16+
@PrimaryColumn(TypeORM.WORKSPACE_ID_COLUMN_TYPE)
1717
id: string;
1818

1919
@Column({
@@ -24,10 +24,6 @@ export class DBVolumeSnapshot implements VolumeSnapshot {
2424
})
2525
creationTime: string;
2626

27-
@Column(TypeORM.WORKSPACE_ID_COLUMN_TYPE)
28-
@Index("ind_originalWorkspaceId")
29-
originalWorkspaceId: string;
30-
3127
@Column("varchar")
3228
volumeHandle: string;
3329
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 { MigrationInterface, QueryRunner } from "typeorm";
8+
import { tableExists } from "./helper/helper";
9+
10+
export class VolumeSnapshot21652221085595 implements MigrationInterface {
11+
public async up(queryRunner: QueryRunner): Promise<void> {
12+
if (await tableExists(queryRunner, "d_b_volume_snapshot")) {
13+
await queryRunner.query("DROP TABLE `d_b_volume_snapshot`");
14+
}
15+
await queryRunner.query(
16+
`CREATE TABLE IF NOT EXISTS d_b_volume_snapshot ( id char(36) NOT NULL, creationTime timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), volumeHandle varchar(255) NOT NULL, PRIMARY KEY (id), KEY ind_dbsync (creationTime)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`,
17+
);
18+
}
19+
20+
public async down(queryRunner: QueryRunner): Promise<void> {}
21+
}

components/gitpod-db/src/typeorm/workspace-db-impl.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -729,11 +729,6 @@ export abstract class AbstractTypeORMWorkspaceDBImpl implements WorkspaceDB {
729729
await volumeSnapshots.update(volumeSnapshot.id, volumeSnapshot);
730730
}
731731

732-
public async findVolumeSnapshotsByWorkspaceId(workspaceId: string): Promise<VolumeSnapshot[]> {
733-
const volumeSnapshots = await this.getVolumeSnapshotRepo();
734-
return volumeSnapshots.find({ where: { originalWorkspaceId: workspaceId } });
735-
}
736-
737732
public async storePrebuiltWorkspace(pws: PrebuiltWorkspace): Promise<PrebuiltWorkspace> {
738733
const repo = await this.getPrebuiltWorkspaceRepo();
739734
if (pws.error && pws.error.length > 255) {

components/gitpod-db/src/workspace-db.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ export interface WorkspaceDB {
161161
updateSnapshot(snapshot: DeepPartial<Snapshot> & Pick<Snapshot, "id">): Promise<void>;
162162

163163
findVolumeSnapshotById(volumeSnapshotId: string): Promise<VolumeSnapshot | undefined>;
164-
findVolumeSnapshotsByWorkspaceId(workspaceId: string): Promise<VolumeSnapshot[]>;
165164
storeVolumeSnapshot(snapshot: VolumeSnapshot): Promise<VolumeSnapshot>;
166165
deleteVolumeSnapshot(volumeSnapshotId: string): Promise<void>;
167166
updateVolumeSnapshot(snapshot: DeepPartial<VolumeSnapshot> & Pick<VolumeSnapshot, "id">): Promise<void>;

components/gitpod-protocol/src/protocol.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ export namespace UserEnvVar {
243243
return "Name must not be empty.";
244244
}
245245
if (name.length > 255) {
246-
return 'Name too long. Maximum name length is 255 characters.';
246+
return "Name too long. Maximum name length is 255 characters.";
247247
}
248248
if (!/^[a-zA-Z_]+[a-zA-Z0-9_]*$/.test(name)) {
249249
return "Name must match /^[a-zA-Z_]+[a-zA-Z0-9_]*$/.";
@@ -252,7 +252,7 @@ export namespace UserEnvVar {
252252
return "Value must not be empty.";
253253
}
254254
if (variable.value.length > 32767) {
255-
return 'Value too long. Maximum value length is 32767 characters.';
255+
return "Value too long. Maximum value length is 32767 characters.";
256256
}
257257
if (pattern.trim() === "") {
258258
return "Scope must not be empty.";
@@ -491,7 +491,6 @@ export interface Snapshot {
491491
export interface VolumeSnapshot {
492492
id: string;
493493
creationTime: string;
494-
originalWorkspaceId: string;
495494
volumeHandle: string;
496495
}
497496

components/server/src/workspace/workspace-starter.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,15 @@ export class WorkspaceStarter {
259259

260260
// check if there has been an instance before, i.e. if this is a restart
261261
const pastInstances = await this.workspaceDb.trace({ span }).findInstances(workspace.id);
262-
const mustHaveBackup = pastInstances.some(
262+
const hasValidBackup = pastInstances.some(
263263
(i) => !!i.status && !!i.status.conditions && !i.status.conditions.failed,
264264
);
265+
let lastValidWorkspaceInstanceId = "";
266+
if (hasValidBackup) {
267+
lastValidWorkspaceInstanceId = pastInstances.reduce((previousValue, currentValue) =>
268+
currentValue.creationTime > previousValue.creationTime ? currentValue : previousValue,
269+
).id;
270+
}
265271

266272
const ideConfig = await this.ideConfigService.config;
267273

@@ -305,7 +311,7 @@ export class WorkspaceStarter {
305311
instance,
306312
workspace,
307313
user,
308-
mustHaveBackup,
314+
lastValidWorkspaceInstanceId,
309315
ideConfig,
310316
userEnvVars,
311317
projectEnvVars,
@@ -320,7 +326,7 @@ export class WorkspaceStarter {
320326
instance,
321327
workspace,
322328
user,
323-
mustHaveBackup,
329+
lastValidWorkspaceInstanceId,
324330
ideConfig,
325331
userEnvVars,
326332
projectEnvVars,
@@ -357,7 +363,7 @@ export class WorkspaceStarter {
357363
instance: WorkspaceInstance,
358364
workspace: Workspace,
359365
user: User,
360-
mustHaveBackup: boolean,
366+
lastValidWorkspaceInstanceId: string,
361367
ideConfig: IDEConfig,
362368
userEnvVars: UserEnvVar[],
363369
projectEnvVars: ProjectEnvVar[],
@@ -392,7 +398,7 @@ export class WorkspaceStarter {
392398
user,
393399
workspace,
394400
instance,
395-
mustHaveBackup,
401+
lastValidWorkspaceInstanceId,
396402
ideConfig,
397403
userEnvVars,
398404
projectEnvVars,
@@ -856,7 +862,7 @@ export class WorkspaceStarter {
856862
workspace,
857863
workspace.context,
858864
user,
859-
false,
865+
"",
860866
false,
861867
);
862868
source = initializer;
@@ -1080,7 +1086,7 @@ export class WorkspaceStarter {
10801086
user: User,
10811087
workspace: Workspace,
10821088
instance: WorkspaceInstance,
1083-
mustHaveBackup: boolean,
1089+
lastValidWorkspaceInstanceId: string,
10841090
ideConfig: IDEConfig,
10851091
userEnvVars: UserEnvVarValue[],
10861092
projectEnvVars: ProjectEnvVar[],
@@ -1261,22 +1267,21 @@ export class WorkspaceStarter {
12611267
}
12621268

12631269
let volumeSnapshotInfo = new PvcSnapshotVolumeInfo();
1264-
const volumeSnapshots = await this.workspaceDb.trace(traceCtx).findVolumeSnapshotsByWorkspaceId(workspace.id);
1265-
if (volumeSnapshots.length > 0) {
1266-
const latestVolumeSnapshot = volumeSnapshots.reduce((previousValue, currentValue) =>
1267-
currentValue.creationTime > previousValue.creationTime ? currentValue : previousValue,
1268-
);
1269-
volumeSnapshotInfo.setSnapshotVolumeName(latestVolumeSnapshot.id);
1270-
volumeSnapshotInfo.setSnapshotVolumeHandle(latestVolumeSnapshot.volumeHandle);
1270+
const volumeSnapshots = await this.workspaceDb
1271+
.trace(traceCtx)
1272+
.findVolumeSnapshotById(lastValidWorkspaceInstanceId);
1273+
if (volumeSnapshots !== undefined) {
1274+
volumeSnapshotInfo.setSnapshotVolumeName(volumeSnapshots.id);
1275+
volumeSnapshotInfo.setSnapshotVolumeHandle(volumeSnapshots.volumeHandle);
12711276
}
12721277

12731278
const initializerPromise = this.createInitializer(
12741279
traceCtx,
12751280
workspace,
12761281
workspace.context,
12771282
user,
1278-
mustHaveBackup,
1279-
volumeSnapshotInfo.getSnapshotVolumeName() != "",
1283+
lastValidWorkspaceInstanceId,
1284+
volumeSnapshots !== undefined,
12801285
);
12811286
const userTimeoutPromise = this.userService.getDefaultWorkspaceTimeout(user);
12821287

@@ -1426,13 +1431,13 @@ export class WorkspaceStarter {
14261431
workspace: Workspace,
14271432
context: WorkspaceContext,
14281433
user: User,
1429-
mustHaveBackup: boolean,
1434+
lastValidWorkspaceInstanceId: string,
14301435
hasVolumeSnapshot: boolean,
14311436
): Promise<{ initializer: WorkspaceInitializer; disposable: Disposable }> {
14321437
let result = new WorkspaceInitializer();
14331438
const disp = new DisposableCollection();
14341439

1435-
if (mustHaveBackup) {
1440+
if (lastValidWorkspaceInstanceId != "") {
14361441
const backup = new FromBackupInitializer();
14371442
if (CommitContext.is(context)) {
14381443
backup.setCheckoutLocation(context.checkoutLocation || "");

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,14 +360,20 @@ export class WorkspaceManagerBridge implements Disposable {
360360
status.conditions.pvcSnapshotVolume.snapshotVolumeName != "" &&
361361
writeToDB
362362
) {
363+
if (status.conditions.pvcSnapshotVolume.snapshotVolumeName != instance.id) {
364+
log.error(
365+
"snapshot volume name doesn't match workspace instance id",
366+
status.conditions.pvcSnapshotVolume.snapshotVolumeName,
367+
instance.id,
368+
);
369+
}
363370
let existingSnapshot = await this.workspaceDB
364371
.trace(ctx)
365372
.findVolumeSnapshotById(status.conditions.pvcSnapshotVolume.snapshotVolumeName);
366373
if (existingSnapshot === undefined) {
367374
await this.workspaceDB.trace(ctx).storeVolumeSnapshot({
368375
id: status.conditions.pvcSnapshotVolume.snapshotVolumeName,
369376
creationTime: new Date().toISOString(),
370-
originalWorkspaceId: workspaceId,
371377
volumeHandle: status.conditions.pvcSnapshotVolume.snapshotVolumeHandle,
372378
});
373379
}

components/ws-manager/pkg/manager/monitor.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2727
"k8s.io/apimachinery/pkg/labels"
2828
"k8s.io/apimachinery/pkg/types"
29-
"k8s.io/apimachinery/pkg/util/uuid"
3029
"k8s.io/apimachinery/pkg/util/wait"
3130
"k8s.io/apimachinery/pkg/watch"
3231
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -877,7 +876,7 @@ func (m *Monitor) finalizeWorkspaceContent(ctx context.Context, wso *workspaceOb
877876
)
878877
if wso.Pod != nil {
879878
_, pvcFeatureEnabled = wso.Pod.Labels[pvcWorkspaceFeatureAnnotation]
880-
pvcSnapshotVolumeName = string(uuid.NewUUID())
879+
pvcSnapshotVolumeName = workspaceID
881880
wsClassName := ""
882881
if _, ok := wso.Pod.Labels[workspaceClassLabel]; ok {
883882
wsClassName = wso.Pod.Labels[workspaceClassLabel]
@@ -998,7 +997,6 @@ func (m *Monitor) finalizeWorkspaceContent(ctx context.Context, wso *workspaceOb
998997

999998
snapshotHandle := *volumeSnapshotContent.Status.SnapshotHandle
1000999

1001-
log.Infof("snapshot name: %s, handle: %s", pvcSnapshotVolumeName, snapshotHandle)
10021000
b, err := json.Marshal(workspaceSnapshotVolumeStatus{PvcSnapshotVolumeName: pvcSnapshotVolumeName, PvcSnapshotVolumeHandle: snapshotHandle})
10031001
if err != nil {
10041002
return true, nil, err

0 commit comments

Comments
 (0)