2015-06-01 15:49:10 +00:00
|
|
|
package nomad
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
2017-01-11 21:18:36 +00:00
|
|
|
"reflect"
|
2017-02-05 20:03:11 +00:00
|
|
|
"sync"
|
2015-06-01 15:49:10 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/armon/go-metrics"
|
2017-02-08 04:31:23 +00:00
|
|
|
memdb "github.com/hashicorp/go-memdb"
|
2015-08-11 21:27:14 +00:00
|
|
|
"github.com/hashicorp/nomad/nomad/state"
|
2015-06-01 15:49:10 +00:00
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
2016-07-26 00:26:10 +00:00
|
|
|
"github.com/hashicorp/nomad/scheduler"
|
2015-06-01 15:49:10 +00:00
|
|
|
"github.com/hashicorp/raft"
|
2016-02-21 02:05:17 +00:00
|
|
|
"github.com/ugorji/go/codec"
|
2015-06-01 15:49:10 +00:00
|
|
|
)
|
|
|
|
|
2015-08-16 00:38:13 +00:00
|
|
|
const (
|
|
|
|
// timeTableGranularity is the granularity of index to time tracking
|
|
|
|
timeTableGranularity = 5 * time.Minute
|
|
|
|
|
|
|
|
// timeTableLimit is the maximum limit of our tracking
|
2015-09-07 18:01:29 +00:00
|
|
|
timeTableLimit = 72 * time.Hour
|
2015-08-16 00:38:13 +00:00
|
|
|
)
|
|
|
|
|
2015-07-06 21:51:01 +00:00
|
|
|
// SnapshotType is prefixed to a record in the FSM snapshot
|
|
|
|
// so that we can determine the type for restore
|
|
|
|
type SnapshotType byte
|
|
|
|
|
|
|
|
const (
|
|
|
|
NodeSnapshot SnapshotType = iota
|
2015-07-07 16:55:47 +00:00
|
|
|
JobSnapshot
|
2015-07-06 21:51:01 +00:00
|
|
|
IndexSnapshot
|
2015-07-23 22:52:38 +00:00
|
|
|
EvalSnapshot
|
2015-08-04 21:04:26 +00:00
|
|
|
AllocSnapshot
|
2015-08-16 00:38:13 +00:00
|
|
|
TimeTableSnapshot
|
2015-12-07 23:58:17 +00:00
|
|
|
PeriodicLaunchSnapshot
|
2016-07-05 18:50:44 +00:00
|
|
|
JobSummarySnapshot
|
2016-08-19 01:14:58 +00:00
|
|
|
VaultAccessorSnapshot
|
2017-04-24 21:49:23 +00:00
|
|
|
JobVersionSnapshot
|
|
|
|
DeploymentSnapshot
|
2015-07-06 21:51:01 +00:00
|
|
|
)
|
|
|
|
|
2015-06-01 15:49:10 +00:00
|
|
|
// nomadFSM implements a finite state machine that is used
|
|
|
|
// along with Raft to provide strong consistency. We implement
|
|
|
|
// this outside the Server to avoid exposing this outside the package.
|
|
|
|
type nomadFSM struct {
|
2015-12-19 01:26:05 +00:00
|
|
|
evalBroker *EvalBroker
|
2016-01-29 23:31:32 +00:00
|
|
|
blockedEvals *BlockedEvals
|
2015-12-19 01:26:05 +00:00
|
|
|
periodicDispatcher *PeriodicDispatch
|
|
|
|
logOutput io.Writer
|
|
|
|
logger *log.Logger
|
|
|
|
state *state.StateStore
|
|
|
|
timetable *TimeTable
|
2017-02-05 20:03:11 +00:00
|
|
|
|
|
|
|
// stateLock is only used to protect outside callers to State() from
|
|
|
|
// racing with Restore(), which is called by Raft (it puts in a totally
|
|
|
|
// new state store). Everything internal here is synchronized by the
|
|
|
|
// Raft side, so doesn't need to lock this.
|
|
|
|
stateLock sync.RWMutex
|
2015-06-01 15:49:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// nomadSnapshot is used to provide a snapshot of the current
|
|
|
|
// state in a way that can be accessed concurrently with operations
|
|
|
|
// that may modify the live state.
|
|
|
|
type nomadSnapshot struct {
|
2015-08-16 00:38:13 +00:00
|
|
|
snap *state.StateSnapshot
|
|
|
|
timetable *TimeTable
|
2015-07-06 20:01:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// snapshotHeader is the first entry in our snapshot
|
|
|
|
type snapshotHeader struct {
|
2015-06-01 15:49:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewFSMPath is used to construct a new FSM with a blank state
|
2016-01-29 23:31:32 +00:00
|
|
|
func NewFSM(evalBroker *EvalBroker, periodic *PeriodicDispatch,
|
|
|
|
blocked *BlockedEvals, logOutput io.Writer) (*nomadFSM, error) {
|
2015-06-01 15:49:10 +00:00
|
|
|
// Create a state store
|
2015-08-11 21:27:14 +00:00
|
|
|
state, err := state.NewStateStore(logOutput)
|
2015-06-01 15:49:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
fsm := &nomadFSM{
|
2015-12-19 01:26:05 +00:00
|
|
|
evalBroker: evalBroker,
|
|
|
|
periodicDispatcher: periodic,
|
2016-01-29 23:31:32 +00:00
|
|
|
blockedEvals: blocked,
|
2015-12-19 01:26:05 +00:00
|
|
|
logOutput: logOutput,
|
|
|
|
logger: log.New(logOutput, "", log.LstdFlags),
|
|
|
|
state: state,
|
|
|
|
timetable: NewTimeTable(timeTableGranularity, timeTableLimit),
|
2015-06-01 15:49:10 +00:00
|
|
|
}
|
|
|
|
return fsm, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close is used to cleanup resources associated with the FSM
|
|
|
|
func (n *nomadFSM) Close() error {
|
2015-07-03 21:46:30 +00:00
|
|
|
return nil
|
2015-06-01 15:49:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// State is used to return a handle to the current state
|
2015-08-11 21:27:14 +00:00
|
|
|
func (n *nomadFSM) State() *state.StateStore {
|
2017-02-05 20:03:11 +00:00
|
|
|
n.stateLock.RLock()
|
|
|
|
defer n.stateLock.RUnlock()
|
2015-06-01 15:49:10 +00:00
|
|
|
return n.state
|
|
|
|
}
|
|
|
|
|
2015-08-16 00:38:13 +00:00
|
|
|
// TimeTable returns the time table of transactions
|
|
|
|
func (n *nomadFSM) TimeTable() *TimeTable {
|
|
|
|
return n.timetable
|
|
|
|
}
|
|
|
|
|
2015-06-01 15:49:10 +00:00
|
|
|
func (n *nomadFSM) Apply(log *raft.Log) interface{} {
|
|
|
|
buf := log.Data
|
|
|
|
msgType := structs.MessageType(buf[0])
|
|
|
|
|
2015-08-16 00:38:13 +00:00
|
|
|
// Witness this write
|
|
|
|
n.timetable.Witness(log.Index, time.Now().UTC())
|
|
|
|
|
2015-06-01 15:49:10 +00:00
|
|
|
// Check if this message type should be ignored when unknown. This is
|
|
|
|
// used so that new commands can be added with developer control if older
|
|
|
|
// versions can safely ignore the command, or if they should crash.
|
|
|
|
ignoreUnknown := false
|
|
|
|
if msgType&structs.IgnoreUnknownTypeFlag == structs.IgnoreUnknownTypeFlag {
|
|
|
|
msgType &= ^structs.IgnoreUnknownTypeFlag
|
|
|
|
ignoreUnknown = true
|
|
|
|
}
|
|
|
|
|
|
|
|
switch msgType {
|
2015-07-07 16:51:42 +00:00
|
|
|
case structs.NodeRegisterRequestType:
|
2015-09-07 03:47:42 +00:00
|
|
|
return n.applyUpsertNode(buf[1:], log.Index)
|
2015-07-07 16:51:42 +00:00
|
|
|
case structs.NodeDeregisterRequestType:
|
|
|
|
return n.applyDeregisterNode(buf[1:], log.Index)
|
2015-07-04 01:41:36 +00:00
|
|
|
case structs.NodeUpdateStatusRequestType:
|
|
|
|
return n.applyStatusUpdate(buf[1:], log.Index)
|
2015-09-07 02:55:38 +00:00
|
|
|
case structs.NodeUpdateDrainRequestType:
|
|
|
|
return n.applyDrainUpdate(buf[1:], log.Index)
|
2015-07-07 16:51:42 +00:00
|
|
|
case structs.JobRegisterRequestType:
|
2015-09-07 03:47:42 +00:00
|
|
|
return n.applyUpsertJob(buf[1:], log.Index)
|
2015-07-07 16:51:42 +00:00
|
|
|
case structs.JobDeregisterRequestType:
|
|
|
|
return n.applyDeregisterJob(buf[1:], log.Index)
|
2015-07-23 22:52:38 +00:00
|
|
|
case structs.EvalUpdateRequestType:
|
|
|
|
return n.applyUpdateEval(buf[1:], log.Index)
|
|
|
|
case structs.EvalDeleteRequestType:
|
|
|
|
return n.applyDeleteEval(buf[1:], log.Index)
|
2015-08-04 21:04:26 +00:00
|
|
|
case structs.AllocUpdateRequestType:
|
|
|
|
return n.applyAllocUpdate(buf[1:], log.Index)
|
2015-08-26 01:00:14 +00:00
|
|
|
case structs.AllocClientUpdateRequestType:
|
|
|
|
return n.applyAllocClientUpdate(buf[1:], log.Index)
|
2016-08-03 23:08:30 +00:00
|
|
|
case structs.ReconcileJobSummariesRequestType:
|
|
|
|
return n.applyReconcileSummaries(buf[1:], log.Index)
|
2016-08-19 01:14:58 +00:00
|
|
|
case structs.VaultAccessorRegisterRequestType:
|
|
|
|
return n.applyUpsertVaultAccessor(buf[1:], log.Index)
|
2016-08-22 20:57:27 +00:00
|
|
|
case structs.VaultAccessorDegisterRequestType:
|
|
|
|
return n.applyDeregisterVaultAccessor(buf[1:], log.Index)
|
2017-05-05 20:52:01 +00:00
|
|
|
case structs.ApplyPlanResultsRequestType:
|
|
|
|
return n.applyPlanResults(buf[1:], log.Index)
|
2015-06-01 15:49:10 +00:00
|
|
|
default:
|
|
|
|
if ignoreUnknown {
|
|
|
|
n.logger.Printf("[WARN] nomad.fsm: ignoring unknown message type (%d), upgrade to newer version", msgType)
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
panic(fmt.Errorf("failed to apply request: %#v", buf))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-07-04 01:41:36 +00:00
|
|
|
|
2015-09-07 03:47:42 +00:00
|
|
|
func (n *nomadFSM) applyUpsertNode(buf []byte, index uint64) interface{} {
|
2015-07-07 16:51:42 +00:00
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "register_node"}, time.Now())
|
|
|
|
var req structs.NodeRegisterRequest
|
2015-07-04 01:41:36 +00:00
|
|
|
if err := structs.Decode(buf, &req); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode request: %v", err))
|
|
|
|
}
|
|
|
|
|
2015-09-07 03:47:42 +00:00
|
|
|
if err := n.state.UpsertNode(index, req.Node); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: UpsertNode failed: %v", err)
|
2015-07-04 01:41:36 +00:00
|
|
|
return err
|
|
|
|
}
|
2016-04-01 01:11:27 +00:00
|
|
|
|
|
|
|
// Unblock evals for the nodes computed node class if it is in a ready
|
|
|
|
// state.
|
|
|
|
if req.Node.Status == structs.NodeStatusReady {
|
2016-05-23 22:24:31 +00:00
|
|
|
n.blockedEvals.Unblock(req.Node.ComputedClass, index)
|
2016-04-01 01:11:27 +00:00
|
|
|
}
|
|
|
|
|
2015-07-04 01:41:36 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-07-07 16:51:42 +00:00
|
|
|
func (n *nomadFSM) applyDeregisterNode(buf []byte, index uint64) interface{} {
|
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "deregister_node"}, time.Now())
|
|
|
|
var req structs.NodeDeregisterRequest
|
2015-07-04 01:41:36 +00:00
|
|
|
if err := structs.Decode(buf, &req); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode request: %v", err))
|
|
|
|
}
|
|
|
|
|
2015-09-07 03:47:42 +00:00
|
|
|
if err := n.state.DeleteNode(index, req.NodeID); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: DeleteNode failed: %v", err)
|
2015-07-04 01:41:36 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *nomadFSM) applyStatusUpdate(buf []byte, index uint64) interface{} {
|
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "node_status_update"}, time.Now())
|
2015-07-07 16:51:42 +00:00
|
|
|
var req structs.NodeUpdateStatusRequest
|
2015-07-04 01:41:36 +00:00
|
|
|
if err := structs.Decode(buf, &req); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode request: %v", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := n.state.UpdateNodeStatus(index, req.NodeID, req.Status); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: UpdateNodeStatus failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
2016-01-29 23:31:32 +00:00
|
|
|
|
|
|
|
// Unblock evals for the nodes computed node class if it is in a ready
|
|
|
|
// state.
|
|
|
|
if req.Status == structs.NodeStatusReady {
|
2017-02-08 04:31:23 +00:00
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
node, err := n.state.NodeByID(ws, req.NodeID)
|
2016-01-29 23:31:32 +00:00
|
|
|
if err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: looking up node %q failed: %v", req.NodeID, err)
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
2016-05-23 22:24:31 +00:00
|
|
|
n.blockedEvals.Unblock(node.ComputedClass, index)
|
2016-01-29 23:31:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-04 01:41:36 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-09-07 02:55:38 +00:00
|
|
|
func (n *nomadFSM) applyDrainUpdate(buf []byte, index uint64) interface{} {
|
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "node_drain_update"}, time.Now())
|
|
|
|
var req structs.NodeUpdateDrainRequest
|
|
|
|
if err := structs.Decode(buf, &req); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode request: %v", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := n.state.UpdateNodeDrain(index, req.NodeID, req.Drain); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: UpdateNodeDrain failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-09-07 03:47:42 +00:00
|
|
|
func (n *nomadFSM) applyUpsertJob(buf []byte, index uint64) interface{} {
|
2015-07-07 16:51:42 +00:00
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "register_job"}, time.Now())
|
|
|
|
var req structs.JobRegisterRequest
|
|
|
|
if err := structs.Decode(buf, &req); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode request: %v", err))
|
|
|
|
}
|
|
|
|
|
2016-11-08 23:24:51 +00:00
|
|
|
// COMPAT: Remove in 0.6
|
2016-07-18 23:17:38 +00:00
|
|
|
// Empty maps and slices should be treated as nil to avoid
|
|
|
|
// un-intended destructive updates in scheduler since we use
|
|
|
|
// reflect.DeepEqual. Starting Nomad 0.4.1, job submission sanatizes
|
|
|
|
// the incoming job.
|
2016-07-20 23:07:15 +00:00
|
|
|
req.Job.Canonicalize()
|
2016-07-18 23:17:38 +00:00
|
|
|
|
2015-09-07 03:47:42 +00:00
|
|
|
if err := n.state.UpsertJob(index, req.Job); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: UpsertJob failed: %v", err)
|
2015-07-07 16:51:42 +00:00
|
|
|
return err
|
|
|
|
}
|
2015-12-01 22:54:57 +00:00
|
|
|
|
2015-12-21 21:25:50 +00:00
|
|
|
// We always add the job to the periodic dispatcher because there is the
|
|
|
|
// possibility that the periodic spec was removed and then we should stop
|
|
|
|
// tracking it.
|
|
|
|
if err := n.periodicDispatcher.Add(req.Job); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: periodicDispatcher.Add failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
2015-12-19 01:51:30 +00:00
|
|
|
|
2017-02-08 04:31:23 +00:00
|
|
|
// Create a watch set
|
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
|
2015-12-21 21:25:50 +00:00
|
|
|
// If it is periodic, record the time it was inserted. This is necessary for
|
|
|
|
// recovering during leader election. It is possible that from the time it
|
|
|
|
// is added to when it was suppose to launch, leader election occurs and the
|
|
|
|
// job was not launched. In this case, we use the insertion time to
|
|
|
|
// determine if a launch was missed.
|
|
|
|
if req.Job.IsPeriodic() {
|
2017-02-08 04:31:23 +00:00
|
|
|
prevLaunch, err := n.state.PeriodicLaunchByID(ws, req.Job.ID)
|
2015-12-24 03:02:31 +00:00
|
|
|
if err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: PeriodicLaunchByID failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-12-21 21:25:50 +00:00
|
|
|
// Record the insertion time as a launch. We overload the launch table
|
|
|
|
// such that the first entry is the insertion time.
|
2015-12-24 03:02:31 +00:00
|
|
|
if prevLaunch == nil {
|
|
|
|
launch := &structs.PeriodicLaunch{ID: req.Job.ID, Launch: time.Now()}
|
|
|
|
if err := n.state.UpsertPeriodicLaunch(index, launch); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: UpsertPeriodicLaunch failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
2015-12-19 01:51:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the parent job is periodic and mark the launch time.
|
|
|
|
parentID := req.Job.ParentID
|
|
|
|
if parentID != "" {
|
2017-02-08 04:31:23 +00:00
|
|
|
parent, err := n.state.JobByID(ws, parentID)
|
2015-12-19 01:51:30 +00:00
|
|
|
if err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: JobByID(%v) lookup for parent failed: %v", parentID, err)
|
|
|
|
return err
|
|
|
|
} else if parent == nil {
|
|
|
|
// The parent has been deregistered.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-03-27 23:55:17 +00:00
|
|
|
if parent.IsPeriodic() && !parent.IsParameterized() {
|
2015-12-24 02:22:16 +00:00
|
|
|
t, err := n.periodicDispatcher.LaunchTime(req.Job.ID)
|
|
|
|
if err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: LaunchTime(%v) failed: %v", req.Job.ID, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
launch := &structs.PeriodicLaunch{ID: parentID, Launch: t}
|
2015-12-19 01:51:30 +00:00
|
|
|
if err := n.state.UpsertPeriodicLaunch(index, launch); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: UpsertPeriodicLaunch failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2015-12-01 22:54:57 +00:00
|
|
|
}
|
|
|
|
|
2015-07-07 16:51:42 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *nomadFSM) applyDeregisterJob(buf []byte, index uint64) interface{} {
|
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "deregister_job"}, time.Now())
|
|
|
|
var req structs.JobDeregisterRequest
|
|
|
|
if err := structs.Decode(buf, &req); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode request: %v", err))
|
|
|
|
}
|
|
|
|
|
2017-04-15 03:54:30 +00:00
|
|
|
// If it is periodic remove it from the dispatcher
|
2015-12-24 01:47:37 +00:00
|
|
|
if err := n.periodicDispatcher.Remove(req.JobID); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: periodicDispatcher.Remove failed: %v", err)
|
2015-12-01 22:54:57 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-04-15 03:54:30 +00:00
|
|
|
if req.Purge {
|
|
|
|
if err := n.state.DeleteJob(index, req.JobID); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: DeleteJob failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// We always delete from the periodic launch table because it is possible that
|
|
|
|
// the job was updated to be non-perioidic, thus checking if it is periodic
|
|
|
|
// doesn't ensure we clean it up properly.
|
|
|
|
n.state.DeletePeriodicLaunch(index, req.JobID)
|
|
|
|
} else {
|
|
|
|
// Get the current job and mark it as stopped and re-insert it.
|
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
current, err := n.state.JobByID(ws, req.JobID)
|
|
|
|
if err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: JobByID lookup failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if current == nil {
|
|
|
|
return fmt.Errorf("job %q doesn't exist to be deregistered", req.JobID)
|
|
|
|
}
|
|
|
|
|
|
|
|
stopped := current.Copy()
|
|
|
|
stopped.Stop = true
|
|
|
|
|
|
|
|
if err := n.state.UpsertJob(index, stopped); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: UpsertJob failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2015-12-19 01:51:30 +00:00
|
|
|
|
2015-07-07 16:51:42 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-07-23 22:52:38 +00:00
|
|
|
func (n *nomadFSM) applyUpdateEval(buf []byte, index uint64) interface{} {
|
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "update_eval"}, time.Now())
|
|
|
|
var req structs.EvalUpdateRequest
|
|
|
|
if err := structs.Decode(buf, &req); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode request: %v", err))
|
|
|
|
}
|
|
|
|
|
2015-08-06 21:51:15 +00:00
|
|
|
if err := n.state.UpsertEvals(index, req.Evals); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: UpsertEvals failed: %v", err)
|
2015-07-23 22:52:38 +00:00
|
|
|
return err
|
|
|
|
}
|
2015-08-06 18:32:42 +00:00
|
|
|
|
2015-08-06 21:51:15 +00:00
|
|
|
for _, eval := range req.Evals {
|
|
|
|
if eval.ShouldEnqueue() {
|
2016-05-18 18:35:15 +00:00
|
|
|
n.evalBroker.Enqueue(eval)
|
2016-01-29 23:31:32 +00:00
|
|
|
} else if eval.ShouldBlock() {
|
|
|
|
n.blockedEvals.Block(eval)
|
2017-01-04 23:25:03 +00:00
|
|
|
} else if eval.Status == structs.EvalStatusComplete &&
|
|
|
|
len(eval.FailedTGAllocs) == 0 {
|
|
|
|
// If we have a successful evaluation for a node, untrack any
|
|
|
|
// blocked evaluation
|
|
|
|
n.blockedEvals.Untrack(eval.JobID)
|
2015-08-06 18:32:42 +00:00
|
|
|
}
|
|
|
|
}
|
2015-07-23 22:52:38 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *nomadFSM) applyDeleteEval(buf []byte, index uint64) interface{} {
|
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "delete_eval"}, time.Now())
|
|
|
|
var req structs.EvalDeleteRequest
|
|
|
|
if err := structs.Decode(buf, &req); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode request: %v", err))
|
|
|
|
}
|
|
|
|
|
2015-08-15 22:39:29 +00:00
|
|
|
if err := n.state.DeleteEval(index, req.Evals, req.Allocs); err != nil {
|
2015-07-23 22:52:38 +00:00
|
|
|
n.logger.Printf("[ERR] nomad.fsm: DeleteEval failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-08-04 21:04:26 +00:00
|
|
|
func (n *nomadFSM) applyAllocUpdate(buf []byte, index uint64) interface{} {
|
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "alloc_update"}, time.Now())
|
|
|
|
var req structs.AllocUpdateRequest
|
|
|
|
if err := structs.Decode(buf, &req); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode request: %v", err))
|
|
|
|
}
|
|
|
|
|
2016-03-01 22:09:25 +00:00
|
|
|
// Attach the job to all the allocations. It is pulled out in the
|
2016-02-21 19:42:54 +00:00
|
|
|
// payload to avoid the redundancy of encoding, but should be denormalized
|
|
|
|
// prior to being inserted into MemDB.
|
2017-05-01 21:49:57 +00:00
|
|
|
structs.DenormalizeAllocationJobs(req.Job, req.Alloc)
|
2016-02-21 19:42:54 +00:00
|
|
|
|
2016-03-01 22:09:25 +00:00
|
|
|
// Calculate the total resources of allocations. It is pulled out in the
|
|
|
|
// payload to avoid encoding something that can be computed, but should be
|
|
|
|
// denormalized prior to being inserted into MemDB.
|
|
|
|
for _, alloc := range req.Alloc {
|
|
|
|
if alloc.Resources != nil {
|
2016-08-29 19:49:52 +00:00
|
|
|
// COMPAT 0.4.1 -> 0.5
|
|
|
|
// Set the shared resources for allocations which don't have them
|
|
|
|
if alloc.SharedResources == nil {
|
|
|
|
alloc.SharedResources = &structs.Resources{
|
|
|
|
DiskMB: alloc.Resources.DiskMB,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-01 22:09:25 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
alloc.Resources = new(structs.Resources)
|
|
|
|
for _, task := range alloc.TaskResources {
|
|
|
|
alloc.Resources.Add(task)
|
|
|
|
}
|
2016-08-27 03:08:03 +00:00
|
|
|
|
2016-08-29 19:49:52 +00:00
|
|
|
// Add the shared resources
|
|
|
|
alloc.Resources.Add(alloc.SharedResources)
|
2016-03-01 22:09:25 +00:00
|
|
|
}
|
|
|
|
|
2015-09-07 03:47:42 +00:00
|
|
|
if err := n.state.UpsertAllocs(index, req.Alloc); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: UpsertAllocs failed: %v", err)
|
2015-08-04 21:04:26 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-08-26 01:00:14 +00:00
|
|
|
func (n *nomadFSM) applyAllocClientUpdate(buf []byte, index uint64) interface{} {
|
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "alloc_client_update"}, time.Now())
|
|
|
|
var req structs.AllocUpdateRequest
|
|
|
|
if err := structs.Decode(buf, &req); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode request: %v", err))
|
|
|
|
}
|
|
|
|
if len(req.Alloc) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-02-08 04:31:23 +00:00
|
|
|
// Create a watch set
|
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
|
2016-07-21 21:43:21 +00:00
|
|
|
// Updating the allocs with the job id and task group name
|
2016-07-20 21:09:03 +00:00
|
|
|
for _, alloc := range req.Alloc {
|
2017-02-08 04:31:23 +00:00
|
|
|
if existing, _ := n.state.AllocByID(ws, alloc.ID); existing != nil {
|
2016-07-20 21:09:03 +00:00
|
|
|
alloc.JobID = existing.JobID
|
|
|
|
alloc.TaskGroup = existing.TaskGroup
|
|
|
|
}
|
2016-07-19 23:15:57 +00:00
|
|
|
}
|
|
|
|
|
2016-02-22 01:58:56 +00:00
|
|
|
// Update all the client allocations
|
|
|
|
if err := n.state.UpdateAllocsFromClient(index, req.Alloc); err != nil {
|
2015-08-26 01:00:14 +00:00
|
|
|
n.logger.Printf("[ERR] nomad.fsm: UpdateAllocFromClient failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
2016-01-29 23:31:32 +00:00
|
|
|
|
|
|
|
// Unblock evals for the nodes computed node class if the client has
|
|
|
|
// finished running an allocation.
|
2016-02-22 01:58:56 +00:00
|
|
|
for _, alloc := range req.Alloc {
|
2016-03-24 01:08:19 +00:00
|
|
|
if alloc.ClientStatus == structs.AllocClientStatusComplete ||
|
2016-02-22 01:58:56 +00:00
|
|
|
alloc.ClientStatus == structs.AllocClientStatusFailed {
|
|
|
|
nodeID := alloc.NodeID
|
2017-02-08 04:31:23 +00:00
|
|
|
node, err := n.state.NodeByID(ws, nodeID)
|
2016-02-22 01:58:56 +00:00
|
|
|
if err != nil || node == nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: looking up node %q failed: %v", nodeID, err)
|
|
|
|
return err
|
2016-01-29 23:31:32 +00:00
|
|
|
|
2016-02-22 01:58:56 +00:00
|
|
|
}
|
2016-05-23 22:24:31 +00:00
|
|
|
n.blockedEvals.Unblock(node.ComputedClass, index)
|
2016-01-29 23:31:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-26 01:00:14 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-04 01:08:37 +00:00
|
|
|
// applyReconcileSummaries reconciles summaries for all the jobs
|
2016-08-03 23:08:30 +00:00
|
|
|
func (n *nomadFSM) applyReconcileSummaries(buf []byte, index uint64) interface{} {
|
|
|
|
if err := n.state.ReconcileJobSummaries(index); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return n.reconcileQueuedAllocations(index)
|
|
|
|
}
|
|
|
|
|
2016-08-22 18:41:47 +00:00
|
|
|
// applyUpsertVaultAccessor stores the Vault accessors for a given allocation
|
|
|
|
// and task
|
2016-08-19 01:14:58 +00:00
|
|
|
func (n *nomadFSM) applyUpsertVaultAccessor(buf []byte, index uint64) interface{} {
|
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "upsert_vault_accessor"}, time.Now())
|
2016-08-22 20:57:27 +00:00
|
|
|
var req structs.VaultAccessorsRequest
|
2016-08-19 01:14:58 +00:00
|
|
|
if err := structs.Decode(buf, &req); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode request: %v", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := n.state.UpsertVaultAccessor(index, req.Accessors); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: UpsertVaultAccessor failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-31 21:10:33 +00:00
|
|
|
// applyDeregisterVaultAccessor deregisters a set of Vault accessors
|
2016-08-22 20:57:27 +00:00
|
|
|
func (n *nomadFSM) applyDeregisterVaultAccessor(buf []byte, index uint64) interface{} {
|
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "deregister_vault_accessor"}, time.Now())
|
|
|
|
var req structs.VaultAccessorsRequest
|
|
|
|
if err := structs.Decode(buf, &req); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode request: %v", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := n.state.DeleteVaultAccessors(index, req.Accessors); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: DeregisterVaultAccessor failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-05-05 20:52:01 +00:00
|
|
|
// applyPlanApply applies the results of a plan application
|
|
|
|
func (n *nomadFSM) applyPlanResults(buf []byte, index uint64) interface{} {
|
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "apply_plan_results"}, time.Now())
|
|
|
|
var req structs.ApplyPlanResultsRequest
|
|
|
|
if err := structs.Decode(buf, &req); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to decode request: %v", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := n.state.UpsertPlanResults(index, &req); err != nil {
|
|
|
|
n.logger.Printf("[ERR] nomad.fsm: ApplyPlan failed: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-06-01 15:49:10 +00:00
|
|
|
func (n *nomadFSM) Snapshot() (raft.FSMSnapshot, error) {
|
|
|
|
// Create a new snapshot
|
|
|
|
snap, err := n.state.Snapshot()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-08-16 00:38:13 +00:00
|
|
|
|
|
|
|
ns := &nomadSnapshot{
|
|
|
|
snap: snap,
|
|
|
|
timetable: n.timetable,
|
|
|
|
}
|
|
|
|
return ns, nil
|
2015-06-01 15:49:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (n *nomadFSM) Restore(old io.ReadCloser) error {
|
|
|
|
defer old.Close()
|
2015-07-06 20:01:10 +00:00
|
|
|
|
|
|
|
// Create a new state store
|
2015-08-11 21:27:14 +00:00
|
|
|
newState, err := state.NewStateStore(n.logOutput)
|
2015-07-06 20:01:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start the state restore
|
2015-08-11 21:27:14 +00:00
|
|
|
restore, err := newState.Restore()
|
2015-07-06 20:01:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer restore.Abort()
|
|
|
|
|
|
|
|
// Create a decoder
|
2015-11-18 23:16:42 +00:00
|
|
|
dec := codec.NewDecoder(old, structs.MsgpackHandle)
|
2015-07-06 20:01:10 +00:00
|
|
|
|
|
|
|
// Read in the header
|
|
|
|
var header snapshotHeader
|
|
|
|
if err := dec.Decode(&header); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Populate the new state
|
|
|
|
msgType := make([]byte, 1)
|
|
|
|
for {
|
|
|
|
// Read the message type
|
|
|
|
_, err := old.Read(msgType)
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode
|
2015-07-06 21:51:01 +00:00
|
|
|
switch SnapshotType(msgType[0]) {
|
2015-08-16 00:38:13 +00:00
|
|
|
case TimeTableSnapshot:
|
|
|
|
if err := n.timetable.Deserialize(dec); err != nil {
|
|
|
|
return fmt.Errorf("time table deserialize failed: %v", err)
|
|
|
|
}
|
|
|
|
|
2015-07-06 21:51:01 +00:00
|
|
|
case NodeSnapshot:
|
|
|
|
node := new(structs.Node)
|
|
|
|
if err := dec.Decode(node); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := restore.NodeRestore(node); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-07-07 16:55:47 +00:00
|
|
|
case JobSnapshot:
|
|
|
|
job := new(structs.Job)
|
|
|
|
if err := dec.Decode(job); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-07-18 23:17:38 +00:00
|
|
|
|
|
|
|
// COMPAT: Remove in 0.5
|
|
|
|
// Empty maps and slices should be treated as nil to avoid
|
|
|
|
// un-intended destructive updates in scheduler since we use
|
|
|
|
// reflect.DeepEqual. Starting Nomad 0.4.1, job submission sanatizes
|
|
|
|
// the incoming job.
|
2016-07-20 23:07:15 +00:00
|
|
|
job.Canonicalize()
|
2016-07-18 23:17:38 +00:00
|
|
|
|
2015-07-07 16:55:47 +00:00
|
|
|
if err := restore.JobRestore(job); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-07-23 22:52:38 +00:00
|
|
|
case EvalSnapshot:
|
|
|
|
eval := new(structs.Evaluation)
|
|
|
|
if err := dec.Decode(eval); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := restore.EvalRestore(eval); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-08-04 21:04:26 +00:00
|
|
|
case AllocSnapshot:
|
|
|
|
alloc := new(structs.Allocation)
|
|
|
|
if err := dec.Decode(alloc); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := restore.AllocRestore(alloc); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-07-06 21:51:01 +00:00
|
|
|
case IndexSnapshot:
|
2015-08-11 21:27:14 +00:00
|
|
|
idx := new(state.IndexEntry)
|
2015-07-06 21:51:01 +00:00
|
|
|
if err := dec.Decode(idx); err != nil {
|
2015-07-06 20:01:10 +00:00
|
|
|
return err
|
|
|
|
}
|
2015-07-06 21:51:01 +00:00
|
|
|
if err := restore.IndexRestore(idx); err != nil {
|
2015-07-06 20:01:10 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-12-07 23:58:17 +00:00
|
|
|
case PeriodicLaunchSnapshot:
|
|
|
|
launch := new(structs.PeriodicLaunch)
|
|
|
|
if err := dec.Decode(launch); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := restore.PeriodicLaunchRestore(launch); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-07-05 18:50:44 +00:00
|
|
|
case JobSummarySnapshot:
|
|
|
|
summary := new(structs.JobSummary)
|
|
|
|
if err := dec.Decode(summary); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := restore.JobSummaryRestore(summary); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-08-19 01:14:58 +00:00
|
|
|
case VaultAccessorSnapshot:
|
|
|
|
accessor := new(structs.VaultAccessor)
|
|
|
|
if err := dec.Decode(accessor); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := restore.VaultAccessorRestore(accessor); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-04-24 21:49:23 +00:00
|
|
|
case JobVersionSnapshot:
|
|
|
|
version := new(structs.Job)
|
|
|
|
if err := dec.Decode(version); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := restore.JobVersionRestore(version); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
case DeploymentSnapshot:
|
|
|
|
deployment := new(structs.Deployment)
|
|
|
|
if err := dec.Decode(deployment); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := restore.DeploymentRestore(deployment); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-07-06 20:01:10 +00:00
|
|
|
default:
|
2015-07-06 21:51:01 +00:00
|
|
|
return fmt.Errorf("Unrecognized snapshot type: %v", msgType)
|
2015-07-06 20:01:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-03 18:58:36 +00:00
|
|
|
restore.Commit()
|
|
|
|
|
2016-07-22 06:59:28 +00:00
|
|
|
// Create Job Summaries
|
|
|
|
// COMPAT 0.4 -> 0.4.1
|
2016-08-04 01:08:37 +00:00
|
|
|
// We can remove this in 0.5. This exists so that the server creates job
|
|
|
|
// summaries if they were not present previously. When users upgrade to 0.5
|
|
|
|
// from 0.4.1, the snapshot will contain job summaries so it will be safe to
|
|
|
|
// remove this block.
|
2017-02-08 19:51:48 +00:00
|
|
|
index, err := newState.Index("job_summary")
|
2016-07-26 00:26:10 +00:00
|
|
|
if err != nil {
|
2016-08-03 18:58:36 +00:00
|
|
|
return fmt.Errorf("couldn't fetch index of job summary table: %v", err)
|
2016-07-26 00:26:10 +00:00
|
|
|
}
|
2016-08-03 23:08:30 +00:00
|
|
|
|
|
|
|
// If the index is 0 that means there is no job summary in the snapshot so
|
|
|
|
// we will have to create them
|
2016-08-03 18:58:36 +00:00
|
|
|
if index == 0 {
|
2016-08-03 23:08:30 +00:00
|
|
|
// query the latest index
|
2017-02-08 19:51:48 +00:00
|
|
|
latestIndex, err := newState.LatestIndex()
|
2016-08-03 23:08:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to query latest index: %v", index)
|
|
|
|
}
|
2017-02-08 19:51:48 +00:00
|
|
|
if err := newState.ReconcileJobSummaries(latestIndex); err != nil {
|
2016-08-03 18:58:36 +00:00
|
|
|
return fmt.Errorf("error reconciling summaries: %v", err)
|
|
|
|
}
|
2016-07-22 06:59:28 +00:00
|
|
|
}
|
|
|
|
|
2017-02-05 20:03:11 +00:00
|
|
|
// External code might be calling State(), so we need to synchronize
|
|
|
|
// here to make sure we swap in the new state store atomically.
|
|
|
|
n.stateLock.Lock()
|
|
|
|
stateOld := n.state
|
|
|
|
n.state = newState
|
|
|
|
n.stateLock.Unlock()
|
|
|
|
|
|
|
|
// Signal that the old state store has been abandoned. This is required
|
|
|
|
// because we don't operate on it any more, we just throw it away, so
|
|
|
|
// blocking queries won't see any changes and need to be woken up.
|
|
|
|
stateOld.Abandon()
|
|
|
|
|
2016-08-03 18:58:36 +00:00
|
|
|
return nil
|
2016-07-26 00:26:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// reconcileSummaries re-calculates the queued allocations for every job that we
|
|
|
|
// created a Job Summary during the snap shot restore
|
2016-08-03 23:08:30 +00:00
|
|
|
func (n *nomadFSM) reconcileQueuedAllocations(index uint64) error {
|
2016-08-03 18:58:36 +00:00
|
|
|
// Get all the jobs
|
2017-02-08 04:31:23 +00:00
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
iter, err := n.state.Jobs(ws)
|
2016-08-03 18:58:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-07-26 00:26:10 +00:00
|
|
|
snap, err := n.state.Snapshot()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to create snapshot: %v", err)
|
|
|
|
}
|
2016-08-04 01:08:37 +00:00
|
|
|
|
|
|
|
// Invoking the scheduler for every job so that we can populate the number
|
|
|
|
// of queued allocations for every job
|
|
|
|
for {
|
|
|
|
rawJob := iter.Next()
|
|
|
|
if rawJob == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
job := rawJob.(*structs.Job)
|
2016-07-26 00:26:10 +00:00
|
|
|
planner := &scheduler.Harness{
|
|
|
|
State: &snap.StateStore,
|
|
|
|
}
|
|
|
|
// Create an eval and mark it as requiring annotations and insert that as well
|
|
|
|
eval := &structs.Evaluation{
|
|
|
|
ID: structs.GenerateUUID(),
|
|
|
|
Priority: job.Priority,
|
|
|
|
Type: job.Type,
|
|
|
|
TriggeredBy: structs.EvalTriggerJobRegister,
|
|
|
|
JobID: job.ID,
|
|
|
|
JobModifyIndex: job.JobModifyIndex + 1,
|
|
|
|
Status: structs.EvalStatusPending,
|
|
|
|
AnnotatePlan: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the scheduler and run it
|
|
|
|
sched, err := scheduler.NewScheduler(eval.Type, n.logger, snap, planner)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := sched.Process(eval); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-08-03 23:08:30 +00:00
|
|
|
|
|
|
|
// Get the job summary from the fsm state store
|
2017-02-08 04:31:23 +00:00
|
|
|
originalSummary, err := n.state.JobSummaryByID(ws, job.ID)
|
2016-07-26 00:26:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-01-11 21:18:36 +00:00
|
|
|
summary := originalSummary.Copy()
|
2016-08-03 23:08:30 +00:00
|
|
|
|
|
|
|
// Add the allocations scheduler has made to queued since these
|
|
|
|
// allocations are never getting placed until the scheduler is invoked
|
|
|
|
// with a real planner
|
|
|
|
if l := len(planner.Plans); l != 1 {
|
|
|
|
return fmt.Errorf("unexpected number of plans during restore %d. Please file an issue including the logs", l)
|
|
|
|
}
|
|
|
|
for _, allocations := range planner.Plans[0].NodeAllocation {
|
|
|
|
for _, allocation := range allocations {
|
|
|
|
tgSummary, ok := summary.Summary[allocation.TaskGroup]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("task group %q not found while updating queued count", allocation.TaskGroup)
|
|
|
|
}
|
|
|
|
tgSummary.Queued += 1
|
|
|
|
summary.Summary[allocation.TaskGroup] = tgSummary
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the queued allocations attached to the evaluation to the queued
|
|
|
|
// counter of the job summary
|
2016-07-26 05:11:11 +00:00
|
|
|
if l := len(planner.Evals); l != 1 {
|
|
|
|
return fmt.Errorf("unexpected number of evals during restore %d. Please file an issue including the logs", l)
|
|
|
|
}
|
2016-07-26 00:26:10 +00:00
|
|
|
for tg, queued := range planner.Evals[0].QueuedAllocations {
|
|
|
|
tgSummary, ok := summary.Summary[tg]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("task group %q not found while updating queued count", tg)
|
|
|
|
}
|
2017-01-06 18:34:55 +00:00
|
|
|
|
|
|
|
// We add instead of setting here because we want to take into
|
|
|
|
// consideration what the scheduler with a mock planner thinks it
|
|
|
|
// placed. Those should be counted as queued as well
|
2016-08-03 23:08:30 +00:00
|
|
|
tgSummary.Queued += queued
|
2016-07-26 00:26:10 +00:00
|
|
|
summary.Summary[tg] = tgSummary
|
|
|
|
}
|
|
|
|
|
2017-01-11 21:18:36 +00:00
|
|
|
if !reflect.DeepEqual(summary, originalSummary) {
|
|
|
|
summary.ModifyIndex = index
|
|
|
|
if err := n.state.UpsertJobSummary(index, summary); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-07-26 00:26:10 +00:00
|
|
|
}
|
|
|
|
}
|
2015-06-01 15:49:10 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *nomadSnapshot) Persist(sink raft.SnapshotSink) error {
|
|
|
|
defer metrics.MeasureSince([]string{"nomad", "fsm", "persist"}, time.Now())
|
2015-07-06 20:01:10 +00:00
|
|
|
// Register the nodes
|
2015-11-18 23:16:42 +00:00
|
|
|
encoder := codec.NewEncoder(sink, structs.MsgpackHandle)
|
2015-07-06 20:01:10 +00:00
|
|
|
|
|
|
|
// Write the header
|
|
|
|
header := snapshotHeader{}
|
|
|
|
if err := encoder.Encode(&header); err != nil {
|
|
|
|
sink.Cancel()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-08-16 00:38:13 +00:00
|
|
|
// Write the time table
|
|
|
|
sink.Write([]byte{byte(TimeTableSnapshot)})
|
|
|
|
if err := s.timetable.Serialize(encoder); err != nil {
|
|
|
|
sink.Cancel()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-07-06 20:01:10 +00:00
|
|
|
// Write all the data out
|
2015-07-06 21:51:01 +00:00
|
|
|
if err := s.persistIndexes(sink, encoder); err != nil {
|
|
|
|
sink.Cancel()
|
|
|
|
return err
|
|
|
|
}
|
2015-07-06 20:01:10 +00:00
|
|
|
if err := s.persistNodes(sink, encoder); err != nil {
|
|
|
|
sink.Cancel()
|
|
|
|
return err
|
|
|
|
}
|
2015-07-07 16:55:47 +00:00
|
|
|
if err := s.persistJobs(sink, encoder); err != nil {
|
|
|
|
sink.Cancel()
|
|
|
|
return err
|
|
|
|
}
|
2015-07-23 22:52:38 +00:00
|
|
|
if err := s.persistEvals(sink, encoder); err != nil {
|
|
|
|
sink.Cancel()
|
|
|
|
return err
|
|
|
|
}
|
2015-08-04 21:04:26 +00:00
|
|
|
if err := s.persistAllocs(sink, encoder); err != nil {
|
|
|
|
sink.Cancel()
|
|
|
|
return err
|
|
|
|
}
|
2015-12-07 23:58:17 +00:00
|
|
|
if err := s.persistPeriodicLaunches(sink, encoder); err != nil {
|
|
|
|
sink.Cancel()
|
|
|
|
return err
|
|
|
|
}
|
2016-07-05 18:50:44 +00:00
|
|
|
if err := s.persistJobSummaries(sink, encoder); err != nil {
|
|
|
|
sink.Cancel()
|
|
|
|
return err
|
|
|
|
}
|
2016-08-19 01:14:58 +00:00
|
|
|
if err := s.persistVaultAccessors(sink, encoder); err != nil {
|
|
|
|
sink.Cancel()
|
|
|
|
return err
|
|
|
|
}
|
2017-04-24 21:49:23 +00:00
|
|
|
if err := s.persistJobVersions(sink, encoder); err != nil {
|
|
|
|
sink.Cancel()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := s.persistDeployments(sink, encoder); err != nil {
|
|
|
|
sink.Cancel()
|
|
|
|
return err
|
|
|
|
}
|
2015-06-01 15:49:10 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-07-06 21:51:01 +00:00
|
|
|
func (s *nomadSnapshot) persistIndexes(sink raft.SnapshotSink,
|
|
|
|
encoder *codec.Encoder) error {
|
|
|
|
// Get all the indexes
|
|
|
|
iter, err := s.snap.Indexes()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
// Get the next item
|
|
|
|
raw := iter.Next()
|
|
|
|
if raw == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare the request struct
|
2015-08-11 21:27:14 +00:00
|
|
|
idx := raw.(*state.IndexEntry)
|
2015-07-06 21:51:01 +00:00
|
|
|
|
|
|
|
// Write out a node registration
|
|
|
|
sink.Write([]byte{byte(IndexSnapshot)})
|
|
|
|
if err := encoder.Encode(idx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-07-06 20:01:10 +00:00
|
|
|
func (s *nomadSnapshot) persistNodes(sink raft.SnapshotSink,
|
|
|
|
encoder *codec.Encoder) error {
|
|
|
|
// Get all the nodes
|
2017-02-08 04:31:23 +00:00
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
nodes, err := s.snap.Nodes(ws)
|
2015-07-06 20:01:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
// Get the next item
|
|
|
|
raw := nodes.Next()
|
|
|
|
if raw == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare the request struct
|
|
|
|
node := raw.(*structs.Node)
|
|
|
|
|
|
|
|
// Write out a node registration
|
2015-07-06 21:51:01 +00:00
|
|
|
sink.Write([]byte{byte(NodeSnapshot)})
|
|
|
|
if err := encoder.Encode(node); err != nil {
|
2015-07-06 20:01:10 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2015-06-01 15:49:10 +00:00
|
|
|
}
|
2015-07-06 20:01:10 +00:00
|
|
|
|
2015-07-07 16:55:47 +00:00
|
|
|
func (s *nomadSnapshot) persistJobs(sink raft.SnapshotSink,
|
|
|
|
encoder *codec.Encoder) error {
|
|
|
|
// Get all the jobs
|
2017-02-08 04:31:23 +00:00
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
jobs, err := s.snap.Jobs(ws)
|
2015-07-07 16:55:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
// Get the next item
|
|
|
|
raw := jobs.Next()
|
|
|
|
if raw == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare the request struct
|
|
|
|
job := raw.(*structs.Job)
|
|
|
|
|
|
|
|
// Write out a job registration
|
|
|
|
sink.Write([]byte{byte(JobSnapshot)})
|
|
|
|
if err := encoder.Encode(job); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-07-23 22:52:38 +00:00
|
|
|
func (s *nomadSnapshot) persistEvals(sink raft.SnapshotSink,
|
|
|
|
encoder *codec.Encoder) error {
|
|
|
|
// Get all the evaluations
|
2017-02-08 04:31:23 +00:00
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
evals, err := s.snap.Evals(ws)
|
2015-07-23 22:52:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
// Get the next item
|
|
|
|
raw := evals.Next()
|
|
|
|
if raw == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare the request struct
|
|
|
|
eval := raw.(*structs.Evaluation)
|
|
|
|
|
|
|
|
// Write out the evaluation
|
|
|
|
sink.Write([]byte{byte(EvalSnapshot)})
|
|
|
|
if err := encoder.Encode(eval); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-08-04 21:04:26 +00:00
|
|
|
func (s *nomadSnapshot) persistAllocs(sink raft.SnapshotSink,
|
|
|
|
encoder *codec.Encoder) error {
|
|
|
|
// Get all the allocations
|
2017-02-08 04:31:23 +00:00
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
allocs, err := s.snap.Allocs(ws)
|
2015-08-04 21:04:26 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
// Get the next item
|
|
|
|
raw := allocs.Next()
|
|
|
|
if raw == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare the request struct
|
|
|
|
alloc := raw.(*structs.Allocation)
|
|
|
|
|
|
|
|
// Write out the evaluation
|
|
|
|
sink.Write([]byte{byte(AllocSnapshot)})
|
|
|
|
if err := encoder.Encode(alloc); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-12-07 23:58:17 +00:00
|
|
|
func (s *nomadSnapshot) persistPeriodicLaunches(sink raft.SnapshotSink,
|
|
|
|
encoder *codec.Encoder) error {
|
|
|
|
// Get all the jobs
|
2017-02-08 04:31:23 +00:00
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
launches, err := s.snap.PeriodicLaunches(ws)
|
2015-12-07 23:58:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
// Get the next item
|
|
|
|
raw := launches.Next()
|
|
|
|
if raw == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare the request struct
|
|
|
|
launch := raw.(*structs.PeriodicLaunch)
|
|
|
|
|
|
|
|
// Write out a job registration
|
|
|
|
sink.Write([]byte{byte(PeriodicLaunchSnapshot)})
|
|
|
|
if err := encoder.Encode(launch); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-07-05 18:50:44 +00:00
|
|
|
func (s *nomadSnapshot) persistJobSummaries(sink raft.SnapshotSink,
|
|
|
|
encoder *codec.Encoder) error {
|
|
|
|
|
2017-02-08 04:31:23 +00:00
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
summaries, err := s.snap.JobSummaries(ws)
|
2016-07-05 18:50:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
raw := summaries.Next()
|
|
|
|
if raw == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2017-01-06 18:34:55 +00:00
|
|
|
jobSummary := raw.(*structs.JobSummary)
|
2016-07-05 18:50:44 +00:00
|
|
|
|
|
|
|
sink.Write([]byte{byte(JobSummarySnapshot)})
|
|
|
|
if err := encoder.Encode(jobSummary); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-19 01:14:58 +00:00
|
|
|
func (s *nomadSnapshot) persistVaultAccessors(sink raft.SnapshotSink,
|
|
|
|
encoder *codec.Encoder) error {
|
|
|
|
|
2017-02-08 04:31:23 +00:00
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
accessors, err := s.snap.VaultAccessors(ws)
|
2016-08-19 01:14:58 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
raw := accessors.Next()
|
|
|
|
if raw == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
accessor := raw.(*structs.VaultAccessor)
|
|
|
|
|
|
|
|
sink.Write([]byte{byte(VaultAccessorSnapshot)})
|
|
|
|
if err := encoder.Encode(accessor); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-04-24 21:49:23 +00:00
|
|
|
func (s *nomadSnapshot) persistJobVersions(sink raft.SnapshotSink,
|
|
|
|
encoder *codec.Encoder) error {
|
|
|
|
// Get all the jobs
|
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
versions, err := s.snap.JobVersions(ws)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
// Get the next item
|
|
|
|
raw := versions.Next()
|
|
|
|
if raw == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare the request struct
|
|
|
|
job := raw.(*structs.Job)
|
|
|
|
|
|
|
|
// Write out a job registration
|
|
|
|
sink.Write([]byte{byte(JobVersionSnapshot)})
|
|
|
|
if err := encoder.Encode(job); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *nomadSnapshot) persistDeployments(sink raft.SnapshotSink,
|
|
|
|
encoder *codec.Encoder) error {
|
|
|
|
// Get all the jobs
|
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
deployments, err := s.snap.Deployments(ws)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
// Get the next item
|
|
|
|
raw := deployments.Next()
|
|
|
|
if raw == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare the request struct
|
|
|
|
deployment := raw.(*structs.Deployment)
|
|
|
|
|
|
|
|
// Write out a job registration
|
|
|
|
sink.Write([]byte{byte(DeploymentSnapshot)})
|
|
|
|
if err := encoder.Encode(deployment); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-07-06 20:01:10 +00:00
|
|
|
// Release is a no-op, as we just need to GC the pointer
|
|
|
|
// to the state store snapshot. There is nothing to explicitly
|
|
|
|
// cleanup.
|
|
|
|
func (s *nomadSnapshot) Release() {}
|