903b5baaa4
When replication of a single key fails, the replication loop breaks early and therefore keys that fall later in the sorting order will never get replicated. This is particularly a problem for clusters impacted by the bug that caused #14981 and that were later upgraded; the keys that were never replicated can now never be replicated, and so we need to handle them safely. Included in the replication fix: * Refactor the replication loop so that each key replicated in a function call that returns an error, to make the workflow more clear and reduce nesting. Log the error and continue. * Improve stability of keyring replication tests. We no longer block leadership on initializing the keyring, so there's a race condition in the keyring tests where we can test for the existence of the root key before the keyring has been initialize. Change this to an "eventually" test. But these fixes aren't enough to fix #14981 because they'll end up seeing an error once a second complaining about the missing key, so we also need to fix keyring GC so the keys can be removed from the state store. Now we'll store the key ID used to sign a workload identity in the Allocation, and we'll index the Allocation table on that so we can track whether any live Allocation was signed with a particular key ID.
1519 lines
37 KiB
Go
1519 lines
37 KiB
Go
package state
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
memdb "github.com/hashicorp/go-memdb"
|
|
"github.com/hashicorp/nomad/nomad/state/indexer"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
)
|
|
|
|
const (
|
|
tableIndex = "index"
|
|
|
|
TableNamespaces = "namespaces"
|
|
TableServiceRegistrations = "service_registrations"
|
|
TableVariables = "variables"
|
|
TableVariablesQuotas = "variables_quota"
|
|
TableRootKeyMeta = "root_key_meta"
|
|
TableACLRoles = "acl_roles"
|
|
TableAllocs = "allocs"
|
|
)
|
|
|
|
const (
|
|
indexID = "id"
|
|
indexJob = "job"
|
|
indexNodeID = "node_id"
|
|
indexAllocID = "alloc_id"
|
|
indexServiceName = "service_name"
|
|
indexExpiresGlobal = "expires-global"
|
|
indexExpiresLocal = "expires-local"
|
|
indexKeyID = "key_id"
|
|
indexPath = "path"
|
|
indexName = "name"
|
|
indexSigningKey = "signing_key"
|
|
)
|
|
|
|
var (
|
|
schemaFactories SchemaFactories
|
|
factoriesLock sync.Mutex
|
|
)
|
|
|
|
// 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,
|
|
periodicLaunchTableSchema,
|
|
evalTableSchema,
|
|
allocTableSchema,
|
|
vaultAccessorTableSchema,
|
|
siTokenAccessorTableSchema,
|
|
aclPolicyTableSchema,
|
|
aclTokenTableSchema,
|
|
oneTimeTokenTableSchema,
|
|
autopilotConfigTableSchema,
|
|
schedulerConfigTableSchema,
|
|
clusterMetaTableSchema,
|
|
csiVolumeTableSchema,
|
|
csiPluginTableSchema,
|
|
scalingPolicyTableSchema,
|
|
scalingEventTableSchema,
|
|
namespaceTableSchema,
|
|
serviceRegistrationsTableSchema,
|
|
variablesTableSchema,
|
|
variablesQuotasTableSchema,
|
|
variablesRootKeyMetaSchema,
|
|
aclRolesTableSchema,
|
|
}...)
|
|
}
|
|
|
|
// 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
|
|
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{
|
|
"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.
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.UUIDFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
"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
|
|
// unique within a namespace.
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
|
|
// 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",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"type": {
|
|
Name: "type",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Type",
|
|
Lowercase: false,
|
|
},
|
|
},
|
|
"gc": {
|
|
Name: "gc",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.ConditionalIndex{
|
|
Conditional: jobIsGCable,
|
|
},
|
|
},
|
|
"periodic": {
|
|
Name: "periodic",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.ConditionalIndex{
|
|
Conditional: jobIsPeriodic,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// jobSummarySchema returns the memdb schema for the job summary table
|
|
func jobSummarySchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: "job_summary",
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
|
|
// 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
|
|
// keeps a historical view of job versions.
|
|
func jobVersionSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: "job_version",
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
|
|
// Use a compound index so the tuple of (Namespace, ID, Version) is
|
|
// uniquely identifying
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
|
|
&memdb.StringFieldIndex{
|
|
Field: "ID",
|
|
Lowercase: true,
|
|
},
|
|
|
|
&memdb.UintFieldIndex{
|
|
Field: "Version",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// If the job is periodic or parameterized it is only garbage collectable if
|
|
// it is stopped.
|
|
periodic := j.Periodic != nil && j.Periodic.Enabled
|
|
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
|
|
}
|
|
|
|
switch j.Type {
|
|
// Otherwise, batch and sysbatch jobs are eligible because they complete on
|
|
// their own without a user stopping them.
|
|
case structs.JobTypeBatch, structs.JobTypeSysBatch:
|
|
return true, nil
|
|
|
|
default:
|
|
// other job types may not be GC until stopped
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
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{
|
|
// id index is used for direct lookup of an deployment by ID.
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.UUIDFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
|
|
// create index is used for listing deploy, ordering them by
|
|
// creation chronology. (Use a reverse iterator for newest first).
|
|
//
|
|
// There may be more than one deployment per CreateIndex.
|
|
"create": {
|
|
Name: "create",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.UintFieldIndex{
|
|
Field: "CreateIndex",
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// namespace is used to lookup evaluations by namespace.
|
|
"namespace": {
|
|
Name: "namespace",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
},
|
|
|
|
// namespace_create index is used to lookup deployments by namespace
|
|
// in their original chronological order based on CreateIndex.
|
|
//
|
|
// Use a prefix iterator (namespace_create_prefix) to iterate deployments
|
|
// of a Namespace in order of CreateIndex.
|
|
//
|
|
// There may be more than one deployment per CreateIndex.
|
|
"namespace_create": {
|
|
Name: "namespace_create",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.CompoundIndex{
|
|
AllowMissing: false,
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
&memdb.UintFieldIndex{
|
|
Field: "CreateIndex",
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// job index is used to lookup deployments 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",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// periodicLaunchTableSchema returns the MemDB schema tracking the most recent
|
|
// launch time for a periodic job.
|
|
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.
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
|
|
// 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",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// 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{
|
|
Name: "evals",
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
// id index is used for direct lookup of an evaluation by ID.
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.UUIDFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
|
|
// create index is used for listing evaluations, ordering them by
|
|
// creation chronology. (Use a reverse iterator for newest first).
|
|
"create": {
|
|
Name: "create",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.UintFieldIndex{
|
|
Field: "CreateIndex",
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// job index is used to lookup evaluations by job ID.
|
|
"job": {
|
|
Name: "job",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
|
|
&memdb.StringFieldIndex{
|
|
Field: "JobID",
|
|
Lowercase: true,
|
|
},
|
|
|
|
&memdb.StringFieldIndex{
|
|
Field: "Status",
|
|
Lowercase: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// namespace is used to lookup evaluations by namespace.
|
|
"namespace": {
|
|
Name: "namespace",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
},
|
|
|
|
// namespace_create index is used to lookup evaluations by namespace
|
|
// in their original chronological order based on CreateIndex.
|
|
//
|
|
// Use a prefix iterator (namespace_prefix) on a Namespace to iterate
|
|
// those evaluations in order of CreateIndex.
|
|
"namespace_create": {
|
|
Name: "namespace_create",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.CompoundIndex{
|
|
AllowMissing: false,
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
&memdb.UintFieldIndex{
|
|
Field: "CreateIndex",
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// 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{
|
|
// id index is used for direct lookup of allocation by ID.
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.UUIDFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
|
|
// create index is used for listing allocations, ordering them by
|
|
// creation chronology. (Use a reverse iterator for newest first).
|
|
"create": {
|
|
Name: "create",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.UintFieldIndex{
|
|
Field: "CreateIndex",
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// namespace is used to lookup evaluations by namespace.
|
|
// todo(shoenig): i think we can deprecate this and other like it
|
|
"namespace": {
|
|
Name: "namespace",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
},
|
|
|
|
// namespace_create index is used to lookup evaluations by namespace
|
|
// in their original chronological order based on CreateIndex.
|
|
//
|
|
// Use a prefix iterator (namespace_prefix) on a Namespace to iterate
|
|
// those evaluations in order of CreateIndex.
|
|
"namespace_create": {
|
|
Name: "namespace_create",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.CompoundIndex{
|
|
AllowMissing: false,
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
&memdb.UintFieldIndex{
|
|
Field: "CreateIndex",
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// Node index is used to lookup allocations by node
|
|
"node": {
|
|
Name: "node",
|
|
AllowMissing: true, // Missing is allow for failed allocations
|
|
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
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// Job index is used to lookup allocations by job
|
|
"job": {
|
|
Name: "job",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
|
|
&memdb.StringFieldIndex{
|
|
Field: "JobID",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// Eval index is used to lookup allocations by eval
|
|
"eval": {
|
|
Name: "eval",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.UUIDFieldIndex{
|
|
Field: "EvalID",
|
|
},
|
|
},
|
|
|
|
// Deployment index is used to lookup allocations by deployment
|
|
"deployment": {
|
|
Name: "deployment",
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: &memdb.UUIDFieldIndex{
|
|
Field: "DeploymentID",
|
|
},
|
|
},
|
|
|
|
// signing_key index is used to lookup live allocations by signing
|
|
// key ID
|
|
indexSigningKey: {
|
|
Name: indexSigningKey,
|
|
AllowMissing: true, // terminal allocations won't be indexed
|
|
Unique: false,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "SigningKeyID",
|
|
},
|
|
&memdb.ConditionalIndex{
|
|
Conditional: func(obj interface{}) (bool, error) {
|
|
alloc, ok := obj.(*structs.Allocation)
|
|
if !ok {
|
|
return false, fmt.Errorf(
|
|
"wrong type, got %t should be Allocation", obj)
|
|
}
|
|
// note: this isn't alloc.TerminalStatus(),
|
|
// because we only want to consider the key
|
|
// unused if the allocation is terminal on both
|
|
// server and client
|
|
return !(alloc.ClientTerminalStatus() && alloc.ServerTerminalStatus()), nil
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// 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
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Accessor",
|
|
},
|
|
},
|
|
|
|
"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",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
// 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{
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Name",
|
|
},
|
|
},
|
|
"job": {
|
|
Name: "job",
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: &ACLPolicyJobACLFieldIndex{},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// ACLPolicyJobACLFieldIndex is used to extract the policy's JobACL field and
|
|
// build an index on it.
|
|
type ACLPolicyJobACLFieldIndex struct{}
|
|
|
|
// FromObject is used to extract an index value from an
|
|
// object or to indicate that the index value is missing.
|
|
func (a *ACLPolicyJobACLFieldIndex) FromObject(obj interface{}) (bool, []byte, error) {
|
|
policy, ok := obj.(*structs.ACLPolicy)
|
|
if !ok {
|
|
return false, nil, fmt.Errorf("object %#v is not an ACLPolicy", obj)
|
|
}
|
|
|
|
if policy.JobACL == nil {
|
|
return false, nil, nil
|
|
}
|
|
|
|
ns := policy.JobACL.Namespace
|
|
if ns == "" {
|
|
return false, nil, nil
|
|
}
|
|
jobID := policy.JobACL.JobID
|
|
if jobID == "" {
|
|
return false, nil, fmt.Errorf(
|
|
"object %#v is not a valid ACLPolicy: JobACL.JobID without Namespace", obj)
|
|
}
|
|
|
|
val := ns + "\x00" + jobID + "\x00"
|
|
return true, []byte(val), nil
|
|
}
|
|
|
|
// FromArgs is used to build an exact index lookup based on arguments
|
|
func (a *ACLPolicyJobACLFieldIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
|
if len(args) != 2 {
|
|
return nil, fmt.Errorf("must provide two arguments")
|
|
}
|
|
arg0, ok := args[0].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("argument must be a string: %#v", args[0])
|
|
}
|
|
arg1, ok := args[1].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("argument must be a string: %#v", args[0])
|
|
}
|
|
|
|
// Add the null character as a terminator
|
|
arg0 += "\x00" + arg1 + "\x00"
|
|
return []byte(arg0), nil
|
|
}
|
|
|
|
// PrefixFromArgs returns a prefix that should be used for scanning based on the arguments
|
|
func (a *ACLPolicyJobACLFieldIndex) PrefixFromArgs(args ...interface{}) ([]byte, error) {
|
|
val, err := a.FromArgs(args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Strip the null terminator, the rest is a prefix
|
|
n := len(val)
|
|
if n > 0 {
|
|
return val[:n-1], nil
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
// 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{
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.UUIDFieldIndex{
|
|
Field: "AccessorID",
|
|
},
|
|
},
|
|
"create": {
|
|
Name: "create",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.UintFieldIndex{
|
|
Field: "CreateIndex",
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "AccessorID",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"secret": {
|
|
Name: "secret",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.UUIDFieldIndex{
|
|
Field: "SecretID",
|
|
},
|
|
},
|
|
"global": {
|
|
Name: "global",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.FieldSetIndex{
|
|
Field: "Global",
|
|
},
|
|
},
|
|
indexExpiresGlobal: {
|
|
Name: indexExpiresGlobal,
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: indexer.SingleIndexer{
|
|
ReadIndex: indexer.ReadIndex(indexer.IndexFromTimeQuery),
|
|
WriteIndex: indexer.WriteIndex(indexExpiresGlobalFromACLToken),
|
|
},
|
|
},
|
|
indexExpiresLocal: {
|
|
Name: indexExpiresLocal,
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: indexer.SingleIndexer{
|
|
ReadIndex: indexer.ReadIndex(indexer.IndexFromTimeQuery),
|
|
WriteIndex: indexer.WriteIndex(indexExpiresLocalFromACLToken),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func indexExpiresLocalFromACLToken(raw interface{}) ([]byte, error) {
|
|
return indexExpiresFromACLToken(raw, false)
|
|
}
|
|
|
|
func indexExpiresGlobalFromACLToken(raw interface{}) ([]byte, error) {
|
|
return indexExpiresFromACLToken(raw, true)
|
|
}
|
|
|
|
// indexExpiresFromACLToken implements the indexer.WriteIndex interface and
|
|
// allows us to use an ACL tokens ExpirationTime as an index, if it is a
|
|
// non-default value. This allows for efficient lookups when trying to deal
|
|
// with removal of expired tokens from state.
|
|
func indexExpiresFromACLToken(raw interface{}, global bool) ([]byte, error) {
|
|
p, ok := raw.(*structs.ACLToken)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected type %T for structs.ACLToken index", raw)
|
|
}
|
|
if p.Global != global {
|
|
return nil, indexer.ErrMissingValueForIndex
|
|
}
|
|
if !p.HasExpirationTime() {
|
|
return nil, indexer.ErrMissingValueForIndex
|
|
}
|
|
if p.ExpirationTime.Unix() < 0 {
|
|
return nil, fmt.Errorf("token expiration time cannot be before the unix epoch: %s", p.ExpirationTime)
|
|
}
|
|
|
|
var b indexer.IndexBuilder
|
|
b.Time(*p.ExpirationTime)
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
// oneTimeTokenTableSchema returns the MemDB schema for the tokens table.
|
|
// This table is used to store one-time tokens for ACL tokens
|
|
func oneTimeTokenTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: "one_time_token",
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
"secret": {
|
|
Name: "secret",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.UUIDFieldIndex{
|
|
Field: "OneTimeSecretID",
|
|
},
|
|
},
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.UUIDFieldIndex{
|
|
Field: "AccessorID",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// 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
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// CSIVolumes are identified by id globally, and searchable by driver
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"plugin_id": {
|
|
Name: "plugin_id",
|
|
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",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// StringFieldIndex is used to extract a field from an object
|
|
// using reflection and builds an index on that field.
|
|
type ScalingPolicyTargetFieldIndex struct {
|
|
Field string
|
|
|
|
// AllowMissing controls if the field should be ignored if the field is
|
|
// not provided.
|
|
AllowMissing bool
|
|
}
|
|
|
|
// FromObject is used to extract an index value from an
|
|
// object or to indicate that the index value is missing.
|
|
func (s *ScalingPolicyTargetFieldIndex) FromObject(obj interface{}) (bool, []byte, error) {
|
|
policy, ok := obj.(*structs.ScalingPolicy)
|
|
if !ok {
|
|
return false, nil, fmt.Errorf("object %#v is not a ScalingPolicy", obj)
|
|
}
|
|
|
|
if policy.Target == nil {
|
|
return false, nil, nil
|
|
}
|
|
|
|
val, ok := policy.Target[s.Field]
|
|
if !ok && !s.AllowMissing {
|
|
return false, nil, nil
|
|
}
|
|
|
|
// Add the null character as a terminator
|
|
val += "\x00"
|
|
return true, []byte(val), nil
|
|
}
|
|
|
|
// FromArgs is used to build an exact index lookup based on arguments
|
|
func (s *ScalingPolicyTargetFieldIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
|
if len(args) != 1 {
|
|
return nil, fmt.Errorf("must provide only a single argument")
|
|
}
|
|
arg, ok := args[0].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("argument must be a string: %#v", args[0])
|
|
}
|
|
// Add the null character as a terminator
|
|
arg += "\x00"
|
|
return []byte(arg), nil
|
|
}
|
|
|
|
// PrefixFromArgs returns a prefix that should be used for scanning based on the arguments
|
|
func (s *ScalingPolicyTargetFieldIndex) PrefixFromArgs(args ...interface{}) ([]byte, error) {
|
|
val, err := s.FromArgs(args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Strip the null terminator, the rest is a prefix
|
|
n := len(val)
|
|
if n > 0 {
|
|
return val[:n-1], nil
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
// scalingPolicyTableSchema returns the MemDB schema for the policy table.
|
|
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,
|
|
|
|
// UUID is uniquely identifying
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
// Target index is used for listing by namespace or job, or looking up a specific target.
|
|
// A given task group can have only a single scaling policies, so this is guaranteed to be unique.
|
|
"target": {
|
|
Name: "target",
|
|
Unique: false,
|
|
|
|
// Use a compound index so the tuple of (Namespace, Job, Group, Task) is
|
|
// used when looking for a policy
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&ScalingPolicyTargetFieldIndex{
|
|
Field: "Namespace",
|
|
AllowMissing: true,
|
|
},
|
|
|
|
&ScalingPolicyTargetFieldIndex{
|
|
Field: "Job",
|
|
AllowMissing: true,
|
|
},
|
|
|
|
&ScalingPolicyTargetFieldIndex{
|
|
Field: "Group",
|
|
AllowMissing: true,
|
|
},
|
|
|
|
&ScalingPolicyTargetFieldIndex{
|
|
Field: "Task",
|
|
AllowMissing: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// Type index is used for listing by policy type
|
|
"type": {
|
|
Name: "type",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Type",
|
|
},
|
|
},
|
|
// Used to filter by enabled
|
|
"enabled": {
|
|
Name: "enabled",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.FieldSetIndex{
|
|
Field: "Enabled",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// scalingEventTableSchema returns the memdb schema for job scaling events
|
|
func scalingEventTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: "scaling_event",
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
|
|
// 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",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// TODO: need to figure out whether we want to index these or the jobs or ...
|
|
// "error": {
|
|
// Name: "error",
|
|
// AllowMissing: false,
|
|
// Unique: false,
|
|
// Indexer: &memdb.FieldSetIndex{
|
|
// Field: "Error",
|
|
// },
|
|
// },
|
|
},
|
|
}
|
|
}
|
|
|
|
// namespaceTableSchema returns the MemDB schema for the namespace table.
|
|
func namespaceTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: TableNamespaces,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
"id": {
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Name",
|
|
},
|
|
},
|
|
"quota": {
|
|
Name: "quota",
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Quota",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// serviceRegistrationsTableSchema returns the MemDB schema for Nomad native
|
|
// service registrations.
|
|
func serviceRegistrationsTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: TableServiceRegistrations,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
// The serviceID in combination with namespace forms a unique
|
|
// identifier for a service registration. This is used to look up
|
|
// and delete services in individual isolation.
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
indexServiceName: {
|
|
Name: indexServiceName,
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "ServiceName",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
indexJob: {
|
|
Name: indexJob,
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "JobID",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// The nodeID index allows lookups and deletions to be performed
|
|
// for an entire node. This is primarily used when a node becomes
|
|
// lost.
|
|
indexNodeID: {
|
|
Name: indexNodeID,
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "NodeID",
|
|
},
|
|
},
|
|
indexAllocID: {
|
|
Name: indexAllocID,
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "AllocID",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// variablesTableSchema returns the MemDB schema for Nomad variables.
|
|
func variablesTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: TableVariables,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "Path",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
indexKeyID: {
|
|
Name: indexKeyID,
|
|
AllowMissing: false,
|
|
Indexer: &variableKeyIDFieldIndexer{},
|
|
},
|
|
indexPath: {
|
|
Name: indexPath,
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Path",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
type variableKeyIDFieldIndexer struct{}
|
|
|
|
// FromArgs implements go-memdb/Indexer and is used to build an exact
|
|
// index lookup based on arguments
|
|
func (s *variableKeyIDFieldIndexer) FromArgs(args ...interface{}) ([]byte, error) {
|
|
if len(args) != 1 {
|
|
return nil, fmt.Errorf("must provide only a single argument")
|
|
}
|
|
arg, ok := args[0].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("argument must be a string: %#v", args[0])
|
|
}
|
|
// Add the null character as a terminator
|
|
arg += "\x00"
|
|
return []byte(arg), nil
|
|
}
|
|
|
|
// PrefixFromArgs implements go-memdb/PrefixIndexer and returns a
|
|
// prefix that should be used for scanning based on the arguments
|
|
func (s *variableKeyIDFieldIndexer) PrefixFromArgs(args ...interface{}) ([]byte, error) {
|
|
val, err := s.FromArgs(args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Strip the null terminator, the rest is a prefix
|
|
n := len(val)
|
|
if n > 0 {
|
|
return val[:n-1], nil
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
// FromObject implements go-memdb/SingleIndexer and is used to extract
|
|
// an index value from an object or to indicate that the index value
|
|
// is missing.
|
|
func (s *variableKeyIDFieldIndexer) FromObject(obj interface{}) (bool, []byte, error) {
|
|
variable, ok := obj.(*structs.VariableEncrypted)
|
|
if !ok {
|
|
return false, nil, fmt.Errorf("object %#v is not a Variable", obj)
|
|
}
|
|
|
|
keyID := variable.KeyID
|
|
if keyID == "" {
|
|
return false, nil, nil
|
|
}
|
|
|
|
// Add the null character as a terminator
|
|
keyID += "\x00"
|
|
return true, []byte(keyID), nil
|
|
}
|
|
|
|
// variablesQuotasTableSchema returns the MemDB schema for Nomad variables
|
|
// quotas tracking
|
|
func variablesQuotasTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: TableVariablesQuotas,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Namespace",
|
|
Lowercase: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// variablesRootKeyMetaSchema returns the MemDB schema for Nomad root keys
|
|
func variablesRootKeyMetaSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: TableRootKeyMeta,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "KeyID",
|
|
Lowercase: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func aclRolesTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: TableACLRoles,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
indexName: {
|
|
Name: indexName,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Name",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|