github.com/aminovpavel/nomad@v0.11.8/nomad/state/schema.go (about) 1 package state 2 3 import ( 4 "fmt" 5 "sync" 6 7 memdb "github.com/hashicorp/go-memdb" 8 9 "github.com/hashicorp/nomad/nomad/structs" 10 ) 11 12 var ( 13 schemaFactories SchemaFactories 14 factoriesLock sync.Mutex 15 ) 16 17 // SchemaFactory is the factory method for returning a TableSchema 18 type SchemaFactory func() *memdb.TableSchema 19 type SchemaFactories []SchemaFactory 20 21 // RegisterSchemaFactories is used to register a table schema. 22 func RegisterSchemaFactories(factories ...SchemaFactory) { 23 factoriesLock.Lock() 24 defer factoriesLock.Unlock() 25 schemaFactories = append(schemaFactories, factories...) 26 } 27 28 func GetFactories() SchemaFactories { 29 return schemaFactories 30 } 31 32 func init() { 33 // Register all schemas 34 RegisterSchemaFactories([]SchemaFactory{ 35 indexTableSchema, 36 nodeTableSchema, 37 jobTableSchema, 38 jobSummarySchema, 39 jobVersionSchema, 40 deploymentSchema, 41 periodicLaunchTableSchema, 42 evalTableSchema, 43 allocTableSchema, 44 vaultAccessorTableSchema, 45 siTokenAccessorTableSchema, 46 aclPolicyTableSchema, 47 aclTokenTableSchema, 48 autopilotConfigTableSchema, 49 schedulerConfigTableSchema, 50 clusterMetaTableSchema, 51 csiVolumeTableSchema, 52 csiPluginTableSchema, 53 scalingPolicyTableSchema, 54 scalingEventTableSchema, 55 }...) 56 } 57 58 // stateStoreSchema is used to return the schema for the state store 59 func stateStoreSchema() *memdb.DBSchema { 60 // Create the root DB schema 61 db := &memdb.DBSchema{ 62 Tables: make(map[string]*memdb.TableSchema), 63 } 64 65 // Add each of the tables 66 for _, schemaFn := range GetFactories() { 67 schema := schemaFn() 68 if _, ok := db.Tables[schema.Name]; ok { 69 panic(fmt.Sprintf("duplicate table name: %s", schema.Name)) 70 } 71 db.Tables[schema.Name] = schema 72 } 73 return db 74 } 75 76 // indexTableSchema is used for tracking the most recent index used for each table. 77 func indexTableSchema() *memdb.TableSchema { 78 return &memdb.TableSchema{ 79 Name: "index", 80 Indexes: map[string]*memdb.IndexSchema{ 81 "id": { 82 Name: "id", 83 AllowMissing: false, 84 Unique: true, 85 Indexer: &memdb.StringFieldIndex{ 86 Field: "Key", 87 Lowercase: true, 88 }, 89 }, 90 }, 91 } 92 } 93 94 // nodeTableSchema returns the MemDB schema for the nodes table. 95 // This table is used to store all the client nodes that are registered. 96 func nodeTableSchema() *memdb.TableSchema { 97 return &memdb.TableSchema{ 98 Name: "nodes", 99 Indexes: map[string]*memdb.IndexSchema{ 100 // Primary index is used for node management 101 // and simple direct lookup. ID is required to be 102 // unique. 103 "id": { 104 Name: "id", 105 AllowMissing: false, 106 Unique: true, 107 Indexer: &memdb.UUIDFieldIndex{ 108 Field: "ID", 109 }, 110 }, 111 "secret_id": { 112 Name: "secret_id", 113 AllowMissing: false, 114 Unique: true, 115 Indexer: &memdb.UUIDFieldIndex{ 116 Field: "SecretID", 117 }, 118 }, 119 }, 120 } 121 } 122 123 // jobTableSchema returns the MemDB schema for the jobs table. 124 // This table is used to store all the jobs that have been submitted. 125 func jobTableSchema() *memdb.TableSchema { 126 return &memdb.TableSchema{ 127 Name: "jobs", 128 Indexes: map[string]*memdb.IndexSchema{ 129 // Primary index is used for job management 130 // and simple direct lookup. ID is required to be 131 // unique within a namespace. 132 "id": { 133 Name: "id", 134 AllowMissing: false, 135 Unique: true, 136 137 // Use a compound index so the tuple of (Namespace, ID) is 138 // uniquely identifying 139 Indexer: &memdb.CompoundIndex{ 140 Indexes: []memdb.Indexer{ 141 &memdb.StringFieldIndex{ 142 Field: "Namespace", 143 }, 144 145 &memdb.StringFieldIndex{ 146 Field: "ID", 147 }, 148 }, 149 }, 150 }, 151 "type": { 152 Name: "type", 153 AllowMissing: false, 154 Unique: false, 155 Indexer: &memdb.StringFieldIndex{ 156 Field: "Type", 157 Lowercase: false, 158 }, 159 }, 160 "gc": { 161 Name: "gc", 162 AllowMissing: false, 163 Unique: false, 164 Indexer: &memdb.ConditionalIndex{ 165 Conditional: jobIsGCable, 166 }, 167 }, 168 "periodic": { 169 Name: "periodic", 170 AllowMissing: false, 171 Unique: false, 172 Indexer: &memdb.ConditionalIndex{ 173 Conditional: jobIsPeriodic, 174 }, 175 }, 176 }, 177 } 178 } 179 180 // jobSummarySchema returns the memdb schema for the job summary table 181 func jobSummarySchema() *memdb.TableSchema { 182 return &memdb.TableSchema{ 183 Name: "job_summary", 184 Indexes: map[string]*memdb.IndexSchema{ 185 "id": { 186 Name: "id", 187 AllowMissing: false, 188 Unique: true, 189 190 // Use a compound index so the tuple of (Namespace, JobID) is 191 // uniquely identifying 192 Indexer: &memdb.CompoundIndex{ 193 Indexes: []memdb.Indexer{ 194 &memdb.StringFieldIndex{ 195 Field: "Namespace", 196 }, 197 198 &memdb.StringFieldIndex{ 199 Field: "JobID", 200 }, 201 }, 202 }, 203 }, 204 }, 205 } 206 } 207 208 // jobVersionSchema returns the memdb schema for the job version table which 209 // keeps a historical view of job versions. 210 func jobVersionSchema() *memdb.TableSchema { 211 return &memdb.TableSchema{ 212 Name: "job_version", 213 Indexes: map[string]*memdb.IndexSchema{ 214 "id": { 215 Name: "id", 216 AllowMissing: false, 217 Unique: true, 218 219 // Use a compound index so the tuple of (Namespace, ID, Version) is 220 // uniquely identifying 221 Indexer: &memdb.CompoundIndex{ 222 Indexes: []memdb.Indexer{ 223 &memdb.StringFieldIndex{ 224 Field: "Namespace", 225 }, 226 227 &memdb.StringFieldIndex{ 228 Field: "ID", 229 Lowercase: true, 230 }, 231 232 &memdb.UintFieldIndex{ 233 Field: "Version", 234 }, 235 }, 236 }, 237 }, 238 }, 239 } 240 } 241 242 // jobIsGCable satisfies the ConditionalIndexFunc interface and creates an index 243 // on whether a job is eligible for garbage collection. 244 func jobIsGCable(obj interface{}) (bool, error) { 245 j, ok := obj.(*structs.Job) 246 if !ok { 247 return false, fmt.Errorf("Unexpected type: %v", obj) 248 } 249 250 // If the job is periodic or parameterized it is only garbage collectable if 251 // it is stopped. 252 periodic := j.Periodic != nil && j.Periodic.Enabled 253 parameterized := j.IsParameterized() 254 if periodic || parameterized { 255 return j.Stop, nil 256 } 257 258 // If the job isn't dead it isn't eligible 259 if j.Status != structs.JobStatusDead { 260 return false, nil 261 } 262 263 // Any job that is stopped is eligible for garbage collection 264 if j.Stop { 265 return true, nil 266 } 267 268 // Otherwise, only batch jobs are eligible because they complete on their 269 // own without a user stopping them. 270 if j.Type != structs.JobTypeBatch { 271 return false, nil 272 } 273 274 return true, nil 275 } 276 277 // jobIsPeriodic satisfies the ConditionalIndexFunc interface and creates an index 278 // on whether a job is periodic. 279 func jobIsPeriodic(obj interface{}) (bool, error) { 280 j, ok := obj.(*structs.Job) 281 if !ok { 282 return false, fmt.Errorf("Unexpected type: %v", obj) 283 } 284 285 if j.Periodic != nil && j.Periodic.Enabled == true { 286 return true, nil 287 } 288 289 return false, nil 290 } 291 292 // deploymentSchema returns the MemDB schema tracking a job's deployments 293 func deploymentSchema() *memdb.TableSchema { 294 return &memdb.TableSchema{ 295 Name: "deployment", 296 Indexes: map[string]*memdb.IndexSchema{ 297 "id": { 298 Name: "id", 299 AllowMissing: false, 300 Unique: true, 301 Indexer: &memdb.UUIDFieldIndex{ 302 Field: "ID", 303 }, 304 }, 305 306 "namespace": { 307 Name: "namespace", 308 AllowMissing: false, 309 Unique: false, 310 Indexer: &memdb.StringFieldIndex{ 311 Field: "Namespace", 312 }, 313 }, 314 315 // Job index is used to lookup deployments by job 316 "job": { 317 Name: "job", 318 AllowMissing: false, 319 Unique: false, 320 321 // Use a compound index so the tuple of (Namespace, JobID) is 322 // uniquely identifying 323 Indexer: &memdb.CompoundIndex{ 324 Indexes: []memdb.Indexer{ 325 &memdb.StringFieldIndex{ 326 Field: "Namespace", 327 }, 328 329 &memdb.StringFieldIndex{ 330 Field: "JobID", 331 }, 332 }, 333 }, 334 }, 335 }, 336 } 337 } 338 339 // periodicLaunchTableSchema returns the MemDB schema tracking the most recent 340 // launch time for a periodic job. 341 func periodicLaunchTableSchema() *memdb.TableSchema { 342 return &memdb.TableSchema{ 343 Name: "periodic_launch", 344 Indexes: map[string]*memdb.IndexSchema{ 345 // Primary index is used for job management 346 // and simple direct lookup. ID is required to be 347 // unique. 348 "id": { 349 Name: "id", 350 AllowMissing: false, 351 Unique: true, 352 353 // Use a compound index so the tuple of (Namespace, JobID) is 354 // uniquely identifying 355 Indexer: &memdb.CompoundIndex{ 356 Indexes: []memdb.Indexer{ 357 &memdb.StringFieldIndex{ 358 Field: "Namespace", 359 }, 360 361 &memdb.StringFieldIndex{ 362 Field: "ID", 363 }, 364 }, 365 }, 366 }, 367 }, 368 } 369 } 370 371 // evalTableSchema returns the MemDB schema for the eval table. 372 // This table is used to store all the evaluations that are pending 373 // or recently completed. 374 func evalTableSchema() *memdb.TableSchema { 375 return &memdb.TableSchema{ 376 Name: "evals", 377 Indexes: map[string]*memdb.IndexSchema{ 378 // Primary index is used for direct lookup. 379 "id": { 380 Name: "id", 381 AllowMissing: false, 382 Unique: true, 383 Indexer: &memdb.UUIDFieldIndex{ 384 Field: "ID", 385 }, 386 }, 387 388 "namespace": { 389 Name: "namespace", 390 AllowMissing: false, 391 Unique: false, 392 Indexer: &memdb.StringFieldIndex{ 393 Field: "Namespace", 394 }, 395 }, 396 397 // Job index is used to lookup allocations by job 398 "job": { 399 Name: "job", 400 AllowMissing: false, 401 Unique: false, 402 Indexer: &memdb.CompoundIndex{ 403 Indexes: []memdb.Indexer{ 404 &memdb.StringFieldIndex{ 405 Field: "Namespace", 406 }, 407 408 &memdb.StringFieldIndex{ 409 Field: "JobID", 410 Lowercase: true, 411 }, 412 413 &memdb.StringFieldIndex{ 414 Field: "Status", 415 Lowercase: true, 416 }, 417 }, 418 }, 419 }, 420 }, 421 } 422 } 423 424 // allocTableSchema returns the MemDB schema for the allocation table. 425 // This table is used to store all the task allocations between task groups 426 // and nodes. 427 func allocTableSchema() *memdb.TableSchema { 428 return &memdb.TableSchema{ 429 Name: "allocs", 430 Indexes: map[string]*memdb.IndexSchema{ 431 // Primary index is a UUID 432 "id": { 433 Name: "id", 434 AllowMissing: false, 435 Unique: true, 436 Indexer: &memdb.UUIDFieldIndex{ 437 Field: "ID", 438 }, 439 }, 440 441 "namespace": { 442 Name: "namespace", 443 AllowMissing: false, 444 Unique: false, 445 Indexer: &memdb.StringFieldIndex{ 446 Field: "Namespace", 447 }, 448 }, 449 450 // Node index is used to lookup allocations by node 451 "node": { 452 Name: "node", 453 AllowMissing: true, // Missing is allow for failed allocations 454 Unique: false, 455 Indexer: &memdb.CompoundIndex{ 456 Indexes: []memdb.Indexer{ 457 &memdb.StringFieldIndex{ 458 Field: "NodeID", 459 Lowercase: true, 460 }, 461 462 // Conditional indexer on if allocation is terminal 463 &memdb.ConditionalIndex{ 464 Conditional: func(obj interface{}) (bool, error) { 465 // Cast to allocation 466 alloc, ok := obj.(*structs.Allocation) 467 if !ok { 468 return false, fmt.Errorf("wrong type, got %t should be Allocation", obj) 469 } 470 471 // Check if the allocation is terminal 472 return alloc.TerminalStatus(), nil 473 }, 474 }, 475 }, 476 }, 477 }, 478 479 // Job index is used to lookup allocations by job 480 "job": { 481 Name: "job", 482 AllowMissing: false, 483 Unique: false, 484 485 Indexer: &memdb.CompoundIndex{ 486 Indexes: []memdb.Indexer{ 487 &memdb.StringFieldIndex{ 488 Field: "Namespace", 489 }, 490 491 &memdb.StringFieldIndex{ 492 Field: "JobID", 493 }, 494 }, 495 }, 496 }, 497 498 // Eval index is used to lookup allocations by eval 499 "eval": { 500 Name: "eval", 501 AllowMissing: false, 502 Unique: false, 503 Indexer: &memdb.UUIDFieldIndex{ 504 Field: "EvalID", 505 }, 506 }, 507 508 // Deployment index is used to lookup allocations by deployment 509 "deployment": { 510 Name: "deployment", 511 AllowMissing: true, 512 Unique: false, 513 Indexer: &memdb.UUIDFieldIndex{ 514 Field: "DeploymentID", 515 }, 516 }, 517 }, 518 } 519 } 520 521 // vaultAccessorTableSchema returns the MemDB schema for the Vault Accessor 522 // Table. This table tracks Vault accessors for tokens created on behalf of 523 // allocations required Vault tokens. 524 func vaultAccessorTableSchema() *memdb.TableSchema { 525 return &memdb.TableSchema{ 526 Name: "vault_accessors", 527 Indexes: map[string]*memdb.IndexSchema{ 528 // The primary index is the accessor id 529 "id": { 530 Name: "id", 531 AllowMissing: false, 532 Unique: true, 533 Indexer: &memdb.StringFieldIndex{ 534 Field: "Accessor", 535 }, 536 }, 537 538 "alloc_id": { 539 Name: "alloc_id", 540 AllowMissing: false, 541 Unique: false, 542 Indexer: &memdb.StringFieldIndex{ 543 Field: "AllocID", 544 }, 545 }, 546 547 "node_id": { 548 Name: "node_id", 549 AllowMissing: false, 550 Unique: false, 551 Indexer: &memdb.StringFieldIndex{ 552 Field: "NodeID", 553 }, 554 }, 555 }, 556 } 557 } 558 559 // siTokenAccessorTableSchema returns the MemDB schema for the Service Identity 560 // token accessor table. This table tracks accessors for tokens created on behalf 561 // of allocations with Consul connect enabled tasks that need SI tokens. 562 func siTokenAccessorTableSchema() *memdb.TableSchema { 563 return &memdb.TableSchema{ 564 Name: siTokenAccessorTable, 565 Indexes: map[string]*memdb.IndexSchema{ 566 // The primary index is the accessor id 567 "id": { 568 Name: "id", 569 AllowMissing: false, 570 Unique: true, 571 Indexer: &memdb.StringFieldIndex{ 572 Field: "AccessorID", 573 }, 574 }, 575 576 "alloc_id": { 577 Name: "alloc_id", 578 AllowMissing: false, 579 Unique: false, 580 Indexer: &memdb.StringFieldIndex{ 581 Field: "AllocID", 582 }, 583 }, 584 585 "node_id": { 586 Name: "node_id", 587 AllowMissing: false, 588 Unique: false, 589 Indexer: &memdb.StringFieldIndex{ 590 Field: "NodeID", 591 }, 592 }, 593 }, 594 } 595 } 596 597 // aclPolicyTableSchema returns the MemDB schema for the policy table. 598 // This table is used to store the policies which are referenced by tokens 599 func aclPolicyTableSchema() *memdb.TableSchema { 600 return &memdb.TableSchema{ 601 Name: "acl_policy", 602 Indexes: map[string]*memdb.IndexSchema{ 603 "id": { 604 Name: "id", 605 AllowMissing: false, 606 Unique: true, 607 Indexer: &memdb.StringFieldIndex{ 608 Field: "Name", 609 }, 610 }, 611 }, 612 } 613 } 614 615 // aclTokenTableSchema returns the MemDB schema for the tokens table. 616 // This table is used to store the bearer tokens which are used to authenticate 617 func aclTokenTableSchema() *memdb.TableSchema { 618 return &memdb.TableSchema{ 619 Name: "acl_token", 620 Indexes: map[string]*memdb.IndexSchema{ 621 "id": { 622 Name: "id", 623 AllowMissing: false, 624 Unique: true, 625 Indexer: &memdb.UUIDFieldIndex{ 626 Field: "AccessorID", 627 }, 628 }, 629 "secret": { 630 Name: "secret", 631 AllowMissing: false, 632 Unique: true, 633 Indexer: &memdb.UUIDFieldIndex{ 634 Field: "SecretID", 635 }, 636 }, 637 "global": { 638 Name: "global", 639 AllowMissing: false, 640 Unique: false, 641 Indexer: &memdb.FieldSetIndex{ 642 Field: "Global", 643 }, 644 }, 645 }, 646 } 647 } 648 649 // singletonRecord can be used to describe tables which should contain only 1 entry. 650 // Example uses include storing node config or cluster metadata blobs. 651 var singletonRecord = &memdb.ConditionalIndex{ 652 Conditional: func(interface{}) (bool, error) { return true, nil }, 653 } 654 655 // schedulerConfigTableSchema returns the MemDB schema for the scheduler config table. 656 // This table is used to store configuration options for the scheduler 657 func schedulerConfigTableSchema() *memdb.TableSchema { 658 return &memdb.TableSchema{ 659 Name: "scheduler_config", 660 Indexes: map[string]*memdb.IndexSchema{ 661 "id": { 662 Name: "id", 663 AllowMissing: true, 664 Unique: true, 665 Indexer: singletonRecord, // we store only 1 scheduler config 666 }, 667 }, 668 } 669 } 670 671 // clusterMetaTableSchema returns the MemDB schema for the scheduler config table. 672 func clusterMetaTableSchema() *memdb.TableSchema { 673 return &memdb.TableSchema{ 674 Name: "cluster_meta", 675 Indexes: map[string]*memdb.IndexSchema{ 676 "id": { 677 Name: "id", 678 AllowMissing: false, 679 Unique: true, 680 Indexer: singletonRecord, // we store only 1 cluster metadata 681 }, 682 }, 683 } 684 } 685 686 // CSIVolumes are identified by id globally, and searchable by driver 687 func csiVolumeTableSchema() *memdb.TableSchema { 688 return &memdb.TableSchema{ 689 Name: "csi_volumes", 690 Indexes: map[string]*memdb.IndexSchema{ 691 "id": { 692 Name: "id", 693 AllowMissing: false, 694 Unique: true, 695 Indexer: &memdb.CompoundIndex{ 696 Indexes: []memdb.Indexer{ 697 &memdb.StringFieldIndex{ 698 Field: "Namespace", 699 }, 700 &memdb.StringFieldIndex{ 701 Field: "ID", 702 }, 703 }, 704 }, 705 }, 706 "plugin_id": { 707 Name: "plugin_id", 708 AllowMissing: false, 709 Unique: false, 710 Indexer: &memdb.StringFieldIndex{ 711 Field: "PluginID", 712 }, 713 }, 714 }, 715 } 716 } 717 718 // CSIPlugins are identified by id globally, and searchable by driver 719 func csiPluginTableSchema() *memdb.TableSchema { 720 return &memdb.TableSchema{ 721 Name: "csi_plugins", 722 Indexes: map[string]*memdb.IndexSchema{ 723 "id": { 724 Name: "id", 725 AllowMissing: false, 726 Unique: true, 727 Indexer: &memdb.StringFieldIndex{ 728 Field: "ID", 729 }, 730 }, 731 }, 732 } 733 } 734 735 // StringFieldIndex is used to extract a field from an object 736 // using reflection and builds an index on that field. 737 type ScalingPolicyTargetFieldIndex struct { 738 Field string 739 } 740 741 // FromObject is used to extract an index value from an 742 // object or to indicate that the index value is missing. 743 func (s *ScalingPolicyTargetFieldIndex) FromObject(obj interface{}) (bool, []byte, error) { 744 policy, ok := obj.(*structs.ScalingPolicy) 745 if !ok { 746 return false, nil, fmt.Errorf("object %#v is not a ScalingPolicy", obj) 747 } 748 749 if policy.Target == nil { 750 return false, nil, nil 751 } 752 753 val, ok := policy.Target[s.Field] 754 if !ok { 755 return false, nil, nil 756 } 757 758 // Add the null character as a terminator 759 val += "\x00" 760 return true, []byte(val), nil 761 } 762 763 // FromArgs is used to build an exact index lookup based on arguments 764 func (s *ScalingPolicyTargetFieldIndex) FromArgs(args ...interface{}) ([]byte, error) { 765 if len(args) != 1 { 766 return nil, fmt.Errorf("must provide only a single argument") 767 } 768 arg, ok := args[0].(string) 769 if !ok { 770 return nil, fmt.Errorf("argument must be a string: %#v", args[0]) 771 } 772 // Add the null character as a terminator 773 arg += "\x00" 774 return []byte(arg), nil 775 } 776 777 // PrefixFromArgs returns a prefix that should be used for scanning based on the arguments 778 func (s *ScalingPolicyTargetFieldIndex) PrefixFromArgs(args ...interface{}) ([]byte, error) { 779 val, err := s.FromArgs(args...) 780 if err != nil { 781 return nil, err 782 } 783 784 // Strip the null terminator, the rest is a prefix 785 n := len(val) 786 if n > 0 { 787 return val[:n-1], nil 788 } 789 return val, nil 790 } 791 792 // scalingPolicyTableSchema returns the MemDB schema for the policy table. 793 func scalingPolicyTableSchema() *memdb.TableSchema { 794 return &memdb.TableSchema{ 795 Name: "scaling_policy", 796 Indexes: map[string]*memdb.IndexSchema{ 797 // Primary index is used for simple direct lookup. 798 "id": { 799 Name: "id", 800 AllowMissing: false, 801 Unique: true, 802 803 // UUID is uniquely identifying 804 Indexer: &memdb.StringFieldIndex{ 805 Field: "ID", 806 }, 807 }, 808 // Target index is used for listing by namespace or job, or looking up a specific target. 809 // A given task group can have only a single scaling policies, so this is guaranteed to be unique. 810 "target": { 811 Name: "target", 812 AllowMissing: false, 813 Unique: true, 814 815 // Use a compound index so the tuple of (Namespace, Job, Group) is 816 // uniquely identifying 817 Indexer: &memdb.CompoundIndex{ 818 Indexes: []memdb.Indexer{ 819 &ScalingPolicyTargetFieldIndex{ 820 Field: "Namespace", 821 }, 822 823 &ScalingPolicyTargetFieldIndex{ 824 Field: "Job", 825 }, 826 827 &ScalingPolicyTargetFieldIndex{ 828 Field: "Group", 829 }, 830 }, 831 }, 832 }, 833 // Used to filter by enabled 834 "enabled": { 835 Name: "enabled", 836 AllowMissing: false, 837 Unique: false, 838 Indexer: &memdb.FieldSetIndex{ 839 Field: "Enabled", 840 }, 841 }, 842 }, 843 } 844 } 845 846 // scalingEventTableSchema returns the memdb schema for job scaling events 847 func scalingEventTableSchema() *memdb.TableSchema { 848 return &memdb.TableSchema{ 849 Name: "scaling_event", 850 Indexes: map[string]*memdb.IndexSchema{ 851 "id": { 852 Name: "id", 853 AllowMissing: false, 854 Unique: true, 855 856 // Use a compound index so the tuple of (Namespace, JobID) is 857 // uniquely identifying 858 Indexer: &memdb.CompoundIndex{ 859 Indexes: []memdb.Indexer{ 860 &memdb.StringFieldIndex{ 861 Field: "Namespace", 862 }, 863 864 &memdb.StringFieldIndex{ 865 Field: "JobID", 866 }, 867 }, 868 }, 869 }, 870 871 // TODO: need to figure out whether we want to index these or the jobs or ... 872 // "error": { 873 // Name: "error", 874 // AllowMissing: false, 875 // Unique: false, 876 // Indexer: &memdb.FieldSetIndex{ 877 // Field: "Error", 878 // }, 879 // }, 880 }, 881 } 882 }