Files
threshold/internal/servers/repository.go
T
s-christoffandJim 6b78108ecf Update primary error functions to take a context, deprecate old functions (#1358)
* add new error funcs which take a ctx and deprecate existing funcs

* use context where available when writing error events

* remove circular internal/errors pkg dep

* suppress some chatty errors using errors.WithoutEvent()

* convert auth oidc funcs to take context where needed to write events

Co-authored-by: Jim <jlambert@hashicorp.com>
2021-07-29 20:52:56 -04:00

250 lines
7.5 KiB
Go

package servers
import (
"context"
"fmt"
"time"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/types/resource"
)
const (
// DefaultLiveness is the setting that controls the server "liveness" time,
// or the maximum allowable time that a worker can't send a status update to
// the controller for. After this, the server is considered dead, and it will
// be taken out of the rotation for allowable workers for connections, and
// connections will possibly start to be terminated and marked as closed
// depending on the grace period setting (see
// base.Server.StatusGracePeriodDuration). This value serves as the default
// and minimum allowable setting for the grace period.
DefaultLiveness = 15 * time.Second
)
type ServerType string
const (
ServerTypeController ServerType = "controller"
ServerTypeWorker ServerType = "worker"
)
func (s ServerType) String() string {
return string(s)
}
// Repository is the servers database repository
type Repository struct {
reader db.Reader
writer db.Writer
kms *kms.Kms
}
// NewRepository creates a new servers Repository. Supports the options: WithLimit
// which sets a default limit on results returned by repo operations.
func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms) (*Repository, error) {
const op = "servers.NewRepository"
if r == nil {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "nil reader")
}
if w == nil {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "nil writer")
}
if kms == nil {
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "nil kms")
}
return &Repository{
reader: r,
writer: w,
kms: kms,
}, nil
}
// ListServers is a passthrough to listServersWithReader that uses the repo's
// normal reader.
func (r *Repository) ListServers(ctx context.Context, serverType ServerType, opt ...Option) ([]*Server, error) {
return r.listServersWithReader(ctx, r.reader, serverType, opt...)
}
// listServersWithReader 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) listServersWithReader(ctx context.Context, reader db.Reader, serverType ServerType, opt ...Option) ([]*Server, error) {
opts := getOpts(opt...)
liveness := opts.withLiveness
if liveness == 0 {
liveness = DefaultLiveness
}
var where string
if liveness > 0 {
where = fmt.Sprintf("type = $1 and update_time > now() - interval '%d seconds'", uint32(liveness.Seconds()))
} else {
where = "type = $1"
}
var servers []*Server
if err := reader.SearchWhere(
ctx,
&servers,
where,
[]interface{}{serverType},
db.WithLimit(-1),
); err != nil {
return nil, errors.Wrap(ctx, err, "servers.listServersWithReader")
}
return servers, nil
}
// ServerTag holds the information for the server_tag table for Gorm.
type ServerTag struct {
ServerId string
Key string
Value string
}
// TableName overrides the table name used by ServerTag to `server_tag`
func (ServerTag) TableName() string {
return "server_tag"
}
// ListTagsForServers pulls out tag tuples into ServerTag structs for the
// given server ID values.
func (r *Repository) ListTagsForServers(ctx context.Context, serverIds []string, opt ...Option) ([]*ServerTag, error) {
var serverTags []*ServerTag
if err := r.reader.SearchWhere(
ctx,
&serverTags,
"server_id in (?)",
[]interface{}{serverIds},
db.WithLimit(-1),
); err != nil {
return nil, errors.Wrap(ctx, err, "servers.ListTagsForServers", errors.WithMsg(fmt.Sprintf("server IDs %v", serverIds)))
}
return serverTags, nil
}
// UpsertServer adds or updates a server in the DB
func (r *Repository) UpsertServer(ctx context.Context, server *Server, opt ...Option) ([]*Server, int, error) {
const op = "servers.UpsertServer"
if server == nil {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "server is nil")
}
opts := getOpts(opt...)
var rowsUpdated int
var controllers []*Server
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(read db.Reader, w db.Writer) error {
var err error
rowsUpdated, err = w.Exec(ctx,
serverUpsertQuery,
[]interface{}{
server.PrivateId,
server.Type,
server.Description,
server.Address,
})
if err != nil {
return errors.Wrap(ctx, err, op+":Upsert")
}
// If it's a worker, fetch the current controllers to feed to them
if server.Type == resource.Worker.String() {
// Fetch current controllers to feed to the workers
controllers, err = r.listServersWithReader(ctx, read, ServerTypeController)
if err != nil {
return errors.Wrap(ctx, err, op+":ListServer")
}
}
// If we've been told to update tags, we need to clean out old
// ones and add new ones. Within the current transaction, simply
// delete all tags for the given worker, then add the new ones
// we've been sent.
if opts.withUpdateTags {
_, err = w.Delete(ctx, &ServerTag{}, db.WithWhere(deleteTagsSql, server.PrivateId))
if err != nil {
return errors.Wrap(ctx, err, op+":DeleteTags", errors.WithMsg(server.PrivateId))
}
// If tags were cleared out entirely, then we'll have nothing
// to do here, e.g., it will result in deletion of all tags.
// Otherwise, go through and stage each tuple for insertion
// below.
if len(server.Tags) > 0 {
tags := make([]interface{}, 0, len(server.Tags))
for k, v := range server.Tags {
if v == nil {
return errors.New(ctx, errors.InvalidParameter, op+":RangeTags", fmt.Sprintf("found nil tag value for worker %s and key %s", server.PrivateId, k))
}
for _, val := range v.Values {
tags = append(tags, ServerTag{
ServerId: server.PrivateId,
Key: k,
Value: val,
})
}
}
if err = w.CreateItems(ctx, tags); err != nil {
return errors.Wrap(ctx, err, op+":CreateTags", errors.WithMsg(server.PrivateId))
}
}
}
return nil
},
)
if err != nil {
return nil, db.NoRowsAffected, err
}
return controllers, rowsUpdated, nil
}
type RecoveryNonce struct {
Nonce string
}
// AddRecoveryNonce adds a nonce
func (r *Repository) AddRecoveryNonce(ctx context.Context, nonce string, opt ...Option) error {
const op = "servers.AddRecoveryNonce"
if nonce == "" {
return errors.New(ctx, errors.InvalidParameter, op, "empty nonce")
}
rn := &RecoveryNonce{Nonce: nonce}
if err := r.writer.Create(ctx, rn); err != nil {
return errors.Wrap(ctx, err, op+":Insertion")
}
return nil
}
// CleanupNonces removes nonces that no longer need to be stored
func (r *Repository) CleanupNonces(ctx context.Context, opt ...Option) (int, error) {
// If something was inserted before 3x the actual validity period, clean it out
endTime := time.Now().Add(-3 * globals.RecoveryTokenValidityPeriod)
rows, err := r.writer.Delete(ctx, &RecoveryNonce{}, db.WithWhere(deleteWhereCreateTimeSql, endTime))
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, "servers.CleanupNonces")
}
return rows, nil
}
// ListNonces lists nonces. Used only for tests at the moment.
func (r *Repository) ListNonces(ctx context.Context, opt ...Option) ([]*RecoveryNonce, error) {
var nonces []*RecoveryNonce
if err := r.reader.SearchWhere(ctx, &nonces, "", nil, db.WithLimit(-1)); err != nil {
return nil, errors.Wrap(ctx, err, "servers.ListNonces")
}
return nonces, nil
}