From b7f899e29be211ed165066fcd25a593c6f71bb7f Mon Sep 17 00:00:00 2001 From: Dhia Ayachi Date: Fri, 20 Oct 2023 13:29:42 -0400 Subject: [PATCH] tenancy bridge v2 for v2 resources --- agent/consul/server.go | 9 +- .../services/resource/list_by_owner.go | 4 +- agent/grpc-external/services/resource/read.go | 4 +- .../grpc-external/services/resource/server.go | 16 +-- .../services/resource/server_test.go | 2 +- .../services/resource/testing/testing.go | 23 +++- .../services/resource/testing/testing_ce.go | 45 ++++++++ .../grpc-external/services/resource/write.go | 8 +- .../services/resource/write_status.go | 4 +- internal/resource/http/http_test.go | 7 +- internal/resource/tenancy.go | 9 -- internal/tenancy/exports.go | 9 ++ .../tenancy/internal/bridge/tenancy_bridge.go | 51 +++++++++ .../internal/bridge}/tenancy_bridge_ce.go | 13 +-- .../{namespace_test.go => types_test.go} | 65 +---------- .../tenancy/tenancytest/namespace_test.go | 107 ++++++++++++++++++ 16 files changed, 264 insertions(+), 112 deletions(-) create mode 100644 internal/tenancy/internal/bridge/tenancy_bridge.go rename internal/{resource => tenancy/internal/bridge}/tenancy_bridge_ce.go (52%) rename internal/tenancy/internal/types/{namespace_test.go => types_test.go} (67%) create mode 100644 internal/tenancy/tenancytest/namespace_test.go diff --git a/agent/consul/server.go b/agent/consul/server.go index 1fe709b4b6e3..73a26bd805c4 100644 --- a/agent/consul/server.go +++ b/agent/consul/server.go @@ -8,6 +8,7 @@ import ( "crypto/x509" "errors" "fmt" + "github.com/hashicorp/consul/internal/tenancy" "io" "net" "os" @@ -1456,7 +1457,8 @@ func (s *Server) setupExternalGRPC(config *Config, deps Deps, logger hclog.Logge tenancyBridge := NewV1TenancyBridge(s) if stringslice.Contains(deps.Experiments, V2TenancyExperimentName) { - tenancyBridge = resource.NewV2TenancyBridge() + tenancyBridgeV2 := tenancy.NewV2TenancyBridge() + tenancyBridge = tenancyBridgeV2.WithClient(s.insecureResourceServiceClient) } s.resourceServiceServer = resourcegrpc.NewServer(resourcegrpc.Config{ @@ -1477,8 +1479,9 @@ func (s *Server) setupInsecureResourceServiceClient(typeRegistry resource.Regist } tenancyBridge := NewV1TenancyBridge(s) + tenancyBridgeV2 := tenancy.NewV2TenancyBridge() if stringslice.Contains(deps.Experiments, V2TenancyExperimentName) { - tenancyBridge = resource.NewV2TenancyBridge() + tenancyBridge = tenancyBridgeV2 } server := resourcegrpc.NewServer(resourcegrpc.Config{ Registry: typeRegistry, @@ -1493,7 +1496,7 @@ func (s *Server) setupInsecureResourceServiceClient(typeRegistry resource.Regist return err } s.insecureResourceServiceClient = pbresource.NewResourceServiceClient(conn) - + tenancyBridgeV2.WithClient(s.insecureResourceServiceClient) return nil } diff --git a/agent/grpc-external/services/resource/list_by_owner.go b/agent/grpc-external/services/resource/list_by_owner.go index a9b1754498fe..dba7bc23d45e 100644 --- a/agent/grpc-external/services/resource/list_by_owner.go +++ b/agent/grpc-external/services/resource/list_by_owner.go @@ -42,8 +42,8 @@ func (s *Server) ListByOwner(ctx context.Context, req *pbresource.ListByOwnerReq return nil, status.Errorf(codes.Internal, "failed list acl: %v", err) } - // Check v1 tenancy exists for the v2 resource. - if err = v1TenancyExists(reg, s.TenancyBridge, req.Owner.Tenancy, codes.InvalidArgument); err != nil { + // Check tenancy exists for the v2 resource. + if err = tenancyExists(reg, s.TenancyBridge, req.Owner.Tenancy, codes.InvalidArgument); err != nil { return nil, err } diff --git a/agent/grpc-external/services/resource/read.go b/agent/grpc-external/services/resource/read.go index b6cec3725456..cab8a2d6f2af 100644 --- a/agent/grpc-external/services/resource/read.go +++ b/agent/grpc-external/services/resource/read.go @@ -59,8 +59,8 @@ func (s *Server) Read(ctx context.Context, req *pbresource.ReadRequest) (*pbreso return nil, status.Errorf(codes.Internal, "failed read acl: %v", err) } - // Check V1 tenancy exists for the V2 resource. - if err = v1TenancyExists(reg, s.TenancyBridge, req.Id.Tenancy, codes.NotFound); err != nil { + // Check tenancy exists for the V2 resource. + if err = tenancyExists(reg, s.TenancyBridge, req.Id.Tenancy, codes.NotFound); err != nil { return nil, err } diff --git a/agent/grpc-external/services/resource/server.go b/agent/grpc-external/services/resource/server.go index 1084fd3860a1..401631f73668 100644 --- a/agent/grpc-external/services/resource/server.go +++ b/agent/grpc-external/services/resource/server.go @@ -208,10 +208,10 @@ func validateWildcardTenancy(tenancy *pbresource.Tenancy, namePrefix string) err return nil } -// v1TenancyExists return an error with the passed in gRPC status code when tenancy partition or namespace do not exist. -func v1TenancyExists(reg *resource.Registration, v1Bridge TenancyBridge, tenancy *pbresource.Tenancy, errCode codes.Code) error { +// tenancyExists return an error with the passed in gRPC status code when tenancy partition or namespace do not exist. +func tenancyExists(reg *resource.Registration, tenancyBridge TenancyBridge, tenancy *pbresource.Tenancy, errCode codes.Code) error { if reg.Scope == resource.ScopePartition || reg.Scope == resource.ScopeNamespace { - exists, err := v1Bridge.PartitionExists(tenancy.Partition) + exists, err := tenancyBridge.PartitionExists(tenancy.Partition) switch { case err != nil: return err @@ -221,7 +221,7 @@ func v1TenancyExists(reg *resource.Registration, v1Bridge TenancyBridge, tenancy } if reg.Scope == resource.ScopeNamespace { - exists, err := v1Bridge.NamespaceExists(tenancy.Partition, tenancy.Namespace) + exists, err := tenancyBridge.NamespaceExists(tenancy.Partition, tenancy.Namespace) switch { case err != nil: return err @@ -232,10 +232,10 @@ func v1TenancyExists(reg *resource.Registration, v1Bridge TenancyBridge, tenancy return nil } -// v1TenancyMarkedForDeletion returns a gRPC InvalidArgument when either partition or namespace is marked for deletion. -func v1TenancyMarkedForDeletion(reg *resource.Registration, v1Bridge TenancyBridge, tenancy *pbresource.Tenancy) error { +// tenancyMarkedForDeletion returns a gRPC InvalidArgument when either partition or namespace is marked for deletion. +func tenancyMarkedForDeletion(reg *resource.Registration, tenancyBridge TenancyBridge, tenancy *pbresource.Tenancy) error { if reg.Scope == resource.ScopePartition || reg.Scope == resource.ScopeNamespace { - marked, err := v1Bridge.IsPartitionMarkedForDeletion(tenancy.Partition) + marked, err := tenancyBridge.IsPartitionMarkedForDeletion(tenancy.Partition) switch { case err != nil: return err @@ -245,7 +245,7 @@ func v1TenancyMarkedForDeletion(reg *resource.Registration, v1Bridge TenancyBrid } if reg.Scope == resource.ScopeNamespace { - marked, err := v1Bridge.IsNamespaceMarkedForDeletion(tenancy.Partition, tenancy.Namespace) + marked, err := tenancyBridge.IsNamespaceMarkedForDeletion(tenancy.Partition, tenancy.Namespace) switch { case err != nil: return err diff --git a/agent/grpc-external/services/resource/server_test.go b/agent/grpc-external/services/resource/server_test.go index ffe7df52c401..969173b429db 100644 --- a/agent/grpc-external/services/resource/server_test.go +++ b/agent/grpc-external/services/resource/server_test.go @@ -75,7 +75,7 @@ func testServer(t *testing.T) *Server { } }) - // Mock the V1 tenancy bridge since we can't use the real thing. + // Mock the tenancy bridge since we can't use the real thing. mockTenancyBridge := &MockTenancyBridge{} mockTenancyBridge.On("PartitionExists", resource.DefaultPartitionName).Return(true, nil) mockTenancyBridge.On("NamespaceExists", resource.DefaultPartitionName, resource.DefaultNamespaceName).Return(true, nil) diff --git a/agent/grpc-external/services/resource/testing/testing.go b/agent/grpc-external/services/resource/testing/testing.go index 2375ae95b889..e7c3fb7a0507 100644 --- a/agent/grpc-external/services/resource/testing/testing.go +++ b/agent/grpc-external/services/resource/testing/testing.go @@ -5,14 +5,13 @@ package testing import ( "context" - "testing" - + "github.com/hashicorp/consul/internal/tenancy" + "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - - "github.com/hashicorp/go-uuid" + "testing" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl/resolver" @@ -98,6 +97,12 @@ func RunResourceServiceWithConfig(t *testing.T, config svc.Config, registerFns . mockTenancyBridge.On("IsPartitionMarkedForDeletion", "foo").Return(false, nil) mockTenancyBridge.On("IsNamespaceMarkedForDeletion", resource.DefaultPartitionName, resource.DefaultNamespaceName).Return(false, nil) config.TenancyBridge = mockTenancyBridge + } else { + switch config.TenancyBridge.(type) { + case *tenancy.V2TenancyBridge: + err = initTenancy(ctx, backend) + require.NoError(t, err) + } } if config.ACLResolver == nil { @@ -140,6 +145,14 @@ func RunResourceServiceWithConfig(t *testing.T, config svc.Config, registerFns . ) require.NoError(t, err) t.Cleanup(func() { _ = conn.Close() }) + client := pbresource.NewResourceServiceClient(conn) + if config.TenancyBridge != nil { + switch config.TenancyBridge.(type) { + case *tenancy.V2TenancyBridge: + config.TenancyBridge.(*tenancy.V2TenancyBridge).WithClient(client) + } + + } - return pbresource.NewResourceServiceClient(conn) + return client } diff --git a/agent/grpc-external/services/resource/testing/testing_ce.go b/agent/grpc-external/services/resource/testing/testing_ce.go index 023fa5189ccc..2d1c22b561b8 100644 --- a/agent/grpc-external/services/resource/testing/testing_ce.go +++ b/agent/grpc-external/services/resource/testing/testing_ce.go @@ -6,7 +6,17 @@ package testing import ( + "context" + "errors" "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/internal/resource" + "github.com/hashicorp/consul/internal/storage" + "github.com/hashicorp/consul/internal/storage/inmem" + "github.com/hashicorp/consul/proto-public/pbresource" + pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" + "github.com/oklog/ulid/v2" + "google.golang.org/protobuf/types/known/anypb" + "time" ) func FillEntMeta(entMeta *acl.EnterpriseMeta) { @@ -16,3 +26,38 @@ func FillEntMeta(entMeta *acl.EnterpriseMeta) { func FillAuthorizerContext(authzContext *acl.AuthorizerContext) { // nothing to to in CE. } + +// initTenancy create the base tenancy objects (default/default) +func initTenancy(ctx context.Context, b *inmem.Backend) error { + //TODO(dhiaayachi): This is now called for testing purpose but at some point we need to add something similar + // when bootstrapping a server, probably in the tenancy controllers. + nsData, err := anypb.New(&pbtenancy.Namespace{Description: "default namespace in default partition"}) + if err != nil { + return err + } + nsID := &pbresource.ID{ + Type: pbtenancy.NamespaceType, + Name: resource.DefaultNamespaceName, + Tenancy: resource.DefaultPartitionedTenancy(), + Uid: ulid.Make().String(), + } + read, err := b.Read(ctx, storage.StrongConsistency, nsID) + if err != nil && !errors.Is(err, storage.ErrNotFound) { + return err + } + if read == nil && errors.Is(err, storage.ErrNotFound) { + _, err = b.WriteCAS(ctx, &pbresource.Resource{ + Id: nsID, + Generation: ulid.Make().String(), + Data: nsData, + Metadata: map[string]string{ + "generated_at": time.Now().Format(time.RFC3339), + }, + }) + if err != nil { + return err + } + } + return nil + +} diff --git a/agent/grpc-external/services/resource/write.go b/agent/grpc-external/services/resource/write.go index 7110122313fa..aacd1ec859bf 100644 --- a/agent/grpc-external/services/resource/write.go +++ b/agent/grpc-external/services/resource/write.go @@ -78,13 +78,13 @@ func (s *Server) Write(ctx context.Context, req *pbresource.WriteRequest) (*pbre return nil, status.Errorf(codes.Internal, "failed write acl: %v", err) } - // Check V1 tenancy exists for the V2 resource - if err = v1TenancyExists(reg, s.TenancyBridge, req.Resource.Id.Tenancy, codes.InvalidArgument); err != nil { + // Check tenancy exists for the V2 resource + if err = tenancyExists(reg, s.TenancyBridge, req.Resource.Id.Tenancy, codes.InvalidArgument); err != nil { return nil, err } - // Check V1 tenancy not marked for deletion. - if err = v1TenancyMarkedForDeletion(reg, s.TenancyBridge, req.Resource.Id.Tenancy); err != nil { + // Check tenancy not marked for deletion. + if err = tenancyMarkedForDeletion(reg, s.TenancyBridge, req.Resource.Id.Tenancy); err != nil { return nil, err } diff --git a/agent/grpc-external/services/resource/write_status.go b/agent/grpc-external/services/resource/write_status.go index 993c8382e2c4..7009a7fd72bb 100644 --- a/agent/grpc-external/services/resource/write_status.go +++ b/agent/grpc-external/services/resource/write_status.go @@ -34,9 +34,9 @@ func (s *Server) WriteStatus(ctx context.Context, req *pbresource.WriteStatusReq // Apply defaults when tenancy units empty. v1EntMetaToV2Tenancy(reg, entMeta, req.Id.Tenancy) - // Check V1 tenancy exists for the V2 resource. Ignore "marked for deletion" since status updates + // Check tenancy exists for the V2 resource. Ignore "marked for deletion" since status updates // should still work regardless. - if err = v1TenancyExists(reg, s.TenancyBridge, req.Id.Tenancy, codes.InvalidArgument); err != nil { + if err = tenancyExists(reg, s.TenancyBridge, req.Id.Tenancy, codes.InvalidArgument); err != nil { return nil, err } diff --git a/internal/resource/http/http_test.go b/internal/resource/http/http_test.go index 46d2af363590..aeb85f0b8332 100644 --- a/internal/resource/http/http_test.go +++ b/internal/resource/http/http_test.go @@ -157,7 +157,7 @@ func TestResourceWriteHandler(t *testing.T) { require.Equal(t, http.StatusForbidden, rsp.Result().StatusCode) }) - + var readRsp *pbresource.ReadResponse t.Run("should write to the resource backend", func(t *testing.T) { rsp := httptest.NewRecorder() req := httptest.NewRequest("PUT", "/demo/v2/artist/keith-urban?partition=default&peer_name=local&namespace=default", strings.NewReader(` @@ -183,7 +183,8 @@ func TestResourceWriteHandler(t *testing.T) { require.Equal(t, "Keith Urban", result["data"].(map[string]any)["name"]) require.Equal(t, "keith-urban", result["id"].(map[string]any)["name"]) - readRsp, err := client.Read(testutil.TestContext(t), &pbresource.ReadRequest{ + var err error + readRsp, err = client.Read(testutil.TestContext(t), &pbresource.ReadRequest{ Id: &pbresource.ID{ Type: demo.TypeV2Artist, Tenancy: resource.DefaultNamespacedTenancy(), @@ -200,7 +201,7 @@ func TestResourceWriteHandler(t *testing.T) { t.Run("should update the record with version parameter", func(t *testing.T) { rsp := httptest.NewRecorder() - req := httptest.NewRequest("PUT", "/demo/v2/artist/keith-urban?partition=default&peer_name=local&namespace=default&version=1", strings.NewReader(` + req := httptest.NewRequest("PUT", fmt.Sprintf("/demo/v2/artist/keith-urban?partition=default&peer_name=local&namespace=default&version=%s", readRsp.Resource.Version), strings.NewReader(` { "metadata": { "foo": "bar" diff --git a/internal/resource/tenancy.go b/internal/resource/tenancy.go index 99756d503ff3..597253aa8dc1 100644 --- a/internal/resource/tenancy.go +++ b/internal/resource/tenancy.go @@ -24,15 +24,6 @@ const ( DefaultPeerName = "local" ) -// V2TenancyBridge is used by the resource service to access V2 implementations of -// partitions and namespaces. -type V2TenancyBridge struct { -} - -func NewV2TenancyBridge() TenancyBridge { - return &V2TenancyBridge{} -} - // Scope describes the tenancy scope of a resource. type Scope int diff --git a/internal/tenancy/exports.go b/internal/tenancy/exports.go index 934f895955d0..b126e7445f74 100644 --- a/internal/tenancy/exports.go +++ b/internal/tenancy/exports.go @@ -5,6 +5,7 @@ package tenancy import ( "github.com/hashicorp/consul/internal/resource" + "github.com/hashicorp/consul/internal/tenancy/internal/bridge" "github.com/hashicorp/consul/internal/tenancy/internal/types" ) @@ -21,8 +22,16 @@ var ( NamespaceV2Beta1Type = types.NamespaceV2Beta1Type ) +type ( + V2TenancyBridge = bridge.V2TenancyBridge +) + // RegisterTypes adds all resource types within the "tenancy" API group // to the given type registry func RegisterTypes(r resource.Registry) { types.Register(r) } + +func NewV2TenancyBridge() *V2TenancyBridge { + return bridge.NewV2TenancyBridge() +} diff --git a/internal/tenancy/internal/bridge/tenancy_bridge.go b/internal/tenancy/internal/bridge/tenancy_bridge.go new file mode 100644 index 000000000000..1d70add85b76 --- /dev/null +++ b/internal/tenancy/internal/bridge/tenancy_bridge.go @@ -0,0 +1,51 @@ +package bridge + +import ( + "context" + "github.com/hashicorp/consul/internal/tenancy/internal/types" + "github.com/hashicorp/consul/proto-public/pbresource" +) + +// V2TenancyBridge is used by the resource service to access V2 implementations of +// partitions and namespaces. +type V2TenancyBridge struct { + client pbresource.ResourceServiceClient +} + +// WithClient inject a ResourceServiceClient in the V2TenancyBridge. +// This is needed to break a circular dependency between +// the ResourceServiceServer, ResourceServiceClient and the TenancyBridge +func (b *V2TenancyBridge) WithClient(client pbresource.ResourceServiceClient) *V2TenancyBridge { + b.client = client + return b +} + +func NewV2TenancyBridge() *V2TenancyBridge { + return &V2TenancyBridge{} +} + +func (b *V2TenancyBridge) NamespaceExists(partition, namespace string) (bool, error) { + read, err := b.client.Read(context.Background(), &pbresource.ReadRequest{ + Id: &pbresource.ID{ + Name: namespace, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + }, + Type: types.NamespaceType, + }, + }) + return read != nil && read.Resource != nil, err +} + +func (b *V2TenancyBridge) IsNamespaceMarkedForDeletion(partition, namespace string) (bool, error) { + read, err := b.client.Read(context.Background(), &pbresource.ReadRequest{ + Id: &pbresource.ID{ + Name: namespace, + Tenancy: &pbresource.Tenancy{ + Partition: partition, + }, + Type: types.NamespaceType, + }, + }) + return read.Resource != nil, err +} diff --git a/internal/resource/tenancy_bridge_ce.go b/internal/tenancy/internal/bridge/tenancy_bridge_ce.go similarity index 52% rename from internal/resource/tenancy_bridge_ce.go rename to internal/tenancy/internal/bridge/tenancy_bridge_ce.go index 9f88c2236199..dcf4df663cc7 100644 --- a/internal/resource/tenancy_bridge_ce.go +++ b/internal/tenancy/internal/bridge/tenancy_bridge_ce.go @@ -3,7 +3,7 @@ //go:build !consulent -package resource +package bridge func (b *V2TenancyBridge) PartitionExists(partition string) (bool, error) { if partition == "default" { @@ -15,14 +15,3 @@ func (b *V2TenancyBridge) PartitionExists(partition string) (bool, error) { func (b *V2TenancyBridge) IsPartitionMarkedForDeletion(partition string) (bool, error) { return false, nil } - -func (b *V2TenancyBridge) NamespaceExists(partition, namespace string) (bool, error) { - if partition == "default" && namespace == "default" { - return true, nil - } - return false, nil -} - -func (b *V2TenancyBridge) IsNamespaceMarkedForDeletion(partition, namespace string) (bool, error) { - return false, nil -} diff --git a/internal/tenancy/internal/types/namespace_test.go b/internal/tenancy/internal/types/types_test.go similarity index 67% rename from internal/tenancy/internal/types/namespace_test.go rename to internal/tenancy/internal/types/types_test.go index 2c68097d4cde..b8365a669894 100644 --- a/internal/tenancy/internal/types/namespace_test.go +++ b/internal/tenancy/internal/types/types_test.go @@ -1,27 +1,15 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - package types import ( - "context" "errors" - "testing" - - svctest "github.com/hashicorp/consul/agent/grpc-external/services/resource/testing" - rtest "github.com/hashicorp/consul/internal/resource/resourcetest" + "github.com/hashicorp/consul/internal/resource" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto/private/prototest" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - + "github.com/hashicorp/consul/proto-public/pbresource" + pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" "github.com/stretchr/testify/require" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/known/anypb" - - "github.com/hashicorp/consul/internal/resource" - "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" + "testing" ) func createNamespaceResource(t *testing.T, data protoreflect.ProtoMessage) *pbresource.Resource { @@ -144,51 +132,6 @@ func TestValidateNamespace(t *testing.T) { } } -func TestRead_Success(t *testing.T) { - client := svctest.RunResourceService(t, Register) - client = rtest.NewClient(client) - - res := rtest.Resource(NamespaceType, "ns1"). - WithData(t, validNamespace()). - Write(t, client) - - readRsp, err := client.Read(context.Background(), &pbresource.ReadRequest{Id: res.Id}) - require.NoError(t, err) - prototest.AssertDeepEqual(t, res.Id, readRsp.Resource.Id) -} - -func TestRead_NotFound(t *testing.T) { - client := svctest.RunResourceService(t, Register) - client = rtest.NewClient(client) - - res := rtest.Resource(NamespaceType, "ns1"). - WithData(t, validNamespace()).Build() - - _, err := client.Read(context.Background(), &pbresource.ReadRequest{Id: res.Id}) - require.Error(t, err) - require.Equal(t, codes.NotFound.String(), status.Code(err).String()) -} - -func TestDelete_Success(t *testing.T) { - client := svctest.RunResourceService(t, Register) - client = rtest.NewClient(client) - - res := rtest.Resource(NamespaceType, "ns1"). - WithData(t, validNamespace()).Write(t, client) - - readRsp, err := client.Read(context.Background(), &pbresource.ReadRequest{Id: res.Id}) - require.NoError(t, err) - prototest.AssertDeepEqual(t, res.Id, readRsp.Resource.Id) - - _, err = client.Delete(context.Background(), &pbresource.DeleteRequest{Id: res.Id}) - require.NoError(t, err) - - _, err = client.Read(context.Background(), &pbresource.ReadRequest{Id: res.Id}) - require.Error(t, err) - require.Equal(t, codes.NotFound.String(), status.Code(err).String()) - -} - func validNamespace() *pbtenancy.Namespace { return &pbtenancy.Namespace{ Description: "ns namespace", diff --git a/internal/tenancy/tenancytest/namespace_test.go b/internal/tenancy/tenancytest/namespace_test.go new file mode 100644 index 000000000000..56b0c7049a20 --- /dev/null +++ b/internal/tenancy/tenancytest/namespace_test.go @@ -0,0 +1,107 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package tenancytest + +import ( + "context" + "github.com/hashicorp/consul/agent/grpc-external/services/resource" + svctest "github.com/hashicorp/consul/agent/grpc-external/services/resource/testing" + resource2 "github.com/hashicorp/consul/internal/resource" + "github.com/hashicorp/consul/internal/tenancy" + "testing" + + rtest "github.com/hashicorp/consul/internal/resource/resourcetest" + "github.com/hashicorp/consul/proto/private/prototest" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/hashicorp/consul/proto-public/pbresource" + pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" + "github.com/stretchr/testify/require" +) + +func TestReadNamespace_Success(t *testing.T) { + v2TenancyBridge := tenancy.NewV2TenancyBridge() + config := resource.Config{TenancyBridge: v2TenancyBridge} + client := svctest.RunResourceServiceWithConfig(t, config, tenancy.RegisterTypes) + cl := rtest.NewClient(client) + + res := rtest.Resource(pbtenancy.NamespaceType, "ns1"). + WithData(t, validNamespace()). + Write(t, cl) + + readRsp, err := cl.Read(context.Background(), &pbresource.ReadRequest{Id: res.Id}) + require.NoError(t, err) + prototest.AssertDeepEqual(t, res.Id, readRsp.Resource.Id) +} + +func TestReadNamespace_NotFound(t *testing.T) { + v2TenancyBridge := tenancy.NewV2TenancyBridge() + config := resource.Config{TenancyBridge: v2TenancyBridge} + client := svctest.RunResourceServiceWithConfig(t, config, tenancy.RegisterTypes) + cl := rtest.NewClient(client) + + res := rtest.Resource(pbtenancy.NamespaceType, "ns1"). + WithData(t, validNamespace()).Build() + + _, err := cl.Read(context.Background(), &pbresource.ReadRequest{Id: res.Id}) + require.Error(t, err) + require.Equal(t, codes.NotFound.String(), status.Code(err).String()) +} + +func TestDeleteNamespace_Success(t *testing.T) { + v2TenancyBridge := tenancy.NewV2TenancyBridge() + config := resource.Config{TenancyBridge: v2TenancyBridge} + client := svctest.RunResourceServiceWithConfig(t, config, tenancy.RegisterTypes) + cl := rtest.NewClient(client) + + res := rtest.Resource(pbtenancy.NamespaceType, "ns1"). + WithData(t, validNamespace()).Write(t, cl) + + readRsp, err := cl.Read(context.Background(), &pbresource.ReadRequest{Id: res.Id}) + require.NoError(t, err) + prototest.AssertDeepEqual(t, res.Id, readRsp.Resource.Id) + + _, err = cl.Delete(context.Background(), &pbresource.DeleteRequest{Id: res.Id}) + require.NoError(t, err) + + _, err = cl.Read(context.Background(), &pbresource.ReadRequest{Id: res.Id}) + require.Error(t, err) + require.Equal(t, codes.NotFound.String(), status.Code(err).String()) + +} + +func TestListNamespace_Success(t *testing.T) { + v2TenancyBridge := tenancy.NewV2TenancyBridge() + config := resource.Config{TenancyBridge: v2TenancyBridge} + client := svctest.RunResourceServiceWithConfig(t, config, tenancy.RegisterTypes) + cl := rtest.NewClient(client) + + res := rtest.Resource(pbtenancy.NamespaceType, "ns1"). + WithData(t, validNamespace()).Write(t, cl) + + require.NotNil(t, res) + res = rtest.Resource(pbtenancy.NamespaceType, "ns2"). + WithData(t, validNamespace()).Write(t, cl) + + require.NotNil(t, res) + + listRsp, err := cl.List(context.Background(), &pbresource.ListRequest{Type: pbtenancy.NamespaceType, Tenancy: resource2.DefaultPartitionedTenancy()}) + require.NoError(t, err) + require.Len(t, listRsp.Resources, 3) + names := []string{ + listRsp.Resources[0].Id.Name, + listRsp.Resources[1].Id.Name, + listRsp.Resources[2].Id.Name, + } + require.Contains(t, names, "default") + require.Contains(t, names, "ns1") + require.Contains(t, names, "ns2") +} + +func validNamespace() *pbtenancy.Namespace { + return &pbtenancy.Namespace{ + Description: "ns namespace", + } +}