Files
threshold/internal/host/plugin/repository_host_set_test.go
T
ToddandJeff Mitchell bbfef2802f Placeholder Plugin Providers for Repos and added testPlugin struct (#1532)
* Create minimalistic plugin map and test plugin.

* Pass secrets and attributes to the plugin when creating catalogs or sets.  Add tests for CreateSet and CreateCatalog repo methods.

* make fmt and extract out getPrivateCatalog and getCatalog helper methods.

* Exporting TestPluginServer so it can be used in the handlers.

* Keep separate persisted data from host catalogs lookups which facilitate passing it onto the plugins.

* Add TODO to merge get Catalog and get Persisted data.

* Adding tests for unimplemented being returned by the plugin.

* Use GetFields() instead of AsMap() to confirm attributes should be populated. Make clone consistent between catalog and sets.

Co-authored-by: Jeff Mitchell <jeffrey.mitchell@gmail.com>
2021-09-21 09:18:49 -07:00

510 lines
14 KiB
Go

package plugin
import (
"context"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/host"
"github.com/hashicorp/boundary/internal/host/plugin/store"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
hostplg "github.com/hashicorp/boundary/internal/plugin/host"
plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/structpb"
)
func TestRepository_CreateSet(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
_, prj := iam.TestScopes(t, iamRepo)
plg := hostplg.TestPlugin(t, conn, "create", "create")
unimplementedPlugin := hostplg.TestPlugin(t, conn, "unimplemented", "unimplemented")
var pluginReceivedAttrs *structpb.Struct
plgm := map[string]plgpb.HostPluginServiceServer{
plg.GetPublicId(): &TestPluginServer{OnCreateSetFn: func(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) {
pluginReceivedAttrs = req.GetSet().GetAttributes()
return &plgpb.OnCreateSetResponse{}, nil
}},
unimplementedPlugin.GetPublicId(): &plgpb.UnimplementedHostPluginServiceServer{},
}
catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
unimplementedPluginCatalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
attrs := []byte{}
tests := []struct {
name string
in *HostSet
opts []Option
want *HostSet
wantIsErr errors.Code
}{
{
name: "nil-HostSet",
wantIsErr: errors.InvalidParameter,
},
{
name: "nil-embedded-HostSet",
in: &HostSet{},
wantIsErr: errors.InvalidParameter,
},
{
name: "invalid-no-catalog-id",
in: &HostSet{
HostSet: &store.HostSet{
Attributes: attrs,
},
},
wantIsErr: errors.InvalidParameter,
},
{
name: "invalid-public-id-set",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
PublicId: "abcd_OOOOOOOOOO",
Attributes: attrs,
},
},
wantIsErr: errors.InvalidParameter,
},
{
name: "invalid-no-attribte",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
},
},
wantIsErr: errors.InvalidParameter,
},
{
name: "valid-no-options",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Attributes: attrs,
},
},
want: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Attributes: attrs,
},
},
},
{
name: "valid-preferred-endpoints",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Attributes: attrs,
PreferredEndpoints: []string{"cidr:1.2.3.4/32", "dns:a.b.c"},
},
},
want: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Attributes: attrs,
PreferredEndpoints: []string{"cidr:1.2.3.4/32", "dns:a.b.c"},
},
},
},
{
name: "valid-with-name",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Name: "test-name-repo",
Attributes: attrs,
},
},
want: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Name: "test-name-repo",
Attributes: attrs,
},
},
},
{
name: "valid-unimplemented-plugin",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: unimplementedPluginCatalog.PublicId,
Name: "valid-unimplemented-plugin",
Attributes: attrs,
},
},
want: &HostSet{
HostSet: &store.HostSet{
CatalogId: unimplementedPluginCatalog.PublicId,
Name: "valid-unimplemented-plugin",
Attributes: attrs,
},
},
},
{
name: "valid-with-description",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Description: ("test-description-repo"),
Attributes: attrs,
},
},
want: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Description: ("test-description-repo"),
Attributes: attrs,
},
},
},
{
name: "valid-with-custom-attributes",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Description: ("test-description-repo"),
Attributes: func() []byte {
b, err := proto.Marshal(&structpb.Struct{Fields: map[string]*structpb.Value{"k1": structpb.NewStringValue("foo")}})
require.NoError(t, err)
return b
}(),
},
},
want: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Description: ("test-description-repo"),
Attributes: func() []byte {
b, err := proto.Marshal(&structpb.Struct{Fields: map[string]*structpb.Value{"k1": structpb.NewStringValue("foo")}})
require.NoError(t, err)
return b
}(),
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(rw, rw, kms, plgm)
require.NoError(err)
require.NotNil(repo)
got, err := repo.CreateSet(context.Background(), prj.GetPublicId(), tt.in, tt.opts...)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err)
assert.Nil(got)
return
}
require.NoError(err)
assert.Empty(tt.in.PublicId)
require.NotNil(got)
assert.True(strings.HasPrefix(got.GetPublicId(), HostSetPrefix))
assert.NotSame(tt.in, got)
assert.Equal(tt.want.Name, got.GetName())
assert.Equal(tt.want.Description, got.GetDescription())
assert.Equal(got.GetCreateTime(), got.GetUpdateTime())
wantedPluginAttributes := &structpb.Struct{}
require.NoError(proto.Unmarshal(tt.want.Attributes, wantedPluginAttributes))
assert.Empty(cmp.Diff(wantedPluginAttributes, pluginReceivedAttrs, protocmp.Transform()))
assert.NoError(db.TestVerifyOplog(t, rw, got.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)))
})
}
t.Run("invalid-duplicate-names", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(rw, rw, kms, plgm)
require.NoError(err)
require.NotNil(repo)
_, prj := iam.TestScopes(t, iamRepo)
catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
in := &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Name: "test-name-repo",
Attributes: []byte{},
},
}
got, err := repo.CreateSet(context.Background(), prj.GetPublicId(), in)
require.NoError(err)
require.NotNil(got)
assert.True(strings.HasPrefix(got.GetPublicId(), HostSetPrefix))
assert.NotSame(in, got)
assert.Equal(in.Name, got.GetName())
assert.Equal(in.Description, got.GetDescription())
assert.Equal(got.GetCreateTime(), got.GetUpdateTime())
got2, err := repo.CreateSet(context.Background(), prj.GetPublicId(), in)
assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err)
assert.Nil(got2)
})
t.Run("valid-duplicate-names-diff-catalogs", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(rw, rw, kms, plgm)
require.NoError(err)
require.NotNil(repo)
_, prj := iam.TestScopes(t, iamRepo)
catalogA := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
catalogB := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
in := &HostSet{
HostSet: &store.HostSet{
Name: "test-name-repo",
Attributes: []byte{},
},
}
in2 := in.clone()
in.CatalogId = catalogA.PublicId
got, err := repo.CreateSet(context.Background(), prj.GetPublicId(), in)
require.NoError(err)
require.NotNil(got)
assert.True(strings.HasPrefix(got.GetPublicId(), HostSetPrefix))
assert.NotSame(in, got)
assert.Equal(in.Name, got.GetName())
assert.Equal(in.Description, got.GetDescription())
assert.Equal(got.GetCreateTime(), got.GetUpdateTime())
in2.CatalogId = catalogB.PublicId
got2, err := repo.CreateSet(context.Background(), prj.GetPublicId(), in2)
require.NoError(err)
require.NotNil(got2)
assert.True(strings.HasPrefix(got.GetPublicId(), HostSetPrefix))
assert.NotSame(in2, got2)
assert.Equal(in2.Name, got2.GetName())
assert.Equal(in2.Description, got2.GetDescription())
assert.Equal(got2.GetCreateTime(), got2.GetUpdateTime())
})
}
func TestRepository_LookupSet(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
_, prj := iam.TestScopes(t, iamRepo)
plg := hostplg.TestPlugin(t, conn, "lookup", "lookup")
plgm := map[string]plgpb.HostPluginServiceServer{
plg.GetPublicId(): &TestPluginServer{},
}
catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
hostSet := TestSet(t, conn, kms, catalog, map[string]plgpb.HostPluginServiceServer{
plg.GetPublicId(): &TestPluginServer{},
})
hostSetId, err := newHostSetId(ctx, plg.GetIdPrefix())
require.NoError(t, err)
tests := []struct {
name string
in string
want *HostSet
wantIsErr errors.Code
}{
{
name: "with-no-public-id",
wantIsErr: errors.InvalidParameter,
},
{
name: "with-non-existing-host-set-id",
in: hostSetId,
},
{
name: "with-existing-host-set-id",
in: hostSet.PublicId,
want: hostSet,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(rw, rw, kms, plgm)
assert.NoError(err)
require.NotNil(repo)
got, err := repo.LookupSet(ctx, tt.in)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err)
assert.Nil(got)
return
}
require.NoError(err)
if tt.want != nil {
assert.Empty(cmp.Diff(got, tt.want, protocmp.Transform()), "LookupSet(%q) got response %q, wanted %q", tt.in, got, tt.want)
}
})
}
}
func TestRepository_ListSets(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
_, prj := iam.TestScopes(t, iamRepo)
plg := hostplg.TestPlugin(t, conn, "list", "list")
plgm := map[string]plgpb.HostPluginServiceServer{
plg.GetPublicId(): &TestPluginServer{},
}
catalogA := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
catalogB := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
hostSets := []*HostSet{
TestSet(t, conn, kms, catalogA, plgm),
TestSet(t, conn, kms, catalogA, plgm),
TestSet(t, conn, kms, catalogA, plgm),
}
tests := []struct {
name string
in string
opts []host.Option
want []*HostSet
wantIsErr errors.Code
}{
{
name: "with-no-catalog-id",
wantIsErr: errors.InvalidParameter,
},
{
name: "Catalog-with-no-host-sets",
in: catalogB.PublicId,
want: nil,
},
{
name: "Catalog-with-host-sets",
in: catalogA.PublicId,
want: hostSets,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(rw, rw, kms, plgm)
assert.NoError(err)
require.NotNil(repo)
got, err := repo.ListSets(context.Background(), tt.in, tt.opts...)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err)
assert.Nil(got)
return
}
require.NoError(err)
opts := []cmp.Option{
cmpopts.SortSlices(func(x, y *HostSet) bool { return x.PublicId < y.PublicId }),
protocmp.Transform(),
}
assert.Empty(cmp.Diff(tt.want, got, opts...))
})
}
}
func TestRepository_ListSets_Limits(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
_, prj := iam.TestScopes(t, iamRepo)
plg := hostplg.TestPlugin(t, conn, "listlimit", "listlimit")
plgm := map[string]plgpb.HostPluginServiceServer{
plg.GetPublicId(): &TestPluginServer{},
}
catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
count := 10
var hostSets []*HostSet
for i := 0; i < count; i++ {
hostSets = append(hostSets, TestSet(t, conn, kms, catalog, plgm))
}
tests := []struct {
name string
repoOpts []host.Option
listOpts []host.Option
wantLen int
}{
{
name: "With no limits",
wantLen: count,
},
{
name: "With repo limit",
repoOpts: []host.Option{host.WithLimit(3)},
wantLen: 3,
},
{
name: "With negative repo limit",
repoOpts: []host.Option{host.WithLimit(-1)},
wantLen: count,
},
{
name: "With List limit",
listOpts: []host.Option{host.WithLimit(3)},
wantLen: 3,
},
{
name: "With negative List limit",
listOpts: []host.Option{host.WithLimit(-1)},
wantLen: count,
},
{
name: "With repo smaller than list limit",
repoOpts: []host.Option{host.WithLimit(2)},
listOpts: []host.Option{host.WithLimit(6)},
wantLen: 6,
},
{
name: "With repo larger than list limit",
repoOpts: []host.Option{host.WithLimit(6)},
listOpts: []host.Option{host.WithLimit(2)},
wantLen: 2,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(rw, rw, kms, plgm, tt.repoOpts...)
assert.NoError(err)
require.NotNil(repo)
got, err := repo.ListSets(context.Background(), hostSets[0].CatalogId, tt.listOpts...)
require.NoError(err)
assert.Len(got, tt.wantLen)
})
}
}