github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/nomad/state/schema.go (about)

     1  package state
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/go-memdb"
     7  	"github.com/hashicorp/nomad/nomad/structs"
     8  )
     9  
    10  // stateStoreSchema is used to return the schema for the state store
    11  func stateStoreSchema() *memdb.DBSchema {
    12  	// Create the root DB schema
    13  	db := &memdb.DBSchema{
    14  		Tables: make(map[string]*memdb.TableSchema),
    15  	}
    16  
    17  	// Collect all the schemas that are needed
    18  	schemas := []func() *memdb.TableSchema{
    19  		indexTableSchema,
    20  		nodeTableSchema,
    21  		jobTableSchema,
    22  		jobSummarySchema,
    23  		jobVersionSchema,
    24  		deploymentSchema,
    25  		periodicLaunchTableSchema,
    26  		evalTableSchema,
    27  		allocTableSchema,
    28  		vaultAccessorTableSchema,
    29  	}
    30  
    31  	// Add each of the tables
    32  	for _, schemaFn := range schemas {
    33  		schema := schemaFn()
    34  		if _, ok := db.Tables[schema.Name]; ok {
    35  			panic(fmt.Sprintf("duplicate table name: %s", schema.Name))
    36  		}
    37  		db.Tables[schema.Name] = schema
    38  	}
    39  	return db
    40  }
    41  
    42  // indexTableSchema is used for
    43  func indexTableSchema() *memdb.TableSchema {
    44  	return &memdb.TableSchema{
    45  		Name: "index",
    46  		Indexes: map[string]*memdb.IndexSchema{
    47  			"id": &memdb.IndexSchema{
    48  				Name:         "id",
    49  				AllowMissing: false,
    50  				Unique:       true,
    51  				Indexer: &memdb.StringFieldIndex{
    52  					Field:     "Key",
    53  					Lowercase: true,
    54  				},
    55  			},
    56  		},
    57  	}
    58  }
    59  
    60  // nodeTableSchema returns the MemDB schema for the nodes table.
    61  // This table is used to store all the client nodes that are registered.
    62  func nodeTableSchema() *memdb.TableSchema {
    63  	return &memdb.TableSchema{
    64  		Name: "nodes",
    65  		Indexes: map[string]*memdb.IndexSchema{
    66  			// Primary index is used for node management
    67  			// and simple direct lookup. ID is required to be
    68  			// unique.
    69  			"id": &memdb.IndexSchema{
    70  				Name:         "id",
    71  				AllowMissing: false,
    72  				Unique:       true,
    73  				Indexer: &memdb.UUIDFieldIndex{
    74  					Field: "ID",
    75  				},
    76  			},
    77  		},
    78  	}
    79  }
    80  
    81  // jobTableSchema returns the MemDB schema for the jobs table.
    82  // This table is used to store all the jobs that have been submitted.
    83  func jobTableSchema() *memdb.TableSchema {
    84  	return &memdb.TableSchema{
    85  		Name: "jobs",
    86  		Indexes: map[string]*memdb.IndexSchema{
    87  			// Primary index is used for job management
    88  			// and simple direct lookup. ID is required to be
    89  			// unique.
    90  			"id": &memdb.IndexSchema{
    91  				Name:         "id",
    92  				AllowMissing: false,
    93  				Unique:       true,
    94  				Indexer: &memdb.StringFieldIndex{
    95  					Field:     "ID",
    96  					Lowercase: true,
    97  				},
    98  			},
    99  			"type": &memdb.IndexSchema{
   100  				Name:         "type",
   101  				AllowMissing: false,
   102  				Unique:       false,
   103  				Indexer: &memdb.StringFieldIndex{
   104  					Field:     "Type",
   105  					Lowercase: false,
   106  				},
   107  			},
   108  			"gc": &memdb.IndexSchema{
   109  				Name:         "gc",
   110  				AllowMissing: false,
   111  				Unique:       false,
   112  				Indexer: &memdb.ConditionalIndex{
   113  					Conditional: jobIsGCable,
   114  				},
   115  			},
   116  			"periodic": &memdb.IndexSchema{
   117  				Name:         "periodic",
   118  				AllowMissing: false,
   119  				Unique:       false,
   120  				Indexer: &memdb.ConditionalIndex{
   121  					Conditional: jobIsPeriodic,
   122  				},
   123  			},
   124  		},
   125  	}
   126  }
   127  
   128  // jobSummarySchema returns the memdb schema for the job summary table
   129  func jobSummarySchema() *memdb.TableSchema {
   130  	return &memdb.TableSchema{
   131  		Name: "job_summary",
   132  		Indexes: map[string]*memdb.IndexSchema{
   133  			"id": &memdb.IndexSchema{
   134  				Name:         "id",
   135  				AllowMissing: false,
   136  				Unique:       true,
   137  				Indexer: &memdb.StringFieldIndex{
   138  					Field:     "JobID",
   139  					Lowercase: true,
   140  				},
   141  			},
   142  		},
   143  	}
   144  }
   145  
   146  // jobVersionSchema returns the memdb schema for the job version table which
   147  // keeps a historical view of job versions.
   148  func jobVersionSchema() *memdb.TableSchema {
   149  	return &memdb.TableSchema{
   150  		Name: "job_version",
   151  		Indexes: map[string]*memdb.IndexSchema{
   152  			"id": &memdb.IndexSchema{
   153  				Name:         "id",
   154  				AllowMissing: false,
   155  				Unique:       true,
   156  
   157  				// Use a compound index so the tuple of (JobID, Version) is
   158  				// uniquely identifying
   159  				Indexer: &memdb.CompoundIndex{
   160  					Indexes: []memdb.Indexer{
   161  						&memdb.StringFieldIndex{
   162  							Field:     "ID",
   163  							Lowercase: true,
   164  						},
   165  
   166  						// Will need to create a new indexer
   167  						&memdb.UintFieldIndex{
   168  							Field: "Version",
   169  						},
   170  					},
   171  				},
   172  			},
   173  		},
   174  	}
   175  }
   176  
   177  // jobIsGCable satisfies the ConditionalIndexFunc interface and creates an index
   178  // on whether a job is eligible for garbage collection.
   179  func jobIsGCable(obj interface{}) (bool, error) {
   180  	j, ok := obj.(*structs.Job)
   181  	if !ok {
   182  		return false, fmt.Errorf("Unexpected type: %v", obj)
   183  	}
   184  
   185  	// If the job is periodic or parameterized it is only garbage collectable if
   186  	// it is stopped.
   187  	periodic := j.Periodic != nil && j.Periodic.Enabled
   188  	parameterized := j.IsParameterized()
   189  	if periodic || parameterized {
   190  		return j.Stop, nil
   191  	}
   192  
   193  	// If the job isn't dead it isn't eligible
   194  	if j.Status != structs.JobStatusDead {
   195  		return false, nil
   196  	}
   197  
   198  	// Any job that is stopped is eligible for garbage collection
   199  	if j.Stop {
   200  		return true, nil
   201  	}
   202  
   203  	// Otherwise, only batch jobs are eligible because they complete on their
   204  	// own without a user stopping them.
   205  	if j.Type != structs.JobTypeBatch {
   206  		return false, nil
   207  	}
   208  
   209  	return true, nil
   210  }
   211  
   212  // jobIsPeriodic satisfies the ConditionalIndexFunc interface and creates an index
   213  // on whether a job is periodic.
   214  func jobIsPeriodic(obj interface{}) (bool, error) {
   215  	j, ok := obj.(*structs.Job)
   216  	if !ok {
   217  		return false, fmt.Errorf("Unexpected type: %v", obj)
   218  	}
   219  
   220  	if j.Periodic != nil && j.Periodic.Enabled == true {
   221  		return true, nil
   222  	}
   223  
   224  	return false, nil
   225  }
   226  
   227  // deploymentSchema returns the MemDB schema tracking a job's deployments
   228  func deploymentSchema() *memdb.TableSchema {
   229  	return &memdb.TableSchema{
   230  		Name: "deployment",
   231  		Indexes: map[string]*memdb.IndexSchema{
   232  			"id": &memdb.IndexSchema{
   233  				Name:         "id",
   234  				AllowMissing: false,
   235  				Unique:       true,
   236  				Indexer: &memdb.UUIDFieldIndex{
   237  					Field: "ID",
   238  				},
   239  			},
   240  
   241  			// Job index is used to lookup deployments by job
   242  			"job": &memdb.IndexSchema{
   243  				Name:         "job",
   244  				AllowMissing: false,
   245  				Unique:       false,
   246  				Indexer: &memdb.StringFieldIndex{
   247  					Field:     "JobID",
   248  					Lowercase: true,
   249  				},
   250  			},
   251  		},
   252  	}
   253  }
   254  
   255  // periodicLaunchTableSchema returns the MemDB schema tracking the most recent
   256  // launch time for a perioidic job.
   257  func periodicLaunchTableSchema() *memdb.TableSchema {
   258  	return &memdb.TableSchema{
   259  		Name: "periodic_launch",
   260  		Indexes: map[string]*memdb.IndexSchema{
   261  			// Primary index is used for job management
   262  			// and simple direct lookup. ID is required to be
   263  			// unique.
   264  			"id": &memdb.IndexSchema{
   265  				Name:         "id",
   266  				AllowMissing: false,
   267  				Unique:       true,
   268  				Indexer: &memdb.StringFieldIndex{
   269  					Field:     "ID",
   270  					Lowercase: true,
   271  				},
   272  			},
   273  		},
   274  	}
   275  }
   276  
   277  // evalTableSchema returns the MemDB schema for the eval table.
   278  // This table is used to store all the evaluations that are pending
   279  // or recently completed.
   280  func evalTableSchema() *memdb.TableSchema {
   281  	return &memdb.TableSchema{
   282  		Name: "evals",
   283  		Indexes: map[string]*memdb.IndexSchema{
   284  			// Primary index is used for direct lookup.
   285  			"id": &memdb.IndexSchema{
   286  				Name:         "id",
   287  				AllowMissing: false,
   288  				Unique:       true,
   289  				Indexer: &memdb.UUIDFieldIndex{
   290  					Field: "ID",
   291  				},
   292  			},
   293  
   294  			// Job index is used to lookup allocations by job
   295  			"job": &memdb.IndexSchema{
   296  				Name:         "job",
   297  				AllowMissing: false,
   298  				Unique:       false,
   299  				Indexer: &memdb.CompoundIndex{
   300  					Indexes: []memdb.Indexer{
   301  						&memdb.StringFieldIndex{
   302  							Field:     "JobID",
   303  							Lowercase: true,
   304  						},
   305  						&memdb.StringFieldIndex{
   306  							Field:     "Status",
   307  							Lowercase: true,
   308  						},
   309  					},
   310  				},
   311  			},
   312  		},
   313  	}
   314  }
   315  
   316  // allocTableSchema returns the MemDB schema for the allocation table.
   317  // This table is used to store all the task allocations between task groups
   318  // and nodes.
   319  func allocTableSchema() *memdb.TableSchema {
   320  	return &memdb.TableSchema{
   321  		Name: "allocs",
   322  		Indexes: map[string]*memdb.IndexSchema{
   323  			// Primary index is a UUID
   324  			"id": &memdb.IndexSchema{
   325  				Name:         "id",
   326  				AllowMissing: false,
   327  				Unique:       true,
   328  				Indexer: &memdb.UUIDFieldIndex{
   329  					Field: "ID",
   330  				},
   331  			},
   332  
   333  			// Node index is used to lookup allocations by node
   334  			"node": &memdb.IndexSchema{
   335  				Name:         "node",
   336  				AllowMissing: true, // Missing is allow for failed allocations
   337  				Unique:       false,
   338  				Indexer: &memdb.CompoundIndex{
   339  					Indexes: []memdb.Indexer{
   340  						&memdb.StringFieldIndex{
   341  							Field:     "NodeID",
   342  							Lowercase: true,
   343  						},
   344  
   345  						// Conditional indexer on if allocation is terminal
   346  						&memdb.ConditionalIndex{
   347  							Conditional: func(obj interface{}) (bool, error) {
   348  								// Cast to allocation
   349  								alloc, ok := obj.(*structs.Allocation)
   350  								if !ok {
   351  									return false, fmt.Errorf("wrong type, got %t should be Allocation", obj)
   352  								}
   353  
   354  								// Check if the allocation is terminal
   355  								return alloc.TerminalStatus(), nil
   356  							},
   357  						},
   358  					},
   359  				},
   360  			},
   361  
   362  			// Job index is used to lookup allocations by job
   363  			"job": &memdb.IndexSchema{
   364  				Name:         "job",
   365  				AllowMissing: false,
   366  				Unique:       false,
   367  				Indexer: &memdb.StringFieldIndex{
   368  					Field:     "JobID",
   369  					Lowercase: true,
   370  				},
   371  			},
   372  
   373  			// Eval index is used to lookup allocations by eval
   374  			"eval": &memdb.IndexSchema{
   375  				Name:         "eval",
   376  				AllowMissing: false,
   377  				Unique:       false,
   378  				Indexer: &memdb.UUIDFieldIndex{
   379  					Field: "EvalID",
   380  				},
   381  			},
   382  
   383  			// Deployment index is used to lookup allocations by deployment
   384  			"deployment": &memdb.IndexSchema{
   385  				Name:         "deployment",
   386  				AllowMissing: true,
   387  				Unique:       false,
   388  				Indexer: &memdb.UUIDFieldIndex{
   389  					Field: "DeploymentID",
   390  				},
   391  			},
   392  		},
   393  	}
   394  }
   395  
   396  // vaultAccessorTableSchema returns the MemDB schema for the Vault Accessor
   397  // Table. This table tracks Vault accessors for tokens created on behalf of
   398  // allocations required Vault tokens.
   399  func vaultAccessorTableSchema() *memdb.TableSchema {
   400  	return &memdb.TableSchema{
   401  		Name: "vault_accessors",
   402  		Indexes: map[string]*memdb.IndexSchema{
   403  			// The primary index is the accessor id
   404  			"id": &memdb.IndexSchema{
   405  				Name:         "id",
   406  				AllowMissing: false,
   407  				Unique:       true,
   408  				Indexer: &memdb.StringFieldIndex{
   409  					Field: "Accessor",
   410  				},
   411  			},
   412  
   413  			"alloc_id": &memdb.IndexSchema{
   414  				Name:         "alloc_id",
   415  				AllowMissing: false,
   416  				Unique:       false,
   417  				Indexer: &memdb.StringFieldIndex{
   418  					Field: "AllocID",
   419  				},
   420  			},
   421  
   422  			"node_id": &memdb.IndexSchema{
   423  				Name:         "node_id",
   424  				AllowMissing: false,
   425  				Unique:       false,
   426  				Indexer: &memdb.StringFieldIndex{
   427  					Field: "NodeID",
   428  				},
   429  			},
   430  		},
   431  	}
   432  }