Files
threshold/internal/server/repository_controller.go
T
Ryan Derr 6d9f97d6c8 ICU-16457 Split Liveness Query In Two (#5628)
* 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
2025-03-26 14:54:02 -05:00

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
}