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