github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/nomad/state/schema.go (about)

     1  package state
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/hashicorp/go-memdb"
     8  	"github.com/hashicorp/nomad/nomad/structs"
     9  )
    10  
    11  var (
    12  	schemaFactories SchemaFactories
    13  	factoriesLock   sync.Mutex
    14  )
    15  
    16  // SchemaFactory is the factory method for returning a TableSchema
    17  type SchemaFactory func() *memdb.TableSchema
    18  type SchemaFactories []SchemaFactory
    19  
    20  // RegisterSchemaFactories is used to register a table schema.
    21  func RegisterSchemaFactories(factories ...SchemaFactory) {
    22  	factoriesLock.Lock()
    23  	defer factoriesLock.Unlock()
    24  	schemaFactories = append(schemaFactories, factories...)
    25  }
    26  
    27  func GetFactories() SchemaFactories {
    28  	return schemaFactories
    29  }
    30  
    31  func init() {
    32  	// Register all schemas
    33  	RegisterSchemaFactories([]SchemaFactory{
    34  		indexTableSchema,
    35  		nodeTableSchema,
    36  		jobTableSchema,
    37  		jobSummarySchema,
    38  		jobVersionSchema,
    39  		deploymentSchema,
    40  		periodicLaunchTableSchema,
    41  		evalTableSchema,
    42  		allocTableSchema,
    43  		vaultAccessorTableSchema,
    44  		aclPolicyTableSchema,
    45  		aclTokenTableSchema,
    46  	}...)
    47  }
    48  
    49  // stateStoreSchema is used to return the schema for the state store
    50  func stateStoreSchema() *memdb.DBSchema {
    51  	// Create the root DB schema
    52  	db := &memdb.DBSchema{
    53  		Tables: make(map[string]*memdb.TableSchema),
    54  	}
    55  
    56  	// Add each of the tables
    57  	for _, schemaFn := range GetFactories() {
    58  		schema := schemaFn()
    59  		if _, ok := db.Tables[schema.Name]; ok {
    60  			panic(fmt.Sprintf("duplicate table name: %s", schema.Name))
    61  		}
    62  		db.Tables[schema.Name] = schema
    63  	}
    64  	return db
    65  }
    66  
    67  // indexTableSchema is used for
    68  func indexTableSchema() *memdb.TableSchema {
    69  	return &memdb.TableSchema{
    70  		Name: "index",
    71  		Indexes: map[string]*memdb.IndexSchema{
    72  			"id": {
    73  				Name:         "id",
    74  				AllowMissing: false,
    75  				Unique:       true,
    76  				Indexer: &memdb.StringFieldIndex{
    77  					Field:     "Key",
    78  					Lowercase: true,
    79  				},
    80  			},
    81  		},
    82  	}
    83  }
    84  
    85  // nodeTableSchema returns the MemDB schema for the nodes table.
    86  // This table is used to store all the client nodes that are registered.
    87  func nodeTableSchema() *memdb.TableSchema {
    88  	return &memdb.TableSchema{
    89  		Name: "nodes",
    90  		Indexes: map[string]*memdb.IndexSchema{
    91  			// Primary index is used for node management
    92  			// and simple direct lookup. ID is required to be
    93  			// unique.
    94  			"id": {
    95  				Name:         "id",
    96  				AllowMissing: false,
    97  				Unique:       true,
    98  				Indexer: &memdb.UUIDFieldIndex{
    99  					Field: "ID",
   100  				},
   101  			},
   102  			"secret_id": {
   103  				Name:         "secret_id",
   104  				AllowMissing: false,
   105  				Unique:       true,
   106  				Indexer: &memdb.UUIDFieldIndex{
   107  					Field: "SecretID",
   108  				},
   109  			},
   110  		},
   111  	}
   112  }
   113  
   114  // jobTableSchema returns the MemDB schema for the jobs table.
   115  // This table is used to store all the jobs that have been submitted.
   116  func jobTableSchema() *memdb.TableSchema {
   117  	return &memdb.TableSchema{
   118  		Name: "jobs",
   119  		Indexes: map[string]*memdb.IndexSchema{
   120  			// Primary index is used for job management
   121  			// and simple direct lookup. ID is required to be
   122  			// unique within a namespace.
   123  			"id": {
   124  				Name:         "id",
   125  				AllowMissing: false,
   126  				Unique:       true,
   127  
   128  				// Use a compound index so the tuple of (Namespace, ID) is
   129  				// uniquely identifying
   130  				Indexer: &memdb.CompoundIndex{
   131  					Indexes: []memdb.Indexer{
   132  						&memdb.StringFieldIndex{
   133  							Field: "Namespace",
   134  						},
   135  
   136  						&memdb.StringFieldIndex{
   137  							Field: "ID",
   138  						},
   139  					},
   140  				},
   141  			},
   142  			"type": {
   143  				Name:         "type",
   144  				AllowMissing: false,
   145  				Unique:       false,
   146  				Indexer: &memdb.StringFieldIndex{
   147  					Field:     "Type",
   148  					Lowercase: false,
   149  				},
   150  			},
   151  			"gc": {
   152  				Name:         "gc",
   153  				AllowMissing: false,
   154  				Unique:       false,
   155  				Indexer: &memdb.ConditionalIndex{
   156  					Conditional: jobIsGCable,
   157  				},
   158  			},
   159  			"periodic": {
   160  				Name:         "periodic",
   161  				AllowMissing: false,
   162  				Unique:       false,
   163  				Indexer: &memdb.ConditionalIndex{
   164  					Conditional: jobIsPeriodic,
   165  				},
   166  			},
   167  		},
   168  	}
   169  }
   170  
   171  // jobSummarySchema returns the memdb schema for the job summary table
   172  func jobSummarySchema() *memdb.TableSchema {
   173  	return &memdb.TableSchema{
   174  		Name: "job_summary",
   175  		Indexes: map[string]*memdb.IndexSchema{
   176  			"id": {
   177  				Name:         "id",
   178  				AllowMissing: false,
   179  				Unique:       true,
   180  
   181  				// Use a compound index so the tuple of (Namespace, JobID) is
   182  				// uniquely identifying
   183  				Indexer: &memdb.CompoundIndex{
   184  					Indexes: []memdb.Indexer{
   185  						&memdb.StringFieldIndex{
   186  							Field: "Namespace",
   187  						},
   188  
   189  						&memdb.StringFieldIndex{
   190  							Field: "JobID",
   191  						},
   192  					},
   193  				},
   194  			},
   195  		},
   196  	}
   197  }
   198  
   199  // jobVersionSchema returns the memdb schema for the job version table which
   200  // keeps a historical view of job versions.
   201  func jobVersionSchema() *memdb.TableSchema {
   202  	return &memdb.TableSchema{
   203  		Name: "job_version",
   204  		Indexes: map[string]*memdb.IndexSchema{
   205  			"id": {
   206  				Name:         "id",
   207  				AllowMissing: false,
   208  				Unique:       true,
   209  
   210  				// Use a compound index so the tuple of (Namespace, ID, Version) is
   211  				// uniquely identifying
   212  				Indexer: &memdb.CompoundIndex{
   213  					Indexes: []memdb.Indexer{
   214  						&memdb.StringFieldIndex{
   215  							Field: "Namespace",
   216  						},
   217  
   218  						&memdb.StringFieldIndex{
   219  							Field:     "ID",
   220  							Lowercase: true,
   221  						},
   222  
   223  						&memdb.UintFieldIndex{
   224  							Field: "Version",
   225  						},
   226  					},
   227  				},
   228  			},
   229  		},
   230  	}
   231  }
   232  
   233  // jobIsGCable satisfies the ConditionalIndexFunc interface and creates an index
   234  // on whether a job is eligible for garbage collection.
   235  func jobIsGCable(obj interface{}) (bool, error) {
   236  	j, ok := obj.(*structs.Job)
   237  	if !ok {
   238  		return false, fmt.Errorf("Unexpected type: %v", obj)
   239  	}
   240  
   241  	// If the job is periodic or parameterized it is only garbage collectable if
   242  	// it is stopped.
   243  	periodic := j.Periodic != nil && j.Periodic.Enabled
   244  	parameterized := j.IsParameterized()
   245  	if periodic || parameterized {
   246  		return j.Stop, nil
   247  	}
   248  
   249  	// If the job isn't dead it isn't eligible
   250  	if j.Status != structs.JobStatusDead {
   251  		return false, nil
   252  	}
   253  
   254  	// Any job that is stopped is eligible for garbage collection
   255  	if j.Stop {
   256  		return true, nil
   257  	}
   258  
   259  	// Otherwise, only batch jobs are eligible because they complete on their
   260  	// own without a user stopping them.
   261  	if j.Type != structs.JobTypeBatch {
   262  		return false, nil
   263  	}
   264  
   265  	return true, nil
   266  }
   267  
   268  // jobIsPeriodic satisfies the ConditionalIndexFunc interface and creates an index
   269  // on whether a job is periodic.
   270  func jobIsPeriodic(obj interface{}) (bool, error) {
   271  	j, ok := obj.(*structs.Job)
   272  	if !ok {
   273  		return false, fmt.Errorf("Unexpected type: %v", obj)
   274  	}
   275  
   276  	if j.Periodic != nil && j.Periodic.Enabled == true {
   277  		return true, nil
   278  	}
   279  
   280  	return false, nil
   281  }
   282  
   283  // deploymentSchema returns the MemDB schema tracking a job's deployments
   284  func deploymentSchema() *memdb.TableSchema {
   285  	return &memdb.TableSchema{
   286  		Name: "deployment",
   287  		Indexes: map[string]*memdb.IndexSchema{
   288  			"id": {
   289  				Name:         "id",
   290  				AllowMissing: false,
   291  				Unique:       true,
   292  				Indexer: &memdb.UUIDFieldIndex{
   293  					Field: "ID",
   294  				},
   295  			},
   296  
   297  			"namespace": {
   298  				Name:         "namespace",
   299  				AllowMissing: false,
   300  				Unique:       false,
   301  				Indexer: &memdb.StringFieldIndex{
   302  					Field: "Namespace",
   303  				},
   304  			},
   305  
   306  			// Job index is used to lookup deployments by job
   307  			"job": {
   308  				Name:         "job",
   309  				AllowMissing: false,
   310  				Unique:       false,
   311  
   312  				// Use a compound index so the tuple of (Namespace, JobID) is
   313  				// uniquely identifying
   314  				Indexer: &memdb.CompoundIndex{
   315  					Indexes: []memdb.Indexer{
   316  						&memdb.StringFieldIndex{
   317  							Field: "Namespace",
   318  						},
   319  
   320  						&memdb.StringFieldIndex{
   321  							Field: "JobID",
   322  						},
   323  					},
   324  				},
   325  			},
   326  		},
   327  	}
   328  }
   329  
   330  // periodicLaunchTableSchema returns the MemDB schema tracking the most recent
   331  // launch time for a perioidic job.
   332  func periodicLaunchTableSchema() *memdb.TableSchema {
   333  	return &memdb.TableSchema{
   334  		Name: "periodic_launch",
   335  		Indexes: map[string]*memdb.IndexSchema{
   336  			// Primary index is used for job management
   337  			// and simple direct lookup. ID is required to be
   338  			// unique.
   339  			"id": {
   340  				Name:         "id",
   341  				AllowMissing: false,
   342  				Unique:       true,
   343  
   344  				// Use a compound index so the tuple of (Namespace, JobID) is
   345  				// uniquely identifying
   346  				Indexer: &memdb.CompoundIndex{
   347  					Indexes: []memdb.Indexer{
   348  						&memdb.StringFieldIndex{
   349  							Field: "Namespace",
   350  						},
   351  
   352  						&memdb.StringFieldIndex{
   353  							Field: "ID",
   354  						},
   355  					},
   356  				},
   357  			},
   358  		},
   359  	}
   360  }
   361  
   362  // evalTableSchema returns the MemDB schema for the eval table.
   363  // This table is used to store all the evaluations that are pending
   364  // or recently completed.
   365  func evalTableSchema() *memdb.TableSchema {
   366  	return &memdb.TableSchema{
   367  		Name: "evals",
   368  		Indexes: map[string]*memdb.IndexSchema{
   369  			// Primary index is used for direct lookup.
   370  			"id": {
   371  				Name:         "id",
   372  				AllowMissing: false,
   373  				Unique:       true,
   374  				Indexer: &memdb.UUIDFieldIndex{
   375  					Field: "ID",
   376  				},
   377  			},
   378  
   379  			"namespace": {
   380  				Name:         "namespace",
   381  				AllowMissing: false,
   382  				Unique:       false,
   383  				Indexer: &memdb.StringFieldIndex{
   384  					Field: "Namespace",
   385  				},
   386  			},
   387  
   388  			// Job index is used to lookup allocations by job
   389  			"job": {
   390  				Name:         "job",
   391  				AllowMissing: false,
   392  				Unique:       false,
   393  				Indexer: &memdb.CompoundIndex{
   394  					Indexes: []memdb.Indexer{
   395  						&memdb.StringFieldIndex{
   396  							Field: "Namespace",
   397  						},
   398  
   399  						&memdb.StringFieldIndex{
   400  							Field:     "JobID",
   401  							Lowercase: true,
   402  						},
   403  
   404  						&memdb.StringFieldIndex{
   405  							Field:     "Status",
   406  							Lowercase: true,
   407  						},
   408  					},
   409  				},
   410  			},
   411  		},
   412  	}
   413  }
   414  
   415  // allocTableSchema returns the MemDB schema for the allocation table.
   416  // This table is used to store all the task allocations between task groups
   417  // and nodes.
   418  func allocTableSchema() *memdb.TableSchema {
   419  	return &memdb.TableSchema{
   420  		Name: "allocs",
   421  		Indexes: map[string]*memdb.IndexSchema{
   422  			// Primary index is a UUID
   423  			"id": {
   424  				Name:         "id",
   425  				AllowMissing: false,
   426  				Unique:       true,
   427  				Indexer: &memdb.UUIDFieldIndex{
   428  					Field: "ID",
   429  				},
   430  			},
   431  
   432  			"namespace": {
   433  				Name:         "namespace",
   434  				AllowMissing: false,
   435  				Unique:       false,
   436  				Indexer: &memdb.StringFieldIndex{
   437  					Field: "Namespace",
   438  				},
   439  			},
   440  
   441  			// Node index is used to lookup allocations by node
   442  			"node": {
   443  				Name:         "node",
   444  				AllowMissing: true, // Missing is allow for failed allocations
   445  				Unique:       false,
   446  				Indexer: &memdb.CompoundIndex{
   447  					Indexes: []memdb.Indexer{
   448  						&memdb.StringFieldIndex{
   449  							Field:     "NodeID",
   450  							Lowercase: true,
   451  						},
   452  
   453  						// Conditional indexer on if allocation is terminal
   454  						&memdb.ConditionalIndex{
   455  							Conditional: func(obj interface{}) (bool, error) {
   456  								// Cast to allocation
   457  								alloc, ok := obj.(*structs.Allocation)
   458  								if !ok {
   459  									return false, fmt.Errorf("wrong type, got %t should be Allocation", obj)
   460  								}
   461  
   462  								// Check if the allocation is terminal
   463  								return alloc.TerminalStatus(), nil
   464  							},
   465  						},
   466  					},
   467  				},
   468  			},
   469  
   470  			// Job index is used to lookup allocations by job
   471  			"job": {
   472  				Name:         "job",
   473  				AllowMissing: false,
   474  				Unique:       false,
   475  
   476  				Indexer: &memdb.CompoundIndex{
   477  					Indexes: []memdb.Indexer{
   478  						&memdb.StringFieldIndex{
   479  							Field: "Namespace",
   480  						},
   481  
   482  						&memdb.StringFieldIndex{
   483  							Field: "JobID",
   484  						},
   485  					},
   486  				},
   487  			},
   488  
   489  			// Eval index is used to lookup allocations by eval
   490  			"eval": {
   491  				Name:         "eval",
   492  				AllowMissing: false,
   493  				Unique:       false,
   494  				Indexer: &memdb.UUIDFieldIndex{
   495  					Field: "EvalID",
   496  				},
   497  			},
   498  
   499  			// Deployment index is used to lookup allocations by deployment
   500  			"deployment": {
   501  				Name:         "deployment",
   502  				AllowMissing: true,
   503  				Unique:       false,
   504  				Indexer: &memdb.UUIDFieldIndex{
   505  					Field: "DeploymentID",
   506  				},
   507  			},
   508  		},
   509  	}
   510  }
   511  
   512  // vaultAccessorTableSchema returns the MemDB schema for the Vault Accessor
   513  // Table. This table tracks Vault accessors for tokens created on behalf of
   514  // allocations required Vault tokens.
   515  func vaultAccessorTableSchema() *memdb.TableSchema {
   516  	return &memdb.TableSchema{
   517  		Name: "vault_accessors",
   518  		Indexes: map[string]*memdb.IndexSchema{
   519  			// The primary index is the accessor id
   520  			"id": {
   521  				Name:         "id",
   522  				AllowMissing: false,
   523  				Unique:       true,
   524  				Indexer: &memdb.StringFieldIndex{
   525  					Field: "Accessor",
   526  				},
   527  			},
   528  
   529  			"alloc_id": {
   530  				Name:         "alloc_id",
   531  				AllowMissing: false,
   532  				Unique:       false,
   533  				Indexer: &memdb.StringFieldIndex{
   534  					Field: "AllocID",
   535  				},
   536  			},
   537  
   538  			"node_id": {
   539  				Name:         "node_id",
   540  				AllowMissing: false,
   541  				Unique:       false,
   542  				Indexer: &memdb.StringFieldIndex{
   543  					Field: "NodeID",
   544  				},
   545  			},
   546  		},
   547  	}
   548  }
   549  
   550  // aclPolicyTableSchema returns the MemDB schema for the policy table.
   551  // This table is used to store the policies which are refrenced by tokens
   552  func aclPolicyTableSchema() *memdb.TableSchema {
   553  	return &memdb.TableSchema{
   554  		Name: "acl_policy",
   555  		Indexes: map[string]*memdb.IndexSchema{
   556  			"id": {
   557  				Name:         "id",
   558  				AllowMissing: false,
   559  				Unique:       true,
   560  				Indexer: &memdb.StringFieldIndex{
   561  					Field: "Name",
   562  				},
   563  			},
   564  		},
   565  	}
   566  }
   567  
   568  // aclTokenTableSchema returns the MemDB schema for the tokens table.
   569  // This table is used to store the bearer tokens which are used to authenticate
   570  func aclTokenTableSchema() *memdb.TableSchema {
   571  	return &memdb.TableSchema{
   572  		Name: "acl_token",
   573  		Indexes: map[string]*memdb.IndexSchema{
   574  			"id": {
   575  				Name:         "id",
   576  				AllowMissing: false,
   577  				Unique:       true,
   578  				Indexer: &memdb.UUIDFieldIndex{
   579  					Field: "AccessorID",
   580  				},
   581  			},
   582  			"secret": {
   583  				Name:         "secret",
   584  				AllowMissing: false,
   585  				Unique:       true,
   586  				Indexer: &memdb.UUIDFieldIndex{
   587  					Field: "SecretID",
   588  				},
   589  			},
   590  			"global": {
   591  				Name:         "global",
   592  				AllowMissing: false,
   593  				Unique:       false,
   594  				Indexer: &memdb.FieldSetIndex{
   595  					Field: "Global",
   596  				},
   597  			},
   598  		},
   599  	}
   600  }