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 }