Add worker update to server repo (#2117)
This commit is contained in:
2 files changed
+351
No files matched your search
@@ -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
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user