github.com/LINBIT/golinstor@v0.52.0/cache/cache.go (about) 1 // Package cache 2 // 3 // Implement client side caching for client.Client. This is useful for burst-happy applications that will try to query 4 // a lot of the same information in small chunks. 5 // 6 // For example, an application could try to check the state of nodes, but do so using one request per node. This is 7 // obviously not ideal in larger cluster, where it would be more efficient to request the state of all nodes at once. 8 // Depending on the application, this may not be possible, however. 9 // 10 // This package contains ready-to-use client side caches with configurable duration and automatic invalidation under the 11 // assumption that modifications are made from the same client. 12 package cache 13 14 import ( 15 "sync" 16 "time" 17 18 "github.com/LINBIT/golinstor/client" 19 ) 20 21 var ( 22 yes = true 23 cacheOpt = &client.ListOpts{Cached: &yes} 24 ) 25 26 type Cache interface { 27 apply(c *client.Client) 28 } 29 30 // WithCaches sets up the given caches on the client.Client. 31 func WithCaches(caches ...Cache) client.Option { 32 return func(cl *client.Client) error { 33 for _, ca := range caches { 34 ca.apply(cl) 35 } 36 37 return nil 38 } 39 } 40 41 type cache struct { 42 mu sync.Mutex 43 lastUpdate time.Time 44 cache any 45 } 46 47 // Invalidate forcefully resets the cache. 48 // The next call to Get will always invoke the provided function. 49 func (c *cache) Invalidate() { 50 c.mu.Lock() 51 c.lastUpdate = time.Time{} 52 c.cache = nil 53 c.mu.Unlock() 54 } 55 56 // Get returns a cached response or the result of the provided update function. 57 // 58 // If the cache is current, it will return the last successful cached response. 59 // If the cache is outdated, it will run the provided function to retrieve a result. A successful response 60 // is cached for later use. 61 func (c *cache) Get(timeout time.Duration, updateFunc func() (any, error)) (any, error) { 62 c.mu.Lock() 63 defer c.mu.Unlock() 64 65 now := time.Now() 66 67 if timeout != 0 && c.lastUpdate.Add(timeout).Before(now) { 68 result, err := updateFunc() 69 if err != nil { 70 return nil, err 71 } 72 73 c.cache = result 74 c.lastUpdate = now 75 } 76 77 return c.cache, nil 78 } 79 80 // filterNodeAndPoolOpts filters generic items based on the provided client.ListOpts 81 // This tries to mimic the behaviour of LINSTOR when using the node and storage pool query parameters. 82 func filterNodeAndPoolOpts[T any](items []T, getNodeAndPoolNames func(*T) ([]string, []string), opts ...*client.ListOpts) []T { 83 filterNames := make(map[string]struct{}) 84 filterPools := make(map[string]struct{}) 85 86 for _, o := range opts { 87 for _, n := range o.Node { 88 filterNames[n] = struct{}{} 89 } 90 91 for _, sp := range o.StoragePool { 92 filterPools[sp] = struct{}{} 93 } 94 } 95 96 var result []T 97 98 outer: 99 for i := range items { 100 nodes, pools := getNodeAndPoolNames(&items[i]) 101 102 if len(filterNames) > 0 { 103 for _, nodeName := range nodes { 104 if _, ok := filterNames[nodeName]; !ok { 105 continue outer 106 } 107 } 108 } 109 110 if len(filterPools) > 0 { 111 for _, poolName := range pools { 112 if _, ok := filterPools[poolName]; !ok { 113 continue outer 114 } 115 } 116 } 117 118 result = append(result, items[i]) 119 } 120 121 return result 122 }