diff --git a/internal/resource/reaper/controller_test.go b/internal/resource/reaper/controller_test.go index c06ccedab5..d78a105d21 100644 --- a/internal/resource/reaper/controller_test.go +++ b/internal/resource/reaper/controller_test.go @@ -4,6 +4,7 @@ package reaper import ( + "github.com/hashicorp/consul/internal/resource/resourcetest" "testing" "time" @@ -20,174 +21,198 @@ import ( ) func TestReconcile_ResourceWithNoChildren(t *testing.T) { - client := svctest.RunResourceService(t, demo.RegisterTypes) + client := svctest.RunResourceServiceWithTenancies(t, demo.RegisterTypes) - // Seed the database with an artist. - res, err := demo.GenerateV2Artist() - require.NoError(t, err) - ctx := testutil.TestContext(t) - writeRsp, err := client.Write(ctx, &pbresource.WriteRequest{Resource: res}) - require.NoError(t, err) + runReaperTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) { + // Seed the database with an artist. + res, err := demo.GenerateV2Artist() - // Delete the artist to create a tombstone - _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: writeRsp.Resource.Id}) - require.NoError(t, err) + // set resource tenancy from default to test tenancy + res.Id.Tenancy = tenancy - // Retrieve tombstone - listRsp, err := client.List(ctx, &pbresource.ListRequest{ - Type: resource.TypeV1Tombstone, - Tenancy: writeRsp.Resource.Id.Tenancy, + require.NoError(t, err) + ctx := testutil.TestContext(t) + writeRsp, err := client.Write(ctx, &pbresource.WriteRequest{Resource: res}) + require.NoError(t, err) + + // Delete the artist to create a tombstone + _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: writeRsp.Resource.Id}) + require.NoError(t, err) + + // Retrieve tombstone + listRsp, err := client.List(ctx, &pbresource.ListRequest{ + Type: resource.TypeV1Tombstone, + Tenancy: tenancy, + }) + require.NoError(t, err) + require.Len(t, listRsp.Resources, 1) + tombstone := listRsp.Resources[0] + + // Verify reconcile does first pass and queues up for a second pass + rec := newReconciler() + runtime := controller.Runtime{ + Client: client, + Logger: testutil.Logger(t), + } + req := controller.Request{ID: tombstone.Id} + require.ErrorIs(t, controller.RequeueAfterError(secondPassDelay), rec.Reconcile(ctx, runtime, req)) + + // Verify condition FirstPassCompleted is true + readRsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: tombstone.Id}) + require.NoError(t, err) + tombstone = readRsp.Resource + condition := tombstone.Status[statusKeyReaperController].Conditions[0] + require.Equal(t, conditionTypeFirstPassCompleted, condition.Type) + require.Equal(t, pbresource.Condition_STATE_TRUE, condition.State) + + // Verify reconcile does second pass and tombstone is deleted + // Fake out time so elapsed time > secondPassDelay + rec.timeNow = func() time.Time { return time.Now().Add(secondPassDelay + time.Second) } + require.NoError(t, rec.Reconcile(ctx, runtime, req)) + _, err = client.Read(ctx, &pbresource.ReadRequest{Id: tombstone.Id}) + require.Error(t, err) + require.Equal(t, codes.NotFound.String(), status.Code(err).String()) + + // Reconcile again to verify no-op on an already deleted tombstone + require.NoError(t, rec.Reconcile(ctx, runtime, req)) }) - require.NoError(t, err) - require.Len(t, listRsp.Resources, 1) - tombstone := listRsp.Resources[0] - - // Verify reconcile does first pass and queues up for a second pass - rec := newReconciler() - runtime := controller.Runtime{ - Client: client, - Logger: testutil.Logger(t), - } - req := controller.Request{ID: tombstone.Id} - require.ErrorIs(t, controller.RequeueAfterError(secondPassDelay), rec.Reconcile(ctx, runtime, req)) - - // Verify condition FirstPassCompleted is true - readRsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: tombstone.Id}) - require.NoError(t, err) - tombstone = readRsp.Resource - condition := tombstone.Status[statusKeyReaperController].Conditions[0] - require.Equal(t, conditionTypeFirstPassCompleted, condition.Type) - require.Equal(t, pbresource.Condition_STATE_TRUE, condition.State) - - // Verify reconcile does second pass and tombstone is deleted - // Fake out time so elapsed time > secondPassDelay - rec.timeNow = func() time.Time { return time.Now().Add(secondPassDelay + time.Second) } - require.NoError(t, rec.Reconcile(ctx, runtime, req)) - _, err = client.Read(ctx, &pbresource.ReadRequest{Id: tombstone.Id}) - require.Error(t, err) - require.Equal(t, codes.NotFound.String(), status.Code(err).String()) - - // Reconcile again to verify no-op on an already deleted tombstone - require.NoError(t, rec.Reconcile(ctx, runtime, req)) } func TestReconcile_ResourceWithChildren(t *testing.T) { - client := svctest.RunResourceService(t, demo.RegisterTypes) + client := svctest.RunResourceServiceWithTenancies(t, demo.RegisterTypes) - // Seed the database with an artist - res, err := demo.GenerateV2Artist() - require.NoError(t, err) - ctx := testutil.TestContext(t) - writeRsp, err := client.Write(ctx, &pbresource.WriteRequest{Resource: res}) - require.NoError(t, err) - artist := writeRsp.Resource + runReaperTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) { + // Seed the database with an artist + res, err := demo.GenerateV2Artist() + + // set resource tenancy from default to test tenancy + res.Id.Tenancy = tenancy - // Create 3 albums owned by the artist - numAlbums := 3 - for i := 0; i < numAlbums; i++ { - res, err = demo.GenerateV2Album(artist.Id) require.NoError(t, err) - _, err := client.Write(ctx, &pbresource.WriteRequest{Resource: res}) + ctx := testutil.TestContext(t) + writeRsp, err := client.Write(ctx, &pbresource.WriteRequest{Resource: res}) require.NoError(t, err) - } + artist := writeRsp.Resource - // Delete the artist to create a tombstone - _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: writeRsp.Resource.Id}) - require.NoError(t, err) + // Create 3 albums owned by the artist + numAlbums := 3 + for i := 0; i < numAlbums; i++ { + res, err = demo.GenerateV2Album(artist.Id) + require.NoError(t, err) + _, err := client.Write(ctx, &pbresource.WriteRequest{Resource: res}) + require.NoError(t, err) + } - // Retrieve the tombstone - listRsp, err := client.List(ctx, &pbresource.ListRequest{ - Type: resource.TypeV1Tombstone, - Tenancy: writeRsp.Resource.Id.Tenancy, + // Delete the artist to create a tombstone + _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: writeRsp.Resource.Id}) + require.NoError(t, err) + + // Retrieve the tombstone + listRsp, err := client.List(ctx, &pbresource.ListRequest{ + Type: resource.TypeV1Tombstone, + Tenancy: writeRsp.Resource.Id.Tenancy, + }) + require.NoError(t, err) + require.Len(t, listRsp.Resources, 1) + tombstone := listRsp.Resources[0] + + // Verify reconcile does first pass delete and queues up for a second pass + rec := newReconciler() + runtime := controller.Runtime{ + Client: client, + Logger: testutil.Logger(t), + } + req := controller.Request{ID: tombstone.Id} + require.ErrorIs(t, controller.RequeueAfterError(secondPassDelay), rec.Reconcile(ctx, runtime, req)) + + // Verify 3 albums deleted + listRsp, err = client.List(ctx, &pbresource.ListRequest{ + Type: demo.TypeV2Album, + Tenancy: artist.Id.Tenancy, + }) + require.NoError(t, err) + require.Empty(t, listRsp.Resources) + + // Verify condition FirstPassCompleted is true + readRsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: tombstone.Id}) + require.NoError(t, err) + tombstone = readRsp.Resource + condition := tombstone.Status[statusKeyReaperController].Conditions[0] + require.Equal(t, conditionTypeFirstPassCompleted, condition.Type) + require.Equal(t, pbresource.Condition_STATE_TRUE, condition.State) + + // Verify reconcile does second pass + // Fake out time so elapsed time > secondPassDelay + rec.timeNow = func() time.Time { return time.Now().Add(secondPassDelay + time.Second) } + require.NoError(t, rec.Reconcile(ctx, runtime, req)) + + // Verify artist tombstone deleted + _, err = client.Read(ctx, &pbresource.ReadRequest{Id: tombstone.Id}) + require.Error(t, err) + require.Equal(t, codes.NotFound.String(), status.Code(err).String()) + + // Verify tombstones for 3 albums created + listRsp, err = client.List(ctx, &pbresource.ListRequest{ + Type: resource.TypeV1Tombstone, + Tenancy: artist.Id.Tenancy, + }) + require.NoError(t, err) + require.Len(t, listRsp.Resources, numAlbums) }) - require.NoError(t, err) - require.Len(t, listRsp.Resources, 1) - tombstone := listRsp.Resources[0] - - // Verify reconcile does first pass delete and queues up for a second pass - rec := newReconciler() - runtime := controller.Runtime{ - Client: client, - Logger: testutil.Logger(t), - } - req := controller.Request{ID: tombstone.Id} - require.ErrorIs(t, controller.RequeueAfterError(secondPassDelay), rec.Reconcile(ctx, runtime, req)) - - // Verify 3 albums deleted - listRsp, err = client.List(ctx, &pbresource.ListRequest{ - Type: demo.TypeV2Album, - Tenancy: artist.Id.Tenancy, - }) - require.NoError(t, err) - require.Empty(t, listRsp.Resources) - - // Verify condition FirstPassCompleted is true - readRsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: tombstone.Id}) - require.NoError(t, err) - tombstone = readRsp.Resource - condition := tombstone.Status[statusKeyReaperController].Conditions[0] - require.Equal(t, conditionTypeFirstPassCompleted, condition.Type) - require.Equal(t, pbresource.Condition_STATE_TRUE, condition.State) - - // Verify reconcile does second pass - // Fake out time so elapsed time > secondPassDelay - rec.timeNow = func() time.Time { return time.Now().Add(secondPassDelay + time.Second) } - require.NoError(t, rec.Reconcile(ctx, runtime, req)) - - // Verify artist tombstone deleted - _, err = client.Read(ctx, &pbresource.ReadRequest{Id: tombstone.Id}) - require.Error(t, err) - require.Equal(t, codes.NotFound.String(), status.Code(err).String()) - - // Verify tombstones for 3 albums created - listRsp, err = client.List(ctx, &pbresource.ListRequest{ - Type: resource.TypeV1Tombstone, - Tenancy: artist.Id.Tenancy, - }) - require.NoError(t, err) - require.Len(t, listRsp.Resources, numAlbums) } func TestReconcile_RequeueWithDelayWhenSecondPassDelayNotElapsed(t *testing.T) { - client := svctest.RunResourceService(t, demo.RegisterTypes) + client := svctest.RunResourceServiceWithTenancies(t, demo.RegisterTypes) - // Seed the database with an artist. - res, err := demo.GenerateV2Artist() - require.NoError(t, err) - ctx := testutil.TestContext(t) - writeRsp, err := client.Write(ctx, &pbresource.WriteRequest{Resource: res}) - require.NoError(t, err) + runReaperTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) { + // Seed the database with an artist. + res, err := demo.GenerateV2Artist() - // Delete the artist to create a tombstone - _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: writeRsp.Resource.Id}) - require.NoError(t, err) + // set resource tenancy from default to test tenancy + res.Id.Tenancy = tenancy - // Retrieve tombstone - listRsp, err := client.List(ctx, &pbresource.ListRequest{ - Type: resource.TypeV1Tombstone, - Tenancy: writeRsp.Resource.Id.Tenancy, + require.NoError(t, err) + ctx := testutil.TestContext(t) + writeRsp, err := client.Write(ctx, &pbresource.WriteRequest{Resource: res}) + require.NoError(t, err) + + // Delete the artist to create a tombstone + _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: writeRsp.Resource.Id}) + require.NoError(t, err) + + // Retrieve tombstone + listRsp, err := client.List(ctx, &pbresource.ListRequest{ + Type: resource.TypeV1Tombstone, + Tenancy: writeRsp.Resource.Id.Tenancy, + }) + require.NoError(t, err) + require.Len(t, listRsp.Resources, 1) + tombstone := listRsp.Resources[0] + + // Verify reconcile does first pass and queues up for a second pass + rec := newReconciler() + runtime := controller.Runtime{ + Client: client, + Logger: testutil.Logger(t), + } + req := controller.Request{ID: tombstone.Id} + require.ErrorIs(t, controller.RequeueAfterError(secondPassDelay), rec.Reconcile(ctx, runtime, req)) + + // Verify condition FirstPassCompleted is true + readRsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: tombstone.Id}) + require.NoError(t, err) + tombstone = readRsp.Resource + condition := tombstone.Status[statusKeyReaperController].Conditions[0] + require.Equal(t, conditionTypeFirstPassCompleted, condition.Type) + require.Equal(t, pbresource.Condition_STATE_TRUE, condition.State) + + // Verify requeued for second pass since secondPassDelay time has not elapsed + require.ErrorIs(t, controller.RequeueAfterError(secondPassDelay), rec.Reconcile(ctx, runtime, req)) }) - require.NoError(t, err) - require.Len(t, listRsp.Resources, 1) - tombstone := listRsp.Resources[0] - - // Verify reconcile does first pass and queues up for a second pass - rec := newReconciler() - runtime := controller.Runtime{ - Client: client, - Logger: testutil.Logger(t), - } - req := controller.Request{ID: tombstone.Id} - require.ErrorIs(t, controller.RequeueAfterError(secondPassDelay), rec.Reconcile(ctx, runtime, req)) - - // Verify condition FirstPassCompleted is true - readRsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: tombstone.Id}) - require.NoError(t, err) - tombstone = readRsp.Resource - condition := tombstone.Status[statusKeyReaperController].Conditions[0] - require.Equal(t, conditionTypeFirstPassCompleted, condition.Type) - require.Equal(t, pbresource.Condition_STATE_TRUE, condition.State) - - // Verify requeued for second pass since secondPassDelay time has not elapsed - require.ErrorIs(t, controller.RequeueAfterError(secondPassDelay), rec.Reconcile(ctx, runtime, req)) +} + +func runReaperTestCaseWithTenancies(testCase func(tenancy *pbresource.Tenancy)) { + for _, tenancy := range resourcetest.TestTenancies() { + testCase(tenancy) + } }