2015-06-01 15:49:10 +00:00
|
|
|
|
package structs
|
|
|
|
|
|
2015-06-05 22:21:17 +00:00
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
2018-10-16 22:34:32 +00:00
|
|
|
|
"container/heap"
|
2016-03-14 22:46:06 +00:00
|
|
|
|
"crypto/md5"
|
2015-11-21 20:34:01 +00:00
|
|
|
|
"crypto/sha1"
|
2016-03-14 22:46:06 +00:00
|
|
|
|
"crypto/sha256"
|
|
|
|
|
"crypto/sha512"
|
2017-12-08 19:50:14 +00:00
|
|
|
|
"encoding/base32"
|
2019-02-20 16:41:51 +00:00
|
|
|
|
"encoding/base64"
|
2016-03-14 22:46:06 +00:00
|
|
|
|
"encoding/hex"
|
2015-09-15 17:46:10 +00:00
|
|
|
|
"errors"
|
2015-06-05 22:41:03 +00:00
|
|
|
|
"fmt"
|
2018-10-16 22:34:32 +00:00
|
|
|
|
"math"
|
2016-11-03 21:14:52 +00:00
|
|
|
|
"net"
|
2016-10-05 20:41:29 +00:00
|
|
|
|
"os"
|
2016-03-18 19:01:46 +00:00
|
|
|
|
"path/filepath"
|
2015-11-16 22:25:19 +00:00
|
|
|
|
"reflect"
|
2015-10-11 19:50:16 +00:00
|
|
|
|
"regexp"
|
2016-10-20 20:55:35 +00:00
|
|
|
|
"sort"
|
2015-12-18 20:26:28 +00:00
|
|
|
|
"strconv"
|
2015-09-16 00:38:23 +00:00
|
|
|
|
"strings"
|
2015-06-05 22:41:03 +00:00
|
|
|
|
"time"
|
2015-06-05 22:21:17 +00:00
|
|
|
|
|
2015-12-01 00:51:56 +00:00
|
|
|
|
"github.com/gorhill/cronexpr"
|
2018-10-16 22:34:32 +00:00
|
|
|
|
hcodec "github.com/hashicorp/go-msgpack/codec"
|
2019-03-04 09:49:32 +00:00
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
|
|
|
"github.com/hashicorp/go-version"
|
2017-08-12 21:11:49 +00:00
|
|
|
|
"github.com/hashicorp/nomad/acl"
|
2017-01-18 23:55:14 +00:00
|
|
|
|
"github.com/hashicorp/nomad/helper"
|
2015-11-27 03:26:00 +00:00
|
|
|
|
"github.com/hashicorp/nomad/helper/args"
|
2017-09-29 16:58:48 +00:00
|
|
|
|
"github.com/hashicorp/nomad/helper/uuid"
|
2018-08-18 00:26:43 +00:00
|
|
|
|
"github.com/hashicorp/nomad/lib/kheap"
|
2018-10-16 22:34:32 +00:00
|
|
|
|
psstructs "github.com/hashicorp/nomad/plugins/shared/structs"
|
2015-12-18 20:26:28 +00:00
|
|
|
|
"github.com/mitchellh/copystructure"
|
2016-02-21 02:05:17 +00:00
|
|
|
|
"github.com/ugorji/go/codec"
|
2018-10-16 22:34:32 +00:00
|
|
|
|
"golang.org/x/crypto/blake2b"
|
2015-06-05 22:21:17 +00:00
|
|
|
|
)
|
|
|
|
|
|
2015-06-05 22:41:03 +00:00
|
|
|
|
var (
|
2017-08-12 21:11:49 +00:00
|
|
|
|
// validPolicyName is used to validate a policy name
|
|
|
|
|
validPolicyName = regexp.MustCompile("^[a-zA-Z0-9-]{1,128}$")
|
2017-12-08 23:54:04 +00:00
|
|
|
|
|
|
|
|
|
// b32 is a lowercase base32 encoding for use in URL friendly service hashes
|
|
|
|
|
b32 = base32.NewEncoding(strings.ToLower("abcdefghijklmnopqrstuvwxyz234567"))
|
2015-06-05 22:41:03 +00:00
|
|
|
|
)
|
|
|
|
|
|
2015-06-01 15:49:10 +00:00
|
|
|
|
type MessageType uint8
|
|
|
|
|
|
|
|
|
|
const (
|
2015-07-07 16:51:42 +00:00
|
|
|
|
NodeRegisterRequestType MessageType = iota
|
|
|
|
|
NodeDeregisterRequestType
|
2015-07-04 01:41:36 +00:00
|
|
|
|
NodeUpdateStatusRequestType
|
2015-09-07 02:55:38 +00:00
|
|
|
|
NodeUpdateDrainRequestType
|
2015-07-07 16:51:42 +00:00
|
|
|
|
JobRegisterRequestType
|
|
|
|
|
JobDeregisterRequestType
|
2015-07-23 22:52:38 +00:00
|
|
|
|
EvalUpdateRequestType
|
|
|
|
|
EvalDeleteRequestType
|
2015-08-04 21:04:33 +00:00
|
|
|
|
AllocUpdateRequestType
|
2015-08-26 01:00:14 +00:00
|
|
|
|
AllocClientUpdateRequestType
|
2016-08-03 23:08:30 +00:00
|
|
|
|
ReconcileJobSummariesRequestType
|
2016-08-19 01:14:58 +00:00
|
|
|
|
VaultAccessorRegisterRequestType
|
2018-03-11 17:53:22 +00:00
|
|
|
|
VaultAccessorDeregisterRequestType
|
2017-05-05 20:52:01 +00:00
|
|
|
|
ApplyPlanResultsRequestType
|
2017-06-26 21:23:52 +00:00
|
|
|
|
DeploymentStatusUpdateRequestType
|
|
|
|
|
DeploymentPromoteRequestType
|
2017-06-27 17:31:32 +00:00
|
|
|
|
DeploymentAllocHealthRequestType
|
2017-06-29 19:32:37 +00:00
|
|
|
|
DeploymentDeleteRequestType
|
2017-07-06 19:49:13 +00:00
|
|
|
|
JobStabilityRequestType
|
2017-08-08 03:53:07 +00:00
|
|
|
|
ACLPolicyUpsertRequestType
|
|
|
|
|
ACLPolicyDeleteRequestType
|
2017-08-12 22:44:05 +00:00
|
|
|
|
ACLTokenUpsertRequestType
|
|
|
|
|
ACLTokenDeleteRequestType
|
2017-08-21 01:19:26 +00:00
|
|
|
|
ACLTokenBootstrapRequestType
|
2017-12-18 21:16:23 +00:00
|
|
|
|
AutopilotRequestType
|
2018-03-14 00:52:12 +00:00
|
|
|
|
UpsertNodeEventsType
|
2018-03-14 22:32:18 +00:00
|
|
|
|
JobBatchDeregisterRequestType
|
2018-02-23 01:38:44 +00:00
|
|
|
|
AllocUpdateDesiredTransitionRequestType
|
2018-02-27 00:34:42 +00:00
|
|
|
|
NodeUpdateEligibilityRequestType
|
2018-03-09 22:15:21 +00:00
|
|
|
|
BatchNodeUpdateDrainRequestType
|
2018-09-28 04:27:38 +00:00
|
|
|
|
SchedulerConfigRequestType
|
2019-06-26 14:57:58 +00:00
|
|
|
|
NodeBatchDeregisterRequestType
|
2015-06-01 15:49:10 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
// IgnoreUnknownTypeFlag is set along with a MessageType
|
|
|
|
|
// to indicate that the message type can be safely ignored
|
|
|
|
|
// if it is not recognized. This is for future proofing, so
|
|
|
|
|
// that new commands can be added in a way that won't cause
|
|
|
|
|
// old servers to crash when the FSM attempts to process them.
|
|
|
|
|
IgnoreUnknownTypeFlag MessageType = 128
|
2016-05-28 01:14:34 +00:00
|
|
|
|
|
|
|
|
|
// ApiMajorVersion is returned as part of the Status.Version request.
|
|
|
|
|
// It should be incremented anytime the APIs are changed in a way
|
|
|
|
|
// that would break clients for sane client versioning.
|
|
|
|
|
ApiMajorVersion = 1
|
|
|
|
|
|
|
|
|
|
// ApiMinorVersion is returned as part of the Status.Version request.
|
|
|
|
|
// It should be incremented anytime the APIs are changed to allow
|
|
|
|
|
// for sane client versioning. Minor changes should be compatible
|
|
|
|
|
// within the major version.
|
|
|
|
|
ApiMinorVersion = 1
|
|
|
|
|
|
|
|
|
|
ProtocolVersion = "protocol"
|
|
|
|
|
APIMajorVersion = "api.major"
|
|
|
|
|
APIMinorVersion = "api.minor"
|
2017-07-06 03:44:49 +00:00
|
|
|
|
|
|
|
|
|
GetterModeAny = "any"
|
|
|
|
|
GetterModeFile = "file"
|
|
|
|
|
GetterModeDir = "dir"
|
2017-08-12 21:11:49 +00:00
|
|
|
|
|
|
|
|
|
// maxPolicyDescriptionLength limits a policy description length
|
|
|
|
|
maxPolicyDescriptionLength = 256
|
2017-08-12 21:36:10 +00:00
|
|
|
|
|
|
|
|
|
// maxTokenNameLength limits a ACL token name length
|
2018-02-20 17:51:34 +00:00
|
|
|
|
maxTokenNameLength = 256
|
2017-08-12 21:36:10 +00:00
|
|
|
|
|
|
|
|
|
// ACLClientToken and ACLManagementToken are the only types of tokens
|
|
|
|
|
ACLClientToken = "client"
|
|
|
|
|
ACLManagementToken = "management"
|
2017-08-21 04:31:45 +00:00
|
|
|
|
|
|
|
|
|
// DefaultNamespace is the default namespace.
|
2017-09-07 23:56:15 +00:00
|
|
|
|
DefaultNamespace = "default"
|
|
|
|
|
DefaultNamespaceDescription = "Default shared namespace"
|
2018-01-26 02:15:21 +00:00
|
|
|
|
|
|
|
|
|
// JitterFraction is a the limit to the amount of jitter we apply
|
|
|
|
|
// to a user specified MaxQueryTime. We divide the specified time by
|
|
|
|
|
// the fraction. So 16 == 6.25% limit of jitter. This jitter is also
|
|
|
|
|
// applied to RPCHoldTimeout.
|
|
|
|
|
JitterFraction = 16
|
2018-03-12 01:00:13 +00:00
|
|
|
|
|
|
|
|
|
// MaxRetainedNodeEvents is the maximum number of node events that will be
|
|
|
|
|
// retained for a single node
|
|
|
|
|
MaxRetainedNodeEvents = 10
|
2018-08-08 14:41:56 +00:00
|
|
|
|
|
|
|
|
|
// MaxRetainedNodeScores is the number of top scoring nodes for which we
|
|
|
|
|
// retain scoring metadata
|
|
|
|
|
MaxRetainedNodeScores = 5
|
2018-08-17 23:44:00 +00:00
|
|
|
|
|
|
|
|
|
// Normalized scorer name
|
|
|
|
|
NormScorerName = "normalized-score"
|
2015-06-01 15:49:10 +00:00
|
|
|
|
)
|
2015-06-05 22:21:17 +00:00
|
|
|
|
|
2017-08-18 20:06:25 +00:00
|
|
|
|
// Context defines the scope in which a search for Nomad object operates, and
|
|
|
|
|
// is also used to query the matching index value for this context
|
2017-08-11 21:21:35 +00:00
|
|
|
|
type Context string
|
|
|
|
|
|
|
|
|
|
const (
|
2017-08-18 20:06:25 +00:00
|
|
|
|
Allocs Context = "allocs"
|
|
|
|
|
Deployments Context = "deployment"
|
|
|
|
|
Evals Context = "evals"
|
|
|
|
|
Jobs Context = "jobs"
|
|
|
|
|
Nodes Context = "nodes"
|
2017-09-07 23:56:15 +00:00
|
|
|
|
Namespaces Context = "namespaces"
|
2017-10-13 21:36:02 +00:00
|
|
|
|
Quotas Context = "quotas"
|
2017-08-26 00:04:23 +00:00
|
|
|
|
All Context = "all"
|
2017-08-11 21:21:35 +00:00
|
|
|
|
)
|
|
|
|
|
|
2017-09-07 23:56:15 +00:00
|
|
|
|
// NamespacedID is a tuple of an ID and a namespace
|
|
|
|
|
type NamespacedID struct {
|
|
|
|
|
ID string
|
|
|
|
|
Namespace string
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-07 18:22:08 +00:00
|
|
|
|
// NewNamespacedID returns a new namespaced ID given the ID and namespace
|
|
|
|
|
func NewNamespacedID(id, ns string) NamespacedID {
|
|
|
|
|
return NamespacedID{
|
|
|
|
|
ID: id,
|
|
|
|
|
Namespace: ns,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-19 17:12:12 +00:00
|
|
|
|
func (n NamespacedID) String() string {
|
|
|
|
|
return fmt.Sprintf("<ns: %q, id: %q>", n.Namespace, n.ID)
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-05 22:41:03 +00:00
|
|
|
|
// RPCInfo is used to describe common information about query
|
|
|
|
|
type RPCInfo interface {
|
|
|
|
|
RequestRegion() string
|
|
|
|
|
IsRead() bool
|
|
|
|
|
AllowStaleRead() bool
|
2018-03-27 01:10:43 +00:00
|
|
|
|
IsForwarded() bool
|
|
|
|
|
SetForwarded()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// InternalRpcInfo allows adding internal RPC metadata to an RPC. This struct
|
|
|
|
|
// should NOT be replicated in the API package as it is internal only.
|
|
|
|
|
type InternalRpcInfo struct {
|
|
|
|
|
// Forwarded marks whether the RPC has been forwarded.
|
|
|
|
|
Forwarded bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IsForwarded returns whether the RPC is forwarded from another server.
|
|
|
|
|
func (i *InternalRpcInfo) IsForwarded() bool {
|
|
|
|
|
return i.Forwarded
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetForwarded marks that the RPC is being forwarded from another server.
|
|
|
|
|
func (i *InternalRpcInfo) SetForwarded() {
|
|
|
|
|
i.Forwarded = true
|
2015-06-05 22:41:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// QueryOptions is used to specify various flags for read queries
|
|
|
|
|
type QueryOptions struct {
|
2015-07-03 23:04:24 +00:00
|
|
|
|
// The target region for this query
|
|
|
|
|
Region string
|
|
|
|
|
|
2017-09-07 23:56:15 +00:00
|
|
|
|
// Namespace is the target namespace for the query.
|
|
|
|
|
Namespace string
|
|
|
|
|
|
2015-06-07 18:18:59 +00:00
|
|
|
|
// If set, wait until query exceeds given index. Must be provided
|
|
|
|
|
// with MaxQueryTime.
|
|
|
|
|
MinQueryIndex uint64
|
|
|
|
|
|
|
|
|
|
// Provided with MinQueryIndex to wait for change.
|
|
|
|
|
MaxQueryTime time.Duration
|
|
|
|
|
|
2015-06-05 22:41:03 +00:00
|
|
|
|
// If set, any follower can service the request. Results
|
|
|
|
|
// may be arbitrarily stale.
|
|
|
|
|
AllowStale bool
|
2015-12-22 22:44:33 +00:00
|
|
|
|
|
|
|
|
|
// If set, used as prefix for resource list searches
|
|
|
|
|
Prefix string
|
2017-08-21 03:12:11 +00:00
|
|
|
|
|
2017-10-12 22:16:33 +00:00
|
|
|
|
// AuthToken is secret portion of the ACL token used for the request
|
|
|
|
|
AuthToken string
|
2018-03-27 01:10:43 +00:00
|
|
|
|
|
|
|
|
|
InternalRpcInfo
|
2015-06-05 22:41:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (q QueryOptions) RequestRegion() string {
|
|
|
|
|
return q.Region
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-07 23:56:15 +00:00
|
|
|
|
func (q QueryOptions) RequestNamespace() string {
|
|
|
|
|
if q.Namespace == "" {
|
|
|
|
|
return DefaultNamespace
|
|
|
|
|
}
|
|
|
|
|
return q.Namespace
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-05 22:41:03 +00:00
|
|
|
|
// QueryOption only applies to reads, so always true
|
|
|
|
|
func (q QueryOptions) IsRead() bool {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (q QueryOptions) AllowStaleRead() bool {
|
|
|
|
|
return q.AllowStale
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type WriteRequest struct {
|
2015-07-03 23:04:24 +00:00
|
|
|
|
// The target region for this write
|
2015-06-05 22:41:03 +00:00
|
|
|
|
Region string
|
2017-08-21 03:12:11 +00:00
|
|
|
|
|
2017-09-07 23:56:15 +00:00
|
|
|
|
// Namespace is the target namespace for the write.
|
|
|
|
|
Namespace string
|
|
|
|
|
|
2017-10-12 22:16:33 +00:00
|
|
|
|
// AuthToken is secret portion of the ACL token used for the request
|
|
|
|
|
AuthToken string
|
2018-03-27 01:10:43 +00:00
|
|
|
|
|
|
|
|
|
InternalRpcInfo
|
2015-06-05 22:41:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w WriteRequest) RequestRegion() string {
|
|
|
|
|
// The target region for this request
|
|
|
|
|
return w.Region
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-07 23:56:15 +00:00
|
|
|
|
func (w WriteRequest) RequestNamespace() string {
|
|
|
|
|
if w.Namespace == "" {
|
|
|
|
|
return DefaultNamespace
|
|
|
|
|
}
|
|
|
|
|
return w.Namespace
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-05 22:41:03 +00:00
|
|
|
|
// WriteRequest only applies to writes, always false
|
|
|
|
|
func (w WriteRequest) IsRead() bool {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w WriteRequest) AllowStaleRead() bool {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// QueryMeta allows a query response to include potentially
|
|
|
|
|
// useful metadata about a query
|
|
|
|
|
type QueryMeta struct {
|
|
|
|
|
// This is the index associated with the read
|
|
|
|
|
Index uint64
|
|
|
|
|
|
|
|
|
|
// If AllowStale is used, this is time elapsed since
|
|
|
|
|
// last contact between the follower and leader. This
|
|
|
|
|
// can be used to gauge staleness.
|
|
|
|
|
LastContact time.Duration
|
|
|
|
|
|
|
|
|
|
// Used to indicate if there is a known leader node
|
|
|
|
|
KnownLeader bool
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-13 18:19:53 +00:00
|
|
|
|
// WriteMeta allows a write response to include potentially
|
2015-07-03 23:04:24 +00:00
|
|
|
|
// useful metadata about the write
|
|
|
|
|
type WriteMeta struct {
|
|
|
|
|
// This is the index associated with the write
|
|
|
|
|
Index uint64
|
|
|
|
|
}
|
2015-06-07 18:18:59 +00:00
|
|
|
|
|
2015-09-07 03:31:32 +00:00
|
|
|
|
// NodeRegisterRequest is used for Node.Register endpoint
|
2015-06-07 18:18:59 +00:00
|
|
|
|
// to register a node as being a schedulable entity.
|
2015-07-07 16:51:42 +00:00
|
|
|
|
type NodeRegisterRequest struct {
|
2018-05-12 00:26:25 +00:00
|
|
|
|
Node *Node
|
|
|
|
|
NodeEvent *NodeEvent
|
2015-07-03 23:04:24 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-07 03:31:32 +00:00
|
|
|
|
// NodeDeregisterRequest is used for Node.Deregister endpoint
|
2019-06-26 15:08:41 +00:00
|
|
|
|
// to deregister a node as being a schedulable entity.
|
2015-07-07 16:51:42 +00:00
|
|
|
|
type NodeDeregisterRequest struct {
|
2019-06-12 20:41:26 +00:00
|
|
|
|
NodeID string
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-26 14:57:58 +00:00
|
|
|
|
// NodeBatchDeregisterRequest is used for Node.BatchDeregister endpoint
|
2019-06-12 20:41:26 +00:00
|
|
|
|
// to deregister a batch of nodes from being schedulable entities.
|
2019-06-26 14:57:58 +00:00
|
|
|
|
type NodeBatchDeregisterRequest struct {
|
2019-06-03 17:18:10 +00:00
|
|
|
|
NodeIDs []string
|
2015-07-04 01:41:36 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-23 18:09:31 +00:00
|
|
|
|
// NodeServerInfo is used to in NodeUpdateResponse to return Nomad server
|
|
|
|
|
// information used in RPC server lists.
|
|
|
|
|
type NodeServerInfo struct {
|
|
|
|
|
// RPCAdvertiseAddr is the IP endpoint that a Nomad Server wishes to
|
|
|
|
|
// be contacted at for RPCs.
|
2016-06-11 03:26:15 +00:00
|
|
|
|
RPCAdvertiseAddr string
|
2016-05-23 18:09:31 +00:00
|
|
|
|
|
2016-05-28 01:14:34 +00:00
|
|
|
|
// RpcMajorVersion is the major version number the Nomad Server
|
|
|
|
|
// supports
|
2016-06-11 03:26:15 +00:00
|
|
|
|
RPCMajorVersion int32
|
2016-05-28 01:14:34 +00:00
|
|
|
|
|
|
|
|
|
// RpcMinorVersion is the minor version number the Nomad Server
|
|
|
|
|
// supports
|
2016-06-11 03:26:15 +00:00
|
|
|
|
RPCMinorVersion int32
|
2016-05-27 10:45:09 +00:00
|
|
|
|
|
|
|
|
|
// Datacenter is the datacenter that a Nomad server belongs to
|
|
|
|
|
Datacenter string
|
2016-05-23 18:09:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-07 03:31:32 +00:00
|
|
|
|
// NodeUpdateStatusRequest is used for Node.UpdateStatus endpoint
|
2015-07-04 01:41:36 +00:00
|
|
|
|
// to update the status of a node.
|
2015-07-07 16:51:42 +00:00
|
|
|
|
type NodeUpdateStatusRequest struct {
|
2018-05-11 21:53:41 +00:00
|
|
|
|
NodeID string
|
|
|
|
|
Status string
|
|
|
|
|
NodeEvent *NodeEvent
|
2019-05-21 19:45:00 +00:00
|
|
|
|
UpdatedAt int64
|
2015-07-04 01:41:36 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-23 18:42:43 +00:00
|
|
|
|
// NodeUpdateDrainRequest is used for updating the drain strategy
|
2015-09-07 02:55:38 +00:00
|
|
|
|
type NodeUpdateDrainRequest struct {
|
2018-02-23 18:42:43 +00:00
|
|
|
|
NodeID string
|
|
|
|
|
DrainStrategy *DrainStrategy
|
2018-03-08 19:06:30 +00:00
|
|
|
|
|
2018-04-03 22:24:20 +00:00
|
|
|
|
// COMPAT Remove in version 0.10
|
|
|
|
|
// As part of Nomad 0.8 we have deprecated the drain boolean in favor of a
|
|
|
|
|
// drain strategy but we need to handle the upgrade path where the Raft log
|
|
|
|
|
// contains drain updates with just the drain boolean being manipulated.
|
|
|
|
|
Drain bool
|
|
|
|
|
|
2018-03-08 19:06:30 +00:00
|
|
|
|
// MarkEligible marks the node as eligible if removing the drain strategy.
|
|
|
|
|
MarkEligible bool
|
2018-05-10 23:30:54 +00:00
|
|
|
|
|
|
|
|
|
// NodeEvent is the event added to the node
|
|
|
|
|
NodeEvent *NodeEvent
|
|
|
|
|
|
2019-05-21 19:45:00 +00:00
|
|
|
|
// UpdatedAt represents server time of receiving request
|
|
|
|
|
UpdatedAt int64
|
|
|
|
|
|
2015-09-07 02:55:38 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-09 22:15:21 +00:00
|
|
|
|
// BatchNodeUpdateDrainRequest is used for updating the drain strategy for a
|
|
|
|
|
// batch of nodes
|
|
|
|
|
type BatchNodeUpdateDrainRequest struct {
|
|
|
|
|
// Updates is a mapping of nodes to their updated drain strategy
|
|
|
|
|
Updates map[string]*DrainUpdate
|
2018-05-10 23:30:54 +00:00
|
|
|
|
|
|
|
|
|
// NodeEvents is a mapping of the node to the event to add to the node
|
|
|
|
|
NodeEvents map[string]*NodeEvent
|
|
|
|
|
|
2019-05-21 19:45:00 +00:00
|
|
|
|
// UpdatedAt represents server time of receiving request
|
|
|
|
|
UpdatedAt int64
|
|
|
|
|
|
2018-03-09 22:15:21 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DrainUpdate is used to update the drain of a node
|
|
|
|
|
type DrainUpdate struct {
|
|
|
|
|
// DrainStrategy is the new strategy for the node
|
|
|
|
|
DrainStrategy *DrainStrategy
|
|
|
|
|
|
|
|
|
|
// MarkEligible marks the node as eligible if removing the drain strategy.
|
|
|
|
|
MarkEligible bool
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-27 00:34:42 +00:00
|
|
|
|
// NodeUpdateEligibilityRequest is used for updating the scheduling eligibility
|
|
|
|
|
type NodeUpdateEligibilityRequest struct {
|
|
|
|
|
NodeID string
|
|
|
|
|
Eligibility string
|
2018-05-11 21:32:34 +00:00
|
|
|
|
|
|
|
|
|
// NodeEvent is the event added to the node
|
|
|
|
|
NodeEvent *NodeEvent
|
|
|
|
|
|
2019-05-21 19:45:00 +00:00
|
|
|
|
// UpdatedAt represents server time of receiving request
|
|
|
|
|
UpdatedAt int64
|
|
|
|
|
|
2015-09-07 02:55:38 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-13 17:36:03 +00:00
|
|
|
|
// NodeEvaluateRequest is used to re-evaluate the node
|
2015-08-16 01:20:35 +00:00
|
|
|
|
type NodeEvaluateRequest struct {
|
|
|
|
|
NodeID string
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-06 21:23:15 +00:00
|
|
|
|
// NodeSpecificRequest is used when we just need to specify a target node
|
|
|
|
|
type NodeSpecificRequest struct {
|
2016-08-16 06:11:57 +00:00
|
|
|
|
NodeID string
|
|
|
|
|
SecretID string
|
2015-08-16 01:20:35 +00:00
|
|
|
|
QueryOptions
|
2015-07-06 21:23:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-10 19:24:11 +00:00
|
|
|
|
// SearchResponse is used to return matches and information about whether
|
2017-08-04 15:08:12 +00:00
|
|
|
|
// the match list is truncated specific to each type of context.
|
2017-08-10 19:24:11 +00:00
|
|
|
|
type SearchResponse struct {
|
2017-08-10 16:40:14 +00:00
|
|
|
|
// Map of context types to ids which match a specified prefix
|
2017-08-11 21:21:35 +00:00
|
|
|
|
Matches map[Context][]string
|
2017-08-07 14:16:24 +00:00
|
|
|
|
|
|
|
|
|
// Truncations indicates whether the matches for a particular context have
|
|
|
|
|
// been truncated
|
2017-08-11 21:21:35 +00:00
|
|
|
|
Truncations map[Context]bool
|
2017-08-07 14:16:24 +00:00
|
|
|
|
|
2017-07-28 21:48:15 +00:00
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-10 19:24:11 +00:00
|
|
|
|
// SearchRequest is used to parameterize a request, and returns a
|
2017-08-10 16:40:14 +00:00
|
|
|
|
// list of matches made up of jobs, allocations, evaluations, and/or nodes,
|
|
|
|
|
// along with whether or not the information returned is truncated.
|
2017-08-10 19:24:11 +00:00
|
|
|
|
type SearchRequest struct {
|
2017-08-10 16:40:14 +00:00
|
|
|
|
// Prefix is what ids are matched to. I.e, if the given prefix were
|
2017-08-04 22:18:49 +00:00
|
|
|
|
// "a", potential matches might be "abcd" or "aabb"
|
|
|
|
|
Prefix string
|
2017-08-07 14:16:24 +00:00
|
|
|
|
|
2017-08-10 16:40:14 +00:00
|
|
|
|
// Context is the type that can be matched against. A context can be a job,
|
|
|
|
|
// node, evaluation, allocation, or empty (indicated every context should be
|
2017-08-04 22:18:49 +00:00
|
|
|
|
// matched)
|
2017-08-11 21:21:35 +00:00
|
|
|
|
Context Context
|
2017-08-26 22:56:13 +00:00
|
|
|
|
|
|
|
|
|
QueryOptions
|
2017-07-28 21:48:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-07-07 16:51:42 +00:00
|
|
|
|
// JobRegisterRequest is used for Job.Register endpoint
|
|
|
|
|
// to register a job as being a schedulable entity.
|
|
|
|
|
type JobRegisterRequest struct {
|
|
|
|
|
Job *Job
|
2016-06-08 23:48:02 +00:00
|
|
|
|
|
|
|
|
|
// If EnforceIndex is set then the job will only be registered if the passed
|
|
|
|
|
// JobModifyIndex matches the current Jobs index. If the index is zero, the
|
|
|
|
|
// register only occurs if the job is new.
|
|
|
|
|
EnforceIndex bool
|
|
|
|
|
JobModifyIndex uint64
|
|
|
|
|
|
2017-09-19 14:47:10 +00:00
|
|
|
|
// PolicyOverride is set when the user is attempting to override any policies
|
|
|
|
|
PolicyOverride bool
|
|
|
|
|
|
2015-07-07 16:51:42 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// JobDeregisterRequest is used for Job.Deregister endpoint
|
|
|
|
|
// to deregister a job as being a schedulable entity.
|
|
|
|
|
type JobDeregisterRequest struct {
|
2015-07-23 22:15:48 +00:00
|
|
|
|
JobID string
|
2017-04-15 03:54:30 +00:00
|
|
|
|
|
|
|
|
|
// Purge controls whether the deregister purges the job from the system or
|
|
|
|
|
// whether the job is just marked as stopped and will be removed by the
|
|
|
|
|
// garbage collector
|
|
|
|
|
Purge bool
|
|
|
|
|
|
2015-07-07 16:51:42 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-14 22:32:18 +00:00
|
|
|
|
// JobBatchDeregisterRequest is used to batch deregister jobs and upsert
|
|
|
|
|
// evaluations.
|
|
|
|
|
type JobBatchDeregisterRequest struct {
|
|
|
|
|
// Jobs is the set of jobs to deregister
|
|
|
|
|
Jobs map[NamespacedID]*JobDeregisterOptions
|
|
|
|
|
|
|
|
|
|
// Evals is the set of evaluations to create.
|
|
|
|
|
Evals []*Evaluation
|
|
|
|
|
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// JobDeregisterOptions configures how a job is deregistered.
|
|
|
|
|
type JobDeregisterOptions struct {
|
|
|
|
|
// Purge controls whether the deregister purges the job from the system or
|
|
|
|
|
// whether the job is just marked as stopped and will be removed by the
|
|
|
|
|
// garbage collector
|
|
|
|
|
Purge bool
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-16 01:11:26 +00:00
|
|
|
|
// JobEvaluateRequest is used when we just need to re-evaluate a target job
|
|
|
|
|
type JobEvaluateRequest struct {
|
2018-05-08 22:26:36 +00:00
|
|
|
|
JobID string
|
|
|
|
|
EvalOptions EvalOptions
|
2015-08-16 01:11:26 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-09 20:04:27 +00:00
|
|
|
|
// EvalOptions is used to encapsulate options when forcing a job evaluation
|
2018-05-08 22:26:36 +00:00
|
|
|
|
type EvalOptions struct {
|
|
|
|
|
ForceReschedule bool
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-23 21:41:18 +00:00
|
|
|
|
// JobSpecificRequest is used when we just need to specify a target job
|
|
|
|
|
type JobSpecificRequest struct {
|
2019-05-10 22:15:27 +00:00
|
|
|
|
JobID string
|
|
|
|
|
All bool
|
2015-08-15 19:59:10 +00:00
|
|
|
|
QueryOptions
|
2015-07-23 21:41:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 19:18:45 +00:00
|
|
|
|
// JobListRequest is used to parameterize a list request
|
|
|
|
|
type JobListRequest struct {
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-05 18:21:58 +00:00
|
|
|
|
// JobPlanRequest is used for the Job.Plan endpoint to trigger a dry-run
|
|
|
|
|
// evaluation of the Job.
|
|
|
|
|
type JobPlanRequest struct {
|
|
|
|
|
Job *Job
|
|
|
|
|
Diff bool // Toggles an annotated diff
|
2017-09-19 14:47:10 +00:00
|
|
|
|
// PolicyOverride is set when the user is attempting to override any policies
|
|
|
|
|
PolicyOverride bool
|
2016-05-05 18:21:58 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-18 23:51:47 +00:00
|
|
|
|
// JobSummaryRequest is used when we just need to get a specific job summary
|
|
|
|
|
type JobSummaryRequest struct {
|
|
|
|
|
JobID string
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-20 18:33:52 +00:00
|
|
|
|
// JobDispatchRequest is used to dispatch a job based on a parameterized job
|
2016-11-26 02:04:55 +00:00
|
|
|
|
type JobDispatchRequest struct {
|
2016-12-14 20:50:08 +00:00
|
|
|
|
JobID string
|
|
|
|
|
Payload []byte
|
|
|
|
|
Meta map[string]string
|
2016-11-26 02:04:55 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-06 19:48:28 +00:00
|
|
|
|
// JobValidateRequest is used to validate a job
|
|
|
|
|
type JobValidateRequest struct {
|
|
|
|
|
Job *Job
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-18 20:09:24 +00:00
|
|
|
|
// JobRevertRequest is used to revert a job to a prior version.
|
|
|
|
|
type JobRevertRequest struct {
|
|
|
|
|
// JobID is the ID of the job being reverted
|
|
|
|
|
JobID string
|
2017-02-06 19:48:28 +00:00
|
|
|
|
|
2017-04-18 20:09:24 +00:00
|
|
|
|
// JobVersion the version to revert to.
|
|
|
|
|
JobVersion uint64
|
2017-03-03 23:00:39 +00:00
|
|
|
|
|
2017-04-18 20:09:24 +00:00
|
|
|
|
// EnforcePriorVersion if set will enforce that the job is at the given
|
|
|
|
|
// version before reverting.
|
|
|
|
|
EnforcePriorVersion *uint64
|
|
|
|
|
|
2019-03-25 23:12:39 +00:00
|
|
|
|
// VaultToken is the Vault token that proves the submitter of the job revert
|
|
|
|
|
// has access to any Vault policies specified in the targeted job version. This
|
|
|
|
|
// field is only used to transfer the token and is not stored after the Job
|
|
|
|
|
// revert.
|
|
|
|
|
VaultToken string
|
|
|
|
|
|
2017-04-18 20:09:24 +00:00
|
|
|
|
WriteRequest
|
2017-02-06 19:48:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-06 19:49:13 +00:00
|
|
|
|
// JobStabilityRequest is used to marked a job as stable.
|
|
|
|
|
type JobStabilityRequest struct {
|
|
|
|
|
// Job to set the stability on
|
|
|
|
|
JobID string
|
|
|
|
|
JobVersion uint64
|
|
|
|
|
|
|
|
|
|
// Set the stability
|
|
|
|
|
Stable bool
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// JobStabilityResponse is the response when marking a job as stable.
|
|
|
|
|
type JobStabilityResponse struct {
|
|
|
|
|
WriteMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 21:28:29 +00:00
|
|
|
|
// NodeListRequest is used to parameterize a list request
|
|
|
|
|
type NodeListRequest struct {
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-06 21:51:15 +00:00
|
|
|
|
// EvalUpdateRequest is used for upserting evaluations.
|
2015-07-23 22:52:38 +00:00
|
|
|
|
type EvalUpdateRequest struct {
|
2015-08-15 21:22:21 +00:00
|
|
|
|
Evals []*Evaluation
|
|
|
|
|
EvalToken string
|
2015-07-23 22:52:38 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EvalDeleteRequest is used for deleting an evaluation.
|
|
|
|
|
type EvalDeleteRequest struct {
|
2015-08-15 23:07:22 +00:00
|
|
|
|
Evals []string
|
|
|
|
|
Allocs []string
|
2015-07-23 22:52:38 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-23 23:00:19 +00:00
|
|
|
|
// EvalSpecificRequest is used when we just need to specify a target evaluation
|
|
|
|
|
type EvalSpecificRequest struct {
|
|
|
|
|
EvalID string
|
2015-08-16 01:20:35 +00:00
|
|
|
|
QueryOptions
|
2015-07-23 23:00:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-12 22:25:31 +00:00
|
|
|
|
// EvalAckRequest is used to Ack/Nack a specific evaluation
|
|
|
|
|
type EvalAckRequest struct {
|
|
|
|
|
EvalID string
|
|
|
|
|
Token string
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-24 04:58:51 +00:00
|
|
|
|
// EvalDequeueRequest is used when we want to dequeue an evaluation
|
|
|
|
|
type EvalDequeueRequest struct {
|
2016-10-26 21:52:48 +00:00
|
|
|
|
Schedulers []string
|
|
|
|
|
Timeout time.Duration
|
|
|
|
|
SchedulerVersion uint16
|
2015-07-24 04:58:51 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 23:01:16 +00:00
|
|
|
|
// EvalListRequest is used to list the evaluations
|
|
|
|
|
type EvalListRequest struct {
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-27 22:31:49 +00:00
|
|
|
|
// PlanRequest is used to submit an allocation plan to the leader
|
|
|
|
|
type PlanRequest struct {
|
|
|
|
|
Plan *Plan
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-05 20:52:01 +00:00
|
|
|
|
// ApplyPlanResultsRequest is used by the planner to apply a Raft transaction
|
|
|
|
|
// committing the result of a plan.
|
|
|
|
|
type ApplyPlanResultsRequest struct {
|
|
|
|
|
// AllocUpdateRequest holds the allocation updates to be made by the
|
|
|
|
|
// scheduler.
|
|
|
|
|
AllocUpdateRequest
|
|
|
|
|
|
2017-07-04 20:31:01 +00:00
|
|
|
|
// Deployment is the deployment created or updated as a result of a
|
|
|
|
|
// scheduling event.
|
|
|
|
|
Deployment *Deployment
|
2017-05-11 19:49:04 +00:00
|
|
|
|
|
|
|
|
|
// DeploymentUpdates is a set of status updates to apply to the given
|
|
|
|
|
// deployments. This allows the scheduler to cancel any unneeded deployment
|
|
|
|
|
// because the job is stopped or the update block is removed.
|
|
|
|
|
DeploymentUpdates []*DeploymentStatusUpdate
|
2017-12-18 16:03:55 +00:00
|
|
|
|
|
2017-12-18 21:13:16 +00:00
|
|
|
|
// EvalID is the eval ID of the plan being applied. The modify index of the
|
|
|
|
|
// evaluation is updated as part of applying the plan to ensure that subsequent
|
|
|
|
|
// scheduling events for the same job will wait for the index that last produced
|
|
|
|
|
// state changes. This is necessary for blocked evaluations since they can be
|
|
|
|
|
// processed many times, potentially making state updates, without the state of
|
|
|
|
|
// the evaluation itself being updated.
|
2017-12-18 16:03:55 +00:00
|
|
|
|
EvalID string
|
2018-09-10 17:38:36 +00:00
|
|
|
|
|
2019-04-11 00:15:04 +00:00
|
|
|
|
// COMPAT 0.11
|
|
|
|
|
// NodePreemptions is a slice of allocations from other lower priority jobs
|
|
|
|
|
// that are preempted. Preempted allocations are marked as evicted.
|
|
|
|
|
// Deprecated: Replaced with AllocsPreempted which contains only the diff
|
|
|
|
|
NodePreemptions []*Allocation
|
|
|
|
|
|
|
|
|
|
// AllocsPreempted is a slice of allocation diffs from other lower priority jobs
|
2018-09-10 18:13:10 +00:00
|
|
|
|
// that are preempted. Preempted allocations are marked as evicted.
|
2019-04-11 00:15:04 +00:00
|
|
|
|
AllocsPreempted []*AllocationDiff
|
2018-09-10 17:38:36 +00:00
|
|
|
|
|
|
|
|
|
// PreemptionEvals is a slice of follow up evals for jobs whose allocations
|
|
|
|
|
// have been preempted to place allocs in this plan
|
|
|
|
|
PreemptionEvals []*Evaluation
|
2017-05-05 20:52:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-04 21:04:33 +00:00
|
|
|
|
// AllocUpdateRequest is used to submit changes to allocations, either
|
2018-03-11 17:37:22 +00:00
|
|
|
|
// to cause evictions or to assign new allocations. Both can be done
|
2015-08-04 21:04:33 +00:00
|
|
|
|
// within a single transaction
|
|
|
|
|
type AllocUpdateRequest struct {
|
2019-03-08 11:18:56 +00:00
|
|
|
|
// COMPAT 0.11
|
2015-08-04 21:04:33 +00:00
|
|
|
|
// Alloc is the list of new allocations to assign
|
2019-03-04 09:49:32 +00:00
|
|
|
|
// Deprecated: Replaced with two separate slices, one containing stopped allocations
|
|
|
|
|
// and another containing updated allocations
|
2015-08-04 21:04:33 +00:00
|
|
|
|
Alloc []*Allocation
|
2016-02-21 19:31:27 +00:00
|
|
|
|
|
2019-03-04 09:49:32 +00:00
|
|
|
|
// Allocations to stop. Contains only the diff, not the entire allocation
|
|
|
|
|
AllocsStopped []*AllocationDiff
|
|
|
|
|
|
|
|
|
|
// New or updated allocations
|
|
|
|
|
AllocsUpdated []*Allocation
|
|
|
|
|
|
2018-01-16 14:55:35 +00:00
|
|
|
|
// Evals is the list of new evaluations to create
|
|
|
|
|
// Evals are valid only when used in the Raft RPC
|
|
|
|
|
Evals []*Evaluation
|
|
|
|
|
|
2016-02-21 19:31:27 +00:00
|
|
|
|
// Job is the shared parent job of the allocations.
|
|
|
|
|
// It is pulled out since it is common to reduce payload size.
|
|
|
|
|
Job *Job
|
|
|
|
|
|
2015-08-26 01:12:51 +00:00
|
|
|
|
WriteRequest
|
2015-08-04 21:04:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-23 01:38:44 +00:00
|
|
|
|
// AllocUpdateDesiredTransitionRequest is used to submit changes to allocations
|
|
|
|
|
// desired transition state.
|
|
|
|
|
type AllocUpdateDesiredTransitionRequest struct {
|
2018-02-21 18:58:04 +00:00
|
|
|
|
// Allocs is the mapping of allocation ids to their desired state
|
2018-02-23 01:38:44 +00:00
|
|
|
|
// transition
|
|
|
|
|
Allocs map[string]*DesiredTransition
|
|
|
|
|
|
|
|
|
|
// Evals is the set of evaluations to create
|
|
|
|
|
Evals []*Evaluation
|
2018-02-21 18:58:04 +00:00
|
|
|
|
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-01 14:21:03 +00:00
|
|
|
|
// AllocStopRequest is used to stop and reschedule a running Allocation.
|
|
|
|
|
type AllocStopRequest struct {
|
|
|
|
|
AllocID string
|
|
|
|
|
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AllocStopResponse is the response to an `AllocStopRequest`
|
|
|
|
|
type AllocStopResponse struct {
|
|
|
|
|
// EvalID is the id of the follow up evalution for the rescheduled alloc.
|
|
|
|
|
EvalID string
|
|
|
|
|
|
|
|
|
|
WriteMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 22:34:28 +00:00
|
|
|
|
// AllocListRequest is used to request a list of allocations
|
|
|
|
|
type AllocListRequest struct {
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 22:46:45 +00:00
|
|
|
|
// AllocSpecificRequest is used to query a specific allocation
|
|
|
|
|
type AllocSpecificRequest struct {
|
|
|
|
|
AllocID string
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-03 10:46:15 +00:00
|
|
|
|
// AllocSignalRequest is used to signal a specific allocation
|
|
|
|
|
type AllocSignalRequest struct {
|
|
|
|
|
AllocID string
|
|
|
|
|
Task string
|
|
|
|
|
Signal string
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-27 06:48:34 +00:00
|
|
|
|
// AllocsGetRequest is used to query a set of allocations
|
2016-02-01 21:57:35 +00:00
|
|
|
|
type AllocsGetRequest struct {
|
|
|
|
|
AllocIDs []string
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-01 12:56:02 +00:00
|
|
|
|
// AllocRestartRequest is used to restart a specific allocations tasks.
|
|
|
|
|
type AllocRestartRequest struct {
|
|
|
|
|
AllocID string
|
|
|
|
|
TaskName string
|
|
|
|
|
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-11 18:42:43 +00:00
|
|
|
|
// PeriodicForceRequest is used to force a specific periodic job.
|
2016-01-13 18:19:53 +00:00
|
|
|
|
type PeriodicForceRequest struct {
|
|
|
|
|
JobID string
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-03 21:14:52 +00:00
|
|
|
|
// ServerMembersResponse has the list of servers in a cluster
|
|
|
|
|
type ServerMembersResponse struct {
|
|
|
|
|
ServerName string
|
|
|
|
|
ServerRegion string
|
|
|
|
|
ServerDC string
|
|
|
|
|
Members []*ServerMember
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ServerMember holds information about a Nomad server agent in a cluster
|
|
|
|
|
type ServerMember struct {
|
|
|
|
|
Name string
|
|
|
|
|
Addr net.IP
|
|
|
|
|
Port uint16
|
|
|
|
|
Tags map[string]string
|
|
|
|
|
Status string
|
|
|
|
|
ProtocolMin uint8
|
|
|
|
|
ProtocolMax uint8
|
|
|
|
|
ProtocolCur uint8
|
|
|
|
|
DelegateMin uint8
|
|
|
|
|
DelegateMax uint8
|
|
|
|
|
DelegateCur uint8
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-18 21:31:44 +00:00
|
|
|
|
// DeriveVaultTokenRequest is used to request wrapped Vault tokens for the
|
|
|
|
|
// following tasks in the given allocation
|
2016-08-18 17:50:47 +00:00
|
|
|
|
type DeriveVaultTokenRequest struct {
|
|
|
|
|
NodeID string
|
|
|
|
|
SecretID string
|
|
|
|
|
AllocID string
|
|
|
|
|
Tasks []string
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-22 20:57:27 +00:00
|
|
|
|
// VaultAccessorsRequest is used to operate on a set of Vault accessors
|
|
|
|
|
type VaultAccessorsRequest struct {
|
2016-08-19 01:14:58 +00:00
|
|
|
|
Accessors []*VaultAccessor
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// VaultAccessor is a reference to a created Vault token on behalf of
|
|
|
|
|
// an allocation's task.
|
|
|
|
|
type VaultAccessor struct {
|
|
|
|
|
AllocID string
|
|
|
|
|
Task string
|
|
|
|
|
NodeID string
|
|
|
|
|
Accessor string
|
2016-08-19 01:57:33 +00:00
|
|
|
|
CreationTTL int
|
2016-08-19 01:14:58 +00:00
|
|
|
|
|
|
|
|
|
// Raft Indexes
|
|
|
|
|
CreateIndex uint64
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-18 21:31:44 +00:00
|
|
|
|
// DeriveVaultTokenResponse returns the wrapped tokens for each requested task
|
2016-08-18 17:50:47 +00:00
|
|
|
|
type DeriveVaultTokenResponse struct {
|
2016-08-19 01:57:33 +00:00
|
|
|
|
// Tasks is a mapping between the task name and the wrapped token
|
2016-08-18 21:31:44 +00:00
|
|
|
|
Tasks map[string]string
|
2016-10-23 01:08:30 +00:00
|
|
|
|
|
2017-08-07 21:13:05 +00:00
|
|
|
|
// Error stores any error that occurred. Errors are stored here so we can
|
2016-10-23 01:08:30 +00:00
|
|
|
|
// communicate whether it is retriable
|
|
|
|
|
Error *RecoverableError
|
|
|
|
|
|
2016-08-18 17:50:47 +00:00
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-15 19:59:10 +00:00
|
|
|
|
// GenericRequest is used to request where no
|
|
|
|
|
// specific information is needed.
|
|
|
|
|
type GenericRequest struct {
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-27 18:15:07 +00:00
|
|
|
|
// DeploymentListRequest is used to list the deployments
|
|
|
|
|
type DeploymentListRequest struct {
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-29 19:32:37 +00:00
|
|
|
|
// DeploymentDeleteRequest is used for deleting deployments.
|
|
|
|
|
type DeploymentDeleteRequest struct {
|
|
|
|
|
Deployments []string
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-26 21:23:52 +00:00
|
|
|
|
// DeploymentStatusUpdateRequest is used to update the status of a deployment as
|
|
|
|
|
// well as optionally creating an evaluation atomically.
|
|
|
|
|
type DeploymentStatusUpdateRequest struct {
|
|
|
|
|
// Eval, if set, is used to create an evaluation at the same time as
|
|
|
|
|
// updating the status of a deployment.
|
|
|
|
|
Eval *Evaluation
|
|
|
|
|
|
|
|
|
|
// DeploymentUpdate is a status update to apply to the given
|
|
|
|
|
// deployment.
|
|
|
|
|
DeploymentUpdate *DeploymentStatusUpdate
|
|
|
|
|
|
|
|
|
|
// Job is used to optionally upsert a job. This is used when setting the
|
|
|
|
|
// allocation health results in a deployment failure and the deployment
|
|
|
|
|
// auto-reverts to the latest stable job.
|
|
|
|
|
Job *Job
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DeploymentAllocHealthRequest is used to set the health of a set of
|
|
|
|
|
// allocations as part of a deployment.
|
|
|
|
|
type DeploymentAllocHealthRequest struct {
|
|
|
|
|
DeploymentID string
|
|
|
|
|
|
|
|
|
|
// Marks these allocations as healthy, allow further allocations
|
|
|
|
|
// to be rolled.
|
|
|
|
|
HealthyAllocationIDs []string
|
|
|
|
|
|
|
|
|
|
// Any unhealthy allocations fail the deployment
|
|
|
|
|
UnhealthyAllocationIDs []string
|
2017-06-29 05:00:18 +00:00
|
|
|
|
|
|
|
|
|
WriteRequest
|
2017-06-26 21:23:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ApplyDeploymentAllocHealthRequest is used to apply an alloc health request via Raft
|
|
|
|
|
type ApplyDeploymentAllocHealthRequest struct {
|
|
|
|
|
DeploymentAllocHealthRequest
|
|
|
|
|
|
2018-04-06 20:11:58 +00:00
|
|
|
|
// Timestamp is the timestamp to use when setting the allocations health.
|
|
|
|
|
Timestamp time.Time
|
|
|
|
|
|
2017-06-26 21:23:52 +00:00
|
|
|
|
// An optional field to update the status of a deployment
|
|
|
|
|
DeploymentUpdate *DeploymentStatusUpdate
|
|
|
|
|
|
|
|
|
|
// Job is used to optionally upsert a job. This is used when setting the
|
|
|
|
|
// allocation health results in a deployment failure and the deployment
|
|
|
|
|
// auto-reverts to the latest stable job.
|
|
|
|
|
Job *Job
|
|
|
|
|
|
|
|
|
|
// An optional evaluation to create after promoting the canaries
|
|
|
|
|
Eval *Evaluation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DeploymentPromoteRequest is used to promote task groups in a deployment
|
|
|
|
|
type DeploymentPromoteRequest struct {
|
|
|
|
|
DeploymentID string
|
|
|
|
|
|
|
|
|
|
// All is to promote all task groups
|
|
|
|
|
All bool
|
|
|
|
|
|
|
|
|
|
// Groups is used to set the promotion status per task group
|
2017-06-29 22:15:21 +00:00
|
|
|
|
Groups []string
|
2017-06-29 05:00:18 +00:00
|
|
|
|
|
|
|
|
|
WriteRequest
|
2017-06-26 21:23:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ApplyDeploymentPromoteRequest is used to apply a promotion request via Raft
|
|
|
|
|
type ApplyDeploymentPromoteRequest struct {
|
|
|
|
|
DeploymentPromoteRequest
|
|
|
|
|
|
|
|
|
|
// An optional evaluation to create after promoting the canaries
|
|
|
|
|
Eval *Evaluation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DeploymentPauseRequest is used to pause a deployment
|
|
|
|
|
type DeploymentPauseRequest struct {
|
|
|
|
|
DeploymentID string
|
|
|
|
|
|
|
|
|
|
// Pause sets the pause status
|
|
|
|
|
Pause bool
|
2017-06-29 05:00:18 +00:00
|
|
|
|
|
|
|
|
|
WriteRequest
|
2017-06-26 21:23:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DeploymentSpecificRequest is used to make a request specific to a particular
|
|
|
|
|
// deployment
|
|
|
|
|
type DeploymentSpecificRequest struct {
|
|
|
|
|
DeploymentID string
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-29 05:00:18 +00:00
|
|
|
|
// DeploymentFailRequest is used to fail a particular deployment
|
|
|
|
|
type DeploymentFailRequest struct {
|
|
|
|
|
DeploymentID string
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SingleDeploymentResponse is used to respond with a single deployment
|
|
|
|
|
type SingleDeploymentResponse struct {
|
|
|
|
|
Deployment *Deployment
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-04 01:41:36 +00:00
|
|
|
|
// GenericResponse is used to respond to a request where no
|
|
|
|
|
// specific response information is needed.
|
|
|
|
|
type GenericResponse struct {
|
2015-07-03 23:04:24 +00:00
|
|
|
|
WriteMeta
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-11 18:48:24 +00:00
|
|
|
|
// VersionResponse is used for the Status.Version response
|
2015-08-15 19:59:10 +00:00
|
|
|
|
type VersionResponse struct {
|
2015-08-15 20:08:06 +00:00
|
|
|
|
Build string
|
|
|
|
|
Versions map[string]int
|
2015-08-15 19:59:10 +00:00
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-06 18:48:44 +00:00
|
|
|
|
// JobRegisterResponse is used to respond to a job registration
|
|
|
|
|
type JobRegisterResponse struct {
|
|
|
|
|
EvalID string
|
|
|
|
|
EvalCreateIndex uint64
|
|
|
|
|
JobModifyIndex uint64
|
2017-05-10 03:52:47 +00:00
|
|
|
|
|
|
|
|
|
// Warnings contains any warnings about the given job. These may include
|
|
|
|
|
// deprecation warnings.
|
|
|
|
|
Warnings string
|
|
|
|
|
|
2015-08-06 18:48:44 +00:00
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-06 21:17:18 +00:00
|
|
|
|
// JobDeregisterResponse is used to respond to a job deregistration
|
|
|
|
|
type JobDeregisterResponse struct {
|
|
|
|
|
EvalID string
|
|
|
|
|
EvalCreateIndex uint64
|
|
|
|
|
JobModifyIndex uint64
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-14 22:32:18 +00:00
|
|
|
|
// JobBatchDeregisterResponse is used to respond to a batch job deregistration
|
|
|
|
|
type JobBatchDeregisterResponse struct {
|
|
|
|
|
// JobEvals maps the job to its created evaluation
|
|
|
|
|
JobEvals map[NamespacedID]string
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-18 20:09:24 +00:00
|
|
|
|
// JobValidateResponse is the response from validate request
|
|
|
|
|
type JobValidateResponse struct {
|
|
|
|
|
// DriverConfigValidated indicates whether the agent validated the driver
|
|
|
|
|
// config
|
|
|
|
|
DriverConfigValidated bool
|
|
|
|
|
|
|
|
|
|
// ValidationErrors is a list of validation errors
|
|
|
|
|
ValidationErrors []string
|
|
|
|
|
|
2017-08-07 21:13:05 +00:00
|
|
|
|
// Error is a string version of any error that may have occurred
|
2017-04-18 20:09:24 +00:00
|
|
|
|
Error string
|
2017-05-10 03:52:47 +00:00
|
|
|
|
|
|
|
|
|
// Warnings contains any warnings about the given job. These may include
|
|
|
|
|
// deprecation warnings.
|
|
|
|
|
Warnings string
|
2017-04-18 20:09:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-06 23:39:20 +00:00
|
|
|
|
// NodeUpdateResponse is used to respond to a node update
|
|
|
|
|
type NodeUpdateResponse struct {
|
2015-08-23 00:37:50 +00:00
|
|
|
|
HeartbeatTTL time.Duration
|
2015-08-06 23:39:20 +00:00
|
|
|
|
EvalIDs []string
|
|
|
|
|
EvalCreateIndex uint64
|
|
|
|
|
NodeModifyIndex uint64
|
2016-05-23 18:09:31 +00:00
|
|
|
|
|
|
|
|
|
// LeaderRPCAddr is the RPC address of the current Raft Leader. If
|
|
|
|
|
// empty, the current Nomad Server is in the minority of a partition.
|
|
|
|
|
LeaderRPCAddr string
|
|
|
|
|
|
|
|
|
|
// NumNodes is the number of Nomad nodes attached to this quorum of
|
|
|
|
|
// Nomad Servers at the time of the response. This value can
|
|
|
|
|
// fluctuate based on the health of the cluster between heartbeats.
|
|
|
|
|
NumNodes int32
|
|
|
|
|
|
|
|
|
|
// Servers is the full list of known Nomad servers in the local
|
|
|
|
|
// region.
|
|
|
|
|
Servers []*NodeServerInfo
|
|
|
|
|
|
2015-08-06 23:39:20 +00:00
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-07 03:00:12 +00:00
|
|
|
|
// NodeDrainUpdateResponse is used to respond to a node drain update
|
|
|
|
|
type NodeDrainUpdateResponse struct {
|
|
|
|
|
NodeModifyIndex uint64
|
2018-03-27 22:53:24 +00:00
|
|
|
|
EvalIDs []string
|
|
|
|
|
EvalCreateIndex uint64
|
|
|
|
|
WriteMeta
|
|
|
|
|
}
|
2018-02-23 18:42:43 +00:00
|
|
|
|
|
2018-03-27 22:53:24 +00:00
|
|
|
|
// NodeEligibilityUpdateResponse is used to respond to a node eligibility update
|
|
|
|
|
type NodeEligibilityUpdateResponse struct {
|
|
|
|
|
NodeModifyIndex uint64
|
2018-02-23 18:42:43 +00:00
|
|
|
|
EvalIDs []string
|
|
|
|
|
EvalCreateIndex uint64
|
2018-03-27 22:53:24 +00:00
|
|
|
|
WriteMeta
|
2015-09-07 03:00:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-23 02:17:49 +00:00
|
|
|
|
// NodeAllocsResponse is used to return allocs for a single node
|
|
|
|
|
type NodeAllocsResponse struct {
|
|
|
|
|
Allocs []*Allocation
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-29 14:29:52 +00:00
|
|
|
|
// NodeClientAllocsResponse is used to return allocs meta data for a single node
|
|
|
|
|
type NodeClientAllocsResponse struct {
|
|
|
|
|
Allocs map[string]uint64
|
2017-10-02 19:18:33 +00:00
|
|
|
|
|
2017-10-10 00:23:26 +00:00
|
|
|
|
// MigrateTokens are used when ACLs are enabled to allow cross node,
|
|
|
|
|
// authenticated access to sticky volumes
|
2017-10-02 19:18:33 +00:00
|
|
|
|
MigrateTokens map[string]string
|
|
|
|
|
|
2016-01-29 14:29:52 +00:00
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-06 21:23:15 +00:00
|
|
|
|
// SingleNodeResponse is used to return a single node
|
|
|
|
|
type SingleNodeResponse struct {
|
|
|
|
|
Node *Node
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-13 22:47:59 +00:00
|
|
|
|
// NodeListResponse is used for a list request
|
2015-09-06 21:28:29 +00:00
|
|
|
|
type NodeListResponse struct {
|
|
|
|
|
Nodes []*NodeListStub
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-23 21:41:18 +00:00
|
|
|
|
// SingleJobResponse is used to return a single job
|
|
|
|
|
type SingleJobResponse struct {
|
|
|
|
|
Job *Job
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-22 06:13:07 +00:00
|
|
|
|
// JobSummaryResponse is used to return a single job summary
|
2016-07-21 21:43:21 +00:00
|
|
|
|
type JobSummaryResponse struct {
|
2016-07-18 23:51:47 +00:00
|
|
|
|
JobSummary *JobSummary
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-26 02:04:55 +00:00
|
|
|
|
type JobDispatchResponse struct {
|
|
|
|
|
DispatchedJobID string
|
|
|
|
|
EvalID string
|
|
|
|
|
EvalCreateIndex uint64
|
|
|
|
|
JobCreateIndex uint64
|
2017-02-17 19:43:14 +00:00
|
|
|
|
WriteMeta
|
2016-11-26 02:04:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 19:18:45 +00:00
|
|
|
|
// JobListResponse is used for a list request
|
|
|
|
|
type JobListResponse struct {
|
|
|
|
|
Jobs []*JobListStub
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 00:16:20 +00:00
|
|
|
|
// JobVersionsRequest is used to get a jobs versions
|
|
|
|
|
type JobVersionsRequest struct {
|
|
|
|
|
JobID string
|
|
|
|
|
Diffs bool
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-13 22:47:59 +00:00
|
|
|
|
// JobVersionsResponse is used for a job get versions request
|
|
|
|
|
type JobVersionsResponse struct {
|
|
|
|
|
Versions []*Job
|
2017-06-30 00:16:20 +00:00
|
|
|
|
Diffs []*JobDiff
|
2017-04-13 22:47:59 +00:00
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-05 18:21:58 +00:00
|
|
|
|
// JobPlanResponse is used to respond to a job plan request
|
|
|
|
|
type JobPlanResponse struct {
|
2016-05-12 18:29:38 +00:00
|
|
|
|
// Annotations stores annotations explaining decisions the scheduler made.
|
|
|
|
|
Annotations *PlanAnnotations
|
2016-05-05 18:21:58 +00:00
|
|
|
|
|
2016-05-31 21:51:23 +00:00
|
|
|
|
// FailedTGAllocs is the placement failures per task group.
|
|
|
|
|
FailedTGAllocs map[string]*AllocMetric
|
|
|
|
|
|
2016-05-16 18:48:44 +00:00
|
|
|
|
// JobModifyIndex is the modification index of the job. The value can be
|
|
|
|
|
// used when running `nomad run` to ensure that the Job wasn’t modified
|
|
|
|
|
// since the last plan. If the job is being created, the value is zero.
|
|
|
|
|
JobModifyIndex uint64
|
2016-05-05 18:21:58 +00:00
|
|
|
|
|
|
|
|
|
// CreatedEvals is the set of evaluations created by the scheduler. The
|
|
|
|
|
// reasons for this can be rolling-updates or blocked evals.
|
|
|
|
|
CreatedEvals []*Evaluation
|
|
|
|
|
|
|
|
|
|
// Diff contains the diff of the job and annotations on whether the change
|
|
|
|
|
// causes an in-place update or create/destroy
|
|
|
|
|
Diff *JobDiff
|
|
|
|
|
|
2016-06-15 20:34:45 +00:00
|
|
|
|
// NextPeriodicLaunch is the time duration till the job would be launched if
|
|
|
|
|
// submitted.
|
|
|
|
|
NextPeriodicLaunch time.Time
|
|
|
|
|
|
2017-05-10 03:52:47 +00:00
|
|
|
|
// Warnings contains any warnings about the given job. These may include
|
|
|
|
|
// deprecation warnings.
|
|
|
|
|
Warnings string
|
|
|
|
|
|
2016-05-12 01:51:48 +00:00
|
|
|
|
WriteMeta
|
2016-05-05 18:21:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 22:46:45 +00:00
|
|
|
|
// SingleAllocResponse is used to return a single allocation
|
|
|
|
|
type SingleAllocResponse struct {
|
|
|
|
|
Alloc *Allocation
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-01 21:57:35 +00:00
|
|
|
|
// AllocsGetResponse is used to return a set of allocations
|
|
|
|
|
type AllocsGetResponse struct {
|
|
|
|
|
Allocs []*Allocation
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 19:18:45 +00:00
|
|
|
|
// JobAllocationsResponse is used to return the allocations for a job
|
|
|
|
|
type JobAllocationsResponse struct {
|
2015-09-06 23:14:41 +00:00
|
|
|
|
Allocations []*AllocListStub
|
2015-09-06 19:18:45 +00:00
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// JobEvaluationsResponse is used to return the evaluations for a job
|
|
|
|
|
type JobEvaluationsResponse struct {
|
|
|
|
|
Evaluations []*Evaluation
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-23 23:00:19 +00:00
|
|
|
|
// SingleEvalResponse is used to return a single evaluation
|
|
|
|
|
type SingleEvalResponse struct {
|
|
|
|
|
Eval *Evaluation
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-12 22:25:31 +00:00
|
|
|
|
// EvalDequeueResponse is used to return from a dequeue
|
|
|
|
|
type EvalDequeueResponse struct {
|
|
|
|
|
Eval *Evaluation
|
|
|
|
|
Token string
|
2017-09-13 20:47:01 +00:00
|
|
|
|
|
|
|
|
|
// WaitIndex is the Raft index the worker should wait until invoking the
|
|
|
|
|
// scheduler.
|
|
|
|
|
WaitIndex uint64
|
|
|
|
|
|
2015-08-12 22:25:31 +00:00
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-13 20:47:01 +00:00
|
|
|
|
// GetWaitIndex is used to retrieve the Raft index in which state should be at
|
|
|
|
|
// or beyond before invoking the scheduler.
|
|
|
|
|
func (e *EvalDequeueResponse) GetWaitIndex() uint64 {
|
|
|
|
|
// Prefer the wait index sent. This will be populated on all responses from
|
|
|
|
|
// 0.7.0 and above
|
|
|
|
|
if e.WaitIndex != 0 {
|
|
|
|
|
return e.WaitIndex
|
|
|
|
|
} else if e.Eval != nil {
|
|
|
|
|
return e.Eval.ModifyIndex
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This should never happen
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-27 22:31:49 +00:00
|
|
|
|
// PlanResponse is used to return from a PlanRequest
|
|
|
|
|
type PlanResponse struct {
|
|
|
|
|
Result *PlanResult
|
|
|
|
|
WriteMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 22:34:28 +00:00
|
|
|
|
// AllocListResponse is used for a list request
|
|
|
|
|
type AllocListResponse struct {
|
|
|
|
|
Allocations []*AllocListStub
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-27 18:15:07 +00:00
|
|
|
|
// DeploymentListResponse is used for a list request
|
|
|
|
|
type DeploymentListResponse struct {
|
|
|
|
|
Deployments []*Deployment
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 23:01:16 +00:00
|
|
|
|
// EvalListResponse is used for a list request
|
|
|
|
|
type EvalListResponse struct {
|
|
|
|
|
Evaluations []*Evaluation
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 23:14:41 +00:00
|
|
|
|
// EvalAllocationsResponse is used to return the allocations for an evaluation
|
|
|
|
|
type EvalAllocationsResponse struct {
|
|
|
|
|
Allocations []*AllocListStub
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-13 18:19:53 +00:00
|
|
|
|
// PeriodicForceResponse is used to respond to a periodic job force launch
|
|
|
|
|
type PeriodicForceResponse struct {
|
|
|
|
|
EvalID string
|
|
|
|
|
EvalCreateIndex uint64
|
|
|
|
|
WriteMeta
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-26 21:23:52 +00:00
|
|
|
|
// DeploymentUpdateResponse is used to respond to a deployment change. The
|
|
|
|
|
// response will include the modify index of the deployment as well as details
|
|
|
|
|
// of any triggered evaluation.
|
|
|
|
|
type DeploymentUpdateResponse struct {
|
|
|
|
|
EvalID string
|
|
|
|
|
EvalCreateIndex uint64
|
|
|
|
|
DeploymentModifyIndex uint64
|
2017-07-06 20:55:44 +00:00
|
|
|
|
|
|
|
|
|
// RevertedJobVersion is the version the job was reverted to. If unset, the
|
|
|
|
|
// job wasn't reverted
|
|
|
|
|
RevertedJobVersion *uint64
|
|
|
|
|
|
2017-06-29 05:00:18 +00:00
|
|
|
|
WriteMeta
|
2017-06-26 21:23:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-13 00:52:24 +00:00
|
|
|
|
// NodeConnQueryResponse is used to respond to a query of whether a server has
|
|
|
|
|
// a connection to a specific Node
|
|
|
|
|
type NodeConnQueryResponse struct {
|
|
|
|
|
// Connected indicates whether a connection to the Client exists
|
|
|
|
|
Connected bool
|
|
|
|
|
|
|
|
|
|
// Established marks the time at which the connection was established
|
|
|
|
|
Established time.Time
|
|
|
|
|
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-14 00:52:12 +00:00
|
|
|
|
// EmitNodeEventsRequest is a request to update the node events source
|
|
|
|
|
// with a new client-side event
|
|
|
|
|
type EmitNodeEventsRequest struct {
|
|
|
|
|
// NodeEvents are a map where the key is a node id, and value is a list of
|
|
|
|
|
// events for that node
|
|
|
|
|
NodeEvents map[string][]*NodeEvent
|
|
|
|
|
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EmitNodeEventsResponse is a response to the client about the status of
|
|
|
|
|
// the node event source update.
|
|
|
|
|
type EmitNodeEventsResponse struct {
|
|
|
|
|
WriteMeta
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const (
|
2018-03-14 00:59:37 +00:00
|
|
|
|
NodeEventSubsystemDrain = "Drain"
|
|
|
|
|
NodeEventSubsystemDriver = "Driver"
|
|
|
|
|
NodeEventSubsystemHeartbeat = "Heartbeat"
|
|
|
|
|
NodeEventSubsystemCluster = "Cluster"
|
2018-03-14 00:52:12 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// NodeEvent is a single unit representing a node’s state change
|
|
|
|
|
type NodeEvent struct {
|
|
|
|
|
Message string
|
2018-03-14 00:59:37 +00:00
|
|
|
|
Subsystem string
|
2018-03-14 00:52:12 +00:00
|
|
|
|
Details map[string]string
|
2018-03-27 17:22:29 +00:00
|
|
|
|
Timestamp time.Time
|
2018-03-14 00:52:12 +00:00
|
|
|
|
CreateIndex uint64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ne *NodeEvent) String() string {
|
|
|
|
|
var details []string
|
|
|
|
|
for k, v := range ne.Details {
|
|
|
|
|
details = append(details, fmt.Sprintf("%s: %s", k, v))
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-27 19:21:02 +00:00
|
|
|
|
return fmt.Sprintf("Message: %s, Subsystem: %s, Details: %s, Timestamp: %s", ne.Message, ne.Subsystem, strings.Join(details, ","), ne.Timestamp.String())
|
2018-03-14 00:52:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ne *NodeEvent) Copy() *NodeEvent {
|
|
|
|
|
c := new(NodeEvent)
|
|
|
|
|
*c = *ne
|
|
|
|
|
c.Details = helper.CopyMapStringString(ne.Details)
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 23:54:43 +00:00
|
|
|
|
// NewNodeEvent generates a new node event storing the current time as the
|
|
|
|
|
// timestamp
|
|
|
|
|
func NewNodeEvent() *NodeEvent {
|
|
|
|
|
return &NodeEvent{Timestamp: time.Now()}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetMessage is used to set the message on the node event
|
|
|
|
|
func (ne *NodeEvent) SetMessage(msg string) *NodeEvent {
|
|
|
|
|
ne.Message = msg
|
|
|
|
|
return ne
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetSubsystem is used to set the subsystem on the node event
|
|
|
|
|
func (ne *NodeEvent) SetSubsystem(sys string) *NodeEvent {
|
|
|
|
|
ne.Subsystem = sys
|
|
|
|
|
return ne
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-12 00:26:25 +00:00
|
|
|
|
// SetTimestamp is used to set the timestamp on the node event
|
|
|
|
|
func (ne *NodeEvent) SetTimestamp(ts time.Time) *NodeEvent {
|
|
|
|
|
ne.Timestamp = ts
|
|
|
|
|
return ne
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 23:54:43 +00:00
|
|
|
|
// AddDetail is used to add a detail to the node event
|
|
|
|
|
func (ne *NodeEvent) AddDetail(k, v string) *NodeEvent {
|
|
|
|
|
if ne.Details == nil {
|
|
|
|
|
ne.Details = make(map[string]string, 1)
|
|
|
|
|
}
|
|
|
|
|
ne.Details[k] = v
|
|
|
|
|
return ne
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 23:36:16 +00:00
|
|
|
|
const (
|
|
|
|
|
NodeStatusInit = "initializing"
|
|
|
|
|
NodeStatusReady = "ready"
|
|
|
|
|
NodeStatusDown = "down"
|
|
|
|
|
)
|
|
|
|
|
|
2015-08-13 23:40:51 +00:00
|
|
|
|
// ShouldDrainNode checks if a given node status should trigger an
|
2015-08-06 23:39:20 +00:00
|
|
|
|
// evaluation. Some states don't require any further action.
|
2015-08-13 23:40:51 +00:00
|
|
|
|
func ShouldDrainNode(status string) bool {
|
2015-08-06 23:39:20 +00:00
|
|
|
|
switch status {
|
2015-09-07 02:47:02 +00:00
|
|
|
|
case NodeStatusInit, NodeStatusReady:
|
2015-08-06 23:39:20 +00:00
|
|
|
|
return false
|
2015-09-07 02:47:02 +00:00
|
|
|
|
case NodeStatusDown:
|
2015-08-06 23:39:20 +00:00
|
|
|
|
return true
|
|
|
|
|
default:
|
|
|
|
|
panic(fmt.Sprintf("unhandled node status %s", status))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ValidNodeStatus is used to check if a node status is valid
|
|
|
|
|
func ValidNodeStatus(status string) bool {
|
|
|
|
|
switch status {
|
2015-09-07 02:47:02 +00:00
|
|
|
|
case NodeStatusInit, NodeStatusReady, NodeStatusDown:
|
2015-08-06 23:39:20 +00:00
|
|
|
|
return true
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 23:36:16 +00:00
|
|
|
|
const (
|
|
|
|
|
// NodeSchedulingEligible and Ineligible marks the node as eligible or not,
|
|
|
|
|
// respectively, for receiving allocations. This is orthoginal to the node
|
|
|
|
|
// status being ready.
|
|
|
|
|
NodeSchedulingEligible = "eligible"
|
|
|
|
|
NodeSchedulingIneligible = "ineligible"
|
|
|
|
|
)
|
|
|
|
|
|
2018-02-23 23:56:36 +00:00
|
|
|
|
// DrainSpec describes a Node's desired drain behavior.
|
|
|
|
|
type DrainSpec struct {
|
2018-01-24 00:47:00 +00:00
|
|
|
|
// Deadline is the duration after StartTime when the remaining
|
|
|
|
|
// allocations on a draining Node should be told to stop.
|
|
|
|
|
Deadline time.Duration
|
2018-02-23 18:42:43 +00:00
|
|
|
|
|
|
|
|
|
// IgnoreSystemJobs allows systems jobs to remain on the node even though it
|
|
|
|
|
// has been marked for draining.
|
|
|
|
|
IgnoreSystemJobs bool
|
2018-01-24 00:47:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-23 23:56:36 +00:00
|
|
|
|
// DrainStrategy describes a Node's drain behavior.
|
|
|
|
|
type DrainStrategy struct {
|
|
|
|
|
// DrainSpec is the user declared drain specification
|
|
|
|
|
DrainSpec
|
|
|
|
|
|
2018-03-01 00:25:56 +00:00
|
|
|
|
// ForceDeadline is the deadline time for the drain after which drains will
|
|
|
|
|
// be forced
|
|
|
|
|
ForceDeadline time.Time
|
2018-02-23 23:56:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-24 00:47:00 +00:00
|
|
|
|
func (d *DrainStrategy) Copy() *DrainStrategy {
|
|
|
|
|
if d == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nd := new(DrainStrategy)
|
|
|
|
|
*nd = *d
|
|
|
|
|
return nd
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-01 00:25:56 +00:00
|
|
|
|
// DeadlineTime returns a boolean whether the drain strategy allows an infinite
|
|
|
|
|
// duration or otherwise the deadline time. The force drain is captured by the
|
|
|
|
|
// deadline time being in the past.
|
|
|
|
|
func (d *DrainStrategy) DeadlineTime() (infinite bool, deadline time.Time) {
|
|
|
|
|
// Treat the nil case as a force drain so during an upgrade where a node may
|
|
|
|
|
// not have a drain strategy but has Drain set to true, it is treated as a
|
|
|
|
|
// force to mimick old behavior.
|
2018-01-24 00:47:00 +00:00
|
|
|
|
if d == nil {
|
2018-03-01 00:25:56 +00:00
|
|
|
|
return false, time.Time{}
|
2018-01-24 00:47:00 +00:00
|
|
|
|
}
|
2018-03-01 00:25:56 +00:00
|
|
|
|
|
|
|
|
|
ns := d.Deadline.Nanoseconds()
|
|
|
|
|
switch {
|
|
|
|
|
case ns < 0: // Force
|
|
|
|
|
return false, time.Time{}
|
|
|
|
|
case ns == 0: // Infinite
|
|
|
|
|
return true, time.Time{}
|
|
|
|
|
default:
|
|
|
|
|
return false, d.ForceDeadline
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *DrainStrategy) Equal(o *DrainStrategy) bool {
|
|
|
|
|
if d == nil && o == nil {
|
|
|
|
|
return true
|
|
|
|
|
} else if o != nil && d == nil {
|
|
|
|
|
return false
|
|
|
|
|
} else if d != nil && o == nil {
|
|
|
|
|
return false
|
2018-01-24 00:47:00 +00:00
|
|
|
|
}
|
2018-03-01 00:25:56 +00:00
|
|
|
|
|
|
|
|
|
// Compare values
|
|
|
|
|
if d.ForceDeadline != o.ForceDeadline {
|
|
|
|
|
return false
|
|
|
|
|
} else if d.Deadline != o.Deadline {
|
|
|
|
|
return false
|
|
|
|
|
} else if d.IgnoreSystemJobs != o.IgnoreSystemJobs {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
2018-01-24 00:47:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-07-03 23:04:24 +00:00
|
|
|
|
// Node is a representation of a schedulable client node
|
|
|
|
|
type Node struct {
|
|
|
|
|
// ID is a unique identifier for the node. It can be constructed
|
|
|
|
|
// by doing a concatenation of the Name and Datacenter as a simple
|
|
|
|
|
// approach. Alternatively a UUID may be used.
|
|
|
|
|
ID string
|
|
|
|
|
|
2016-08-16 06:11:57 +00:00
|
|
|
|
// SecretID is an ID that is only known by the Node and the set of Servers.
|
|
|
|
|
// It is not accessible via the API and is used to authenticate nodes
|
2018-01-08 20:56:07 +00:00
|
|
|
|
// conducting privileged activities.
|
2016-08-16 06:11:57 +00:00
|
|
|
|
SecretID string
|
|
|
|
|
|
2015-06-07 18:18:59 +00:00
|
|
|
|
// Datacenter for this node
|
|
|
|
|
Datacenter string
|
|
|
|
|
|
2015-06-07 19:14:41 +00:00
|
|
|
|
// Node name
|
2015-07-03 23:04:24 +00:00
|
|
|
|
Name string
|
2015-06-07 18:18:59 +00:00
|
|
|
|
|
2016-01-27 19:06:48 +00:00
|
|
|
|
// HTTPAddr is the address on which the Nomad client is listening for http
|
|
|
|
|
// requests
|
2016-01-26 22:31:52 +00:00
|
|
|
|
HTTPAddr string
|
|
|
|
|
|
2016-10-26 18:13:53 +00:00
|
|
|
|
// TLSEnabled indicates if the Agent has TLS enabled for the HTTP API
|
|
|
|
|
TLSEnabled bool
|
|
|
|
|
|
2015-06-07 18:18:59 +00:00
|
|
|
|
// Attributes is an arbitrary set of key/value
|
|
|
|
|
// data that can be used for constraints. Examples
|
2015-08-28 08:30:47 +00:00
|
|
|
|
// include "kernel.name=linux", "arch=386", "driver.docker=1",
|
2015-07-03 23:04:24 +00:00
|
|
|
|
// "docker.runtime=1.8.3"
|
2015-07-06 20:01:10 +00:00
|
|
|
|
Attributes map[string]string
|
2015-06-07 18:18:59 +00:00
|
|
|
|
|
2018-09-30 00:23:41 +00:00
|
|
|
|
// NodeResources captures the available resources on the client.
|
|
|
|
|
NodeResources *NodeResources
|
|
|
|
|
|
2018-09-30 01:44:55 +00:00
|
|
|
|
// ReservedResources captures the set resources on the client that are
|
|
|
|
|
// reserved from scheduling.
|
|
|
|
|
ReservedResources *NodeReservedResources
|
|
|
|
|
|
2015-06-07 18:18:59 +00:00
|
|
|
|
// Resources is the available resources on the client.
|
|
|
|
|
// For example 'cpu=2' 'memory=2048'
|
2018-11-14 03:20:03 +00:00
|
|
|
|
// COMPAT(0.10): Remove in 0.10
|
2015-07-06 20:01:10 +00:00
|
|
|
|
Resources *Resources
|
2015-06-07 18:18:59 +00:00
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Reserved is the set of resources that are reserved,
|
|
|
|
|
// and should be subtracted from the total resources for
|
|
|
|
|
// the purposes of scheduling. This may be provide certain
|
|
|
|
|
// high-watermark tolerances or because of external schedulers
|
|
|
|
|
// consuming resources.
|
|
|
|
|
Reserved *Resources
|
|
|
|
|
|
2015-06-07 18:18:59 +00:00
|
|
|
|
// Links are used to 'link' this client to external
|
|
|
|
|
// systems. For example 'consul=foo.dc1' 'aws=i-83212'
|
|
|
|
|
// 'ami=ami-123'
|
2015-07-06 20:01:10 +00:00
|
|
|
|
Links map[string]string
|
2015-06-07 18:18:59 +00:00
|
|
|
|
|
|
|
|
|
// Meta is used to associate arbitrary metadata with this
|
|
|
|
|
// client. This is opaque to Nomad.
|
|
|
|
|
Meta map[string]string
|
|
|
|
|
|
2015-07-04 00:37:01 +00:00
|
|
|
|
// NodeClass is an opaque identifier used to group nodes
|
|
|
|
|
// together for the purpose of determining scheduling pressure.
|
|
|
|
|
NodeClass string
|
|
|
|
|
|
2016-01-21 01:30:02 +00:00
|
|
|
|
// ComputedClass is a unique id that identifies nodes with a common set of
|
|
|
|
|
// attributes and capabilities.
|
2016-01-30 01:46:44 +00:00
|
|
|
|
ComputedClass string
|
2016-01-21 01:30:02 +00:00
|
|
|
|
|
2018-02-23 18:42:43 +00:00
|
|
|
|
// COMPAT: Remove in Nomad 0.9
|
2015-09-07 02:47:02 +00:00
|
|
|
|
// Drain is controlled by the servers, and not the client.
|
|
|
|
|
// If true, no jobs will be scheduled to this node, and existing
|
2018-01-24 00:47:00 +00:00
|
|
|
|
// allocations will be drained. Superceded by DrainStrategy in Nomad
|
|
|
|
|
// 0.8 but kept for backward compat.
|
2015-09-07 02:47:02 +00:00
|
|
|
|
Drain bool
|
|
|
|
|
|
2018-01-24 00:47:00 +00:00
|
|
|
|
// DrainStrategy determines the node's draining behavior. Will be nil
|
|
|
|
|
// when Drain=false.
|
|
|
|
|
DrainStrategy *DrainStrategy
|
|
|
|
|
|
|
|
|
|
// SchedulingEligibility determines whether this node will receive new
|
|
|
|
|
// placements.
|
|
|
|
|
SchedulingEligibility string
|
|
|
|
|
|
2015-07-03 23:04:24 +00:00
|
|
|
|
// Status of this node
|
|
|
|
|
Status string
|
2015-07-04 00:50:54 +00:00
|
|
|
|
|
2015-08-15 20:08:06 +00:00
|
|
|
|
// StatusDescription is meant to provide more human useful information
|
|
|
|
|
StatusDescription string
|
|
|
|
|
|
2016-07-12 17:29:23 +00:00
|
|
|
|
// StatusUpdatedAt is the time stamp at which the state of the node was
|
|
|
|
|
// updated
|
|
|
|
|
StatusUpdatedAt int64
|
|
|
|
|
|
2018-03-14 00:59:37 +00:00
|
|
|
|
// Events is the most recent set of events generated for the node,
|
2018-03-12 01:00:13 +00:00
|
|
|
|
// retaining only MaxRetainedNodeEvents number at a time
|
2018-03-14 00:59:37 +00:00
|
|
|
|
Events []*NodeEvent
|
2018-03-01 22:17:33 +00:00
|
|
|
|
|
2018-01-25 16:30:15 +00:00
|
|
|
|
// Drivers is a map of driver names to current driver information
|
|
|
|
|
Drivers map[string]*DriverInfo
|
|
|
|
|
|
2019-07-25 14:44:08 +00:00
|
|
|
|
// HostVolumes is a map of host volume names to their configuration
|
|
|
|
|
HostVolumes map[string]*ClientHostVolumeConfig
|
|
|
|
|
|
2015-07-04 00:50:54 +00:00
|
|
|
|
// Raft Indexes
|
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
2015-06-07 18:18:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-24 00:47:00 +00:00
|
|
|
|
// Ready returns true if the node is ready for running allocations
|
2016-09-24 04:15:50 +00:00
|
|
|
|
func (n *Node) Ready() bool {
|
2018-01-24 00:47:00 +00:00
|
|
|
|
// Drain is checked directly to support pre-0.8 Node data
|
|
|
|
|
return n.Status == NodeStatusReady && !n.Drain && n.SchedulingEligibility == NodeSchedulingEligible
|
2016-09-24 04:15:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-29 23:33:11 +00:00
|
|
|
|
func (n *Node) Canonicalize() {
|
|
|
|
|
if n == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// COMPAT Remove in 0.10
|
|
|
|
|
// In v0.8.0 we introduced scheduling eligibility, so we need to set it for
|
|
|
|
|
// upgrading nodes
|
|
|
|
|
if n.SchedulingEligibility == "" {
|
|
|
|
|
if n.Drain {
|
|
|
|
|
n.SchedulingEligibility = NodeSchedulingIneligible
|
|
|
|
|
} else {
|
|
|
|
|
n.SchedulingEligibility = NodeSchedulingEligible
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-10 21:44:53 +00:00
|
|
|
|
func (n *Node) Copy() *Node {
|
2016-02-11 01:54:43 +00:00
|
|
|
|
if n == nil {
|
2016-02-10 21:44:53 +00:00
|
|
|
|
return nil
|
|
|
|
|
}
|
2016-02-11 01:54:43 +00:00
|
|
|
|
nn := new(Node)
|
|
|
|
|
*nn = *n
|
2017-01-18 23:55:14 +00:00
|
|
|
|
nn.Attributes = helper.CopyMapStringString(nn.Attributes)
|
2016-02-11 01:54:43 +00:00
|
|
|
|
nn.Resources = nn.Resources.Copy()
|
|
|
|
|
nn.Reserved = nn.Reserved.Copy()
|
2018-10-02 20:36:04 +00:00
|
|
|
|
nn.NodeResources = nn.NodeResources.Copy()
|
|
|
|
|
nn.ReservedResources = nn.ReservedResources.Copy()
|
2017-01-18 23:55:14 +00:00
|
|
|
|
nn.Links = helper.CopyMapStringString(nn.Links)
|
|
|
|
|
nn.Meta = helper.CopyMapStringString(nn.Meta)
|
2018-03-14 00:59:37 +00:00
|
|
|
|
nn.Events = copyNodeEvents(n.Events)
|
2018-02-23 18:42:43 +00:00
|
|
|
|
nn.DrainStrategy = nn.DrainStrategy.Copy()
|
2018-04-16 22:02:00 +00:00
|
|
|
|
nn.Drivers = copyNodeDrivers(n.Drivers)
|
2019-07-25 14:44:08 +00:00
|
|
|
|
nn.HostVolumes = copyNodeHostVolumes(n.HostVolumes)
|
2016-02-11 01:54:43 +00:00
|
|
|
|
return nn
|
2016-02-10 21:44:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-14 00:52:12 +00:00
|
|
|
|
// copyNodeEvents is a helper to copy a list of NodeEvent's
|
|
|
|
|
func copyNodeEvents(events []*NodeEvent) []*NodeEvent {
|
|
|
|
|
l := len(events)
|
|
|
|
|
if l == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c := make([]*NodeEvent, l)
|
|
|
|
|
for i, event := range events {
|
|
|
|
|
c[i] = event.Copy()
|
|
|
|
|
}
|
|
|
|
|
return c
|
2018-03-08 14:34:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-16 22:02:00 +00:00
|
|
|
|
// copyNodeDrivers is a helper to copy a map of DriverInfo
|
|
|
|
|
func copyNodeDrivers(drivers map[string]*DriverInfo) map[string]*DriverInfo {
|
|
|
|
|
l := len(drivers)
|
|
|
|
|
if l == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c := make(map[string]*DriverInfo, l)
|
|
|
|
|
for driver, info := range drivers {
|
|
|
|
|
c[driver] = info.Copy()
|
|
|
|
|
}
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-25 14:44:08 +00:00
|
|
|
|
// copyNodeHostVolumes is a helper to copy a map of string to Volume
|
|
|
|
|
func copyNodeHostVolumes(volumes map[string]*ClientHostVolumeConfig) map[string]*ClientHostVolumeConfig {
|
|
|
|
|
l := len(volumes)
|
|
|
|
|
if l == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c := make(map[string]*ClientHostVolumeConfig, l)
|
|
|
|
|
for volume, v := range volumes {
|
|
|
|
|
c[volume] = v.Copy()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-23 00:17:13 +00:00
|
|
|
|
// TerminalStatus returns if the current status is terminal and
|
|
|
|
|
// will no longer transition.
|
|
|
|
|
func (n *Node) TerminalStatus() bool {
|
|
|
|
|
switch n.Status {
|
|
|
|
|
case NodeStatusDown:
|
|
|
|
|
return true
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// COMPAT(0.11): Remove in 0.11
|
|
|
|
|
// ComparableReservedResources returns the reserved resouces on the node
|
|
|
|
|
// handling upgrade paths. Reserved networks must be handled separately. After
|
|
|
|
|
// 0.11 calls to this should be replaced with:
|
|
|
|
|
// node.ReservedResources.Comparable()
|
2018-10-04 21:33:09 +00:00
|
|
|
|
func (n *Node) ComparableReservedResources() *ComparableResources {
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// See if we can no-op
|
|
|
|
|
if n.Reserved == nil && n.ReservedResources == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Node already has 0.9+ behavior
|
|
|
|
|
if n.ReservedResources != nil {
|
|
|
|
|
return n.ReservedResources.Comparable()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Upgrade path
|
2018-10-04 21:33:09 +00:00
|
|
|
|
return &ComparableResources{
|
2018-10-02 20:36:04 +00:00
|
|
|
|
Flattened: AllocatedTaskResources{
|
|
|
|
|
Cpu: AllocatedCpuResources{
|
2018-10-16 22:34:32 +00:00
|
|
|
|
CpuShares: int64(n.Reserved.CPU),
|
2018-10-02 20:36:04 +00:00
|
|
|
|
},
|
|
|
|
|
Memory: AllocatedMemoryResources{
|
2018-10-16 22:34:32 +00:00
|
|
|
|
MemoryMB: int64(n.Reserved.MemoryMB),
|
2018-10-02 20:36:04 +00:00
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Shared: AllocatedSharedResources{
|
2018-10-16 22:34:32 +00:00
|
|
|
|
DiskMB: int64(n.Reserved.DiskMB),
|
2018-10-02 20:36:04 +00:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// COMPAT(0.11): Remove in 0.11
|
|
|
|
|
// ComparableResources returns the resouces on the node
|
|
|
|
|
// handling upgrade paths. Networking must be handled separately. After 0.11
|
|
|
|
|
// calls to this should be replaced with: node.NodeResources.Comparable()
|
2018-10-04 21:33:09 +00:00
|
|
|
|
func (n *Node) ComparableResources() *ComparableResources {
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// Node already has 0.9+ behavior
|
|
|
|
|
if n.NodeResources != nil {
|
|
|
|
|
return n.NodeResources.Comparable()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Upgrade path
|
2018-10-04 21:33:09 +00:00
|
|
|
|
return &ComparableResources{
|
2018-10-02 20:36:04 +00:00
|
|
|
|
Flattened: AllocatedTaskResources{
|
|
|
|
|
Cpu: AllocatedCpuResources{
|
2018-10-16 22:34:32 +00:00
|
|
|
|
CpuShares: int64(n.Resources.CPU),
|
2018-10-02 20:36:04 +00:00
|
|
|
|
},
|
|
|
|
|
Memory: AllocatedMemoryResources{
|
2018-10-16 22:34:32 +00:00
|
|
|
|
MemoryMB: int64(n.Resources.MemoryMB),
|
2018-10-02 20:36:04 +00:00
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Shared: AllocatedSharedResources{
|
2018-10-16 22:34:32 +00:00
|
|
|
|
DiskMB: int64(n.Resources.DiskMB),
|
2018-10-02 20:36:04 +00:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 22:34:28 +00:00
|
|
|
|
// Stub returns a summarized version of the node
|
|
|
|
|
func (n *Node) Stub() *NodeListStub {
|
2018-01-11 07:17:58 +00:00
|
|
|
|
|
|
|
|
|
addr, _, _ := net.SplitHostPort(n.HTTPAddr)
|
|
|
|
|
|
2015-09-06 22:34:28 +00:00
|
|
|
|
return &NodeListStub{
|
2019-01-05 00:36:06 +00:00
|
|
|
|
Address: addr,
|
|
|
|
|
ID: n.ID,
|
|
|
|
|
Datacenter: n.Datacenter,
|
|
|
|
|
Name: n.Name,
|
|
|
|
|
NodeClass: n.NodeClass,
|
|
|
|
|
Version: n.Attributes["nomad.version"],
|
|
|
|
|
Drain: n.Drain,
|
2018-01-24 00:47:00 +00:00
|
|
|
|
SchedulingEligibility: n.SchedulingEligibility,
|
|
|
|
|
Status: n.Status,
|
|
|
|
|
StatusDescription: n.StatusDescription,
|
2018-05-09 15:21:54 +00:00
|
|
|
|
Drivers: n.Drivers,
|
2018-01-24 00:47:00 +00:00
|
|
|
|
CreateIndex: n.CreateIndex,
|
|
|
|
|
ModifyIndex: n.ModifyIndex,
|
2015-09-06 22:34:28 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NodeListStub is used to return a subset of job information
|
|
|
|
|
// for the job list
|
|
|
|
|
type NodeListStub struct {
|
2018-01-24 00:47:00 +00:00
|
|
|
|
Address string
|
|
|
|
|
ID string
|
|
|
|
|
Datacenter string
|
|
|
|
|
Name string
|
|
|
|
|
NodeClass string
|
|
|
|
|
Version string
|
|
|
|
|
Drain bool
|
|
|
|
|
SchedulingEligibility string
|
|
|
|
|
Status string
|
|
|
|
|
StatusDescription string
|
2018-05-09 15:21:54 +00:00
|
|
|
|
Drivers map[string]*DriverInfo
|
2018-01-24 00:47:00 +00:00
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
2015-09-06 22:34:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-07 18:18:59 +00:00
|
|
|
|
// Resources is used to define the resources available
|
|
|
|
|
// on a client
|
|
|
|
|
type Resources struct {
|
2015-09-23 18:14:32 +00:00
|
|
|
|
CPU int
|
2017-02-22 20:30:05 +00:00
|
|
|
|
MemoryMB int
|
|
|
|
|
DiskMB int
|
2018-12-07 00:17:09 +00:00
|
|
|
|
IOPS int // COMPAT(0.10): Only being used to issue warnings
|
2017-06-09 17:29:41 +00:00
|
|
|
|
Networks Networks
|
2019-04-11 17:17:26 +00:00
|
|
|
|
Devices ResourceDevices
|
2015-06-07 18:18:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-11 07:20:53 +00:00
|
|
|
|
const (
|
|
|
|
|
BytesInMegabyte = 1024 * 1024
|
|
|
|
|
)
|
|
|
|
|
|
2017-11-13 17:05:30 +00:00
|
|
|
|
// DefaultResources is a small resources object that contains the
|
|
|
|
|
// default resources requests that we will provide to an object.
|
2017-11-13 17:32:52 +00:00
|
|
|
|
// --- THIS FUNCTION IS REPLICATED IN api/resources.go and should
|
2017-11-13 17:05:30 +00:00
|
|
|
|
// be kept in sync.
|
2016-02-02 20:00:26 +00:00
|
|
|
|
func DefaultResources() *Resources {
|
2017-11-13 17:05:30 +00:00
|
|
|
|
return &Resources{
|
|
|
|
|
CPU: 100,
|
|
|
|
|
MemoryMB: 300,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MinResources is a small resources object that contains the
|
|
|
|
|
// absolute minimum resources that we will provide to an object.
|
2017-11-13 17:32:52 +00:00
|
|
|
|
// This should not be confused with the defaults which are
|
|
|
|
|
// provided in Canonicalize() --- THIS FUNCTION IS REPLICATED IN
|
2017-11-13 17:05:30 +00:00
|
|
|
|
// api/resources.go and should be kept in sync.
|
|
|
|
|
func MinResources() *Resources {
|
2016-02-02 20:00:26 +00:00
|
|
|
|
return &Resources{
|
2018-01-02 14:56:02 +00:00
|
|
|
|
CPU: 20,
|
2016-02-02 20:00:26 +00:00
|
|
|
|
MemoryMB: 10,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-11 07:20:53 +00:00
|
|
|
|
// DiskInBytes returns the amount of disk resources in bytes.
|
|
|
|
|
func (r *Resources) DiskInBytes() int64 {
|
|
|
|
|
return int64(r.DiskMB * BytesInMegabyte)
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-10 17:32:44 +00:00
|
|
|
|
func (r *Resources) Validate() error {
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
if err := r.MeetsMinResources(); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure the task isn't asking for disk resources
|
|
|
|
|
if r.DiskMB > 0 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Task can't ask for disk resources, they have to be specified at the task group level."))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i, d := range r.Devices {
|
|
|
|
|
if err := d.Validate(); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("device %d failed validation: %v", i+1, err))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-02 20:07:16 +00:00
|
|
|
|
// Merge merges this resource with another resource.
|
2019-04-11 17:17:26 +00:00
|
|
|
|
// COMPAT(0.10): Remove in 0.10
|
2016-02-02 20:00:26 +00:00
|
|
|
|
func (r *Resources) Merge(other *Resources) {
|
|
|
|
|
if other.CPU != 0 {
|
|
|
|
|
r.CPU = other.CPU
|
|
|
|
|
}
|
|
|
|
|
if other.MemoryMB != 0 {
|
|
|
|
|
r.MemoryMB = other.MemoryMB
|
|
|
|
|
}
|
|
|
|
|
if other.DiskMB != 0 {
|
|
|
|
|
r.DiskMB = other.DiskMB
|
|
|
|
|
}
|
|
|
|
|
if len(other.Networks) != 0 {
|
|
|
|
|
r.Networks = other.Networks
|
|
|
|
|
}
|
2018-10-08 22:38:03 +00:00
|
|
|
|
if len(other.Devices) != 0 {
|
|
|
|
|
r.Devices = other.Devices
|
|
|
|
|
}
|
2016-02-02 20:00:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-11 17:17:26 +00:00
|
|
|
|
// COMPAT(0.10): Remove in 0.10
|
|
|
|
|
func (r *Resources) Equals(o *Resources) bool {
|
|
|
|
|
if r == o {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if r == nil || o == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return r.CPU == o.CPU &&
|
|
|
|
|
r.MemoryMB == o.MemoryMB &&
|
|
|
|
|
r.DiskMB == o.DiskMB &&
|
|
|
|
|
r.IOPS == o.IOPS &&
|
|
|
|
|
r.Networks.Equals(&o.Networks) &&
|
|
|
|
|
r.Devices.Equals(&o.Devices)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// COMPAT(0.10): Remove in 0.10
|
|
|
|
|
// ResourceDevices are part of Resources
|
|
|
|
|
type ResourceDevices []*RequestedDevice
|
|
|
|
|
|
|
|
|
|
// COMPAT(0.10): Remove in 0.10
|
|
|
|
|
// Equals ResourceDevices as set keyed by Name
|
|
|
|
|
func (d *ResourceDevices) Equals(o *ResourceDevices) bool {
|
|
|
|
|
if d == o {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if d == nil || o == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if len(*d) != len(*o) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
m := make(map[string]*RequestedDevice, len(*d))
|
|
|
|
|
for _, e := range *d {
|
|
|
|
|
m[e.Name] = e
|
|
|
|
|
}
|
|
|
|
|
for _, oe := range *o {
|
|
|
|
|
de, ok := m[oe.Name]
|
|
|
|
|
if !ok || !de.Equals(oe) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// COMPAT(0.10): Remove in 0.10
|
2016-07-20 23:07:15 +00:00
|
|
|
|
func (r *Resources) Canonicalize() {
|
2016-07-18 23:17:38 +00:00
|
|
|
|
// Ensure that an empty and nil slices are treated the same to avoid scheduling
|
|
|
|
|
// problems since we use reflect DeepEquals.
|
|
|
|
|
if len(r.Networks) == 0 {
|
|
|
|
|
r.Networks = nil
|
|
|
|
|
}
|
2018-10-08 22:38:03 +00:00
|
|
|
|
if len(r.Devices) == 0 {
|
|
|
|
|
r.Devices = nil
|
|
|
|
|
}
|
2016-07-18 23:17:38 +00:00
|
|
|
|
|
|
|
|
|
for _, n := range r.Networks {
|
2016-07-20 23:07:15 +00:00
|
|
|
|
n.Canonicalize()
|
2016-07-18 23:17:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-02 20:07:16 +00:00
|
|
|
|
// MeetsMinResources returns an error if the resources specified are less than
|
|
|
|
|
// the minimum allowed.
|
2017-11-13 17:05:30 +00:00
|
|
|
|
// This is based on the minimums defined in the Resources type
|
2019-04-11 17:17:26 +00:00
|
|
|
|
// COMPAT(0.10): Remove in 0.10
|
2016-02-02 20:00:26 +00:00
|
|
|
|
func (r *Resources) MeetsMinResources() error {
|
|
|
|
|
var mErr multierror.Error
|
2017-11-13 17:05:30 +00:00
|
|
|
|
minResources := MinResources()
|
|
|
|
|
if r.CPU < minResources.CPU {
|
2017-11-13 17:51:19 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum CPU value is %d; got %d", minResources.CPU, r.CPU))
|
2016-02-02 20:00:26 +00:00
|
|
|
|
}
|
2017-11-13 17:05:30 +00:00
|
|
|
|
if r.MemoryMB < minResources.MemoryMB {
|
2017-11-29 00:44:33 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum MemoryMB value is %d; got %d", minResources.MemoryMB, r.MemoryMB))
|
2016-02-02 20:00:26 +00:00
|
|
|
|
}
|
2016-02-02 20:07:16 +00:00
|
|
|
|
for i, n := range r.Networks {
|
|
|
|
|
if err := n.MeetsMinResources(); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("network resource at index %d failed: %v", i, err))
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-02-02 20:00:26 +00:00
|
|
|
|
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-13 21:30:45 +00:00
|
|
|
|
// Copy returns a deep copy of the resources
|
|
|
|
|
func (r *Resources) Copy() *Resources {
|
2016-02-11 01:54:43 +00:00
|
|
|
|
if r == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2015-09-13 21:30:45 +00:00
|
|
|
|
newR := new(Resources)
|
|
|
|
|
*newR = *r
|
2018-10-08 22:38:03 +00:00
|
|
|
|
|
|
|
|
|
// Copy the network objects
|
2019-06-25 22:13:07 +00:00
|
|
|
|
newR.Networks = r.Networks.Copy()
|
2018-10-08 22:38:03 +00:00
|
|
|
|
|
|
|
|
|
// Copy the devices
|
|
|
|
|
if r.Devices != nil {
|
|
|
|
|
n := len(r.Devices)
|
|
|
|
|
newR.Devices = make([]*RequestedDevice, n)
|
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
|
newR.Devices[i] = r.Devices[i].Copy()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-13 21:30:45 +00:00
|
|
|
|
return newR
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-13 22:04:36 +00:00
|
|
|
|
// NetIndex finds the matching net index using device name
|
2019-04-11 17:17:26 +00:00
|
|
|
|
// COMPAT(0.10): Remove in 0.10
|
2015-09-13 00:04:09 +00:00
|
|
|
|
func (r *Resources) NetIndex(n *NetworkResource) int {
|
2018-10-03 16:47:18 +00:00
|
|
|
|
return r.Networks.NetIndex(n)
|
2015-09-12 23:21:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-05 00:32:57 +00:00
|
|
|
|
// Superset checks if one set of resources is a superset
|
2015-09-13 21:59:34 +00:00
|
|
|
|
// of another. This ignores network resources, and the NetworkIndex
|
|
|
|
|
// should be used for that.
|
2019-04-11 17:17:26 +00:00
|
|
|
|
// COMPAT(0.10): Remove in 0.10
|
2015-09-14 01:38:11 +00:00
|
|
|
|
func (r *Resources) Superset(other *Resources) (bool, string) {
|
2015-08-05 00:32:57 +00:00
|
|
|
|
if r.CPU < other.CPU {
|
2017-11-29 04:15:32 +00:00
|
|
|
|
return false, "cpu"
|
2015-08-05 00:32:57 +00:00
|
|
|
|
}
|
|
|
|
|
if r.MemoryMB < other.MemoryMB {
|
2017-11-29 04:15:32 +00:00
|
|
|
|
return false, "memory"
|
2015-08-05 00:32:57 +00:00
|
|
|
|
}
|
|
|
|
|
if r.DiskMB < other.DiskMB {
|
2017-11-29 04:15:32 +00:00
|
|
|
|
return false, "disk"
|
2015-08-05 00:32:57 +00:00
|
|
|
|
}
|
2015-09-14 01:38:11 +00:00
|
|
|
|
return true, ""
|
2015-08-05 00:32:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-05 00:41:02 +00:00
|
|
|
|
// Add adds the resources of the delta to this, potentially
|
|
|
|
|
// returning an error if not possible.
|
2019-04-11 17:17:26 +00:00
|
|
|
|
// COMPAT(0.10): Remove in 0.10
|
2015-08-05 00:41:02 +00:00
|
|
|
|
func (r *Resources) Add(delta *Resources) error {
|
|
|
|
|
if delta == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
r.CPU += delta.CPU
|
|
|
|
|
r.MemoryMB += delta.MemoryMB
|
|
|
|
|
r.DiskMB += delta.DiskMB
|
|
|
|
|
|
2015-09-12 23:33:41 +00:00
|
|
|
|
for _, n := range delta.Networks {
|
|
|
|
|
// Find the matching interface by IP or CIDR
|
2015-09-13 00:04:09 +00:00
|
|
|
|
idx := r.NetIndex(n)
|
2015-08-05 00:41:02 +00:00
|
|
|
|
if idx == -1 {
|
2015-09-13 23:40:53 +00:00
|
|
|
|
r.Networks = append(r.Networks, n.Copy())
|
2015-09-12 23:33:41 +00:00
|
|
|
|
} else {
|
|
|
|
|
r.Networks[idx].Add(n)
|
2015-08-05 00:41:02 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-11 17:17:26 +00:00
|
|
|
|
// COMPAT(0.10): Remove in 0.10
|
2015-09-15 00:43:42 +00:00
|
|
|
|
func (r *Resources) GoString() string {
|
|
|
|
|
return fmt.Sprintf("*%#v", *r)
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-14 02:09:42 +00:00
|
|
|
|
type Port struct {
|
|
|
|
|
Label string
|
2017-02-22 20:30:05 +00:00
|
|
|
|
Value int
|
2019-04-29 17:15:12 +00:00
|
|
|
|
To int
|
2015-11-14 02:09:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-24 06:56:25 +00:00
|
|
|
|
// NetworkResource is used to represent available network
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// resources
|
2015-06-07 18:18:59 +00:00
|
|
|
|
type NetworkResource struct {
|
2019-04-29 17:15:12 +00:00
|
|
|
|
Mode string // Mode of the network
|
2015-11-14 02:09:42 +00:00
|
|
|
|
Device string // Name of the device
|
|
|
|
|
CIDR string // CIDR block of addresses
|
2017-06-09 17:29:41 +00:00
|
|
|
|
IP string // Host IP address
|
2015-11-14 02:09:42 +00:00
|
|
|
|
MBits int // Throughput
|
2017-06-09 17:29:41 +00:00
|
|
|
|
ReservedPorts []Port // Host Reserved ports
|
|
|
|
|
DynamicPorts []Port // Host Dynamically assigned ports
|
2015-07-03 23:57:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-27 17:21:06 +00:00
|
|
|
|
func (nr *NetworkResource) Equals(other *NetworkResource) bool {
|
2019-04-29 17:15:12 +00:00
|
|
|
|
if nr.Mode != other.Mode {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-27 17:21:06 +00:00
|
|
|
|
if nr.Device != other.Device {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if nr.CIDR != other.CIDR {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if nr.IP != other.IP {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if nr.MBits != other.MBits {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(nr.ReservedPorts) != len(other.ReservedPorts) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i, port := range nr.ReservedPorts {
|
|
|
|
|
if len(other.ReservedPorts) <= i {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if port != other.ReservedPorts[i] {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(nr.DynamicPorts) != len(other.DynamicPorts) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for i, port := range nr.DynamicPorts {
|
|
|
|
|
if len(other.DynamicPorts) <= i {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if port != other.DynamicPorts[i] {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-24 15:29:26 +00:00
|
|
|
|
|
2018-02-27 17:21:06 +00:00
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-20 23:07:15 +00:00
|
|
|
|
func (n *NetworkResource) Canonicalize() {
|
2016-07-18 23:17:38 +00:00
|
|
|
|
// Ensure that an empty and nil slices are treated the same to avoid scheduling
|
|
|
|
|
// problems since we use reflect DeepEquals.
|
|
|
|
|
if len(n.ReservedPorts) == 0 {
|
|
|
|
|
n.ReservedPorts = nil
|
|
|
|
|
}
|
|
|
|
|
if len(n.DynamicPorts) == 0 {
|
|
|
|
|
n.DynamicPorts = nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-02 20:07:16 +00:00
|
|
|
|
// MeetsMinResources returns an error if the resources specified are less than
|
|
|
|
|
// the minimum allowed.
|
|
|
|
|
func (n *NetworkResource) MeetsMinResources() error {
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
if n.MBits < 1 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum MBits value is 1; got %d", n.MBits))
|
|
|
|
|
}
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-13 21:30:45 +00:00
|
|
|
|
// Copy returns a deep copy of the network resource
|
|
|
|
|
func (n *NetworkResource) Copy() *NetworkResource {
|
2016-02-11 01:54:43 +00:00
|
|
|
|
if n == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2015-09-13 21:30:45 +00:00
|
|
|
|
newR := new(NetworkResource)
|
|
|
|
|
*newR = *n
|
2015-09-14 01:38:11 +00:00
|
|
|
|
if n.ReservedPorts != nil {
|
2015-11-14 02:09:42 +00:00
|
|
|
|
newR.ReservedPorts = make([]Port, len(n.ReservedPorts))
|
2015-09-14 01:38:11 +00:00
|
|
|
|
copy(newR.ReservedPorts, n.ReservedPorts)
|
|
|
|
|
}
|
2015-11-24 00:32:30 +00:00
|
|
|
|
if n.DynamicPorts != nil {
|
|
|
|
|
newR.DynamicPorts = make([]Port, len(n.DynamicPorts))
|
|
|
|
|
copy(newR.DynamicPorts, n.DynamicPorts)
|
|
|
|
|
}
|
2015-09-13 21:30:45 +00:00
|
|
|
|
return newR
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-05 00:41:02 +00:00
|
|
|
|
// Add adds the resources of the delta to this, potentially
|
|
|
|
|
// returning an error if not possible.
|
|
|
|
|
func (n *NetworkResource) Add(delta *NetworkResource) {
|
|
|
|
|
if len(delta.ReservedPorts) > 0 {
|
|
|
|
|
n.ReservedPorts = append(n.ReservedPorts, delta.ReservedPorts...)
|
|
|
|
|
}
|
|
|
|
|
n.MBits += delta.MBits
|
2015-09-22 20:33:16 +00:00
|
|
|
|
n.DynamicPorts = append(n.DynamicPorts, delta.DynamicPorts...)
|
2015-08-05 00:41:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-15 01:27:37 +00:00
|
|
|
|
func (n *NetworkResource) GoString() string {
|
|
|
|
|
return fmt.Sprintf("*%#v", *n)
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-09 17:29:41 +00:00
|
|
|
|
// PortLabels returns a map of port labels to their assigned host ports.
|
|
|
|
|
func (n *NetworkResource) PortLabels() map[string]int {
|
|
|
|
|
num := len(n.ReservedPorts) + len(n.DynamicPorts)
|
|
|
|
|
labelValues := make(map[string]int, num)
|
|
|
|
|
for _, port := range n.ReservedPorts {
|
|
|
|
|
labelValues[port.Label] = port.Value
|
|
|
|
|
}
|
|
|
|
|
for _, port := range n.DynamicPorts {
|
|
|
|
|
labelValues[port.Label] = port.Value
|
2015-11-15 09:13:42 +00:00
|
|
|
|
}
|
|
|
|
|
return labelValues
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-14 22:02:00 +00:00
|
|
|
|
// ConnectPort returns the Connect port for the given service. Returns false if
|
|
|
|
|
// no port was found for a service with that name.
|
|
|
|
|
func (n *NetworkResource) PortForService(serviceName string) (Port, bool) {
|
|
|
|
|
label := fmt.Sprintf("%s-%s", ConnectProxyPrefix, serviceName)
|
|
|
|
|
for _, port := range n.ReservedPorts {
|
|
|
|
|
if port.Label == label {
|
|
|
|
|
return port, true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, port := range n.DynamicPorts {
|
|
|
|
|
if port.Label == label {
|
|
|
|
|
return port, true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Port{}, false
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-30 00:23:41 +00:00
|
|
|
|
// Networks defined for a task on the Resources struct.
|
|
|
|
|
type Networks []*NetworkResource
|
|
|
|
|
|
2019-06-25 22:13:07 +00:00
|
|
|
|
func (ns Networks) Copy() Networks {
|
|
|
|
|
if len(ns) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out := make([]*NetworkResource, len(ns))
|
|
|
|
|
for i := range ns {
|
|
|
|
|
out[i] = ns[i].Copy()
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-30 00:23:41 +00:00
|
|
|
|
// Port assignment and IP for the given label or empty values.
|
|
|
|
|
func (ns Networks) Port(label string) (string, int) {
|
|
|
|
|
for _, n := range ns {
|
|
|
|
|
for _, p := range n.ReservedPorts {
|
|
|
|
|
if p.Label == label {
|
2019-06-25 22:13:07 +00:00
|
|
|
|
return n.IP, p.Value
|
2018-09-30 00:23:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, p := range n.DynamicPorts {
|
|
|
|
|
if p.Label == label {
|
2019-06-25 22:13:07 +00:00
|
|
|
|
return n.IP, p.Value
|
2018-09-30 00:23:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return "", 0
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-03 16:47:18 +00:00
|
|
|
|
func (ns Networks) NetIndex(n *NetworkResource) int {
|
|
|
|
|
for idx, net := range ns {
|
|
|
|
|
if net.Device == n.Device {
|
|
|
|
|
return idx
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-08 22:38:03 +00:00
|
|
|
|
// RequestedDevice is used to request a device for a task.
|
|
|
|
|
type RequestedDevice struct {
|
|
|
|
|
// Name is the request name. The possible values are as follows:
|
|
|
|
|
// * <type>: A single value only specifies the type of request.
|
|
|
|
|
// * <vendor>/<type>: A single slash delimiter assumes the vendor and type of device is specified.
|
|
|
|
|
// * <vendor>/<type>/<name>: Two slash delimiters assume vendor, type and specific model are specified.
|
|
|
|
|
//
|
|
|
|
|
// Examples are as follows:
|
|
|
|
|
// * "gpu"
|
|
|
|
|
// * "nvidia/gpu"
|
|
|
|
|
// * "nvidia/gpu/GTX2080Ti"
|
|
|
|
|
Name string
|
|
|
|
|
|
|
|
|
|
// Count is the number of requested devices
|
|
|
|
|
Count uint64
|
2018-10-11 21:05:19 +00:00
|
|
|
|
|
|
|
|
|
// Constraints are a set of constraints to apply when selecting the device
|
|
|
|
|
// to use.
|
2019-04-11 17:17:26 +00:00
|
|
|
|
Constraints Constraints
|
2018-10-11 21:05:19 +00:00
|
|
|
|
|
|
|
|
|
// Affinities are a set of affinites to apply when selecting the device
|
|
|
|
|
// to use.
|
2019-04-11 17:17:26 +00:00
|
|
|
|
Affinities Affinities
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *RequestedDevice) Equals(o *RequestedDevice) bool {
|
|
|
|
|
if r == o {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if r == nil || o == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return r.Name == o.Name &&
|
|
|
|
|
r.Count == o.Count &&
|
|
|
|
|
r.Constraints.Equals(&o.Constraints) &&
|
|
|
|
|
r.Affinities.Equals(&o.Affinities)
|
2019-03-27 16:43:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-08 22:38:03 +00:00
|
|
|
|
func (r *RequestedDevice) Copy() *RequestedDevice {
|
|
|
|
|
if r == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nr := *r
|
2018-10-11 21:05:19 +00:00
|
|
|
|
nr.Constraints = CopySliceConstraints(nr.Constraints)
|
|
|
|
|
nr.Affinities = CopySliceAffinities(nr.Affinities)
|
|
|
|
|
|
2018-10-08 22:38:03 +00:00
|
|
|
|
return &nr
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-10 17:32:44 +00:00
|
|
|
|
func (r *RequestedDevice) ID() *DeviceIdTuple {
|
|
|
|
|
if r == nil || r.Name == "" {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parts := strings.SplitN(r.Name, "/", 3)
|
|
|
|
|
switch len(parts) {
|
|
|
|
|
case 1:
|
|
|
|
|
return &DeviceIdTuple{
|
|
|
|
|
Type: parts[0],
|
|
|
|
|
}
|
|
|
|
|
case 2:
|
|
|
|
|
return &DeviceIdTuple{
|
|
|
|
|
Vendor: parts[0],
|
|
|
|
|
Type: parts[1],
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return &DeviceIdTuple{
|
|
|
|
|
Vendor: parts[0],
|
|
|
|
|
Type: parts[1],
|
|
|
|
|
Name: parts[2],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *RequestedDevice) Validate() error {
|
|
|
|
|
if r == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-11 21:15:08 +00:00
|
|
|
|
var mErr multierror.Error
|
2018-10-10 17:32:44 +00:00
|
|
|
|
if r.Name == "" {
|
2018-10-11 21:15:08 +00:00
|
|
|
|
multierror.Append(&mErr, errors.New("device name must be given as one of the following: type, vendor/type, or vendor/type/name"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for idx, constr := range r.Constraints {
|
|
|
|
|
// Ensure that the constraint doesn't use an operand we do not allow
|
|
|
|
|
switch constr.Operand {
|
|
|
|
|
case ConstraintDistinctHosts, ConstraintDistinctProperty:
|
|
|
|
|
outer := fmt.Errorf("Constraint %d validation failed: using unsupported operand %q", idx+1, constr.Operand)
|
|
|
|
|
multierror.Append(&mErr, outer)
|
|
|
|
|
default:
|
|
|
|
|
if err := constr.Validate(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Constraint %d validation failed: %s", idx+1, err)
|
|
|
|
|
multierror.Append(&mErr, outer)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for idx, affinity := range r.Affinities {
|
|
|
|
|
if err := affinity.Validate(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Affinity %d validation failed: %s", idx+1, err)
|
|
|
|
|
multierror.Append(&mErr, outer)
|
|
|
|
|
}
|
2018-10-10 17:32:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-11 21:15:08 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
2018-10-10 17:32:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// NodeResources is used to define the resources available on a client node.
|
2018-09-30 00:23:41 +00:00
|
|
|
|
type NodeResources struct {
|
|
|
|
|
Cpu NodeCpuResources
|
|
|
|
|
Memory NodeMemoryResources
|
|
|
|
|
Disk NodeDiskResources
|
|
|
|
|
Networks Networks
|
2018-10-09 21:36:58 +00:00
|
|
|
|
Devices []*NodeDeviceResource
|
2018-09-30 00:23:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
func (n *NodeResources) Copy() *NodeResources {
|
|
|
|
|
if n == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2018-10-09 21:36:58 +00:00
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
newN := new(NodeResources)
|
|
|
|
|
*newN = *n
|
2018-10-09 21:36:58 +00:00
|
|
|
|
|
|
|
|
|
// Copy the networks
|
2019-06-25 22:13:07 +00:00
|
|
|
|
newN.Networks = n.Networks.Copy()
|
2018-10-09 21:36:58 +00:00
|
|
|
|
|
|
|
|
|
// Copy the devices
|
|
|
|
|
if n.Devices != nil {
|
|
|
|
|
devices := len(n.Devices)
|
|
|
|
|
newN.Devices = make([]*NodeDeviceResource, devices)
|
|
|
|
|
for i := 0; i < devices; i++ {
|
|
|
|
|
newN.Devices[i] = n.Devices[i].Copy()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
return newN
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Comparable returns a comparable version of the nodes resources. This
|
|
|
|
|
// conversion can be lossy so care must be taken when using it.
|
2018-10-04 21:33:09 +00:00
|
|
|
|
func (n *NodeResources) Comparable() *ComparableResources {
|
2018-10-02 20:36:04 +00:00
|
|
|
|
if n == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-04 21:33:09 +00:00
|
|
|
|
c := &ComparableResources{
|
2018-10-02 20:36:04 +00:00
|
|
|
|
Flattened: AllocatedTaskResources{
|
|
|
|
|
Cpu: AllocatedCpuResources{
|
2018-10-04 21:33:09 +00:00
|
|
|
|
CpuShares: n.Cpu.CpuShares,
|
2018-10-02 20:36:04 +00:00
|
|
|
|
},
|
|
|
|
|
Memory: AllocatedMemoryResources{
|
|
|
|
|
MemoryMB: n.Memory.MemoryMB,
|
|
|
|
|
},
|
|
|
|
|
Networks: n.Networks,
|
|
|
|
|
},
|
|
|
|
|
Shared: AllocatedSharedResources{
|
|
|
|
|
DiskMB: n.Disk.DiskMB,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-30 00:23:41 +00:00
|
|
|
|
func (n *NodeResources) Merge(o *NodeResources) {
|
|
|
|
|
if o == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n.Cpu.Merge(&o.Cpu)
|
|
|
|
|
n.Memory.Merge(&o.Memory)
|
|
|
|
|
n.Disk.Merge(&o.Disk)
|
|
|
|
|
|
|
|
|
|
if len(o.Networks) != 0 {
|
|
|
|
|
n.Networks = o.Networks
|
|
|
|
|
}
|
2018-10-09 21:36:58 +00:00
|
|
|
|
|
|
|
|
|
if len(o.Devices) != 0 {
|
|
|
|
|
n.Devices = o.Devices
|
|
|
|
|
}
|
2018-09-30 00:23:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeResources) Equals(o *NodeResources) bool {
|
|
|
|
|
if o == nil && n == nil {
|
|
|
|
|
return true
|
|
|
|
|
} else if o == nil {
|
|
|
|
|
return false
|
|
|
|
|
} else if n == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !n.Cpu.Equals(&o.Cpu) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if !n.Memory.Equals(&o.Memory) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if !n.Disk.Equals(&o.Disk) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2019-04-11 17:17:26 +00:00
|
|
|
|
if !n.Networks.Equals(&o.Networks) {
|
2018-09-30 00:23:41 +00:00
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-09 21:36:58 +00:00
|
|
|
|
// Check the devices
|
2018-11-14 03:20:03 +00:00
|
|
|
|
if !DevicesEquals(n.Devices, o.Devices) {
|
2018-10-09 21:36:58 +00:00
|
|
|
|
return false
|
|
|
|
|
}
|
2018-11-14 03:20:03 +00:00
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-11 17:17:26 +00:00
|
|
|
|
// Equals equates Networks as a set
|
|
|
|
|
func (n *Networks) Equals(o *Networks) bool {
|
|
|
|
|
if n == o {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if n == nil || o == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if len(*n) != len(*o) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2019-04-18 17:36:45 +00:00
|
|
|
|
SETEQUALS:
|
2019-04-11 17:17:26 +00:00
|
|
|
|
for _, ne := range *n {
|
|
|
|
|
for _, oe := range *o {
|
2019-04-18 16:28:34 +00:00
|
|
|
|
if ne.Equals(oe) {
|
2019-04-18 17:36:45 +00:00
|
|
|
|
continue SETEQUALS
|
2019-04-11 17:17:26 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-18 17:36:45 +00:00
|
|
|
|
return false
|
2019-04-11 17:17:26 +00:00
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DevicesEquals returns true if the two device arrays are set equal
|
2018-11-14 03:20:03 +00:00
|
|
|
|
func DevicesEquals(d1, d2 []*NodeDeviceResource) bool {
|
|
|
|
|
if len(d1) != len(d2) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
idMap := make(map[DeviceIdTuple]*NodeDeviceResource, len(d1))
|
|
|
|
|
for _, d := range d1 {
|
2018-10-09 21:36:58 +00:00
|
|
|
|
idMap[*d.ID()] = d
|
|
|
|
|
}
|
2018-11-14 03:20:03 +00:00
|
|
|
|
for _, otherD := range d2 {
|
2018-10-09 21:36:58 +00:00
|
|
|
|
if d, ok := idMap[*otherD.ID()]; !ok || !d.Equals(otherD) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-30 00:23:41 +00:00
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// NodeCpuResources captures the CPU resources of the node.
|
2018-09-30 00:23:41 +00:00
|
|
|
|
type NodeCpuResources struct {
|
2018-10-04 21:33:09 +00:00
|
|
|
|
// CpuShares is the CPU shares available. This is calculated by number of
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// cores multiplied by the core frequency.
|
2018-10-16 22:34:32 +00:00
|
|
|
|
CpuShares int64
|
2018-09-30 00:23:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeCpuResources) Merge(o *NodeCpuResources) {
|
|
|
|
|
if o == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-04 21:33:09 +00:00
|
|
|
|
if o.CpuShares != 0 {
|
|
|
|
|
n.CpuShares = o.CpuShares
|
2018-09-30 00:23:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeCpuResources) Equals(o *NodeCpuResources) bool {
|
|
|
|
|
if o == nil && n == nil {
|
|
|
|
|
return true
|
|
|
|
|
} else if o == nil {
|
|
|
|
|
return false
|
|
|
|
|
} else if n == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-04 21:33:09 +00:00
|
|
|
|
if n.CpuShares != o.CpuShares {
|
2018-09-30 00:23:41 +00:00
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// NodeMemoryResources captures the memory resources of the node
|
2018-09-30 00:23:41 +00:00
|
|
|
|
type NodeMemoryResources struct {
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// MemoryMB is the total available memory on the node
|
2018-10-16 22:34:32 +00:00
|
|
|
|
MemoryMB int64
|
2018-09-30 00:23:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeMemoryResources) Merge(o *NodeMemoryResources) {
|
|
|
|
|
if o == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if o.MemoryMB != 0 {
|
|
|
|
|
n.MemoryMB = o.MemoryMB
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeMemoryResources) Equals(o *NodeMemoryResources) bool {
|
|
|
|
|
if o == nil && n == nil {
|
|
|
|
|
return true
|
|
|
|
|
} else if o == nil {
|
|
|
|
|
return false
|
|
|
|
|
} else if n == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n.MemoryMB != o.MemoryMB {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// NodeDiskResources captures the disk resources of the node
|
2018-09-30 00:23:41 +00:00
|
|
|
|
type NodeDiskResources struct {
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// DiskMB is the total available disk space on the node
|
2018-10-16 22:34:32 +00:00
|
|
|
|
DiskMB int64
|
2018-09-30 00:23:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeDiskResources) Merge(o *NodeDiskResources) {
|
|
|
|
|
if o == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if o.DiskMB != 0 {
|
|
|
|
|
n.DiskMB = o.DiskMB
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeDiskResources) Equals(o *NodeDiskResources) bool {
|
|
|
|
|
if o == nil && n == nil {
|
|
|
|
|
return true
|
|
|
|
|
} else if o == nil {
|
|
|
|
|
return false
|
|
|
|
|
} else if n == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n.DiskMB != o.DiskMB {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-09 21:36:58 +00:00
|
|
|
|
// DeviceIdTuple is the tuple that identifies a device
|
|
|
|
|
type DeviceIdTuple struct {
|
|
|
|
|
Vendor string
|
|
|
|
|
Type string
|
|
|
|
|
Name string
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-16 23:29:59 +00:00
|
|
|
|
func (d *DeviceIdTuple) String() string {
|
|
|
|
|
if d == nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fmt.Sprintf("%s/%s/%s", d.Vendor, d.Type, d.Name)
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-10 17:32:44 +00:00
|
|
|
|
// Matches returns if this Device ID is a superset of the passed ID.
|
|
|
|
|
func (id *DeviceIdTuple) Matches(other *DeviceIdTuple) bool {
|
|
|
|
|
if other == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if other.Name != "" && other.Name != id.Name {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if other.Vendor != "" && other.Vendor != id.Vendor {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if other.Type != "" && other.Type != id.Type {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-15 22:15:46 +00:00
|
|
|
|
// Equals returns if this Device ID is the same as the passed ID.
|
|
|
|
|
func (id *DeviceIdTuple) Equals(o *DeviceIdTuple) bool {
|
|
|
|
|
if id == nil && o == nil {
|
|
|
|
|
return true
|
|
|
|
|
} else if id == nil || o == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return o.Vendor == id.Vendor && o.Type == id.Type && o.Name == id.Name
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-09 21:36:58 +00:00
|
|
|
|
// NodeDeviceResource captures a set of devices sharing a common
|
|
|
|
|
// vendor/type/device_name tuple.
|
|
|
|
|
type NodeDeviceResource struct {
|
|
|
|
|
Vendor string
|
|
|
|
|
Type string
|
|
|
|
|
Name string
|
|
|
|
|
Instances []*NodeDevice
|
2018-10-13 21:08:58 +00:00
|
|
|
|
Attributes map[string]*psstructs.Attribute
|
2018-10-09 21:36:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeDeviceResource) ID() *DeviceIdTuple {
|
|
|
|
|
if n == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &DeviceIdTuple{
|
|
|
|
|
Vendor: n.Vendor,
|
|
|
|
|
Type: n.Type,
|
|
|
|
|
Name: n.Name,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeDeviceResource) Copy() *NodeDeviceResource {
|
|
|
|
|
if n == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy the primitives
|
|
|
|
|
nn := *n
|
|
|
|
|
|
|
|
|
|
// Copy the device instances
|
|
|
|
|
if l := len(nn.Instances); l != 0 {
|
|
|
|
|
nn.Instances = make([]*NodeDevice, 0, l)
|
|
|
|
|
for _, d := range n.Instances {
|
|
|
|
|
nn.Instances = append(nn.Instances, d.Copy())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy the Attributes
|
2018-10-13 21:08:58 +00:00
|
|
|
|
nn.Attributes = psstructs.CopyMapStringAttribute(nn.Attributes)
|
2018-10-09 21:36:58 +00:00
|
|
|
|
|
|
|
|
|
return &nn
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeDeviceResource) Equals(o *NodeDeviceResource) bool {
|
|
|
|
|
if o == nil && n == nil {
|
|
|
|
|
return true
|
|
|
|
|
} else if o == nil {
|
|
|
|
|
return false
|
|
|
|
|
} else if n == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n.Vendor != o.Vendor {
|
|
|
|
|
return false
|
|
|
|
|
} else if n.Type != o.Type {
|
|
|
|
|
return false
|
|
|
|
|
} else if n.Name != o.Name {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check the attributes
|
|
|
|
|
if len(n.Attributes) != len(o.Attributes) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for k, v := range n.Attributes {
|
|
|
|
|
if otherV, ok := o.Attributes[k]; !ok || v != otherV {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check the instances
|
|
|
|
|
if len(n.Instances) != len(o.Instances) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
idMap := make(map[string]*NodeDevice, len(n.Instances))
|
|
|
|
|
for _, d := range n.Instances {
|
|
|
|
|
idMap[d.ID] = d
|
|
|
|
|
}
|
|
|
|
|
for _, otherD := range o.Instances {
|
|
|
|
|
if d, ok := idMap[otherD.ID]; !ok || !d.Equals(otherD) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NodeDevice is an instance of a particular device.
|
|
|
|
|
type NodeDevice struct {
|
|
|
|
|
// ID is the ID of the device.
|
|
|
|
|
ID string
|
|
|
|
|
|
|
|
|
|
// Healthy captures whether the device is healthy.
|
|
|
|
|
Healthy bool
|
|
|
|
|
|
|
|
|
|
// HealthDescription is used to provide a human readable description of why
|
|
|
|
|
// the device may be unhealthy.
|
|
|
|
|
HealthDescription string
|
|
|
|
|
|
|
|
|
|
// Locality stores HW locality information for the node to optionally be
|
|
|
|
|
// used when making placement decisions.
|
|
|
|
|
Locality *NodeDeviceLocality
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeDevice) Equals(o *NodeDevice) bool {
|
|
|
|
|
if o == nil && n == nil {
|
|
|
|
|
return true
|
|
|
|
|
} else if o == nil {
|
|
|
|
|
return false
|
|
|
|
|
} else if n == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n.ID != o.ID {
|
|
|
|
|
return false
|
|
|
|
|
} else if n.Healthy != o.Healthy {
|
|
|
|
|
return false
|
|
|
|
|
} else if n.HealthDescription != o.HealthDescription {
|
|
|
|
|
return false
|
|
|
|
|
} else if !n.Locality.Equals(o.Locality) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeDevice) Copy() *NodeDevice {
|
|
|
|
|
if n == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy the primitives
|
|
|
|
|
nn := *n
|
|
|
|
|
|
|
|
|
|
// Copy the locality
|
|
|
|
|
nn.Locality = nn.Locality.Copy()
|
|
|
|
|
|
|
|
|
|
return &nn
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NodeDeviceLocality stores information about the devices hardware locality on
|
|
|
|
|
// the node.
|
|
|
|
|
type NodeDeviceLocality struct {
|
|
|
|
|
// PciBusID is the PCI Bus ID for the device.
|
|
|
|
|
PciBusID string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeDeviceLocality) Equals(o *NodeDeviceLocality) bool {
|
|
|
|
|
if o == nil && n == nil {
|
|
|
|
|
return true
|
|
|
|
|
} else if o == nil {
|
|
|
|
|
return false
|
|
|
|
|
} else if n == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n.PciBusID != o.PciBusID {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *NodeDeviceLocality) Copy() *NodeDeviceLocality {
|
|
|
|
|
if n == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy the primitives
|
|
|
|
|
nn := *n
|
|
|
|
|
return &nn
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// NodeReservedResources is used to capture the resources on a client node that
|
|
|
|
|
// should be reserved and not made available to jobs.
|
2018-09-30 01:44:55 +00:00
|
|
|
|
type NodeReservedResources struct {
|
|
|
|
|
Cpu NodeReservedCpuResources
|
|
|
|
|
Memory NodeReservedMemoryResources
|
|
|
|
|
Disk NodeReservedDiskResources
|
|
|
|
|
Networks NodeReservedNetworkResources
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
func (n *NodeReservedResources) Copy() *NodeReservedResources {
|
|
|
|
|
if n == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
newN := new(NodeReservedResources)
|
|
|
|
|
*newN = *n
|
|
|
|
|
return newN
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Comparable returns a comparable version of the node's reserved resources. The
|
|
|
|
|
// returned resources doesn't contain any network information. This conversion
|
|
|
|
|
// can be lossy so care must be taken when using it.
|
2018-10-04 21:33:09 +00:00
|
|
|
|
func (n *NodeReservedResources) Comparable() *ComparableResources {
|
2018-10-02 20:36:04 +00:00
|
|
|
|
if n == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-04 21:33:09 +00:00
|
|
|
|
c := &ComparableResources{
|
2018-10-02 20:36:04 +00:00
|
|
|
|
Flattened: AllocatedTaskResources{
|
|
|
|
|
Cpu: AllocatedCpuResources{
|
2018-10-04 21:33:09 +00:00
|
|
|
|
CpuShares: n.Cpu.CpuShares,
|
2018-10-02 20:36:04 +00:00
|
|
|
|
},
|
|
|
|
|
Memory: AllocatedMemoryResources{
|
|
|
|
|
MemoryMB: n.Memory.MemoryMB,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Shared: AllocatedSharedResources{
|
|
|
|
|
DiskMB: n.Disk.DiskMB,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NodeReservedCpuResources captures the reserved CPU resources of the node.
|
2018-09-30 01:44:55 +00:00
|
|
|
|
type NodeReservedCpuResources struct {
|
2018-10-16 22:34:32 +00:00
|
|
|
|
CpuShares int64
|
2018-09-30 01:44:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// NodeReservedMemoryResources captures the reserved memory resources of the node.
|
2018-09-30 01:44:55 +00:00
|
|
|
|
type NodeReservedMemoryResources struct {
|
2018-10-16 22:34:32 +00:00
|
|
|
|
MemoryMB int64
|
2018-09-30 01:44:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// NodeReservedDiskResources captures the reserved disk resources of the node.
|
2018-09-30 01:44:55 +00:00
|
|
|
|
type NodeReservedDiskResources struct {
|
2018-10-16 22:34:32 +00:00
|
|
|
|
DiskMB int64
|
2018-09-30 01:44:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// NodeReservedNetworkResources captures the reserved network resources of the node.
|
2018-09-30 01:44:55 +00:00
|
|
|
|
type NodeReservedNetworkResources struct {
|
|
|
|
|
// ReservedHostPorts is the set of ports reserved on all host network
|
|
|
|
|
// interfaces. Its format is a comma separate list of integers or integer
|
|
|
|
|
// ranges. (80,443,1000-2000,2005)
|
|
|
|
|
ReservedHostPorts string
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// ParsePortHostPorts returns the reserved host ports.
|
|
|
|
|
func (n *NodeReservedNetworkResources) ParseReservedHostPorts() ([]uint64, error) {
|
|
|
|
|
return ParsePortRanges(n.ReservedHostPorts)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AllocatedResources is the set of resources to be used by an allocation.
|
2018-09-30 01:02:48 +00:00
|
|
|
|
type AllocatedResources struct {
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// Tasks is a mapping of task name to the resources for the task.
|
|
|
|
|
Tasks map[string]*AllocatedTaskResources
|
|
|
|
|
|
|
|
|
|
// Shared is the set of resource that are shared by all tasks in the group.
|
|
|
|
|
Shared AllocatedSharedResources
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AllocatedResources) Copy() *AllocatedResources {
|
|
|
|
|
if a == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2019-06-25 22:13:07 +00:00
|
|
|
|
|
|
|
|
|
out := AllocatedResources{
|
|
|
|
|
Shared: a.Shared.Copy(),
|
|
|
|
|
}
|
2018-10-02 20:36:04 +00:00
|
|
|
|
|
|
|
|
|
if a.Tasks != nil {
|
2019-06-25 22:13:07 +00:00
|
|
|
|
out.Tasks = make(map[string]*AllocatedTaskResources, len(out.Tasks))
|
|
|
|
|
for task, resource := range a.Tasks {
|
|
|
|
|
out.Tasks[task] = resource.Copy()
|
2018-10-02 20:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-25 22:13:07 +00:00
|
|
|
|
return &out
|
2018-09-30 01:02:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// Comparable returns a comparable version of the allocations allocated
|
|
|
|
|
// resources. This conversion can be lossy so care must be taken when using it.
|
2018-10-04 21:33:09 +00:00
|
|
|
|
func (a *AllocatedResources) Comparable() *ComparableResources {
|
2018-10-02 20:36:04 +00:00
|
|
|
|
if a == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-04 21:33:09 +00:00
|
|
|
|
c := &ComparableResources{
|
2018-10-02 20:36:04 +00:00
|
|
|
|
Shared: a.Shared,
|
|
|
|
|
}
|
|
|
|
|
for _, r := range a.Tasks {
|
|
|
|
|
c.Flattened.Add(r)
|
|
|
|
|
}
|
2019-07-03 18:29:47 +00:00
|
|
|
|
// Add network resources that are at the task group level
|
2019-07-05 15:54:00 +00:00
|
|
|
|
for _, network := range a.Shared.Networks {
|
|
|
|
|
c.Flattened.Add(&AllocatedTaskResources{
|
|
|
|
|
Networks: []*NetworkResource{network},
|
|
|
|
|
})
|
2019-07-03 18:29:47 +00:00
|
|
|
|
}
|
2019-07-05 15:54:00 +00:00
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// OldTaskResources returns the pre-0.9.0 map of task resources
|
|
|
|
|
func (a *AllocatedResources) OldTaskResources() map[string]*Resources {
|
|
|
|
|
m := make(map[string]*Resources, len(a.Tasks))
|
|
|
|
|
for name, res := range a.Tasks {
|
|
|
|
|
m[name] = &Resources{
|
|
|
|
|
CPU: int(res.Cpu.CpuShares),
|
|
|
|
|
MemoryMB: int(res.Memory.MemoryMB),
|
|
|
|
|
Networks: res.Networks,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return m
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AllocatedTaskResources are the set of resources allocated to a task.
|
2018-09-30 01:02:48 +00:00
|
|
|
|
type AllocatedTaskResources struct {
|
2018-10-02 20:36:04 +00:00
|
|
|
|
Cpu AllocatedCpuResources
|
2018-09-30 01:02:48 +00:00
|
|
|
|
Memory AllocatedMemoryResources
|
|
|
|
|
Networks Networks
|
2018-10-15 20:45:38 +00:00
|
|
|
|
Devices []*AllocatedDeviceResource
|
2018-09-30 01:02:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
func (a *AllocatedTaskResources) Copy() *AllocatedTaskResources {
|
|
|
|
|
if a == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
newA := new(AllocatedTaskResources)
|
|
|
|
|
*newA = *a
|
2018-10-15 20:45:38 +00:00
|
|
|
|
|
|
|
|
|
// Copy the networks
|
2019-06-25 22:13:07 +00:00
|
|
|
|
newA.Networks = a.Networks.Copy()
|
2018-10-15 20:45:38 +00:00
|
|
|
|
|
|
|
|
|
// Copy the devices
|
|
|
|
|
if newA.Devices != nil {
|
|
|
|
|
n := len(a.Devices)
|
|
|
|
|
newA.Devices = make([]*AllocatedDeviceResource, n)
|
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
|
newA.Devices[i] = a.Devices[i].Copy()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
return newA
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NetIndex finds the matching net index using device name
|
|
|
|
|
func (a *AllocatedTaskResources) NetIndex(n *NetworkResource) int {
|
2018-10-03 16:47:18 +00:00
|
|
|
|
return a.Networks.NetIndex(n)
|
2018-10-02 20:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AllocatedTaskResources) Add(delta *AllocatedTaskResources) {
|
|
|
|
|
if delta == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.Cpu.Add(&delta.Cpu)
|
|
|
|
|
a.Memory.Add(&delta.Memory)
|
|
|
|
|
|
|
|
|
|
for _, n := range delta.Networks {
|
|
|
|
|
// Find the matching interface by IP or CIDR
|
|
|
|
|
idx := a.NetIndex(n)
|
|
|
|
|
if idx == -1 {
|
|
|
|
|
a.Networks = append(a.Networks, n.Copy())
|
|
|
|
|
} else {
|
|
|
|
|
a.Networks[idx].Add(n)
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-10-15 20:45:38 +00:00
|
|
|
|
|
|
|
|
|
for _, d := range delta.Devices {
|
|
|
|
|
// Find the matching device
|
2018-10-15 22:15:46 +00:00
|
|
|
|
idx := AllocatedDevices(a.Devices).Index(d)
|
2018-10-15 20:45:38 +00:00
|
|
|
|
if idx == -1 {
|
|
|
|
|
a.Devices = append(a.Devices, d.Copy())
|
|
|
|
|
} else {
|
|
|
|
|
a.Devices[idx].Add(d)
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-10-02 20:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-16 21:21:42 +00:00
|
|
|
|
// Comparable turns AllocatedTaskResources into ComparableResources
|
|
|
|
|
// as a helper step in preemption
|
|
|
|
|
func (a *AllocatedTaskResources) Comparable() *ComparableResources {
|
|
|
|
|
ret := &ComparableResources{
|
|
|
|
|
Flattened: AllocatedTaskResources{
|
|
|
|
|
Cpu: AllocatedCpuResources{
|
|
|
|
|
CpuShares: a.Cpu.CpuShares,
|
|
|
|
|
},
|
|
|
|
|
Memory: AllocatedMemoryResources{
|
|
|
|
|
MemoryMB: a.Memory.MemoryMB,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
if len(a.Networks) > 0 {
|
|
|
|
|
for _, net := range a.Networks {
|
|
|
|
|
ret.Flattened.Networks = append(ret.Flattened.Networks, net)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-02 23:11:36 +00:00
|
|
|
|
// Subtract only subtracts CPU and Memory resources. Network utilization
|
|
|
|
|
// is managed separately in NetworkIndex
|
2018-10-16 21:21:42 +00:00
|
|
|
|
func (a *AllocatedTaskResources) Subtract(delta *AllocatedTaskResources) {
|
|
|
|
|
if delta == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.Cpu.Subtract(&delta.Cpu)
|
|
|
|
|
a.Memory.Subtract(&delta.Memory)
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// AllocatedSharedResources are the set of resources allocated to a task group.
|
2018-09-30 01:02:48 +00:00
|
|
|
|
type AllocatedSharedResources struct {
|
2019-04-29 17:15:12 +00:00
|
|
|
|
Networks Networks
|
|
|
|
|
DiskMB int64
|
2018-09-30 01:02:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-25 22:13:07 +00:00
|
|
|
|
func (a AllocatedSharedResources) Copy() AllocatedSharedResources {
|
|
|
|
|
return AllocatedSharedResources{
|
|
|
|
|
Networks: a.Networks.Copy(),
|
|
|
|
|
DiskMB: a.DiskMB,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
func (a *AllocatedSharedResources) Add(delta *AllocatedSharedResources) {
|
|
|
|
|
if delta == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
2019-04-29 17:15:12 +00:00
|
|
|
|
a.Networks = append(a.Networks, delta.Networks...)
|
2018-10-02 20:36:04 +00:00
|
|
|
|
a.DiskMB += delta.DiskMB
|
2019-04-29 17:15:12 +00:00
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-16 21:21:42 +00:00
|
|
|
|
func (a *AllocatedSharedResources) Subtract(delta *AllocatedSharedResources) {
|
|
|
|
|
if delta == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-29 17:15:12 +00:00
|
|
|
|
diff := map[*NetworkResource]bool{}
|
|
|
|
|
for _, n := range delta.Networks {
|
|
|
|
|
diff[n] = true
|
|
|
|
|
}
|
|
|
|
|
var nets Networks
|
|
|
|
|
for _, n := range a.Networks {
|
|
|
|
|
if _, ok := diff[n]; !ok {
|
|
|
|
|
nets = append(nets, n)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
a.Networks = nets
|
2018-10-16 21:21:42 +00:00
|
|
|
|
a.DiskMB -= delta.DiskMB
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// AllocatedCpuResources captures the allocated CPU resources.
|
2018-09-30 01:02:48 +00:00
|
|
|
|
type AllocatedCpuResources struct {
|
2018-10-16 22:34:32 +00:00
|
|
|
|
CpuShares int64
|
2018-09-30 01:02:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
func (a *AllocatedCpuResources) Add(delta *AllocatedCpuResources) {
|
|
|
|
|
if delta == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.CpuShares += delta.CpuShares
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-16 21:21:42 +00:00
|
|
|
|
func (a *AllocatedCpuResources) Subtract(delta *AllocatedCpuResources) {
|
|
|
|
|
if delta == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.CpuShares -= delta.CpuShares
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// AllocatedMemoryResources captures the allocated memory resources.
|
2018-09-30 01:02:48 +00:00
|
|
|
|
type AllocatedMemoryResources struct {
|
2018-10-16 22:34:32 +00:00
|
|
|
|
MemoryMB int64
|
2018-10-02 20:36:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AllocatedMemoryResources) Add(delta *AllocatedMemoryResources) {
|
|
|
|
|
if delta == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.MemoryMB += delta.MemoryMB
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-16 21:21:42 +00:00
|
|
|
|
func (a *AllocatedMemoryResources) Subtract(delta *AllocatedMemoryResources) {
|
|
|
|
|
if delta == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.MemoryMB -= delta.MemoryMB
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-15 20:45:38 +00:00
|
|
|
|
type AllocatedDevices []*AllocatedDeviceResource
|
|
|
|
|
|
|
|
|
|
// Index finds the matching index using the passed device. If not found, -1 is
|
|
|
|
|
// returned.
|
|
|
|
|
func (a AllocatedDevices) Index(d *AllocatedDeviceResource) int {
|
|
|
|
|
if d == nil {
|
|
|
|
|
return -1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i, o := range a {
|
2018-10-15 22:15:46 +00:00
|
|
|
|
if o.ID().Equals(d.ID()) {
|
2018-10-15 20:45:38 +00:00
|
|
|
|
return i
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AllocatedDeviceResource captures a set of allocated devices.
|
|
|
|
|
type AllocatedDeviceResource struct {
|
|
|
|
|
// Vendor, Type, and Name are used to select the plugin to request the
|
|
|
|
|
// device IDs from.
|
|
|
|
|
Vendor string
|
|
|
|
|
Type string
|
|
|
|
|
Name string
|
|
|
|
|
|
|
|
|
|
// DeviceIDs is the set of allocated devices
|
|
|
|
|
DeviceIDs []string
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-15 22:15:46 +00:00
|
|
|
|
func (a *AllocatedDeviceResource) ID() *DeviceIdTuple {
|
|
|
|
|
if a == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &DeviceIdTuple{
|
|
|
|
|
Vendor: a.Vendor,
|
|
|
|
|
Type: a.Type,
|
|
|
|
|
Name: a.Name,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-15 20:45:38 +00:00
|
|
|
|
func (a *AllocatedDeviceResource) Add(delta *AllocatedDeviceResource) {
|
|
|
|
|
if delta == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.DeviceIDs = append(a.DeviceIDs, delta.DeviceIDs...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AllocatedDeviceResource) Copy() *AllocatedDeviceResource {
|
|
|
|
|
if a == nil {
|
|
|
|
|
return a
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
na := *a
|
|
|
|
|
|
|
|
|
|
// Copy the devices
|
|
|
|
|
na.DeviceIDs = make([]string, len(a.DeviceIDs))
|
|
|
|
|
for i, id := range a.DeviceIDs {
|
|
|
|
|
na.DeviceIDs[i] = id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &na
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-04 21:33:09 +00:00
|
|
|
|
// ComparableResources is the set of resources allocated to a task group but
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// not keyed by Task, making it easier to compare.
|
2018-10-04 21:33:09 +00:00
|
|
|
|
type ComparableResources struct {
|
2018-10-02 20:36:04 +00:00
|
|
|
|
Flattened AllocatedTaskResources
|
|
|
|
|
Shared AllocatedSharedResources
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-04 21:33:09 +00:00
|
|
|
|
func (c *ComparableResources) Add(delta *ComparableResources) {
|
2018-10-02 20:36:04 +00:00
|
|
|
|
if delta == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.Flattened.Add(&delta.Flattened)
|
|
|
|
|
c.Shared.Add(&delta.Shared)
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-16 21:21:42 +00:00
|
|
|
|
func (c *ComparableResources) Subtract(delta *ComparableResources) {
|
|
|
|
|
if delta == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.Flattened.Subtract(&delta.Flattened)
|
|
|
|
|
c.Shared.Subtract(&delta.Shared)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ComparableResources) Copy() *ComparableResources {
|
|
|
|
|
if c == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
newR := new(ComparableResources)
|
|
|
|
|
*newR = *c
|
|
|
|
|
return newR
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// Superset checks if one set of resources is a superset of another. This
|
|
|
|
|
// ignores network resources, and the NetworkIndex should be used for that.
|
2018-10-04 21:33:09 +00:00
|
|
|
|
func (c *ComparableResources) Superset(other *ComparableResources) (bool, string) {
|
2018-10-02 20:36:04 +00:00
|
|
|
|
if c.Flattened.Cpu.CpuShares < other.Flattened.Cpu.CpuShares {
|
|
|
|
|
return false, "cpu"
|
|
|
|
|
}
|
|
|
|
|
if c.Flattened.Memory.MemoryMB < other.Flattened.Memory.MemoryMB {
|
|
|
|
|
return false, "memory"
|
|
|
|
|
}
|
|
|
|
|
if c.Shared.DiskMB < other.Shared.DiskMB {
|
|
|
|
|
return false, "disk"
|
|
|
|
|
}
|
|
|
|
|
return true, ""
|
2018-09-30 01:02:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-03 16:47:18 +00:00
|
|
|
|
// allocated finds the matching net index using device name
|
2018-10-04 21:33:09 +00:00
|
|
|
|
func (c *ComparableResources) NetIndex(n *NetworkResource) int {
|
2018-10-03 16:47:18 +00:00
|
|
|
|
return c.Flattened.Networks.NetIndex(n)
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
const (
|
2015-08-15 19:38:58 +00:00
|
|
|
|
// JobTypeNomad is reserved for internal system tasks and is
|
|
|
|
|
// always handled by the CoreScheduler.
|
|
|
|
|
JobTypeCore = "_core"
|
2015-07-03 23:57:48 +00:00
|
|
|
|
JobTypeService = "service"
|
|
|
|
|
JobTypeBatch = "batch"
|
2015-10-14 23:43:06 +00:00
|
|
|
|
JobTypeSystem = "system"
|
2015-07-03 23:57:48 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2016-01-08 04:08:14 +00:00
|
|
|
|
JobStatusPending = "pending" // Pending means the job is waiting on scheduling
|
|
|
|
|
JobStatusRunning = "running" // Running means the job has non-terminal allocations
|
|
|
|
|
JobStatusDead = "dead" // Dead means all evaluation's and allocations are terminal
|
2015-07-03 23:57:48 +00:00
|
|
|
|
)
|
|
|
|
|
|
2015-08-06 18:48:44 +00:00
|
|
|
|
const (
|
|
|
|
|
// JobMinPriority is the minimum allowed priority
|
|
|
|
|
JobMinPriority = 1
|
|
|
|
|
|
|
|
|
|
// JobDefaultPriority is the default priority if not
|
|
|
|
|
// not specified.
|
|
|
|
|
JobDefaultPriority = 50
|
|
|
|
|
|
|
|
|
|
// JobMaxPriority is the maximum allowed priority
|
|
|
|
|
JobMaxPriority = 100
|
2015-08-15 22:15:00 +00:00
|
|
|
|
|
|
|
|
|
// Ensure CoreJobPriority is higher than any user
|
|
|
|
|
// specified job so that it gets priority. This is important
|
|
|
|
|
// for the system to remain healthy.
|
|
|
|
|
CoreJobPriority = JobMaxPriority * 2
|
2017-04-13 20:54:57 +00:00
|
|
|
|
|
2017-04-13 21:54:22 +00:00
|
|
|
|
// JobTrackedVersions is the number of historic job versions that are
|
2017-04-13 20:54:57 +00:00
|
|
|
|
// kept.
|
2017-04-13 21:54:22 +00:00
|
|
|
|
JobTrackedVersions = 6
|
2015-08-06 18:48:44 +00:00
|
|
|
|
)
|
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Job is the scope of a scheduling request to Nomad. It is the largest
|
|
|
|
|
// scoped object, and is a named collection of task groups. Each task group
|
|
|
|
|
// is further composed of tasks. A task group (TG) is the unit of scheduling
|
|
|
|
|
// however.
|
|
|
|
|
type Job struct {
|
2017-04-15 03:54:30 +00:00
|
|
|
|
// Stop marks whether the user has stopped the job. A stopped job will
|
|
|
|
|
// have all created allocations stopped and acts as a way to stop a job
|
|
|
|
|
// without purging it from the system. This allows existing allocs to be
|
|
|
|
|
// queried and the job to be inspected as it is being killed.
|
|
|
|
|
Stop bool
|
|
|
|
|
|
2015-09-15 17:46:10 +00:00
|
|
|
|
// Region is the Nomad region that handles scheduling this job
|
|
|
|
|
Region string
|
|
|
|
|
|
2017-09-07 23:56:15 +00:00
|
|
|
|
// Namespace is the namespace the job is submitted into.
|
|
|
|
|
Namespace string
|
|
|
|
|
|
2015-09-15 17:46:10 +00:00
|
|
|
|
// ID is a unique identifier for the job per region. It can be
|
|
|
|
|
// specified hierarchically like LineOfBiz/OrgName/Team/Project
|
2015-07-23 22:15:48 +00:00
|
|
|
|
ID string
|
|
|
|
|
|
2015-12-18 20:26:28 +00:00
|
|
|
|
// ParentID is the unique identifier of the job that spawned this job.
|
|
|
|
|
ParentID string
|
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Name is the logical name of the job used to refer to it. This is unique
|
|
|
|
|
// per region, but not unique globally.
|
|
|
|
|
Name string
|
|
|
|
|
|
|
|
|
|
// Type is used to control various behaviors about the job. Most jobs
|
|
|
|
|
// are service jobs, meaning they are expected to be long lived.
|
|
|
|
|
// Some jobs are batch oriented meaning they run and then terminate.
|
|
|
|
|
// This can be extended in the future to support custom schedulers.
|
|
|
|
|
Type string
|
|
|
|
|
|
|
|
|
|
// Priority is used to control scheduling importance and if this job
|
|
|
|
|
// can preempt other jobs.
|
|
|
|
|
Priority int
|
|
|
|
|
|
|
|
|
|
// AllAtOnce is used to control if incremental scheduling of task groups
|
|
|
|
|
// is allowed or if we must do a gang scheduling of the entire job. This
|
|
|
|
|
// can slow down larger jobs if resources are not available.
|
2017-02-22 20:30:05 +00:00
|
|
|
|
AllAtOnce bool
|
2015-07-03 23:57:48 +00:00
|
|
|
|
|
2015-08-13 21:02:39 +00:00
|
|
|
|
// Datacenters contains all the datacenters this job is allowed to span
|
|
|
|
|
Datacenters []string
|
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Constraints can be specified at a job level and apply to
|
|
|
|
|
// all the task groups and tasks.
|
|
|
|
|
Constraints []*Constraint
|
|
|
|
|
|
2018-07-16 13:30:58 +00:00
|
|
|
|
// Affinities can be specified at the job level to express
|
|
|
|
|
// scheduling preferences that apply to all groups and tasks
|
|
|
|
|
Affinities []*Affinity
|
|
|
|
|
|
2018-07-16 17:52:24 +00:00
|
|
|
|
// Spread can be specified at the job level to express spreading
|
|
|
|
|
// allocations across a desired attribute, such as datacenter
|
|
|
|
|
Spreads []*Spread
|
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// TaskGroups are the collections of task groups that this job needs
|
|
|
|
|
// to run. Each task group is an atomic unit of scheduling and placement.
|
|
|
|
|
TaskGroups []*TaskGroup
|
|
|
|
|
|
2019-05-21 21:24:18 +00:00
|
|
|
|
// See agent.ApiJobToStructJob
|
|
|
|
|
// Update provides defaults for the TaskGroup Update stanzas
|
2015-09-07 22:08:50 +00:00
|
|
|
|
Update UpdateStrategy
|
|
|
|
|
|
2015-12-01 00:51:56 +00:00
|
|
|
|
// Periodic is used to define the interval the job is run at.
|
2015-12-01 16:40:32 +00:00
|
|
|
|
Periodic *PeriodicConfig
|
2015-12-01 00:51:56 +00:00
|
|
|
|
|
2017-01-20 18:33:52 +00:00
|
|
|
|
// ParameterizedJob is used to specify the job as a parameterized job
|
|
|
|
|
// for dispatching.
|
|
|
|
|
ParameterizedJob *ParameterizedJobConfig
|
2016-11-23 22:56:50 +00:00
|
|
|
|
|
2018-06-11 15:59:03 +00:00
|
|
|
|
// Dispatched is used to identify if the Job has been dispatched from a
|
|
|
|
|
// parameterized job.
|
|
|
|
|
Dispatched bool
|
|
|
|
|
|
2016-12-14 20:50:08 +00:00
|
|
|
|
// Payload is the payload supplied when the job was dispatched.
|
|
|
|
|
Payload []byte
|
2016-11-26 02:04:55 +00:00
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Meta is used to associate arbitrary metadata with this
|
|
|
|
|
// job. This is opaque to Nomad.
|
|
|
|
|
Meta map[string]string
|
|
|
|
|
|
2016-08-10 18:51:19 +00:00
|
|
|
|
// VaultToken is the Vault token that proves the submitter of the job has
|
|
|
|
|
// access to the specified Vault policies. This field is only used to
|
|
|
|
|
// transfer the token and is not stored after Job submission.
|
2017-02-22 20:30:05 +00:00
|
|
|
|
VaultToken string
|
2016-08-10 18:51:19 +00:00
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Job status
|
|
|
|
|
Status string
|
2015-07-04 00:50:54 +00:00
|
|
|
|
|
2015-08-15 20:08:06 +00:00
|
|
|
|
// StatusDescription is meant to provide more human useful information
|
|
|
|
|
StatusDescription string
|
|
|
|
|
|
2017-04-13 20:54:57 +00:00
|
|
|
|
// Stable marks a job as stable. Stability is only defined on "service" and
|
|
|
|
|
// "system" jobs. The stability of a job will be set automatically as part
|
2019-08-05 20:00:53 +00:00
|
|
|
|
// of a deployment and can be manually set via APIs. This field is updated
|
|
|
|
|
// when the status of a corresponding deployment transitions to Failed
|
|
|
|
|
// or Successful. This field is not meaningful for jobs that don't have an
|
|
|
|
|
// update stanza.
|
2017-04-13 20:54:57 +00:00
|
|
|
|
Stable bool
|
|
|
|
|
|
2018-03-11 18:28:31 +00:00
|
|
|
|
// Version is a monotonically increasing version number that is incremented
|
2017-04-13 20:54:57 +00:00
|
|
|
|
// on each job register.
|
|
|
|
|
Version uint64
|
|
|
|
|
|
2017-06-30 02:08:25 +00:00
|
|
|
|
// SubmitTime is the time at which the job was submitted as a UnixNano in
|
|
|
|
|
// UTC
|
|
|
|
|
SubmitTime int64
|
|
|
|
|
|
2015-07-04 00:50:54 +00:00
|
|
|
|
// Raft Indexes
|
2016-01-12 17:50:33 +00:00
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
|
|
|
|
JobModifyIndex uint64
|
2015-07-03 23:57:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-26 22:15:43 +00:00
|
|
|
|
// NamespacedID returns the namespaced id useful for logging
|
|
|
|
|
func (j *Job) NamespacedID() *NamespacedID {
|
|
|
|
|
return &NamespacedID{
|
|
|
|
|
ID: j.ID,
|
|
|
|
|
Namespace: j.Namespace,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-20 23:07:15 +00:00
|
|
|
|
// Canonicalize is used to canonicalize fields in the Job. This should be called
|
2017-07-07 02:08:51 +00:00
|
|
|
|
// when registering a Job. A set of warnings are returned if the job was changed
|
|
|
|
|
// in anyway that the user should be made aware of.
|
|
|
|
|
func (j *Job) Canonicalize() (warnings error) {
|
2017-09-07 23:56:15 +00:00
|
|
|
|
if j == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-07 02:08:51 +00:00
|
|
|
|
var mErr multierror.Error
|
2016-07-18 23:17:38 +00:00
|
|
|
|
// Ensure that an empty and nil map are treated the same to avoid scheduling
|
|
|
|
|
// problems since we use reflect DeepEquals.
|
|
|
|
|
if len(j.Meta) == 0 {
|
|
|
|
|
j.Meta = nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-07 23:56:15 +00:00
|
|
|
|
// Ensure the job is in a namespace.
|
|
|
|
|
if j.Namespace == "" {
|
|
|
|
|
j.Namespace = DefaultNamespace
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-18 20:17:13 +00:00
|
|
|
|
for _, tg := range j.TaskGroups {
|
2016-07-20 23:07:15 +00:00
|
|
|
|
tg.Canonicalize(j)
|
2015-12-18 20:17:13 +00:00
|
|
|
|
}
|
2016-11-23 22:56:50 +00:00
|
|
|
|
|
2017-01-20 18:33:52 +00:00
|
|
|
|
if j.ParameterizedJob != nil {
|
|
|
|
|
j.ParameterizedJob.Canonicalize()
|
2016-11-23 22:56:50 +00:00
|
|
|
|
}
|
2017-02-15 22:37:06 +00:00
|
|
|
|
|
|
|
|
|
if j.Periodic != nil {
|
|
|
|
|
j.Periodic.Canonicalize()
|
|
|
|
|
}
|
2017-05-10 20:48:53 +00:00
|
|
|
|
|
2017-07-07 02:08:51 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
2015-12-15 03:20:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-18 20:26:28 +00:00
|
|
|
|
// Copy returns a deep copy of the Job. It is expected that callers use recover.
|
|
|
|
|
// This job can panic if the deep copy failed as it uses reflection.
|
|
|
|
|
func (j *Job) Copy() *Job {
|
2016-02-11 01:54:43 +00:00
|
|
|
|
if j == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
nj := new(Job)
|
|
|
|
|
*nj = *j
|
2017-01-18 23:55:14 +00:00
|
|
|
|
nj.Datacenters = helper.CopySliceString(nj.Datacenters)
|
2016-02-11 17:08:20 +00:00
|
|
|
|
nj.Constraints = CopySliceConstraints(nj.Constraints)
|
2018-07-16 17:54:46 +00:00
|
|
|
|
nj.Affinities = CopySliceAffinities(nj.Affinities)
|
2015-12-18 20:26:28 +00:00
|
|
|
|
|
2016-03-21 23:29:21 +00:00
|
|
|
|
if j.TaskGroups != nil {
|
|
|
|
|
tgs := make([]*TaskGroup, len(nj.TaskGroups))
|
|
|
|
|
for i, tg := range nj.TaskGroups {
|
|
|
|
|
tgs[i] = tg.Copy()
|
|
|
|
|
}
|
|
|
|
|
nj.TaskGroups = tgs
|
2016-02-11 01:54:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nj.Periodic = nj.Periodic.Copy()
|
2017-01-18 23:55:14 +00:00
|
|
|
|
nj.Meta = helper.CopyMapStringString(nj.Meta)
|
2017-01-20 18:33:52 +00:00
|
|
|
|
nj.ParameterizedJob = nj.ParameterizedJob.Copy()
|
2016-02-11 01:54:43 +00:00
|
|
|
|
return nj
|
2015-12-18 20:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-15 17:46:10 +00:00
|
|
|
|
// Validate is used to sanity check a job input
|
|
|
|
|
func (j *Job) Validate() error {
|
|
|
|
|
var mErr multierror.Error
|
2017-02-06 19:48:28 +00:00
|
|
|
|
|
2015-09-15 17:46:10 +00:00
|
|
|
|
if j.Region == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing job region"))
|
|
|
|
|
}
|
|
|
|
|
if j.ID == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing job ID"))
|
2015-09-16 00:38:23 +00:00
|
|
|
|
} else if strings.Contains(j.ID, " ") {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Job ID contains a space"))
|
2015-09-15 17:46:10 +00:00
|
|
|
|
}
|
|
|
|
|
if j.Name == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing job name"))
|
|
|
|
|
}
|
2017-09-07 23:56:15 +00:00
|
|
|
|
if j.Namespace == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Job must be in a namespace"))
|
|
|
|
|
}
|
2017-07-07 22:34:26 +00:00
|
|
|
|
switch j.Type {
|
|
|
|
|
case JobTypeCore, JobTypeService, JobTypeBatch, JobTypeSystem:
|
|
|
|
|
case "":
|
2015-09-15 17:46:10 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing job type"))
|
2017-07-07 22:34:26 +00:00
|
|
|
|
default:
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Invalid job type: %q", j.Type))
|
2015-09-15 17:46:10 +00:00
|
|
|
|
}
|
|
|
|
|
if j.Priority < JobMinPriority || j.Priority > JobMaxPriority {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Job priority must be between [%d, %d]", JobMinPriority, JobMaxPriority))
|
|
|
|
|
}
|
|
|
|
|
if len(j.Datacenters) == 0 {
|
2015-09-25 19:27:31 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing job datacenters"))
|
2019-05-03 13:48:02 +00:00
|
|
|
|
} else {
|
|
|
|
|
for _, v := range j.Datacenters {
|
|
|
|
|
if v == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Job datacenter must be non-empty string"))
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-09-15 17:46:10 +00:00
|
|
|
|
}
|
|
|
|
|
if len(j.TaskGroups) == 0 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing job task groups"))
|
|
|
|
|
}
|
2015-10-11 19:50:16 +00:00
|
|
|
|
for idx, constr := range j.Constraints {
|
|
|
|
|
if err := constr.Validate(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Constraint %d validation failed: %s", idx+1, err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-23 16:06:49 +00:00
|
|
|
|
if j.Type == JobTypeSystem {
|
|
|
|
|
if j.Affinities != nil {
|
2018-07-24 15:37:13 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("System jobs may not have an affinity stanza"))
|
2018-07-23 16:06:49 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for idx, affinity := range j.Affinities {
|
|
|
|
|
if err := affinity.Validate(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Affinity %d validation failed: %s", idx+1, err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
2018-07-16 13:30:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-26 00:08:25 +00:00
|
|
|
|
if j.Type == JobTypeSystem {
|
|
|
|
|
if j.Spreads != nil {
|
2018-07-31 02:59:35 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("System jobs may not have a spread stanza"))
|
2018-07-26 00:08:25 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for idx, spread := range j.Spreads {
|
|
|
|
|
if err := spread.Validate(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Spread %d validation failed: %s", idx+1, err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
2018-07-18 16:04:59 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-15 17:46:10 +00:00
|
|
|
|
// Check for duplicate task groups
|
|
|
|
|
taskGroups := make(map[string]int)
|
|
|
|
|
for idx, tg := range j.TaskGroups {
|
|
|
|
|
if tg.Name == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Job task group %d missing name", idx+1))
|
|
|
|
|
} else if existing, ok := taskGroups[tg.Name]; ok {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Job task group %d redefines '%s' from group %d", idx+1, tg.Name, existing+1))
|
|
|
|
|
} else {
|
|
|
|
|
taskGroups[tg.Name] = idx
|
|
|
|
|
}
|
2015-10-16 21:15:01 +00:00
|
|
|
|
|
2016-07-13 19:50:08 +00:00
|
|
|
|
if j.Type == "system" && tg.Count > 1 {
|
2015-10-16 21:15:01 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors,
|
2016-07-20 13:23:35 +00:00
|
|
|
|
fmt.Errorf("Job task group %s has count %d. Count cannot exceed 1 with system scheduler",
|
|
|
|
|
tg.Name, tg.Count))
|
2015-10-16 21:15:01 +00:00
|
|
|
|
}
|
2015-09-15 18:23:03 +00:00
|
|
|
|
}
|
2015-09-15 17:46:10 +00:00
|
|
|
|
|
2015-09-15 18:23:03 +00:00
|
|
|
|
// Validate the task group
|
2016-07-20 13:23:35 +00:00
|
|
|
|
for _, tg := range j.TaskGroups {
|
2017-07-07 02:08:51 +00:00
|
|
|
|
if err := tg.Validate(j); err != nil {
|
2017-03-03 23:00:39 +00:00
|
|
|
|
outer := fmt.Errorf("Task group %s validation failed: %v", tg.Name, err)
|
2015-09-21 00:08:57 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
2015-09-15 17:46:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2015-12-01 00:51:56 +00:00
|
|
|
|
|
|
|
|
|
// Validate periodic is only used with batch jobs.
|
2016-07-13 19:50:08 +00:00
|
|
|
|
if j.IsPeriodic() && j.Periodic.Enabled {
|
2015-12-04 23:10:08 +00:00
|
|
|
|
if j.Type != JobTypeBatch {
|
|
|
|
|
mErr.Errors = append(mErr.Errors,
|
|
|
|
|
fmt.Errorf("Periodic can only be used with %q scheduler", JobTypeBatch))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := j.Periodic.Validate(); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
2015-12-01 00:51:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-20 18:33:52 +00:00
|
|
|
|
if j.IsParameterized() {
|
2016-12-16 23:20:12 +00:00
|
|
|
|
if j.Type != JobTypeBatch {
|
|
|
|
|
mErr.Errors = append(mErr.Errors,
|
2017-01-20 18:33:52 +00:00
|
|
|
|
fmt.Errorf("Parameterized job can only be used with %q scheduler", JobTypeBatch))
|
2016-12-16 23:20:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-20 18:33:52 +00:00
|
|
|
|
if err := j.ParameterizedJob.Validate(); err != nil {
|
2016-11-23 22:56:50 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-15 17:46:10 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-10 03:52:47 +00:00
|
|
|
|
// Warnings returns a list of warnings that may be from dubious settings or
|
|
|
|
|
// deprecation warnings.
|
|
|
|
|
func (j *Job) Warnings() error {
|
|
|
|
|
var mErr multierror.Error
|
2017-07-13 20:10:45 +00:00
|
|
|
|
|
|
|
|
|
// Check the groups
|
2019-05-21 20:52:43 +00:00
|
|
|
|
ap := 0
|
2017-07-13 20:10:45 +00:00
|
|
|
|
for _, tg := range j.TaskGroups {
|
|
|
|
|
if err := tg.Warnings(j); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Group %q has warnings: %v", tg.Name, err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
2019-05-21 22:43:38 +00:00
|
|
|
|
if tg.Update != nil && tg.Update.AutoPromote {
|
2019-05-21 20:52:43 +00:00
|
|
|
|
ap += 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check AutoPromote, should be all or none
|
|
|
|
|
if ap > 0 && ap < len(j.TaskGroups) {
|
|
|
|
|
err := fmt.Errorf("auto_promote must be true for all groups to enable automatic promotion")
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
2017-07-13 20:10:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-10 03:52:47 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-23 23:49:48 +00:00
|
|
|
|
// LookupTaskGroup finds a task group by name
|
2015-08-30 02:14:47 +00:00
|
|
|
|
func (j *Job) LookupTaskGroup(name string) *TaskGroup {
|
2015-08-23 23:49:48 +00:00
|
|
|
|
for _, tg := range j.TaskGroups {
|
|
|
|
|
if tg.Name == name {
|
|
|
|
|
return tg
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-16 01:08:38 +00:00
|
|
|
|
// CombinedTaskMeta takes a TaskGroup and Task name and returns the combined
|
|
|
|
|
// meta data for the task. When joining Job, Group and Task Meta, the precedence
|
|
|
|
|
// is by deepest scope (Task > Group > Job).
|
|
|
|
|
func (j *Job) CombinedTaskMeta(groupName, taskName string) map[string]string {
|
|
|
|
|
group := j.LookupTaskGroup(groupName)
|
|
|
|
|
if group == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
task := group.LookupTask(taskName)
|
|
|
|
|
if task == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-18 23:55:14 +00:00
|
|
|
|
meta := helper.CopyMapStringString(task.Meta)
|
2016-12-16 01:08:38 +00:00
|
|
|
|
if meta == nil {
|
|
|
|
|
meta = make(map[string]string, len(group.Meta)+len(j.Meta))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add the group specific meta
|
|
|
|
|
for k, v := range group.Meta {
|
|
|
|
|
if _, ok := meta[k]; !ok {
|
|
|
|
|
meta[k] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add the job specific meta
|
|
|
|
|
for k, v := range j.Meta {
|
|
|
|
|
if _, ok := meta[k]; !ok {
|
|
|
|
|
meta[k] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return meta
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-19 17:54:03 +00:00
|
|
|
|
// Stopped returns if a job is stopped.
|
|
|
|
|
func (j *Job) Stopped() bool {
|
|
|
|
|
return j == nil || j.Stop
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-23 00:06:46 +00:00
|
|
|
|
// HasUpdateStrategy returns if any task group in the job has an update strategy
|
2017-05-22 17:58:34 +00:00
|
|
|
|
func (j *Job) HasUpdateStrategy() bool {
|
|
|
|
|
for _, tg := range j.TaskGroups {
|
2019-09-02 17:30:09 +00:00
|
|
|
|
if !tg.Update.IsEmpty() {
|
2017-05-22 17:58:34 +00:00
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 22:34:28 +00:00
|
|
|
|
// Stub is used to return a summary of the job
|
2016-07-21 20:21:47 +00:00
|
|
|
|
func (j *Job) Stub(summary *JobSummary) *JobListStub {
|
2015-09-06 22:34:28 +00:00
|
|
|
|
return &JobListStub{
|
|
|
|
|
ID: j.ID,
|
2016-01-07 22:43:55 +00:00
|
|
|
|
ParentID: j.ParentID,
|
2015-09-06 22:34:28 +00:00
|
|
|
|
Name: j.Name,
|
2019-01-22 18:59:13 +00:00
|
|
|
|
Datacenters: j.Datacenters,
|
2015-09-06 22:34:28 +00:00
|
|
|
|
Type: j.Type,
|
|
|
|
|
Priority: j.Priority,
|
2017-04-16 00:05:52 +00:00
|
|
|
|
Periodic: j.IsPeriodic(),
|
|
|
|
|
ParameterizedJob: j.IsParameterized(),
|
2017-04-15 03:54:30 +00:00
|
|
|
|
Stop: j.Stop,
|
2015-09-06 22:34:28 +00:00
|
|
|
|
Status: j.Status,
|
|
|
|
|
StatusDescription: j.StatusDescription,
|
|
|
|
|
CreateIndex: j.CreateIndex,
|
|
|
|
|
ModifyIndex: j.ModifyIndex,
|
2016-06-08 23:48:02 +00:00
|
|
|
|
JobModifyIndex: j.JobModifyIndex,
|
2017-06-30 02:08:25 +00:00
|
|
|
|
SubmitTime: j.SubmitTime,
|
2016-07-21 20:21:47 +00:00
|
|
|
|
JobSummary: summary,
|
2015-09-06 22:34:28 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-01 16:40:32 +00:00
|
|
|
|
// IsPeriodic returns whether a job is periodic.
|
|
|
|
|
func (j *Job) IsPeriodic() bool {
|
|
|
|
|
return j.Periodic != nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-11 21:55:17 +00:00
|
|
|
|
// IsPeriodicActive returns whether the job is an active periodic job that will
|
|
|
|
|
// create child jobs
|
|
|
|
|
func (j *Job) IsPeriodicActive() bool {
|
|
|
|
|
return j.IsPeriodic() && j.Periodic.Enabled && !j.Stopped() && !j.IsParameterized()
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-20 18:33:52 +00:00
|
|
|
|
// IsParameterized returns whether a job is parameterized job.
|
|
|
|
|
func (j *Job) IsParameterized() bool {
|
2018-06-11 15:59:03 +00:00
|
|
|
|
return j.ParameterizedJob != nil && !j.Dispatched
|
2016-11-23 22:56:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-17 00:50:14 +00:00
|
|
|
|
// VaultPolicies returns the set of Vault policies per task group, per task
|
2016-08-18 17:50:47 +00:00
|
|
|
|
func (j *Job) VaultPolicies() map[string]map[string]*Vault {
|
|
|
|
|
policies := make(map[string]map[string]*Vault, len(j.TaskGroups))
|
2016-08-17 00:50:14 +00:00
|
|
|
|
|
|
|
|
|
for _, tg := range j.TaskGroups {
|
2016-08-18 17:50:47 +00:00
|
|
|
|
tgPolicies := make(map[string]*Vault, len(tg.Tasks))
|
2016-08-17 00:50:14 +00:00
|
|
|
|
|
|
|
|
|
for _, task := range tg.Tasks {
|
|
|
|
|
if task.Vault == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-18 17:50:47 +00:00
|
|
|
|
tgPolicies[task.Name] = task.Vault
|
2016-08-17 00:50:14 +00:00
|
|
|
|
}
|
2016-09-21 18:14:09 +00:00
|
|
|
|
|
|
|
|
|
if len(tgPolicies) != 0 {
|
|
|
|
|
policies[tg.Name] = tgPolicies
|
|
|
|
|
}
|
2016-08-17 00:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return policies
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-20 20:55:35 +00:00
|
|
|
|
// RequiredSignals returns a mapping of task groups to tasks to their required
|
|
|
|
|
// set of signals
|
|
|
|
|
func (j *Job) RequiredSignals() map[string]map[string][]string {
|
|
|
|
|
signals := make(map[string]map[string][]string)
|
|
|
|
|
|
|
|
|
|
for _, tg := range j.TaskGroups {
|
|
|
|
|
for _, task := range tg.Tasks {
|
|
|
|
|
// Use this local one as a set
|
|
|
|
|
taskSignals := make(map[string]struct{})
|
|
|
|
|
|
|
|
|
|
// Check if the Vault change mode uses signals
|
|
|
|
|
if task.Vault != nil && task.Vault.ChangeMode == VaultChangeModeSignal {
|
|
|
|
|
taskSignals[task.Vault.ChangeSignal] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-30 21:53:35 +00:00
|
|
|
|
// If a user has specified a KillSignal, add it to required signals
|
|
|
|
|
if task.KillSignal != "" {
|
|
|
|
|
taskSignals[task.KillSignal] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-20 20:55:35 +00:00
|
|
|
|
// Check if any template change mode uses signals
|
|
|
|
|
for _, t := range task.Templates {
|
|
|
|
|
if t.ChangeMode != TemplateChangeModeSignal {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
taskSignals[t.ChangeSignal] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Flatten and sort the signals
|
|
|
|
|
l := len(taskSignals)
|
|
|
|
|
if l == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
flat := make([]string, 0, l)
|
|
|
|
|
for sig := range taskSignals {
|
|
|
|
|
flat = append(flat, sig)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sort.Strings(flat)
|
|
|
|
|
tgSignals, ok := signals[tg.Name]
|
|
|
|
|
if !ok {
|
|
|
|
|
tgSignals = make(map[string][]string)
|
|
|
|
|
signals[tg.Name] = tgSignals
|
|
|
|
|
}
|
|
|
|
|
tgSignals[task.Name] = flat
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return signals
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-23 00:02:20 +00:00
|
|
|
|
// SpecChanged determines if the functional specification has changed between
|
2017-06-21 20:14:03 +00:00
|
|
|
|
// two job versions.
|
2017-05-23 00:02:20 +00:00
|
|
|
|
func (j *Job) SpecChanged(new *Job) bool {
|
|
|
|
|
if j == nil {
|
|
|
|
|
return new != nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-21 20:14:03 +00:00
|
|
|
|
// Create a copy of the new job
|
|
|
|
|
c := new.Copy()
|
2017-05-23 00:02:20 +00:00
|
|
|
|
|
|
|
|
|
// Update the new job so we can do a reflect
|
2017-06-21 20:14:03 +00:00
|
|
|
|
c.Status = j.Status
|
|
|
|
|
c.StatusDescription = j.StatusDescription
|
|
|
|
|
c.Stable = j.Stable
|
|
|
|
|
c.Version = j.Version
|
|
|
|
|
c.CreateIndex = j.CreateIndex
|
|
|
|
|
c.ModifyIndex = j.ModifyIndex
|
|
|
|
|
c.JobModifyIndex = j.JobModifyIndex
|
2017-06-30 02:08:25 +00:00
|
|
|
|
c.SubmitTime = j.SubmitTime
|
2017-05-23 00:02:20 +00:00
|
|
|
|
|
|
|
|
|
// Deep equals the jobs
|
2017-06-21 20:14:03 +00:00
|
|
|
|
return !reflect.DeepEqual(j, c)
|
2017-05-23 00:02:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-30 02:08:25 +00:00
|
|
|
|
func (j *Job) SetSubmitTime() {
|
|
|
|
|
j.SubmitTime = time.Now().UTC().UnixNano()
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-06 22:34:28 +00:00
|
|
|
|
// JobListStub is used to return a subset of job information
|
|
|
|
|
// for the job list
|
|
|
|
|
type JobListStub struct {
|
|
|
|
|
ID string
|
2016-01-07 22:43:55 +00:00
|
|
|
|
ParentID string
|
2015-09-06 22:34:28 +00:00
|
|
|
|
Name string
|
2019-01-22 18:59:13 +00:00
|
|
|
|
Datacenters []string
|
2015-09-06 22:34:28 +00:00
|
|
|
|
Type string
|
|
|
|
|
Priority int
|
2017-04-16 00:05:52 +00:00
|
|
|
|
Periodic bool
|
|
|
|
|
ParameterizedJob bool
|
2017-04-15 03:54:30 +00:00
|
|
|
|
Stop bool
|
2015-09-06 22:34:28 +00:00
|
|
|
|
Status string
|
|
|
|
|
StatusDescription string
|
2016-07-21 20:34:19 +00:00
|
|
|
|
JobSummary *JobSummary
|
2015-09-06 22:34:28 +00:00
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
2016-06-08 23:48:02 +00:00
|
|
|
|
JobModifyIndex uint64
|
2017-06-30 02:08:25 +00:00
|
|
|
|
SubmitTime int64
|
2015-09-06 22:34:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-06 01:24:37 +00:00
|
|
|
|
// JobSummary summarizes the state of the allocations of a job
|
|
|
|
|
type JobSummary struct {
|
2017-09-07 23:56:15 +00:00
|
|
|
|
// JobID is the ID of the job the summary is for
|
2016-12-06 01:24:37 +00:00
|
|
|
|
JobID string
|
|
|
|
|
|
2017-09-07 23:56:15 +00:00
|
|
|
|
// Namespace is the namespace of the job and its summary
|
|
|
|
|
Namespace string
|
|
|
|
|
|
2018-03-11 19:00:07 +00:00
|
|
|
|
// Summary contains the summary per task group for the Job
|
2016-12-06 01:24:37 +00:00
|
|
|
|
Summary map[string]TaskGroupSummary
|
|
|
|
|
|
2016-12-15 00:58:54 +00:00
|
|
|
|
// Children contains a summary for the children of this job.
|
2016-12-06 01:24:37 +00:00
|
|
|
|
Children *JobChildrenSummary
|
|
|
|
|
|
|
|
|
|
// Raft Indexes
|
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy returns a new copy of JobSummary
|
|
|
|
|
func (js *JobSummary) Copy() *JobSummary {
|
|
|
|
|
newJobSummary := new(JobSummary)
|
|
|
|
|
*newJobSummary = *js
|
|
|
|
|
newTGSummary := make(map[string]TaskGroupSummary, len(js.Summary))
|
|
|
|
|
for k, v := range js.Summary {
|
|
|
|
|
newTGSummary[k] = v
|
|
|
|
|
}
|
|
|
|
|
newJobSummary.Summary = newTGSummary
|
|
|
|
|
newJobSummary.Children = newJobSummary.Children.Copy()
|
|
|
|
|
return newJobSummary
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// JobChildrenSummary contains the summary of children job statuses
|
|
|
|
|
type JobChildrenSummary struct {
|
2016-12-07 00:58:44 +00:00
|
|
|
|
Pending int64
|
|
|
|
|
Running int64
|
|
|
|
|
Dead int64
|
2016-12-06 01:24:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy returns a new copy of a JobChildrenSummary
|
|
|
|
|
func (jc *JobChildrenSummary) Copy() *JobChildrenSummary {
|
|
|
|
|
if jc == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
njc := new(JobChildrenSummary)
|
|
|
|
|
*njc = *jc
|
|
|
|
|
return njc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TaskGroup summarizes the state of all the allocations of a particular
|
|
|
|
|
// TaskGroup
|
|
|
|
|
type TaskGroupSummary struct {
|
|
|
|
|
Queued int
|
|
|
|
|
Complete int
|
|
|
|
|
Failed int
|
|
|
|
|
Running int
|
|
|
|
|
Starting int
|
|
|
|
|
Lost int
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-09 00:44:26 +00:00
|
|
|
|
const (
|
|
|
|
|
// Checks uses any registered health check state in combination with task
|
|
|
|
|
// states to determine if a allocation is healthy.
|
|
|
|
|
UpdateStrategyHealthCheck_Checks = "checks"
|
|
|
|
|
|
|
|
|
|
// TaskStates uses the task states of an allocation to determine if the
|
|
|
|
|
// allocation is healthy.
|
|
|
|
|
UpdateStrategyHealthCheck_TaskStates = "task_states"
|
|
|
|
|
|
|
|
|
|
// Manual allows the operator to manually signal to Nomad when an
|
|
|
|
|
// allocations is healthy. This allows more advanced health checking that is
|
|
|
|
|
// outside of the scope of Nomad.
|
|
|
|
|
UpdateStrategyHealthCheck_Manual = "manual"
|
|
|
|
|
)
|
|
|
|
|
|
2017-05-10 20:48:53 +00:00
|
|
|
|
var (
|
|
|
|
|
// DefaultUpdateStrategy provides a baseline that can be used to upgrade
|
2017-05-10 20:51:52 +00:00
|
|
|
|
// jobs with the old policy or for populating field defaults.
|
2017-05-10 20:48:53 +00:00
|
|
|
|
DefaultUpdateStrategy = &UpdateStrategy{
|
2018-03-23 17:56:00 +00:00
|
|
|
|
Stagger: 30 * time.Second,
|
|
|
|
|
MaxParallel: 1,
|
|
|
|
|
HealthCheck: UpdateStrategyHealthCheck_Checks,
|
|
|
|
|
MinHealthyTime: 10 * time.Second,
|
|
|
|
|
HealthyDeadline: 5 * time.Minute,
|
2018-04-10 23:00:52 +00:00
|
|
|
|
ProgressDeadline: 10 * time.Minute,
|
2018-03-23 17:56:00 +00:00
|
|
|
|
AutoRevert: false,
|
2019-05-09 13:42:18 +00:00
|
|
|
|
AutoPromote: false,
|
2018-03-23 17:56:00 +00:00
|
|
|
|
Canary: 0,
|
2017-05-10 20:48:53 +00:00
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
2015-09-07 22:08:50 +00:00
|
|
|
|
// UpdateStrategy is used to modify how updates are done
|
|
|
|
|
type UpdateStrategy struct {
|
2017-07-07 02:08:51 +00:00
|
|
|
|
// Stagger is used to determine the rate at which allocations are migrated
|
|
|
|
|
// due to down or draining nodes.
|
2015-09-07 22:08:50 +00:00
|
|
|
|
Stagger time.Duration
|
|
|
|
|
|
|
|
|
|
// MaxParallel is how many updates can be done in parallel
|
2017-02-22 20:30:05 +00:00
|
|
|
|
MaxParallel int
|
2017-05-09 00:44:26 +00:00
|
|
|
|
|
|
|
|
|
// HealthCheck specifies the mechanism in which allocations are marked
|
|
|
|
|
// healthy or unhealthy as part of a deployment.
|
|
|
|
|
HealthCheck string
|
|
|
|
|
|
|
|
|
|
// MinHealthyTime is the minimum time an allocation must be in the healthy
|
2018-03-11 17:37:22 +00:00
|
|
|
|
// state before it is marked as healthy, unblocking more allocations to be
|
2017-05-09 00:44:26 +00:00
|
|
|
|
// rolled.
|
|
|
|
|
MinHealthyTime time.Duration
|
|
|
|
|
|
|
|
|
|
// HealthyDeadline is the time in which an allocation must be marked as
|
2018-03-11 19:06:05 +00:00
|
|
|
|
// healthy before it is automatically transitioned to unhealthy. This time
|
2017-05-09 00:44:26 +00:00
|
|
|
|
// period doesn't count against the MinHealthyTime.
|
|
|
|
|
HealthyDeadline time.Duration
|
|
|
|
|
|
2018-03-23 17:56:00 +00:00
|
|
|
|
// ProgressDeadline is the time in which an allocation as part of the
|
2018-04-10 18:42:13 +00:00
|
|
|
|
// deployment must transition to healthy. If no allocation becomes healthy
|
2018-03-23 17:56:00 +00:00
|
|
|
|
// after the deadline, the deployment is marked as failed. If the deadline
|
|
|
|
|
// is zero, the first failure causes the deployment to fail.
|
|
|
|
|
ProgressDeadline time.Duration
|
|
|
|
|
|
2017-05-09 00:44:26 +00:00
|
|
|
|
// AutoRevert declares that if a deployment fails because of unhealthy
|
|
|
|
|
// allocations, there should be an attempt to auto-revert the job to a
|
|
|
|
|
// stable version.
|
|
|
|
|
AutoRevert bool
|
|
|
|
|
|
2019-05-09 13:42:18 +00:00
|
|
|
|
// AutoPromote declares that the deployment should be promoted when all canaries are
|
|
|
|
|
// healthy
|
|
|
|
|
AutoPromote bool
|
|
|
|
|
|
2017-05-09 00:44:26 +00:00
|
|
|
|
// Canary is the number of canaries to deploy when a change to the task
|
|
|
|
|
// group is detected.
|
|
|
|
|
Canary int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (u *UpdateStrategy) Copy() *UpdateStrategy {
|
|
|
|
|
if u == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
copy := new(UpdateStrategy)
|
|
|
|
|
*copy = *u
|
|
|
|
|
return copy
|
2015-09-07 22:08:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-09 00:44:26 +00:00
|
|
|
|
func (u *UpdateStrategy) Validate() error {
|
|
|
|
|
if u == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
switch u.HealthCheck {
|
|
|
|
|
case UpdateStrategyHealthCheck_Checks, UpdateStrategyHealthCheck_TaskStates, UpdateStrategyHealthCheck_Manual:
|
|
|
|
|
default:
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Invalid health check given: %q", u.HealthCheck))
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-02 17:30:09 +00:00
|
|
|
|
if u.MaxParallel < 0 {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Max parallel can not be less than zero: %d < 0", u.MaxParallel))
|
2017-05-09 00:44:26 +00:00
|
|
|
|
}
|
|
|
|
|
if u.Canary < 0 {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Canary count can not be less than zero: %d < 0", u.Canary))
|
|
|
|
|
}
|
2019-05-20 18:36:58 +00:00
|
|
|
|
if u.Canary == 0 && u.AutoPromote {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Auto Promote requires a Canary count greater than zero"))
|
|
|
|
|
}
|
2017-05-09 00:44:26 +00:00
|
|
|
|
if u.MinHealthyTime < 0 {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Minimum healthy time may not be less than zero: %v", u.MinHealthyTime))
|
|
|
|
|
}
|
|
|
|
|
if u.HealthyDeadline <= 0 {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Healthy deadline must be greater than zero: %v", u.HealthyDeadline))
|
|
|
|
|
}
|
2018-03-23 17:56:00 +00:00
|
|
|
|
if u.ProgressDeadline < 0 {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Progress deadline must be zero or greater: %v", u.ProgressDeadline))
|
|
|
|
|
}
|
2017-07-25 23:30:53 +00:00
|
|
|
|
if u.MinHealthyTime >= u.HealthyDeadline {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Minimum healthy time must be less than healthy deadline: %v > %v", u.MinHealthyTime, u.HealthyDeadline))
|
|
|
|
|
}
|
2018-04-06 21:44:04 +00:00
|
|
|
|
if u.ProgressDeadline != 0 && u.HealthyDeadline >= u.ProgressDeadline {
|
2018-03-23 17:56:00 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Healthy deadline must be less than progress deadline: %v > %v", u.HealthyDeadline, u.ProgressDeadline))
|
|
|
|
|
}
|
2017-07-07 02:08:51 +00:00
|
|
|
|
if u.Stagger <= 0 {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Stagger must be greater than zero: %v", u.Stagger))
|
|
|
|
|
}
|
2017-05-09 00:44:26 +00:00
|
|
|
|
|
|
|
|
|
return mErr.ErrorOrNil()
|
2015-09-07 22:08:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-02 17:30:09 +00:00
|
|
|
|
func (u *UpdateStrategy) IsEmpty() bool {
|
|
|
|
|
if u == nil {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return u.MaxParallel == 0
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-09 00:44:26 +00:00
|
|
|
|
// TODO(alexdadgar): Remove once no longer used by the scheduler.
|
2015-09-07 22:08:50 +00:00
|
|
|
|
// Rolling returns if a rolling strategy should be used
|
|
|
|
|
func (u *UpdateStrategy) Rolling() bool {
|
|
|
|
|
return u.Stagger > 0 && u.MaxParallel > 0
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-01 00:51:56 +00:00
|
|
|
|
const (
|
|
|
|
|
// PeriodicSpecCron is used for a cron spec.
|
|
|
|
|
PeriodicSpecCron = "cron"
|
2015-12-18 20:26:28 +00:00
|
|
|
|
|
|
|
|
|
// PeriodicSpecTest is only used by unit tests. It is a sorted, comma
|
2016-05-15 16:41:34 +00:00
|
|
|
|
// separated list of unix timestamps at which to launch.
|
2015-12-16 21:46:09 +00:00
|
|
|
|
PeriodicSpecTest = "_internal_test"
|
2015-12-01 00:51:56 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Periodic defines the interval a job should be run at.
|
|
|
|
|
type PeriodicConfig struct {
|
|
|
|
|
// Enabled determines if the job should be run periodically.
|
|
|
|
|
Enabled bool
|
|
|
|
|
|
|
|
|
|
// Spec specifies the interval the job should be run as. It is parsed based
|
|
|
|
|
// on the SpecType.
|
|
|
|
|
Spec string
|
|
|
|
|
|
|
|
|
|
// SpecType defines the format of the spec.
|
|
|
|
|
SpecType string
|
2016-01-07 19:19:46 +00:00
|
|
|
|
|
|
|
|
|
// ProhibitOverlap enforces that spawned jobs do not run in parallel.
|
2017-02-22 20:30:05 +00:00
|
|
|
|
ProhibitOverlap bool
|
2017-02-15 22:37:06 +00:00
|
|
|
|
|
|
|
|
|
// TimeZone is the user specified string that determines the time zone to
|
|
|
|
|
// launch against. The time zones must be specified from IANA Time Zone
|
|
|
|
|
// database, such as "America/New_York".
|
|
|
|
|
// Reference: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
|
|
|
|
// Reference: https://www.iana.org/time-zones
|
2017-02-22 20:30:05 +00:00
|
|
|
|
TimeZone string
|
2017-02-15 22:37:06 +00:00
|
|
|
|
|
|
|
|
|
// location is the time zone to evaluate the launch time against
|
|
|
|
|
location *time.Location
|
2015-12-01 00:51:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-11 01:54:43 +00:00
|
|
|
|
func (p *PeriodicConfig) Copy() *PeriodicConfig {
|
|
|
|
|
if p == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
np := new(PeriodicConfig)
|
|
|
|
|
*np = *p
|
|
|
|
|
return np
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-01 00:51:56 +00:00
|
|
|
|
func (p *PeriodicConfig) Validate() error {
|
|
|
|
|
if !p.Enabled {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-15 22:37:06 +00:00
|
|
|
|
var mErr multierror.Error
|
2015-12-01 00:56:44 +00:00
|
|
|
|
if p.Spec == "" {
|
2017-02-15 22:37:06 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Must specify a spec"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if we got a valid time zone
|
|
|
|
|
if p.TimeZone != "" {
|
|
|
|
|
if _, err := time.LoadLocation(p.TimeZone); err != nil {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Invalid time zone %q: %v", p.TimeZone, err))
|
|
|
|
|
}
|
2015-12-01 00:51:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch p.SpecType {
|
|
|
|
|
case PeriodicSpecCron:
|
|
|
|
|
// Validate the cron spec
|
|
|
|
|
if _, err := cronexpr.Parse(p.Spec); err != nil {
|
2017-02-15 22:37:06 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Invalid cron spec %q: %v", p.Spec, err))
|
2015-12-01 00:51:56 +00:00
|
|
|
|
}
|
2015-12-18 20:26:28 +00:00
|
|
|
|
case PeriodicSpecTest:
|
|
|
|
|
// No-op
|
2015-12-01 00:51:56 +00:00
|
|
|
|
default:
|
2017-02-15 22:37:06 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Unknown periodic specification type %q", p.SpecType))
|
2015-12-01 00:51:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-15 22:37:06 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *PeriodicConfig) Canonicalize() {
|
|
|
|
|
// Load the location
|
|
|
|
|
l, err := time.LoadLocation(p.TimeZone)
|
|
|
|
|
if err != nil {
|
2017-02-17 19:21:49 +00:00
|
|
|
|
p.location = time.UTC
|
2017-02-15 22:37:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.location = l
|
2015-12-01 00:51:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 23:36:16 +00:00
|
|
|
|
// CronParseNext is a helper that parses the next time for the given expression
|
|
|
|
|
// but captures any panic that may occur in the underlying library.
|
|
|
|
|
func CronParseNext(e *cronexpr.Expression, fromTime time.Time, spec string) (t time.Time, err error) {
|
|
|
|
|
defer func() {
|
|
|
|
|
if recover() != nil {
|
|
|
|
|
t = time.Time{}
|
|
|
|
|
err = fmt.Errorf("failed parsing cron expression: %q", spec)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
return e.Next(fromTime), nil
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-01 00:51:56 +00:00
|
|
|
|
// Next returns the closest time instant matching the spec that is after the
|
|
|
|
|
// passed time. If no matching instance exists, the zero value of time.Time is
|
|
|
|
|
// returned. The `time.Location` of the returned value matches that of the
|
|
|
|
|
// passed time.
|
2018-04-26 20:57:45 +00:00
|
|
|
|
func (p *PeriodicConfig) Next(fromTime time.Time) (time.Time, error) {
|
2015-12-01 00:51:56 +00:00
|
|
|
|
switch p.SpecType {
|
|
|
|
|
case PeriodicSpecCron:
|
|
|
|
|
if e, err := cronexpr.Parse(p.Spec); err == nil {
|
2018-04-26 22:46:22 +00:00
|
|
|
|
return CronParseNext(e, fromTime, p.Spec)
|
2015-12-01 00:51:56 +00:00
|
|
|
|
}
|
2015-12-18 20:26:28 +00:00
|
|
|
|
case PeriodicSpecTest:
|
|
|
|
|
split := strings.Split(p.Spec, ",")
|
|
|
|
|
if len(split) == 1 && split[0] == "" {
|
2018-04-26 20:57:45 +00:00
|
|
|
|
return time.Time{}, nil
|
2015-12-18 20:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse the times
|
|
|
|
|
times := make([]time.Time, len(split))
|
|
|
|
|
for i, s := range split {
|
|
|
|
|
unix, err := strconv.Atoi(s)
|
|
|
|
|
if err != nil {
|
2018-04-26 20:57:45 +00:00
|
|
|
|
return time.Time{}, nil
|
2015-12-18 20:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-21 21:55:26 +00:00
|
|
|
|
times[i] = time.Unix(int64(unix), 0)
|
2015-12-18 20:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find the next match
|
|
|
|
|
for _, next := range times {
|
|
|
|
|
if fromTime.Before(next) {
|
2018-04-26 20:57:45 +00:00
|
|
|
|
return next, nil
|
2015-12-18 20:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2015-12-01 00:51:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-26 20:57:45 +00:00
|
|
|
|
return time.Time{}, nil
|
2015-12-01 00:51:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-15 22:37:06 +00:00
|
|
|
|
// GetLocation returns the location to use for determining the time zone to run
|
|
|
|
|
// the periodic job against.
|
|
|
|
|
func (p *PeriodicConfig) GetLocation() *time.Location {
|
|
|
|
|
// Jobs pre 0.5.5 will not have this
|
|
|
|
|
if p.location != nil {
|
|
|
|
|
return p.location
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return time.UTC
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-07 22:24:25 +00:00
|
|
|
|
const (
|
|
|
|
|
// PeriodicLaunchSuffix is the string appended to the periodic jobs ID
|
|
|
|
|
// when launching derived instances of it.
|
|
|
|
|
PeriodicLaunchSuffix = "/periodic-"
|
|
|
|
|
)
|
|
|
|
|
|
2015-12-19 01:51:30 +00:00
|
|
|
|
// PeriodicLaunch tracks the last launch time of a periodic job.
|
|
|
|
|
type PeriodicLaunch struct {
|
2017-09-07 23:56:15 +00:00
|
|
|
|
ID string // ID of the periodic job.
|
|
|
|
|
Namespace string // Namespace of the periodic job
|
|
|
|
|
Launch time.Time // The last launch time.
|
2015-12-16 21:46:09 +00:00
|
|
|
|
|
|
|
|
|
// Raft Indexes
|
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
2015-12-19 01:51:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-11-23 22:56:50 +00:00
|
|
|
|
const (
|
2016-12-14 20:50:08 +00:00
|
|
|
|
DispatchPayloadForbidden = "forbidden"
|
|
|
|
|
DispatchPayloadOptional = "optional"
|
|
|
|
|
DispatchPayloadRequired = "required"
|
2016-11-26 02:04:55 +00:00
|
|
|
|
|
2017-01-20 18:33:52 +00:00
|
|
|
|
// DispatchLaunchSuffix is the string appended to the parameterized job's ID
|
2016-12-14 20:50:08 +00:00
|
|
|
|
// when dispatching instances of it.
|
2017-01-20 18:33:52 +00:00
|
|
|
|
DispatchLaunchSuffix = "/dispatch-"
|
2016-11-23 22:56:50 +00:00
|
|
|
|
)
|
|
|
|
|
|
2017-01-20 18:33:52 +00:00
|
|
|
|
// ParameterizedJobConfig is used to configure the parameterized job
|
|
|
|
|
type ParameterizedJobConfig struct {
|
2016-12-14 20:50:08 +00:00
|
|
|
|
// Payload configure the payload requirements
|
|
|
|
|
Payload string
|
2016-11-23 22:56:50 +00:00
|
|
|
|
|
|
|
|
|
// MetaRequired is metadata keys that must be specified by the dispatcher
|
2017-02-22 20:30:05 +00:00
|
|
|
|
MetaRequired []string
|
2016-11-23 22:56:50 +00:00
|
|
|
|
|
|
|
|
|
// MetaOptional is metadata keys that may be specified by the dispatcher
|
2017-02-22 20:30:05 +00:00
|
|
|
|
MetaOptional []string
|
2016-11-23 22:56:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-20 18:33:52 +00:00
|
|
|
|
func (d *ParameterizedJobConfig) Validate() error {
|
2016-11-23 22:56:50 +00:00
|
|
|
|
var mErr multierror.Error
|
2016-12-14 20:50:08 +00:00
|
|
|
|
switch d.Payload {
|
|
|
|
|
case DispatchPayloadOptional, DispatchPayloadRequired, DispatchPayloadForbidden:
|
2016-11-23 22:56:50 +00:00
|
|
|
|
default:
|
2016-12-14 20:50:08 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Unknown payload requirement: %q", d.Payload))
|
2016-11-23 22:56:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check that the meta configurations are disjoint sets
|
2017-01-18 23:55:14 +00:00
|
|
|
|
disjoint, offending := helper.SliceSetDisjoint(d.MetaRequired, d.MetaOptional)
|
2016-11-23 22:56:50 +00:00
|
|
|
|
if !disjoint {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Required and optional meta keys should be disjoint. Following keys exist in both: %v", offending))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-20 18:33:52 +00:00
|
|
|
|
func (d *ParameterizedJobConfig) Canonicalize() {
|
2016-12-14 20:50:08 +00:00
|
|
|
|
if d.Payload == "" {
|
|
|
|
|
d.Payload = DispatchPayloadOptional
|
2016-11-23 22:56:50 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-20 18:33:52 +00:00
|
|
|
|
func (d *ParameterizedJobConfig) Copy() *ParameterizedJobConfig {
|
2016-11-23 22:56:50 +00:00
|
|
|
|
if d == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2017-01-20 18:33:52 +00:00
|
|
|
|
nd := new(ParameterizedJobConfig)
|
2016-11-23 22:56:50 +00:00
|
|
|
|
*nd = *d
|
2017-01-18 23:55:14 +00:00
|
|
|
|
nd.MetaOptional = helper.CopySliceString(nd.MetaOptional)
|
|
|
|
|
nd.MetaRequired = helper.CopySliceString(nd.MetaRequired)
|
2016-11-23 22:56:50 +00:00
|
|
|
|
return nd
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-26 02:04:55 +00:00
|
|
|
|
// DispatchedID returns an ID appropriate for a job dispatched against a
|
2017-01-20 18:33:52 +00:00
|
|
|
|
// particular parameterized job
|
2016-11-26 04:02:18 +00:00
|
|
|
|
func DispatchedID(templateID string, t time.Time) string {
|
2017-09-29 16:58:48 +00:00
|
|
|
|
u := uuid.Generate()[:8]
|
2017-01-20 18:33:52 +00:00
|
|
|
|
return fmt.Sprintf("%s%s%d-%s", templateID, DispatchLaunchSuffix, t.Unix(), u)
|
2016-11-26 02:04:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-26 05:06:16 +00:00
|
|
|
|
// DispatchPayloadConfig configures how a task gets its input from a job dispatch
|
|
|
|
|
type DispatchPayloadConfig struct {
|
2016-11-23 22:56:50 +00:00
|
|
|
|
// File specifies a relative path to where the input data should be written
|
|
|
|
|
File string
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-26 05:06:16 +00:00
|
|
|
|
func (d *DispatchPayloadConfig) Copy() *DispatchPayloadConfig {
|
2016-11-23 22:56:50 +00:00
|
|
|
|
if d == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2017-01-26 05:06:16 +00:00
|
|
|
|
nd := new(DispatchPayloadConfig)
|
2016-11-23 22:56:50 +00:00
|
|
|
|
*nd = *d
|
|
|
|
|
return nd
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-26 05:06:16 +00:00
|
|
|
|
func (d *DispatchPayloadConfig) Validate() error {
|
2016-12-18 23:48:30 +00:00
|
|
|
|
// Verify the destination doesn't escape
|
|
|
|
|
escaped, err := PathEscapesAllocDir("task/local/", d.File)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("invalid destination path: %v", err)
|
|
|
|
|
} else if escaped {
|
|
|
|
|
return fmt.Errorf("destination escapes allocation directory")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-18 20:17:13 +00:00
|
|
|
|
var (
|
2019-01-18 23:36:16 +00:00
|
|
|
|
// These default restart policies needs to be in sync with
|
|
|
|
|
// Canonicalize in api/tasks.go
|
|
|
|
|
|
2018-01-31 22:33:00 +00:00
|
|
|
|
DefaultServiceJobRestartPolicy = RestartPolicy{
|
2019-01-18 23:36:16 +00:00
|
|
|
|
Delay: 15 * time.Second,
|
|
|
|
|
Attempts: 2,
|
|
|
|
|
Interval: 30 * time.Minute,
|
|
|
|
|
Mode: RestartPolicyModeFail,
|
2015-12-18 20:17:13 +00:00
|
|
|
|
}
|
2018-01-31 22:33:00 +00:00
|
|
|
|
DefaultBatchJobRestartPolicy = RestartPolicy{
|
2019-01-18 23:36:16 +00:00
|
|
|
|
Delay: 15 * time.Second,
|
|
|
|
|
Attempts: 3,
|
|
|
|
|
Interval: 24 * time.Hour,
|
|
|
|
|
Mode: RestartPolicyModeFail,
|
2015-12-18 20:17:13 +00:00
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
2018-01-14 15:03:08 +00:00
|
|
|
|
var (
|
2019-01-18 23:36:16 +00:00
|
|
|
|
// These default reschedule policies needs to be in sync with
|
|
|
|
|
// NewDefaultReschedulePolicy in api/tasks.go
|
|
|
|
|
|
2018-01-17 17:05:22 +00:00
|
|
|
|
DefaultServiceJobReschedulePolicy = ReschedulePolicy{
|
2019-01-18 23:36:16 +00:00
|
|
|
|
Delay: 30 * time.Second,
|
|
|
|
|
DelayFunction: "exponential",
|
|
|
|
|
MaxDelay: 1 * time.Hour,
|
|
|
|
|
Unlimited: true,
|
2018-01-14 15:03:08 +00:00
|
|
|
|
}
|
2018-01-17 17:05:22 +00:00
|
|
|
|
DefaultBatchJobReschedulePolicy = ReschedulePolicy{
|
2019-01-18 23:36:16 +00:00
|
|
|
|
Attempts: 1,
|
|
|
|
|
Interval: 24 * time.Hour,
|
|
|
|
|
Delay: 5 * time.Second,
|
|
|
|
|
DelayFunction: "constant",
|
2018-01-14 15:03:08 +00:00
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
2015-12-18 20:17:13 +00:00
|
|
|
|
const (
|
2019-01-18 23:36:16 +00:00
|
|
|
|
// RestartPolicyModeDelay causes an artificial delay till the next interval is
|
|
|
|
|
// reached when the specified attempts have been reached in the interval.
|
|
|
|
|
RestartPolicyModeDelay = "delay"
|
|
|
|
|
|
|
|
|
|
// RestartPolicyModeFail causes a job to fail if the specified number of
|
|
|
|
|
// attempts are reached within an interval.
|
|
|
|
|
RestartPolicyModeFail = "fail"
|
|
|
|
|
|
2017-02-13 23:27:36 +00:00
|
|
|
|
// RestartPolicyMinInterval is the minimum interval that is accepted for a
|
|
|
|
|
// restart policy.
|
|
|
|
|
RestartPolicyMinInterval = 5 * time.Second
|
2017-11-03 14:34:30 +00:00
|
|
|
|
|
|
|
|
|
// ReasonWithinPolicy describes restart events that are within policy
|
|
|
|
|
ReasonWithinPolicy = "Restart within policy"
|
2015-12-18 20:17:13 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// RestartPolicy configures how Tasks are restarted when they crash or fail.
|
2015-10-30 22:51:39 +00:00
|
|
|
|
type RestartPolicy struct {
|
2015-12-18 20:17:13 +00:00
|
|
|
|
// Attempts is the number of restart that will occur in an interval.
|
2015-10-30 22:51:39 +00:00
|
|
|
|
Attempts int
|
2015-12-18 20:17:13 +00:00
|
|
|
|
|
|
|
|
|
// Interval is a duration in which we can limit the number of restarts
|
|
|
|
|
// within.
|
2015-10-30 22:51:39 +00:00
|
|
|
|
Interval time.Duration
|
2015-12-18 20:17:13 +00:00
|
|
|
|
|
|
|
|
|
// Delay is the time between a failure and a restart.
|
|
|
|
|
Delay time.Duration
|
|
|
|
|
|
|
|
|
|
// Mode controls what happens when the task restarts more than attempt times
|
|
|
|
|
// in an interval.
|
|
|
|
|
Mode string
|
2015-10-30 22:51:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-11 01:54:43 +00:00
|
|
|
|
func (r *RestartPolicy) Copy() *RestartPolicy {
|
|
|
|
|
if r == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
nrp := new(RestartPolicy)
|
|
|
|
|
*nrp = *r
|
|
|
|
|
return nrp
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-30 22:51:39 +00:00
|
|
|
|
func (r *RestartPolicy) Validate() error {
|
2017-02-13 23:27:36 +00:00
|
|
|
|
var mErr multierror.Error
|
2015-12-18 20:17:13 +00:00
|
|
|
|
switch r.Mode {
|
|
|
|
|
case RestartPolicyModeDelay, RestartPolicyModeFail:
|
|
|
|
|
default:
|
2017-02-13 23:27:36 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Unsupported restart mode: %q", r.Mode))
|
2015-12-18 20:17:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-02 22:32:30 +00:00
|
|
|
|
// Check for ambiguous/confusing settings
|
|
|
|
|
if r.Attempts == 0 && r.Mode != RestartPolicyModeFail {
|
2017-02-13 23:27:36 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Restart policy %q with %d attempts is ambiguous", r.Mode, r.Attempts))
|
2016-02-02 22:32:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-13 23:27:36 +00:00
|
|
|
|
if r.Interval.Nanoseconds() < RestartPolicyMinInterval.Nanoseconds() {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Interval can not be less than %v (got %v)", RestartPolicyMinInterval, r.Interval))
|
2015-11-17 07:56:11 +00:00
|
|
|
|
}
|
2015-10-30 22:51:39 +00:00
|
|
|
|
if time.Duration(r.Attempts)*r.Delay > r.Interval {
|
2017-02-13 23:27:36 +00:00
|
|
|
|
multierror.Append(&mErr,
|
|
|
|
|
fmt.Errorf("Nomad can't restart the TaskGroup %v times in an interval of %v with a delay of %v", r.Attempts, r.Interval, r.Delay))
|
2015-10-30 22:51:39 +00:00
|
|
|
|
}
|
2017-02-13 23:27:36 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
2015-10-30 22:51:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewRestartPolicy(jobType string) *RestartPolicy {
|
2015-11-02 21:35:51 +00:00
|
|
|
|
switch jobType {
|
2015-11-06 20:38:25 +00:00
|
|
|
|
case JobTypeService, JobTypeSystem:
|
2018-01-31 22:33:00 +00:00
|
|
|
|
rp := DefaultServiceJobRestartPolicy
|
2015-11-03 01:00:17 +00:00
|
|
|
|
return &rp
|
2015-11-02 21:35:51 +00:00
|
|
|
|
case JobTypeBatch:
|
2018-01-31 22:33:00 +00:00
|
|
|
|
rp := DefaultBatchJobRestartPolicy
|
2015-11-03 01:00:17 +00:00
|
|
|
|
return &rp
|
2015-10-30 22:51:39 +00:00
|
|
|
|
}
|
2015-11-03 01:00:17 +00:00
|
|
|
|
return nil
|
2015-10-30 22:51:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-14 15:03:08 +00:00
|
|
|
|
const ReschedulePolicyMinInterval = 15 * time.Second
|
2018-02-22 23:43:07 +00:00
|
|
|
|
const ReschedulePolicyMinDelay = 5 * time.Second
|
|
|
|
|
|
2018-03-26 19:45:09 +00:00
|
|
|
|
var RescheduleDelayFunctions = [...]string{"constant", "exponential", "fibonacci"}
|
2018-01-14 15:03:08 +00:00
|
|
|
|
|
|
|
|
|
// ReschedulePolicy configures how Tasks are rescheduled when they crash or fail.
|
|
|
|
|
type ReschedulePolicy struct {
|
|
|
|
|
// Attempts limits the number of rescheduling attempts that can occur in an interval.
|
|
|
|
|
Attempts int
|
|
|
|
|
|
|
|
|
|
// Interval is a duration in which we can limit the number of reschedule attempts.
|
|
|
|
|
Interval time.Duration
|
|
|
|
|
|
2018-02-22 23:43:07 +00:00
|
|
|
|
// Delay is a minimum duration to wait between reschedule attempts.
|
|
|
|
|
// The delay function determines how much subsequent reschedule attempts are delayed by.
|
|
|
|
|
Delay time.Duration
|
|
|
|
|
|
|
|
|
|
// DelayFunction determines how the delay progressively changes on subsequent reschedule
|
2018-03-26 19:45:09 +00:00
|
|
|
|
// attempts. Valid values are "exponential", "constant", and "fibonacci".
|
2018-02-22 23:43:07 +00:00
|
|
|
|
DelayFunction string
|
|
|
|
|
|
2018-03-13 15:06:26 +00:00
|
|
|
|
// MaxDelay is an upper bound on the delay.
|
|
|
|
|
MaxDelay time.Duration
|
2018-02-22 23:43:07 +00:00
|
|
|
|
|
2018-03-02 00:20:09 +00:00
|
|
|
|
// Unlimited allows infinite rescheduling attempts. Only allowed when delay is set
|
|
|
|
|
// between reschedule attempts.
|
2018-02-22 23:43:07 +00:00
|
|
|
|
Unlimited bool
|
2018-01-14 15:03:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *ReschedulePolicy) Copy() *ReschedulePolicy {
|
|
|
|
|
if r == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
nrp := new(ReschedulePolicy)
|
|
|
|
|
*nrp = *r
|
|
|
|
|
return nrp
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-08 22:26:36 +00:00
|
|
|
|
func (r *ReschedulePolicy) Enabled() bool {
|
|
|
|
|
enabled := r != nil && (r.Attempts > 0 || r.Unlimited)
|
|
|
|
|
return enabled
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-22 23:43:07 +00:00
|
|
|
|
// Validate uses different criteria to validate the reschedule policy
|
|
|
|
|
// Delay must be a minimum of 5 seconds
|
2018-03-26 19:45:09 +00:00
|
|
|
|
// Delay Ceiling is ignored if Delay Function is "constant"
|
2018-02-22 23:43:07 +00:00
|
|
|
|
// Number of possible attempts is validated, given the interval, delay and delay function
|
2018-01-14 15:03:08 +00:00
|
|
|
|
func (r *ReschedulePolicy) Validate() error {
|
2018-05-08 22:26:36 +00:00
|
|
|
|
if !r.Enabled() {
|
2018-02-22 23:43:07 +00:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
// Check for ambiguous/confusing settings
|
2018-03-24 15:29:20 +00:00
|
|
|
|
if r.Attempts > 0 {
|
|
|
|
|
if r.Interval <= 0 {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Interval must be a non zero value if Attempts > 0"))
|
|
|
|
|
}
|
|
|
|
|
if r.Unlimited {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Reschedule Policy with Attempts = %v, Interval = %v, "+
|
|
|
|
|
"and Unlimited = %v is ambiguous", r.Attempts, r.Interval, r.Unlimited))
|
2018-03-26 18:30:09 +00:00
|
|
|
|
multierror.Append(&mErr, errors.New("If Attempts >0, Unlimited cannot also be set to true"))
|
2018-03-24 15:29:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-02-22 23:43:07 +00:00
|
|
|
|
|
|
|
|
|
delayPreCheck := true
|
|
|
|
|
// Delay should be bigger than the default
|
|
|
|
|
if r.Delay.Nanoseconds() < ReschedulePolicyMinDelay.Nanoseconds() {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Delay cannot be less than %v (got %v)", ReschedulePolicyMinDelay, r.Delay))
|
|
|
|
|
delayPreCheck = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Must use a valid delay function
|
|
|
|
|
if !isValidDelayFunction(r.DelayFunction) {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Invalid delay function %q, must be one of %q", r.DelayFunction, RescheduleDelayFunctions))
|
|
|
|
|
delayPreCheck = false
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-13 15:06:26 +00:00
|
|
|
|
// Validate MaxDelay if not using linear delay progression
|
2018-03-26 19:45:09 +00:00
|
|
|
|
if r.DelayFunction != "constant" {
|
2018-03-13 15:06:26 +00:00
|
|
|
|
if r.MaxDelay.Nanoseconds() < ReschedulePolicyMinDelay.Nanoseconds() {
|
2018-03-24 15:29:20 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Max Delay cannot be less than %v (got %v)", ReschedulePolicyMinDelay, r.Delay))
|
2018-02-22 23:43:07 +00:00
|
|
|
|
delayPreCheck = false
|
|
|
|
|
}
|
2018-03-13 15:06:26 +00:00
|
|
|
|
if r.MaxDelay < r.Delay {
|
2018-03-24 15:29:20 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Max Delay cannot be less than Delay %v (got %v)", r.Delay, r.MaxDelay))
|
2018-02-22 23:43:07 +00:00
|
|
|
|
delayPreCheck = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-06 03:46:54 +00:00
|
|
|
|
// Validate Interval and other delay parameters if attempts are limited
|
2018-02-22 23:43:07 +00:00
|
|
|
|
if !r.Unlimited {
|
2018-01-17 17:05:22 +00:00
|
|
|
|
if r.Interval.Nanoseconds() < ReschedulePolicyMinInterval.Nanoseconds() {
|
2018-02-22 23:43:07 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Interval cannot be less than %v (got %v)", ReschedulePolicyMinInterval, r.Interval))
|
|
|
|
|
}
|
|
|
|
|
if !delayPreCheck {
|
|
|
|
|
// We can't cross validate the rest of the delay params if delayPreCheck fails, so return early
|
|
|
|
|
return mErr.ErrorOrNil()
|
2018-01-17 17:05:22 +00:00
|
|
|
|
}
|
2018-02-22 23:43:07 +00:00
|
|
|
|
crossValidationErr := r.validateDelayParams()
|
|
|
|
|
if crossValidationErr != nil {
|
|
|
|
|
multierror.Append(&mErr, crossValidationErr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
2018-01-14 15:03:08 +00:00
|
|
|
|
|
2018-02-22 23:43:07 +00:00
|
|
|
|
func isValidDelayFunction(delayFunc string) bool {
|
|
|
|
|
for _, value := range RescheduleDelayFunctions {
|
|
|
|
|
if value == delayFunc {
|
|
|
|
|
return true
|
|
|
|
|
}
|
2018-01-14 15:03:08 +00:00
|
|
|
|
}
|
2018-02-22 23:43:07 +00:00
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *ReschedulePolicy) validateDelayParams() error {
|
|
|
|
|
ok, possibleAttempts, recommendedInterval := r.viableAttempts()
|
|
|
|
|
if ok {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
var mErr multierror.Error
|
2018-03-26 19:45:09 +00:00
|
|
|
|
if r.DelayFunction == "constant" {
|
2018-02-22 23:43:07 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Nomad can only make %v attempts in %v with initial delay %v and "+
|
|
|
|
|
"delay function %q", possibleAttempts, r.Interval, r.Delay, r.DelayFunction))
|
|
|
|
|
} else {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Nomad can only make %v attempts in %v with initial delay %v, "+
|
2018-03-13 15:06:26 +00:00
|
|
|
|
"delay function %q, and delay ceiling %v", possibleAttempts, r.Interval, r.Delay, r.DelayFunction, r.MaxDelay))
|
2018-02-22 23:43:07 +00:00
|
|
|
|
}
|
2018-03-19 15:40:36 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Set the interval to at least %v to accommodate %v attempts", recommendedInterval.Round(time.Second), r.Attempts))
|
2018-02-22 23:43:07 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *ReschedulePolicy) viableAttempts() (bool, int, time.Duration) {
|
|
|
|
|
var possibleAttempts int
|
|
|
|
|
var recommendedInterval time.Duration
|
|
|
|
|
valid := true
|
|
|
|
|
switch r.DelayFunction {
|
2018-03-26 19:45:09 +00:00
|
|
|
|
case "constant":
|
2018-02-22 23:43:07 +00:00
|
|
|
|
recommendedInterval = time.Duration(r.Attempts) * r.Delay
|
|
|
|
|
if r.Interval < recommendedInterval {
|
|
|
|
|
possibleAttempts = int(r.Interval / r.Delay)
|
|
|
|
|
valid = false
|
|
|
|
|
}
|
|
|
|
|
case "exponential":
|
|
|
|
|
for i := 0; i < r.Attempts; i++ {
|
|
|
|
|
nextDelay := time.Duration(math.Pow(2, float64(i))) * r.Delay
|
2018-03-13 15:06:26 +00:00
|
|
|
|
if nextDelay > r.MaxDelay {
|
|
|
|
|
nextDelay = r.MaxDelay
|
2018-02-22 23:43:07 +00:00
|
|
|
|
recommendedInterval += nextDelay
|
|
|
|
|
} else {
|
|
|
|
|
recommendedInterval = nextDelay
|
|
|
|
|
}
|
|
|
|
|
if recommendedInterval < r.Interval {
|
|
|
|
|
possibleAttempts++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if possibleAttempts < r.Attempts {
|
|
|
|
|
valid = false
|
|
|
|
|
}
|
|
|
|
|
case "fibonacci":
|
|
|
|
|
var slots []time.Duration
|
|
|
|
|
slots = append(slots, r.Delay)
|
|
|
|
|
slots = append(slots, r.Delay)
|
|
|
|
|
reachedCeiling := false
|
|
|
|
|
for i := 2; i < r.Attempts; i++ {
|
|
|
|
|
var nextDelay time.Duration
|
|
|
|
|
if reachedCeiling {
|
|
|
|
|
//switch to linear
|
2018-03-13 15:06:26 +00:00
|
|
|
|
nextDelay = slots[i-1] + r.MaxDelay
|
2018-02-22 23:43:07 +00:00
|
|
|
|
} else {
|
|
|
|
|
nextDelay = slots[i-1] + slots[i-2]
|
2018-03-13 15:06:26 +00:00
|
|
|
|
if nextDelay > r.MaxDelay {
|
|
|
|
|
nextDelay = r.MaxDelay
|
2018-02-22 23:43:07 +00:00
|
|
|
|
reachedCeiling = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
slots = append(slots, nextDelay)
|
|
|
|
|
}
|
|
|
|
|
recommendedInterval = slots[len(slots)-1]
|
|
|
|
|
if r.Interval < recommendedInterval {
|
|
|
|
|
valid = false
|
|
|
|
|
// calculate possible attempts
|
|
|
|
|
for i := 0; i < len(slots); i++ {
|
|
|
|
|
if slots[i] > r.Interval {
|
|
|
|
|
possibleAttempts = i
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return false, 0, 0
|
|
|
|
|
}
|
|
|
|
|
if possibleAttempts < 0 { // can happen if delay is bigger than interval
|
|
|
|
|
possibleAttempts = 0
|
|
|
|
|
}
|
|
|
|
|
return valid, possibleAttempts, recommendedInterval
|
2018-01-14 15:03:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-11 18:50:50 +00:00
|
|
|
|
func NewReschedulePolicy(jobType string) *ReschedulePolicy {
|
2018-01-14 15:03:08 +00:00
|
|
|
|
switch jobType {
|
2018-01-17 17:05:22 +00:00
|
|
|
|
case JobTypeService:
|
|
|
|
|
rp := DefaultServiceJobReschedulePolicy
|
2018-01-14 15:03:08 +00:00
|
|
|
|
return &rp
|
|
|
|
|
case JobTypeBatch:
|
2018-01-17 17:05:22 +00:00
|
|
|
|
rp := DefaultBatchJobReschedulePolicy
|
2018-01-14 15:03:08 +00:00
|
|
|
|
return &rp
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-24 00:47:00 +00:00
|
|
|
|
const (
|
|
|
|
|
MigrateStrategyHealthChecks = "checks"
|
|
|
|
|
MigrateStrategyHealthStates = "task_states"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type MigrateStrategy struct {
|
|
|
|
|
MaxParallel int
|
|
|
|
|
HealthCheck string
|
|
|
|
|
MinHealthyTime time.Duration
|
|
|
|
|
HealthyDeadline time.Duration
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DefaultMigrateStrategy is used for backwards compat with pre-0.8 Allocations
|
|
|
|
|
// that lack an update strategy.
|
|
|
|
|
//
|
|
|
|
|
// This function should match its counterpart in api/tasks.go
|
|
|
|
|
func DefaultMigrateStrategy() *MigrateStrategy {
|
|
|
|
|
return &MigrateStrategy{
|
|
|
|
|
MaxParallel: 1,
|
|
|
|
|
HealthCheck: MigrateStrategyHealthChecks,
|
|
|
|
|
MinHealthyTime: 10 * time.Second,
|
|
|
|
|
HealthyDeadline: 5 * time.Minute,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *MigrateStrategy) Validate() error {
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
|
|
|
|
|
if m.MaxParallel < 0 {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("MaxParallel must be >= 0 but found %d", m.MaxParallel))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch m.HealthCheck {
|
|
|
|
|
case MigrateStrategyHealthChecks, MigrateStrategyHealthStates:
|
|
|
|
|
// ok
|
|
|
|
|
case "":
|
|
|
|
|
if m.MaxParallel > 0 {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Missing HealthCheck"))
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Invalid HealthCheck: %q", m.HealthCheck))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if m.MinHealthyTime < 0 {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("MinHealthyTime is %s and must be >= 0", m.MinHealthyTime))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if m.HealthyDeadline < 0 {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("HealthyDeadline is %s and must be >= 0", m.HealthyDeadline))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if m.MinHealthyTime > m.HealthyDeadline {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("MinHealthyTime must be less than HealthyDeadline"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// TaskGroup is an atomic unit of placement. Each task group belongs to
|
|
|
|
|
// a job and may contain any number of tasks. A task group support running
|
|
|
|
|
// in many replicas using the same configuration..
|
|
|
|
|
type TaskGroup struct {
|
|
|
|
|
// Name of the task group
|
|
|
|
|
Name string
|
|
|
|
|
|
|
|
|
|
// Count is the number of replicas of this task group that should
|
|
|
|
|
// be scheduled.
|
|
|
|
|
Count int
|
|
|
|
|
|
2017-05-09 00:44:26 +00:00
|
|
|
|
// Update is used to control the update strategy for this task group
|
|
|
|
|
Update *UpdateStrategy
|
|
|
|
|
|
2018-01-24 00:47:00 +00:00
|
|
|
|
// Migrate is used to control the migration strategy for this task group
|
|
|
|
|
Migrate *MigrateStrategy
|
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Constraints can be specified at a task group level and apply to
|
|
|
|
|
// all the tasks contained.
|
|
|
|
|
Constraints []*Constraint
|
|
|
|
|
|
2015-10-30 22:51:39 +00:00
|
|
|
|
//RestartPolicy of a TaskGroup
|
|
|
|
|
RestartPolicy *RestartPolicy
|
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Tasks are the collection of tasks that this task group needs to run
|
|
|
|
|
Tasks []*Task
|
|
|
|
|
|
2016-09-14 22:43:42 +00:00
|
|
|
|
// EphemeralDisk is the disk resources that the task group requests
|
|
|
|
|
EphemeralDisk *EphemeralDisk
|
2016-08-24 18:51:15 +00:00
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Meta is used to associate arbitrary metadata with this
|
|
|
|
|
// task group. This is opaque to Nomad.
|
|
|
|
|
Meta map[string]string
|
2018-01-14 15:03:08 +00:00
|
|
|
|
|
2018-01-15 23:27:55 +00:00
|
|
|
|
// ReschedulePolicy is used to configure how the scheduler should
|
|
|
|
|
// retry failed allocations.
|
2018-01-14 15:03:08 +00:00
|
|
|
|
ReschedulePolicy *ReschedulePolicy
|
2018-07-16 13:30:58 +00:00
|
|
|
|
|
|
|
|
|
// Affinities can be specified at the task group level to express
|
|
|
|
|
// scheduling preferences.
|
|
|
|
|
Affinities []*Affinity
|
2018-07-16 17:52:24 +00:00
|
|
|
|
|
|
|
|
|
// Spread can be specified at the task group level to express spreading
|
|
|
|
|
// allocations across a desired attribute, such as datacenter
|
|
|
|
|
Spreads []*Spread
|
2019-04-29 17:15:12 +00:00
|
|
|
|
|
|
|
|
|
// Networks are the network configuration for the task group. This can be
|
2019-04-29 19:39:55 +00:00
|
|
|
|
// overridden in the task.
|
2019-04-29 17:15:12 +00:00
|
|
|
|
Networks Networks
|
2019-06-24 15:29:26 +00:00
|
|
|
|
|
|
|
|
|
// Services this group provides
|
|
|
|
|
Services []*Service
|
2019-07-25 14:44:08 +00:00
|
|
|
|
|
|
|
|
|
// Volumes is a map of volumes that have been requested by the task group.
|
|
|
|
|
Volumes map[string]*VolumeRequest
|
2015-07-03 23:57:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-04 05:22:18 +00:00
|
|
|
|
func (tg *TaskGroup) Copy() *TaskGroup {
|
2016-02-11 01:54:43 +00:00
|
|
|
|
if tg == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
ntg := new(TaskGroup)
|
|
|
|
|
*ntg = *tg
|
2017-05-09 00:44:26 +00:00
|
|
|
|
ntg.Update = ntg.Update.Copy()
|
2016-02-11 17:08:20 +00:00
|
|
|
|
ntg.Constraints = CopySliceConstraints(ntg.Constraints)
|
2016-02-11 01:54:43 +00:00
|
|
|
|
ntg.RestartPolicy = ntg.RestartPolicy.Copy()
|
2018-01-14 15:03:08 +00:00
|
|
|
|
ntg.ReschedulePolicy = ntg.ReschedulePolicy.Copy()
|
2018-07-16 13:30:58 +00:00
|
|
|
|
ntg.Affinities = CopySliceAffinities(ntg.Affinities)
|
2018-07-16 17:52:24 +00:00
|
|
|
|
ntg.Spreads = CopySliceSpreads(ntg.Spreads)
|
2019-07-25 14:44:08 +00:00
|
|
|
|
ntg.Volumes = CopyMapVolumeRequest(ntg.Volumes)
|
2016-02-11 01:54:43 +00:00
|
|
|
|
|
2019-04-29 17:15:12 +00:00
|
|
|
|
// Copy the network objects
|
|
|
|
|
if tg.Networks != nil {
|
|
|
|
|
n := len(tg.Networks)
|
|
|
|
|
ntg.Networks = make([]*NetworkResource, n)
|
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
|
ntg.Networks[i] = tg.Networks[i].Copy()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-21 23:29:21 +00:00
|
|
|
|
if tg.Tasks != nil {
|
|
|
|
|
tasks := make([]*Task, len(ntg.Tasks))
|
|
|
|
|
for i, t := range ntg.Tasks {
|
|
|
|
|
tasks[i] = t.Copy()
|
|
|
|
|
}
|
|
|
|
|
ntg.Tasks = tasks
|
2016-02-04 05:22:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-18 23:55:14 +00:00
|
|
|
|
ntg.Meta = helper.CopyMapStringString(ntg.Meta)
|
2016-08-26 19:24:47 +00:00
|
|
|
|
|
2016-09-14 22:43:42 +00:00
|
|
|
|
if tg.EphemeralDisk != nil {
|
|
|
|
|
ntg.EphemeralDisk = tg.EphemeralDisk.Copy()
|
2016-08-26 19:24:47 +00:00
|
|
|
|
}
|
2019-06-24 15:29:26 +00:00
|
|
|
|
|
|
|
|
|
if tg.Services != nil {
|
|
|
|
|
ntg.Services = make([]*Service, len(tg.Services))
|
|
|
|
|
for i, s := range tg.Services {
|
|
|
|
|
ntg.Services[i] = s.Copy()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-11 01:54:43 +00:00
|
|
|
|
return ntg
|
2016-02-04 05:22:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-07-20 23:07:15 +00:00
|
|
|
|
// Canonicalize is used to canonicalize fields in the TaskGroup.
|
|
|
|
|
func (tg *TaskGroup) Canonicalize(job *Job) {
|
2016-07-18 23:17:38 +00:00
|
|
|
|
// Ensure that an empty and nil map are treated the same to avoid scheduling
|
|
|
|
|
// problems since we use reflect DeepEquals.
|
|
|
|
|
if len(tg.Meta) == 0 {
|
|
|
|
|
tg.Meta = nil
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-18 20:17:13 +00:00
|
|
|
|
// Set the default restart policy.
|
|
|
|
|
if tg.RestartPolicy == nil {
|
|
|
|
|
tg.RestartPolicy = NewRestartPolicy(job.Type)
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-14 15:03:08 +00:00
|
|
|
|
if tg.ReschedulePolicy == nil {
|
2018-03-11 18:50:50 +00:00
|
|
|
|
tg.ReschedulePolicy = NewReschedulePolicy(job.Type)
|
2018-01-14 15:03:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-30 00:42:58 +00:00
|
|
|
|
// Canonicalize Migrate for service jobs
|
|
|
|
|
if job.Type == JobTypeService && tg.Migrate == nil {
|
|
|
|
|
tg.Migrate = DefaultMigrateStrategy()
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-02 22:07:22 +00:00
|
|
|
|
// Set a default ephemeral disk object if the user has not requested for one
|
|
|
|
|
if tg.EphemeralDisk == nil {
|
|
|
|
|
tg.EphemeralDisk = DefaultEphemeralDisk()
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-04 12:55:47 +00:00
|
|
|
|
for _, service := range tg.Services {
|
|
|
|
|
service.Canonicalize(job.Name, tg.Name, "group")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, network := range tg.Networks {
|
|
|
|
|
network.Canonicalize()
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-27 03:26:00 +00:00
|
|
|
|
for _, task := range tg.Tasks {
|
2016-07-20 23:07:15 +00:00
|
|
|
|
task.Canonicalize(job, tg)
|
2015-11-27 03:26:00 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-15 17:46:10 +00:00
|
|
|
|
// Validate is used to sanity check a task group
|
2017-07-07 02:08:51 +00:00
|
|
|
|
func (tg *TaskGroup) Validate(j *Job) error {
|
2015-09-15 17:46:10 +00:00
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
if tg.Name == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing task group name"))
|
|
|
|
|
}
|
2016-03-17 18:02:59 +00:00
|
|
|
|
if tg.Count < 0 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Task group count can't be negative"))
|
2015-09-15 17:46:10 +00:00
|
|
|
|
}
|
|
|
|
|
if len(tg.Tasks) == 0 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing tasks for task group"))
|
|
|
|
|
}
|
2015-10-11 19:50:16 +00:00
|
|
|
|
for idx, constr := range tg.Constraints {
|
|
|
|
|
if err := constr.Validate(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Constraint %d validation failed: %s", idx+1, err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-23 16:06:49 +00:00
|
|
|
|
if j.Type == JobTypeSystem {
|
|
|
|
|
if tg.Affinities != nil {
|
2018-07-24 15:37:13 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("System jobs may not have an affinity stanza"))
|
2018-07-23 16:06:49 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for idx, affinity := range tg.Affinities {
|
|
|
|
|
if err := affinity.Validate(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Affinity %d validation failed: %s", idx+1, err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
2018-07-16 13:30:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-11 23:10:39 +00:00
|
|
|
|
if tg.RestartPolicy != nil {
|
|
|
|
|
if err := tg.RestartPolicy.Validate(); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Task Group %v should have a restart policy", tg.Name))
|
2015-10-30 22:51:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-26 00:08:25 +00:00
|
|
|
|
if j.Type == JobTypeSystem {
|
|
|
|
|
if tg.Spreads != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("System jobs may not have a spread stanza"))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for idx, spread := range tg.Spreads {
|
|
|
|
|
if err := spread.Validate(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Spread %d validation failed: %s", idx+1, err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
2018-07-18 16:04:59 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-11 19:56:20 +00:00
|
|
|
|
if j.Type == JobTypeSystem {
|
|
|
|
|
if tg.ReschedulePolicy != nil {
|
2018-04-11 22:07:14 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("System jobs should not have a reschedule policy"))
|
2018-04-11 19:56:20 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2018-02-22 23:43:07 +00:00
|
|
|
|
if tg.ReschedulePolicy != nil {
|
|
|
|
|
if err := tg.ReschedulePolicy.Validate(); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Task Group %v should have a reschedule policy", tg.Name))
|
2018-01-14 15:03:08 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-14 22:43:42 +00:00
|
|
|
|
if tg.EphemeralDisk != nil {
|
|
|
|
|
if err := tg.EphemeralDisk.Validate(); err != nil {
|
2016-08-24 18:51:15 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2016-09-28 00:57:05 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Task Group %v should have an ephemeral disk object", tg.Name))
|
2016-08-24 18:51:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-09 00:44:26 +00:00
|
|
|
|
// Validate the update strategy
|
|
|
|
|
if u := tg.Update; u != nil {
|
2017-07-07 02:08:51 +00:00
|
|
|
|
switch j.Type {
|
|
|
|
|
case JobTypeService, JobTypeSystem:
|
|
|
|
|
default:
|
2018-03-29 18:13:50 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Job type %q does not allow update block", j.Type))
|
2017-07-07 02:08:51 +00:00
|
|
|
|
}
|
2017-05-09 00:44:26 +00:00
|
|
|
|
if err := u.Validate(); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-24 00:47:00 +00:00
|
|
|
|
// Validate the migration strategy
|
|
|
|
|
switch j.Type {
|
|
|
|
|
case JobTypeService:
|
2018-02-23 18:42:43 +00:00
|
|
|
|
if tg.Migrate != nil {
|
|
|
|
|
if err := tg.Migrate.Validate(); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
2018-01-24 00:47:00 +00:00
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
if tg.Migrate != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Job type %q does not allow migrate block", j.Type))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-08 17:55:57 +00:00
|
|
|
|
// Check that there is only one leader task if any
|
|
|
|
|
tasks := make(map[string]int)
|
|
|
|
|
leaderTasks := 0
|
|
|
|
|
for idx, task := range tg.Tasks {
|
|
|
|
|
if task.Name == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Task %d missing name", idx+1))
|
|
|
|
|
} else if existing, ok := tasks[task.Name]; ok {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Task %d redefines '%s' from task %d", idx+1, task.Name, existing+1))
|
|
|
|
|
} else {
|
|
|
|
|
tasks[task.Name] = idx
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if task.Leader {
|
|
|
|
|
leaderTasks++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if leaderTasks > 1 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Only one task may be marked as leader"))
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-25 14:44:08 +00:00
|
|
|
|
// Validate the Host Volumes
|
|
|
|
|
for name, decl := range tg.Volumes {
|
2019-08-01 09:33:26 +00:00
|
|
|
|
if decl.Type != VolumeTypeHost {
|
2019-07-25 14:44:08 +00:00
|
|
|
|
// TODO: Remove this error when adding new volume types
|
2019-08-01 09:33:26 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Volume %s has unrecognised type %s", name, decl.Type))
|
2019-07-25 14:44:08 +00:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
config: Hoist volume.config.source into volume
Currently, using a Volume in a job uses the following configuration:
```
volume "alias-name" {
type = "volume-type"
read_only = true
config {
source = "host_volume_name"
}
}
```
This commit migrates to the following:
```
volume "alias-name" {
type = "volume-type"
source = "host_volume_name"
read_only = true
}
```
The original design was based due to being uncertain about the future of storage
plugins, and to allow maxium flexibility.
However, this causes a few issues, namely:
- We frequently need to parse this configuration during submission,
scheduling, and mounting
- It complicates the configuration from and end users perspective
- It complicates the ability to do validation
As we understand the problem space of CSI a little more, it has become
clear that we won't need the `source` to be in config, as it will be
used in the majority of cases:
- Host Volumes: Always need a source
- Preallocated CSI Volumes: Always needs a source from a volume or claim name
- Dynamic Persistent CSI Volumes*: Always needs a source to attach the volumes
to for managing upgrades and to avoid dangling.
- Dynamic Ephemeral CSI Volumes*: Less thought out, but `source` will probably point
to the plugin name, and a `config` block will
allow you to pass meta to the plugin. Or will
point to a pre-configured ephemeral config.
*If implemented
The new design simplifies this by merging the source into the volume
stanza to solve the above issues with usability, performance, and error
handling.
2019-09-13 02:09:58 +00:00
|
|
|
|
if decl.Source == "" {
|
2019-07-25 14:44:08 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Volume %s has an empty source", name))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-08 17:55:57 +00:00
|
|
|
|
// Validate task group and task network resources
|
|
|
|
|
if err := tg.validateNetworks(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Task group network validation failed: %v", err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-19 13:17:38 +00:00
|
|
|
|
// Validate task group and task services
|
|
|
|
|
if err := tg.validateServices(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Task group service validation failed: %v", err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-08 17:55:57 +00:00
|
|
|
|
// Validate the tasks
|
|
|
|
|
for _, task := range tg.Tasks {
|
2019-07-25 14:44:08 +00:00
|
|
|
|
// Validate the task does not reference undefined volume mounts
|
|
|
|
|
for i, mnt := range task.VolumeMounts {
|
|
|
|
|
if mnt.Volume == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Task %s has a volume mount (%d) referencing an empty volume", task.Name, i))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, ok := tg.Volumes[mnt.Volume]; !ok {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Task %s has a volume mount (%d) referencing undefined volume %s", task.Name, i, mnt.Volume))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-08 19:45:24 +00:00
|
|
|
|
if err := task.Validate(tg.EphemeralDisk, j.Type, tg.Services); err != nil {
|
2019-05-08 17:55:57 +00:00
|
|
|
|
outer := fmt.Errorf("Task %s validation failed: %v", task.Name, err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tg *TaskGroup) validateNetworks() error {
|
|
|
|
|
var mErr multierror.Error
|
2019-05-03 14:26:26 +00:00
|
|
|
|
portLabels := make(map[string]string)
|
2017-07-07 23:17:05 +00:00
|
|
|
|
staticPorts := make(map[int]string)
|
2019-05-03 14:26:26 +00:00
|
|
|
|
mappedPorts := make(map[int]string)
|
|
|
|
|
|
|
|
|
|
for _, net := range tg.Networks {
|
|
|
|
|
for _, port := range append(net.ReservedPorts, net.DynamicPorts...) {
|
|
|
|
|
if other, ok := portLabels[port.Label]; ok {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Port label %s already in use by %s", port.Label, other))
|
|
|
|
|
} else {
|
|
|
|
|
portLabels[port.Label] = "taskgroup network"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if port.Value != 0 {
|
|
|
|
|
// static port
|
|
|
|
|
if other, ok := staticPorts[port.Value]; ok {
|
|
|
|
|
err := fmt.Errorf("Static port %d already reserved by %s", port.Value, other)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
} else {
|
|
|
|
|
staticPorts[port.Value] = fmt.Sprintf("taskgroup network:%s", port.Label)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if port.To != 0 {
|
|
|
|
|
if other, ok := mappedPorts[port.To]; ok {
|
|
|
|
|
err := fmt.Errorf("Port mapped to %d already in use by %s", port.To, other)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
} else {
|
|
|
|
|
mappedPorts[port.To] = fmt.Sprintf("taskgroup network:%s", port.Label)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-08 17:55:57 +00:00
|
|
|
|
// Check for duplicate tasks or port labels, and no duplicated static or mapped ports
|
|
|
|
|
for _, task := range tg.Tasks {
|
2017-07-07 23:17:05 +00:00
|
|
|
|
if task.Resources == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, net := range task.Resources.Networks {
|
2019-05-03 14:26:26 +00:00
|
|
|
|
for _, port := range append(net.ReservedPorts, net.DynamicPorts...) {
|
|
|
|
|
if other, ok := portLabels[port.Label]; ok {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Port label %s already in use by %s", port.Label, other))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if port.Value != 0 {
|
|
|
|
|
if other, ok := staticPorts[port.Value]; ok {
|
|
|
|
|
err := fmt.Errorf("Static port %d already reserved by %s", port.Value, other)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
} else {
|
|
|
|
|
staticPorts[port.Value] = fmt.Sprintf("%s:%s", task.Name, port.Label)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if port.To != 0 {
|
|
|
|
|
if other, ok := mappedPorts[port.To]; ok {
|
|
|
|
|
err := fmt.Errorf("Port mapped to %d already in use by %s", port.To, other)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
} else {
|
|
|
|
|
mappedPorts[port.To] = fmt.Sprintf("taskgroup network:%s", port.Label)
|
|
|
|
|
}
|
2017-07-07 23:17:05 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-11 00:57:47 +00:00
|
|
|
|
}
|
2015-09-15 17:46:10 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-19 13:17:38 +00:00
|
|
|
|
// validateServices runs Service.Validate() on group-level services,
|
|
|
|
|
// checks that group services do not conflict with task services and that
|
|
|
|
|
// group service checks that refer to tasks only refer to tasks that exist.
|
|
|
|
|
func (tg *TaskGroup) validateServices() error {
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
knownTasks := make(map[string]struct{})
|
|
|
|
|
knownServices := make(map[string]struct{})
|
|
|
|
|
|
|
|
|
|
// Create a map of known tasks and their services so we can compare
|
|
|
|
|
// vs the group-level services and checks
|
|
|
|
|
for _, task := range tg.Tasks {
|
|
|
|
|
knownTasks[task.Name] = struct{}{}
|
|
|
|
|
if task.Services == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
for _, service := range task.Services {
|
|
|
|
|
if _, ok := knownServices[service.Name+service.PortLabel]; ok {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Service %s is duplicate", service.Name))
|
|
|
|
|
}
|
|
|
|
|
for _, check := range service.Checks {
|
|
|
|
|
if check.TaskName != "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Check %s is invalid: only task group service checks can be assigned tasks", check.Name))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
knownServices[service.Name+service.PortLabel] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for i, service := range tg.Services {
|
|
|
|
|
if err := service.Validate(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Service[%d] %s validation failed: %s", i, service.Name, err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
// we break here to avoid the risk of crashing on null-pointer
|
|
|
|
|
// access in a later step, accepting that we might miss out on
|
|
|
|
|
// error messages to provide the user.
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if _, ok := knownServices[service.Name+service.PortLabel]; ok {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Service %s is duplicate", service.Name))
|
|
|
|
|
}
|
|
|
|
|
knownServices[service.Name+service.PortLabel] = struct{}{}
|
|
|
|
|
for _, check := range service.Checks {
|
|
|
|
|
if check.TaskName != "" {
|
|
|
|
|
if check.Type != ServiceCheckScript && check.Type != ServiceCheckGRPC {
|
|
|
|
|
mErr.Errors = append(mErr.Errors,
|
|
|
|
|
fmt.Errorf("Check %s invalid: only script and gRPC checks should have tasks", check.Name))
|
|
|
|
|
}
|
|
|
|
|
if _, ok := knownTasks[check.TaskName]; !ok {
|
|
|
|
|
mErr.Errors = append(mErr.Errors,
|
|
|
|
|
fmt.Errorf("Check %s invalid: refers to non-existent task %s", check.Name, check.TaskName))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-13 20:10:45 +00:00
|
|
|
|
// Warnings returns a list of warnings that may be from dubious settings or
|
|
|
|
|
// deprecation warnings.
|
|
|
|
|
func (tg *TaskGroup) Warnings(j *Job) error {
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
|
|
|
|
|
// Validate the update strategy
|
|
|
|
|
if u := tg.Update; u != nil {
|
|
|
|
|
// Check the counts are appropriate
|
|
|
|
|
if u.MaxParallel > tg.Count {
|
|
|
|
|
mErr.Errors = append(mErr.Errors,
|
|
|
|
|
fmt.Errorf("Update max parallel count is greater than task group count (%d > %d). "+
|
2017-07-17 22:35:14 +00:00
|
|
|
|
"A destructive change would result in the simultaneous replacement of all allocations.", u.MaxParallel, tg.Count))
|
2017-07-13 20:10:45 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-07 00:17:09 +00:00
|
|
|
|
for _, t := range tg.Tasks {
|
|
|
|
|
if err := t.Warnings(); err != nil {
|
|
|
|
|
err = multierror.Prefix(err, fmt.Sprintf("Task %q:", t.Name))
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-13 20:10:45 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-07 19:23:34 +00:00
|
|
|
|
// LookupTask finds a task by name
|
|
|
|
|
func (tg *TaskGroup) LookupTask(name string) *Task {
|
|
|
|
|
for _, t := range tg.Tasks {
|
|
|
|
|
if t.Name == name {
|
|
|
|
|
return t
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-15 00:43:42 +00:00
|
|
|
|
func (tg *TaskGroup) GoString() string {
|
|
|
|
|
return fmt.Sprintf("*%#v", *tg)
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 00:18:06 +00:00
|
|
|
|
// CheckRestart describes if and when a task should be restarted based on
|
|
|
|
|
// failing health checks.
|
|
|
|
|
type CheckRestart struct {
|
2017-09-11 00:00:25 +00:00
|
|
|
|
Limit int // Restart task after this many unhealthy intervals
|
|
|
|
|
Grace time.Duration // Grace time to give tasks after starting to get healthy
|
|
|
|
|
IgnoreWarnings bool // If true treat checks in `warning` as passing
|
2017-08-25 00:18:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *CheckRestart) Copy() *CheckRestart {
|
|
|
|
|
if c == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nc := new(CheckRestart)
|
|
|
|
|
*nc = *c
|
|
|
|
|
return nc
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-24 15:29:26 +00:00
|
|
|
|
func (c *CheckRestart) Equals(o *CheckRestart) bool {
|
|
|
|
|
if c == nil || o == nil {
|
|
|
|
|
return c == o
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if c.Limit != o.Limit {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if c.Grace != o.Grace {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if c.IgnoreWarnings != o.IgnoreWarnings {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 00:18:06 +00:00
|
|
|
|
func (c *CheckRestart) Validate() error {
|
|
|
|
|
if c == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-15 22:12:47 +00:00
|
|
|
|
var mErr multierror.Error
|
2017-08-25 00:18:06 +00:00
|
|
|
|
if c.Limit < 0 {
|
2017-09-15 22:12:47 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("limit must be greater than or equal to 0 but found %d", c.Limit))
|
2017-08-25 00:18:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if c.Grace < 0 {
|
2017-09-15 22:12:47 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("grace period must be greater than or equal to 0 but found %d", c.Grace))
|
2017-08-25 00:18:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-15 22:12:47 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
2017-08-25 00:18:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-23 00:10:30 +00:00
|
|
|
|
const (
|
|
|
|
|
// DefaultKillTimeout is the default timeout between signaling a task it
|
|
|
|
|
// will be killed and killing it.
|
|
|
|
|
DefaultKillTimeout = 5 * time.Second
|
|
|
|
|
)
|
|
|
|
|
|
2016-02-05 07:28:01 +00:00
|
|
|
|
// LogConfig provides configuration for log rotation
|
|
|
|
|
type LogConfig struct {
|
2017-02-22 20:30:05 +00:00
|
|
|
|
MaxFiles int
|
|
|
|
|
MaxFileSizeMB int
|
2016-02-05 07:28:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-20 05:22:46 +00:00
|
|
|
|
func (l *LogConfig) Copy() *LogConfig {
|
|
|
|
|
if l == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return &LogConfig{
|
|
|
|
|
MaxFiles: l.MaxFiles,
|
|
|
|
|
MaxFileSizeMB: l.MaxFileSizeMB,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-19 06:37:54 +00:00
|
|
|
|
// DefaultLogConfig returns the default LogConfig values.
|
2016-02-11 00:44:31 +00:00
|
|
|
|
func DefaultLogConfig() *LogConfig {
|
|
|
|
|
return &LogConfig{
|
|
|
|
|
MaxFiles: 10,
|
|
|
|
|
MaxFileSizeMB: 10,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-11 22:44:35 +00:00
|
|
|
|
// Validate returns an error if the log config specified are less than
|
2016-02-05 07:28:01 +00:00
|
|
|
|
// the minimum allowed.
|
2016-02-11 22:44:35 +00:00
|
|
|
|
func (l *LogConfig) Validate() error {
|
2016-02-05 07:28:01 +00:00
|
|
|
|
var mErr multierror.Error
|
2016-02-11 22:44:35 +00:00
|
|
|
|
if l.MaxFiles < 1 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum number of files is 1; got %d", l.MaxFiles))
|
2016-02-05 07:28:01 +00:00
|
|
|
|
}
|
2016-02-11 22:44:35 +00:00
|
|
|
|
if l.MaxFileSizeMB < 1 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum file size is 1MB; got %d", l.MaxFileSizeMB))
|
2016-02-05 07:28:01 +00:00
|
|
|
|
}
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Task is a single process typically that is executed as part of a task group.
|
|
|
|
|
type Task struct {
|
|
|
|
|
// Name of the task
|
|
|
|
|
Name string
|
|
|
|
|
|
|
|
|
|
// Driver is used to control which driver is used
|
|
|
|
|
Driver string
|
|
|
|
|
|
2016-03-23 11:57:31 +00:00
|
|
|
|
// User is used to determine which user will run the task. It defaults to
|
|
|
|
|
// the same user the Nomad client is being run as.
|
|
|
|
|
User string
|
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Config is provided to the driver to initialize
|
2015-11-14 02:09:42 +00:00
|
|
|
|
Config map[string]interface{}
|
2015-07-03 23:57:48 +00:00
|
|
|
|
|
2015-09-30 16:18:43 +00:00
|
|
|
|
// Map of environment variables to be used by the driver
|
|
|
|
|
Env map[string]string
|
|
|
|
|
|
2015-11-17 07:20:35 +00:00
|
|
|
|
// List of service definitions exposed by the Task
|
2016-06-12 23:36:49 +00:00
|
|
|
|
Services []*Service
|
2015-11-17 06:37:09 +00:00
|
|
|
|
|
2016-08-09 22:23:44 +00:00
|
|
|
|
// Vault is used to define the set of Vault policies that this task should
|
|
|
|
|
// have access to.
|
|
|
|
|
Vault *Vault
|
|
|
|
|
|
2016-09-23 22:39:52 +00:00
|
|
|
|
// Templates are the set of templates to be rendered for the task.
|
|
|
|
|
Templates []*Template
|
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Constraints can be specified at a task level and apply only to
|
|
|
|
|
// the particular task.
|
|
|
|
|
Constraints []*Constraint
|
|
|
|
|
|
2018-07-16 13:30:58 +00:00
|
|
|
|
// Affinities can be specified at the task level to express
|
|
|
|
|
// scheduling preferences
|
|
|
|
|
Affinities []*Affinity
|
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Resources is the resources needed by this task
|
|
|
|
|
Resources *Resources
|
|
|
|
|
|
2017-01-26 05:06:16 +00:00
|
|
|
|
// DispatchPayload configures how the task retrieves its input from a dispatch
|
|
|
|
|
DispatchPayload *DispatchPayloadConfig
|
2016-11-23 22:56:50 +00:00
|
|
|
|
|
2015-07-03 23:57:48 +00:00
|
|
|
|
// Meta is used to associate arbitrary metadata with this
|
|
|
|
|
// task. This is opaque to Nomad.
|
|
|
|
|
Meta map[string]string
|
2015-12-23 00:10:30 +00:00
|
|
|
|
|
|
|
|
|
// KillTimeout is the time between signaling a task that it will be
|
|
|
|
|
// killed and killing it.
|
2017-02-22 20:30:05 +00:00
|
|
|
|
KillTimeout time.Duration
|
2016-02-05 07:28:01 +00:00
|
|
|
|
|
|
|
|
|
// LogConfig provides configuration for log rotation
|
2017-02-22 20:30:05 +00:00
|
|
|
|
LogConfig *LogConfig
|
2016-03-14 05:29:07 +00:00
|
|
|
|
|
|
|
|
|
// Artifacts is a list of artifacts to download and extract before running
|
|
|
|
|
// the task.
|
2016-03-14 18:13:43 +00:00
|
|
|
|
Artifacts []*TaskArtifact
|
2017-02-11 00:57:47 +00:00
|
|
|
|
|
|
|
|
|
// Leader marks the task as the leader within the group. When the leader
|
|
|
|
|
// task exits, other tasks will be gracefully terminated.
|
|
|
|
|
Leader bool
|
2017-08-17 00:54:11 +00:00
|
|
|
|
|
|
|
|
|
// ShutdownDelay is the duration of the delay between deregistering a
|
|
|
|
|
// task from Consul and sending it a signal to shutdown. See #2441
|
|
|
|
|
ShutdownDelay time.Duration
|
2017-11-30 21:53:35 +00:00
|
|
|
|
|
2019-07-25 14:44:08 +00:00
|
|
|
|
// VolumeMounts is a list of Volume name <-> mount configurations that will be
|
|
|
|
|
// attached to this task.
|
|
|
|
|
VolumeMounts []*VolumeMount
|
|
|
|
|
|
2017-11-30 21:53:35 +00:00
|
|
|
|
// The kill signal to use for the task. This is an optional specification,
|
2017-12-07 15:45:21 +00:00
|
|
|
|
|
|
|
|
|
// KillSignal is the kill signal to use for the task. This is an optional
|
|
|
|
|
// specification and defaults to SIGINT
|
2017-11-30 21:53:35 +00:00
|
|
|
|
KillSignal string
|
2019-08-07 23:43:36 +00:00
|
|
|
|
|
2019-08-12 22:41:40 +00:00
|
|
|
|
// Used internally to manage tasks according to their TaskKind. Initial use case
|
2019-08-07 23:43:36 +00:00
|
|
|
|
// is for Consul Connect
|
2019-08-12 22:41:40 +00:00
|
|
|
|
Kind TaskKind
|
2016-03-14 18:13:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-10 21:44:53 +00:00
|
|
|
|
func (t *Task) Copy() *Task {
|
2016-02-11 01:54:43 +00:00
|
|
|
|
if t == nil {
|
2016-02-10 21:44:53 +00:00
|
|
|
|
return nil
|
|
|
|
|
}
|
2016-02-11 01:54:43 +00:00
|
|
|
|
nt := new(Task)
|
|
|
|
|
*nt = *t
|
2017-01-18 23:55:14 +00:00
|
|
|
|
nt.Env = helper.CopyMapStringString(nt.Env)
|
2016-02-11 01:54:43 +00:00
|
|
|
|
|
2016-06-12 23:36:49 +00:00
|
|
|
|
if t.Services != nil {
|
|
|
|
|
services := make([]*Service, len(nt.Services))
|
|
|
|
|
for i, s := range nt.Services {
|
2016-03-21 23:29:21 +00:00
|
|
|
|
services[i] = s.Copy()
|
|
|
|
|
}
|
2016-06-12 23:36:49 +00:00
|
|
|
|
nt.Services = services
|
2016-02-11 01:54:43 +00:00
|
|
|
|
}
|
2016-03-21 23:29:21 +00:00
|
|
|
|
|
2016-02-11 17:08:20 +00:00
|
|
|
|
nt.Constraints = CopySliceConstraints(nt.Constraints)
|
2018-07-16 17:54:46 +00:00
|
|
|
|
nt.Affinities = CopySliceAffinities(nt.Affinities)
|
2019-07-25 14:44:08 +00:00
|
|
|
|
nt.VolumeMounts = CopySliceVolumeMount(nt.VolumeMounts)
|
2016-02-11 01:54:43 +00:00
|
|
|
|
|
2016-08-09 22:23:44 +00:00
|
|
|
|
nt.Vault = nt.Vault.Copy()
|
2016-02-11 01:54:43 +00:00
|
|
|
|
nt.Resources = nt.Resources.Copy()
|
2019-08-20 05:22:46 +00:00
|
|
|
|
nt.LogConfig = nt.LogConfig.Copy()
|
2017-01-18 23:55:14 +00:00
|
|
|
|
nt.Meta = helper.CopyMapStringString(nt.Meta)
|
2017-01-26 05:06:16 +00:00
|
|
|
|
nt.DispatchPayload = nt.DispatchPayload.Copy()
|
2016-02-11 01:54:43 +00:00
|
|
|
|
|
2016-03-21 23:29:21 +00:00
|
|
|
|
if t.Artifacts != nil {
|
2016-03-22 00:23:04 +00:00
|
|
|
|
artifacts := make([]*TaskArtifact, 0, len(t.Artifacts))
|
2016-03-21 23:29:21 +00:00
|
|
|
|
for _, a := range nt.Artifacts {
|
|
|
|
|
artifacts = append(artifacts, a.Copy())
|
|
|
|
|
}
|
|
|
|
|
nt.Artifacts = artifacts
|
2016-03-14 22:46:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-11 01:54:43 +00:00
|
|
|
|
if i, err := copystructure.Copy(nt.Config); err != nil {
|
2017-07-18 00:18:12 +00:00
|
|
|
|
panic(err.Error())
|
2017-07-18 00:53:21 +00:00
|
|
|
|
} else {
|
2016-02-11 01:54:43 +00:00
|
|
|
|
nt.Config = i.(map[string]interface{})
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-23 22:39:52 +00:00
|
|
|
|
if t.Templates != nil {
|
|
|
|
|
templates := make([]*Template, len(t.Templates))
|
|
|
|
|
for i, tmpl := range nt.Templates {
|
|
|
|
|
templates[i] = tmpl.Copy()
|
|
|
|
|
}
|
|
|
|
|
nt.Templates = templates
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-11 01:54:43 +00:00
|
|
|
|
return nt
|
2016-02-10 21:44:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-07-20 23:07:15 +00:00
|
|
|
|
// Canonicalize canonicalizes fields in the task.
|
|
|
|
|
func (t *Task) Canonicalize(job *Job, tg *TaskGroup) {
|
2016-07-18 23:17:38 +00:00
|
|
|
|
// Ensure that an empty and nil map are treated the same to avoid scheduling
|
|
|
|
|
// problems since we use reflect DeepEquals.
|
|
|
|
|
if len(t.Meta) == 0 {
|
|
|
|
|
t.Meta = nil
|
|
|
|
|
}
|
|
|
|
|
if len(t.Config) == 0 {
|
|
|
|
|
t.Config = nil
|
|
|
|
|
}
|
|
|
|
|
if len(t.Env) == 0 {
|
|
|
|
|
t.Env = nil
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-20 23:07:15 +00:00
|
|
|
|
for _, service := range t.Services {
|
|
|
|
|
service.Canonicalize(job.Name, tg.Name, t.Name)
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-26 20:21:09 +00:00
|
|
|
|
// If Resources are nil initialize them to defaults, otherwise canonicalize
|
2016-10-26 17:36:41 +00:00
|
|
|
|
if t.Resources == nil {
|
|
|
|
|
t.Resources = DefaultResources()
|
|
|
|
|
} else {
|
2016-07-20 23:43:20 +00:00
|
|
|
|
t.Resources.Canonicalize()
|
|
|
|
|
}
|
2015-12-23 00:10:30 +00:00
|
|
|
|
|
|
|
|
|
// Set the default timeout if it is not specified.
|
|
|
|
|
if t.KillTimeout == 0 {
|
|
|
|
|
t.KillTimeout = DefaultKillTimeout
|
|
|
|
|
}
|
2016-10-25 18:09:22 +00:00
|
|
|
|
|
|
|
|
|
if t.Vault != nil {
|
|
|
|
|
t.Vault.Canonicalize()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, template := range t.Templates {
|
|
|
|
|
template.Canonicalize()
|
|
|
|
|
}
|
2015-12-18 20:17:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-15 00:43:42 +00:00
|
|
|
|
func (t *Task) GoString() string {
|
|
|
|
|
return fmt.Sprintf("*%#v", *t)
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-16 03:21:52 +00:00
|
|
|
|
// Validate is used to sanity check a task
|
2019-08-08 19:45:24 +00:00
|
|
|
|
func (t *Task) Validate(ephemeralDisk *EphemeralDisk, jobType string, tgServices []*Service) error {
|
2016-03-14 22:46:06 +00:00
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
if t.Name == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing task name"))
|
|
|
|
|
}
|
2016-05-28 00:17:10 +00:00
|
|
|
|
if strings.ContainsAny(t.Name, `/\`) {
|
2016-05-28 00:43:20 +00:00
|
|
|
|
// We enforce this so that when creating the directory on disk it will
|
|
|
|
|
// not have any slashes.
|
2016-11-01 23:05:34 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Task name cannot include slashes"))
|
2016-05-28 00:17:10 +00:00
|
|
|
|
}
|
2016-03-14 22:46:06 +00:00
|
|
|
|
if t.Driver == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing task driver"))
|
|
|
|
|
}
|
2017-08-17 00:54:11 +00:00
|
|
|
|
if t.KillTimeout < 0 {
|
2016-03-14 22:46:06 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("KillTimeout must be a positive value"))
|
|
|
|
|
}
|
2017-08-17 00:54:11 +00:00
|
|
|
|
if t.ShutdownDelay < 0 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("ShutdownDelay must be a positive value"))
|
|
|
|
|
}
|
2016-03-14 22:46:06 +00:00
|
|
|
|
|
|
|
|
|
// Validate the resources.
|
|
|
|
|
if t.Resources == nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing task resources"))
|
2018-10-10 17:32:44 +00:00
|
|
|
|
} else if err := t.Resources.Validate(); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
2016-08-25 18:53:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-14 22:46:06 +00:00
|
|
|
|
// Validate the log config
|
|
|
|
|
if t.LogConfig == nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing Log Config"))
|
|
|
|
|
} else if err := t.LogConfig.Validate(); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for idx, constr := range t.Constraints {
|
|
|
|
|
if err := constr.Validate(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Constraint %d validation failed: %s", idx+1, err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
2017-03-12 00:23:24 +00:00
|
|
|
|
|
|
|
|
|
switch constr.Operand {
|
|
|
|
|
case ConstraintDistinctHosts, ConstraintDistinctProperty:
|
|
|
|
|
outer := fmt.Errorf("Constraint %d has disallowed Operand at task level: %s", idx+1, constr.Operand)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
2016-03-14 22:46:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-23 16:06:49 +00:00
|
|
|
|
if jobType == JobTypeSystem {
|
|
|
|
|
if t.Affinities != nil {
|
2018-07-24 15:37:13 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("System jobs may not have an affinity stanza"))
|
2018-07-23 16:06:49 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for idx, affinity := range t.Affinities {
|
|
|
|
|
if err := affinity.Validate(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Affinity %d validation failed: %s", idx+1, err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
2018-07-16 13:30:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-19 02:38:47 +00:00
|
|
|
|
// Validate Services
|
|
|
|
|
if err := validateServices(t); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
2016-03-14 22:46:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-14 22:43:42 +00:00
|
|
|
|
if t.LogConfig != nil && ephemeralDisk != nil {
|
2016-03-14 22:46:06 +00:00
|
|
|
|
logUsage := (t.LogConfig.MaxFiles * t.LogConfig.MaxFileSizeMB)
|
2016-09-14 22:43:42 +00:00
|
|
|
|
if ephemeralDisk.SizeMB <= logUsage {
|
2016-03-14 22:46:06 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors,
|
2016-03-22 17:27:14 +00:00
|
|
|
|
fmt.Errorf("log storage (%d MB) must be less than requested disk capacity (%d MB)",
|
2016-09-14 22:43:42 +00:00
|
|
|
|
logUsage, ephemeralDisk.SizeMB))
|
2016-03-14 22:46:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for idx, artifact := range t.Artifacts {
|
|
|
|
|
if err := artifact.Validate(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Artifact %d validation failed: %v", idx+1, err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-09 22:23:44 +00:00
|
|
|
|
if t.Vault != nil {
|
|
|
|
|
if err := t.Vault.Validate(); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Vault validation failed: %v", err))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-10 22:19:00 +00:00
|
|
|
|
destinations := make(map[string]int, len(t.Templates))
|
2016-09-23 22:39:52 +00:00
|
|
|
|
for idx, tmpl := range t.Templates {
|
|
|
|
|
if err := tmpl.Validate(); err != nil {
|
|
|
|
|
outer := fmt.Errorf("Template %d validation failed: %s", idx+1, err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
2016-10-10 22:19:00 +00:00
|
|
|
|
|
|
|
|
|
if other, ok := destinations[tmpl.DestPath]; ok {
|
|
|
|
|
outer := fmt.Errorf("Template %d has same destination as %d", idx+1, other)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
} else {
|
|
|
|
|
destinations[tmpl.DestPath] = idx + 1
|
|
|
|
|
}
|
2016-09-23 22:39:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-26 05:06:16 +00:00
|
|
|
|
// Validate the dispatch payload block if there
|
|
|
|
|
if t.DispatchPayload != nil {
|
|
|
|
|
if err := t.DispatchPayload.Validate(); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Dispatch Payload validation failed: %v", err))
|
2016-12-18 23:48:30 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-12 22:41:40 +00:00
|
|
|
|
// Validation for TaskKind field which is used for Consul Connect integration
|
2019-08-15 15:22:37 +00:00
|
|
|
|
if t.Kind.IsConnectProxy() {
|
2019-08-08 19:45:24 +00:00
|
|
|
|
// This task is a Connect proxy so it should not have service stanzas
|
|
|
|
|
if len(t.Services) > 0 {
|
2019-08-12 22:03:30 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Connect proxy task must not have a service stanza"))
|
2019-08-08 19:45:24 +00:00
|
|
|
|
}
|
|
|
|
|
if t.Leader {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Connect proxy task must not have leader set"))
|
|
|
|
|
}
|
2019-08-15 15:22:37 +00:00
|
|
|
|
serviceErr := ValidateConnectProxyService(t.Kind.Value(), tgServices)
|
2019-08-09 21:40:51 +00:00
|
|
|
|
if serviceErr != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, serviceErr)
|
2019-08-08 19:45:24 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-14 22:46:06 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-19 02:38:47 +00:00
|
|
|
|
// validateServices takes a task and validates the services within it are valid
|
|
|
|
|
// and reference ports that exist.
|
|
|
|
|
func validateServices(t *Task) error {
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
|
2018-03-12 18:26:37 +00:00
|
|
|
|
// Ensure that services don't ask for nonexistent ports and their names are
|
2016-05-02 20:40:49 +00:00
|
|
|
|
// unique.
|
2017-12-08 21:49:57 +00:00
|
|
|
|
servicePorts := make(map[string]map[string]struct{})
|
|
|
|
|
addServicePort := func(label, service string) {
|
|
|
|
|
if _, ok := servicePorts[label]; !ok {
|
|
|
|
|
servicePorts[label] = map[string]struct{}{}
|
|
|
|
|
}
|
|
|
|
|
servicePorts[label][service] = struct{}{}
|
|
|
|
|
}
|
2016-05-02 20:40:49 +00:00
|
|
|
|
knownServices := make(map[string]struct{})
|
2016-06-12 23:36:49 +00:00
|
|
|
|
for i, service := range t.Services {
|
2016-04-19 02:38:47 +00:00
|
|
|
|
if err := service.Validate(); err != nil {
|
2016-06-14 01:19:40 +00:00
|
|
|
|
outer := fmt.Errorf("service[%d] %+q validation failed: %s", i, service.Name, err)
|
2016-04-19 02:38:47 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, outer)
|
|
|
|
|
}
|
2017-01-17 20:26:07 +00:00
|
|
|
|
|
|
|
|
|
// Ensure that services with the same name are not being registered for
|
|
|
|
|
// the same port
|
|
|
|
|
if _, ok := knownServices[service.Name+service.PortLabel]; ok {
|
2016-05-02 20:40:49 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q is duplicate", service.Name))
|
|
|
|
|
}
|
2017-01-17 20:26:07 +00:00
|
|
|
|
knownServices[service.Name+service.PortLabel] = struct{}{}
|
2016-04-19 02:38:47 +00:00
|
|
|
|
|
|
|
|
|
if service.PortLabel != "" {
|
2017-12-08 06:09:37 +00:00
|
|
|
|
if service.AddressMode == "driver" {
|
|
|
|
|
// Numeric port labels are valid for address_mode=driver
|
|
|
|
|
_, err := strconv.Atoi(service.PortLabel)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Not a numeric port label, add it to list to check
|
2017-12-08 21:49:57 +00:00
|
|
|
|
addServicePort(service.PortLabel, service.Name)
|
2017-12-08 06:09:37 +00:00
|
|
|
|
}
|
2017-12-05 19:39:42 +00:00
|
|
|
|
} else {
|
2017-12-08 21:49:57 +00:00
|
|
|
|
addServicePort(service.PortLabel, service.Name)
|
2017-12-05 19:39:42 +00:00
|
|
|
|
}
|
2016-04-19 02:38:47 +00:00
|
|
|
|
}
|
2016-05-03 20:16:02 +00:00
|
|
|
|
|
2017-12-08 21:49:57 +00:00
|
|
|
|
// Ensure that check names are unique and have valid ports
|
2016-05-03 20:16:02 +00:00
|
|
|
|
knownChecks := make(map[string]struct{})
|
|
|
|
|
for _, check := range service.Checks {
|
|
|
|
|
if _, ok := knownChecks[check.Name]; ok {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q is duplicate", check.Name))
|
|
|
|
|
}
|
|
|
|
|
knownChecks[check.Name] = struct{}{}
|
2017-12-08 21:49:57 +00:00
|
|
|
|
|
|
|
|
|
if !check.RequiresPort() {
|
|
|
|
|
// No need to continue validating check if it doesn't need a port
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
effectivePort := check.PortLabel
|
|
|
|
|
if effectivePort == "" {
|
|
|
|
|
// Inherits from service
|
|
|
|
|
effectivePort = service.PortLabel
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if effectivePort == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q is missing a port", check.Name))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isNumeric := false
|
|
|
|
|
portNumber, err := strconv.Atoi(effectivePort)
|
|
|
|
|
if err == nil {
|
|
|
|
|
isNumeric = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Numeric ports are fine for address_mode = "driver"
|
|
|
|
|
if check.AddressMode == "driver" && isNumeric {
|
|
|
|
|
if portNumber <= 0 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q has invalid numeric port %d", check.Name, portNumber))
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if isNumeric {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf(`check %q cannot use a numeric port %d without setting address_mode="driver"`, check.Name, portNumber))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PortLabel must exist, report errors by its parent service
|
|
|
|
|
addServicePort(effectivePort, service.Name)
|
2016-05-03 20:16:02 +00:00
|
|
|
|
}
|
2016-04-19 02:38:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the set of port labels.
|
|
|
|
|
portLabels := make(map[string]struct{})
|
|
|
|
|
if t.Resources != nil {
|
|
|
|
|
for _, network := range t.Resources.Networks {
|
2017-06-09 17:29:41 +00:00
|
|
|
|
ports := network.PortLabels()
|
2017-09-26 22:26:33 +00:00
|
|
|
|
for portLabel := range ports {
|
2016-04-19 02:38:47 +00:00
|
|
|
|
portLabels[portLabel] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-19 00:18:42 +00:00
|
|
|
|
// Iterate over a sorted list of keys to make error listings stable
|
|
|
|
|
keys := make([]string, 0, len(servicePorts))
|
|
|
|
|
for p := range servicePorts {
|
|
|
|
|
keys = append(keys, p)
|
|
|
|
|
}
|
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
2016-04-19 02:38:47 +00:00
|
|
|
|
// Ensure all ports referenced in services exist.
|
2017-12-19 00:18:42 +00:00
|
|
|
|
for _, servicePort := range keys {
|
|
|
|
|
services := servicePorts[servicePort]
|
2016-04-19 02:38:47 +00:00
|
|
|
|
_, ok := portLabels[servicePort]
|
|
|
|
|
if !ok {
|
2017-12-08 21:49:57 +00:00
|
|
|
|
names := make([]string, 0, len(services))
|
|
|
|
|
for name := range services {
|
|
|
|
|
names = append(names, name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Keep order deterministic
|
|
|
|
|
sort.Strings(names)
|
|
|
|
|
joined := strings.Join(names, ", ")
|
2016-04-19 02:38:47 +00:00
|
|
|
|
err := fmt.Errorf("port label %q referenced by services %v does not exist", servicePort, joined)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-09 17:29:41 +00:00
|
|
|
|
|
|
|
|
|
// Ensure address mode is valid
|
2016-04-19 02:38:47 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-07 00:17:09 +00:00
|
|
|
|
func (t *Task) Warnings() error {
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
|
|
|
|
|
// Validate the resources
|
|
|
|
|
if t.Resources != nil && t.Resources.IOPS != 0 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("IOPS has been deprecated as of Nomad 0.9.0. Please remove IOPS from resource stanza."))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-15 15:22:37 +00:00
|
|
|
|
// TaskKind identifies the special kinds of tasks using the following format:
|
|
|
|
|
// '<kind_name>(:<identifier>)`. The TaskKind can optionally include an identifier that
|
|
|
|
|
// is opague to the Task. This identier can be used to relate the task to some
|
|
|
|
|
// other entity based on the kind.
|
|
|
|
|
//
|
|
|
|
|
// For example, a task may have the TaskKind of `connect-proxy:service` where
|
|
|
|
|
// 'connect-proxy' is the kind name and 'service' is the identifier that relates the
|
|
|
|
|
// task to the service name of which it is a connect proxy for.
|
2019-08-12 22:41:40 +00:00
|
|
|
|
type TaskKind string
|
2019-08-09 21:40:51 +00:00
|
|
|
|
|
2019-08-15 15:22:37 +00:00
|
|
|
|
// Name returns the kind name portion of the TaskKind
|
|
|
|
|
func (k TaskKind) Name() string {
|
|
|
|
|
return strings.Split(string(k), ":")[0]
|
|
|
|
|
}
|
2019-08-09 21:40:51 +00:00
|
|
|
|
|
2019-08-15 15:22:37 +00:00
|
|
|
|
// Value returns the identifier of the TaskKind or an empty string if it doesn't
|
|
|
|
|
// include one.
|
|
|
|
|
func (k TaskKind) Value() string {
|
|
|
|
|
if s := strings.SplitN(string(k), ":", 2); len(s) > 1 {
|
|
|
|
|
return s[1]
|
|
|
|
|
}
|
|
|
|
|
return ""
|
2019-08-09 21:40:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-15 15:22:37 +00:00
|
|
|
|
// IsConnectProxy returns true if the TaskKind is connect-proxy
|
|
|
|
|
func (k TaskKind) IsConnectProxy() bool {
|
|
|
|
|
return strings.HasPrefix(string(k), ConnectProxyPrefix+":") && len(k) > len(ConnectProxyPrefix)+1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ConnectProxyPrefix is the prefix used for fields referencing a Consul Connect
|
|
|
|
|
// Proxy
|
|
|
|
|
const ConnectProxyPrefix = "connect-proxy"
|
|
|
|
|
|
|
|
|
|
// ValidateConnectProxyService checks that the service that is being
|
2019-08-12 22:41:40 +00:00
|
|
|
|
// proxied by this task exists in the task group and contains
|
|
|
|
|
// valid Connect config.
|
2019-08-15 15:22:37 +00:00
|
|
|
|
func ValidateConnectProxyService(serviceName string, tgServices []*Service) error {
|
2019-08-09 21:40:51 +00:00
|
|
|
|
found := false
|
|
|
|
|
for _, svc := range tgServices {
|
|
|
|
|
if svc.Name == serviceName && svc.Connect != nil && svc.Connect.SidecarService != nil {
|
|
|
|
|
found = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !found {
|
2019-08-12 22:41:39 +00:00
|
|
|
|
return fmt.Errorf("Connect proxy service name not found in services from task group")
|
2019-08-09 21:40:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-12 22:41:39 +00:00
|
|
|
|
return nil
|
2019-08-09 21:40:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-23 22:39:52 +00:00
|
|
|
|
const (
|
|
|
|
|
// TemplateChangeModeNoop marks that no action should be taken if the
|
|
|
|
|
// template is re-rendered
|
|
|
|
|
TemplateChangeModeNoop = "noop"
|
|
|
|
|
|
|
|
|
|
// TemplateChangeModeSignal marks that the task should be signaled if the
|
|
|
|
|
// template is re-rendered
|
|
|
|
|
TemplateChangeModeSignal = "signal"
|
|
|
|
|
|
|
|
|
|
// TemplateChangeModeRestart marks that the task should be restarted if the
|
|
|
|
|
// template is re-rendered
|
|
|
|
|
TemplateChangeModeRestart = "restart"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
// TemplateChangeModeInvalidError is the error for when an invalid change
|
|
|
|
|
// mode is given
|
|
|
|
|
TemplateChangeModeInvalidError = errors.New("Invalid change mode. Must be one of the following: noop, signal, restart")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Template represents a template configuration to be rendered for a given task
|
|
|
|
|
type Template struct {
|
2016-10-11 19:31:40 +00:00
|
|
|
|
// SourcePath is the path to the template to be rendered
|
2017-02-22 20:30:05 +00:00
|
|
|
|
SourcePath string
|
2016-09-23 22:39:52 +00:00
|
|
|
|
|
|
|
|
|
// DestPath is the path to where the template should be rendered
|
2017-02-22 20:30:05 +00:00
|
|
|
|
DestPath string
|
2016-09-23 22:39:52 +00:00
|
|
|
|
|
2016-10-03 19:42:18 +00:00
|
|
|
|
// EmbeddedTmpl store the raw template. This is useful for smaller templates
|
2018-01-08 11:30:00 +00:00
|
|
|
|
// where they are embedded in the job file rather than sent as an artifact
|
2017-02-22 20:30:05 +00:00
|
|
|
|
EmbeddedTmpl string
|
2016-09-23 22:39:52 +00:00
|
|
|
|
|
|
|
|
|
// ChangeMode indicates what should be done if the template is re-rendered
|
2017-02-22 20:30:05 +00:00
|
|
|
|
ChangeMode string
|
2016-09-23 22:39:52 +00:00
|
|
|
|
|
2016-10-03 19:42:18 +00:00
|
|
|
|
// ChangeSignal is the signal that should be sent if the change mode
|
2016-09-23 22:39:52 +00:00
|
|
|
|
// requires it.
|
2017-02-22 20:30:05 +00:00
|
|
|
|
ChangeSignal string
|
2016-09-23 22:39:52 +00:00
|
|
|
|
|
|
|
|
|
// Splay is used to avoid coordinated restarts of processes by applying a
|
|
|
|
|
// random wait between 0 and the given splay value before signalling the
|
|
|
|
|
// application of a change
|
2017-02-22 20:30:05 +00:00
|
|
|
|
Splay time.Duration
|
2017-02-01 04:00:33 +00:00
|
|
|
|
|
|
|
|
|
// Perms is the permission the file should be written out with.
|
2017-02-22 20:30:05 +00:00
|
|
|
|
Perms string
|
2017-02-21 00:43:28 +00:00
|
|
|
|
|
|
|
|
|
// LeftDelim and RightDelim are optional configurations to control what
|
|
|
|
|
// delimiter is utilized when parsing the template.
|
2017-02-22 20:30:05 +00:00
|
|
|
|
LeftDelim string
|
|
|
|
|
RightDelim string
|
2017-05-13 00:07:54 +00:00
|
|
|
|
|
|
|
|
|
// Envvars enables exposing the template as environment variables
|
|
|
|
|
// instead of as a file. The template must be of the form:
|
|
|
|
|
//
|
|
|
|
|
// VAR_NAME_1={{ key service/my-key }}
|
|
|
|
|
// VAR_NAME_2=raw string and {{ env "attr.kernel.name" }}
|
|
|
|
|
//
|
|
|
|
|
// Lines will be split on the initial "=" with the first part being the
|
|
|
|
|
// key name and the second part the value.
|
|
|
|
|
// Empty lines and lines starting with # will be ignored, but to avoid
|
|
|
|
|
// escaping issues #s within lines will not be treated as comments.
|
|
|
|
|
Envvars bool
|
2017-08-01 21:14:08 +00:00
|
|
|
|
|
|
|
|
|
// VaultGrace is the grace duration between lease renewal and reacquiring a
|
|
|
|
|
// secret. If the lease of a secret is less than the grace, a new secret is
|
|
|
|
|
// acquired.
|
|
|
|
|
VaultGrace time.Duration
|
2016-09-23 22:39:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-26 22:23:26 +00:00
|
|
|
|
// DefaultTemplate returns a default template.
|
|
|
|
|
func DefaultTemplate() *Template {
|
|
|
|
|
return &Template{
|
|
|
|
|
ChangeMode: TemplateChangeModeRestart,
|
|
|
|
|
Splay: 5 * time.Second,
|
2017-02-01 04:00:33 +00:00
|
|
|
|
Perms: "0644",
|
2016-09-26 22:23:26 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-23 22:39:52 +00:00
|
|
|
|
func (t *Template) Copy() *Template {
|
|
|
|
|
if t == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
copy := new(Template)
|
|
|
|
|
*copy = *t
|
|
|
|
|
return copy
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-25 18:09:22 +00:00
|
|
|
|
func (t *Template) Canonicalize() {
|
|
|
|
|
if t.ChangeSignal != "" {
|
|
|
|
|
t.ChangeSignal = strings.ToUpper(t.ChangeSignal)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-23 22:39:52 +00:00
|
|
|
|
func (t *Template) Validate() error {
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
|
|
|
|
|
// Verify we have something to render
|
2016-10-03 19:42:18 +00:00
|
|
|
|
if t.SourcePath == "" && t.EmbeddedTmpl == "" {
|
2016-10-05 20:41:29 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Must specify a source path or have an embedded template"))
|
2016-09-23 22:39:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify we can render somewhere
|
|
|
|
|
if t.DestPath == "" {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Must specify a destination for the template"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify the destination doesn't escape
|
2016-12-18 23:48:30 +00:00
|
|
|
|
escaped, err := PathEscapesAllocDir("task", t.DestPath)
|
2016-09-23 22:39:52 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid destination path: %v", err))
|
|
|
|
|
} else if escaped {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("destination escapes allocation directory"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify a proper change mode
|
|
|
|
|
switch t.ChangeMode {
|
|
|
|
|
case TemplateChangeModeNoop, TemplateChangeModeRestart:
|
|
|
|
|
case TemplateChangeModeSignal:
|
2016-10-03 19:42:18 +00:00
|
|
|
|
if t.ChangeSignal == "" {
|
2016-09-23 22:39:52 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Must specify signal value when change mode is signal"))
|
|
|
|
|
}
|
2017-07-25 23:34:41 +00:00
|
|
|
|
if t.Envvars {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("cannot use signals with env var templates"))
|
|
|
|
|
}
|
2016-09-23 22:39:52 +00:00
|
|
|
|
default:
|
|
|
|
|
multierror.Append(&mErr, TemplateChangeModeInvalidError)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify the splay is positive
|
|
|
|
|
if t.Splay < 0 {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Must specify positive splay value"))
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-01 04:00:33 +00:00
|
|
|
|
// Verify the permissions
|
|
|
|
|
if t.Perms != "" {
|
|
|
|
|
if _, err := strconv.ParseUint(t.Perms, 8, 12); err != nil {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Failed to parse %q as octal: %v", t.Perms, err))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-01 21:14:08 +00:00
|
|
|
|
if t.VaultGrace.Nanoseconds() < 0 {
|
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Vault grace must be greater than zero: %v < 0", t.VaultGrace))
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-23 22:39:52 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-12 23:28:22 +00:00
|
|
|
|
// Set of possible states for a task.
|
|
|
|
|
const (
|
|
|
|
|
TaskStatePending = "pending" // The task is waiting to be run.
|
|
|
|
|
TaskStateRunning = "running" // The task is currently running.
|
|
|
|
|
TaskStateDead = "dead" // Terminal state of task.
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// TaskState tracks the current state of a task and events that caused state
|
2016-05-15 16:41:34 +00:00
|
|
|
|
// transitions.
|
2015-11-12 23:28:22 +00:00
|
|
|
|
type TaskState struct {
|
|
|
|
|
// The current state of the task.
|
|
|
|
|
State string
|
|
|
|
|
|
2016-10-21 00:27:16 +00:00
|
|
|
|
// Failed marks a task as having failed
|
|
|
|
|
Failed bool
|
|
|
|
|
|
2017-07-03 04:49:56 +00:00
|
|
|
|
// Restarts is the number of times the task has restarted
|
|
|
|
|
Restarts uint64
|
|
|
|
|
|
2017-07-07 06:04:32 +00:00
|
|
|
|
// LastRestart is the time the task last restarted. It is updated each time the
|
|
|
|
|
// task restarts
|
|
|
|
|
LastRestart time.Time
|
|
|
|
|
|
2017-03-31 22:57:10 +00:00
|
|
|
|
// StartedAt is the time the task is started. It is updated each time the
|
|
|
|
|
// task starts
|
|
|
|
|
StartedAt time.Time
|
|
|
|
|
|
2018-03-11 19:06:05 +00:00
|
|
|
|
// FinishedAt is the time at which the task transitioned to dead and will
|
2017-03-31 22:57:10 +00:00
|
|
|
|
// not be started again.
|
|
|
|
|
FinishedAt time.Time
|
|
|
|
|
|
2016-05-15 16:41:34 +00:00
|
|
|
|
// Series of task events that transition the state of the task.
|
2015-11-12 23:28:22 +00:00
|
|
|
|
Events []*TaskEvent
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-16 22:17:36 +00:00
|
|
|
|
// NewTaskState returns a TaskState initialized in the Pending state.
|
|
|
|
|
func NewTaskState() *TaskState {
|
|
|
|
|
return &TaskState{
|
|
|
|
|
State: TaskStatePending,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Canonicalize ensures the TaskState has a State set. It should default to
|
|
|
|
|
// Pending.
|
|
|
|
|
func (ts *TaskState) Canonicalize() {
|
|
|
|
|
if ts.State == "" {
|
|
|
|
|
ts.State = TaskStatePending
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-02 01:47:53 +00:00
|
|
|
|
func (ts *TaskState) Copy() *TaskState {
|
2016-02-11 01:54:43 +00:00
|
|
|
|
if ts == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2016-02-02 01:47:53 +00:00
|
|
|
|
copy := new(TaskState)
|
2017-07-03 04:49:56 +00:00
|
|
|
|
*copy = *ts
|
2016-03-21 23:29:21 +00:00
|
|
|
|
|
|
|
|
|
if ts.Events != nil {
|
|
|
|
|
copy.Events = make([]*TaskEvent, len(ts.Events))
|
|
|
|
|
for i, e := range ts.Events {
|
|
|
|
|
copy.Events[i] = e.Copy()
|
|
|
|
|
}
|
2016-02-02 01:47:53 +00:00
|
|
|
|
}
|
|
|
|
|
return copy
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-04 22:20:32 +00:00
|
|
|
|
// Successful returns whether a task finished successfully. This doesn't really
|
|
|
|
|
// have meaning on a non-batch allocation because a service and system
|
|
|
|
|
// allocation should not finish.
|
2016-05-25 00:23:18 +00:00
|
|
|
|
func (ts *TaskState) Successful() bool {
|
2019-01-05 00:08:47 +00:00
|
|
|
|
return ts.State == TaskStateDead && !ts.Failed
|
2016-05-25 00:23:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-11-12 23:28:22 +00:00
|
|
|
|
const (
|
2016-10-10 21:49:37 +00:00
|
|
|
|
// TaskSetupFailure indicates that the task could not be started due to a
|
|
|
|
|
// a setup failure.
|
|
|
|
|
TaskSetupFailure = "Setup Failure"
|
|
|
|
|
|
2016-03-24 17:55:14 +00:00
|
|
|
|
// TaskDriveFailure indicates that the task could not be started due to a
|
2019-07-19 17:04:39 +00:00
|
|
|
|
// failure in the driver. TaskDriverFailure is considered Recoverable.
|
2015-11-14 22:13:32 +00:00
|
|
|
|
TaskDriverFailure = "Driver Failure"
|
2015-11-12 23:28:22 +00:00
|
|
|
|
|
2016-03-24 17:55:14 +00:00
|
|
|
|
// TaskReceived signals that the task has been pulled by the client at the
|
2016-02-19 22:49:43 +00:00
|
|
|
|
// given timestamp.
|
|
|
|
|
TaskReceived = "Received"
|
|
|
|
|
|
2019-07-19 17:04:39 +00:00
|
|
|
|
// TaskFailedValidation indicates the task was invalid and as such was not run.
|
|
|
|
|
// TaskFailedValidation is not considered Recoverable.
|
2016-03-24 17:55:14 +00:00
|
|
|
|
TaskFailedValidation = "Failed Validation"
|
|
|
|
|
|
|
|
|
|
// TaskStarted signals that the task was started and its timestamp can be
|
2015-11-12 23:28:22 +00:00
|
|
|
|
// used to determine the running length of the task.
|
2015-11-14 22:13:32 +00:00
|
|
|
|
TaskStarted = "Started"
|
2015-11-12 23:28:22 +00:00
|
|
|
|
|
2016-03-24 17:55:14 +00:00
|
|
|
|
// TaskTerminated indicates that the task was started and exited.
|
2015-11-14 22:13:32 +00:00
|
|
|
|
TaskTerminated = "Terminated"
|
2015-11-12 23:28:22 +00:00
|
|
|
|
|
2016-07-21 22:49:54 +00:00
|
|
|
|
// TaskKilling indicates a kill signal has been sent to the task.
|
|
|
|
|
TaskKilling = "Killing"
|
|
|
|
|
|
2016-03-24 17:55:14 +00:00
|
|
|
|
// TaskKilled indicates a user has killed the task.
|
2015-11-14 22:13:32 +00:00
|
|
|
|
TaskKilled = "Killed"
|
2016-02-29 00:56:05 +00:00
|
|
|
|
|
2016-10-05 22:11:09 +00:00
|
|
|
|
// TaskRestarting indicates that task terminated and is being restarted.
|
2016-02-29 00:56:05 +00:00
|
|
|
|
TaskRestarting = "Restarting"
|
|
|
|
|
|
|
|
|
|
// TaskNotRestarting indicates that the task has failed and is not being
|
|
|
|
|
// restarted because it has exceeded its restart policy.
|
2016-03-24 22:43:55 +00:00
|
|
|
|
TaskNotRestarting = "Not Restarting"
|
2016-03-15 17:53:20 +00:00
|
|
|
|
|
2016-10-05 22:11:09 +00:00
|
|
|
|
// TaskRestartSignal indicates that the task has been signalled to be
|
|
|
|
|
// restarted
|
|
|
|
|
TaskRestartSignal = "Restart Signaled"
|
|
|
|
|
|
|
|
|
|
// TaskSignaling indicates that the task is being signalled.
|
|
|
|
|
TaskSignaling = "Signaling"
|
|
|
|
|
|
2016-03-24 17:55:14 +00:00
|
|
|
|
// TaskDownloadingArtifacts means the task is downloading the artifacts
|
2016-03-15 17:53:20 +00:00
|
|
|
|
// specified in the task.
|
|
|
|
|
TaskDownloadingArtifacts = "Downloading Artifacts"
|
|
|
|
|
|
|
|
|
|
// TaskArtifactDownloadFailed indicates that downloading the artifacts
|
|
|
|
|
// failed.
|
|
|
|
|
TaskArtifactDownloadFailed = "Failed Artifact Download"
|
2016-08-11 07:20:53 +00:00
|
|
|
|
|
2017-02-27 19:40:24 +00:00
|
|
|
|
// TaskBuildingTaskDir indicates that the task directory/chroot is being
|
2017-02-10 01:40:13 +00:00
|
|
|
|
// built.
|
|
|
|
|
TaskBuildingTaskDir = "Building Task Directory"
|
|
|
|
|
|
|
|
|
|
// TaskSetup indicates the task runner is setting up the task environment
|
|
|
|
|
TaskSetup = "Task Setup"
|
|
|
|
|
|
2016-08-11 07:20:53 +00:00
|
|
|
|
// TaskDiskExceeded indicates that one of the tasks in a taskgroup has
|
|
|
|
|
// exceeded the requested disk resources.
|
|
|
|
|
TaskDiskExceeded = "Disk Resources Exceeded"
|
|
|
|
|
|
|
|
|
|
// TaskSiblingFailed indicates that a sibling task in the task group has
|
|
|
|
|
// failed.
|
2017-02-11 01:55:19 +00:00
|
|
|
|
TaskSiblingFailed = "Sibling Task Failed"
|
2016-11-30 00:39:36 +00:00
|
|
|
|
|
2016-12-20 19:51:09 +00:00
|
|
|
|
// TaskDriverMessage is an informational event message emitted by
|
|
|
|
|
// drivers such as when they're performing a long running action like
|
|
|
|
|
// downloading an image.
|
|
|
|
|
TaskDriverMessage = "Driver"
|
2017-02-11 01:55:19 +00:00
|
|
|
|
|
|
|
|
|
// TaskLeaderDead indicates that the leader task within the has finished.
|
|
|
|
|
TaskLeaderDead = "Leader Task Dead"
|
2018-12-13 17:20:18 +00:00
|
|
|
|
|
|
|
|
|
// TaskHookFailed indicates that one of the hooks for a task failed.
|
|
|
|
|
TaskHookFailed = "Task hook failed"
|
2019-05-08 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
// TaskRestoreFailed indicates Nomad was unable to reattach to a
|
|
|
|
|
// restored task.
|
|
|
|
|
TaskRestoreFailed = "Failed Restoring Task"
|
2015-11-12 23:28:22 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// TaskEvent is an event that effects the state of a task and contains meta-data
|
|
|
|
|
// appropriate to the events type.
|
|
|
|
|
type TaskEvent struct {
|
2015-11-14 22:13:32 +00:00
|
|
|
|
Type string
|
2015-11-12 23:28:22 +00:00
|
|
|
|
Time int64 // Unix Nanosecond timestamp
|
|
|
|
|
|
2017-11-03 14:34:30 +00:00
|
|
|
|
Message string // A possible message explaining the termination of the task.
|
|
|
|
|
|
|
|
|
|
// DisplayMessage is a human friendly message about the event
|
|
|
|
|
DisplayMessage string
|
|
|
|
|
|
|
|
|
|
// Details is a map with annotated info about the event
|
|
|
|
|
Details map[string]string
|
|
|
|
|
|
|
|
|
|
// DEPRECATION NOTICE: The following fields are deprecated and will be removed
|
|
|
|
|
// in a future release. Field values are available in the Details map.
|
|
|
|
|
|
|
|
|
|
// FailsTask marks whether this event fails the task.
|
|
|
|
|
// Deprecated, use Details["fails_task"] to access this.
|
2016-10-21 00:27:16 +00:00
|
|
|
|
FailsTask bool
|
|
|
|
|
|
2016-03-24 22:43:55 +00:00
|
|
|
|
// Restart fields.
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["restart_reason"] to access this.
|
2016-03-24 22:43:55 +00:00
|
|
|
|
RestartReason string
|
|
|
|
|
|
2016-10-10 21:49:37 +00:00
|
|
|
|
// Setup Failure fields.
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["setup_error"] to access this.
|
2016-10-10 21:49:37 +00:00
|
|
|
|
SetupError string
|
|
|
|
|
|
2015-11-12 23:28:22 +00:00
|
|
|
|
// Driver Failure fields.
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["driver_error"] to access this.
|
2016-05-15 16:41:34 +00:00
|
|
|
|
DriverError string // A driver error occurred while starting the task.
|
2015-11-12 23:28:22 +00:00
|
|
|
|
|
|
|
|
|
// Task Terminated Fields.
|
2017-11-03 14:34:30 +00:00
|
|
|
|
|
|
|
|
|
// Deprecated, use Details["exit_code"] to access this.
|
|
|
|
|
ExitCode int // The exit code of the task.
|
|
|
|
|
|
|
|
|
|
// Deprecated, use Details["signal"] to access this.
|
|
|
|
|
Signal int // The signal that terminated the task.
|
2015-11-14 06:07:13 +00:00
|
|
|
|
|
2016-07-21 22:49:54 +00:00
|
|
|
|
// Killing fields
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["kill_timeout"] to access this.
|
2016-07-21 22:49:54 +00:00
|
|
|
|
KillTimeout time.Duration
|
|
|
|
|
|
2015-11-14 06:07:13 +00:00
|
|
|
|
// Task Killed Fields.
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["kill_error"] to access this.
|
2015-11-14 06:07:13 +00:00
|
|
|
|
KillError string // Error killing the task.
|
2016-02-29 00:56:05 +00:00
|
|
|
|
|
2016-10-05 20:41:29 +00:00
|
|
|
|
// KillReason is the reason the task was killed
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["kill_reason"] to access this.
|
2016-10-05 20:41:29 +00:00
|
|
|
|
KillReason string
|
|
|
|
|
|
2016-02-29 00:56:05 +00:00
|
|
|
|
// TaskRestarting fields.
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["start_delay"] to access this.
|
2016-02-29 00:56:05 +00:00
|
|
|
|
StartDelay int64 // The sleep period before restarting the task in unix nanoseconds.
|
2016-03-15 17:53:20 +00:00
|
|
|
|
|
|
|
|
|
// Artifact Download fields
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["download_error"] to access this.
|
2016-03-15 17:53:20 +00:00
|
|
|
|
DownloadError string // Error downloading artifacts
|
2016-03-24 17:55:14 +00:00
|
|
|
|
|
|
|
|
|
// Validation fields
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["validation_error"] to access this.
|
2016-03-24 17:55:14 +00:00
|
|
|
|
ValidationError string // Validation error
|
2016-08-11 07:20:53 +00:00
|
|
|
|
|
|
|
|
|
// The maximum allowed task disk size.
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["disk_limit"] to access this.
|
2016-08-11 07:20:53 +00:00
|
|
|
|
DiskLimit int64
|
|
|
|
|
|
|
|
|
|
// Name of the sibling task that caused termination of the task that
|
|
|
|
|
// the TaskEvent refers to.
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["failed_sibling"] to access this.
|
2016-08-11 07:20:53 +00:00
|
|
|
|
FailedSibling string
|
2016-09-14 20:30:01 +00:00
|
|
|
|
|
2016-09-15 01:27:13 +00:00
|
|
|
|
// VaultError is the error from token renewal
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["vault_renewal_error"] to access this.
|
2016-09-15 01:27:13 +00:00
|
|
|
|
VaultError string
|
2016-10-05 20:41:29 +00:00
|
|
|
|
|
|
|
|
|
// TaskSignalReason indicates the reason the task is being signalled.
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["task_signal_reason"] to access this.
|
2016-10-05 20:41:29 +00:00
|
|
|
|
TaskSignalReason string
|
|
|
|
|
|
|
|
|
|
// TaskSignal is the signal that was sent to the task
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["task_signal"] to access this.
|
2016-10-05 20:41:29 +00:00
|
|
|
|
TaskSignal string
|
2016-11-30 00:39:36 +00:00
|
|
|
|
|
2016-12-20 19:51:09 +00:00
|
|
|
|
// DriverMessage indicates a driver action being taken.
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, use Details["driver_message"] to access this.
|
2016-12-20 19:51:09 +00:00
|
|
|
|
DriverMessage string
|
2017-08-08 04:26:04 +00:00
|
|
|
|
|
|
|
|
|
// GenericSource is the source of a message.
|
2017-11-03 14:34:30 +00:00
|
|
|
|
// Deprecated, is redundant with event type.
|
2017-08-08 04:26:04 +00:00
|
|
|
|
GenericSource string
|
2017-10-31 15:35:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (event *TaskEvent) PopulateEventDisplayMessage() {
|
|
|
|
|
// Build up the description based on the event type.
|
2017-11-03 14:34:30 +00:00
|
|
|
|
if event == nil { //TODO(preetha) needs investigation alloc_runner's Run method sends a nil event when sigterming nomad. Why?
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if event.DisplayMessage != "" {
|
2017-10-31 15:35:14 +00:00
|
|
|
|
return
|
|
|
|
|
}
|
2017-11-03 14:34:30 +00:00
|
|
|
|
|
2017-10-31 15:35:14 +00:00
|
|
|
|
var desc string
|
|
|
|
|
switch event.Type {
|
|
|
|
|
case TaskSetup:
|
|
|
|
|
desc = event.Message
|
|
|
|
|
case TaskStarted:
|
|
|
|
|
desc = "Task started by client"
|
|
|
|
|
case TaskReceived:
|
|
|
|
|
desc = "Task received by client"
|
|
|
|
|
case TaskFailedValidation:
|
|
|
|
|
if event.ValidationError != "" {
|
|
|
|
|
desc = event.ValidationError
|
|
|
|
|
} else {
|
|
|
|
|
desc = "Validation of task failed"
|
|
|
|
|
}
|
|
|
|
|
case TaskSetupFailure:
|
|
|
|
|
if event.SetupError != "" {
|
|
|
|
|
desc = event.SetupError
|
|
|
|
|
} else {
|
|
|
|
|
desc = "Task setup failed"
|
|
|
|
|
}
|
|
|
|
|
case TaskDriverFailure:
|
|
|
|
|
if event.DriverError != "" {
|
|
|
|
|
desc = event.DriverError
|
|
|
|
|
} else {
|
|
|
|
|
desc = "Failed to start task"
|
|
|
|
|
}
|
|
|
|
|
case TaskDownloadingArtifacts:
|
|
|
|
|
desc = "Client is downloading artifacts"
|
|
|
|
|
case TaskArtifactDownloadFailed:
|
|
|
|
|
if event.DownloadError != "" {
|
|
|
|
|
desc = event.DownloadError
|
|
|
|
|
} else {
|
|
|
|
|
desc = "Failed to download artifacts"
|
|
|
|
|
}
|
|
|
|
|
case TaskKilling:
|
|
|
|
|
if event.KillReason != "" {
|
2018-03-13 22:09:03 +00:00
|
|
|
|
desc = event.KillReason
|
2017-10-31 15:35:14 +00:00
|
|
|
|
} else if event.KillTimeout != 0 {
|
|
|
|
|
desc = fmt.Sprintf("Sent interrupt. Waiting %v before force killing", event.KillTimeout)
|
|
|
|
|
} else {
|
|
|
|
|
desc = "Sent interrupt"
|
|
|
|
|
}
|
|
|
|
|
case TaskKilled:
|
|
|
|
|
if event.KillError != "" {
|
|
|
|
|
desc = event.KillError
|
|
|
|
|
} else {
|
|
|
|
|
desc = "Task successfully killed"
|
|
|
|
|
}
|
|
|
|
|
case TaskTerminated:
|
|
|
|
|
var parts []string
|
|
|
|
|
parts = append(parts, fmt.Sprintf("Exit Code: %d", event.ExitCode))
|
|
|
|
|
|
|
|
|
|
if event.Signal != 0 {
|
|
|
|
|
parts = append(parts, fmt.Sprintf("Signal: %d", event.Signal))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if event.Message != "" {
|
|
|
|
|
parts = append(parts, fmt.Sprintf("Exit Message: %q", event.Message))
|
|
|
|
|
}
|
|
|
|
|
desc = strings.Join(parts, ", ")
|
|
|
|
|
case TaskRestarting:
|
|
|
|
|
in := fmt.Sprintf("Task restarting in %v", time.Duration(event.StartDelay))
|
|
|
|
|
if event.RestartReason != "" && event.RestartReason != ReasonWithinPolicy {
|
|
|
|
|
desc = fmt.Sprintf("%s - %s", event.RestartReason, in)
|
|
|
|
|
} else {
|
|
|
|
|
desc = in
|
|
|
|
|
}
|
|
|
|
|
case TaskNotRestarting:
|
|
|
|
|
if event.RestartReason != "" {
|
|
|
|
|
desc = event.RestartReason
|
|
|
|
|
} else {
|
|
|
|
|
desc = "Task exceeded restart policy"
|
|
|
|
|
}
|
|
|
|
|
case TaskSiblingFailed:
|
|
|
|
|
if event.FailedSibling != "" {
|
|
|
|
|
desc = fmt.Sprintf("Task's sibling %q failed", event.FailedSibling)
|
|
|
|
|
} else {
|
|
|
|
|
desc = "Task's sibling failed"
|
|
|
|
|
}
|
|
|
|
|
case TaskSignaling:
|
|
|
|
|
sig := event.TaskSignal
|
|
|
|
|
reason := event.TaskSignalReason
|
|
|
|
|
|
|
|
|
|
if sig == "" && reason == "" {
|
|
|
|
|
desc = "Task being sent a signal"
|
|
|
|
|
} else if sig == "" {
|
|
|
|
|
desc = reason
|
|
|
|
|
} else if reason == "" {
|
|
|
|
|
desc = fmt.Sprintf("Task being sent signal %v", sig)
|
|
|
|
|
} else {
|
|
|
|
|
desc = fmt.Sprintf("Task being sent signal %v: %v", sig, reason)
|
|
|
|
|
}
|
|
|
|
|
case TaskRestartSignal:
|
|
|
|
|
if event.RestartReason != "" {
|
|
|
|
|
desc = event.RestartReason
|
|
|
|
|
} else {
|
|
|
|
|
desc = "Task signaled to restart"
|
|
|
|
|
}
|
|
|
|
|
case TaskDriverMessage:
|
|
|
|
|
desc = event.DriverMessage
|
|
|
|
|
case TaskLeaderDead:
|
|
|
|
|
desc = "Leader Task in Group dead"
|
|
|
|
|
default:
|
2017-11-13 17:14:57 +00:00
|
|
|
|
desc = event.Message
|
2017-10-31 15:35:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
event.DisplayMessage = desc
|
2016-02-29 00:56:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (te *TaskEvent) GoString() string {
|
2017-09-13 06:15:46 +00:00
|
|
|
|
return fmt.Sprintf("%v - %v", te.Time, te.Type)
|
2015-11-14 06:07:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-16 21:37:27 +00:00
|
|
|
|
// SetDisplayMessage sets the display message of TaskEvent
|
|
|
|
|
func (te *TaskEvent) SetDisplayMessage(msg string) *TaskEvent {
|
|
|
|
|
te.DisplayMessage = msg
|
|
|
|
|
return te
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-10 01:40:13 +00:00
|
|
|
|
// SetMessage sets the message of TaskEvent
|
|
|
|
|
func (te *TaskEvent) SetMessage(msg string) *TaskEvent {
|
|
|
|
|
te.Message = msg
|
2017-10-31 15:35:14 +00:00
|
|
|
|
te.Details["message"] = msg
|
2017-02-10 01:40:13 +00:00
|
|
|
|
return te
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-02 01:47:53 +00:00
|
|
|
|
func (te *TaskEvent) Copy() *TaskEvent {
|
2016-02-11 01:54:43 +00:00
|
|
|
|
if te == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2016-02-02 01:47:53 +00:00
|
|
|
|
copy := new(TaskEvent)
|
|
|
|
|
*copy = *te
|
|
|
|
|
return copy
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-14 22:13:32 +00:00
|
|
|
|
func NewTaskEvent(event string) *TaskEvent {
|
2015-11-14 06:07:13 +00:00
|
|
|
|
return &TaskEvent{
|
2017-10-31 15:35:14 +00:00
|
|
|
|
Type: event,
|
|
|
|
|
Time: time.Now().UnixNano(),
|
|
|
|
|
Details: make(map[string]string),
|
2015-11-14 06:07:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-07 21:13:05 +00:00
|
|
|
|
// SetSetupError is used to store an error that occurred while setting up the
|
2016-10-18 18:23:27 +00:00
|
|
|
|
// task
|
2016-10-10 21:49:37 +00:00
|
|
|
|
func (e *TaskEvent) SetSetupError(err error) *TaskEvent {
|
|
|
|
|
if err != nil {
|
|
|
|
|
e.SetupError = err.Error()
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["setup_error"] = err.Error()
|
2016-10-10 21:49:37 +00:00
|
|
|
|
}
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-21 00:27:16 +00:00
|
|
|
|
func (e *TaskEvent) SetFailsTask() *TaskEvent {
|
|
|
|
|
e.FailsTask = true
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["fails_task"] = "true"
|
2016-10-21 00:27:16 +00:00
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-14 06:07:13 +00:00
|
|
|
|
func (e *TaskEvent) SetDriverError(err error) *TaskEvent {
|
|
|
|
|
if err != nil {
|
|
|
|
|
e.DriverError = err.Error()
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["driver_error"] = err.Error()
|
2015-11-14 06:07:13 +00:00
|
|
|
|
}
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *TaskEvent) SetExitCode(c int) *TaskEvent {
|
|
|
|
|
e.ExitCode = c
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["exit_code"] = fmt.Sprintf("%d", c)
|
2015-11-14 06:07:13 +00:00
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *TaskEvent) SetSignal(s int) *TaskEvent {
|
|
|
|
|
e.Signal = s
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["signal"] = fmt.Sprintf("%d", s)
|
2015-11-14 06:07:13 +00:00
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-03 10:46:15 +00:00
|
|
|
|
func (e *TaskEvent) SetSignalText(s string) *TaskEvent {
|
|
|
|
|
e.Details["signal"] = s
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-16 22:46:18 +00:00
|
|
|
|
func (e *TaskEvent) SetExitMessage(err error) *TaskEvent {
|
|
|
|
|
if err != nil {
|
|
|
|
|
e.Message = err.Error()
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["exit_message"] = err.Error()
|
2015-11-16 22:46:18 +00:00
|
|
|
|
}
|
2015-11-14 06:07:13 +00:00
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *TaskEvent) SetKillError(err error) *TaskEvent {
|
|
|
|
|
if err != nil {
|
|
|
|
|
e.KillError = err.Error()
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["kill_error"] = err.Error()
|
2015-11-14 06:07:13 +00:00
|
|
|
|
}
|
|
|
|
|
return e
|
2015-11-12 23:28:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 20:41:29 +00:00
|
|
|
|
func (e *TaskEvent) SetKillReason(r string) *TaskEvent {
|
|
|
|
|
e.KillReason = r
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["kill_reason"] = r
|
2016-10-05 20:41:29 +00:00
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-29 00:56:05 +00:00
|
|
|
|
func (e *TaskEvent) SetRestartDelay(delay time.Duration) *TaskEvent {
|
|
|
|
|
e.StartDelay = int64(delay)
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["start_delay"] = fmt.Sprintf("%d", delay)
|
2016-02-29 00:56:05 +00:00
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-24 22:43:55 +00:00
|
|
|
|
func (e *TaskEvent) SetRestartReason(reason string) *TaskEvent {
|
|
|
|
|
e.RestartReason = reason
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["restart_reason"] = reason
|
2016-03-24 22:43:55 +00:00
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 20:41:29 +00:00
|
|
|
|
func (e *TaskEvent) SetTaskSignalReason(r string) *TaskEvent {
|
|
|
|
|
e.TaskSignalReason = r
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["task_signal_reason"] = r
|
2016-10-05 20:41:29 +00:00
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *TaskEvent) SetTaskSignal(s os.Signal) *TaskEvent {
|
|
|
|
|
e.TaskSignal = s.String()
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["task_signal"] = s.String()
|
2016-10-05 20:41:29 +00:00
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-15 17:53:20 +00:00
|
|
|
|
func (e *TaskEvent) SetDownloadError(err error) *TaskEvent {
|
|
|
|
|
if err != nil {
|
|
|
|
|
e.DownloadError = err.Error()
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["download_error"] = err.Error()
|
2016-03-15 17:53:20 +00:00
|
|
|
|
}
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-24 17:55:14 +00:00
|
|
|
|
func (e *TaskEvent) SetValidationError(err error) *TaskEvent {
|
|
|
|
|
if err != nil {
|
|
|
|
|
e.ValidationError = err.Error()
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["validation_error"] = err.Error()
|
2016-03-24 17:55:14 +00:00
|
|
|
|
}
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-21 22:49:54 +00:00
|
|
|
|
func (e *TaskEvent) SetKillTimeout(timeout time.Duration) *TaskEvent {
|
|
|
|
|
e.KillTimeout = timeout
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["kill_timeout"] = timeout.String()
|
2016-07-21 22:49:54 +00:00
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-11 07:20:53 +00:00
|
|
|
|
func (e *TaskEvent) SetDiskLimit(limit int64) *TaskEvent {
|
|
|
|
|
e.DiskLimit = limit
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["disk_limit"] = fmt.Sprintf("%d", limit)
|
2016-08-11 07:20:53 +00:00
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *TaskEvent) SetFailedSibling(sibling string) *TaskEvent {
|
|
|
|
|
e.FailedSibling = sibling
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["failed_sibling"] = sibling
|
2016-08-11 07:20:53 +00:00
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-14 20:30:01 +00:00
|
|
|
|
func (e *TaskEvent) SetVaultRenewalError(err error) *TaskEvent {
|
|
|
|
|
if err != nil {
|
2016-09-15 01:27:13 +00:00
|
|
|
|
e.VaultError = err.Error()
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["vault_renewal_error"] = err.Error()
|
2016-09-14 20:30:01 +00:00
|
|
|
|
}
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-20 19:51:09 +00:00
|
|
|
|
func (e *TaskEvent) SetDriverMessage(m string) *TaskEvent {
|
|
|
|
|
e.DriverMessage = m
|
2017-10-31 15:35:14 +00:00
|
|
|
|
e.Details["driver_message"] = m
|
2016-11-30 00:39:36 +00:00
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-16 19:17:23 +00:00
|
|
|
|
func (e *TaskEvent) SetOOMKilled(oom bool) *TaskEvent {
|
|
|
|
|
e.Details["oom_killed"] = strconv.FormatBool(oom)
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-14 22:46:06 +00:00
|
|
|
|
// TaskArtifact is an artifact to download before running the task.
|
|
|
|
|
type TaskArtifact struct {
|
|
|
|
|
// GetterSource is the source to download an artifact using go-getter
|
2017-02-22 20:30:05 +00:00
|
|
|
|
GetterSource string
|
2016-02-02 20:00:26 +00:00
|
|
|
|
|
2016-03-14 22:46:06 +00:00
|
|
|
|
// GetterOptions are options to use when downloading the artifact using
|
|
|
|
|
// go-getter.
|
2017-02-22 20:30:05 +00:00
|
|
|
|
GetterOptions map[string]string
|
2016-03-18 19:01:46 +00:00
|
|
|
|
|
2017-07-06 03:44:49 +00:00
|
|
|
|
// GetterMode is the go-getter.ClientMode for fetching resources.
|
|
|
|
|
// Defaults to "any" but can be set to "file" or "dir".
|
|
|
|
|
GetterMode string
|
|
|
|
|
|
2016-03-18 19:01:46 +00:00
|
|
|
|
// RelativeDest is the download destination given relative to the task's
|
|
|
|
|
// directory.
|
2017-02-22 20:30:05 +00:00
|
|
|
|
RelativeDest string
|
2016-03-14 22:46:06 +00:00
|
|
|
|
}
|
2016-02-02 20:00:26 +00:00
|
|
|
|
|
2016-03-14 22:46:06 +00:00
|
|
|
|
func (ta *TaskArtifact) Copy() *TaskArtifact {
|
|
|
|
|
if ta == nil {
|
|
|
|
|
return nil
|
2016-02-05 07:28:01 +00:00
|
|
|
|
}
|
2016-03-14 22:46:06 +00:00
|
|
|
|
nta := new(TaskArtifact)
|
|
|
|
|
*nta = *ta
|
2017-01-18 23:55:14 +00:00
|
|
|
|
nta.GetterOptions = helper.CopyMapStringString(ta.GetterOptions)
|
2016-03-14 22:46:06 +00:00
|
|
|
|
return nta
|
|
|
|
|
}
|
2016-02-05 07:28:01 +00:00
|
|
|
|
|
2016-03-18 19:01:46 +00:00
|
|
|
|
func (ta *TaskArtifact) GoString() string {
|
|
|
|
|
return fmt.Sprintf("%+v", ta)
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-20 16:41:51 +00:00
|
|
|
|
// Hash creates a unique identifier for a TaskArtifact as the same GetterSource
|
|
|
|
|
// may be specified multiple times with different destinations.
|
|
|
|
|
func (ta *TaskArtifact) Hash() string {
|
|
|
|
|
hash, err := blake2b.New256(nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hash.Write([]byte(ta.GetterSource))
|
|
|
|
|
|
|
|
|
|
// Must iterate over keys in a consistent order
|
|
|
|
|
keys := make([]string, 0, len(ta.GetterOptions))
|
|
|
|
|
for k := range ta.GetterOptions {
|
|
|
|
|
keys = append(keys, k)
|
|
|
|
|
}
|
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
for _, k := range keys {
|
|
|
|
|
hash.Write([]byte(k))
|
|
|
|
|
hash.Write([]byte(ta.GetterOptions[k]))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hash.Write([]byte(ta.GetterMode))
|
|
|
|
|
hash.Write([]byte(ta.RelativeDest))
|
|
|
|
|
return base64.RawStdEncoding.EncodeToString(hash.Sum(nil))
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-03 21:58:44 +00:00
|
|
|
|
// PathEscapesAllocDir returns if the given path escapes the allocation
|
2016-12-18 23:48:30 +00:00
|
|
|
|
// directory. The prefix allows adding a prefix if the path will be joined, for
|
|
|
|
|
// example a "task/local" prefix may be provided if the path will be joined
|
|
|
|
|
// against that prefix.
|
|
|
|
|
func PathEscapesAllocDir(prefix, path string) (bool, error) {
|
2016-03-18 19:01:46 +00:00
|
|
|
|
// Verify the destination doesn't escape the tasks directory
|
2016-12-18 23:48:30 +00:00
|
|
|
|
alloc, err := filepath.Abs(filepath.Join("/", "alloc-dir/", "alloc-id/"))
|
2016-03-23 03:54:36 +00:00
|
|
|
|
if err != nil {
|
2016-09-23 22:39:52 +00:00
|
|
|
|
return false, err
|
2016-03-23 03:54:36 +00:00
|
|
|
|
}
|
2016-12-18 23:48:30 +00:00
|
|
|
|
abs, err := filepath.Abs(filepath.Join(alloc, prefix, path))
|
2016-03-15 02:55:30 +00:00
|
|
|
|
if err != nil {
|
2016-09-23 22:39:52 +00:00
|
|
|
|
return false, err
|
2016-03-18 19:01:46 +00:00
|
|
|
|
}
|
|
|
|
|
rel, err := filepath.Rel(alloc, abs)
|
|
|
|
|
if err != nil {
|
2016-09-23 22:39:52 +00:00
|
|
|
|
return false, err
|
2016-03-18 19:01:46 +00:00
|
|
|
|
}
|
2016-09-23 22:39:52 +00:00
|
|
|
|
|
|
|
|
|
return strings.HasPrefix(rel, ".."), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ta *TaskArtifact) Validate() error {
|
|
|
|
|
// Verify the source
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
if ta.GetterSource == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("source must be specified"))
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-06 03:44:49 +00:00
|
|
|
|
switch ta.GetterMode {
|
|
|
|
|
case "":
|
|
|
|
|
// Default to any
|
|
|
|
|
ta.GetterMode = GetterModeAny
|
|
|
|
|
case GetterModeAny, GetterModeFile, GetterModeDir:
|
|
|
|
|
// Ok
|
|
|
|
|
default:
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid artifact mode %q; must be one of: %s, %s, %s",
|
|
|
|
|
ta.GetterMode, GetterModeAny, GetterModeFile, GetterModeDir))
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-18 23:48:30 +00:00
|
|
|
|
escaped, err := PathEscapesAllocDir("task", ta.RelativeDest)
|
2016-09-23 22:39:52 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid destination path: %v", err))
|
|
|
|
|
} else if escaped {
|
2016-12-19 00:32:14 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("destination escapes allocation directory"))
|
2016-03-15 02:55:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-30 17:24:30 +00:00
|
|
|
|
if err := ta.validateChecksum(); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
2015-11-17 21:36:59 +00:00
|
|
|
|
|
2018-10-30 17:24:30 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
2016-02-11 20:30:47 +00:00
|
|
|
|
|
2018-10-30 17:24:30 +00:00
|
|
|
|
func (ta *TaskArtifact) validateChecksum() error {
|
|
|
|
|
check, ok := ta.GetterOptions["checksum"]
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2016-03-14 22:46:06 +00:00
|
|
|
|
|
2018-10-30 17:58:52 +00:00
|
|
|
|
// Job struct validation occurs before interpolation resolution can be effective.
|
|
|
|
|
// Skip checking if checksum contain variable reference, and artifacts fetching will
|
|
|
|
|
// eventually fail, if checksum is indeed invalid.
|
2018-10-30 17:24:30 +00:00
|
|
|
|
if args.ContainsEnv(check) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2016-03-14 22:46:06 +00:00
|
|
|
|
|
2018-10-30 17:24:30 +00:00
|
|
|
|
check = strings.TrimSpace(check)
|
|
|
|
|
if check == "" {
|
|
|
|
|
return fmt.Errorf("checksum value cannot be empty")
|
2016-02-11 20:30:47 +00:00
|
|
|
|
}
|
2016-03-14 22:46:06 +00:00
|
|
|
|
|
2018-10-30 17:24:30 +00:00
|
|
|
|
parts := strings.Split(check, ":")
|
|
|
|
|
if l := len(parts); l != 2 {
|
|
|
|
|
return fmt.Errorf(`checksum must be given as "type:value"; got %q`, check)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
checksumVal := parts[1]
|
|
|
|
|
checksumBytes, err := hex.DecodeString(checksumVal)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("invalid checksum: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
checksumType := parts[0]
|
|
|
|
|
expectedLength := 0
|
|
|
|
|
switch checksumType {
|
|
|
|
|
case "md5":
|
|
|
|
|
expectedLength = md5.Size
|
|
|
|
|
case "sha1":
|
|
|
|
|
expectedLength = sha1.Size
|
|
|
|
|
case "sha256":
|
|
|
|
|
expectedLength = sha256.Size
|
|
|
|
|
case "sha512":
|
|
|
|
|
expectedLength = sha512.Size
|
|
|
|
|
default:
|
|
|
|
|
return fmt.Errorf("unsupported checksum type: %s", checksumType)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(checksumBytes) != expectedLength {
|
|
|
|
|
return fmt.Errorf("invalid %s checksum: %v", checksumType, checksumVal)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
2015-09-15 17:46:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-26 20:47:56 +00:00
|
|
|
|
const (
|
2018-11-13 23:57:59 +00:00
|
|
|
|
ConstraintDistinctProperty = "distinct_property"
|
|
|
|
|
ConstraintDistinctHosts = "distinct_hosts"
|
|
|
|
|
ConstraintRegex = "regexp"
|
|
|
|
|
ConstraintVersion = "version"
|
|
|
|
|
ConstraintSetContains = "set_contains"
|
|
|
|
|
ConstraintSetContainsAll = "set_contains_all"
|
|
|
|
|
ConstraintSetContainsAny = "set_contains_any"
|
|
|
|
|
ConstraintAttributeIsSet = "is_set"
|
|
|
|
|
ConstraintAttributeIsNotSet = "is_not_set"
|
2015-10-26 20:47:56 +00:00
|
|
|
|
)
|
|
|
|
|
|
2015-10-27 21:31:14 +00:00
|
|
|
|
// Constraints are used to restrict placement options.
|
2015-07-03 23:57:48 +00:00
|
|
|
|
type Constraint struct {
|
|
|
|
|
LTarget string // Left-hand target
|
|
|
|
|
RTarget string // Right-hand target
|
|
|
|
|
Operand string // Constraint operand (<=, <, =, !=, >, >=), contains, near
|
2016-01-26 21:45:03 +00:00
|
|
|
|
str string // Memoized string
|
2015-06-07 18:18:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-01 21:23:40 +00:00
|
|
|
|
// Equal checks if two constraints are equal
|
2019-04-11 17:17:26 +00:00
|
|
|
|
func (c *Constraint) Equals(o *Constraint) bool {
|
|
|
|
|
return c == o ||
|
|
|
|
|
c.LTarget == o.LTarget &&
|
|
|
|
|
c.RTarget == o.RTarget &&
|
|
|
|
|
c.Operand == o.Operand
|
2016-09-01 21:23:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-11 14:25:19 +00:00
|
|
|
|
func (c *Constraint) Equal(o *Constraint) bool {
|
2019-04-19 14:12:44 +00:00
|
|
|
|
return c.Equals(o)
|
2016-09-01 21:23:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-11 01:54:43 +00:00
|
|
|
|
func (c *Constraint) Copy() *Constraint {
|
|
|
|
|
if c == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
nc := new(Constraint)
|
|
|
|
|
*nc = *c
|
|
|
|
|
return nc
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-14 04:46:33 +00:00
|
|
|
|
func (c *Constraint) String() string {
|
2016-01-26 21:45:03 +00:00
|
|
|
|
if c.str != "" {
|
|
|
|
|
return c.str
|
|
|
|
|
}
|
|
|
|
|
c.str = fmt.Sprintf("%s %s %s", c.LTarget, c.Operand, c.RTarget)
|
|
|
|
|
return c.str
|
2015-08-14 04:46:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-11 19:50:16 +00:00
|
|
|
|
func (c *Constraint) Validate() error {
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
if c.Operand == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing constraint operand"))
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-31 23:44:17 +00:00
|
|
|
|
// requireLtarget specifies whether the constraint requires an LTarget to be
|
|
|
|
|
// provided.
|
|
|
|
|
requireLtarget := true
|
|
|
|
|
|
2015-10-11 19:50:16 +00:00
|
|
|
|
// Perform additional validation based on operand
|
|
|
|
|
switch c.Operand {
|
2017-07-31 23:44:17 +00:00
|
|
|
|
case ConstraintDistinctHosts:
|
|
|
|
|
requireLtarget = false
|
2018-10-15 22:31:13 +00:00
|
|
|
|
case ConstraintSetContainsAll, ConstraintSetContainsAny, ConstraintSetContains:
|
2017-07-31 23:44:17 +00:00
|
|
|
|
if c.RTarget == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Set contains constraint requires an RTarget"))
|
|
|
|
|
}
|
2015-10-26 20:47:56 +00:00
|
|
|
|
case ConstraintRegex:
|
2015-10-11 19:50:16 +00:00
|
|
|
|
if _, err := regexp.Compile(c.RTarget); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Regular expression failed to compile: %v", err))
|
|
|
|
|
}
|
2015-10-26 20:47:56 +00:00
|
|
|
|
case ConstraintVersion:
|
2015-10-11 19:50:16 +00:00
|
|
|
|
if _, err := version.NewConstraint(c.RTarget); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Version constraint is invalid: %v", err))
|
|
|
|
|
}
|
2017-07-31 23:44:17 +00:00
|
|
|
|
case ConstraintDistinctProperty:
|
|
|
|
|
// If a count is set, make sure it is convertible to a uint64
|
|
|
|
|
if c.RTarget != "" {
|
|
|
|
|
count, err := strconv.ParseUint(c.RTarget, 10, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Failed to convert RTarget %q to uint64: %v", c.RTarget, err))
|
|
|
|
|
} else if count < 1 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Distinct Property must have an allowed count of 1 or greater: %d < 1", count))
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-11-13 23:57:59 +00:00
|
|
|
|
case ConstraintAttributeIsSet, ConstraintAttributeIsNotSet:
|
|
|
|
|
if c.RTarget != "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Operator %q does not support an RTarget", c.Operand))
|
|
|
|
|
}
|
2017-07-31 23:44:17 +00:00
|
|
|
|
case "=", "==", "is", "!=", "not", "<", "<=", ">", ">=":
|
|
|
|
|
if c.RTarget == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Operator %q requires an RTarget", c.Operand))
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Unknown constraint type %q", c.Operand))
|
2015-10-11 19:50:16 +00:00
|
|
|
|
}
|
2017-07-31 23:44:17 +00:00
|
|
|
|
|
|
|
|
|
// Ensure we have an LTarget for the constraints that need one
|
|
|
|
|
if requireLtarget && c.LTarget == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("No LTarget provided but is required by constraint"))
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-11 19:50:16 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-11 17:17:26 +00:00
|
|
|
|
type Constraints []*Constraint
|
|
|
|
|
|
|
|
|
|
// Equals compares Constraints as a set
|
|
|
|
|
func (xs *Constraints) Equals(ys *Constraints) bool {
|
|
|
|
|
if xs == ys {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if xs == nil || ys == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if len(*xs) != len(*ys) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2019-04-18 17:36:45 +00:00
|
|
|
|
SETEQUALS:
|
2019-04-11 17:17:26 +00:00
|
|
|
|
for _, x := range *xs {
|
|
|
|
|
for _, y := range *ys {
|
2019-04-18 16:28:34 +00:00
|
|
|
|
if x.Equals(y) {
|
2019-04-18 17:36:45 +00:00
|
|
|
|
continue SETEQUALS
|
2019-04-11 17:17:26 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-18 17:36:45 +00:00
|
|
|
|
return false
|
2019-04-11 17:17:26 +00:00
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-16 13:30:58 +00:00
|
|
|
|
// Affinity is used to score placement options based on a weight
|
|
|
|
|
type Affinity struct {
|
2019-01-30 20:20:38 +00:00
|
|
|
|
LTarget string // Left-hand target
|
|
|
|
|
RTarget string // Right-hand target
|
|
|
|
|
Operand string // Affinity operand (<=, <, =, !=, >, >=), set_contains_all, set_contains_any
|
|
|
|
|
Weight int8 // Weight applied to nodes that match the affinity. Can be negative
|
|
|
|
|
str string // Memoized string
|
2018-07-16 13:30:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Equal checks if two affinities are equal
|
2019-04-11 17:17:26 +00:00
|
|
|
|
func (a *Affinity) Equals(o *Affinity) bool {
|
|
|
|
|
return a == o ||
|
|
|
|
|
a.LTarget == o.LTarget &&
|
|
|
|
|
a.RTarget == o.RTarget &&
|
|
|
|
|
a.Operand == o.Operand &&
|
|
|
|
|
a.Weight == o.Weight
|
2018-07-16 13:30:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-11 14:25:19 +00:00
|
|
|
|
func (a *Affinity) Equal(o *Affinity) bool {
|
2019-04-19 14:12:44 +00:00
|
|
|
|
return a.Equals(o)
|
2018-07-16 13:30:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *Affinity) Copy() *Affinity {
|
|
|
|
|
if a == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
na := new(Affinity)
|
|
|
|
|
*na = *a
|
|
|
|
|
return na
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *Affinity) String() string {
|
|
|
|
|
if a.str != "" {
|
|
|
|
|
return a.str
|
|
|
|
|
}
|
|
|
|
|
a.str = fmt.Sprintf("%s %s %s %v", a.LTarget, a.Operand, a.RTarget, a.Weight)
|
|
|
|
|
return a.str
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *Affinity) Validate() error {
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
if a.Operand == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing affinity operand"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Perform additional validation based on operand
|
|
|
|
|
switch a.Operand {
|
2018-10-15 22:31:13 +00:00
|
|
|
|
case ConstraintSetContainsAll, ConstraintSetContainsAny, ConstraintSetContains:
|
2018-07-16 13:30:58 +00:00
|
|
|
|
if a.RTarget == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Set contains operators require an RTarget"))
|
|
|
|
|
}
|
|
|
|
|
case ConstraintRegex:
|
|
|
|
|
if _, err := regexp.Compile(a.RTarget); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Regular expression failed to compile: %v", err))
|
|
|
|
|
}
|
|
|
|
|
case ConstraintVersion:
|
|
|
|
|
if _, err := version.NewConstraint(a.RTarget); err != nil {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Version affinity is invalid: %v", err))
|
|
|
|
|
}
|
|
|
|
|
case "=", "==", "is", "!=", "not", "<", "<=", ">", ">=":
|
|
|
|
|
if a.RTarget == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Operator %q requires an RTarget", a.Operand))
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Unknown affinity operator %q", a.Operand))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure we have an LTarget
|
|
|
|
|
if a.LTarget == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("No LTarget provided but is required"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure that weight is between -100 and 100, and not zero
|
|
|
|
|
if a.Weight == 0 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Affinity weight cannot be zero"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if a.Weight > 100 || a.Weight < -100 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Affinity weight must be within the range [-100,100]"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-26 00:08:25 +00:00
|
|
|
|
// Spread is used to specify desired distribution of allocations according to weight
|
2018-07-16 17:52:24 +00:00
|
|
|
|
type Spread struct {
|
2018-07-26 00:08:25 +00:00
|
|
|
|
// Attribute is the node attribute used as the spread criteria
|
|
|
|
|
Attribute string
|
2018-07-29 00:49:09 +00:00
|
|
|
|
|
2018-07-26 00:08:25 +00:00
|
|
|
|
// Weight is the relative weight of this spread, useful when there are multiple
|
|
|
|
|
// spread and affinities
|
2019-01-30 20:20:38 +00:00
|
|
|
|
Weight int8
|
2018-07-29 00:49:09 +00:00
|
|
|
|
|
2018-07-26 00:08:25 +00:00
|
|
|
|
// SpreadTarget is used to describe desired percentages for each attribute value
|
2018-07-16 17:52:24 +00:00
|
|
|
|
SpreadTarget []*SpreadTarget
|
2018-07-29 00:49:09 +00:00
|
|
|
|
|
|
|
|
|
// Memoized string representation
|
|
|
|
|
str string
|
2018-07-16 17:52:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-11 17:17:26 +00:00
|
|
|
|
type Affinities []*Affinity
|
|
|
|
|
|
|
|
|
|
// Equals compares Affinities as a set
|
|
|
|
|
func (xs *Affinities) Equals(ys *Affinities) bool {
|
|
|
|
|
if xs == ys {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if xs == nil || ys == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if len(*xs) != len(*ys) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2019-04-18 17:36:45 +00:00
|
|
|
|
SETEQUALS:
|
2019-04-11 17:17:26 +00:00
|
|
|
|
for _, x := range *xs {
|
|
|
|
|
for _, y := range *ys {
|
2019-04-18 16:28:34 +00:00
|
|
|
|
if x.Equals(y) {
|
2019-04-18 17:36:45 +00:00
|
|
|
|
continue SETEQUALS
|
2019-04-11 17:17:26 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-18 17:36:45 +00:00
|
|
|
|
return false
|
2019-04-11 17:17:26 +00:00
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-16 17:52:24 +00:00
|
|
|
|
func (s *Spread) Copy() *Spread {
|
|
|
|
|
if s == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
ns := new(Spread)
|
|
|
|
|
*ns = *s
|
|
|
|
|
|
|
|
|
|
ns.SpreadTarget = CopySliceSpreadTarget(s.SpreadTarget)
|
|
|
|
|
return ns
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Spread) String() string {
|
|
|
|
|
if s.str != "" {
|
|
|
|
|
return s.str
|
|
|
|
|
}
|
2018-07-17 23:10:54 +00:00
|
|
|
|
s.str = fmt.Sprintf("%s %s %v", s.Attribute, s.SpreadTarget, s.Weight)
|
2018-07-16 17:52:24 +00:00
|
|
|
|
return s.str
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-17 22:21:00 +00:00
|
|
|
|
func (s *Spread) Validate() error {
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
if s.Attribute == "" {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Missing spread attribute"))
|
|
|
|
|
}
|
|
|
|
|
if s.Weight <= 0 || s.Weight > 100 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New("Spread stanza must have a positive weight from 0 to 100"))
|
|
|
|
|
}
|
|
|
|
|
seen := make(map[string]struct{})
|
2018-07-26 00:08:25 +00:00
|
|
|
|
sumPercent := uint32(0)
|
|
|
|
|
|
2018-07-17 22:21:00 +00:00
|
|
|
|
for _, target := range s.SpreadTarget {
|
|
|
|
|
// Make sure there are no duplicates
|
|
|
|
|
_, ok := seen[target.Value]
|
|
|
|
|
if !ok {
|
|
|
|
|
seen[target.Value] = struct{}{}
|
|
|
|
|
} else {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New(fmt.Sprintf("Spread target value %q already defined", target.Value)))
|
|
|
|
|
}
|
2018-07-26 00:08:25 +00:00
|
|
|
|
if target.Percent < 0 || target.Percent > 100 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New(fmt.Sprintf("Spread target percentage for value %q must be between 0 and 100", target.Value)))
|
|
|
|
|
}
|
2019-01-30 20:20:38 +00:00
|
|
|
|
sumPercent += uint32(target.Percent)
|
2018-07-26 00:08:25 +00:00
|
|
|
|
}
|
|
|
|
|
if sumPercent > 100 {
|
2018-07-31 02:59:35 +00:00
|
|
|
|
mErr.Errors = append(mErr.Errors, errors.New(fmt.Sprintf("Sum of spread target percentages must not be greater than 100%%; got %d%%", sumPercent)))
|
2018-07-17 22:21:00 +00:00
|
|
|
|
}
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-30 13:35:26 +00:00
|
|
|
|
// SpreadTarget is used to specify desired percentages for each attribute value
|
2018-07-26 00:08:25 +00:00
|
|
|
|
type SpreadTarget struct {
|
|
|
|
|
// Value is a single attribute value, like "dc1"
|
|
|
|
|
Value string
|
2018-07-29 00:49:09 +00:00
|
|
|
|
|
2018-07-26 00:08:25 +00:00
|
|
|
|
// Percent is the desired percentage of allocs
|
2019-01-30 20:20:38 +00:00
|
|
|
|
Percent uint8
|
2018-07-29 00:49:09 +00:00
|
|
|
|
|
|
|
|
|
// Memoized string representation
|
|
|
|
|
str string
|
2018-07-26 00:08:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *SpreadTarget) Copy() *SpreadTarget {
|
|
|
|
|
if s == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ns := new(SpreadTarget)
|
|
|
|
|
*ns = *s
|
|
|
|
|
return ns
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *SpreadTarget) String() string {
|
|
|
|
|
if s.str != "" {
|
|
|
|
|
return s.str
|
|
|
|
|
}
|
|
|
|
|
s.str = fmt.Sprintf("%q %v%%", s.Value, s.Percent)
|
|
|
|
|
return s.str
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-14 22:43:42 +00:00
|
|
|
|
// EphemeralDisk is an ephemeral disk object
|
|
|
|
|
type EphemeralDisk struct {
|
2016-08-25 20:26:28 +00:00
|
|
|
|
// Sticky indicates whether the allocation is sticky to a node
|
2016-08-24 18:51:15 +00:00
|
|
|
|
Sticky bool
|
2016-08-25 20:26:28 +00:00
|
|
|
|
|
2016-09-14 22:43:42 +00:00
|
|
|
|
// SizeMB is the size of the local disk
|
2017-02-22 20:30:05 +00:00
|
|
|
|
SizeMB int
|
2016-10-03 16:59:57 +00:00
|
|
|
|
|
|
|
|
|
// Migrate determines if Nomad client should migrate the allocation dir for
|
|
|
|
|
// sticky allocations
|
|
|
|
|
Migrate bool
|
2016-08-24 18:51:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-14 22:43:42 +00:00
|
|
|
|
// DefaultEphemeralDisk returns a EphemeralDisk with default configurations
|
|
|
|
|
func DefaultEphemeralDisk() *EphemeralDisk {
|
|
|
|
|
return &EphemeralDisk{
|
|
|
|
|
SizeMB: 300,
|
2016-08-24 18:51:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-14 22:43:42 +00:00
|
|
|
|
// Validate validates EphemeralDisk
|
|
|
|
|
func (d *EphemeralDisk) Validate() error {
|
|
|
|
|
if d.SizeMB < 10 {
|
|
|
|
|
return fmt.Errorf("minimum DiskMB value is 10; got %d", d.SizeMB)
|
2016-08-24 18:51:15 +00:00
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-14 22:43:42 +00:00
|
|
|
|
// Copy copies the EphemeralDisk struct and returns a new one
|
|
|
|
|
func (d *EphemeralDisk) Copy() *EphemeralDisk {
|
|
|
|
|
ld := new(EphemeralDisk)
|
2016-08-26 19:24:47 +00:00
|
|
|
|
*ld = *d
|
|
|
|
|
return ld
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-03 21:29:22 +00:00
|
|
|
|
var (
|
|
|
|
|
// VaultUnrecoverableError matches unrecoverable errors returned by a Vault
|
|
|
|
|
// server
|
|
|
|
|
VaultUnrecoverableError = regexp.MustCompile(`Code:\s+40(0|3|4)`)
|
|
|
|
|
)
|
|
|
|
|
|
2016-10-11 22:25:49 +00:00
|
|
|
|
const (
|
|
|
|
|
// VaultChangeModeNoop takes no action when a new token is retrieved.
|
|
|
|
|
VaultChangeModeNoop = "noop"
|
|
|
|
|
|
|
|
|
|
// VaultChangeModeSignal signals the task when a new token is retrieved.
|
|
|
|
|
VaultChangeModeSignal = "signal"
|
|
|
|
|
|
|
|
|
|
// VaultChangeModeRestart restarts the task when a new token is retrieved.
|
|
|
|
|
VaultChangeModeRestart = "restart"
|
|
|
|
|
)
|
|
|
|
|
|
2017-08-07 21:13:05 +00:00
|
|
|
|
// Vault stores the set of permissions a task needs access to from Vault.
|
2016-08-09 22:23:44 +00:00
|
|
|
|
type Vault struct {
|
|
|
|
|
// Policies is the set of policies that the task needs access to
|
|
|
|
|
Policies []string
|
2016-09-20 20:22:29 +00:00
|
|
|
|
|
|
|
|
|
// Env marks whether the Vault Token should be exposed as an environment
|
|
|
|
|
// variable
|
|
|
|
|
Env bool
|
2016-10-11 22:25:49 +00:00
|
|
|
|
|
|
|
|
|
// ChangeMode is used to configure the task's behavior when the Vault
|
|
|
|
|
// token changes because the original token could not be renewed in time.
|
2017-02-22 20:30:05 +00:00
|
|
|
|
ChangeMode string
|
2016-10-11 22:25:49 +00:00
|
|
|
|
|
|
|
|
|
// ChangeSignal is the signal sent to the task when a new token is
|
|
|
|
|
// retrieved. This is only valid when using the signal change mode.
|
2017-02-22 20:30:05 +00:00
|
|
|
|
ChangeSignal string
|
2016-08-09 22:23:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-17 18:41:22 +00:00
|
|
|
|
func DefaultVaultBlock() *Vault {
|
|
|
|
|
return &Vault{
|
|
|
|
|
Env: true,
|
|
|
|
|
ChangeMode: VaultChangeModeRestart,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-09 22:23:44 +00:00
|
|
|
|
// Copy returns a copy of this Vault block.
|
|
|
|
|
func (v *Vault) Copy() *Vault {
|
|
|
|
|
if v == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nv := new(Vault)
|
|
|
|
|
*nv = *v
|
|
|
|
|
return nv
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-25 18:09:22 +00:00
|
|
|
|
func (v *Vault) Canonicalize() {
|
|
|
|
|
if v.ChangeSignal != "" {
|
|
|
|
|
v.ChangeSignal = strings.ToUpper(v.ChangeSignal)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-09 22:23:44 +00:00
|
|
|
|
// Validate returns if the Vault block is valid.
|
|
|
|
|
func (v *Vault) Validate() error {
|
|
|
|
|
if v == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-13 18:51:29 +00:00
|
|
|
|
var mErr multierror.Error
|
2016-08-09 22:23:44 +00:00
|
|
|
|
if len(v.Policies) == 0 {
|
2017-02-13 18:51:29 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Policy list cannot be empty"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, p := range v.Policies {
|
|
|
|
|
if p == "root" {
|
2017-08-07 21:13:05 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Can not specify \"root\" policy"))
|
2017-02-13 18:51:29 +00:00
|
|
|
|
}
|
2016-08-09 22:23:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-11 22:25:49 +00:00
|
|
|
|
switch v.ChangeMode {
|
|
|
|
|
case VaultChangeModeSignal:
|
|
|
|
|
if v.ChangeSignal == "" {
|
2017-02-13 18:51:29 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Signal must be specified when using change mode %q", VaultChangeModeSignal))
|
2016-10-11 22:25:49 +00:00
|
|
|
|
}
|
|
|
|
|
case VaultChangeModeNoop, VaultChangeModeRestart:
|
|
|
|
|
default:
|
2017-02-13 18:51:29 +00:00
|
|
|
|
multierror.Append(&mErr, fmt.Errorf("Unknown change mode %q", v.ChangeMode))
|
2016-10-11 22:25:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-13 18:51:29 +00:00
|
|
|
|
return mErr.ErrorOrNil()
|
2016-08-09 22:23:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-21 23:37:10 +00:00
|
|
|
|
const (
|
|
|
|
|
// DeploymentStatuses are the various states a deployment can be be in
|
|
|
|
|
DeploymentStatusRunning = "running"
|
2017-06-27 18:52:14 +00:00
|
|
|
|
DeploymentStatusPaused = "paused"
|
2017-04-21 23:37:10 +00:00
|
|
|
|
DeploymentStatusFailed = "failed"
|
|
|
|
|
DeploymentStatusSuccessful = "successful"
|
|
|
|
|
DeploymentStatusCancelled = "cancelled"
|
2017-05-18 19:36:04 +00:00
|
|
|
|
|
2019-05-21 21:16:12 +00:00
|
|
|
|
// TODO Statuses and Descriptions do not match 1:1 and we sometimes use the Description as a status flag
|
|
|
|
|
|
2017-05-18 19:36:04 +00:00
|
|
|
|
// DeploymentStatusDescriptions are the various descriptions of the states a
|
|
|
|
|
// deployment can be in.
|
2017-07-07 06:30:46 +00:00
|
|
|
|
DeploymentStatusDescriptionRunning = "Deployment is running"
|
2019-05-20 15:37:49 +00:00
|
|
|
|
DeploymentStatusDescriptionRunningNeedsPromotion = "Deployment is running but requires manual promotion"
|
2019-05-17 21:53:27 +00:00
|
|
|
|
DeploymentStatusDescriptionRunningAutoPromotion = "Deployment is running pending automatic promotion"
|
2017-07-07 06:30:46 +00:00
|
|
|
|
DeploymentStatusDescriptionPaused = "Deployment is paused"
|
|
|
|
|
DeploymentStatusDescriptionSuccessful = "Deployment completed successfully"
|
|
|
|
|
DeploymentStatusDescriptionStoppedJob = "Cancelled because job is stopped"
|
|
|
|
|
DeploymentStatusDescriptionNewerJob = "Cancelled due to newer version of job"
|
|
|
|
|
DeploymentStatusDescriptionFailedAllocations = "Failed due to unhealthy allocations"
|
2018-03-23 17:56:00 +00:00
|
|
|
|
DeploymentStatusDescriptionProgressDeadline = "Failed due to progress deadline"
|
2017-07-07 06:30:46 +00:00
|
|
|
|
DeploymentStatusDescriptionFailedByUser = "Deployment marked as failed"
|
2017-04-21 23:37:10 +00:00
|
|
|
|
)
|
|
|
|
|
|
2017-06-28 19:58:05 +00:00
|
|
|
|
// DeploymentStatusDescriptionRollback is used to get the status description of
|
|
|
|
|
// a deployment when rolling back to an older job.
|
|
|
|
|
func DeploymentStatusDescriptionRollback(baseDescription string, jobVersion uint64) string {
|
|
|
|
|
return fmt.Sprintf("%s - rolling back to job version %d", baseDescription, jobVersion)
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-03 20:33:34 +00:00
|
|
|
|
// DeploymentStatusDescriptionRollbackNoop is used to get the status description of
|
|
|
|
|
// a deployment when rolling back is not possible because it has the same specification
|
|
|
|
|
func DeploymentStatusDescriptionRollbackNoop(baseDescription string, jobVersion uint64) string {
|
2017-11-03 21:07:06 +00:00
|
|
|
|
return fmt.Sprintf("%s - not rolling back to stable job version %d as current job has same specification", baseDescription, jobVersion)
|
2017-11-02 20:00:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-12 22:50:51 +00:00
|
|
|
|
// DeploymentStatusDescriptionNoRollbackTarget is used to get the status description of
|
2018-03-11 17:42:45 +00:00
|
|
|
|
// a deployment when there is no target to rollback to but autorevert is desired.
|
2017-08-12 22:50:51 +00:00
|
|
|
|
func DeploymentStatusDescriptionNoRollbackTarget(baseDescription string) string {
|
|
|
|
|
return fmt.Sprintf("%s - no stable job version to auto revert to", baseDescription)
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-21 23:37:10 +00:00
|
|
|
|
// Deployment is the object that represents a job deployment which is used to
|
2017-08-07 21:13:05 +00:00
|
|
|
|
// transition a job between versions.
|
2017-04-21 23:37:10 +00:00
|
|
|
|
type Deployment struct {
|
|
|
|
|
// ID is a generated UUID for the deployment
|
|
|
|
|
ID string
|
|
|
|
|
|
2017-09-07 23:56:15 +00:00
|
|
|
|
// Namespace is the namespace the deployment is created in
|
|
|
|
|
Namespace string
|
|
|
|
|
|
2017-04-21 23:37:10 +00:00
|
|
|
|
// JobID is the job the deployment is created for
|
|
|
|
|
JobID string
|
|
|
|
|
|
|
|
|
|
// JobVersion is the version of the job at which the deployment is tracking
|
|
|
|
|
JobVersion uint64
|
|
|
|
|
|
2018-05-31 17:57:08 +00:00
|
|
|
|
// JobModifyIndex is the ModifyIndex of the job which the deployment is
|
|
|
|
|
// tracking.
|
2017-04-21 23:37:10 +00:00
|
|
|
|
JobModifyIndex uint64
|
|
|
|
|
|
2018-05-31 17:57:08 +00:00
|
|
|
|
// JobSpecModifyIndex is the JobModifyIndex of the job which the
|
|
|
|
|
// deployment is tracking.
|
2018-05-30 18:33:56 +00:00
|
|
|
|
JobSpecModifyIndex uint64
|
|
|
|
|
|
2017-04-21 23:37:10 +00:00
|
|
|
|
// JobCreateIndex is the create index of the job which the deployment is
|
|
|
|
|
// tracking. It is needed so that if the job gets stopped and reran we can
|
|
|
|
|
// present the correct list of deployments for the job and not old ones.
|
|
|
|
|
JobCreateIndex uint64
|
|
|
|
|
|
|
|
|
|
// TaskGroups is the set of task groups effected by the deployment and their
|
|
|
|
|
// current deployment status.
|
|
|
|
|
TaskGroups map[string]*DeploymentState
|
|
|
|
|
|
|
|
|
|
// The status of the deployment
|
|
|
|
|
Status string
|
|
|
|
|
|
|
|
|
|
// StatusDescription allows a human readable description of the deployment
|
|
|
|
|
// status.
|
|
|
|
|
StatusDescription string
|
|
|
|
|
|
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-23 00:06:46 +00:00
|
|
|
|
// NewDeployment creates a new deployment given the job.
|
2017-05-22 17:58:34 +00:00
|
|
|
|
func NewDeployment(job *Job) *Deployment {
|
|
|
|
|
return &Deployment{
|
2018-05-30 18:33:56 +00:00
|
|
|
|
ID: uuid.Generate(),
|
|
|
|
|
Namespace: job.Namespace,
|
|
|
|
|
JobID: job.ID,
|
|
|
|
|
JobVersion: job.Version,
|
|
|
|
|
JobModifyIndex: job.ModifyIndex,
|
|
|
|
|
JobSpecModifyIndex: job.JobModifyIndex,
|
|
|
|
|
JobCreateIndex: job.CreateIndex,
|
|
|
|
|
Status: DeploymentStatusRunning,
|
|
|
|
|
StatusDescription: DeploymentStatusDescriptionRunning,
|
|
|
|
|
TaskGroups: make(map[string]*DeploymentState, len(job.TaskGroups)),
|
2017-05-22 17:58:34 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-24 21:49:23 +00:00
|
|
|
|
func (d *Deployment) Copy() *Deployment {
|
2017-07-05 19:55:51 +00:00
|
|
|
|
if d == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-24 21:49:23 +00:00
|
|
|
|
c := &Deployment{}
|
|
|
|
|
*c = *d
|
|
|
|
|
|
|
|
|
|
c.TaskGroups = nil
|
2017-05-10 22:26:00 +00:00
|
|
|
|
if l := len(d.TaskGroups); d.TaskGroups != nil {
|
2017-04-24 21:49:23 +00:00
|
|
|
|
c.TaskGroups = make(map[string]*DeploymentState, l)
|
|
|
|
|
for tg, s := range d.TaskGroups {
|
|
|
|
|
c.TaskGroups[tg] = s.Copy()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Active returns whether the deployment is active or terminal.
|
|
|
|
|
func (d *Deployment) Active() bool {
|
|
|
|
|
switch d.Status {
|
|
|
|
|
case DeploymentStatusRunning, DeploymentStatusPaused:
|
|
|
|
|
return true
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-06 00:13:45 +00:00
|
|
|
|
// GetID is a helper for getting the ID when the object may be nil
|
|
|
|
|
func (d *Deployment) GetID() string {
|
|
|
|
|
if d == nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
return d.ID
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-06 04:26:04 +00:00
|
|
|
|
// HasPlacedCanaries returns whether the deployment has placed canaries
|
|
|
|
|
func (d *Deployment) HasPlacedCanaries() bool {
|
|
|
|
|
if d == nil || len(d.TaskGroups) == 0 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for _, group := range d.TaskGroups {
|
|
|
|
|
if len(group.PlacedCanaries) != 0 {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-07 06:30:46 +00:00
|
|
|
|
// RequiresPromotion returns whether the deployment requires promotion to
|
|
|
|
|
// continue
|
|
|
|
|
func (d *Deployment) RequiresPromotion() bool {
|
|
|
|
|
if d == nil || len(d.TaskGroups) == 0 || d.Status != DeploymentStatusRunning {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for _, group := range d.TaskGroups {
|
|
|
|
|
if group.DesiredCanaries > 0 && !group.Promoted {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-21 20:52:43 +00:00
|
|
|
|
// HasAutoPromote determines if all taskgroups are marked auto_promote
|
2019-05-17 21:53:27 +00:00
|
|
|
|
func (d *Deployment) HasAutoPromote() bool {
|
|
|
|
|
if d == nil || len(d.TaskGroups) == 0 || d.Status != DeploymentStatusRunning {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for _, group := range d.TaskGroups {
|
2019-05-21 20:52:43 +00:00
|
|
|
|
if !group.AutoPromote {
|
|
|
|
|
return false
|
2019-05-17 21:53:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-21 20:52:43 +00:00
|
|
|
|
return true
|
2019-05-17 21:53:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-06 21:08:46 +00:00
|
|
|
|
func (d *Deployment) GoString() string {
|
2017-06-29 05:00:18 +00:00
|
|
|
|
base := fmt.Sprintf("Deployment ID %q for job %q has status %q (%v):", d.ID, d.JobID, d.Status, d.StatusDescription)
|
2017-06-06 21:08:46 +00:00
|
|
|
|
for group, state := range d.TaskGroups {
|
|
|
|
|
base += fmt.Sprintf("\nTask Group %q has state:\n%#v", group, state)
|
|
|
|
|
}
|
|
|
|
|
return base
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-21 23:37:10 +00:00
|
|
|
|
// DeploymentState tracks the state of a deployment for a given task group.
|
|
|
|
|
type DeploymentState struct {
|
2017-06-30 19:35:59 +00:00
|
|
|
|
// AutoRevert marks whether the task group has indicated the job should be
|
|
|
|
|
// reverted on failure
|
|
|
|
|
AutoRevert bool
|
|
|
|
|
|
2019-05-09 13:42:18 +00:00
|
|
|
|
// AutoPromote marks promotion triggered automatically by healthy canaries
|
|
|
|
|
// copied from TaskGroup UpdateStrategy in scheduler.reconcile
|
|
|
|
|
AutoPromote bool
|
|
|
|
|
|
2018-04-10 18:42:13 +00:00
|
|
|
|
// ProgressDeadline is the deadline by which an allocation must transition
|
2018-04-04 22:39:45 +00:00
|
|
|
|
// to healthy before the deployment is considered failed.
|
|
|
|
|
ProgressDeadline time.Duration
|
|
|
|
|
|
2018-04-10 18:42:13 +00:00
|
|
|
|
// RequireProgressBy is the time by which an allocation must transition
|
2018-04-04 22:39:45 +00:00
|
|
|
|
// to healthy before the deployment is considered failed.
|
|
|
|
|
RequireProgressBy time.Time
|
|
|
|
|
|
2017-05-22 17:58:34 +00:00
|
|
|
|
// Promoted marks whether the canaries have been promoted
|
2017-04-21 23:37:10 +00:00
|
|
|
|
Promoted bool
|
|
|
|
|
|
2017-07-04 20:31:01 +00:00
|
|
|
|
// PlacedCanaries is the set of placed canary allocations
|
|
|
|
|
PlacedCanaries []string
|
|
|
|
|
|
2017-04-21 23:37:10 +00:00
|
|
|
|
// DesiredCanaries is the number of canaries that should be created.
|
|
|
|
|
DesiredCanaries int
|
|
|
|
|
|
|
|
|
|
// DesiredTotal is the total number of allocations that should be created as
|
|
|
|
|
// part of the deployment.
|
|
|
|
|
DesiredTotal int
|
|
|
|
|
|
|
|
|
|
// PlacedAllocs is the number of allocations that have been placed
|
|
|
|
|
PlacedAllocs int
|
|
|
|
|
|
|
|
|
|
// HealthyAllocs is the number of allocations that have been marked healthy.
|
|
|
|
|
HealthyAllocs int
|
|
|
|
|
|
|
|
|
|
// UnhealthyAllocs are allocations that have been marked as unhealthy.
|
|
|
|
|
UnhealthyAllocs int
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-06 21:08:46 +00:00
|
|
|
|
func (d *DeploymentState) GoString() string {
|
2017-07-05 19:55:51 +00:00
|
|
|
|
base := fmt.Sprintf("\tDesired Total: %d", d.DesiredTotal)
|
|
|
|
|
base += fmt.Sprintf("\n\tDesired Canaries: %d", d.DesiredCanaries)
|
|
|
|
|
base += fmt.Sprintf("\n\tPlaced Canaries: %#v", d.PlacedCanaries)
|
|
|
|
|
base += fmt.Sprintf("\n\tPromoted: %v", d.Promoted)
|
|
|
|
|
base += fmt.Sprintf("\n\tPlaced: %d", d.PlacedAllocs)
|
|
|
|
|
base += fmt.Sprintf("\n\tHealthy: %d", d.HealthyAllocs)
|
|
|
|
|
base += fmt.Sprintf("\n\tUnhealthy: %d", d.UnhealthyAllocs)
|
|
|
|
|
base += fmt.Sprintf("\n\tAutoRevert: %v", d.AutoRevert)
|
2019-05-09 13:42:18 +00:00
|
|
|
|
base += fmt.Sprintf("\n\tAutoPromote: %v", d.AutoPromote)
|
2017-06-06 21:08:46 +00:00
|
|
|
|
return base
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-24 21:49:23 +00:00
|
|
|
|
func (d *DeploymentState) Copy() *DeploymentState {
|
|
|
|
|
c := &DeploymentState{}
|
|
|
|
|
*c = *d
|
2017-07-04 20:31:01 +00:00
|
|
|
|
c.PlacedCanaries = helper.CopySliceString(d.PlacedCanaries)
|
2017-04-24 21:49:23 +00:00
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-11 19:49:04 +00:00
|
|
|
|
// DeploymentStatusUpdate is used to update the status of a given deployment
|
|
|
|
|
type DeploymentStatusUpdate struct {
|
|
|
|
|
// DeploymentID is the ID of the deployment to update
|
|
|
|
|
DeploymentID string
|
|
|
|
|
|
|
|
|
|
// Status is the new status of the deployment.
|
|
|
|
|
Status string
|
|
|
|
|
|
|
|
|
|
// StatusDescription is the new status description of the deployment.
|
|
|
|
|
StatusDescription string
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-17 17:05:22 +00:00
|
|
|
|
// RescheduleTracker encapsulates previous reschedule events
|
2018-01-14 15:03:08 +00:00
|
|
|
|
type RescheduleTracker struct {
|
2018-01-17 17:05:22 +00:00
|
|
|
|
Events []*RescheduleEvent
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-17 17:44:06 +00:00
|
|
|
|
func (rt *RescheduleTracker) Copy() *RescheduleTracker {
|
|
|
|
|
if rt == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
nt := &RescheduleTracker{}
|
|
|
|
|
*nt = *rt
|
|
|
|
|
rescheduleEvents := make([]*RescheduleEvent, 0, len(rt.Events))
|
|
|
|
|
for _, tracker := range rt.Events {
|
|
|
|
|
rescheduleEvents = append(rescheduleEvents, tracker.Copy())
|
|
|
|
|
}
|
|
|
|
|
nt.Events = rescheduleEvents
|
|
|
|
|
return nt
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-17 17:05:22 +00:00
|
|
|
|
// RescheduleEvent is used to keep track of previous attempts at rescheduling an allocation
|
|
|
|
|
type RescheduleEvent struct {
|
2018-01-14 15:03:08 +00:00
|
|
|
|
// RescheduleTime is the timestamp of a reschedule attempt
|
|
|
|
|
RescheduleTime int64
|
|
|
|
|
|
|
|
|
|
// PrevAllocID is the ID of the previous allocation being restarted
|
|
|
|
|
PrevAllocID string
|
|
|
|
|
|
|
|
|
|
// PrevNodeID is the node ID of the previous allocation
|
|
|
|
|
PrevNodeID string
|
2018-03-02 00:20:09 +00:00
|
|
|
|
|
|
|
|
|
// Delay is the reschedule delay associated with the attempt
|
|
|
|
|
Delay time.Duration
|
2018-01-14 15:03:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-02 00:20:09 +00:00
|
|
|
|
func NewRescheduleEvent(rescheduleTime int64, prevAllocID string, prevNodeID string, delay time.Duration) *RescheduleEvent {
|
2018-01-19 21:20:00 +00:00
|
|
|
|
return &RescheduleEvent{RescheduleTime: rescheduleTime,
|
|
|
|
|
PrevAllocID: prevAllocID,
|
2018-03-02 00:20:09 +00:00
|
|
|
|
PrevNodeID: prevNodeID,
|
|
|
|
|
Delay: delay}
|
2018-01-19 21:20:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-17 17:44:06 +00:00
|
|
|
|
func (re *RescheduleEvent) Copy() *RescheduleEvent {
|
|
|
|
|
if re == nil {
|
2018-01-14 15:03:08 +00:00
|
|
|
|
return nil
|
|
|
|
|
}
|
2018-01-17 17:05:22 +00:00
|
|
|
|
copy := new(RescheduleEvent)
|
2018-01-17 17:44:06 +00:00
|
|
|
|
*copy = *re
|
2018-01-14 15:03:08 +00:00
|
|
|
|
return copy
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-23 01:38:44 +00:00
|
|
|
|
// DesiredTransition is used to mark an allocation as having a desired state
|
|
|
|
|
// transition. This information can be used by the scheduler to make the
|
2018-02-21 18:58:04 +00:00
|
|
|
|
// correct decision.
|
2018-02-23 01:38:44 +00:00
|
|
|
|
type DesiredTransition struct {
|
2018-02-21 18:58:04 +00:00
|
|
|
|
// Migrate is used to indicate that this allocation should be stopped and
|
|
|
|
|
// migrated to another node.
|
|
|
|
|
Migrate *bool
|
2018-04-07 00:23:35 +00:00
|
|
|
|
|
|
|
|
|
// Reschedule is used to indicate that this allocation is eligible to be
|
|
|
|
|
// rescheduled. Most allocations are automatically eligible for
|
|
|
|
|
// rescheduling, so this field is only required when an allocation is not
|
|
|
|
|
// automatically eligible. An example is an allocation that is part of a
|
|
|
|
|
// deployment.
|
|
|
|
|
Reschedule *bool
|
2018-05-08 22:26:36 +00:00
|
|
|
|
|
|
|
|
|
// ForceReschedule is used to indicate that this allocation must be rescheduled.
|
|
|
|
|
// This field is only used when operators want to force a placement even if
|
|
|
|
|
// a failed allocation is not eligible to be rescheduled
|
|
|
|
|
ForceReschedule *bool
|
2018-02-21 18:58:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Merge merges the two desired transitions, preferring the values from the
|
|
|
|
|
// passed in object.
|
2018-02-23 01:38:44 +00:00
|
|
|
|
func (d *DesiredTransition) Merge(o *DesiredTransition) {
|
2018-02-21 18:58:04 +00:00
|
|
|
|
if o.Migrate != nil {
|
|
|
|
|
d.Migrate = o.Migrate
|
|
|
|
|
}
|
2018-04-07 00:23:35 +00:00
|
|
|
|
|
|
|
|
|
if o.Reschedule != nil {
|
|
|
|
|
d.Reschedule = o.Reschedule
|
|
|
|
|
}
|
2018-05-08 22:26:36 +00:00
|
|
|
|
|
|
|
|
|
if o.ForceReschedule != nil {
|
|
|
|
|
d.ForceReschedule = o.ForceReschedule
|
|
|
|
|
}
|
2018-02-21 18:58:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-23 01:38:44 +00:00
|
|
|
|
// ShouldMigrate returns whether the transition object dictates a migration.
|
|
|
|
|
func (d *DesiredTransition) ShouldMigrate() bool {
|
2018-02-21 18:58:04 +00:00
|
|
|
|
return d.Migrate != nil && *d.Migrate
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-07 00:23:35 +00:00
|
|
|
|
// ShouldReschedule returns whether the transition object dictates a
|
|
|
|
|
// rescheduling.
|
|
|
|
|
func (d *DesiredTransition) ShouldReschedule() bool {
|
|
|
|
|
return d.Reschedule != nil && *d.Reschedule
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-08 22:26:36 +00:00
|
|
|
|
// ShouldForceReschedule returns whether the transition object dictates a
|
|
|
|
|
// forced rescheduling.
|
|
|
|
|
func (d *DesiredTransition) ShouldForceReschedule() bool {
|
|
|
|
|
if d == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return d.ForceReschedule != nil && *d.ForceReschedule
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 23:36:16 +00:00
|
|
|
|
const (
|
|
|
|
|
AllocDesiredStatusRun = "run" // Allocation should run
|
|
|
|
|
AllocDesiredStatusStop = "stop" // Allocation should stop
|
|
|
|
|
AllocDesiredStatusEvict = "evict" // Allocation should stop, and was evicted
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
AllocClientStatusPending = "pending"
|
|
|
|
|
AllocClientStatusRunning = "running"
|
|
|
|
|
AllocClientStatusComplete = "complete"
|
|
|
|
|
AllocClientStatusFailed = "failed"
|
|
|
|
|
AllocClientStatusLost = "lost"
|
|
|
|
|
)
|
|
|
|
|
|
2015-07-04 00:11:53 +00:00
|
|
|
|
// Allocation is used to allocate the placement of a task group to a node.
|
|
|
|
|
type Allocation struct {
|
2019-03-04 09:49:32 +00:00
|
|
|
|
// msgpack omit empty fields during serialization
|
|
|
|
|
_struct bool `codec:",omitempty"` // nolint: structcheck
|
|
|
|
|
|
2015-07-04 00:11:53 +00:00
|
|
|
|
// ID of the allocation (UUID)
|
|
|
|
|
ID string
|
|
|
|
|
|
2017-09-07 23:56:15 +00:00
|
|
|
|
// Namespace is the namespace the allocation is created in
|
|
|
|
|
Namespace string
|
|
|
|
|
|
2015-08-15 20:08:06 +00:00
|
|
|
|
// ID of the evaluation that generated this allocation
|
|
|
|
|
EvalID string
|
|
|
|
|
|
2015-08-11 23:34:06 +00:00
|
|
|
|
// Name is a logical name of the allocation.
|
|
|
|
|
Name string
|
|
|
|
|
|
2015-07-04 00:11:53 +00:00
|
|
|
|
// NodeID is the node this is being placed on
|
|
|
|
|
NodeID string
|
|
|
|
|
|
2018-07-17 10:03:13 +00:00
|
|
|
|
// NodeName is the name of the node this is being placed on.
|
|
|
|
|
NodeName string
|
|
|
|
|
|
2015-07-04 00:11:53 +00:00
|
|
|
|
// Job is the parent job of the task group being allocated.
|
|
|
|
|
// This is copied at allocation time to avoid issues if the job
|
|
|
|
|
// definition is updated.
|
2015-07-23 22:15:48 +00:00
|
|
|
|
JobID string
|
|
|
|
|
Job *Job
|
2015-07-04 00:11:53 +00:00
|
|
|
|
|
2015-08-23 23:30:57 +00:00
|
|
|
|
// TaskGroup is the name of the task group that should be run
|
|
|
|
|
TaskGroup string
|
|
|
|
|
|
2018-09-30 01:02:48 +00:00
|
|
|
|
// COMPAT(0.11): Remove in 0.11
|
2015-09-12 23:22:18 +00:00
|
|
|
|
// Resources is the total set of resources allocated as part
|
2018-07-20 00:40:25 +00:00
|
|
|
|
// of this allocation of the task group. Dynamic ports will be set by
|
|
|
|
|
// the scheduler.
|
2015-07-04 00:11:53 +00:00
|
|
|
|
Resources *Resources
|
|
|
|
|
|
2018-09-30 01:02:48 +00:00
|
|
|
|
// COMPAT(0.11): Remove in 0.11
|
2016-08-29 19:49:52 +00:00
|
|
|
|
// SharedResources are the resources that are shared by all the tasks in an
|
|
|
|
|
// allocation
|
|
|
|
|
SharedResources *Resources
|
|
|
|
|
|
2018-09-30 01:02:48 +00:00
|
|
|
|
// COMPAT(0.11): Remove in 0.11
|
2015-09-12 23:22:18 +00:00
|
|
|
|
// TaskResources is the set of resources allocated to each
|
2018-07-20 00:40:25 +00:00
|
|
|
|
// task. These should sum to the total Resources. Dynamic ports will be
|
|
|
|
|
// set by the scheduler.
|
2015-09-12 23:22:18 +00:00
|
|
|
|
TaskResources map[string]*Resources
|
|
|
|
|
|
2018-09-30 01:02:48 +00:00
|
|
|
|
// AllocatedResources is the total resources allocated for the task group.
|
|
|
|
|
AllocatedResources *AllocatedResources
|
|
|
|
|
|
2015-07-04 00:37:01 +00:00
|
|
|
|
// Metrics associated with this allocation
|
|
|
|
|
Metrics *AllocMetric
|
|
|
|
|
|
2015-08-25 23:18:37 +00:00
|
|
|
|
// Desired Status of the allocation on the client
|
|
|
|
|
DesiredStatus string
|
2015-07-04 00:50:54 +00:00
|
|
|
|
|
2015-08-25 23:18:37 +00:00
|
|
|
|
// DesiredStatusDescription is meant to provide more human useful information
|
|
|
|
|
DesiredDescription string
|
|
|
|
|
|
2018-02-23 01:38:44 +00:00
|
|
|
|
// DesiredTransition is used to indicate that a state transition
|
2018-02-21 18:58:04 +00:00
|
|
|
|
// is desired for a given reason.
|
2018-02-23 01:38:44 +00:00
|
|
|
|
DesiredTransition DesiredTransition
|
2018-02-21 18:58:04 +00:00
|
|
|
|
|
2015-08-25 23:18:37 +00:00
|
|
|
|
// Status of the allocation on the client
|
|
|
|
|
ClientStatus string
|
|
|
|
|
|
|
|
|
|
// ClientStatusDescription is meant to provide more human useful information
|
2015-08-25 23:26:34 +00:00
|
|
|
|
ClientDescription string
|
2015-08-15 20:08:06 +00:00
|
|
|
|
|
2015-11-12 23:28:22 +00:00
|
|
|
|
// TaskStates stores the state of each task,
|
|
|
|
|
TaskStates map[string]*TaskState
|
|
|
|
|
|
2016-08-16 17:49:45 +00:00
|
|
|
|
// PreviousAllocation is the allocation that this allocation is replacing
|
|
|
|
|
PreviousAllocation string
|
|
|
|
|
|
2018-01-24 20:56:57 +00:00
|
|
|
|
// NextAllocation is the allocation that this allocation is being replaced by
|
|
|
|
|
NextAllocation string
|
|
|
|
|
|
2017-04-21 23:37:10 +00:00
|
|
|
|
// DeploymentID identifies an allocation as being created from a
|
|
|
|
|
// particular deployment
|
|
|
|
|
DeploymentID string
|
|
|
|
|
|
2017-05-11 18:03:15 +00:00
|
|
|
|
// DeploymentStatus captures the status of the allocation as part of the
|
|
|
|
|
// given deployment
|
|
|
|
|
DeploymentStatus *AllocDeploymentStatus
|
2017-04-21 23:37:10 +00:00
|
|
|
|
|
2018-03-02 00:20:09 +00:00
|
|
|
|
// RescheduleTrackers captures details of previous reschedule attempts of the allocation
|
|
|
|
|
RescheduleTracker *RescheduleTracker
|
|
|
|
|
|
|
|
|
|
// FollowupEvalID captures a follow up evaluation created to handle a failed allocation
|
|
|
|
|
// that can be rescheduled in the future
|
|
|
|
|
FollowupEvalID string
|
|
|
|
|
|
2018-09-10 17:38:36 +00:00
|
|
|
|
// PreemptedAllocations captures IDs of any allocations that were preempted
|
|
|
|
|
// in order to place this allocation
|
|
|
|
|
PreemptedAllocations []string
|
|
|
|
|
|
|
|
|
|
// PreemptedByAllocation tracks the alloc ID of the allocation that caused this allocation
|
|
|
|
|
// to stop running because it got preempted
|
|
|
|
|
PreemptedByAllocation string
|
|
|
|
|
|
2015-07-04 00:50:54 +00:00
|
|
|
|
// Raft Indexes
|
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
2016-02-02 02:15:12 +00:00
|
|
|
|
|
|
|
|
|
// AllocModifyIndex is not updated when the client updates allocations. This
|
|
|
|
|
// lets the client pull only the allocs updated by the server.
|
2016-02-01 21:57:35 +00:00
|
|
|
|
AllocModifyIndex uint64
|
2016-02-09 05:58:05 +00:00
|
|
|
|
|
|
|
|
|
// CreateTime is the time the allocation has finished scheduling and been
|
|
|
|
|
// verified by the plan applier.
|
|
|
|
|
CreateTime int64
|
2017-10-25 18:06:25 +00:00
|
|
|
|
|
|
|
|
|
// ModifyTime is the time the allocation was last updated.
|
|
|
|
|
ModifyTime int64
|
2015-07-04 00:11:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-31 18:34:46 +00:00
|
|
|
|
// Index returns the index of the allocation. If the allocation is from a task
|
|
|
|
|
// group with count greater than 1, there will be multiple allocations for it.
|
|
|
|
|
func (a *Allocation) Index() uint {
|
|
|
|
|
l := len(a.Name)
|
|
|
|
|
prefix := len(a.JobID) + len(a.TaskGroup) + 2
|
|
|
|
|
if l <= 3 || l <= prefix {
|
|
|
|
|
return uint(0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
strNum := a.Name[prefix : len(a.Name)-1]
|
|
|
|
|
num, _ := strconv.Atoi(strNum)
|
|
|
|
|
return uint(num)
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-04 09:49:32 +00:00
|
|
|
|
// Copy provides a copy of the allocation and deep copies the job
|
2016-02-02 01:47:53 +00:00
|
|
|
|
func (a *Allocation) Copy() *Allocation {
|
2017-05-17 18:07:06 +00:00
|
|
|
|
return a.copyImpl(true)
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-04 09:49:32 +00:00
|
|
|
|
// CopySkipJob provides a copy of the allocation but doesn't deep copy the job
|
2017-05-17 18:07:06 +00:00
|
|
|
|
func (a *Allocation) CopySkipJob() *Allocation {
|
|
|
|
|
return a.copyImpl(false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *Allocation) copyImpl(job bool) *Allocation {
|
2016-02-11 01:54:43 +00:00
|
|
|
|
if a == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
na := new(Allocation)
|
|
|
|
|
*na = *a
|
|
|
|
|
|
2017-05-17 18:07:06 +00:00
|
|
|
|
if job {
|
|
|
|
|
na.Job = na.Job.Copy()
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
na.AllocatedResources = na.AllocatedResources.Copy()
|
2016-02-11 01:54:43 +00:00
|
|
|
|
na.Resources = na.Resources.Copy()
|
2016-08-29 19:49:52 +00:00
|
|
|
|
na.SharedResources = na.SharedResources.Copy()
|
2016-02-11 01:54:43 +00:00
|
|
|
|
|
2016-03-21 23:29:21 +00:00
|
|
|
|
if a.TaskResources != nil {
|
|
|
|
|
tr := make(map[string]*Resources, len(na.TaskResources))
|
|
|
|
|
for task, resource := range na.TaskResources {
|
|
|
|
|
tr[task] = resource.Copy()
|
|
|
|
|
}
|
|
|
|
|
na.TaskResources = tr
|
2016-02-11 01:54:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
na.Metrics = na.Metrics.Copy()
|
2017-05-11 18:03:15 +00:00
|
|
|
|
na.DeploymentStatus = na.DeploymentStatus.Copy()
|
2016-02-11 01:54:43 +00:00
|
|
|
|
|
2016-03-21 23:29:21 +00:00
|
|
|
|
if a.TaskStates != nil {
|
|
|
|
|
ts := make(map[string]*TaskState, len(na.TaskStates))
|
|
|
|
|
for task, state := range na.TaskStates {
|
|
|
|
|
ts[task] = state.Copy()
|
|
|
|
|
}
|
|
|
|
|
na.TaskStates = ts
|
2016-02-11 01:54:43 +00:00
|
|
|
|
}
|
2018-01-14 15:03:08 +00:00
|
|
|
|
|
2018-01-17 17:44:06 +00:00
|
|
|
|
na.RescheduleTracker = a.RescheduleTracker.Copy()
|
2018-09-10 17:38:36 +00:00
|
|
|
|
na.PreemptedAllocations = helper.CopySliceString(a.PreemptedAllocations)
|
2016-02-11 01:54:43 +00:00
|
|
|
|
return na
|
2015-07-04 00:11:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-15 03:20:57 +00:00
|
|
|
|
// TerminalStatus returns if the desired or actual status is terminal and
|
|
|
|
|
// will no longer transition.
|
2015-08-15 23:07:22 +00:00
|
|
|
|
func (a *Allocation) TerminalStatus() bool {
|
2015-12-16 22:34:17 +00:00
|
|
|
|
// First check the desired state and if that isn't terminal, check client
|
|
|
|
|
// state.
|
2019-01-22 22:43:32 +00:00
|
|
|
|
return a.ServerTerminalStatus() || a.ClientTerminalStatus()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ServerTerminalStatus returns true if the desired state of the allocation is terminal
|
|
|
|
|
func (a *Allocation) ServerTerminalStatus() bool {
|
2015-08-25 23:18:37 +00:00
|
|
|
|
switch a.DesiredStatus {
|
2016-07-13 19:20:46 +00:00
|
|
|
|
case AllocDesiredStatusStop, AllocDesiredStatusEvict:
|
2015-08-15 23:07:22 +00:00
|
|
|
|
return true
|
2016-04-08 21:22:06 +00:00
|
|
|
|
default:
|
2019-01-22 22:43:32 +00:00
|
|
|
|
return false
|
2016-04-08 21:22:06 +00:00
|
|
|
|
}
|
2018-04-04 19:38:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ClientTerminalStatus returns if the client status is terminal and will no longer transition
|
|
|
|
|
func (a *Allocation) ClientTerminalStatus() bool {
|
2016-04-08 21:22:06 +00:00
|
|
|
|
switch a.ClientStatus {
|
2016-08-03 22:45:42 +00:00
|
|
|
|
case AllocClientStatusComplete, AllocClientStatusFailed, AllocClientStatusLost:
|
2016-04-08 21:22:06 +00:00
|
|
|
|
return true
|
2015-12-16 22:34:17 +00:00
|
|
|
|
default:
|
|
|
|
|
return false
|
2015-08-15 23:07:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-14 15:03:08 +00:00
|
|
|
|
// ShouldReschedule returns if the allocation is eligible to be rescheduled according
|
2018-01-17 17:05:22 +00:00
|
|
|
|
// to its status and ReschedulePolicy given its failure time
|
|
|
|
|
func (a *Allocation) ShouldReschedule(reschedulePolicy *ReschedulePolicy, failTime time.Time) bool {
|
2018-01-14 15:03:08 +00:00
|
|
|
|
// First check the desired state
|
|
|
|
|
switch a.DesiredStatus {
|
|
|
|
|
case AllocDesiredStatusStop, AllocDesiredStatusEvict:
|
|
|
|
|
return false
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
switch a.ClientStatus {
|
|
|
|
|
case AllocClientStatusFailed:
|
2018-01-17 17:05:22 +00:00
|
|
|
|
return a.RescheduleEligible(reschedulePolicy, failTime)
|
2018-01-14 15:03:08 +00:00
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-16 21:01:31 +00:00
|
|
|
|
// RescheduleEligible returns if the allocation is eligible to be rescheduled according
|
|
|
|
|
// to its ReschedulePolicy and the current state of its reschedule trackers
|
2018-01-17 17:05:22 +00:00
|
|
|
|
func (a *Allocation) RescheduleEligible(reschedulePolicy *ReschedulePolicy, failTime time.Time) bool {
|
2018-01-16 21:01:31 +00:00
|
|
|
|
if reschedulePolicy == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
attempts := reschedulePolicy.Attempts
|
|
|
|
|
interval := reschedulePolicy.Interval
|
2018-03-02 00:20:09 +00:00
|
|
|
|
enabled := attempts > 0 || reschedulePolicy.Unlimited
|
|
|
|
|
if !enabled {
|
2018-01-14 15:03:08 +00:00
|
|
|
|
return false
|
|
|
|
|
}
|
2018-03-24 15:29:20 +00:00
|
|
|
|
if reschedulePolicy.Unlimited {
|
|
|
|
|
return true
|
|
|
|
|
}
|
2018-03-26 18:30:09 +00:00
|
|
|
|
// Early return true if there are no attempts yet and the number of allowed attempts is > 0
|
2018-03-24 15:29:20 +00:00
|
|
|
|
if (a.RescheduleTracker == nil || len(a.RescheduleTracker.Events) == 0) && attempts > 0 {
|
2018-01-14 15:03:08 +00:00
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
attempted := 0
|
2018-01-17 17:05:22 +00:00
|
|
|
|
for j := len(a.RescheduleTracker.Events) - 1; j >= 0; j-- {
|
|
|
|
|
lastAttempt := a.RescheduleTracker.Events[j].RescheduleTime
|
|
|
|
|
timeDiff := failTime.UTC().UnixNano() - lastAttempt
|
2018-01-14 15:03:08 +00:00
|
|
|
|
if timeDiff < interval.Nanoseconds() {
|
|
|
|
|
attempted += 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return attempted < attempts
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-02 00:20:09 +00:00
|
|
|
|
// LastEventTime is the time of the last task event in the allocation.
|
2018-03-29 02:22:25 +00:00
|
|
|
|
// It is used to determine allocation failure time. If the FinishedAt field
|
|
|
|
|
// is not set, the alloc's modify time is used
|
2018-03-02 00:20:09 +00:00
|
|
|
|
func (a *Allocation) LastEventTime() time.Time {
|
|
|
|
|
var lastEventTime time.Time
|
|
|
|
|
if a.TaskStates != nil {
|
2018-03-29 19:05:56 +00:00
|
|
|
|
for _, s := range a.TaskStates {
|
|
|
|
|
if lastEventTime.IsZero() || s.FinishedAt.After(lastEventTime) {
|
|
|
|
|
lastEventTime = s.FinishedAt
|
2018-03-02 00:20:09 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-03 20:49:18 +00:00
|
|
|
|
|
2018-03-29 02:22:25 +00:00
|
|
|
|
if lastEventTime.IsZero() {
|
|
|
|
|
return time.Unix(0, a.ModifyTime).UTC()
|
|
|
|
|
}
|
2018-03-02 00:20:09 +00:00
|
|
|
|
return lastEventTime
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-08 15:36:01 +00:00
|
|
|
|
// ReschedulePolicy returns the reschedule policy based on the task group
|
|
|
|
|
func (a *Allocation) ReschedulePolicy() *ReschedulePolicy {
|
|
|
|
|
tg := a.Job.LookupTaskGroup(a.TaskGroup)
|
|
|
|
|
if tg == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return tg.ReschedulePolicy
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-08 00:02:22 +00:00
|
|
|
|
// NextRescheduleTime returns a time on or after which the allocation is eligible to be rescheduled,
|
2018-03-02 00:20:09 +00:00
|
|
|
|
// and whether the next reschedule time is within policy's interval if the policy doesn't allow unlimited reschedules
|
2018-03-08 15:36:01 +00:00
|
|
|
|
func (a *Allocation) NextRescheduleTime() (time.Time, bool) {
|
2018-03-02 00:20:09 +00:00
|
|
|
|
failTime := a.LastEventTime()
|
2018-03-08 15:36:01 +00:00
|
|
|
|
reschedulePolicy := a.ReschedulePolicy()
|
2018-03-29 14:28:52 +00:00
|
|
|
|
if a.DesiredStatus == AllocDesiredStatusStop || a.ClientStatus != AllocClientStatusFailed || failTime.IsZero() || reschedulePolicy == nil {
|
2018-03-02 00:20:09 +00:00
|
|
|
|
return time.Time{}, false
|
|
|
|
|
}
|
2018-03-08 15:36:01 +00:00
|
|
|
|
|
|
|
|
|
nextDelay := a.NextDelay()
|
2018-03-02 00:20:09 +00:00
|
|
|
|
nextRescheduleTime := failTime.Add(nextDelay)
|
|
|
|
|
rescheduleEligible := reschedulePolicy.Unlimited || (reschedulePolicy.Attempts > 0 && a.RescheduleTracker == nil)
|
|
|
|
|
if reschedulePolicy.Attempts > 0 && a.RescheduleTracker != nil && a.RescheduleTracker.Events != nil {
|
|
|
|
|
// Check for eligibility based on the interval if max attempts is set
|
|
|
|
|
attempted := 0
|
|
|
|
|
for j := len(a.RescheduleTracker.Events) - 1; j >= 0; j-- {
|
|
|
|
|
lastAttempt := a.RescheduleTracker.Events[j].RescheduleTime
|
|
|
|
|
timeDiff := failTime.UTC().UnixNano() - lastAttempt
|
|
|
|
|
if timeDiff < reschedulePolicy.Interval.Nanoseconds() {
|
|
|
|
|
attempted += 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
rescheduleEligible = attempted < reschedulePolicy.Attempts && nextDelay < reschedulePolicy.Interval
|
|
|
|
|
}
|
|
|
|
|
return nextRescheduleTime, rescheduleEligible
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-08 00:02:22 +00:00
|
|
|
|
// NextDelay returns a duration after which the allocation can be rescheduled.
|
|
|
|
|
// It is calculated according to the delay function and previous reschedule attempts.
|
2018-03-08 15:36:01 +00:00
|
|
|
|
func (a *Allocation) NextDelay() time.Duration {
|
|
|
|
|
policy := a.ReschedulePolicy()
|
2018-09-05 22:01:02 +00:00
|
|
|
|
// Can be nil if the task group was updated to remove its reschedule policy
|
|
|
|
|
if policy == nil {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
2018-03-02 00:20:09 +00:00
|
|
|
|
delayDur := policy.Delay
|
|
|
|
|
if a.RescheduleTracker == nil || a.RescheduleTracker.Events == nil || len(a.RescheduleTracker.Events) == 0 {
|
|
|
|
|
return delayDur
|
|
|
|
|
}
|
|
|
|
|
events := a.RescheduleTracker.Events
|
|
|
|
|
switch policy.DelayFunction {
|
|
|
|
|
case "exponential":
|
|
|
|
|
delayDur = a.RescheduleTracker.Events[len(a.RescheduleTracker.Events)-1].Delay * 2
|
|
|
|
|
case "fibonacci":
|
|
|
|
|
if len(events) >= 2 {
|
|
|
|
|
fibN1Delay := events[len(events)-1].Delay
|
|
|
|
|
fibN2Delay := events[len(events)-2].Delay
|
|
|
|
|
// Handle reset of delay ceiling which should cause
|
|
|
|
|
// a new series to start
|
2018-03-13 15:06:26 +00:00
|
|
|
|
if fibN2Delay == policy.MaxDelay && fibN1Delay == policy.Delay {
|
2018-03-02 00:20:09 +00:00
|
|
|
|
delayDur = fibN1Delay
|
|
|
|
|
} else {
|
|
|
|
|
delayDur = fibN1Delay + fibN2Delay
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return delayDur
|
|
|
|
|
}
|
2018-03-13 15:06:26 +00:00
|
|
|
|
if policy.MaxDelay > 0 && delayDur > policy.MaxDelay {
|
|
|
|
|
delayDur = policy.MaxDelay
|
2018-03-02 00:20:09 +00:00
|
|
|
|
// check if delay needs to be reset
|
2018-03-08 00:02:22 +00:00
|
|
|
|
|
|
|
|
|
lastRescheduleEvent := a.RescheduleTracker.Events[len(a.RescheduleTracker.Events)-1]
|
|
|
|
|
timeDiff := a.LastEventTime().UTC().UnixNano() - lastRescheduleEvent.RescheduleTime
|
|
|
|
|
if timeDiff > delayDur.Nanoseconds() {
|
|
|
|
|
delayDur = policy.Delay
|
2018-03-02 00:20:09 +00:00
|
|
|
|
}
|
2018-03-08 00:02:22 +00:00
|
|
|
|
|
2018-03-02 00:20:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return delayDur
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-22 16:34:24 +00:00
|
|
|
|
// Terminated returns if the allocation is in a terminal state on a client.
|
|
|
|
|
func (a *Allocation) Terminated() bool {
|
|
|
|
|
if a.ClientStatus == AllocClientStatusFailed ||
|
|
|
|
|
a.ClientStatus == AllocClientStatusComplete ||
|
|
|
|
|
a.ClientStatus == AllocClientStatusLost {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-25 00:23:18 +00:00
|
|
|
|
// RanSuccessfully returns whether the client has ran the allocation and all
|
2018-01-04 22:20:32 +00:00
|
|
|
|
// tasks finished successfully. Critically this function returns whether the
|
|
|
|
|
// allocation has ran to completion and not just that the alloc has converged to
|
|
|
|
|
// its desired state. That is to say that a batch allocation must have finished
|
|
|
|
|
// with exit code 0 on all task groups. This doesn't really have meaning on a
|
|
|
|
|
// non-batch allocation because a service and system allocation should not
|
|
|
|
|
// finish.
|
2016-05-25 00:23:18 +00:00
|
|
|
|
func (a *Allocation) RanSuccessfully() bool {
|
2018-01-04 22:20:32 +00:00
|
|
|
|
// Handle the case the client hasn't started the allocation.
|
|
|
|
|
if len(a.TaskStates) == 0 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-14 15:03:08 +00:00
|
|
|
|
// Check to see if all the tasks finished successfully in the allocation
|
2018-01-04 22:20:32 +00:00
|
|
|
|
allSuccess := true
|
|
|
|
|
for _, state := range a.TaskStates {
|
|
|
|
|
allSuccess = allSuccess && state.Successful()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return allSuccess
|
2016-05-25 00:23:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-03 16:59:57 +00:00
|
|
|
|
// ShouldMigrate returns if the allocation needs data migration
|
|
|
|
|
func (a *Allocation) ShouldMigrate() bool {
|
2017-10-12 00:04:09 +00:00
|
|
|
|
if a.PreviousAllocation == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-03 16:59:57 +00:00
|
|
|
|
if a.DesiredStatus == AllocDesiredStatusStop || a.DesiredStatus == AllocDesiredStatusEvict {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-19 18:12:25 +00:00
|
|
|
|
tg := a.Job.LookupTaskGroup(a.TaskGroup)
|
|
|
|
|
|
|
|
|
|
// if the task group is nil or the ephemeral disk block isn't present then
|
|
|
|
|
// we won't migrate
|
|
|
|
|
if tg == nil || tg.EphemeralDisk == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We won't migrate any data is the user hasn't enabled migration or the
|
|
|
|
|
// disk is not marked as sticky
|
|
|
|
|
if !tg.EphemeralDisk.Migrate || !tg.EphemeralDisk.Sticky {
|
2016-10-03 16:59:57 +00:00
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-18 15:30:50 +00:00
|
|
|
|
// SetEventDisplayMessage populates the display message if its not already set,
|
|
|
|
|
// a temporary fix to handle old allocations that don't have it.
|
|
|
|
|
// This method will be removed in a future release.
|
2017-11-17 20:53:26 +00:00
|
|
|
|
func (a *Allocation) SetEventDisplayMessages() {
|
|
|
|
|
setDisplayMsg(a.TaskStates)
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// COMPAT(0.11): Remove in 0.11
|
2019-07-03 18:29:47 +00:00
|
|
|
|
// ComparableResources returns the resources on the allocation
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// handling upgrade paths. After 0.11 calls to this should be replaced with:
|
|
|
|
|
// alloc.AllocatedResources.Comparable()
|
2018-10-04 21:33:09 +00:00
|
|
|
|
func (a *Allocation) ComparableResources() *ComparableResources {
|
2018-10-02 20:36:04 +00:00
|
|
|
|
// ALloc already has 0.9+ behavior
|
|
|
|
|
if a.AllocatedResources != nil {
|
|
|
|
|
return a.AllocatedResources.Comparable()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var resources *Resources
|
|
|
|
|
if a.Resources != nil {
|
|
|
|
|
resources = a.Resources
|
|
|
|
|
} else if a.TaskResources != nil {
|
|
|
|
|
resources = new(Resources)
|
|
|
|
|
resources.Add(a.SharedResources)
|
|
|
|
|
for _, taskResource := range a.TaskResources {
|
|
|
|
|
resources.Add(taskResource)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Upgrade path
|
2018-10-04 21:33:09 +00:00
|
|
|
|
return &ComparableResources{
|
2018-10-02 20:36:04 +00:00
|
|
|
|
Flattened: AllocatedTaskResources{
|
|
|
|
|
Cpu: AllocatedCpuResources{
|
2018-10-16 22:34:32 +00:00
|
|
|
|
CpuShares: int64(resources.CPU),
|
2018-10-02 20:36:04 +00:00
|
|
|
|
},
|
|
|
|
|
Memory: AllocatedMemoryResources{
|
2018-10-16 22:34:32 +00:00
|
|
|
|
MemoryMB: int64(resources.MemoryMB),
|
2018-10-02 20:36:04 +00:00
|
|
|
|
},
|
2018-10-30 14:14:56 +00:00
|
|
|
|
Networks: resources.Networks,
|
2018-10-02 20:36:04 +00:00
|
|
|
|
},
|
|
|
|
|
Shared: AllocatedSharedResources{
|
2018-10-16 22:34:32 +00:00
|
|
|
|
DiskMB: int64(resources.DiskMB),
|
2018-10-02 20:36:04 +00:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-20 00:40:25 +00:00
|
|
|
|
// LookupTask by name from the Allocation. Returns nil if the Job is not set, the
|
|
|
|
|
// TaskGroup does not exist, or the task name cannot be found.
|
|
|
|
|
func (a *Allocation) LookupTask(name string) *Task {
|
|
|
|
|
if a.Job == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tg := a.Job.LookupTaskGroup(a.TaskGroup)
|
|
|
|
|
if tg == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return tg.LookupTask(name)
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-02 23:11:29 +00:00
|
|
|
|
// Stub returns a list stub for the allocation
|
|
|
|
|
func (a *Allocation) Stub() *AllocListStub {
|
|
|
|
|
return &AllocListStub{
|
2019-04-18 15:24:31 +00:00
|
|
|
|
ID: a.ID,
|
|
|
|
|
EvalID: a.EvalID,
|
|
|
|
|
Name: a.Name,
|
|
|
|
|
Namespace: a.Namespace,
|
|
|
|
|
NodeID: a.NodeID,
|
|
|
|
|
NodeName: a.NodeName,
|
|
|
|
|
JobID: a.JobID,
|
|
|
|
|
JobType: a.Job.Type,
|
|
|
|
|
JobVersion: a.Job.Version,
|
|
|
|
|
TaskGroup: a.TaskGroup,
|
|
|
|
|
DesiredStatus: a.DesiredStatus,
|
|
|
|
|
DesiredDescription: a.DesiredDescription,
|
|
|
|
|
ClientStatus: a.ClientStatus,
|
|
|
|
|
ClientDescription: a.ClientDescription,
|
|
|
|
|
DesiredTransition: a.DesiredTransition,
|
|
|
|
|
TaskStates: a.TaskStates,
|
|
|
|
|
DeploymentStatus: a.DeploymentStatus,
|
|
|
|
|
FollowupEvalID: a.FollowupEvalID,
|
|
|
|
|
RescheduleTracker: a.RescheduleTracker,
|
|
|
|
|
PreemptedAllocations: a.PreemptedAllocations,
|
|
|
|
|
PreemptedByAllocation: a.PreemptedByAllocation,
|
|
|
|
|
CreateIndex: a.CreateIndex,
|
|
|
|
|
ModifyIndex: a.ModifyIndex,
|
|
|
|
|
CreateTime: a.CreateTime,
|
|
|
|
|
ModifyTime: a.ModifyTime,
|
2017-06-02 23:11:29 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-11 00:15:04 +00:00
|
|
|
|
// AllocationDiff converts an Allocation type to an AllocationDiff type
|
|
|
|
|
// If at any time, modification are made to AllocationDiff so that an
|
|
|
|
|
// Allocation can no longer be safely converted to AllocationDiff,
|
|
|
|
|
// this method should be changed accordingly.
|
|
|
|
|
func (a *Allocation) AllocationDiff() *AllocationDiff {
|
|
|
|
|
return (*AllocationDiff)(a)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AllocationDiff is another named type for Allocation (to use the same fields),
|
|
|
|
|
// which is used to represent the delta for an Allocation. If you need a method
|
|
|
|
|
// defined on the al
|
|
|
|
|
type AllocationDiff Allocation
|
|
|
|
|
|
2015-09-06 22:34:28 +00:00
|
|
|
|
// AllocListStub is used to return a subset of alloc information
|
|
|
|
|
type AllocListStub struct {
|
2019-04-18 15:24:31 +00:00
|
|
|
|
ID string
|
|
|
|
|
EvalID string
|
|
|
|
|
Name string
|
|
|
|
|
Namespace string
|
|
|
|
|
NodeID string
|
|
|
|
|
NodeName string
|
|
|
|
|
JobID string
|
|
|
|
|
JobType string
|
|
|
|
|
JobVersion uint64
|
|
|
|
|
TaskGroup string
|
|
|
|
|
DesiredStatus string
|
|
|
|
|
DesiredDescription string
|
|
|
|
|
ClientStatus string
|
|
|
|
|
ClientDescription string
|
|
|
|
|
DesiredTransition DesiredTransition
|
|
|
|
|
TaskStates map[string]*TaskState
|
|
|
|
|
DeploymentStatus *AllocDeploymentStatus
|
|
|
|
|
FollowupEvalID string
|
|
|
|
|
RescheduleTracker *RescheduleTracker
|
|
|
|
|
PreemptedAllocations []string
|
|
|
|
|
PreemptedByAllocation string
|
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
|
|
|
|
CreateTime int64
|
|
|
|
|
ModifyTime int64
|
2015-09-06 22:34:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-18 15:30:50 +00:00
|
|
|
|
// SetEventDisplayMessage populates the display message if its not already set,
|
|
|
|
|
// a temporary fix to handle old allocations that don't have it.
|
|
|
|
|
// This method will be removed in a future release.
|
2017-11-17 20:53:26 +00:00
|
|
|
|
func (a *AllocListStub) SetEventDisplayMessages() {
|
|
|
|
|
setDisplayMsg(a.TaskStates)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func setDisplayMsg(taskStates map[string]*TaskState) {
|
|
|
|
|
if taskStates != nil {
|
|
|
|
|
for _, taskState := range taskStates {
|
|
|
|
|
for _, event := range taskState.Events {
|
|
|
|
|
event.PopulateEventDisplayMessage()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-04 00:37:01 +00:00
|
|
|
|
// AllocMetric is used to track various metrics while attempting
|
|
|
|
|
// to make an allocation. These are used to debug a job, or to better
|
|
|
|
|
// understand the pressure within the system.
|
|
|
|
|
type AllocMetric struct {
|
|
|
|
|
// NodesEvaluated is the number of nodes that were evaluated
|
|
|
|
|
NodesEvaluated int
|
|
|
|
|
|
2015-10-27 21:31:14 +00:00
|
|
|
|
// NodesFiltered is the number of nodes filtered due to a constraint
|
2015-07-04 00:37:01 +00:00
|
|
|
|
NodesFiltered int
|
|
|
|
|
|
2016-01-04 20:07:33 +00:00
|
|
|
|
// NodesAvailable is the number of nodes available for evaluation per DC.
|
|
|
|
|
NodesAvailable map[string]int
|
|
|
|
|
|
2015-07-04 00:37:01 +00:00
|
|
|
|
// ClassFiltered is the number of nodes filtered by class
|
|
|
|
|
ClassFiltered map[string]int
|
|
|
|
|
|
|
|
|
|
// ConstraintFiltered is the number of failures caused by constraint
|
|
|
|
|
ConstraintFiltered map[string]int
|
|
|
|
|
|
2015-09-24 06:56:25 +00:00
|
|
|
|
// NodesExhausted is the number of nodes skipped due to being
|
2015-07-04 00:37:01 +00:00
|
|
|
|
// exhausted of at least one resource
|
|
|
|
|
NodesExhausted int
|
|
|
|
|
|
|
|
|
|
// ClassExhausted is the number of nodes exhausted by class
|
|
|
|
|
ClassExhausted map[string]int
|
|
|
|
|
|
2015-09-23 00:37:33 +00:00
|
|
|
|
// DimensionExhausted provides the count by dimension or reason
|
|
|
|
|
DimensionExhausted map[string]int
|
2015-09-13 23:48:01 +00:00
|
|
|
|
|
2017-10-13 21:36:02 +00:00
|
|
|
|
// QuotaExhausted provides the exhausted dimensions
|
|
|
|
|
QuotaExhausted []string
|
|
|
|
|
|
2015-07-04 00:37:01 +00:00
|
|
|
|
// Scores is the scores of the final few nodes remaining
|
|
|
|
|
// for placement. The top score is typically selected.
|
2018-08-08 14:41:56 +00:00
|
|
|
|
// Deprecated: Replaced by ScoreMetaData in Nomad 0.9
|
2015-08-14 04:46:33 +00:00
|
|
|
|
Scores map[string]float64
|
2015-07-04 00:37:01 +00:00
|
|
|
|
|
2018-08-17 23:44:00 +00:00
|
|
|
|
// ScoreMetaData is a slice of top scoring nodes displayed in the CLI
|
|
|
|
|
ScoreMetaData []*NodeScoreMeta
|
|
|
|
|
|
|
|
|
|
// nodeScoreMeta is used to keep scores for a single node id. It is cleared out after
|
|
|
|
|
// we receive normalized score during the last step of the scoring stack.
|
|
|
|
|
nodeScoreMeta *NodeScoreMeta
|
2018-08-08 14:41:56 +00:00
|
|
|
|
|
2018-08-17 23:44:00 +00:00
|
|
|
|
// topScores is used to maintain a heap of the top K nodes with
|
|
|
|
|
// the highest normalized score
|
2018-08-18 00:26:43 +00:00
|
|
|
|
topScores *kheap.ScoreHeap
|
2018-08-08 14:41:56 +00:00
|
|
|
|
|
2015-07-04 00:37:01 +00:00
|
|
|
|
// AllocationTime is a measure of how long the allocation
|
|
|
|
|
// attempt took. This can affect performance and SLAs.
|
|
|
|
|
AllocationTime time.Duration
|
2015-08-16 17:03:21 +00:00
|
|
|
|
|
|
|
|
|
// CoalescedFailures indicates the number of other
|
|
|
|
|
// allocations that were coalesced into this failed allocation.
|
|
|
|
|
// This is to prevent creating many failed allocations for a
|
|
|
|
|
// single task group.
|
|
|
|
|
CoalescedFailures int
|
2015-07-04 00:37:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-11 01:54:43 +00:00
|
|
|
|
func (a *AllocMetric) Copy() *AllocMetric {
|
|
|
|
|
if a == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
na := new(AllocMetric)
|
|
|
|
|
*na = *a
|
2017-01-18 23:55:14 +00:00
|
|
|
|
na.NodesAvailable = helper.CopyMapStringInt(na.NodesAvailable)
|
|
|
|
|
na.ClassFiltered = helper.CopyMapStringInt(na.ClassFiltered)
|
|
|
|
|
na.ConstraintFiltered = helper.CopyMapStringInt(na.ConstraintFiltered)
|
|
|
|
|
na.ClassExhausted = helper.CopyMapStringInt(na.ClassExhausted)
|
|
|
|
|
na.DimensionExhausted = helper.CopyMapStringInt(na.DimensionExhausted)
|
2017-10-13 21:36:02 +00:00
|
|
|
|
na.QuotaExhausted = helper.CopySliceString(na.QuotaExhausted)
|
2017-01-18 23:55:14 +00:00
|
|
|
|
na.Scores = helper.CopyMapStringFloat64(na.Scores)
|
2018-08-17 23:44:00 +00:00
|
|
|
|
na.ScoreMetaData = CopySliceNodeScoreMeta(na.ScoreMetaData)
|
2016-02-11 01:54:43 +00:00
|
|
|
|
return na
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-14 04:46:33 +00:00
|
|
|
|
func (a *AllocMetric) EvaluateNode() {
|
|
|
|
|
a.NodesEvaluated += 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *AllocMetric) FilterNode(node *Node, constraint string) {
|
|
|
|
|
a.NodesFiltered += 1
|
|
|
|
|
if node != nil && node.NodeClass != "" {
|
|
|
|
|
if a.ClassFiltered == nil {
|
|
|
|
|
a.ClassFiltered = make(map[string]int)
|
|
|
|
|
}
|
|
|
|
|
a.ClassFiltered[node.NodeClass] += 1
|
|
|
|
|
}
|
|
|
|
|
if constraint != "" {
|
|
|
|
|
if a.ConstraintFiltered == nil {
|
|
|
|
|
a.ConstraintFiltered = make(map[string]int)
|
|
|
|
|
}
|
|
|
|
|
a.ConstraintFiltered[constraint] += 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-13 23:48:01 +00:00
|
|
|
|
func (a *AllocMetric) ExhaustedNode(node *Node, dimension string) {
|
2015-08-14 04:46:33 +00:00
|
|
|
|
a.NodesExhausted += 1
|
|
|
|
|
if node != nil && node.NodeClass != "" {
|
|
|
|
|
if a.ClassExhausted == nil {
|
|
|
|
|
a.ClassExhausted = make(map[string]int)
|
|
|
|
|
}
|
|
|
|
|
a.ClassExhausted[node.NodeClass] += 1
|
|
|
|
|
}
|
2015-09-13 23:48:01 +00:00
|
|
|
|
if dimension != "" {
|
2015-09-23 00:37:33 +00:00
|
|
|
|
if a.DimensionExhausted == nil {
|
|
|
|
|
a.DimensionExhausted = make(map[string]int)
|
2015-09-13 23:48:01 +00:00
|
|
|
|
}
|
2015-09-23 00:37:33 +00:00
|
|
|
|
a.DimensionExhausted[dimension] += 1
|
2015-09-13 23:48:01 +00:00
|
|
|
|
}
|
2015-08-14 04:46:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-13 21:36:02 +00:00
|
|
|
|
func (a *AllocMetric) ExhaustQuota(dimensions []string) {
|
|
|
|
|
if a.QuotaExhausted == nil {
|
|
|
|
|
a.QuotaExhausted = make([]string, 0, len(dimensions))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.QuotaExhausted = append(a.QuotaExhausted, dimensions...)
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-08 14:41:56 +00:00
|
|
|
|
// ScoreNode is used to gather top K scoring nodes in a heap
|
2015-08-16 16:57:30 +00:00
|
|
|
|
func (a *AllocMetric) ScoreNode(node *Node, name string, score float64) {
|
2018-08-31 16:20:33 +00:00
|
|
|
|
// Create nodeScoreMeta lazily if its the first time or if its a new node
|
|
|
|
|
if a.nodeScoreMeta == nil || a.nodeScoreMeta.NodeID != node.ID {
|
2018-08-17 23:44:00 +00:00
|
|
|
|
a.nodeScoreMeta = &NodeScoreMeta{
|
|
|
|
|
NodeID: node.ID,
|
|
|
|
|
Scores: make(map[string]float64),
|
|
|
|
|
}
|
2018-08-08 14:41:56 +00:00
|
|
|
|
}
|
2018-08-17 23:44:00 +00:00
|
|
|
|
if name == NormScorerName {
|
|
|
|
|
a.nodeScoreMeta.NormScore = score
|
|
|
|
|
// Once we have the normalized score we can push to the heap
|
|
|
|
|
// that tracks top K by normalized score
|
|
|
|
|
|
|
|
|
|
// Create the heap if its not there already
|
|
|
|
|
if a.topScores == nil {
|
2018-08-18 00:26:43 +00:00
|
|
|
|
a.topScores = kheap.NewScoreHeap(MaxRetainedNodeScores)
|
2018-08-17 23:44:00 +00:00
|
|
|
|
}
|
|
|
|
|
heap.Push(a.topScores, a.nodeScoreMeta)
|
|
|
|
|
|
|
|
|
|
// Clear out this entry because its now in the heap
|
|
|
|
|
a.nodeScoreMeta = nil
|
|
|
|
|
} else {
|
|
|
|
|
a.nodeScoreMeta.Scores[name] = score
|
2018-08-08 14:41:56 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PopulateScoreMetaData populates a map of scorer to scoring metadata
|
|
|
|
|
// The map is populated by popping elements from a heap of top K scores
|
|
|
|
|
// maintained per scorer
|
|
|
|
|
func (a *AllocMetric) PopulateScoreMetaData() {
|
2018-08-31 16:20:33 +00:00
|
|
|
|
if a.topScores == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if a.ScoreMetaData == nil {
|
|
|
|
|
a.ScoreMetaData = make([]*NodeScoreMeta, a.topScores.Len())
|
|
|
|
|
}
|
|
|
|
|
heapItems := a.topScores.GetItemsReverse()
|
|
|
|
|
for i, item := range heapItems {
|
|
|
|
|
a.ScoreMetaData[i] = item.(*NodeScoreMeta)
|
2018-08-08 14:41:56 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NodeScoreMeta captures scoring meta data derived from
|
2018-08-17 23:44:00 +00:00
|
|
|
|
// different scoring factors.
|
2018-08-08 14:41:56 +00:00
|
|
|
|
type NodeScoreMeta struct {
|
2018-08-17 23:44:00 +00:00
|
|
|
|
NodeID string
|
|
|
|
|
Scores map[string]float64
|
|
|
|
|
NormScore float64
|
2018-08-08 14:41:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *NodeScoreMeta) Copy() *NodeScoreMeta {
|
|
|
|
|
if s == nil {
|
|
|
|
|
return nil
|
2015-08-14 04:46:33 +00:00
|
|
|
|
}
|
2018-08-08 14:41:56 +00:00
|
|
|
|
ns := new(NodeScoreMeta)
|
|
|
|
|
*ns = *s
|
|
|
|
|
return ns
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *NodeScoreMeta) String() string {
|
2018-08-17 23:44:00 +00:00
|
|
|
|
return fmt.Sprintf("%s %f %v", s.NodeID, s.NormScore, s.Scores)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *NodeScoreMeta) Score() float64 {
|
|
|
|
|
return s.NormScore
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *NodeScoreMeta) Data() interface{} {
|
|
|
|
|
return s
|
2015-08-14 04:46:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-11 18:03:15 +00:00
|
|
|
|
// AllocDeploymentStatus captures the status of the allocation as part of the
|
|
|
|
|
// deployment. This can include things like if the allocation has been marked as
|
2018-03-11 18:12:57 +00:00
|
|
|
|
// healthy.
|
2017-05-11 18:03:15 +00:00
|
|
|
|
type AllocDeploymentStatus struct {
|
|
|
|
|
// Healthy marks whether the allocation has been marked healthy or unhealthy
|
|
|
|
|
// as part of a deployment. It can be unset if it has neither been marked
|
|
|
|
|
// healthy or unhealthy.
|
|
|
|
|
Healthy *bool
|
2017-06-06 21:08:46 +00:00
|
|
|
|
|
2018-03-23 17:56:00 +00:00
|
|
|
|
// Timestamp is the time at which the health status was set.
|
|
|
|
|
Timestamp time.Time
|
|
|
|
|
|
2018-04-19 20:58:06 +00:00
|
|
|
|
// Canary marks whether the allocation is a canary or not. A canary that has
|
|
|
|
|
// been promoted will have this field set to false.
|
|
|
|
|
Canary bool
|
|
|
|
|
|
2017-06-26 21:23:52 +00:00
|
|
|
|
// ModifyIndex is the raft index in which the deployment status was last
|
|
|
|
|
// changed.
|
|
|
|
|
ModifyIndex uint64
|
2017-06-06 21:08:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-28 23:49:56 +00:00
|
|
|
|
// HasHealth returns true if the allocation has its health set.
|
|
|
|
|
func (a *AllocDeploymentStatus) HasHealth() bool {
|
|
|
|
|
return a != nil && a.Healthy != nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-06 21:08:46 +00:00
|
|
|
|
// IsHealthy returns if the allocation is marked as healthy as part of a
|
|
|
|
|
// deployment
|
|
|
|
|
func (a *AllocDeploymentStatus) IsHealthy() bool {
|
|
|
|
|
if a == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return a.Healthy != nil && *a.Healthy
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-26 21:23:52 +00:00
|
|
|
|
// IsUnhealthy returns if the allocation is marked as unhealthy as part of a
|
|
|
|
|
// deployment
|
|
|
|
|
func (a *AllocDeploymentStatus) IsUnhealthy() bool {
|
|
|
|
|
if a == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return a.Healthy != nil && !*a.Healthy
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-23 23:35:25 +00:00
|
|
|
|
// IsCanary returns if the allocation is marked as a canary
|
|
|
|
|
func (a *AllocDeploymentStatus) IsCanary() bool {
|
|
|
|
|
if a == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return a.Canary
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-11 18:03:15 +00:00
|
|
|
|
func (a *AllocDeploymentStatus) Copy() *AllocDeploymentStatus {
|
|
|
|
|
if a == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c := new(AllocDeploymentStatus)
|
2017-06-06 21:08:46 +00:00
|
|
|
|
*c = *a
|
2017-05-11 18:03:15 +00:00
|
|
|
|
|
|
|
|
|
if a.Healthy != nil {
|
|
|
|
|
c.Healthy = helper.BoolToPtr(*a.Healthy)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-23 22:27:13 +00:00
|
|
|
|
const (
|
2016-01-31 00:16:13 +00:00
|
|
|
|
EvalStatusBlocked = "blocked"
|
|
|
|
|
EvalStatusPending = "pending"
|
|
|
|
|
EvalStatusComplete = "complete"
|
|
|
|
|
EvalStatusFailed = "failed"
|
2016-02-01 02:46:45 +00:00
|
|
|
|
EvalStatusCancelled = "canceled"
|
2015-07-23 22:27:13 +00:00
|
|
|
|
)
|
|
|
|
|
|
2015-08-06 18:48:44 +00:00
|
|
|
|
const (
|
2017-07-03 18:08:35 +00:00
|
|
|
|
EvalTriggerJobRegister = "job-register"
|
|
|
|
|
EvalTriggerJobDeregister = "job-deregister"
|
|
|
|
|
EvalTriggerPeriodicJob = "periodic-job"
|
2018-01-24 00:47:00 +00:00
|
|
|
|
EvalTriggerNodeDrain = "node-drain"
|
2017-07-03 18:08:35 +00:00
|
|
|
|
EvalTriggerNodeUpdate = "node-update"
|
2019-04-01 14:21:03 +00:00
|
|
|
|
EvalTriggerAllocStop = "alloc-stop"
|
2017-07-03 18:08:35 +00:00
|
|
|
|
EvalTriggerScheduled = "scheduled"
|
|
|
|
|
EvalTriggerRollingUpdate = "rolling-update"
|
|
|
|
|
EvalTriggerDeploymentWatcher = "deployment-watcher"
|
|
|
|
|
EvalTriggerFailedFollowUp = "failed-follow-up"
|
|
|
|
|
EvalTriggerMaxPlans = "max-plan-attempts"
|
2018-01-17 17:05:22 +00:00
|
|
|
|
EvalTriggerRetryFailedAlloc = "alloc-failure"
|
2018-09-24 21:47:49 +00:00
|
|
|
|
EvalTriggerQueuedAllocs = "queued-allocs"
|
2018-09-21 21:05:00 +00:00
|
|
|
|
EvalTriggerPreemption = "preemption"
|
2015-08-15 22:15:00 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2015-09-07 18:01:29 +00:00
|
|
|
|
// CoreJobEvalGC is used for the garbage collection of evaluations
|
2015-09-24 06:56:25 +00:00
|
|
|
|
// and allocations. We periodically scan evaluations in a terminal state,
|
2015-09-07 18:01:29 +00:00
|
|
|
|
// in which all the corresponding allocations are also terminal. We
|
|
|
|
|
// delete these out of the system to bound the state.
|
2015-08-15 22:15:00 +00:00
|
|
|
|
CoreJobEvalGC = "eval-gc"
|
2015-09-07 18:01:29 +00:00
|
|
|
|
|
|
|
|
|
// CoreJobNodeGC is used for the garbage collection of failed nodes.
|
|
|
|
|
// We periodically scan nodes in a terminal state, and if they have no
|
|
|
|
|
// corresponding allocations we delete these out of the system.
|
|
|
|
|
CoreJobNodeGC = "node-gc"
|
2015-12-15 03:20:57 +00:00
|
|
|
|
|
|
|
|
|
// CoreJobJobGC is used for the garbage collection of eligible jobs. We
|
|
|
|
|
// periodically scan garbage collectible jobs and check if both their
|
|
|
|
|
// evaluations and allocations are terminal. If so, we delete these out of
|
|
|
|
|
// the system.
|
|
|
|
|
CoreJobJobGC = "job-gc"
|
2016-04-08 18:42:02 +00:00
|
|
|
|
|
2017-06-29 19:32:37 +00:00
|
|
|
|
// CoreJobDeploymentGC is used for the garbage collection of eligible
|
|
|
|
|
// deployments. We periodically scan garbage collectible deployments and
|
|
|
|
|
// check if they are terminal. If so, we delete these out of the system.
|
|
|
|
|
CoreJobDeploymentGC = "deployment-gc"
|
|
|
|
|
|
2016-04-08 18:42:02 +00:00
|
|
|
|
// CoreJobForceGC is used to force garbage collection of all GCable objects.
|
|
|
|
|
CoreJobForceGC = "force-gc"
|
2015-08-06 18:48:44 +00:00
|
|
|
|
)
|
|
|
|
|
|
2015-07-23 22:27:13 +00:00
|
|
|
|
// Evaluation is used anytime we need to apply business logic as a result
|
|
|
|
|
// of a change to our desired state (job specification) or the emergent state
|
|
|
|
|
// (registered nodes). When the inputs change, we need to "evaluate" them,
|
|
|
|
|
// potentially taking action (allocation of work) or doing nothing if the state
|
|
|
|
|
// of the world does not require it.
|
|
|
|
|
type Evaluation struct {
|
2019-03-04 09:49:32 +00:00
|
|
|
|
// msgpack omit empty fields during serialization
|
|
|
|
|
_struct bool `codec:",omitempty"` // nolint: structcheck
|
|
|
|
|
|
2017-12-13 17:36:03 +00:00
|
|
|
|
// ID is a randomly generated UUID used for this evaluation. This
|
2015-07-23 22:27:13 +00:00
|
|
|
|
// is assigned upon the creation of the evaluation.
|
|
|
|
|
ID string
|
|
|
|
|
|
2017-09-07 23:56:15 +00:00
|
|
|
|
// Namespace is the namespace the evaluation is created in
|
|
|
|
|
Namespace string
|
|
|
|
|
|
2015-07-24 00:31:08 +00:00
|
|
|
|
// Priority is used to control scheduling importance and if this job
|
|
|
|
|
// can preempt other jobs.
|
|
|
|
|
Priority int
|
|
|
|
|
|
|
|
|
|
// Type is used to control which schedulers are available to handle
|
|
|
|
|
// this evaluation.
|
|
|
|
|
Type string
|
|
|
|
|
|
2015-07-24 05:30:08 +00:00
|
|
|
|
// TriggeredBy is used to give some insight into why this Eval
|
|
|
|
|
// was created. (Job change, node failure, alloc failure, etc).
|
|
|
|
|
TriggeredBy string
|
|
|
|
|
|
2015-09-24 06:56:25 +00:00
|
|
|
|
// JobID is the job this evaluation is scoped to. Evaluations cannot
|
2015-08-06 00:55:15 +00:00
|
|
|
|
// be run in parallel for a given JobID, so we serialize on this.
|
|
|
|
|
JobID string
|
|
|
|
|
|
2015-08-06 18:48:44 +00:00
|
|
|
|
// JobModifyIndex is the modify index of the job at the time
|
|
|
|
|
// the evaluation was created
|
|
|
|
|
JobModifyIndex uint64
|
|
|
|
|
|
2015-08-06 23:39:20 +00:00
|
|
|
|
// NodeID is the node that was affected triggering the evaluation.
|
|
|
|
|
NodeID string
|
|
|
|
|
|
|
|
|
|
// NodeModifyIndex is the modify index of the node at the time
|
|
|
|
|
// the evaluation was created
|
|
|
|
|
NodeModifyIndex uint64
|
|
|
|
|
|
2017-06-26 21:23:52 +00:00
|
|
|
|
// DeploymentID is the ID of the deployment that triggered the evaluation.
|
|
|
|
|
DeploymentID string
|
|
|
|
|
|
2015-07-23 22:27:13 +00:00
|
|
|
|
// Status of the evaluation
|
|
|
|
|
Status string
|
|
|
|
|
|
2015-08-15 20:08:06 +00:00
|
|
|
|
// StatusDescription is meant to provide more human useful information
|
|
|
|
|
StatusDescription string
|
|
|
|
|
|
2015-09-07 19:47:48 +00:00
|
|
|
|
// Wait is a minimum wait time for running the eval. This is used to
|
2018-03-02 00:20:09 +00:00
|
|
|
|
// support a rolling upgrade in versions prior to 0.7.0
|
|
|
|
|
// Deprecated
|
2015-09-07 19:47:48 +00:00
|
|
|
|
Wait time.Duration
|
|
|
|
|
|
2018-03-02 00:20:09 +00:00
|
|
|
|
// WaitUntil is the time when this eval should be run. This is used to
|
|
|
|
|
// supported delayed rescheduling of failed allocations
|
|
|
|
|
WaitUntil time.Time
|
|
|
|
|
|
2015-09-07 19:47:48 +00:00
|
|
|
|
// NextEval is the evaluation ID for the eval created to do a followup.
|
2019-02-22 18:22:52 +00:00
|
|
|
|
// This is used to support rolling upgrades and failed-follow-up evals, where
|
|
|
|
|
// we need a chain of evaluations.
|
2015-09-07 19:47:48 +00:00
|
|
|
|
NextEval string
|
|
|
|
|
|
|
|
|
|
// PreviousEval is the evaluation ID for the eval creating this one to do a followup.
|
2019-02-22 18:22:52 +00:00
|
|
|
|
// This is used to support rolling upgrades and failed-follow-up evals, where
|
|
|
|
|
// we need a chain of evaluations.
|
2015-09-07 19:47:48 +00:00
|
|
|
|
PreviousEval string
|
|
|
|
|
|
2016-05-25 01:12:59 +00:00
|
|
|
|
// BlockedEval is the evaluation ID for a created blocked eval. A
|
2016-05-19 20:09:52 +00:00
|
|
|
|
// blocked eval will be created if all allocations could not be placed due
|
|
|
|
|
// to constraints or lacking resources.
|
2016-05-25 01:12:59 +00:00
|
|
|
|
BlockedEval string
|
2016-05-19 20:09:52 +00:00
|
|
|
|
|
2016-05-19 01:11:40 +00:00
|
|
|
|
// FailedTGAllocs are task groups which have allocations that could not be
|
|
|
|
|
// made, but the metrics are persisted so that the user can use the feedback
|
|
|
|
|
// to determine the cause.
|
|
|
|
|
FailedTGAllocs map[string]*AllocMetric
|
|
|
|
|
|
2016-05-15 16:41:34 +00:00
|
|
|
|
// ClassEligibility tracks computed node classes that have been explicitly
|
2016-01-30 01:46:44 +00:00
|
|
|
|
// marked as eligible or ineligible.
|
|
|
|
|
ClassEligibility map[string]bool
|
2016-01-28 21:43:48 +00:00
|
|
|
|
|
2017-10-13 21:36:02 +00:00
|
|
|
|
// QuotaLimitReached marks whether a quota limit was reached for the
|
|
|
|
|
// evaluation.
|
|
|
|
|
QuotaLimitReached string
|
|
|
|
|
|
2016-01-28 21:43:48 +00:00
|
|
|
|
// EscapedComputedClass marks whether the job has constraints that are not
|
|
|
|
|
// captured by computed node classes.
|
2016-01-30 01:46:44 +00:00
|
|
|
|
EscapedComputedClass bool
|
2016-01-28 21:43:48 +00:00
|
|
|
|
|
2016-05-05 18:21:58 +00:00
|
|
|
|
// AnnotatePlan triggers the scheduler to provide additional annotations
|
|
|
|
|
// during the evaluation. This should not be set during normal operations.
|
|
|
|
|
AnnotatePlan bool
|
|
|
|
|
|
2017-03-09 20:37:41 +00:00
|
|
|
|
// QueuedAllocations is the number of unplaced allocations at the time the
|
|
|
|
|
// evaluation was processed. The map is keyed by Task Group names.
|
|
|
|
|
QueuedAllocations map[string]int
|
|
|
|
|
|
2017-10-23 22:12:45 +00:00
|
|
|
|
// LeaderACL provides the ACL token to when issuing RPCs back to the
|
2017-10-23 22:04:00 +00:00
|
|
|
|
// leader. This will be a valid management token as long as the leader is
|
|
|
|
|
// active. This should not ever be exposed via the API.
|
|
|
|
|
LeaderACL string
|
|
|
|
|
|
2016-05-21 01:07:10 +00:00
|
|
|
|
// SnapshotIndex is the Raft index of the snapshot used to process the
|
2018-09-24 20:27:41 +00:00
|
|
|
|
// evaluation. The index will either be set when it has gone through the
|
|
|
|
|
// scheduler or if a blocked evaluation is being created. The index is set
|
|
|
|
|
// in this case so we can determine if an early unblocking is required since
|
|
|
|
|
// capacity has changed since the evaluation was created. This can result in
|
|
|
|
|
// the SnapshotIndex being less than the CreateIndex.
|
2016-05-21 01:07:10 +00:00
|
|
|
|
SnapshotIndex uint64
|
|
|
|
|
|
2015-07-23 22:27:13 +00:00
|
|
|
|
// Raft Indexes
|
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
2019-08-07 16:50:35 +00:00
|
|
|
|
|
|
|
|
|
CreateTime int64
|
|
|
|
|
ModifyTime int64
|
2015-07-23 22:27:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-15 23:07:22 +00:00
|
|
|
|
// TerminalStatus returns if the current status is terminal and
|
|
|
|
|
// will no longer transition.
|
|
|
|
|
func (e *Evaluation) TerminalStatus() bool {
|
|
|
|
|
switch e.Status {
|
2016-01-31 00:16:13 +00:00
|
|
|
|
case EvalStatusComplete, EvalStatusFailed, EvalStatusCancelled:
|
2015-08-15 23:07:22 +00:00
|
|
|
|
return true
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-14 00:11:20 +00:00
|
|
|
|
func (e *Evaluation) GoString() string {
|
2017-09-07 23:56:15 +00:00
|
|
|
|
return fmt.Sprintf("<Eval %q JobID: %q Namespace: %q>", e.ID, e.JobID, e.Namespace)
|
2015-08-14 00:11:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-15 21:16:40 +00:00
|
|
|
|
func (e *Evaluation) Copy() *Evaluation {
|
2016-02-11 01:54:43 +00:00
|
|
|
|
if e == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2015-08-15 21:16:40 +00:00
|
|
|
|
ne := new(Evaluation)
|
|
|
|
|
*ne = *e
|
2016-05-19 01:11:40 +00:00
|
|
|
|
|
|
|
|
|
// Copy ClassEligibility
|
|
|
|
|
if e.ClassEligibility != nil {
|
|
|
|
|
classes := make(map[string]bool, len(e.ClassEligibility))
|
|
|
|
|
for class, elig := range e.ClassEligibility {
|
|
|
|
|
classes[class] = elig
|
|
|
|
|
}
|
|
|
|
|
ne.ClassEligibility = classes
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy FailedTGAllocs
|
|
|
|
|
if e.FailedTGAllocs != nil {
|
|
|
|
|
failedTGs := make(map[string]*AllocMetric, len(e.FailedTGAllocs))
|
|
|
|
|
for tg, metric := range e.FailedTGAllocs {
|
|
|
|
|
failedTGs[tg] = metric.Copy()
|
|
|
|
|
}
|
|
|
|
|
ne.FailedTGAllocs = failedTGs
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-18 22:04:05 +00:00
|
|
|
|
// Copy queued allocations
|
|
|
|
|
if e.QueuedAllocations != nil {
|
|
|
|
|
queuedAllocations := make(map[string]int, len(e.QueuedAllocations))
|
|
|
|
|
for tg, num := range e.QueuedAllocations {
|
|
|
|
|
queuedAllocations[tg] = num
|
|
|
|
|
}
|
|
|
|
|
ne.QueuedAllocations = queuedAllocations
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-15 21:16:40 +00:00
|
|
|
|
return ne
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-29 23:31:32 +00:00
|
|
|
|
// ShouldEnqueue checks if a given evaluation should be enqueued into the
|
|
|
|
|
// eval_broker
|
2015-08-06 18:28:55 +00:00
|
|
|
|
func (e *Evaluation) ShouldEnqueue() bool {
|
|
|
|
|
switch e.Status {
|
|
|
|
|
case EvalStatusPending:
|
|
|
|
|
return true
|
2016-01-31 00:16:13 +00:00
|
|
|
|
case EvalStatusComplete, EvalStatusFailed, EvalStatusBlocked, EvalStatusCancelled:
|
2016-01-29 23:31:32 +00:00
|
|
|
|
return false
|
|
|
|
|
default:
|
|
|
|
|
panic(fmt.Sprintf("unhandled evaluation (%s) status %s", e.ID, e.Status))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ShouldBlock checks if a given evaluation should be entered into the blocked
|
|
|
|
|
// eval tracker.
|
|
|
|
|
func (e *Evaluation) ShouldBlock() bool {
|
|
|
|
|
switch e.Status {
|
|
|
|
|
case EvalStatusBlocked:
|
|
|
|
|
return true
|
2016-01-31 00:16:13 +00:00
|
|
|
|
case EvalStatusComplete, EvalStatusFailed, EvalStatusPending, EvalStatusCancelled:
|
2015-08-06 18:28:55 +00:00
|
|
|
|
return false
|
|
|
|
|
default:
|
|
|
|
|
panic(fmt.Sprintf("unhandled evaluation (%s) status %s", e.ID, e.Status))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-11 23:34:06 +00:00
|
|
|
|
// MakePlan is used to make a plan from the given evaluation
|
|
|
|
|
// for a given Job
|
2019-03-08 12:48:12 +00:00
|
|
|
|
func (e *Evaluation) MakePlan(j *Job) *Plan {
|
2015-08-11 23:34:06 +00:00
|
|
|
|
p := &Plan{
|
2018-09-10 17:38:36 +00:00
|
|
|
|
EvalID: e.ID,
|
|
|
|
|
Priority: e.Priority,
|
|
|
|
|
Job: j,
|
|
|
|
|
NodeUpdate: make(map[string][]*Allocation),
|
|
|
|
|
NodeAllocation: make(map[string][]*Allocation),
|
|
|
|
|
NodePreemptions: make(map[string][]*Allocation),
|
2015-08-11 23:34:06 +00:00
|
|
|
|
}
|
2015-08-13 23:29:28 +00:00
|
|
|
|
if j != nil {
|
|
|
|
|
p.AllAtOnce = j.AllAtOnce
|
|
|
|
|
}
|
2015-08-11 23:34:06 +00:00
|
|
|
|
return p
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-07 22:08:50 +00:00
|
|
|
|
// NextRollingEval creates an evaluation to followup this eval for rolling updates
|
|
|
|
|
func (e *Evaluation) NextRollingEval(wait time.Duration) *Evaluation {
|
2019-08-07 16:50:35 +00:00
|
|
|
|
now := time.Now().UTC().UnixNano()
|
2015-09-07 22:08:50 +00:00
|
|
|
|
return &Evaluation{
|
2017-09-29 16:58:48 +00:00
|
|
|
|
ID: uuid.Generate(),
|
2017-09-07 23:56:15 +00:00
|
|
|
|
Namespace: e.Namespace,
|
2015-09-07 22:08:50 +00:00
|
|
|
|
Priority: e.Priority,
|
|
|
|
|
Type: e.Type,
|
|
|
|
|
TriggeredBy: EvalTriggerRollingUpdate,
|
|
|
|
|
JobID: e.JobID,
|
|
|
|
|
JobModifyIndex: e.JobModifyIndex,
|
|
|
|
|
Status: EvalStatusPending,
|
|
|
|
|
Wait: wait,
|
|
|
|
|
PreviousEval: e.ID,
|
2019-08-07 16:50:35 +00:00
|
|
|
|
CreateTime: now,
|
|
|
|
|
ModifyTime: now,
|
2015-09-07 22:08:50 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-25 01:12:59 +00:00
|
|
|
|
// CreateBlockedEval creates a blocked evaluation to followup this eval to place any
|
2016-05-15 16:41:34 +00:00
|
|
|
|
// failed allocations. It takes the classes marked explicitly eligible or
|
2017-10-13 21:36:02 +00:00
|
|
|
|
// ineligible, whether the job has escaped computed node classes and whether the
|
|
|
|
|
// quota limit was reached.
|
|
|
|
|
func (e *Evaluation) CreateBlockedEval(classEligibility map[string]bool,
|
|
|
|
|
escaped bool, quotaReached string) *Evaluation {
|
2019-08-07 16:50:35 +00:00
|
|
|
|
now := time.Now().UTC().UnixNano()
|
2016-01-28 21:43:48 +00:00
|
|
|
|
return &Evaluation{
|
2017-09-29 16:58:48 +00:00
|
|
|
|
ID: uuid.Generate(),
|
2017-09-07 23:56:15 +00:00
|
|
|
|
Namespace: e.Namespace,
|
2016-01-28 21:43:48 +00:00
|
|
|
|
Priority: e.Priority,
|
|
|
|
|
Type: e.Type,
|
2018-09-24 21:47:49 +00:00
|
|
|
|
TriggeredBy: EvalTriggerQueuedAllocs,
|
2016-01-28 21:43:48 +00:00
|
|
|
|
JobID: e.JobID,
|
|
|
|
|
JobModifyIndex: e.JobModifyIndex,
|
|
|
|
|
Status: EvalStatusBlocked,
|
|
|
|
|
PreviousEval: e.ID,
|
2016-01-30 01:46:44 +00:00
|
|
|
|
ClassEligibility: classEligibility,
|
2016-01-28 21:43:48 +00:00
|
|
|
|
EscapedComputedClass: escaped,
|
2017-10-13 21:36:02 +00:00
|
|
|
|
QuotaLimitReached: quotaReached,
|
2019-08-07 16:50:35 +00:00
|
|
|
|
CreateTime: now,
|
|
|
|
|
ModifyTime: now,
|
2016-01-28 21:43:48 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-12 21:47:59 +00:00
|
|
|
|
// CreateFailedFollowUpEval creates a follow up evaluation when the current one
|
2017-08-07 21:13:05 +00:00
|
|
|
|
// has been marked as failed because it has hit the delivery limit and will not
|
2019-02-22 18:22:52 +00:00
|
|
|
|
// be retried by the eval_broker. Callers should copy the created eval's ID to
|
|
|
|
|
// into the old eval's NextEval field.
|
2017-04-12 21:47:59 +00:00
|
|
|
|
func (e *Evaluation) CreateFailedFollowUpEval(wait time.Duration) *Evaluation {
|
2019-08-07 16:50:35 +00:00
|
|
|
|
now := time.Now().UTC().UnixNano()
|
2017-04-12 21:47:59 +00:00
|
|
|
|
return &Evaluation{
|
2017-09-29 16:58:48 +00:00
|
|
|
|
ID: uuid.Generate(),
|
2017-09-07 23:56:15 +00:00
|
|
|
|
Namespace: e.Namespace,
|
2017-04-12 21:47:59 +00:00
|
|
|
|
Priority: e.Priority,
|
|
|
|
|
Type: e.Type,
|
|
|
|
|
TriggeredBy: EvalTriggerFailedFollowUp,
|
|
|
|
|
JobID: e.JobID,
|
|
|
|
|
JobModifyIndex: e.JobModifyIndex,
|
|
|
|
|
Status: EvalStatusPending,
|
|
|
|
|
Wait: wait,
|
|
|
|
|
PreviousEval: e.ID,
|
2019-08-07 16:50:35 +00:00
|
|
|
|
CreateTime: now,
|
|
|
|
|
ModifyTime: now,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UpdateModifyTime takes into account that clocks on different servers may be
|
|
|
|
|
// slightly out of sync. Even in case of a leader change, this method will
|
|
|
|
|
// guarantee that ModifyTime will always be after CreateTime.
|
|
|
|
|
func (e *Evaluation) UpdateModifyTime() {
|
|
|
|
|
now := time.Now().UTC().UnixNano()
|
|
|
|
|
if now <= e.CreateTime {
|
|
|
|
|
e.ModifyTime = e.CreateTime + 1
|
|
|
|
|
} else {
|
|
|
|
|
e.ModifyTime = now
|
2017-04-12 21:47:59 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-27 21:59:16 +00:00
|
|
|
|
// Plan is used to submit a commit plan for task allocations. These
|
|
|
|
|
// are submitted to the leader which verifies that resources have
|
2018-03-11 17:35:27 +00:00
|
|
|
|
// not been overcommitted before admitting the plan.
|
2015-07-27 21:59:16 +00:00
|
|
|
|
type Plan struct {
|
2019-03-04 09:49:32 +00:00
|
|
|
|
// msgpack omit empty fields during serialization
|
|
|
|
|
_struct bool `codec:",omitempty"` // nolint: structcheck
|
|
|
|
|
|
2015-07-29 00:49:45 +00:00
|
|
|
|
// EvalID is the evaluation ID this plan is associated with
|
|
|
|
|
EvalID string
|
|
|
|
|
|
2015-08-12 22:44:36 +00:00
|
|
|
|
// EvalToken is used to prevent a split-brain processing of
|
|
|
|
|
// an evaluation. There should only be a single scheduler running
|
|
|
|
|
// an Eval at a time, but this could be violated after a leadership
|
|
|
|
|
// transition. This unique token is used to reject plans that are
|
|
|
|
|
// being submitted from a different leader.
|
|
|
|
|
EvalToken string
|
|
|
|
|
|
2015-07-27 21:59:16 +00:00
|
|
|
|
// Priority is the priority of the upstream job
|
|
|
|
|
Priority int
|
|
|
|
|
|
2015-07-29 00:49:45 +00:00
|
|
|
|
// AllAtOnce is used to control if incremental scheduling of task groups
|
|
|
|
|
// is allowed or if we must do a gang scheduling of the entire job.
|
|
|
|
|
// If this is false, a plan may be partially applied. Otherwise, the
|
|
|
|
|
// entire plan must be able to make progress.
|
|
|
|
|
AllAtOnce bool
|
|
|
|
|
|
2016-02-21 19:31:27 +00:00
|
|
|
|
// Job is the parent job of all the allocations in the Plan.
|
|
|
|
|
// Since a Plan only involves a single Job, we can reduce the size
|
|
|
|
|
// of the plan by only including it once.
|
|
|
|
|
Job *Job
|
|
|
|
|
|
2015-08-25 23:52:56 +00:00
|
|
|
|
// NodeUpdate contains all the allocations for each node. For each node,
|
|
|
|
|
// this is a list of the allocations to update to either stop or evict.
|
|
|
|
|
NodeUpdate map[string][]*Allocation
|
2015-07-29 00:49:45 +00:00
|
|
|
|
|
|
|
|
|
// NodeAllocation contains all the allocations for each node.
|
|
|
|
|
// The evicts must be considered prior to the allocations.
|
2015-08-04 23:32:46 +00:00
|
|
|
|
NodeAllocation map[string][]*Allocation
|
2015-08-15 20:27:42 +00:00
|
|
|
|
|
2016-05-05 18:21:58 +00:00
|
|
|
|
// Annotations contains annotations by the scheduler to be used by operators
|
|
|
|
|
// to understand the decisions made by the scheduler.
|
|
|
|
|
Annotations *PlanAnnotations
|
2017-04-21 23:37:10 +00:00
|
|
|
|
|
2017-07-04 20:31:01 +00:00
|
|
|
|
// Deployment is the deployment created or updated by the scheduler that
|
|
|
|
|
// should be applied by the planner.
|
|
|
|
|
Deployment *Deployment
|
2017-05-11 19:49:04 +00:00
|
|
|
|
|
|
|
|
|
// DeploymentUpdates is a set of status updates to apply to the given
|
|
|
|
|
// deployments. This allows the scheduler to cancel any unneeded deployment
|
|
|
|
|
// because the job is stopped or the update block is removed.
|
|
|
|
|
DeploymentUpdates []*DeploymentStatusUpdate
|
2018-09-10 17:38:36 +00:00
|
|
|
|
|
|
|
|
|
// NodePreemptions is a map from node id to a set of allocations from other
|
|
|
|
|
// lower priority jobs that are preempted. Preempted allocations are marked
|
|
|
|
|
// as evicted.
|
|
|
|
|
NodePreemptions map[string][]*Allocation
|
2019-06-06 22:44:47 +00:00
|
|
|
|
|
|
|
|
|
// SnapshotIndex is the Raft index of the snapshot used to create the
|
|
|
|
|
// Plan. The leader will wait to evaluate the plan until its StateStore
|
|
|
|
|
// has reached at least this index.
|
|
|
|
|
SnapshotIndex uint64
|
2015-07-27 21:59:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-04 09:49:32 +00:00
|
|
|
|
// AppendStoppedAlloc marks an allocation to be stopped. The clientStatus of the
|
2016-08-03 22:45:42 +00:00
|
|
|
|
// allocation may be optionally set by passing in a non-empty value.
|
2019-03-04 09:49:32 +00:00
|
|
|
|
func (p *Plan) AppendStoppedAlloc(alloc *Allocation, desiredDesc, clientStatus string) {
|
2015-08-26 00:06:06 +00:00
|
|
|
|
newAlloc := new(Allocation)
|
|
|
|
|
*newAlloc = *alloc
|
2016-02-24 22:50:59 +00:00
|
|
|
|
|
|
|
|
|
// If the job is not set in the plan we are deregistering a job so we
|
|
|
|
|
// extract the job from the allocation.
|
|
|
|
|
if p.Job == nil && newAlloc.Job != nil {
|
|
|
|
|
p.Job = newAlloc.Job
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Normalize the job
|
|
|
|
|
newAlloc.Job = nil
|
2016-03-01 22:09:25 +00:00
|
|
|
|
|
|
|
|
|
// Strip the resources as it can be rebuilt.
|
|
|
|
|
newAlloc.Resources = nil
|
|
|
|
|
|
2019-03-04 09:49:32 +00:00
|
|
|
|
newAlloc.DesiredStatus = AllocDesiredStatusStop
|
2016-08-03 22:45:42 +00:00
|
|
|
|
newAlloc.DesiredDescription = desiredDesc
|
|
|
|
|
|
|
|
|
|
if clientStatus != "" {
|
|
|
|
|
newAlloc.ClientStatus = clientStatus
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-13 21:02:39 +00:00
|
|
|
|
node := alloc.NodeID
|
2015-08-25 23:52:56 +00:00
|
|
|
|
existing := p.NodeUpdate[node]
|
2015-08-26 00:06:06 +00:00
|
|
|
|
p.NodeUpdate[node] = append(existing, newAlloc)
|
2015-08-13 21:02:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-09-21 21:05:00 +00:00
|
|
|
|
// AppendPreemptedAlloc is used to append an allocation that's being preempted to the plan.
|
|
|
|
|
// To minimize the size of the plan, this only sets a minimal set of fields in the allocation
|
2019-03-04 09:49:32 +00:00
|
|
|
|
func (p *Plan) AppendPreemptedAlloc(alloc *Allocation, preemptingAllocID string) {
|
2018-09-21 21:05:00 +00:00
|
|
|
|
newAlloc := &Allocation{}
|
|
|
|
|
newAlloc.ID = alloc.ID
|
|
|
|
|
newAlloc.JobID = alloc.JobID
|
|
|
|
|
newAlloc.Namespace = alloc.Namespace
|
2019-03-04 09:49:32 +00:00
|
|
|
|
newAlloc.DesiredStatus = AllocDesiredStatusEvict
|
2018-09-10 17:38:36 +00:00
|
|
|
|
newAlloc.PreemptedByAllocation = preemptingAllocID
|
|
|
|
|
|
|
|
|
|
desiredDesc := fmt.Sprintf("Preempted by alloc ID %v", preemptingAllocID)
|
|
|
|
|
newAlloc.DesiredDescription = desiredDesc
|
|
|
|
|
|
2018-09-21 21:05:00 +00:00
|
|
|
|
// TaskResources are needed by the plan applier to check if allocations fit
|
|
|
|
|
// after removing preempted allocations
|
2018-10-18 04:49:37 +00:00
|
|
|
|
if alloc.AllocatedResources != nil {
|
|
|
|
|
newAlloc.AllocatedResources = alloc.AllocatedResources
|
|
|
|
|
} else {
|
2018-11-01 22:05:17 +00:00
|
|
|
|
// COMPAT Remove in version 0.11
|
2018-10-18 04:49:37 +00:00
|
|
|
|
newAlloc.TaskResources = alloc.TaskResources
|
|
|
|
|
newAlloc.SharedResources = alloc.SharedResources
|
|
|
|
|
}
|
2018-09-21 21:05:00 +00:00
|
|
|
|
|
2018-09-10 17:38:36 +00:00
|
|
|
|
// Append this alloc to slice for this node
|
2018-10-18 04:49:37 +00:00
|
|
|
|
node := alloc.NodeID
|
2018-09-10 17:38:36 +00:00
|
|
|
|
existing := p.NodePreemptions[node]
|
|
|
|
|
p.NodePreemptions[node] = append(existing, newAlloc)
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-07 19:00:34 +00:00
|
|
|
|
func (p *Plan) PopUpdate(alloc *Allocation) {
|
|
|
|
|
existing := p.NodeUpdate[alloc.NodeID]
|
|
|
|
|
n := len(existing)
|
|
|
|
|
if n > 0 && existing[n-1].ID == alloc.ID {
|
|
|
|
|
existing = existing[:n-1]
|
2015-09-07 19:05:18 +00:00
|
|
|
|
if len(existing) > 0 {
|
|
|
|
|
p.NodeUpdate[alloc.NodeID] = existing
|
|
|
|
|
} else {
|
|
|
|
|
delete(p.NodeUpdate, alloc.NodeID)
|
|
|
|
|
}
|
2015-09-07 19:00:34 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-13 21:02:39 +00:00
|
|
|
|
func (p *Plan) AppendAlloc(alloc *Allocation) {
|
|
|
|
|
node := alloc.NodeID
|
|
|
|
|
existing := p.NodeAllocation[node]
|
2018-09-21 23:55:35 +00:00
|
|
|
|
|
|
|
|
|
// Normalize the job
|
|
|
|
|
alloc.Job = nil
|
|
|
|
|
|
2015-08-13 21:02:39 +00:00
|
|
|
|
p.NodeAllocation[node] = append(existing, alloc)
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-14 01:16:32 +00:00
|
|
|
|
// IsNoOp checks if this plan would do nothing
|
|
|
|
|
func (p *Plan) IsNoOp() bool {
|
2017-05-18 19:36:04 +00:00
|
|
|
|
return len(p.NodeUpdate) == 0 &&
|
|
|
|
|
len(p.NodeAllocation) == 0 &&
|
2017-07-04 20:31:01 +00:00
|
|
|
|
p.Deployment == nil &&
|
2017-05-18 19:36:04 +00:00
|
|
|
|
len(p.DeploymentUpdates) == 0
|
2015-08-14 01:16:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-11 00:15:04 +00:00
|
|
|
|
// NormalizeAllocations normalizes allocations to remove fields that can
|
|
|
|
|
// be fetched from the MemDB instead of sending over the wire
|
2019-03-04 09:49:32 +00:00
|
|
|
|
func (p *Plan) NormalizeAllocations() {
|
2019-03-08 12:48:12 +00:00
|
|
|
|
for _, allocs := range p.NodeUpdate {
|
|
|
|
|
for i, alloc := range allocs {
|
|
|
|
|
allocs[i] = &Allocation{
|
|
|
|
|
ID: alloc.ID,
|
|
|
|
|
DesiredDescription: alloc.DesiredDescription,
|
|
|
|
|
ClientStatus: alloc.ClientStatus,
|
2019-03-04 09:49:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-08 12:48:12 +00:00
|
|
|
|
}
|
2019-03-04 09:49:32 +00:00
|
|
|
|
|
2019-03-08 12:48:12 +00:00
|
|
|
|
for _, allocs := range p.NodePreemptions {
|
|
|
|
|
for i, alloc := range allocs {
|
|
|
|
|
allocs[i] = &Allocation{
|
|
|
|
|
ID: alloc.ID,
|
|
|
|
|
PreemptedByAllocation: alloc.PreemptedByAllocation,
|
2019-03-04 09:49:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-27 22:31:49 +00:00
|
|
|
|
// PlanResult is the result of a plan submitted to the leader.
|
2015-07-27 21:59:16 +00:00
|
|
|
|
type PlanResult struct {
|
2015-08-25 23:52:56 +00:00
|
|
|
|
// NodeUpdate contains all the updates that were committed.
|
|
|
|
|
NodeUpdate map[string][]*Allocation
|
2015-07-29 00:49:45 +00:00
|
|
|
|
|
|
|
|
|
// NodeAllocation contains all the allocations that were committed.
|
2015-08-04 23:32:46 +00:00
|
|
|
|
NodeAllocation map[string][]*Allocation
|
2015-07-29 00:49:45 +00:00
|
|
|
|
|
2017-07-06 04:26:04 +00:00
|
|
|
|
// Deployment is the deployment that was committed.
|
|
|
|
|
Deployment *Deployment
|
|
|
|
|
|
2017-08-07 21:13:05 +00:00
|
|
|
|
// DeploymentUpdates is the set of deployment updates that were committed.
|
2017-07-06 04:26:04 +00:00
|
|
|
|
DeploymentUpdates []*DeploymentStatusUpdate
|
|
|
|
|
|
2018-09-10 17:38:36 +00:00
|
|
|
|
// NodePreemptions is a map from node id to a set of allocations from other
|
|
|
|
|
// lower priority jobs that are preempted. Preempted allocations are marked
|
|
|
|
|
// as stopped.
|
|
|
|
|
NodePreemptions map[string][]*Allocation
|
|
|
|
|
|
2015-07-29 00:49:45 +00:00
|
|
|
|
// RefreshIndex is the index the worker should refresh state up to.
|
|
|
|
|
// This allows all evictions and allocations to be materialized.
|
|
|
|
|
// If any allocations were rejected due to stale data (node state,
|
|
|
|
|
// over committed) this can be used to force a worker refresh.
|
2015-07-28 23:36:15 +00:00
|
|
|
|
RefreshIndex uint64
|
|
|
|
|
|
2015-07-29 00:49:45 +00:00
|
|
|
|
// AllocIndex is the Raft index in which the evictions and
|
|
|
|
|
// allocations took place. This is used for the write index.
|
2015-07-27 22:31:49 +00:00
|
|
|
|
AllocIndex uint64
|
2015-07-27 21:59:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-26 00:36:52 +00:00
|
|
|
|
// IsNoOp checks if this plan result would do nothing
|
|
|
|
|
func (p *PlanResult) IsNoOp() bool {
|
2017-07-06 04:26:04 +00:00
|
|
|
|
return len(p.NodeUpdate) == 0 && len(p.NodeAllocation) == 0 &&
|
|
|
|
|
len(p.DeploymentUpdates) == 0 && p.Deployment == nil
|
2015-08-26 00:36:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-13 22:17:24 +00:00
|
|
|
|
// FullCommit is used to check if all the allocations in a plan
|
|
|
|
|
// were committed as part of the result. Returns if there was
|
|
|
|
|
// a match, and the number of expected and actual allocations.
|
|
|
|
|
func (p *PlanResult) FullCommit(plan *Plan) (bool, int, int) {
|
|
|
|
|
expected := 0
|
|
|
|
|
actual := 0
|
|
|
|
|
for name, allocList := range plan.NodeAllocation {
|
|
|
|
|
didAlloc, _ := p.NodeAllocation[name]
|
|
|
|
|
expected += len(allocList)
|
|
|
|
|
actual += len(didAlloc)
|
|
|
|
|
}
|
|
|
|
|
return actual == expected, expected, actual
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-05 18:21:58 +00:00
|
|
|
|
// PlanAnnotations holds annotations made by the scheduler to give further debug
|
|
|
|
|
// information to operators.
|
|
|
|
|
type PlanAnnotations struct {
|
|
|
|
|
// DesiredTGUpdates is the set of desired updates per task group.
|
|
|
|
|
DesiredTGUpdates map[string]*DesiredUpdates
|
2018-09-10 18:13:10 +00:00
|
|
|
|
|
2018-09-10 17:38:36 +00:00
|
|
|
|
// PreemptedAllocs is the set of allocations to be preempted to make the placement successful.
|
|
|
|
|
PreemptedAllocs []*AllocListStub
|
2016-05-05 18:21:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DesiredUpdates is the set of changes the scheduler would like to make given
|
|
|
|
|
// sufficient resources and cluster capacity.
|
|
|
|
|
type DesiredUpdates struct {
|
|
|
|
|
Ignore uint64
|
|
|
|
|
Place uint64
|
|
|
|
|
Migrate uint64
|
|
|
|
|
Stop uint64
|
|
|
|
|
InPlaceUpdate uint64
|
|
|
|
|
DestructiveUpdate uint64
|
2017-05-23 20:02:47 +00:00
|
|
|
|
Canary uint64
|
2018-10-18 04:49:37 +00:00
|
|
|
|
Preemptions uint64
|
2016-05-05 18:21:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-07 23:49:08 +00:00
|
|
|
|
func (d *DesiredUpdates) GoString() string {
|
|
|
|
|
return fmt.Sprintf("(place %d) (inplace %d) (destructive %d) (stop %d) (migrate %d) (ignore %d) (canary %d)",
|
|
|
|
|
d.Place, d.InPlaceUpdate, d.DestructiveUpdate, d.Stop, d.Migrate, d.Ignore, d.Canary)
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-05 22:21:17 +00:00
|
|
|
|
// msgpackHandle is a shared handle for encoding/decoding of structs
|
2015-11-18 23:16:42 +00:00
|
|
|
|
var MsgpackHandle = func() *codec.MsgpackHandle {
|
2019-05-08 13:04:16 +00:00
|
|
|
|
h := &codec.MsgpackHandle{}
|
|
|
|
|
h.RawToString = true
|
|
|
|
|
|
|
|
|
|
// maintain binary format from time prior to upgrading latest ugorji
|
|
|
|
|
h.BasicHandle.TimeNotBuiltin = true
|
2015-11-16 22:25:19 +00:00
|
|
|
|
|
|
|
|
|
// Sets the default type for decoding a map into a nil interface{}.
|
|
|
|
|
// This is necessary in particular because we store the driver configs as a
|
|
|
|
|
// nil interface{}.
|
|
|
|
|
h.MapType = reflect.TypeOf(map[string]interface{}(nil))
|
2019-05-08 13:04:16 +00:00
|
|
|
|
|
2015-11-16 22:25:19 +00:00
|
|
|
|
return h
|
|
|
|
|
}()
|
2015-06-05 22:21:17 +00:00
|
|
|
|
|
2017-04-28 20:18:04 +00:00
|
|
|
|
var (
|
|
|
|
|
// JsonHandle and JsonHandlePretty are the codec handles to JSON encode
|
|
|
|
|
// structs. The pretty handle will add indents for easier human consumption.
|
|
|
|
|
JsonHandle = &codec.JsonHandle{
|
|
|
|
|
HTMLCharsAsIs: true,
|
|
|
|
|
}
|
|
|
|
|
JsonHandlePretty = &codec.JsonHandle{
|
|
|
|
|
HTMLCharsAsIs: true,
|
|
|
|
|
Indent: 4,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
2018-01-19 00:51:49 +00:00
|
|
|
|
// TODO Figure out if we can remove this. This is our fork that is just way
|
|
|
|
|
// behind. I feel like its original purpose was to pin at a stable version but
|
|
|
|
|
// now we can accomplish this with vendoring.
|
2016-02-21 02:05:17 +00:00
|
|
|
|
var HashiMsgpackHandle = func() *hcodec.MsgpackHandle {
|
2019-05-08 13:04:16 +00:00
|
|
|
|
h := &hcodec.MsgpackHandle{}
|
|
|
|
|
h.RawToString = true
|
|
|
|
|
|
|
|
|
|
// maintain binary format from time prior to upgrading latest ugorji
|
|
|
|
|
h.BasicHandle.TimeNotBuiltin = true
|
2016-02-21 02:05:17 +00:00
|
|
|
|
|
|
|
|
|
// Sets the default type for decoding a map into a nil interface{}.
|
|
|
|
|
// This is necessary in particular because we store the driver configs as a
|
|
|
|
|
// nil interface{}.
|
|
|
|
|
h.MapType = reflect.TypeOf(map[string]interface{}(nil))
|
|
|
|
|
return h
|
|
|
|
|
}()
|
|
|
|
|
|
2015-06-05 22:21:17 +00:00
|
|
|
|
// Decode is used to decode a MsgPack encoded object
|
|
|
|
|
func Decode(buf []byte, out interface{}) error {
|
2015-11-18 23:16:42 +00:00
|
|
|
|
return codec.NewDecoder(bytes.NewReader(buf), MsgpackHandle).Decode(out)
|
2015-06-05 22:21:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Encode is used to encode a MsgPack object with type prefix
|
|
|
|
|
func Encode(t MessageType, msg interface{}) ([]byte, error) {
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
buf.WriteByte(uint8(t))
|
2015-11-18 23:16:42 +00:00
|
|
|
|
err := codec.NewEncoder(&buf, MsgpackHandle).Encode(msg)
|
2015-06-05 22:21:17 +00:00
|
|
|
|
return buf.Bytes(), err
|
|
|
|
|
}
|
2016-10-17 17:48:04 +00:00
|
|
|
|
|
|
|
|
|
// KeyringResponse is a unified key response and can be used for install,
|
|
|
|
|
// remove, use, as well as listing key queries.
|
|
|
|
|
type KeyringResponse struct {
|
|
|
|
|
Messages map[string]string
|
|
|
|
|
Keys map[string]int
|
|
|
|
|
NumNodes int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// KeyringRequest is request objects for serf key operations.
|
|
|
|
|
type KeyringRequest struct {
|
|
|
|
|
Key string
|
|
|
|
|
}
|
2016-10-23 01:08:30 +00:00
|
|
|
|
|
|
|
|
|
// RecoverableError wraps an error and marks whether it is recoverable and could
|
|
|
|
|
// be retried or it is fatal.
|
|
|
|
|
type RecoverableError struct {
|
|
|
|
|
Err string
|
2017-03-27 23:27:24 +00:00
|
|
|
|
Recoverable bool
|
2016-10-23 01:08:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewRecoverableError is used to wrap an error and mark it as recoverable or
|
|
|
|
|
// not.
|
2017-01-14 00:46:08 +00:00
|
|
|
|
func NewRecoverableError(e error, recoverable bool) error {
|
2016-10-23 01:08:30 +00:00
|
|
|
|
if e == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &RecoverableError{
|
|
|
|
|
Err: e.Error(),
|
2017-03-27 23:27:24 +00:00
|
|
|
|
Recoverable: recoverable,
|
2016-10-23 01:08:30 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-27 22:37:15 +00:00
|
|
|
|
// WrapRecoverable wraps an existing error in a new RecoverableError with a new
|
|
|
|
|
// message. If the error was recoverable before the returned error is as well;
|
|
|
|
|
// otherwise it is unrecoverable.
|
|
|
|
|
func WrapRecoverable(msg string, err error) error {
|
2017-03-27 23:27:24 +00:00
|
|
|
|
return &RecoverableError{Err: msg, Recoverable: IsRecoverable(err)}
|
2017-03-27 22:37:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-23 01:08:30 +00:00
|
|
|
|
func (r *RecoverableError) Error() string {
|
|
|
|
|
return r.Err
|
|
|
|
|
}
|
2017-01-14 00:46:08 +00:00
|
|
|
|
|
2017-03-27 23:27:24 +00:00
|
|
|
|
func (r *RecoverableError) IsRecoverable() bool {
|
|
|
|
|
return r.Recoverable
|
2017-03-24 22:26:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-12 17:16:12 +00:00
|
|
|
|
func (r *RecoverableError) IsUnrecoverable() bool {
|
|
|
|
|
return !r.Recoverable
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-24 22:26:05 +00:00
|
|
|
|
// Recoverable is an interface for errors to implement to indicate whether or
|
|
|
|
|
// not they are fatal or recoverable.
|
|
|
|
|
type Recoverable interface {
|
|
|
|
|
error
|
2017-03-27 23:27:24 +00:00
|
|
|
|
IsRecoverable() bool
|
2017-03-24 22:26:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-14 00:46:08 +00:00
|
|
|
|
// IsRecoverable returns true if error is a RecoverableError with
|
|
|
|
|
// Recoverable=true. Otherwise false is returned.
|
|
|
|
|
func IsRecoverable(e error) bool {
|
2017-03-24 22:26:05 +00:00
|
|
|
|
if re, ok := e.(Recoverable); ok {
|
2017-03-27 23:27:24 +00:00
|
|
|
|
return re.IsRecoverable()
|
2017-01-14 00:46:08 +00:00
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
2017-08-07 21:21:00 +00:00
|
|
|
|
|
2018-03-13 22:09:03 +00:00
|
|
|
|
// WrappedServerError wraps an error and satisfies
|
|
|
|
|
// both the Recoverable and the ServerSideError interfaces
|
|
|
|
|
type WrappedServerError struct {
|
2018-03-14 01:49:01 +00:00
|
|
|
|
Err error
|
2018-03-13 22:09:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewWrappedServerError is used to create a wrapped server side error
|
|
|
|
|
func NewWrappedServerError(e error) error {
|
|
|
|
|
return &WrappedServerError{
|
|
|
|
|
Err: e,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *WrappedServerError) IsRecoverable() bool {
|
|
|
|
|
return IsRecoverable(r.Err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *WrappedServerError) Error() string {
|
|
|
|
|
return r.Err.Error()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *WrappedServerError) IsServerSide() bool {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-13 23:25:41 +00:00
|
|
|
|
// ServerSideError is an interface for errors to implement to indicate
|
2018-03-14 01:49:01 +00:00
|
|
|
|
// errors occurring after the request makes it to a server
|
2018-03-13 22:09:03 +00:00
|
|
|
|
type ServerSideError interface {
|
|
|
|
|
error
|
|
|
|
|
IsServerSide() bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IsServerSide returns true if error is a wrapped
|
|
|
|
|
// server side error
|
|
|
|
|
func IsServerSide(e error) bool {
|
|
|
|
|
if se, ok := e.(ServerSideError); ok {
|
|
|
|
|
return se.IsServerSide()
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-07 21:21:00 +00:00
|
|
|
|
// ACLPolicy is used to represent an ACL policy
|
|
|
|
|
type ACLPolicy struct {
|
2017-08-08 00:10:04 +00:00
|
|
|
|
Name string // Unique name
|
|
|
|
|
Description string // Human readable
|
|
|
|
|
Rules string // HCL or JSON format
|
2017-08-30 16:58:42 +00:00
|
|
|
|
Hash []byte
|
2017-08-08 00:10:04 +00:00
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
|
|
|
|
}
|
2017-08-07 21:21:00 +00:00
|
|
|
|
|
2017-08-30 16:58:42 +00:00
|
|
|
|
// SetHash is used to compute and set the hash of the ACL policy
|
|
|
|
|
func (c *ACLPolicy) SetHash() []byte {
|
|
|
|
|
// Initialize a 256bit Blake2 hash (32 bytes)
|
|
|
|
|
hash, err := blake2b.New256(nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write all the user set fields
|
|
|
|
|
hash.Write([]byte(c.Name))
|
|
|
|
|
hash.Write([]byte(c.Description))
|
|
|
|
|
hash.Write([]byte(c.Rules))
|
|
|
|
|
|
|
|
|
|
// Finalize the hash
|
|
|
|
|
hashVal := hash.Sum(nil)
|
|
|
|
|
|
|
|
|
|
// Set and return the hash
|
|
|
|
|
c.Hash = hashVal
|
|
|
|
|
return hashVal
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-08 22:19:59 +00:00
|
|
|
|
func (a *ACLPolicy) Stub() *ACLPolicyListStub {
|
|
|
|
|
return &ACLPolicyListStub{
|
|
|
|
|
Name: a.Name,
|
|
|
|
|
Description: a.Description,
|
2017-08-30 16:58:42 +00:00
|
|
|
|
Hash: a.Hash,
|
2017-08-08 22:19:59 +00:00
|
|
|
|
CreateIndex: a.CreateIndex,
|
|
|
|
|
ModifyIndex: a.ModifyIndex,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-12 21:11:49 +00:00
|
|
|
|
func (a *ACLPolicy) Validate() error {
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
if !validPolicyName.MatchString(a.Name) {
|
|
|
|
|
err := fmt.Errorf("invalid name '%s'", a.Name)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := acl.Parse(a.Rules); err != nil {
|
|
|
|
|
err = fmt.Errorf("failed to parse rules: %v", err)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
|
|
|
|
if len(a.Description) > maxPolicyDescriptionLength {
|
|
|
|
|
err := fmt.Errorf("description longer than %d", maxPolicyDescriptionLength)
|
|
|
|
|
mErr.Errors = append(mErr.Errors, err)
|
|
|
|
|
}
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-08 00:10:04 +00:00
|
|
|
|
// ACLPolicyListStub is used to for listing ACL policies
|
|
|
|
|
type ACLPolicyListStub struct {
|
|
|
|
|
Name string
|
|
|
|
|
Description string
|
2017-08-30 16:58:42 +00:00
|
|
|
|
Hash []byte
|
2017-08-07 21:21:00 +00:00
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
|
|
|
|
}
|
2017-08-08 00:10:04 +00:00
|
|
|
|
|
|
|
|
|
// ACLPolicyListRequest is used to request a list of policies
|
|
|
|
|
type ACLPolicyListRequest struct {
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ACLPolicySpecificRequest is used to query a specific policy
|
|
|
|
|
type ACLPolicySpecificRequest struct {
|
|
|
|
|
Name string
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 22:21:59 +00:00
|
|
|
|
// ACLPolicySetRequest is used to query a set of policies
|
|
|
|
|
type ACLPolicySetRequest struct {
|
|
|
|
|
Names []string
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-08 00:10:04 +00:00
|
|
|
|
// ACLPolicyListResponse is used for a list request
|
|
|
|
|
type ACLPolicyListResponse struct {
|
|
|
|
|
Policies []*ACLPolicyListStub
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SingleACLPolicyResponse is used to return a single policy
|
|
|
|
|
type SingleACLPolicyResponse struct {
|
|
|
|
|
Policy *ACLPolicy
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
2017-08-08 03:53:07 +00:00
|
|
|
|
|
2017-08-20 22:21:59 +00:00
|
|
|
|
// ACLPolicySetResponse is used to return a set of policies
|
|
|
|
|
type ACLPolicySetResponse struct {
|
|
|
|
|
Policies map[string]*ACLPolicy
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-08 03:53:07 +00:00
|
|
|
|
// ACLPolicyDeleteRequest is used to delete a set of policies
|
|
|
|
|
type ACLPolicyDeleteRequest struct {
|
|
|
|
|
Names []string
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
2017-08-08 04:01:14 +00:00
|
|
|
|
|
|
|
|
|
// ACLPolicyUpsertRequest is used to upsert a set of policies
|
|
|
|
|
type ACLPolicyUpsertRequest struct {
|
|
|
|
|
Policies []*ACLPolicy
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
2017-08-12 21:36:10 +00:00
|
|
|
|
|
|
|
|
|
// ACLToken represents a client token which is used to Authenticate
|
|
|
|
|
type ACLToken struct {
|
2017-08-30 16:58:42 +00:00
|
|
|
|
AccessorID string // Public Accessor ID (UUID)
|
|
|
|
|
SecretID string // Secret ID, private (UUID)
|
|
|
|
|
Name string // Human friendly name
|
|
|
|
|
Type string // Client or Management
|
|
|
|
|
Policies []string // Policies this token ties to
|
|
|
|
|
Global bool // Global or Region local
|
|
|
|
|
Hash []byte
|
2017-08-12 21:36:10 +00:00
|
|
|
|
CreateTime time.Time // Time of creation
|
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 01:29:04 +00:00
|
|
|
|
var (
|
|
|
|
|
// AnonymousACLToken is used no SecretID is provided, and the
|
|
|
|
|
// request is made anonymously.
|
|
|
|
|
AnonymousACLToken = &ACLToken{
|
|
|
|
|
AccessorID: "anonymous",
|
|
|
|
|
Name: "Anonymous Token",
|
|
|
|
|
Type: ACLClientToken,
|
|
|
|
|
Policies: []string{"anonymous"},
|
|
|
|
|
Global: false,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
2017-08-12 23:08:00 +00:00
|
|
|
|
type ACLTokenListStub struct {
|
|
|
|
|
AccessorID string
|
|
|
|
|
Name string
|
|
|
|
|
Type string
|
|
|
|
|
Policies []string
|
|
|
|
|
Global bool
|
2017-08-30 16:58:42 +00:00
|
|
|
|
Hash []byte
|
2017-08-12 23:08:00 +00:00
|
|
|
|
CreateTime time.Time
|
|
|
|
|
CreateIndex uint64
|
|
|
|
|
ModifyIndex uint64
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-30 16:58:42 +00:00
|
|
|
|
// SetHash is used to compute and set the hash of the ACL token
|
|
|
|
|
func (a *ACLToken) SetHash() []byte {
|
|
|
|
|
// Initialize a 256bit Blake2 hash (32 bytes)
|
|
|
|
|
hash, err := blake2b.New256(nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write all the user set fields
|
|
|
|
|
hash.Write([]byte(a.Name))
|
|
|
|
|
hash.Write([]byte(a.Type))
|
|
|
|
|
for _, policyName := range a.Policies {
|
|
|
|
|
hash.Write([]byte(policyName))
|
|
|
|
|
}
|
|
|
|
|
if a.Global {
|
|
|
|
|
hash.Write([]byte("global"))
|
|
|
|
|
} else {
|
|
|
|
|
hash.Write([]byte("local"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Finalize the hash
|
|
|
|
|
hashVal := hash.Sum(nil)
|
|
|
|
|
|
|
|
|
|
// Set and return the hash
|
|
|
|
|
a.Hash = hashVal
|
|
|
|
|
return hashVal
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-12 23:08:00 +00:00
|
|
|
|
func (a *ACLToken) Stub() *ACLTokenListStub {
|
|
|
|
|
return &ACLTokenListStub{
|
|
|
|
|
AccessorID: a.AccessorID,
|
|
|
|
|
Name: a.Name,
|
|
|
|
|
Type: a.Type,
|
|
|
|
|
Policies: a.Policies,
|
|
|
|
|
Global: a.Global,
|
2017-08-30 16:58:42 +00:00
|
|
|
|
Hash: a.Hash,
|
2017-08-12 23:08:00 +00:00
|
|
|
|
CreateTime: a.CreateTime,
|
|
|
|
|
CreateIndex: a.CreateIndex,
|
|
|
|
|
ModifyIndex: a.ModifyIndex,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-12 21:36:10 +00:00
|
|
|
|
// Validate is used to sanity check a token
|
|
|
|
|
func (a *ACLToken) Validate() error {
|
|
|
|
|
var mErr multierror.Error
|
|
|
|
|
if len(a.Name) > maxTokenNameLength {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("token name too long"))
|
|
|
|
|
}
|
|
|
|
|
switch a.Type {
|
|
|
|
|
case ACLClientToken:
|
|
|
|
|
if len(a.Policies) == 0 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("client token missing policies"))
|
|
|
|
|
}
|
|
|
|
|
case ACLManagementToken:
|
|
|
|
|
if len(a.Policies) != 0 {
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("management token cannot be associated with policies"))
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("token type must be client or management"))
|
|
|
|
|
}
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
|
}
|
2017-08-12 22:44:05 +00:00
|
|
|
|
|
2017-08-22 00:45:11 +00:00
|
|
|
|
// PolicySubset checks if a given set of policies is a subset of the token
|
|
|
|
|
func (a *ACLToken) PolicySubset(policies []string) bool {
|
|
|
|
|
// Hot-path the management tokens, superset of all policies.
|
|
|
|
|
if a.Type == ACLManagementToken {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
associatedPolicies := make(map[string]struct{}, len(a.Policies))
|
|
|
|
|
for _, policy := range a.Policies {
|
|
|
|
|
associatedPolicies[policy] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
for _, policy := range policies {
|
|
|
|
|
if _, ok := associatedPolicies[policy]; !ok {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-12 22:44:05 +00:00
|
|
|
|
// ACLTokenListRequest is used to request a list of tokens
|
|
|
|
|
type ACLTokenListRequest struct {
|
2017-08-13 23:32:46 +00:00
|
|
|
|
GlobalOnly bool
|
2017-08-12 22:44:05 +00:00
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ACLTokenSpecificRequest is used to query a specific token
|
|
|
|
|
type ACLTokenSpecificRequest struct {
|
|
|
|
|
AccessorID string
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 22:21:59 +00:00
|
|
|
|
// ACLTokenSetRequest is used to query a set of tokens
|
|
|
|
|
type ACLTokenSetRequest struct {
|
|
|
|
|
AccessorIDS []string
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-12 22:44:05 +00:00
|
|
|
|
// ACLTokenListResponse is used for a list request
|
|
|
|
|
type ACLTokenListResponse struct {
|
2017-08-12 23:08:00 +00:00
|
|
|
|
Tokens []*ACLTokenListStub
|
2017-08-12 22:44:05 +00:00
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SingleACLTokenResponse is used to return a single token
|
|
|
|
|
type SingleACLTokenResponse struct {
|
|
|
|
|
Token *ACLToken
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 22:21:59 +00:00
|
|
|
|
// ACLTokenSetResponse is used to return a set of token
|
|
|
|
|
type ACLTokenSetResponse struct {
|
|
|
|
|
Tokens map[string]*ACLToken // Keyed by Accessor ID
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 21:53:51 +00:00
|
|
|
|
// ResolveACLTokenRequest is used to resolve a specific token
|
|
|
|
|
type ResolveACLTokenRequest struct {
|
|
|
|
|
SecretID string
|
|
|
|
|
QueryOptions
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ResolveACLTokenResponse is used to resolve a single token
|
|
|
|
|
type ResolveACLTokenResponse struct {
|
|
|
|
|
Token *ACLToken
|
|
|
|
|
QueryMeta
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-12 22:44:05 +00:00
|
|
|
|
// ACLTokenDeleteRequest is used to delete a set of tokens
|
|
|
|
|
type ACLTokenDeleteRequest struct {
|
|
|
|
|
AccessorIDs []string
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-21 01:19:26 +00:00
|
|
|
|
// ACLTokenBootstrapRequest is used to bootstrap ACLs
|
|
|
|
|
type ACLTokenBootstrapRequest struct {
|
2017-09-10 23:03:30 +00:00
|
|
|
|
Token *ACLToken // Not client specifiable
|
|
|
|
|
ResetIndex uint64 // Reset index is used to clear the bootstrap token
|
2017-08-21 01:19:26 +00:00
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-12 22:44:05 +00:00
|
|
|
|
// ACLTokenUpsertRequest is used to upsert a set of tokens
|
|
|
|
|
type ACLTokenUpsertRequest struct {
|
|
|
|
|
Tokens []*ACLToken
|
|
|
|
|
WriteRequest
|
|
|
|
|
}
|
2017-08-12 23:29:11 +00:00
|
|
|
|
|
|
|
|
|
// ACLTokenUpsertResponse is used to return from an ACLTokenUpsertRequest
|
|
|
|
|
type ACLTokenUpsertResponse struct {
|
|
|
|
|
Tokens []*ACLToken
|
|
|
|
|
WriteMeta
|
|
|
|
|
}
|