|
9 | 9 | "time"
|
10 | 10 |
|
11 | 11 | "github.com/prometheus/client_golang/prometheus"
|
| 12 | + "golang.org/x/xerrors" |
12 | 13 |
|
13 | 14 | "github.com/coder/coder/v2/coderd/database/dbtime"
|
14 | 15 | "github.com/coder/coder/v2/coderd/util/slice"
|
@@ -303,107 +304,128 @@ func TestPrebuildReconciliation(t *testing.T) {
|
303 | 304 | for _, prebuildLatestTransition := range tc.prebuildLatestTransitions {
|
304 | 305 | for _, prebuildJobStatus := range tc.prebuildJobStatuses {
|
305 | 306 | for _, templateDeleted := range tc.templateDeleted {
|
306 |
| - t.Run(fmt.Sprintf("%s - %s - %s", tc.name, prebuildLatestTransition, prebuildJobStatus), func(t *testing.T) { |
307 |
| - t.Parallel() |
308 |
| - t.Cleanup(func() { |
309 |
| - if t.Failed() { |
310 |
| - t.Logf("failed to run test: %s", tc.name) |
311 |
| - t.Logf("templateVersionActive: %t", templateVersionActive) |
312 |
| - t.Logf("prebuildLatestTransition: %s", prebuildLatestTransition) |
313 |
| - t.Logf("prebuildJobStatus: %s", prebuildJobStatus) |
| 307 | + for _, useBrokenPubsub := range []bool{true, false} { |
| 308 | + t.Run(fmt.Sprintf("%s - %s - %s - pubsub_broken=%v", tc.name, prebuildLatestTransition, prebuildJobStatus, useBrokenPubsub), func(t *testing.T) { |
| 309 | + t.Parallel() |
| 310 | + t.Cleanup(func() { |
| 311 | + if t.Failed() { |
| 312 | + t.Logf("failed to run test: %s", tc.name) |
| 313 | + t.Logf("templateVersionActive: %t", templateVersionActive) |
| 314 | + t.Logf("prebuildLatestTransition: %s", prebuildLatestTransition) |
| 315 | + t.Logf("prebuildJobStatus: %s", prebuildJobStatus) |
| 316 | + } |
| 317 | + }) |
| 318 | + clock := quartz.NewMock(t) |
| 319 | + ctx := testutil.Context(t, testutil.WaitShort) |
| 320 | + cfg := codersdk.PrebuildsConfig{} |
| 321 | + logger := slogtest.Make( |
| 322 | + t, &slogtest.Options{IgnoreErrors: true}, |
| 323 | + ).Leveled(slog.LevelDebug) |
| 324 | + db, pubSub := dbtestutil.NewDB(t) |
| 325 | + |
| 326 | + ownerID := uuid.New() |
| 327 | + dbgen.User(t, db, database.User{ |
| 328 | + ID: ownerID, |
| 329 | + }) |
| 330 | + org, template := setupTestDBTemplate(t, db, ownerID, templateDeleted) |
| 331 | + templateVersionID := setupTestDBTemplateVersion( |
| 332 | + ctx, |
| 333 | + t, |
| 334 | + clock, |
| 335 | + db, |
| 336 | + pubSub, |
| 337 | + org.ID, |
| 338 | + ownerID, |
| 339 | + template.ID, |
| 340 | + ) |
| 341 | + preset := setupTestDBPreset( |
| 342 | + t, |
| 343 | + db, |
| 344 | + templateVersionID, |
| 345 | + 1, |
| 346 | + uuid.New().String(), |
| 347 | + ) |
| 348 | + prebuild := setupTestDBPrebuild( |
| 349 | + t, |
| 350 | + clock, |
| 351 | + db, |
| 352 | + pubSub, |
| 353 | + prebuildLatestTransition, |
| 354 | + prebuildJobStatus, |
| 355 | + org.ID, |
| 356 | + preset, |
| 357 | + template.ID, |
| 358 | + templateVersionID, |
| 359 | + ) |
| 360 | + |
| 361 | + if !templateVersionActive { |
| 362 | + // Create a new template version and mark it as active |
| 363 | + // This marks the template version that we care about as inactive |
| 364 | + setupTestDBTemplateVersion(ctx, t, clock, db, pubSub, org.ID, ownerID, template.ID) |
314 | 365 | }
|
315 |
| - }) |
316 |
| - clock := quartz.NewMock(t) |
317 |
| - ctx := testutil.Context(t, testutil.WaitShort) |
318 |
| - cfg := codersdk.PrebuildsConfig{} |
319 |
| - logger := slogtest.Make( |
320 |
| - t, &slogtest.Options{IgnoreErrors: true}, |
321 |
| - ).Leveled(slog.LevelDebug) |
322 |
| - db, pubSub := dbtestutil.NewDB(t) |
323 |
| - controller := prebuilds.NewStoreReconciler(db, pubSub, cfg, logger, quartz.NewMock(t), prometheus.NewRegistry()) |
324 |
| - |
325 |
| - ownerID := uuid.New() |
326 |
| - dbgen.User(t, db, database.User{ |
327 |
| - ID: ownerID, |
328 |
| - }) |
329 |
| - org, template := setupTestDBTemplate(t, db, ownerID, templateDeleted) |
330 |
| - templateVersionID := setupTestDBTemplateVersion( |
331 |
| - ctx, |
332 |
| - t, |
333 |
| - clock, |
334 |
| - db, |
335 |
| - pubSub, |
336 |
| - org.ID, |
337 |
| - ownerID, |
338 |
| - template.ID, |
339 |
| - ) |
340 |
| - preset := setupTestDBPreset( |
341 |
| - t, |
342 |
| - db, |
343 |
| - templateVersionID, |
344 |
| - 1, |
345 |
| - uuid.New().String(), |
346 |
| - ) |
347 |
| - prebuild := setupTestDBPrebuild( |
348 |
| - t, |
349 |
| - clock, |
350 |
| - db, |
351 |
| - pubSub, |
352 |
| - prebuildLatestTransition, |
353 |
| - prebuildJobStatus, |
354 |
| - org.ID, |
355 |
| - preset, |
356 |
| - template.ID, |
357 |
| - templateVersionID, |
358 |
| - ) |
359 |
| - |
360 |
| - if !templateVersionActive { |
361 |
| - // Create a new template version and mark it as active |
362 |
| - // This marks the template version that we care about as inactive |
363 |
| - setupTestDBTemplateVersion(ctx, t, clock, db, pubSub, org.ID, ownerID, template.ID) |
364 |
| - } |
365 |
| - |
366 |
| - // Run the reconciliation multiple times to ensure idempotency |
367 |
| - // 8 was arbitrary, but large enough to reasonably trust the result |
368 |
| - for i := 1; i <= 8; i++ { |
369 |
| - require.NoErrorf(t, controller.ReconcileAll(ctx), "failed on iteration %d", i) |
370 |
| - |
371 |
| - if tc.shouldCreateNewPrebuild != nil { |
372 |
| - newPrebuildCount := 0 |
373 |
| - workspaces, err := db.GetWorkspacesByTemplateID(ctx, template.ID) |
374 |
| - require.NoError(t, err) |
375 |
| - for _, workspace := range workspaces { |
376 |
| - if workspace.ID != prebuild.ID { |
377 |
| - newPrebuildCount++ |
| 366 | + |
| 367 | + if useBrokenPubsub { |
| 368 | + pubSub = &brokenPublisher{Pubsub: pubSub} |
| 369 | + } |
| 370 | + controller := prebuilds.NewStoreReconciler(db, pubSub, cfg, logger, quartz.NewMock(t), prometheus.NewRegistry()) |
| 371 | + |
| 372 | + // Run the reconciliation multiple times to ensure idempotency |
| 373 | + // 8 was arbitrary, but large enough to reasonably trust the result |
| 374 | + for i := 1; i <= 8; i++ { |
| 375 | + require.NoErrorf(t, controller.ReconcileAll(ctx), "failed on iteration %d", i) |
| 376 | + |
| 377 | + if tc.shouldCreateNewPrebuild != nil { |
| 378 | + newPrebuildCount := 0 |
| 379 | + workspaces, err := db.GetWorkspacesByTemplateID(ctx, template.ID) |
| 380 | + require.NoError(t, err) |
| 381 | + for _, workspace := range workspaces { |
| 382 | + if workspace.ID != prebuild.ID { |
| 383 | + newPrebuildCount++ |
| 384 | + } |
378 | 385 | }
|
| 386 | + // This test configures a preset that desires one prebuild. |
| 387 | + // In cases where new prebuilds should be created, there should be exactly one. |
| 388 | + require.Equal(t, *tc.shouldCreateNewPrebuild, newPrebuildCount == 1) |
379 | 389 | }
|
380 |
| - // This test configures a preset that desires one prebuild. |
381 |
| - // In cases where new prebuilds should be created, there should be exactly one. |
382 |
| - require.Equal(t, *tc.shouldCreateNewPrebuild, newPrebuildCount == 1) |
383 |
| - } |
384 | 390 |
|
385 |
| - if tc.shouldDeleteOldPrebuild != nil { |
386 |
| - builds, err := db.GetWorkspaceBuildsByWorkspaceID(ctx, database.GetWorkspaceBuildsByWorkspaceIDParams{ |
387 |
| - WorkspaceID: prebuild.ID, |
388 |
| - }) |
389 |
| - require.NoError(t, err) |
390 |
| - if *tc.shouldDeleteOldPrebuild { |
391 |
| - require.Equal(t, 2, len(builds)) |
392 |
| - require.Equal(t, database.WorkspaceTransitionDelete, builds[0].Transition) |
393 |
| - } else { |
394 |
| - require.Equal(t, 1, len(builds)) |
395 |
| - require.Equal(t, prebuildLatestTransition, builds[0].Transition) |
| 391 | + if tc.shouldDeleteOldPrebuild != nil { |
| 392 | + builds, err := db.GetWorkspaceBuildsByWorkspaceID(ctx, database.GetWorkspaceBuildsByWorkspaceIDParams{ |
| 393 | + WorkspaceID: prebuild.ID, |
| 394 | + }) |
| 395 | + require.NoError(t, err) |
| 396 | + if *tc.shouldDeleteOldPrebuild { |
| 397 | + require.Equal(t, 2, len(builds)) |
| 398 | + require.Equal(t, database.WorkspaceTransitionDelete, builds[0].Transition) |
| 399 | + } else { |
| 400 | + require.Equal(t, 1, len(builds)) |
| 401 | + require.Equal(t, prebuildLatestTransition, builds[0].Transition) |
| 402 | + } |
396 | 403 | }
|
397 | 404 | }
|
398 |
| - } |
399 |
| - }) |
| 405 | + }) |
| 406 | + } |
400 | 407 | }
|
401 | 408 | }
|
402 | 409 | }
|
403 | 410 | }
|
404 | 411 | }
|
405 | 412 | }
|
406 | 413 |
|
| 414 | +// brokenPublisher is used to validate that Publish() calls which always fail do not affect the reconciler's behavior, |
| 415 | +// since the messages published are not essential but merely advisory. |
| 416 | +type brokenPublisher struct { |
| 417 | + pubsub.Pubsub |
| 418 | +} |
| 419 | + |
| 420 | +// Publish deliberately fails. |
| 421 | +// I'm explicitly _not_ checking for EventJobPosted (coderd/database/provisionerjobs/provisionerjobs.go) since that |
| 422 | +// requires too much knowledge of the underlying implementation. |
| 423 | +func (*brokenPublisher) Publish(event string, _ []byte) error { |
| 424 | + // Mimick some work being done. |
| 425 | + <-time.After(testutil.IntervalFast) |
| 426 | + return xerrors.Errorf("failed to publish %q", event) |
| 427 | +} |
| 428 | + |
407 | 429 | func TestMultiplePresetsPerTemplateVersion(t *testing.T) {
|
408 | 430 | t.Parallel()
|
409 | 431 |
|
|
0 commit comments