github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/state/db_bolt.go (about) 1 package state 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path/filepath" 8 "time" 9 10 hclog "github.com/hashicorp/go-hclog" 11 trstate "github.com/hashicorp/nomad/client/allocrunner/taskrunner/state" 12 dmstate "github.com/hashicorp/nomad/client/devicemanager/state" 13 "github.com/hashicorp/nomad/client/dynamicplugins" 14 driverstate "github.com/hashicorp/nomad/client/pluginmanager/drivermanager/state" 15 "github.com/hashicorp/nomad/client/serviceregistration/checks" 16 "github.com/hashicorp/nomad/helper/boltdd" 17 "github.com/hashicorp/nomad/nomad/structs" 18 "go.etcd.io/bbolt" 19 ) 20 21 /* 22 The client has a boltDB backed state store. The schema as of 0.9 looks as follows: 23 24 meta/ 25 |--> version -> '2' (not msgpack encoded) 26 |--> upgraded -> time.Now().Format(timeRFC3339) 27 allocations/ 28 |--> <alloc-id>/ 29 |--> alloc -> allocEntry{*structs.Allocation} 30 |--> deploy_status -> deployStatusEntry{*structs.AllocDeploymentStatus} 31 |--> network_status -> networkStatusEntry{*structs.AllocNetworkStatus} 32 |--> task-<name>/ 33 |--> local_state -> *trstate.LocalState # Local-only state 34 |--> task_state -> *structs.TaskState # Syncs to servers 35 |--> checks/ 36 |--> check-<id> -> *structs.CheckState # Syncs to servers 37 38 devicemanager/ 39 |--> plugin_state -> *dmstate.PluginState 40 41 drivermanager/ 42 |--> plugin_state -> *driverstate.PluginState 43 44 dynamicplugins/ 45 |--> registry_state -> *dynamicplugins.RegistryState 46 */ 47 48 var ( 49 // metaBucketName is the name of the metadata bucket 50 metaBucketName = []byte("meta") 51 52 // metaVersionKey is the key the state schema version is stored under. 53 metaVersionKey = []byte("version") 54 55 // metaVersion is the value of the state schema version to detect when 56 // an upgrade is needed. It skips the usual boltdd/msgpack backend to 57 // be as portable and futureproof as possible. 58 metaVersion = []byte{'3'} 59 60 // metaUpgradedKey is the key that stores the timestamp of the last 61 // time the schema was upgraded. 62 metaUpgradedKey = []byte("upgraded") 63 64 // allocationsBucketName is the bucket name containing all allocation related 65 // data 66 allocationsBucketName = []byte("allocations") 67 68 // allocKey is the key Allocations are stored under encapsulated in 69 // allocEntry structs. 70 allocKey = []byte("alloc") 71 72 // allocDeployStatusKey is the key *structs.AllocDeploymentStatus is 73 // stored under. 74 allocDeployStatusKey = []byte("deploy_status") 75 76 // allocNetworkStatusKey is the key *structs.AllocNetworkStatus is 77 // stored under 78 allocNetworkStatusKey = []byte("network_status") 79 80 // checkResultsBucket is the bucket name in which check query results are stored 81 checkResultsBucket = []byte("check_results") 82 83 // allocations -> $allocid -> task-$taskname -> the keys below 84 taskLocalStateKey = []byte("local_state") 85 taskStateKey = []byte("task_state") 86 87 // devManagerBucket is the bucket name containing all device manager related 88 // data 89 devManagerBucket = []byte("devicemanager") 90 91 // driverManagerBucket is the bucket name containing all driver manager 92 // related data 93 driverManagerBucket = []byte("drivermanager") 94 95 // managerPluginStateKey is the key by which plugin manager plugin state is 96 // stored at 97 managerPluginStateKey = []byte("plugin_state") 98 99 // dynamicPluginBucketName is the bucket name containing all dynamic plugin 100 // registry data. each dynamic plugin registry will have its own subbucket. 101 dynamicPluginBucketName = []byte("dynamicplugins") 102 103 // registryStateKey is the key at which dynamic plugin registry state is stored 104 registryStateKey = []byte("registry_state") 105 ) 106 107 // taskBucketName returns the bucket name for the given task name. 108 func taskBucketName(taskName string) []byte { 109 return []byte("task-" + taskName) 110 } 111 112 // NewStateDBFunc creates a StateDB given a state directory. 113 type NewStateDBFunc func(logger hclog.Logger, stateDir string) (StateDB, error) 114 115 // GetStateDBFactory returns a func for creating a StateDB 116 func GetStateDBFactory(devMode bool) NewStateDBFunc { 117 // Return a noop state db implementation when in debug mode 118 if devMode { 119 return func(hclog.Logger, string) (StateDB, error) { 120 return NoopDB{}, nil 121 } 122 } 123 124 return NewBoltStateDB 125 } 126 127 // BoltStateDB persists and restores Nomad client state in a boltdb. All 128 // methods are safe for concurrent access. 129 type BoltStateDB struct { 130 stateDir string 131 db *boltdd.DB 132 logger hclog.Logger 133 } 134 135 // NewBoltStateDB creates or opens an existing boltdb state file or returns an 136 // error. 137 func NewBoltStateDB(logger hclog.Logger, stateDir string) (StateDB, error) { 138 fn := filepath.Join(stateDir, "state.db") 139 140 // Check to see if the DB already exists 141 fi, err := os.Stat(fn) 142 if err != nil && !os.IsNotExist(err) { 143 return nil, err 144 } 145 firstRun := fi == nil 146 147 // Timeout to force failure when accessing a data dir that is already in use 148 timeout := &bbolt.Options{Timeout: 5 * time.Second} 149 150 // Create or open the boltdb state database 151 db, err := boltdd.Open(fn, 0600, timeout) 152 if err == bbolt.ErrTimeout { 153 return nil, fmt.Errorf("timed out while opening database, is another Nomad process accessing data_dir %s?", stateDir) 154 } else if err != nil { 155 return nil, fmt.Errorf("failed to create state database: %v", err) 156 } 157 158 sdb := &BoltStateDB{ 159 stateDir: stateDir, 160 db: db, 161 logger: logger, 162 } 163 164 // If db did not already exist, initialize metadata fields 165 if firstRun { 166 if err := sdb.init(); err != nil { 167 return nil, err 168 } 169 } 170 171 return sdb, nil 172 } 173 174 func (s *BoltStateDB) Name() string { 175 return "boltdb" 176 } 177 178 // GetAllAllocations gets all allocations persisted by this client and returns 179 // a map of alloc ids to errors for any allocations that could not be restored. 180 // 181 // If a fatal error was encountered it will be returned and the other two 182 // values will be nil. 183 func (s *BoltStateDB) GetAllAllocations() ([]*structs.Allocation, map[string]error, error) { 184 var allocs []*structs.Allocation 185 var errs map[string]error 186 187 err := s.db.View(func(tx *boltdd.Tx) error { 188 allocs, errs = s.getAllAllocations(tx) 189 return nil 190 }) 191 192 // db.View itself may return an error, so still check 193 if err != nil { 194 return nil, nil, err 195 } 196 197 return allocs, errs, nil 198 } 199 200 // allocEntry wraps values in the Allocations buckets 201 type allocEntry struct { 202 Alloc *structs.Allocation 203 } 204 205 func (s *BoltStateDB) getAllAllocations(tx *boltdd.Tx) ([]*structs.Allocation, map[string]error) { 206 allocs := make([]*structs.Allocation, 0, 10) 207 errs := map[string]error{} 208 209 allocationsBkt := tx.Bucket(allocationsBucketName) 210 if allocationsBkt == nil { 211 // No allocs 212 return allocs, errs 213 } 214 215 // Create a cursor for iteration. 216 c := allocationsBkt.BoltBucket().Cursor() 217 218 // Iterate over all the allocation buckets 219 for k, _ := c.First(); k != nil; k, _ = c.Next() { 220 allocID := string(k) 221 allocBkt := allocationsBkt.Bucket(k) 222 if allocBkt == nil { 223 errs[allocID] = fmt.Errorf("missing alloc bucket") 224 continue 225 } 226 227 var ae allocEntry 228 if err := allocBkt.Get(allocKey, &ae); err != nil { 229 errs[allocID] = fmt.Errorf("failed to decode alloc: %v", err) 230 continue 231 } 232 233 // Handle upgrade path 234 ae.Alloc.Canonicalize() 235 ae.Alloc.Job.Canonicalize() 236 237 allocs = append(allocs, ae.Alloc) 238 } 239 240 return allocs, errs 241 } 242 243 // PutAllocation stores an allocation or returns an error. 244 func (s *BoltStateDB) PutAllocation(alloc *structs.Allocation, opts ...WriteOption) error { 245 return s.updateWithOptions(opts, func(tx *boltdd.Tx) error { 246 // Retrieve the root allocations bucket 247 allocsBkt, err := tx.CreateBucketIfNotExists(allocationsBucketName) 248 if err != nil { 249 return err 250 } 251 252 // Retrieve the specific allocations bucket 253 key := []byte(alloc.ID) 254 allocBkt, err := allocsBkt.CreateBucketIfNotExists(key) 255 if err != nil { 256 return err 257 } 258 259 allocState := allocEntry{ 260 Alloc: alloc, 261 } 262 return allocBkt.Put(allocKey, &allocState) 263 }) 264 } 265 266 // deployStatusEntry wraps values for DeploymentStatus keys. 267 type deployStatusEntry struct { 268 DeploymentStatus *structs.AllocDeploymentStatus 269 } 270 271 // PutDeploymentStatus stores an allocation's DeploymentStatus or returns an 272 // error. 273 func (s *BoltStateDB) PutDeploymentStatus(allocID string, ds *structs.AllocDeploymentStatus) error { 274 return s.db.Update(func(tx *boltdd.Tx) error { 275 return putDeploymentStatusImpl(tx, allocID, ds) 276 }) 277 } 278 279 func putDeploymentStatusImpl(tx *boltdd.Tx, allocID string, ds *structs.AllocDeploymentStatus) error { 280 allocBkt, err := getAllocationBucket(tx, allocID) 281 if err != nil { 282 return err 283 } 284 285 entry := deployStatusEntry{ 286 DeploymentStatus: ds, 287 } 288 return allocBkt.Put(allocDeployStatusKey, &entry) 289 } 290 291 // GetDeploymentStatus retrieves an allocation's DeploymentStatus or returns an 292 // error. 293 func (s *BoltStateDB) GetDeploymentStatus(allocID string) (*structs.AllocDeploymentStatus, error) { 294 var entry deployStatusEntry 295 296 err := s.db.View(func(tx *boltdd.Tx) error { 297 allAllocsBkt := tx.Bucket(allocationsBucketName) 298 if allAllocsBkt == nil { 299 // No state, return 300 return nil 301 } 302 303 allocBkt := allAllocsBkt.Bucket([]byte(allocID)) 304 if allocBkt == nil { 305 // No state for alloc, return 306 return nil 307 } 308 309 return allocBkt.Get(allocDeployStatusKey, &entry) 310 }) 311 312 // It's valid for this field to be nil/missing 313 if boltdd.IsErrNotFound(err) { 314 return nil, nil 315 } 316 317 if err != nil { 318 return nil, err 319 } 320 321 return entry.DeploymentStatus, nil 322 } 323 324 // networkStatusEntry wraps values for NetworkStatus keys. 325 type networkStatusEntry struct { 326 NetworkStatus *structs.AllocNetworkStatus 327 } 328 329 // PutNetworkStatus stores an allocation's DeploymentStatus or returns an 330 // error. 331 func (s *BoltStateDB) PutNetworkStatus(allocID string, ds *structs.AllocNetworkStatus, opts ...WriteOption) error { 332 return s.updateWithOptions(opts, func(tx *boltdd.Tx) error { 333 return putNetworkStatusImpl(tx, allocID, ds) 334 }) 335 } 336 337 func putNetworkStatusImpl(tx *boltdd.Tx, allocID string, ds *structs.AllocNetworkStatus) error { 338 allocBkt, err := getAllocationBucket(tx, allocID) 339 if err != nil { 340 return err 341 } 342 343 entry := networkStatusEntry{ 344 NetworkStatus: ds, 345 } 346 return allocBkt.Put(allocNetworkStatusKey, &entry) 347 } 348 349 // GetNetworkStatus retrieves an allocation's NetworkStatus or returns an 350 // error. 351 func (s *BoltStateDB) GetNetworkStatus(allocID string) (*structs.AllocNetworkStatus, error) { 352 var entry networkStatusEntry 353 354 err := s.db.View(func(tx *boltdd.Tx) error { 355 allAllocsBkt := tx.Bucket(allocationsBucketName) 356 if allAllocsBkt == nil { 357 // No state, return 358 return nil 359 } 360 361 allocBkt := allAllocsBkt.Bucket([]byte(allocID)) 362 if allocBkt == nil { 363 // No state for alloc, return 364 return nil 365 } 366 367 return allocBkt.Get(allocNetworkStatusKey, &entry) 368 }) 369 370 // It's valid for this field to be nil/missing 371 if boltdd.IsErrNotFound(err) { 372 return nil, nil 373 } 374 375 if err != nil { 376 return nil, err 377 } 378 379 return entry.NetworkStatus, nil 380 } 381 382 // GetTaskRunnerState returns the LocalState and TaskState for a 383 // TaskRunner. LocalState or TaskState will be nil if they do not exist. 384 // 385 // If an error is encountered both LocalState and TaskState will be nil. 386 func (s *BoltStateDB) GetTaskRunnerState(allocID, taskName string) (*trstate.LocalState, *structs.TaskState, error) { 387 var ls *trstate.LocalState 388 var ts *structs.TaskState 389 390 err := s.db.View(func(tx *boltdd.Tx) error { 391 allAllocsBkt := tx.Bucket(allocationsBucketName) 392 if allAllocsBkt == nil { 393 // No state, return 394 return nil 395 } 396 397 allocBkt := allAllocsBkt.Bucket([]byte(allocID)) 398 if allocBkt == nil { 399 // No state for alloc, return 400 return nil 401 } 402 403 taskBkt := allocBkt.Bucket(taskBucketName(taskName)) 404 if taskBkt == nil { 405 // No state for task, return 406 return nil 407 } 408 409 // Restore Local State if it exists 410 ls = &trstate.LocalState{} 411 if err := taskBkt.Get(taskLocalStateKey, ls); err != nil { 412 if !boltdd.IsErrNotFound(err) { 413 return fmt.Errorf("failed to read local task runner state: %v", err) 414 } 415 416 // Key not found, reset ls to nil 417 ls = nil 418 } 419 420 // Restore Task State if it exists 421 ts = &structs.TaskState{} 422 if err := taskBkt.Get(taskStateKey, ts); err != nil { 423 if !boltdd.IsErrNotFound(err) { 424 return fmt.Errorf("failed to read task state: %v", err) 425 } 426 427 // Key not found, reset ts to nil 428 ts = nil 429 } 430 431 return nil 432 }) 433 434 if err != nil { 435 return nil, nil, err 436 } 437 438 return ls, ts, nil 439 } 440 441 // PutTaskRunnerLocalState stores TaskRunner's LocalState or returns an error. 442 func (s *BoltStateDB) PutTaskRunnerLocalState(allocID, taskName string, val *trstate.LocalState) error { 443 return s.db.Update(func(tx *boltdd.Tx) error { 444 return putTaskRunnerLocalStateImpl(tx, allocID, taskName, val) 445 }) 446 } 447 448 // putTaskRunnerLocalStateImpl stores TaskRunner's LocalState in an ongoing 449 // transaction or returns an error. 450 func putTaskRunnerLocalStateImpl(tx *boltdd.Tx, allocID, taskName string, val *trstate.LocalState) error { 451 taskBkt, err := getTaskBucket(tx, allocID, taskName) 452 if err != nil { 453 return fmt.Errorf("failed to retrieve allocation bucket: %v", err) 454 } 455 456 if err := taskBkt.Put(taskLocalStateKey, val); err != nil { 457 return fmt.Errorf("failed to write task_runner state: %v", err) 458 } 459 460 return nil 461 } 462 463 // PutTaskState stores a task's state or returns an error. 464 func (s *BoltStateDB) PutTaskState(allocID, taskName string, state *structs.TaskState) error { 465 return s.db.Update(func(tx *boltdd.Tx) error { 466 return putTaskStateImpl(tx, allocID, taskName, state) 467 }) 468 } 469 470 // putTaskStateImpl stores a task's state in an ongoing transaction or returns 471 // an error. 472 func putTaskStateImpl(tx *boltdd.Tx, allocID, taskName string, state *structs.TaskState) error { 473 taskBkt, err := getTaskBucket(tx, allocID, taskName) 474 if err != nil { 475 return fmt.Errorf("failed to retrieve allocation bucket: %v", err) 476 } 477 478 return taskBkt.Put(taskStateKey, state) 479 } 480 481 // DeleteTaskBucket is used to delete a task bucket if it exists. 482 func (s *BoltStateDB) DeleteTaskBucket(allocID, taskName string) error { 483 return s.db.Update(func(tx *boltdd.Tx) error { 484 // Retrieve the root allocations bucket 485 allocations := tx.Bucket(allocationsBucketName) 486 if allocations == nil { 487 return nil 488 } 489 490 // Retrieve the specific allocations bucket 491 alloc := allocations.Bucket([]byte(allocID)) 492 if alloc == nil { 493 return nil 494 } 495 496 // Check if the bucket exists 497 key := taskBucketName(taskName) 498 return alloc.DeleteBucket(key) 499 }) 500 } 501 502 // DeleteAllocationBucket is used to delete an allocation bucket if it exists. 503 func (s *BoltStateDB) DeleteAllocationBucket(allocID string, opts ...WriteOption) error { 504 return s.updateWithOptions(opts, func(tx *boltdd.Tx) error { 505 // Retrieve the root allocations bucket 506 allocations := tx.Bucket(allocationsBucketName) 507 if allocations == nil { 508 return nil 509 } 510 511 key := []byte(allocID) 512 return allocations.DeleteBucket(key) 513 }) 514 } 515 516 // Close releases all database resources and unlocks the database file on disk. 517 // All transactions must be closed before closing the database. 518 func (s *BoltStateDB) Close() error { 519 return s.db.Close() 520 } 521 522 // getAllocationBucket returns the bucket used to persist state about a 523 // particular allocation. If the root allocation bucket or the specific 524 // allocation bucket doesn't exist, it will be created as long as the 525 // transaction is writable. 526 func getAllocationBucket(tx *boltdd.Tx, allocID string) (*boltdd.Bucket, error) { 527 var err error 528 w := tx.Writable() 529 530 // Retrieve the root allocations bucket 531 allocations := tx.Bucket(allocationsBucketName) 532 if allocations == nil { 533 if !w { 534 return nil, fmt.Errorf("Allocations bucket doesn't exist and transaction is not writable") 535 } 536 537 allocations, err = tx.CreateBucketIfNotExists(allocationsBucketName) 538 if err != nil { 539 return nil, err 540 } 541 } 542 543 // Retrieve the specific allocations bucket 544 key := []byte(allocID) 545 alloc := allocations.Bucket(key) 546 if alloc == nil { 547 if !w { 548 return nil, fmt.Errorf("Allocation bucket doesn't exist and transaction is not writable") 549 } 550 551 alloc, err = allocations.CreateBucket(key) 552 if err != nil { 553 return nil, err 554 } 555 } 556 557 return alloc, nil 558 } 559 560 // getTaskBucket returns the bucket used to persist state about a 561 // particular task. If the root allocation bucket, the specific 562 // allocation or task bucket doesn't exist, they will be created as long as the 563 // transaction is writable. 564 func getTaskBucket(tx *boltdd.Tx, allocID, taskName string) (*boltdd.Bucket, error) { 565 alloc, err := getAllocationBucket(tx, allocID) 566 if err != nil { 567 return nil, err 568 } 569 570 // Retrieve the specific task bucket 571 w := tx.Writable() 572 key := taskBucketName(taskName) 573 task := alloc.Bucket(key) 574 if task == nil { 575 if !w { 576 return nil, fmt.Errorf("Task bucket doesn't exist and transaction is not writable") 577 } 578 579 task, err = alloc.CreateBucket(key) 580 if err != nil { 581 return nil, err 582 } 583 } 584 585 return task, nil 586 } 587 588 // PutDevicePluginState stores the device manager's plugin state or returns an 589 // error. 590 func (s *BoltStateDB) PutDevicePluginState(ps *dmstate.PluginState) error { 591 return s.db.Update(func(tx *boltdd.Tx) error { 592 // Retrieve the root device manager bucket 593 devBkt, err := tx.CreateBucketIfNotExists(devManagerBucket) 594 if err != nil { 595 return err 596 } 597 598 return devBkt.Put(managerPluginStateKey, ps) 599 }) 600 } 601 602 // GetDevicePluginState stores the device manager's plugin state or returns an 603 // error. 604 func (s *BoltStateDB) GetDevicePluginState() (*dmstate.PluginState, error) { 605 var ps *dmstate.PluginState 606 607 err := s.db.View(func(tx *boltdd.Tx) error { 608 devBkt := tx.Bucket(devManagerBucket) 609 if devBkt == nil { 610 // No state, return 611 return nil 612 } 613 614 // Restore Plugin State if it exists 615 ps = &dmstate.PluginState{} 616 if err := devBkt.Get(managerPluginStateKey, ps); err != nil { 617 if !boltdd.IsErrNotFound(err) { 618 return fmt.Errorf("failed to read device manager plugin state: %v", err) 619 } 620 621 // Key not found, reset ps to nil 622 ps = nil 623 } 624 625 return nil 626 }) 627 628 if err != nil { 629 return nil, err 630 } 631 632 return ps, nil 633 } 634 635 // PutDriverPluginState stores the driver manager's plugin state or returns an 636 // error. 637 func (s *BoltStateDB) PutDriverPluginState(ps *driverstate.PluginState) error { 638 return s.db.Update(func(tx *boltdd.Tx) error { 639 // Retrieve the root driver manager bucket 640 driverBkt, err := tx.CreateBucketIfNotExists(driverManagerBucket) 641 if err != nil { 642 return err 643 } 644 645 return driverBkt.Put(managerPluginStateKey, ps) 646 }) 647 } 648 649 // GetDriverPluginState stores the driver manager's plugin state or returns an 650 // error. 651 func (s *BoltStateDB) GetDriverPluginState() (*driverstate.PluginState, error) { 652 var ps *driverstate.PluginState 653 654 err := s.db.View(func(tx *boltdd.Tx) error { 655 driverBkt := tx.Bucket(driverManagerBucket) 656 if driverBkt == nil { 657 // No state, return 658 return nil 659 } 660 661 // Restore Plugin State if it exists 662 ps = &driverstate.PluginState{} 663 if err := driverBkt.Get(managerPluginStateKey, ps); err != nil { 664 if !boltdd.IsErrNotFound(err) { 665 return fmt.Errorf("failed to read driver manager plugin state: %v", err) 666 } 667 668 // Key not found, reset ps to nil 669 ps = nil 670 } 671 672 return nil 673 }) 674 675 if err != nil { 676 return nil, err 677 } 678 679 return ps, nil 680 } 681 682 // PutDynamicPluginRegistryState stores the dynamic plugin registry's 683 // state or returns an error. 684 func (s *BoltStateDB) PutDynamicPluginRegistryState(ps *dynamicplugins.RegistryState) error { 685 return s.db.Update(func(tx *boltdd.Tx) error { 686 // Retrieve the root dynamic plugin manager bucket 687 dynamicBkt, err := tx.CreateBucketIfNotExists(dynamicPluginBucketName) 688 if err != nil { 689 return err 690 } 691 return dynamicBkt.Put(registryStateKey, ps) 692 }) 693 } 694 695 // GetDynamicPluginRegistryState stores the dynamic plugin registry's 696 // registry state or returns an error. 697 func (s *BoltStateDB) GetDynamicPluginRegistryState() (*dynamicplugins.RegistryState, error) { 698 var ps *dynamicplugins.RegistryState 699 700 err := s.db.View(func(tx *boltdd.Tx) error { 701 dynamicBkt := tx.Bucket(dynamicPluginBucketName) 702 if dynamicBkt == nil { 703 // No state, return 704 return nil 705 } 706 707 // Restore Plugin State if it exists 708 ps = &dynamicplugins.RegistryState{} 709 if err := dynamicBkt.Get(registryStateKey, ps); err != nil { 710 if !boltdd.IsErrNotFound(err) { 711 return fmt.Errorf("failed to read dynamic plugin registry state: %v", err) 712 } 713 714 // Key not found, reset ps to nil 715 ps = nil 716 } 717 718 return nil 719 }) 720 721 if err != nil { 722 return nil, err 723 } 724 725 return ps, nil 726 } 727 728 func keyForCheck(allocID string, checkID structs.CheckID) []byte { 729 return []byte(fmt.Sprintf("%s_%s", allocID, checkID)) 730 } 731 732 // PutCheckResult puts qr into the state store. 733 func (s *BoltStateDB) PutCheckResult(allocID string, qr *structs.CheckQueryResult) error { 734 return s.db.Update(func(tx *boltdd.Tx) error { 735 bkt, err := tx.CreateBucketIfNotExists(checkResultsBucket) 736 if err != nil { 737 return err 738 } 739 key := keyForCheck(allocID, qr.ID) 740 return bkt.Put(key, qr) 741 }) 742 } 743 744 // GetCheckResults gets the check results associated with allocID from the state store. 745 func (s *BoltStateDB) GetCheckResults() (checks.ClientResults, error) { 746 m := make(checks.ClientResults) 747 748 err := s.db.View(func(tx *boltdd.Tx) error { 749 bkt := tx.Bucket(checkResultsBucket) 750 if bkt == nil { 751 return nil // nothing set yet 752 } 753 754 if err := boltdd.Iterate(bkt, nil, func(key []byte, qr structs.CheckQueryResult) { 755 parts := bytes.SplitN(key, []byte("_"), 2) 756 allocID, _ := parts[0], parts[1] 757 m.Insert(string(allocID), &qr) 758 }); err != nil { 759 return err 760 } 761 return nil 762 }) 763 return m, err 764 } 765 766 func (s *BoltStateDB) DeleteCheckResults(allocID string, checkIDs []structs.CheckID) error { 767 return s.db.Update(func(tx *boltdd.Tx) error { 768 bkt := tx.Bucket(checkResultsBucket) 769 if bkt == nil { 770 return nil // nothing set yet 771 } 772 773 for _, id := range checkIDs { 774 key := keyForCheck(allocID, id) 775 if err := bkt.Delete(key); err != nil { 776 return err 777 } 778 } 779 return nil 780 }) 781 } 782 783 func (s *BoltStateDB) PurgeCheckResults(allocID string) error { 784 return s.db.Update(func(tx *boltdd.Tx) error { 785 bkt := tx.Bucket(checkResultsBucket) 786 if bkt == nil { 787 return nil // nothing set yet 788 } 789 return bkt.DeletePrefix([]byte(allocID + "_")) 790 }) 791 } 792 793 // init initializes metadata entries in a newly created state database. 794 func (s *BoltStateDB) init() error { 795 return s.db.Update(func(tx *boltdd.Tx) error { 796 return addMeta(tx.BoltTx()) 797 }) 798 } 799 800 // updateWithOptions enables adjustments to db.Update operation, including Batch mode. 801 func (s *BoltStateDB) updateWithOptions(opts []WriteOption, updateFn func(tx *boltdd.Tx) error) error { 802 writeOpts := mergeWriteOptions(opts) 803 804 if writeOpts.BatchMode { 805 // In Batch mode, BoltDB opportunistically combines multiple concurrent writes into one or 806 // several transactions. See boltdb.Batch() documentation for details. 807 return s.db.Batch(updateFn) 808 } else { 809 return s.db.Update(updateFn) 810 } 811 } 812 813 // Upgrade bolt state db from 0.8 schema to 0.9 schema. Noop if already using 814 // 0.9 schema. Creates a backup before upgrading. 815 func (s *BoltStateDB) Upgrade() error { 816 // Check to see if the underlying DB needs upgrading. 817 upgrade09, upgrade13, err := NeedsUpgrade(s.db.BoltDB()) 818 if err != nil { 819 return err 820 } 821 if !upgrade09 && !upgrade13 { 822 // No upgrade needed! 823 return nil 824 } 825 826 // Upgraded needed. Backup the boltdb first. 827 backupFileName := filepath.Join(s.stateDir, "state.db.backup") 828 if err := backupDB(s.db.BoltDB(), backupFileName); err != nil { 829 return fmt.Errorf("error backing up state db: %v", err) 830 } 831 832 // Perform the upgrade 833 if err := s.db.Update(func(tx *boltdd.Tx) error { 834 835 if upgrade09 { 836 if err := UpgradeAllocs(s.logger, tx); err != nil { 837 return err 838 } 839 } 840 if upgrade13 { 841 if err := UpgradeDynamicPluginRegistry(s.logger, tx); err != nil { 842 return err 843 } 844 } 845 846 // Add standard metadata 847 if err := addMeta(tx.BoltTx()); err != nil { 848 return err 849 } 850 851 // Write the time the upgrade was done 852 bkt, err := tx.CreateBucketIfNotExists(metaBucketName) 853 if err != nil { 854 return err 855 } 856 857 return bkt.Put(metaUpgradedKey, time.Now().Format(time.RFC3339)) 858 }); err != nil { 859 return err 860 } 861 862 s.logger.Info("successfully upgraded state") 863 return nil 864 } 865 866 // DB allows access to the underlying BoltDB for testing purposes. 867 func (s *BoltStateDB) DB() *boltdd.DB { 868 return s.db 869 }