github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/serviceregistration/checks/checkstore/shim.go (about) 1 package checkstore 2 3 import ( 4 "sync" 5 6 "github.com/hashicorp/go-hclog" 7 "github.com/hashicorp/nomad/client/serviceregistration/checks" 8 "github.com/hashicorp/nomad/client/state" 9 "github.com/hashicorp/nomad/nomad/structs" 10 "golang.org/x/exp/maps" 11 "golang.org/x/exp/slices" 12 ) 13 14 // A Shim is used to track the latest check status information, one layer above 15 // the client persistent store so we can do efficient indexing, etc. 16 type Shim interface { 17 // Set the latest result for a specific check. 18 Set(allocID string, result *structs.CheckQueryResult) error 19 20 // List the latest results for a specific allocation. 21 List(allocID string) map[structs.CheckID]*structs.CheckQueryResult 22 23 // Difference returns the set of IDs being stored that are not in ids. 24 Difference(allocID string, ids []structs.CheckID) []structs.CheckID 25 26 // Remove will remove ids from the cache and persistent store. 27 Remove(allocID string, ids []structs.CheckID) error 28 29 // Purge results for a specific allocation. 30 Purge(allocID string) error 31 32 // Snapshot returns a copy of the current status of every check indexed by 33 // checkID, for use by CheckWatcher. 34 Snapshot() map[string]string 35 } 36 37 type shim struct { 38 log hclog.Logger 39 40 db state.StateDB 41 42 lock sync.RWMutex 43 current checks.ClientResults 44 } 45 46 // NewStore creates a new store. 47 func NewStore(log hclog.Logger, db state.StateDB) Shim { 48 s := &shim{ 49 log: log.Named("check_store"), 50 db: db, 51 current: make(checks.ClientResults), 52 } 53 s.restore() 54 return s 55 } 56 57 func (s *shim) restore() { 58 s.lock.Lock() 59 defer s.lock.Unlock() 60 61 results, err := s.db.GetCheckResults() 62 if err != nil { 63 s.log.Error("failed to restore health check results", "error", err) 64 // may as well continue and let the check observers repopulate - maybe 65 // the persistent storage error was transitory 66 return 67 } 68 69 for id, m := range results { 70 s.current[id] = maps.Clone(m) 71 } 72 } 73 74 func (s *shim) Set(allocID string, qr *structs.CheckQueryResult) error { 75 s.lock.Lock() 76 defer s.lock.Unlock() 77 78 if _, exists := s.current[allocID]; !exists { 79 s.current[allocID] = make(map[structs.CheckID]*structs.CheckQueryResult) 80 } 81 82 // lookup existing result 83 previous, exists := s.current[allocID][qr.ID] 84 85 // only insert a stub if no result exists yet 86 if qr.Status == structs.CheckPending && exists { 87 return nil 88 } 89 90 s.log.Trace("setting check status", "alloc_id", allocID, "check_id", qr.ID, "status", qr.Status) 91 92 // always keep in-memory shim up to date with latest result 93 s.current[allocID][qr.ID] = qr 94 95 // only update persistent store if status changes (optimization) 96 // on Client restart restored check results may be outdated but the status 97 // is the same as the most recent result 98 if !exists || previous.Status != qr.Status { 99 if err := s.db.PutCheckResult(allocID, qr); err != nil { 100 s.log.Error("failed to set check status", "alloc_id", allocID, "check_id", qr.ID, "error", err) 101 return err 102 } 103 } 104 105 return nil 106 } 107 108 func (s *shim) List(allocID string) map[structs.CheckID]*structs.CheckQueryResult { 109 s.lock.RLock() 110 defer s.lock.RUnlock() 111 112 m, exists := s.current[allocID] 113 if !exists { 114 return nil 115 } 116 117 return maps.Clone(m) 118 } 119 120 func (s *shim) Purge(allocID string) error { 121 s.lock.Lock() 122 defer s.lock.Unlock() 123 124 // remove from our map 125 delete(s.current, allocID) 126 127 // remove from persistent store 128 return s.db.PurgeCheckResults(allocID) 129 } 130 131 func (s *shim) Remove(allocID string, ids []structs.CheckID) error { 132 s.lock.Lock() 133 defer s.lock.Unlock() 134 135 // remove from cache 136 for _, id := range ids { 137 delete(s.current[allocID], id) 138 } 139 140 // remove from persistent store 141 return s.db.DeleteCheckResults(allocID, ids) 142 } 143 144 func (s *shim) Difference(allocID string, ids []structs.CheckID) []structs.CheckID { 145 s.lock.Lock() 146 defer s.lock.Unlock() 147 148 var remove []structs.CheckID 149 for id := range s.current[allocID] { 150 if !slices.Contains(ids, id) { 151 remove = append(remove, id) 152 } 153 } 154 155 return remove 156 } 157 158 func (s *shim) Snapshot() map[string]string { 159 s.lock.RLock() 160 defer s.lock.RUnlock() 161 162 result := make(map[string]string) 163 for _, m := range s.current { 164 for checkID, status := range m { 165 result[string(checkID)] = string(status.Status) 166 } 167 } 168 return result 169 }