@@ -11,6 +11,7 @@ import (
11
11
"time"
12
12
13
13
"github.com/google/uuid"
14
+ "github.com/stretchr/testify/assert"
14
15
"github.com/stretchr/testify/require"
15
16
"go.uber.org/goleak"
16
17
"golang.org/x/exp/slices"
@@ -181,139 +182,188 @@ func containsWorkspaceAgentStat(stats []database.GetWorkspaceAgentStatsRow, need
181
182
182
183
//nolint:paralleltest // It uses LockIDDBPurge.
183
184
func TestDeleteOldWorkspaceAgentLogs (t * testing.T ) {
184
- db , _ := dbtestutil .NewDB (t )
185
+ ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitShort )
186
+ defer cancel ()
187
+ clk := quartz .NewMock (t )
188
+ now := dbtime .Now ()
189
+ threshold := now .Add (- 7 * 24 * time .Hour )
190
+ beforeThreshold := threshold .Add (- time .Hour )
191
+ afterThreshold := threshold .Add (time .Hour )
192
+ clk .Set (now ).MustWait (ctx )
193
+
194
+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
185
195
org := dbgen .Organization (t , db , database.Organization {})
186
196
user := dbgen .User (t , db , database.User {})
187
197
_ = dbgen .OrganizationMember (t , db , database.OrganizationMember {UserID : user .ID , OrganizationID : org .ID })
188
198
tv := dbgen .TemplateVersion (t , db , database.TemplateVersion {OrganizationID : org .ID , CreatedBy : user .ID })
189
199
tmpl := dbgen .Template (t , db , database.Template {OrganizationID : org .ID , ActiveVersionID : tv .ID , CreatedBy : user .ID })
190
200
191
201
logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
192
- now := dbtime .Now ()
193
202
194
- //nolint:paralleltest // It uses LockIDDBPurge.
195
- t .Run ("AgentHasNotConnectedSinceWeek_LogsExpired" , func (t * testing.T ) {
196
- ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitShort )
197
- defer cancel ()
198
- clk := quartz .NewMock (t )
199
- clk .Set (now ).MustWait (ctx )
200
-
201
- // After dbpurge completes, the ticker is reset. Trap this call.
202
- trapReset := clk .Trap ().TickerReset ()
203
- defer trapReset .Close ()
204
-
205
- // given: an agent with logs older than threshold
206
- agent := mustCreateAgentWithLogs (ctx , t , db , user , org , tmpl , tv , now .Add (- 8 * 24 * time .Hour ), t .Name ())
207
-
208
- // when dbpurge runs
209
- closer := dbpurge .New (ctx , logger , db , clk )
210
- defer closer .Close ()
211
- // Wait for the initial nanosecond tick.
212
- clk .Advance (time .Nanosecond ).MustWait (ctx )
213
-
214
- trapReset .MustWait (ctx ).Release () // Wait for ticker.Reset()
215
- d , w := clk .AdvanceNext ()
216
- require .Equal (t , 10 * time .Minute , d )
217
-
218
- closer .Close () // doTick() has now run.
219
- w .MustWait (ctx )
220
-
221
- // then the logs should be gone
222
- agentLogs , err := db .GetWorkspaceAgentLogsAfter (ctx , database.GetWorkspaceAgentLogsAfterParams {
223
- AgentID : agent ,
224
- CreatedAfter : 0 ,
225
- })
226
- require .NoError (t , err )
227
- require .Empty (t , agentLogs , "expected agent logs to be empty" )
228
- })
203
+ // Given the following:
204
+
205
+ // Workspace A was built once before the threshold, and never connected.
206
+ wsA := dbgen .Workspace (t , db , database.Workspace {OwnerID : user .ID , OrganizationID : org .ID , TemplateID : tmpl .ID })
207
+ wbA1 := mustCreateWorkspaceBuild (t , db , org , tv , wsA .ID , beforeThreshold , 1 )
208
+ agentA1 := mustCreateAgent (t , db , wbA1 )
209
+ mustCreateAgentLogs (ctx , t , db , agentA1 .ID , nil , "agent a1 logs should be deleted" )
210
+
211
+ // Workspace B was built twice before the threshold.
212
+ wsB := dbgen .Workspace (t , db , database.Workspace {OwnerID : user .ID , OrganizationID : org .ID , TemplateID : tmpl .ID })
213
+ wbB1 := mustCreateWorkspaceBuild (t , db , org , tv , wsB .ID , beforeThreshold , 1 )
214
+ wbB2 := mustCreateWorkspaceBuild (t , db , org , tv , wsB .ID , beforeThreshold , 2 )
215
+ agentB1 := mustCreateAgent (t , db , wbB1 )
216
+ agentB2 := mustCreateAgent (t , db , wbB2 )
217
+ mustCreateAgentLogs (ctx , t , db , agentB1 .ID , & beforeThreshold , "agent b1 logs should be deleted" )
218
+ mustCreateAgentLogs (ctx , t , db , agentB2 .ID , & beforeThreshold , "agent b2 logs should be retained" )
219
+
220
+ // Workspace C was built once before the threshold, and once after.
221
+ wsC := dbgen .Workspace (t , db , database.Workspace {OwnerID : user .ID , OrganizationID : org .ID , TemplateID : tmpl .ID })
222
+ wbC1 := mustCreateWorkspaceBuild (t , db , org , tv , wsC .ID , beforeThreshold , 1 )
223
+ wbC2 := mustCreateWorkspaceBuild (t , db , org , tv , wsC .ID , afterThreshold , 2 )
224
+ agentC1 := mustCreateAgent (t , db , wbC1 )
225
+ agentC2 := mustCreateAgent (t , db , wbC2 )
226
+ mustCreateAgentLogs (ctx , t , db , agentC1 .ID , & beforeThreshold , "agent c1 logs should be deleted" )
227
+ mustCreateAgentLogs (ctx , t , db , agentC2 .ID , & afterThreshold , "agent c2 logs should be retained" )
228
+
229
+ // Workspace D was built twice after the threshold.
230
+ wsD := dbgen .Workspace (t , db , database.Workspace {OwnerID : user .ID , OrganizationID : org .ID , TemplateID : tmpl .ID })
231
+ wbD1 := mustCreateWorkspaceBuild (t , db , org , tv , wsD .ID , afterThreshold , 1 )
232
+ wbD2 := mustCreateWorkspaceBuild (t , db , org , tv , wsD .ID , afterThreshold , 2 )
233
+ agentD1 := mustCreateAgent (t , db , wbD1 )
234
+ agentD2 := mustCreateAgent (t , db , wbD2 )
235
+ mustCreateAgentLogs (ctx , t , db , agentD1 .ID , & afterThreshold , "agent d1 logs should be retained" )
236
+ mustCreateAgentLogs (ctx , t , db , agentD2 .ID , & afterThreshold , "agent d2 logs should be retained" )
237
+
238
+ // Workspace E was build once after threshold but never connected.
239
+ wsE := dbgen .Workspace (t , db , database.Workspace {OwnerID : user .ID , OrganizationID : org .ID , TemplateID : tmpl .ID })
240
+ wbE1 := mustCreateWorkspaceBuild (t , db , org , tv , wsE .ID , beforeThreshold , 1 )
241
+ agentE1 := mustCreateAgent (t , db , wbE1 )
242
+ mustCreateAgentLogs (ctx , t , db , agentE1 .ID , nil , "agent e1 logs should be retained" )
243
+
244
+ // when dbpurge runs
245
+
246
+ // After dbpurge completes, the ticker is reset. Trap this call.
247
+ trapReset := clk .Trap ().TickerReset ()
248
+ defer trapReset .Close ()
229
249
230
- //nolint:paralleltest // It uses LockIDDBPurge.
231
- t .Run ("AgentConnectedSixDaysAgo_LogsValid" , func (t * testing.T ) {
232
- ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitShort )
233
- defer cancel ()
234
- clk := quartz .NewMock (t )
235
- clk .Set (now ).MustWait (ctx )
236
-
237
- // After dbpurge completes, the ticker is reset. Trap this call.
238
- trapReset := clk .Trap ().TickerReset ()
239
- defer trapReset .Close ()
240
-
241
- // given: an agent with logs newer than threshold
242
- agent := mustCreateAgentWithLogs (ctx , t , db , user , org , tmpl , tv , now .Add (- 6 * 24 * time .Hour ), t .Name ())
243
-
244
- // when dbpurge runs
245
- closer := dbpurge .New (ctx , logger , db , clk )
246
- defer closer .Close ()
247
-
248
- // Wait for the initial nanosecond tick.
249
- clk .Advance (time .Nanosecond ).MustWait (ctx )
250
-
251
- trapReset .MustWait (ctx ).Release () // Wait for ticker.Reset()
252
- d , w := clk .AdvanceNext ()
253
- require .Equal (t , 10 * time .Minute , d )
254
-
255
- closer .Close () // doTick() has now run.
256
- w .MustWait (ctx )
257
-
258
- // then the logs should still be there
259
- agentLogs , err := db .GetWorkspaceAgentLogsAfter (ctx , database.GetWorkspaceAgentLogsAfterParams {
260
- AgentID : agent ,
261
- })
262
- require .NoError (t , err )
263
- require .NotEmpty (t , agentLogs )
264
- for _ , al := range agentLogs {
265
- require .Equal (t , t .Name (), al .Output )
266
- }
267
- })
268
- }
250
+ closer := dbpurge .New (ctx , logger , db , clk )
251
+ defer closer .Close ()
252
+ // Wait for the initial nanosecond tick.
253
+ clk .Advance (time .Nanosecond ).MustWait (ctx )
269
254
270
- func mustCreateAgentWithLogs (ctx context.Context , t * testing.T , db database.Store , user database.User , org database.Organization , tmpl database.Template , tv database.TemplateVersion , agentLastConnectedAt time.Time , output string ) uuid.UUID {
271
- agent := mustCreateAgent (t , db , user , org , tmpl , tv )
255
+ trapReset .MustWait (ctx ).Release () // Wait for ticker.Reset()
256
+ d , w := clk .AdvanceNext ()
257
+ require .Equal (t , 10 * time .Minute , d )
258
+
259
+ closer .Close () // doTick() has now run.
260
+ w .MustWait (ctx )
261
+
262
+ // then logs related to the following agents should be deleted:
263
+ // Agent A1 never connected and was created before the threshold.
264
+ assertNoWorkspaceAgentLogs (ctx , t , db , agentA1 .ID )
265
+ // Agent B1 is not the latest build and the logs are from before threshold.
266
+ assertNoWorkspaceAgentLogs (ctx , t , db , agentB1 .ID )
267
+ // Agent C1 is not the latest build and the logs are from before threshold.
268
+ assertNoWorkspaceAgentLogs (ctx , t , db , agentC1 .ID )
269
+
270
+ // then logs related to the following agents should be retained:
271
+ // Agent B2 is the latest build.
272
+ assertWorkspaceAgentLogs (ctx , t , db , agentB2 .ID , "agent b2 logs should be retained" )
273
+ // Agent C2 is the latest build.
274
+ assertWorkspaceAgentLogs (ctx , t , db , agentC2 .ID , "agent c2 logs should be retained" )
275
+ // Agents D1, D2, and E1 are all after threshold.
276
+ assertWorkspaceAgentLogs (ctx , t , db , agentD1 .ID , "agent d1 logs should be retained" )
277
+ assertWorkspaceAgentLogs (ctx , t , db , agentD2 .ID , "agent d2 logs should be retained" )
278
+ assertWorkspaceAgentLogs (ctx , t , db , agentE1 .ID , "agent e1 logs should be retained" )
279
+ }
272
280
273
- err := db .UpdateWorkspaceAgentConnectionByID (ctx , database.UpdateWorkspaceAgentConnectionByIDParams {
274
- ID : agent .ID ,
275
- LastConnectedAt : sql.NullTime {Time : agentLastConnectedAt , Valid : true },
276
- })
277
- require .NoError (t , err )
278
- _ , err = db .InsertWorkspaceAgentLogs (ctx , database.InsertWorkspaceAgentLogsParams {
279
- AgentID : agent .ID ,
280
- CreatedAt : agentLastConnectedAt ,
281
- Output : []string {output },
282
- Level : []database.LogLevel {database .LogLevelDebug },
281
+ func assertNoWorkspaceAgentLogs (ctx context.Context , t * testing.T , db database.Store , agentID uuid.UUID ) {
282
+ t .Helper ()
283
+ agentLogs , err := db .GetWorkspaceAgentLogsAfter (ctx , database.GetWorkspaceAgentLogsAfterParams {
284
+ AgentID : agentID ,
285
+ CreatedAfter : 0 ,
283
286
})
284
287
require .NoError (t , err )
285
- // Make sure that agent logs have been collected.
288
+ assert .Empty (t , agentLogs )
289
+ }
290
+
291
+ func assertWorkspaceAgentLogs (ctx context.Context , t * testing.T , db database.Store , agentID uuid.UUID , msg string ) {
292
+ t .Helper ()
286
293
agentLogs , err := db .GetWorkspaceAgentLogsAfter (ctx , database.GetWorkspaceAgentLogsAfterParams {
287
- AgentID : agent .ID ,
294
+ AgentID : agentID ,
295
+ CreatedAfter : 0 ,
288
296
})
289
297
require .NoError (t , err )
290
- require .NotZero (t , agentLogs , "agent logs must be present" )
291
- return agent .ID
298
+ assert .NotEmpty (t , agentLogs )
299
+ for _ , al := range agentLogs {
300
+ assert .Equal (t , msg , al .Output )
301
+ }
292
302
}
293
303
294
- func mustCreateAgent (t * testing.T , db database.Store , user database.User , org database.Organization , tmpl database. Template , tv database. TemplateVersion ) database.WorkspaceAgent {
295
- workspace := dbgen . Workspace ( t , db , database. Workspace { OwnerID : user . ID , OrganizationID : org . ID , TemplateID : tmpl . ID } )
304
+ func mustCreateWorkspaceBuild (t * testing.T , db database.Store , org database.Organization , tv database.TemplateVersion , wsID uuid. UUID , createdAt time. Time , n int32 ) database.WorkspaceBuild {
305
+ t . Helper ( )
296
306
job := dbgen .ProvisionerJob (t , db , nil , database.ProvisionerJob {
307
+ CreatedAt : createdAt ,
297
308
OrganizationID : org .ID ,
298
309
Type : database .ProvisionerJobTypeWorkspaceBuild ,
299
310
Provisioner : database .ProvisionerTypeEcho ,
300
311
StorageMethod : database .ProvisionerStorageMethodFile ,
301
312
})
302
- _ = dbgen .WorkspaceBuild (t , db , database.WorkspaceBuild {
303
- WorkspaceID : workspace .ID ,
313
+ wb := dbgen .WorkspaceBuild (t , db , database.WorkspaceBuild {
314
+ CreatedAt : createdAt ,
315
+ WorkspaceID : wsID ,
304
316
JobID : job .ID ,
305
317
TemplateVersionID : tv .ID ,
306
318
Transition : database .WorkspaceTransitionStart ,
307
319
Reason : database .BuildReasonInitiator ,
320
+ BuildNumber : n ,
308
321
})
322
+ require .Equal (t , createdAt .UTC (), wb .CreatedAt .UTC ())
323
+ return wb
324
+ }
325
+
326
+ func mustCreateAgent (t * testing.T , db database.Store , wb database.WorkspaceBuild ) database.WorkspaceAgent {
327
+ t .Helper ()
309
328
resource := dbgen .WorkspaceResource (t , db , database.WorkspaceResource {
310
- JobID : job . ID ,
329
+ JobID : wb . JobID ,
311
330
Transition : database .WorkspaceTransitionStart ,
331
+ CreatedAt : wb .CreatedAt ,
312
332
})
313
333
314
- return dbgen .WorkspaceAgent (t , db , database.WorkspaceAgent {
315
- ResourceID : resource .ID ,
334
+ wa := dbgen .WorkspaceAgent (t , db , database.WorkspaceAgent {
335
+ ResourceID : resource .ID ,
336
+ CreatedAt : wb .CreatedAt ,
337
+ FirstConnectedAt : sql.NullTime {},
338
+ DisconnectedAt : sql.NullTime {},
339
+ LastConnectedAt : sql.NullTime {},
316
340
})
341
+ require .Equal (t , wb .CreatedAt .UTC (), wa .CreatedAt .UTC ())
342
+ return wa
343
+ }
344
+
345
+ func mustCreateAgentLogs (ctx context.Context , t * testing.T , db database.Store , agentID uuid.UUID , agentLastConnectedAt * time.Time , output string ) uuid.UUID {
346
+ t .Helper ()
347
+ if agentLastConnectedAt != nil {
348
+ require .NoError (t , db .UpdateWorkspaceAgentConnectionByID (ctx , database.UpdateWorkspaceAgentConnectionByIDParams {
349
+ ID : agentID ,
350
+ LastConnectedAt : sql.NullTime {Time : * agentLastConnectedAt , Valid : true },
351
+ }))
352
+ }
353
+ _ , err := db .InsertWorkspaceAgentLogs (ctx , database.InsertWorkspaceAgentLogsParams {
354
+ AgentID : agentID ,
355
+ // CreatedAt: agentLastConnectedAt,
356
+ Output : []string {output },
357
+ Level : []database.LogLevel {database .LogLevelDebug },
358
+ })
359
+ require .NoError (t , err )
360
+ // Make sure that agent logs have been collected.
361
+ agentLogs , err := db .GetWorkspaceAgentLogsAfter (ctx , database.GetWorkspaceAgentLogsAfterParams {
362
+ AgentID : agentID ,
363
+ })
364
+ require .NoError (t , err )
365
+ require .NotEmpty (t , agentLogs , "agent logs must be present" )
366
+ return agentID
317
367
}
318
368
319
369
//nolint:paralleltest // It uses LockIDDBPurge.
0 commit comments