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  }