open-nomad/nomad/state/schema.go

804 lines
19 KiB
Go
Raw Normal View History

package state
import (
"fmt"
2017-09-07 23:56:15 +00:00
"sync"
2019-01-15 19:46:12 +00:00
memdb "github.com/hashicorp/go-memdb"
2015-12-15 03:20:57 +00:00
"github.com/hashicorp/nomad/nomad/structs"
)
2017-09-07 23:56:15 +00:00
var (
schemaFactories SchemaFactories
factoriesLock sync.Mutex
)
2017-09-07 23:56:15 +00:00
// SchemaFactory is the factory method for returning a TableSchema
type SchemaFactory func() *memdb.TableSchema
type SchemaFactories []SchemaFactory
// RegisterSchemaFactories is used to register a table schema.
func RegisterSchemaFactories(factories ...SchemaFactory) {
factoriesLock.Lock()
defer factoriesLock.Unlock()
schemaFactories = append(schemaFactories, factories...)
}
func GetFactories() SchemaFactories {
return schemaFactories
}
func init() {
// Register all schemas
RegisterSchemaFactories([]SchemaFactory{
indexTableSchema,
nodeTableSchema,
jobTableSchema,
jobSummarySchema,
jobVersionSchema,
deploymentSchema,
2015-12-19 01:51:30 +00:00
periodicLaunchTableSchema,
2015-07-23 22:27:13 +00:00
evalTableSchema,
2015-07-04 00:11:53 +00:00
allocTableSchema,
vaultAccessorTableSchema,
siTokenAccessorTableSchema,
aclPolicyTableSchema,
2017-08-12 21:36:20 +00:00
aclTokenTableSchema,
autopilotConfigTableSchema,
schedulerConfigTableSchema,
clusterMetaTableSchema,
2019-10-15 15:43:55 +00:00
csiVolumeTableSchema,
csiPluginTableSchema,
scalingPolicyTableSchema,
2017-09-07 23:56:15 +00:00
}...)
}
// stateStoreSchema is used to return the schema for the state store
func stateStoreSchema() *memdb.DBSchema {
// Create the root DB schema
db := &memdb.DBSchema{
Tables: make(map[string]*memdb.TableSchema),
}
// Add each of the tables
2017-09-07 23:56:15 +00:00
for _, schemaFn := range GetFactories() {
schema := schemaFn()
if _, ok := db.Tables[schema.Name]; ok {
panic(fmt.Sprintf("duplicate table name: %s", schema.Name))
}
db.Tables[schema.Name] = schema
}
return db
}
// indexTableSchema is used for tracking the most recent index used for each table.
func indexTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "index",
Indexes: map[string]*memdb.IndexSchema{
2017-09-26 22:26:33 +00:00
"id": {
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "Key",
Lowercase: true,
},
},
},
}
}
// nodeTableSchema returns the MemDB schema for the nodes table.
// This table is used to store all the client nodes that are registered.
func nodeTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "nodes",
Indexes: map[string]*memdb.IndexSchema{
// Primary index is used for node management
// and simple direct lookup. ID is required to be
// unique.
2017-09-26 22:26:33 +00:00
"id": {
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.UUIDFieldIndex{
Field: "ID",
},
},
2017-10-12 22:21:20 +00:00
"secret_id": {
Name: "secret_id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.UUIDFieldIndex{
Field: "SecretID",
},
},
},
}
}
// jobTableSchema returns the MemDB schema for the jobs table.
// This table is used to store all the jobs that have been submitted.
func jobTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "jobs",
Indexes: map[string]*memdb.IndexSchema{
// Primary index is used for job management
// and simple direct lookup. ID is required to be
2017-09-07 23:56:15 +00:00
// unique within a namespace.
2017-09-26 22:26:33 +00:00
"id": {
Name: "id",
AllowMissing: false,
Unique: true,
2017-09-07 23:56:15 +00:00
// Use a compound index so the tuple of (Namespace, ID) is
// uniquely identifying
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{
Field: "Namespace",
},
&memdb.StringFieldIndex{
Field: "ID",
},
},
},
},
2017-09-26 22:26:33 +00:00
"type": {
Name: "type",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "Type",
Lowercase: false,
},
},
2017-09-26 22:26:33 +00:00
"gc": {
2015-12-15 03:20:57 +00:00
Name: "gc",
AllowMissing: false,
Unique: false,
Indexer: &memdb.ConditionalIndex{
Conditional: jobIsGCable,
},
},
2017-09-26 22:26:33 +00:00
"periodic": {
2015-12-04 17:49:42 +00:00
Name: "periodic",
AllowMissing: false,
Unique: false,
2015-12-24 00:31:54 +00:00
Indexer: &memdb.ConditionalIndex{
Conditional: jobIsPeriodic,
2015-12-04 17:49:42 +00:00
},
},
},
}
}
// jobSummarySchema returns the memdb schema for the job summary table
func jobSummarySchema() *memdb.TableSchema {
return &memdb.TableSchema{
2016-07-12 22:00:35 +00:00
Name: "job_summary",
Indexes: map[string]*memdb.IndexSchema{
2017-09-26 22:26:33 +00:00
"id": {
Name: "id",
AllowMissing: false,
Unique: true,
2017-09-07 23:56:15 +00:00
// Use a compound index so the tuple of (Namespace, JobID) is
// uniquely identifying
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{
Field: "Namespace",
},
&memdb.StringFieldIndex{
Field: "JobID",
},
},
},
},
},
}
}
// jobVersionSchema returns the memdb schema for the job version table which
2017-04-13 21:54:22 +00:00
// keeps a historical view of job versions.
func jobVersionSchema() *memdb.TableSchema {
2017-04-12 22:44:30 +00:00
return &memdb.TableSchema{
Name: "job_version",
2017-04-12 22:44:30 +00:00
Indexes: map[string]*memdb.IndexSchema{
2017-09-26 22:26:33 +00:00
"id": {
2017-04-12 22:44:30 +00:00
Name: "id",
AllowMissing: false,
Unique: true,
2017-09-07 23:56:15 +00:00
// Use a compound index so the tuple of (Namespace, ID, Version) is
2017-04-12 22:44:30 +00:00
// uniquely identifying
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
2017-09-07 23:56:15 +00:00
&memdb.StringFieldIndex{
Field: "Namespace",
},
2017-04-12 22:44:30 +00:00
&memdb.StringFieldIndex{
2017-04-13 20:54:57 +00:00
Field: "ID",
2017-04-12 22:44:30 +00:00
Lowercase: true,
},
&memdb.UintFieldIndex{
Field: "Version",
},
},
},
},
},
}
}
2015-12-15 03:20:57 +00:00
// jobIsGCable satisfies the ConditionalIndexFunc interface and creates an index
// on whether a job is eligible for garbage collection.
func jobIsGCable(obj interface{}) (bool, error) {
j, ok := obj.(*structs.Job)
if !ok {
return false, fmt.Errorf("Unexpected type: %v", obj)
}
2017-04-15 23:47:19 +00:00
// If the job is periodic or parameterized it is only garbage collectable if
// it is stopped.
periodic := j.Periodic != nil && j.Periodic.Enabled
2017-04-15 23:47:19 +00:00
parameterized := j.IsParameterized()
if periodic || parameterized {
return j.Stop, nil
}
// If the job isn't dead it isn't eligible
if j.Status != structs.JobStatusDead {
return false, nil
}
// Any job that is stopped is eligible for garbage collection
if j.Stop {
return true, nil
}
// Otherwise, only batch jobs are eligible because they complete on their
// own without a user stopping them.
if j.Type != structs.JobTypeBatch {
return false, nil
}
return true, nil
2015-12-15 03:20:57 +00:00
}
2015-12-24 00:31:54 +00:00
// jobIsPeriodic satisfies the ConditionalIndexFunc interface and creates an index
// on whether a job is periodic.
func jobIsPeriodic(obj interface{}) (bool, error) {
j, ok := obj.(*structs.Job)
if !ok {
return false, fmt.Errorf("Unexpected type: %v", obj)
}
if j.Periodic != nil && j.Periodic.Enabled == true {
return true, nil
}
return false, nil
}
// deploymentSchema returns the MemDB schema tracking a job's deployments
func deploymentSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "deployment",
Indexes: map[string]*memdb.IndexSchema{
2017-09-26 22:26:33 +00:00
"id": {
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.UUIDFieldIndex{
Field: "ID",
},
},
2017-09-26 22:26:33 +00:00
"namespace": {
2017-09-07 23:56:15 +00:00
Name: "namespace",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "Namespace",
},
},
2017-05-10 22:26:00 +00:00
// Job index is used to lookup deployments by job
2017-09-26 22:26:33 +00:00
"job": {
Name: "job",
AllowMissing: false,
Unique: false,
2017-09-07 23:56:15 +00:00
// Use a compound index so the tuple of (Namespace, JobID) is
// uniquely identifying
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{
Field: "Namespace",
},
&memdb.StringFieldIndex{
Field: "JobID",
},
},
},
},
},
}
}
2015-12-19 01:51:30 +00:00
// periodicLaunchTableSchema returns the MemDB schema tracking the most recent
2018-03-11 18:37:05 +00:00
// launch time for a periodic job.
2015-12-19 01:51:30 +00:00
func periodicLaunchTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "periodic_launch",
Indexes: map[string]*memdb.IndexSchema{
// Primary index is used for job management
// and simple direct lookup. ID is required to be
// unique.
2017-09-26 22:26:33 +00:00
"id": {
2015-12-19 01:51:30 +00:00
Name: "id",
AllowMissing: false,
Unique: true,
2017-09-07 23:56:15 +00:00
// Use a compound index so the tuple of (Namespace, JobID) is
// uniquely identifying
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{
Field: "Namespace",
},
&memdb.StringFieldIndex{
Field: "ID",
},
},
2015-12-19 01:51:30 +00:00
},
},
},
}
}
2015-07-23 22:27:13 +00:00
// evalTableSchema returns the MemDB schema for the eval table.
// This table is used to store all the evaluations that are pending
// or recently completed.
func evalTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
2015-07-23 22:27:13 +00:00
Name: "evals",
Indexes: map[string]*memdb.IndexSchema{
2015-07-23 22:27:13 +00:00
// Primary index is used for direct lookup.
2017-09-26 22:26:33 +00:00
"id": {
Name: "id",
AllowMissing: false,
Unique: true,
2015-07-23 22:27:13 +00:00
Indexer: &memdb.UUIDFieldIndex{
Field: "ID",
},
},
2017-09-26 22:26:33 +00:00
"namespace": {
2017-09-07 23:56:15 +00:00
Name: "namespace",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "Namespace",
},
},
// Job index is used to lookup allocations by job
2017-09-26 22:26:33 +00:00
"job": {
Name: "job",
AllowMissing: false,
Unique: false,
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
2017-09-07 23:56:15 +00:00
&memdb.StringFieldIndex{
Field: "Namespace",
},
&memdb.StringFieldIndex{
Field: "JobID",
Lowercase: true,
},
2017-09-07 23:56:15 +00:00
&memdb.StringFieldIndex{
Field: "Status",
Lowercase: true,
},
},
},
},
},
}
}
2015-07-04 00:11:53 +00:00
// allocTableSchema returns the MemDB schema for the allocation table.
// This table is used to store all the task allocations between task groups
// and nodes.
func allocTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "allocs",
Indexes: map[string]*memdb.IndexSchema{
// Primary index is a UUID
2017-09-26 22:26:33 +00:00
"id": {
2015-07-04 00:48:02 +00:00
Name: "id",
2015-07-04 00:11:53 +00:00
AllowMissing: false,
Unique: true,
Indexer: &memdb.UUIDFieldIndex{
Field: "ID",
},
},
2017-09-26 22:26:33 +00:00
"namespace": {
2017-09-07 23:56:15 +00:00
Name: "namespace",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "Namespace",
},
},
2015-07-04 00:11:53 +00:00
// Node index is used to lookup allocations by node
2017-09-26 22:26:33 +00:00
"node": {
2015-07-04 00:11:53 +00:00
Name: "node",
AllowMissing: true, // Missing is allow for failed allocations
2015-07-04 00:11:53 +00:00
Unique: false,
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{
Field: "NodeID",
Lowercase: true,
},
// Conditional indexer on if allocation is terminal
&memdb.ConditionalIndex{
Conditional: func(obj interface{}) (bool, error) {
// Cast to allocation
alloc, ok := obj.(*structs.Allocation)
if !ok {
return false, fmt.Errorf("wrong type, got %t should be Allocation", obj)
}
// Check if the allocation is terminal
return alloc.TerminalStatus(), nil
},
},
},
2015-07-04 00:11:53 +00:00
},
},
2015-08-07 00:36:10 +00:00
// Job index is used to lookup allocations by job
2017-09-26 22:26:33 +00:00
"job": {
2015-08-07 00:36:10 +00:00
Name: "job",
AllowMissing: false,
Unique: false,
2017-09-07 23:56:15 +00:00
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{
Field: "Namespace",
},
&memdb.StringFieldIndex{
Field: "JobID",
},
},
2015-08-07 00:36:10 +00:00
},
},
// Eval index is used to lookup allocations by eval
2017-09-26 22:26:33 +00:00
"eval": {
Name: "eval",
AllowMissing: false,
Unique: false,
Indexer: &memdb.UUIDFieldIndex{
Field: "EvalID",
},
},
2017-06-26 21:23:52 +00:00
// Deployment index is used to lookup allocations by deployment
2017-09-26 22:26:33 +00:00
"deployment": {
2017-06-26 21:23:52 +00:00
Name: "deployment",
2017-06-28 23:14:50 +00:00
AllowMissing: true,
2017-06-26 21:23:52 +00:00
Unique: false,
Indexer: &memdb.UUIDFieldIndex{
Field: "DeploymentID",
},
},
2015-07-04 00:11:53 +00:00
},
}
}
// vaultAccessorTableSchema returns the MemDB schema for the Vault Accessor
// Table. This table tracks Vault accessors for tokens created on behalf of
// allocations required Vault tokens.
func vaultAccessorTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "vault_accessors",
Indexes: map[string]*memdb.IndexSchema{
// The primary index is the accessor id
2017-09-26 22:26:33 +00:00
"id": {
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "Accessor",
},
},
2017-09-26 22:26:33 +00:00
"alloc_id": {
Name: "alloc_id",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "AllocID",
},
},
2017-09-26 22:26:33 +00:00
"node_id": {
Name: "node_id",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "NodeID",
},
},
},
}
}
// siTokenAccessorTableSchema returns the MemDB schema for the Service Identity
// token accessor table. This table tracks accessors for tokens created on behalf
// of allocations with Consul connect enabled tasks that need SI tokens.
func siTokenAccessorTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: siTokenAccessorTable,
Indexes: map[string]*memdb.IndexSchema{
// The primary index is the accessor id
"id": {
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "AccessorID",
},
},
"alloc_id": {
Name: "alloc_id",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "AllocID",
},
},
"node_id": {
Name: "node_id",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "NodeID",
},
},
},
}
}
// aclPolicyTableSchema returns the MemDB schema for the policy table.
2018-03-11 18:40:45 +00:00
// This table is used to store the policies which are referenced by tokens
func aclPolicyTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "acl_policy",
Indexes: map[string]*memdb.IndexSchema{
2017-09-26 22:26:33 +00:00
"id": {
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "Name",
},
},
},
}
}
2017-08-12 21:36:20 +00:00
// aclTokenTableSchema returns the MemDB schema for the tokens table.
// This table is used to store the bearer tokens which are used to authenticate
func aclTokenTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "acl_token",
Indexes: map[string]*memdb.IndexSchema{
2017-09-26 22:26:33 +00:00
"id": {
2017-08-12 21:36:20 +00:00
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.UUIDFieldIndex{
Field: "AccessorID",
},
},
2017-09-26 22:26:33 +00:00
"secret": {
2017-08-12 21:36:20 +00:00
Name: "secret",
AllowMissing: false,
Unique: true,
Indexer: &memdb.UUIDFieldIndex{
Field: "SecretID",
},
},
2017-09-26 22:26:33 +00:00
"global": {
Name: "global",
AllowMissing: false,
Unique: false,
Indexer: &memdb.FieldSetIndex{
Field: "Global",
},
},
2017-08-12 21:36:20 +00:00
},
}
}
// singletonRecord can be used to describe tables which should contain only 1 entry.
// Example uses include storing node config or cluster metadata blobs.
var singletonRecord = &memdb.ConditionalIndex{
Conditional: func(interface{}) (bool, error) { return true, nil },
}
// schedulerConfigTableSchema returns the MemDB schema for the scheduler config table.
// This table is used to store configuration options for the scheduler
func schedulerConfigTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "scheduler_config",
Indexes: map[string]*memdb.IndexSchema{
"id": {
Name: "id",
AllowMissing: true,
Unique: true,
Indexer: singletonRecord, // we store only 1 scheduler config
},
},
}
}
// clusterMetaTableSchema returns the MemDB schema for the scheduler config table.
func clusterMetaTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "cluster_meta",
Indexes: map[string]*memdb.IndexSchema{
"id": {
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: singletonRecord, // we store only 1 cluster metadata
},
},
}
}
2019-10-15 15:43:55 +00:00
// CSIVolumes are identified by id globally, and searchable by driver
2019-10-15 15:43:55 +00:00
func csiVolumeTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "csi_volumes",
Indexes: map[string]*memdb.IndexSchema{
"id": {
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{
Field: "Namespace",
},
&memdb.StringFieldIndex{
Field: "ID",
},
},
2019-10-15 15:43:55 +00:00
},
},
"plugin_id": {
Name: "plugin_id",
2019-10-15 15:43:55 +00:00
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "PluginID",
},
},
},
}
}
// CSIPlugins are identified by id globally, and searchable by driver
func csiPluginTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "csi_plugins",
Indexes: map[string]*memdb.IndexSchema{
"id": {
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "ID",
2019-10-15 15:43:55 +00:00
},
},
},
}
}
// scalingPolicyTableSchema returns the MemDB schema for the policy table.
// This table is used to store the policies which are referenced by tokens
func scalingPolicyTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "scaling_policy",
Indexes: map[string]*memdb.IndexSchema{
// Primary index is used for simple direct lookup.
"id": {
Name: "id",
AllowMissing: false,
Unique: true,
// Use a compound index so the tuple of (Namespace, Target) is
// uniquely identifying
Indexer: &memdb.StringFieldIndex{
Field: "ID",
},
},
// Target index is used for looking up by target or listing policies in namespace
// A target can only have a single scaling policy, so this is guaranteed to be unique.
"target": {
Name: "target",
AllowMissing: false,
Unique: true,
// Use a compound index so the tuple of (Namespace, Target) is
// uniquely identifying
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{
Field: "Namespace",
},
&memdb.StringFieldIndex{
Field: "Target",
},
},
},
},
// Job index is used to lookup scaling policies by job
"job": {
Name: "job",
AllowMissing: false,
Unique: false,
// Use a compound index so the tuple of (Namespace, JobID) is
// uniquely identifying
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{
Field: "Namespace",
},
&memdb.StringFieldIndex{
Field: "JobID",
},
},
},
},
// Used to filter by enabled
"enabled": {
Name: "enabled",
AllowMissing: false,
Unique: false,
Indexer: &memdb.FieldSetIndex{
Field: "Enabled",
},
},
},
}
}