Add basic loopback test plugin (#1555)
This commit is contained in:
1 parent
8fb1898c3e
commit
e4e788553f
3 files changed
+317
No files matched your search
@@ -0,0 +1,116 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
const loopbackPluginHostInfoAttrField = "host_info"
|
||||
|
||||
var _ plgpb.HostPluginServiceServer = (*loopbackPlugin)(nil)
|
||||
|
||||
// loopbackPlugin provides a host plugin with functionality useful for certain
|
||||
// kinds of testing. It is not (currently) thread-safe.
|
||||
//
|
||||
// Over time, if useful, it can be enhanced to do things like handle multiple
|
||||
// hosts per set.
|
||||
type loopbackPlugin struct {
|
||||
*TestPluginServer
|
||||
|
||||
hostMap map[string]*loopbackPluginHostInfo
|
||||
}
|
||||
|
||||
type loopbackPluginHostInfo struct {
|
||||
ExternalId string `mapstructure:"external_id"`
|
||||
IpAddresses []string `mapstructure:"ip_addresses"`
|
||||
DnsNames []string `mapstructure:"dns_names"`
|
||||
}
|
||||
|
||||
// NewLoopbackPlugin returns a new loopback plugin
|
||||
func NewLoopbackPlugin() plgpb.HostPluginServiceServer {
|
||||
ret := &loopbackPlugin{
|
||||
TestPluginServer: new(TestPluginServer),
|
||||
hostMap: make(map[string]*loopbackPluginHostInfo),
|
||||
}
|
||||
ret.OnCreateCatalogFn = ret.onCreateCatalog
|
||||
ret.OnCreateSetFn = ret.onCreateSet
|
||||
ret.OnDeleteSetFn = ret.onDeleteSet
|
||||
ret.ListHostsFn = ret.listHosts
|
||||
return ret
|
||||
}
|
||||
|
||||
func (l *loopbackPlugin) onCreateCatalog(ctx context.Context, req *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) {
|
||||
const op = "plugin.(loopbackPlugin).onCreateCatalog"
|
||||
if req == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil")
|
||||
}
|
||||
if cat := req.GetCatalog(); cat != nil {
|
||||
if secrets := cat.GetSecrets(); secrets != nil {
|
||||
return &plgpb.OnCreateCatalogResponse{
|
||||
Persisted: &plgpb.HostCatalogPersisted{
|
||||
Secrets: secrets,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (l *loopbackPlugin) onCreateSet(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) {
|
||||
const op = "plugin.(loopbackPlugin).onCreateSet"
|
||||
if req == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil")
|
||||
}
|
||||
set := req.GetSet()
|
||||
if set == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "set is nil")
|
||||
}
|
||||
if attrs := set.GetAttributes(); attrs != nil {
|
||||
attrsMap := attrs.AsMap()
|
||||
if field := attrsMap[loopbackPluginHostInfoAttrField]; field != nil {
|
||||
hostInfo := new(loopbackPluginHostInfo)
|
||||
if err := mapstructure.Decode(field, hostInfo); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
l.hostMap[set.GetId()] = hostInfo
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (l *loopbackPlugin) onDeleteSet(ctx context.Context, req *plgpb.OnDeleteSetRequest) (*plgpb.OnDeleteSetResponse, error) {
|
||||
const op = "plugin.(loopbackPlugin).onDeleteSet"
|
||||
if req == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil")
|
||||
}
|
||||
set := req.GetSet()
|
||||
if set == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "set is nil")
|
||||
}
|
||||
delete(l.hostMap, set.GetId())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (l *loopbackPlugin) listHosts(ctx context.Context, req *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error) {
|
||||
const op = "plugin.(loopbackPlugin).listHosts"
|
||||
if req == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil")
|
||||
}
|
||||
resp := new(plgpb.ListHostsResponse)
|
||||
for _, set := range req.GetSets() {
|
||||
hostInfo := l.hostMap[set.GetId()]
|
||||
if hostInfo == nil {
|
||||
continue
|
||||
}
|
||||
resp.Hosts = append(resp.Hosts, &plgpb.ListHostsResponseHost{
|
||||
SetIds: []string{set.GetId()},
|
||||
ExternalId: hostInfo.ExternalId,
|
||||
IpAddresses: hostInfo.IpAddresses,
|
||||
DnsNames: hostInfo.DnsNames,
|
||||
})
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
|
||||
plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
ta "github.com/stretchr/testify/assert"
|
||||
tr "github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
// TestLoopbackPlugin is a quick test of basic functionality.
|
||||
func TestLoopbackPlugin(t *testing.T) {
|
||||
require, assert := tr.New(t), ta.New(t)
|
||||
ctx := context.Background()
|
||||
|
||||
plg := NewLoopbackPlugin()
|
||||
secretsMap := map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"baz": true,
|
||||
}
|
||||
secrets, err := structpb.NewStruct(secretsMap)
|
||||
require.NoError(err)
|
||||
|
||||
// First, test that if we give it secrets, those secrets come back as
|
||||
// persisted data
|
||||
catResp, err := plg.OnCreateCatalog(ctx, &plgpb.OnCreateCatalogRequest{
|
||||
Catalog: &hostcatalogs.HostCatalog{
|
||||
Secrets: secrets,
|
||||
},
|
||||
})
|
||||
require.NoError(err)
|
||||
require.NotNil(catResp)
|
||||
require.NotNil(catResp.GetPersisted())
|
||||
require.NotNil(catResp.GetPersisted().GetSecrets())
|
||||
assert.EqualValues(secretsMap, catResp.GetPersisted().GetSecrets().AsMap())
|
||||
|
||||
// Add data to some sets
|
||||
hostInfo1 := map[string]interface{}{
|
||||
loopbackPluginHostInfoAttrField: map[string]interface{}{
|
||||
"set_ids": []interface{}{"set1"},
|
||||
"external_id": "host1",
|
||||
"ip_addresses": []interface{}{"1.2.3.4", "2.3.4.5"},
|
||||
"dns_names": []interface{}{"foo.com"},
|
||||
},
|
||||
}
|
||||
attrs, err := structpb.NewStruct(hostInfo1)
|
||||
require.NoError(err)
|
||||
_, err = plg.OnCreateSet(ctx, &plgpb.OnCreateSetRequest{
|
||||
Set: &hostsets.HostSet{
|
||||
Id: "set1",
|
||||
Attributes: attrs,
|
||||
},
|
||||
})
|
||||
require.NoError(err)
|
||||
hostInfo2 := map[string]interface{}{
|
||||
loopbackPluginHostInfoAttrField: map[string]interface{}{
|
||||
"set_ids": []interface{}{"set2"},
|
||||
"external_id": "host2",
|
||||
"ip_addresses": []interface{}{"5.6.7.8", "6.7.8.9"},
|
||||
"dns_names": []interface{}{"bar.com"},
|
||||
},
|
||||
}
|
||||
attrs, err = structpb.NewStruct(hostInfo2)
|
||||
require.NoError(err)
|
||||
_, err = plg.OnCreateSet(ctx, &plgpb.OnCreateSetRequest{
|
||||
Set: &hostsets.HostSet{
|
||||
Id: "set2",
|
||||
Attributes: attrs,
|
||||
},
|
||||
})
|
||||
require.NoError(err)
|
||||
|
||||
// Define test struct and validation function
|
||||
type testInfo struct {
|
||||
name string
|
||||
sets []string
|
||||
found []map[string]interface{}
|
||||
}
|
||||
validateSets := func(t *testing.T, tt testInfo) {
|
||||
require, assert := tr.New(t), ta.New(t)
|
||||
var hostSets []*hostsets.HostSet
|
||||
for _, set := range tt.sets {
|
||||
hostSets = append(hostSets, &hostsets.HostSet{Id: set})
|
||||
}
|
||||
resp, err := plg.ListHosts(ctx, &plgpb.ListHostsRequest{
|
||||
Sets: hostSets,
|
||||
})
|
||||
require.NoError(err)
|
||||
if len(tt.found) == 0 {
|
||||
assert.Len(resp.GetHosts(), 0)
|
||||
return
|
||||
}
|
||||
|
||||
require.Greater(len(resp.GetHosts()), 0)
|
||||
|
||||
var found []map[string]interface{}
|
||||
for _, host := range resp.GetHosts() {
|
||||
hostMap := map[string]interface{}{
|
||||
"external_id": host.GetExternalId(),
|
||||
}
|
||||
var sets []interface{}
|
||||
for _, set := range host.SetIds {
|
||||
sets = append(sets, set)
|
||||
}
|
||||
var ips []interface{}
|
||||
for _, ip := range host.GetIpAddresses() {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
var names []interface{}
|
||||
for _, name := range host.GetDnsNames() {
|
||||
names = append(names, name)
|
||||
}
|
||||
if len(sets) > 0 {
|
||||
hostMap["set_ids"] = sets
|
||||
}
|
||||
if len(ips) > 0 {
|
||||
hostMap["ip_addresses"] = ips
|
||||
}
|
||||
if len(names) > 0 {
|
||||
hostMap["dns_names"] = names
|
||||
}
|
||||
found = append(found, hostMap)
|
||||
}
|
||||
assert.ElementsMatch(tt.found, found)
|
||||
}
|
||||
|
||||
// First set of tests: check that we can look up sets individually and
|
||||
// together
|
||||
setTests := []testInfo{
|
||||
{
|
||||
name: "set 1",
|
||||
sets: []string{"set1"},
|
||||
found: []map[string]interface{}{
|
||||
hostInfo1[loopbackPluginHostInfoAttrField].(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set 2",
|
||||
sets: []string{"set2"},
|
||||
found: []map[string]interface{}{
|
||||
hostInfo2[loopbackPluginHostInfoAttrField].(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sets 1 and 2",
|
||||
sets: []string{"set1", "set2"},
|
||||
found: []map[string]interface{}{
|
||||
hostInfo1[loopbackPluginHostInfoAttrField].(map[string]interface{}),
|
||||
hostInfo2[loopbackPluginHostInfoAttrField].(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range setTests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
validateSets(t, tt)
|
||||
})
|
||||
}
|
||||
|
||||
// Remove a set
|
||||
_, err = plg.OnDeleteSet(ctx, &plgpb.OnDeleteSetRequest{
|
||||
Set: &hostsets.HostSet{
|
||||
Id: "set1",
|
||||
},
|
||||
})
|
||||
require.NoError(err)
|
||||
|
||||
// Run tests again, making sure we no longer find that host either
|
||||
// individually or together
|
||||
setTests = []testInfo{
|
||||
{
|
||||
name: "set 1 deleted",
|
||||
sets: []string{"set1"},
|
||||
found: []map[string]interface{}{},
|
||||
},
|
||||
{
|
||||
name: "set 2 not deleted",
|
||||
sets: []string{"set2"},
|
||||
found: []map[string]interface{}{
|
||||
hostInfo2[loopbackPluginHostInfoAttrField].(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sets 1 and 2 set 1 deleted",
|
||||
sets: []string{"set1", "set2"},
|
||||
found: []map[string]interface{}{
|
||||
hostInfo2[loopbackPluginHostInfoAttrField].(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range setTests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
validateSets(t, tt)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,8 @@ func TestSet(t *testing.T, conn *gorm.DB, kmsCache *kms.Kms, hc *HostCatalog, pl
|
||||
return set
|
||||
}
|
||||
|
||||
var _ plgpb.HostPluginServiceServer = (*TestPluginServer)(nil)
|
||||
|
||||
// TestPluginServer provides a host plugin service server where each method can be overwritten for tests.
|
||||
type TestPluginServer struct {
|
||||
OnCreateCatalogFn func(context.Context, *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error)
|
||||
|
||||
Reference in new issue
Block a user