mirror of
https://github.com/status-im/consul.git
synced 2025-02-12 21:56:46 +00:00
The peer name will eventually show up elsewhere in the resource. For now though this rips it out of where we don’t want it to be.
291 lines
8.7 KiB
Go
291 lines
8.7 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package resource_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/types/known/anypb"
|
|
|
|
"github.com/hashicorp/go-uuid"
|
|
|
|
"github.com/hashicorp/consul/acl"
|
|
"github.com/hashicorp/consul/acl/resolver"
|
|
svc "github.com/hashicorp/consul/agent/grpc-external/services/resource"
|
|
"github.com/hashicorp/consul/agent/grpc-external/testutils"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/consul/internal/resource"
|
|
"github.com/hashicorp/consul/internal/resource/demo"
|
|
"github.com/hashicorp/consul/internal/storage"
|
|
"github.com/hashicorp/consul/internal/storage/inmem"
|
|
"github.com/hashicorp/consul/proto-public/pbresource"
|
|
pbdemov2 "github.com/hashicorp/consul/proto/private/pbdemo/v2"
|
|
"github.com/hashicorp/consul/sdk/testutil"
|
|
)
|
|
|
|
func randomACLIdentity(t *testing.T) structs.ACLIdentity {
|
|
id, err := uuid.GenerateUUID()
|
|
require.NoError(t, err)
|
|
|
|
return &structs.ACLToken{AccessorID: id}
|
|
}
|
|
|
|
func AuthorizerFrom(t *testing.T, policyStrs ...string) resolver.Result {
|
|
policies := []*acl.Policy{}
|
|
for _, policyStr := range policyStrs {
|
|
policy, err := acl.NewPolicyFromSource(policyStr, nil, nil)
|
|
require.NoError(t, err)
|
|
policies = append(policies, policy)
|
|
}
|
|
|
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), policies, nil)
|
|
require.NoError(t, err)
|
|
|
|
return resolver.Result{
|
|
Authorizer: authz,
|
|
ACLIdentity: randomACLIdentity(t),
|
|
}
|
|
}
|
|
|
|
// Deprecated: use NewResourceServiceBuilder instead
|
|
func testServer(t *testing.T) *svc.Server {
|
|
t.Helper()
|
|
|
|
backend, err := inmem.NewBackend()
|
|
require.NoError(t, err)
|
|
go backend.Run(testContext(t))
|
|
|
|
// Mock the ACL Resolver to "allow all" for testing.
|
|
mockACLResolver := &svc.MockACLResolver{}
|
|
mockACLResolver.On("ResolveTokenAndDefaultMeta", mock.Anything, mock.Anything, mock.Anything).
|
|
Return(testutils.ACLsDisabled(t), nil).
|
|
Run(func(args mock.Arguments) {
|
|
// Caller expecting passed in tokenEntMeta and authorizerContext to be filled in.
|
|
tokenEntMeta := args.Get(1).(*acl.EnterpriseMeta)
|
|
if tokenEntMeta != nil {
|
|
fillEntMeta(tokenEntMeta)
|
|
}
|
|
|
|
authzContext := args.Get(2).(*acl.AuthorizerContext)
|
|
if authzContext != nil {
|
|
fillAuthorizerContext(authzContext)
|
|
}
|
|
})
|
|
|
|
// Mock the tenancy bridge since we can't use the real thing.
|
|
mockTenancyBridge := &svc.MockTenancyBridge{}
|
|
mockTenancyBridge.On("PartitionExists", resource.DefaultPartitionName).Return(true, nil)
|
|
mockTenancyBridge.On("NamespaceExists", resource.DefaultPartitionName, resource.DefaultNamespaceName).Return(true, nil)
|
|
mockTenancyBridge.On("PartitionExists", mock.Anything).Return(false, nil)
|
|
mockTenancyBridge.On("NamespaceExists", mock.Anything, mock.Anything).Return(false, nil)
|
|
mockTenancyBridge.On("IsPartitionMarkedForDeletion", resource.DefaultPartitionName).Return(false, nil)
|
|
mockTenancyBridge.On("IsNamespaceMarkedForDeletion", resource.DefaultPartitionName, resource.DefaultNamespaceName).Return(false, nil)
|
|
|
|
return svc.NewServer(svc.Config{
|
|
Logger: testutil.Logger(t),
|
|
Registry: resource.NewRegistry(),
|
|
Backend: backend,
|
|
ACLResolver: mockACLResolver,
|
|
TenancyBridge: mockTenancyBridge,
|
|
})
|
|
}
|
|
|
|
// Deprecated: use NewResourceServiceBuilder instead
|
|
func testClient(t *testing.T, server *svc.Server) pbresource.ResourceServiceClient {
|
|
t.Helper()
|
|
|
|
addr := testutils.RunTestServer(t, server)
|
|
|
|
//nolint:staticcheck
|
|
conn, err := grpc.DialContext(context.Background(), addr.String(), grpc.WithInsecure())
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
require.NoError(t, conn.Close())
|
|
})
|
|
|
|
return pbresource.NewResourceServiceClient(conn)
|
|
}
|
|
|
|
func testContext(t *testing.T) context.Context {
|
|
t.Helper()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
return ctx
|
|
}
|
|
|
|
func modifyArtist(t *testing.T, res *pbresource.Resource) *pbresource.Resource {
|
|
t.Helper()
|
|
|
|
var artist pbdemov2.Artist
|
|
require.NoError(t, res.Data.UnmarshalTo(&artist))
|
|
artist.Name = fmt.Sprintf("The artist formerly known as %s", artist.Name)
|
|
|
|
data, err := anypb.New(&artist)
|
|
require.NoError(t, err)
|
|
|
|
res = clone(res)
|
|
res.Data = data
|
|
return res
|
|
}
|
|
|
|
// wildcardTenancyCases returns permutations of tenancy and type scope used as input
|
|
// to endpoints that accept wildcards for tenancy.
|
|
func wildcardTenancyCases() map[string]struct {
|
|
typ *pbresource.Type
|
|
tenancy *pbresource.Tenancy
|
|
} {
|
|
return map[string]struct {
|
|
typ *pbresource.Type
|
|
tenancy *pbresource.Tenancy
|
|
}{
|
|
"namespaced type with empty partition": {
|
|
typ: demo.TypeV2Artist,
|
|
tenancy: &pbresource.Tenancy{
|
|
Partition: "",
|
|
Namespace: resource.DefaultNamespaceName,
|
|
},
|
|
},
|
|
"namespaced type with empty namespace": {
|
|
typ: demo.TypeV2Artist,
|
|
tenancy: &pbresource.Tenancy{
|
|
Partition: resource.DefaultPartitionName,
|
|
Namespace: "",
|
|
},
|
|
},
|
|
"namespaced type with empty partition and namespace": {
|
|
typ: demo.TypeV2Artist,
|
|
tenancy: &pbresource.Tenancy{
|
|
Partition: "",
|
|
Namespace: "",
|
|
},
|
|
},
|
|
"namespaced type with wildcard partition and empty namespace": {
|
|
typ: demo.TypeV2Artist,
|
|
tenancy: &pbresource.Tenancy{
|
|
Partition: "*",
|
|
Namespace: "",
|
|
},
|
|
},
|
|
"namespaced type with empty partition and wildcard namespace": {
|
|
typ: demo.TypeV2Artist,
|
|
tenancy: &pbresource.Tenancy{
|
|
Partition: "",
|
|
Namespace: "*",
|
|
},
|
|
},
|
|
"partitioned type with empty partition": {
|
|
typ: demo.TypeV1RecordLabel,
|
|
tenancy: &pbresource.Tenancy{
|
|
Partition: "",
|
|
Namespace: "",
|
|
},
|
|
},
|
|
"partitioned type with wildcard partition": {
|
|
typ: demo.TypeV1RecordLabel,
|
|
tenancy: &pbresource.Tenancy{
|
|
Partition: "*",
|
|
},
|
|
},
|
|
"partitioned type with wildcard partition and namespace": {
|
|
typ: demo.TypeV1RecordLabel,
|
|
tenancy: &pbresource.Tenancy{
|
|
Partition: "*",
|
|
Namespace: "*",
|
|
},
|
|
},
|
|
"cluster type with empty partition and namespace": {
|
|
typ: demo.TypeV1Executive,
|
|
tenancy: &pbresource.Tenancy{
|
|
Partition: "",
|
|
Namespace: "",
|
|
},
|
|
},
|
|
|
|
"cluster type with wildcard partition and namespace": {
|
|
typ: demo.TypeV1Executive,
|
|
tenancy: &pbresource.Tenancy{
|
|
Partition: "*",
|
|
Namespace: "*",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// tenancyCases returns permutations of valid tenancy structs in a resource id to use as inputs.
|
|
// - the id is for a recordLabel when the resource is partition scoped
|
|
// - the id is for an artist when the resource is namespace scoped
|
|
func tenancyCases() map[string]func(artistId, recordlabelId *pbresource.ID) *pbresource.ID {
|
|
tenancyCases := map[string]func(artistId, recordlabelId *pbresource.ID) *pbresource.ID{
|
|
"namespaced resource provides nonempty partition and namespace": func(artistId, recordLabelId *pbresource.ID) *pbresource.ID {
|
|
return artistId
|
|
},
|
|
"namespaced resource inherits tokens partition when empty": func(artistId, _ *pbresource.ID) *pbresource.ID {
|
|
id := clone(artistId)
|
|
id.Tenancy.Partition = ""
|
|
return id
|
|
},
|
|
"namespaced resource inherits tokens namespace when empty": func(artistId, _ *pbresource.ID) *pbresource.ID {
|
|
id := clone(artistId)
|
|
id.Tenancy.Namespace = ""
|
|
return id
|
|
},
|
|
"namespaced resource inherits tokens partition and namespace when empty": func(artistId, _ *pbresource.ID) *pbresource.ID {
|
|
id := clone(artistId)
|
|
id.Tenancy.Partition = ""
|
|
id.Tenancy.Namespace = ""
|
|
return id
|
|
},
|
|
"namespaced resource inherits tokens partition and namespace when tenacy nil": func(artistId, _ *pbresource.ID) *pbresource.ID {
|
|
id := clone(artistId)
|
|
id.Tenancy = nil
|
|
return id
|
|
},
|
|
"partitioned resource provides nonempty partition": func(_, recordLabelId *pbresource.ID) *pbresource.ID {
|
|
return recordLabelId
|
|
},
|
|
"partitioned resource inherits tokens partition when empty": func(_, recordLabelId *pbresource.ID) *pbresource.ID {
|
|
id := clone(recordLabelId)
|
|
id.Tenancy.Partition = ""
|
|
return id
|
|
},
|
|
"partitioned resource inherits tokens partition when tenancy nil": func(_, recordLabelId *pbresource.ID) *pbresource.ID {
|
|
id := clone(recordLabelId)
|
|
id.Tenancy = nil
|
|
return id
|
|
},
|
|
}
|
|
return tenancyCases
|
|
}
|
|
|
|
type blockOnceBackend struct {
|
|
storage.Backend
|
|
|
|
done uint32
|
|
readCompletedCh chan struct{}
|
|
blockCh chan struct{}
|
|
}
|
|
|
|
func (b *blockOnceBackend) Read(ctx context.Context, consistency storage.ReadConsistency, id *pbresource.ID) (*pbresource.Resource, error) {
|
|
res, err := b.Backend.Read(ctx, consistency, id)
|
|
|
|
// Block for exactly one call to Read. All subsequent calls (including those
|
|
// concurrent to the blocked call) will return immediately.
|
|
if atomic.CompareAndSwapUint32(&b.done, 0, 1) {
|
|
close(b.readCompletedCh)
|
|
<-b.blockCh
|
|
}
|
|
|
|
return res, err
|
|
}
|
|
|
|
func clone[T proto.Message](v T) T { return proto.Clone(v).(T) }
|