Skip to content

Commit 976b57e

Browse files
committed
fix: improve object cache reliability
* make sure objects expire after DefaultTTLSeconds * make cached objects have GKV information * fix cache retrieval and removal logic Closes #502 Signed-off-by: Marco Nenciarini <[email protected]>
1 parent 2dc29a5 commit 976b57e

File tree

2 files changed

+70
-24
lines changed

2 files changed

+70
-24
lines changed

internal/cnpgi/instance/internal/client/client.go

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ const DefaultTTLSeconds = 10
2121
type cachedEntry struct {
2222
entry client.Object
2323
fetchUnixTime int64
24-
ttl time.Duration
24+
ttlSeconds int64
2525
}
2626

2727
func (e *cachedEntry) isExpired() bool {
28-
return time.Now().Unix()-e.fetchUnixTime > int64(e.ttl)
28+
return time.Now().Unix()-e.fetchUnixTime > e.ttlSeconds
2929
}
3030

3131
// ExtendedClient is an extended client that is capable of caching multiple secrets without relying on informers
@@ -71,6 +71,28 @@ func (e *ExtendedClient) Get(
7171
return e.Client.Get(ctx, key, obj, opts...)
7272
}
7373

74+
// addTypeInformationToObject adds TypeMeta information to a client.Object based upon the client Scheme
75+
// inspired by: https://github.com/kubernetes/cli-runtime/blob/v0.19.2/pkg/printers/typesetter.go#L41
76+
func (e *ExtendedClient) addTypeInformationToObject(obj client.Object) error {
77+
gvks, _, err := e.Client.Scheme().ObjectKinds(obj)
78+
if err != nil {
79+
return fmt.Errorf("missing apiVersion or kind and cannot assign it; %w", err)
80+
}
81+
82+
for _, gvk := range gvks {
83+
if len(gvk.Kind) == 0 {
84+
continue
85+
}
86+
if len(gvk.Version) == 0 || gvk.Version == runtime.APIVersionInternal {
87+
continue
88+
}
89+
obj.GetObjectKind().SetGroupVersionKind(gvk)
90+
break
91+
}
92+
93+
return nil
94+
}
95+
7496
func (e *ExtendedClient) getCachedObject(
7597
ctx context.Context,
7698
key client.ObjectKey,
@@ -81,6 +103,12 @@ func (e *ExtendedClient) getCachedObject(
81103
WithName("extended_client").
82104
WithValues("name", key.Name, "namespace", key.Namespace)
83105

106+
// Make sure the object has GVK information
107+
// This is needed to compare the object type with the cached one
108+
if err := e.addTypeInformationToObject(obj); err != nil {
109+
return fmt.Errorf("cannot add type metadata to object of type %T: %w", obj, err)
110+
}
111+
84112
contextLogger.Trace("locking the cache")
85113
e.mux.Lock()
86114
defer e.mux.Unlock()
@@ -120,9 +148,16 @@ func (e *ExtendedClient) getCachedObject(
120148
return err
121149
}
122150

151+
// Populate the GKV information again, as the client.Get() may have
152+
// returned an object without this information set
153+
if err := e.addTypeInformationToObject(obj); err != nil {
154+
return fmt.Errorf("cannot add type metadata to object of type %T: %w", obj, err)
155+
}
156+
123157
cs := cachedEntry{
124-
entry: obj.(runtime.Object).DeepCopyObject().(client.Object),
158+
entry: obj.DeepCopyObject().(client.Object),
125159
fetchUnixTime: time.Now().Unix(),
160+
ttlSeconds: DefaultTTLSeconds,
126161
}
127162

128163
contextLogger.Debug("setting object in the cache")
@@ -143,7 +178,7 @@ func (e *ExtendedClient) removeObject(object client.Object) {
143178
for i, cache := range e.cachedObjects {
144179
if cache.entry.GetNamespace() == object.GetNamespace() &&
145180
cache.entry.GetName() == object.GetName() &&
146-
cache.entry.GetObjectKind().GroupVersionKind() != object.GetObjectKind().GroupVersionKind() {
181+
cache.entry.GetObjectKind().GroupVersionKind() == object.GetObjectKind().GroupVersionKind() {
147182
e.cachedObjects = append(e.cachedObjects[:i], e.cachedObjects[i+1:]...)
148183
return
149184
}

internal/cnpgi/instance/internal/client/client_test.go

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"sigs.k8s.io/controller-runtime/pkg/client"
1010
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1111

12-
v1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
12+
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
1313

1414
. "github.com/onsi/ginkgo/v2"
1515
. "github.com/onsi/gomega"
@@ -20,16 +20,28 @@ var scheme = buildScheme()
2020
func buildScheme() *runtime.Scheme {
2121
scheme := runtime.NewScheme()
2222
_ = corev1.AddToScheme(scheme)
23-
_ = v1.AddToScheme(scheme)
23+
_ = barmancloudv1.AddToScheme(scheme)
2424

2525
return scheme
2626
}
2727

28+
func addToCache(c *ExtendedClient, obj client.Object, fetchUnixTime int64) {
29+
ce := cachedEntry{
30+
entry: obj.DeepCopyObject().(client.Object),
31+
fetchUnixTime: fetchUnixTime,
32+
ttlSeconds: DefaultTTLSeconds,
33+
}
34+
ce.entry.SetResourceVersion("from cache")
35+
err := c.addTypeInformationToObject(ce.entry)
36+
Expect(err).ToNot(HaveOccurred())
37+
c.cachedObjects = append(c.cachedObjects, ce)
38+
}
39+
2840
var _ = Describe("ExtendedClient Get", func() {
2941
var (
3042
extendedClient *ExtendedClient
3143
secretInClient *corev1.Secret
32-
objectStore *v1.ObjectStore
44+
objectStore *barmancloudv1.ObjectStore
3345
)
3446

3547
BeforeEach(func() {
@@ -39,12 +51,12 @@ var _ = Describe("ExtendedClient Get", func() {
3951
Name: "test-secret",
4052
},
4153
}
42-
objectStore = &v1.ObjectStore{
54+
objectStore = &barmancloudv1.ObjectStore{
4355
ObjectMeta: metav1.ObjectMeta{
4456
Namespace: "default",
4557
Name: "test-object-store",
4658
},
47-
Spec: v1.ObjectStoreSpec{},
59+
Spec: barmancloudv1.ObjectStoreSpec{},
4860
}
4961

5062
baseClient := fake.NewClientBuilder().
@@ -61,35 +73,34 @@ var _ = Describe("ExtendedClient Get", func() {
6173
},
6274
}
6375

64-
// manually add the secret to the cache, this is not present in the fake client so we are sure it is from the
65-
// cache
66-
extendedClient.cachedObjects = []cachedEntry{
67-
{
68-
entry: secretNotInClient,
69-
fetchUnixTime: time.Now().Unix(),
70-
},
71-
}
76+
// manually add the secret to the cache, this is not present in the fake client,
77+
// so we are sure it is from the cache
78+
addToCache(extendedClient, secretNotInClient, time.Now().Unix())
7279

7380
err := extendedClient.Get(ctx, client.ObjectKeyFromObject(secretNotInClient), secretInClient)
7481
Expect(err).NotTo(HaveOccurred())
75-
Expect(secretNotInClient).To(Equal(extendedClient.cachedObjects[0].entry))
82+
Expect(secretInClient).To(Equal(extendedClient.cachedObjects[0].entry))
83+
Expect(secretInClient.GetResourceVersion()).To(Equal("from cache"))
7684
})
7785

7886
It("fetches secret from base client if cache is expired", func(ctx SpecContext) {
79-
extendedClient.cachedObjects = []cachedEntry{
80-
{
81-
entry: secretInClient.DeepCopy(),
82-
fetchUnixTime: time.Now().Add(-2 * time.Minute).Unix(),
83-
},
84-
}
87+
addToCache(extendedClient, secretInClient, time.Now().Add(-2*time.Minute).Unix())
8588

8689
err := extendedClient.Get(ctx, client.ObjectKeyFromObject(secretInClient), secretInClient)
8790
Expect(err).NotTo(HaveOccurred())
91+
Expect(secretInClient.GetResourceVersion()).NotTo(Equal("from cache"))
92+
93+
// the cache is updated with the new value
94+
Expect(extendedClient.cachedObjects).To(HaveLen(1))
95+
Expect(extendedClient.cachedObjects[0].entry.GetResourceVersion()).NotTo(Equal("from cache"))
8896
})
8997

9098
It("fetches secret from base client if not in cache", func(ctx SpecContext) {
9199
err := extendedClient.Get(ctx, client.ObjectKeyFromObject(secretInClient), secretInClient)
92100
Expect(err).NotTo(HaveOccurred())
101+
102+
// the cache is updated with the new value
103+
Expect(extendedClient.cachedObjects).To(HaveLen(1))
93104
})
94105

95106
It("does not cache non-secret objects", func(ctx SpecContext) {

0 commit comments

Comments
 (0)