@@ -19,6 +19,7 @@ import (
19
19
"github.com/coder/coder/v2/coderd/database/dbauthz"
20
20
"github.com/coder/coder/v2/coderd/database/dbgen"
21
21
"github.com/coder/coder/v2/coderd/database/dbtestutil"
22
+ "github.com/coder/coder/v2/coderd/database/dbtime"
22
23
agplprebuilds"github.com/coder/coder/v2/coderd/prebuilds"
23
24
"github.com/coder/coder/v2/codersdk"
24
25
"github.com/coder/coder/v2/enterprise/coderd/prebuilds"
@@ -165,7 +166,7 @@ func TestMetricsCollector(t *testing.T) {
165
166
eligible : []bool {false },
166
167
},
167
168
{
168
- name :"deleted templatesnever desire prebuilds " ,
169
+ name :"deleted templatesshould not be included in exported metrics " ,
169
170
transitions :allTransitions ,
170
171
jobStatuses :allJobStatuses ,
171
172
initiatorIDs : []uuid.UUID {agplprebuilds .SystemUserID },
@@ -174,16 +175,6 @@ func TestMetricsCollector(t *testing.T) {
174
175
templateDeleted : []bool {true },
175
176
eligible : []bool {false },
176
177
},
177
- {
178
- name :"running prebuilds for deleted templates are still counted, so that they can be deleted" ,
179
- transitions : []database.WorkspaceTransition {database .WorkspaceTransitionStart },
180
- jobStatuses : []database.ProvisionerJobStatus {database .ProvisionerJobStatusSucceeded },
181
- initiatorIDs : []uuid.UUID {agplprebuilds .SystemUserID },
182
- ownerIDs : []uuid.UUID {agplprebuilds .SystemUserID },
183
- metrics :nil ,
184
- templateDeleted : []bool {true },
185
- eligible : []bool {false },
186
- },
187
178
}
188
179
for _ ,test := range tests {
189
180
test := test // capture for parallel
@@ -308,6 +299,9 @@ func TestMetricsCollector(t *testing.T) {
308
299
}
309
300
}
310
301
302
+ // TestMetricsCollector_DuplicateTemplateNames validates a bug that we saw previously which caused duplicate metric series
303
+ // registration when a template was deleted and a new one created with the same name (and preset name).
304
+ // We are now excluding deleted templates from our metric collection.
311
305
func TestMetricsCollector_DuplicateTemplateNames (t * testing.T ) {
312
306
t .Parallel ()
313
307
@@ -352,81 +346,82 @@ func TestMetricsCollector_DuplicateTemplateNames(t *testing.T) {
352
346
reconciler := prebuilds .NewStoreReconciler (db ,pubsub , codersdk.PrebuildsConfig {},logger ,quartz .NewMock (t ),prometheus .NewRegistry (),newNoopEnqueuer ())
353
347
ctx := testutil .Context (t ,testutil .WaitLong )
354
348
355
- createdUsers := []uuid.UUID {agplprebuilds .SystemUserID }
356
- for _ ,user := range []uuid.UUID {test .ownerID ,test .initiatorID } {
357
- if ! slices .Contains (createdUsers ,user ) {
358
- dbgen .User (t ,db , database.User {
359
- ID :user ,
360
- })
361
- createdUsers = append (createdUsers ,user )
362
- }
363
- }
364
-
365
349
collector := prebuilds .NewMetricsCollector (db ,logger ,reconciler )
366
350
registry := prometheus .NewPedanticRegistry ()
367
351
registry .Register (collector )
368
352
369
- defaultOrgName := "default-org"
370
- defaultTemplateName := "default-template"
371
- defaultPresetName := "default-preset"
372
- defaultOrg := dbgen .Organization (t ,db , database.Organization {
373
- Name :defaultOrgName ,
374
- })
375
- setupTemplateWithDeps := func (templateDeleted bool ) database.Template {
376
- template := setupTestDBTemplateWithinOrg (t ,db ,test .ownerID ,templateDeleted ,defaultTemplateName ,defaultOrg )
353
+ presetName := "default-preset"
354
+ defaultOrg := dbgen .Organization (t ,db , database.Organization {})
355
+ setupTemplateWithDeps := func () database.Template {
356
+ template := setupTestDBTemplateWithinOrg (t ,db ,test .ownerID ,false ,"default-template" ,defaultOrg )
377
357
templateVersionID := setupTestDBTemplateVersion (ctx ,t ,clock ,db ,pubsub ,defaultOrg .ID ,test .ownerID ,template .ID )
378
- preset := setupTestDBPreset (t ,db ,templateVersionID ,1 ,defaultPresetName )
358
+ preset := setupTestDBPreset (t ,db ,templateVersionID ,1 ,"default-preset" )
379
359
workspace ,_ := setupTestDBWorkspace (
380
360
t ,clock ,db ,pubsub ,
381
361
test .transition ,test .jobStatus ,defaultOrg .ID ,preset ,template .ID ,templateVersionID ,test .initiatorID ,test .ownerID ,
382
362
)
383
363
setupTestDBWorkspaceAgent (t ,db ,workspace .ID ,test .eligible )
384
364
return template
385
365
}
386
- // Simulates creating and then deleting a template.
387
- setupTemplateWithDeps (true )
388
- // Simulates creating a template with the same org, template name, and preset name.
389
- newTemplate := setupTemplateWithDeps (false )
390
366
391
- // Force an update to the metrics state to allow the collector to collect fresh metrics.
367
+ // When: starting with a regular template.
368
+ template := setupTemplateWithDeps ()
369
+ labels := map [string ]string {
370
+ "template_name" :template .Name ,
371
+ "preset_name" :presetName ,
372
+ "organization_name" :defaultOrg .Name ,
373
+ }
374
+
392
375
// nolint:gocritic // Authz context needed to retrieve state.
393
- require . NoError ( t , collector . UpdateState ( dbauthz .AsPrebuildsOrchestrator (ctx ), testutil . WaitLong ) )
376
+ ctx = dbauthz .AsPrebuildsOrchestrator (ctx )
394
377
378
+ // Then: metrics collect successfully.
379
+ require .NoError (t ,collector .UpdateState (ctx ,testutil .WaitLong ))
395
380
metricsFamilies ,err := registry .Gather ()
396
381
require .NoError (t ,err )
397
-
398
- templateVersions ,err := db .GetTemplateVersionsByTemplateID (ctx , database.GetTemplateVersionsByTemplateIDParams {
399
- TemplateID :newTemplate .ID ,
400
- })
382
+ require .NotEmpty (t ,findAllMetricSeries (metricsFamilies ,labels ))
383
+
384
+ // When: the template is deleted.
385
+ require .NoError (t ,db .UpdateTemplateDeletedByID (ctx , database.UpdateTemplateDeletedByIDParams {
386
+ ID :template .ID ,
387
+ Deleted :true ,
388
+ UpdatedAt :dbtime .Now (),
389
+ }))
390
+
391
+ // Then: metrics collect successfully but are empty because the template is deleted.
392
+ require .NoError (t ,collector .UpdateState (ctx ,testutil .WaitLong ))
393
+ metricsFamilies ,err = registry .Gather ()
401
394
require .NoError (t ,err )
402
- require .Equal (t ,1 ,len (templateVersions ))
395
+ require .Empty (t ,findAllMetricSeries (metricsFamilies ,labels ))
396
+
397
+ // When: a new template is created with the same name as the deleted template.
398
+ newTemplate := setupTemplateWithDeps ()
399
+
400
+ // Ensure the database has both the new and old (delete) template.
401
+ {
402
+ deleted ,err := db .GetTemplateByOrganizationAndName (ctx , database.GetTemplateByOrganizationAndNameParams {
403
+ OrganizationID :template .OrganizationID ,
404
+ Deleted :true ,
405
+ Name :template .Name ,
406
+ })
407
+ require .NoError (t ,err )
408
+ require .Equal (t ,template .ID ,deleted .ID )
409
+
410
+ current ,err := db .GetTemplateByOrganizationAndName (ctx , database.GetTemplateByOrganizationAndNameParams {
411
+ // Use details from deleted template to ensure they're aligned.
412
+ OrganizationID :template .OrganizationID ,
413
+ Deleted :false ,
414
+ Name :template .Name ,
415
+ })
416
+ require .NoError (t ,err )
417
+ require .Equal (t ,newTemplate .ID ,current .ID )
418
+ }
403
419
404
- presets ,err := db .GetPresetsByTemplateVersionID (ctx ,templateVersions [0 ].ID )
420
+ // Then: metrics collect successfully.
421
+ require .NoError (t ,collector .UpdateState (ctx ,testutil .WaitLong ))
422
+ metricsFamilies ,err = registry .Gather ()
405
423
require .NoError (t ,err )
406
- require .Equal (t ,1 ,len (presets ))
407
-
408
- for _ ,preset := range presets {
409
- labels := map [string ]string {
410
- "template_name" :newTemplate .Name ,
411
- "preset_name" :preset .Name ,
412
- "organization_name" :defaultOrg .Name ,
413
- }
414
-
415
- for _ ,check := range test .metrics {
416
- metric := findMetric (metricsFamilies ,check .name ,labels )
417
- if check .value == nil {
418
- continue
419
- }
420
-
421
- require .NotNil (t ,metric ,"metric %s should exist" ,check .name )
422
-
423
- if check .isCounter {
424
- require .Equal (t ,* check .value ,metric .GetCounter ().GetValue (),"counter %s value mismatch" ,check .name )
425
- }else {
426
- require .Equal (t ,* check .value ,metric .GetGauge ().GetValue (),"gauge %s value mismatch" ,check .name )
427
- }
428
- }
429
- }
424
+ require .NotEmpty (t ,findAllMetricSeries (metricsFamilies ,labels ))
430
425
}
431
426
432
427
func findMetric (metricsFamilies []* prometheus_client.MetricFamily ,name string ,labels map [string ]string )* prometheus_client.Metric {