mirror of
https://github.com/cloudnative-pg/plugin-barman-cloud.git
synced 2026-07-09 19:22:21 +02:00
Compare commits
5 Commits
6bfc0516df
...
b93c834a95
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b93c834a95 | ||
|
|
376e178ab5 | ||
|
|
25d72ce0a3 | ||
|
|
6a55a361a3 | ||
|
|
62b579101f |
6
go.mod
6
go.mod
@ -7,7 +7,7 @@ toolchain go1.26.1
|
||||
require (
|
||||
github.com/cert-manager/cert-manager v1.19.4
|
||||
github.com/cloudnative-pg/api v1.28.1
|
||||
github.com/cloudnative-pg/barman-cloud v0.4.1-0.20260302035520-53b8620f1bd6
|
||||
github.com/cloudnative-pg/barman-cloud v0.5.0
|
||||
github.com/cloudnative-pg/cloudnative-pg v1.28.1
|
||||
github.com/cloudnative-pg/cnpg-i v0.3.1
|
||||
github.com/cloudnative-pg/cnpg-i-machinery v0.4.2
|
||||
@ -16,7 +16,7 @@ require (
|
||||
github.com/onsi/gomega v1.39.1
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/viper v1.21.0
|
||||
google.golang.org/grpc v1.79.2
|
||||
google.golang.org/grpc v1.79.3
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.35.2
|
||||
k8s.io/apiextensions-apiserver v0.35.2
|
||||
@ -117,7 +117,7 @@ require (
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/term v0.39.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
|
||||
12
go.sum
12
go.sum
@ -18,8 +18,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudnative-pg/api v1.28.1 h1:hp/TDgR+RrTa4EMp/pAKZia+kZ52i8ZPLG+JG10eeVU=
|
||||
github.com/cloudnative-pg/api v1.28.1/go.mod h1:J0RTwrOYxQP17N1YMrDxcUmr/0wPYHQW179P7ibkVsM=
|
||||
github.com/cloudnative-pg/barman-cloud v0.4.1-0.20260302035520-53b8620f1bd6 h1:BpKKrciaTMC01jMgwWOosC/S8pvDFCxZdJx2ahzksIQ=
|
||||
github.com/cloudnative-pg/barman-cloud v0.4.1-0.20260302035520-53b8620f1bd6/go.mod h1:N/ax2XSnj67scfJ9iFXKxCRMLFghD9B2XX5/SrRsPys=
|
||||
github.com/cloudnative-pg/barman-cloud v0.5.0 h1:DykSaX4o7ee2vyu5FQoG1RJsntHd+EIttIKZbJPlB1Q=
|
||||
github.com/cloudnative-pg/barman-cloud v0.5.0/go.mod h1:SO2HzLa+GWlSIpGyxnISoJAFPIcaa/qDa33Bb3jefac=
|
||||
github.com/cloudnative-pg/cloudnative-pg v1.28.1 h1:HdOUWgFhta558uHfXeO/199qCApxaj5yi05x6nWNmgs=
|
||||
github.com/cloudnative-pg/cloudnative-pg v1.28.1/go.mod h1:yhRa4GqJAjNd0tT9AiRgk1KdqLhMjo/JmGGoASRl2CU=
|
||||
github.com/cloudnative-pg/cnpg-i v0.3.1 h1:fKj8NoToWI11HUL2UWYJBpkVzmaTvbs3kDMo7wQF8RU=
|
||||
@ -277,8 +277,8 @@ golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
@ -295,8 +295,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
|
||||
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@ -36,6 +36,9 @@ import (
|
||||
// DefaultTTLSeconds is the default TTL in seconds of cache entries
|
||||
const DefaultTTLSeconds = 10
|
||||
|
||||
// DefaultCleanupIntervalSeconds is the default interval in seconds for cache cleanup
|
||||
const DefaultCleanupIntervalSeconds = 30
|
||||
|
||||
type cachedEntry struct {
|
||||
entry client.Object
|
||||
fetchUnixTime int64
|
||||
@ -51,16 +54,28 @@ type ExtendedClient struct {
|
||||
client.Client
|
||||
cachedObjects []cachedEntry
|
||||
mux *sync.Mutex
|
||||
cleanupInterval time.Duration
|
||||
cleanupDone chan struct{} // Signals when cleanup routine exits
|
||||
}
|
||||
|
||||
// NewExtendedClient returns an extended client capable of caching secrets on the 'Get' operation
|
||||
// NewExtendedClient returns an extended client capable of caching secrets on the 'Get' operation.
|
||||
// It starts a background goroutine that periodically cleans up expired cache entries.
|
||||
// The cleanup routine will stop when the provided context is cancelled.
|
||||
func NewExtendedClient(
|
||||
ctx context.Context,
|
||||
baseClient client.Client,
|
||||
) client.Client {
|
||||
return &ExtendedClient{
|
||||
ec := &ExtendedClient{
|
||||
Client: baseClient,
|
||||
mux: &sync.Mutex{},
|
||||
cleanupInterval: DefaultCleanupIntervalSeconds * time.Second,
|
||||
cleanupDone: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Start the background cleanup routine
|
||||
go ec.startCleanupRoutine(ctx)
|
||||
|
||||
return ec
|
||||
}
|
||||
|
||||
func (e *ExtendedClient) isObjectCached(obj client.Object) bool {
|
||||
@ -208,3 +223,55 @@ func (e *ExtendedClient) Patch(
|
||||
|
||||
return e.Client.Patch(ctx, obj, patch, opts...)
|
||||
}
|
||||
|
||||
// startCleanupRoutine periodically removes expired entries from the cache.
|
||||
// It runs until the context is cancelled.
|
||||
func (e *ExtendedClient) startCleanupRoutine(ctx context.Context) {
|
||||
defer close(e.cleanupDone)
|
||||
contextLogger := log.FromContext(ctx).WithName("extended_client_cleanup")
|
||||
ticker := time.NewTicker(e.cleanupInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
contextLogger.Debug("stopping cache cleanup routine")
|
||||
return
|
||||
case <-ticker.C:
|
||||
// Check context before cleanup to avoid unnecessary work during shutdown
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
e.cleanupExpiredEntries(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupExpiredEntries removes all expired entries from the cache.
|
||||
func (e *ExtendedClient) cleanupExpiredEntries(ctx context.Context) {
|
||||
contextLogger := log.FromContext(ctx).WithName("extended_client_cleanup")
|
||||
|
||||
e.mux.Lock()
|
||||
defer e.mux.Unlock()
|
||||
|
||||
initialCount := len(e.cachedObjects)
|
||||
if initialCount == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Create a new slice with only non-expired entries
|
||||
validEntries := make([]cachedEntry, 0, initialCount)
|
||||
for _, entry := range e.cachedObjects {
|
||||
if !entry.isExpired() {
|
||||
validEntries = append(validEntries, entry)
|
||||
}
|
||||
}
|
||||
|
||||
removedCount := initialCount - len(validEntries)
|
||||
if removedCount > 0 {
|
||||
e.cachedObjects = validEntries
|
||||
contextLogger.Debug("cleaned up expired cache entries",
|
||||
"removedCount", removedCount,
|
||||
"remainingCount", len(validEntries))
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ SPDX-License-Identifier: Apache-2.0
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@ -59,6 +60,7 @@ var _ = Describe("ExtendedClient Get", func() {
|
||||
extendedClient *ExtendedClient
|
||||
secretInClient *corev1.Secret
|
||||
objectStore *barmancloudv1.ObjectStore
|
||||
cancelCtx context.CancelFunc
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
@ -79,7 +81,14 @@ var _ = Describe("ExtendedClient Get", func() {
|
||||
baseClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(secretInClient, objectStore).Build()
|
||||
extendedClient = NewExtendedClient(baseClient).(*ExtendedClient)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancelCtx = cancel
|
||||
extendedClient = NewExtendedClient(ctx, baseClient).(*ExtendedClient)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// Cancel the context to stop the cleanup routine
|
||||
cancelCtx()
|
||||
})
|
||||
|
||||
It("returns secret from cache if not expired", func(ctx SpecContext) {
|
||||
@ -164,3 +173,141 @@ var _ = Describe("ExtendedClient Get", func() {
|
||||
Expect(objectStore.GetResourceVersion()).To(Equal("from cache"))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("ExtendedClient Cache Cleanup", func() {
|
||||
var (
|
||||
extendedClient *ExtendedClient
|
||||
cancelCtx context.CancelFunc
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
baseClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
Build()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancelCtx = cancel
|
||||
extendedClient = NewExtendedClient(ctx, baseClient).(*ExtendedClient)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
cancelCtx()
|
||||
})
|
||||
|
||||
It("cleans up expired entries", func(ctx SpecContext) {
|
||||
// Add some expired entries
|
||||
expiredSecret1 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "expired-secret-1",
|
||||
},
|
||||
}
|
||||
expiredSecret2 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "expired-secret-2",
|
||||
},
|
||||
}
|
||||
validSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "valid-secret",
|
||||
},
|
||||
}
|
||||
|
||||
// Add expired entries (2 minutes ago)
|
||||
addToCache(extendedClient, expiredSecret1, time.Now().Add(-2*time.Minute).Unix())
|
||||
addToCache(extendedClient, expiredSecret2, time.Now().Add(-2*time.Minute).Unix())
|
||||
// Add valid entry (just now)
|
||||
addToCache(extendedClient, validSecret, time.Now().Unix())
|
||||
|
||||
Expect(extendedClient.cachedObjects).To(HaveLen(3))
|
||||
|
||||
// Trigger cleanup
|
||||
extendedClient.cleanupExpiredEntries(ctx)
|
||||
|
||||
// Only the valid entry should remain
|
||||
Expect(extendedClient.cachedObjects).To(HaveLen(1))
|
||||
Expect(extendedClient.cachedObjects[0].entry.GetName()).To(Equal("valid-secret"))
|
||||
})
|
||||
|
||||
It("does nothing when all entries are valid", func(ctx SpecContext) {
|
||||
validSecret1 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "valid-secret-1",
|
||||
},
|
||||
}
|
||||
validSecret2 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "valid-secret-2",
|
||||
},
|
||||
}
|
||||
|
||||
addToCache(extendedClient, validSecret1, time.Now().Unix())
|
||||
addToCache(extendedClient, validSecret2, time.Now().Unix())
|
||||
|
||||
Expect(extendedClient.cachedObjects).To(HaveLen(2))
|
||||
|
||||
// Trigger cleanup
|
||||
extendedClient.cleanupExpiredEntries(ctx)
|
||||
|
||||
// Both entries should remain
|
||||
Expect(extendedClient.cachedObjects).To(HaveLen(2))
|
||||
})
|
||||
|
||||
It("does nothing when cache is empty", func(ctx SpecContext) {
|
||||
Expect(extendedClient.cachedObjects).To(BeEmpty())
|
||||
|
||||
// Trigger cleanup
|
||||
extendedClient.cleanupExpiredEntries(ctx)
|
||||
|
||||
Expect(extendedClient.cachedObjects).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("removes all entries when all are expired", func(ctx SpecContext) {
|
||||
expiredSecret1 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "expired-secret-1",
|
||||
},
|
||||
}
|
||||
expiredSecret2 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Name: "expired-secret-2",
|
||||
},
|
||||
}
|
||||
|
||||
addToCache(extendedClient, expiredSecret1, time.Now().Add(-2*time.Minute).Unix())
|
||||
addToCache(extendedClient, expiredSecret2, time.Now().Add(-2*time.Minute).Unix())
|
||||
|
||||
Expect(extendedClient.cachedObjects).To(HaveLen(2))
|
||||
|
||||
// Trigger cleanup
|
||||
extendedClient.cleanupExpiredEntries(ctx)
|
||||
|
||||
Expect(extendedClient.cachedObjects).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("stops cleanup routine when context is cancelled", func() {
|
||||
// Create a new client with a short cleanup interval for testing
|
||||
baseClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
Build()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ec := NewExtendedClient(ctx, baseClient).(*ExtendedClient)
|
||||
ec.cleanupInterval = 10 * time.Millisecond
|
||||
|
||||
// Cancel the context immediately
|
||||
cancel()
|
||||
|
||||
// Verify the cleanup routine actually stops by waiting for the done channel
|
||||
select {
|
||||
case <-ec.cleanupDone:
|
||||
// Success: cleanup routine exited as expected
|
||||
case <-time.After(1 * time.Second):
|
||||
Fail("cleanup routine did not stop within timeout")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -84,7 +84,7 @@ func Start(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
customCacheClient := extendedclient.NewExtendedClient(mgr.GetClient())
|
||||
customCacheClient := extendedclient.NewExtendedClient(ctx, mgr.GetClient())
|
||||
|
||||
if err := mgr.Add(&CNPGI{
|
||||
Client: customCacheClient,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user