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