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