* Fix #5602: Ensure controller status update triggers update_time
Rework and fix the logic of #5602 query split. Initial implementation failed to update controller status within `server_controller` as `w.Update` did not trigger `update_time` if `address` or `description` were not updated. Now using `w.Exec` with `updateController` query to always set `update_time` to `now()`.
Additional Notes:
- Updated `repository_controller` tests to verify `update_time` updates even when controller values don't change.
- Added `cleanupFunc` to test cases to remove inserted controllers after each test.
- Verified via `boundary dev` that `worker.upstreamDialerFunc` errors are not present.
Refs: 6c0b3a8a1c
* Resolve Comments
122 lines
3.7 KiB
Go
122 lines
3.7 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
package server
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
)
|
|
|
|
func (r *Repository) ListControllers(ctx context.Context, opt ...Option) ([]*Controller, error) {
|
|
return r.listControllersWithReader(ctx, r.reader, opt...)
|
|
}
|
|
|
|
// listControllersWithReader will return a listing of resources and honor the
|
|
// WithLimit option or the repo defaultLimit. It accepts a reader, allowing it
|
|
// to be used within a transaction or without.
|
|
func (r *Repository) listControllersWithReader(ctx context.Context, reader db.Reader, opt ...Option) ([]*Controller, error) {
|
|
opts := GetOpts(opt...)
|
|
liveness := opts.withLiveness
|
|
if liveness == 0 {
|
|
liveness = DefaultLiveness
|
|
}
|
|
var where string
|
|
if liveness > 0 {
|
|
where = fmt.Sprintf("update_time > now() - interval '%d seconds'", uint32(liveness.Seconds()))
|
|
}
|
|
var controllers []*Controller
|
|
if err := reader.SearchWhere(
|
|
ctx,
|
|
&controllers,
|
|
where,
|
|
[]any{},
|
|
db.WithLimit(-1),
|
|
); err != nil {
|
|
return nil, errors.Wrap(ctx, err, "workers.listControllersWithReader")
|
|
}
|
|
return controllers, nil
|
|
}
|
|
|
|
func (r *Repository) UpsertController(ctx context.Context, controller *Controller) (int, error) {
|
|
const op = "server.(Repository).UpsertController"
|
|
if controller == nil {
|
|
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "controller is nil")
|
|
}
|
|
var rowsUpdated int64
|
|
_, err := r.writer.DoTx(
|
|
ctx,
|
|
db.StdRetryCnt,
|
|
db.ExpBackoff{},
|
|
func(read db.Reader, w db.Writer) error {
|
|
var err error
|
|
onConflict := &db.OnConflict{
|
|
Target: db.Columns{"private_id"},
|
|
Action: append(db.SetColumns([]string{"description", "address"}), db.SetColumnValues(map[string]any{"update_time": "now()"})...),
|
|
}
|
|
err = w.Create(ctx, controller, db.WithOnConflict(onConflict), db.WithReturnRowsAffected(&rowsUpdated))
|
|
if err != nil {
|
|
return errors.Wrap(ctx, err, op+":Upsert")
|
|
}
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return db.NoRowsAffected, err
|
|
}
|
|
|
|
return int(rowsUpdated), nil
|
|
}
|
|
|
|
// UpdateControllerStatus updates the controller's status in the repository.
|
|
// This includes updating the address or description of the controller as well
|
|
// as updating the update_time attribute, which is required for liveness checks
|
|
// as part of a controller's status ticking.
|
|
func (r *Repository) UpdateControllerStatus(ctx context.Context, controller *Controller) (int, error) {
|
|
const op = "server.(Repository).UpdateControllerStatus"
|
|
|
|
if controller == nil {
|
|
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "controller is nil")
|
|
}
|
|
if controller.PrivateId == "" {
|
|
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "controller private_id is empty")
|
|
}
|
|
if controller.Address == "" {
|
|
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "controller address is empty")
|
|
}
|
|
|
|
var rowsUpdated int
|
|
_, err := r.writer.DoTx(
|
|
ctx,
|
|
db.StdRetryCnt,
|
|
db.ExpBackoff{},
|
|
func(reader db.Reader, w db.Writer) error {
|
|
var err error
|
|
rowsUpdated, err = w.Exec(ctx, updateController,
|
|
[]any{
|
|
sql.Named("controller_address", controller.Address),
|
|
sql.Named("controller_description", controller.Description),
|
|
sql.Named("controller_private_id", controller.PrivateId),
|
|
})
|
|
switch {
|
|
case err != nil:
|
|
return errors.Wrap(ctx, err, op+":Update")
|
|
case rowsUpdated > 1:
|
|
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated")
|
|
case rowsUpdated == 0:
|
|
return errors.New(ctx, errors.RecordNotFound, op, "no resources would have been updated")
|
|
default:
|
|
return nil
|
|
}
|
|
},
|
|
)
|
|
if err != nil {
|
|
return db.NoRowsAffected, err
|
|
}
|
|
|
|
return rowsUpdated, nil
|
|
}
|