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 }