Add worker update to server repo (#2117)

This commit is contained in:
Todd authored and GitHub committed 2022-06-01 14:00:24 -07:00
1 parent 731a45eba6
commit 587c6372c2
2 files changed
+351

No files matched your search

+86
View File
@@ -3,8 +3,10 @@ package servers
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
dbcommon "github.com/hashicorp/boundary/internal/db/common"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/servers/store"
"github.com/hashicorp/boundary/internal/types/scope"
@@ -284,6 +286,90 @@ func setWorkerTags(ctx context.Context, w db.Writer, id string, ts TagSource, ta
return nil
}
// UpdateWorker will update a worker in the repository and return the resulting
// worker. fieldMaskPaths provides field_mask.proto paths for fields that should
// be updated. Fields will be set to NULL if the field is a zero value and
// included in fieldMask. Name, Description, and Address are the only updatable
// fields, if no updatable fields are included in the fieldMaskPaths, then an
// error is returned. If any paths besides those listed above are included in
// the path then an error is returned.
func (r *Repository) UpdateWorker(ctx context.Context, worker *Worker, version uint32, fieldMaskPaths []string, opt ...Option) (*Worker, int, error) {
const (
nameField = "name"
descField = "description"
addressField = "address"
)
const op = "servers.(Repository).UpdateWorker"
switch {
case worker == nil:
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "worker is nil")
case worker.PublicId == "":
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing public id")
case version == 0:
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "version is zero")
}
for _, f := range fieldMaskPaths {
switch {
case strings.EqualFold(nameField, f):
case strings.EqualFold(descField, f):
case strings.EqualFold(addressField, f):
default:
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f))
}
}
var dbMask, nullFields []string
dbMask, nullFields = dbcommon.BuildUpdatePaths(
map[string]interface{}{
nameField: worker.Name,
descField: worker.Description,
addressField: worker.Address,
},
fieldMaskPaths,
nil,
)
if len(dbMask) == 0 && len(nullFields) == 0 {
return nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "no fields to update")
}
var rowsUpdated int
var ret *Worker
var err error
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
worker := worker.clone()
rowsUpdated, err = w.Update(ctx, worker, dbMask, nullFields, db.WithVersion(&version))
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsUpdated > 1 {
// return err, which will result in a rollback of the update
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated")
}
wAgg := &workerAggregate{PublicId: worker.GetPublicId()}
if err := reader.LookupById(ctx, wAgg); err != nil {
return errors.Wrap(ctx, err, op)
}
if ret, err = wAgg.toWorker(ctx); err != nil {
return err
}
return nil
},
)
if err != nil {
if errors.IsUniqueError(err) {
return nil, db.NoRowsAffected, errors.New(ctx, errors.NotUnique, op, fmt.Sprintf("worker with name %q already exists", worker.Name))
}
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("for %s", worker.GetPublicId())))
}
return ret, rowsUpdated, nil
}
// CreateWorker will create a worker in the repository and return the written
// worker. Creating a worker is not intentionally oplogged. A worker's
// ReportedStatus and Tags are intentionally ignored when creating a worker (not
+265
View File
@@ -614,3 +614,268 @@ func TestRepository_CreateWorker(t *testing.T) {
})
}
}
func TestRepository_UpdateWorker(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, wrapper)
repo, err := servers.NewRepository(rw, rw, kmsCache)
require.NoError(t, err)
setCases := []struct {
name string
modifyWorker func(*testing.T, *servers.Worker)
path []string
assertGot func(*testing.T, *servers.Worker)
wantErr bool
}{
{
name: "update address",
modifyWorker: func(t *testing.T, w *servers.Worker) {
t.Helper()
w.Address = "foo"
},
path: []string{"address"},
assertGot: func(t *testing.T, w *servers.Worker) {
t.Helper()
assert.Equal(t, "foo", w.GetAddress())
assert.Equal(t, uint32(2), w.GetVersion())
assert.Equal(t, w.GetLastStatusTime().AsTime(), w.GetCreateTime().AsTime())
assert.Greater(t, w.GetUpdateTime().AsTime(), w.GetCreateTime().AsTime())
},
},
{
name: "update name",
modifyWorker: func(t *testing.T, w *servers.Worker) {
t.Helper()
w.Name = "foo"
},
path: []string{"Name"},
assertGot: func(t *testing.T, w *servers.Worker) {
t.Helper()
assert.Equal(t, "foo", w.GetName())
assert.Equal(t, uint32(2), w.GetVersion())
assert.Equal(t, w.GetLastStatusTime().AsTime(), w.GetCreateTime().AsTime())
assert.Greater(t, w.GetUpdateTime().AsTime(), w.GetCreateTime().AsTime())
},
},
{
name: "update description",
modifyWorker: func(t *testing.T, w *servers.Worker) {
t.Helper()
w.Description = "foo"
},
path: []string{"Description"},
assertGot: func(t *testing.T, w *servers.Worker) {
t.Helper()
assert.Equal(t, "foo", w.GetDescription())
assert.Equal(t, uint32(2), w.GetVersion())
assert.Equal(t, w.GetLastStatusTime().AsTime(), w.GetCreateTime().AsTime())
assert.Greater(t, w.GetUpdateTime().AsTime(), w.GetCreateTime().AsTime())
},
},
{
name: "update worker reported name",
modifyWorker: func(t *testing.T, w *servers.Worker) {
t.Helper()
w.WorkerReportedName = "foo"
},
path: []string{"WorkerReportedName"},
wantErr: true,
},
{
name: "update worker reported name and name",
modifyWorker: func(t *testing.T, w *servers.Worker) {
t.Helper()
w.WorkerReportedName = "foo"
w.Name = "another"
},
path: []string{"WorkerReportedName", "Name"},
wantErr: true,
},
{
name: "Clear worker reported name",
modifyWorker: func(t *testing.T, w *servers.Worker) {},
path: []string{"WorkerReportedName"},
wantErr: true,
},
}
for _, tt := range setCases {
t.Run(tt.name, func(t *testing.T) {
wkr := servers.TestWorker(t, conn, wrapper)
tt.modifyWorker(t, wkr)
got, _, err := repo.UpdateWorker(ctx, wkr, 1, tt.path)
if tt.wantErr {
assert.Error(t, err)
return
} else {
require.NoError(t, err)
}
tt.assertGot(t, got)
})
}
// Clear cases
clearCases := []struct {
name string
modifyWorker func(*testing.T, *servers.Worker)
path []string
assertGot func(*testing.T, *servers.Worker)
wantErr bool
}{
{
name: "clear address",
modifyWorker: func(t *testing.T, w *servers.Worker) {
t.Helper()
w.Address = ""
},
path: []string{"address"},
assertGot: func(t *testing.T, w *servers.Worker) {
t.Helper()
assert.Empty(t, w.GetAddress())
assert.Equal(t, w.GetLastStatusTime().AsTime(), w.GetCreateTime().AsTime())
assert.Greater(t, w.GetUpdateTime().AsTime(), w.GetCreateTime().AsTime())
},
},
{
name: "clear name",
modifyWorker: func(t *testing.T, w *servers.Worker) {
t.Helper()
w.Name = ""
},
path: []string{"Name"},
assertGot: func(t *testing.T, w *servers.Worker) {
t.Helper()
assert.Empty(t, w.GetName())
assert.Equal(t, w.GetLastStatusTime().AsTime(), w.GetCreateTime().AsTime())
assert.Greater(t, w.GetUpdateTime().AsTime(), w.GetCreateTime().AsTime())
},
},
{
name: "clear description",
modifyWorker: func(t *testing.T, w *servers.Worker) {
t.Helper()
w.Description = ""
},
path: []string{"Description"},
assertGot: func(t *testing.T, w *servers.Worker) {
t.Helper()
assert.Empty(t, w.GetDescription())
assert.Equal(t, w.GetLastStatusTime().AsTime(), w.GetCreateTime().AsTime())
assert.Greater(t, w.GetUpdateTime().AsTime(), w.GetCreateTime().AsTime())
},
},
}
for _, tt := range clearCases {
t.Run(tt.name, func(t *testing.T) {
wkr := servers.TestWorker(t, conn, wrapper)
wkr.Name = tt.name
wkr.Description = tt.name
wkr.Address = tt.name
wkr, _, err := repo.UpdateWorker(ctx, wkr, 1, []string{"name", "description", "address"})
require.NoError(t, err)
tt.modifyWorker(t, wkr)
got, _, err := repo.UpdateWorker(ctx, wkr, 2, tt.path)
if tt.wantErr {
assert.Error(t, err)
return
} else {
require.NoError(t, err)
}
tt.assertGot(t, got)
})
}
t.Run("version is wrong", func(t *testing.T) {
wkr := servers.TestWorker(t, conn, wrapper)
wkr.Address = "version is wrong"
result, numUpdated, err := repo.UpdateWorker(ctx, wkr, 2, []string{"address"})
require.NoError(t, err)
assert.Zero(t, numUpdated)
assert.Equal(t, wkr.GetUpdateTime().AsTime(), result.GetUpdateTime().AsTime())
})
errorCases := []struct {
name string
input *servers.Worker
path []string
version uint32
wantErr *errors.Template
}{
{
name: "nil worker",
path: []string{"name"},
version: 1,
wantErr: errors.T(errors.InvalidParameter),
},
{
name: "empty path",
input: servers.TestWorker(t, conn, wrapper),
version: 1,
wantErr: errors.T(errors.EmptyFieldMask),
},
{
name: "0 version",
input: servers.TestWorker(t, conn, wrapper),
path: []string{"name"},
version: 0,
wantErr: errors.T(errors.InvalidParameter),
},
{
name: "no public id",
input: func() *servers.Worker {
w := servers.TestWorker(t, conn, wrapper)
w.PublicId = ""
return w
}(),
path: []string{"name"},
version: 0,
wantErr: errors.T(errors.InvalidParameter),
},
{
name: "unrecognized path",
input: servers.TestWorker(t, conn, wrapper),
path: []string{"UnrecognizedField"},
version: 1,
wantErr: errors.T(errors.InvalidFieldMask),
},
{
name: "not found worker",
input: func() *servers.Worker {
w := servers.TestWorker(t, conn, wrapper)
w.PublicId = "w_notfoundworker"
return w
}(),
path: []string{"name"},
version: 1,
wantErr: errors.T(errors.RecordNotFound),
},
{
name: "duplicate name",
input: func() *servers.Worker {
w1 := servers.TestWorker(t, conn, wrapper)
w1.Name = "somenamethatijustmadeup"
w1, _, err := repo.UpdateWorker(ctx, w1, w1.Version, []string{"name"}, nil)
require.NoError(t, err)
w2 := servers.TestWorker(t, conn, wrapper)
w2.Name = w1.Name
return w2
}(),
path: []string{"name"},
version: 1,
wantErr: errors.T("worker with name \"somenamethatijustmadeup\" already exists"),
},
}
for _, tt := range errorCases {
t.Run(tt.name, func(t *testing.T) {
_, updated, err := repo.UpdateWorker(ctx, tt.input, tt.version, tt.path)
assert.Equal(t, 0, updated)
assert.Truef(t, errors.Match(tt.wantErr, err), "Didn't match error %v", err)
})
}
}