@@ -5,12 +5,14 @@ import (
5
5
"database/sql"
6
6
"encoding/json"
7
7
"fmt"
8
+ "math/rand"
8
9
"os"
9
10
"reflect"
10
11
"sync/atomic"
11
12
"testing"
12
13
"time"
13
14
15
+ "github.com/coder/coder/v2/cryptorand"
14
16
"github.com/google/uuid"
15
17
"github.com/prometheus/client_golang/prometheus"
16
18
"github.com/stretchr/testify/assert"
@@ -110,7 +112,7 @@ func TestActiveUsers(t *testing.T) {
110
112
}
111
113
}
112
114
113
- func TestWorkspaces (t * testing.T ) {
115
+ func TestWorkspaceStatuses (t * testing.T ) {
114
116
t .Parallel ()
115
117
116
118
insertRunning := func (db database.Store ) database.ProvisionerJob {
@@ -229,33 +231,273 @@ func TestWorkspaces(t *testing.T) {
229
231
t .Run (tc .Name , func (t * testing.T ) {
230
232
t .Parallel ()
231
233
registry := prometheus .NewRegistry ()
232
- closeFunc , err := prometheusmetrics .Workspaces (context .Background (), slogtest .Make (t , nil ), registry , tc .Database (), time . Millisecond )
234
+ closeFunc , err := prometheusmetrics .Workspaces (context .Background (), slogtest .Make (t , nil ), registry , tc .Database (), testutil . IntervalFast )
233
235
require .NoError (t , err )
234
236
t .Cleanup (closeFunc )
235
237
236
238
require .Eventually (t , func () bool {
237
239
metrics , err := registry .Gather ()
238
240
assert .NoError (t , err )
239
- if len (metrics ) < 1 {
240
- return false
241
- }
242
241
sum := 0
243
- for _ , metric := range metrics [0 ].Metric {
244
- count , ok := tc .Status [codersdk .ProvisionerJobStatus (metric .Label [0 ].GetValue ())]
245
- if metric .Gauge .GetValue () == 0 {
242
+ for _ , m := range metrics {
243
+ if m .GetName () != "coderd_api_workspace_latest_build_total" {
246
244
continue
247
245
}
248
- if ! ok {
249
- t .Fail ()
250
- }
251
- if metric .Gauge .GetValue () != float64 (count ) {
252
- return false
246
+
247
+ for _ , metric := range m .Metric {
248
+ count , ok := tc .Status [codersdk .ProvisionerJobStatus (metric .Label [0 ].GetValue ())]
249
+ if metric .Gauge .GetValue () == 0 {
250
+ continue
251
+ }
252
+ if ! ok {
253
+ t .Fail ()
254
+ }
255
+ if metric .Gauge .GetValue () != float64 (count ) {
256
+ return false
257
+ }
258
+ sum += int (metric .Gauge .GetValue ())
253
259
}
254
- sum += int (metric .Gauge .GetValue ())
255
260
}
256
261
t .Logf ("sum %d == total %d" , sum , tc .Total )
257
262
return sum == tc .Total
258
- }, testutil .WaitShort , testutil .IntervalFast )
263
+ }, testutil .WaitSuperShort , testutil .IntervalFast )
264
+ })
265
+ }
266
+ }
267
+
268
+ func TestWorkspaceDetails (t * testing.T ) {
269
+ t .Parallel ()
270
+
271
+ templateA := uuid .New ()
272
+ templateVersionA := uuid .New ()
273
+ templateB := uuid .New ()
274
+ templateVersionB := uuid .New ()
275
+
276
+ insertTemplates := func (db database.Store ) {
277
+ require .NoError (t , db .InsertTemplate (context .Background (), database.InsertTemplateParams {
278
+ ID : templateA ,
279
+ Name : "template-a" ,
280
+ Provisioner : database .ProvisionerTypeTerraform ,
281
+ MaxPortSharingLevel : database .AppSharingLevelAuthenticated ,
282
+ }))
283
+
284
+ require .NoError (t , db .InsertTemplateVersion (context .Background (), database.InsertTemplateVersionParams {
285
+ ID : templateVersionA ,
286
+ TemplateID : uuid.NullUUID {UUID : templateA },
287
+ Name : "version-1a" ,
288
+ }))
289
+
290
+ require .NoError (t , db .InsertTemplate (context .Background (), database.InsertTemplateParams {
291
+ ID : templateB ,
292
+ Name : "template-b" ,
293
+ Provisioner : database .ProvisionerTypeTerraform ,
294
+ MaxPortSharingLevel : database .AppSharingLevelAuthenticated ,
295
+ }))
296
+
297
+ require .NoError (t , db .InsertTemplateVersion (context .Background (), database.InsertTemplateVersionParams {
298
+ ID : templateVersionB ,
299
+ TemplateID : uuid.NullUUID {UUID : templateB },
300
+ Name : "version-1b" ,
301
+ }))
302
+ }
303
+
304
+ insertUser := func (db database.Store ) database.User {
305
+ username , err := cryptorand .String (8 )
306
+ require .NoError (t , err )
307
+
308
+ user , err := db .InsertUser (context .Background (), database.InsertUserParams {
309
+ ID : uuid .New (),
310
+ Username : username ,
311
+ LoginType : database .LoginTypeNone ,
312
+ })
313
+ require .NoError (t , err )
314
+
315
+ return user
316
+ }
317
+
318
+ insertRunning := func (db database.Store ) database.ProvisionerJob {
319
+ var template , templateVersion uuid.UUID
320
+ if rand .Intn (10 ) > 5 {
321
+ template = templateB
322
+ templateVersion = templateVersionB
323
+ } else {
324
+ template = templateA
325
+ templateVersion = templateVersionA
326
+ }
327
+
328
+ workspace , err := db .InsertWorkspace (context .Background (), database.InsertWorkspaceParams {
329
+ ID : uuid .New (),
330
+ OwnerID : insertUser (db ).ID ,
331
+ Name : uuid .NewString (),
332
+ TemplateID : template ,
333
+ AutomaticUpdates : database .AutomaticUpdatesNever ,
334
+ })
335
+ require .NoError (t , err )
336
+
337
+ job , err := db .InsertProvisionerJob (context .Background (), database.InsertProvisionerJobParams {
338
+ ID : uuid .New (),
339
+ CreatedAt : dbtime .Now (),
340
+ UpdatedAt : dbtime .Now (),
341
+ Provisioner : database .ProvisionerTypeEcho ,
342
+ StorageMethod : database .ProvisionerStorageMethodFile ,
343
+ Type : database .ProvisionerJobTypeWorkspaceBuild ,
344
+ })
345
+ require .NoError (t , err )
346
+ err = db .InsertWorkspaceBuild (context .Background (), database.InsertWorkspaceBuildParams {
347
+ ID : uuid .New (),
348
+ WorkspaceID : workspace .ID ,
349
+ JobID : job .ID ,
350
+ BuildNumber : 1 ,
351
+ Transition : database .WorkspaceTransitionStart ,
352
+ Reason : database .BuildReasonInitiator ,
353
+ TemplateVersionID : templateVersion ,
354
+ })
355
+ require .NoError (t , err )
356
+ // This marks the job as started.
357
+ _ , err = db .AcquireProvisionerJob (context .Background (), database.AcquireProvisionerJobParams {
358
+ OrganizationID : job .OrganizationID ,
359
+ StartedAt : sql.NullTime {
360
+ Time : dbtime .Now (),
361
+ Valid : true ,
362
+ },
363
+ Types : []database.ProvisionerType {database .ProvisionerTypeEcho },
364
+ })
365
+ require .NoError (t , err )
366
+ return job
367
+ }
368
+
369
+ insertCanceled := func (db database.Store ) {
370
+ job := insertRunning (db )
371
+ err := db .UpdateProvisionerJobWithCancelByID (context .Background (), database.UpdateProvisionerJobWithCancelByIDParams {
372
+ ID : job .ID ,
373
+ CanceledAt : sql.NullTime {
374
+ Time : dbtime .Now (),
375
+ Valid : true ,
376
+ },
377
+ })
378
+ require .NoError (t , err )
379
+ err = db .UpdateProvisionerJobWithCompleteByID (context .Background (), database.UpdateProvisionerJobWithCompleteByIDParams {
380
+ ID : job .ID ,
381
+ CompletedAt : sql.NullTime {
382
+ Time : dbtime .Now (),
383
+ Valid : true ,
384
+ },
385
+ })
386
+ require .NoError (t , err )
387
+ }
388
+
389
+ insertFailed := func (db database.Store ) {
390
+ job := insertRunning (db )
391
+ err := db .UpdateProvisionerJobWithCompleteByID (context .Background (), database.UpdateProvisionerJobWithCompleteByIDParams {
392
+ ID : job .ID ,
393
+ CompletedAt : sql.NullTime {
394
+ Time : dbtime .Now (),
395
+ Valid : true ,
396
+ },
397
+ Error : sql.NullString {
398
+ String : "failed" ,
399
+ Valid : true ,
400
+ },
401
+ })
402
+ require .NoError (t , err )
403
+ }
404
+
405
+ insertSuccess := func (db database.Store ) {
406
+ job := insertRunning (db )
407
+ err := db .UpdateProvisionerJobWithCompleteByID (context .Background (), database.UpdateProvisionerJobWithCompleteByIDParams {
408
+ ID : job .ID ,
409
+ CompletedAt : sql.NullTime {
410
+ Time : dbtime .Now (),
411
+ Valid : true ,
412
+ },
413
+ })
414
+ require .NoError (t , err )
415
+ }
416
+
417
+ for _ , tc := range []struct {
418
+ Name string
419
+ Database func () database.Store
420
+ ExpectedSeries int
421
+ ExpectedStatuses map [codersdk.ProvisionerJobStatus ]int
422
+ ExpectedWorkspaces int
423
+ }{{
424
+ Name : "None" ,
425
+ Database : func () database.Store {
426
+ return dbmem .New ()
427
+ },
428
+ ExpectedSeries : 0 ,
429
+ ExpectedWorkspaces : 0 ,
430
+ }, {
431
+ Name : "Multiple" ,
432
+ Database : func () database.Store {
433
+ db := dbmem .New ()
434
+ insertTemplates (db )
435
+ insertCanceled (db )
436
+ insertFailed (db )
437
+ insertFailed (db )
438
+ insertSuccess (db )
439
+ insertSuccess (db )
440
+ insertSuccess (db )
441
+ insertRunning (db )
442
+ return db
443
+ },
444
+ ExpectedSeries : 7 ,
445
+ ExpectedWorkspaces : 7 ,
446
+ ExpectedStatuses : map [codersdk.ProvisionerJobStatus ]int {
447
+ codersdk .ProvisionerJobCanceled : 1 ,
448
+ codersdk .ProvisionerJobFailed : 2 ,
449
+ codersdk .ProvisionerJobSucceeded : 3 ,
450
+ codersdk .ProvisionerJobRunning : 1 ,
451
+ },
452
+ }} {
453
+ tc := tc
454
+ t .Run (tc .Name , func (t * testing.T ) {
455
+ t .Parallel ()
456
+ registry := prometheus .NewRegistry ()
457
+ closeFunc , err := prometheusmetrics .Workspaces (context .Background (), slogtest .Make (t , nil ), registry , tc .Database (), testutil .IntervalFast )
458
+ require .NoError (t , err )
459
+ t .Cleanup (closeFunc )
460
+
461
+ require .Eventually (t , func () bool {
462
+ metrics , err := registry .Gather ()
463
+ assert .NoError (t , err )
464
+ stMap := map [codersdk.ProvisionerJobStatus ]int {}
465
+ wMap := map [string ]struct {}{}
466
+ for _ , m := range metrics {
467
+ if m .GetName () != "coderd_api_workspace_detail" {
468
+ continue
469
+ }
470
+
471
+ for _ , metric := range m .Metric {
472
+ for _ , l := range metric .Label {
473
+ if l == nil {
474
+ continue
475
+ }
476
+
477
+ switch l .GetName () {
478
+ case "status" :
479
+ status := codersdk .ProvisionerJobStatus (l .GetValue ())
480
+ stMap [status ] += int (metric .Gauge .GetValue ())
481
+ case "workspace_name" :
482
+ wMap [l .GetValue ()] = struct {}{}
483
+ }
484
+ }
485
+ }
486
+ }
487
+
488
+ stSum := 0
489
+ for st , count := range stMap {
490
+ if tc .ExpectedStatuses [st ] != count {
491
+ return false
492
+ }
493
+
494
+ stSum += count
495
+ }
496
+
497
+ t .Logf ("status series = %d, expected == %d" , stSum , tc .ExpectedSeries )
498
+ t .Logf ("workspace series = %d, expected == %d" , len (wMap ), tc .ExpectedWorkspaces )
499
+ return stSum == tc .ExpectedSeries && len (wMap ) == tc .ExpectedWorkspaces
500
+ }, testutil .WaitSuperShort , testutil .IntervalFast )
259
501
})
260
502
}
261
503
}
0 commit comments