mirror of https://github.com/status-im/consul.git
Tenancy Bridge v2 (#19220)
* tenancy bridge v2 for v2 resources * add missing copywrite headers
This commit is contained in:
parent
b962d91056
commit
d5c9f11b59
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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",
|
|
@ -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",
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue