Tenancy Bridge v2 (#19220)

* tenancy bridge v2 for v2 resources

* add missing copywrite headers
This commit is contained in:
Dhia Ayachi 2023-10-20 14:49:54 -04:00 committed by GitHub
parent b962d91056
commit d5c9f11b59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 267 additions and 109 deletions

View File

@ -8,6 +8,7 @@ import (
"crypto/x509" "crypto/x509"
"errors" "errors"
"fmt" "fmt"
"github.com/hashicorp/consul/internal/tenancy"
"io" "io"
"net" "net"
"os" "os"
@ -1456,7 +1457,8 @@ func (s *Server) setupExternalGRPC(config *Config, deps Deps, logger hclog.Logge
tenancyBridge := NewV1TenancyBridge(s) tenancyBridge := NewV1TenancyBridge(s)
if stringslice.Contains(deps.Experiments, V2TenancyExperimentName) { if stringslice.Contains(deps.Experiments, V2TenancyExperimentName) {
tenancyBridge = resource.NewV2TenancyBridge() tenancyBridgeV2 := tenancy.NewV2TenancyBridge()
tenancyBridge = tenancyBridgeV2.WithClient(s.insecureResourceServiceClient)
} }
s.resourceServiceServer = resourcegrpc.NewServer(resourcegrpc.Config{ s.resourceServiceServer = resourcegrpc.NewServer(resourcegrpc.Config{
@ -1477,8 +1479,9 @@ func (s *Server) setupInsecureResourceServiceClient(typeRegistry resource.Regist
} }
tenancyBridge := NewV1TenancyBridge(s) tenancyBridge := NewV1TenancyBridge(s)
tenancyBridgeV2 := tenancy.NewV2TenancyBridge()
if stringslice.Contains(deps.Experiments, V2TenancyExperimentName) { if stringslice.Contains(deps.Experiments, V2TenancyExperimentName) {
tenancyBridge = resource.NewV2TenancyBridge() tenancyBridge = tenancyBridgeV2
} }
server := resourcegrpc.NewServer(resourcegrpc.Config{ server := resourcegrpc.NewServer(resourcegrpc.Config{
Registry: typeRegistry, Registry: typeRegistry,
@ -1493,7 +1496,7 @@ func (s *Server) setupInsecureResourceServiceClient(typeRegistry resource.Regist
return err return err
} }
s.insecureResourceServiceClient = pbresource.NewResourceServiceClient(conn) s.insecureResourceServiceClient = pbresource.NewResourceServiceClient(conn)
tenancyBridgeV2.WithClient(s.insecureResourceServiceClient)
return nil return nil
} }

View File

@ -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) return nil, status.Errorf(codes.Internal, "failed list acl: %v", err)
} }
// Check v1 tenancy exists for the v2 resource. // Check tenancy exists for the v2 resource.
if err = v1TenancyExists(reg, s.TenancyBridge, req.Owner.Tenancy, codes.InvalidArgument); err != nil { if err = tenancyExists(reg, s.TenancyBridge, req.Owner.Tenancy, codes.InvalidArgument); err != nil {
return nil, err return nil, err
} }

View File

@ -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) return nil, status.Errorf(codes.Internal, "failed read acl: %v", err)
} }
// Check V1 tenancy exists for the V2 resource. // Check tenancy exists for the V2 resource.
if err = v1TenancyExists(reg, s.TenancyBridge, req.Id.Tenancy, codes.NotFound); err != nil { if err = tenancyExists(reg, s.TenancyBridge, req.Id.Tenancy, codes.NotFound); err != nil {
return nil, err return nil, err
} }

View File

@ -208,10 +208,10 @@ func validateWildcardTenancy(tenancy *pbresource.Tenancy, namePrefix string) err
return nil return nil
} }
// v1TenancyExists return an error with the passed in gRPC status code when tenancy partition or namespace do not exist. // tenancyExists 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 { func tenancyExists(reg *resource.Registration, tenancyBridge TenancyBridge, tenancy *pbresource.Tenancy, errCode codes.Code) error {
if reg.Scope == resource.ScopePartition || reg.Scope == resource.ScopeNamespace { if reg.Scope == resource.ScopePartition || reg.Scope == resource.ScopeNamespace {
exists, err := v1Bridge.PartitionExists(tenancy.Partition) exists, err := tenancyBridge.PartitionExists(tenancy.Partition)
switch { switch {
case err != nil: case err != nil:
return err return err
@ -221,7 +221,7 @@ func v1TenancyExists(reg *resource.Registration, v1Bridge TenancyBridge, tenancy
} }
if reg.Scope == resource.ScopeNamespace { if reg.Scope == resource.ScopeNamespace {
exists, err := v1Bridge.NamespaceExists(tenancy.Partition, tenancy.Namespace) exists, err := tenancyBridge.NamespaceExists(tenancy.Partition, tenancy.Namespace)
switch { switch {
case err != nil: case err != nil:
return err return err
@ -232,10 +232,10 @@ func v1TenancyExists(reg *resource.Registration, v1Bridge TenancyBridge, tenancy
return nil return nil
} }
// v1TenancyMarkedForDeletion returns a gRPC InvalidArgument when either partition or namespace is marked for deletion. // tenancyMarkedForDeletion returns a gRPC InvalidArgument when either partition or namespace is marked for deletion.
func v1TenancyMarkedForDeletion(reg *resource.Registration, v1Bridge TenancyBridge, tenancy *pbresource.Tenancy) error { func tenancyMarkedForDeletion(reg *resource.Registration, tenancyBridge TenancyBridge, tenancy *pbresource.Tenancy) error {
if reg.Scope == resource.ScopePartition || reg.Scope == resource.ScopeNamespace { if reg.Scope == resource.ScopePartition || reg.Scope == resource.ScopeNamespace {
marked, err := v1Bridge.IsPartitionMarkedForDeletion(tenancy.Partition) marked, err := tenancyBridge.IsPartitionMarkedForDeletion(tenancy.Partition)
switch { switch {
case err != nil: case err != nil:
return err return err
@ -245,7 +245,7 @@ func v1TenancyMarkedForDeletion(reg *resource.Registration, v1Bridge TenancyBrid
} }
if reg.Scope == resource.ScopeNamespace { if reg.Scope == resource.ScopeNamespace {
marked, err := v1Bridge.IsNamespaceMarkedForDeletion(tenancy.Partition, tenancy.Namespace) marked, err := tenancyBridge.IsNamespaceMarkedForDeletion(tenancy.Partition, tenancy.Namespace)
switch { switch {
case err != nil: case err != nil:
return err return err

View File

@ -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 := &MockTenancyBridge{}
mockTenancyBridge.On("PartitionExists", resource.DefaultPartitionName).Return(true, nil) mockTenancyBridge.On("PartitionExists", resource.DefaultPartitionName).Return(true, nil)
mockTenancyBridge.On("NamespaceExists", resource.DefaultPartitionName, resource.DefaultNamespaceName).Return(true, nil) mockTenancyBridge.On("NamespaceExists", resource.DefaultPartitionName, resource.DefaultNamespaceName).Return(true, nil)

View File

@ -5,14 +5,13 @@ package testing
import ( import (
"context" "context"
"testing" "github.com/hashicorp/consul/internal/tenancy"
"github.com/hashicorp/go-uuid"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"testing"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/acl/resolver" "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("IsPartitionMarkedForDeletion", "foo").Return(false, nil)
mockTenancyBridge.On("IsNamespaceMarkedForDeletion", resource.DefaultPartitionName, resource.DefaultNamespaceName).Return(false, nil) mockTenancyBridge.On("IsNamespaceMarkedForDeletion", resource.DefaultPartitionName, resource.DefaultNamespaceName).Return(false, nil)
config.TenancyBridge = mockTenancyBridge config.TenancyBridge = mockTenancyBridge
} else {
switch config.TenancyBridge.(type) {
case *tenancy.V2TenancyBridge:
err = initTenancy(ctx, backend)
require.NoError(t, err)
}
} }
if config.ACLResolver == nil { if config.ACLResolver == nil {
@ -140,6 +145,14 @@ func RunResourceServiceWithConfig(t *testing.T, config svc.Config, registerFns .
) )
require.NoError(t, err) require.NoError(t, err)
t.Cleanup(func() { _ = conn.Close() }) t.Cleanup(func() { _ = conn.Close() })
client := pbresource.NewResourceServiceClient(conn)
return pbresource.NewResourceServiceClient(conn) if config.TenancyBridge != nil {
switch config.TenancyBridge.(type) {
case *tenancy.V2TenancyBridge:
config.TenancyBridge.(*tenancy.V2TenancyBridge).WithClient(client)
}
}
return client
} }

View File

@ -6,7 +6,17 @@
package testing package testing
import ( import (
"context"
"errors"
"github.com/hashicorp/consul/acl" "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) { func FillEntMeta(entMeta *acl.EnterpriseMeta) {
@ -16,3 +26,38 @@ func FillEntMeta(entMeta *acl.EnterpriseMeta) {
func FillAuthorizerContext(authzContext *acl.AuthorizerContext) { func FillAuthorizerContext(authzContext *acl.AuthorizerContext) {
// nothing to to in CE. // 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
}

View File

@ -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) return nil, status.Errorf(codes.Internal, "failed write acl: %v", err)
} }
// Check V1 tenancy exists for the V2 resource // Check tenancy exists for the V2 resource
if err = v1TenancyExists(reg, s.TenancyBridge, req.Resource.Id.Tenancy, codes.InvalidArgument); err != nil { if err = tenancyExists(reg, s.TenancyBridge, req.Resource.Id.Tenancy, codes.InvalidArgument); err != nil {
return nil, err return nil, err
} }
// Check V1 tenancy not marked for deletion. // Check tenancy not marked for deletion.
if err = v1TenancyMarkedForDeletion(reg, s.TenancyBridge, req.Resource.Id.Tenancy); err != nil { if err = tenancyMarkedForDeletion(reg, s.TenancyBridge, req.Resource.Id.Tenancy); err != nil {
return nil, err return nil, err
} }

View File

@ -34,9 +34,9 @@ func (s *Server) WriteStatus(ctx context.Context, req *pbresource.WriteStatusReq
// Apply defaults when tenancy units empty. // Apply defaults when tenancy units empty.
v1EntMetaToV2Tenancy(reg, entMeta, req.Id.Tenancy) 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. // 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 return nil, err
} }

View File

@ -157,7 +157,7 @@ func TestResourceWriteHandler(t *testing.T) {
require.Equal(t, http.StatusForbidden, rsp.Result().StatusCode) require.Equal(t, http.StatusForbidden, rsp.Result().StatusCode)
}) })
var readRsp *pbresource.ReadResponse
t.Run("should write to the resource backend", func(t *testing.T) { t.Run("should write to the resource backend", func(t *testing.T) {
rsp := httptest.NewRecorder() rsp := httptest.NewRecorder()
req := httptest.NewRequest("PUT", "/demo/v2/artist/keith-urban?partition=default&peer_name=local&namespace=default", strings.NewReader(` 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["data"].(map[string]any)["name"])
require.Equal(t, "keith-urban", result["id"].(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{ Id: &pbresource.ID{
Type: demo.TypeV2Artist, Type: demo.TypeV2Artist,
Tenancy: resource.DefaultNamespacedTenancy(), 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) { t.Run("should update the record with version parameter", func(t *testing.T) {
rsp := httptest.NewRecorder() 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": { "metadata": {
"foo": "bar" "foo": "bar"

View File

@ -24,15 +24,6 @@ const (
DefaultPeerName = "local" 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. // Scope describes the tenancy scope of a resource.
type Scope int type Scope int

View File

@ -5,6 +5,7 @@ package tenancy
import ( import (
"github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/tenancy/internal/bridge"
"github.com/hashicorp/consul/internal/tenancy/internal/types" "github.com/hashicorp/consul/internal/tenancy/internal/types"
) )
@ -21,8 +22,16 @@ var (
NamespaceV2Beta1Type = types.NamespaceV2Beta1Type NamespaceV2Beta1Type = types.NamespaceV2Beta1Type
) )
type (
V2TenancyBridge = bridge.V2TenancyBridge
)
// RegisterTypes adds all resource types within the "tenancy" API group // RegisterTypes adds all resource types within the "tenancy" API group
// to the given type registry // to the given type registry
func RegisterTypes(r resource.Registry) { func RegisterTypes(r resource.Registry) {
types.Register(r) types.Register(r)
} }
func NewV2TenancyBridge() *V2TenancyBridge {
return bridge.NewV2TenancyBridge()
}

View File

@ -0,0 +1,54 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
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
}

View File

@ -3,7 +3,7 @@
//go:build !consulent //go:build !consulent
package resource package bridge
func (b *V2TenancyBridge) PartitionExists(partition string) (bool, error) { func (b *V2TenancyBridge) PartitionExists(partition string) (bool, error) {
if partition == "default" { if partition == "default" {
@ -15,14 +15,3 @@ func (b *V2TenancyBridge) PartitionExists(partition string) (bool, error) {
func (b *V2TenancyBridge) IsPartitionMarkedForDeletion(partition string) (bool, error) { func (b *V2TenancyBridge) IsPartitionMarkedForDeletion(partition string) (bool, error) {
return false, nil 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
}

View File

@ -4,24 +4,15 @@
package types package types
import ( import (
"context"
"errors" "errors"
"testing" "github.com/hashicorp/consul/internal/resource"
svctest "github.com/hashicorp/consul/agent/grpc-external/services/resource/testing"
rtest "github.com/hashicorp/consul/internal/resource/resourcetest"
pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1"
"github.com/hashicorp/consul/proto/private/prototest" "github.com/hashicorp/consul/proto-public/pbresource"
"google.golang.org/grpc/codes" pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1"
"google.golang.org/grpc/status"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/anypb"
"testing"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/proto-public/pbresource"
pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1"
) )
func createNamespaceResource(t *testing.T, data protoreflect.ProtoMessage) *pbresource.Resource { func createNamespaceResource(t *testing.T, data protoreflect.ProtoMessage) *pbresource.Resource {
@ -144,51 +135,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 { func validNamespace() *pbtenancy.Namespace {
return &pbtenancy.Namespace{ return &pbtenancy.Namespace{
Description: "ns namespace", Description: "ns namespace",

View File

@ -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",
}
}