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  }