github.com/anuvu/nomad@v0.8.7-atom1/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/base32" 10 "encoding/hex" 11 "errors" 12 "fmt" 13 "io" 14 "net" 15 "net/url" 16 "os" 17 "path/filepath" 18 "reflect" 19 "regexp" 20 "sort" 21 "strconv" 22 "strings" 23 "time" 24 25 "golang.org/x/crypto/blake2b" 26 27 "github.com/gorhill/cronexpr" 28 "github.com/hashicorp/consul/api" 29 multierror "github.com/hashicorp/go-multierror" 30 "github.com/hashicorp/go-version" 31 "github.com/hashicorp/nomad/acl" 32 "github.com/hashicorp/nomad/helper" 33 "github.com/hashicorp/nomad/helper/args" 34 "github.com/hashicorp/nomad/helper/uuid" 35 "github.com/mitchellh/copystructure" 36 "github.com/ugorji/go/codec" 37 38 "math" 39 40 hcodec "github.com/hashicorp/go-msgpack/codec" 41 ) 42 43 var ( 44 // validPolicyName is used to validate a policy name 45 validPolicyName = regexp.MustCompile("^[a-zA-Z0-9-]{1,128}$") 46 47 // b32 is a lowercase base32 encoding for use in URL friendly service hashes 48 b32 = base32.NewEncoding(strings.ToLower("abcdefghijklmnopqrstuvwxyz234567")) 49 ) 50 51 type MessageType uint8 52 53 const ( 54 NodeRegisterRequestType MessageType = iota 55 NodeDeregisterRequestType 56 NodeUpdateStatusRequestType 57 NodeUpdateDrainRequestType 58 JobRegisterRequestType 59 JobDeregisterRequestType 60 EvalUpdateRequestType 61 EvalDeleteRequestType 62 AllocUpdateRequestType 63 AllocClientUpdateRequestType 64 ReconcileJobSummariesRequestType 65 VaultAccessorRegisterRequestType 66 VaultAccessorDeregisterRequestType 67 ApplyPlanResultsRequestType 68 DeploymentStatusUpdateRequestType 69 DeploymentPromoteRequestType 70 DeploymentAllocHealthRequestType 71 DeploymentDeleteRequestType 72 JobStabilityRequestType 73 ACLPolicyUpsertRequestType 74 ACLPolicyDeleteRequestType 75 ACLTokenUpsertRequestType 76 ACLTokenDeleteRequestType 77 ACLTokenBootstrapRequestType 78 AutopilotRequestType 79 UpsertNodeEventsType 80 JobBatchDeregisterRequestType 81 AllocUpdateDesiredTransitionRequestType 82 NodeUpdateEligibilityRequestType 83 BatchNodeUpdateDrainRequestType 84 ) 85 86 const ( 87 // IgnoreUnknownTypeFlag is set along with a MessageType 88 // to indicate that the message type can be safely ignored 89 // if it is not recognized. This is for future proofing, so 90 // that new commands can be added in a way that won't cause 91 // old servers to crash when the FSM attempts to process them. 92 IgnoreUnknownTypeFlag MessageType = 128 93 94 // ApiMajorVersion is returned as part of the Status.Version request. 95 // It should be incremented anytime the APIs are changed in a way 96 // that would break clients for sane client versioning. 97 ApiMajorVersion = 1 98 99 // ApiMinorVersion is returned as part of the Status.Version request. 100 // It should be incremented anytime the APIs are changed to allow 101 // for sane client versioning. Minor changes should be compatible 102 // within the major version. 103 ApiMinorVersion = 1 104 105 ProtocolVersion = "protocol" 106 APIMajorVersion = "api.major" 107 APIMinorVersion = "api.minor" 108 109 GetterModeAny = "any" 110 GetterModeFile = "file" 111 GetterModeDir = "dir" 112 113 // maxPolicyDescriptionLength limits a policy description length 114 maxPolicyDescriptionLength = 256 115 116 // maxTokenNameLength limits a ACL token name length 117 maxTokenNameLength = 256 118 119 // ACLClientToken and ACLManagementToken are the only types of tokens 120 ACLClientToken = "client" 121 ACLManagementToken = "management" 122 123 // DefaultNamespace is the default namespace. 124 DefaultNamespace = "default" 125 DefaultNamespaceDescription = "Default shared namespace" 126 127 // JitterFraction is a the limit to the amount of jitter we apply 128 // to a user specified MaxQueryTime. We divide the specified time by 129 // the fraction. So 16 == 6.25% limit of jitter. This jitter is also 130 // applied to RPCHoldTimeout. 131 JitterFraction = 16 132 133 // MaxRetainedNodeEvents is the maximum number of node events that will be 134 // retained for a single node 135 MaxRetainedNodeEvents = 10 136 ) 137 138 // Context defines the scope in which a search for Nomad object operates, and 139 // is also used to query the matching index value for this context 140 type Context string 141 142 const ( 143 Allocs Context = "allocs" 144 Deployments Context = "deployment" 145 Evals Context = "evals" 146 Jobs Context = "jobs" 147 Nodes Context = "nodes" 148 Namespaces Context = "namespaces" 149 Quotas Context = "quotas" 150 All Context = "all" 151 ) 152 153 // NamespacedID is a tuple of an ID and a namespace 154 type NamespacedID struct { 155 ID string 156 Namespace string 157 } 158 159 // NewNamespacedID returns a new namespaced ID given the ID and namespace 160 func NewNamespacedID(id, ns string) NamespacedID { 161 return NamespacedID{ 162 ID: id, 163 Namespace: ns, 164 } 165 } 166 167 func (n NamespacedID) String() string { 168 return fmt.Sprintf("<ns: %q, id: %q>", n.Namespace, n.ID) 169 } 170 171 // RPCInfo is used to describe common information about query 172 type RPCInfo interface { 173 RequestRegion() string 174 IsRead() bool 175 AllowStaleRead() bool 176 IsForwarded() bool 177 SetForwarded() 178 } 179 180 // InternalRpcInfo allows adding internal RPC metadata to an RPC. This struct 181 // should NOT be replicated in the API package as it is internal only. 182 type InternalRpcInfo struct { 183 // Forwarded marks whether the RPC has been forwarded. 184 Forwarded bool 185 } 186 187 // IsForwarded returns whether the RPC is forwarded from another server. 188 func (i *InternalRpcInfo) IsForwarded() bool { 189 return i.Forwarded 190 } 191 192 // SetForwarded marks that the RPC is being forwarded from another server. 193 func (i *InternalRpcInfo) SetForwarded() { 194 i.Forwarded = true 195 } 196 197 // QueryOptions is used to specify various flags for read queries 198 type QueryOptions struct { 199 // The target region for this query 200 Region string 201 202 // Namespace is the target namespace for the query. 203 Namespace string 204 205 // If set, wait until query exceeds given index. Must be provided 206 // with MaxQueryTime. 207 MinQueryIndex uint64 208 209 // Provided with MinQueryIndex to wait for change. 210 MaxQueryTime time.Duration 211 212 // If set, any follower can service the request. Results 213 // may be arbitrarily stale. 214 AllowStale bool 215 216 // If set, used as prefix for resource list searches 217 Prefix string 218 219 // AuthToken is secret portion of the ACL token used for the request 220 AuthToken string 221 222 InternalRpcInfo 223 } 224 225 func (q QueryOptions) RequestRegion() string { 226 return q.Region 227 } 228 229 func (q QueryOptions) RequestNamespace() string { 230 if q.Namespace == "" { 231 return DefaultNamespace 232 } 233 return q.Namespace 234 } 235 236 // QueryOption only applies to reads, so always true 237 func (q QueryOptions) IsRead() bool { 238 return true 239 } 240 241 func (q QueryOptions) AllowStaleRead() bool { 242 return q.AllowStale 243 } 244 245 type WriteRequest struct { 246 // The target region for this write 247 Region string 248 249 // Namespace is the target namespace for the write. 250 Namespace string 251 252 // AuthToken is secret portion of the ACL token used for the request 253 AuthToken string 254 255 InternalRpcInfo 256 } 257 258 func (w WriteRequest) RequestRegion() string { 259 // The target region for this request 260 return w.Region 261 } 262 263 func (w WriteRequest) RequestNamespace() string { 264 if w.Namespace == "" { 265 return DefaultNamespace 266 } 267 return w.Namespace 268 } 269 270 // WriteRequest only applies to writes, always false 271 func (w WriteRequest) IsRead() bool { 272 return false 273 } 274 275 func (w WriteRequest) AllowStaleRead() bool { 276 return false 277 } 278 279 // QueryMeta allows a query response to include potentially 280 // useful metadata about a query 281 type QueryMeta struct { 282 // This is the index associated with the read 283 Index uint64 284 285 // If AllowStale is used, this is time elapsed since 286 // last contact between the follower and leader. This 287 // can be used to gauge staleness. 288 LastContact time.Duration 289 290 // Used to indicate if there is a known leader node 291 KnownLeader bool 292 } 293 294 // WriteMeta allows a write response to include potentially 295 // useful metadata about the write 296 type WriteMeta struct { 297 // This is the index associated with the write 298 Index uint64 299 } 300 301 // NodeRegisterRequest is used for Node.Register endpoint 302 // to register a node as being a schedulable entity. 303 type NodeRegisterRequest struct { 304 Node *Node 305 NodeEvent *NodeEvent 306 WriteRequest 307 } 308 309 // NodeDeregisterRequest is used for Node.Deregister endpoint 310 // to deregister a node as being a schedulable entity. 311 type NodeDeregisterRequest struct { 312 NodeID string 313 WriteRequest 314 } 315 316 // NodeServerInfo is used to in NodeUpdateResponse to return Nomad server 317 // information used in RPC server lists. 318 type NodeServerInfo struct { 319 // RPCAdvertiseAddr is the IP endpoint that a Nomad Server wishes to 320 // be contacted at for RPCs. 321 RPCAdvertiseAddr string 322 323 // RpcMajorVersion is the major version number the Nomad Server 324 // supports 325 RPCMajorVersion int32 326 327 // RpcMinorVersion is the minor version number the Nomad Server 328 // supports 329 RPCMinorVersion int32 330 331 // Datacenter is the datacenter that a Nomad server belongs to 332 Datacenter string 333 } 334 335 // NodeUpdateStatusRequest is used for Node.UpdateStatus endpoint 336 // to update the status of a node. 337 type NodeUpdateStatusRequest struct { 338 NodeID string 339 Status string 340 NodeEvent *NodeEvent 341 WriteRequest 342 } 343 344 // NodeUpdateDrainRequest is used for updating the drain strategy 345 type NodeUpdateDrainRequest struct { 346 NodeID string 347 DrainStrategy *DrainStrategy 348 349 // COMPAT Remove in version 0.10 350 // As part of Nomad 0.8 we have deprecated the drain boolean in favor of a 351 // drain strategy but we need to handle the upgrade path where the Raft log 352 // contains drain updates with just the drain boolean being manipulated. 353 Drain bool 354 355 // MarkEligible marks the node as eligible if removing the drain strategy. 356 MarkEligible bool 357 358 // NodeEvent is the event added to the node 359 NodeEvent *NodeEvent 360 361 WriteRequest 362 } 363 364 // BatchNodeUpdateDrainRequest is used for updating the drain strategy for a 365 // batch of nodes 366 type BatchNodeUpdateDrainRequest struct { 367 // Updates is a mapping of nodes to their updated drain strategy 368 Updates map[string]*DrainUpdate 369 370 // NodeEvents is a mapping of the node to the event to add to the node 371 NodeEvents map[string]*NodeEvent 372 373 WriteRequest 374 } 375 376 // DrainUpdate is used to update the drain of a node 377 type DrainUpdate struct { 378 // DrainStrategy is the new strategy for the node 379 DrainStrategy *DrainStrategy 380 381 // MarkEligible marks the node as eligible if removing the drain strategy. 382 MarkEligible bool 383 } 384 385 // NodeUpdateEligibilityRequest is used for updating the scheduling eligibility 386 type NodeUpdateEligibilityRequest struct { 387 NodeID string 388 Eligibility string 389 390 // NodeEvent is the event added to the node 391 NodeEvent *NodeEvent 392 393 WriteRequest 394 } 395 396 // NodeEvaluateRequest is used to re-evaluate the node 397 type NodeEvaluateRequest struct { 398 NodeID string 399 WriteRequest 400 } 401 402 // NodeSpecificRequest is used when we just need to specify a target node 403 type NodeSpecificRequest struct { 404 NodeID string 405 SecretID string 406 QueryOptions 407 } 408 409 // SearchResponse is used to return matches and information about whether 410 // the match list is truncated specific to each type of context. 411 type SearchResponse struct { 412 // Map of context types to ids which match a specified prefix 413 Matches map[Context][]string 414 415 // Truncations indicates whether the matches for a particular context have 416 // been truncated 417 Truncations map[Context]bool 418 419 QueryMeta 420 } 421 422 // SearchRequest is used to parameterize a request, and returns a 423 // list of matches made up of jobs, allocations, evaluations, and/or nodes, 424 // along with whether or not the information returned is truncated. 425 type SearchRequest struct { 426 // Prefix is what ids are matched to. I.e, if the given prefix were 427 // "a", potential matches might be "abcd" or "aabb" 428 Prefix string 429 430 // Context is the type that can be matched against. A context can be a job, 431 // node, evaluation, allocation, or empty (indicated every context should be 432 // matched) 433 Context Context 434 435 QueryOptions 436 } 437 438 // JobRegisterRequest is used for Job.Register endpoint 439 // to register a job as being a schedulable entity. 440 type JobRegisterRequest struct { 441 Job *Job 442 443 // If EnforceIndex is set then the job will only be registered if the passed 444 // JobModifyIndex matches the current Jobs index. If the index is zero, the 445 // register only occurs if the job is new. 446 EnforceIndex bool 447 JobModifyIndex uint64 448 449 // PolicyOverride is set when the user is attempting to override any policies 450 PolicyOverride bool 451 452 WriteRequest 453 } 454 455 // JobDeregisterRequest is used for Job.Deregister endpoint 456 // to deregister a job as being a schedulable entity. 457 type JobDeregisterRequest struct { 458 JobID string 459 460 // Purge controls whether the deregister purges the job from the system or 461 // whether the job is just marked as stopped and will be removed by the 462 // garbage collector 463 Purge bool 464 465 WriteRequest 466 } 467 468 // JobBatchDeregisterRequest is used to batch deregister jobs and upsert 469 // evaluations. 470 type JobBatchDeregisterRequest struct { 471 // Jobs is the set of jobs to deregister 472 Jobs map[NamespacedID]*JobDeregisterOptions 473 474 // Evals is the set of evaluations to create. 475 Evals []*Evaluation 476 477 WriteRequest 478 } 479 480 // JobDeregisterOptions configures how a job is deregistered. 481 type JobDeregisterOptions struct { 482 // Purge controls whether the deregister purges the job from the system or 483 // whether the job is just marked as stopped and will be removed by the 484 // garbage collector 485 Purge bool 486 } 487 488 // JobEvaluateRequest is used when we just need to re-evaluate a target job 489 type JobEvaluateRequest struct { 490 JobID string 491 EvalOptions EvalOptions 492 WriteRequest 493 } 494 495 // EvalOptions is used to encapsulate options when forcing a job evaluation 496 type EvalOptions struct { 497 ForceReschedule bool 498 } 499 500 // JobSpecificRequest is used when we just need to specify a target job 501 type JobSpecificRequest struct { 502 JobID string 503 AllAllocs bool 504 QueryOptions 505 } 506 507 // JobListRequest is used to parameterize a list request 508 type JobListRequest struct { 509 QueryOptions 510 } 511 512 // JobPlanRequest is used for the Job.Plan endpoint to trigger a dry-run 513 // evaluation of the Job. 514 type JobPlanRequest struct { 515 Job *Job 516 Diff bool // Toggles an annotated diff 517 // PolicyOverride is set when the user is attempting to override any policies 518 PolicyOverride bool 519 WriteRequest 520 } 521 522 // JobSummaryRequest is used when we just need to get a specific job summary 523 type JobSummaryRequest struct { 524 JobID string 525 QueryOptions 526 } 527 528 // JobDispatchRequest is used to dispatch a job based on a parameterized job 529 type JobDispatchRequest struct { 530 JobID string 531 Payload []byte 532 Meta map[string]string 533 WriteRequest 534 } 535 536 // JobValidateRequest is used to validate a job 537 type JobValidateRequest struct { 538 Job *Job 539 WriteRequest 540 } 541 542 // JobRevertRequest is used to revert a job to a prior version. 543 type JobRevertRequest struct { 544 // JobID is the ID of the job being reverted 545 JobID string 546 547 // JobVersion the version to revert to. 548 JobVersion uint64 549 550 // EnforcePriorVersion if set will enforce that the job is at the given 551 // version before reverting. 552 EnforcePriorVersion *uint64 553 554 WriteRequest 555 } 556 557 // JobStabilityRequest is used to marked a job as stable. 558 type JobStabilityRequest struct { 559 // Job to set the stability on 560 JobID string 561 JobVersion uint64 562 563 // Set the stability 564 Stable bool 565 WriteRequest 566 } 567 568 // JobStabilityResponse is the response when marking a job as stable. 569 type JobStabilityResponse struct { 570 WriteMeta 571 } 572 573 // NodeListRequest is used to parameterize a list request 574 type NodeListRequest struct { 575 QueryOptions 576 } 577 578 // EvalUpdateRequest is used for upserting evaluations. 579 type EvalUpdateRequest struct { 580 Evals []*Evaluation 581 EvalToken string 582 WriteRequest 583 } 584 585 // EvalDeleteRequest is used for deleting an evaluation. 586 type EvalDeleteRequest struct { 587 Evals []string 588 Allocs []string 589 WriteRequest 590 } 591 592 // EvalSpecificRequest is used when we just need to specify a target evaluation 593 type EvalSpecificRequest struct { 594 EvalID string 595 QueryOptions 596 } 597 598 // EvalAckRequest is used to Ack/Nack a specific evaluation 599 type EvalAckRequest struct { 600 EvalID string 601 Token string 602 WriteRequest 603 } 604 605 // EvalDequeueRequest is used when we want to dequeue an evaluation 606 type EvalDequeueRequest struct { 607 Schedulers []string 608 Timeout time.Duration 609 SchedulerVersion uint16 610 WriteRequest 611 } 612 613 // EvalListRequest is used to list the evaluations 614 type EvalListRequest struct { 615 QueryOptions 616 } 617 618 // PlanRequest is used to submit an allocation plan to the leader 619 type PlanRequest struct { 620 Plan *Plan 621 WriteRequest 622 } 623 624 // ApplyPlanResultsRequest is used by the planner to apply a Raft transaction 625 // committing the result of a plan. 626 type ApplyPlanResultsRequest struct { 627 // AllocUpdateRequest holds the allocation updates to be made by the 628 // scheduler. 629 AllocUpdateRequest 630 631 // Deployment is the deployment created or updated as a result of a 632 // scheduling event. 633 Deployment *Deployment 634 635 // DeploymentUpdates is a set of status updates to apply to the given 636 // deployments. This allows the scheduler to cancel any unneeded deployment 637 // because the job is stopped or the update block is removed. 638 DeploymentUpdates []*DeploymentStatusUpdate 639 640 // EvalID is the eval ID of the plan being applied. The modify index of the 641 // evaluation is updated as part of applying the plan to ensure that subsequent 642 // scheduling events for the same job will wait for the index that last produced 643 // state changes. This is necessary for blocked evaluations since they can be 644 // processed many times, potentially making state updates, without the state of 645 // the evaluation itself being updated. 646 EvalID string 647 } 648 649 // AllocUpdateRequest is used to submit changes to allocations, either 650 // to cause evictions or to assign new allocations. Both can be done 651 // within a single transaction 652 type AllocUpdateRequest struct { 653 // Alloc is the list of new allocations to assign 654 Alloc []*Allocation 655 656 // Evals is the list of new evaluations to create 657 // Evals are valid only when used in the Raft RPC 658 Evals []*Evaluation 659 660 // Job is the shared parent job of the allocations. 661 // It is pulled out since it is common to reduce payload size. 662 Job *Job 663 664 WriteRequest 665 } 666 667 // AllocUpdateDesiredTransitionRequest is used to submit changes to allocations 668 // desired transition state. 669 type AllocUpdateDesiredTransitionRequest struct { 670 // Allocs is the mapping of allocation ids to their desired state 671 // transition 672 Allocs map[string]*DesiredTransition 673 674 // Evals is the set of evaluations to create 675 Evals []*Evaluation 676 677 WriteRequest 678 } 679 680 // AllocListRequest is used to request a list of allocations 681 type AllocListRequest struct { 682 QueryOptions 683 } 684 685 // AllocSpecificRequest is used to query a specific allocation 686 type AllocSpecificRequest struct { 687 AllocID string 688 QueryOptions 689 } 690 691 // AllocsGetRequest is used to query a set of allocations 692 type AllocsGetRequest struct { 693 AllocIDs []string 694 QueryOptions 695 } 696 697 // PeriodicForceRequest is used to force a specific periodic job. 698 type PeriodicForceRequest struct { 699 JobID string 700 WriteRequest 701 } 702 703 // ServerMembersResponse has the list of servers in a cluster 704 type ServerMembersResponse struct { 705 ServerName string 706 ServerRegion string 707 ServerDC string 708 Members []*ServerMember 709 } 710 711 // ServerMember holds information about a Nomad server agent in a cluster 712 type ServerMember struct { 713 Name string 714 Addr net.IP 715 Port uint16 716 Tags map[string]string 717 Status string 718 ProtocolMin uint8 719 ProtocolMax uint8 720 ProtocolCur uint8 721 DelegateMin uint8 722 DelegateMax uint8 723 DelegateCur uint8 724 } 725 726 // DeriveVaultTokenRequest is used to request wrapped Vault tokens for the 727 // following tasks in the given allocation 728 type DeriveVaultTokenRequest struct { 729 NodeID string 730 SecretID string 731 AllocID string 732 Tasks []string 733 QueryOptions 734 } 735 736 // VaultAccessorsRequest is used to operate on a set of Vault accessors 737 type VaultAccessorsRequest struct { 738 Accessors []*VaultAccessor 739 } 740 741 // VaultAccessor is a reference to a created Vault token on behalf of 742 // an allocation's task. 743 type VaultAccessor struct { 744 AllocID string 745 Task string 746 NodeID string 747 Accessor string 748 CreationTTL int 749 750 // Raft Indexes 751 CreateIndex uint64 752 } 753 754 // DeriveVaultTokenResponse returns the wrapped tokens for each requested task 755 type DeriveVaultTokenResponse struct { 756 // Tasks is a mapping between the task name and the wrapped token 757 Tasks map[string]string 758 759 // Error stores any error that occurred. Errors are stored here so we can 760 // communicate whether it is retriable 761 Error *RecoverableError 762 763 QueryMeta 764 } 765 766 // GenericRequest is used to request where no 767 // specific information is needed. 768 type GenericRequest struct { 769 QueryOptions 770 } 771 772 // DeploymentListRequest is used to list the deployments 773 type DeploymentListRequest struct { 774 QueryOptions 775 } 776 777 // DeploymentDeleteRequest is used for deleting deployments. 778 type DeploymentDeleteRequest struct { 779 Deployments []string 780 WriteRequest 781 } 782 783 // DeploymentStatusUpdateRequest is used to update the status of a deployment as 784 // well as optionally creating an evaluation atomically. 785 type DeploymentStatusUpdateRequest struct { 786 // Eval, if set, is used to create an evaluation at the same time as 787 // updating the status of a deployment. 788 Eval *Evaluation 789 790 // DeploymentUpdate is a status update to apply to the given 791 // deployment. 792 DeploymentUpdate *DeploymentStatusUpdate 793 794 // Job is used to optionally upsert a job. This is used when setting the 795 // allocation health results in a deployment failure and the deployment 796 // auto-reverts to the latest stable job. 797 Job *Job 798 } 799 800 // DeploymentAllocHealthRequest is used to set the health of a set of 801 // allocations as part of a deployment. 802 type DeploymentAllocHealthRequest struct { 803 DeploymentID string 804 805 // Marks these allocations as healthy, allow further allocations 806 // to be rolled. 807 HealthyAllocationIDs []string 808 809 // Any unhealthy allocations fail the deployment 810 UnhealthyAllocationIDs []string 811 812 WriteRequest 813 } 814 815 // ApplyDeploymentAllocHealthRequest is used to apply an alloc health request via Raft 816 type ApplyDeploymentAllocHealthRequest struct { 817 DeploymentAllocHealthRequest 818 819 // Timestamp is the timestamp to use when setting the allocations health. 820 Timestamp time.Time 821 822 // An optional field to update the status of a deployment 823 DeploymentUpdate *DeploymentStatusUpdate 824 825 // Job is used to optionally upsert a job. This is used when setting the 826 // allocation health results in a deployment failure and the deployment 827 // auto-reverts to the latest stable job. 828 Job *Job 829 830 // An optional evaluation to create after promoting the canaries 831 Eval *Evaluation 832 } 833 834 // DeploymentPromoteRequest is used to promote task groups in a deployment 835 type DeploymentPromoteRequest struct { 836 DeploymentID string 837 838 // All is to promote all task groups 839 All bool 840 841 // Groups is used to set the promotion status per task group 842 Groups []string 843 844 WriteRequest 845 } 846 847 // ApplyDeploymentPromoteRequest is used to apply a promotion request via Raft 848 type ApplyDeploymentPromoteRequest struct { 849 DeploymentPromoteRequest 850 851 // An optional evaluation to create after promoting the canaries 852 Eval *Evaluation 853 } 854 855 // DeploymentPauseRequest is used to pause a deployment 856 type DeploymentPauseRequest struct { 857 DeploymentID string 858 859 // Pause sets the pause status 860 Pause bool 861 862 WriteRequest 863 } 864 865 // DeploymentSpecificRequest is used to make a request specific to a particular 866 // deployment 867 type DeploymentSpecificRequest struct { 868 DeploymentID string 869 QueryOptions 870 } 871 872 // DeploymentFailRequest is used to fail a particular deployment 873 type DeploymentFailRequest struct { 874 DeploymentID string 875 WriteRequest 876 } 877 878 // SingleDeploymentResponse is used to respond with a single deployment 879 type SingleDeploymentResponse struct { 880 Deployment *Deployment 881 QueryMeta 882 } 883 884 // GenericResponse is used to respond to a request where no 885 // specific response information is needed. 886 type GenericResponse struct { 887 WriteMeta 888 } 889 890 // VersionResponse is used for the Status.Version response 891 type VersionResponse struct { 892 Build string 893 Versions map[string]int 894 QueryMeta 895 } 896 897 // JobRegisterResponse is used to respond to a job registration 898 type JobRegisterResponse struct { 899 EvalID string 900 EvalCreateIndex uint64 901 JobModifyIndex uint64 902 903 // Warnings contains any warnings about the given job. These may include 904 // deprecation warnings. 905 Warnings string 906 907 QueryMeta 908 } 909 910 // JobDeregisterResponse is used to respond to a job deregistration 911 type JobDeregisterResponse struct { 912 EvalID string 913 EvalCreateIndex uint64 914 JobModifyIndex uint64 915 QueryMeta 916 } 917 918 // JobBatchDeregisterResponse is used to respond to a batch job deregistration 919 type JobBatchDeregisterResponse struct { 920 // JobEvals maps the job to its created evaluation 921 JobEvals map[NamespacedID]string 922 QueryMeta 923 } 924 925 // JobValidateResponse is the response from validate request 926 type JobValidateResponse struct { 927 // DriverConfigValidated indicates whether the agent validated the driver 928 // config 929 DriverConfigValidated bool 930 931 // ValidationErrors is a list of validation errors 932 ValidationErrors []string 933 934 // Error is a string version of any error that may have occurred 935 Error string 936 937 // Warnings contains any warnings about the given job. These may include 938 // deprecation warnings. 939 Warnings string 940 } 941 942 // NodeUpdateResponse is used to respond to a node update 943 type NodeUpdateResponse struct { 944 HeartbeatTTL time.Duration 945 EvalIDs []string 946 EvalCreateIndex uint64 947 NodeModifyIndex uint64 948 949 // LeaderRPCAddr is the RPC address of the current Raft Leader. If 950 // empty, the current Nomad Server is in the minority of a partition. 951 LeaderRPCAddr string 952 953 // NumNodes is the number of Nomad nodes attached to this quorum of 954 // Nomad Servers at the time of the response. This value can 955 // fluctuate based on the health of the cluster between heartbeats. 956 NumNodes int32 957 958 // Servers is the full list of known Nomad servers in the local 959 // region. 960 Servers []*NodeServerInfo 961 962 QueryMeta 963 } 964 965 // NodeDrainUpdateResponse is used to respond to a node drain update 966 type NodeDrainUpdateResponse struct { 967 NodeModifyIndex uint64 968 EvalIDs []string 969 EvalCreateIndex uint64 970 WriteMeta 971 } 972 973 // NodeEligibilityUpdateResponse is used to respond to a node eligibility update 974 type NodeEligibilityUpdateResponse struct { 975 NodeModifyIndex uint64 976 EvalIDs []string 977 EvalCreateIndex uint64 978 WriteMeta 979 } 980 981 // NodeAllocsResponse is used to return allocs for a single node 982 type NodeAllocsResponse struct { 983 Allocs []*Allocation 984 QueryMeta 985 } 986 987 // NodeClientAllocsResponse is used to return allocs meta data for a single node 988 type NodeClientAllocsResponse struct { 989 Allocs map[string]uint64 990 991 // MigrateTokens are used when ACLs are enabled to allow cross node, 992 // authenticated access to sticky volumes 993 MigrateTokens map[string]string 994 995 QueryMeta 996 } 997 998 // SingleNodeResponse is used to return a single node 999 type SingleNodeResponse struct { 1000 Node *Node 1001 QueryMeta 1002 } 1003 1004 // NodeListResponse is used for a list request 1005 type NodeListResponse struct { 1006 Nodes []*NodeListStub 1007 QueryMeta 1008 } 1009 1010 // SingleJobResponse is used to return a single job 1011 type SingleJobResponse struct { 1012 Job *Job 1013 QueryMeta 1014 } 1015 1016 // JobSummaryResponse is used to return a single job summary 1017 type JobSummaryResponse struct { 1018 JobSummary *JobSummary 1019 QueryMeta 1020 } 1021 1022 type JobDispatchResponse struct { 1023 DispatchedJobID string 1024 EvalID string 1025 EvalCreateIndex uint64 1026 JobCreateIndex uint64 1027 WriteMeta 1028 } 1029 1030 // JobListResponse is used for a list request 1031 type JobListResponse struct { 1032 Jobs []*JobListStub 1033 QueryMeta 1034 } 1035 1036 // JobVersionsRequest is used to get a jobs versions 1037 type JobVersionsRequest struct { 1038 JobID string 1039 Diffs bool 1040 QueryOptions 1041 } 1042 1043 // JobVersionsResponse is used for a job get versions request 1044 type JobVersionsResponse struct { 1045 Versions []*Job 1046 Diffs []*JobDiff 1047 QueryMeta 1048 } 1049 1050 // JobPlanResponse is used to respond to a job plan request 1051 type JobPlanResponse struct { 1052 // Annotations stores annotations explaining decisions the scheduler made. 1053 Annotations *PlanAnnotations 1054 1055 // FailedTGAllocs is the placement failures per task group. 1056 FailedTGAllocs map[string]*AllocMetric 1057 1058 // JobModifyIndex is the modification index of the job. The value can be 1059 // used when running `nomad run` to ensure that the Job wasn’t modified 1060 // since the last plan. If the job is being created, the value is zero. 1061 JobModifyIndex uint64 1062 1063 // CreatedEvals is the set of evaluations created by the scheduler. The 1064 // reasons for this can be rolling-updates or blocked evals. 1065 CreatedEvals []*Evaluation 1066 1067 // Diff contains the diff of the job and annotations on whether the change 1068 // causes an in-place update or create/destroy 1069 Diff *JobDiff 1070 1071 // NextPeriodicLaunch is the time duration till the job would be launched if 1072 // submitted. 1073 NextPeriodicLaunch time.Time 1074 1075 // Warnings contains any warnings about the given job. These may include 1076 // deprecation warnings. 1077 Warnings string 1078 1079 WriteMeta 1080 } 1081 1082 // SingleAllocResponse is used to return a single allocation 1083 type SingleAllocResponse struct { 1084 Alloc *Allocation 1085 QueryMeta 1086 } 1087 1088 // AllocsGetResponse is used to return a set of allocations 1089 type AllocsGetResponse struct { 1090 Allocs []*Allocation 1091 QueryMeta 1092 } 1093 1094 // JobAllocationsResponse is used to return the allocations for a job 1095 type JobAllocationsResponse struct { 1096 Allocations []*AllocListStub 1097 QueryMeta 1098 } 1099 1100 // JobEvaluationsResponse is used to return the evaluations for a job 1101 type JobEvaluationsResponse struct { 1102 Evaluations []*Evaluation 1103 QueryMeta 1104 } 1105 1106 // SingleEvalResponse is used to return a single evaluation 1107 type SingleEvalResponse struct { 1108 Eval *Evaluation 1109 QueryMeta 1110 } 1111 1112 // EvalDequeueResponse is used to return from a dequeue 1113 type EvalDequeueResponse struct { 1114 Eval *Evaluation 1115 Token string 1116 1117 // WaitIndex is the Raft index the worker should wait until invoking the 1118 // scheduler. 1119 WaitIndex uint64 1120 1121 QueryMeta 1122 } 1123 1124 // GetWaitIndex is used to retrieve the Raft index in which state should be at 1125 // or beyond before invoking the scheduler. 1126 func (e *EvalDequeueResponse) GetWaitIndex() uint64 { 1127 // Prefer the wait index sent. This will be populated on all responses from 1128 // 0.7.0 and above 1129 if e.WaitIndex != 0 { 1130 return e.WaitIndex 1131 } else if e.Eval != nil { 1132 return e.Eval.ModifyIndex 1133 } 1134 1135 // This should never happen 1136 return 1 1137 } 1138 1139 // PlanResponse is used to return from a PlanRequest 1140 type PlanResponse struct { 1141 Result *PlanResult 1142 WriteMeta 1143 } 1144 1145 // AllocListResponse is used for a list request 1146 type AllocListResponse struct { 1147 Allocations []*AllocListStub 1148 QueryMeta 1149 } 1150 1151 // DeploymentListResponse is used for a list request 1152 type DeploymentListResponse struct { 1153 Deployments []*Deployment 1154 QueryMeta 1155 } 1156 1157 // EvalListResponse is used for a list request 1158 type EvalListResponse struct { 1159 Evaluations []*Evaluation 1160 QueryMeta 1161 } 1162 1163 // EvalAllocationsResponse is used to return the allocations for an evaluation 1164 type EvalAllocationsResponse struct { 1165 Allocations []*AllocListStub 1166 QueryMeta 1167 } 1168 1169 // PeriodicForceResponse is used to respond to a periodic job force launch 1170 type PeriodicForceResponse struct { 1171 EvalID string 1172 EvalCreateIndex uint64 1173 WriteMeta 1174 } 1175 1176 // DeploymentUpdateResponse is used to respond to a deployment change. The 1177 // response will include the modify index of the deployment as well as details 1178 // of any triggered evaluation. 1179 type DeploymentUpdateResponse struct { 1180 EvalID string 1181 EvalCreateIndex uint64 1182 DeploymentModifyIndex uint64 1183 1184 // RevertedJobVersion is the version the job was reverted to. If unset, the 1185 // job wasn't reverted 1186 RevertedJobVersion *uint64 1187 1188 WriteMeta 1189 } 1190 1191 // NodeConnQueryResponse is used to respond to a query of whether a server has 1192 // a connection to a specific Node 1193 type NodeConnQueryResponse struct { 1194 // Connected indicates whether a connection to the Client exists 1195 Connected bool 1196 1197 // Established marks the time at which the connection was established 1198 Established time.Time 1199 1200 QueryMeta 1201 } 1202 1203 // EmitNodeEventsRequest is a request to update the node events source 1204 // with a new client-side event 1205 type EmitNodeEventsRequest struct { 1206 // NodeEvents are a map where the key is a node id, and value is a list of 1207 // events for that node 1208 NodeEvents map[string][]*NodeEvent 1209 1210 WriteRequest 1211 } 1212 1213 // EmitNodeEventsResponse is a response to the client about the status of 1214 // the node event source update. 1215 type EmitNodeEventsResponse struct { 1216 Index uint64 1217 WriteMeta 1218 } 1219 1220 const ( 1221 NodeEventSubsystemDrain = "Drain" 1222 NodeEventSubsystemDriver = "Driver" 1223 NodeEventSubsystemHeartbeat = "Heartbeat" 1224 NodeEventSubsystemCluster = "Cluster" 1225 ) 1226 1227 // NodeEvent is a single unit representing a node’s state change 1228 type NodeEvent struct { 1229 Message string 1230 Subsystem string 1231 Details map[string]string 1232 Timestamp time.Time 1233 CreateIndex uint64 1234 } 1235 1236 func (ne *NodeEvent) String() string { 1237 var details []string 1238 for k, v := range ne.Details { 1239 details = append(details, fmt.Sprintf("%s: %s", k, v)) 1240 } 1241 1242 return fmt.Sprintf("Message: %s, Subsystem: %s, Details: %s, Timestamp: %s", ne.Message, ne.Subsystem, strings.Join(details, ","), ne.Timestamp.String()) 1243 } 1244 1245 func (ne *NodeEvent) Copy() *NodeEvent { 1246 c := new(NodeEvent) 1247 *c = *ne 1248 c.Details = helper.CopyMapStringString(ne.Details) 1249 return c 1250 } 1251 1252 // NewNodeEvent generates a new node event storing the current time as the 1253 // timestamp 1254 func NewNodeEvent() *NodeEvent { 1255 return &NodeEvent{Timestamp: time.Now()} 1256 } 1257 1258 // SetMessage is used to set the message on the node event 1259 func (ne *NodeEvent) SetMessage(msg string) *NodeEvent { 1260 ne.Message = msg 1261 return ne 1262 } 1263 1264 // SetSubsystem is used to set the subsystem on the node event 1265 func (ne *NodeEvent) SetSubsystem(sys string) *NodeEvent { 1266 ne.Subsystem = sys 1267 return ne 1268 } 1269 1270 // SetTimestamp is used to set the timestamp on the node event 1271 func (ne *NodeEvent) SetTimestamp(ts time.Time) *NodeEvent { 1272 ne.Timestamp = ts 1273 return ne 1274 } 1275 1276 // AddDetail is used to add a detail to the node event 1277 func (ne *NodeEvent) AddDetail(k, v string) *NodeEvent { 1278 if ne.Details == nil { 1279 ne.Details = make(map[string]string, 1) 1280 } 1281 ne.Details[k] = v 1282 return ne 1283 } 1284 1285 const ( 1286 NodeStatusInit = "initializing" 1287 NodeStatusReady = "ready" 1288 NodeStatusDown = "down" 1289 ) 1290 1291 // ShouldDrainNode checks if a given node status should trigger an 1292 // evaluation. Some states don't require any further action. 1293 func ShouldDrainNode(status string) bool { 1294 switch status { 1295 case NodeStatusInit, NodeStatusReady: 1296 return false 1297 case NodeStatusDown: 1298 return true 1299 default: 1300 panic(fmt.Sprintf("unhandled node status %s", status)) 1301 } 1302 } 1303 1304 // ValidNodeStatus is used to check if a node status is valid 1305 func ValidNodeStatus(status string) bool { 1306 switch status { 1307 case NodeStatusInit, NodeStatusReady, NodeStatusDown: 1308 return true 1309 default: 1310 return false 1311 } 1312 } 1313 1314 const ( 1315 // NodeSchedulingEligible and Ineligible marks the node as eligible or not, 1316 // respectively, for receiving allocations. This is orthoginal to the node 1317 // status being ready. 1318 NodeSchedulingEligible = "eligible" 1319 NodeSchedulingIneligible = "ineligible" 1320 ) 1321 1322 // DrainSpec describes a Node's desired drain behavior. 1323 type DrainSpec struct { 1324 // Deadline is the duration after StartTime when the remaining 1325 // allocations on a draining Node should be told to stop. 1326 Deadline time.Duration 1327 1328 // IgnoreSystemJobs allows systems jobs to remain on the node even though it 1329 // has been marked for draining. 1330 IgnoreSystemJobs bool 1331 } 1332 1333 // DrainStrategy describes a Node's drain behavior. 1334 type DrainStrategy struct { 1335 // DrainSpec is the user declared drain specification 1336 DrainSpec 1337 1338 // ForceDeadline is the deadline time for the drain after which drains will 1339 // be forced 1340 ForceDeadline time.Time 1341 } 1342 1343 func (d *DrainStrategy) Copy() *DrainStrategy { 1344 if d == nil { 1345 return nil 1346 } 1347 1348 nd := new(DrainStrategy) 1349 *nd = *d 1350 return nd 1351 } 1352 1353 // DeadlineTime returns a boolean whether the drain strategy allows an infinite 1354 // duration or otherwise the deadline time. The force drain is captured by the 1355 // deadline time being in the past. 1356 func (d *DrainStrategy) DeadlineTime() (infinite bool, deadline time.Time) { 1357 // Treat the nil case as a force drain so during an upgrade where a node may 1358 // not have a drain strategy but has Drain set to true, it is treated as a 1359 // force to mimick old behavior. 1360 if d == nil { 1361 return false, time.Time{} 1362 } 1363 1364 ns := d.Deadline.Nanoseconds() 1365 switch { 1366 case ns < 0: // Force 1367 return false, time.Time{} 1368 case ns == 0: // Infinite 1369 return true, time.Time{} 1370 default: 1371 return false, d.ForceDeadline 1372 } 1373 } 1374 1375 func (d *DrainStrategy) Equal(o *DrainStrategy) bool { 1376 if d == nil && o == nil { 1377 return true 1378 } else if o != nil && d == nil { 1379 return false 1380 } else if d != nil && o == nil { 1381 return false 1382 } 1383 1384 // Compare values 1385 if d.ForceDeadline != o.ForceDeadline { 1386 return false 1387 } else if d.Deadline != o.Deadline { 1388 return false 1389 } else if d.IgnoreSystemJobs != o.IgnoreSystemJobs { 1390 return false 1391 } 1392 1393 return true 1394 } 1395 1396 // Node is a representation of a schedulable client node 1397 type Node struct { 1398 // ID is a unique identifier for the node. It can be constructed 1399 // by doing a concatenation of the Name and Datacenter as a simple 1400 // approach. Alternatively a UUID may be used. 1401 ID string 1402 1403 // SecretID is an ID that is only known by the Node and the set of Servers. 1404 // It is not accessible via the API and is used to authenticate nodes 1405 // conducting privileged activities. 1406 SecretID string 1407 1408 // Datacenter for this node 1409 Datacenter string 1410 1411 // Node name 1412 Name string 1413 1414 // HTTPAddr is the address on which the Nomad client is listening for http 1415 // requests 1416 HTTPAddr string 1417 1418 // TLSEnabled indicates if the Agent has TLS enabled for the HTTP API 1419 TLSEnabled bool 1420 1421 // Attributes is an arbitrary set of key/value 1422 // data that can be used for constraints. Examples 1423 // include "kernel.name=linux", "arch=386", "driver.docker=1", 1424 // "docker.runtime=1.8.3" 1425 Attributes map[string]string 1426 1427 // Resources is the available resources on the client. 1428 // For example 'cpu=2' 'memory=2048' 1429 Resources *Resources 1430 1431 // Reserved is the set of resources that are reserved, 1432 // and should be subtracted from the total resources for 1433 // the purposes of scheduling. This may be provide certain 1434 // high-watermark tolerances or because of external schedulers 1435 // consuming resources. 1436 Reserved *Resources 1437 1438 // Links are used to 'link' this client to external 1439 // systems. For example 'consul=foo.dc1' 'aws=i-83212' 1440 // 'ami=ami-123' 1441 Links map[string]string 1442 1443 // Meta is used to associate arbitrary metadata with this 1444 // client. This is opaque to Nomad. 1445 Meta map[string]string 1446 1447 // NodeClass is an opaque identifier used to group nodes 1448 // together for the purpose of determining scheduling pressure. 1449 NodeClass string 1450 1451 // ComputedClass is a unique id that identifies nodes with a common set of 1452 // attributes and capabilities. 1453 ComputedClass string 1454 1455 // COMPAT: Remove in Nomad 0.9 1456 // Drain is controlled by the servers, and not the client. 1457 // If true, no jobs will be scheduled to this node, and existing 1458 // allocations will be drained. Superceded by DrainStrategy in Nomad 1459 // 0.8 but kept for backward compat. 1460 Drain bool 1461 1462 // DrainStrategy determines the node's draining behavior. Will be nil 1463 // when Drain=false. 1464 DrainStrategy *DrainStrategy 1465 1466 // SchedulingEligibility determines whether this node will receive new 1467 // placements. 1468 SchedulingEligibility string 1469 1470 // Status of this node 1471 Status string 1472 1473 // StatusDescription is meant to provide more human useful information 1474 StatusDescription string 1475 1476 // StatusUpdatedAt is the time stamp at which the state of the node was 1477 // updated 1478 StatusUpdatedAt int64 1479 1480 // Events is the most recent set of events generated for the node, 1481 // retaining only MaxRetainedNodeEvents number at a time 1482 Events []*NodeEvent 1483 1484 // Drivers is a map of driver names to current driver information 1485 Drivers map[string]*DriverInfo 1486 1487 // Raft Indexes 1488 CreateIndex uint64 1489 ModifyIndex uint64 1490 } 1491 1492 // Ready returns true if the node is ready for running allocations 1493 func (n *Node) Ready() bool { 1494 // Drain is checked directly to support pre-0.8 Node data 1495 return n.Status == NodeStatusReady && !n.Drain && n.SchedulingEligibility == NodeSchedulingEligible 1496 } 1497 1498 func (n *Node) Canonicalize() { 1499 if n == nil { 1500 return 1501 } 1502 1503 // COMPAT Remove in 0.10 1504 // In v0.8.0 we introduced scheduling eligibility, so we need to set it for 1505 // upgrading nodes 1506 if n.SchedulingEligibility == "" { 1507 if n.Drain { 1508 n.SchedulingEligibility = NodeSchedulingIneligible 1509 } else { 1510 n.SchedulingEligibility = NodeSchedulingEligible 1511 } 1512 } 1513 } 1514 1515 func (n *Node) Copy() *Node { 1516 if n == nil { 1517 return nil 1518 } 1519 nn := new(Node) 1520 *nn = *n 1521 nn.Attributes = helper.CopyMapStringString(nn.Attributes) 1522 nn.Resources = nn.Resources.Copy() 1523 nn.Reserved = nn.Reserved.Copy() 1524 nn.Links = helper.CopyMapStringString(nn.Links) 1525 nn.Meta = helper.CopyMapStringString(nn.Meta) 1526 nn.Events = copyNodeEvents(n.Events) 1527 nn.DrainStrategy = nn.DrainStrategy.Copy() 1528 nn.Drivers = copyNodeDrivers(n.Drivers) 1529 return nn 1530 } 1531 1532 // copyNodeEvents is a helper to copy a list of NodeEvent's 1533 func copyNodeEvents(events []*NodeEvent) []*NodeEvent { 1534 l := len(events) 1535 if l == 0 { 1536 return nil 1537 } 1538 1539 c := make([]*NodeEvent, l) 1540 for i, event := range events { 1541 c[i] = event.Copy() 1542 } 1543 return c 1544 } 1545 1546 // copyNodeDrivers is a helper to copy a map of DriverInfo 1547 func copyNodeDrivers(drivers map[string]*DriverInfo) map[string]*DriverInfo { 1548 l := len(drivers) 1549 if l == 0 { 1550 return nil 1551 } 1552 1553 c := make(map[string]*DriverInfo, l) 1554 for driver, info := range drivers { 1555 c[driver] = info.Copy() 1556 } 1557 return c 1558 } 1559 1560 // TerminalStatus returns if the current status is terminal and 1561 // will no longer transition. 1562 func (n *Node) TerminalStatus() bool { 1563 switch n.Status { 1564 case NodeStatusDown: 1565 return true 1566 default: 1567 return false 1568 } 1569 } 1570 1571 // Stub returns a summarized version of the node 1572 func (n *Node) Stub() *NodeListStub { 1573 1574 addr, _, _ := net.SplitHostPort(n.HTTPAddr) 1575 1576 return &NodeListStub{ 1577 Address: addr, 1578 ID: n.ID, 1579 Datacenter: n.Datacenter, 1580 Name: n.Name, 1581 NodeClass: n.NodeClass, 1582 Version: n.Attributes["nomad.version"], 1583 Drain: n.Drain, 1584 SchedulingEligibility: n.SchedulingEligibility, 1585 Status: n.Status, 1586 StatusDescription: n.StatusDescription, 1587 Drivers: n.Drivers, 1588 CreateIndex: n.CreateIndex, 1589 ModifyIndex: n.ModifyIndex, 1590 } 1591 } 1592 1593 // NodeListStub is used to return a subset of job information 1594 // for the job list 1595 type NodeListStub struct { 1596 Address string 1597 ID string 1598 Datacenter string 1599 Name string 1600 NodeClass string 1601 Version string 1602 Drain bool 1603 SchedulingEligibility string 1604 Status string 1605 StatusDescription string 1606 Drivers map[string]*DriverInfo 1607 CreateIndex uint64 1608 ModifyIndex uint64 1609 } 1610 1611 // Networks defined for a task on the Resources struct. 1612 type Networks []*NetworkResource 1613 1614 // Port assignment and IP for the given label or empty values. 1615 func (ns Networks) Port(label string) (string, int) { 1616 for _, n := range ns { 1617 for _, p := range n.ReservedPorts { 1618 if p.Label == label { 1619 return n.IP, p.Value 1620 } 1621 } 1622 for _, p := range n.DynamicPorts { 1623 if p.Label == label { 1624 return n.IP, p.Value 1625 } 1626 } 1627 } 1628 return "", 0 1629 } 1630 1631 // Resources is used to define the resources available 1632 // on a client 1633 type Resources struct { 1634 CPU int 1635 MemoryMB int 1636 DiskMB int 1637 IOPS int 1638 Networks Networks 1639 } 1640 1641 const ( 1642 BytesInMegabyte = 1024 * 1024 1643 ) 1644 1645 // DefaultResources is a small resources object that contains the 1646 // default resources requests that we will provide to an object. 1647 // --- THIS FUNCTION IS REPLICATED IN api/resources.go and should 1648 // be kept in sync. 1649 func DefaultResources() *Resources { 1650 return &Resources{ 1651 CPU: 100, 1652 MemoryMB: 300, 1653 IOPS: 0, 1654 } 1655 } 1656 1657 // MinResources is a small resources object that contains the 1658 // absolute minimum resources that we will provide to an object. 1659 // This should not be confused with the defaults which are 1660 // provided in Canonicalize() --- THIS FUNCTION IS REPLICATED IN 1661 // api/resources.go and should be kept in sync. 1662 func MinResources() *Resources { 1663 return &Resources{ 1664 CPU: 20, 1665 MemoryMB: 10, 1666 IOPS: 0, 1667 } 1668 } 1669 1670 // DiskInBytes returns the amount of disk resources in bytes. 1671 func (r *Resources) DiskInBytes() int64 { 1672 return int64(r.DiskMB * BytesInMegabyte) 1673 } 1674 1675 // Merge merges this resource with another resource. 1676 func (r *Resources) Merge(other *Resources) { 1677 if other.CPU != 0 { 1678 r.CPU = other.CPU 1679 } 1680 if other.MemoryMB != 0 { 1681 r.MemoryMB = other.MemoryMB 1682 } 1683 if other.DiskMB != 0 { 1684 r.DiskMB = other.DiskMB 1685 } 1686 if other.IOPS != 0 { 1687 r.IOPS = other.IOPS 1688 } 1689 if len(other.Networks) != 0 { 1690 r.Networks = other.Networks 1691 } 1692 } 1693 1694 func (r *Resources) Canonicalize() { 1695 // Ensure that an empty and nil slices are treated the same to avoid scheduling 1696 // problems since we use reflect DeepEquals. 1697 if len(r.Networks) == 0 { 1698 r.Networks = nil 1699 } 1700 1701 for _, n := range r.Networks { 1702 n.Canonicalize() 1703 } 1704 } 1705 1706 // MeetsMinResources returns an error if the resources specified are less than 1707 // the minimum allowed. 1708 // This is based on the minimums defined in the Resources type 1709 func (r *Resources) MeetsMinResources() error { 1710 var mErr multierror.Error 1711 minResources := MinResources() 1712 if r.CPU < minResources.CPU { 1713 mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum CPU value is %d; got %d", minResources.CPU, r.CPU)) 1714 } 1715 if r.MemoryMB < minResources.MemoryMB { 1716 mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum MemoryMB value is %d; got %d", minResources.MemoryMB, r.MemoryMB)) 1717 } 1718 if r.IOPS < minResources.IOPS { 1719 mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum IOPS value is %d; got %d", minResources.IOPS, r.IOPS)) 1720 } 1721 for i, n := range r.Networks { 1722 if err := n.MeetsMinResources(); err != nil { 1723 mErr.Errors = append(mErr.Errors, fmt.Errorf("network resource at index %d failed: %v", i, err)) 1724 } 1725 } 1726 1727 return mErr.ErrorOrNil() 1728 } 1729 1730 // Copy returns a deep copy of the resources 1731 func (r *Resources) Copy() *Resources { 1732 if r == nil { 1733 return nil 1734 } 1735 newR := new(Resources) 1736 *newR = *r 1737 if r.Networks != nil { 1738 n := len(r.Networks) 1739 newR.Networks = make([]*NetworkResource, n) 1740 for i := 0; i < n; i++ { 1741 newR.Networks[i] = r.Networks[i].Copy() 1742 } 1743 } 1744 return newR 1745 } 1746 1747 // NetIndex finds the matching net index using device name 1748 func (r *Resources) NetIndex(n *NetworkResource) int { 1749 for idx, net := range r.Networks { 1750 if net.Device == n.Device { 1751 return idx 1752 } 1753 } 1754 return -1 1755 } 1756 1757 // Superset checks if one set of resources is a superset 1758 // of another. This ignores network resources, and the NetworkIndex 1759 // should be used for that. 1760 func (r *Resources) Superset(other *Resources) (bool, string) { 1761 if r.CPU < other.CPU { 1762 return false, "cpu" 1763 } 1764 if r.MemoryMB < other.MemoryMB { 1765 return false, "memory" 1766 } 1767 if r.DiskMB < other.DiskMB { 1768 return false, "disk" 1769 } 1770 if r.IOPS < other.IOPS { 1771 return false, "iops" 1772 } 1773 return true, "" 1774 } 1775 1776 // Add adds the resources of the delta to this, potentially 1777 // returning an error if not possible. 1778 func (r *Resources) Add(delta *Resources) error { 1779 if delta == nil { 1780 return nil 1781 } 1782 r.CPU += delta.CPU 1783 r.MemoryMB += delta.MemoryMB 1784 r.DiskMB += delta.DiskMB 1785 r.IOPS += delta.IOPS 1786 1787 for _, n := range delta.Networks { 1788 // Find the matching interface by IP or CIDR 1789 idx := r.NetIndex(n) 1790 if idx == -1 { 1791 r.Networks = append(r.Networks, n.Copy()) 1792 } else { 1793 r.Networks[idx].Add(n) 1794 } 1795 } 1796 return nil 1797 } 1798 1799 func (r *Resources) GoString() string { 1800 return fmt.Sprintf("*%#v", *r) 1801 } 1802 1803 type Port struct { 1804 Label string 1805 Value int 1806 } 1807 1808 // NetworkResource is used to represent available network 1809 // resources 1810 type NetworkResource struct { 1811 Device string // Name of the device 1812 CIDR string // CIDR block of addresses 1813 IP string // Host IP address 1814 MBits int // Throughput 1815 ReservedPorts []Port // Host Reserved ports 1816 DynamicPorts []Port // Host Dynamically assigned ports 1817 } 1818 1819 func (nr *NetworkResource) Equals(other *NetworkResource) bool { 1820 if nr.Device != other.Device { 1821 return false 1822 } 1823 1824 if nr.CIDR != other.CIDR { 1825 return false 1826 } 1827 1828 if nr.IP != other.IP { 1829 return false 1830 } 1831 1832 if nr.MBits != other.MBits { 1833 return false 1834 } 1835 1836 if len(nr.ReservedPorts) != len(other.ReservedPorts) { 1837 return false 1838 } 1839 1840 for i, port := range nr.ReservedPorts { 1841 if len(other.ReservedPorts) <= i { 1842 return false 1843 } 1844 if port != other.ReservedPorts[i] { 1845 return false 1846 } 1847 } 1848 1849 if len(nr.DynamicPorts) != len(other.DynamicPorts) { 1850 return false 1851 } 1852 for i, port := range nr.DynamicPorts { 1853 if len(other.DynamicPorts) <= i { 1854 return false 1855 } 1856 if port != other.DynamicPorts[i] { 1857 return false 1858 } 1859 } 1860 return true 1861 } 1862 1863 func (n *NetworkResource) Canonicalize() { 1864 // Ensure that an empty and nil slices are treated the same to avoid scheduling 1865 // problems since we use reflect DeepEquals. 1866 if len(n.ReservedPorts) == 0 { 1867 n.ReservedPorts = nil 1868 } 1869 if len(n.DynamicPorts) == 0 { 1870 n.DynamicPorts = nil 1871 } 1872 } 1873 1874 // MeetsMinResources returns an error if the resources specified are less than 1875 // the minimum allowed. 1876 func (n *NetworkResource) MeetsMinResources() error { 1877 var mErr multierror.Error 1878 if n.MBits < 1 { 1879 mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum MBits value is 1; got %d", n.MBits)) 1880 } 1881 return mErr.ErrorOrNil() 1882 } 1883 1884 // Copy returns a deep copy of the network resource 1885 func (n *NetworkResource) Copy() *NetworkResource { 1886 if n == nil { 1887 return nil 1888 } 1889 newR := new(NetworkResource) 1890 *newR = *n 1891 if n.ReservedPorts != nil { 1892 newR.ReservedPorts = make([]Port, len(n.ReservedPorts)) 1893 copy(newR.ReservedPorts, n.ReservedPorts) 1894 } 1895 if n.DynamicPorts != nil { 1896 newR.DynamicPorts = make([]Port, len(n.DynamicPorts)) 1897 copy(newR.DynamicPorts, n.DynamicPorts) 1898 } 1899 return newR 1900 } 1901 1902 // Add adds the resources of the delta to this, potentially 1903 // returning an error if not possible. 1904 func (n *NetworkResource) Add(delta *NetworkResource) { 1905 if len(delta.ReservedPorts) > 0 { 1906 n.ReservedPorts = append(n.ReservedPorts, delta.ReservedPorts...) 1907 } 1908 n.MBits += delta.MBits 1909 n.DynamicPorts = append(n.DynamicPorts, delta.DynamicPorts...) 1910 } 1911 1912 func (n *NetworkResource) GoString() string { 1913 return fmt.Sprintf("*%#v", *n) 1914 } 1915 1916 // PortLabels returns a map of port labels to their assigned host ports. 1917 func (n *NetworkResource) PortLabels() map[string]int { 1918 num := len(n.ReservedPorts) + len(n.DynamicPorts) 1919 labelValues := make(map[string]int, num) 1920 for _, port := range n.ReservedPorts { 1921 labelValues[port.Label] = port.Value 1922 } 1923 for _, port := range n.DynamicPorts { 1924 labelValues[port.Label] = port.Value 1925 } 1926 return labelValues 1927 } 1928 1929 const ( 1930 // JobTypeNomad is reserved for internal system tasks and is 1931 // always handled by the CoreScheduler. 1932 JobTypeCore = "_core" 1933 JobTypeService = "service" 1934 JobTypeBatch = "batch" 1935 JobTypeSystem = "system" 1936 ) 1937 1938 const ( 1939 JobStatusPending = "pending" // Pending means the job is waiting on scheduling 1940 JobStatusRunning = "running" // Running means the job has non-terminal allocations 1941 JobStatusDead = "dead" // Dead means all evaluation's and allocations are terminal 1942 ) 1943 1944 const ( 1945 // JobMinPriority is the minimum allowed priority 1946 JobMinPriority = 1 1947 1948 // JobDefaultPriority is the default priority if not 1949 // not specified. 1950 JobDefaultPriority = 50 1951 1952 // JobMaxPriority is the maximum allowed priority 1953 JobMaxPriority = 100 1954 1955 // Ensure CoreJobPriority is higher than any user 1956 // specified job so that it gets priority. This is important 1957 // for the system to remain healthy. 1958 CoreJobPriority = JobMaxPriority * 2 1959 1960 // JobTrackedVersions is the number of historic job versions that are 1961 // kept. 1962 JobTrackedVersions = 6 1963 ) 1964 1965 // Job is the scope of a scheduling request to Nomad. It is the largest 1966 // scoped object, and is a named collection of task groups. Each task group 1967 // is further composed of tasks. A task group (TG) is the unit of scheduling 1968 // however. 1969 type Job struct { 1970 // Stop marks whether the user has stopped the job. A stopped job will 1971 // have all created allocations stopped and acts as a way to stop a job 1972 // without purging it from the system. This allows existing allocs to be 1973 // queried and the job to be inspected as it is being killed. 1974 Stop bool 1975 1976 // Region is the Nomad region that handles scheduling this job 1977 Region string 1978 1979 // Namespace is the namespace the job is submitted into. 1980 Namespace string 1981 1982 // ID is a unique identifier for the job per region. It can be 1983 // specified hierarchically like LineOfBiz/OrgName/Team/Project 1984 ID string 1985 1986 // ParentID is the unique identifier of the job that spawned this job. 1987 ParentID string 1988 1989 // Name is the logical name of the job used to refer to it. This is unique 1990 // per region, but not unique globally. 1991 Name string 1992 1993 // Type is used to control various behaviors about the job. Most jobs 1994 // are service jobs, meaning they are expected to be long lived. 1995 // Some jobs are batch oriented meaning they run and then terminate. 1996 // This can be extended in the future to support custom schedulers. 1997 Type string 1998 1999 // Priority is used to control scheduling importance and if this job 2000 // can preempt other jobs. 2001 Priority int 2002 2003 // AllAtOnce is used to control if incremental scheduling of task groups 2004 // is allowed or if we must do a gang scheduling of the entire job. This 2005 // can slow down larger jobs if resources are not available. 2006 AllAtOnce bool 2007 2008 // Datacenters contains all the datacenters this job is allowed to span 2009 Datacenters []string 2010 2011 // Constraints can be specified at a job level and apply to 2012 // all the task groups and tasks. 2013 Constraints []*Constraint 2014 2015 // TaskGroups are the collections of task groups that this job needs 2016 // to run. Each task group is an atomic unit of scheduling and placement. 2017 TaskGroups []*TaskGroup 2018 2019 // COMPAT: Remove in 0.7.0. Stagger is deprecated in 0.6.0. 2020 Update UpdateStrategy 2021 2022 // Periodic is used to define the interval the job is run at. 2023 Periodic *PeriodicConfig 2024 2025 // ParameterizedJob is used to specify the job as a parameterized job 2026 // for dispatching. 2027 ParameterizedJob *ParameterizedJobConfig 2028 2029 // Dispatched is used to identify if the Job has been dispatched from a 2030 // parameterized job. 2031 Dispatched bool 2032 2033 // Payload is the payload supplied when the job was dispatched. 2034 Payload []byte 2035 2036 // Meta is used to associate arbitrary metadata with this 2037 // job. This is opaque to Nomad. 2038 Meta map[string]string 2039 2040 // VaultToken is the Vault token that proves the submitter of the job has 2041 // access to the specified Vault policies. This field is only used to 2042 // transfer the token and is not stored after Job submission. 2043 VaultToken string 2044 2045 // Job status 2046 Status string 2047 2048 // StatusDescription is meant to provide more human useful information 2049 StatusDescription string 2050 2051 // Stable marks a job as stable. Stability is only defined on "service" and 2052 // "system" jobs. The stability of a job will be set automatically as part 2053 // of a deployment and can be manually set via APIs. 2054 Stable bool 2055 2056 // Version is a monotonically increasing version number that is incremented 2057 // on each job register. 2058 Version uint64 2059 2060 // SubmitTime is the time at which the job was submitted as a UnixNano in 2061 // UTC 2062 SubmitTime int64 2063 2064 // Raft Indexes 2065 CreateIndex uint64 2066 ModifyIndex uint64 2067 JobModifyIndex uint64 2068 } 2069 2070 // NamespacedID returns the namespaced id useful for logging 2071 func (j *Job) NamespacedID() *NamespacedID { 2072 return &NamespacedID{ 2073 ID: j.ID, 2074 Namespace: j.Namespace, 2075 } 2076 } 2077 2078 // Canonicalize is used to canonicalize fields in the Job. This should be called 2079 // when registering a Job. A set of warnings are returned if the job was changed 2080 // in anyway that the user should be made aware of. 2081 func (j *Job) Canonicalize() (warnings error) { 2082 if j == nil { 2083 return nil 2084 } 2085 2086 var mErr multierror.Error 2087 // Ensure that an empty and nil map are treated the same to avoid scheduling 2088 // problems since we use reflect DeepEquals. 2089 if len(j.Meta) == 0 { 2090 j.Meta = nil 2091 } 2092 2093 // Ensure the job is in a namespace. 2094 if j.Namespace == "" { 2095 j.Namespace = DefaultNamespace 2096 } 2097 2098 for _, tg := range j.TaskGroups { 2099 tg.Canonicalize(j) 2100 } 2101 2102 if j.ParameterizedJob != nil { 2103 j.ParameterizedJob.Canonicalize() 2104 } 2105 2106 if j.Periodic != nil { 2107 j.Periodic.Canonicalize() 2108 } 2109 2110 return mErr.ErrorOrNil() 2111 } 2112 2113 // Copy returns a deep copy of the Job. It is expected that callers use recover. 2114 // This job can panic if the deep copy failed as it uses reflection. 2115 func (j *Job) Copy() *Job { 2116 if j == nil { 2117 return nil 2118 } 2119 nj := new(Job) 2120 *nj = *j 2121 nj.Datacenters = helper.CopySliceString(nj.Datacenters) 2122 nj.Constraints = CopySliceConstraints(nj.Constraints) 2123 2124 if j.TaskGroups != nil { 2125 tgs := make([]*TaskGroup, len(nj.TaskGroups)) 2126 for i, tg := range nj.TaskGroups { 2127 tgs[i] = tg.Copy() 2128 } 2129 nj.TaskGroups = tgs 2130 } 2131 2132 nj.Periodic = nj.Periodic.Copy() 2133 nj.Meta = helper.CopyMapStringString(nj.Meta) 2134 nj.ParameterizedJob = nj.ParameterizedJob.Copy() 2135 return nj 2136 } 2137 2138 // Validate is used to sanity check a job input 2139 func (j *Job) Validate() error { 2140 var mErr multierror.Error 2141 2142 if j.Region == "" { 2143 mErr.Errors = append(mErr.Errors, errors.New("Missing job region")) 2144 } 2145 if j.ID == "" { 2146 mErr.Errors = append(mErr.Errors, errors.New("Missing job ID")) 2147 } else if strings.Contains(j.ID, " ") { 2148 mErr.Errors = append(mErr.Errors, errors.New("Job ID contains a space")) 2149 } 2150 if j.Name == "" { 2151 mErr.Errors = append(mErr.Errors, errors.New("Missing job name")) 2152 } 2153 if j.Namespace == "" { 2154 mErr.Errors = append(mErr.Errors, errors.New("Job must be in a namespace")) 2155 } 2156 switch j.Type { 2157 case JobTypeCore, JobTypeService, JobTypeBatch, JobTypeSystem: 2158 case "": 2159 mErr.Errors = append(mErr.Errors, errors.New("Missing job type")) 2160 default: 2161 mErr.Errors = append(mErr.Errors, fmt.Errorf("Invalid job type: %q", j.Type)) 2162 } 2163 if j.Priority < JobMinPriority || j.Priority > JobMaxPriority { 2164 mErr.Errors = append(mErr.Errors, fmt.Errorf("Job priority must be between [%d, %d]", JobMinPriority, JobMaxPriority)) 2165 } 2166 if len(j.Datacenters) == 0 { 2167 mErr.Errors = append(mErr.Errors, errors.New("Missing job datacenters")) 2168 } 2169 if len(j.TaskGroups) == 0 { 2170 mErr.Errors = append(mErr.Errors, errors.New("Missing job task groups")) 2171 } 2172 for idx, constr := range j.Constraints { 2173 if err := constr.Validate(); err != nil { 2174 outer := fmt.Errorf("Constraint %d validation failed: %s", idx+1, err) 2175 mErr.Errors = append(mErr.Errors, outer) 2176 } 2177 } 2178 2179 // Check for duplicate task groups 2180 taskGroups := make(map[string]int) 2181 for idx, tg := range j.TaskGroups { 2182 if tg.Name == "" { 2183 mErr.Errors = append(mErr.Errors, fmt.Errorf("Job task group %d missing name", idx+1)) 2184 } else if existing, ok := taskGroups[tg.Name]; ok { 2185 mErr.Errors = append(mErr.Errors, fmt.Errorf("Job task group %d redefines '%s' from group %d", idx+1, tg.Name, existing+1)) 2186 } else { 2187 taskGroups[tg.Name] = idx 2188 } 2189 2190 if j.Type == "system" && tg.Count > 1 { 2191 mErr.Errors = append(mErr.Errors, 2192 fmt.Errorf("Job task group %s has count %d. Count cannot exceed 1 with system scheduler", 2193 tg.Name, tg.Count)) 2194 } 2195 } 2196 2197 // Validate the task group 2198 for _, tg := range j.TaskGroups { 2199 if err := tg.Validate(j); err != nil { 2200 outer := fmt.Errorf("Task group %s validation failed: %v", tg.Name, err) 2201 mErr.Errors = append(mErr.Errors, outer) 2202 } 2203 } 2204 2205 // Validate periodic is only used with batch jobs. 2206 if j.IsPeriodic() && j.Periodic.Enabled { 2207 if j.Type != JobTypeBatch { 2208 mErr.Errors = append(mErr.Errors, 2209 fmt.Errorf("Periodic can only be used with %q scheduler", JobTypeBatch)) 2210 } 2211 2212 if err := j.Periodic.Validate(); err != nil { 2213 mErr.Errors = append(mErr.Errors, err) 2214 } 2215 } 2216 2217 if j.IsParameterized() { 2218 if j.Type != JobTypeBatch { 2219 mErr.Errors = append(mErr.Errors, 2220 fmt.Errorf("Parameterized job can only be used with %q scheduler", JobTypeBatch)) 2221 } 2222 2223 if err := j.ParameterizedJob.Validate(); err != nil { 2224 mErr.Errors = append(mErr.Errors, err) 2225 } 2226 } 2227 2228 return mErr.ErrorOrNil() 2229 } 2230 2231 // Warnings returns a list of warnings that may be from dubious settings or 2232 // deprecation warnings. 2233 func (j *Job) Warnings() error { 2234 var mErr multierror.Error 2235 2236 // Check the groups 2237 for _, tg := range j.TaskGroups { 2238 if err := tg.Warnings(j); err != nil { 2239 outer := fmt.Errorf("Group %q has warnings: %v", tg.Name, err) 2240 mErr.Errors = append(mErr.Errors, outer) 2241 } 2242 } 2243 2244 return mErr.ErrorOrNil() 2245 } 2246 2247 // LookupTaskGroup finds a task group by name 2248 func (j *Job) LookupTaskGroup(name string) *TaskGroup { 2249 for _, tg := range j.TaskGroups { 2250 if tg.Name == name { 2251 return tg 2252 } 2253 } 2254 return nil 2255 } 2256 2257 // CombinedTaskMeta takes a TaskGroup and Task name and returns the combined 2258 // meta data for the task. When joining Job, Group and Task Meta, the precedence 2259 // is by deepest scope (Task > Group > Job). 2260 func (j *Job) CombinedTaskMeta(groupName, taskName string) map[string]string { 2261 group := j.LookupTaskGroup(groupName) 2262 if group == nil { 2263 return nil 2264 } 2265 2266 task := group.LookupTask(taskName) 2267 if task == nil { 2268 return nil 2269 } 2270 2271 meta := helper.CopyMapStringString(task.Meta) 2272 if meta == nil { 2273 meta = make(map[string]string, len(group.Meta)+len(j.Meta)) 2274 } 2275 2276 // Add the group specific meta 2277 for k, v := range group.Meta { 2278 if _, ok := meta[k]; !ok { 2279 meta[k] = v 2280 } 2281 } 2282 2283 // Add the job specific meta 2284 for k, v := range j.Meta { 2285 if _, ok := meta[k]; !ok { 2286 meta[k] = v 2287 } 2288 } 2289 2290 return meta 2291 } 2292 2293 // Stopped returns if a job is stopped. 2294 func (j *Job) Stopped() bool { 2295 return j == nil || j.Stop 2296 } 2297 2298 // HasUpdateStrategy returns if any task group in the job has an update strategy 2299 func (j *Job) HasUpdateStrategy() bool { 2300 for _, tg := range j.TaskGroups { 2301 if tg.Update != nil { 2302 return true 2303 } 2304 } 2305 2306 return false 2307 } 2308 2309 // Stub is used to return a summary of the job 2310 func (j *Job) Stub(summary *JobSummary) *JobListStub { 2311 return &JobListStub{ 2312 ID: j.ID, 2313 ParentID: j.ParentID, 2314 Name: j.Name, 2315 Type: j.Type, 2316 Priority: j.Priority, 2317 Periodic: j.IsPeriodic(), 2318 ParameterizedJob: j.IsParameterized(), 2319 Stop: j.Stop, 2320 Status: j.Status, 2321 StatusDescription: j.StatusDescription, 2322 CreateIndex: j.CreateIndex, 2323 ModifyIndex: j.ModifyIndex, 2324 JobModifyIndex: j.JobModifyIndex, 2325 SubmitTime: j.SubmitTime, 2326 JobSummary: summary, 2327 } 2328 } 2329 2330 // IsPeriodic returns whether a job is periodic. 2331 func (j *Job) IsPeriodic() bool { 2332 return j.Periodic != nil 2333 } 2334 2335 // IsPeriodicActive returns whether the job is an active periodic job that will 2336 // create child jobs 2337 func (j *Job) IsPeriodicActive() bool { 2338 return j.IsPeriodic() && j.Periodic.Enabled && !j.Stopped() && !j.IsParameterized() 2339 } 2340 2341 // IsParameterized returns whether a job is parameterized job. 2342 func (j *Job) IsParameterized() bool { 2343 return j.ParameterizedJob != nil && !j.Dispatched 2344 } 2345 2346 // VaultPolicies returns the set of Vault policies per task group, per task 2347 func (j *Job) VaultPolicies() map[string]map[string]*Vault { 2348 policies := make(map[string]map[string]*Vault, len(j.TaskGroups)) 2349 2350 for _, tg := range j.TaskGroups { 2351 tgPolicies := make(map[string]*Vault, len(tg.Tasks)) 2352 2353 for _, task := range tg.Tasks { 2354 if task.Vault == nil { 2355 continue 2356 } 2357 2358 tgPolicies[task.Name] = task.Vault 2359 } 2360 2361 if len(tgPolicies) != 0 { 2362 policies[tg.Name] = tgPolicies 2363 } 2364 } 2365 2366 return policies 2367 } 2368 2369 // RequiredSignals returns a mapping of task groups to tasks to their required 2370 // set of signals 2371 func (j *Job) RequiredSignals() map[string]map[string][]string { 2372 signals := make(map[string]map[string][]string) 2373 2374 for _, tg := range j.TaskGroups { 2375 for _, task := range tg.Tasks { 2376 // Use this local one as a set 2377 taskSignals := make(map[string]struct{}) 2378 2379 // Check if the Vault change mode uses signals 2380 if task.Vault != nil && task.Vault.ChangeMode == VaultChangeModeSignal { 2381 taskSignals[task.Vault.ChangeSignal] = struct{}{} 2382 } 2383 2384 // If a user has specified a KillSignal, add it to required signals 2385 if task.KillSignal != "" { 2386 taskSignals[task.KillSignal] = struct{}{} 2387 } 2388 2389 // Check if any template change mode uses signals 2390 for _, t := range task.Templates { 2391 if t.ChangeMode != TemplateChangeModeSignal { 2392 continue 2393 } 2394 2395 taskSignals[t.ChangeSignal] = struct{}{} 2396 } 2397 2398 // Flatten and sort the signals 2399 l := len(taskSignals) 2400 if l == 0 { 2401 continue 2402 } 2403 2404 flat := make([]string, 0, l) 2405 for sig := range taskSignals { 2406 flat = append(flat, sig) 2407 } 2408 2409 sort.Strings(flat) 2410 tgSignals, ok := signals[tg.Name] 2411 if !ok { 2412 tgSignals = make(map[string][]string) 2413 signals[tg.Name] = tgSignals 2414 } 2415 tgSignals[task.Name] = flat 2416 } 2417 2418 } 2419 2420 return signals 2421 } 2422 2423 // SpecChanged determines if the functional specification has changed between 2424 // two job versions. 2425 func (j *Job) SpecChanged(new *Job) bool { 2426 if j == nil { 2427 return new != nil 2428 } 2429 2430 // Create a copy of the new job 2431 c := new.Copy() 2432 2433 // Update the new job so we can do a reflect 2434 c.Status = j.Status 2435 c.StatusDescription = j.StatusDescription 2436 c.Stable = j.Stable 2437 c.Version = j.Version 2438 c.CreateIndex = j.CreateIndex 2439 c.ModifyIndex = j.ModifyIndex 2440 c.JobModifyIndex = j.JobModifyIndex 2441 c.SubmitTime = j.SubmitTime 2442 2443 // Deep equals the jobs 2444 return !reflect.DeepEqual(j, c) 2445 } 2446 2447 func (j *Job) SetSubmitTime() { 2448 j.SubmitTime = time.Now().UTC().UnixNano() 2449 } 2450 2451 // JobListStub is used to return a subset of job information 2452 // for the job list 2453 type JobListStub struct { 2454 ID string 2455 ParentID string 2456 Name string 2457 Type string 2458 Priority int 2459 Periodic bool 2460 ParameterizedJob bool 2461 Stop bool 2462 Status string 2463 StatusDescription string 2464 JobSummary *JobSummary 2465 CreateIndex uint64 2466 ModifyIndex uint64 2467 JobModifyIndex uint64 2468 SubmitTime int64 2469 } 2470 2471 // JobSummary summarizes the state of the allocations of a job 2472 type JobSummary struct { 2473 // JobID is the ID of the job the summary is for 2474 JobID string 2475 2476 // Namespace is the namespace of the job and its summary 2477 Namespace string 2478 2479 // Summary contains the summary per task group for the Job 2480 Summary map[string]TaskGroupSummary 2481 2482 // Children contains a summary for the children of this job. 2483 Children *JobChildrenSummary 2484 2485 // Raft Indexes 2486 CreateIndex uint64 2487 ModifyIndex uint64 2488 } 2489 2490 // Copy returns a new copy of JobSummary 2491 func (js *JobSummary) Copy() *JobSummary { 2492 newJobSummary := new(JobSummary) 2493 *newJobSummary = *js 2494 newTGSummary := make(map[string]TaskGroupSummary, len(js.Summary)) 2495 for k, v := range js.Summary { 2496 newTGSummary[k] = v 2497 } 2498 newJobSummary.Summary = newTGSummary 2499 newJobSummary.Children = newJobSummary.Children.Copy() 2500 return newJobSummary 2501 } 2502 2503 // JobChildrenSummary contains the summary of children job statuses 2504 type JobChildrenSummary struct { 2505 Pending int64 2506 Running int64 2507 Dead int64 2508 } 2509 2510 // Copy returns a new copy of a JobChildrenSummary 2511 func (jc *JobChildrenSummary) Copy() *JobChildrenSummary { 2512 if jc == nil { 2513 return nil 2514 } 2515 2516 njc := new(JobChildrenSummary) 2517 *njc = *jc 2518 return njc 2519 } 2520 2521 // TaskGroup summarizes the state of all the allocations of a particular 2522 // TaskGroup 2523 type TaskGroupSummary struct { 2524 Queued int 2525 Complete int 2526 Failed int 2527 Running int 2528 Starting int 2529 Lost int 2530 } 2531 2532 const ( 2533 // Checks uses any registered health check state in combination with task 2534 // states to determine if a allocation is healthy. 2535 UpdateStrategyHealthCheck_Checks = "checks" 2536 2537 // TaskStates uses the task states of an allocation to determine if the 2538 // allocation is healthy. 2539 UpdateStrategyHealthCheck_TaskStates = "task_states" 2540 2541 // Manual allows the operator to manually signal to Nomad when an 2542 // allocations is healthy. This allows more advanced health checking that is 2543 // outside of the scope of Nomad. 2544 UpdateStrategyHealthCheck_Manual = "manual" 2545 ) 2546 2547 var ( 2548 // DefaultUpdateStrategy provides a baseline that can be used to upgrade 2549 // jobs with the old policy or for populating field defaults. 2550 DefaultUpdateStrategy = &UpdateStrategy{ 2551 Stagger: 30 * time.Second, 2552 MaxParallel: 1, 2553 HealthCheck: UpdateStrategyHealthCheck_Checks, 2554 MinHealthyTime: 10 * time.Second, 2555 HealthyDeadline: 5 * time.Minute, 2556 ProgressDeadline: 10 * time.Minute, 2557 AutoRevert: false, 2558 Canary: 0, 2559 } 2560 ) 2561 2562 // UpdateStrategy is used to modify how updates are done 2563 type UpdateStrategy struct { 2564 // Stagger is used to determine the rate at which allocations are migrated 2565 // due to down or draining nodes. 2566 Stagger time.Duration 2567 2568 // MaxParallel is how many updates can be done in parallel 2569 MaxParallel int 2570 2571 // HealthCheck specifies the mechanism in which allocations are marked 2572 // healthy or unhealthy as part of a deployment. 2573 HealthCheck string 2574 2575 // MinHealthyTime is the minimum time an allocation must be in the healthy 2576 // state before it is marked as healthy, unblocking more allocations to be 2577 // rolled. 2578 MinHealthyTime time.Duration 2579 2580 // HealthyDeadline is the time in which an allocation must be marked as 2581 // healthy before it is automatically transitioned to unhealthy. This time 2582 // period doesn't count against the MinHealthyTime. 2583 HealthyDeadline time.Duration 2584 2585 // ProgressDeadline is the time in which an allocation as part of the 2586 // deployment must transition to healthy. If no allocation becomes healthy 2587 // after the deadline, the deployment is marked as failed. If the deadline 2588 // is zero, the first failure causes the deployment to fail. 2589 ProgressDeadline time.Duration 2590 2591 // AutoRevert declares that if a deployment fails because of unhealthy 2592 // allocations, there should be an attempt to auto-revert the job to a 2593 // stable version. 2594 AutoRevert bool 2595 2596 // Canary is the number of canaries to deploy when a change to the task 2597 // group is detected. 2598 Canary int 2599 } 2600 2601 func (u *UpdateStrategy) Copy() *UpdateStrategy { 2602 if u == nil { 2603 return nil 2604 } 2605 2606 copy := new(UpdateStrategy) 2607 *copy = *u 2608 return copy 2609 } 2610 2611 func (u *UpdateStrategy) Validate() error { 2612 if u == nil { 2613 return nil 2614 } 2615 2616 var mErr multierror.Error 2617 switch u.HealthCheck { 2618 case UpdateStrategyHealthCheck_Checks, UpdateStrategyHealthCheck_TaskStates, UpdateStrategyHealthCheck_Manual: 2619 default: 2620 multierror.Append(&mErr, fmt.Errorf("Invalid health check given: %q", u.HealthCheck)) 2621 } 2622 2623 if u.MaxParallel < 1 { 2624 multierror.Append(&mErr, fmt.Errorf("Max parallel can not be less than one: %d < 1", u.MaxParallel)) 2625 } 2626 if u.Canary < 0 { 2627 multierror.Append(&mErr, fmt.Errorf("Canary count can not be less than zero: %d < 0", u.Canary)) 2628 } 2629 if u.MinHealthyTime < 0 { 2630 multierror.Append(&mErr, fmt.Errorf("Minimum healthy time may not be less than zero: %v", u.MinHealthyTime)) 2631 } 2632 if u.HealthyDeadline <= 0 { 2633 multierror.Append(&mErr, fmt.Errorf("Healthy deadline must be greater than zero: %v", u.HealthyDeadline)) 2634 } 2635 if u.ProgressDeadline < 0 { 2636 multierror.Append(&mErr, fmt.Errorf("Progress deadline must be zero or greater: %v", u.ProgressDeadline)) 2637 } 2638 if u.MinHealthyTime >= u.HealthyDeadline { 2639 multierror.Append(&mErr, fmt.Errorf("Minimum healthy time must be less than healthy deadline: %v > %v", u.MinHealthyTime, u.HealthyDeadline)) 2640 } 2641 if u.ProgressDeadline != 0 && u.HealthyDeadline >= u.ProgressDeadline { 2642 multierror.Append(&mErr, fmt.Errorf("Healthy deadline must be less than progress deadline: %v > %v", u.HealthyDeadline, u.ProgressDeadline)) 2643 } 2644 if u.Stagger <= 0 { 2645 multierror.Append(&mErr, fmt.Errorf("Stagger must be greater than zero: %v", u.Stagger)) 2646 } 2647 2648 return mErr.ErrorOrNil() 2649 } 2650 2651 // TODO(alexdadgar): Remove once no longer used by the scheduler. 2652 // Rolling returns if a rolling strategy should be used 2653 func (u *UpdateStrategy) Rolling() bool { 2654 return u.Stagger > 0 && u.MaxParallel > 0 2655 } 2656 2657 const ( 2658 // PeriodicSpecCron is used for a cron spec. 2659 PeriodicSpecCron = "cron" 2660 2661 // PeriodicSpecTest is only used by unit tests. It is a sorted, comma 2662 // separated list of unix timestamps at which to launch. 2663 PeriodicSpecTest = "_internal_test" 2664 ) 2665 2666 // Periodic defines the interval a job should be run at. 2667 type PeriodicConfig struct { 2668 // Enabled determines if the job should be run periodically. 2669 Enabled bool 2670 2671 // Spec specifies the interval the job should be run as. It is parsed based 2672 // on the SpecType. 2673 Spec string 2674 2675 // SpecType defines the format of the spec. 2676 SpecType string 2677 2678 // ProhibitOverlap enforces that spawned jobs do not run in parallel. 2679 ProhibitOverlap bool 2680 2681 // TimeZone is the user specified string that determines the time zone to 2682 // launch against. The time zones must be specified from IANA Time Zone 2683 // database, such as "America/New_York". 2684 // Reference: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones 2685 // Reference: https://www.iana.org/time-zones 2686 TimeZone string 2687 2688 // location is the time zone to evaluate the launch time against 2689 location *time.Location 2690 } 2691 2692 func (p *PeriodicConfig) Copy() *PeriodicConfig { 2693 if p == nil { 2694 return nil 2695 } 2696 np := new(PeriodicConfig) 2697 *np = *p 2698 return np 2699 } 2700 2701 func (p *PeriodicConfig) Validate() error { 2702 if !p.Enabled { 2703 return nil 2704 } 2705 2706 var mErr multierror.Error 2707 if p.Spec == "" { 2708 multierror.Append(&mErr, fmt.Errorf("Must specify a spec")) 2709 } 2710 2711 // Check if we got a valid time zone 2712 if p.TimeZone != "" { 2713 if _, err := time.LoadLocation(p.TimeZone); err != nil { 2714 multierror.Append(&mErr, fmt.Errorf("Invalid time zone %q: %v", p.TimeZone, err)) 2715 } 2716 } 2717 2718 switch p.SpecType { 2719 case PeriodicSpecCron: 2720 // Validate the cron spec 2721 if _, err := cronexpr.Parse(p.Spec); err != nil { 2722 multierror.Append(&mErr, fmt.Errorf("Invalid cron spec %q: %v", p.Spec, err)) 2723 } 2724 case PeriodicSpecTest: 2725 // No-op 2726 default: 2727 multierror.Append(&mErr, fmt.Errorf("Unknown periodic specification type %q", p.SpecType)) 2728 } 2729 2730 return mErr.ErrorOrNil() 2731 } 2732 2733 func (p *PeriodicConfig) Canonicalize() { 2734 // Load the location 2735 l, err := time.LoadLocation(p.TimeZone) 2736 if err != nil { 2737 p.location = time.UTC 2738 } 2739 2740 p.location = l 2741 } 2742 2743 // CronParseNext is a helper that parses the next time for the given expression 2744 // but captures any panic that may occur in the underlying library. 2745 func CronParseNext(e *cronexpr.Expression, fromTime time.Time, spec string) (t time.Time, err error) { 2746 defer func() { 2747 if recover() != nil { 2748 t = time.Time{} 2749 err = fmt.Errorf("failed parsing cron expression: %q", spec) 2750 } 2751 }() 2752 2753 return e.Next(fromTime), nil 2754 } 2755 2756 // Next returns the closest time instant matching the spec that is after the 2757 // passed time. If no matching instance exists, the zero value of time.Time is 2758 // returned. The `time.Location` of the returned value matches that of the 2759 // passed time. 2760 func (p *PeriodicConfig) Next(fromTime time.Time) (time.Time, error) { 2761 switch p.SpecType { 2762 case PeriodicSpecCron: 2763 if e, err := cronexpr.Parse(p.Spec); err == nil { 2764 return CronParseNext(e, fromTime, p.Spec) 2765 } 2766 case PeriodicSpecTest: 2767 split := strings.Split(p.Spec, ",") 2768 if len(split) == 1 && split[0] == "" { 2769 return time.Time{}, nil 2770 } 2771 2772 // Parse the times 2773 times := make([]time.Time, len(split)) 2774 for i, s := range split { 2775 unix, err := strconv.Atoi(s) 2776 if err != nil { 2777 return time.Time{}, nil 2778 } 2779 2780 times[i] = time.Unix(int64(unix), 0) 2781 } 2782 2783 // Find the next match 2784 for _, next := range times { 2785 if fromTime.Before(next) { 2786 return next, nil 2787 } 2788 } 2789 } 2790 2791 return time.Time{}, nil 2792 } 2793 2794 // GetLocation returns the location to use for determining the time zone to run 2795 // the periodic job against. 2796 func (p *PeriodicConfig) GetLocation() *time.Location { 2797 // Jobs pre 0.5.5 will not have this 2798 if p.location != nil { 2799 return p.location 2800 } 2801 2802 return time.UTC 2803 } 2804 2805 const ( 2806 // PeriodicLaunchSuffix is the string appended to the periodic jobs ID 2807 // when launching derived instances of it. 2808 PeriodicLaunchSuffix = "/periodic-" 2809 ) 2810 2811 // PeriodicLaunch tracks the last launch time of a periodic job. 2812 type PeriodicLaunch struct { 2813 ID string // ID of the periodic job. 2814 Namespace string // Namespace of the periodic job 2815 Launch time.Time // The last launch time. 2816 2817 // Raft Indexes 2818 CreateIndex uint64 2819 ModifyIndex uint64 2820 } 2821 2822 const ( 2823 DispatchPayloadForbidden = "forbidden" 2824 DispatchPayloadOptional = "optional" 2825 DispatchPayloadRequired = "required" 2826 2827 // DispatchLaunchSuffix is the string appended to the parameterized job's ID 2828 // when dispatching instances of it. 2829 DispatchLaunchSuffix = "/dispatch-" 2830 ) 2831 2832 // ParameterizedJobConfig is used to configure the parameterized job 2833 type ParameterizedJobConfig struct { 2834 // Payload configure the payload requirements 2835 Payload string 2836 2837 // MetaRequired is metadata keys that must be specified by the dispatcher 2838 MetaRequired []string 2839 2840 // MetaOptional is metadata keys that may be specified by the dispatcher 2841 MetaOptional []string 2842 } 2843 2844 func (d *ParameterizedJobConfig) Validate() error { 2845 var mErr multierror.Error 2846 switch d.Payload { 2847 case DispatchPayloadOptional, DispatchPayloadRequired, DispatchPayloadForbidden: 2848 default: 2849 multierror.Append(&mErr, fmt.Errorf("Unknown payload requirement: %q", d.Payload)) 2850 } 2851 2852 // Check that the meta configurations are disjoint sets 2853 disjoint, offending := helper.SliceSetDisjoint(d.MetaRequired, d.MetaOptional) 2854 if !disjoint { 2855 multierror.Append(&mErr, fmt.Errorf("Required and optional meta keys should be disjoint. Following keys exist in both: %v", offending)) 2856 } 2857 2858 return mErr.ErrorOrNil() 2859 } 2860 2861 func (d *ParameterizedJobConfig) Canonicalize() { 2862 if d.Payload == "" { 2863 d.Payload = DispatchPayloadOptional 2864 } 2865 } 2866 2867 func (d *ParameterizedJobConfig) Copy() *ParameterizedJobConfig { 2868 if d == nil { 2869 return nil 2870 } 2871 nd := new(ParameterizedJobConfig) 2872 *nd = *d 2873 nd.MetaOptional = helper.CopySliceString(nd.MetaOptional) 2874 nd.MetaRequired = helper.CopySliceString(nd.MetaRequired) 2875 return nd 2876 } 2877 2878 // DispatchedID returns an ID appropriate for a job dispatched against a 2879 // particular parameterized job 2880 func DispatchedID(templateID string, t time.Time) string { 2881 u := uuid.Generate()[:8] 2882 return fmt.Sprintf("%s%s%d-%s", templateID, DispatchLaunchSuffix, t.Unix(), u) 2883 } 2884 2885 // DispatchPayloadConfig configures how a task gets its input from a job dispatch 2886 type DispatchPayloadConfig struct { 2887 // File specifies a relative path to where the input data should be written 2888 File string 2889 } 2890 2891 func (d *DispatchPayloadConfig) Copy() *DispatchPayloadConfig { 2892 if d == nil { 2893 return nil 2894 } 2895 nd := new(DispatchPayloadConfig) 2896 *nd = *d 2897 return nd 2898 } 2899 2900 func (d *DispatchPayloadConfig) Validate() error { 2901 // Verify the destination doesn't escape 2902 escaped, err := PathEscapesAllocDir("task/local/", d.File) 2903 if err != nil { 2904 return fmt.Errorf("invalid destination path: %v", err) 2905 } else if escaped { 2906 return fmt.Errorf("destination escapes allocation directory") 2907 } 2908 2909 return nil 2910 } 2911 2912 var ( 2913 DefaultServiceJobRestartPolicy = RestartPolicy{ 2914 Delay: 15 * time.Second, 2915 Attempts: 2, 2916 Interval: 30 * time.Minute, 2917 Mode: RestartPolicyModeFail, 2918 } 2919 DefaultBatchJobRestartPolicy = RestartPolicy{ 2920 Delay: 15 * time.Second, 2921 Attempts: 3, 2922 Interval: 24 * time.Hour, 2923 Mode: RestartPolicyModeFail, 2924 } 2925 ) 2926 2927 var ( 2928 DefaultServiceJobReschedulePolicy = ReschedulePolicy{ 2929 Delay: 30 * time.Second, 2930 DelayFunction: "exponential", 2931 MaxDelay: 1 * time.Hour, 2932 Unlimited: true, 2933 } 2934 DefaultBatchJobReschedulePolicy = ReschedulePolicy{ 2935 Attempts: 1, 2936 Interval: 24 * time.Hour, 2937 Delay: 5 * time.Second, 2938 DelayFunction: "constant", 2939 } 2940 ) 2941 2942 const ( 2943 // RestartPolicyModeDelay causes an artificial delay till the next interval is 2944 // reached when the specified attempts have been reached in the interval. 2945 RestartPolicyModeDelay = "delay" 2946 2947 // RestartPolicyModeFail causes a job to fail if the specified number of 2948 // attempts are reached within an interval. 2949 RestartPolicyModeFail = "fail" 2950 2951 // RestartPolicyMinInterval is the minimum interval that is accepted for a 2952 // restart policy. 2953 RestartPolicyMinInterval = 5 * time.Second 2954 2955 // ReasonWithinPolicy describes restart events that are within policy 2956 ReasonWithinPolicy = "Restart within policy" 2957 ) 2958 2959 // RestartPolicy configures how Tasks are restarted when they crash or fail. 2960 type RestartPolicy struct { 2961 // Attempts is the number of restart that will occur in an interval. 2962 Attempts int 2963 2964 // Interval is a duration in which we can limit the number of restarts 2965 // within. 2966 Interval time.Duration 2967 2968 // Delay is the time between a failure and a restart. 2969 Delay time.Duration 2970 2971 // Mode controls what happens when the task restarts more than attempt times 2972 // in an interval. 2973 Mode string 2974 } 2975 2976 func (r *RestartPolicy) Copy() *RestartPolicy { 2977 if r == nil { 2978 return nil 2979 } 2980 nrp := new(RestartPolicy) 2981 *nrp = *r 2982 return nrp 2983 } 2984 2985 func (r *RestartPolicy) Validate() error { 2986 var mErr multierror.Error 2987 switch r.Mode { 2988 case RestartPolicyModeDelay, RestartPolicyModeFail: 2989 default: 2990 multierror.Append(&mErr, fmt.Errorf("Unsupported restart mode: %q", r.Mode)) 2991 } 2992 2993 // Check for ambiguous/confusing settings 2994 if r.Attempts == 0 && r.Mode != RestartPolicyModeFail { 2995 multierror.Append(&mErr, fmt.Errorf("Restart policy %q with %d attempts is ambiguous", r.Mode, r.Attempts)) 2996 } 2997 2998 if r.Interval.Nanoseconds() < RestartPolicyMinInterval.Nanoseconds() { 2999 multierror.Append(&mErr, fmt.Errorf("Interval can not be less than %v (got %v)", RestartPolicyMinInterval, r.Interval)) 3000 } 3001 if time.Duration(r.Attempts)*r.Delay > r.Interval { 3002 multierror.Append(&mErr, 3003 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)) 3004 } 3005 return mErr.ErrorOrNil() 3006 } 3007 3008 func NewRestartPolicy(jobType string) *RestartPolicy { 3009 switch jobType { 3010 case JobTypeService, JobTypeSystem: 3011 rp := DefaultServiceJobRestartPolicy 3012 return &rp 3013 case JobTypeBatch: 3014 rp := DefaultBatchJobRestartPolicy 3015 return &rp 3016 } 3017 return nil 3018 } 3019 3020 const ReschedulePolicyMinInterval = 15 * time.Second 3021 const ReschedulePolicyMinDelay = 5 * time.Second 3022 3023 var RescheduleDelayFunctions = [...]string{"constant", "exponential", "fibonacci"} 3024 3025 // ReschedulePolicy configures how Tasks are rescheduled when they crash or fail. 3026 type ReschedulePolicy struct { 3027 // Attempts limits the number of rescheduling attempts that can occur in an interval. 3028 Attempts int 3029 3030 // Interval is a duration in which we can limit the number of reschedule attempts. 3031 Interval time.Duration 3032 3033 // Delay is a minimum duration to wait between reschedule attempts. 3034 // The delay function determines how much subsequent reschedule attempts are delayed by. 3035 Delay time.Duration 3036 3037 // DelayFunction determines how the delay progressively changes on subsequent reschedule 3038 // attempts. Valid values are "exponential", "constant", and "fibonacci". 3039 DelayFunction string 3040 3041 // MaxDelay is an upper bound on the delay. 3042 MaxDelay time.Duration 3043 3044 // Unlimited allows infinite rescheduling attempts. Only allowed when delay is set 3045 // between reschedule attempts. 3046 Unlimited bool 3047 } 3048 3049 func (r *ReschedulePolicy) Copy() *ReschedulePolicy { 3050 if r == nil { 3051 return nil 3052 } 3053 nrp := new(ReschedulePolicy) 3054 *nrp = *r 3055 return nrp 3056 } 3057 3058 func (r *ReschedulePolicy) Enabled() bool { 3059 enabled := r != nil && (r.Attempts > 0 || r.Unlimited) 3060 return enabled 3061 } 3062 3063 // Validate uses different criteria to validate the reschedule policy 3064 // Delay must be a minimum of 5 seconds 3065 // Delay Ceiling is ignored if Delay Function is "constant" 3066 // Number of possible attempts is validated, given the interval, delay and delay function 3067 func (r *ReschedulePolicy) Validate() error { 3068 if !r.Enabled() { 3069 return nil 3070 } 3071 var mErr multierror.Error 3072 // Check for ambiguous/confusing settings 3073 if r.Attempts > 0 { 3074 if r.Interval <= 0 { 3075 multierror.Append(&mErr, fmt.Errorf("Interval must be a non zero value if Attempts > 0")) 3076 } 3077 if r.Unlimited { 3078 multierror.Append(&mErr, fmt.Errorf("Reschedule Policy with Attempts = %v, Interval = %v, "+ 3079 "and Unlimited = %v is ambiguous", r.Attempts, r.Interval, r.Unlimited)) 3080 multierror.Append(&mErr, errors.New("If Attempts >0, Unlimited cannot also be set to true")) 3081 } 3082 } 3083 3084 delayPreCheck := true 3085 // Delay should be bigger than the default 3086 if r.Delay.Nanoseconds() < ReschedulePolicyMinDelay.Nanoseconds() { 3087 multierror.Append(&mErr, fmt.Errorf("Delay cannot be less than %v (got %v)", ReschedulePolicyMinDelay, r.Delay)) 3088 delayPreCheck = false 3089 } 3090 3091 // Must use a valid delay function 3092 if !isValidDelayFunction(r.DelayFunction) { 3093 multierror.Append(&mErr, fmt.Errorf("Invalid delay function %q, must be one of %q", r.DelayFunction, RescheduleDelayFunctions)) 3094 delayPreCheck = false 3095 } 3096 3097 // Validate MaxDelay if not using linear delay progression 3098 if r.DelayFunction != "constant" { 3099 if r.MaxDelay.Nanoseconds() < ReschedulePolicyMinDelay.Nanoseconds() { 3100 multierror.Append(&mErr, fmt.Errorf("Max Delay cannot be less than %v (got %v)", ReschedulePolicyMinDelay, r.Delay)) 3101 delayPreCheck = false 3102 } 3103 if r.MaxDelay < r.Delay { 3104 multierror.Append(&mErr, fmt.Errorf("Max Delay cannot be less than Delay %v (got %v)", r.Delay, r.MaxDelay)) 3105 delayPreCheck = false 3106 } 3107 3108 } 3109 3110 // Validate Interval and other delay parameters if attempts are limited 3111 if !r.Unlimited { 3112 if r.Interval.Nanoseconds() < ReschedulePolicyMinInterval.Nanoseconds() { 3113 multierror.Append(&mErr, fmt.Errorf("Interval cannot be less than %v (got %v)", ReschedulePolicyMinInterval, r.Interval)) 3114 } 3115 if !delayPreCheck { 3116 // We can't cross validate the rest of the delay params if delayPreCheck fails, so return early 3117 return mErr.ErrorOrNil() 3118 } 3119 crossValidationErr := r.validateDelayParams() 3120 if crossValidationErr != nil { 3121 multierror.Append(&mErr, crossValidationErr) 3122 } 3123 } 3124 return mErr.ErrorOrNil() 3125 } 3126 3127 func isValidDelayFunction(delayFunc string) bool { 3128 for _, value := range RescheduleDelayFunctions { 3129 if value == delayFunc { 3130 return true 3131 } 3132 } 3133 return false 3134 } 3135 3136 func (r *ReschedulePolicy) validateDelayParams() error { 3137 ok, possibleAttempts, recommendedInterval := r.viableAttempts() 3138 if ok { 3139 return nil 3140 } 3141 var mErr multierror.Error 3142 if r.DelayFunction == "constant" { 3143 multierror.Append(&mErr, fmt.Errorf("Nomad can only make %v attempts in %v with initial delay %v and "+ 3144 "delay function %q", possibleAttempts, r.Interval, r.Delay, r.DelayFunction)) 3145 } else { 3146 multierror.Append(&mErr, fmt.Errorf("Nomad can only make %v attempts in %v with initial delay %v, "+ 3147 "delay function %q, and delay ceiling %v", possibleAttempts, r.Interval, r.Delay, r.DelayFunction, r.MaxDelay)) 3148 } 3149 multierror.Append(&mErr, fmt.Errorf("Set the interval to at least %v to accommodate %v attempts", recommendedInterval.Round(time.Second), r.Attempts)) 3150 return mErr.ErrorOrNil() 3151 } 3152 3153 func (r *ReschedulePolicy) viableAttempts() (bool, int, time.Duration) { 3154 var possibleAttempts int 3155 var recommendedInterval time.Duration 3156 valid := true 3157 switch r.DelayFunction { 3158 case "constant": 3159 recommendedInterval = time.Duration(r.Attempts) * r.Delay 3160 if r.Interval < recommendedInterval { 3161 possibleAttempts = int(r.Interval / r.Delay) 3162 valid = false 3163 } 3164 case "exponential": 3165 for i := 0; i < r.Attempts; i++ { 3166 nextDelay := time.Duration(math.Pow(2, float64(i))) * r.Delay 3167 if nextDelay > r.MaxDelay { 3168 nextDelay = r.MaxDelay 3169 recommendedInterval += nextDelay 3170 } else { 3171 recommendedInterval = nextDelay 3172 } 3173 if recommendedInterval < r.Interval { 3174 possibleAttempts++ 3175 } 3176 } 3177 if possibleAttempts < r.Attempts { 3178 valid = false 3179 } 3180 case "fibonacci": 3181 var slots []time.Duration 3182 slots = append(slots, r.Delay) 3183 slots = append(slots, r.Delay) 3184 reachedCeiling := false 3185 for i := 2; i < r.Attempts; i++ { 3186 var nextDelay time.Duration 3187 if reachedCeiling { 3188 //switch to linear 3189 nextDelay = slots[i-1] + r.MaxDelay 3190 } else { 3191 nextDelay = slots[i-1] + slots[i-2] 3192 if nextDelay > r.MaxDelay { 3193 nextDelay = r.MaxDelay 3194 reachedCeiling = true 3195 } 3196 } 3197 slots = append(slots, nextDelay) 3198 } 3199 recommendedInterval = slots[len(slots)-1] 3200 if r.Interval < recommendedInterval { 3201 valid = false 3202 // calculate possible attempts 3203 for i := 0; i < len(slots); i++ { 3204 if slots[i] > r.Interval { 3205 possibleAttempts = i 3206 break 3207 } 3208 } 3209 } 3210 default: 3211 return false, 0, 0 3212 } 3213 if possibleAttempts < 0 { // can happen if delay is bigger than interval 3214 possibleAttempts = 0 3215 } 3216 return valid, possibleAttempts, recommendedInterval 3217 } 3218 3219 func NewReschedulePolicy(jobType string) *ReschedulePolicy { 3220 switch jobType { 3221 case JobTypeService: 3222 rp := DefaultServiceJobReschedulePolicy 3223 return &rp 3224 case JobTypeBatch: 3225 rp := DefaultBatchJobReschedulePolicy 3226 return &rp 3227 } 3228 return nil 3229 } 3230 3231 const ( 3232 MigrateStrategyHealthChecks = "checks" 3233 MigrateStrategyHealthStates = "task_states" 3234 ) 3235 3236 type MigrateStrategy struct { 3237 MaxParallel int 3238 HealthCheck string 3239 MinHealthyTime time.Duration 3240 HealthyDeadline time.Duration 3241 } 3242 3243 // DefaultMigrateStrategy is used for backwards compat with pre-0.8 Allocations 3244 // that lack an update strategy. 3245 // 3246 // This function should match its counterpart in api/tasks.go 3247 func DefaultMigrateStrategy() *MigrateStrategy { 3248 return &MigrateStrategy{ 3249 MaxParallel: 1, 3250 HealthCheck: MigrateStrategyHealthChecks, 3251 MinHealthyTime: 10 * time.Second, 3252 HealthyDeadline: 5 * time.Minute, 3253 } 3254 } 3255 3256 func (m *MigrateStrategy) Validate() error { 3257 var mErr multierror.Error 3258 3259 if m.MaxParallel < 0 { 3260 multierror.Append(&mErr, fmt.Errorf("MaxParallel must be >= 0 but found %d", m.MaxParallel)) 3261 } 3262 3263 switch m.HealthCheck { 3264 case MigrateStrategyHealthChecks, MigrateStrategyHealthStates: 3265 // ok 3266 case "": 3267 if m.MaxParallel > 0 { 3268 multierror.Append(&mErr, fmt.Errorf("Missing HealthCheck")) 3269 } 3270 default: 3271 multierror.Append(&mErr, fmt.Errorf("Invalid HealthCheck: %q", m.HealthCheck)) 3272 } 3273 3274 if m.MinHealthyTime < 0 { 3275 multierror.Append(&mErr, fmt.Errorf("MinHealthyTime is %s and must be >= 0", m.MinHealthyTime)) 3276 } 3277 3278 if m.HealthyDeadline < 0 { 3279 multierror.Append(&mErr, fmt.Errorf("HealthyDeadline is %s and must be >= 0", m.HealthyDeadline)) 3280 } 3281 3282 if m.MinHealthyTime > m.HealthyDeadline { 3283 multierror.Append(&mErr, fmt.Errorf("MinHealthyTime must be less than HealthyDeadline")) 3284 } 3285 3286 return mErr.ErrorOrNil() 3287 } 3288 3289 // TaskGroup is an atomic unit of placement. Each task group belongs to 3290 // a job and may contain any number of tasks. A task group support running 3291 // in many replicas using the same configuration.. 3292 type TaskGroup struct { 3293 // Name of the task group 3294 Name string 3295 3296 // Count is the number of replicas of this task group that should 3297 // be scheduled. 3298 Count int 3299 3300 // Update is used to control the update strategy for this task group 3301 Update *UpdateStrategy 3302 3303 // Migrate is used to control the migration strategy for this task group 3304 Migrate *MigrateStrategy 3305 3306 // Constraints can be specified at a task group level and apply to 3307 // all the tasks contained. 3308 Constraints []*Constraint 3309 3310 //RestartPolicy of a TaskGroup 3311 RestartPolicy *RestartPolicy 3312 3313 // Tasks are the collection of tasks that this task group needs to run 3314 Tasks []*Task 3315 3316 // EphemeralDisk is the disk resources that the task group requests 3317 EphemeralDisk *EphemeralDisk 3318 3319 // Meta is used to associate arbitrary metadata with this 3320 // task group. This is opaque to Nomad. 3321 Meta map[string]string 3322 3323 // ReschedulePolicy is used to configure how the scheduler should 3324 // retry failed allocations. 3325 ReschedulePolicy *ReschedulePolicy 3326 } 3327 3328 func (tg *TaskGroup) Copy() *TaskGroup { 3329 if tg == nil { 3330 return nil 3331 } 3332 ntg := new(TaskGroup) 3333 *ntg = *tg 3334 ntg.Update = ntg.Update.Copy() 3335 ntg.Constraints = CopySliceConstraints(ntg.Constraints) 3336 ntg.RestartPolicy = ntg.RestartPolicy.Copy() 3337 ntg.ReschedulePolicy = ntg.ReschedulePolicy.Copy() 3338 3339 if tg.Tasks != nil { 3340 tasks := make([]*Task, len(ntg.Tasks)) 3341 for i, t := range ntg.Tasks { 3342 tasks[i] = t.Copy() 3343 } 3344 ntg.Tasks = tasks 3345 } 3346 3347 ntg.Meta = helper.CopyMapStringString(ntg.Meta) 3348 3349 if tg.EphemeralDisk != nil { 3350 ntg.EphemeralDisk = tg.EphemeralDisk.Copy() 3351 } 3352 return ntg 3353 } 3354 3355 // Canonicalize is used to canonicalize fields in the TaskGroup. 3356 func (tg *TaskGroup) Canonicalize(job *Job) { 3357 // Ensure that an empty and nil map are treated the same to avoid scheduling 3358 // problems since we use reflect DeepEquals. 3359 if len(tg.Meta) == 0 { 3360 tg.Meta = nil 3361 } 3362 3363 // Set the default restart policy. 3364 if tg.RestartPolicy == nil { 3365 tg.RestartPolicy = NewRestartPolicy(job.Type) 3366 } 3367 3368 if tg.ReschedulePolicy == nil { 3369 tg.ReschedulePolicy = NewReschedulePolicy(job.Type) 3370 } 3371 3372 // Canonicalize Migrate for service jobs 3373 if job.Type == JobTypeService && tg.Migrate == nil { 3374 tg.Migrate = DefaultMigrateStrategy() 3375 } 3376 3377 // Set a default ephemeral disk object if the user has not requested for one 3378 if tg.EphemeralDisk == nil { 3379 tg.EphemeralDisk = DefaultEphemeralDisk() 3380 } 3381 3382 for _, task := range tg.Tasks { 3383 task.Canonicalize(job, tg) 3384 } 3385 3386 // Add up the disk resources to EphemeralDisk. This is done so that users 3387 // are not required to move their disk attribute from resources to 3388 // EphemeralDisk section of the job spec in Nomad 0.5 3389 // COMPAT 0.4.1 -> 0.5 3390 // Remove in 0.6 3391 var diskMB int 3392 for _, task := range tg.Tasks { 3393 diskMB += task.Resources.DiskMB 3394 } 3395 if diskMB > 0 { 3396 tg.EphemeralDisk.SizeMB = diskMB 3397 } 3398 } 3399 3400 // Validate is used to sanity check a task group 3401 func (tg *TaskGroup) Validate(j *Job) error { 3402 var mErr multierror.Error 3403 if tg.Name == "" { 3404 mErr.Errors = append(mErr.Errors, errors.New("Missing task group name")) 3405 } 3406 if tg.Count < 0 { 3407 mErr.Errors = append(mErr.Errors, errors.New("Task group count can't be negative")) 3408 } 3409 if len(tg.Tasks) == 0 { 3410 mErr.Errors = append(mErr.Errors, errors.New("Missing tasks for task group")) 3411 } 3412 for idx, constr := range tg.Constraints { 3413 if err := constr.Validate(); err != nil { 3414 outer := fmt.Errorf("Constraint %d validation failed: %s", idx+1, err) 3415 mErr.Errors = append(mErr.Errors, outer) 3416 } 3417 } 3418 3419 if tg.RestartPolicy != nil { 3420 if err := tg.RestartPolicy.Validate(); err != nil { 3421 mErr.Errors = append(mErr.Errors, err) 3422 } 3423 } else { 3424 mErr.Errors = append(mErr.Errors, fmt.Errorf("Task Group %v should have a restart policy", tg.Name)) 3425 } 3426 3427 if j.Type == JobTypeSystem { 3428 if tg.ReschedulePolicy != nil { 3429 mErr.Errors = append(mErr.Errors, fmt.Errorf("System jobs should not have a reschedule policy")) 3430 } 3431 } else { 3432 if tg.ReschedulePolicy != nil { 3433 if err := tg.ReschedulePolicy.Validate(); err != nil { 3434 mErr.Errors = append(mErr.Errors, err) 3435 } 3436 } else { 3437 mErr.Errors = append(mErr.Errors, fmt.Errorf("Task Group %v should have a reschedule policy", tg.Name)) 3438 } 3439 } 3440 3441 if tg.EphemeralDisk != nil { 3442 if err := tg.EphemeralDisk.Validate(); err != nil { 3443 mErr.Errors = append(mErr.Errors, err) 3444 } 3445 } else { 3446 mErr.Errors = append(mErr.Errors, fmt.Errorf("Task Group %v should have an ephemeral disk object", tg.Name)) 3447 } 3448 3449 // Validate the update strategy 3450 if u := tg.Update; u != nil { 3451 switch j.Type { 3452 case JobTypeService, JobTypeSystem: 3453 default: 3454 mErr.Errors = append(mErr.Errors, fmt.Errorf("Job type %q does not allow update block", j.Type)) 3455 } 3456 if err := u.Validate(); err != nil { 3457 mErr.Errors = append(mErr.Errors, err) 3458 } 3459 } 3460 3461 // Validate the migration strategy 3462 switch j.Type { 3463 case JobTypeService: 3464 if tg.Migrate != nil { 3465 if err := tg.Migrate.Validate(); err != nil { 3466 mErr.Errors = append(mErr.Errors, err) 3467 } 3468 } 3469 default: 3470 if tg.Migrate != nil { 3471 mErr.Errors = append(mErr.Errors, fmt.Errorf("Job type %q does not allow migrate block", j.Type)) 3472 } 3473 } 3474 3475 // Check for duplicate tasks, that there is only leader task if any, 3476 // and no duplicated static ports 3477 tasks := make(map[string]int) 3478 staticPorts := make(map[int]string) 3479 leaderTasks := 0 3480 for idx, task := range tg.Tasks { 3481 if task.Name == "" { 3482 mErr.Errors = append(mErr.Errors, fmt.Errorf("Task %d missing name", idx+1)) 3483 } else if existing, ok := tasks[task.Name]; ok { 3484 mErr.Errors = append(mErr.Errors, fmt.Errorf("Task %d redefines '%s' from task %d", idx+1, task.Name, existing+1)) 3485 } else { 3486 tasks[task.Name] = idx 3487 } 3488 3489 if task.Leader { 3490 leaderTasks++ 3491 } 3492 3493 if task.Resources == nil { 3494 continue 3495 } 3496 3497 for _, net := range task.Resources.Networks { 3498 for _, port := range net.ReservedPorts { 3499 if other, ok := staticPorts[port.Value]; ok { 3500 err := fmt.Errorf("Static port %d already reserved by %s", port.Value, other) 3501 mErr.Errors = append(mErr.Errors, err) 3502 } else { 3503 staticPorts[port.Value] = fmt.Sprintf("%s:%s", task.Name, port.Label) 3504 } 3505 } 3506 } 3507 } 3508 3509 if leaderTasks > 1 { 3510 mErr.Errors = append(mErr.Errors, fmt.Errorf("Only one task may be marked as leader")) 3511 } 3512 3513 // Validate the tasks 3514 for _, task := range tg.Tasks { 3515 if err := task.Validate(tg.EphemeralDisk); err != nil { 3516 outer := fmt.Errorf("Task %s validation failed: %v", task.Name, err) 3517 mErr.Errors = append(mErr.Errors, outer) 3518 } 3519 } 3520 return mErr.ErrorOrNil() 3521 } 3522 3523 // Warnings returns a list of warnings that may be from dubious settings or 3524 // deprecation warnings. 3525 func (tg *TaskGroup) Warnings(j *Job) error { 3526 var mErr multierror.Error 3527 3528 // Validate the update strategy 3529 if u := tg.Update; u != nil { 3530 // Check the counts are appropriate 3531 if u.MaxParallel > tg.Count { 3532 mErr.Errors = append(mErr.Errors, 3533 fmt.Errorf("Update max parallel count is greater than task group count (%d > %d). "+ 3534 "A destructive change would result in the simultaneous replacement of all allocations.", u.MaxParallel, tg.Count)) 3535 } 3536 } 3537 3538 return mErr.ErrorOrNil() 3539 } 3540 3541 // LookupTask finds a task by name 3542 func (tg *TaskGroup) LookupTask(name string) *Task { 3543 for _, t := range tg.Tasks { 3544 if t.Name == name { 3545 return t 3546 } 3547 } 3548 return nil 3549 } 3550 3551 func (tg *TaskGroup) GoString() string { 3552 return fmt.Sprintf("*%#v", *tg) 3553 } 3554 3555 // CombinedResources returns the combined resources for the task group 3556 func (tg *TaskGroup) CombinedResources() *Resources { 3557 r := &Resources{ 3558 DiskMB: tg.EphemeralDisk.SizeMB, 3559 } 3560 for _, task := range tg.Tasks { 3561 r.Add(task.Resources) 3562 } 3563 return r 3564 } 3565 3566 // CheckRestart describes if and when a task should be restarted based on 3567 // failing health checks. 3568 type CheckRestart struct { 3569 Limit int // Restart task after this many unhealthy intervals 3570 Grace time.Duration // Grace time to give tasks after starting to get healthy 3571 IgnoreWarnings bool // If true treat checks in `warning` as passing 3572 } 3573 3574 func (c *CheckRestart) Copy() *CheckRestart { 3575 if c == nil { 3576 return nil 3577 } 3578 3579 nc := new(CheckRestart) 3580 *nc = *c 3581 return nc 3582 } 3583 3584 func (c *CheckRestart) Validate() error { 3585 if c == nil { 3586 return nil 3587 } 3588 3589 var mErr multierror.Error 3590 if c.Limit < 0 { 3591 mErr.Errors = append(mErr.Errors, fmt.Errorf("limit must be greater than or equal to 0 but found %d", c.Limit)) 3592 } 3593 3594 if c.Grace < 0 { 3595 mErr.Errors = append(mErr.Errors, fmt.Errorf("grace period must be greater than or equal to 0 but found %d", c.Grace)) 3596 } 3597 3598 return mErr.ErrorOrNil() 3599 } 3600 3601 const ( 3602 ServiceCheckHTTP = "http" 3603 ServiceCheckTCP = "tcp" 3604 ServiceCheckScript = "script" 3605 ServiceCheckGRPC = "grpc" 3606 3607 // minCheckInterval is the minimum check interval permitted. Consul 3608 // currently has its MinInterval set to 1s. Mirror that here for 3609 // consistency. 3610 minCheckInterval = 1 * time.Second 3611 3612 // minCheckTimeout is the minimum check timeout permitted for Consul 3613 // script TTL checks. 3614 minCheckTimeout = 1 * time.Second 3615 ) 3616 3617 // The ServiceCheck data model represents the consul health check that 3618 // Nomad registers for a Task 3619 type ServiceCheck struct { 3620 Name string // Name of the check, defaults to id 3621 Type string // Type of the check - tcp, http, docker and script 3622 Command string // Command is the command to run for script checks 3623 Args []string // Args is a list of arguments for script checks 3624 Path string // path of the health check url for http type check 3625 Protocol string // Protocol to use if check is http, defaults to http 3626 PortLabel string // The port to use for tcp/http checks 3627 AddressMode string // 'host' to use host ip:port or 'driver' to use driver's 3628 Interval time.Duration // Interval of the check 3629 Timeout time.Duration // Timeout of the response from the check before consul fails the check 3630 InitialStatus string // Initial status of the check 3631 TLSSkipVerify bool // Skip TLS verification when Protocol=https 3632 Method string // HTTP Method to use (GET by default) 3633 Header map[string][]string // HTTP Headers for Consul to set when making HTTP checks 3634 CheckRestart *CheckRestart // If and when a task should be restarted based on checks 3635 GRPCService string // Service for GRPC checks 3636 GRPCUseTLS bool // Whether or not to use TLS for GRPC checks 3637 } 3638 3639 func (sc *ServiceCheck) Copy() *ServiceCheck { 3640 if sc == nil { 3641 return nil 3642 } 3643 nsc := new(ServiceCheck) 3644 *nsc = *sc 3645 nsc.Args = helper.CopySliceString(sc.Args) 3646 nsc.Header = helper.CopyMapStringSliceString(sc.Header) 3647 nsc.CheckRestart = sc.CheckRestart.Copy() 3648 return nsc 3649 } 3650 3651 func (sc *ServiceCheck) Canonicalize(serviceName string) { 3652 // Ensure empty maps/slices are treated as null to avoid scheduling 3653 // issues when using DeepEquals. 3654 if len(sc.Args) == 0 { 3655 sc.Args = nil 3656 } 3657 3658 if len(sc.Header) == 0 { 3659 sc.Header = nil 3660 } else { 3661 for k, v := range sc.Header { 3662 if len(v) == 0 { 3663 sc.Header[k] = nil 3664 } 3665 } 3666 } 3667 3668 if sc.Name == "" { 3669 sc.Name = fmt.Sprintf("service: %q check", serviceName) 3670 } 3671 } 3672 3673 // validate a Service's ServiceCheck 3674 func (sc *ServiceCheck) validate() error { 3675 // Validate Type 3676 switch strings.ToLower(sc.Type) { 3677 case ServiceCheckGRPC: 3678 case ServiceCheckTCP: 3679 case ServiceCheckHTTP: 3680 if sc.Path == "" { 3681 return fmt.Errorf("http type must have a valid http path") 3682 } 3683 url, err := url.Parse(sc.Path) 3684 if err != nil { 3685 return fmt.Errorf("http type must have a valid http path") 3686 } 3687 if url.IsAbs() { 3688 return fmt.Errorf("http type must have a relative http path") 3689 } 3690 3691 case ServiceCheckScript: 3692 if sc.Command == "" { 3693 return fmt.Errorf("script type must have a valid script path") 3694 } 3695 3696 default: 3697 return fmt.Errorf(`invalid type (%+q), must be one of "http", "tcp", or "script" type`, sc.Type) 3698 } 3699 3700 // Validate interval and timeout 3701 if sc.Interval == 0 { 3702 return fmt.Errorf("missing required value interval. Interval cannot be less than %v", minCheckInterval) 3703 } else if sc.Interval < minCheckInterval { 3704 return fmt.Errorf("interval (%v) cannot be lower than %v", sc.Interval, minCheckInterval) 3705 } 3706 3707 if sc.Timeout == 0 { 3708 return fmt.Errorf("missing required value timeout. Timeout cannot be less than %v", minCheckInterval) 3709 } else if sc.Timeout < minCheckTimeout { 3710 return fmt.Errorf("timeout (%v) is lower than required minimum timeout %v", sc.Timeout, minCheckInterval) 3711 } 3712 3713 // Validate InitialStatus 3714 switch sc.InitialStatus { 3715 case "": 3716 case api.HealthPassing: 3717 case api.HealthWarning: 3718 case api.HealthCritical: 3719 default: 3720 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) 3721 3722 } 3723 3724 // Validate AddressMode 3725 switch sc.AddressMode { 3726 case "", AddressModeHost, AddressModeDriver: 3727 // Ok 3728 case AddressModeAuto: 3729 return fmt.Errorf("invalid address_mode %q - %s only valid for services", sc.AddressMode, AddressModeAuto) 3730 default: 3731 return fmt.Errorf("invalid address_mode %q", sc.AddressMode) 3732 } 3733 3734 return sc.CheckRestart.Validate() 3735 } 3736 3737 // RequiresPort returns whether the service check requires the task has a port. 3738 func (sc *ServiceCheck) RequiresPort() bool { 3739 switch sc.Type { 3740 case ServiceCheckGRPC, ServiceCheckHTTP, ServiceCheckTCP: 3741 return true 3742 default: 3743 return false 3744 } 3745 } 3746 3747 // TriggersRestarts returns true if this check should be watched and trigger a restart 3748 // on failure. 3749 func (sc *ServiceCheck) TriggersRestarts() bool { 3750 return sc.CheckRestart != nil && sc.CheckRestart.Limit > 0 3751 } 3752 3753 // Hash all ServiceCheck fields and the check's corresponding service ID to 3754 // create an identifier. The identifier is not guaranteed to be unique as if 3755 // the PortLabel is blank, the Service's PortLabel will be used after Hash is 3756 // called. 3757 func (sc *ServiceCheck) Hash(serviceID string) string { 3758 h := sha1.New() 3759 io.WriteString(h, serviceID) 3760 io.WriteString(h, sc.Name) 3761 io.WriteString(h, sc.Type) 3762 io.WriteString(h, sc.Command) 3763 io.WriteString(h, strings.Join(sc.Args, "")) 3764 io.WriteString(h, sc.Path) 3765 io.WriteString(h, sc.Protocol) 3766 io.WriteString(h, sc.PortLabel) 3767 io.WriteString(h, sc.Interval.String()) 3768 io.WriteString(h, sc.Timeout.String()) 3769 io.WriteString(h, sc.Method) 3770 // Only include TLSSkipVerify if set to maintain ID stability with Nomad <0.6 3771 if sc.TLSSkipVerify { 3772 io.WriteString(h, "true") 3773 } 3774 3775 // Since map iteration order isn't stable we need to write k/v pairs to 3776 // a slice and sort it before hashing. 3777 if len(sc.Header) > 0 { 3778 headers := make([]string, 0, len(sc.Header)) 3779 for k, v := range sc.Header { 3780 headers = append(headers, k+strings.Join(v, "")) 3781 } 3782 sort.Strings(headers) 3783 io.WriteString(h, strings.Join(headers, "")) 3784 } 3785 3786 // Only include AddressMode if set to maintain ID stability with Nomad <0.7.1 3787 if len(sc.AddressMode) > 0 { 3788 io.WriteString(h, sc.AddressMode) 3789 } 3790 3791 // Only include GRPC if set to maintain ID stability with Nomad <0.8.4 3792 if sc.GRPCService != "" { 3793 io.WriteString(h, sc.GRPCService) 3794 } 3795 if sc.GRPCUseTLS { 3796 io.WriteString(h, "true") 3797 } 3798 3799 return fmt.Sprintf("%x", h.Sum(nil)) 3800 } 3801 3802 const ( 3803 AddressModeAuto = "auto" 3804 AddressModeHost = "host" 3805 AddressModeDriver = "driver" 3806 ) 3807 3808 // Service represents a Consul service definition in Nomad 3809 type Service struct { 3810 // Name of the service registered with Consul. Consul defaults the 3811 // Name to ServiceID if not specified. The Name if specified is used 3812 // as one of the seed values when generating a Consul ServiceID. 3813 Name string 3814 3815 // PortLabel is either the numeric port number or the `host:port`. 3816 // To specify the port number using the host's Consul Advertise 3817 // address, specify an empty host in the PortLabel (e.g. `:port`). 3818 PortLabel string 3819 3820 // AddressMode specifies whether or not to use the host ip:port for 3821 // this service. 3822 AddressMode string 3823 3824 Tags []string // List of tags for the service 3825 CanaryTags []string // List of tags for the service when it is a canary 3826 Checks []*ServiceCheck // List of checks associated with the service 3827 } 3828 3829 func (s *Service) Copy() *Service { 3830 if s == nil { 3831 return nil 3832 } 3833 ns := new(Service) 3834 *ns = *s 3835 ns.Tags = helper.CopySliceString(ns.Tags) 3836 ns.CanaryTags = helper.CopySliceString(ns.CanaryTags) 3837 3838 if s.Checks != nil { 3839 checks := make([]*ServiceCheck, len(ns.Checks)) 3840 for i, c := range ns.Checks { 3841 checks[i] = c.Copy() 3842 } 3843 ns.Checks = checks 3844 } 3845 3846 return ns 3847 } 3848 3849 // Canonicalize interpolates values of Job, Task Group and Task in the Service 3850 // Name. This also generates check names, service id and check ids. 3851 func (s *Service) Canonicalize(job string, taskGroup string, task string) { 3852 // Ensure empty lists are treated as null to avoid scheduler issues when 3853 // using DeepEquals 3854 if len(s.Tags) == 0 { 3855 s.Tags = nil 3856 } 3857 if len(s.CanaryTags) == 0 { 3858 s.CanaryTags = nil 3859 } 3860 if len(s.Checks) == 0 { 3861 s.Checks = nil 3862 } 3863 3864 s.Name = args.ReplaceEnv(s.Name, map[string]string{ 3865 "JOB": job, 3866 "TASKGROUP": taskGroup, 3867 "TASK": task, 3868 "BASE": fmt.Sprintf("%s-%s-%s", job, taskGroup, task), 3869 }, 3870 ) 3871 3872 for _, check := range s.Checks { 3873 check.Canonicalize(s.Name) 3874 } 3875 } 3876 3877 // Validate checks if the Check definition is valid 3878 func (s *Service) Validate() error { 3879 var mErr multierror.Error 3880 3881 // Ensure the service name is valid per the below RFCs but make an exception 3882 // for our interpolation syntax by first stripping any environment variables from the name 3883 3884 serviceNameStripped := args.ReplaceEnvWithPlaceHolder(s.Name, "ENV-VAR") 3885 3886 if err := s.ValidateName(serviceNameStripped); err != nil { 3887 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)) 3888 } 3889 3890 switch s.AddressMode { 3891 case "", AddressModeAuto, AddressModeHost, AddressModeDriver: 3892 // OK 3893 default: 3894 mErr.Errors = append(mErr.Errors, fmt.Errorf("service address_mode must be %q, %q, or %q; not %q", AddressModeAuto, AddressModeHost, AddressModeDriver, s.AddressMode)) 3895 } 3896 3897 for _, c := range s.Checks { 3898 if s.PortLabel == "" && c.PortLabel == "" && c.RequiresPort() { 3899 mErr.Errors = append(mErr.Errors, fmt.Errorf("check %s invalid: check requires a port but neither check nor service %+q have a port", c.Name, s.Name)) 3900 continue 3901 } 3902 3903 if err := c.validate(); err != nil { 3904 mErr.Errors = append(mErr.Errors, fmt.Errorf("check %s invalid: %v", c.Name, err)) 3905 } 3906 } 3907 3908 return mErr.ErrorOrNil() 3909 } 3910 3911 // ValidateName checks if the services Name is valid and should be called after 3912 // the name has been interpolated 3913 func (s *Service) ValidateName(name string) error { 3914 // Ensure the service name is valid per RFC-952 §1 3915 // (https://tools.ietf.org/html/rfc952), RFC-1123 §2.1 3916 // (https://tools.ietf.org/html/rfc1123), and RFC-2782 3917 // (https://tools.ietf.org/html/rfc2782). 3918 re := regexp.MustCompile(`^(?i:[a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])$`) 3919 if !re.MatchString(name) { 3920 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) 3921 } 3922 return nil 3923 } 3924 3925 // Hash returns a base32 encoded hash of a Service's contents excluding checks 3926 // as they're hashed independently. 3927 func (s *Service) Hash(allocID, taskName string, canary bool) string { 3928 h := sha1.New() 3929 io.WriteString(h, allocID) 3930 io.WriteString(h, taskName) 3931 io.WriteString(h, s.Name) 3932 io.WriteString(h, s.PortLabel) 3933 io.WriteString(h, s.AddressMode) 3934 for _, tag := range s.Tags { 3935 io.WriteString(h, tag) 3936 } 3937 for _, tag := range s.CanaryTags { 3938 io.WriteString(h, tag) 3939 } 3940 3941 // Vary ID on whether or not CanaryTags will be used 3942 if canary { 3943 h.Write([]byte("Canary")) 3944 } 3945 3946 // Base32 is used for encoding the hash as sha1 hashes can always be 3947 // encoded without padding, only 4 bytes larger than base64, and saves 3948 // 8 bytes vs hex. Since these hashes are used in Consul URLs it's nice 3949 // to have a reasonably compact URL-safe representation. 3950 return b32.EncodeToString(h.Sum(nil)) 3951 } 3952 3953 const ( 3954 // DefaultKillTimeout is the default timeout between signaling a task it 3955 // will be killed and killing it. 3956 DefaultKillTimeout = 5 * time.Second 3957 ) 3958 3959 // LogConfig provides configuration for log rotation 3960 type LogConfig struct { 3961 MaxFiles int 3962 MaxFileSizeMB int 3963 } 3964 3965 // DefaultLogConfig returns the default LogConfig values. 3966 func DefaultLogConfig() *LogConfig { 3967 return &LogConfig{ 3968 MaxFiles: 10, 3969 MaxFileSizeMB: 10, 3970 } 3971 } 3972 3973 // Validate returns an error if the log config specified are less than 3974 // the minimum allowed. 3975 func (l *LogConfig) Validate() error { 3976 var mErr multierror.Error 3977 if l.MaxFiles < 1 { 3978 mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum number of files is 1; got %d", l.MaxFiles)) 3979 } 3980 if l.MaxFileSizeMB < 1 { 3981 mErr.Errors = append(mErr.Errors, fmt.Errorf("minimum file size is 1MB; got %d", l.MaxFileSizeMB)) 3982 } 3983 return mErr.ErrorOrNil() 3984 } 3985 3986 // Task is a single process typically that is executed as part of a task group. 3987 type Task struct { 3988 // Name of the task 3989 Name string 3990 3991 // Driver is used to control which driver is used 3992 Driver string 3993 3994 // User is used to determine which user will run the task. It defaults to 3995 // the same user the Nomad client is being run as. 3996 User string 3997 3998 // Config is provided to the driver to initialize 3999 Config map[string]interface{} 4000 4001 // Map of environment variables to be used by the driver 4002 Env map[string]string 4003 4004 // List of service definitions exposed by the Task 4005 Services []*Service 4006 4007 // Vault is used to define the set of Vault policies that this task should 4008 // have access to. 4009 Vault *Vault 4010 4011 // Templates are the set of templates to be rendered for the task. 4012 Templates []*Template 4013 4014 // Constraints can be specified at a task level and apply only to 4015 // the particular task. 4016 Constraints []*Constraint 4017 4018 // Resources is the resources needed by this task 4019 Resources *Resources 4020 4021 // DispatchPayload configures how the task retrieves its input from a dispatch 4022 DispatchPayload *DispatchPayloadConfig 4023 4024 // Meta is used to associate arbitrary metadata with this 4025 // task. This is opaque to Nomad. 4026 Meta map[string]string 4027 4028 // KillTimeout is the time between signaling a task that it will be 4029 // killed and killing it. 4030 KillTimeout time.Duration 4031 4032 // LogConfig provides configuration for log rotation 4033 LogConfig *LogConfig 4034 4035 // Artifacts is a list of artifacts to download and extract before running 4036 // the task. 4037 Artifacts []*TaskArtifact 4038 4039 // Leader marks the task as the leader within the group. When the leader 4040 // task exits, other tasks will be gracefully terminated. 4041 Leader bool 4042 4043 // ShutdownDelay is the duration of the delay between deregistering a 4044 // task from Consul and sending it a signal to shutdown. See #2441 4045 ShutdownDelay time.Duration 4046 4047 // The kill signal to use for the task. This is an optional specification, 4048 4049 // KillSignal is the kill signal to use for the task. This is an optional 4050 // specification and defaults to SIGINT 4051 KillSignal string 4052 } 4053 4054 func (t *Task) Copy() *Task { 4055 if t == nil { 4056 return nil 4057 } 4058 nt := new(Task) 4059 *nt = *t 4060 nt.Env = helper.CopyMapStringString(nt.Env) 4061 4062 if t.Services != nil { 4063 services := make([]*Service, len(nt.Services)) 4064 for i, s := range nt.Services { 4065 services[i] = s.Copy() 4066 } 4067 nt.Services = services 4068 } 4069 4070 nt.Constraints = CopySliceConstraints(nt.Constraints) 4071 4072 nt.Vault = nt.Vault.Copy() 4073 nt.Resources = nt.Resources.Copy() 4074 nt.Meta = helper.CopyMapStringString(nt.Meta) 4075 nt.DispatchPayload = nt.DispatchPayload.Copy() 4076 4077 if t.Artifacts != nil { 4078 artifacts := make([]*TaskArtifact, 0, len(t.Artifacts)) 4079 for _, a := range nt.Artifacts { 4080 artifacts = append(artifacts, a.Copy()) 4081 } 4082 nt.Artifacts = artifacts 4083 } 4084 4085 if i, err := copystructure.Copy(nt.Config); err != nil { 4086 panic(err.Error()) 4087 } else { 4088 nt.Config = i.(map[string]interface{}) 4089 } 4090 4091 if t.Templates != nil { 4092 templates := make([]*Template, len(t.Templates)) 4093 for i, tmpl := range nt.Templates { 4094 templates[i] = tmpl.Copy() 4095 } 4096 nt.Templates = templates 4097 } 4098 4099 return nt 4100 } 4101 4102 // Canonicalize canonicalizes fields in the task. 4103 func (t *Task) Canonicalize(job *Job, tg *TaskGroup) { 4104 // Ensure that an empty and nil map are treated the same to avoid scheduling 4105 // problems since we use reflect DeepEquals. 4106 if len(t.Meta) == 0 { 4107 t.Meta = nil 4108 } 4109 if len(t.Config) == 0 { 4110 t.Config = nil 4111 } 4112 if len(t.Env) == 0 { 4113 t.Env = nil 4114 } 4115 4116 for _, service := range t.Services { 4117 service.Canonicalize(job.Name, tg.Name, t.Name) 4118 } 4119 4120 // If Resources are nil initialize them to defaults, otherwise canonicalize 4121 if t.Resources == nil { 4122 t.Resources = DefaultResources() 4123 } else { 4124 t.Resources.Canonicalize() 4125 } 4126 4127 // Set the default timeout if it is not specified. 4128 if t.KillTimeout == 0 { 4129 t.KillTimeout = DefaultKillTimeout 4130 } 4131 4132 if t.Vault != nil { 4133 t.Vault.Canonicalize() 4134 } 4135 4136 for _, template := range t.Templates { 4137 template.Canonicalize() 4138 } 4139 } 4140 4141 func (t *Task) GoString() string { 4142 return fmt.Sprintf("*%#v", *t) 4143 } 4144 4145 // Validate is used to sanity check a task 4146 func (t *Task) Validate(ephemeralDisk *EphemeralDisk) error { 4147 var mErr multierror.Error 4148 if t.Name == "" { 4149 mErr.Errors = append(mErr.Errors, errors.New("Missing task name")) 4150 } 4151 if strings.ContainsAny(t.Name, `/\`) { 4152 // We enforce this so that when creating the directory on disk it will 4153 // not have any slashes. 4154 mErr.Errors = append(mErr.Errors, errors.New("Task name cannot include slashes")) 4155 } 4156 if t.Driver == "" { 4157 mErr.Errors = append(mErr.Errors, errors.New("Missing task driver")) 4158 } 4159 if t.KillTimeout < 0 { 4160 mErr.Errors = append(mErr.Errors, errors.New("KillTimeout must be a positive value")) 4161 } 4162 if t.ShutdownDelay < 0 { 4163 mErr.Errors = append(mErr.Errors, errors.New("ShutdownDelay must be a positive value")) 4164 } 4165 4166 // Validate the resources. 4167 if t.Resources == nil { 4168 mErr.Errors = append(mErr.Errors, errors.New("Missing task resources")) 4169 } else { 4170 if err := t.Resources.MeetsMinResources(); err != nil { 4171 mErr.Errors = append(mErr.Errors, err) 4172 } 4173 4174 // Ensure the task isn't asking for disk resources 4175 if t.Resources.DiskMB > 0 { 4176 mErr.Errors = append(mErr.Errors, errors.New("Task can't ask for disk resources, they have to be specified at the task group level.")) 4177 } 4178 } 4179 4180 // Validate the log config 4181 if t.LogConfig == nil { 4182 mErr.Errors = append(mErr.Errors, errors.New("Missing Log Config")) 4183 } else if err := t.LogConfig.Validate(); err != nil { 4184 mErr.Errors = append(mErr.Errors, err) 4185 } 4186 4187 for idx, constr := range t.Constraints { 4188 if err := constr.Validate(); err != nil { 4189 outer := fmt.Errorf("Constraint %d validation failed: %s", idx+1, err) 4190 mErr.Errors = append(mErr.Errors, outer) 4191 } 4192 4193 switch constr.Operand { 4194 case ConstraintDistinctHosts, ConstraintDistinctProperty: 4195 outer := fmt.Errorf("Constraint %d has disallowed Operand at task level: %s", idx+1, constr.Operand) 4196 mErr.Errors = append(mErr.Errors, outer) 4197 } 4198 } 4199 4200 // Validate Services 4201 if err := validateServices(t); err != nil { 4202 mErr.Errors = append(mErr.Errors, err) 4203 } 4204 4205 if t.LogConfig != nil && ephemeralDisk != nil { 4206 logUsage := (t.LogConfig.MaxFiles * t.LogConfig.MaxFileSizeMB) 4207 if ephemeralDisk.SizeMB <= logUsage { 4208 mErr.Errors = append(mErr.Errors, 4209 fmt.Errorf("log storage (%d MB) must be less than requested disk capacity (%d MB)", 4210 logUsage, ephemeralDisk.SizeMB)) 4211 } 4212 } 4213 4214 for idx, artifact := range t.Artifacts { 4215 if err := artifact.Validate(); err != nil { 4216 outer := fmt.Errorf("Artifact %d validation failed: %v", idx+1, err) 4217 mErr.Errors = append(mErr.Errors, outer) 4218 } 4219 } 4220 4221 if t.Vault != nil { 4222 if err := t.Vault.Validate(); err != nil { 4223 mErr.Errors = append(mErr.Errors, fmt.Errorf("Vault validation failed: %v", err)) 4224 } 4225 } 4226 4227 destinations := make(map[string]int, len(t.Templates)) 4228 for idx, tmpl := range t.Templates { 4229 if err := tmpl.Validate(); err != nil { 4230 outer := fmt.Errorf("Template %d validation failed: %s", idx+1, err) 4231 mErr.Errors = append(mErr.Errors, outer) 4232 } 4233 4234 if other, ok := destinations[tmpl.DestPath]; ok { 4235 outer := fmt.Errorf("Template %d has same destination as %d", idx+1, other) 4236 mErr.Errors = append(mErr.Errors, outer) 4237 } else { 4238 destinations[tmpl.DestPath] = idx + 1 4239 } 4240 } 4241 4242 // Validate the dispatch payload block if there 4243 if t.DispatchPayload != nil { 4244 if err := t.DispatchPayload.Validate(); err != nil { 4245 mErr.Errors = append(mErr.Errors, fmt.Errorf("Dispatch Payload validation failed: %v", err)) 4246 } 4247 } 4248 4249 return mErr.ErrorOrNil() 4250 } 4251 4252 // validateServices takes a task and validates the services within it are valid 4253 // and reference ports that exist. 4254 func validateServices(t *Task) error { 4255 var mErr multierror.Error 4256 4257 // Ensure that services don't ask for nonexistent ports and their names are 4258 // unique. 4259 servicePorts := make(map[string]map[string]struct{}) 4260 addServicePort := func(label, service string) { 4261 if _, ok := servicePorts[label]; !ok { 4262 servicePorts[label] = map[string]struct{}{} 4263 } 4264 servicePorts[label][service] = struct{}{} 4265 } 4266 knownServices := make(map[string]struct{}) 4267 for i, service := range t.Services { 4268 if err := service.Validate(); err != nil { 4269 outer := fmt.Errorf("service[%d] %+q validation failed: %s", i, service.Name, err) 4270 mErr.Errors = append(mErr.Errors, outer) 4271 } 4272 4273 // Ensure that services with the same name are not being registered for 4274 // the same port 4275 if _, ok := knownServices[service.Name+service.PortLabel]; ok { 4276 mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q is duplicate", service.Name)) 4277 } 4278 knownServices[service.Name+service.PortLabel] = struct{}{} 4279 4280 if service.PortLabel != "" { 4281 if service.AddressMode == "driver" { 4282 // Numeric port labels are valid for address_mode=driver 4283 _, err := strconv.Atoi(service.PortLabel) 4284 if err != nil { 4285 // Not a numeric port label, add it to list to check 4286 addServicePort(service.PortLabel, service.Name) 4287 } 4288 } else { 4289 addServicePort(service.PortLabel, service.Name) 4290 } 4291 } 4292 4293 // Ensure that check names are unique and have valid ports 4294 knownChecks := make(map[string]struct{}) 4295 for _, check := range service.Checks { 4296 if _, ok := knownChecks[check.Name]; ok { 4297 mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q is duplicate", check.Name)) 4298 } 4299 knownChecks[check.Name] = struct{}{} 4300 4301 if !check.RequiresPort() { 4302 // No need to continue validating check if it doesn't need a port 4303 continue 4304 } 4305 4306 effectivePort := check.PortLabel 4307 if effectivePort == "" { 4308 // Inherits from service 4309 effectivePort = service.PortLabel 4310 } 4311 4312 if effectivePort == "" { 4313 mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q is missing a port", check.Name)) 4314 continue 4315 } 4316 4317 isNumeric := false 4318 portNumber, err := strconv.Atoi(effectivePort) 4319 if err == nil { 4320 isNumeric = true 4321 } 4322 4323 // Numeric ports are fine for address_mode = "driver" 4324 if check.AddressMode == "driver" && isNumeric { 4325 if portNumber <= 0 { 4326 mErr.Errors = append(mErr.Errors, fmt.Errorf("check %q has invalid numeric port %d", check.Name, portNumber)) 4327 } 4328 continue 4329 } 4330 4331 if isNumeric { 4332 mErr.Errors = append(mErr.Errors, fmt.Errorf(`check %q cannot use a numeric port %d without setting address_mode="driver"`, check.Name, portNumber)) 4333 continue 4334 } 4335 4336 // PortLabel must exist, report errors by its parent service 4337 addServicePort(effectivePort, service.Name) 4338 } 4339 } 4340 4341 // Get the set of port labels. 4342 portLabels := make(map[string]struct{}) 4343 if t.Resources != nil { 4344 for _, network := range t.Resources.Networks { 4345 ports := network.PortLabels() 4346 for portLabel := range ports { 4347 portLabels[portLabel] = struct{}{} 4348 } 4349 } 4350 } 4351 4352 // Iterate over a sorted list of keys to make error listings stable 4353 keys := make([]string, 0, len(servicePorts)) 4354 for p := range servicePorts { 4355 keys = append(keys, p) 4356 } 4357 sort.Strings(keys) 4358 4359 // Ensure all ports referenced in services exist. 4360 for _, servicePort := range keys { 4361 services := servicePorts[servicePort] 4362 _, ok := portLabels[servicePort] 4363 if !ok { 4364 names := make([]string, 0, len(services)) 4365 for name := range services { 4366 names = append(names, name) 4367 } 4368 4369 // Keep order deterministic 4370 sort.Strings(names) 4371 joined := strings.Join(names, ", ") 4372 err := fmt.Errorf("port label %q referenced by services %v does not exist", servicePort, joined) 4373 mErr.Errors = append(mErr.Errors, err) 4374 } 4375 } 4376 4377 // Ensure address mode is valid 4378 return mErr.ErrorOrNil() 4379 } 4380 4381 const ( 4382 // TemplateChangeModeNoop marks that no action should be taken if the 4383 // template is re-rendered 4384 TemplateChangeModeNoop = "noop" 4385 4386 // TemplateChangeModeSignal marks that the task should be signaled if the 4387 // template is re-rendered 4388 TemplateChangeModeSignal = "signal" 4389 4390 // TemplateChangeModeRestart marks that the task should be restarted if the 4391 // template is re-rendered 4392 TemplateChangeModeRestart = "restart" 4393 ) 4394 4395 var ( 4396 // TemplateChangeModeInvalidError is the error for when an invalid change 4397 // mode is given 4398 TemplateChangeModeInvalidError = errors.New("Invalid change mode. Must be one of the following: noop, signal, restart") 4399 ) 4400 4401 // Template represents a template configuration to be rendered for a given task 4402 type Template struct { 4403 // SourcePath is the path to the template to be rendered 4404 SourcePath string 4405 4406 // DestPath is the path to where the template should be rendered 4407 DestPath string 4408 4409 // EmbeddedTmpl store the raw template. This is useful for smaller templates 4410 // where they are embedded in the job file rather than sent as an artifact 4411 EmbeddedTmpl string 4412 4413 // ChangeMode indicates what should be done if the template is re-rendered 4414 ChangeMode string 4415 4416 // ChangeSignal is the signal that should be sent if the change mode 4417 // requires it. 4418 ChangeSignal string 4419 4420 // Splay is used to avoid coordinated restarts of processes by applying a 4421 // random wait between 0 and the given splay value before signalling the 4422 // application of a change 4423 Splay time.Duration 4424 4425 // Perms is the permission the file should be written out with. 4426 Perms string 4427 4428 // LeftDelim and RightDelim are optional configurations to control what 4429 // delimiter is utilized when parsing the template. 4430 LeftDelim string 4431 RightDelim string 4432 4433 // Envvars enables exposing the template as environment variables 4434 // instead of as a file. The template must be of the form: 4435 // 4436 // VAR_NAME_1={{ key service/my-key }} 4437 // VAR_NAME_2=raw string and {{ env "attr.kernel.name" }} 4438 // 4439 // Lines will be split on the initial "=" with the first part being the 4440 // key name and the second part the value. 4441 // Empty lines and lines starting with # will be ignored, but to avoid 4442 // escaping issues #s within lines will not be treated as comments. 4443 Envvars bool 4444 4445 // VaultGrace is the grace duration between lease renewal and reacquiring a 4446 // secret. If the lease of a secret is less than the grace, a new secret is 4447 // acquired. 4448 VaultGrace time.Duration 4449 } 4450 4451 // DefaultTemplate returns a default template. 4452 func DefaultTemplate() *Template { 4453 return &Template{ 4454 ChangeMode: TemplateChangeModeRestart, 4455 Splay: 5 * time.Second, 4456 Perms: "0644", 4457 } 4458 } 4459 4460 func (t *Template) Copy() *Template { 4461 if t == nil { 4462 return nil 4463 } 4464 copy := new(Template) 4465 *copy = *t 4466 return copy 4467 } 4468 4469 func (t *Template) Canonicalize() { 4470 if t.ChangeSignal != "" { 4471 t.ChangeSignal = strings.ToUpper(t.ChangeSignal) 4472 } 4473 } 4474 4475 func (t *Template) Validate() error { 4476 var mErr multierror.Error 4477 4478 // Verify we have something to render 4479 if t.SourcePath == "" && t.EmbeddedTmpl == "" { 4480 multierror.Append(&mErr, fmt.Errorf("Must specify a source path or have an embedded template")) 4481 } 4482 4483 // Verify we can render somewhere 4484 if t.DestPath == "" { 4485 multierror.Append(&mErr, fmt.Errorf("Must specify a destination for the template")) 4486 } 4487 4488 // Verify the destination doesn't escape 4489 escaped, err := PathEscapesAllocDir("task", t.DestPath) 4490 if err != nil { 4491 mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid destination path: %v", err)) 4492 } else if escaped { 4493 mErr.Errors = append(mErr.Errors, fmt.Errorf("destination escapes allocation directory")) 4494 } 4495 4496 // Verify a proper change mode 4497 switch t.ChangeMode { 4498 case TemplateChangeModeNoop, TemplateChangeModeRestart: 4499 case TemplateChangeModeSignal: 4500 if t.ChangeSignal == "" { 4501 multierror.Append(&mErr, fmt.Errorf("Must specify signal value when change mode is signal")) 4502 } 4503 if t.Envvars { 4504 multierror.Append(&mErr, fmt.Errorf("cannot use signals with env var templates")) 4505 } 4506 default: 4507 multierror.Append(&mErr, TemplateChangeModeInvalidError) 4508 } 4509 4510 // Verify the splay is positive 4511 if t.Splay < 0 { 4512 multierror.Append(&mErr, fmt.Errorf("Must specify positive splay value")) 4513 } 4514 4515 // Verify the permissions 4516 if t.Perms != "" { 4517 if _, err := strconv.ParseUint(t.Perms, 8, 12); err != nil { 4518 multierror.Append(&mErr, fmt.Errorf("Failed to parse %q as octal: %v", t.Perms, err)) 4519 } 4520 } 4521 4522 if t.VaultGrace.Nanoseconds() < 0 { 4523 multierror.Append(&mErr, fmt.Errorf("Vault grace must be greater than zero: %v < 0", t.VaultGrace)) 4524 } 4525 4526 return mErr.ErrorOrNil() 4527 } 4528 4529 // Set of possible states for a task. 4530 const ( 4531 TaskStatePending = "pending" // The task is waiting to be run. 4532 TaskStateRunning = "running" // The task is currently running. 4533 TaskStateDead = "dead" // Terminal state of task. 4534 ) 4535 4536 // TaskState tracks the current state of a task and events that caused state 4537 // transitions. 4538 type TaskState struct { 4539 // The current state of the task. 4540 State string 4541 4542 // Failed marks a task as having failed 4543 Failed bool 4544 4545 // Restarts is the number of times the task has restarted 4546 Restarts uint64 4547 4548 // LastRestart is the time the task last restarted. It is updated each time the 4549 // task restarts 4550 LastRestart time.Time 4551 4552 // StartedAt is the time the task is started. It is updated each time the 4553 // task starts 4554 StartedAt time.Time 4555 4556 // FinishedAt is the time at which the task transitioned to dead and will 4557 // not be started again. 4558 FinishedAt time.Time 4559 4560 // Series of task events that transition the state of the task. 4561 Events []*TaskEvent 4562 } 4563 4564 func (ts *TaskState) Copy() *TaskState { 4565 if ts == nil { 4566 return nil 4567 } 4568 copy := new(TaskState) 4569 *copy = *ts 4570 4571 if ts.Events != nil { 4572 copy.Events = make([]*TaskEvent, len(ts.Events)) 4573 for i, e := range ts.Events { 4574 copy.Events[i] = e.Copy() 4575 } 4576 } 4577 return copy 4578 } 4579 4580 // Successful returns whether a task finished successfully. This doesn't really 4581 // have meaning on a non-batch allocation because a service and system 4582 // allocation should not finish. 4583 func (ts *TaskState) Successful() bool { 4584 l := len(ts.Events) 4585 if ts.State != TaskStateDead || l == 0 { 4586 return false 4587 } 4588 4589 e := ts.Events[l-1] 4590 if e.Type != TaskTerminated { 4591 return false 4592 } 4593 4594 return e.ExitCode == 0 4595 } 4596 4597 const ( 4598 // TaskSetupFailure indicates that the task could not be started due to a 4599 // a setup failure. 4600 TaskSetupFailure = "Setup Failure" 4601 4602 // TaskDriveFailure indicates that the task could not be started due to a 4603 // failure in the driver. 4604 TaskDriverFailure = "Driver Failure" 4605 4606 // TaskReceived signals that the task has been pulled by the client at the 4607 // given timestamp. 4608 TaskReceived = "Received" 4609 4610 // TaskFailedValidation indicates the task was invalid and as such was not 4611 // run. 4612 TaskFailedValidation = "Failed Validation" 4613 4614 // TaskStarted signals that the task was started and its timestamp can be 4615 // used to determine the running length of the task. 4616 TaskStarted = "Started" 4617 4618 // TaskTerminated indicates that the task was started and exited. 4619 TaskTerminated = "Terminated" 4620 4621 // TaskKilling indicates a kill signal has been sent to the task. 4622 TaskKilling = "Killing" 4623 4624 // TaskKilled indicates a user has killed the task. 4625 TaskKilled = "Killed" 4626 4627 // TaskRestarting indicates that task terminated and is being restarted. 4628 TaskRestarting = "Restarting" 4629 4630 // TaskNotRestarting indicates that the task has failed and is not being 4631 // restarted because it has exceeded its restart policy. 4632 TaskNotRestarting = "Not Restarting" 4633 4634 // TaskRestartSignal indicates that the task has been signalled to be 4635 // restarted 4636 TaskRestartSignal = "Restart Signaled" 4637 4638 // TaskSignaling indicates that the task is being signalled. 4639 TaskSignaling = "Signaling" 4640 4641 // TaskDownloadingArtifacts means the task is downloading the artifacts 4642 // specified in the task. 4643 TaskDownloadingArtifacts = "Downloading Artifacts" 4644 4645 // TaskArtifactDownloadFailed indicates that downloading the artifacts 4646 // failed. 4647 TaskArtifactDownloadFailed = "Failed Artifact Download" 4648 4649 // TaskBuildingTaskDir indicates that the task directory/chroot is being 4650 // built. 4651 TaskBuildingTaskDir = "Building Task Directory" 4652 4653 // TaskSetup indicates the task runner is setting up the task environment 4654 TaskSetup = "Task Setup" 4655 4656 // TaskDiskExceeded indicates that one of the tasks in a taskgroup has 4657 // exceeded the requested disk resources. 4658 TaskDiskExceeded = "Disk Resources Exceeded" 4659 4660 // TaskSiblingFailed indicates that a sibling task in the task group has 4661 // failed. 4662 TaskSiblingFailed = "Sibling Task Failed" 4663 4664 // TaskDriverMessage is an informational event message emitted by 4665 // drivers such as when they're performing a long running action like 4666 // downloading an image. 4667 TaskDriverMessage = "Driver" 4668 4669 // TaskLeaderDead indicates that the leader task within the has finished. 4670 TaskLeaderDead = "Leader Task Dead" 4671 ) 4672 4673 // TaskEvent is an event that effects the state of a task and contains meta-data 4674 // appropriate to the events type. 4675 type TaskEvent struct { 4676 Type string 4677 Time int64 // Unix Nanosecond timestamp 4678 4679 Message string // A possible message explaining the termination of the task. 4680 4681 // DisplayMessage is a human friendly message about the event 4682 DisplayMessage string 4683 4684 // Details is a map with annotated info about the event 4685 Details map[string]string 4686 4687 // DEPRECATION NOTICE: The following fields are deprecated and will be removed 4688 // in a future release. Field values are available in the Details map. 4689 4690 // FailsTask marks whether this event fails the task. 4691 // Deprecated, use Details["fails_task"] to access this. 4692 FailsTask bool 4693 4694 // Restart fields. 4695 // Deprecated, use Details["restart_reason"] to access this. 4696 RestartReason string 4697 4698 // Setup Failure fields. 4699 // Deprecated, use Details["setup_error"] to access this. 4700 SetupError string 4701 4702 // Driver Failure fields. 4703 // Deprecated, use Details["driver_error"] to access this. 4704 DriverError string // A driver error occurred while starting the task. 4705 4706 // Task Terminated Fields. 4707 4708 // Deprecated, use Details["exit_code"] to access this. 4709 ExitCode int // The exit code of the task. 4710 4711 // Deprecated, use Details["signal"] to access this. 4712 Signal int // The signal that terminated the task. 4713 4714 // Killing fields 4715 // Deprecated, use Details["kill_timeout"] to access this. 4716 KillTimeout time.Duration 4717 4718 // Task Killed Fields. 4719 // Deprecated, use Details["kill_error"] to access this. 4720 KillError string // Error killing the task. 4721 4722 // KillReason is the reason the task was killed 4723 // Deprecated, use Details["kill_reason"] to access this. 4724 KillReason string 4725 4726 // TaskRestarting fields. 4727 // Deprecated, use Details["start_delay"] to access this. 4728 StartDelay int64 // The sleep period before restarting the task in unix nanoseconds. 4729 4730 // Artifact Download fields 4731 // Deprecated, use Details["download_error"] to access this. 4732 DownloadError string // Error downloading artifacts 4733 4734 // Validation fields 4735 // Deprecated, use Details["validation_error"] to access this. 4736 ValidationError string // Validation error 4737 4738 // The maximum allowed task disk size. 4739 // Deprecated, use Details["disk_limit"] to access this. 4740 DiskLimit int64 4741 4742 // Name of the sibling task that caused termination of the task that 4743 // the TaskEvent refers to. 4744 // Deprecated, use Details["failed_sibling"] to access this. 4745 FailedSibling string 4746 4747 // VaultError is the error from token renewal 4748 // Deprecated, use Details["vault_renewal_error"] to access this. 4749 VaultError string 4750 4751 // TaskSignalReason indicates the reason the task is being signalled. 4752 // Deprecated, use Details["task_signal_reason"] to access this. 4753 TaskSignalReason string 4754 4755 // TaskSignal is the signal that was sent to the task 4756 // Deprecated, use Details["task_signal"] to access this. 4757 TaskSignal string 4758 4759 // DriverMessage indicates a driver action being taken. 4760 // Deprecated, use Details["driver_message"] to access this. 4761 DriverMessage string 4762 4763 // GenericSource is the source of a message. 4764 // Deprecated, is redundant with event type. 4765 GenericSource string 4766 } 4767 4768 func (event *TaskEvent) PopulateEventDisplayMessage() { 4769 // Build up the description based on the event type. 4770 if event == nil { //TODO(preetha) needs investigation alloc_runner's Run method sends a nil event when sigterming nomad. Why? 4771 return 4772 } 4773 4774 if event.DisplayMessage != "" { 4775 return 4776 } 4777 4778 var desc string 4779 switch event.Type { 4780 case TaskSetup: 4781 desc = event.Message 4782 case TaskStarted: 4783 desc = "Task started by client" 4784 case TaskReceived: 4785 desc = "Task received by client" 4786 case TaskFailedValidation: 4787 if event.ValidationError != "" { 4788 desc = event.ValidationError 4789 } else { 4790 desc = "Validation of task failed" 4791 } 4792 case TaskSetupFailure: 4793 if event.SetupError != "" { 4794 desc = event.SetupError 4795 } else { 4796 desc = "Task setup failed" 4797 } 4798 case TaskDriverFailure: 4799 if event.DriverError != "" { 4800 desc = event.DriverError 4801 } else { 4802 desc = "Failed to start task" 4803 } 4804 case TaskDownloadingArtifacts: 4805 desc = "Client is downloading artifacts" 4806 case TaskArtifactDownloadFailed: 4807 if event.DownloadError != "" { 4808 desc = event.DownloadError 4809 } else { 4810 desc = "Failed to download artifacts" 4811 } 4812 case TaskKilling: 4813 if event.KillReason != "" { 4814 desc = event.KillReason 4815 } else if event.KillTimeout != 0 { 4816 desc = fmt.Sprintf("Sent interrupt. Waiting %v before force killing", event.KillTimeout) 4817 } else { 4818 desc = "Sent interrupt" 4819 } 4820 case TaskKilled: 4821 if event.KillError != "" { 4822 desc = event.KillError 4823 } else { 4824 desc = "Task successfully killed" 4825 } 4826 case TaskTerminated: 4827 var parts []string 4828 parts = append(parts, fmt.Sprintf("Exit Code: %d", event.ExitCode)) 4829 4830 if event.Signal != 0 { 4831 parts = append(parts, fmt.Sprintf("Signal: %d", event.Signal)) 4832 } 4833 4834 if event.Message != "" { 4835 parts = append(parts, fmt.Sprintf("Exit Message: %q", event.Message)) 4836 } 4837 desc = strings.Join(parts, ", ") 4838 case TaskRestarting: 4839 in := fmt.Sprintf("Task restarting in %v", time.Duration(event.StartDelay)) 4840 if event.RestartReason != "" && event.RestartReason != ReasonWithinPolicy { 4841 desc = fmt.Sprintf("%s - %s", event.RestartReason, in) 4842 } else { 4843 desc = in 4844 } 4845 case TaskNotRestarting: 4846 if event.RestartReason != "" { 4847 desc = event.RestartReason 4848 } else { 4849 desc = "Task exceeded restart policy" 4850 } 4851 case TaskSiblingFailed: 4852 if event.FailedSibling != "" { 4853 desc = fmt.Sprintf("Task's sibling %q failed", event.FailedSibling) 4854 } else { 4855 desc = "Task's sibling failed" 4856 } 4857 case TaskSignaling: 4858 sig := event.TaskSignal 4859 reason := event.TaskSignalReason 4860 4861 if sig == "" && reason == "" { 4862 desc = "Task being sent a signal" 4863 } else if sig == "" { 4864 desc = reason 4865 } else if reason == "" { 4866 desc = fmt.Sprintf("Task being sent signal %v", sig) 4867 } else { 4868 desc = fmt.Sprintf("Task being sent signal %v: %v", sig, reason) 4869 } 4870 case TaskRestartSignal: 4871 if event.RestartReason != "" { 4872 desc = event.RestartReason 4873 } else { 4874 desc = "Task signaled to restart" 4875 } 4876 case TaskDriverMessage: 4877 desc = event.DriverMessage 4878 case TaskLeaderDead: 4879 desc = "Leader Task in Group dead" 4880 default: 4881 desc = event.Message 4882 } 4883 4884 event.DisplayMessage = desc 4885 } 4886 4887 func (te *TaskEvent) GoString() string { 4888 return fmt.Sprintf("%v - %v", te.Time, te.Type) 4889 } 4890 4891 // SetMessage sets the message of TaskEvent 4892 func (te *TaskEvent) SetMessage(msg string) *TaskEvent { 4893 te.Message = msg 4894 te.Details["message"] = msg 4895 return te 4896 } 4897 4898 func (te *TaskEvent) Copy() *TaskEvent { 4899 if te == nil { 4900 return nil 4901 } 4902 copy := new(TaskEvent) 4903 *copy = *te 4904 return copy 4905 } 4906 4907 func NewTaskEvent(event string) *TaskEvent { 4908 return &TaskEvent{ 4909 Type: event, 4910 Time: time.Now().UnixNano(), 4911 Details: make(map[string]string), 4912 } 4913 } 4914 4915 // SetSetupError is used to store an error that occurred while setting up the 4916 // task 4917 func (e *TaskEvent) SetSetupError(err error) *TaskEvent { 4918 if err != nil { 4919 e.SetupError = err.Error() 4920 e.Details["setup_error"] = err.Error() 4921 } 4922 return e 4923 } 4924 4925 func (e *TaskEvent) SetFailsTask() *TaskEvent { 4926 e.FailsTask = true 4927 e.Details["fails_task"] = "true" 4928 return e 4929 } 4930 4931 func (e *TaskEvent) SetDriverError(err error) *TaskEvent { 4932 if err != nil { 4933 e.DriverError = err.Error() 4934 e.Details["driver_error"] = err.Error() 4935 } 4936 return e 4937 } 4938 4939 func (e *TaskEvent) SetExitCode(c int) *TaskEvent { 4940 e.ExitCode = c 4941 e.Details["exit_code"] = fmt.Sprintf("%d", c) 4942 return e 4943 } 4944 4945 func (e *TaskEvent) SetSignal(s int) *TaskEvent { 4946 e.Signal = s 4947 e.Details["signal"] = fmt.Sprintf("%d", s) 4948 return e 4949 } 4950 4951 func (e *TaskEvent) SetExitMessage(err error) *TaskEvent { 4952 if err != nil { 4953 e.Message = err.Error() 4954 e.Details["exit_message"] = err.Error() 4955 } 4956 return e 4957 } 4958 4959 func (e *TaskEvent) SetKillError(err error) *TaskEvent { 4960 if err != nil { 4961 e.KillError = err.Error() 4962 e.Details["kill_error"] = err.Error() 4963 } 4964 return e 4965 } 4966 4967 func (e *TaskEvent) SetKillReason(r string) *TaskEvent { 4968 e.KillReason = r 4969 e.Details["kill_reason"] = r 4970 return e 4971 } 4972 4973 func (e *TaskEvent) SetRestartDelay(delay time.Duration) *TaskEvent { 4974 e.StartDelay = int64(delay) 4975 e.Details["start_delay"] = fmt.Sprintf("%d", delay) 4976 return e 4977 } 4978 4979 func (e *TaskEvent) SetRestartReason(reason string) *TaskEvent { 4980 e.RestartReason = reason 4981 e.Details["restart_reason"] = reason 4982 return e 4983 } 4984 4985 func (e *TaskEvent) SetTaskSignalReason(r string) *TaskEvent { 4986 e.TaskSignalReason = r 4987 e.Details["task_signal_reason"] = r 4988 return e 4989 } 4990 4991 func (e *TaskEvent) SetTaskSignal(s os.Signal) *TaskEvent { 4992 e.TaskSignal = s.String() 4993 e.Details["task_signal"] = s.String() 4994 return e 4995 } 4996 4997 func (e *TaskEvent) SetDownloadError(err error) *TaskEvent { 4998 if err != nil { 4999 e.DownloadError = err.Error() 5000 e.Details["download_error"] = err.Error() 5001 } 5002 return e 5003 } 5004 5005 func (e *TaskEvent) SetValidationError(err error) *TaskEvent { 5006 if err != nil { 5007 e.ValidationError = err.Error() 5008 e.Details["validation_error"] = err.Error() 5009 } 5010 return e 5011 } 5012 5013 func (e *TaskEvent) SetKillTimeout(timeout time.Duration) *TaskEvent { 5014 e.KillTimeout = timeout 5015 e.Details["kill_timeout"] = timeout.String() 5016 return e 5017 } 5018 5019 func (e *TaskEvent) SetDiskLimit(limit int64) *TaskEvent { 5020 e.DiskLimit = limit 5021 e.Details["disk_limit"] = fmt.Sprintf("%d", limit) 5022 return e 5023 } 5024 5025 func (e *TaskEvent) SetFailedSibling(sibling string) *TaskEvent { 5026 e.FailedSibling = sibling 5027 e.Details["failed_sibling"] = sibling 5028 return e 5029 } 5030 5031 func (e *TaskEvent) SetVaultRenewalError(err error) *TaskEvent { 5032 if err != nil { 5033 e.VaultError = err.Error() 5034 e.Details["vault_renewal_error"] = err.Error() 5035 } 5036 return e 5037 } 5038 5039 func (e *TaskEvent) SetDriverMessage(m string) *TaskEvent { 5040 e.DriverMessage = m 5041 e.Details["driver_message"] = m 5042 return e 5043 } 5044 5045 // TaskArtifact is an artifact to download before running the task. 5046 type TaskArtifact struct { 5047 // GetterSource is the source to download an artifact using go-getter 5048 GetterSource string 5049 5050 // GetterOptions are options to use when downloading the artifact using 5051 // go-getter. 5052 GetterOptions map[string]string 5053 5054 // GetterMode is the go-getter.ClientMode for fetching resources. 5055 // Defaults to "any" but can be set to "file" or "dir". 5056 GetterMode string 5057 5058 // RelativeDest is the download destination given relative to the task's 5059 // directory. 5060 RelativeDest string 5061 } 5062 5063 func (ta *TaskArtifact) Copy() *TaskArtifact { 5064 if ta == nil { 5065 return nil 5066 } 5067 nta := new(TaskArtifact) 5068 *nta = *ta 5069 nta.GetterOptions = helper.CopyMapStringString(ta.GetterOptions) 5070 return nta 5071 } 5072 5073 func (ta *TaskArtifact) GoString() string { 5074 return fmt.Sprintf("%+v", ta) 5075 } 5076 5077 // PathEscapesAllocDir returns if the given path escapes the allocation 5078 // directory. The prefix allows adding a prefix if the path will be joined, for 5079 // example a "task/local" prefix may be provided if the path will be joined 5080 // against that prefix. 5081 func PathEscapesAllocDir(prefix, path string) (bool, error) { 5082 // Verify the destination doesn't escape the tasks directory 5083 alloc, err := filepath.Abs(filepath.Join("/", "alloc-dir/", "alloc-id/")) 5084 if err != nil { 5085 return false, err 5086 } 5087 abs, err := filepath.Abs(filepath.Join(alloc, prefix, path)) 5088 if err != nil { 5089 return false, err 5090 } 5091 rel, err := filepath.Rel(alloc, abs) 5092 if err != nil { 5093 return false, err 5094 } 5095 5096 return strings.HasPrefix(rel, ".."), nil 5097 } 5098 5099 func (ta *TaskArtifact) Validate() error { 5100 // Verify the source 5101 var mErr multierror.Error 5102 if ta.GetterSource == "" { 5103 mErr.Errors = append(mErr.Errors, fmt.Errorf("source must be specified")) 5104 } 5105 5106 switch ta.GetterMode { 5107 case "": 5108 // Default to any 5109 ta.GetterMode = GetterModeAny 5110 case GetterModeAny, GetterModeFile, GetterModeDir: 5111 // Ok 5112 default: 5113 mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid artifact mode %q; must be one of: %s, %s, %s", 5114 ta.GetterMode, GetterModeAny, GetterModeFile, GetterModeDir)) 5115 } 5116 5117 escaped, err := PathEscapesAllocDir("task", ta.RelativeDest) 5118 if err != nil { 5119 mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid destination path: %v", err)) 5120 } else if escaped { 5121 mErr.Errors = append(mErr.Errors, fmt.Errorf("destination escapes allocation directory")) 5122 } 5123 5124 // Verify the checksum 5125 if check, ok := ta.GetterOptions["checksum"]; ok { 5126 check = strings.TrimSpace(check) 5127 if check == "" { 5128 mErr.Errors = append(mErr.Errors, fmt.Errorf("checksum value cannot be empty")) 5129 return mErr.ErrorOrNil() 5130 } 5131 5132 parts := strings.Split(check, ":") 5133 if l := len(parts); l != 2 { 5134 mErr.Errors = append(mErr.Errors, fmt.Errorf(`checksum must be given as "type:value"; got %q`, check)) 5135 return mErr.ErrorOrNil() 5136 } 5137 5138 checksumVal := parts[1] 5139 checksumBytes, err := hex.DecodeString(checksumVal) 5140 if err != nil { 5141 mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid checksum: %v", err)) 5142 return mErr.ErrorOrNil() 5143 } 5144 5145 checksumType := parts[0] 5146 expectedLength := 0 5147 switch checksumType { 5148 case "md5": 5149 expectedLength = md5.Size 5150 case "sha1": 5151 expectedLength = sha1.Size 5152 case "sha256": 5153 expectedLength = sha256.Size 5154 case "sha512": 5155 expectedLength = sha512.Size 5156 default: 5157 mErr.Errors = append(mErr.Errors, fmt.Errorf("unsupported checksum type: %s", checksumType)) 5158 return mErr.ErrorOrNil() 5159 } 5160 5161 if len(checksumBytes) != expectedLength { 5162 mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid %s checksum: %v", checksumType, checksumVal)) 5163 return mErr.ErrorOrNil() 5164 } 5165 } 5166 5167 return mErr.ErrorOrNil() 5168 } 5169 5170 const ( 5171 ConstraintDistinctProperty = "distinct_property" 5172 ConstraintDistinctHosts = "distinct_hosts" 5173 ConstraintRegex = "regexp" 5174 ConstraintVersion = "version" 5175 ConstraintSetContains = "set_contains" 5176 ) 5177 5178 // Constraints are used to restrict placement options. 5179 type Constraint struct { 5180 LTarget string // Left-hand target 5181 RTarget string // Right-hand target 5182 Operand string // Constraint operand (<=, <, =, !=, >, >=), contains, near 5183 str string // Memoized string 5184 } 5185 5186 // Equal checks if two constraints are equal 5187 func (c *Constraint) Equal(o *Constraint) bool { 5188 return c.LTarget == o.LTarget && 5189 c.RTarget == o.RTarget && 5190 c.Operand == o.Operand 5191 } 5192 5193 func (c *Constraint) Copy() *Constraint { 5194 if c == nil { 5195 return nil 5196 } 5197 nc := new(Constraint) 5198 *nc = *c 5199 return nc 5200 } 5201 5202 func (c *Constraint) String() string { 5203 if c.str != "" { 5204 return c.str 5205 } 5206 c.str = fmt.Sprintf("%s %s %s", c.LTarget, c.Operand, c.RTarget) 5207 return c.str 5208 } 5209 5210 func (c *Constraint) Validate() error { 5211 var mErr multierror.Error 5212 if c.Operand == "" { 5213 mErr.Errors = append(mErr.Errors, errors.New("Missing constraint operand")) 5214 } 5215 5216 // requireLtarget specifies whether the constraint requires an LTarget to be 5217 // provided. 5218 requireLtarget := true 5219 5220 // Perform additional validation based on operand 5221 switch c.Operand { 5222 case ConstraintDistinctHosts: 5223 requireLtarget = false 5224 case ConstraintSetContains: 5225 if c.RTarget == "" { 5226 mErr.Errors = append(mErr.Errors, fmt.Errorf("Set contains constraint requires an RTarget")) 5227 } 5228 case ConstraintRegex: 5229 if _, err := regexp.Compile(c.RTarget); err != nil { 5230 mErr.Errors = append(mErr.Errors, fmt.Errorf("Regular expression failed to compile: %v", err)) 5231 } 5232 case ConstraintVersion: 5233 if _, err := version.NewConstraint(c.RTarget); err != nil { 5234 mErr.Errors = append(mErr.Errors, fmt.Errorf("Version constraint is invalid: %v", err)) 5235 } 5236 case ConstraintDistinctProperty: 5237 // If a count is set, make sure it is convertible to a uint64 5238 if c.RTarget != "" { 5239 count, err := strconv.ParseUint(c.RTarget, 10, 64) 5240 if err != nil { 5241 mErr.Errors = append(mErr.Errors, fmt.Errorf("Failed to convert RTarget %q to uint64: %v", c.RTarget, err)) 5242 } else if count < 1 { 5243 mErr.Errors = append(mErr.Errors, fmt.Errorf("Distinct Property must have an allowed count of 1 or greater: %d < 1", count)) 5244 } 5245 } 5246 case "=", "==", "is", "!=", "not", "<", "<=", ">", ">=": 5247 if c.RTarget == "" { 5248 mErr.Errors = append(mErr.Errors, fmt.Errorf("Operator %q requires an RTarget", c.Operand)) 5249 } 5250 default: 5251 mErr.Errors = append(mErr.Errors, fmt.Errorf("Unknown constraint type %q", c.Operand)) 5252 } 5253 5254 // Ensure we have an LTarget for the constraints that need one 5255 if requireLtarget && c.LTarget == "" { 5256 mErr.Errors = append(mErr.Errors, fmt.Errorf("No LTarget provided but is required by constraint")) 5257 } 5258 5259 return mErr.ErrorOrNil() 5260 } 5261 5262 // EphemeralDisk is an ephemeral disk object 5263 type EphemeralDisk struct { 5264 // Sticky indicates whether the allocation is sticky to a node 5265 Sticky bool 5266 5267 // SizeMB is the size of the local disk 5268 SizeMB int 5269 5270 // Migrate determines if Nomad client should migrate the allocation dir for 5271 // sticky allocations 5272 Migrate bool 5273 } 5274 5275 // DefaultEphemeralDisk returns a EphemeralDisk with default configurations 5276 func DefaultEphemeralDisk() *EphemeralDisk { 5277 return &EphemeralDisk{ 5278 SizeMB: 300, 5279 } 5280 } 5281 5282 // Validate validates EphemeralDisk 5283 func (d *EphemeralDisk) Validate() error { 5284 if d.SizeMB < 10 { 5285 return fmt.Errorf("minimum DiskMB value is 10; got %d", d.SizeMB) 5286 } 5287 return nil 5288 } 5289 5290 // Copy copies the EphemeralDisk struct and returns a new one 5291 func (d *EphemeralDisk) Copy() *EphemeralDisk { 5292 ld := new(EphemeralDisk) 5293 *ld = *d 5294 return ld 5295 } 5296 5297 var ( 5298 // VaultUnrecoverableError matches unrecoverable errors returned by a Vault 5299 // server 5300 VaultUnrecoverableError = regexp.MustCompile(`Code:\s+40(0|3|4)`) 5301 ) 5302 5303 const ( 5304 // VaultChangeModeNoop takes no action when a new token is retrieved. 5305 VaultChangeModeNoop = "noop" 5306 5307 // VaultChangeModeSignal signals the task when a new token is retrieved. 5308 VaultChangeModeSignal = "signal" 5309 5310 // VaultChangeModeRestart restarts the task when a new token is retrieved. 5311 VaultChangeModeRestart = "restart" 5312 ) 5313 5314 // Vault stores the set of permissions a task needs access to from Vault. 5315 type Vault struct { 5316 // Policies is the set of policies that the task needs access to 5317 Policies []string 5318 5319 // Env marks whether the Vault Token should be exposed as an environment 5320 // variable 5321 Env bool 5322 5323 // ChangeMode is used to configure the task's behavior when the Vault 5324 // token changes because the original token could not be renewed in time. 5325 ChangeMode string 5326 5327 // ChangeSignal is the signal sent to the task when a new token is 5328 // retrieved. This is only valid when using the signal change mode. 5329 ChangeSignal string 5330 } 5331 5332 func DefaultVaultBlock() *Vault { 5333 return &Vault{ 5334 Env: true, 5335 ChangeMode: VaultChangeModeRestart, 5336 } 5337 } 5338 5339 // Copy returns a copy of this Vault block. 5340 func (v *Vault) Copy() *Vault { 5341 if v == nil { 5342 return nil 5343 } 5344 5345 nv := new(Vault) 5346 *nv = *v 5347 return nv 5348 } 5349 5350 func (v *Vault) Canonicalize() { 5351 if v.ChangeSignal != "" { 5352 v.ChangeSignal = strings.ToUpper(v.ChangeSignal) 5353 } 5354 } 5355 5356 // Validate returns if the Vault block is valid. 5357 func (v *Vault) Validate() error { 5358 if v == nil { 5359 return nil 5360 } 5361 5362 var mErr multierror.Error 5363 if len(v.Policies) == 0 { 5364 multierror.Append(&mErr, fmt.Errorf("Policy list cannot be empty")) 5365 } 5366 5367 for _, p := range v.Policies { 5368 if p == "root" { 5369 multierror.Append(&mErr, fmt.Errorf("Can not specify \"root\" policy")) 5370 } 5371 } 5372 5373 switch v.ChangeMode { 5374 case VaultChangeModeSignal: 5375 if v.ChangeSignal == "" { 5376 multierror.Append(&mErr, fmt.Errorf("Signal must be specified when using change mode %q", VaultChangeModeSignal)) 5377 } 5378 case VaultChangeModeNoop, VaultChangeModeRestart: 5379 default: 5380 multierror.Append(&mErr, fmt.Errorf("Unknown change mode %q", v.ChangeMode)) 5381 } 5382 5383 return mErr.ErrorOrNil() 5384 } 5385 5386 const ( 5387 // DeploymentStatuses are the various states a deployment can be be in 5388 DeploymentStatusRunning = "running" 5389 DeploymentStatusPaused = "paused" 5390 DeploymentStatusFailed = "failed" 5391 DeploymentStatusSuccessful = "successful" 5392 DeploymentStatusCancelled = "cancelled" 5393 5394 // DeploymentStatusDescriptions are the various descriptions of the states a 5395 // deployment can be in. 5396 DeploymentStatusDescriptionRunning = "Deployment is running" 5397 DeploymentStatusDescriptionRunningNeedsPromotion = "Deployment is running but requires promotion" 5398 DeploymentStatusDescriptionPaused = "Deployment is paused" 5399 DeploymentStatusDescriptionSuccessful = "Deployment completed successfully" 5400 DeploymentStatusDescriptionStoppedJob = "Cancelled because job is stopped" 5401 DeploymentStatusDescriptionNewerJob = "Cancelled due to newer version of job" 5402 DeploymentStatusDescriptionFailedAllocations = "Failed due to unhealthy allocations" 5403 DeploymentStatusDescriptionProgressDeadline = "Failed due to progress deadline" 5404 DeploymentStatusDescriptionFailedByUser = "Deployment marked as failed" 5405 ) 5406 5407 // DeploymentStatusDescriptionRollback is used to get the status description of 5408 // a deployment when rolling back to an older job. 5409 func DeploymentStatusDescriptionRollback(baseDescription string, jobVersion uint64) string { 5410 return fmt.Sprintf("%s - rolling back to job version %d", baseDescription, jobVersion) 5411 } 5412 5413 // DeploymentStatusDescriptionRollbackNoop is used to get the status description of 5414 // a deployment when rolling back is not possible because it has the same specification 5415 func DeploymentStatusDescriptionRollbackNoop(baseDescription string, jobVersion uint64) string { 5416 return fmt.Sprintf("%s - not rolling back to stable job version %d as current job has same specification", baseDescription, jobVersion) 5417 } 5418 5419 // DeploymentStatusDescriptionNoRollbackTarget is used to get the status description of 5420 // a deployment when there is no target to rollback to but autorevert is desired. 5421 func DeploymentStatusDescriptionNoRollbackTarget(baseDescription string) string { 5422 return fmt.Sprintf("%s - no stable job version to auto revert to", baseDescription) 5423 } 5424 5425 // Deployment is the object that represents a job deployment which is used to 5426 // transition a job between versions. 5427 type Deployment struct { 5428 // ID is a generated UUID for the deployment 5429 ID string 5430 5431 // Namespace is the namespace the deployment is created in 5432 Namespace string 5433 5434 // JobID is the job the deployment is created for 5435 JobID string 5436 5437 // JobVersion is the version of the job at which the deployment is tracking 5438 JobVersion uint64 5439 5440 // JobModifyIndex is the ModifyIndex of the job which the deployment is 5441 // tracking. 5442 JobModifyIndex uint64 5443 5444 // JobSpecModifyIndex is the JobModifyIndex of the job which the 5445 // deployment is tracking. 5446 JobSpecModifyIndex uint64 5447 5448 // JobCreateIndex is the create index of the job which the deployment is 5449 // tracking. It is needed so that if the job gets stopped and reran we can 5450 // present the correct list of deployments for the job and not old ones. 5451 JobCreateIndex uint64 5452 5453 // TaskGroups is the set of task groups effected by the deployment and their 5454 // current deployment status. 5455 TaskGroups map[string]*DeploymentState 5456 5457 // The status of the deployment 5458 Status string 5459 5460 // StatusDescription allows a human readable description of the deployment 5461 // status. 5462 StatusDescription string 5463 5464 CreateIndex uint64 5465 ModifyIndex uint64 5466 } 5467 5468 // NewDeployment creates a new deployment given the job. 5469 func NewDeployment(job *Job) *Deployment { 5470 return &Deployment{ 5471 ID: uuid.Generate(), 5472 Namespace: job.Namespace, 5473 JobID: job.ID, 5474 JobVersion: job.Version, 5475 JobModifyIndex: job.ModifyIndex, 5476 JobSpecModifyIndex: job.JobModifyIndex, 5477 JobCreateIndex: job.CreateIndex, 5478 Status: DeploymentStatusRunning, 5479 StatusDescription: DeploymentStatusDescriptionRunning, 5480 TaskGroups: make(map[string]*DeploymentState, len(job.TaskGroups)), 5481 } 5482 } 5483 5484 func (d *Deployment) Copy() *Deployment { 5485 if d == nil { 5486 return nil 5487 } 5488 5489 c := &Deployment{} 5490 *c = *d 5491 5492 c.TaskGroups = nil 5493 if l := len(d.TaskGroups); d.TaskGroups != nil { 5494 c.TaskGroups = make(map[string]*DeploymentState, l) 5495 for tg, s := range d.TaskGroups { 5496 c.TaskGroups[tg] = s.Copy() 5497 } 5498 } 5499 5500 return c 5501 } 5502 5503 // Active returns whether the deployment is active or terminal. 5504 func (d *Deployment) Active() bool { 5505 switch d.Status { 5506 case DeploymentStatusRunning, DeploymentStatusPaused: 5507 return true 5508 default: 5509 return false 5510 } 5511 } 5512 5513 // GetID is a helper for getting the ID when the object may be nil 5514 func (d *Deployment) GetID() string { 5515 if d == nil { 5516 return "" 5517 } 5518 return d.ID 5519 } 5520 5521 // HasPlacedCanaries returns whether the deployment has placed canaries 5522 func (d *Deployment) HasPlacedCanaries() bool { 5523 if d == nil || len(d.TaskGroups) == 0 { 5524 return false 5525 } 5526 for _, group := range d.TaskGroups { 5527 if len(group.PlacedCanaries) != 0 { 5528 return true 5529 } 5530 } 5531 return false 5532 } 5533 5534 // RequiresPromotion returns whether the deployment requires promotion to 5535 // continue 5536 func (d *Deployment) RequiresPromotion() bool { 5537 if d == nil || len(d.TaskGroups) == 0 || d.Status != DeploymentStatusRunning { 5538 return false 5539 } 5540 for _, group := range d.TaskGroups { 5541 if group.DesiredCanaries > 0 && !group.Promoted { 5542 return true 5543 } 5544 } 5545 return false 5546 } 5547 5548 func (d *Deployment) GoString() string { 5549 base := fmt.Sprintf("Deployment ID %q for job %q has status %q (%v):", d.ID, d.JobID, d.Status, d.StatusDescription) 5550 for group, state := range d.TaskGroups { 5551 base += fmt.Sprintf("\nTask Group %q has state:\n%#v", group, state) 5552 } 5553 return base 5554 } 5555 5556 // DeploymentState tracks the state of a deployment for a given task group. 5557 type DeploymentState struct { 5558 // AutoRevert marks whether the task group has indicated the job should be 5559 // reverted on failure 5560 AutoRevert bool 5561 5562 // ProgressDeadline is the deadline by which an allocation must transition 5563 // to healthy before the deployment is considered failed. 5564 ProgressDeadline time.Duration 5565 5566 // RequireProgressBy is the time by which an allocation must transition 5567 // to healthy before the deployment is considered failed. 5568 RequireProgressBy time.Time 5569 5570 // Promoted marks whether the canaries have been promoted 5571 Promoted bool 5572 5573 // PlacedCanaries is the set of placed canary allocations 5574 PlacedCanaries []string 5575 5576 // DesiredCanaries is the number of canaries that should be created. 5577 DesiredCanaries int 5578 5579 // DesiredTotal is the total number of allocations that should be created as 5580 // part of the deployment. 5581 DesiredTotal int 5582 5583 // PlacedAllocs is the number of allocations that have been placed 5584 PlacedAllocs int 5585 5586 // HealthyAllocs is the number of allocations that have been marked healthy. 5587 HealthyAllocs int 5588 5589 // UnhealthyAllocs are allocations that have been marked as unhealthy. 5590 UnhealthyAllocs int 5591 } 5592 5593 func (d *DeploymentState) GoString() string { 5594 base := fmt.Sprintf("\tDesired Total: %d", d.DesiredTotal) 5595 base += fmt.Sprintf("\n\tDesired Canaries: %d", d.DesiredCanaries) 5596 base += fmt.Sprintf("\n\tPlaced Canaries: %#v", d.PlacedCanaries) 5597 base += fmt.Sprintf("\n\tPromoted: %v", d.Promoted) 5598 base += fmt.Sprintf("\n\tPlaced: %d", d.PlacedAllocs) 5599 base += fmt.Sprintf("\n\tHealthy: %d", d.HealthyAllocs) 5600 base += fmt.Sprintf("\n\tUnhealthy: %d", d.UnhealthyAllocs) 5601 base += fmt.Sprintf("\n\tAutoRevert: %v", d.AutoRevert) 5602 return base 5603 } 5604 5605 func (d *DeploymentState) Copy() *DeploymentState { 5606 c := &DeploymentState{} 5607 *c = *d 5608 c.PlacedCanaries = helper.CopySliceString(d.PlacedCanaries) 5609 return c 5610 } 5611 5612 // DeploymentStatusUpdate is used to update the status of a given deployment 5613 type DeploymentStatusUpdate struct { 5614 // DeploymentID is the ID of the deployment to update 5615 DeploymentID string 5616 5617 // Status is the new status of the deployment. 5618 Status string 5619 5620 // StatusDescription is the new status description of the deployment. 5621 StatusDescription string 5622 } 5623 5624 // RescheduleTracker encapsulates previous reschedule events 5625 type RescheduleTracker struct { 5626 Events []*RescheduleEvent 5627 } 5628 5629 func (rt *RescheduleTracker) Copy() *RescheduleTracker { 5630 if rt == nil { 5631 return nil 5632 } 5633 nt := &RescheduleTracker{} 5634 *nt = *rt 5635 rescheduleEvents := make([]*RescheduleEvent, 0, len(rt.Events)) 5636 for _, tracker := range rt.Events { 5637 rescheduleEvents = append(rescheduleEvents, tracker.Copy()) 5638 } 5639 nt.Events = rescheduleEvents 5640 return nt 5641 } 5642 5643 // RescheduleEvent is used to keep track of previous attempts at rescheduling an allocation 5644 type RescheduleEvent struct { 5645 // RescheduleTime is the timestamp of a reschedule attempt 5646 RescheduleTime int64 5647 5648 // PrevAllocID is the ID of the previous allocation being restarted 5649 PrevAllocID string 5650 5651 // PrevNodeID is the node ID of the previous allocation 5652 PrevNodeID string 5653 5654 // Delay is the reschedule delay associated with the attempt 5655 Delay time.Duration 5656 } 5657 5658 func NewRescheduleEvent(rescheduleTime int64, prevAllocID string, prevNodeID string, delay time.Duration) *RescheduleEvent { 5659 return &RescheduleEvent{RescheduleTime: rescheduleTime, 5660 PrevAllocID: prevAllocID, 5661 PrevNodeID: prevNodeID, 5662 Delay: delay} 5663 } 5664 5665 func (re *RescheduleEvent) Copy() *RescheduleEvent { 5666 if re == nil { 5667 return nil 5668 } 5669 copy := new(RescheduleEvent) 5670 *copy = *re 5671 return copy 5672 } 5673 5674 // DesiredTransition is used to mark an allocation as having a desired state 5675 // transition. This information can be used by the scheduler to make the 5676 // correct decision. 5677 type DesiredTransition struct { 5678 // Migrate is used to indicate that this allocation should be stopped and 5679 // migrated to another node. 5680 Migrate *bool 5681 5682 // Reschedule is used to indicate that this allocation is eligible to be 5683 // rescheduled. Most allocations are automatically eligible for 5684 // rescheduling, so this field is only required when an allocation is not 5685 // automatically eligible. An example is an allocation that is part of a 5686 // deployment. 5687 Reschedule *bool 5688 5689 // ForceReschedule is used to indicate that this allocation must be rescheduled. 5690 // This field is only used when operators want to force a placement even if 5691 // a failed allocation is not eligible to be rescheduled 5692 ForceReschedule *bool 5693 } 5694 5695 // Merge merges the two desired transitions, preferring the values from the 5696 // passed in object. 5697 func (d *DesiredTransition) Merge(o *DesiredTransition) { 5698 if o.Migrate != nil { 5699 d.Migrate = o.Migrate 5700 } 5701 5702 if o.Reschedule != nil { 5703 d.Reschedule = o.Reschedule 5704 } 5705 5706 if o.ForceReschedule != nil { 5707 d.ForceReschedule = o.ForceReschedule 5708 } 5709 } 5710 5711 // ShouldMigrate returns whether the transition object dictates a migration. 5712 func (d *DesiredTransition) ShouldMigrate() bool { 5713 return d.Migrate != nil && *d.Migrate 5714 } 5715 5716 // ShouldReschedule returns whether the transition object dictates a 5717 // rescheduling. 5718 func (d *DesiredTransition) ShouldReschedule() bool { 5719 return d.Reschedule != nil && *d.Reschedule 5720 } 5721 5722 // ShouldForceReschedule returns whether the transition object dictates a 5723 // forced rescheduling. 5724 func (d *DesiredTransition) ShouldForceReschedule() bool { 5725 if d == nil { 5726 return false 5727 } 5728 return d.ForceReschedule != nil && *d.ForceReschedule 5729 } 5730 5731 const ( 5732 AllocDesiredStatusRun = "run" // Allocation should run 5733 AllocDesiredStatusStop = "stop" // Allocation should stop 5734 AllocDesiredStatusEvict = "evict" // Allocation should stop, and was evicted 5735 ) 5736 5737 const ( 5738 AllocClientStatusPending = "pending" 5739 AllocClientStatusRunning = "running" 5740 AllocClientStatusComplete = "complete" 5741 AllocClientStatusFailed = "failed" 5742 AllocClientStatusLost = "lost" 5743 ) 5744 5745 // Allocation is used to allocate the placement of a task group to a node. 5746 type Allocation struct { 5747 // ID of the allocation (UUID) 5748 ID string 5749 5750 // Namespace is the namespace the allocation is created in 5751 Namespace string 5752 5753 // ID of the evaluation that generated this allocation 5754 EvalID string 5755 5756 // Name is a logical name of the allocation. 5757 Name string 5758 5759 // NodeID is the node this is being placed on 5760 NodeID string 5761 5762 // Job is the parent job of the task group being allocated. 5763 // This is copied at allocation time to avoid issues if the job 5764 // definition is updated. 5765 JobID string 5766 Job *Job 5767 5768 // TaskGroup is the name of the task group that should be run 5769 TaskGroup string 5770 5771 // Resources is the total set of resources allocated as part 5772 // of this allocation of the task group. 5773 Resources *Resources 5774 5775 // SharedResources are the resources that are shared by all the tasks in an 5776 // allocation 5777 SharedResources *Resources 5778 5779 // TaskResources is the set of resources allocated to each 5780 // task. These should sum to the total Resources. 5781 TaskResources map[string]*Resources 5782 5783 // Metrics associated with this allocation 5784 Metrics *AllocMetric 5785 5786 // Desired Status of the allocation on the client 5787 DesiredStatus string 5788 5789 // DesiredStatusDescription is meant to provide more human useful information 5790 DesiredDescription string 5791 5792 // DesiredTransition is used to indicate that a state transition 5793 // is desired for a given reason. 5794 DesiredTransition DesiredTransition 5795 5796 // Status of the allocation on the client 5797 ClientStatus string 5798 5799 // ClientStatusDescription is meant to provide more human useful information 5800 ClientDescription string 5801 5802 // TaskStates stores the state of each task, 5803 TaskStates map[string]*TaskState 5804 5805 // PreviousAllocation is the allocation that this allocation is replacing 5806 PreviousAllocation string 5807 5808 // NextAllocation is the allocation that this allocation is being replaced by 5809 NextAllocation string 5810 5811 // DeploymentID identifies an allocation as being created from a 5812 // particular deployment 5813 DeploymentID string 5814 5815 // DeploymentStatus captures the status of the allocation as part of the 5816 // given deployment 5817 DeploymentStatus *AllocDeploymentStatus 5818 5819 // RescheduleTrackers captures details of previous reschedule attempts of the allocation 5820 RescheduleTracker *RescheduleTracker 5821 5822 // FollowupEvalID captures a follow up evaluation created to handle a failed allocation 5823 // that can be rescheduled in the future 5824 FollowupEvalID string 5825 5826 // Raft Indexes 5827 CreateIndex uint64 5828 ModifyIndex uint64 5829 5830 // AllocModifyIndex is not updated when the client updates allocations. This 5831 // lets the client pull only the allocs updated by the server. 5832 AllocModifyIndex uint64 5833 5834 // CreateTime is the time the allocation has finished scheduling and been 5835 // verified by the plan applier. 5836 CreateTime int64 5837 5838 // ModifyTime is the time the allocation was last updated. 5839 ModifyTime int64 5840 } 5841 5842 // Index returns the index of the allocation. If the allocation is from a task 5843 // group with count greater than 1, there will be multiple allocations for it. 5844 func (a *Allocation) Index() uint { 5845 l := len(a.Name) 5846 prefix := len(a.JobID) + len(a.TaskGroup) + 2 5847 if l <= 3 || l <= prefix { 5848 return uint(0) 5849 } 5850 5851 strNum := a.Name[prefix : len(a.Name)-1] 5852 num, _ := strconv.Atoi(strNum) 5853 return uint(num) 5854 } 5855 5856 func (a *Allocation) Copy() *Allocation { 5857 return a.copyImpl(true) 5858 } 5859 5860 // Copy provides a copy of the allocation but doesn't deep copy the job 5861 func (a *Allocation) CopySkipJob() *Allocation { 5862 return a.copyImpl(false) 5863 } 5864 5865 func (a *Allocation) copyImpl(job bool) *Allocation { 5866 if a == nil { 5867 return nil 5868 } 5869 na := new(Allocation) 5870 *na = *a 5871 5872 if job { 5873 na.Job = na.Job.Copy() 5874 } 5875 5876 na.Resources = na.Resources.Copy() 5877 na.SharedResources = na.SharedResources.Copy() 5878 5879 if a.TaskResources != nil { 5880 tr := make(map[string]*Resources, len(na.TaskResources)) 5881 for task, resource := range na.TaskResources { 5882 tr[task] = resource.Copy() 5883 } 5884 na.TaskResources = tr 5885 } 5886 5887 na.Metrics = na.Metrics.Copy() 5888 na.DeploymentStatus = na.DeploymentStatus.Copy() 5889 5890 if a.TaskStates != nil { 5891 ts := make(map[string]*TaskState, len(na.TaskStates)) 5892 for task, state := range na.TaskStates { 5893 ts[task] = state.Copy() 5894 } 5895 na.TaskStates = ts 5896 } 5897 5898 na.RescheduleTracker = a.RescheduleTracker.Copy() 5899 return na 5900 } 5901 5902 // TerminalStatus returns if the desired or actual status is terminal and 5903 // will no longer transition. 5904 func (a *Allocation) TerminalStatus() bool { 5905 // First check the desired state and if that isn't terminal, check client 5906 // state. 5907 switch a.DesiredStatus { 5908 case AllocDesiredStatusStop, AllocDesiredStatusEvict: 5909 return true 5910 default: 5911 } 5912 5913 return a.ClientTerminalStatus() 5914 } 5915 5916 // ClientTerminalStatus returns if the client status is terminal and will no longer transition 5917 func (a *Allocation) ClientTerminalStatus() bool { 5918 switch a.ClientStatus { 5919 case AllocClientStatusComplete, AllocClientStatusFailed, AllocClientStatusLost: 5920 return true 5921 default: 5922 return false 5923 } 5924 } 5925 5926 // ShouldReschedule returns if the allocation is eligible to be rescheduled according 5927 // to its status and ReschedulePolicy given its failure time 5928 func (a *Allocation) ShouldReschedule(reschedulePolicy *ReschedulePolicy, failTime time.Time) bool { 5929 // First check the desired state 5930 switch a.DesiredStatus { 5931 case AllocDesiredStatusStop, AllocDesiredStatusEvict: 5932 return false 5933 default: 5934 } 5935 switch a.ClientStatus { 5936 case AllocClientStatusFailed: 5937 return a.RescheduleEligible(reschedulePolicy, failTime) 5938 default: 5939 return false 5940 } 5941 } 5942 5943 // RescheduleEligible returns if the allocation is eligible to be rescheduled according 5944 // to its ReschedulePolicy and the current state of its reschedule trackers 5945 func (a *Allocation) RescheduleEligible(reschedulePolicy *ReschedulePolicy, failTime time.Time) bool { 5946 if reschedulePolicy == nil { 5947 return false 5948 } 5949 attempts := reschedulePolicy.Attempts 5950 interval := reschedulePolicy.Interval 5951 enabled := attempts > 0 || reschedulePolicy.Unlimited 5952 if !enabled { 5953 return false 5954 } 5955 if reschedulePolicy.Unlimited { 5956 return true 5957 } 5958 // Early return true if there are no attempts yet and the number of allowed attempts is > 0 5959 if (a.RescheduleTracker == nil || len(a.RescheduleTracker.Events) == 0) && attempts > 0 { 5960 return true 5961 } 5962 attempted := 0 5963 for j := len(a.RescheduleTracker.Events) - 1; j >= 0; j-- { 5964 lastAttempt := a.RescheduleTracker.Events[j].RescheduleTime 5965 timeDiff := failTime.UTC().UnixNano() - lastAttempt 5966 if timeDiff < interval.Nanoseconds() { 5967 attempted += 1 5968 } 5969 } 5970 return attempted < attempts 5971 } 5972 5973 // LastEventTime is the time of the last task event in the allocation. 5974 // It is used to determine allocation failure time. If the FinishedAt field 5975 // is not set, the alloc's modify time is used 5976 func (a *Allocation) LastEventTime() time.Time { 5977 var lastEventTime time.Time 5978 if a.TaskStates != nil { 5979 for _, s := range a.TaskStates { 5980 if lastEventTime.IsZero() || s.FinishedAt.After(lastEventTime) { 5981 lastEventTime = s.FinishedAt 5982 } 5983 } 5984 } 5985 5986 if lastEventTime.IsZero() { 5987 return time.Unix(0, a.ModifyTime).UTC() 5988 } 5989 return lastEventTime 5990 } 5991 5992 // ReschedulePolicy returns the reschedule policy based on the task group 5993 func (a *Allocation) ReschedulePolicy() *ReschedulePolicy { 5994 tg := a.Job.LookupTaskGroup(a.TaskGroup) 5995 if tg == nil { 5996 return nil 5997 } 5998 return tg.ReschedulePolicy 5999 } 6000 6001 // NextRescheduleTime returns a time on or after which the allocation is eligible to be rescheduled, 6002 // and whether the next reschedule time is within policy's interval if the policy doesn't allow unlimited reschedules 6003 func (a *Allocation) NextRescheduleTime() (time.Time, bool) { 6004 failTime := a.LastEventTime() 6005 reschedulePolicy := a.ReschedulePolicy() 6006 if a.DesiredStatus == AllocDesiredStatusStop || a.ClientStatus != AllocClientStatusFailed || failTime.IsZero() || reschedulePolicy == nil { 6007 return time.Time{}, false 6008 } 6009 6010 nextDelay := a.NextDelay() 6011 nextRescheduleTime := failTime.Add(nextDelay) 6012 rescheduleEligible := reschedulePolicy.Unlimited || (reschedulePolicy.Attempts > 0 && a.RescheduleTracker == nil) 6013 if reschedulePolicy.Attempts > 0 && a.RescheduleTracker != nil && a.RescheduleTracker.Events != nil { 6014 // Check for eligibility based on the interval if max attempts is set 6015 attempted := 0 6016 for j := len(a.RescheduleTracker.Events) - 1; j >= 0; j-- { 6017 lastAttempt := a.RescheduleTracker.Events[j].RescheduleTime 6018 timeDiff := failTime.UTC().UnixNano() - lastAttempt 6019 if timeDiff < reschedulePolicy.Interval.Nanoseconds() { 6020 attempted += 1 6021 } 6022 } 6023 rescheduleEligible = attempted < reschedulePolicy.Attempts && nextDelay < reschedulePolicy.Interval 6024 } 6025 return nextRescheduleTime, rescheduleEligible 6026 } 6027 6028 // NextDelay returns a duration after which the allocation can be rescheduled. 6029 // It is calculated according to the delay function and previous reschedule attempts. 6030 func (a *Allocation) NextDelay() time.Duration { 6031 policy := a.ReschedulePolicy() 6032 // Can be nil if the task group was updated to remove its reschedule policy 6033 if policy == nil { 6034 return 0 6035 } 6036 delayDur := policy.Delay 6037 if a.RescheduleTracker == nil || a.RescheduleTracker.Events == nil || len(a.RescheduleTracker.Events) == 0 { 6038 return delayDur 6039 } 6040 events := a.RescheduleTracker.Events 6041 switch policy.DelayFunction { 6042 case "exponential": 6043 delayDur = a.RescheduleTracker.Events[len(a.RescheduleTracker.Events)-1].Delay * 2 6044 case "fibonacci": 6045 if len(events) >= 2 { 6046 fibN1Delay := events[len(events)-1].Delay 6047 fibN2Delay := events[len(events)-2].Delay 6048 // Handle reset of delay ceiling which should cause 6049 // a new series to start 6050 if fibN2Delay == policy.MaxDelay && fibN1Delay == policy.Delay { 6051 delayDur = fibN1Delay 6052 } else { 6053 delayDur = fibN1Delay + fibN2Delay 6054 } 6055 } 6056 default: 6057 return delayDur 6058 } 6059 if policy.MaxDelay > 0 && delayDur > policy.MaxDelay { 6060 delayDur = policy.MaxDelay 6061 // check if delay needs to be reset 6062 6063 lastRescheduleEvent := a.RescheduleTracker.Events[len(a.RescheduleTracker.Events)-1] 6064 timeDiff := a.LastEventTime().UTC().UnixNano() - lastRescheduleEvent.RescheduleTime 6065 if timeDiff > delayDur.Nanoseconds() { 6066 delayDur = policy.Delay 6067 } 6068 6069 } 6070 6071 return delayDur 6072 } 6073 6074 // Terminated returns if the allocation is in a terminal state on a client. 6075 func (a *Allocation) Terminated() bool { 6076 if a.ClientStatus == AllocClientStatusFailed || 6077 a.ClientStatus == AllocClientStatusComplete || 6078 a.ClientStatus == AllocClientStatusLost { 6079 return true 6080 } 6081 return false 6082 } 6083 6084 // RanSuccessfully returns whether the client has ran the allocation and all 6085 // tasks finished successfully. Critically this function returns whether the 6086 // allocation has ran to completion and not just that the alloc has converged to 6087 // its desired state. That is to say that a batch allocation must have finished 6088 // with exit code 0 on all task groups. This doesn't really have meaning on a 6089 // non-batch allocation because a service and system allocation should not 6090 // finish. 6091 func (a *Allocation) RanSuccessfully() bool { 6092 // Handle the case the client hasn't started the allocation. 6093 if len(a.TaskStates) == 0 { 6094 return false 6095 } 6096 6097 // Check to see if all the tasks finished successfully in the allocation 6098 allSuccess := true 6099 for _, state := range a.TaskStates { 6100 allSuccess = allSuccess && state.Successful() 6101 } 6102 6103 return allSuccess 6104 } 6105 6106 // ShouldMigrate returns if the allocation needs data migration 6107 func (a *Allocation) ShouldMigrate() bool { 6108 if a.PreviousAllocation == "" { 6109 return false 6110 } 6111 6112 if a.DesiredStatus == AllocDesiredStatusStop || a.DesiredStatus == AllocDesiredStatusEvict { 6113 return false 6114 } 6115 6116 tg := a.Job.LookupTaskGroup(a.TaskGroup) 6117 6118 // if the task group is nil or the ephemeral disk block isn't present then 6119 // we won't migrate 6120 if tg == nil || tg.EphemeralDisk == nil { 6121 return false 6122 } 6123 6124 // We won't migrate any data is the user hasn't enabled migration or the 6125 // disk is not marked as sticky 6126 if !tg.EphemeralDisk.Migrate || !tg.EphemeralDisk.Sticky { 6127 return false 6128 } 6129 6130 return true 6131 } 6132 6133 // SetEventDisplayMessage populates the display message if its not already set, 6134 // a temporary fix to handle old allocations that don't have it. 6135 // This method will be removed in a future release. 6136 func (a *Allocation) SetEventDisplayMessages() { 6137 setDisplayMsg(a.TaskStates) 6138 } 6139 6140 // Stub returns a list stub for the allocation 6141 func (a *Allocation) Stub() *AllocListStub { 6142 return &AllocListStub{ 6143 ID: a.ID, 6144 EvalID: a.EvalID, 6145 Name: a.Name, 6146 NodeID: a.NodeID, 6147 JobID: a.JobID, 6148 JobVersion: a.Job.Version, 6149 TaskGroup: a.TaskGroup, 6150 DesiredStatus: a.DesiredStatus, 6151 DesiredDescription: a.DesiredDescription, 6152 ClientStatus: a.ClientStatus, 6153 ClientDescription: a.ClientDescription, 6154 DesiredTransition: a.DesiredTransition, 6155 TaskStates: a.TaskStates, 6156 DeploymentStatus: a.DeploymentStatus, 6157 FollowupEvalID: a.FollowupEvalID, 6158 RescheduleTracker: a.RescheduleTracker, 6159 CreateIndex: a.CreateIndex, 6160 ModifyIndex: a.ModifyIndex, 6161 CreateTime: a.CreateTime, 6162 ModifyTime: a.ModifyTime, 6163 } 6164 } 6165 6166 // AllocListStub is used to return a subset of alloc information 6167 type AllocListStub struct { 6168 ID string 6169 EvalID string 6170 Name string 6171 NodeID string 6172 JobID string 6173 JobVersion uint64 6174 TaskGroup string 6175 DesiredStatus string 6176 DesiredDescription string 6177 ClientStatus string 6178 ClientDescription string 6179 DesiredTransition DesiredTransition 6180 TaskStates map[string]*TaskState 6181 DeploymentStatus *AllocDeploymentStatus 6182 FollowupEvalID string 6183 RescheduleTracker *RescheduleTracker 6184 CreateIndex uint64 6185 ModifyIndex uint64 6186 CreateTime int64 6187 ModifyTime int64 6188 } 6189 6190 // SetEventDisplayMessage populates the display message if its not already set, 6191 // a temporary fix to handle old allocations that don't have it. 6192 // This method will be removed in a future release. 6193 func (a *AllocListStub) SetEventDisplayMessages() { 6194 setDisplayMsg(a.TaskStates) 6195 } 6196 6197 func setDisplayMsg(taskStates map[string]*TaskState) { 6198 if taskStates != nil { 6199 for _, taskState := range taskStates { 6200 for _, event := range taskState.Events { 6201 event.PopulateEventDisplayMessage() 6202 } 6203 } 6204 } 6205 } 6206 6207 // AllocMetric is used to track various metrics while attempting 6208 // to make an allocation. These are used to debug a job, or to better 6209 // understand the pressure within the system. 6210 type AllocMetric struct { 6211 // NodesEvaluated is the number of nodes that were evaluated 6212 NodesEvaluated int 6213 6214 // NodesFiltered is the number of nodes filtered due to a constraint 6215 NodesFiltered int 6216 6217 // NodesAvailable is the number of nodes available for evaluation per DC. 6218 NodesAvailable map[string]int 6219 6220 // ClassFiltered is the number of nodes filtered by class 6221 ClassFiltered map[string]int 6222 6223 // ConstraintFiltered is the number of failures caused by constraint 6224 ConstraintFiltered map[string]int 6225 6226 // NodesExhausted is the number of nodes skipped due to being 6227 // exhausted of at least one resource 6228 NodesExhausted int 6229 6230 // ClassExhausted is the number of nodes exhausted by class 6231 ClassExhausted map[string]int 6232 6233 // DimensionExhausted provides the count by dimension or reason 6234 DimensionExhausted map[string]int 6235 6236 // QuotaExhausted provides the exhausted dimensions 6237 QuotaExhausted []string 6238 6239 // Scores is the scores of the final few nodes remaining 6240 // for placement. The top score is typically selected. 6241 Scores map[string]float64 6242 6243 // AllocationTime is a measure of how long the allocation 6244 // attempt took. This can affect performance and SLAs. 6245 AllocationTime time.Duration 6246 6247 // CoalescedFailures indicates the number of other 6248 // allocations that were coalesced into this failed allocation. 6249 // This is to prevent creating many failed allocations for a 6250 // single task group. 6251 CoalescedFailures int 6252 } 6253 6254 func (a *AllocMetric) Copy() *AllocMetric { 6255 if a == nil { 6256 return nil 6257 } 6258 na := new(AllocMetric) 6259 *na = *a 6260 na.NodesAvailable = helper.CopyMapStringInt(na.NodesAvailable) 6261 na.ClassFiltered = helper.CopyMapStringInt(na.ClassFiltered) 6262 na.ConstraintFiltered = helper.CopyMapStringInt(na.ConstraintFiltered) 6263 na.ClassExhausted = helper.CopyMapStringInt(na.ClassExhausted) 6264 na.DimensionExhausted = helper.CopyMapStringInt(na.DimensionExhausted) 6265 na.QuotaExhausted = helper.CopySliceString(na.QuotaExhausted) 6266 na.Scores = helper.CopyMapStringFloat64(na.Scores) 6267 return na 6268 } 6269 6270 func (a *AllocMetric) EvaluateNode() { 6271 a.NodesEvaluated += 1 6272 } 6273 6274 func (a *AllocMetric) FilterNode(node *Node, constraint string) { 6275 a.NodesFiltered += 1 6276 if node != nil && node.NodeClass != "" { 6277 if a.ClassFiltered == nil { 6278 a.ClassFiltered = make(map[string]int) 6279 } 6280 a.ClassFiltered[node.NodeClass] += 1 6281 } 6282 if constraint != "" { 6283 if a.ConstraintFiltered == nil { 6284 a.ConstraintFiltered = make(map[string]int) 6285 } 6286 a.ConstraintFiltered[constraint] += 1 6287 } 6288 } 6289 6290 func (a *AllocMetric) ExhaustedNode(node *Node, dimension string) { 6291 a.NodesExhausted += 1 6292 if node != nil && node.NodeClass != "" { 6293 if a.ClassExhausted == nil { 6294 a.ClassExhausted = make(map[string]int) 6295 } 6296 a.ClassExhausted[node.NodeClass] += 1 6297 } 6298 if dimension != "" { 6299 if a.DimensionExhausted == nil { 6300 a.DimensionExhausted = make(map[string]int) 6301 } 6302 a.DimensionExhausted[dimension] += 1 6303 } 6304 } 6305 6306 func (a *AllocMetric) ExhaustQuota(dimensions []string) { 6307 if a.QuotaExhausted == nil { 6308 a.QuotaExhausted = make([]string, 0, len(dimensions)) 6309 } 6310 6311 a.QuotaExhausted = append(a.QuotaExhausted, dimensions...) 6312 } 6313 6314 func (a *AllocMetric) ScoreNode(node *Node, name string, score float64) { 6315 if a.Scores == nil { 6316 a.Scores = make(map[string]float64) 6317 } 6318 key := fmt.Sprintf("%s.%s", node.ID, name) 6319 a.Scores[key] = score 6320 } 6321 6322 // AllocDeploymentStatus captures the status of the allocation as part of the 6323 // deployment. This can include things like if the allocation has been marked as 6324 // healthy. 6325 type AllocDeploymentStatus struct { 6326 // Healthy marks whether the allocation has been marked healthy or unhealthy 6327 // as part of a deployment. It can be unset if it has neither been marked 6328 // healthy or unhealthy. 6329 Healthy *bool 6330 6331 // Timestamp is the time at which the health status was set. 6332 Timestamp time.Time 6333 6334 // Canary marks whether the allocation is a canary or not. A canary that has 6335 // been promoted will have this field set to false. 6336 Canary bool 6337 6338 // ModifyIndex is the raft index in which the deployment status was last 6339 // changed. 6340 ModifyIndex uint64 6341 } 6342 6343 // HasHealth returns true if the allocation has its health set. 6344 func (a *AllocDeploymentStatus) HasHealth() bool { 6345 return a != nil && a.Healthy != nil 6346 } 6347 6348 // IsHealthy returns if the allocation is marked as healthy as part of a 6349 // deployment 6350 func (a *AllocDeploymentStatus) IsHealthy() bool { 6351 if a == nil { 6352 return false 6353 } 6354 6355 return a.Healthy != nil && *a.Healthy 6356 } 6357 6358 // IsUnhealthy returns if the allocation is marked as unhealthy as part of a 6359 // deployment 6360 func (a *AllocDeploymentStatus) IsUnhealthy() bool { 6361 if a == nil { 6362 return false 6363 } 6364 6365 return a.Healthy != nil && !*a.Healthy 6366 } 6367 6368 // IsCanary returns if the allocation is marked as a canary 6369 func (a *AllocDeploymentStatus) IsCanary() bool { 6370 if a == nil { 6371 return false 6372 } 6373 6374 return a.Canary 6375 } 6376 6377 func (a *AllocDeploymentStatus) Copy() *AllocDeploymentStatus { 6378 if a == nil { 6379 return nil 6380 } 6381 6382 c := new(AllocDeploymentStatus) 6383 *c = *a 6384 6385 if a.Healthy != nil { 6386 c.Healthy = helper.BoolToPtr(*a.Healthy) 6387 } 6388 6389 return c 6390 } 6391 6392 const ( 6393 EvalStatusBlocked = "blocked" 6394 EvalStatusPending = "pending" 6395 EvalStatusComplete = "complete" 6396 EvalStatusFailed = "failed" 6397 EvalStatusCancelled = "canceled" 6398 ) 6399 6400 const ( 6401 EvalTriggerJobRegister = "job-register" 6402 EvalTriggerJobDeregister = "job-deregister" 6403 EvalTriggerPeriodicJob = "periodic-job" 6404 EvalTriggerNodeDrain = "node-drain" 6405 EvalTriggerNodeUpdate = "node-update" 6406 EvalTriggerScheduled = "scheduled" 6407 EvalTriggerRollingUpdate = "rolling-update" 6408 EvalTriggerDeploymentWatcher = "deployment-watcher" 6409 EvalTriggerFailedFollowUp = "failed-follow-up" 6410 EvalTriggerMaxPlans = "max-plan-attempts" 6411 EvalTriggerRetryFailedAlloc = "alloc-failure" 6412 EvalTriggerQueuedAllocs = "queued-allocs" 6413 ) 6414 6415 const ( 6416 // CoreJobEvalGC is used for the garbage collection of evaluations 6417 // and allocations. We periodically scan evaluations in a terminal state, 6418 // in which all the corresponding allocations are also terminal. We 6419 // delete these out of the system to bound the state. 6420 CoreJobEvalGC = "eval-gc" 6421 6422 // CoreJobNodeGC is used for the garbage collection of failed nodes. 6423 // We periodically scan nodes in a terminal state, and if they have no 6424 // corresponding allocations we delete these out of the system. 6425 CoreJobNodeGC = "node-gc" 6426 6427 // CoreJobJobGC is used for the garbage collection of eligible jobs. We 6428 // periodically scan garbage collectible jobs and check if both their 6429 // evaluations and allocations are terminal. If so, we delete these out of 6430 // the system. 6431 CoreJobJobGC = "job-gc" 6432 6433 // CoreJobDeploymentGC is used for the garbage collection of eligible 6434 // deployments. We periodically scan garbage collectible deployments and 6435 // check if they are terminal. If so, we delete these out of the system. 6436 CoreJobDeploymentGC = "deployment-gc" 6437 6438 // CoreJobForceGC is used to force garbage collection of all GCable objects. 6439 CoreJobForceGC = "force-gc" 6440 ) 6441 6442 // Evaluation is used anytime we need to apply business logic as a result 6443 // of a change to our desired state (job specification) or the emergent state 6444 // (registered nodes). When the inputs change, we need to "evaluate" them, 6445 // potentially taking action (allocation of work) or doing nothing if the state 6446 // of the world does not require it. 6447 type Evaluation struct { 6448 // ID is a randomly generated UUID used for this evaluation. This 6449 // is assigned upon the creation of the evaluation. 6450 ID string 6451 6452 // Namespace is the namespace the evaluation is created in 6453 Namespace string 6454 6455 // Priority is used to control scheduling importance and if this job 6456 // can preempt other jobs. 6457 Priority int 6458 6459 // Type is used to control which schedulers are available to handle 6460 // this evaluation. 6461 Type string 6462 6463 // TriggeredBy is used to give some insight into why this Eval 6464 // was created. (Job change, node failure, alloc failure, etc). 6465 TriggeredBy string 6466 6467 // JobID is the job this evaluation is scoped to. Evaluations cannot 6468 // be run in parallel for a given JobID, so we serialize on this. 6469 JobID string 6470 6471 // JobModifyIndex is the modify index of the job at the time 6472 // the evaluation was created 6473 JobModifyIndex uint64 6474 6475 // NodeID is the node that was affected triggering the evaluation. 6476 NodeID string 6477 6478 // NodeModifyIndex is the modify index of the node at the time 6479 // the evaluation was created 6480 NodeModifyIndex uint64 6481 6482 // DeploymentID is the ID of the deployment that triggered the evaluation. 6483 DeploymentID string 6484 6485 // Status of the evaluation 6486 Status string 6487 6488 // StatusDescription is meant to provide more human useful information 6489 StatusDescription string 6490 6491 // Wait is a minimum wait time for running the eval. This is used to 6492 // support a rolling upgrade in versions prior to 0.7.0 6493 // Deprecated 6494 Wait time.Duration 6495 6496 // WaitUntil is the time when this eval should be run. This is used to 6497 // supported delayed rescheduling of failed allocations 6498 WaitUntil time.Time 6499 6500 // NextEval is the evaluation ID for the eval created to do a followup. 6501 // This is used to support rolling upgrades, where we need a chain of evaluations. 6502 NextEval string 6503 6504 // PreviousEval is the evaluation ID for the eval creating this one to do a followup. 6505 // This is used to support rolling upgrades, where we need a chain of evaluations. 6506 PreviousEval string 6507 6508 // BlockedEval is the evaluation ID for a created blocked eval. A 6509 // blocked eval will be created if all allocations could not be placed due 6510 // to constraints or lacking resources. 6511 BlockedEval string 6512 6513 // FailedTGAllocs are task groups which have allocations that could not be 6514 // made, but the metrics are persisted so that the user can use the feedback 6515 // to determine the cause. 6516 FailedTGAllocs map[string]*AllocMetric 6517 6518 // ClassEligibility tracks computed node classes that have been explicitly 6519 // marked as eligible or ineligible. 6520 ClassEligibility map[string]bool 6521 6522 // QuotaLimitReached marks whether a quota limit was reached for the 6523 // evaluation. 6524 QuotaLimitReached string 6525 6526 // EscapedComputedClass marks whether the job has constraints that are not 6527 // captured by computed node classes. 6528 EscapedComputedClass bool 6529 6530 // AnnotatePlan triggers the scheduler to provide additional annotations 6531 // during the evaluation. This should not be set during normal operations. 6532 AnnotatePlan bool 6533 6534 // QueuedAllocations is the number of unplaced allocations at the time the 6535 // evaluation was processed. The map is keyed by Task Group names. 6536 QueuedAllocations map[string]int 6537 6538 // LeaderACL provides the ACL token to when issuing RPCs back to the 6539 // leader. This will be a valid management token as long as the leader is 6540 // active. This should not ever be exposed via the API. 6541 LeaderACL string 6542 6543 // SnapshotIndex is the Raft index of the snapshot used to process the 6544 // evaluation. The index will either be set when it has gone through the 6545 // scheduler or if a blocked evaluation is being created. The index is set 6546 // in this case so we can determine if an early unblocking is required since 6547 // capacity has changed since the evaluation was created. This can result in 6548 // the SnapshotIndex being less than the CreateIndex. 6549 SnapshotIndex uint64 6550 6551 // Raft Indexes 6552 CreateIndex uint64 6553 ModifyIndex uint64 6554 } 6555 6556 // TerminalStatus returns if the current status is terminal and 6557 // will no longer transition. 6558 func (e *Evaluation) TerminalStatus() bool { 6559 switch e.Status { 6560 case EvalStatusComplete, EvalStatusFailed, EvalStatusCancelled: 6561 return true 6562 default: 6563 return false 6564 } 6565 } 6566 6567 func (e *Evaluation) GoString() string { 6568 return fmt.Sprintf("<Eval %q JobID: %q Namespace: %q>", e.ID, e.JobID, e.Namespace) 6569 } 6570 6571 func (e *Evaluation) Copy() *Evaluation { 6572 if e == nil { 6573 return nil 6574 } 6575 ne := new(Evaluation) 6576 *ne = *e 6577 6578 // Copy ClassEligibility 6579 if e.ClassEligibility != nil { 6580 classes := make(map[string]bool, len(e.ClassEligibility)) 6581 for class, elig := range e.ClassEligibility { 6582 classes[class] = elig 6583 } 6584 ne.ClassEligibility = classes 6585 } 6586 6587 // Copy FailedTGAllocs 6588 if e.FailedTGAllocs != nil { 6589 failedTGs := make(map[string]*AllocMetric, len(e.FailedTGAllocs)) 6590 for tg, metric := range e.FailedTGAllocs { 6591 failedTGs[tg] = metric.Copy() 6592 } 6593 ne.FailedTGAllocs = failedTGs 6594 } 6595 6596 // Copy queued allocations 6597 if e.QueuedAllocations != nil { 6598 queuedAllocations := make(map[string]int, len(e.QueuedAllocations)) 6599 for tg, num := range e.QueuedAllocations { 6600 queuedAllocations[tg] = num 6601 } 6602 ne.QueuedAllocations = queuedAllocations 6603 } 6604 6605 return ne 6606 } 6607 6608 // ShouldEnqueue checks if a given evaluation should be enqueued into the 6609 // eval_broker 6610 func (e *Evaluation) ShouldEnqueue() bool { 6611 switch e.Status { 6612 case EvalStatusPending: 6613 return true 6614 case EvalStatusComplete, EvalStatusFailed, EvalStatusBlocked, EvalStatusCancelled: 6615 return false 6616 default: 6617 panic(fmt.Sprintf("unhandled evaluation (%s) status %s", e.ID, e.Status)) 6618 } 6619 } 6620 6621 // ShouldBlock checks if a given evaluation should be entered into the blocked 6622 // eval tracker. 6623 func (e *Evaluation) ShouldBlock() bool { 6624 switch e.Status { 6625 case EvalStatusBlocked: 6626 return true 6627 case EvalStatusComplete, EvalStatusFailed, EvalStatusPending, EvalStatusCancelled: 6628 return false 6629 default: 6630 panic(fmt.Sprintf("unhandled evaluation (%s) status %s", e.ID, e.Status)) 6631 } 6632 } 6633 6634 // MakePlan is used to make a plan from the given evaluation 6635 // for a given Job 6636 func (e *Evaluation) MakePlan(j *Job) *Plan { 6637 p := &Plan{ 6638 EvalID: e.ID, 6639 Priority: e.Priority, 6640 Job: j, 6641 NodeUpdate: make(map[string][]*Allocation), 6642 NodeAllocation: make(map[string][]*Allocation), 6643 } 6644 if j != nil { 6645 p.AllAtOnce = j.AllAtOnce 6646 } 6647 return p 6648 } 6649 6650 // NextRollingEval creates an evaluation to followup this eval for rolling updates 6651 func (e *Evaluation) NextRollingEval(wait time.Duration) *Evaluation { 6652 return &Evaluation{ 6653 ID: uuid.Generate(), 6654 Namespace: e.Namespace, 6655 Priority: e.Priority, 6656 Type: e.Type, 6657 TriggeredBy: EvalTriggerRollingUpdate, 6658 JobID: e.JobID, 6659 JobModifyIndex: e.JobModifyIndex, 6660 Status: EvalStatusPending, 6661 Wait: wait, 6662 PreviousEval: e.ID, 6663 } 6664 } 6665 6666 // CreateBlockedEval creates a blocked evaluation to followup this eval to place any 6667 // failed allocations. It takes the classes marked explicitly eligible or 6668 // ineligible, whether the job has escaped computed node classes and whether the 6669 // quota limit was reached. 6670 func (e *Evaluation) CreateBlockedEval(classEligibility map[string]bool, 6671 escaped bool, quotaReached string) *Evaluation { 6672 6673 return &Evaluation{ 6674 ID: uuid.Generate(), 6675 Namespace: e.Namespace, 6676 Priority: e.Priority, 6677 Type: e.Type, 6678 TriggeredBy: EvalTriggerQueuedAllocs, 6679 JobID: e.JobID, 6680 JobModifyIndex: e.JobModifyIndex, 6681 Status: EvalStatusBlocked, 6682 PreviousEval: e.ID, 6683 ClassEligibility: classEligibility, 6684 EscapedComputedClass: escaped, 6685 QuotaLimitReached: quotaReached, 6686 } 6687 } 6688 6689 // CreateFailedFollowUpEval creates a follow up evaluation when the current one 6690 // has been marked as failed because it has hit the delivery limit and will not 6691 // be retried by the eval_broker. 6692 func (e *Evaluation) CreateFailedFollowUpEval(wait time.Duration) *Evaluation { 6693 return &Evaluation{ 6694 ID: uuid.Generate(), 6695 Namespace: e.Namespace, 6696 Priority: e.Priority, 6697 Type: e.Type, 6698 TriggeredBy: EvalTriggerFailedFollowUp, 6699 JobID: e.JobID, 6700 JobModifyIndex: e.JobModifyIndex, 6701 Status: EvalStatusPending, 6702 Wait: wait, 6703 PreviousEval: e.ID, 6704 } 6705 } 6706 6707 // Plan is used to submit a commit plan for task allocations. These 6708 // are submitted to the leader which verifies that resources have 6709 // not been overcommitted before admitting the plan. 6710 type Plan struct { 6711 // EvalID is the evaluation ID this plan is associated with 6712 EvalID string 6713 6714 // EvalToken is used to prevent a split-brain processing of 6715 // an evaluation. There should only be a single scheduler running 6716 // an Eval at a time, but this could be violated after a leadership 6717 // transition. This unique token is used to reject plans that are 6718 // being submitted from a different leader. 6719 EvalToken string 6720 6721 // Priority is the priority of the upstream job 6722 Priority int 6723 6724 // AllAtOnce is used to control if incremental scheduling of task groups 6725 // is allowed or if we must do a gang scheduling of the entire job. 6726 // If this is false, a plan may be partially applied. Otherwise, the 6727 // entire plan must be able to make progress. 6728 AllAtOnce bool 6729 6730 // Job is the parent job of all the allocations in the Plan. 6731 // Since a Plan only involves a single Job, we can reduce the size 6732 // of the plan by only including it once. 6733 Job *Job 6734 6735 // NodeUpdate contains all the allocations for each node. For each node, 6736 // this is a list of the allocations to update to either stop or evict. 6737 NodeUpdate map[string][]*Allocation 6738 6739 // NodeAllocation contains all the allocations for each node. 6740 // The evicts must be considered prior to the allocations. 6741 NodeAllocation map[string][]*Allocation 6742 6743 // Annotations contains annotations by the scheduler to be used by operators 6744 // to understand the decisions made by the scheduler. 6745 Annotations *PlanAnnotations 6746 6747 // Deployment is the deployment created or updated by the scheduler that 6748 // should be applied by the planner. 6749 Deployment *Deployment 6750 6751 // DeploymentUpdates is a set of status updates to apply to the given 6752 // deployments. This allows the scheduler to cancel any unneeded deployment 6753 // because the job is stopped or the update block is removed. 6754 DeploymentUpdates []*DeploymentStatusUpdate 6755 } 6756 6757 // AppendUpdate marks the allocation for eviction. The clientStatus of the 6758 // allocation may be optionally set by passing in a non-empty value. 6759 func (p *Plan) AppendUpdate(alloc *Allocation, desiredStatus, desiredDesc, clientStatus string) { 6760 newAlloc := new(Allocation) 6761 *newAlloc = *alloc 6762 6763 // If the job is not set in the plan we are deregistering a job so we 6764 // extract the job from the allocation. 6765 if p.Job == nil && newAlloc.Job != nil { 6766 p.Job = newAlloc.Job 6767 } 6768 6769 // Normalize the job 6770 newAlloc.Job = nil 6771 6772 // Strip the resources as it can be rebuilt. 6773 newAlloc.Resources = nil 6774 6775 newAlloc.DesiredStatus = desiredStatus 6776 newAlloc.DesiredDescription = desiredDesc 6777 6778 if clientStatus != "" { 6779 newAlloc.ClientStatus = clientStatus 6780 } 6781 6782 node := alloc.NodeID 6783 existing := p.NodeUpdate[node] 6784 p.NodeUpdate[node] = append(existing, newAlloc) 6785 } 6786 6787 func (p *Plan) PopUpdate(alloc *Allocation) { 6788 existing := p.NodeUpdate[alloc.NodeID] 6789 n := len(existing) 6790 if n > 0 && existing[n-1].ID == alloc.ID { 6791 existing = existing[:n-1] 6792 if len(existing) > 0 { 6793 p.NodeUpdate[alloc.NodeID] = existing 6794 } else { 6795 delete(p.NodeUpdate, alloc.NodeID) 6796 } 6797 } 6798 } 6799 6800 func (p *Plan) AppendAlloc(alloc *Allocation) { 6801 node := alloc.NodeID 6802 existing := p.NodeAllocation[node] 6803 6804 // Normalize the job 6805 alloc.Job = nil 6806 6807 p.NodeAllocation[node] = append(existing, alloc) 6808 } 6809 6810 // IsNoOp checks if this plan would do nothing 6811 func (p *Plan) IsNoOp() bool { 6812 return len(p.NodeUpdate) == 0 && 6813 len(p.NodeAllocation) == 0 && 6814 p.Deployment == nil && 6815 len(p.DeploymentUpdates) == 0 6816 } 6817 6818 // PlanResult is the result of a plan submitted to the leader. 6819 type PlanResult struct { 6820 // NodeUpdate contains all the updates that were committed. 6821 NodeUpdate map[string][]*Allocation 6822 6823 // NodeAllocation contains all the allocations that were committed. 6824 NodeAllocation map[string][]*Allocation 6825 6826 // Deployment is the deployment that was committed. 6827 Deployment *Deployment 6828 6829 // DeploymentUpdates is the set of deployment updates that were committed. 6830 DeploymentUpdates []*DeploymentStatusUpdate 6831 6832 // RefreshIndex is the index the worker should refresh state up to. 6833 // This allows all evictions and allocations to be materialized. 6834 // If any allocations were rejected due to stale data (node state, 6835 // over committed) this can be used to force a worker refresh. 6836 RefreshIndex uint64 6837 6838 // AllocIndex is the Raft index in which the evictions and 6839 // allocations took place. This is used for the write index. 6840 AllocIndex uint64 6841 } 6842 6843 // IsNoOp checks if this plan result would do nothing 6844 func (p *PlanResult) IsNoOp() bool { 6845 return len(p.NodeUpdate) == 0 && len(p.NodeAllocation) == 0 && 6846 len(p.DeploymentUpdates) == 0 && p.Deployment == nil 6847 } 6848 6849 // FullCommit is used to check if all the allocations in a plan 6850 // were committed as part of the result. Returns if there was 6851 // a match, and the number of expected and actual allocations. 6852 func (p *PlanResult) FullCommit(plan *Plan) (bool, int, int) { 6853 expected := 0 6854 actual := 0 6855 for name, allocList := range plan.NodeAllocation { 6856 didAlloc, _ := p.NodeAllocation[name] 6857 expected += len(allocList) 6858 actual += len(didAlloc) 6859 } 6860 return actual == expected, expected, actual 6861 } 6862 6863 // PlanAnnotations holds annotations made by the scheduler to give further debug 6864 // information to operators. 6865 type PlanAnnotations struct { 6866 // DesiredTGUpdates is the set of desired updates per task group. 6867 DesiredTGUpdates map[string]*DesiredUpdates 6868 } 6869 6870 // DesiredUpdates is the set of changes the scheduler would like to make given 6871 // sufficient resources and cluster capacity. 6872 type DesiredUpdates struct { 6873 Ignore uint64 6874 Place uint64 6875 Migrate uint64 6876 Stop uint64 6877 InPlaceUpdate uint64 6878 DestructiveUpdate uint64 6879 Canary uint64 6880 } 6881 6882 func (d *DesiredUpdates) GoString() string { 6883 return fmt.Sprintf("(place %d) (inplace %d) (destructive %d) (stop %d) (migrate %d) (ignore %d) (canary %d)", 6884 d.Place, d.InPlaceUpdate, d.DestructiveUpdate, d.Stop, d.Migrate, d.Ignore, d.Canary) 6885 } 6886 6887 // msgpackHandle is a shared handle for encoding/decoding of structs 6888 var MsgpackHandle = func() *codec.MsgpackHandle { 6889 h := &codec.MsgpackHandle{RawToString: true} 6890 6891 // Sets the default type for decoding a map into a nil interface{}. 6892 // This is necessary in particular because we store the driver configs as a 6893 // nil interface{}. 6894 h.MapType = reflect.TypeOf(map[string]interface{}(nil)) 6895 return h 6896 }() 6897 6898 var ( 6899 // JsonHandle and JsonHandlePretty are the codec handles to JSON encode 6900 // structs. The pretty handle will add indents for easier human consumption. 6901 JsonHandle = &codec.JsonHandle{ 6902 HTMLCharsAsIs: true, 6903 } 6904 JsonHandlePretty = &codec.JsonHandle{ 6905 HTMLCharsAsIs: true, 6906 Indent: 4, 6907 } 6908 ) 6909 6910 // TODO Figure out if we can remove this. This is our fork that is just way 6911 // behind. I feel like its original purpose was to pin at a stable version but 6912 // now we can accomplish this with vendoring. 6913 var HashiMsgpackHandle = func() *hcodec.MsgpackHandle { 6914 h := &hcodec.MsgpackHandle{RawToString: true} 6915 6916 // Sets the default type for decoding a map into a nil interface{}. 6917 // This is necessary in particular because we store the driver configs as a 6918 // nil interface{}. 6919 h.MapType = reflect.TypeOf(map[string]interface{}(nil)) 6920 return h 6921 }() 6922 6923 // Decode is used to decode a MsgPack encoded object 6924 func Decode(buf []byte, out interface{}) error { 6925 return codec.NewDecoder(bytes.NewReader(buf), MsgpackHandle).Decode(out) 6926 } 6927 6928 // Encode is used to encode a MsgPack object with type prefix 6929 func Encode(t MessageType, msg interface{}) ([]byte, error) { 6930 var buf bytes.Buffer 6931 buf.WriteByte(uint8(t)) 6932 err := codec.NewEncoder(&buf, MsgpackHandle).Encode(msg) 6933 return buf.Bytes(), err 6934 } 6935 6936 // KeyringResponse is a unified key response and can be used for install, 6937 // remove, use, as well as listing key queries. 6938 type KeyringResponse struct { 6939 Messages map[string]string 6940 Keys map[string]int 6941 NumNodes int 6942 } 6943 6944 // KeyringRequest is request objects for serf key operations. 6945 type KeyringRequest struct { 6946 Key string 6947 } 6948 6949 // RecoverableError wraps an error and marks whether it is recoverable and could 6950 // be retried or it is fatal. 6951 type RecoverableError struct { 6952 Err string 6953 Recoverable bool 6954 } 6955 6956 // NewRecoverableError is used to wrap an error and mark it as recoverable or 6957 // not. 6958 func NewRecoverableError(e error, recoverable bool) error { 6959 if e == nil { 6960 return nil 6961 } 6962 6963 return &RecoverableError{ 6964 Err: e.Error(), 6965 Recoverable: recoverable, 6966 } 6967 } 6968 6969 // WrapRecoverable wraps an existing error in a new RecoverableError with a new 6970 // message. If the error was recoverable before the returned error is as well; 6971 // otherwise it is unrecoverable. 6972 func WrapRecoverable(msg string, err error) error { 6973 return &RecoverableError{Err: msg, Recoverable: IsRecoverable(err)} 6974 } 6975 6976 func (r *RecoverableError) Error() string { 6977 return r.Err 6978 } 6979 6980 func (r *RecoverableError) IsRecoverable() bool { 6981 return r.Recoverable 6982 } 6983 6984 // Recoverable is an interface for errors to implement to indicate whether or 6985 // not they are fatal or recoverable. 6986 type Recoverable interface { 6987 error 6988 IsRecoverable() bool 6989 } 6990 6991 // IsRecoverable returns true if error is a RecoverableError with 6992 // Recoverable=true. Otherwise false is returned. 6993 func IsRecoverable(e error) bool { 6994 if re, ok := e.(Recoverable); ok { 6995 return re.IsRecoverable() 6996 } 6997 return false 6998 } 6999 7000 // WrappedServerError wraps an error and satisfies 7001 // both the Recoverable and the ServerSideError interfaces 7002 type WrappedServerError struct { 7003 Err error 7004 } 7005 7006 // NewWrappedServerError is used to create a wrapped server side error 7007 func NewWrappedServerError(e error) error { 7008 return &WrappedServerError{ 7009 Err: e, 7010 } 7011 } 7012 7013 func (r *WrappedServerError) IsRecoverable() bool { 7014 return IsRecoverable(r.Err) 7015 } 7016 7017 func (r *WrappedServerError) Error() string { 7018 return r.Err.Error() 7019 } 7020 7021 func (r *WrappedServerError) IsServerSide() bool { 7022 return true 7023 } 7024 7025 // ServerSideError is an interface for errors to implement to indicate 7026 // errors occurring after the request makes it to a server 7027 type ServerSideError interface { 7028 error 7029 IsServerSide() bool 7030 } 7031 7032 // IsServerSide returns true if error is a wrapped 7033 // server side error 7034 func IsServerSide(e error) bool { 7035 if se, ok := e.(ServerSideError); ok { 7036 return se.IsServerSide() 7037 } 7038 return false 7039 } 7040 7041 // ACLPolicy is used to represent an ACL policy 7042 type ACLPolicy struct { 7043 Name string // Unique name 7044 Description string // Human readable 7045 Rules string // HCL or JSON format 7046 Hash []byte 7047 CreateIndex uint64 7048 ModifyIndex uint64 7049 } 7050 7051 // SetHash is used to compute and set the hash of the ACL policy 7052 func (c *ACLPolicy) SetHash() []byte { 7053 // Initialize a 256bit Blake2 hash (32 bytes) 7054 hash, err := blake2b.New256(nil) 7055 if err != nil { 7056 panic(err) 7057 } 7058 7059 // Write all the user set fields 7060 hash.Write([]byte(c.Name)) 7061 hash.Write([]byte(c.Description)) 7062 hash.Write([]byte(c.Rules)) 7063 7064 // Finalize the hash 7065 hashVal := hash.Sum(nil) 7066 7067 // Set and return the hash 7068 c.Hash = hashVal 7069 return hashVal 7070 } 7071 7072 func (a *ACLPolicy) Stub() *ACLPolicyListStub { 7073 return &ACLPolicyListStub{ 7074 Name: a.Name, 7075 Description: a.Description, 7076 Hash: a.Hash, 7077 CreateIndex: a.CreateIndex, 7078 ModifyIndex: a.ModifyIndex, 7079 } 7080 } 7081 7082 func (a *ACLPolicy) Validate() error { 7083 var mErr multierror.Error 7084 if !validPolicyName.MatchString(a.Name) { 7085 err := fmt.Errorf("invalid name '%s'", a.Name) 7086 mErr.Errors = append(mErr.Errors, err) 7087 } 7088 if _, err := acl.Parse(a.Rules); err != nil { 7089 err = fmt.Errorf("failed to parse rules: %v", err) 7090 mErr.Errors = append(mErr.Errors, err) 7091 } 7092 if len(a.Description) > maxPolicyDescriptionLength { 7093 err := fmt.Errorf("description longer than %d", maxPolicyDescriptionLength) 7094 mErr.Errors = append(mErr.Errors, err) 7095 } 7096 return mErr.ErrorOrNil() 7097 } 7098 7099 // ACLPolicyListStub is used to for listing ACL policies 7100 type ACLPolicyListStub struct { 7101 Name string 7102 Description string 7103 Hash []byte 7104 CreateIndex uint64 7105 ModifyIndex uint64 7106 } 7107 7108 // ACLPolicyListRequest is used to request a list of policies 7109 type ACLPolicyListRequest struct { 7110 QueryOptions 7111 } 7112 7113 // ACLPolicySpecificRequest is used to query a specific policy 7114 type ACLPolicySpecificRequest struct { 7115 Name string 7116 QueryOptions 7117 } 7118 7119 // ACLPolicySetRequest is used to query a set of policies 7120 type ACLPolicySetRequest struct { 7121 Names []string 7122 QueryOptions 7123 } 7124 7125 // ACLPolicyListResponse is used for a list request 7126 type ACLPolicyListResponse struct { 7127 Policies []*ACLPolicyListStub 7128 QueryMeta 7129 } 7130 7131 // SingleACLPolicyResponse is used to return a single policy 7132 type SingleACLPolicyResponse struct { 7133 Policy *ACLPolicy 7134 QueryMeta 7135 } 7136 7137 // ACLPolicySetResponse is used to return a set of policies 7138 type ACLPolicySetResponse struct { 7139 Policies map[string]*ACLPolicy 7140 QueryMeta 7141 } 7142 7143 // ACLPolicyDeleteRequest is used to delete a set of policies 7144 type ACLPolicyDeleteRequest struct { 7145 Names []string 7146 WriteRequest 7147 } 7148 7149 // ACLPolicyUpsertRequest is used to upsert a set of policies 7150 type ACLPolicyUpsertRequest struct { 7151 Policies []*ACLPolicy 7152 WriteRequest 7153 } 7154 7155 // ACLToken represents a client token which is used to Authenticate 7156 type ACLToken struct { 7157 AccessorID string // Public Accessor ID (UUID) 7158 SecretID string // Secret ID, private (UUID) 7159 Name string // Human friendly name 7160 Type string // Client or Management 7161 Policies []string // Policies this token ties to 7162 Global bool // Global or Region local 7163 Hash []byte 7164 CreateTime time.Time // Time of creation 7165 CreateIndex uint64 7166 ModifyIndex uint64 7167 } 7168 7169 var ( 7170 // AnonymousACLToken is used no SecretID is provided, and the 7171 // request is made anonymously. 7172 AnonymousACLToken = &ACLToken{ 7173 AccessorID: "anonymous", 7174 Name: "Anonymous Token", 7175 Type: ACLClientToken, 7176 Policies: []string{"anonymous"}, 7177 Global: false, 7178 } 7179 ) 7180 7181 type ACLTokenListStub struct { 7182 AccessorID string 7183 Name string 7184 Type string 7185 Policies []string 7186 Global bool 7187 Hash []byte 7188 CreateTime time.Time 7189 CreateIndex uint64 7190 ModifyIndex uint64 7191 } 7192 7193 // SetHash is used to compute and set the hash of the ACL token 7194 func (a *ACLToken) SetHash() []byte { 7195 // Initialize a 256bit Blake2 hash (32 bytes) 7196 hash, err := blake2b.New256(nil) 7197 if err != nil { 7198 panic(err) 7199 } 7200 7201 // Write all the user set fields 7202 hash.Write([]byte(a.Name)) 7203 hash.Write([]byte(a.Type)) 7204 for _, policyName := range a.Policies { 7205 hash.Write([]byte(policyName)) 7206 } 7207 if a.Global { 7208 hash.Write([]byte("global")) 7209 } else { 7210 hash.Write([]byte("local")) 7211 } 7212 7213 // Finalize the hash 7214 hashVal := hash.Sum(nil) 7215 7216 // Set and return the hash 7217 a.Hash = hashVal 7218 return hashVal 7219 } 7220 7221 func (a *ACLToken) Stub() *ACLTokenListStub { 7222 return &ACLTokenListStub{ 7223 AccessorID: a.AccessorID, 7224 Name: a.Name, 7225 Type: a.Type, 7226 Policies: a.Policies, 7227 Global: a.Global, 7228 Hash: a.Hash, 7229 CreateTime: a.CreateTime, 7230 CreateIndex: a.CreateIndex, 7231 ModifyIndex: a.ModifyIndex, 7232 } 7233 } 7234 7235 // Validate is used to sanity check a token 7236 func (a *ACLToken) Validate() error { 7237 var mErr multierror.Error 7238 if len(a.Name) > maxTokenNameLength { 7239 mErr.Errors = append(mErr.Errors, fmt.Errorf("token name too long")) 7240 } 7241 switch a.Type { 7242 case ACLClientToken: 7243 if len(a.Policies) == 0 { 7244 mErr.Errors = append(mErr.Errors, fmt.Errorf("client token missing policies")) 7245 } 7246 case ACLManagementToken: 7247 if len(a.Policies) != 0 { 7248 mErr.Errors = append(mErr.Errors, fmt.Errorf("management token cannot be associated with policies")) 7249 } 7250 default: 7251 mErr.Errors = append(mErr.Errors, fmt.Errorf("token type must be client or management")) 7252 } 7253 return mErr.ErrorOrNil() 7254 } 7255 7256 // PolicySubset checks if a given set of policies is a subset of the token 7257 func (a *ACLToken) PolicySubset(policies []string) bool { 7258 // Hot-path the management tokens, superset of all policies. 7259 if a.Type == ACLManagementToken { 7260 return true 7261 } 7262 associatedPolicies := make(map[string]struct{}, len(a.Policies)) 7263 for _, policy := range a.Policies { 7264 associatedPolicies[policy] = struct{}{} 7265 } 7266 for _, policy := range policies { 7267 if _, ok := associatedPolicies[policy]; !ok { 7268 return false 7269 } 7270 } 7271 return true 7272 } 7273 7274 // ACLTokenListRequest is used to request a list of tokens 7275 type ACLTokenListRequest struct { 7276 GlobalOnly bool 7277 QueryOptions 7278 } 7279 7280 // ACLTokenSpecificRequest is used to query a specific token 7281 type ACLTokenSpecificRequest struct { 7282 AccessorID string 7283 QueryOptions 7284 } 7285 7286 // ACLTokenSetRequest is used to query a set of tokens 7287 type ACLTokenSetRequest struct { 7288 AccessorIDS []string 7289 QueryOptions 7290 } 7291 7292 // ACLTokenListResponse is used for a list request 7293 type ACLTokenListResponse struct { 7294 Tokens []*ACLTokenListStub 7295 QueryMeta 7296 } 7297 7298 // SingleACLTokenResponse is used to return a single token 7299 type SingleACLTokenResponse struct { 7300 Token *ACLToken 7301 QueryMeta 7302 } 7303 7304 // ACLTokenSetResponse is used to return a set of token 7305 type ACLTokenSetResponse struct { 7306 Tokens map[string]*ACLToken // Keyed by Accessor ID 7307 QueryMeta 7308 } 7309 7310 // ResolveACLTokenRequest is used to resolve a specific token 7311 type ResolveACLTokenRequest struct { 7312 SecretID string 7313 QueryOptions 7314 } 7315 7316 // ResolveACLTokenResponse is used to resolve a single token 7317 type ResolveACLTokenResponse struct { 7318 Token *ACLToken 7319 QueryMeta 7320 } 7321 7322 // ACLTokenDeleteRequest is used to delete a set of tokens 7323 type ACLTokenDeleteRequest struct { 7324 AccessorIDs []string 7325 WriteRequest 7326 } 7327 7328 // ACLTokenBootstrapRequest is used to bootstrap ACLs 7329 type ACLTokenBootstrapRequest struct { 7330 Token *ACLToken // Not client specifiable 7331 ResetIndex uint64 // Reset index is used to clear the bootstrap token 7332 WriteRequest 7333 } 7334 7335 // ACLTokenUpsertRequest is used to upsert a set of tokens 7336 type ACLTokenUpsertRequest struct { 7337 Tokens []*ACLToken 7338 WriteRequest 7339 } 7340 7341 // ACLTokenUpsertResponse is used to return from an ACLTokenUpsertRequest 7342 type ACLTokenUpsertResponse struct { 7343 Tokens []*ACLToken 7344 WriteMeta 7345 }