github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/state/schema.go (about) 1 package state 2 3 import ( 4 "fmt" 5 "sync" 6 7 memdb "github.com/hashicorp/go-memdb" 8 "github.com/hashicorp/nomad/nomad/state/indexer" 9 "github.com/hashicorp/nomad/nomad/structs" 10 ) 11 12 const ( 13 tableIndex = "index" 14 15 TableNamespaces = "namespaces" 16 TableServiceRegistrations = "service_registrations" 17 TableVariables = "variables" 18 TableVariablesQuotas = "variables_quota" 19 TableRootKeyMeta = "root_key_meta" 20 TableACLRoles = "acl_roles" 21 TableACLAuthMethods = "acl_auth_methods" 22 TableACLBindingRules = "acl_binding_rules" 23 TableAllocs = "allocs" 24 ) 25 26 const ( 27 indexID = "id" 28 indexJob = "job" 29 indexNodeID = "node_id" 30 indexAllocID = "alloc_id" 31 indexServiceName = "service_name" 32 indexExpiresGlobal = "expires-global" 33 indexExpiresLocal = "expires-local" 34 indexKeyID = "key_id" 35 indexPath = "path" 36 indexName = "name" 37 indexSigningKey = "signing_key" 38 indexAuthMethod = "auth_method" 39 ) 40 41 var ( 42 schemaFactories SchemaFactories 43 factoriesLock sync.Mutex 44 ) 45 46 // SchemaFactory is the factory method for returning a TableSchema 47 type SchemaFactory func() *memdb.TableSchema 48 type SchemaFactories []SchemaFactory 49 50 // RegisterSchemaFactories is used to register a table schema. 51 func RegisterSchemaFactories(factories ...SchemaFactory) { 52 factoriesLock.Lock() 53 defer factoriesLock.Unlock() 54 schemaFactories = append(schemaFactories, factories...) 55 } 56 57 func GetFactories() SchemaFactories { 58 return schemaFactories 59 } 60 61 func init() { 62 // Register all schemas 63 RegisterSchemaFactories([]SchemaFactory{ 64 indexTableSchema, 65 nodeTableSchema, 66 jobTableSchema, 67 jobSummarySchema, 68 jobVersionSchema, 69 deploymentSchema, 70 periodicLaunchTableSchema, 71 evalTableSchema, 72 allocTableSchema, 73 vaultAccessorTableSchema, 74 siTokenAccessorTableSchema, 75 aclPolicyTableSchema, 76 aclTokenTableSchema, 77 oneTimeTokenTableSchema, 78 autopilotConfigTableSchema, 79 schedulerConfigTableSchema, 80 clusterMetaTableSchema, 81 csiVolumeTableSchema, 82 csiPluginTableSchema, 83 scalingPolicyTableSchema, 84 scalingEventTableSchema, 85 namespaceTableSchema, 86 serviceRegistrationsTableSchema, 87 variablesTableSchema, 88 variablesQuotasTableSchema, 89 variablesRootKeyMetaSchema, 90 aclRolesTableSchema, 91 aclAuthMethodsTableSchema, 92 bindingRulesTableSchema, 93 }...) 94 } 95 96 // stateStoreSchema is used to return the schema for the state store 97 func stateStoreSchema() *memdb.DBSchema { 98 // Create the root DB schema 99 db := &memdb.DBSchema{ 100 Tables: make(map[string]*memdb.TableSchema), 101 } 102 103 // Add each of the tables 104 for _, schemaFn := range GetFactories() { 105 schema := schemaFn() 106 if _, ok := db.Tables[schema.Name]; ok { 107 panic(fmt.Sprintf("duplicate table name: %s", schema.Name)) 108 } 109 db.Tables[schema.Name] = schema 110 } 111 return db 112 } 113 114 // indexTableSchema is used for tracking the most recent index used for each table. 115 func indexTableSchema() *memdb.TableSchema { 116 return &memdb.TableSchema{ 117 Name: "index", 118 Indexes: map[string]*memdb.IndexSchema{ 119 "id": { 120 Name: "id", 121 AllowMissing: false, 122 Unique: true, 123 Indexer: &memdb.StringFieldIndex{ 124 Field: "Key", 125 Lowercase: true, 126 }, 127 }, 128 }, 129 } 130 } 131 132 // nodeTableSchema returns the MemDB schema for the nodes table. 133 // This table is used to store all the client nodes that are registered. 134 func nodeTableSchema() *memdb.TableSchema { 135 return &memdb.TableSchema{ 136 Name: "nodes", 137 Indexes: map[string]*memdb.IndexSchema{ 138 // Primary index is used for node management 139 // and simple direct lookup. ID is required to be 140 // unique. 141 "id": { 142 Name: "id", 143 AllowMissing: false, 144 Unique: true, 145 Indexer: &memdb.UUIDFieldIndex{ 146 Field: "ID", 147 }, 148 }, 149 "secret_id": { 150 Name: "secret_id", 151 AllowMissing: false, 152 Unique: true, 153 Indexer: &memdb.UUIDFieldIndex{ 154 Field: "SecretID", 155 }, 156 }, 157 }, 158 } 159 } 160 161 // jobTableSchema returns the MemDB schema for the jobs table. 162 // This table is used to store all the jobs that have been submitted. 163 func jobTableSchema() *memdb.TableSchema { 164 return &memdb.TableSchema{ 165 Name: "jobs", 166 Indexes: map[string]*memdb.IndexSchema{ 167 // Primary index is used for job management 168 // and simple direct lookup. ID is required to be 169 // unique within a namespace. 170 "id": { 171 Name: "id", 172 AllowMissing: false, 173 Unique: true, 174 175 // Use a compound index so the tuple of (Namespace, ID) is 176 // uniquely identifying 177 Indexer: &memdb.CompoundIndex{ 178 Indexes: []memdb.Indexer{ 179 &memdb.StringFieldIndex{ 180 Field: "Namespace", 181 }, 182 183 &memdb.StringFieldIndex{ 184 Field: "ID", 185 }, 186 }, 187 }, 188 }, 189 "type": { 190 Name: "type", 191 AllowMissing: false, 192 Unique: false, 193 Indexer: &memdb.StringFieldIndex{ 194 Field: "Type", 195 Lowercase: false, 196 }, 197 }, 198 "gc": { 199 Name: "gc", 200 AllowMissing: false, 201 Unique: false, 202 Indexer: &memdb.ConditionalIndex{ 203 Conditional: jobIsGCable, 204 }, 205 }, 206 "periodic": { 207 Name: "periodic", 208 AllowMissing: false, 209 Unique: false, 210 Indexer: &memdb.ConditionalIndex{ 211 Conditional: jobIsPeriodic, 212 }, 213 }, 214 }, 215 } 216 } 217 218 // jobSummarySchema returns the memdb schema for the job summary table 219 func jobSummarySchema() *memdb.TableSchema { 220 return &memdb.TableSchema{ 221 Name: "job_summary", 222 Indexes: map[string]*memdb.IndexSchema{ 223 "id": { 224 Name: "id", 225 AllowMissing: false, 226 Unique: true, 227 228 // Use a compound index so the tuple of (Namespace, JobID) is 229 // uniquely identifying 230 Indexer: &memdb.CompoundIndex{ 231 Indexes: []memdb.Indexer{ 232 &memdb.StringFieldIndex{ 233 Field: "Namespace", 234 }, 235 236 &memdb.StringFieldIndex{ 237 Field: "JobID", 238 }, 239 }, 240 }, 241 }, 242 }, 243 } 244 } 245 246 // jobVersionSchema returns the memdb schema for the job version table which 247 // keeps a historical view of job versions. 248 func jobVersionSchema() *memdb.TableSchema { 249 return &memdb.TableSchema{ 250 Name: "job_version", 251 Indexes: map[string]*memdb.IndexSchema{ 252 "id": { 253 Name: "id", 254 AllowMissing: false, 255 Unique: true, 256 257 // Use a compound index so the tuple of (Namespace, ID, Version) is 258 // uniquely identifying 259 Indexer: &memdb.CompoundIndex{ 260 Indexes: []memdb.Indexer{ 261 &memdb.StringFieldIndex{ 262 Field: "Namespace", 263 }, 264 265 &memdb.StringFieldIndex{ 266 Field: "ID", 267 Lowercase: true, 268 }, 269 270 &memdb.UintFieldIndex{ 271 Field: "Version", 272 }, 273 }, 274 }, 275 }, 276 }, 277 } 278 } 279 280 // jobIsGCable satisfies the ConditionalIndexFunc interface and creates an index 281 // on whether a job is eligible for garbage collection. 282 func jobIsGCable(obj interface{}) (bool, error) { 283 j, ok := obj.(*structs.Job) 284 if !ok { 285 return false, fmt.Errorf("Unexpected type: %v", obj) 286 } 287 288 // If the job is periodic or parameterized it is only garbage collectable if 289 // it is stopped. 290 periodic := j.Periodic != nil && j.Periodic.Enabled 291 parameterized := j.IsParameterized() 292 if periodic || parameterized { 293 return j.Stop, nil 294 } 295 296 // If the job isn't dead it isn't eligible 297 if j.Status != structs.JobStatusDead { 298 return false, nil 299 } 300 301 // Any job that is stopped is eligible for garbage collection 302 if j.Stop { 303 return true, nil 304 } 305 306 switch j.Type { 307 // Otherwise, batch and sysbatch jobs are eligible because they complete on 308 // their own without a user stopping them. 309 case structs.JobTypeBatch, structs.JobTypeSysBatch: 310 return true, nil 311 312 default: 313 // other job types may not be GC until stopped 314 return false, nil 315 } 316 } 317 318 // jobIsPeriodic satisfies the ConditionalIndexFunc interface and creates an index 319 // on whether a job is periodic. 320 func jobIsPeriodic(obj interface{}) (bool, error) { 321 j, ok := obj.(*structs.Job) 322 if !ok { 323 return false, fmt.Errorf("Unexpected type: %v", obj) 324 } 325 326 if j.Periodic != nil && j.Periodic.Enabled { 327 return true, nil 328 } 329 330 return false, nil 331 } 332 333 // deploymentSchema returns the MemDB schema tracking a job's deployments 334 func deploymentSchema() *memdb.TableSchema { 335 return &memdb.TableSchema{ 336 Name: "deployment", 337 Indexes: map[string]*memdb.IndexSchema{ 338 // id index is used for direct lookup of an deployment by ID. 339 "id": { 340 Name: "id", 341 AllowMissing: false, 342 Unique: true, 343 Indexer: &memdb.UUIDFieldIndex{ 344 Field: "ID", 345 }, 346 }, 347 348 // create index is used for listing deploy, ordering them by 349 // creation chronology. (Use a reverse iterator for newest first). 350 // 351 // There may be more than one deployment per CreateIndex. 352 "create": { 353 Name: "create", 354 AllowMissing: false, 355 Unique: true, 356 Indexer: &memdb.CompoundIndex{ 357 Indexes: []memdb.Indexer{ 358 &memdb.UintFieldIndex{ 359 Field: "CreateIndex", 360 }, 361 &memdb.StringFieldIndex{ 362 Field: "ID", 363 }, 364 }, 365 }, 366 }, 367 368 // namespace is used to lookup evaluations by namespace. 369 "namespace": { 370 Name: "namespace", 371 AllowMissing: false, 372 Unique: false, 373 Indexer: &memdb.StringFieldIndex{ 374 Field: "Namespace", 375 }, 376 }, 377 378 // namespace_create index is used to lookup deployments by namespace 379 // in their original chronological order based on CreateIndex. 380 // 381 // Use a prefix iterator (namespace_create_prefix) to iterate deployments 382 // of a Namespace in order of CreateIndex. 383 // 384 // There may be more than one deployment per CreateIndex. 385 "namespace_create": { 386 Name: "namespace_create", 387 AllowMissing: false, 388 Unique: true, 389 Indexer: &memdb.CompoundIndex{ 390 AllowMissing: false, 391 Indexes: []memdb.Indexer{ 392 &memdb.StringFieldIndex{ 393 Field: "Namespace", 394 }, 395 &memdb.UintFieldIndex{ 396 Field: "CreateIndex", 397 }, 398 &memdb.StringFieldIndex{ 399 Field: "ID", 400 }, 401 }, 402 }, 403 }, 404 405 // job index is used to lookup deployments by job 406 "job": { 407 Name: "job", 408 AllowMissing: false, 409 Unique: false, 410 411 // Use a compound index so the tuple of (Namespace, JobID) is 412 // uniquely identifying 413 Indexer: &memdb.CompoundIndex{ 414 Indexes: []memdb.Indexer{ 415 &memdb.StringFieldIndex{ 416 Field: "Namespace", 417 }, 418 419 &memdb.StringFieldIndex{ 420 Field: "JobID", 421 }, 422 }, 423 }, 424 }, 425 }, 426 } 427 } 428 429 // periodicLaunchTableSchema returns the MemDB schema tracking the most recent 430 // launch time for a periodic job. 431 func periodicLaunchTableSchema() *memdb.TableSchema { 432 return &memdb.TableSchema{ 433 Name: "periodic_launch", 434 Indexes: map[string]*memdb.IndexSchema{ 435 // Primary index is used for job management 436 // and simple direct lookup. ID is required to be 437 // unique. 438 "id": { 439 Name: "id", 440 AllowMissing: false, 441 Unique: true, 442 443 // Use a compound index so the tuple of (Namespace, JobID) is 444 // uniquely identifying 445 Indexer: &memdb.CompoundIndex{ 446 Indexes: []memdb.Indexer{ 447 &memdb.StringFieldIndex{ 448 Field: "Namespace", 449 }, 450 451 &memdb.StringFieldIndex{ 452 Field: "ID", 453 }, 454 }, 455 }, 456 }, 457 }, 458 } 459 } 460 461 // evalTableSchema returns the MemDB schema for the eval table. 462 // This table is used to store all the evaluations that are pending 463 // or recently completed. 464 func evalTableSchema() *memdb.TableSchema { 465 return &memdb.TableSchema{ 466 Name: "evals", 467 Indexes: map[string]*memdb.IndexSchema{ 468 // id index is used for direct lookup of an evaluation by ID. 469 "id": { 470 Name: "id", 471 AllowMissing: false, 472 Unique: true, 473 Indexer: &memdb.UUIDFieldIndex{ 474 Field: "ID", 475 }, 476 }, 477 478 // create index is used for listing evaluations, ordering them by 479 // creation chronology. (Use a reverse iterator for newest first). 480 "create": { 481 Name: "create", 482 AllowMissing: false, 483 Unique: true, 484 Indexer: &memdb.CompoundIndex{ 485 Indexes: []memdb.Indexer{ 486 &memdb.UintFieldIndex{ 487 Field: "CreateIndex", 488 }, 489 &memdb.StringFieldIndex{ 490 Field: "ID", 491 }, 492 }, 493 }, 494 }, 495 496 // job index is used to lookup evaluations by job ID. 497 "job": { 498 Name: "job", 499 AllowMissing: false, 500 Unique: false, 501 Indexer: &memdb.CompoundIndex{ 502 Indexes: []memdb.Indexer{ 503 &memdb.StringFieldIndex{ 504 Field: "Namespace", 505 }, 506 507 &memdb.StringFieldIndex{ 508 Field: "JobID", 509 Lowercase: true, 510 }, 511 512 &memdb.StringFieldIndex{ 513 Field: "Status", 514 Lowercase: true, 515 }, 516 }, 517 }, 518 }, 519 520 // namespace is used to lookup evaluations by namespace. 521 "namespace": { 522 Name: "namespace", 523 AllowMissing: false, 524 Unique: false, 525 Indexer: &memdb.StringFieldIndex{ 526 Field: "Namespace", 527 }, 528 }, 529 530 // namespace_create index is used to lookup evaluations by namespace 531 // in their original chronological order based on CreateIndex. 532 // 533 // Use a prefix iterator (namespace_prefix) on a Namespace to iterate 534 // those evaluations in order of CreateIndex. 535 "namespace_create": { 536 Name: "namespace_create", 537 AllowMissing: false, 538 Unique: true, 539 Indexer: &memdb.CompoundIndex{ 540 AllowMissing: false, 541 Indexes: []memdb.Indexer{ 542 &memdb.StringFieldIndex{ 543 Field: "Namespace", 544 }, 545 &memdb.UintFieldIndex{ 546 Field: "CreateIndex", 547 }, 548 &memdb.StringFieldIndex{ 549 Field: "ID", 550 }, 551 }, 552 }, 553 }, 554 }, 555 } 556 } 557 558 // allocTableSchema returns the MemDB schema for the allocation table. 559 // This table is used to store all the task allocations between task groups 560 // and nodes. 561 func allocTableSchema() *memdb.TableSchema { 562 return &memdb.TableSchema{ 563 Name: "allocs", 564 Indexes: map[string]*memdb.IndexSchema{ 565 // id index is used for direct lookup of allocation by ID. 566 "id": { 567 Name: "id", 568 AllowMissing: false, 569 Unique: true, 570 Indexer: &memdb.UUIDFieldIndex{ 571 Field: "ID", 572 }, 573 }, 574 575 // create index is used for listing allocations, ordering them by 576 // creation chronology. (Use a reverse iterator for newest first). 577 "create": { 578 Name: "create", 579 AllowMissing: false, 580 Unique: true, 581 Indexer: &memdb.CompoundIndex{ 582 Indexes: []memdb.Indexer{ 583 &memdb.UintFieldIndex{ 584 Field: "CreateIndex", 585 }, 586 &memdb.StringFieldIndex{ 587 Field: "ID", 588 }, 589 }, 590 }, 591 }, 592 593 // namespace is used to lookup evaluations by namespace. 594 // todo(shoenig): i think we can deprecate this and other like it 595 "namespace": { 596 Name: "namespace", 597 AllowMissing: false, 598 Unique: false, 599 Indexer: &memdb.StringFieldIndex{ 600 Field: "Namespace", 601 }, 602 }, 603 604 // namespace_create index is used to lookup evaluations by namespace 605 // in their original chronological order based on CreateIndex. 606 // 607 // Use a prefix iterator (namespace_prefix) on a Namespace to iterate 608 // those evaluations in order of CreateIndex. 609 "namespace_create": { 610 Name: "namespace_create", 611 AllowMissing: false, 612 Unique: true, 613 Indexer: &memdb.CompoundIndex{ 614 AllowMissing: false, 615 Indexes: []memdb.Indexer{ 616 &memdb.StringFieldIndex{ 617 Field: "Namespace", 618 }, 619 &memdb.UintFieldIndex{ 620 Field: "CreateIndex", 621 }, 622 &memdb.StringFieldIndex{ 623 Field: "ID", 624 }, 625 }, 626 }, 627 }, 628 629 // Node index is used to lookup allocations by node 630 "node": { 631 Name: "node", 632 AllowMissing: true, // Missing is allow for failed allocations 633 Unique: false, 634 Indexer: &memdb.CompoundIndex{ 635 Indexes: []memdb.Indexer{ 636 &memdb.StringFieldIndex{ 637 Field: "NodeID", 638 Lowercase: true, 639 }, 640 641 // Conditional indexer on if allocation is terminal 642 &memdb.ConditionalIndex{ 643 Conditional: func(obj interface{}) (bool, error) { 644 // Cast to allocation 645 alloc, ok := obj.(*structs.Allocation) 646 if !ok { 647 return false, fmt.Errorf("wrong type, got %t should be Allocation", obj) 648 } 649 650 // Check if the allocation is terminal 651 return alloc.TerminalStatus(), nil 652 }, 653 }, 654 }, 655 }, 656 }, 657 658 // Job index is used to lookup allocations by job 659 "job": { 660 Name: "job", 661 AllowMissing: false, 662 Unique: false, 663 664 Indexer: &memdb.CompoundIndex{ 665 Indexes: []memdb.Indexer{ 666 &memdb.StringFieldIndex{ 667 Field: "Namespace", 668 }, 669 670 &memdb.StringFieldIndex{ 671 Field: "JobID", 672 }, 673 }, 674 }, 675 }, 676 677 // Eval index is used to lookup allocations by eval 678 "eval": { 679 Name: "eval", 680 AllowMissing: false, 681 Unique: false, 682 Indexer: &memdb.UUIDFieldIndex{ 683 Field: "EvalID", 684 }, 685 }, 686 687 // Deployment index is used to lookup allocations by deployment 688 "deployment": { 689 Name: "deployment", 690 AllowMissing: true, 691 Unique: false, 692 Indexer: &memdb.UUIDFieldIndex{ 693 Field: "DeploymentID", 694 }, 695 }, 696 697 // signing_key index is used to lookup live allocations by signing 698 // key ID 699 indexSigningKey: { 700 Name: indexSigningKey, 701 AllowMissing: true, // terminal allocations won't be indexed 702 Unique: false, 703 Indexer: &memdb.CompoundIndex{ 704 Indexes: []memdb.Indexer{ 705 &memdb.StringFieldIndex{ 706 Field: "SigningKeyID", 707 }, 708 &memdb.ConditionalIndex{ 709 Conditional: func(obj interface{}) (bool, error) { 710 alloc, ok := obj.(*structs.Allocation) 711 if !ok { 712 return false, fmt.Errorf( 713 "wrong type, got %t should be Allocation", obj) 714 } 715 // note: this isn't alloc.TerminalStatus(), 716 // because we only want to consider the key 717 // unused if the allocation is terminal on both 718 // server and client 719 return !(alloc.ClientTerminalStatus() && alloc.ServerTerminalStatus()), nil 720 }, 721 }, 722 }, 723 }, 724 }, 725 }, 726 } 727 } 728 729 // vaultAccessorTableSchema returns the MemDB schema for the Vault Accessor 730 // Table. This table tracks Vault accessors for tokens created on behalf of 731 // allocations required Vault tokens. 732 func vaultAccessorTableSchema() *memdb.TableSchema { 733 return &memdb.TableSchema{ 734 Name: "vault_accessors", 735 Indexes: map[string]*memdb.IndexSchema{ 736 // The primary index is the accessor id 737 "id": { 738 Name: "id", 739 AllowMissing: false, 740 Unique: true, 741 Indexer: &memdb.StringFieldIndex{ 742 Field: "Accessor", 743 }, 744 }, 745 746 "alloc_id": { 747 Name: "alloc_id", 748 AllowMissing: false, 749 Unique: false, 750 Indexer: &memdb.StringFieldIndex{ 751 Field: "AllocID", 752 }, 753 }, 754 755 "node_id": { 756 Name: "node_id", 757 AllowMissing: false, 758 Unique: false, 759 Indexer: &memdb.StringFieldIndex{ 760 Field: "NodeID", 761 }, 762 }, 763 }, 764 } 765 } 766 767 // siTokenAccessorTableSchema returns the MemDB schema for the Service Identity 768 // token accessor table. This table tracks accessors for tokens created on behalf 769 // of allocations with Consul connect enabled tasks that need SI tokens. 770 func siTokenAccessorTableSchema() *memdb.TableSchema { 771 return &memdb.TableSchema{ 772 Name: siTokenAccessorTable, 773 Indexes: map[string]*memdb.IndexSchema{ 774 // The primary index is the accessor id 775 "id": { 776 Name: "id", 777 AllowMissing: false, 778 Unique: true, 779 Indexer: &memdb.StringFieldIndex{ 780 Field: "AccessorID", 781 }, 782 }, 783 784 "alloc_id": { 785 Name: "alloc_id", 786 AllowMissing: false, 787 Unique: false, 788 Indexer: &memdb.StringFieldIndex{ 789 Field: "AllocID", 790 }, 791 }, 792 793 "node_id": { 794 Name: "node_id", 795 AllowMissing: false, 796 Unique: false, 797 Indexer: &memdb.StringFieldIndex{ 798 Field: "NodeID", 799 }, 800 }, 801 }, 802 } 803 } 804 805 // aclPolicyTableSchema returns the MemDB schema for the policy table. 806 // This table is used to store the policies which are referenced by tokens 807 func aclPolicyTableSchema() *memdb.TableSchema { 808 return &memdb.TableSchema{ 809 Name: "acl_policy", 810 Indexes: map[string]*memdb.IndexSchema{ 811 "id": { 812 Name: "id", 813 AllowMissing: false, 814 Unique: true, 815 Indexer: &memdb.StringFieldIndex{ 816 Field: "Name", 817 }, 818 }, 819 "job": { 820 Name: "job", 821 AllowMissing: true, 822 Unique: false, 823 Indexer: &ACLPolicyJobACLFieldIndex{}, 824 }, 825 }, 826 } 827 } 828 829 // ACLPolicyJobACLFieldIndex is used to extract the policy's JobACL field and 830 // build an index on it. 831 type ACLPolicyJobACLFieldIndex struct{} 832 833 // FromObject is used to extract an index value from an 834 // object or to indicate that the index value is missing. 835 func (a *ACLPolicyJobACLFieldIndex) FromObject(obj interface{}) (bool, []byte, error) { 836 policy, ok := obj.(*structs.ACLPolicy) 837 if !ok { 838 return false, nil, fmt.Errorf("object %#v is not an ACLPolicy", obj) 839 } 840 841 if policy.JobACL == nil { 842 return false, nil, nil 843 } 844 845 ns := policy.JobACL.Namespace 846 if ns == "" { 847 return false, nil, nil 848 } 849 jobID := policy.JobACL.JobID 850 if jobID == "" { 851 return false, nil, fmt.Errorf( 852 "object %#v is not a valid ACLPolicy: JobACL.JobID without Namespace", obj) 853 } 854 855 val := ns + "\x00" + jobID + "\x00" 856 return true, []byte(val), nil 857 } 858 859 // FromArgs is used to build an exact index lookup based on arguments 860 func (a *ACLPolicyJobACLFieldIndex) FromArgs(args ...interface{}) ([]byte, error) { 861 if len(args) != 2 { 862 return nil, fmt.Errorf("must provide two arguments") 863 } 864 arg0, ok := args[0].(string) 865 if !ok { 866 return nil, fmt.Errorf("argument must be a string: %#v", args[0]) 867 } 868 arg1, ok := args[1].(string) 869 if !ok { 870 return nil, fmt.Errorf("argument must be a string: %#v", args[0]) 871 } 872 873 // Add the null character as a terminator 874 arg0 += "\x00" + arg1 + "\x00" 875 return []byte(arg0), nil 876 } 877 878 // PrefixFromArgs returns a prefix that should be used for scanning based on the arguments 879 func (a *ACLPolicyJobACLFieldIndex) PrefixFromArgs(args ...interface{}) ([]byte, error) { 880 val, err := a.FromArgs(args...) 881 if err != nil { 882 return nil, err 883 } 884 885 // Strip the null terminator, the rest is a prefix 886 n := len(val) 887 if n > 0 { 888 return val[:n-1], nil 889 } 890 return val, nil 891 } 892 893 // aclTokenTableSchema returns the MemDB schema for the tokens table. 894 // This table is used to store the bearer tokens which are used to authenticate 895 func aclTokenTableSchema() *memdb.TableSchema { 896 return &memdb.TableSchema{ 897 Name: "acl_token", 898 Indexes: map[string]*memdb.IndexSchema{ 899 "id": { 900 Name: "id", 901 AllowMissing: false, 902 Unique: true, 903 Indexer: &memdb.UUIDFieldIndex{ 904 Field: "AccessorID", 905 }, 906 }, 907 "create": { 908 Name: "create", 909 AllowMissing: false, 910 Unique: true, 911 Indexer: &memdb.CompoundIndex{ 912 Indexes: []memdb.Indexer{ 913 &memdb.UintFieldIndex{ 914 Field: "CreateIndex", 915 }, 916 &memdb.StringFieldIndex{ 917 Field: "AccessorID", 918 }, 919 }, 920 }, 921 }, 922 "secret": { 923 Name: "secret", 924 AllowMissing: false, 925 Unique: true, 926 Indexer: &memdb.UUIDFieldIndex{ 927 Field: "SecretID", 928 }, 929 }, 930 "global": { 931 Name: "global", 932 AllowMissing: false, 933 Unique: false, 934 Indexer: &memdb.FieldSetIndex{ 935 Field: "Global", 936 }, 937 }, 938 indexExpiresGlobal: { 939 Name: indexExpiresGlobal, 940 AllowMissing: true, 941 Unique: false, 942 Indexer: indexer.SingleIndexer{ 943 ReadIndex: indexer.ReadIndex(indexer.IndexFromTimeQuery), 944 WriteIndex: indexer.WriteIndex(indexExpiresGlobalFromACLToken), 945 }, 946 }, 947 indexExpiresLocal: { 948 Name: indexExpiresLocal, 949 AllowMissing: true, 950 Unique: false, 951 Indexer: indexer.SingleIndexer{ 952 ReadIndex: indexer.ReadIndex(indexer.IndexFromTimeQuery), 953 WriteIndex: indexer.WriteIndex(indexExpiresLocalFromACLToken), 954 }, 955 }, 956 }, 957 } 958 } 959 960 func indexExpiresLocalFromACLToken(raw interface{}) ([]byte, error) { 961 return indexExpiresFromACLToken(raw, false) 962 } 963 964 func indexExpiresGlobalFromACLToken(raw interface{}) ([]byte, error) { 965 return indexExpiresFromACLToken(raw, true) 966 } 967 968 // indexExpiresFromACLToken implements the indexer.WriteIndex interface and 969 // allows us to use an ACL tokens ExpirationTime as an index, if it is a 970 // non-default value. This allows for efficient lookups when trying to deal 971 // with removal of expired tokens from state. 972 func indexExpiresFromACLToken(raw interface{}, global bool) ([]byte, error) { 973 p, ok := raw.(*structs.ACLToken) 974 if !ok { 975 return nil, fmt.Errorf("unexpected type %T for structs.ACLToken index", raw) 976 } 977 if p.Global != global { 978 return nil, indexer.ErrMissingValueForIndex 979 } 980 if !p.HasExpirationTime() { 981 return nil, indexer.ErrMissingValueForIndex 982 } 983 if p.ExpirationTime.Unix() < 0 { 984 return nil, fmt.Errorf("token expiration time cannot be before the unix epoch: %s", p.ExpirationTime) 985 } 986 987 var b indexer.IndexBuilder 988 b.Time(*p.ExpirationTime) 989 return b.Bytes(), nil 990 } 991 992 // oneTimeTokenTableSchema returns the MemDB schema for the tokens table. 993 // This table is used to store one-time tokens for ACL tokens 994 func oneTimeTokenTableSchema() *memdb.TableSchema { 995 return &memdb.TableSchema{ 996 Name: "one_time_token", 997 Indexes: map[string]*memdb.IndexSchema{ 998 "secret": { 999 Name: "secret", 1000 AllowMissing: false, 1001 Unique: true, 1002 Indexer: &memdb.UUIDFieldIndex{ 1003 Field: "OneTimeSecretID", 1004 }, 1005 }, 1006 "id": { 1007 Name: "id", 1008 AllowMissing: false, 1009 Unique: true, 1010 Indexer: &memdb.UUIDFieldIndex{ 1011 Field: "AccessorID", 1012 }, 1013 }, 1014 }, 1015 } 1016 } 1017 1018 // singletonRecord can be used to describe tables which should contain only 1 entry. 1019 // Example uses include storing node config or cluster metadata blobs. 1020 var singletonRecord = &memdb.ConditionalIndex{ 1021 Conditional: func(interface{}) (bool, error) { return true, nil }, 1022 } 1023 1024 // schedulerConfigTableSchema returns the MemDB schema for the scheduler config table. 1025 // This table is used to store configuration options for the scheduler 1026 func schedulerConfigTableSchema() *memdb.TableSchema { 1027 return &memdb.TableSchema{ 1028 Name: "scheduler_config", 1029 Indexes: map[string]*memdb.IndexSchema{ 1030 "id": { 1031 Name: "id", 1032 AllowMissing: true, 1033 Unique: true, 1034 Indexer: singletonRecord, // we store only 1 scheduler config 1035 }, 1036 }, 1037 } 1038 } 1039 1040 // clusterMetaTableSchema returns the MemDB schema for the scheduler config table. 1041 func clusterMetaTableSchema() *memdb.TableSchema { 1042 return &memdb.TableSchema{ 1043 Name: "cluster_meta", 1044 Indexes: map[string]*memdb.IndexSchema{ 1045 "id": { 1046 Name: "id", 1047 AllowMissing: false, 1048 Unique: true, 1049 Indexer: singletonRecord, // we store only 1 cluster metadata 1050 }, 1051 }, 1052 } 1053 } 1054 1055 // CSIVolumes are identified by id globally, and searchable by driver 1056 func csiVolumeTableSchema() *memdb.TableSchema { 1057 return &memdb.TableSchema{ 1058 Name: "csi_volumes", 1059 Indexes: map[string]*memdb.IndexSchema{ 1060 "id": { 1061 Name: "id", 1062 AllowMissing: false, 1063 Unique: true, 1064 Indexer: &memdb.CompoundIndex{ 1065 Indexes: []memdb.Indexer{ 1066 &memdb.StringFieldIndex{ 1067 Field: "Namespace", 1068 }, 1069 &memdb.StringFieldIndex{ 1070 Field: "ID", 1071 }, 1072 }, 1073 }, 1074 }, 1075 "plugin_id": { 1076 Name: "plugin_id", 1077 AllowMissing: false, 1078 Unique: false, 1079 Indexer: &memdb.StringFieldIndex{ 1080 Field: "PluginID", 1081 }, 1082 }, 1083 }, 1084 } 1085 } 1086 1087 // CSIPlugins are identified by id globally, and searchable by driver 1088 func csiPluginTableSchema() *memdb.TableSchema { 1089 return &memdb.TableSchema{ 1090 Name: "csi_plugins", 1091 Indexes: map[string]*memdb.IndexSchema{ 1092 "id": { 1093 Name: "id", 1094 AllowMissing: false, 1095 Unique: true, 1096 Indexer: &memdb.StringFieldIndex{ 1097 Field: "ID", 1098 }, 1099 }, 1100 }, 1101 } 1102 } 1103 1104 // StringFieldIndex is used to extract a field from an object 1105 // using reflection and builds an index on that field. 1106 type ScalingPolicyTargetFieldIndex struct { 1107 Field string 1108 1109 // AllowMissing controls if the field should be ignored if the field is 1110 // not provided. 1111 AllowMissing bool 1112 } 1113 1114 // FromObject is used to extract an index value from an 1115 // object or to indicate that the index value is missing. 1116 func (s *ScalingPolicyTargetFieldIndex) FromObject(obj interface{}) (bool, []byte, error) { 1117 policy, ok := obj.(*structs.ScalingPolicy) 1118 if !ok { 1119 return false, nil, fmt.Errorf("object %#v is not a ScalingPolicy", obj) 1120 } 1121 1122 if policy.Target == nil { 1123 return false, nil, nil 1124 } 1125 1126 val, ok := policy.Target[s.Field] 1127 if !ok && !s.AllowMissing { 1128 return false, nil, nil 1129 } 1130 1131 // Add the null character as a terminator 1132 val += "\x00" 1133 return true, []byte(val), nil 1134 } 1135 1136 // FromArgs is used to build an exact index lookup based on arguments 1137 func (s *ScalingPolicyTargetFieldIndex) FromArgs(args ...interface{}) ([]byte, error) { 1138 if len(args) != 1 { 1139 return nil, fmt.Errorf("must provide only a single argument") 1140 } 1141 arg, ok := args[0].(string) 1142 if !ok { 1143 return nil, fmt.Errorf("argument must be a string: %#v", args[0]) 1144 } 1145 // Add the null character as a terminator 1146 arg += "\x00" 1147 return []byte(arg), nil 1148 } 1149 1150 // PrefixFromArgs returns a prefix that should be used for scanning based on the arguments 1151 func (s *ScalingPolicyTargetFieldIndex) PrefixFromArgs(args ...interface{}) ([]byte, error) { 1152 val, err := s.FromArgs(args...) 1153 if err != nil { 1154 return nil, err 1155 } 1156 1157 // Strip the null terminator, the rest is a prefix 1158 n := len(val) 1159 if n > 0 { 1160 return val[:n-1], nil 1161 } 1162 return val, nil 1163 } 1164 1165 // scalingPolicyTableSchema returns the MemDB schema for the policy table. 1166 func scalingPolicyTableSchema() *memdb.TableSchema { 1167 return &memdb.TableSchema{ 1168 Name: "scaling_policy", 1169 Indexes: map[string]*memdb.IndexSchema{ 1170 // Primary index is used for simple direct lookup. 1171 "id": { 1172 Name: "id", 1173 AllowMissing: false, 1174 Unique: true, 1175 1176 // UUID is uniquely identifying 1177 Indexer: &memdb.StringFieldIndex{ 1178 Field: "ID", 1179 }, 1180 }, 1181 // Target index is used for listing by namespace or job, or looking up a specific target. 1182 // A given task group can have only a single scaling policies, so this is guaranteed to be unique. 1183 "target": { 1184 Name: "target", 1185 Unique: false, 1186 1187 // Use a compound index so the tuple of (Namespace, Job, Group, Task) is 1188 // used when looking for a policy 1189 Indexer: &memdb.CompoundIndex{ 1190 Indexes: []memdb.Indexer{ 1191 &ScalingPolicyTargetFieldIndex{ 1192 Field: "Namespace", 1193 AllowMissing: true, 1194 }, 1195 1196 &ScalingPolicyTargetFieldIndex{ 1197 Field: "Job", 1198 AllowMissing: true, 1199 }, 1200 1201 &ScalingPolicyTargetFieldIndex{ 1202 Field: "Group", 1203 AllowMissing: true, 1204 }, 1205 1206 &ScalingPolicyTargetFieldIndex{ 1207 Field: "Task", 1208 AllowMissing: true, 1209 }, 1210 }, 1211 }, 1212 }, 1213 // Type index is used for listing by policy type 1214 "type": { 1215 Name: "type", 1216 AllowMissing: false, 1217 Unique: false, 1218 Indexer: &memdb.StringFieldIndex{ 1219 Field: "Type", 1220 }, 1221 }, 1222 // Used to filter by enabled 1223 "enabled": { 1224 Name: "enabled", 1225 AllowMissing: false, 1226 Unique: false, 1227 Indexer: &memdb.FieldSetIndex{ 1228 Field: "Enabled", 1229 }, 1230 }, 1231 }, 1232 } 1233 } 1234 1235 // scalingEventTableSchema returns the memdb schema for job scaling events 1236 func scalingEventTableSchema() *memdb.TableSchema { 1237 return &memdb.TableSchema{ 1238 Name: "scaling_event", 1239 Indexes: map[string]*memdb.IndexSchema{ 1240 "id": { 1241 Name: "id", 1242 AllowMissing: false, 1243 Unique: true, 1244 1245 // Use a compound index so the tuple of (Namespace, JobID) is 1246 // uniquely identifying 1247 Indexer: &memdb.CompoundIndex{ 1248 Indexes: []memdb.Indexer{ 1249 &memdb.StringFieldIndex{ 1250 Field: "Namespace", 1251 }, 1252 1253 &memdb.StringFieldIndex{ 1254 Field: "JobID", 1255 }, 1256 }, 1257 }, 1258 }, 1259 1260 // TODO: need to figure out whether we want to index these or the jobs or ... 1261 // "error": { 1262 // Name: "error", 1263 // AllowMissing: false, 1264 // Unique: false, 1265 // Indexer: &memdb.FieldSetIndex{ 1266 // Field: "Error", 1267 // }, 1268 // }, 1269 }, 1270 } 1271 } 1272 1273 // namespaceTableSchema returns the MemDB schema for the namespace table. 1274 func namespaceTableSchema() *memdb.TableSchema { 1275 return &memdb.TableSchema{ 1276 Name: TableNamespaces, 1277 Indexes: map[string]*memdb.IndexSchema{ 1278 "id": { 1279 Name: "id", 1280 AllowMissing: false, 1281 Unique: true, 1282 Indexer: &memdb.StringFieldIndex{ 1283 Field: "Name", 1284 }, 1285 }, 1286 "quota": { 1287 Name: "quota", 1288 AllowMissing: true, 1289 Unique: false, 1290 Indexer: &memdb.StringFieldIndex{ 1291 Field: "Quota", 1292 }, 1293 }, 1294 }, 1295 } 1296 } 1297 1298 // serviceRegistrationsTableSchema returns the MemDB schema for Nomad native 1299 // service registrations. 1300 func serviceRegistrationsTableSchema() *memdb.TableSchema { 1301 return &memdb.TableSchema{ 1302 Name: TableServiceRegistrations, 1303 Indexes: map[string]*memdb.IndexSchema{ 1304 // The serviceID in combination with namespace forms a unique 1305 // identifier for a service registration. This is used to look up 1306 // and delete services in individual isolation. 1307 indexID: { 1308 Name: indexID, 1309 AllowMissing: false, 1310 Unique: true, 1311 Indexer: &memdb.CompoundIndex{ 1312 Indexes: []memdb.Indexer{ 1313 &memdb.StringFieldIndex{ 1314 Field: "Namespace", 1315 }, 1316 &memdb.StringFieldIndex{ 1317 Field: "ID", 1318 }, 1319 }, 1320 }, 1321 }, 1322 indexServiceName: { 1323 Name: indexServiceName, 1324 AllowMissing: false, 1325 Unique: false, 1326 Indexer: &memdb.CompoundIndex{ 1327 Indexes: []memdb.Indexer{ 1328 &memdb.StringFieldIndex{ 1329 Field: "Namespace", 1330 }, 1331 &memdb.StringFieldIndex{ 1332 Field: "ServiceName", 1333 }, 1334 }, 1335 }, 1336 }, 1337 indexJob: { 1338 Name: indexJob, 1339 AllowMissing: false, 1340 Unique: false, 1341 Indexer: &memdb.CompoundIndex{ 1342 Indexes: []memdb.Indexer{ 1343 &memdb.StringFieldIndex{ 1344 Field: "Namespace", 1345 }, 1346 &memdb.StringFieldIndex{ 1347 Field: "JobID", 1348 }, 1349 }, 1350 }, 1351 }, 1352 // The nodeID index allows lookups and deletions to be performed 1353 // for an entire node. This is primarily used when a node becomes 1354 // lost. 1355 indexNodeID: { 1356 Name: indexNodeID, 1357 AllowMissing: false, 1358 Unique: false, 1359 Indexer: &memdb.StringFieldIndex{ 1360 Field: "NodeID", 1361 }, 1362 }, 1363 indexAllocID: { 1364 Name: indexAllocID, 1365 AllowMissing: false, 1366 Unique: false, 1367 Indexer: &memdb.StringFieldIndex{ 1368 Field: "AllocID", 1369 }, 1370 }, 1371 }, 1372 } 1373 } 1374 1375 // variablesTableSchema returns the MemDB schema for Nomad variables. 1376 func variablesTableSchema() *memdb.TableSchema { 1377 return &memdb.TableSchema{ 1378 Name: TableVariables, 1379 Indexes: map[string]*memdb.IndexSchema{ 1380 indexID: { 1381 Name: indexID, 1382 AllowMissing: false, 1383 Unique: true, 1384 Indexer: &memdb.CompoundIndex{ 1385 Indexes: []memdb.Indexer{ 1386 &memdb.StringFieldIndex{ 1387 Field: "Namespace", 1388 }, 1389 &memdb.StringFieldIndex{ 1390 Field: "Path", 1391 }, 1392 }, 1393 }, 1394 }, 1395 indexKeyID: { 1396 Name: indexKeyID, 1397 AllowMissing: false, 1398 Indexer: &variableKeyIDFieldIndexer{}, 1399 }, 1400 indexPath: { 1401 Name: indexPath, 1402 AllowMissing: false, 1403 Unique: false, 1404 Indexer: &memdb.StringFieldIndex{ 1405 Field: "Path", 1406 }, 1407 }, 1408 }, 1409 } 1410 } 1411 1412 type variableKeyIDFieldIndexer struct{} 1413 1414 // FromArgs implements go-memdb/Indexer and is used to build an exact 1415 // index lookup based on arguments 1416 func (s *variableKeyIDFieldIndexer) FromArgs(args ...interface{}) ([]byte, error) { 1417 if len(args) != 1 { 1418 return nil, fmt.Errorf("must provide only a single argument") 1419 } 1420 arg, ok := args[0].(string) 1421 if !ok { 1422 return nil, fmt.Errorf("argument must be a string: %#v", args[0]) 1423 } 1424 // Add the null character as a terminator 1425 arg += "\x00" 1426 return []byte(arg), nil 1427 } 1428 1429 // PrefixFromArgs implements go-memdb/PrefixIndexer and returns a 1430 // prefix that should be used for scanning based on the arguments 1431 func (s *variableKeyIDFieldIndexer) PrefixFromArgs(args ...interface{}) ([]byte, error) { 1432 val, err := s.FromArgs(args...) 1433 if err != nil { 1434 return nil, err 1435 } 1436 1437 // Strip the null terminator, the rest is a prefix 1438 n := len(val) 1439 if n > 0 { 1440 return val[:n-1], nil 1441 } 1442 return val, nil 1443 } 1444 1445 // FromObject implements go-memdb/SingleIndexer and is used to extract 1446 // an index value from an object or to indicate that the index value 1447 // is missing. 1448 func (s *variableKeyIDFieldIndexer) FromObject(obj interface{}) (bool, []byte, error) { 1449 variable, ok := obj.(*structs.VariableEncrypted) 1450 if !ok { 1451 return false, nil, fmt.Errorf("object %#v is not a Variable", obj) 1452 } 1453 1454 keyID := variable.KeyID 1455 if keyID == "" { 1456 return false, nil, nil 1457 } 1458 1459 // Add the null character as a terminator 1460 keyID += "\x00" 1461 return true, []byte(keyID), nil 1462 } 1463 1464 // variablesQuotasTableSchema returns the MemDB schema for Nomad variables 1465 // quotas tracking 1466 func variablesQuotasTableSchema() *memdb.TableSchema { 1467 return &memdb.TableSchema{ 1468 Name: TableVariablesQuotas, 1469 Indexes: map[string]*memdb.IndexSchema{ 1470 indexID: { 1471 Name: indexID, 1472 AllowMissing: false, 1473 Unique: true, 1474 Indexer: &memdb.StringFieldIndex{ 1475 Field: "Namespace", 1476 Lowercase: true, 1477 }, 1478 }, 1479 }, 1480 } 1481 } 1482 1483 // variablesRootKeyMetaSchema returns the MemDB schema for Nomad root keys 1484 func variablesRootKeyMetaSchema() *memdb.TableSchema { 1485 return &memdb.TableSchema{ 1486 Name: TableRootKeyMeta, 1487 Indexes: map[string]*memdb.IndexSchema{ 1488 indexID: { 1489 Name: indexID, 1490 AllowMissing: false, 1491 Unique: true, 1492 Indexer: &memdb.StringFieldIndex{ 1493 Field: "KeyID", 1494 Lowercase: true, 1495 }, 1496 }, 1497 }, 1498 } 1499 } 1500 1501 func aclRolesTableSchema() *memdb.TableSchema { 1502 return &memdb.TableSchema{ 1503 Name: TableACLRoles, 1504 Indexes: map[string]*memdb.IndexSchema{ 1505 indexID: { 1506 Name: indexID, 1507 AllowMissing: false, 1508 Unique: true, 1509 Indexer: &memdb.StringFieldIndex{ 1510 Field: "ID", 1511 }, 1512 }, 1513 indexName: { 1514 Name: indexName, 1515 AllowMissing: false, 1516 Unique: true, 1517 Indexer: &memdb.StringFieldIndex{ 1518 Field: "Name", 1519 }, 1520 }, 1521 }, 1522 } 1523 } 1524 1525 func aclAuthMethodsTableSchema() *memdb.TableSchema { 1526 return &memdb.TableSchema{ 1527 Name: TableACLAuthMethods, 1528 Indexes: map[string]*memdb.IndexSchema{ 1529 indexID: { 1530 Name: indexID, 1531 AllowMissing: false, 1532 Unique: true, 1533 Indexer: &memdb.StringFieldIndex{ 1534 Field: "Name", 1535 }, 1536 }, 1537 }, 1538 } 1539 } 1540 1541 func bindingRulesTableSchema() *memdb.TableSchema { 1542 return &memdb.TableSchema{ 1543 Name: TableACLBindingRules, 1544 Indexes: map[string]*memdb.IndexSchema{ 1545 indexID: { 1546 Name: indexID, 1547 AllowMissing: false, 1548 Unique: true, 1549 Indexer: &memdb.StringFieldIndex{ 1550 Field: "ID", 1551 }, 1552 }, 1553 indexAuthMethod: { 1554 Name: indexAuthMethod, 1555 AllowMissing: false, 1556 Unique: false, 1557 Indexer: &memdb.StringFieldIndex{ 1558 Field: "AuthMethod", 1559 }, 1560 }, 1561 }, 1562 } 1563 }