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