open-nomad/nomad/state/schema.go
Alex Dadgar 86980e08f0 Cancel blocked evals upon successful one for job
This PR causes blocked evaluations to be cancelled if there is a
subsequent successful evaluation for the job. This fixes UX problems
showing failed placements when there are not any in reality and makes GC
possible for these jobs in certain cases.

Fixes https://github.com/hashicorp/nomad/issues/2124
2017-01-04 16:16:04 -08:00

341 lines
8.3 KiB
Go

package state
import (
"fmt"
"github.com/hashicorp/go-memdb"
"github.com/hashicorp/nomad/nomad/structs"
)
// 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),
}
// Collect all the schemas that are needed
schemas := []func() *memdb.TableSchema{
indexTableSchema,
nodeTableSchema,
jobTableSchema,
jobSummarySchema,
periodicLaunchTableSchema,
evalTableSchema,
allocTableSchema,
vaultAccessorTableSchema,
}
// Add each of the tables
for _, schemaFn := range schemas {
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
func indexTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "index",
Indexes: map[string]*memdb.IndexSchema{
"id": &memdb.IndexSchema{
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": &memdb.IndexSchema{
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.UUIDFieldIndex{
Field: "ID",
},
},
},
}
}
// 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.
"id": &memdb.IndexSchema{
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "ID",
Lowercase: true,
},
},
"type": &memdb.IndexSchema{
Name: "type",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "Type",
Lowercase: false,
},
},
"gc": &memdb.IndexSchema{
Name: "gc",
AllowMissing: false,
Unique: false,
Indexer: &memdb.ConditionalIndex{
Conditional: jobIsGCable,
},
},
"periodic": &memdb.IndexSchema{
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": &memdb.IndexSchema{
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "JobID",
Lowercase: true,
},
},
},
}
}
// 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)
}
// The job is GCable if it is batch and it is not periodic
periodic := j.Periodic != nil && j.Periodic.Enabled
gcable := j.Type == structs.JobTypeBatch && !periodic
return gcable, 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 == true {
return true, nil
}
return false, nil
}
// periodicLaunchTableSchema returns the MemDB schema tracking the most recent
// launch time for a perioidic 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": &memdb.IndexSchema{
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "ID",
Lowercase: true,
},
},
},
}
}
// 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{
// Primary index is used for direct lookup.
"id": &memdb.IndexSchema{
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.UUIDFieldIndex{
Field: "ID",
},
},
// Job index is used to lookup allocations by job
"job": &memdb.IndexSchema{
Name: "job",
AllowMissing: false,
Unique: false,
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{
Field: "JobID",
Lowercase: true,
},
&memdb.StringFieldIndex{
Field: "Status",
Lowercase: true,
},
},
},
},
},
}
}
// 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
"id": &memdb.IndexSchema{
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.UUIDFieldIndex{
Field: "ID",
},
},
// Node index is used to lookup allocations by node
"node": &memdb.IndexSchema{
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": &memdb.IndexSchema{
Name: "job",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "JobID",
Lowercase: true,
},
},
// Eval index is used to lookup allocations by eval
"eval": &memdb.IndexSchema{
Name: "eval",
AllowMissing: false,
Unique: false,
Indexer: &memdb.UUIDFieldIndex{
Field: "EvalID",
},
},
},
}
}
// 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": &memdb.IndexSchema{
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "Accessor",
},
},
"alloc_id": &memdb.IndexSchema{
Name: "alloc_id",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "AllocID",
},
},
"node_id": &memdb.IndexSchema{
Name: "node_id",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "NodeID",
},
},
},
}
}