diff --git a/internal/multicluster/internal/controllers/exportedservices/controller_test.go b/internal/multicluster/internal/controllers/exportedservices/controller_test.go index 667f4ef3a9..7386c3ef1c 100644 --- a/internal/multicluster/internal/controllers/exportedservices/controller_test.go +++ b/internal/multicluster/internal/controllers/exportedservices/controller_test.go @@ -53,14 +53,731 @@ func (suite *controllerSuite) SetupTest() { suite.isEnterprise = versiontest.IsEnterprise() } -func (suite *controllerSuite) TestReconcile() { - suite.runTestCaseWithTenancies(suite.reconcileTest) -} - func TestController(t *testing.T) { suite.Run(t, new(controllerSuite)) } +func (suite *controllerSuite) TestReconcile_DeleteOldCES_NoExportedServices() { + // This test's purpose is to ensure that we delete the + // already existing CES when no exported service resources + // are found. + + suite.runTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) { + oldCESData := &pbmulticluster.ComputedExportedServices{ + Consumers: []*pbmulticluster.ComputedExportedService{ + { + TargetRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: tenancy, + Name: "svc0", + }, + Consumers: []*pbmulticluster.ComputedExportedServicesConsumer{ + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{ + Peer: "test-peer", + }, + }, + }, + }, + }, + } + + if suite.isEnterprise { + oldCESData.Consumers[0].Consumers = append(oldCESData.Consumers[0].Consumers, &pbmulticluster.ComputedExportedServicesConsumer{ + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Partition{ + Partition: "part-n", + }, + }) + } + + oldCES := rtest.Resource(pbmulticluster.ComputedExportedServicesType, "global"). + WithData(suite.T(), oldCESData). + WithTenancy(&pbresource.Tenancy{Partition: tenancy.Partition}). + Write(suite.T(), suite.client) + require.NotNil(suite.T(), oldCES) + + err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: oldCES.Id}) + require.NoError(suite.T(), err) + + suite.client.RequireResourceNotFound(suite.T(), oldCES.Id) + }) +} + +func (suite *controllerSuite) TestReconcile_DeleteOldCES_NoMatchingServices() { + // This test's purpose is to ensure that we delete the + // already existing CES when the exported services configs + // don't cover any services present in the partition. + + suite.runTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) { + oldCESData := &pbmulticluster.ComputedExportedServices{ + Consumers: []*pbmulticluster.ComputedExportedService{ + { + TargetRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: tenancy, + Name: "svc0", + }, + Consumers: []*pbmulticluster.ComputedExportedServicesConsumer{ + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{ + Peer: "test-peer", + }, + }, + }, + }, + }, + } + + if suite.isEnterprise { + oldCESData.Consumers[0].Consumers = append(oldCESData.Consumers[0].Consumers, &pbmulticluster.ComputedExportedServicesConsumer{ + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Partition{ + Partition: "part-n", + }, + }) + } + + oldCES := rtest.Resource(pbmulticluster.ComputedExportedServicesType, "global"). + WithData(suite.T(), oldCESData). + WithTenancy(&pbresource.Tenancy{Partition: tenancy.Partition}). + Write(suite.T(), suite.client) + require.NotNil(suite.T(), oldCES) + + exportedSvcData := &pbmulticluster.ExportedServices{ + Services: []string{"random-service-1", "random-service-2"}, + Consumers: []*pbmulticluster.ExportedServicesConsumer{ + {ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{Peer: "peer-1"}}, + }, + } + if suite.isEnterprise { + exportedSvcData.Consumers = append(exportedSvcData.Consumers, &pbmulticluster.ExportedServicesConsumer{ + ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Partition{ + Partition: "part-n", + }, + }) + } + suite.writeExportedService("exported-svcs", tenancy, exportedSvcData) + + if suite.isEnterprise { + nesData := &pbmulticluster.NamespaceExportedServices{ + Consumers: []*pbmulticluster.ExportedServicesConsumer{ + {ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Partition{Partition: "part-n"}}, + {ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{Peer: "peer-2"}}, + }, + } + suite.writeNamespaceExportedService("nes", tenancy, nesData) + } + + err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: oldCES.Id}) + require.NoError(suite.T(), err) + + suite.client.RequireResourceNotFound(suite.T(), oldCES.Id) + }) +} + +func (suite *controllerSuite) TestReconcile_SkipWritingNewCES() { + // This test's purpose is to ensure that we skip + // writing the new CES when there are no changes to + // the existing one + + suite.runTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) { + oldCESData := &pbmulticluster.ComputedExportedServices{ + Consumers: []*pbmulticluster.ComputedExportedService{ + { + TargetRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: tenancy, + Name: "svc-0", + }, + Consumers: []*pbmulticluster.ComputedExportedServicesConsumer{ + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{ + Peer: "peer-1", + }, + }, + }, + }, + }, + } + + if suite.isEnterprise { + oldCESData.Consumers[0].Consumers = append(oldCESData.Consumers[0].Consumers, &pbmulticluster.ComputedExportedServicesConsumer{ + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Partition{ + Partition: "part-n", + }, + }) + } + + oldCES := rtest.Resource(pbmulticluster.ComputedExportedServicesType, "global"). + WithData(suite.T(), oldCESData). + WithTenancy(&pbresource.Tenancy{Partition: tenancy.Partition}). + Write(suite.T(), suite.client) + require.NotNil(suite.T(), oldCES) + + // Export the svc-0 service to just a peer + exportedSvcData := &pbmulticluster.ExportedServices{ + Services: []string{"svc-0"}, + Consumers: []*pbmulticluster.ExportedServicesConsumer{ + {ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{Peer: "peer-1"}}, + }, + } + _ = rtest.Resource(pbmulticluster.ExportedServicesType, "exported-svcs"). + WithData(suite.T(), exportedSvcData). + WithTenancy(tenancy). + Write(suite.T(), suite.client) + + if suite.isEnterprise { + // Export all services in a given partition to `part-n` partition + pesData := &pbmulticluster.PartitionExportedServices{ + Consumers: []*pbmulticluster.ExportedServicesConsumer{ + {ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Partition{Partition: "part-n"}}, + }, + } + _ = rtest.Resource(pbmulticluster.PartitionExportedServicesType, "pes"). + WithData(suite.T(), pesData). + WithTenancy(&pbresource.Tenancy{Partition: tenancy.Partition}). + Write(suite.T(), suite.client) + } + + svcData := &pbcatalog.Service{ + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + _ = rtest.Resource(pbcatalog.ServiceType, "svc-0"). + WithData(suite.T(), svcData). + WithTenancy(tenancy). + Write(suite.T(), suite.client) + + err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: oldCES.Id}) + require.NoError(suite.T(), err) + + // Checking no-op with version + suite.client.RequireVersionUnchanged(suite.T(), oldCES.Id, oldCES.Version) + }) +} + +func (suite *controllerSuite) TestReconcile_ComputeCES() { + suite.runTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) { + suite.writeService("svc-0", tenancy) + + if suite.isEnterprise { + suite.writeService("svc-1", tenancy) + suite.writeService("svc-2", tenancy) + } + + // Export the svc-0 service to just peers + exportedSvcData := &pbmulticluster.ExportedServices{ + Services: []string{"svc-0"}, + Consumers: []*pbmulticluster.ExportedServicesConsumer{ + {ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{Peer: "peer-1"}}, + {ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{Peer: "peer-2"}}, + }, + } + suite.writeExportedService("exp-svc-0", tenancy, exportedSvcData) + + if suite.isEnterprise { + nesData := &pbmulticluster.NamespaceExportedServices{ + Consumers: []*pbmulticluster.ExportedServicesConsumer{ + {ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{Peer: "peer-2"}}, + {ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Partition{Partition: "part-1"}}, + }, + } + suite.writeNamespaceExportedService("nes", tenancy, nesData) + + // Export all services in a given partition to `part-n` partition + pesData := &pbmulticluster.PartitionExportedServices{ + Consumers: []*pbmulticluster.ExportedServicesConsumer{ + {ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Partition{Partition: "part-n"}}, + }, + } + suite.writePartitionedExportedService("pes", tenancy, pesData) + } + + id := rtest.Resource(pbmulticluster.ComputedExportedServicesType, "global").WithTenancy(&pbresource.Tenancy{Partition: tenancy.Partition}).ID() + require.NotNil(suite.T(), id) + + err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) + require.NoError(suite.T(), err) + + computedCES := suite.getComputedExportedSvc(id) + + var expectedCES *pbmulticluster.ComputedExportedServices + if suite.isEnterprise { + expectedCES = &pbmulticluster.ComputedExportedServices{ + Consumers: []*pbmulticluster.ComputedExportedService{ + { + TargetRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: tenancy, + Name: "svc-0", + }, + Consumers: []*pbmulticluster.ComputedExportedServicesConsumer{ + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{ + Peer: "peer-1", + }, + }, + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{ + Peer: "peer-2", + }, + }, + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Partition{ + Partition: "part-1", + }, + }, + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Partition{ + Partition: "part-n", + }, + }, + }, + }, + { + TargetRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: tenancy, + Name: "svc-1", + }, + Consumers: []*pbmulticluster.ComputedExportedServicesConsumer{ + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{ + Peer: "peer-2", + }, + }, + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Partition{ + Partition: "part-1", + }, + }, + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Partition{ + Partition: "part-n", + }, + }, + }, + }, + { + TargetRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: tenancy, + Name: "svc-2", + }, + Consumers: []*pbmulticluster.ComputedExportedServicesConsumer{ + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{ + Peer: "peer-2", + }, + }, + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Partition{ + Partition: "part-1", + }, + }, + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Partition{ + Partition: "part-n", + }, + }, + }, + }, + }, + } + } else { + expectedCES = &pbmulticluster.ComputedExportedServices{ + Consumers: []*pbmulticluster.ComputedExportedService{ + { + TargetRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: resource.DefaultNamespacedTenancy(), + Name: "svc-0", + }, + Consumers: []*pbmulticluster.ComputedExportedServicesConsumer{ + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{ + Peer: "peer-1", + }, + }, + { + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{ + Peer: "peer-2", + }, + }, + }, + }, + }, + } + } + + prototest.AssertDeepEqual(suite.T(), expectedCES, computedCES) + }) +} + +func (suite *controllerSuite) TestController() { + // Run the controller manager + mgr := controller.NewManager(suite.client, suite.rt.Logger) + mgr.Register(Controller()) + mgr.SetRaftLeader(true) + go mgr.Run(suite.ctx) + + suite.runTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) { + id := rtest.Resource(pbmulticluster.ComputedExportedServicesType, "global").WithTenancy(&pbresource.Tenancy{ + Partition: tenancy.Partition, + }).ID() + require.NotNil(suite.T(), id) + + svc1 := suite.writeService("svc1", tenancy) + exportedSvcData := &pbmulticluster.ExportedServices{ + Services: []string{"svc1", "svcx"}, + Consumers: []*pbmulticluster.ExportedServicesConsumer{{ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{ + Peer: "peer-0", + }}}, + } + + if suite.isEnterprise { + exportedSvcData.Consumers = append(exportedSvcData.Consumers, &pbmulticluster.ExportedServicesConsumer{ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Partition{ + Partition: "part-0", + }}) + } + + expSvc := suite.writeExportedService("expsvc", tenancy, exportedSvcData) + require.NotNil(suite.T(), expSvc) + + res := suite.client.WaitForResourceExists(suite.T(), id) + computedCES := suite.getComputedExportedSvc(id) + + expectedComputedExportedService := constructComputedExportedServices( + constructComputedExportedService( + constructSvcReference("svc1", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-0", "peer"), + suite.constructConsumer("part-0", "partition"), + }), + ) + + prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, computedCES) + + svc2 := suite.writeService("svc2", tenancy) + svc0 := suite.writeService("svc0", &pbresource.Tenancy{Partition: tenancy.Partition, Namespace: "app"}) + + exportedNamespaceSvcData := &pbmulticluster.NamespaceExportedServices{ + Consumers: []*pbmulticluster.ExportedServicesConsumer{{ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{ + Peer: "peer-1", + }}}, + } + namespaceExportedSvc := suite.writeNamespaceExportedService("namesvc", tenancy, exportedNamespaceSvcData) + + res = suite.client.WaitForNewVersion(suite.T(), id, res.Version) + computedCES = suite.getComputedExportedSvc(res.Id) + expectedComputedExportedService = constructComputedExportedServices( + constructComputedExportedService( + constructSvcReference("svc1", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-0", "peer"), + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("part-0", "partition"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc2", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + }, + ), + ) + + prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, computedCES) + + svc3 := suite.writeService("svc3", tenancy) + + res = suite.client.WaitForNewVersion(suite.T(), id, res.Version) + computedCES = suite.getComputedExportedSvc(res.Id) + expectedComputedExportedService = constructComputedExportedServices( + constructComputedExportedService( + constructSvcReference("svc1", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-0", "peer"), + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("part-0", "partition"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc2", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc3", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + }, + ), + ) + + prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, computedCES) + + suite.client.MustDelete(suite.T(), svc3.Id) + + res = suite.client.WaitForNewVersion(suite.T(), id, res.Version) + computedCES = suite.getComputedExportedSvc(res.Id) + expectedComputedExportedService = constructComputedExportedServices( + constructComputedExportedService( + constructSvcReference("svc1", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-0", "peer"), + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("part-0", "partition"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc2", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + }, + ), + ) + + prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, computedCES) + + partitionedExportedSvcData := &pbmulticluster.PartitionExportedServices{ + Consumers: []*pbmulticluster.ExportedServicesConsumer{{ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{ + Peer: "peer-1", + }}, {ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{ + Peer: "peer-2", + }}}, + } + partExpService := suite.writePartitionedExportedService("partsvc", tenancy, partitionedExportedSvcData) + + res = suite.client.WaitForNewVersion(suite.T(), id, res.Version) + computedCES = suite.getComputedExportedSvc(res.Id) + expectedComputedExportedService = constructComputedExportedServices( + constructComputedExportedService( + constructSvcReference("svc0", &pbresource.Tenancy{Partition: tenancy.Partition, Namespace: "app", PeerName: resource.DefaultPeerName}), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc1", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-0", "peer"), + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + suite.constructConsumer("part-0", "partition"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc2", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + }, + ), + ) + prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, computedCES) + + svc4 := suite.writeService("svc4", &pbresource.Tenancy{Partition: tenancy.Partition, Namespace: "app"}) + + res = suite.client.WaitForNewVersion(suite.T(), id, res.Version) + computedCES = suite.getComputedExportedSvc(res.Id) + expectedComputedExportedService = constructComputedExportedServices( + constructComputedExportedService( + constructSvcReference("svc0", &pbresource.Tenancy{Partition: tenancy.Partition, Namespace: "app", PeerName: resource.DefaultPeerName}), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc4", &pbresource.Tenancy{Partition: tenancy.Partition, Namespace: "app", PeerName: resource.DefaultPeerName}), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc1", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-0", "peer"), + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + suite.constructConsumer("part-0", "partition"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc2", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + }, + ), + ) + prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, computedCES) + + suite.client.MustDelete(suite.T(), svc4.Id) + + res = suite.client.WaitForNewVersion(suite.T(), id, res.Version) + computedCES = suite.getComputedExportedSvc(res.Id) + expectedComputedExportedService = constructComputedExportedServices( + constructComputedExportedService( + constructSvcReference("svc0", &pbresource.Tenancy{Partition: tenancy.Partition, Namespace: "app", PeerName: resource.DefaultPeerName}), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc1", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-0", "peer"), + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + suite.constructConsumer("part-0", "partition"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc2", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + }, + ), + ) + prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, computedCES) + + suite.writeService("svc5", tenancy) + + res = suite.client.WaitForNewVersion(suite.T(), id, res.Version) + computedCES = suite.getComputedExportedSvc(res.Id) + expectedComputedExportedService = constructComputedExportedServices( + constructComputedExportedService( + constructSvcReference("svc0", &pbresource.Tenancy{Partition: tenancy.Partition, Namespace: "app", PeerName: resource.DefaultPeerName}), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc1", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-0", "peer"), + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + suite.constructConsumer("part-0", "partition"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc2", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc5", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("peer-2", "peer"), + }, + ), + ) + prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, computedCES) + + suite.client.MustDelete(suite.T(), partExpService.Id) + + res = suite.client.WaitForNewVersion(suite.T(), id, res.Version) + computedCES = suite.getComputedExportedSvc(res.Id) + expectedComputedExportedService = constructComputedExportedServices( + constructComputedExportedService( + constructSvcReference("svc1", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-0", "peer"), + suite.constructConsumer("peer-1", "peer"), + suite.constructConsumer("part-0", "partition"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc2", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc5", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + }, + ), + ) + prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, computedCES) + + suite.client.MustDelete(suite.T(), namespaceExportedSvc.Id) + res = suite.client.WaitForNewVersion(suite.T(), id, res.Version) + computedCES = suite.getComputedExportedSvc(res.Id) + expectedComputedExportedService = constructComputedExportedServices( + constructComputedExportedService( + constructSvcReference("svc1", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-0", "peer"), + suite.constructConsumer("part-0", "partition"), + }, + ), + ) + prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, computedCES) + + suite.client.MustDelete(suite.T(), expSvc.Id) + suite.client.WaitForDeletion(suite.T(), res.Id) + + namespaceExportedSvc = suite.writeNamespaceExportedService("namesvc1", &pbresource.Tenancy{Partition: tenancy.Partition, Namespace: "app"}, exportedNamespaceSvcData) + + res = suite.client.WaitForResourceExists(suite.T(), id) + computedCES = suite.getComputedExportedSvc(res.Id) + expectedComputedExportedService = constructComputedExportedServices( + constructComputedExportedService( + constructSvcReference("svc0", &pbresource.Tenancy{Partition: tenancy.Partition, Namespace: "app", PeerName: resource.DefaultPeerName}), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + }, + ), + ) + prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, computedCES) + + expSvc = suite.writeExportedService("expsvc1", tenancy, exportedSvcData) + res = suite.client.WaitForNewVersion(suite.T(), id, res.Version) + computedCES = suite.getComputedExportedSvc(res.Id) + expectedComputedExportedService = constructComputedExportedServices( + constructComputedExportedService( + constructSvcReference("svc0", &pbresource.Tenancy{Partition: tenancy.Partition, Namespace: "app", PeerName: resource.DefaultPeerName}), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-1", "peer"), + }, + ), + constructComputedExportedService( + constructSvcReference("svc1", tenancy), + []*pbmulticluster.ComputedExportedServicesConsumer{ + suite.constructConsumer("peer-0", "peer"), + suite.constructConsumer("part-0", "partition"), + }, + ), + ) + prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, computedCES) + + suite.client.MustDelete(suite.T(), svc0.Id) + suite.client.MustDelete(suite.T(), svc1.Id) + suite.client.WaitForDeletion(suite.T(), res.Id) + + // Delete other resources + suite.client.MustDelete(suite.T(), svc2.Id) + suite.client.MustDelete(suite.T(), svc3.Id) + suite.client.MustDelete(suite.T(), expSvc.Id) + suite.client.MustDelete(suite.T(), namespaceExportedSvc.Id) + suite.client.MustDelete(suite.T(), partExpService.Id) + }) +} + func (suite *controllerSuite) runTestCaseWithTenancies(testFunc func(*pbresource.Tenancy)) { for _, tenancy := range suite.tenancies { suite.Run(suite.appendTenancyInfo(tenancy), func() { @@ -79,13 +796,8 @@ func (suite *controllerSuite) getComputedExportedSvc(id *pbresource.ID) *pbmulti return decodedComputedExportedService.Data } -func (suite *controllerSuite) reconcileTest(tenancy *pbresource.Tenancy) { - id := rtest.Resource(pbmulticluster.ComputedExportedServicesType, "global").WithTenancy(&pbresource.Tenancy{ - Partition: tenancy.Partition, - }).ID() - require.NotNil(suite.T(), id) - - svc1 := rtest.Resource(pbcatalog.ServiceType, "svc1"). +func (suite *controllerSuite) writeService(name string, tenancy *pbresource.Tenancy) *pbresource.Resource { + return rtest.Resource(pbcatalog.ServiceType, name). WithData(suite.T(), &pbcatalog.Service{ Ports: []*pbcatalog.ServicePort{ {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, @@ -93,348 +805,75 @@ func (suite *controllerSuite) reconcileTest(tenancy *pbresource.Tenancy) { }). WithTenancy(tenancy). Write(suite.T(), suite.client) - - exportedSvcData := &pbmulticluster.ExportedServices{ - Services: []string{"svc1", "svcx"}, - Consumers: []*pbmulticluster.ExportedServicesConsumer{{ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{ - Peer: "peer-0", - }}}, - } - - if suite.isEnterprise { - exportedSvcData.Consumers = append(exportedSvcData.Consumers, &pbmulticluster.ExportedServicesConsumer{ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Partition{ - Partition: "part-0", - }}) - } - expSvc := rtest.Resource(pbmulticluster.ExportedServicesType, "expsvc").WithData(suite.T(), exportedSvcData).WithTenancy(tenancy).Write(suite.T(), suite.client) - require.NotNil(suite.T(), expSvc) - - err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - actualComputedExportedService := suite.getComputedExportedSvc(id) - expectedComputedExportedService := getExpectation(tenancy, suite.isEnterprise, 0) - prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, actualComputedExportedService) - - rtest.Resource(pbcatalog.ServiceType, "svc2"). - WithData(suite.T(), &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, - }, - }). - WithTenancy(tenancy). - Write(suite.T(), suite.client) - - svc0 := rtest.Resource(pbcatalog.ServiceType, "svc0"). - WithData(suite.T(), &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, - }, - }). - WithTenancy(&pbresource.Tenancy{ - Partition: tenancy.Partition, - Namespace: "app", - }). - Write(suite.T(), suite.client) - - exportedNamespaceSvcData := &pbmulticluster.NamespaceExportedServices{ - Consumers: []*pbmulticluster.ExportedServicesConsumer{{ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{ - Peer: "peer-1", - }}}, - } - - nameexpSvc := rtest.Resource(pbmulticluster.NamespaceExportedServicesType, "namesvc").WithData(suite.T(), exportedNamespaceSvcData).WithTenancy(tenancy).Write(suite.T(), suite.client) - require.NotNil(suite.T(), nameexpSvc) - err = suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - actualComputedExportedService = suite.getComputedExportedSvc(id) - expectedComputedExportedService = getExpectation(tenancy, suite.isEnterprise, 1) - prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, actualComputedExportedService) - - svc3 := rtest.Resource(pbcatalog.ServiceType, "svc3"). - WithData(suite.T(), &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, - }, - }). - WithTenancy(tenancy). - Write(suite.T(), suite.client) - - err = suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - actualComputedExportedService = suite.getComputedExportedSvc(id) - expectedComputedExportedService = getExpectation(tenancy, suite.isEnterprise, 2) - prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, actualComputedExportedService) - - suite.client.MustDelete(suite.T(), svc3.Id) - err = suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - - actualComputedExportedService = suite.getComputedExportedSvc(id) - expectedComputedExportedService = getExpectation(tenancy, suite.isEnterprise, 3) - prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, actualComputedExportedService) - - partitionedExportedSvcData := &pbmulticluster.PartitionExportedServices{ - Consumers: []*pbmulticluster.ExportedServicesConsumer{{ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{ - Peer: "peer-1", - }}, {ConsumerTenancy: &pbmulticluster.ExportedServicesConsumer_Peer{ - Peer: "peer-2", - }}}, - } - - partexpSvc := rtest.Resource(pbmulticluster.PartitionExportedServicesType, "partsvc").WithData(suite.T(), partitionedExportedSvcData).WithTenancy(&pbresource.Tenancy{Partition: tenancy.Partition}).Write(suite.T(), suite.client) - require.NotNil(suite.T(), partexpSvc) - - err = suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - actualComputedExportedService = suite.getComputedExportedSvc(id) - expectedComputedExportedService = getExpectation(tenancy, suite.isEnterprise, 4) - prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, actualComputedExportedService) - - svc4 := rtest.Resource(pbcatalog.ServiceType, "svc4"). - WithData(suite.T(), &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, - }, - }). - WithTenancy(&pbresource.Tenancy{ - Partition: tenancy.Partition, - Namespace: "app", - }). - Write(suite.T(), suite.client) - - err = suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - actualComputedExportedService = suite.getComputedExportedSvc(id) - expectedComputedExportedService = getExpectation(tenancy, suite.isEnterprise, 5) - prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, actualComputedExportedService) - - suite.client.MustDelete(suite.T(), svc4.Id) - err = suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - - actualComputedExportedService = suite.getComputedExportedSvc(id) - expectedComputedExportedService = getExpectation(tenancy, suite.isEnterprise, 6) - prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, actualComputedExportedService) - - rtest.Resource(pbcatalog.ServiceType, "svc5"). - WithData(suite.T(), &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, - }, - }). - WithTenancy(tenancy). - Write(suite.T(), suite.client) - - err = suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - - actualComputedExportedService = suite.getComputedExportedSvc(id) - expectedComputedExportedService = getExpectation(tenancy, suite.isEnterprise, 7) - prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, actualComputedExportedService) - - suite.client.MustDelete(suite.T(), partexpSvc.Id) - err = suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - - actualComputedExportedService = suite.getComputedExportedSvc(id) - expectedComputedExportedService = getExpectation(tenancy, suite.isEnterprise, 8) - - prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, actualComputedExportedService) - - suite.client.MustDelete(suite.T(), nameexpSvc.Id) - err = suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - - actualComputedExportedService = suite.getComputedExportedSvc(id) - - expectedComputedExportedService = getExpectation(tenancy, suite.isEnterprise, 9) - prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, actualComputedExportedService) - - suite.client.MustDelete(suite.T(), expSvc.Id) - err = suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - - suite.client.RequireResourceNotFound(suite.T(), id) - - nameexpSvc1 := rtest.Resource(pbmulticluster.NamespaceExportedServicesType, "namesvc1").WithData(suite.T(), exportedNamespaceSvcData).WithTenancy(&pbresource.Tenancy{ - Partition: tenancy.Partition, - Namespace: "app", - }).Write(suite.T(), suite.client) - require.NotNil(suite.T(), nameexpSvc1) - err = suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - actualComputedExportedService = suite.getComputedExportedSvc(id) - expectedComputedExportedService = getExpectation(&pbresource.Tenancy{ - Partition: tenancy.Partition, - Namespace: "app", - }, suite.isEnterprise, 10) - prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, actualComputedExportedService) - - expSvc1 := rtest.Resource(pbmulticluster.ExportedServicesType, "expsvc1").WithData(suite.T(), exportedSvcData).WithTenancy(tenancy).Write(suite.T(), suite.client) - require.NotNil(suite.T(), expSvc1) - - err = suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - actualComputedExportedService = suite.getComputedExportedSvc(id) - expectedComputedExportedService = getExpectation(tenancy, suite.isEnterprise, 11) - prototest.AssertDeepEqual(suite.T(), expectedComputedExportedService, actualComputedExportedService) - - suite.client.MustDelete(suite.T(), svc0.Id) - suite.client.MustDelete(suite.T(), svc1.Id) - - err = suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) - require.NoError(suite.T(), err) - - suite.client.RequireResourceNotFound(suite.T(), id) - } -func getExpectation(tenancy *pbresource.Tenancy, isEnterprise bool, testCase int) *pbmulticluster.ComputedExportedServices { - makeCES := func(consumers ...*pbmulticluster.ComputedExportedService) *pbmulticluster.ComputedExportedServices { - return &pbmulticluster.ComputedExportedServices{ - Consumers: consumers, +func (suite *controllerSuite) writeExportedService(name string, tenancy *pbresource.Tenancy, data *pbmulticluster.ExportedServices) *pbresource.Resource { + return rtest.Resource(pbmulticluster.ExportedServicesType, name). + WithData(suite.T(), data). + WithTenancy(tenancy). + Write(suite.T(), suite.client) +} + +func (suite *controllerSuite) writeNamespaceExportedService(name string, tenancy *pbresource.Tenancy, data *pbmulticluster.NamespaceExportedServices) *pbresource.Resource { + return rtest.Resource(pbmulticluster.NamespaceExportedServicesType, name). + WithData(suite.T(), data). + WithTenancy(tenancy). + Write(suite.T(), suite.client) +} + +func (suite *controllerSuite) writePartitionedExportedService(name string, tenancy *pbresource.Tenancy, data *pbmulticluster.PartitionExportedServices) *pbresource.Resource { + return rtest.Resource(pbmulticluster.PartitionExportedServicesType, name). + WithData(suite.T(), data). + WithTenancy(&pbresource.Tenancy{Partition: tenancy.Partition}). + Write(suite.T(), suite.client) +} + +func (suite *controllerSuite) constructConsumer(name, consumerType string) *pbmulticluster.ComputedExportedServicesConsumer { + if consumerType == "peer" { + return &pbmulticluster.ComputedExportedServicesConsumer{ + ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{ + Peer: name, + }, } } - makeConsumer := func(ref *pbresource.Reference, consumers ...*pbmulticluster.ComputedExportedServicesConsumer) *pbmulticluster.ComputedExportedService { - var actual []*pbmulticluster.ComputedExportedServicesConsumer - for _, c := range consumers { - _, ok := c.ConsumerTenancy.(*pbmulticluster.ComputedExportedServicesConsumer_Partition) - if (isEnterprise && ok) || !ok { - actual = append(actual, c) - } - } - return &pbmulticluster.ComputedExportedService{ - TargetRef: ref, - Consumers: actual, - } - } - svc0Ref := &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: tenancy.Partition, - Namespace: "app", - PeerName: resource.DefaultPeerName, - }, - Name: "svc0", - } - svc1Ref := &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: tenancy, - Name: "svc1", - } - svc2Ref := &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: tenancy, - Name: "svc2", - } - svc3Ref := &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: tenancy, - Name: "svc3", - } - svc4Ref := &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: tenancy.Partition, - Namespace: "app", - PeerName: resource.DefaultPeerName, - }, - Name: "svc4", - } - svc5Ref := &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: tenancy, - Name: "svc5", + if !suite.isEnterprise { + return nil } - peer0Consumer := &pbmulticluster.ComputedExportedServicesConsumer{ - ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{ - Peer: "peer-0", - }, - } - peer1Consumer := &pbmulticluster.ComputedExportedServicesConsumer{ - ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{ - Peer: "peer-1", - }, - } - peer2Consumer := &pbmulticluster.ComputedExportedServicesConsumer{ - ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{ - Peer: "peer-2", - }, - } - - part0Consumer := &pbmulticluster.ComputedExportedServicesConsumer{ + return &pbmulticluster.ComputedExportedServicesConsumer{ ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Partition{ - Partition: "part-0", + Partition: name, }, } +} - switch testCase { - case 0: - return makeCES(makeConsumer(svc1Ref, peer0Consumer, part0Consumer)) - case 1: - return makeCES( - makeConsumer(svc1Ref, peer0Consumer, peer1Consumer, part0Consumer), - makeConsumer(svc2Ref, peer1Consumer), - ) - case 2: - return makeCES( - makeConsumer(svc1Ref, peer0Consumer, peer1Consumer, part0Consumer), - makeConsumer(svc2Ref, peer1Consumer), - makeConsumer(svc3Ref, peer1Consumer), - ) - case 3: - return makeCES( - makeConsumer(svc1Ref, peer0Consumer, peer1Consumer, part0Consumer), - makeConsumer(svc2Ref, peer1Consumer), - ) - case 4: - return makeCES( - makeConsumer(svc0Ref, peer1Consumer, peer2Consumer), - makeConsumer(svc1Ref, peer0Consumer, peer1Consumer, peer2Consumer, part0Consumer), - makeConsumer(svc2Ref, peer1Consumer, peer2Consumer), - ) - case 5: - return makeCES( - makeConsumer(svc0Ref, peer1Consumer, peer2Consumer), - makeConsumer(svc4Ref, peer1Consumer, peer2Consumer), - makeConsumer(svc1Ref, peer0Consumer, peer1Consumer, peer2Consumer, part0Consumer), - makeConsumer(svc2Ref, peer1Consumer, peer2Consumer), - ) - case 6: - return makeCES( - makeConsumer(svc0Ref, peer1Consumer, peer2Consumer), - makeConsumer(svc1Ref, peer0Consumer, peer1Consumer, peer2Consumer, part0Consumer), - makeConsumer(svc2Ref, peer1Consumer, peer2Consumer), - ) - case 7: - return makeCES( - makeConsumer(svc0Ref, peer1Consumer, peer2Consumer), - makeConsumer(svc1Ref, peer0Consumer, peer1Consumer, peer2Consumer, part0Consumer), - makeConsumer(svc2Ref, peer1Consumer, peer2Consumer), - makeConsumer(svc5Ref, peer1Consumer, peer2Consumer), - ) - case 8: - return makeCES( - makeConsumer(svc1Ref, peer0Consumer, peer1Consumer, part0Consumer), - makeConsumer(svc2Ref, peer1Consumer), - makeConsumer(svc5Ref, peer1Consumer), - ) - case 9: - return makeCES( - makeConsumer(svc1Ref, peer0Consumer, part0Consumer), - ) - case 10: - return makeCES( - makeConsumer(svc0Ref, peer1Consumer), - ) - case 11: - return makeCES( - makeConsumer(svc0Ref, peer1Consumer), - makeConsumer(svc1Ref, peer0Consumer, part0Consumer), - ) +func constructComputedExportedService(ref *pbresource.Reference, consumers []*pbmulticluster.ComputedExportedServicesConsumer) *pbmulticluster.ComputedExportedService { + finalConsumers := make([]*pbmulticluster.ComputedExportedServicesConsumer, 0) + for _, c := range consumers { + if c == nil { + continue + } + + finalConsumers = append(finalConsumers, c) } - return nil + return &pbmulticluster.ComputedExportedService{ + TargetRef: ref, + Consumers: finalConsumers, + } +} + +func constructComputedExportedServices(consumers ...*pbmulticluster.ComputedExportedService) *pbmulticluster.ComputedExportedServices { + return &pbmulticluster.ComputedExportedServices{ + Consumers: consumers, + } +} + +func constructSvcReference(name string, tenancy *pbresource.Tenancy) *pbresource.Reference { + return &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Tenancy: tenancy, + Name: name, + } }