github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/nomad/state/schema.go (about)

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