You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/host/static/repository_host_set_member_...

726 lines
20 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package static
import (
"context"
"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/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
)
func TestRepository_AddSetMembers_Parameters(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)
c := TestCatalogs(t, conn, prj.PublicId, 1)[0]
set := TestSets(t, conn, c.PublicId, 1)[0]
hosts := TestHosts(t, conn, c.PublicId, 5)
var hostIds []string
for _, h := range hosts {
hostIds = append(hostIds, h.PublicId)
}
badVersion := uint32(12345)
type args struct {
projectId string
setId string
version uint32
hostIds []string
opt []Option
}
tests := []struct {
name string
args args
want []*Host
wantErr bool
wantIsErr errors.Code
}{
{
name: "empty-project-id",
args: args{
setId: set.PublicId,
version: set.Version,
hostIds: hostIds,
},
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-set-id",
args: args{
projectId: prj.PublicId,
version: set.Version,
hostIds: hostIds,
},
wantIsErr: errors.InvalidParameter,
},
{
name: "zero-version",
args: args{
projectId: prj.PublicId,
setId: set.PublicId,
hostIds: hostIds,
},
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-host-ids",
args: args{
projectId: prj.PublicId,
setId: set.PublicId,
version: set.Version,
},
wantIsErr: errors.InvalidParameter,
},
{
name: "invalid-version",
args: args{
projectId: prj.PublicId,
setId: set.PublicId,
version: badVersion,
hostIds: hostIds,
},
wantErr: true,
},
{
name: "valid",
args: args{
projectId: prj.PublicId,
setId: set.PublicId,
version: set.Version,
hostIds: hostIds,
},
want: hosts,
},
}
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(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
got, err := repo.AddSetMembers(ctx, tt.args.projectId, tt.args.setId, tt.args.version, tt.args.hostIds, tt.args.opt...)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err)
assert.Nil(got)
assert.Error(db.TestVerifyOplog(t, rw, tt.args.setId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)))
return
}
if tt.wantErr {
assert.Error(err)
assert.Nil(got)
assert.Error(db.TestVerifyOplog(t, rw, tt.args.setId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)))
return
}
require.NoError(err)
opts := []cmp.Option{
cmpopts.SortSlices(func(x, y *Host) bool { return x.PublicId < y.PublicId }),
protocmp.Transform(),
}
assert.Len(got, len(tt.want))
assert.Empty(cmp.Diff(tt.want, got, opts...))
assert.NoError(db.TestVerifyOplog(t, rw, tt.args.setId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)))
})
}
}
func TestRepository_AddSetMembers_Combinations(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)
assert, require := assert.New(t), require.New(t)
c := TestCatalogs(t, conn, prj.PublicId, 1)[0]
set := TestSets(t, conn, c.PublicId, 1)[0]
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
hosts := TestHosts(t, conn, c.PublicId, 5)
var hostIds []string
for _, h := range hosts {
hostIds = append(hostIds, h.PublicId)
}
// first call - add first set of hosts - should succeed
got, err := repo.AddSetMembers(context.Background(), prj.PublicId, set.PublicId, set.Version, hostIds)
require.NoError(err)
require.NotNil(got)
opts := []cmp.Option{
cmpopts.SortSlices(func(x, y *Host) bool { return x.PublicId < y.PublicId }),
protocmp.Transform(),
}
assert.Len(got, len(hosts))
assert.Empty(cmp.Diff(hosts, got, opts...))
assert.NoError(db.TestVerifyOplog(t, rw, set.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)))
// second call - add new set of hosts - should succeed
set.Version = set.Version + 1
Hosts := TestHosts(t, conn, c.PublicId, 5)
var hostIds2 []string
for _, h := range Hosts {
hostIds2 = append(hostIds2, h.PublicId)
}
got2, err2 := repo.AddSetMembers(ctx, prj.PublicId, set.PublicId, set.Version, hostIds2)
require.NoError(err2)
require.NotNil(got2)
hosts = append(hosts, Hosts...)
assert.Len(got2, len(hosts))
assert.Empty(cmp.Diff(hosts, got2, opts...))
assert.NoError(db.TestVerifyOplog(t, rw, set.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)))
// third call - add new hosts plus a few existing hosts - should fail
set.Version = set.Version + 1
hosts3 := TestHosts(t, conn, c.PublicId, 5)
var hostIds3 []string
for _, h := range hosts3 {
hostIds3 = append(hostIds2, h.PublicId)
}
hostIds3 = append(hostIds3, hostIds2...)
got3, err3 := repo.AddSetMembers(ctx, prj.PublicId, set.PublicId, set.Version, hostIds3)
require.Error(err3)
require.Nil(got3)
}
func TestRepository_DeleteSetMembers_Parameters(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)
c := TestCatalogs(t, conn, prj.PublicId, 1)[0]
set := TestSets(t, conn, c.PublicId, 1)[0]
count := 5
hosts := TestHosts(t, conn, c.PublicId, count)
TestSetMembers(t, conn, set.PublicId, hosts)
var hostIds []string
for _, h := range hosts {
hostIds = append(hostIds, h.PublicId)
}
badVersion := uint32(12345)
type args struct {
projectId string
setId string
version uint32
hostIds []string
opt []Option
}
tests := []struct {
name string
args args
want int
wantErr bool
wantIsErr errors.Code
}{
{
name: "empty-project-id",
args: args{
setId: set.PublicId,
version: set.Version,
hostIds: hostIds,
},
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-set-id",
args: args{
projectId: prj.PublicId,
version: set.Version,
hostIds: hostIds,
},
wantIsErr: errors.InvalidParameter,
},
{
name: "zero-version",
args: args{
projectId: prj.PublicId,
setId: set.PublicId,
hostIds: hostIds,
},
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-host-ids",
args: args{
projectId: prj.PublicId,
setId: set.PublicId,
version: set.Version,
},
wantIsErr: errors.InvalidParameter,
},
{
name: "invalid-version",
args: args{
projectId: prj.PublicId,
setId: set.PublicId,
version: badVersion,
hostIds: hostIds,
},
wantErr: true,
},
{
name: "valid",
args: args{
projectId: prj.PublicId,
setId: set.PublicId,
version: set.Version,
hostIds: hostIds,
},
want: count,
wantErr: false,
},
}
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(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
got, err := repo.DeleteSetMembers(ctx, tt.args.projectId, tt.args.setId, tt.args.version, tt.args.hostIds, tt.args.opt...)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err)
assert.Zero(got)
assert.Error(db.TestVerifyOplog(t, rw, tt.args.setId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)))
return
}
if tt.wantErr {
assert.Error(err)
assert.Zero(got)
assert.Error(db.TestVerifyOplog(t, rw, tt.args.setId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)))
return
}
assert.NoError(err)
assert.Equal(tt.want, got)
assert.NoError(db.TestVerifyOplog(t, rw, tt.args.setId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)))
})
}
}
func TestRepository_DeleteSetMembers_Combinations(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)
assert, require := assert.New(t), require.New(t)
c := TestCatalogs(t, conn, prj.PublicId, 1)[0]
set := TestSets(t, conn, c.PublicId, 1)[0]
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
count := 10
hosts := TestHosts(t, conn, c.PublicId, count)
TestSetMembers(t, conn, set.PublicId, hosts)
var hostIds []string
for _, h := range hosts {
hostIds = append(hostIds, h.PublicId)
}
split := 5
idsA := hostIds[:split]
hostsB, idsB := hosts[split:], hostIds[split:]
// first call - delete first half of hosts - should succeed
got, err := repo.DeleteSetMembers(ctx, prj.PublicId, set.PublicId, set.Version, idsA)
assert.NoError(err)
require.Equal(len(idsA), got)
assert.NoError(db.TestVerifyOplog(t, rw, set.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)))
// verify hostsB are still members
members, err := getHosts(ctx, repo.reader, set.PublicId, unlimited)
require.NoError(err)
opts := []cmp.Option{
cmpopts.SortSlices(func(x, y *Host) bool { return x.PublicId < y.PublicId }),
protocmp.Transform(),
}
assert.Len(members, len(hostsB))
assert.Empty(cmp.Diff(hostsB, members, opts...))
// second call - delete first half of hosts again - should fail
set.Version = set.Version + 1
got2, err2 := repo.DeleteSetMembers(ctx, prj.PublicId, set.PublicId, set.Version, idsA)
require.Error(err2)
assert.Zero(got2)
// third call - delete first half and second half - should fail
got3, err3 := repo.DeleteSetMembers(ctx, prj.PublicId, set.PublicId, set.Version, hostIds)
require.Error(err3)
assert.Zero(got3)
// fourth call - delete second half of hosts - should succeed
got4, err4 := repo.DeleteSetMembers(ctx, prj.PublicId, set.PublicId, set.Version, idsB)
assert.NoError(err4)
require.Equal(len(idsB), got4)
assert.NoError(db.TestVerifyOplog(t, rw, set.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)))
// verify no members remain
Members, err := getHosts(ctx, repo.reader, set.PublicId, unlimited)
require.NoError(err)
require.Empty(Members)
}
func TestRepository_SetSetMembers_Parameters(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)
c := TestCatalogs(t, conn, prj.PublicId, 1)[0]
set := TestSets(t, conn, c.PublicId, 1)[0]
count := 5
hosts := TestHosts(t, conn, c.PublicId, count)
// hostsA has the first 3 hosts, hostsB has the last 3 hosts
// the middle host is shared in both.
hostsA, hostsB := hosts[:3], hosts[2:]
// hostsA is the initial set of hosts in the host set
TestSetMembers(t, conn, set.PublicId, hostsA)
var hostIds []string
for _, h := range hostsB {
hostIds = append(hostIds, h.PublicId)
}
badVersion := uint32(12345)
type args struct {
projectId string
setId string
version uint32
hostIds []string
opt []Option
}
tests := []struct {
name string
args args
want []*Host
wantCount int
wantErr bool
wantIsErr errors.Code
}{
{
name: "empty-project-id",
args: args{
setId: set.PublicId,
version: set.Version,
hostIds: hostIds,
},
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-set-id",
args: args{
projectId: prj.PublicId,
version: set.Version,
hostIds: hostIds,
},
wantIsErr: errors.InvalidParameter,
},
{
name: "zero-version",
args: args{
projectId: prj.PublicId,
setId: set.PublicId,
hostIds: hostIds,
},
wantIsErr: errors.InvalidParameter,
},
{
name: "invalid-version",
args: args{
projectId: prj.PublicId,
setId: set.PublicId,
version: badVersion,
hostIds: hostIds,
},
wantErr: true,
},
{
name: "valid",
args: args{
projectId: prj.PublicId,
setId: set.PublicId,
version: set.Version,
hostIds: hostIds,
},
want: hostsB,
wantCount: 4, // 2 deleted, 2 added
},
}
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(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
got, gotCount, err := repo.SetSetMembers(ctx, tt.args.projectId, tt.args.setId, tt.args.version, tt.args.hostIds, tt.args.opt...)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err)
assert.Nil(got)
assert.Equal(tt.wantCount, gotCount)
assert.Error(db.TestVerifyOplog(t, rw, tt.args.setId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
return
}
if tt.wantErr {
assert.Error(err)
assert.Nil(got)
assert.Equal(tt.wantCount, gotCount)
assert.Error(db.TestVerifyOplog(t, rw, tt.args.setId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
return
}
require.NoError(err)
opts := []cmp.Option{
cmpopts.SortSlices(func(x, y *Host) bool { return x.PublicId < y.PublicId }),
protocmp.Transform(),
}
assert.Equal(tt.wantCount, gotCount)
assert.Len(got, len(tt.want))
assert.Empty(cmp.Diff(tt.want, got, opts...))
assert.NoError(db.TestVerifyOplog(t, rw, tt.args.setId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
})
}
}
func TestRepository_SetSetMembers_Combinations(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)
assert, require := assert.New(t), require.New(t)
c := TestCatalogs(t, conn, prj.PublicId, 1)[0]
set := TestSets(t, conn, c.PublicId, 1)[0]
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
count := 5
hosts := TestHosts(t, conn, c.PublicId, count)
var hostIds []string
for _, h := range hosts {
hostIds = append(hostIds, h.PublicId)
}
// hostsA has the first 3 hosts, hostsB has the last 3 hosts
// the middle host is shared in both.
hostsA, hostsB := hosts[:3], hosts[2:]
hostIdsA, hostIdsB := hostIds[:3], hostIds[2:]
opts := []cmp.Option{
cmpopts.SortSlices(func(x, y *Host) bool { return x.PublicId < y.PublicId }),
protocmp.Transform(),
}
// first call - empty set, empty host Ids - no additions no deletions
got1, gotCount1, err1 := repo.SetSetMembers(ctx, prj.PublicId, set.PublicId, set.Version, nil)
assert.NoError(err1)
assert.Empty(got1)
assert.Zero(gotCount1)
assert.Error(db.TestVerifyOplog(t, rw, set.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
// second call - all additions
got2, gotCount2, err2 := repo.SetSetMembers(context.Background(), prj.PublicId, set.PublicId, set.Version, hostIdsA)
assert.NoError(err2)
assert.Equal(len(hostsA), gotCount2)
assert.NoError(db.TestVerifyOplog(t, rw, set.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
assert.Len(got2, len(hostsA))
assert.Empty(cmp.Diff(hostsA, got2, opts...))
// third call - mix of additions and deletions
set.Version = set.Version + 1
got3, gotCount3, err3 := repo.SetSetMembers(ctx, prj.PublicId, set.PublicId, set.Version, hostIdsB)
assert.NoError(err3)
assert.Equal(4, gotCount3)
assert.NoError(db.TestVerifyOplog(t, rw, set.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
assert.Len(got3, len(hostsB))
assert.Empty(cmp.Diff(hostsB, got3, opts...))
// fourth call - all deletions
set.Version = set.Version + 1
got4, gotCount4, err4 := repo.SetSetMembers(ctx, prj.PublicId, set.PublicId, set.Version, nil)
assert.NoError(err4)
assert.Equal(len(hostsB), gotCount4)
assert.NoError(db.TestVerifyOplog(t, rw, set.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
assert.Empty(got4)
}
func TestRepository_changes(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)
t.Run("all-additions", func(t *testing.T) {
c := TestCatalogs(t, conn, prj.PublicId, 1)[0]
set := TestSets(t, conn, c.PublicId, 1)[0]
count := 5
hosts := TestHosts(t, conn, c.PublicId, count)
var hostIds []string
for _, h := range hosts {
hostIds = append(hostIds, h.PublicId)
}
var want []*change
for _, h := range hosts {
chg := &change{
Action: "add",
HostId: h.PublicId,
}
want = append(want, chg)
}
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
got, err := repo.changes(ctx, set.PublicId, hostIds)
assert.NoError(err)
require.NotNil(got)
opts := []cmp.Option{
cmpopts.SortSlices(func(x, y *change) bool { return x.HostId < y.HostId }),
}
assert.Len(got, len(want))
assert.Empty(cmp.Diff(want, got, opts...))
})
t.Run("all-deletions", func(t *testing.T) {
c := TestCatalogs(t, conn, prj.PublicId, 1)[0]
set := TestSets(t, conn, c.PublicId, 1)[0]
count := 5
hosts := TestHosts(t, conn, c.PublicId, count)
TestSetMembers(t, conn, set.PublicId, hosts)
var want []*change
for _, h := range hosts {
chg := &change{
Action: "delete",
HostId: h.PublicId,
}
want = append(want, chg)
}
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
got, err := repo.changes(ctx, set.PublicId, nil)
assert.NoError(err)
require.NotNil(got)
opts := []cmp.Option{
cmpopts.SortSlices(func(x, y *change) bool { return x.HostId < y.HostId }),
}
assert.Len(got, len(want))
assert.Empty(cmp.Diff(want, got, opts...))
})
t.Run("additions-with-deletions", func(t *testing.T) {
c := TestCatalogs(t, conn, prj.PublicId, 1)[0]
set := TestSets(t, conn, c.PublicId, 1)[0]
count := 5
hosts := TestHosts(t, conn, c.PublicId, count)
initialHosts := hosts[:3]
TestSetMembers(t, conn, set.PublicId, initialHosts)
targetHosts := hosts[2:]
deleteHosts := hosts[:2]
insertHosts := hosts[3:]
var targetHostIds []string
for _, h := range targetHosts {
targetHostIds = append(targetHostIds, h.PublicId)
}
var want []*change
for _, h := range deleteHosts {
chg := &change{
Action: "delete",
HostId: h.PublicId,
}
want = append(want, chg)
}
for _, h := range insertHosts {
chg := &change{
Action: "add",
HostId: h.PublicId,
}
want = append(want, chg)
}
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
got, err := repo.changes(ctx, set.PublicId, targetHostIds)
assert.NoError(err)
require.NotNil(got)
opts := []cmp.Option{
cmpopts.SortSlices(func(x, y *change) bool { return x.HostId < y.HostId }),
}
assert.Len(got, len(want))
assert.Empty(cmp.Diff(want, got, opts...))
})
}