concourse/atc/db/worker.go

355 lines
8.0 KiB
Go

package db
import (
"database/sql"
"errors"
"fmt"
"strings"
"time"
sq "github.com/Masterminds/squirrel"
"github.com/concourse/concourse/atc"
"github.com/lib/pq"
uuid "github.com/nu7hatch/gouuid"
)
var (
ErrWorkerNotPresent = errors.New("worker not present in db")
ErrCannotPruneRunningWorker = errors.New("worker not stalled for pruning")
)
type ContainerOwnerDisappearedError struct {
owner ContainerOwner
}
func (e ContainerOwnerDisappearedError) Error() string {
return fmt.Sprintf("container owner %T disappeared", e.owner)
}
type WorkerState string
const (
WorkerStateRunning = WorkerState("running")
WorkerStateStalled = WorkerState("stalled")
WorkerStateLanding = WorkerState("landing")
WorkerStateLanded = WorkerState("landed")
WorkerStateRetiring = WorkerState("retiring")
)
//go:generate counterfeiter . Worker
type Worker interface {
Name() string
Version() *string
State() WorkerState
GardenAddr() *string
BaggageclaimURL() *string
CertsPath() *string
ResourceCerts() (*UsedWorkerResourceCerts, bool, error)
HTTPProxyURL() string
HTTPSProxyURL() string
NoProxy() string
ActiveContainers() int
ActiveVolumes() int
ResourceTypes() []atc.WorkerResourceType
Platform() string
Tags() []string
TeamID() int
TeamName() string
StartTime() int64
ExpiresAt() time.Time
Ephemeral() bool
Reload() (bool, error)
Land() error
Retire() error
Prune() error
Delete() error
FindContainer(owner ContainerOwner) (CreatingContainer, CreatedContainer, error)
CreateContainer(owner ContainerOwner, meta ContainerMetadata) (CreatingContainer, error)
}
type worker struct {
conn Conn
name string
version *string
state WorkerState
gardenAddr *string
baggageclaimURL *string
httpProxyURL string
httpsProxyURL string
noProxy string
activeContainers int
activeVolumes int
resourceTypes []atc.WorkerResourceType
platform string
tags []string
teamID int
teamName string
startTime int64
expiresAt time.Time
certsPath *string
ephemeral bool
}
func (worker *worker) Name() string { return worker.name }
func (worker *worker) Version() *string { return worker.version }
func (worker *worker) State() WorkerState { return worker.state }
func (worker *worker) GardenAddr() *string { return worker.gardenAddr }
func (worker *worker) CertsPath() *string { return worker.certsPath }
func (worker *worker) BaggageclaimURL() *string { return worker.baggageclaimURL }
func (worker *worker) HTTPProxyURL() string { return worker.httpProxyURL }
func (worker *worker) HTTPSProxyURL() string { return worker.httpsProxyURL }
func (worker *worker) NoProxy() string { return worker.noProxy }
func (worker *worker) ActiveContainers() int { return worker.activeContainers }
func (worker *worker) ActiveVolumes() int { return worker.activeVolumes }
func (worker *worker) ResourceTypes() []atc.WorkerResourceType { return worker.resourceTypes }
func (worker *worker) Platform() string { return worker.platform }
func (worker *worker) Tags() []string { return worker.tags }
func (worker *worker) TeamID() int { return worker.teamID }
func (worker *worker) TeamName() string { return worker.teamName }
func (worker *worker) Ephemeral() bool { return worker.ephemeral }
// TODO: normalize time values
func (worker *worker) StartTime() int64 { return worker.startTime }
func (worker *worker) ExpiresAt() time.Time { return worker.expiresAt }
func (worker *worker) Reload() (bool, error) {
row := workersQuery.Where(sq.Eq{"w.name": worker.name}).
RunWith(worker.conn).
QueryRow()
err := scanWorker(worker, row)
if err != nil {
if err == sql.ErrNoRows {
return false, nil
}
return false, err
}
return true, nil
}
func (worker *worker) Land() error {
cSQL, _, err := sq.Case("state").
When("'landed'::worker_state", "'landed'::worker_state").
Else("'landing'::worker_state").
ToSql()
if err != nil {
return err
}
result, err := psql.Update("workers").
Set("state", sq.Expr("("+cSQL+")")).
Where(sq.Eq{"name": worker.name}).
RunWith(worker.conn).
Exec()
if err != nil {
return err
}
count, err := result.RowsAffected()
if err != nil {
return err
}
if count == 0 {
return ErrWorkerNotPresent
}
return nil
}
func (worker *worker) Retire() error {
result, err := psql.Update("workers").
SetMap(map[string]interface{}{
"state": string(WorkerStateRetiring),
}).
Where(sq.Eq{"name": worker.name}).
RunWith(worker.conn).
Exec()
if err != nil {
return err
}
count, err := result.RowsAffected()
if err != nil {
return err
}
if count == 0 {
return ErrWorkerNotPresent
}
return nil
}
func (worker *worker) Prune() error {
rows, err := sq.Delete("workers").
Where(sq.Eq{
"name": worker.name,
}).
Where(sq.NotEq{
"state": string(WorkerStateRunning),
}).
PlaceholderFormat(sq.Dollar).
RunWith(worker.conn).
Exec()
if err != nil {
return err
}
affected, err := rows.RowsAffected()
if err != nil {
return err
}
if affected == 0 {
//check whether the worker exists in the database at all
var one int
err := psql.Select("1").From("workers").Where(sq.Eq{"name": worker.name}).
RunWith(worker.conn).
QueryRow().
Scan(&one)
if err != nil {
if err == sql.ErrNoRows {
return ErrWorkerNotPresent
}
return err
}
return ErrCannotPruneRunningWorker
}
return nil
}
func (worker *worker) Delete() error {
_, err := sq.Delete("workers").
Where(sq.Eq{
"name": worker.name,
}).
PlaceholderFormat(sq.Dollar).
RunWith(worker.conn).
Exec()
return err
}
func (worker *worker) ResourceCerts() (*UsedWorkerResourceCerts, bool, error) {
if worker.certsPath != nil {
wrc := &WorkerResourceCerts{
WorkerName: worker.name,
CertsPath: *worker.certsPath,
}
return wrc.Find(worker.conn)
}
return nil, false, nil
}
func (worker *worker) FindContainer(owner ContainerOwner) (CreatingContainer, CreatedContainer, error) {
ownerQuery, found, err := owner.Find(worker.conn)
if err != nil {
return nil, nil, err
}
if !found {
return nil, nil, nil
}
return worker.findContainer(sq.And{
sq.Eq{"worker_name": worker.name},
ownerQuery,
})
}
func (worker *worker) CreateContainer(owner ContainerOwner, meta ContainerMetadata) (CreatingContainer, error) {
handle, err := uuid.NewV4()
if err != nil {
return nil, err
}
var containerID int
cols := []interface{}{&containerID}
metadata := &ContainerMetadata{}
cols = append(cols, metadata.ScanTargets()...)
tx, err := worker.conn.Begin()
if err != nil {
return nil, err
}
defer Rollback(tx)
insMap := meta.SQLMap()
insMap["worker_name"] = worker.name
insMap["handle"] = handle.String()
ownerCols, err := owner.Create(tx, worker.name)
if err != nil {
return nil, err
}
for k, v := range ownerCols {
insMap[k] = v
}
err = psql.Insert("containers").
SetMap(insMap).
Suffix("RETURNING id, " + strings.Join(containerMetadataColumns, ", ")).
RunWith(tx).
QueryRow().
Scan(cols...)
if err != nil {
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == pqFKeyViolationErrCode {
return nil, ContainerOwnerDisappearedError{owner}
}
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
return newCreatingContainer(
containerID,
handle.String(),
worker.name,
*metadata,
worker.conn,
), nil
}
func (worker *worker) findContainer(whereClause sq.Sqlizer) (CreatingContainer, CreatedContainer, error) {
creating, created, destroying, _, err := scanContainer(
selectContainers().
Where(whereClause).
RunWith(worker.conn).
QueryRow(),
worker.conn,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil, nil
}
return nil, nil, err
}
if destroying != nil {
return nil, nil, nil
}
return creating, created, nil
}