github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/nomad/state/state_store.go (about) 1 package state 2 3 import ( 4 "fmt" 5 "io" 6 "log" 7 "sync" 8 9 "github.com/hashicorp/go-memdb" 10 "github.com/hashicorp/nomad/nomad/structs" 11 ) 12 13 // The StateStore is responsible for maintaining all the Nomad 14 // state. It is manipulated by the FSM which maintains consistency 15 // through the use of Raft. The goals of the StateStore are to provide 16 // high concurrency for read operations without blocking writes, and 17 // to provide write availability in the face of reads. EVERY object 18 // returned as a result of a read against the state store should be 19 // considered a constant and NEVER modified in place. 20 type StateStore struct { 21 logger *log.Logger 22 db *memdb.MemDB 23 watch *stateWatch 24 } 25 26 // StateSnapshot is used to provide a point-in-time snapshot 27 type StateSnapshot struct { 28 StateStore 29 } 30 31 // StateRestore is used to optimize the performance when 32 // restoring state by only using a single large transaction 33 // instead of thousands of sub transactions 34 type StateRestore struct { 35 txn *memdb.Txn 36 watch *stateWatch 37 allocNodes map[string]struct{} 38 } 39 40 // Abort is used to abort the restore operation 41 func (s *StateRestore) Abort() { 42 s.txn.Abort() 43 } 44 45 // Commit is used to commit the restore operation 46 func (s *StateRestore) Commit() { 47 s.txn.Defer(func() { s.watch.notifyAllocs(s.allocNodes) }) 48 s.txn.Commit() 49 } 50 51 // IndexEntry is used with the "index" table 52 // for managing the latest Raft index affecting a table. 53 type IndexEntry struct { 54 Key string 55 Value uint64 56 } 57 58 // stateWatch holds shared state for watching updates. This is 59 // outside of StateStore so it can be shared with snapshots. 60 type stateWatch struct { 61 allocs map[string]*NotifyGroup 62 allocLock sync.Mutex 63 } 64 65 // NewStateStore is used to create a new state store 66 func NewStateStore(logOutput io.Writer) (*StateStore, error) { 67 // Create the MemDB 68 db, err := memdb.NewMemDB(stateStoreSchema()) 69 if err != nil { 70 return nil, fmt.Errorf("state store setup failed: %v", err) 71 } 72 73 // Create the watch entry 74 watch := &stateWatch{ 75 allocs: make(map[string]*NotifyGroup), 76 } 77 78 // Create the state store 79 s := &StateStore{ 80 logger: log.New(logOutput, "", log.LstdFlags), 81 db: db, 82 watch: watch, 83 } 84 return s, nil 85 } 86 87 // Snapshot is used to create a point in time snapshot. Because 88 // we use MemDB, we just need to snapshot the state of the underlying 89 // database. 90 func (s *StateStore) Snapshot() (*StateSnapshot, error) { 91 snap := &StateSnapshot{ 92 StateStore: StateStore{ 93 logger: s.logger, 94 db: s.db.Snapshot(), 95 watch: s.watch, 96 }, 97 } 98 return snap, nil 99 } 100 101 // Restore is used to optimize the efficiency of rebuilding 102 // state by minimizing the number of transactions and checking 103 // overhead. 104 func (s *StateStore) Restore() (*StateRestore, error) { 105 txn := s.db.Txn(true) 106 r := &StateRestore{ 107 txn: txn, 108 watch: s.watch, 109 allocNodes: make(map[string]struct{}), 110 } 111 return r, nil 112 } 113 114 // WatchAllocs is used to subscribe a channel to changes in allocations for a node 115 func (s *StateStore) WatchAllocs(node string, notify chan struct{}) { 116 s.watch.allocLock.Lock() 117 defer s.watch.allocLock.Unlock() 118 119 // Check for an existing notify group 120 if grp, ok := s.watch.allocs[node]; ok { 121 grp.Wait(notify) 122 return 123 } 124 125 // Create new notify group 126 grp := &NotifyGroup{} 127 grp.Wait(notify) 128 s.watch.allocs[node] = grp 129 } 130 131 // StopWatchAllocs is used to unsubscribe a channel from changes in allocations 132 func (s *StateStore) StopWatchAllocs(node string, notify chan struct{}) { 133 s.watch.allocLock.Lock() 134 defer s.watch.allocLock.Unlock() 135 136 // Check for an existing notify group 137 if grp, ok := s.watch.allocs[node]; ok { 138 grp.Clear(notify) 139 if grp.Empty() { 140 delete(s.watch.allocs, node) 141 } 142 } 143 } 144 145 // notifyAllocs is used to notify any node alloc listeners of a change 146 func (w *stateWatch) notifyAllocs(nodes map[string]struct{}) { 147 w.allocLock.Lock() 148 defer w.allocLock.Unlock() 149 150 for node := range nodes { 151 if grp, ok := w.allocs[node]; ok { 152 grp.Notify() 153 delete(w.allocs, node) 154 } 155 } 156 } 157 158 // UpsertNode is used to register a node or update a node definition 159 // This is assumed to be triggered by the client, so we retain the value 160 // of drain which is set by the scheduler. 161 func (s *StateStore) UpsertNode(index uint64, node *structs.Node) error { 162 txn := s.db.Txn(true) 163 defer txn.Abort() 164 165 // Check if the node already exists 166 existing, err := txn.First("nodes", "id", node.ID) 167 if err != nil { 168 return fmt.Errorf("node lookup failed: %v", err) 169 } 170 171 // Setup the indexes correctly 172 if existing != nil { 173 exist := existing.(*structs.Node) 174 node.CreateIndex = exist.CreateIndex 175 node.ModifyIndex = index 176 node.Drain = exist.Drain // Retain the drain mode 177 } else { 178 node.CreateIndex = index 179 node.ModifyIndex = index 180 } 181 182 // Insert the node 183 if err := txn.Insert("nodes", node); err != nil { 184 return fmt.Errorf("node insert failed: %v", err) 185 } 186 if err := txn.Insert("index", &IndexEntry{"nodes", index}); err != nil { 187 return fmt.Errorf("index update failed: %v", err) 188 } 189 190 txn.Commit() 191 return nil 192 } 193 194 // DeleteNode is used to deregister a node 195 func (s *StateStore) DeleteNode(index uint64, nodeID string) error { 196 txn := s.db.Txn(true) 197 defer txn.Abort() 198 199 // Lookup the node 200 existing, err := txn.First("nodes", "id", nodeID) 201 if err != nil { 202 return fmt.Errorf("node lookup failed: %v", err) 203 } 204 if existing == nil { 205 return fmt.Errorf("node not found") 206 } 207 208 // Delete the node 209 if err := txn.Delete("nodes", existing); err != nil { 210 return fmt.Errorf("node delete failed: %v", err) 211 } 212 if err := txn.Insert("index", &IndexEntry{"nodes", index}); err != nil { 213 return fmt.Errorf("index update failed: %v", err) 214 } 215 216 txn.Commit() 217 return nil 218 } 219 220 // UpdateNodeStatus is used to update the status of a node 221 func (s *StateStore) UpdateNodeStatus(index uint64, nodeID, status string) error { 222 txn := s.db.Txn(true) 223 defer txn.Abort() 224 225 // Lookup the node 226 existing, err := txn.First("nodes", "id", nodeID) 227 if err != nil { 228 return fmt.Errorf("node lookup failed: %v", err) 229 } 230 if existing == nil { 231 return fmt.Errorf("node not found") 232 } 233 234 // Copy the existing node 235 existingNode := existing.(*structs.Node) 236 copyNode := new(structs.Node) 237 *copyNode = *existingNode 238 239 // Update the status in the copy 240 copyNode.Status = status 241 copyNode.ModifyIndex = index 242 243 // Insert the node 244 if err := txn.Insert("nodes", copyNode); err != nil { 245 return fmt.Errorf("node update failed: %v", err) 246 } 247 if err := txn.Insert("index", &IndexEntry{"nodes", index}); err != nil { 248 return fmt.Errorf("index update failed: %v", err) 249 } 250 251 txn.Commit() 252 return nil 253 } 254 255 // UpdateNodeDrain is used to update the drain of a node 256 func (s *StateStore) UpdateNodeDrain(index uint64, nodeID string, drain bool) error { 257 txn := s.db.Txn(true) 258 defer txn.Abort() 259 260 // Lookup the node 261 existing, err := txn.First("nodes", "id", nodeID) 262 if err != nil { 263 return fmt.Errorf("node lookup failed: %v", err) 264 } 265 if existing == nil { 266 return fmt.Errorf("node not found") 267 } 268 269 // Copy the existing node 270 existingNode := existing.(*structs.Node) 271 copyNode := new(structs.Node) 272 *copyNode = *existingNode 273 274 // Update the drain in the copy 275 copyNode.Drain = drain 276 copyNode.ModifyIndex = index 277 278 // Insert the node 279 if err := txn.Insert("nodes", copyNode); err != nil { 280 return fmt.Errorf("node update failed: %v", err) 281 } 282 if err := txn.Insert("index", &IndexEntry{"nodes", index}); err != nil { 283 return fmt.Errorf("index update failed: %v", err) 284 } 285 286 txn.Commit() 287 return nil 288 } 289 290 // NodeByID is used to lookup a node by ID 291 func (s *StateStore) NodeByID(nodeID string) (*structs.Node, error) { 292 txn := s.db.Txn(false) 293 294 existing, err := txn.First("nodes", "id", nodeID) 295 if err != nil { 296 return nil, fmt.Errorf("node lookup failed: %v", err) 297 } 298 299 if existing != nil { 300 return existing.(*structs.Node), nil 301 } 302 return nil, nil 303 } 304 305 // Nodes returns an iterator over all the nodes 306 func (s *StateStore) Nodes() (memdb.ResultIterator, error) { 307 txn := s.db.Txn(false) 308 309 // Walk the entire nodes table 310 iter, err := txn.Get("nodes", "id") 311 if err != nil { 312 return nil, err 313 } 314 return iter, nil 315 } 316 317 // UpsertJob is used to register a job or update a job definition 318 func (s *StateStore) UpsertJob(index uint64, job *structs.Job) error { 319 txn := s.db.Txn(true) 320 defer txn.Abort() 321 322 // Check if the job already exists 323 existing, err := txn.First("jobs", "id", job.ID) 324 if err != nil { 325 return fmt.Errorf("job lookup failed: %v", err) 326 } 327 328 // Setup the indexes correctly 329 if existing != nil { 330 job.CreateIndex = existing.(*structs.Job).CreateIndex 331 job.ModifyIndex = index 332 } else { 333 job.CreateIndex = index 334 job.ModifyIndex = index 335 } 336 337 // Insert the job 338 if err := txn.Insert("jobs", job); err != nil { 339 return fmt.Errorf("job insert failed: %v", err) 340 } 341 if err := txn.Insert("index", &IndexEntry{"jobs", index}); err != nil { 342 return fmt.Errorf("index update failed: %v", err) 343 } 344 345 txn.Commit() 346 return nil 347 } 348 349 // DeleteJob is used to deregister a job 350 func (s *StateStore) DeleteJob(index uint64, jobID string) error { 351 txn := s.db.Txn(true) 352 defer txn.Abort() 353 354 // Lookup the node 355 existing, err := txn.First("jobs", "id", jobID) 356 if err != nil { 357 return fmt.Errorf("job lookup failed: %v", err) 358 } 359 if existing == nil { 360 return fmt.Errorf("job not found") 361 } 362 363 // Delete the node 364 if err := txn.Delete("jobs", existing); err != nil { 365 return fmt.Errorf("job delete failed: %v", err) 366 } 367 if err := txn.Insert("index", &IndexEntry{"jobs", index}); err != nil { 368 return fmt.Errorf("index update failed: %v", err) 369 } 370 371 txn.Commit() 372 return nil 373 } 374 375 // JobByID is used to lookup a job by its ID 376 func (s *StateStore) JobByID(id string) (*structs.Job, error) { 377 txn := s.db.Txn(false) 378 379 existing, err := txn.First("jobs", "id", id) 380 if err != nil { 381 return nil, fmt.Errorf("job lookup failed: %v", err) 382 } 383 384 if existing != nil { 385 return existing.(*structs.Job), nil 386 } 387 return nil, nil 388 } 389 390 // Jobs returns an iterator over all the jobs 391 func (s *StateStore) Jobs() (memdb.ResultIterator, error) { 392 txn := s.db.Txn(false) 393 394 // Walk the entire jobs table 395 iter, err := txn.Get("jobs", "id") 396 if err != nil { 397 return nil, err 398 } 399 return iter, nil 400 } 401 402 // JobsByScheduler returns an iterator over all the jobs with the specific 403 // scheduler type. 404 func (s *StateStore) JobsByScheduler(schedulerType string) (memdb.ResultIterator, error) { 405 txn := s.db.Txn(false) 406 407 // Return an iterator for jobs with the specific type. 408 iter, err := txn.Get("jobs", "type", schedulerType) 409 if err != nil { 410 return nil, err 411 } 412 return iter, nil 413 } 414 415 // UpsertEvaluation is used to upsert an evaluation 416 func (s *StateStore) UpsertEvals(index uint64, evals []*structs.Evaluation) error { 417 txn := s.db.Txn(true) 418 defer txn.Abort() 419 420 // Do a nested upsert 421 for _, eval := range evals { 422 if err := s.nestedUpsertEval(txn, index, eval); err != nil { 423 return err 424 } 425 } 426 427 txn.Commit() 428 return nil 429 } 430 431 // nestedUpsertEvaluation is used to nest an evaluation upsert within a transaction 432 func (s *StateStore) nestedUpsertEval(txn *memdb.Txn, index uint64, eval *structs.Evaluation) error { 433 // Lookup the evaluation 434 existing, err := txn.First("evals", "id", eval.ID) 435 if err != nil { 436 return fmt.Errorf("eval lookup failed: %v", err) 437 } 438 439 // Update the indexes 440 if existing != nil { 441 eval.CreateIndex = existing.(*structs.Evaluation).CreateIndex 442 eval.ModifyIndex = index 443 } else { 444 eval.CreateIndex = index 445 eval.ModifyIndex = index 446 } 447 448 // Insert the eval 449 if err := txn.Insert("evals", eval); err != nil { 450 return fmt.Errorf("eval insert failed: %v", err) 451 } 452 if err := txn.Insert("index", &IndexEntry{"evals", index}); err != nil { 453 return fmt.Errorf("index update failed: %v", err) 454 } 455 return nil 456 } 457 458 // DeleteEval is used to delete an evaluation 459 func (s *StateStore) DeleteEval(index uint64, evals []string, allocs []string) error { 460 txn := s.db.Txn(true) 461 defer txn.Abort() 462 nodes := make(map[string]struct{}) 463 464 for _, eval := range evals { 465 existing, err := txn.First("evals", "id", eval) 466 if err != nil { 467 return fmt.Errorf("eval lookup failed: %v", err) 468 } 469 if existing == nil { 470 continue 471 } 472 if err := txn.Delete("evals", existing); err != nil { 473 return fmt.Errorf("eval delete failed: %v", err) 474 } 475 } 476 477 for _, alloc := range allocs { 478 existing, err := txn.First("allocs", "id", alloc) 479 if err != nil { 480 return fmt.Errorf("alloc lookup failed: %v", err) 481 } 482 if existing == nil { 483 continue 484 } 485 nodes[existing.(*structs.Allocation).NodeID] = struct{}{} 486 if err := txn.Delete("allocs", existing); err != nil { 487 return fmt.Errorf("alloc delete failed: %v", err) 488 } 489 } 490 491 // Update the indexes 492 if err := txn.Insert("index", &IndexEntry{"evals", index}); err != nil { 493 return fmt.Errorf("index update failed: %v", err) 494 } 495 if err := txn.Insert("index", &IndexEntry{"allocs", index}); err != nil { 496 return fmt.Errorf("index update failed: %v", err) 497 } 498 txn.Defer(func() { s.watch.notifyAllocs(nodes) }) 499 txn.Commit() 500 return nil 501 } 502 503 // EvalByID is used to lookup an eval by its ID 504 func (s *StateStore) EvalByID(id string) (*structs.Evaluation, error) { 505 txn := s.db.Txn(false) 506 507 existing, err := txn.First("evals", "id", id) 508 if err != nil { 509 return nil, fmt.Errorf("eval lookup failed: %v", err) 510 } 511 512 if existing != nil { 513 return existing.(*structs.Evaluation), nil 514 } 515 return nil, nil 516 } 517 518 // EvalsByJob returns all the evaluations by job id 519 func (s *StateStore) EvalsByJob(jobID string) ([]*structs.Evaluation, error) { 520 txn := s.db.Txn(false) 521 522 // Get an iterator over the node allocations 523 iter, err := txn.Get("evals", "job", jobID) 524 if err != nil { 525 return nil, err 526 } 527 528 var out []*structs.Evaluation 529 for { 530 raw := iter.Next() 531 if raw == nil { 532 break 533 } 534 out = append(out, raw.(*structs.Evaluation)) 535 } 536 return out, nil 537 } 538 539 // Evals returns an iterator over all the evaluations 540 func (s *StateStore) Evals() (memdb.ResultIterator, error) { 541 txn := s.db.Txn(false) 542 543 // Walk the entire table 544 iter, err := txn.Get("evals", "id") 545 if err != nil { 546 return nil, err 547 } 548 return iter, nil 549 } 550 551 // UpdateAllocFromClient is used to update an allocation based on input 552 // from a client. While the schedulers are the authority on the allocation for 553 // most things, some updates are authoritative from the client. Specifically, 554 // the desired state comes from the schedulers, while the actual state comes 555 // from clients. 556 func (s *StateStore) UpdateAllocFromClient(index uint64, alloc *structs.Allocation) error { 557 txn := s.db.Txn(true) 558 defer txn.Abort() 559 560 // Look for existing alloc 561 existing, err := txn.First("allocs", "id", alloc.ID) 562 if err != nil { 563 return fmt.Errorf("alloc lookup failed: %v", err) 564 } 565 566 // Nothing to do if this does not exist 567 if existing == nil { 568 return nil 569 } 570 exist := existing.(*structs.Allocation) 571 572 // Copy everything from the existing allocation 573 copyAlloc := new(structs.Allocation) 574 *copyAlloc = *exist 575 576 // Pull in anything the client is the authority on 577 copyAlloc.ClientStatus = alloc.ClientStatus 578 copyAlloc.ClientDescription = alloc.ClientDescription 579 580 // Update the modify index 581 copyAlloc.ModifyIndex = index 582 583 // Update the allocation 584 if err := txn.Insert("allocs", copyAlloc); err != nil { 585 return fmt.Errorf("alloc insert failed: %v", err) 586 } 587 588 // Update the indexes 589 if err := txn.Insert("index", &IndexEntry{"allocs", index}); err != nil { 590 return fmt.Errorf("index update failed: %v", err) 591 } 592 593 nodes := map[string]struct{}{alloc.NodeID: struct{}{}} 594 txn.Defer(func() { s.watch.notifyAllocs(nodes) }) 595 txn.Commit() 596 return nil 597 } 598 599 // UpsertAllocs is used to evict a set of allocations 600 // and allocate new ones at the same time. 601 func (s *StateStore) UpsertAllocs(index uint64, allocs []*structs.Allocation) error { 602 txn := s.db.Txn(true) 603 defer txn.Abort() 604 nodes := make(map[string]struct{}) 605 606 // Handle the allocations 607 for _, alloc := range allocs { 608 existing, err := txn.First("allocs", "id", alloc.ID) 609 if err != nil { 610 return fmt.Errorf("alloc lookup failed: %v", err) 611 } 612 613 if existing == nil { 614 alloc.CreateIndex = index 615 alloc.ModifyIndex = index 616 } else { 617 exist := existing.(*structs.Allocation) 618 alloc.CreateIndex = exist.CreateIndex 619 alloc.ModifyIndex = index 620 alloc.ClientStatus = exist.ClientStatus 621 alloc.ClientDescription = exist.ClientDescription 622 } 623 nodes[alloc.NodeID] = struct{}{} 624 if err := txn.Insert("allocs", alloc); err != nil { 625 return fmt.Errorf("alloc insert failed: %v", err) 626 } 627 } 628 629 // Update the indexes 630 if err := txn.Insert("index", &IndexEntry{"allocs", index}); err != nil { 631 return fmt.Errorf("index update failed: %v", err) 632 } 633 634 txn.Defer(func() { s.watch.notifyAllocs(nodes) }) 635 txn.Commit() 636 return nil 637 } 638 639 // AllocByID is used to lookup an allocation by its ID 640 func (s *StateStore) AllocByID(id string) (*structs.Allocation, error) { 641 txn := s.db.Txn(false) 642 643 existing, err := txn.First("allocs", "id", id) 644 if err != nil { 645 return nil, fmt.Errorf("alloc lookup failed: %v", err) 646 } 647 648 if existing != nil { 649 return existing.(*structs.Allocation), nil 650 } 651 return nil, nil 652 } 653 654 // AllocsByNode returns all the allocations by node 655 func (s *StateStore) AllocsByNode(node string) ([]*structs.Allocation, error) { 656 txn := s.db.Txn(false) 657 658 // Get an iterator over the node allocations 659 iter, err := txn.Get("allocs", "node", node) 660 if err != nil { 661 return nil, err 662 } 663 664 var out []*structs.Allocation 665 for { 666 raw := iter.Next() 667 if raw == nil { 668 break 669 } 670 out = append(out, raw.(*structs.Allocation)) 671 } 672 return out, nil 673 } 674 675 // AllocsByJob returns all the allocations by job id 676 func (s *StateStore) AllocsByJob(jobID string) ([]*structs.Allocation, error) { 677 txn := s.db.Txn(false) 678 679 // Get an iterator over the node allocations 680 iter, err := txn.Get("allocs", "job", jobID) 681 if err != nil { 682 return nil, err 683 } 684 685 var out []*structs.Allocation 686 for { 687 raw := iter.Next() 688 if raw == nil { 689 break 690 } 691 out = append(out, raw.(*structs.Allocation)) 692 } 693 return out, nil 694 } 695 696 // AllocsByEval returns all the allocations by eval id 697 func (s *StateStore) AllocsByEval(evalID string) ([]*structs.Allocation, error) { 698 txn := s.db.Txn(false) 699 700 // Get an iterator over the eval allocations 701 iter, err := txn.Get("allocs", "eval", evalID) 702 if err != nil { 703 return nil, err 704 } 705 706 var out []*structs.Allocation 707 for { 708 raw := iter.Next() 709 if raw == nil { 710 break 711 } 712 out = append(out, raw.(*structs.Allocation)) 713 } 714 return out, nil 715 } 716 717 // Allocs returns an iterator over all the evaluations 718 func (s *StateStore) Allocs() (memdb.ResultIterator, error) { 719 txn := s.db.Txn(false) 720 721 // Walk the entire table 722 iter, err := txn.Get("allocs", "id") 723 if err != nil { 724 return nil, err 725 } 726 return iter, nil 727 } 728 729 // Index finds the matching index value 730 func (s *StateStore) Index(name string) (uint64, error) { 731 txn := s.db.Txn(false) 732 733 // Lookup the first matching index 734 out, err := txn.First("index", "id", name) 735 if err != nil { 736 return 0, err 737 } 738 if out == nil { 739 return 0, nil 740 } 741 return out.(*IndexEntry).Value, nil 742 } 743 744 // Indexes returns an iterator over all the indexes 745 func (s *StateStore) Indexes() (memdb.ResultIterator, error) { 746 txn := s.db.Txn(false) 747 748 // Walk the entire nodes table 749 iter, err := txn.Get("index", "id") 750 if err != nil { 751 return nil, err 752 } 753 return iter, nil 754 } 755 756 // NodeRestore is used to restore a node 757 func (r *StateRestore) NodeRestore(node *structs.Node) error { 758 if err := r.txn.Insert("nodes", node); err != nil { 759 return fmt.Errorf("node insert failed: %v", err) 760 } 761 return nil 762 } 763 764 // JobRestore is used to restore a job 765 func (r *StateRestore) JobRestore(job *structs.Job) error { 766 if err := r.txn.Insert("jobs", job); err != nil { 767 return fmt.Errorf("job insert failed: %v", err) 768 } 769 return nil 770 } 771 772 // EvalRestore is used to restore an evaluation 773 func (r *StateRestore) EvalRestore(eval *structs.Evaluation) error { 774 if err := r.txn.Insert("evals", eval); err != nil { 775 return fmt.Errorf("eval insert failed: %v", err) 776 } 777 return nil 778 } 779 780 // AllocRestore is used to restore an allocation 781 func (r *StateRestore) AllocRestore(alloc *structs.Allocation) error { 782 r.allocNodes[alloc.NodeID] = struct{}{} 783 if err := r.txn.Insert("allocs", alloc); err != nil { 784 return fmt.Errorf("alloc insert failed: %v", err) 785 } 786 return nil 787 } 788 789 // IndexRestore is used to restore an index 790 func (r *StateRestore) IndexRestore(idx *IndexEntry) error { 791 if err := r.txn.Insert("index", idx); err != nil { 792 return fmt.Errorf("index insert failed: %v", err) 793 } 794 return nil 795 }