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