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