6
6
7
7
import { injectable , inject , postConstruct } from "inversify" ;
8
8
import { log } from "@gitpod/gitpod-protocol/lib/util/logging" ;
9
- import { WorkspaceDeletionService } from "../workspace/workspace-deletion-service" ;
10
9
import * as opentracing from "opentracing" ;
11
- import { TracedWorkspaceDB , DBWithTracing , WorkspaceDB } from "@gitpod/gitpod-db/lib" ;
10
+ import {
11
+ TracedWorkspaceDB ,
12
+ DBWithTracing ,
13
+ WorkspaceDB ,
14
+ WorkspaceAndOwner ,
15
+ WorkspaceOwnerAndSoftDeleted ,
16
+ } from "@gitpod/gitpod-db/lib" ;
12
17
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing" ;
13
18
import { Config } from "../config" ;
14
19
import { Job } from "./runner" ;
15
20
import { WorkspaceService } from "../workspace/workspace-service" ;
21
+ import { StorageClient } from "../storage/storage-client" ;
16
22
17
23
/**
18
24
* The WorkspaceGarbageCollector has two tasks:
@@ -21,10 +27,12 @@ import { WorkspaceService } from "../workspace/workspace-service";
21
27
*/
22
28
@injectable ( )
23
29
export class WorkspaceGarbageCollector implements Job {
24
- @inject ( WorkspaceService ) protected readonly workspaceService : WorkspaceService ;
25
- @inject ( WorkspaceDeletionService ) protected readonly deletionService : WorkspaceDeletionService ;
26
- @inject ( TracedWorkspaceDB ) protected readonly workspaceDB : DBWithTracing < WorkspaceDB > ;
27
- @inject ( Config ) protected readonly config : Config ;
30
+ constructor (
31
+ @inject ( WorkspaceService ) protected readonly workspaceService : WorkspaceService ,
32
+ @inject ( StorageClient ) protected readonly storageClient : StorageClient ,
33
+ @inject ( TracedWorkspaceDB ) protected readonly workspaceDB : DBWithTracing < WorkspaceDB > ,
34
+ @inject ( Config ) protected readonly config : Config ,
35
+ ) { }
28
36
29
37
public name = "workspace-gc" ;
30
38
public frequencyMs : number ;
@@ -55,7 +63,7 @@ export class WorkspaceGarbageCollector implements Job {
55
63
/**
56
64
* Marks old, unused workspaces as softDeleted
57
65
*/
58
- protected async softDeleteOldWorkspaces ( ) {
66
+ private async softDeleteOldWorkspaces ( ) {
59
67
if ( Date . now ( ) < this . config . workspaceGarbageCollection . startDate ) {
60
68
log . info ( "workspace-gc: garbage collection not yet active." ) ;
61
69
return ;
@@ -89,7 +97,7 @@ export class WorkspaceGarbageCollector implements Job {
89
97
}
90
98
}
91
99
92
- protected async deleteWorkspaceContentAfterRetentionPeriod ( ) {
100
+ private async deleteWorkspaceContentAfterRetentionPeriod ( ) {
93
101
const span = opentracing . globalTracer ( ) . startSpan ( "deleteWorkspaceContentAfterRetentionPeriod" ) ;
94
102
try {
95
103
const workspaces = await this . workspaceDB
@@ -98,9 +106,7 @@ export class WorkspaceGarbageCollector implements Job {
98
106
this . config . workspaceGarbageCollection . contentRetentionPeriodDays ,
99
107
this . config . workspaceGarbageCollection . contentChunkLimit ,
100
108
) ;
101
- const deletes = await Promise . all (
102
- workspaces . map ( ( ws ) => this . deletionService . garbageCollectWorkspace ( { span } , ws ) ) ,
103
- ) ;
109
+ const deletes = await Promise . all ( workspaces . map ( ( ws ) => this . garbageCollectWorkspace ( { span } , ws ) ) ) ;
104
110
105
111
log . info ( `workspace-gc: successfully deleted the content of ${ deletes . length } workspaces` ) ;
106
112
span . addTags ( { nrOfCollectedWorkspaces : deletes . length } ) ;
@@ -115,7 +121,7 @@ export class WorkspaceGarbageCollector implements Job {
115
121
/**
116
122
* This method is meant to purge all traces of a Workspace and it's WorkspaceInstances from the DB
117
123
*/
118
- protected async purgeWorkspacesAfterPurgeRetentionPeriod ( ) {
124
+ private async purgeWorkspacesAfterPurgeRetentionPeriod ( ) {
119
125
const span = opentracing . globalTracer ( ) . startSpan ( "purgeWorkspacesAfterPurgeRetentionPeriod" ) ;
120
126
try {
121
127
const now = new Date ( ) ;
@@ -144,7 +150,7 @@ export class WorkspaceGarbageCollector implements Job {
144
150
}
145
151
}
146
152
147
- protected async deleteOldPrebuilds ( ) {
153
+ private async deleteOldPrebuilds ( ) {
148
154
const span = opentracing . globalTracer ( ) . startSpan ( "deleteOldPrebuilds" ) ;
149
155
try {
150
156
const workspaces = await this . workspaceDB
@@ -153,9 +159,7 @@ export class WorkspaceGarbageCollector implements Job {
153
159
this . config . workspaceGarbageCollection . minAgePrebuildDays ,
154
160
this . config . workspaceGarbageCollection . chunkLimit ,
155
161
) ;
156
- const deletes = await Promise . all (
157
- workspaces . map ( ( ws ) => this . deletionService . garbageCollectPrebuild ( { span } , ws ) ) ,
158
- ) ;
162
+ const deletes = await Promise . all ( workspaces . map ( ( ws ) => this . garbageCollectPrebuild ( { span } , ws ) ) ) ;
159
163
160
164
log . info ( `workspace-gc: successfully deleted ${ deletes . length } prebuilds` ) ;
161
165
span . addTags ( { nrOfCollectedPrebuilds : deletes . length } ) ;
@@ -166,4 +170,75 @@ export class WorkspaceGarbageCollector implements Job {
166
170
span . finish ( ) ;
167
171
}
168
172
}
173
+
174
+ /**
175
+ * This method garbageCollects a workspace. It deletes its contents and sets the workspaces 'contentDeletedTime'
176
+ * @param ctx
177
+ * @param ws
178
+ */
179
+ private async garbageCollectWorkspace ( ctx : TraceContext , ws : WorkspaceOwnerAndSoftDeleted ) : Promise < boolean > {
180
+ const span = TraceContext . startSpan ( "garbageCollectWorkspace" , ctx ) ;
181
+
182
+ try {
183
+ const successfulDeleted = await this . deleteWorkspaceStorage ( { span } , ws , true ) ;
184
+ await this . workspaceDB
185
+ . trace ( { span } )
186
+ . updatePartial ( ws . id , { contentDeletedTime : new Date ( ) . toISOString ( ) } ) ;
187
+ return successfulDeleted ;
188
+ } catch ( err ) {
189
+ TraceContext . setError ( { span } , err ) ;
190
+ throw err ;
191
+ } finally {
192
+ span . finish ( ) ;
193
+ }
194
+ }
195
+
196
+ /**
197
+ * @param ctx
198
+ * @param wsAndOwner
199
+ */
200
+ private async garbageCollectPrebuild ( ctx : TraceContext , ws : WorkspaceAndOwner ) : Promise < boolean > {
201
+ const span = TraceContext . startSpan ( "garbageCollectPrebuild" , ctx ) ;
202
+
203
+ try {
204
+ const successfulDeleted = await this . deleteWorkspaceStorage ( { span } , ws , true ) ;
205
+ const now = new Date ( ) . toISOString ( ) ;
206
+ // Note: soft & content deletion happens at the same time, because prebuilds are reproducible so there's no need for the extra time span.
207
+ await this . workspaceDB . trace ( { span } ) . updatePartial ( ws . id , {
208
+ contentDeletedTime : now ,
209
+ softDeletedTime : now ,
210
+ softDeleted : "gc" ,
211
+ } ) ;
212
+ return successfulDeleted ;
213
+ } catch ( err ) {
214
+ TraceContext . setError ( { span } , err ) ;
215
+ throw err ;
216
+ } finally {
217
+ span . finish ( ) ;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Performs the actual deletion of a workspace's backups (and optionally, snapshots). It:
223
+ * - throws an error if something went wrong during deletion
224
+ * - returns true in case of successful deletion
225
+ * @param ws
226
+ * @param includeSnapshots
227
+ */
228
+ private async deleteWorkspaceStorage (
229
+ ctx : TraceContext ,
230
+ ws : WorkspaceAndOwner ,
231
+ includeSnapshots : boolean ,
232
+ ) : Promise < boolean > {
233
+ const span = TraceContext . startSpan ( "deleteWorkspaceStorage" , ctx ) ;
234
+ try {
235
+ await this . storageClient . deleteWorkspaceBackups ( ws . ownerId , ws . id , includeSnapshots ) ;
236
+ } catch ( err ) {
237
+ TraceContext . setError ( { span } , err ) ;
238
+ throw err ;
239
+ } finally {
240
+ span . finish ( ) ;
241
+ }
242
+ return true ;
243
+ }
169
244
}
0 commit comments