github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/nomad/structs/structs.go (about)

     1  package structs
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/md5"
     6  	"crypto/sha1"
     7  	"crypto/sha256"
     8  	"crypto/sha512"
     9  	"encoding/hex"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"net"
    14  	"os"
    15  	"path/filepath"
    16  	"reflect"
    17  	"regexp"
    18  	"sort"
    19  	"strconv"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/gorhill/cronexpr"
    24  	"github.com/hashicorp/consul/api"
    25  	"github.com/hashicorp/go-multierror"
    26  	"github.com/hashicorp/go-version"
    27  	"github.com/hashicorp/nomad/helper"
    28  	"github.com/hashicorp/nomad/helper/args"
    29  	"github.com/mitchellh/copystructure"
    30  	"github.com/ugorji/go/codec"
    31  
    32  	hcodec "github.com/hashicorp/go-msgpack/codec"
    33  )
    34  
    35  var (
    36  	ErrNoLeader     = fmt.Errorf("No cluster leader")
    37  	ErrNoRegionPath = fmt.Errorf("No path to region")
    38  )
    39  
    40  type MessageType uint8
    41  
    42  const (
    43  	NodeRegisterRequestType MessageType = iota
    44  	NodeDeregisterRequestType
    45  	NodeUpdateStatusRequestType
    46  	NodeUpdateDrainRequestType
    47  	JobRegisterRequestType
    48  	JobDeregisterRequestType
    49  	EvalUpdateRequestType
    50  	EvalDeleteRequestType
    51  	AllocUpdateRequestType
    52  	AllocClientUpdateRequestType
    53  	ReconcileJobSummariesRequestType
    54  	VaultAccessorRegisterRequestType
    55  	VaultAccessorDegisterRequestType
    56  	ApplyPlanResultsRequestType
    57  )
    58  
    59  const (
    60  	// IgnoreUnknownTypeFlag is set along with a MessageType
    61  	// to indicate that the message type can be safely ignored
    62  	// if it is not recognized. This is for future proofing, so
    63  	// that new commands can be added in a way that won't cause
    64  	// old servers to crash when the FSM attempts to process them.
    65  	IgnoreUnknownTypeFlag MessageType = 128
    66  
    67  	// ApiMajorVersion is returned as part of the Status.Version request.
    68  	// It should be incremented anytime the APIs are changed in a way
    69  	// that would break clients for sane client versioning.
    70  	ApiMajorVersion = 1
    71  
    72  	// ApiMinorVersion is returned as part of the Status.Version request.
    73  	// It should be incremented anytime the APIs are changed to allow
    74  	// for sane client versioning. Minor changes should be compatible
    75  	// within the major version.
    76  	ApiMinorVersion = 1
    77  
    78  	ProtocolVersion = "protocol"
    79  	APIMajorVersion = "api.major"
    80  	APIMinorVersion = "api.minor"
    81  )
    82  
    83  // RPCInfo is used to describe common information about query
    84  type RPCInfo interface {
    85  	RequestRegion() string
    86  	IsRead() bool
    87  	AllowStaleRead() bool
    88  }
    89  
    90  // QueryOptions is used to specify various flags for read queries
    91  type QueryOptions struct {
    92  	// The target region for this query
    93  	Region string
    94  
    95  	// If set, wait until query exceeds given index. Must be provided
    96  	// with MaxQueryTime.
    97  	MinQueryIndex uint64
    98  
    99  	// Provided with MinQueryIndex to wait for change.
   100  	MaxQueryTime time.Duration
   101  
   102  	// If set, any follower can service the request. Results
   103  	// may be arbitrarily stale.
   104  	AllowStale bool
   105  
   106  	// If set, used as prefix for resource list searches
   107  	Prefix string
   108  }
   109  
   110  func (q QueryOptions) RequestRegion() string {
   111  	return q.Region
   112  }
   113  
   114  // QueryOption only applies to reads, so always true
   115  func (q QueryOptions) IsRead() bool {
   116  	return true
   117  }
   118  
   119  func (q QueryOptions) AllowStaleRead() bool {
   120  	return q.AllowStale
   121  }
   122  
   123  type WriteRequest struct {
   124  	// The target region for this write
   125  	Region string
   126  }
   127  
   128  func (w WriteRequest) RequestRegion() string {
   129  	// The target region for this request
   130  	return w.Region
   131  }
   132  
   133  // WriteRequest only applies to writes, always false
   134  func (w WriteRequest) IsRead() bool {
   135  	return false
   136  }
   137  
   138  func (w WriteRequest) AllowStaleRead() bool {
   139  	return false
   140  }
   141  
   142  // QueryMeta allows a query response to include potentially
   143  // useful metadata about a query
   144  type QueryMeta struct {
   145  	// This is the index associated with the read
   146  	Index uint64
   147  
   148  	// If AllowStale is used, this is time elapsed since
   149  	// last contact between the follower and leader. This
   150  	// can be used to gauge staleness.
   151  	LastContact time.Duration
   152  
   153  	// Used to indicate if there is a known leader node
   154  	KnownLeader bool
   155  }
   156  
   157  // WriteMeta allows a write response to include potentially
   158  // useful metadata about the write
   159  type WriteMeta struct {
   160  	// This is the index associated with the write
   161  	Index uint64
   162  }
   163  
   164  // NodeRegisterRequest is used for Node.Register endpoint
   165  // to register a node as being a schedulable entity.
   166  type NodeRegisterRequest struct {
   167  	Node *Node
   168  	WriteRequest
   169  }
   170  
   171  // NodeDeregisterRequest is used for Node.Deregister endpoint
   172  // to deregister a node as being a schedulable entity.
   173  type NodeDeregisterRequest struct {
   174  	NodeID string
   175  	WriteRequest
   176  }
   177  
   178  // NodeServerInfo is used to in NodeUpdateResponse to return Nomad server
   179  // information used in RPC server lists.
   180  type NodeServerInfo struct {
   181  	// RPCAdvertiseAddr is the IP endpoint that a Nomad Server wishes to
   182  	// be contacted at for RPCs.
   183  	RPCAdvertiseAddr string
   184  
   185  	// RpcMajorVersion is the major version number the Nomad Server
   186  	// supports
   187  	RPCMajorVersion int32
   188  
   189  	// RpcMinorVersion is the minor version number the Nomad Server
   190  	// supports
   191  	RPCMinorVersion int32
   192  
   193  	// Datacenter is the datacenter that a Nomad server belongs to
   194  	Datacenter string
   195  }
   196  
   197  // NodeUpdateStatusRequest is used for Node.UpdateStatus endpoint
   198  // to update the status of a node.
   199  type NodeUpdateStatusRequest struct {
   200  	NodeID string
   201  	Status string
   202  	WriteRequest
   203  }
   204  
   205  // NodeUpdateDrainRequest is used for updatin the drain status
   206  type NodeUpdateDrainRequest struct {
   207  	NodeID string
   208  	Drain  bool
   209  	WriteRequest
   210  }
   211  
   212  // NodeEvaluateRequest is used to re-evaluate the ndoe
   213  type NodeEvaluateRequest struct {
   214  	NodeID string
   215  	WriteRequest
   216  }
   217  
   218  // NodeSpecificRequest is used when we just need to specify a target node
   219  type NodeSpecificRequest struct {
   220  	NodeID   string
   221  	SecretID string
   222  	QueryOptions
   223  }
   224  
   225  // JobRegisterRequest is used for Job.Register endpoint
   226  // to register a job as being a schedulable entity.
   227  type JobRegisterRequest struct {
   228  	Job *Job
   229  
   230  	// If EnforceIndex is set then the job will only be registered if the passed
   231  	// JobModifyIndex matches the current Jobs index. If the index is zero, the
   232  	// register only occurs if the job is new.
   233  	EnforceIndex   bool
   234  	JobModifyIndex uint64
   235  
   236  	WriteRequest
   237  }
   238  
   239  // JobDeregisterRequest is used for Job.Deregister endpoint
   240  // to deregister a job as being a schedulable entity.
   241  type JobDeregisterRequest struct {
   242  	JobID string
   243  
   244  	// Purge controls whether the deregister purges the job from the system or
   245  	// whether the job is just marked as stopped and will be removed by the
   246  	// garbage collector
   247  	Purge bool
   248  
   249  	WriteRequest
   250  }
   251  
   252  // JobEvaluateRequest is used when we just need to re-evaluate a target job
   253  type JobEvaluateRequest struct {
   254  	JobID string
   255  	WriteRequest
   256  }
   257  
   258  // JobSpecificRequest is used when we just need to specify a target job
   259  type JobSpecificRequest struct {
   260  	JobID     string
   261  	AllAllocs bool
   262  	QueryOptions
   263  }
   264  
   265  // JobListRequest is used to parameterize a list request
   266  type JobListRequest struct {
   267  	QueryOptions
   268  }
   269  
   270  // JobPlanRequest is used for the Job.Plan endpoint to trigger a dry-run
   271  // evaluation of the Job.
   272  type JobPlanRequest struct {
   273  	Job  *Job
   274  	Diff bool // Toggles an annotated diff
   275  	WriteRequest
   276  }
   277  
   278  // JobSummaryRequest is used when we just need to get a specific job summary
   279  type JobSummaryRequest struct {
   280  	JobID string
   281  	QueryOptions
   282  }
   283  
   284  // JobDispatchRequest is used to dispatch a job based on a parameterized job
   285  type JobDispatchRequest struct {
   286  	JobID   string
   287  	Payload []byte
   288  	Meta    map[string]string
   289  	WriteRequest
   290  }
   291  
   292  // JobValidateRequest is used to validate a job
   293  type JobValidateRequest struct {
   294  	Job *Job
   295  	WriteRequest
   296  }
   297  
   298  // JobRevertRequest is used to revert a job to a prior version.
   299  type JobRevertRequest struct {
   300  	// JobID is the ID of the job  being reverted
   301  	JobID string
   302  
   303  	// JobVersion the version to revert to.
   304  	JobVersion uint64
   305  
   306  	// EnforcePriorVersion if set will enforce that the job is at the given
   307  	// version before reverting.
   308  	EnforcePriorVersion *uint64
   309  
   310  	WriteRequest
   311  }
   312  
   313  // NodeListRequest is used to parameterize a list request
   314  type NodeListRequest struct {
   315  	QueryOptions
   316  }
   317  
   318  // EvalUpdateRequest is used for upserting evaluations.
   319  type EvalUpdateRequest struct {
   320  	Evals     []*Evaluation
   321  	EvalToken string
   322  	WriteRequest
   323  }
   324  
   325  // EvalDeleteRequest is used for deleting an evaluation.
   326  type EvalDeleteRequest struct {
   327  	Evals  []string
   328  	Allocs []string
   329  	WriteRequest
   330  }
   331  
   332  // EvalSpecificRequest is used when we just need to specify a target evaluation
   333  type EvalSpecificRequest struct {
   334  	EvalID string
   335  	QueryOptions
   336  }
   337  
   338  // EvalAckRequest is used to Ack/Nack a specific evaluation
   339  type EvalAckRequest struct {
   340  	EvalID string
   341  	Token  string
   342  	WriteRequest
   343  }
   344  
   345  // EvalDequeueRequest is used when we want to dequeue an evaluation
   346  type EvalDequeueRequest struct {
   347  	Schedulers       []string
   348  	Timeout          time.Duration
   349  	SchedulerVersion uint16
   350  	WriteRequest
   351  }
   352  
   353  // EvalListRequest is used to list the evaluations
   354  type EvalListRequest struct {
   355  	QueryOptions
   356  }
   357  
   358  // PlanRequest is used to submit an allocation plan to the leader
   359  type PlanRequest struct {
   360  	Plan *Plan
   361  	WriteRequest
   362  }
   363  
   364  // ApplyPlanResultsRequest is used by the planner to apply a Raft transaction
   365  // committing the result of a plan.
   366  type ApplyPlanResultsRequest struct {
   367  	// AllocUpdateRequest holds the allocation updates to be made by the
   368  	// scheduler.
   369  	AllocUpdateRequest
   370  
   371  	// CreatedDeployment is the deployment created as a result of a scheduling
   372  	// event. Any existing deployment should be cancelled when the new
   373  	// deployment is created.
   374  	CreatedDeployment *Deployment
   375  
   376  	// DeploymentUpdates is a set of status updates to apply to the given
   377  	// deployments. This allows the scheduler to cancel any unneeded deployment
   378  	// because the job is stopped or the update block is removed.
   379  	DeploymentUpdates []*DeploymentStatusUpdate
   380  }
   381  
   382  // AllocUpdateRequest is used to submit changes to allocations, either
   383  // to cause evictions or to assign new allocaitons. Both can be done
   384  // within a single transaction
   385  type AllocUpdateRequest struct {
   386  	// Alloc is the list of new allocations to assign
   387  	Alloc []*Allocation
   388  
   389  	// Job is the shared parent job of the allocations.
   390  	// It is pulled out since it is common to reduce payload size.
   391  	Job *Job
   392  
   393  	WriteRequest
   394  }
   395  
   396  // AllocListRequest is used to request a list of allocations
   397  type AllocListRequest struct {
   398  	QueryOptions
   399  }
   400  
   401  // AllocSpecificRequest is used to query a specific allocation
   402  type AllocSpecificRequest struct {
   403  	AllocID string
   404  	QueryOptions
   405  }
   406  
   407  // AllocsGetRequest is used to query a set of allocations
   408  type AllocsGetRequest struct {
   409  	AllocIDs []string
   410  	QueryOptions
   411  }
   412  
   413  // PeriodicForceReqeuest is used to force a specific periodic job.
   414  type PeriodicForceRequest struct {
   415  	JobID string
   416  	WriteRequest
   417  }
   418  
   419  // ServerMembersResponse has the list of servers in a cluster
   420  type ServerMembersResponse struct {
   421  	ServerName   string
   422  	ServerRegion string
   423  	ServerDC     string
   424  	Members      []*ServerMember
   425  }
   426  
   427  // ServerMember holds information about a Nomad server agent in a cluster
   428  type ServerMember struct {
   429  	Name        string
   430  	Addr        net.IP
   431  	Port        uint16
   432  	Tags        map[string]string
   433  	Status      string
   434  	ProtocolMin uint8
   435  	ProtocolMax uint8
   436  	ProtocolCur uint8
   437  	DelegateMin uint8
   438  	DelegateMax uint8
   439  	DelegateCur uint8
   440  }
   441  
   442  // DeriveVaultTokenRequest is used to request wrapped Vault tokens for the
   443  // following tasks in the given allocation
   444  type DeriveVaultTokenRequest struct {
   445  	NodeID   string
   446  	SecretID string
   447  	AllocID  string
   448  	Tasks    []string
   449  	QueryOptions
   450  }
   451  
   452  // VaultAccessorsRequest is used to operate on a set of Vault accessors
   453  type VaultAccessorsRequest struct {
   454  	Accessors []*VaultAccessor
   455  }
   456  
   457  // VaultAccessor is a reference to a created Vault token on behalf of
   458  // an allocation's task.
   459  type VaultAccessor struct {
   460  	AllocID     string
   461  	Task        string
   462  	NodeID      string
   463  	Accessor    string
   464  	CreationTTL int
   465  
   466  	// Raft Indexes
   467  	CreateIndex uint64
   468  }
   469  
   470  // DeriveVaultTokenResponse returns the wrapped tokens for each requested task
   471  type DeriveVaultTokenResponse struct {
   472  	// Tasks is a mapping between the task name and the wrapped token
   473  	Tasks map[string]string
   474  
   475  	// Error stores any error that occured. Errors are stored here so we can
   476  	// communicate whether it is retriable
   477  	Error *RecoverableError
   478  
   479  	QueryMeta
   480  }
   481  
   482  // GenericRequest is used to request where no
   483  // specific information is needed.
   484  type GenericRequest struct {
   485  	QueryOptions
   486  }
   487  
   488  // GenericResponse is used to respond to a request where no
   489  // specific response information is needed.
   490  type GenericResponse struct {
   491  	WriteMeta
   492  }
   493  
   494  // VersionResponse is used for the Status.Version reseponse
   495  type VersionResponse struct {
   496  	Build    string
   497  	Versions map[string]int
   498  	QueryMeta
   499  }
   500  
   501  // JobRegisterResponse is used to respond to a job registration
   502  type JobRegisterResponse struct {
   503  	EvalID          string
   504  	EvalCreateIndex uint64
   505  	JobModifyIndex  uint64
   506  	QueryMeta
   507  }
   508  
   509  // JobDeregisterResponse is used to respond to a job deregistration
   510  type JobDeregisterResponse struct {
   511  	EvalID          string
   512  	EvalCreateIndex uint64
   513  	JobModifyIndex  uint64
   514  	QueryMeta
   515  }
   516  
   517  // JobValidateResponse is the response from validate request
   518  type JobValidateResponse struct {
   519  	// DriverConfigValidated indicates whether the agent validated the driver
   520  	// config
   521  	DriverConfigValidated bool
   522  
   523  	// ValidationErrors is a list of validation errors
   524  	ValidationErrors []string
   525  
   526  	// Error is a string version of any error that may have occured
   527  	Error string
   528  }
   529  
   530  // NodeUpdateResponse is used to respond to a node update
   531  type NodeUpdateResponse struct {
   532  	HeartbeatTTL    time.Duration
   533  	EvalIDs         []string
   534  	EvalCreateIndex uint64
   535  	NodeModifyIndex uint64
   536  
   537  	// LeaderRPCAddr is the RPC address of the current Raft Leader.  If
   538  	// empty, the current Nomad Server is in the minority of a partition.
   539  	LeaderRPCAddr string
   540  
   541  	// NumNodes is the number of Nomad nodes attached to this quorum of
   542  	// Nomad Servers at the time of the response.  This value can
   543  	// fluctuate based on the health of the cluster between heartbeats.
   544  	NumNodes int32
   545  
   546  	// Servers is the full list of known Nomad servers in the local
   547  	// region.
   548  	Servers []*NodeServerInfo
   549  
   550  	QueryMeta
   551  }
   552  
   553  // NodeDrainUpdateResponse is used to respond to a node drain update
   554  type NodeDrainUpdateResponse struct {
   555  	EvalIDs         []string
   556  	EvalCreateIndex uint64
   557  	NodeModifyIndex uint64
   558  	QueryMeta
   559  }
   560  
   561  // NodeAllocsResponse is used to return allocs for a single node
   562  type NodeAllocsResponse struct {
   563  	Allocs []*Allocation
   564  	QueryMeta
   565  }
   566  
   567  // NodeClientAllocsResponse is used to return allocs meta data for a single node
   568  type NodeClientAllocsResponse struct {
   569  	Allocs map[string]uint64
   570  	QueryMeta
   571  }
   572  
   573  // SingleNodeResponse is used to return a single node
   574  type SingleNodeResponse struct {
   575  	Node *Node
   576  	QueryMeta
   577  }
   578  
   579  // NodeListResponse is used for a list request
   580  type NodeListResponse struct {
   581  	Nodes []*NodeListStub
   582  	QueryMeta
   583  }
   584  
   585  // SingleJobResponse is used to return a single job
   586  type SingleJobResponse struct {
   587  	Job *Job
   588  	QueryMeta
   589  }
   590  
   591  // JobSummaryResponse is used to return a single job summary
   592  type JobSummaryResponse struct {
   593  	JobSummary *JobSummary
   594  	QueryMeta
   595  }
   596  
   597  type JobDispatchResponse struct {
   598  	DispatchedJobID string
   599  	EvalID          string
   600  	EvalCreateIndex uint64
   601  	JobCreateIndex  uint64
   602  	WriteMeta
   603  }
   604  
   605  // JobListResponse is used for a list request
   606  type JobListResponse struct {
   607  	Jobs []*JobListStub
   608  	QueryMeta
   609  }
   610  
   611  // JobVersionsResponse is used for a job get versions request
   612  type JobVersionsResponse struct {
   613  	Versions []*Job
   614  	QueryMeta
   615  }
   616  
   617  // JobPlanResponse is used to respond to a job plan request
   618  type JobPlanResponse struct {
   619  	// Annotations stores annotations explaining decisions the scheduler made.
   620  	Annotations *PlanAnnotations
   621  
   622  	// FailedTGAllocs is the placement failures per task group.
   623  	FailedTGAllocs map[string]*AllocMetric
   624  
   625  	// JobModifyIndex is the modification index of the job. The value can be
   626  	// used when running `nomad run` to ensure that the Job wasn’t modified
   627  	// since the last plan. If the job is being created, the value is zero.
   628  	JobModifyIndex uint64
   629  
   630  	// CreatedEvals is the set of evaluations created by the scheduler. The
   631  	// reasons for this can be rolling-updates or blocked evals.
   632  	CreatedEvals []*Evaluation
   633  
   634  	// Diff contains the diff of the job and annotations on whether the change
   635  	// causes an in-place update or create/destroy
   636  	Diff *JobDiff
   637  
   638  	// NextPeriodicLaunch is the time duration till the job would be launched if
   639  	// submitted.
   640  	NextPeriodicLaunch time.Time
   641  
   642  	WriteMeta
   643  }
   644  
   645  // SingleAllocResponse is used to return a single allocation
   646  type SingleAllocResponse struct {
   647  	Alloc *Allocation
   648  	QueryMeta
   649  }
   650  
   651  // AllocsGetResponse is used to return a set of allocations
   652  type AllocsGetResponse struct {
   653  	Allocs []*Allocation
   654  	QueryMeta
   655  }
   656  
   657  // JobAllocationsResponse is used to return the allocations for a job
   658  type JobAllocationsResponse struct {
   659  	Allocations []*AllocListStub
   660  	QueryMeta
   661  }
   662  
   663  // JobEvaluationsResponse is used to return the evaluations for a job
   664  type JobEvaluationsResponse struct {
   665  	Evaluations []*Evaluation
   666  	QueryMeta
   667  }
   668  
   669  // SingleEvalResponse is used to return a single evaluation
   670  type SingleEvalResponse struct {
   671  	Eval *Evaluation
   672  	QueryMeta
   673  }
   674  
   675  // EvalDequeueResponse is used to return from a dequeue
   676  type EvalDequeueResponse struct {
   677  	Eval  *Evaluation
   678  	Token string
   679  	QueryMeta
   680  }
   681  
   682  // PlanResponse is used to return from a PlanRequest
   683  type PlanResponse struct {
   684  	Result *PlanResult
   685  	WriteMeta
   686  }
   687  
   688  // AllocListResponse is used for a list request
   689  type AllocListResponse struct {
   690  	Allocations []*AllocListStub
   691  	QueryMeta
   692  }
   693  
   694  // EvalListResponse is used for a list request
   695  type EvalListResponse struct {
   696  	Evaluations []*Evaluation
   697  	QueryMeta
   698  }
   699  
   700  // EvalAllocationsResponse is used to return the allocations for an evaluation
   701  type EvalAllocationsResponse struct {
   702  	Allocations []*AllocListStub
   703  	QueryMeta
   704  }
   705  
   706  // PeriodicForceResponse is used to respond to a periodic job force launch
   707  type PeriodicForceResponse struct {
   708  	EvalID          string
   709  	EvalCreateIndex uint64
   710  	WriteMeta
   711  }
   712  
   713  const (
   714  	NodeStatusInit  = "initializing"
   715  	NodeStatusReady = "ready"
   716  	NodeStatusDown  = "down"
   717  )
   718  
   719  // ShouldDrainNode checks if a given node status should trigger an
   720  // evaluation. Some states don't require any further action.
   721  func ShouldDrainNode(status string) bool {
   722  	switch status {
   723  	case NodeStatusInit, NodeStatusReady:
   724  		return false
   725  	case NodeStatusDown:
   726  		return true
   727  	default:
   728  		panic(fmt.Sprintf("unhandled node status %s", status))
   729  	}
   730  }
   731  
   732  // ValidNodeStatus is used to check if a node status is valid
   733  func ValidNodeStatus(status string) bool {
   734  	switch status {
   735  	case NodeStatusInit, NodeStatusReady, NodeStatusDown:
   736  		return true
   737  	default:
   738  		return false
   739  	}
   740  }
   741  
   742  // Node is a representation of a schedulable client node
   743  type Node struct {
   744  	// ID is a unique identifier for the node. It can be constructed
   745  	// by doing a concatenation of the Name and Datacenter as a simple
   746  	// approach. Alternatively a UUID may be used.
   747  	ID string
   748  
   749  	// SecretID is an ID that is only known by the Node and the set of Servers.
   750  	// It is not accessible via the API and is used to authenticate nodes
   751  	// conducting priviledged activities.
   752  	SecretID string
   753  
   754  	// Datacenter for this node
   755  	Datacenter string
   756  
   757  	// Node name
   758  	Name string
   759  
   760  	// HTTPAddr is the address on which the Nomad client is listening for http
   761  	// requests
   762  	HTTPAddr string
   763  
   764  	// TLSEnabled indicates if the Agent has TLS enabled for the HTTP API
   765  	TLSEnabled bool
   766  
   767  	// Attributes is an arbitrary set of key/value
   768  	// data that can be used for constraints. Examples
   769  	// include "kernel.name=linux", "arch=386", "driver.docker=1",
   770  	// "docker.runtime=1.8.3"
   771  	Attributes map[string]string
   772  
   773  	// Resources is the available resources on the client.
   774  	// For example 'cpu=2' 'memory=2048'
   775  	Resources *Resources
   776  
   777  	// Reserved is the set of resources that are reserved,
   778  	// and should be subtracted from the total resources for
   779  	// the purposes of scheduling. This may be provide certain
   780  	// high-watermark tolerances or because of external schedulers
   781  	// consuming resources.
   782  	Reserved *Resources
   783  
   784  	// Links are used to 'link' this client to external
   785  	// systems. For example 'consul=foo.dc1' 'aws=i-83212'
   786  	// 'ami=ami-123'
   787  	Links map[string]string
   788  
   789  	// Meta is used to associate arbitrary metadata with this
   790  	// client. This is opaque to Nomad.
   791  	Meta map[string]string
   792  
   793  	// NodeClass is an opaque identifier used to group nodes
   794  	// together for the purpose of determining scheduling pressure.
   795  	NodeClass string
   796  
   797  	// ComputedClass is a unique id that identifies nodes with a common set of
   798  	// attributes and capabilities.
   799  	ComputedClass string
   800  
   801  	// Drain is controlled by the servers, and not the client.
   802  	// If true, no jobs will be scheduled to this node, and existing
   803  	// allocations will be drained.
   804  	Drain bool
   805  
   806  	// Status of this node
   807  	Status string
   808  
   809  	// StatusDescription is meant to provide more human useful information
   810  	StatusDescription string
   811  
   812  	// StatusUpdatedAt is the time stamp at which the state of the node was
   813  	// updated
   814  	StatusUpdatedAt int64
   815  
   816  	// Raft Indexes
   817  	CreateIndex uint64
   818  	ModifyIndex uint64
   819  }
   820  
   821  // Ready returns if the node is ready for running allocations
   822  func (n *Node) Ready() bool {
   823  	return n.Status == NodeStatusReady && !n.Drain
   824  }
   825  
   826  func (n *Node) Copy() *Node {
   827  	if n == nil {
   828  		return nil
   829  	}
   830  	nn := new(Node)
   831  	*nn = *n
   832  	nn.Attributes = helper.CopyMapStringString(nn.Attributes)
   833  	nn.Resources = nn.Resources.Copy()
   834  	nn.Reserved = nn.Reserved.Copy()
   835  	nn.Links = helper.CopyMapStringString(nn.Links)
   836  	nn.Meta = helper.CopyMapStringString(nn.Meta)
   837  	return nn
   838  }
   839  
   840  // TerminalStatus returns if the current status is terminal and
   841  // will no longer transition.
   842  func (n *Node) TerminalStatus() bool {
   843  	switch n.Status {
   844  	case NodeStatusDown:
   845  		return true
   846  	default:
   847  		return false
   848  	}
   849  }
   850  
   851  // Stub returns a summarized version of the node
   852  func (n *Node) Stub() *NodeListStub {
   853  	return &NodeListStub{
   854  		ID:                n.ID,
   855  		Datacenter:        n.Datacenter,
   856  		Name:              n.Name,
   857  		NodeClass:         n.NodeClass,
   858  		Drain:             n.Drain,
   859  		Status:            n.Status,
   860  		StatusDescription: n.StatusDescription,
   861  		CreateIndex:       n.CreateIndex,
   862  		ModifyIndex:       n.ModifyIndex,
   863  	}
   864  }
   865  
   866  // NodeListStub is used to return a subset of job information
   867  // for the job list
   868  type NodeListStub struct {
   869  	ID                string
   870  	Datacenter        string
   871  	Name              string
   872  	NodeClass         string
   873  	Drain             bool
   874  	Status            string
   875  	StatusDescription string
   876  	CreateIndex       uint64
   877  	ModifyIndex       uint64
   878  }
   879  
   880  // Resources is used to define the resources available
   881  // on a client
   882  type Resources struct {
   883  	CPU      int
   884  	MemoryMB int
   885  	DiskMB   int
   886  	IOPS     int
   887  	Networks []*NetworkResource
   888  }
   889  
   890  const (
   891  	BytesInMegabyte = 1024 * 1024
   892  )
   893  
   894  // DefaultResources returns the default resources for a task.
   895  func DefaultResources() *Resources {
   896  	return &Resources{
   897  		CPU:      100,
   898  		MemoryMB: 10,
   899  		IOPS:     0,
   900  	}
   901  }
   902  
   903  // DiskInBytes returns the amount of disk resources in bytes.
   904  func (r *Resources) DiskInBytes() int64 {
   905  	return int64(r.DiskMB * BytesInMegabyte)
   906  }
   907  
   908  // Merge merges this resource with another resource.
   909  func (r *Resources) Merge(other *Resources) {
   910  	if other.CPU != 0 {
   911  		r.CPU = other.CPU
   912  	}
   913  	if other.MemoryMB != 0 {
   914  		r.MemoryMB = other.MemoryMB
   915  	}
   916  	if other.DiskMB != 0 {
   917  		r.DiskMB = other.DiskMB
   918  	}
   919  	if other.IOPS != 0 {
   920  		r.IOPS = other.IOPS
   921  	}
   922  	if len(other.Networks) != 0 {
   923  		r.Networks = other.Networks
   924  	}
   925  }
   926  
   927  func (r *Resources) Canonicalize() {
   928  	// Ensure that an empty and nil slices are treated the same to avoid scheduling
   929  	// problems since we use reflect DeepEquals.
   930  	if len(r.Networks) == 0 {
   931  		r.Networks = nil
   932  	}
   933  
   934  	for _, n := range r.Networks {
   935  		n.Canonicalize()
   936  	}
   937  }
   938  
   939  // MeetsMinResources returns an error if the resources specified are less than
   940  // the minimum allowed.
   941  func (r *Resources) MeetsMinResources() error {
   942  	var mErr multierror.Error
   943  	if r.CPU < 20 {
   944  		mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum CPU value is 20; got %d", r.CPU))
   945  	}
   946  	if r.MemoryMB < 10 {
   947  		mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum MemoryMB value is 10; got %d", r.MemoryMB))
   948  	}
   949  	if r.IOPS < 0 {
   950  		mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum IOPS value is 0; got %d", r.IOPS))
   951  	}
   952  	for i, n := range r.Networks {
   953  		if err := n.MeetsMinResources(); err != nil {
   954  			mErr.Errors = append(mErr.Errors, fmt.Errorf("network resource at index %d failed: %v", i, err))
   955  		}
   956  	}
   957  
   958  	return mErr.ErrorOrNil()
   959  }
   960  
   961  // Copy returns a deep copy of the resources
   962  func (r *Resources) Copy() *Resources {
   963  	if r == nil {
   964  		return nil
   965  	}
   966  	newR := new(Resources)
   967  	*newR = *r
   968  	if r.Networks != nil {
   969  		n := len(r.Networks)
   970  		newR.Networks = make([]*NetworkResource, n)
   971  		for i := 0; i < n; i++ {
   972  			newR.Networks[i] = r.Networks[i].Copy()
   973  		}
   974  	}
   975  	return newR
   976  }
   977  
   978  // NetIndex finds the matching net index using device name
   979  func (r *Resources) NetIndex(n *NetworkResource) int {
   980  	for idx, net := range r.Networks {
   981  		if net.Device == n.Device {
   982  			return idx
   983  		}
   984  	}
   985  	return -1
   986  }
   987  
   988  // Superset checks if one set of resources is a superset
   989  // of another. This ignores network resources, and the NetworkIndex
   990  // should be used for that.
   991  func (r *Resources) Superset(other *Resources) (bool, string) {
   992  	if r.CPU < other.CPU {
   993  		return false, "cpu exhausted"
   994  	}
   995  	if r.MemoryMB < other.MemoryMB {
   996  		return false, "memory exhausted"
   997  	}
   998  	if r.DiskMB < other.DiskMB {
   999  		return false, "disk exhausted"
  1000  	}
  1001  	if r.IOPS < other.IOPS {
  1002  		return false, "iops exhausted"
  1003  	}
  1004  	return true, ""
  1005  }
  1006  
  1007  // Add adds the resources of the delta to this, potentially
  1008  // returning an error if not possible.
  1009  func (r *Resources) Add(delta *Resources) error {
  1010  	if delta == nil {
  1011  		return nil
  1012  	}
  1013  	r.CPU += delta.CPU
  1014  	r.MemoryMB += delta.MemoryMB
  1015  	r.DiskMB += delta.DiskMB
  1016  	r.IOPS += delta.IOPS
  1017  
  1018  	for _, n := range delta.Networks {
  1019  		// Find the matching interface by IP or CIDR
  1020  		idx := r.NetIndex(n)
  1021  		if idx == -1 {
  1022  			r.Networks = append(r.Networks, n.Copy())
  1023  		} else {
  1024  			r.Networks[idx].Add(n)
  1025  		}
  1026  	}
  1027  	return nil
  1028  }
  1029  
  1030  func (r *Resources) GoString() string {
  1031  	return fmt.Sprintf("*%#v", *r)
  1032  }
  1033  
  1034  type Port struct {
  1035  	Label string
  1036  	Value int
  1037  }
  1038  
  1039  // NetworkResource is used to represent available network
  1040  // resources
  1041  type NetworkResource struct {
  1042  	Device        string // Name of the device
  1043  	CIDR          string // CIDR block of addresses
  1044  	IP            string // IP address
  1045  	MBits         int    // Throughput
  1046  	ReservedPorts []Port // Reserved ports
  1047  	DynamicPorts  []Port // Dynamically assigned ports
  1048  }
  1049  
  1050  func (n *NetworkResource) Canonicalize() {
  1051  	// Ensure that an empty and nil slices are treated the same to avoid scheduling
  1052  	// problems since we use reflect DeepEquals.
  1053  	if len(n.ReservedPorts) == 0 {
  1054  		n.ReservedPorts = nil
  1055  	}
  1056  	if len(n.DynamicPorts) == 0 {
  1057  		n.DynamicPorts = nil
  1058  	}
  1059  }
  1060  
  1061  // MeetsMinResources returns an error if the resources specified are less than
  1062  // the minimum allowed.
  1063  func (n *NetworkResource) MeetsMinResources() error {
  1064  	var mErr multierror.Error
  1065  	if n.MBits < 1 {
  1066  		mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum MBits value is 1; got %d", n.MBits))
  1067  	}
  1068  	return mErr.ErrorOrNil()
  1069  }
  1070  
  1071  // Copy returns a deep copy of the network resource
  1072  func (n *NetworkResource) Copy() *NetworkResource {
  1073  	if n == nil {
  1074  		return nil
  1075  	}
  1076  	newR := new(NetworkResource)
  1077  	*newR = *n
  1078  	if n.ReservedPorts != nil {
  1079  		newR.ReservedPorts = make([]Port, len(n.ReservedPorts))
  1080  		copy(newR.ReservedPorts, n.ReservedPorts)
  1081  	}
  1082  	if n.DynamicPorts != nil {
  1083  		newR.DynamicPorts = make([]Port, len(n.DynamicPorts))
  1084  		copy(newR.DynamicPorts, n.DynamicPorts)
  1085  	}
  1086  	return newR
  1087  }
  1088  
  1089  // Add adds the resources of the delta to this, potentially
  1090  // returning an error if not possible.
  1091  func (n *NetworkResource) Add(delta *NetworkResource) {
  1092  	if len(delta.ReservedPorts) > 0 {
  1093  		n.ReservedPorts = append(n.ReservedPorts, delta.ReservedPorts...)
  1094  	}
  1095  	n.MBits += delta.MBits
  1096  	n.DynamicPorts = append(n.DynamicPorts, delta.DynamicPorts...)
  1097  }
  1098  
  1099  func (n *NetworkResource) GoString() string {
  1100  	return fmt.Sprintf("*%#v", *n)
  1101  }
  1102  
  1103  func (n *NetworkResource) MapLabelToValues(port_map map[string]int) map[string]int {
  1104  	labelValues := make(map[string]int)
  1105  	ports := append(n.ReservedPorts, n.DynamicPorts...)
  1106  	for _, port := range ports {
  1107  		if mapping, ok := port_map[port.Label]; ok {
  1108  			labelValues[port.Label] = mapping
  1109  		} else {
  1110  			labelValues[port.Label] = port.Value
  1111  		}
  1112  	}
  1113  	return labelValues
  1114  }
  1115  
  1116  const (
  1117  	// JobTypeNomad is reserved for internal system tasks and is
  1118  	// always handled by the CoreScheduler.
  1119  	JobTypeCore    = "_core"
  1120  	JobTypeService = "service"
  1121  	JobTypeBatch   = "batch"
  1122  	JobTypeSystem  = "system"
  1123  )
  1124  
  1125  const (
  1126  	JobStatusPending = "pending" // Pending means the job is waiting on scheduling
  1127  	JobStatusRunning = "running" // Running means the job has non-terminal allocations
  1128  	JobStatusDead    = "dead"    // Dead means all evaluation's and allocations are terminal
  1129  )
  1130  
  1131  const (
  1132  	// JobMinPriority is the minimum allowed priority
  1133  	JobMinPriority = 1
  1134  
  1135  	// JobDefaultPriority is the default priority if not
  1136  	// not specified.
  1137  	JobDefaultPriority = 50
  1138  
  1139  	// JobMaxPriority is the maximum allowed priority
  1140  	JobMaxPriority = 100
  1141  
  1142  	// Ensure CoreJobPriority is higher than any user
  1143  	// specified job so that it gets priority. This is important
  1144  	// for the system to remain healthy.
  1145  	CoreJobPriority = JobMaxPriority * 2
  1146  
  1147  	// JobTrackedVersions is the number of historic job versions that are
  1148  	// kept.
  1149  	JobTrackedVersions = 6
  1150  )
  1151  
  1152  // Job is the scope of a scheduling request to Nomad. It is the largest
  1153  // scoped object, and is a named collection of task groups. Each task group
  1154  // is further composed of tasks. A task group (TG) is the unit of scheduling
  1155  // however.
  1156  type Job struct {
  1157  	// Stop marks whether the user has stopped the job. A stopped job will
  1158  	// have all created allocations stopped and acts as a way to stop a job
  1159  	// without purging it from the system. This allows existing allocs to be
  1160  	// queried and the job to be inspected as it is being killed.
  1161  	Stop bool
  1162  
  1163  	// Region is the Nomad region that handles scheduling this job
  1164  	Region string
  1165  
  1166  	// ID is a unique identifier for the job per region. It can be
  1167  	// specified hierarchically like LineOfBiz/OrgName/Team/Project
  1168  	ID string
  1169  
  1170  	// ParentID is the unique identifier of the job that spawned this job.
  1171  	ParentID string
  1172  
  1173  	// Name is the logical name of the job used to refer to it. This is unique
  1174  	// per region, but not unique globally.
  1175  	Name string
  1176  
  1177  	// Type is used to control various behaviors about the job. Most jobs
  1178  	// are service jobs, meaning they are expected to be long lived.
  1179  	// Some jobs are batch oriented meaning they run and then terminate.
  1180  	// This can be extended in the future to support custom schedulers.
  1181  	Type string
  1182  
  1183  	// Priority is used to control scheduling importance and if this job
  1184  	// can preempt other jobs.
  1185  	Priority int
  1186  
  1187  	// AllAtOnce is used to control if incremental scheduling of task groups
  1188  	// is allowed or if we must do a gang scheduling of the entire job. This
  1189  	// can slow down larger jobs if resources are not available.
  1190  	AllAtOnce bool
  1191  
  1192  	// Datacenters contains all the datacenters this job is allowed to span
  1193  	Datacenters []string
  1194  
  1195  	// Constraints can be specified at a job level and apply to
  1196  	// all the task groups and tasks.
  1197  	Constraints []*Constraint
  1198  
  1199  	// TaskGroups are the collections of task groups that this job needs
  1200  	// to run. Each task group is an atomic unit of scheduling and placement.
  1201  	TaskGroups []*TaskGroup
  1202  
  1203  	// Update is used to control the update strategy
  1204  	Update UpdateStrategy
  1205  
  1206  	// Periodic is used to define the interval the job is run at.
  1207  	Periodic *PeriodicConfig
  1208  
  1209  	// ParameterizedJob is used to specify the job as a parameterized job
  1210  	// for dispatching.
  1211  	ParameterizedJob *ParameterizedJobConfig
  1212  
  1213  	// Payload is the payload supplied when the job was dispatched.
  1214  	Payload []byte
  1215  
  1216  	// Meta is used to associate arbitrary metadata with this
  1217  	// job. This is opaque to Nomad.
  1218  	Meta map[string]string
  1219  
  1220  	// VaultToken is the Vault token that proves the submitter of the job has
  1221  	// access to the specified Vault policies. This field is only used to
  1222  	// transfer the token and is not stored after Job submission.
  1223  	VaultToken string
  1224  
  1225  	// Job status
  1226  	Status string
  1227  
  1228  	// StatusDescription is meant to provide more human useful information
  1229  	StatusDescription string
  1230  
  1231  	// Stable marks a job as stable. Stability is only defined on "service" and
  1232  	// "system" jobs. The stability of a job will be set automatically as part
  1233  	// of a deployment and can be manually set via APIs.
  1234  	Stable bool
  1235  
  1236  	// Version is a monitonically increasing version number that is incremened
  1237  	// on each job register.
  1238  	Version uint64
  1239  
  1240  	// Raft Indexes
  1241  	CreateIndex    uint64
  1242  	ModifyIndex    uint64
  1243  	JobModifyIndex uint64
  1244  }
  1245  
  1246  // Canonicalize is used to canonicalize fields in the Job. This should be called
  1247  // when registering a Job.
  1248  func (j *Job) Canonicalize() {
  1249  	// Ensure that an empty and nil map are treated the same to avoid scheduling
  1250  	// problems since we use reflect DeepEquals.
  1251  	if len(j.Meta) == 0 {
  1252  		j.Meta = nil
  1253  	}
  1254  
  1255  	for _, tg := range j.TaskGroups {
  1256  		tg.Canonicalize(j)
  1257  	}
  1258  
  1259  	if j.ParameterizedJob != nil {
  1260  		j.ParameterizedJob.Canonicalize()
  1261  	}
  1262  
  1263  	if j.Periodic != nil {
  1264  		j.Periodic.Canonicalize()
  1265  	}
  1266  }
  1267  
  1268  // Copy returns a deep copy of the Job. It is expected that callers use recover.
  1269  // This job can panic if the deep copy failed as it uses reflection.
  1270  func (j *Job) Copy() *Job {
  1271  	if j == nil {
  1272  		return nil
  1273  	}
  1274  	nj := new(Job)
  1275  	*nj = *j
  1276  	nj.Datacenters = helper.CopySliceString(nj.Datacenters)
  1277  	nj.Constraints = CopySliceConstraints(nj.Constraints)
  1278  
  1279  	if j.TaskGroups != nil {
  1280  		tgs := make([]*TaskGroup, len(nj.TaskGroups))
  1281  		for i, tg := range nj.TaskGroups {
  1282  			tgs[i] = tg.Copy()
  1283  		}
  1284  		nj.TaskGroups = tgs
  1285  	}
  1286  
  1287  	nj.Periodic = nj.Periodic.Copy()
  1288  	nj.Meta = helper.CopyMapStringString(nj.Meta)
  1289  	nj.ParameterizedJob = nj.ParameterizedJob.Copy()
  1290  	return nj
  1291  }
  1292  
  1293  // Validate is used to sanity check a job input
  1294  func (j *Job) Validate() error {
  1295  	var mErr multierror.Error
  1296  
  1297  	if j.Region == "" {
  1298  		mErr.Errors = append(mErr.Errors, errors.New("Missing job region"))
  1299  	}
  1300  	if j.ID == "" {
  1301  		mErr.Errors = append(mErr.Errors, errors.New("Missing job ID"))
  1302  	} else if strings.Contains(j.ID, " ") {
  1303  		mErr.Errors = append(mErr.Errors, errors.New("Job ID contains a space"))
  1304  	}
  1305  	if j.Name == "" {
  1306  		mErr.Errors = append(mErr.Errors, errors.New("Missing job name"))
  1307  	}
  1308  	if j.Type == "" {
  1309  		mErr.Errors = append(mErr.Errors, errors.New("Missing job type"))
  1310  	}
  1311  	if j.Priority < JobMinPriority || j.Priority > JobMaxPriority {
  1312  		mErr.Errors = append(mErr.Errors, fmt.Errorf("Job priority must be between [%d, %d]", JobMinPriority, JobMaxPriority))
  1313  	}
  1314  	if len(j.Datacenters) == 0 {
  1315  		mErr.Errors = append(mErr.Errors, errors.New("Missing job datacenters"))
  1316  	}
  1317  	if len(j.TaskGroups) == 0 {
  1318  		mErr.Errors = append(mErr.Errors, errors.New("Missing job task groups"))
  1319  	}
  1320  	for idx, constr := range j.Constraints {
  1321  		if err := constr.Validate(); err != nil {
  1322  			outer := fmt.Errorf("Constraint %d validation failed: %s", idx+1, err)
  1323  			mErr.Errors = append(mErr.Errors, outer)
  1324  		}
  1325  	}
  1326  
  1327  	// Check for duplicate task groups
  1328  	taskGroups := make(map[string]int)
  1329  	for idx, tg := range j.TaskGroups {
  1330  		if tg.Name == "" {
  1331  			mErr.Errors = append(mErr.Errors, fmt.Errorf("Job task group %d missing name", idx+1))
  1332  		} else if existing, ok := taskGroups[tg.Name]; ok {
  1333  			mErr.Errors = append(mErr.Errors, fmt.Errorf("Job task group %d redefines '%s' from group %d", idx+1, tg.Name, existing+1))
  1334  		} else {
  1335  			taskGroups[tg.Name] = idx
  1336  		}
  1337  
  1338  		if j.Type == "system" && tg.Count > 1 {
  1339  			mErr.Errors = append(mErr.Errors,
  1340  				fmt.Errorf("Job task group %s has count %d. Count cannot exceed 1 with system scheduler",
  1341  					tg.Name, tg.Count))
  1342  		}
  1343  	}
  1344  
  1345  	// Validate the task group
  1346  	for _, tg := range j.TaskGroups {
  1347  		if err := tg.Validate(); err != nil {
  1348  			outer := fmt.Errorf("Task group %s validation failed: %v", tg.Name, err)
  1349  			mErr.Errors = append(mErr.Errors, outer)
  1350  		}
  1351  	}
  1352  
  1353  	// Validate periodic is only used with batch jobs.
  1354  	if j.IsPeriodic() && j.Periodic.Enabled {
  1355  		if j.Type != JobTypeBatch {
  1356  			mErr.Errors = append(mErr.Errors,
  1357  				fmt.Errorf("Periodic can only be used with %q scheduler", JobTypeBatch))
  1358  		}
  1359  
  1360  		if err := j.Periodic.Validate(); err != nil {
  1361  			mErr.Errors = append(mErr.Errors, err)
  1362  		}
  1363  	}
  1364  
  1365  	if j.IsParameterized() {
  1366  		if j.Type != JobTypeBatch {
  1367  			mErr.Errors = append(mErr.Errors,
  1368  				fmt.Errorf("Parameterized job can only be used with %q scheduler", JobTypeBatch))
  1369  		}
  1370  
  1371  		if err := j.ParameterizedJob.Validate(); err != nil {
  1372  			mErr.Errors = append(mErr.Errors, err)
  1373  		}
  1374  	}
  1375  
  1376  	return mErr.ErrorOrNil()
  1377  }
  1378  
  1379  // LookupTaskGroup finds a task group by name
  1380  func (j *Job) LookupTaskGroup(name string) *TaskGroup {
  1381  	for _, tg := range j.TaskGroups {
  1382  		if tg.Name == name {
  1383  			return tg
  1384  		}
  1385  	}
  1386  	return nil
  1387  }
  1388  
  1389  // CombinedTaskMeta takes a TaskGroup and Task name and returns the combined
  1390  // meta data for the task. When joining Job, Group and Task Meta, the precedence
  1391  // is by deepest scope (Task > Group > Job).
  1392  func (j *Job) CombinedTaskMeta(groupName, taskName string) map[string]string {
  1393  	group := j.LookupTaskGroup(groupName)
  1394  	if group == nil {
  1395  		return nil
  1396  	}
  1397  
  1398  	task := group.LookupTask(taskName)
  1399  	if task == nil {
  1400  		return nil
  1401  	}
  1402  
  1403  	meta := helper.CopyMapStringString(task.Meta)
  1404  	if meta == nil {
  1405  		meta = make(map[string]string, len(group.Meta)+len(j.Meta))
  1406  	}
  1407  
  1408  	// Add the group specific meta
  1409  	for k, v := range group.Meta {
  1410  		if _, ok := meta[k]; !ok {
  1411  			meta[k] = v
  1412  		}
  1413  	}
  1414  
  1415  	// Add the job specific meta
  1416  	for k, v := range j.Meta {
  1417  		if _, ok := meta[k]; !ok {
  1418  			meta[k] = v
  1419  		}
  1420  	}
  1421  
  1422  	return meta
  1423  }
  1424  
  1425  // Stopped returns if a job is stopped.
  1426  func (j *Job) Stopped() bool {
  1427  	return j == nil || j.Stop
  1428  }
  1429  
  1430  // Stub is used to return a summary of the job
  1431  func (j *Job) Stub(summary *JobSummary) *JobListStub {
  1432  	return &JobListStub{
  1433  		ID:                j.ID,
  1434  		ParentID:          j.ParentID,
  1435  		Name:              j.Name,
  1436  		Type:              j.Type,
  1437  		Priority:          j.Priority,
  1438  		Periodic:          j.IsPeriodic(),
  1439  		ParameterizedJob:  j.IsParameterized(),
  1440  		Stop:              j.Stop,
  1441  		Status:            j.Status,
  1442  		StatusDescription: j.StatusDescription,
  1443  		CreateIndex:       j.CreateIndex,
  1444  		ModifyIndex:       j.ModifyIndex,
  1445  		JobModifyIndex:    j.JobModifyIndex,
  1446  		JobSummary:        summary,
  1447  	}
  1448  }
  1449  
  1450  // IsPeriodic returns whether a job is periodic.
  1451  func (j *Job) IsPeriodic() bool {
  1452  	return j.Periodic != nil
  1453  }
  1454  
  1455  // IsParameterized returns whether a job is parameterized job.
  1456  func (j *Job) IsParameterized() bool {
  1457  	return j.ParameterizedJob != nil
  1458  }
  1459  
  1460  // VaultPolicies returns the set of Vault policies per task group, per task
  1461  func (j *Job) VaultPolicies() map[string]map[string]*Vault {
  1462  	policies := make(map[string]map[string]*Vault, len(j.TaskGroups))
  1463  
  1464  	for _, tg := range j.TaskGroups {
  1465  		tgPolicies := make(map[string]*Vault, len(tg.Tasks))
  1466  
  1467  		for _, task := range tg.Tasks {
  1468  			if task.Vault == nil {
  1469  				continue
  1470  			}
  1471  
  1472  			tgPolicies[task.Name] = task.Vault
  1473  		}
  1474  
  1475  		if len(tgPolicies) != 0 {
  1476  			policies[tg.Name] = tgPolicies
  1477  		}
  1478  	}
  1479  
  1480  	return policies
  1481  }
  1482  
  1483  // RequiredSignals returns a mapping of task groups to tasks to their required
  1484  // set of signals
  1485  func (j *Job) RequiredSignals() map[string]map[string][]string {
  1486  	signals := make(map[string]map[string][]string)
  1487  
  1488  	for _, tg := range j.TaskGroups {
  1489  		for _, task := range tg.Tasks {
  1490  			// Use this local one as a set
  1491  			taskSignals := make(map[string]struct{})
  1492  
  1493  			// Check if the Vault change mode uses signals
  1494  			if task.Vault != nil && task.Vault.ChangeMode == VaultChangeModeSignal {
  1495  				taskSignals[task.Vault.ChangeSignal] = struct{}{}
  1496  			}
  1497  
  1498  			// Check if any template change mode uses signals
  1499  			for _, t := range task.Templates {
  1500  				if t.ChangeMode != TemplateChangeModeSignal {
  1501  					continue
  1502  				}
  1503  
  1504  				taskSignals[t.ChangeSignal] = struct{}{}
  1505  			}
  1506  
  1507  			// Flatten and sort the signals
  1508  			l := len(taskSignals)
  1509  			if l == 0 {
  1510  				continue
  1511  			}
  1512  
  1513  			flat := make([]string, 0, l)
  1514  			for sig := range taskSignals {
  1515  				flat = append(flat, sig)
  1516  			}
  1517  
  1518  			sort.Strings(flat)
  1519  			tgSignals, ok := signals[tg.Name]
  1520  			if !ok {
  1521  				tgSignals = make(map[string][]string)
  1522  				signals[tg.Name] = tgSignals
  1523  			}
  1524  			tgSignals[task.Name] = flat
  1525  		}
  1526  
  1527  	}
  1528  
  1529  	return signals
  1530  }
  1531  
  1532  // JobListStub is used to return a subset of job information
  1533  // for the job list
  1534  type JobListStub struct {
  1535  	ID                string
  1536  	ParentID          string
  1537  	Name              string
  1538  	Type              string
  1539  	Priority          int
  1540  	Periodic          bool
  1541  	ParameterizedJob  bool
  1542  	Stop              bool
  1543  	Status            string
  1544  	StatusDescription string
  1545  	JobSummary        *JobSummary
  1546  	CreateIndex       uint64
  1547  	ModifyIndex       uint64
  1548  	JobModifyIndex    uint64
  1549  }
  1550  
  1551  // JobSummary summarizes the state of the allocations of a job
  1552  type JobSummary struct {
  1553  	JobID string
  1554  
  1555  	// Summmary contains the summary per task group for the Job
  1556  	Summary map[string]TaskGroupSummary
  1557  
  1558  	// Children contains a summary for the children of this job.
  1559  	Children *JobChildrenSummary
  1560  
  1561  	// Raft Indexes
  1562  	CreateIndex uint64
  1563  	ModifyIndex uint64
  1564  }
  1565  
  1566  // Copy returns a new copy of JobSummary
  1567  func (js *JobSummary) Copy() *JobSummary {
  1568  	newJobSummary := new(JobSummary)
  1569  	*newJobSummary = *js
  1570  	newTGSummary := make(map[string]TaskGroupSummary, len(js.Summary))
  1571  	for k, v := range js.Summary {
  1572  		newTGSummary[k] = v
  1573  	}
  1574  	newJobSummary.Summary = newTGSummary
  1575  	newJobSummary.Children = newJobSummary.Children.Copy()
  1576  	return newJobSummary
  1577  }
  1578  
  1579  // JobChildrenSummary contains the summary of children job statuses
  1580  type JobChildrenSummary struct {
  1581  	Pending int64
  1582  	Running int64
  1583  	Dead    int64
  1584  }
  1585  
  1586  // Copy returns a new copy of a JobChildrenSummary
  1587  func (jc *JobChildrenSummary) Copy() *JobChildrenSummary {
  1588  	if jc == nil {
  1589  		return nil
  1590  	}
  1591  
  1592  	njc := new(JobChildrenSummary)
  1593  	*njc = *jc
  1594  	return njc
  1595  }
  1596  
  1597  // TaskGroup summarizes the state of all the allocations of a particular
  1598  // TaskGroup
  1599  type TaskGroupSummary struct {
  1600  	Queued   int
  1601  	Complete int
  1602  	Failed   int
  1603  	Running  int
  1604  	Starting int
  1605  	Lost     int
  1606  }
  1607  
  1608  // UpdateStrategy is used to modify how updates are done
  1609  type UpdateStrategy struct {
  1610  	// Stagger is the amount of time between the updates
  1611  	Stagger time.Duration
  1612  
  1613  	// MaxParallel is how many updates can be done in parallel
  1614  	MaxParallel int
  1615  }
  1616  
  1617  // Rolling returns if a rolling strategy should be used
  1618  func (u *UpdateStrategy) Rolling() bool {
  1619  	return u.Stagger > 0 && u.MaxParallel > 0
  1620  }
  1621  
  1622  const (
  1623  	// PeriodicSpecCron is used for a cron spec.
  1624  	PeriodicSpecCron = "cron"
  1625  
  1626  	// PeriodicSpecTest is only used by unit tests. It is a sorted, comma
  1627  	// separated list of unix timestamps at which to launch.
  1628  	PeriodicSpecTest = "_internal_test"
  1629  )
  1630  
  1631  // Periodic defines the interval a job should be run at.
  1632  type PeriodicConfig struct {
  1633  	// Enabled determines if the job should be run periodically.
  1634  	Enabled bool
  1635  
  1636  	// Spec specifies the interval the job should be run as. It is parsed based
  1637  	// on the SpecType.
  1638  	Spec string
  1639  
  1640  	// SpecType defines the format of the spec.
  1641  	SpecType string
  1642  
  1643  	// ProhibitOverlap enforces that spawned jobs do not run in parallel.
  1644  	ProhibitOverlap bool
  1645  
  1646  	// TimeZone is the user specified string that determines the time zone to
  1647  	// launch against. The time zones must be specified from IANA Time Zone
  1648  	// database, such as "America/New_York".
  1649  	// Reference: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
  1650  	// Reference: https://www.iana.org/time-zones
  1651  	TimeZone string
  1652  
  1653  	// location is the time zone to evaluate the launch time against
  1654  	location *time.Location
  1655  }
  1656  
  1657  func (p *PeriodicConfig) Copy() *PeriodicConfig {
  1658  	if p == nil {
  1659  		return nil
  1660  	}
  1661  	np := new(PeriodicConfig)
  1662  	*np = *p
  1663  	return np
  1664  }
  1665  
  1666  func (p *PeriodicConfig) Validate() error {
  1667  	if !p.Enabled {
  1668  		return nil
  1669  	}
  1670  
  1671  	var mErr multierror.Error
  1672  	if p.Spec == "" {
  1673  		multierror.Append(&mErr, fmt.Errorf("Must specify a spec"))
  1674  	}
  1675  
  1676  	// Check if we got a valid time zone
  1677  	if p.TimeZone != "" {
  1678  		if _, err := time.LoadLocation(p.TimeZone); err != nil {
  1679  			multierror.Append(&mErr, fmt.Errorf("Invalid time zone %q: %v", p.TimeZone, err))
  1680  		}
  1681  	}
  1682  
  1683  	switch p.SpecType {
  1684  	case PeriodicSpecCron:
  1685  		// Validate the cron spec
  1686  		if _, err := cronexpr.Parse(p.Spec); err != nil {
  1687  			multierror.Append(&mErr, fmt.Errorf("Invalid cron spec %q: %v", p.Spec, err))
  1688  		}
  1689  	case PeriodicSpecTest:
  1690  		// No-op
  1691  	default:
  1692  		multierror.Append(&mErr, fmt.Errorf("Unknown periodic specification type %q", p.SpecType))
  1693  	}
  1694  
  1695  	return mErr.ErrorOrNil()
  1696  }
  1697  
  1698  func (p *PeriodicConfig) Canonicalize() {
  1699  	// Load the location
  1700  	l, err := time.LoadLocation(p.TimeZone)
  1701  	if err != nil {
  1702  		p.location = time.UTC
  1703  	}
  1704  
  1705  	p.location = l
  1706  }
  1707  
  1708  // Next returns the closest time instant matching the spec that is after the
  1709  // passed time. If no matching instance exists, the zero value of time.Time is
  1710  // returned. The `time.Location` of the returned value matches that of the
  1711  // passed time.
  1712  func (p *PeriodicConfig) Next(fromTime time.Time) time.Time {
  1713  	switch p.SpecType {
  1714  	case PeriodicSpecCron:
  1715  		if e, err := cronexpr.Parse(p.Spec); err == nil {
  1716  			return e.Next(fromTime)
  1717  		}
  1718  	case PeriodicSpecTest:
  1719  		split := strings.Split(p.Spec, ",")
  1720  		if len(split) == 1 && split[0] == "" {
  1721  			return time.Time{}
  1722  		}
  1723  
  1724  		// Parse the times
  1725  		times := make([]time.Time, len(split))
  1726  		for i, s := range split {
  1727  			unix, err := strconv.Atoi(s)
  1728  			if err != nil {
  1729  				return time.Time{}
  1730  			}
  1731  
  1732  			times[i] = time.Unix(int64(unix), 0)
  1733  		}
  1734  
  1735  		// Find the next match
  1736  		for _, next := range times {
  1737  			if fromTime.Before(next) {
  1738  				return next
  1739  			}
  1740  		}
  1741  	}
  1742  
  1743  	return time.Time{}
  1744  }
  1745  
  1746  // GetLocation returns the location to use for determining the time zone to run
  1747  // the periodic job against.
  1748  func (p *PeriodicConfig) GetLocation() *time.Location {
  1749  	// Jobs pre 0.5.5 will not have this
  1750  	if p.location != nil {
  1751  		return p.location
  1752  	}
  1753  
  1754  	return time.UTC
  1755  }
  1756  
  1757  const (
  1758  	// PeriodicLaunchSuffix is the string appended to the periodic jobs ID
  1759  	// when launching derived instances of it.
  1760  	PeriodicLaunchSuffix = "/periodic-"
  1761  )
  1762  
  1763  // PeriodicLaunch tracks the last launch time of a periodic job.
  1764  type PeriodicLaunch struct {
  1765  	ID     string    // ID of the periodic job.
  1766  	Launch time.Time // The last launch time.
  1767  
  1768  	// Raft Indexes
  1769  	CreateIndex uint64
  1770  	ModifyIndex uint64
  1771  }
  1772  
  1773  const (
  1774  	DispatchPayloadForbidden = "forbidden"
  1775  	DispatchPayloadOptional  = "optional"
  1776  	DispatchPayloadRequired  = "required"
  1777  
  1778  	// DispatchLaunchSuffix is the string appended to the parameterized job's ID
  1779  	// when dispatching instances of it.
  1780  	DispatchLaunchSuffix = "/dispatch-"
  1781  )
  1782  
  1783  // ParameterizedJobConfig is used to configure the parameterized job
  1784  type ParameterizedJobConfig struct {
  1785  	// Payload configure the payload requirements
  1786  	Payload string
  1787  
  1788  	// MetaRequired is metadata keys that must be specified by the dispatcher
  1789  	MetaRequired []string
  1790  
  1791  	// MetaOptional is metadata keys that may be specified by the dispatcher
  1792  	MetaOptional []string
  1793  }
  1794  
  1795  func (d *ParameterizedJobConfig) Validate() error {
  1796  	var mErr multierror.Error
  1797  	switch d.Payload {
  1798  	case DispatchPayloadOptional, DispatchPayloadRequired, DispatchPayloadForbidden:
  1799  	default:
  1800  		multierror.Append(&mErr, fmt.Errorf("Unknown payload requirement: %q", d.Payload))
  1801  	}
  1802  
  1803  	// Check that the meta configurations are disjoint sets
  1804  	disjoint, offending := helper.SliceSetDisjoint(d.MetaRequired, d.MetaOptional)
  1805  	if !disjoint {
  1806  		multierror.Append(&mErr, fmt.Errorf("Required and optional meta keys should be disjoint. Following keys exist in both: %v", offending))
  1807  	}
  1808  
  1809  	return mErr.ErrorOrNil()
  1810  }
  1811  
  1812  func (d *ParameterizedJobConfig) Canonicalize() {
  1813  	if d.Payload == "" {
  1814  		d.Payload = DispatchPayloadOptional
  1815  	}
  1816  }
  1817  
  1818  func (d *ParameterizedJobConfig) Copy() *ParameterizedJobConfig {
  1819  	if d == nil {
  1820  		return nil
  1821  	}
  1822  	nd := new(ParameterizedJobConfig)
  1823  	*nd = *d
  1824  	nd.MetaOptional = helper.CopySliceString(nd.MetaOptional)
  1825  	nd.MetaRequired = helper.CopySliceString(nd.MetaRequired)
  1826  	return nd
  1827  }
  1828  
  1829  // DispatchedID returns an ID appropriate for a job dispatched against a
  1830  // particular parameterized job
  1831  func DispatchedID(templateID string, t time.Time) string {
  1832  	u := GenerateUUID()[:8]
  1833  	return fmt.Sprintf("%s%s%d-%s", templateID, DispatchLaunchSuffix, t.Unix(), u)
  1834  }
  1835  
  1836  // DispatchPayloadConfig configures how a task gets its input from a job dispatch
  1837  type DispatchPayloadConfig struct {
  1838  	// File specifies a relative path to where the input data should be written
  1839  	File string
  1840  }
  1841  
  1842  func (d *DispatchPayloadConfig) Copy() *DispatchPayloadConfig {
  1843  	if d == nil {
  1844  		return nil
  1845  	}
  1846  	nd := new(DispatchPayloadConfig)
  1847  	*nd = *d
  1848  	return nd
  1849  }
  1850  
  1851  func (d *DispatchPayloadConfig) Validate() error {
  1852  	// Verify the destination doesn't escape
  1853  	escaped, err := PathEscapesAllocDir("task/local/", d.File)
  1854  	if err != nil {
  1855  		return fmt.Errorf("invalid destination path: %v", err)
  1856  	} else if escaped {
  1857  		return fmt.Errorf("destination escapes allocation directory")
  1858  	}
  1859  
  1860  	return nil
  1861  }
  1862  
  1863  var (
  1864  	defaultServiceJobRestartPolicy = RestartPolicy{
  1865  		Delay:    15 * time.Second,
  1866  		Attempts: 2,
  1867  		Interval: 1 * time.Minute,
  1868  		Mode:     RestartPolicyModeDelay,
  1869  	}
  1870  	defaultBatchJobRestartPolicy = RestartPolicy{
  1871  		Delay:    15 * time.Second,
  1872  		Attempts: 15,
  1873  		Interval: 7 * 24 * time.Hour,
  1874  		Mode:     RestartPolicyModeDelay,
  1875  	}
  1876  )
  1877  
  1878  const (
  1879  	// RestartPolicyModeDelay causes an artificial delay till the next interval is
  1880  	// reached when the specified attempts have been reached in the interval.
  1881  	RestartPolicyModeDelay = "delay"
  1882  
  1883  	// RestartPolicyModeFail causes a job to fail if the specified number of
  1884  	// attempts are reached within an interval.
  1885  	RestartPolicyModeFail = "fail"
  1886  
  1887  	// RestartPolicyMinInterval is the minimum interval that is accepted for a
  1888  	// restart policy.
  1889  	RestartPolicyMinInterval = 5 * time.Second
  1890  )
  1891  
  1892  // RestartPolicy configures how Tasks are restarted when they crash or fail.
  1893  type RestartPolicy struct {
  1894  	// Attempts is the number of restart that will occur in an interval.
  1895  	Attempts int
  1896  
  1897  	// Interval is a duration in which we can limit the number of restarts
  1898  	// within.
  1899  	Interval time.Duration
  1900  
  1901  	// Delay is the time between a failure and a restart.
  1902  	Delay time.Duration
  1903  
  1904  	// Mode controls what happens when the task restarts more than attempt times
  1905  	// in an interval.
  1906  	Mode string
  1907  }
  1908  
  1909  func (r *RestartPolicy) Copy() *RestartPolicy {
  1910  	if r == nil {
  1911  		return nil
  1912  	}
  1913  	nrp := new(RestartPolicy)
  1914  	*nrp = *r
  1915  	return nrp
  1916  }
  1917  
  1918  func (r *RestartPolicy) Validate() error {
  1919  	var mErr multierror.Error
  1920  	switch r.Mode {
  1921  	case RestartPolicyModeDelay, RestartPolicyModeFail:
  1922  	default:
  1923  		multierror.Append(&mErr, fmt.Errorf("Unsupported restart mode: %q", r.Mode))
  1924  	}
  1925  
  1926  	// Check for ambiguous/confusing settings
  1927  	if r.Attempts == 0 && r.Mode != RestartPolicyModeFail {
  1928  		multierror.Append(&mErr, fmt.Errorf("Restart policy %q with %d attempts is ambiguous", r.Mode, r.Attempts))
  1929  	}
  1930  
  1931  	if r.Interval.Nanoseconds() < RestartPolicyMinInterval.Nanoseconds() {
  1932  		multierror.Append(&mErr, fmt.Errorf("Interval can not be less than %v (got %v)", RestartPolicyMinInterval, r.Interval))
  1933  	}
  1934  	if time.Duration(r.Attempts)*r.Delay > r.Interval {
  1935  		multierror.Append(&mErr,
  1936  			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))
  1937  	}
  1938  	return mErr.ErrorOrNil()
  1939  }
  1940  
  1941  func NewRestartPolicy(jobType string) *RestartPolicy {
  1942  	switch jobType {
  1943  	case JobTypeService, JobTypeSystem:
  1944  		rp := defaultServiceJobRestartPolicy
  1945  		return &rp
  1946  	case JobTypeBatch:
  1947  		rp := defaultBatchJobRestartPolicy
  1948  		return &rp
  1949  	}
  1950  	return nil
  1951  }
  1952  
  1953  // TaskGroup is an atomic unit of placement. Each task group belongs to
  1954  // a job and may contain any number of tasks. A task group support running
  1955  // in many replicas using the same configuration..
  1956  type TaskGroup struct {
  1957  	// Name of the task group
  1958  	Name string
  1959  
  1960  	// Count is the number of replicas of this task group that should
  1961  	// be scheduled.
  1962  	Count int
  1963  
  1964  	// Constraints can be specified at a task group level and apply to
  1965  	// all the tasks contained.
  1966  	Constraints []*Constraint
  1967  
  1968  	//RestartPolicy of a TaskGroup
  1969  	RestartPolicy *RestartPolicy
  1970  
  1971  	// Tasks are the collection of tasks that this task group needs to run
  1972  	Tasks []*Task
  1973  
  1974  	// EphemeralDisk is the disk resources that the task group requests
  1975  	EphemeralDisk *EphemeralDisk
  1976  
  1977  	// Meta is used to associate arbitrary metadata with this
  1978  	// task group. This is opaque to Nomad.
  1979  	Meta map[string]string
  1980  }
  1981  
  1982  func (tg *TaskGroup) Copy() *TaskGroup {
  1983  	if tg == nil {
  1984  		return nil
  1985  	}
  1986  	ntg := new(TaskGroup)
  1987  	*ntg = *tg
  1988  	ntg.Constraints = CopySliceConstraints(ntg.Constraints)
  1989  
  1990  	ntg.RestartPolicy = ntg.RestartPolicy.Copy()
  1991  
  1992  	if tg.Tasks != nil {
  1993  		tasks := make([]*Task, len(ntg.Tasks))
  1994  		for i, t := range ntg.Tasks {
  1995  			tasks[i] = t.Copy()
  1996  		}
  1997  		ntg.Tasks = tasks
  1998  	}
  1999  
  2000  	ntg.Meta = helper.CopyMapStringString(ntg.Meta)
  2001  
  2002  	if tg.EphemeralDisk != nil {
  2003  		ntg.EphemeralDisk = tg.EphemeralDisk.Copy()
  2004  	}
  2005  	return ntg
  2006  }
  2007  
  2008  // Canonicalize is used to canonicalize fields in the TaskGroup.
  2009  func (tg *TaskGroup) Canonicalize(job *Job) {
  2010  	// Ensure that an empty and nil map are treated the same to avoid scheduling
  2011  	// problems since we use reflect DeepEquals.
  2012  	if len(tg.Meta) == 0 {
  2013  		tg.Meta = nil
  2014  	}
  2015  
  2016  	// Set the default restart policy.
  2017  	if tg.RestartPolicy == nil {
  2018  		tg.RestartPolicy = NewRestartPolicy(job.Type)
  2019  	}
  2020  
  2021  	// Set a default ephemeral disk object if the user has not requested for one
  2022  	if tg.EphemeralDisk == nil {
  2023  		tg.EphemeralDisk = DefaultEphemeralDisk()
  2024  	}
  2025  
  2026  	for _, task := range tg.Tasks {
  2027  		task.Canonicalize(job, tg)
  2028  	}
  2029  
  2030  	// Add up the disk resources to EphemeralDisk. This is done so that users
  2031  	// are not required to move their disk attribute from resources to
  2032  	// EphemeralDisk section of the job spec in Nomad 0.5
  2033  	// COMPAT 0.4.1 -> 0.5
  2034  	// Remove in 0.6
  2035  	var diskMB int
  2036  	for _, task := range tg.Tasks {
  2037  		diskMB += task.Resources.DiskMB
  2038  	}
  2039  	if diskMB > 0 {
  2040  		tg.EphemeralDisk.SizeMB = diskMB
  2041  	}
  2042  }
  2043  
  2044  // Validate is used to sanity check a task group
  2045  func (tg *TaskGroup) Validate() error {
  2046  	var mErr multierror.Error
  2047  	if tg.Name == "" {
  2048  		mErr.Errors = append(mErr.Errors, errors.New("Missing task group name"))
  2049  	}
  2050  	if tg.Count < 0 {
  2051  		mErr.Errors = append(mErr.Errors, errors.New("Task group count can't be negative"))
  2052  	}
  2053  	if len(tg.Tasks) == 0 {
  2054  		mErr.Errors = append(mErr.Errors, errors.New("Missing tasks for task group"))
  2055  	}
  2056  	for idx, constr := range tg.Constraints {
  2057  		if err := constr.Validate(); err != nil {
  2058  			outer := fmt.Errorf("Constraint %d validation failed: %s", idx+1, err)
  2059  			mErr.Errors = append(mErr.Errors, outer)
  2060  		}
  2061  	}
  2062  
  2063  	if tg.RestartPolicy != nil {
  2064  		if err := tg.RestartPolicy.Validate(); err != nil {
  2065  			mErr.Errors = append(mErr.Errors, err)
  2066  		}
  2067  	} else {
  2068  		mErr.Errors = append(mErr.Errors, fmt.Errorf("Task Group %v should have a restart policy", tg.Name))
  2069  	}
  2070  
  2071  	if tg.EphemeralDisk != nil {
  2072  		if err := tg.EphemeralDisk.Validate(); err != nil {
  2073  			mErr.Errors = append(mErr.Errors, err)
  2074  		}
  2075  	} else {
  2076  		mErr.Errors = append(mErr.Errors, fmt.Errorf("Task Group %v should have an ephemeral disk object", tg.Name))
  2077  	}
  2078  
  2079  	// Check for duplicate tasks and that there is only leader task if any
  2080  	tasks := make(map[string]int)
  2081  	leaderTasks := 0
  2082  	for idx, task := range tg.Tasks {
  2083  		if task.Name == "" {
  2084  			mErr.Errors = append(mErr.Errors, fmt.Errorf("Task %d missing name", idx+1))
  2085  		} else if existing, ok := tasks[task.Name]; ok {
  2086  			mErr.Errors = append(mErr.Errors, fmt.Errorf("Task %d redefines '%s' from task %d", idx+1, task.Name, existing+1))
  2087  		} else {
  2088  			tasks[task.Name] = idx
  2089  		}
  2090  
  2091  		if task.Leader {
  2092  			leaderTasks++
  2093  		}
  2094  	}
  2095  
  2096  	if leaderTasks > 1 {
  2097  		mErr.Errors = append(mErr.Errors, fmt.Errorf("Only one task may be marked as leader"))
  2098  	}
  2099  
  2100  	// Validate the tasks
  2101  	for _, task := range tg.Tasks {
  2102  		if err := task.Validate(tg.EphemeralDisk); err != nil {
  2103  			outer := fmt.Errorf("Task %s validation failed: %v", task.Name, err)
  2104  			mErr.Errors = append(mErr.Errors, outer)
  2105  		}
  2106  	}
  2107  	return mErr.ErrorOrNil()
  2108  }
  2109  
  2110  // LookupTask finds a task by name
  2111  func (tg *TaskGroup) LookupTask(name string) *Task {
  2112  	for _, t := range tg.Tasks {
  2113  		if t.Name == name {
  2114  			return t
  2115  		}
  2116  	}
  2117  	return nil
  2118  }
  2119  
  2120  func (tg *TaskGroup) GoString() string {
  2121  	return fmt.Sprintf("*%#v", *tg)
  2122  }
  2123  
  2124  const (
  2125  	// TODO add Consul TTL check
  2126  	ServiceCheckHTTP   = "http"
  2127  	ServiceCheckTCP    = "tcp"
  2128  	ServiceCheckScript = "script"
  2129  
  2130  	// minCheckInterval is the minimum check interval permitted.  Consul
  2131  	// currently has its MinInterval set to 1s.  Mirror that here for
  2132  	// consistency.
  2133  	minCheckInterval = 1 * time.Second
  2134  
  2135  	// minCheckTimeout is the minimum check timeout permitted for Consul
  2136  	// script TTL checks.
  2137  	minCheckTimeout = 1 * time.Second
  2138  )
  2139  
  2140  // The ServiceCheck data model represents the consul health check that
  2141  // Nomad registers for a Task
  2142  type ServiceCheck struct {
  2143  	Name          string        // Name of the check, defaults to id
  2144  	Type          string        // Type of the check - tcp, http, docker and script
  2145  	Command       string        // Command is the command to run for script checks
  2146  	Args          []string      // Args is a list of argumes for script checks
  2147  	Path          string        // path of the health check url for http type check
  2148  	Protocol      string        // Protocol to use if check is http, defaults to http
  2149  	PortLabel     string        // The port to use for tcp/http checks
  2150  	Interval      time.Duration // Interval of the check
  2151  	Timeout       time.Duration // Timeout of the response from the check before consul fails the check
  2152  	InitialStatus string        // Initial status of the check
  2153  	TLSSkipVerify bool          // Skip TLS verification when Protocol=https
  2154  }
  2155  
  2156  func (sc *ServiceCheck) Copy() *ServiceCheck {
  2157  	if sc == nil {
  2158  		return nil
  2159  	}
  2160  	nsc := new(ServiceCheck)
  2161  	*nsc = *sc
  2162  	return nsc
  2163  }
  2164  
  2165  func (sc *ServiceCheck) Canonicalize(serviceName string) {
  2166  	// Ensure empty slices are treated as null to avoid scheduling issues when
  2167  	// using DeepEquals.
  2168  	if len(sc.Args) == 0 {
  2169  		sc.Args = nil
  2170  	}
  2171  
  2172  	if sc.Name == "" {
  2173  		sc.Name = fmt.Sprintf("service: %q check", serviceName)
  2174  	}
  2175  }
  2176  
  2177  // validate a Service's ServiceCheck
  2178  func (sc *ServiceCheck) validate() error {
  2179  	switch strings.ToLower(sc.Type) {
  2180  	case ServiceCheckTCP:
  2181  		if sc.Timeout == 0 {
  2182  			return fmt.Errorf("missing required value timeout. Timeout cannot be less than %v", minCheckInterval)
  2183  		} else if sc.Timeout < minCheckTimeout {
  2184  			return fmt.Errorf("timeout (%v) is lower than required minimum timeout %v", sc.Timeout, minCheckInterval)
  2185  		}
  2186  	case ServiceCheckHTTP:
  2187  		if sc.Path == "" {
  2188  			return fmt.Errorf("http type must have a valid http path")
  2189  		}
  2190  
  2191  		if sc.Timeout == 0 {
  2192  			return fmt.Errorf("missing required value timeout. Timeout cannot be less than %v", minCheckInterval)
  2193  		} else if sc.Timeout < minCheckTimeout {
  2194  			return fmt.Errorf("timeout (%v) is lower than required minimum timeout %v", sc.Timeout, minCheckInterval)
  2195  		}
  2196  	case ServiceCheckScript:
  2197  		if sc.Command == "" {
  2198  			return fmt.Errorf("script type must have a valid script path")
  2199  		}
  2200  
  2201  		// TODO: enforce timeout on the Client side and reenable
  2202  		// validation.
  2203  	default:
  2204  		return fmt.Errorf(`invalid type (%+q), must be one of "http", "tcp", or "script" type`, sc.Type)
  2205  	}
  2206  
  2207  	if sc.Interval == 0 {
  2208  		return fmt.Errorf("missing required value interval. Interval cannot be less than %v", minCheckInterval)
  2209  	} else if sc.Interval < minCheckInterval {
  2210  		return fmt.Errorf("interval (%v) cannot be lower than %v", sc.Interval, minCheckInterval)
  2211  	}
  2212  
  2213  	switch sc.InitialStatus {
  2214  	case "":
  2215  		// case api.HealthUnknown: TODO: Add when Consul releases 0.7.1
  2216  	case api.HealthPassing:
  2217  	case api.HealthWarning:
  2218  	case api.HealthCritical:
  2219  	default:
  2220  		return fmt.Errorf(`invalid initial check state (%s), must be one of %q, %q, %q or empty`, sc.InitialStatus, api.HealthPassing, api.HealthWarning, api.HealthCritical)
  2221  
  2222  	}
  2223  
  2224  	return nil
  2225  }
  2226  
  2227  // RequiresPort returns whether the service check requires the task has a port.
  2228  func (sc *ServiceCheck) RequiresPort() bool {
  2229  	switch sc.Type {
  2230  	case ServiceCheckHTTP, ServiceCheckTCP:
  2231  		return true
  2232  	default:
  2233  		return false
  2234  	}
  2235  }
  2236  
  2237  // Hash all ServiceCheck fields and the check's corresponding service ID to
  2238  // create an identifier. The identifier is not guaranteed to be unique as if
  2239  // the PortLabel is blank, the Service's PortLabel will be used after Hash is
  2240  // called.
  2241  func (sc *ServiceCheck) Hash(serviceID string) string {
  2242  	h := sha1.New()
  2243  	io.WriteString(h, serviceID)
  2244  	io.WriteString(h, sc.Name)
  2245  	io.WriteString(h, sc.Type)
  2246  	io.WriteString(h, sc.Command)
  2247  	io.WriteString(h, strings.Join(sc.Args, ""))
  2248  	io.WriteString(h, sc.Path)
  2249  	io.WriteString(h, sc.Protocol)
  2250  	io.WriteString(h, sc.PortLabel)
  2251  	io.WriteString(h, sc.Interval.String())
  2252  	io.WriteString(h, sc.Timeout.String())
  2253  	// Only include TLSSkipVerify if set to maintain ID stability with Nomad <0.6
  2254  	if sc.TLSSkipVerify {
  2255  		io.WriteString(h, "true")
  2256  	}
  2257  	return fmt.Sprintf("%x", h.Sum(nil))
  2258  }
  2259  
  2260  // Service represents a Consul service definition in Nomad
  2261  type Service struct {
  2262  	// Name of the service registered with Consul. Consul defaults the
  2263  	// Name to ServiceID if not specified.  The Name if specified is used
  2264  	// as one of the seed values when generating a Consul ServiceID.
  2265  	Name string
  2266  
  2267  	// PortLabel is either the numeric port number or the `host:port`.
  2268  	// To specify the port number using the host's Consul Advertise
  2269  	// address, specify an empty host in the PortLabel (e.g. `:port`).
  2270  	PortLabel string
  2271  	Tags      []string        // List of tags for the service
  2272  	Checks    []*ServiceCheck // List of checks associated with the service
  2273  }
  2274  
  2275  func (s *Service) Copy() *Service {
  2276  	if s == nil {
  2277  		return nil
  2278  	}
  2279  	ns := new(Service)
  2280  	*ns = *s
  2281  	ns.Tags = helper.CopySliceString(ns.Tags)
  2282  
  2283  	if s.Checks != nil {
  2284  		checks := make([]*ServiceCheck, len(ns.Checks))
  2285  		for i, c := range ns.Checks {
  2286  			checks[i] = c.Copy()
  2287  		}
  2288  		ns.Checks = checks
  2289  	}
  2290  
  2291  	return ns
  2292  }
  2293  
  2294  // Canonicalize interpolates values of Job, Task Group and Task in the Service
  2295  // Name. This also generates check names, service id and check ids.
  2296  func (s *Service) Canonicalize(job string, taskGroup string, task string) {
  2297  	// Ensure empty lists are treated as null to avoid scheduler issues when
  2298  	// using DeepEquals
  2299  	if len(s.Tags) == 0 {
  2300  		s.Tags = nil
  2301  	}
  2302  	if len(s.Checks) == 0 {
  2303  		s.Checks = nil
  2304  	}
  2305  
  2306  	s.Name = args.ReplaceEnv(s.Name, map[string]string{
  2307  		"JOB":       job,
  2308  		"TASKGROUP": taskGroup,
  2309  		"TASK":      task,
  2310  		"BASE":      fmt.Sprintf("%s-%s-%s", job, taskGroup, task),
  2311  	},
  2312  	)
  2313  
  2314  	for _, check := range s.Checks {
  2315  		check.Canonicalize(s.Name)
  2316  	}
  2317  }
  2318  
  2319  // Validate checks if the Check definition is valid
  2320  func (s *Service) Validate() error {
  2321  	var mErr multierror.Error
  2322  
  2323  	// Ensure the service name is valid per the below RFCs but make an exception
  2324  	// for our interpolation syntax
  2325  	// RFC-952 §1 (https://tools.ietf.org/html/rfc952), RFC-1123 §2.1
  2326  	// (https://tools.ietf.org/html/rfc1123), and RFC-2782
  2327  	// (https://tools.ietf.org/html/rfc2782).
  2328  	re := regexp.MustCompile(`^(?i:[a-z0-9]|[a-z0-9\$][a-zA-Z0-9\-\$\{\}\_\.]*[a-z0-9\}])$`)
  2329  	if !re.MatchString(s.Name) {
  2330  		mErr.Errors = append(mErr.Errors, fmt.Errorf("service name must be valid per RFC 1123 and can contain only alphanumeric characters or dashes: %q", s.Name))
  2331  	}
  2332  
  2333  	for _, c := range s.Checks {
  2334  		if s.PortLabel == "" && c.RequiresPort() {
  2335  			mErr.Errors = append(mErr.Errors, fmt.Errorf("check %s invalid: check requires a port but the service %+q has no port", c.Name, s.Name))
  2336  			continue
  2337  		}
  2338  
  2339  		if err := c.validate(); err != nil {
  2340  			mErr.Errors = append(mErr.Errors, fmt.Errorf("check %s invalid: %v", c.Name, err))
  2341  		}
  2342  	}
  2343  	return mErr.ErrorOrNil()
  2344  }
  2345  
  2346  // ValidateName checks if the services Name is valid and should be called after
  2347  // the name has been interpolated
  2348  func (s *Service) ValidateName(name string) error {
  2349  	// Ensure the service name is valid per RFC-952 §1
  2350  	// (https://tools.ietf.org/html/rfc952), RFC-1123 §2.1
  2351  	// (https://tools.ietf.org/html/rfc1123), and RFC-2782
  2352  	// (https://tools.ietf.org/html/rfc2782).
  2353  	re := regexp.MustCompile(`^(?i:[a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])$`)
  2354  	if !re.MatchString(name) {
  2355  		return fmt.Errorf("service name must be valid per RFC 1123 and can contain only alphanumeric characters or dashes and must be no longer than 63 characters: %q", name)
  2356  	}
  2357  	return nil
  2358  }
  2359  
  2360  // Hash calculates the hash of the check based on it's content and the service
  2361  // which owns it
  2362  func (s *Service) Hash() string {
  2363  	h := sha1.New()
  2364  	io.WriteString(h, s.Name)
  2365  	io.WriteString(h, strings.Join(s.Tags, ""))
  2366  	io.WriteString(h, s.PortLabel)
  2367  	return fmt.Sprintf("%x", h.Sum(nil))
  2368  }
  2369  
  2370  const (
  2371  	// DefaultKillTimeout is the default timeout between signaling a task it
  2372  	// will be killed and killing it.
  2373  	DefaultKillTimeout = 5 * time.Second
  2374  )
  2375  
  2376  // LogConfig provides configuration for log rotation
  2377  type LogConfig struct {
  2378  	MaxFiles      int
  2379  	MaxFileSizeMB int
  2380  }
  2381  
  2382  // DefaultLogConfig returns the default LogConfig values.
  2383  func DefaultLogConfig() *LogConfig {
  2384  	return &LogConfig{
  2385  		MaxFiles:      10,
  2386  		MaxFileSizeMB: 10,
  2387  	}
  2388  }
  2389  
  2390  // Validate returns an error if the log config specified are less than
  2391  // the minimum allowed.
  2392  func (l *LogConfig) Validate() error {
  2393  	var mErr multierror.Error
  2394  	if l.MaxFiles < 1 {
  2395  		mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum number of files is 1; got %d", l.MaxFiles))
  2396  	}
  2397  	if l.MaxFileSizeMB < 1 {
  2398  		mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum file size is 1MB; got %d", l.MaxFileSizeMB))
  2399  	}
  2400  	return mErr.ErrorOrNil()
  2401  }
  2402  
  2403  // Task is a single process typically that is executed as part of a task group.
  2404  type Task struct {
  2405  	// Name of the task
  2406  	Name string
  2407  
  2408  	// Driver is used to control which driver is used
  2409  	Driver string
  2410  
  2411  	// User is used to determine which user will run the task. It defaults to
  2412  	// the same user the Nomad client is being run as.
  2413  	User string
  2414  
  2415  	// Config is provided to the driver to initialize
  2416  	Config map[string]interface{}
  2417  
  2418  	// Map of environment variables to be used by the driver
  2419  	Env map[string]string
  2420  
  2421  	// List of service definitions exposed by the Task
  2422  	Services []*Service
  2423  
  2424  	// Vault is used to define the set of Vault policies that this task should
  2425  	// have access to.
  2426  	Vault *Vault
  2427  
  2428  	// Templates are the set of templates to be rendered for the task.
  2429  	Templates []*Template
  2430  
  2431  	// Constraints can be specified at a task level and apply only to
  2432  	// the particular task.
  2433  	Constraints []*Constraint
  2434  
  2435  	// Resources is the resources needed by this task
  2436  	Resources *Resources
  2437  
  2438  	// DispatchPayload configures how the task retrieves its input from a dispatch
  2439  	DispatchPayload *DispatchPayloadConfig
  2440  
  2441  	// Meta is used to associate arbitrary metadata with this
  2442  	// task. This is opaque to Nomad.
  2443  	Meta map[string]string
  2444  
  2445  	// KillTimeout is the time between signaling a task that it will be
  2446  	// killed and killing it.
  2447  	KillTimeout time.Duration
  2448  
  2449  	// LogConfig provides configuration for log rotation
  2450  	LogConfig *LogConfig
  2451  
  2452  	// Artifacts is a list of artifacts to download and extract before running
  2453  	// the task.
  2454  	Artifacts []*TaskArtifact
  2455  
  2456  	// Leader marks the task as the leader within the group. When the leader
  2457  	// task exits, other tasks will be gracefully terminated.
  2458  	Leader bool
  2459  }
  2460  
  2461  func (t *Task) Copy() *Task {
  2462  	if t == nil {
  2463  		return nil
  2464  	}
  2465  	nt := new(Task)
  2466  	*nt = *t
  2467  	nt.Env = helper.CopyMapStringString(nt.Env)
  2468  
  2469  	if t.Services != nil {
  2470  		services := make([]*Service, len(nt.Services))
  2471  		for i, s := range nt.Services {
  2472  			services[i] = s.Copy()
  2473  		}
  2474  		nt.Services = services
  2475  	}
  2476  
  2477  	nt.Constraints = CopySliceConstraints(nt.Constraints)
  2478  
  2479  	nt.Vault = nt.Vault.Copy()
  2480  	nt.Resources = nt.Resources.Copy()
  2481  	nt.Meta = helper.CopyMapStringString(nt.Meta)
  2482  	nt.DispatchPayload = nt.DispatchPayload.Copy()
  2483  
  2484  	if t.Artifacts != nil {
  2485  		artifacts := make([]*TaskArtifact, 0, len(t.Artifacts))
  2486  		for _, a := range nt.Artifacts {
  2487  			artifacts = append(artifacts, a.Copy())
  2488  		}
  2489  		nt.Artifacts = artifacts
  2490  	}
  2491  
  2492  	if i, err := copystructure.Copy(nt.Config); err != nil {
  2493  		nt.Config = i.(map[string]interface{})
  2494  	}
  2495  
  2496  	if t.Templates != nil {
  2497  		templates := make([]*Template, len(t.Templates))
  2498  		for i, tmpl := range nt.Templates {
  2499  			templates[i] = tmpl.Copy()
  2500  		}
  2501  		nt.Templates = templates
  2502  	}
  2503  
  2504  	return nt
  2505  }
  2506  
  2507  // Canonicalize canonicalizes fields in the task.
  2508  func (t *Task) Canonicalize(job *Job, tg *TaskGroup) {
  2509  	// Ensure that an empty and nil map are treated the same to avoid scheduling
  2510  	// problems since we use reflect DeepEquals.
  2511  	if len(t.Meta) == 0 {
  2512  		t.Meta = nil
  2513  	}
  2514  	if len(t.Config) == 0 {
  2515  		t.Config = nil
  2516  	}
  2517  	if len(t.Env) == 0 {
  2518  		t.Env = nil
  2519  	}
  2520  
  2521  	for _, service := range t.Services {
  2522  		service.Canonicalize(job.Name, tg.Name, t.Name)
  2523  	}
  2524  
  2525  	// If Resources are nil initialize them to defaults, otherwise canonicalize
  2526  	if t.Resources == nil {
  2527  		t.Resources = DefaultResources()
  2528  	} else {
  2529  		t.Resources.Canonicalize()
  2530  	}
  2531  
  2532  	// Set the default timeout if it is not specified.
  2533  	if t.KillTimeout == 0 {
  2534  		t.KillTimeout = DefaultKillTimeout
  2535  	}
  2536  
  2537  	if t.Vault != nil {
  2538  		t.Vault.Canonicalize()
  2539  	}
  2540  
  2541  	for _, template := range t.Templates {
  2542  		template.Canonicalize()
  2543  	}
  2544  }
  2545  
  2546  func (t *Task) GoString() string {
  2547  	return fmt.Sprintf("*%#v", *t)
  2548  }
  2549  
  2550  func (t *Task) FindHostAndPortFor(portLabel string) (string, int) {
  2551  	for _, network := range t.Resources.Networks {
  2552  		if p, ok := network.MapLabelToValues(nil)[portLabel]; ok {
  2553  			return network.IP, p
  2554  		}
  2555  	}
  2556  	return "", 0
  2557  }
  2558  
  2559  // Validate is used to sanity check a task
  2560  func (t *Task) Validate(ephemeralDisk *EphemeralDisk) error {
  2561  	var mErr multierror.Error
  2562  	if t.Name == "" {
  2563  		mErr.Errors = append(mErr.Errors, errors.New("Missing task name"))
  2564  	}
  2565  	if strings.ContainsAny(t.Name, `/\`) {
  2566  		// We enforce this so that when creating the directory on disk it will
  2567  		// not have any slashes.
  2568  		mErr.Errors = append(mErr.Errors, errors.New("Task name cannot include slashes"))
  2569  	}
  2570  	if t.Driver == "" {
  2571  		mErr.Errors = append(mErr.Errors, errors.New("Missing task driver"))
  2572  	}
  2573  	if t.KillTimeout.Nanoseconds() < 0 {
  2574  		mErr.Errors = append(mErr.Errors, errors.New("KillTimeout must be a positive value"))
  2575  	}
  2576  
  2577  	// Validate the resources.
  2578  	if t.Resources == nil {
  2579  		mErr.Errors = append(mErr.Errors, errors.New("Missing task resources"))
  2580  	} else {
  2581  		if err := t.Resources.MeetsMinResources(); err != nil {
  2582  			mErr.Errors = append(mErr.Errors, err)
  2583  		}
  2584  
  2585  		// Ensure the task isn't asking for disk resources
  2586  		if t.Resources.DiskMB > 0 {
  2587  			mErr.Errors = append(mErr.Errors, errors.New("Task can't ask for disk resources, they have to be specified at the task group level."))
  2588  		}
  2589  	}
  2590  
  2591  	// Validate the log config
  2592  	if t.LogConfig == nil {
  2593  		mErr.Errors = append(mErr.Errors, errors.New("Missing Log Config"))
  2594  	} else if err := t.LogConfig.Validate(); err != nil {
  2595  		mErr.Errors = append(mErr.Errors, err)
  2596  	}
  2597  
  2598  	for idx, constr := range t.Constraints {
  2599  		if err := constr.Validate(); err != nil {
  2600  			outer := fmt.Errorf("Constraint %d validation failed: %s", idx+1, err)
  2601  			mErr.Errors = append(mErr.Errors, outer)
  2602  		}
  2603  
  2604  		switch constr.Operand {
  2605  		case ConstraintDistinctHosts, ConstraintDistinctProperty:
  2606  			outer := fmt.Errorf("Constraint %d has disallowed Operand at task level: %s", idx+1, constr.Operand)
  2607  			mErr.Errors = append(mErr.Errors, outer)
  2608  		}
  2609  	}
  2610  
  2611  	// Validate Services
  2612  	if err := validateServices(t); err != nil {
  2613  		mErr.Errors = append(mErr.Errors, err)
  2614  	}
  2615  
  2616  	if t.LogConfig != nil && ephemeralDisk != nil {
  2617  		logUsage := (t.LogConfig.MaxFiles * t.LogConfig.MaxFileSizeMB)
  2618  		if ephemeralDisk.SizeMB <= logUsage {
  2619  			mErr.Errors = append(mErr.Errors,
  2620  				fmt.Errorf("log storage (%d MB) must be less than requested disk capacity (%d MB)",
  2621  					logUsage, ephemeralDisk.SizeMB))
  2622  		}
  2623  	}
  2624  
  2625  	for idx, artifact := range t.Artifacts {
  2626  		if err := artifact.Validate(); err != nil {
  2627  			outer := fmt.Errorf("Artifact %d validation failed: %v", idx+1, err)
  2628  			mErr.Errors = append(mErr.Errors, outer)
  2629  		}
  2630  	}
  2631  
  2632  	if t.Vault != nil {
  2633  		if err := t.Vault.Validate(); err != nil {
  2634  			mErr.Errors = append(mErr.Errors, fmt.Errorf("Vault validation failed: %v", err))
  2635  		}
  2636  	}
  2637  
  2638  	destinations := make(map[string]int, len(t.Templates))
  2639  	for idx, tmpl := range t.Templates {
  2640  		if err := tmpl.Validate(); err != nil {
  2641  			outer := fmt.Errorf("Template %d validation failed: %s", idx+1, err)
  2642  			mErr.Errors = append(mErr.Errors, outer)
  2643  		}
  2644  
  2645  		if other, ok := destinations[tmpl.DestPath]; ok {
  2646  			outer := fmt.Errorf("Template %d has same destination as %d", idx+1, other)
  2647  			mErr.Errors = append(mErr.Errors, outer)
  2648  		} else {
  2649  			destinations[tmpl.DestPath] = idx + 1
  2650  		}
  2651  	}
  2652  
  2653  	// Validate the dispatch payload block if there
  2654  	if t.DispatchPayload != nil {
  2655  		if err := t.DispatchPayload.Validate(); err != nil {
  2656  			mErr.Errors = append(mErr.Errors, fmt.Errorf("Dispatch Payload validation failed: %v", err))
  2657  		}
  2658  	}
  2659  
  2660  	return mErr.ErrorOrNil()
  2661  }
  2662  
  2663  // validateServices takes a task and validates the services within it are valid
  2664  // and reference ports that exist.
  2665  func validateServices(t *Task) error {
  2666  	var mErr multierror.Error
  2667  
  2668  	// Ensure that services don't ask for non-existent ports and their names are
  2669  	// unique.
  2670  	servicePorts := make(map[string][]string)
  2671  	knownServices := make(map[string]struct{})
  2672  	for i, service := range t.Services {
  2673  		if err := service.Validate(); err != nil {
  2674  			outer := fmt.Errorf("service[%d] %+q validation failed: %s", i, service.Name, err)
  2675  			mErr.Errors = append(mErr.Errors, outer)
  2676  		}
  2677  
  2678  		// Ensure that services with the same name are not being registered for
  2679  		// the same port
  2680  		if _, ok := knownServices[service.Name+service.PortLabel]; ok {
  2681  			mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q is duplicate", service.Name))
  2682  		}
  2683  		knownServices[service.Name+service.PortLabel] = struct{}{}
  2684  
  2685  		if service.PortLabel != "" {
  2686  			servicePorts[service.PortLabel] = append(servicePorts[service.PortLabel], service.Name)
  2687  		}
  2688  
  2689  		// Ensure that check names are unique.
  2690  		knownChecks := make(map[string]struct{})
  2691  		for _, check := range service.Checks {
  2692  			if _, ok := knownChecks[check.Name]; ok {
  2693  				mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q is duplicate", check.Name))
  2694  			}
  2695  			knownChecks[check.Name] = struct{}{}
  2696  		}
  2697  	}
  2698  
  2699  	// Get the set of port labels.
  2700  	portLabels := make(map[string]struct{})
  2701  	if t.Resources != nil {
  2702  		for _, network := range t.Resources.Networks {
  2703  			ports := network.MapLabelToValues(nil)
  2704  			for portLabel, _ := range ports {
  2705  				portLabels[portLabel] = struct{}{}
  2706  			}
  2707  		}
  2708  	}
  2709  
  2710  	// Ensure all ports referenced in services exist.
  2711  	for servicePort, services := range servicePorts {
  2712  		_, ok := portLabels[servicePort]
  2713  		if !ok {
  2714  			joined := strings.Join(services, ", ")
  2715  			err := fmt.Errorf("port label %q referenced by services %v does not exist", servicePort, joined)
  2716  			mErr.Errors = append(mErr.Errors, err)
  2717  		}
  2718  	}
  2719  	return mErr.ErrorOrNil()
  2720  }
  2721  
  2722  const (
  2723  	// TemplateChangeModeNoop marks that no action should be taken if the
  2724  	// template is re-rendered
  2725  	TemplateChangeModeNoop = "noop"
  2726  
  2727  	// TemplateChangeModeSignal marks that the task should be signaled if the
  2728  	// template is re-rendered
  2729  	TemplateChangeModeSignal = "signal"
  2730  
  2731  	// TemplateChangeModeRestart marks that the task should be restarted if the
  2732  	// template is re-rendered
  2733  	TemplateChangeModeRestart = "restart"
  2734  )
  2735  
  2736  var (
  2737  	// TemplateChangeModeInvalidError is the error for when an invalid change
  2738  	// mode is given
  2739  	TemplateChangeModeInvalidError = errors.New("Invalid change mode. Must be one of the following: noop, signal, restart")
  2740  )
  2741  
  2742  // Template represents a template configuration to be rendered for a given task
  2743  type Template struct {
  2744  	// SourcePath is the path to the template to be rendered
  2745  	SourcePath string
  2746  
  2747  	// DestPath is the path to where the template should be rendered
  2748  	DestPath string
  2749  
  2750  	// EmbeddedTmpl store the raw template. This is useful for smaller templates
  2751  	// where they are embedded in the job file rather than sent as an artificat
  2752  	EmbeddedTmpl string
  2753  
  2754  	// ChangeMode indicates what should be done if the template is re-rendered
  2755  	ChangeMode string
  2756  
  2757  	// ChangeSignal is the signal that should be sent if the change mode
  2758  	// requires it.
  2759  	ChangeSignal string
  2760  
  2761  	// Splay is used to avoid coordinated restarts of processes by applying a
  2762  	// random wait between 0 and the given splay value before signalling the
  2763  	// application of a change
  2764  	Splay time.Duration
  2765  
  2766  	// Perms is the permission the file should be written out with.
  2767  	Perms string
  2768  
  2769  	// LeftDelim and RightDelim are optional configurations to control what
  2770  	// delimiter is utilized when parsing the template.
  2771  	LeftDelim  string
  2772  	RightDelim string
  2773  }
  2774  
  2775  // DefaultTemplate returns a default template.
  2776  func DefaultTemplate() *Template {
  2777  	return &Template{
  2778  		ChangeMode: TemplateChangeModeRestart,
  2779  		Splay:      5 * time.Second,
  2780  		Perms:      "0644",
  2781  	}
  2782  }
  2783  
  2784  func (t *Template) Copy() *Template {
  2785  	if t == nil {
  2786  		return nil
  2787  	}
  2788  	copy := new(Template)
  2789  	*copy = *t
  2790  	return copy
  2791  }
  2792  
  2793  func (t *Template) Canonicalize() {
  2794  	if t.ChangeSignal != "" {
  2795  		t.ChangeSignal = strings.ToUpper(t.ChangeSignal)
  2796  	}
  2797  }
  2798  
  2799  func (t *Template) Validate() error {
  2800  	var mErr multierror.Error
  2801  
  2802  	// Verify we have something to render
  2803  	if t.SourcePath == "" && t.EmbeddedTmpl == "" {
  2804  		multierror.Append(&mErr, fmt.Errorf("Must specify a source path or have an embedded template"))
  2805  	}
  2806  
  2807  	// Verify we can render somewhere
  2808  	if t.DestPath == "" {
  2809  		multierror.Append(&mErr, fmt.Errorf("Must specify a destination for the template"))
  2810  	}
  2811  
  2812  	// Verify the destination doesn't escape
  2813  	escaped, err := PathEscapesAllocDir("task", t.DestPath)
  2814  	if err != nil {
  2815  		mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid destination path: %v", err))
  2816  	} else if escaped {
  2817  		mErr.Errors = append(mErr.Errors, fmt.Errorf("destination escapes allocation directory"))
  2818  	}
  2819  
  2820  	// Verify a proper change mode
  2821  	switch t.ChangeMode {
  2822  	case TemplateChangeModeNoop, TemplateChangeModeRestart:
  2823  	case TemplateChangeModeSignal:
  2824  		if t.ChangeSignal == "" {
  2825  			multierror.Append(&mErr, fmt.Errorf("Must specify signal value when change mode is signal"))
  2826  		}
  2827  	default:
  2828  		multierror.Append(&mErr, TemplateChangeModeInvalidError)
  2829  	}
  2830  
  2831  	// Verify the splay is positive
  2832  	if t.Splay < 0 {
  2833  		multierror.Append(&mErr, fmt.Errorf("Must specify positive splay value"))
  2834  	}
  2835  
  2836  	// Verify the permissions
  2837  	if t.Perms != "" {
  2838  		if _, err := strconv.ParseUint(t.Perms, 8, 12); err != nil {
  2839  			multierror.Append(&mErr, fmt.Errorf("Failed to parse %q as octal: %v", t.Perms, err))
  2840  		}
  2841  	}
  2842  
  2843  	return mErr.ErrorOrNil()
  2844  }
  2845  
  2846  // Set of possible states for a task.
  2847  const (
  2848  	TaskStatePending = "pending" // The task is waiting to be run.
  2849  	TaskStateRunning = "running" // The task is currently running.
  2850  	TaskStateDead    = "dead"    // Terminal state of task.
  2851  )
  2852  
  2853  // TaskState tracks the current state of a task and events that caused state
  2854  // transitions.
  2855  type TaskState struct {
  2856  	// The current state of the task.
  2857  	State string
  2858  
  2859  	// Failed marks a task as having failed
  2860  	Failed bool
  2861  
  2862  	// StartedAt is the time the task is started. It is updated each time the
  2863  	// task starts
  2864  	StartedAt time.Time
  2865  
  2866  	// FinishedAt is the time at which the task transistioned to dead and will
  2867  	// not be started again.
  2868  	FinishedAt time.Time
  2869  
  2870  	// Series of task events that transition the state of the task.
  2871  	Events []*TaskEvent
  2872  }
  2873  
  2874  func (ts *TaskState) Copy() *TaskState {
  2875  	if ts == nil {
  2876  		return nil
  2877  	}
  2878  	copy := new(TaskState)
  2879  	copy.State = ts.State
  2880  	copy.Failed = ts.Failed
  2881  	copy.StartedAt = ts.StartedAt
  2882  	copy.FinishedAt = ts.FinishedAt
  2883  
  2884  	if ts.Events != nil {
  2885  		copy.Events = make([]*TaskEvent, len(ts.Events))
  2886  		for i, e := range ts.Events {
  2887  			copy.Events[i] = e.Copy()
  2888  		}
  2889  	}
  2890  	return copy
  2891  }
  2892  
  2893  // Successful returns whether a task finished successfully.
  2894  func (ts *TaskState) Successful() bool {
  2895  	l := len(ts.Events)
  2896  	if ts.State != TaskStateDead || l == 0 {
  2897  		return false
  2898  	}
  2899  
  2900  	e := ts.Events[l-1]
  2901  	if e.Type != TaskTerminated {
  2902  		return false
  2903  	}
  2904  
  2905  	return e.ExitCode == 0
  2906  }
  2907  
  2908  const (
  2909  	// TaskSetupFailure indicates that the task could not be started due to a
  2910  	// a setup failure.
  2911  	TaskSetupFailure = "Setup Failure"
  2912  
  2913  	// TaskDriveFailure indicates that the task could not be started due to a
  2914  	// failure in the driver.
  2915  	TaskDriverFailure = "Driver Failure"
  2916  
  2917  	// TaskReceived signals that the task has been pulled by the client at the
  2918  	// given timestamp.
  2919  	TaskReceived = "Received"
  2920  
  2921  	// TaskFailedValidation indicates the task was invalid and as such was not
  2922  	// run.
  2923  	TaskFailedValidation = "Failed Validation"
  2924  
  2925  	// TaskStarted signals that the task was started and its timestamp can be
  2926  	// used to determine the running length of the task.
  2927  	TaskStarted = "Started"
  2928  
  2929  	// TaskTerminated indicates that the task was started and exited.
  2930  	TaskTerminated = "Terminated"
  2931  
  2932  	// TaskKilling indicates a kill signal has been sent to the task.
  2933  	TaskKilling = "Killing"
  2934  
  2935  	// TaskKilled indicates a user has killed the task.
  2936  	TaskKilled = "Killed"
  2937  
  2938  	// TaskRestarting indicates that task terminated and is being restarted.
  2939  	TaskRestarting = "Restarting"
  2940  
  2941  	// TaskNotRestarting indicates that the task has failed and is not being
  2942  	// restarted because it has exceeded its restart policy.
  2943  	TaskNotRestarting = "Not Restarting"
  2944  
  2945  	// TaskRestartSignal indicates that the task has been signalled to be
  2946  	// restarted
  2947  	TaskRestartSignal = "Restart Signaled"
  2948  
  2949  	// TaskSignaling indicates that the task is being signalled.
  2950  	TaskSignaling = "Signaling"
  2951  
  2952  	// TaskDownloadingArtifacts means the task is downloading the artifacts
  2953  	// specified in the task.
  2954  	TaskDownloadingArtifacts = "Downloading Artifacts"
  2955  
  2956  	// TaskArtifactDownloadFailed indicates that downloading the artifacts
  2957  	// failed.
  2958  	TaskArtifactDownloadFailed = "Failed Artifact Download"
  2959  
  2960  	// TaskBuildingTaskDir indicates that the task directory/chroot is being
  2961  	// built.
  2962  	TaskBuildingTaskDir = "Building Task Directory"
  2963  
  2964  	// TaskSetup indicates the task runner is setting up the task environment
  2965  	TaskSetup = "Task Setup"
  2966  
  2967  	// TaskDiskExceeded indicates that one of the tasks in a taskgroup has
  2968  	// exceeded the requested disk resources.
  2969  	TaskDiskExceeded = "Disk Resources Exceeded"
  2970  
  2971  	// TaskSiblingFailed indicates that a sibling task in the task group has
  2972  	// failed.
  2973  	TaskSiblingFailed = "Sibling Task Failed"
  2974  
  2975  	// TaskDriverMessage is an informational event message emitted by
  2976  	// drivers such as when they're performing a long running action like
  2977  	// downloading an image.
  2978  	TaskDriverMessage = "Driver"
  2979  
  2980  	// TaskLeaderDead indicates that the leader task within the has finished.
  2981  	TaskLeaderDead = "Leader Task Dead"
  2982  )
  2983  
  2984  // TaskEvent is an event that effects the state of a task and contains meta-data
  2985  // appropriate to the events type.
  2986  type TaskEvent struct {
  2987  	Type string
  2988  	Time int64 // Unix Nanosecond timestamp
  2989  
  2990  	// FailsTask marks whether this event fails the task
  2991  	FailsTask bool
  2992  
  2993  	// Restart fields.
  2994  	RestartReason string
  2995  
  2996  	// Setup Failure fields.
  2997  	SetupError string
  2998  
  2999  	// Driver Failure fields.
  3000  	DriverError string // A driver error occurred while starting the task.
  3001  
  3002  	// Task Terminated Fields.
  3003  	ExitCode int    // The exit code of the task.
  3004  	Signal   int    // The signal that terminated the task.
  3005  	Message  string // A possible message explaining the termination of the task.
  3006  
  3007  	// Killing fields
  3008  	KillTimeout time.Duration
  3009  
  3010  	// Task Killed Fields.
  3011  	KillError string // Error killing the task.
  3012  
  3013  	// KillReason is the reason the task was killed
  3014  	KillReason string
  3015  
  3016  	// TaskRestarting fields.
  3017  	StartDelay int64 // The sleep period before restarting the task in unix nanoseconds.
  3018  
  3019  	// Artifact Download fields
  3020  	DownloadError string // Error downloading artifacts
  3021  
  3022  	// Validation fields
  3023  	ValidationError string // Validation error
  3024  
  3025  	// The maximum allowed task disk size.
  3026  	DiskLimit int64
  3027  
  3028  	// Name of the sibling task that caused termination of the task that
  3029  	// the TaskEvent refers to.
  3030  	FailedSibling string
  3031  
  3032  	// VaultError is the error from token renewal
  3033  	VaultError string
  3034  
  3035  	// TaskSignalReason indicates the reason the task is being signalled.
  3036  	TaskSignalReason string
  3037  
  3038  	// TaskSignal is the signal that was sent to the task
  3039  	TaskSignal string
  3040  
  3041  	// DriverMessage indicates a driver action being taken.
  3042  	DriverMessage string
  3043  }
  3044  
  3045  func (te *TaskEvent) GoString() string {
  3046  	return fmt.Sprintf("%v at %v", te.Type, te.Time)
  3047  }
  3048  
  3049  // SetMessage sets the message of TaskEvent
  3050  func (te *TaskEvent) SetMessage(msg string) *TaskEvent {
  3051  	te.Message = msg
  3052  	return te
  3053  }
  3054  
  3055  func (te *TaskEvent) Copy() *TaskEvent {
  3056  	if te == nil {
  3057  		return nil
  3058  	}
  3059  	copy := new(TaskEvent)
  3060  	*copy = *te
  3061  	return copy
  3062  }
  3063  
  3064  func NewTaskEvent(event string) *TaskEvent {
  3065  	return &TaskEvent{
  3066  		Type: event,
  3067  		Time: time.Now().UnixNano(),
  3068  	}
  3069  }
  3070  
  3071  // SetSetupError is used to store an error that occured while setting up the
  3072  // task
  3073  func (e *TaskEvent) SetSetupError(err error) *TaskEvent {
  3074  	if err != nil {
  3075  		e.SetupError = err.Error()
  3076  	}
  3077  	return e
  3078  }
  3079  
  3080  func (e *TaskEvent) SetFailsTask() *TaskEvent {
  3081  	e.FailsTask = true
  3082  	return e
  3083  }
  3084  
  3085  func (e *TaskEvent) SetDriverError(err error) *TaskEvent {
  3086  	if err != nil {
  3087  		e.DriverError = err.Error()
  3088  	}
  3089  	return e
  3090  }
  3091  
  3092  func (e *TaskEvent) SetExitCode(c int) *TaskEvent {
  3093  	e.ExitCode = c
  3094  	return e
  3095  }
  3096  
  3097  func (e *TaskEvent) SetSignal(s int) *TaskEvent {
  3098  	e.Signal = s
  3099  	return e
  3100  }
  3101  
  3102  func (e *TaskEvent) SetExitMessage(err error) *TaskEvent {
  3103  	if err != nil {
  3104  		e.Message = err.Error()
  3105  	}
  3106  	return e
  3107  }
  3108  
  3109  func (e *TaskEvent) SetKillError(err error) *TaskEvent {
  3110  	if err != nil {
  3111  		e.KillError = err.Error()
  3112  	}
  3113  	return e
  3114  }
  3115  
  3116  func (e *TaskEvent) SetKillReason(r string) *TaskEvent {
  3117  	e.KillReason = r
  3118  	return e
  3119  }
  3120  
  3121  func (e *TaskEvent) SetRestartDelay(delay time.Duration) *TaskEvent {
  3122  	e.StartDelay = int64(delay)
  3123  	return e
  3124  }
  3125  
  3126  func (e *TaskEvent) SetRestartReason(reason string) *TaskEvent {
  3127  	e.RestartReason = reason
  3128  	return e
  3129  }
  3130  
  3131  func (e *TaskEvent) SetTaskSignalReason(r string) *TaskEvent {
  3132  	e.TaskSignalReason = r
  3133  	return e
  3134  }
  3135  
  3136  func (e *TaskEvent) SetTaskSignal(s os.Signal) *TaskEvent {
  3137  	e.TaskSignal = s.String()
  3138  	return e
  3139  }
  3140  
  3141  func (e *TaskEvent) SetDownloadError(err error) *TaskEvent {
  3142  	if err != nil {
  3143  		e.DownloadError = err.Error()
  3144  	}
  3145  	return e
  3146  }
  3147  
  3148  func (e *TaskEvent) SetValidationError(err error) *TaskEvent {
  3149  	if err != nil {
  3150  		e.ValidationError = err.Error()
  3151  	}
  3152  	return e
  3153  }
  3154  
  3155  func (e *TaskEvent) SetKillTimeout(timeout time.Duration) *TaskEvent {
  3156  	e.KillTimeout = timeout
  3157  	return e
  3158  }
  3159  
  3160  func (e *TaskEvent) SetDiskLimit(limit int64) *TaskEvent {
  3161  	e.DiskLimit = limit
  3162  	return e
  3163  }
  3164  
  3165  func (e *TaskEvent) SetFailedSibling(sibling string) *TaskEvent {
  3166  	e.FailedSibling = sibling
  3167  	return e
  3168  }
  3169  
  3170  func (e *TaskEvent) SetVaultRenewalError(err error) *TaskEvent {
  3171  	if err != nil {
  3172  		e.VaultError = err.Error()
  3173  	}
  3174  	return e
  3175  }
  3176  
  3177  func (e *TaskEvent) SetDriverMessage(m string) *TaskEvent {
  3178  	e.DriverMessage = m
  3179  	return e
  3180  }
  3181  
  3182  // TaskArtifact is an artifact to download before running the task.
  3183  type TaskArtifact struct {
  3184  	// GetterSource is the source to download an artifact using go-getter
  3185  	GetterSource string
  3186  
  3187  	// GetterOptions are options to use when downloading the artifact using
  3188  	// go-getter.
  3189  	GetterOptions map[string]string
  3190  
  3191  	// RelativeDest is the download destination given relative to the task's
  3192  	// directory.
  3193  	RelativeDest string
  3194  }
  3195  
  3196  func (ta *TaskArtifact) Copy() *TaskArtifact {
  3197  	if ta == nil {
  3198  		return nil
  3199  	}
  3200  	nta := new(TaskArtifact)
  3201  	*nta = *ta
  3202  	nta.GetterOptions = helper.CopyMapStringString(ta.GetterOptions)
  3203  	return nta
  3204  }
  3205  
  3206  func (ta *TaskArtifact) GoString() string {
  3207  	return fmt.Sprintf("%+v", ta)
  3208  }
  3209  
  3210  // PathEscapesAllocDir returns if the given path escapes the allocation
  3211  // directory. The prefix allows adding a prefix if the path will be joined, for
  3212  // example a "task/local" prefix may be provided if the path will be joined
  3213  // against that prefix.
  3214  func PathEscapesAllocDir(prefix, path string) (bool, error) {
  3215  	// Verify the destination doesn't escape the tasks directory
  3216  	alloc, err := filepath.Abs(filepath.Join("/", "alloc-dir/", "alloc-id/"))
  3217  	if err != nil {
  3218  		return false, err
  3219  	}
  3220  	abs, err := filepath.Abs(filepath.Join(alloc, prefix, path))
  3221  	if err != nil {
  3222  		return false, err
  3223  	}
  3224  	rel, err := filepath.Rel(alloc, abs)
  3225  	if err != nil {
  3226  		return false, err
  3227  	}
  3228  
  3229  	return strings.HasPrefix(rel, ".."), nil
  3230  }
  3231  
  3232  func (ta *TaskArtifact) Validate() error {
  3233  	// Verify the source
  3234  	var mErr multierror.Error
  3235  	if ta.GetterSource == "" {
  3236  		mErr.Errors = append(mErr.Errors, fmt.Errorf("source must be specified"))
  3237  	}
  3238  
  3239  	escaped, err := PathEscapesAllocDir("task", ta.RelativeDest)
  3240  	if err != nil {
  3241  		mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid destination path: %v", err))
  3242  	} else if escaped {
  3243  		mErr.Errors = append(mErr.Errors, fmt.Errorf("destination escapes allocation directory"))
  3244  	}
  3245  
  3246  	// Verify the checksum
  3247  	if check, ok := ta.GetterOptions["checksum"]; ok {
  3248  		check = strings.TrimSpace(check)
  3249  		if check == "" {
  3250  			mErr.Errors = append(mErr.Errors, fmt.Errorf("checksum value cannot be empty"))
  3251  			return mErr.ErrorOrNil()
  3252  		}
  3253  
  3254  		parts := strings.Split(check, ":")
  3255  		if l := len(parts); l != 2 {
  3256  			mErr.Errors = append(mErr.Errors, fmt.Errorf(`checksum must be given as "type:value"; got %q`, check))
  3257  			return mErr.ErrorOrNil()
  3258  		}
  3259  
  3260  		checksumVal := parts[1]
  3261  		checksumBytes, err := hex.DecodeString(checksumVal)
  3262  		if err != nil {
  3263  			mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid checksum: %v", err))
  3264  			return mErr.ErrorOrNil()
  3265  		}
  3266  
  3267  		checksumType := parts[0]
  3268  		expectedLength := 0
  3269  		switch checksumType {
  3270  		case "md5":
  3271  			expectedLength = md5.Size
  3272  		case "sha1":
  3273  			expectedLength = sha1.Size
  3274  		case "sha256":
  3275  			expectedLength = sha256.Size
  3276  		case "sha512":
  3277  			expectedLength = sha512.Size
  3278  		default:
  3279  			mErr.Errors = append(mErr.Errors, fmt.Errorf("unsupported checksum type: %s", checksumType))
  3280  			return mErr.ErrorOrNil()
  3281  		}
  3282  
  3283  		if len(checksumBytes) != expectedLength {
  3284  			mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid %s checksum: %v", checksumType, checksumVal))
  3285  			return mErr.ErrorOrNil()
  3286  		}
  3287  	}
  3288  
  3289  	return mErr.ErrorOrNil()
  3290  }
  3291  
  3292  const (
  3293  	ConstraintDistinctProperty = "distinct_property"
  3294  	ConstraintDistinctHosts    = "distinct_hosts"
  3295  	ConstraintRegex            = "regexp"
  3296  	ConstraintVersion          = "version"
  3297  	ConstraintSetContains      = "set_contains"
  3298  )
  3299  
  3300  // Constraints are used to restrict placement options.
  3301  type Constraint struct {
  3302  	LTarget string // Left-hand target
  3303  	RTarget string // Right-hand target
  3304  	Operand string // Constraint operand (<=, <, =, !=, >, >=), contains, near
  3305  	str     string // Memoized string
  3306  }
  3307  
  3308  // Equal checks if two constraints are equal
  3309  func (c *Constraint) Equal(o *Constraint) bool {
  3310  	return c.LTarget == o.LTarget &&
  3311  		c.RTarget == o.RTarget &&
  3312  		c.Operand == o.Operand
  3313  }
  3314  
  3315  func (c *Constraint) Copy() *Constraint {
  3316  	if c == nil {
  3317  		return nil
  3318  	}
  3319  	nc := new(Constraint)
  3320  	*nc = *c
  3321  	return nc
  3322  }
  3323  
  3324  func (c *Constraint) String() string {
  3325  	if c.str != "" {
  3326  		return c.str
  3327  	}
  3328  	c.str = fmt.Sprintf("%s %s %s", c.LTarget, c.Operand, c.RTarget)
  3329  	return c.str
  3330  }
  3331  
  3332  func (c *Constraint) Validate() error {
  3333  	var mErr multierror.Error
  3334  	if c.Operand == "" {
  3335  		mErr.Errors = append(mErr.Errors, errors.New("Missing constraint operand"))
  3336  	}
  3337  
  3338  	// Perform additional validation based on operand
  3339  	switch c.Operand {
  3340  	case ConstraintRegex:
  3341  		if _, err := regexp.Compile(c.RTarget); err != nil {
  3342  			mErr.Errors = append(mErr.Errors, fmt.Errorf("Regular expression failed to compile: %v", err))
  3343  		}
  3344  	case ConstraintVersion:
  3345  		if _, err := version.NewConstraint(c.RTarget); err != nil {
  3346  			mErr.Errors = append(mErr.Errors, fmt.Errorf("Version constraint is invalid: %v", err))
  3347  		}
  3348  	}
  3349  	return mErr.ErrorOrNil()
  3350  }
  3351  
  3352  // EphemeralDisk is an ephemeral disk object
  3353  type EphemeralDisk struct {
  3354  	// Sticky indicates whether the allocation is sticky to a node
  3355  	Sticky bool
  3356  
  3357  	// SizeMB is the size of the local disk
  3358  	SizeMB int
  3359  
  3360  	// Migrate determines if Nomad client should migrate the allocation dir for
  3361  	// sticky allocations
  3362  	Migrate bool
  3363  }
  3364  
  3365  // DefaultEphemeralDisk returns a EphemeralDisk with default configurations
  3366  func DefaultEphemeralDisk() *EphemeralDisk {
  3367  	return &EphemeralDisk{
  3368  		SizeMB: 300,
  3369  	}
  3370  }
  3371  
  3372  // Validate validates EphemeralDisk
  3373  func (d *EphemeralDisk) Validate() error {
  3374  	if d.SizeMB < 10 {
  3375  		return fmt.Errorf("minimum DiskMB value is 10; got %d", d.SizeMB)
  3376  	}
  3377  	return nil
  3378  }
  3379  
  3380  // Copy copies the EphemeralDisk struct and returns a new one
  3381  func (d *EphemeralDisk) Copy() *EphemeralDisk {
  3382  	ld := new(EphemeralDisk)
  3383  	*ld = *d
  3384  	return ld
  3385  }
  3386  
  3387  const (
  3388  	// VaultChangeModeNoop takes no action when a new token is retrieved.
  3389  	VaultChangeModeNoop = "noop"
  3390  
  3391  	// VaultChangeModeSignal signals the task when a new token is retrieved.
  3392  	VaultChangeModeSignal = "signal"
  3393  
  3394  	// VaultChangeModeRestart restarts the task when a new token is retrieved.
  3395  	VaultChangeModeRestart = "restart"
  3396  )
  3397  
  3398  // Vault stores the set of premissions a task needs access to from Vault.
  3399  type Vault struct {
  3400  	// Policies is the set of policies that the task needs access to
  3401  	Policies []string
  3402  
  3403  	// Env marks whether the Vault Token should be exposed as an environment
  3404  	// variable
  3405  	Env bool
  3406  
  3407  	// ChangeMode is used to configure the task's behavior when the Vault
  3408  	// token changes because the original token could not be renewed in time.
  3409  	ChangeMode string
  3410  
  3411  	// ChangeSignal is the signal sent to the task when a new token is
  3412  	// retrieved. This is only valid when using the signal change mode.
  3413  	ChangeSignal string
  3414  }
  3415  
  3416  func DefaultVaultBlock() *Vault {
  3417  	return &Vault{
  3418  		Env:        true,
  3419  		ChangeMode: VaultChangeModeRestart,
  3420  	}
  3421  }
  3422  
  3423  // Copy returns a copy of this Vault block.
  3424  func (v *Vault) Copy() *Vault {
  3425  	if v == nil {
  3426  		return nil
  3427  	}
  3428  
  3429  	nv := new(Vault)
  3430  	*nv = *v
  3431  	return nv
  3432  }
  3433  
  3434  func (v *Vault) Canonicalize() {
  3435  	if v.ChangeSignal != "" {
  3436  		v.ChangeSignal = strings.ToUpper(v.ChangeSignal)
  3437  	}
  3438  }
  3439  
  3440  // Validate returns if the Vault block is valid.
  3441  func (v *Vault) Validate() error {
  3442  	if v == nil {
  3443  		return nil
  3444  	}
  3445  
  3446  	var mErr multierror.Error
  3447  	if len(v.Policies) == 0 {
  3448  		multierror.Append(&mErr, fmt.Errorf("Policy list cannot be empty"))
  3449  	}
  3450  
  3451  	for _, p := range v.Policies {
  3452  		if p == "root" {
  3453  			multierror.Append(&mErr, fmt.Errorf("Can not specifiy \"root\" policy"))
  3454  		}
  3455  	}
  3456  
  3457  	switch v.ChangeMode {
  3458  	case VaultChangeModeSignal:
  3459  		if v.ChangeSignal == "" {
  3460  			multierror.Append(&mErr, fmt.Errorf("Signal must be specified when using change mode %q", VaultChangeModeSignal))
  3461  		}
  3462  	case VaultChangeModeNoop, VaultChangeModeRestart:
  3463  	default:
  3464  		multierror.Append(&mErr, fmt.Errorf("Unknown change mode %q", v.ChangeMode))
  3465  	}
  3466  
  3467  	return mErr.ErrorOrNil()
  3468  }
  3469  
  3470  const (
  3471  	// DeploymentStatuses are the various states a deployment can be be in
  3472  	DeploymentStatusRunning    = "running"
  3473  	DeploymentStatusFailed     = "failed"
  3474  	DeploymentStatusSuccessful = "successful"
  3475  	DeploymentStatusCancelled  = "cancelled"
  3476  	DeploymentStatusPaused     = "paused"
  3477  )
  3478  
  3479  // Deployment is the object that represents a job deployment which is used to
  3480  // transistion a job between versions.
  3481  type Deployment struct {
  3482  	// ID is a generated UUID for the deployment
  3483  	ID string
  3484  
  3485  	// JobID is the job the deployment is created for
  3486  	JobID string
  3487  
  3488  	// JobVersion is the version of the job at which the deployment is tracking
  3489  	JobVersion uint64
  3490  
  3491  	// JobModifyIndex is the modify index of the job at which the deployment is tracking
  3492  	JobModifyIndex uint64
  3493  
  3494  	// JobCreateIndex is the create index of the job which the deployment is
  3495  	// tracking. It is needed so that if the job gets stopped and reran we can
  3496  	// present the correct list of deployments for the job and not old ones.
  3497  	JobCreateIndex uint64
  3498  
  3499  	// TaskGroups is the set of task groups effected by the deployment and their
  3500  	// current deployment status.
  3501  	TaskGroups map[string]*DeploymentState
  3502  
  3503  	// The status of the deployment
  3504  	Status string
  3505  
  3506  	// StatusDescription allows a human readable description of the deployment
  3507  	// status.
  3508  	StatusDescription string
  3509  
  3510  	CreateIndex uint64
  3511  	ModifyIndex uint64
  3512  }
  3513  
  3514  func (d *Deployment) Copy() *Deployment {
  3515  	c := &Deployment{}
  3516  	*c = *d
  3517  
  3518  	c.TaskGroups = nil
  3519  	if l := len(d.TaskGroups); d.TaskGroups != nil {
  3520  		c.TaskGroups = make(map[string]*DeploymentState, l)
  3521  		for tg, s := range d.TaskGroups {
  3522  			c.TaskGroups[tg] = s.Copy()
  3523  		}
  3524  	}
  3525  
  3526  	return c
  3527  }
  3528  
  3529  // Active returns whether the deployment is active or terminal.
  3530  func (d *Deployment) Active() bool {
  3531  	switch d.Status {
  3532  	case DeploymentStatusRunning, DeploymentStatusPaused:
  3533  		return true
  3534  	default:
  3535  		return false
  3536  	}
  3537  }
  3538  
  3539  // DeploymentState tracks the state of a deployment for a given task group.
  3540  type DeploymentState struct {
  3541  	// Promoted marks whether the canaries have been. Promotion by
  3542  	// task group is not allowed since that doesn’t allow a single
  3543  	// job to transition into the “stable” state.
  3544  	Promoted bool
  3545  
  3546  	// RequiresPromotion marks whether the deployment is expecting
  3547  	// a promotion. This is computable by checking if the job has canaries
  3548  	// specified, but is stored in the deployment to make it so that consumers
  3549  	// do not need to query job history and deployments to know whether a
  3550  	// promotion is needed.
  3551  	RequiresPromotion bool
  3552  
  3553  	// DesiredCanaries is the number of canaries that should be created.
  3554  	DesiredCanaries int
  3555  
  3556  	// DesiredTotal is the total number of allocations that should be created as
  3557  	// part of the deployment.
  3558  	DesiredTotal int
  3559  
  3560  	// PlacedAllocs is the number of allocations that have been placed
  3561  	PlacedAllocs int
  3562  
  3563  	// HealthyAllocs is the number of allocations that have been marked healthy.
  3564  	HealthyAllocs int
  3565  
  3566  	// UnhealthyAllocs are allocations that have been marked as unhealthy.
  3567  	UnhealthyAllocs int
  3568  }
  3569  
  3570  func (d *DeploymentState) Copy() *DeploymentState {
  3571  	c := &DeploymentState{}
  3572  	*c = *d
  3573  	return c
  3574  }
  3575  
  3576  // DeploymentStatusUpdate is used to update the status of a given deployment
  3577  type DeploymentStatusUpdate struct {
  3578  	// DeploymentID is the ID of the deployment to update
  3579  	DeploymentID string
  3580  
  3581  	// Status is the new status of the deployment.
  3582  	Status string
  3583  
  3584  	// StatusDescription is the new status description of the deployment.
  3585  	StatusDescription string
  3586  }
  3587  
  3588  const (
  3589  	AllocDesiredStatusRun   = "run"   // Allocation should run
  3590  	AllocDesiredStatusStop  = "stop"  // Allocation should stop
  3591  	AllocDesiredStatusEvict = "evict" // Allocation should stop, and was evicted
  3592  )
  3593  
  3594  const (
  3595  	AllocClientStatusPending  = "pending"
  3596  	AllocClientStatusRunning  = "running"
  3597  	AllocClientStatusComplete = "complete"
  3598  	AllocClientStatusFailed   = "failed"
  3599  	AllocClientStatusLost     = "lost"
  3600  )
  3601  
  3602  // Allocation is used to allocate the placement of a task group to a node.
  3603  type Allocation struct {
  3604  	// ID of the allocation (UUID)
  3605  	ID string
  3606  
  3607  	// ID of the evaluation that generated this allocation
  3608  	EvalID string
  3609  
  3610  	// Name is a logical name of the allocation.
  3611  	Name string
  3612  
  3613  	// NodeID is the node this is being placed on
  3614  	NodeID string
  3615  
  3616  	// Job is the parent job of the task group being allocated.
  3617  	// This is copied at allocation time to avoid issues if the job
  3618  	// definition is updated.
  3619  	JobID string
  3620  	Job   *Job
  3621  
  3622  	// TaskGroup is the name of the task group that should be run
  3623  	TaskGroup string
  3624  
  3625  	// Resources is the total set of resources allocated as part
  3626  	// of this allocation of the task group.
  3627  	Resources *Resources
  3628  
  3629  	// SharedResources are the resources that are shared by all the tasks in an
  3630  	// allocation
  3631  	SharedResources *Resources
  3632  
  3633  	// TaskResources is the set of resources allocated to each
  3634  	// task. These should sum to the total Resources.
  3635  	TaskResources map[string]*Resources
  3636  
  3637  	// Metrics associated with this allocation
  3638  	Metrics *AllocMetric
  3639  
  3640  	// Desired Status of the allocation on the client
  3641  	DesiredStatus string
  3642  
  3643  	// DesiredStatusDescription is meant to provide more human useful information
  3644  	DesiredDescription string
  3645  
  3646  	// Status of the allocation on the client
  3647  	ClientStatus string
  3648  
  3649  	// ClientStatusDescription is meant to provide more human useful information
  3650  	ClientDescription string
  3651  
  3652  	// TaskStates stores the state of each task,
  3653  	TaskStates map[string]*TaskState
  3654  
  3655  	// PreviousAllocation is the allocation that this allocation is replacing
  3656  	PreviousAllocation string
  3657  
  3658  	// DeploymentID identifies an allocation as being created from a
  3659  	// particular deployment
  3660  	DeploymentID string
  3661  
  3662  	// DeploymentStatus captures the status of the allocation as part of the
  3663  	// given deployment
  3664  	DeploymentStatus *AllocDeploymentStatus
  3665  
  3666  	// Canary marks this allocation as being a canary
  3667  	Canary bool
  3668  
  3669  	// Raft Indexes
  3670  	CreateIndex uint64
  3671  	ModifyIndex uint64
  3672  
  3673  	// AllocModifyIndex is not updated when the client updates allocations. This
  3674  	// lets the client pull only the allocs updated by the server.
  3675  	AllocModifyIndex uint64
  3676  
  3677  	// CreateTime is the time the allocation has finished scheduling and been
  3678  	// verified by the plan applier.
  3679  	CreateTime int64
  3680  }
  3681  
  3682  func (a *Allocation) Copy() *Allocation {
  3683  	if a == nil {
  3684  		return nil
  3685  	}
  3686  	na := new(Allocation)
  3687  	*na = *a
  3688  
  3689  	na.Job = na.Job.Copy()
  3690  	na.Resources = na.Resources.Copy()
  3691  	na.SharedResources = na.SharedResources.Copy()
  3692  
  3693  	if a.TaskResources != nil {
  3694  		tr := make(map[string]*Resources, len(na.TaskResources))
  3695  		for task, resource := range na.TaskResources {
  3696  			tr[task] = resource.Copy()
  3697  		}
  3698  		na.TaskResources = tr
  3699  	}
  3700  
  3701  	na.Metrics = na.Metrics.Copy()
  3702  	na.DeploymentStatus = na.DeploymentStatus.Copy()
  3703  
  3704  	if a.TaskStates != nil {
  3705  		ts := make(map[string]*TaskState, len(na.TaskStates))
  3706  		for task, state := range na.TaskStates {
  3707  			ts[task] = state.Copy()
  3708  		}
  3709  		na.TaskStates = ts
  3710  	}
  3711  	return na
  3712  }
  3713  
  3714  // TerminalStatus returns if the desired or actual status is terminal and
  3715  // will no longer transition.
  3716  func (a *Allocation) TerminalStatus() bool {
  3717  	// First check the desired state and if that isn't terminal, check client
  3718  	// state.
  3719  	switch a.DesiredStatus {
  3720  	case AllocDesiredStatusStop, AllocDesiredStatusEvict:
  3721  		return true
  3722  	default:
  3723  	}
  3724  
  3725  	switch a.ClientStatus {
  3726  	case AllocClientStatusComplete, AllocClientStatusFailed, AllocClientStatusLost:
  3727  		return true
  3728  	default:
  3729  		return false
  3730  	}
  3731  }
  3732  
  3733  // Terminated returns if the allocation is in a terminal state on a client.
  3734  func (a *Allocation) Terminated() bool {
  3735  	if a.ClientStatus == AllocClientStatusFailed ||
  3736  		a.ClientStatus == AllocClientStatusComplete ||
  3737  		a.ClientStatus == AllocClientStatusLost {
  3738  		return true
  3739  	}
  3740  	return false
  3741  }
  3742  
  3743  // RanSuccessfully returns whether the client has ran the allocation and all
  3744  // tasks finished successfully
  3745  func (a *Allocation) RanSuccessfully() bool {
  3746  	// Handle the case the client hasn't started the allocation.
  3747  	if len(a.TaskStates) == 0 {
  3748  		return false
  3749  	}
  3750  
  3751  	// Check to see if all the tasks finised successfully in the allocation
  3752  	allSuccess := true
  3753  	for _, state := range a.TaskStates {
  3754  		allSuccess = allSuccess && state.Successful()
  3755  	}
  3756  
  3757  	return allSuccess
  3758  }
  3759  
  3760  // Stub returns a list stub for the allocation
  3761  func (a *Allocation) Stub() *AllocListStub {
  3762  	return &AllocListStub{
  3763  		ID:                 a.ID,
  3764  		EvalID:             a.EvalID,
  3765  		Name:               a.Name,
  3766  		NodeID:             a.NodeID,
  3767  		JobID:              a.JobID,
  3768  		TaskGroup:          a.TaskGroup,
  3769  		DesiredStatus:      a.DesiredStatus,
  3770  		DesiredDescription: a.DesiredDescription,
  3771  		ClientStatus:       a.ClientStatus,
  3772  		ClientDescription:  a.ClientDescription,
  3773  		TaskStates:         a.TaskStates,
  3774  		CreateIndex:        a.CreateIndex,
  3775  		ModifyIndex:        a.ModifyIndex,
  3776  		CreateTime:         a.CreateTime,
  3777  	}
  3778  }
  3779  
  3780  // ShouldMigrate returns if the allocation needs data migration
  3781  func (a *Allocation) ShouldMigrate() bool {
  3782  	if a.DesiredStatus == AllocDesiredStatusStop || a.DesiredStatus == AllocDesiredStatusEvict {
  3783  		return false
  3784  	}
  3785  
  3786  	tg := a.Job.LookupTaskGroup(a.TaskGroup)
  3787  
  3788  	// if the task group is nil or the ephemeral disk block isn't present then
  3789  	// we won't migrate
  3790  	if tg == nil || tg.EphemeralDisk == nil {
  3791  		return false
  3792  	}
  3793  
  3794  	// We won't migrate any data is the user hasn't enabled migration or the
  3795  	// disk is not marked as sticky
  3796  	if !tg.EphemeralDisk.Migrate || !tg.EphemeralDisk.Sticky {
  3797  		return false
  3798  	}
  3799  
  3800  	return true
  3801  }
  3802  
  3803  var (
  3804  	// AllocationIndexRegex is a regular expression to find the allocation index.
  3805  	AllocationIndexRegex = regexp.MustCompile(".+\\[(\\d+)\\]$")
  3806  )
  3807  
  3808  // Index returns the index of the allocation. If the allocation is from a task
  3809  // group with count greater than 1, there will be multiple allocations for it.
  3810  func (a *Allocation) Index() int {
  3811  	matches := AllocationIndexRegex.FindStringSubmatch(a.Name)
  3812  	if len(matches) != 2 {
  3813  		return -1
  3814  	}
  3815  
  3816  	index, err := strconv.Atoi(matches[1])
  3817  	if err != nil {
  3818  		return -1
  3819  	}
  3820  
  3821  	return index
  3822  }
  3823  
  3824  // AllocListStub is used to return a subset of alloc information
  3825  type AllocListStub struct {
  3826  	ID                 string
  3827  	EvalID             string
  3828  	Name               string
  3829  	NodeID             string
  3830  	JobID              string
  3831  	TaskGroup          string
  3832  	DesiredStatus      string
  3833  	DesiredDescription string
  3834  	ClientStatus       string
  3835  	ClientDescription  string
  3836  	TaskStates         map[string]*TaskState
  3837  	CreateIndex        uint64
  3838  	ModifyIndex        uint64
  3839  	CreateTime         int64
  3840  }
  3841  
  3842  // AllocMetric is used to track various metrics while attempting
  3843  // to make an allocation. These are used to debug a job, or to better
  3844  // understand the pressure within the system.
  3845  type AllocMetric struct {
  3846  	// NodesEvaluated is the number of nodes that were evaluated
  3847  	NodesEvaluated int
  3848  
  3849  	// NodesFiltered is the number of nodes filtered due to a constraint
  3850  	NodesFiltered int
  3851  
  3852  	// NodesAvailable is the number of nodes available for evaluation per DC.
  3853  	NodesAvailable map[string]int
  3854  
  3855  	// ClassFiltered is the number of nodes filtered by class
  3856  	ClassFiltered map[string]int
  3857  
  3858  	// ConstraintFiltered is the number of failures caused by constraint
  3859  	ConstraintFiltered map[string]int
  3860  
  3861  	// NodesExhausted is the number of nodes skipped due to being
  3862  	// exhausted of at least one resource
  3863  	NodesExhausted int
  3864  
  3865  	// ClassExhausted is the number of nodes exhausted by class
  3866  	ClassExhausted map[string]int
  3867  
  3868  	// DimensionExhausted provides the count by dimension or reason
  3869  	DimensionExhausted map[string]int
  3870  
  3871  	// Scores is the scores of the final few nodes remaining
  3872  	// for placement. The top score is typically selected.
  3873  	Scores map[string]float64
  3874  
  3875  	// AllocationTime is a measure of how long the allocation
  3876  	// attempt took. This can affect performance and SLAs.
  3877  	AllocationTime time.Duration
  3878  
  3879  	// CoalescedFailures indicates the number of other
  3880  	// allocations that were coalesced into this failed allocation.
  3881  	// This is to prevent creating many failed allocations for a
  3882  	// single task group.
  3883  	CoalescedFailures int
  3884  }
  3885  
  3886  func (a *AllocMetric) Copy() *AllocMetric {
  3887  	if a == nil {
  3888  		return nil
  3889  	}
  3890  	na := new(AllocMetric)
  3891  	*na = *a
  3892  	na.NodesAvailable = helper.CopyMapStringInt(na.NodesAvailable)
  3893  	na.ClassFiltered = helper.CopyMapStringInt(na.ClassFiltered)
  3894  	na.ConstraintFiltered = helper.CopyMapStringInt(na.ConstraintFiltered)
  3895  	na.ClassExhausted = helper.CopyMapStringInt(na.ClassExhausted)
  3896  	na.DimensionExhausted = helper.CopyMapStringInt(na.DimensionExhausted)
  3897  	na.Scores = helper.CopyMapStringFloat64(na.Scores)
  3898  	return na
  3899  }
  3900  
  3901  func (a *AllocMetric) EvaluateNode() {
  3902  	a.NodesEvaluated += 1
  3903  }
  3904  
  3905  func (a *AllocMetric) FilterNode(node *Node, constraint string) {
  3906  	a.NodesFiltered += 1
  3907  	if node != nil && node.NodeClass != "" {
  3908  		if a.ClassFiltered == nil {
  3909  			a.ClassFiltered = make(map[string]int)
  3910  		}
  3911  		a.ClassFiltered[node.NodeClass] += 1
  3912  	}
  3913  	if constraint != "" {
  3914  		if a.ConstraintFiltered == nil {
  3915  			a.ConstraintFiltered = make(map[string]int)
  3916  		}
  3917  		a.ConstraintFiltered[constraint] += 1
  3918  	}
  3919  }
  3920  
  3921  func (a *AllocMetric) ExhaustedNode(node *Node, dimension string) {
  3922  	a.NodesExhausted += 1
  3923  	if node != nil && node.NodeClass != "" {
  3924  		if a.ClassExhausted == nil {
  3925  			a.ClassExhausted = make(map[string]int)
  3926  		}
  3927  		a.ClassExhausted[node.NodeClass] += 1
  3928  	}
  3929  	if dimension != "" {
  3930  		if a.DimensionExhausted == nil {
  3931  			a.DimensionExhausted = make(map[string]int)
  3932  		}
  3933  		a.DimensionExhausted[dimension] += 1
  3934  	}
  3935  }
  3936  
  3937  func (a *AllocMetric) ScoreNode(node *Node, name string, score float64) {
  3938  	if a.Scores == nil {
  3939  		a.Scores = make(map[string]float64)
  3940  	}
  3941  	key := fmt.Sprintf("%s.%s", node.ID, name)
  3942  	a.Scores[key] = score
  3943  }
  3944  
  3945  // AllocDeploymentStatus captures the status of the allocation as part of the
  3946  // deployment. This can include things like if the allocation has been marked as
  3947  // heatlhy.
  3948  type AllocDeploymentStatus struct {
  3949  	// Healthy marks whether the allocation has been marked healthy or unhealthy
  3950  	// as part of a deployment. It can be unset if it has neither been marked
  3951  	// healthy or unhealthy.
  3952  	Healthy *bool
  3953  }
  3954  
  3955  func (a *AllocDeploymentStatus) Copy() *AllocDeploymentStatus {
  3956  	if a == nil {
  3957  		return nil
  3958  	}
  3959  
  3960  	c := new(AllocDeploymentStatus)
  3961  
  3962  	if a.Healthy != nil {
  3963  		c.Healthy = helper.BoolToPtr(*a.Healthy)
  3964  	}
  3965  
  3966  	return c
  3967  }
  3968  
  3969  const (
  3970  	EvalStatusBlocked   = "blocked"
  3971  	EvalStatusPending   = "pending"
  3972  	EvalStatusComplete  = "complete"
  3973  	EvalStatusFailed    = "failed"
  3974  	EvalStatusCancelled = "canceled"
  3975  )
  3976  
  3977  const (
  3978  	EvalTriggerJobRegister    = "job-register"
  3979  	EvalTriggerJobDeregister  = "job-deregister"
  3980  	EvalTriggerPeriodicJob    = "periodic-job"
  3981  	EvalTriggerNodeUpdate     = "node-update"
  3982  	EvalTriggerScheduled      = "scheduled"
  3983  	EvalTriggerRollingUpdate  = "rolling-update"
  3984  	EvalTriggerFailedFollowUp = "failed-follow-up"
  3985  	EvalTriggerMaxPlans       = "max-plan-attempts"
  3986  )
  3987  
  3988  const (
  3989  	// CoreJobEvalGC is used for the garbage collection of evaluations
  3990  	// and allocations. We periodically scan evaluations in a terminal state,
  3991  	// in which all the corresponding allocations are also terminal. We
  3992  	// delete these out of the system to bound the state.
  3993  	CoreJobEvalGC = "eval-gc"
  3994  
  3995  	// CoreJobNodeGC is used for the garbage collection of failed nodes.
  3996  	// We periodically scan nodes in a terminal state, and if they have no
  3997  	// corresponding allocations we delete these out of the system.
  3998  	CoreJobNodeGC = "node-gc"
  3999  
  4000  	// CoreJobJobGC is used for the garbage collection of eligible jobs. We
  4001  	// periodically scan garbage collectible jobs and check if both their
  4002  	// evaluations and allocations are terminal. If so, we delete these out of
  4003  	// the system.
  4004  	CoreJobJobGC = "job-gc"
  4005  
  4006  	// CoreJobForceGC is used to force garbage collection of all GCable objects.
  4007  	CoreJobForceGC = "force-gc"
  4008  )
  4009  
  4010  // Evaluation is used anytime we need to apply business logic as a result
  4011  // of a change to our desired state (job specification) or the emergent state
  4012  // (registered nodes). When the inputs change, we need to "evaluate" them,
  4013  // potentially taking action (allocation of work) or doing nothing if the state
  4014  // of the world does not require it.
  4015  type Evaluation struct {
  4016  	// ID is a randonly generated UUID used for this evaluation. This
  4017  	// is assigned upon the creation of the evaluation.
  4018  	ID string
  4019  
  4020  	// Priority is used to control scheduling importance and if this job
  4021  	// can preempt other jobs.
  4022  	Priority int
  4023  
  4024  	// Type is used to control which schedulers are available to handle
  4025  	// this evaluation.
  4026  	Type string
  4027  
  4028  	// TriggeredBy is used to give some insight into why this Eval
  4029  	// was created. (Job change, node failure, alloc failure, etc).
  4030  	TriggeredBy string
  4031  
  4032  	// JobID is the job this evaluation is scoped to. Evaluations cannot
  4033  	// be run in parallel for a given JobID, so we serialize on this.
  4034  	JobID string
  4035  
  4036  	// JobModifyIndex is the modify index of the job at the time
  4037  	// the evaluation was created
  4038  	JobModifyIndex uint64
  4039  
  4040  	// NodeID is the node that was affected triggering the evaluation.
  4041  	NodeID string
  4042  
  4043  	// NodeModifyIndex is the modify index of the node at the time
  4044  	// the evaluation was created
  4045  	NodeModifyIndex uint64
  4046  
  4047  	// Status of the evaluation
  4048  	Status string
  4049  
  4050  	// StatusDescription is meant to provide more human useful information
  4051  	StatusDescription string
  4052  
  4053  	// Wait is a minimum wait time for running the eval. This is used to
  4054  	// support a rolling upgrade.
  4055  	Wait time.Duration
  4056  
  4057  	// NextEval is the evaluation ID for the eval created to do a followup.
  4058  	// This is used to support rolling upgrades, where we need a chain of evaluations.
  4059  	NextEval string
  4060  
  4061  	// PreviousEval is the evaluation ID for the eval creating this one to do a followup.
  4062  	// This is used to support rolling upgrades, where we need a chain of evaluations.
  4063  	PreviousEval string
  4064  
  4065  	// BlockedEval is the evaluation ID for a created blocked eval. A
  4066  	// blocked eval will be created if all allocations could not be placed due
  4067  	// to constraints or lacking resources.
  4068  	BlockedEval string
  4069  
  4070  	// FailedTGAllocs are task groups which have allocations that could not be
  4071  	// made, but the metrics are persisted so that the user can use the feedback
  4072  	// to determine the cause.
  4073  	FailedTGAllocs map[string]*AllocMetric
  4074  
  4075  	// ClassEligibility tracks computed node classes that have been explicitly
  4076  	// marked as eligible or ineligible.
  4077  	ClassEligibility map[string]bool
  4078  
  4079  	// EscapedComputedClass marks whether the job has constraints that are not
  4080  	// captured by computed node classes.
  4081  	EscapedComputedClass bool
  4082  
  4083  	// AnnotatePlan triggers the scheduler to provide additional annotations
  4084  	// during the evaluation. This should not be set during normal operations.
  4085  	AnnotatePlan bool
  4086  
  4087  	// QueuedAllocations is the number of unplaced allocations at the time the
  4088  	// evaluation was processed. The map is keyed by Task Group names.
  4089  	QueuedAllocations map[string]int
  4090  
  4091  	// SnapshotIndex is the Raft index of the snapshot used to process the
  4092  	// evaluation. As such it will only be set once it has gone through the
  4093  	// scheduler.
  4094  	SnapshotIndex uint64
  4095  
  4096  	// Raft Indexes
  4097  	CreateIndex uint64
  4098  	ModifyIndex uint64
  4099  }
  4100  
  4101  // TerminalStatus returns if the current status is terminal and
  4102  // will no longer transition.
  4103  func (e *Evaluation) TerminalStatus() bool {
  4104  	switch e.Status {
  4105  	case EvalStatusComplete, EvalStatusFailed, EvalStatusCancelled:
  4106  		return true
  4107  	default:
  4108  		return false
  4109  	}
  4110  }
  4111  
  4112  func (e *Evaluation) GoString() string {
  4113  	return fmt.Sprintf("<Eval '%s' JobID: '%s'>", e.ID, e.JobID)
  4114  }
  4115  
  4116  func (e *Evaluation) Copy() *Evaluation {
  4117  	if e == nil {
  4118  		return nil
  4119  	}
  4120  	ne := new(Evaluation)
  4121  	*ne = *e
  4122  
  4123  	// Copy ClassEligibility
  4124  	if e.ClassEligibility != nil {
  4125  		classes := make(map[string]bool, len(e.ClassEligibility))
  4126  		for class, elig := range e.ClassEligibility {
  4127  			classes[class] = elig
  4128  		}
  4129  		ne.ClassEligibility = classes
  4130  	}
  4131  
  4132  	// Copy FailedTGAllocs
  4133  	if e.FailedTGAllocs != nil {
  4134  		failedTGs := make(map[string]*AllocMetric, len(e.FailedTGAllocs))
  4135  		for tg, metric := range e.FailedTGAllocs {
  4136  			failedTGs[tg] = metric.Copy()
  4137  		}
  4138  		ne.FailedTGAllocs = failedTGs
  4139  	}
  4140  
  4141  	// Copy queued allocations
  4142  	if e.QueuedAllocations != nil {
  4143  		queuedAllocations := make(map[string]int, len(e.QueuedAllocations))
  4144  		for tg, num := range e.QueuedAllocations {
  4145  			queuedAllocations[tg] = num
  4146  		}
  4147  		ne.QueuedAllocations = queuedAllocations
  4148  	}
  4149  
  4150  	return ne
  4151  }
  4152  
  4153  // ShouldEnqueue checks if a given evaluation should be enqueued into the
  4154  // eval_broker
  4155  func (e *Evaluation) ShouldEnqueue() bool {
  4156  	switch e.Status {
  4157  	case EvalStatusPending:
  4158  		return true
  4159  	case EvalStatusComplete, EvalStatusFailed, EvalStatusBlocked, EvalStatusCancelled:
  4160  		return false
  4161  	default:
  4162  		panic(fmt.Sprintf("unhandled evaluation (%s) status %s", e.ID, e.Status))
  4163  	}
  4164  }
  4165  
  4166  // ShouldBlock checks if a given evaluation should be entered into the blocked
  4167  // eval tracker.
  4168  func (e *Evaluation) ShouldBlock() bool {
  4169  	switch e.Status {
  4170  	case EvalStatusBlocked:
  4171  		return true
  4172  	case EvalStatusComplete, EvalStatusFailed, EvalStatusPending, EvalStatusCancelled:
  4173  		return false
  4174  	default:
  4175  		panic(fmt.Sprintf("unhandled evaluation (%s) status %s", e.ID, e.Status))
  4176  	}
  4177  }
  4178  
  4179  // MakePlan is used to make a plan from the given evaluation
  4180  // for a given Job
  4181  func (e *Evaluation) MakePlan(j *Job) *Plan {
  4182  	p := &Plan{
  4183  		EvalID:         e.ID,
  4184  		Priority:       e.Priority,
  4185  		Job:            j,
  4186  		NodeUpdate:     make(map[string][]*Allocation),
  4187  		NodeAllocation: make(map[string][]*Allocation),
  4188  	}
  4189  	if j != nil {
  4190  		p.AllAtOnce = j.AllAtOnce
  4191  	}
  4192  	return p
  4193  }
  4194  
  4195  // NextRollingEval creates an evaluation to followup this eval for rolling updates
  4196  func (e *Evaluation) NextRollingEval(wait time.Duration) *Evaluation {
  4197  	return &Evaluation{
  4198  		ID:             GenerateUUID(),
  4199  		Priority:       e.Priority,
  4200  		Type:           e.Type,
  4201  		TriggeredBy:    EvalTriggerRollingUpdate,
  4202  		JobID:          e.JobID,
  4203  		JobModifyIndex: e.JobModifyIndex,
  4204  		Status:         EvalStatusPending,
  4205  		Wait:           wait,
  4206  		PreviousEval:   e.ID,
  4207  	}
  4208  }
  4209  
  4210  // CreateBlockedEval creates a blocked evaluation to followup this eval to place any
  4211  // failed allocations. It takes the classes marked explicitly eligible or
  4212  // ineligible and whether the job has escaped computed node classes.
  4213  func (e *Evaluation) CreateBlockedEval(classEligibility map[string]bool, escaped bool) *Evaluation {
  4214  	return &Evaluation{
  4215  		ID:                   GenerateUUID(),
  4216  		Priority:             e.Priority,
  4217  		Type:                 e.Type,
  4218  		TriggeredBy:          e.TriggeredBy,
  4219  		JobID:                e.JobID,
  4220  		JobModifyIndex:       e.JobModifyIndex,
  4221  		Status:               EvalStatusBlocked,
  4222  		PreviousEval:         e.ID,
  4223  		ClassEligibility:     classEligibility,
  4224  		EscapedComputedClass: escaped,
  4225  	}
  4226  }
  4227  
  4228  // CreateFailedFollowUpEval creates a follow up evaluation when the current one
  4229  // has been marked as failed becasue it has hit the delivery limit and will not
  4230  // be retried by the eval_broker.
  4231  func (e *Evaluation) CreateFailedFollowUpEval(wait time.Duration) *Evaluation {
  4232  	return &Evaluation{
  4233  		ID:             GenerateUUID(),
  4234  		Priority:       e.Priority,
  4235  		Type:           e.Type,
  4236  		TriggeredBy:    EvalTriggerFailedFollowUp,
  4237  		JobID:          e.JobID,
  4238  		JobModifyIndex: e.JobModifyIndex,
  4239  		Status:         EvalStatusPending,
  4240  		Wait:           wait,
  4241  		PreviousEval:   e.ID,
  4242  	}
  4243  }
  4244  
  4245  // Plan is used to submit a commit plan for task allocations. These
  4246  // are submitted to the leader which verifies that resources have
  4247  // not been overcommitted before admiting the plan.
  4248  type Plan struct {
  4249  	// EvalID is the evaluation ID this plan is associated with
  4250  	EvalID string
  4251  
  4252  	// EvalToken is used to prevent a split-brain processing of
  4253  	// an evaluation. There should only be a single scheduler running
  4254  	// an Eval at a time, but this could be violated after a leadership
  4255  	// transition. This unique token is used to reject plans that are
  4256  	// being submitted from a different leader.
  4257  	EvalToken string
  4258  
  4259  	// Priority is the priority of the upstream job
  4260  	Priority int
  4261  
  4262  	// AllAtOnce is used to control if incremental scheduling of task groups
  4263  	// is allowed or if we must do a gang scheduling of the entire job.
  4264  	// If this is false, a plan may be partially applied. Otherwise, the
  4265  	// entire plan must be able to make progress.
  4266  	AllAtOnce bool
  4267  
  4268  	// Job is the parent job of all the allocations in the Plan.
  4269  	// Since a Plan only involves a single Job, we can reduce the size
  4270  	// of the plan by only including it once.
  4271  	Job *Job
  4272  
  4273  	// NodeUpdate contains all the allocations for each node. For each node,
  4274  	// this is a list of the allocations to update to either stop or evict.
  4275  	NodeUpdate map[string][]*Allocation
  4276  
  4277  	// NodeAllocation contains all the allocations for each node.
  4278  	// The evicts must be considered prior to the allocations.
  4279  	NodeAllocation map[string][]*Allocation
  4280  
  4281  	// Annotations contains annotations by the scheduler to be used by operators
  4282  	// to understand the decisions made by the scheduler.
  4283  	Annotations *PlanAnnotations
  4284  
  4285  	// CreatedDeployment is the deployment created by the scheduler that should
  4286  	// be applied by the planner. A created deployment will cancel all other
  4287  	// deployments for a given job as there can only be a single running
  4288  	// deployment.
  4289  	CreatedDeployment *Deployment
  4290  
  4291  	// DeploymentUpdates is a set of status updates to apply to the given
  4292  	// deployments. This allows the scheduler to cancel any unneeded deployment
  4293  	// because the job is stopped or the update block is removed.
  4294  	DeploymentUpdates []*DeploymentStatusUpdate
  4295  }
  4296  
  4297  // AppendUpdate marks the allocation for eviction. The clientStatus of the
  4298  // allocation may be optionally set by passing in a non-empty value.
  4299  func (p *Plan) AppendUpdate(alloc *Allocation, desiredStatus, desiredDesc, clientStatus string) {
  4300  	newAlloc := new(Allocation)
  4301  	*newAlloc = *alloc
  4302  
  4303  	// If the job is not set in the plan we are deregistering a job so we
  4304  	// extract the job from the allocation.
  4305  	if p.Job == nil && newAlloc.Job != nil {
  4306  		p.Job = newAlloc.Job
  4307  	}
  4308  
  4309  	// Normalize the job
  4310  	newAlloc.Job = nil
  4311  
  4312  	// Strip the resources as it can be rebuilt.
  4313  	newAlloc.Resources = nil
  4314  
  4315  	newAlloc.DesiredStatus = desiredStatus
  4316  	newAlloc.DesiredDescription = desiredDesc
  4317  
  4318  	if clientStatus != "" {
  4319  		newAlloc.ClientStatus = clientStatus
  4320  	}
  4321  
  4322  	node := alloc.NodeID
  4323  	existing := p.NodeUpdate[node]
  4324  	p.NodeUpdate[node] = append(existing, newAlloc)
  4325  }
  4326  
  4327  func (p *Plan) PopUpdate(alloc *Allocation) {
  4328  	existing := p.NodeUpdate[alloc.NodeID]
  4329  	n := len(existing)
  4330  	if n > 0 && existing[n-1].ID == alloc.ID {
  4331  		existing = existing[:n-1]
  4332  		if len(existing) > 0 {
  4333  			p.NodeUpdate[alloc.NodeID] = existing
  4334  		} else {
  4335  			delete(p.NodeUpdate, alloc.NodeID)
  4336  		}
  4337  	}
  4338  }
  4339  
  4340  func (p *Plan) AppendAlloc(alloc *Allocation) {
  4341  	node := alloc.NodeID
  4342  	existing := p.NodeAllocation[node]
  4343  	p.NodeAllocation[node] = append(existing, alloc)
  4344  }
  4345  
  4346  // IsNoOp checks if this plan would do nothing
  4347  func (p *Plan) IsNoOp() bool {
  4348  	return len(p.NodeUpdate) == 0 && len(p.NodeAllocation) == 0
  4349  }
  4350  
  4351  // PlanResult is the result of a plan submitted to the leader.
  4352  type PlanResult struct {
  4353  	// NodeUpdate contains all the updates that were committed.
  4354  	NodeUpdate map[string][]*Allocation
  4355  
  4356  	// NodeAllocation contains all the allocations that were committed.
  4357  	NodeAllocation map[string][]*Allocation
  4358  
  4359  	// RefreshIndex is the index the worker should refresh state up to.
  4360  	// This allows all evictions and allocations to be materialized.
  4361  	// If any allocations were rejected due to stale data (node state,
  4362  	// over committed) this can be used to force a worker refresh.
  4363  	RefreshIndex uint64
  4364  
  4365  	// AllocIndex is the Raft index in which the evictions and
  4366  	// allocations took place. This is used for the write index.
  4367  	AllocIndex uint64
  4368  }
  4369  
  4370  // IsNoOp checks if this plan result would do nothing
  4371  func (p *PlanResult) IsNoOp() bool {
  4372  	return len(p.NodeUpdate) == 0 && len(p.NodeAllocation) == 0
  4373  }
  4374  
  4375  // FullCommit is used to check if all the allocations in a plan
  4376  // were committed as part of the result. Returns if there was
  4377  // a match, and the number of expected and actual allocations.
  4378  func (p *PlanResult) FullCommit(plan *Plan) (bool, int, int) {
  4379  	expected := 0
  4380  	actual := 0
  4381  	for name, allocList := range plan.NodeAllocation {
  4382  		didAlloc, _ := p.NodeAllocation[name]
  4383  		expected += len(allocList)
  4384  		actual += len(didAlloc)
  4385  	}
  4386  	return actual == expected, expected, actual
  4387  }
  4388  
  4389  // PlanAnnotations holds annotations made by the scheduler to give further debug
  4390  // information to operators.
  4391  type PlanAnnotations struct {
  4392  	// DesiredTGUpdates is the set of desired updates per task group.
  4393  	DesiredTGUpdates map[string]*DesiredUpdates
  4394  }
  4395  
  4396  // DesiredUpdates is the set of changes the scheduler would like to make given
  4397  // sufficient resources and cluster capacity.
  4398  type DesiredUpdates struct {
  4399  	Ignore            uint64
  4400  	Place             uint64
  4401  	Migrate           uint64
  4402  	Stop              uint64
  4403  	InPlaceUpdate     uint64
  4404  	DestructiveUpdate uint64
  4405  }
  4406  
  4407  // msgpackHandle is a shared handle for encoding/decoding of structs
  4408  var MsgpackHandle = func() *codec.MsgpackHandle {
  4409  	h := &codec.MsgpackHandle{RawToString: true}
  4410  
  4411  	// Sets the default type for decoding a map into a nil interface{}.
  4412  	// This is necessary in particular because we store the driver configs as a
  4413  	// nil interface{}.
  4414  	h.MapType = reflect.TypeOf(map[string]interface{}(nil))
  4415  	return h
  4416  }()
  4417  
  4418  var (
  4419  	// JsonHandle and JsonHandlePretty are the codec handles to JSON encode
  4420  	// structs. The pretty handle will add indents for easier human consumption.
  4421  	JsonHandle = &codec.JsonHandle{
  4422  		HTMLCharsAsIs: true,
  4423  	}
  4424  	JsonHandlePretty = &codec.JsonHandle{
  4425  		HTMLCharsAsIs: true,
  4426  		Indent:        4,
  4427  	}
  4428  )
  4429  
  4430  var HashiMsgpackHandle = func() *hcodec.MsgpackHandle {
  4431  	h := &hcodec.MsgpackHandle{RawToString: true}
  4432  
  4433  	// Sets the default type for decoding a map into a nil interface{}.
  4434  	// This is necessary in particular because we store the driver configs as a
  4435  	// nil interface{}.
  4436  	h.MapType = reflect.TypeOf(map[string]interface{}(nil))
  4437  	return h
  4438  }()
  4439  
  4440  // Decode is used to decode a MsgPack encoded object
  4441  func Decode(buf []byte, out interface{}) error {
  4442  	return codec.NewDecoder(bytes.NewReader(buf), MsgpackHandle).Decode(out)
  4443  }
  4444  
  4445  // Encode is used to encode a MsgPack object with type prefix
  4446  func Encode(t MessageType, msg interface{}) ([]byte, error) {
  4447  	var buf bytes.Buffer
  4448  	buf.WriteByte(uint8(t))
  4449  	err := codec.NewEncoder(&buf, MsgpackHandle).Encode(msg)
  4450  	return buf.Bytes(), err
  4451  }
  4452  
  4453  // KeyringResponse is a unified key response and can be used for install,
  4454  // remove, use, as well as listing key queries.
  4455  type KeyringResponse struct {
  4456  	Messages map[string]string
  4457  	Keys     map[string]int
  4458  	NumNodes int
  4459  }
  4460  
  4461  // KeyringRequest is request objects for serf key operations.
  4462  type KeyringRequest struct {
  4463  	Key string
  4464  }
  4465  
  4466  // RecoverableError wraps an error and marks whether it is recoverable and could
  4467  // be retried or it is fatal.
  4468  type RecoverableError struct {
  4469  	Err         string
  4470  	Recoverable bool
  4471  }
  4472  
  4473  // NewRecoverableError is used to wrap an error and mark it as recoverable or
  4474  // not.
  4475  func NewRecoverableError(e error, recoverable bool) error {
  4476  	if e == nil {
  4477  		return nil
  4478  	}
  4479  
  4480  	return &RecoverableError{
  4481  		Err:         e.Error(),
  4482  		Recoverable: recoverable,
  4483  	}
  4484  }
  4485  
  4486  // WrapRecoverable wraps an existing error in a new RecoverableError with a new
  4487  // message. If the error was recoverable before the returned error is as well;
  4488  // otherwise it is unrecoverable.
  4489  func WrapRecoverable(msg string, err error) error {
  4490  	return &RecoverableError{Err: msg, Recoverable: IsRecoverable(err)}
  4491  }
  4492  
  4493  func (r *RecoverableError) Error() string {
  4494  	return r.Err
  4495  }
  4496  
  4497  func (r *RecoverableError) IsRecoverable() bool {
  4498  	return r.Recoverable
  4499  }
  4500  
  4501  // Recoverable is an interface for errors to implement to indicate whether or
  4502  // not they are fatal or recoverable.
  4503  type Recoverable interface {
  4504  	error
  4505  	IsRecoverable() bool
  4506  }
  4507  
  4508  // IsRecoverable returns true if error is a RecoverableError with
  4509  // Recoverable=true. Otherwise false is returned.
  4510  func IsRecoverable(e error) bool {
  4511  	if re, ok := e.(Recoverable); ok {
  4512  		return re.IsRecoverable()
  4513  	}
  4514  	return false
  4515  }