summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMaisem Ali <maisem@tailscale.com>2023-03-23 17:10:09 -0700
committerMaisem Ali <maisem@tailscale.com>2023-03-23 17:10:09 -0700
commit108933c04dc16bd31ed0542d65ccbe445b6d4252 (patch)
tree41c74ed4139b747d096bf154eb323f7a58aa35e5
parent0e203e414f6e2c590db864476eafae35449a54bb (diff)
downloadtailscale-maisem/k8s-cache.tar.xz
tailscale-maisem/k8s-cache.zip
ipn/store/kubestore: cache state in memorymaisem/k8s-cache
Fixes #7671 Signed-off-by: Maisem Ali <maisem@tailscale.com>
-rw-r--r--ipn/store/kubestore/store_kube.go42
-rw-r--r--ipn/store/mem/store_mem.go16
2 files changed, 44 insertions, 14 deletions
diff --git a/ipn/store/kubestore/store_kube.go b/ipn/store/kubestore/store_kube.go
index b82d041ff..57df65c3e 100644
--- a/ipn/store/kubestore/store_kube.go
+++ b/ipn/store/kubestore/store_kube.go
@@ -11,6 +11,7 @@ import (
"time"
"tailscale.com/ipn"
+ "tailscale.com/ipn/store/mem"
"tailscale.com/kube"
"tailscale.com/types/logger"
)
@@ -19,6 +20,8 @@ import (
type Store struct {
client *kube.Client
secretName string
+
+ memory mem.Store
}
// New returns a new Store that persists to the named secret.
@@ -27,31 +30,37 @@ func New(_ logger.Logf, secretName string) (*Store, error) {
if err != nil {
return nil, err
}
- return &Store{
+ s := &Store{
client: c,
secretName: secretName,
- }, nil
+ }
+ // Hydrate cache with the potentially current state
+ if err := s.loadState(); err != nil {
+ return nil, err
+ }
+ return s, nil
}
-func (s *Store) String() string { return "kube.Store" }
-
-// ReadState implements the StateStore interface.
-func (s *Store) ReadState(id ipn.StateKey) ([]byte, error) {
+func (s *Store) loadState() error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
secret, err := s.client.GetSecret(ctx, s.secretName)
if err != nil {
if st, ok := err.(*kube.Status); ok && st.Code == 404 {
- return nil, ipn.ErrStateNotExist
+ return nil
}
- return nil, err
- }
- b, ok := secret.Data[sanitizeKey(id)]
- if !ok {
- return nil, ipn.ErrStateNotExist
+ return err
}
- return b, nil
+ s.memory.LoadFromMap(secret.Data)
+ return nil
+}
+
+func (s *Store) String() string { return "kube.Store" }
+
+// ReadState implements the StateStore interface.
+func (s *Store) ReadState(id ipn.StateKey) ([]byte, error) {
+ return s.memory.ReadState(ipn.StateKey(sanitizeKey(id)))
}
func sanitizeKey(k ipn.StateKey) string {
@@ -66,7 +75,12 @@ func sanitizeKey(k ipn.StateKey) string {
}
// WriteState implements the StateStore interface.
-func (s *Store) WriteState(id ipn.StateKey, bs []byte) error {
+func (s *Store) WriteState(id ipn.StateKey, bs []byte) (err error) {
+ defer func() {
+ if err == nil {
+ s.memory.WriteState(id, bs)
+ }
+ }()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
diff --git a/ipn/store/mem/store_mem.go b/ipn/store/mem/store_mem.go
index 8835374b0..8e75b9f6c 100644
--- a/ipn/store/mem/store_mem.go
+++ b/ipn/store/mem/store_mem.go
@@ -9,6 +9,7 @@ import (
"encoding/json"
"sync"
+ "golang.org/x/exp/maps"
"tailscale.com/ipn"
"tailscale.com/types/logger"
)
@@ -27,6 +28,7 @@ type Store struct {
func (s *Store) String() string { return "mem.Store" }
// ReadState implements the StateStore interface.
+// It returns ipn.ErrStateNotExist if the state does not exist.
func (s *Store) ReadState(id ipn.StateKey) ([]byte, error) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -38,6 +40,7 @@ func (s *Store) ReadState(id ipn.StateKey) ([]byte, error) {
}
// WriteState implements the StateStore interface.
+// It never returns an error.
func (s *Store) WriteState(id ipn.StateKey, bs []byte) error {
s.mu.Lock()
defer s.mu.Unlock()
@@ -48,6 +51,19 @@ func (s *Store) WriteState(id ipn.StateKey, bs []byte) error {
return nil
}
+// LoadFromMap loads the in-memory cache from the provided map.
+// Any existing content is cleared, and the provided map is
+// copied into the cache.
+func (s *Store) LoadFromMap(m map[string][]byte) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ maps.Clear(s.cache)
+ for k, v := range m {
+ s.cache[ipn.StateKey(k)] = v
+ }
+ return
+}
+
// LoadFromJSON attempts to unmarshal json content into the
// in-memory cache.
func (s *Store) LoadFromJSON(data []byte) error {