feat: separate recovery and cluster object store (#76)

Signed-off-by: Leonardo Cecchi <leonardo.cecchi@enterprisedb.com>
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
Co-authored-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
This commit is contained in:
Leonardo Cecchi 2024-12-05 12:05:14 +01:00 committed by GitHub
parent af60a15837
commit e30edd2318
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 314 additions and 192 deletions

View File

@ -0,0 +1,27 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-restore
spec:
instances: 3
imagePullPolicy: IfNotPresent
bootstrap:
recovery:
source: source
plugins:
- name: barman-cloud.cloudnative-pg.io
parameters:
barmanObjectName: minio-store-bis
externalClusters:
- name: source
plugin:
name: barman-cloud.cloudnative-pg.io
parameters:
barmanObjectName: minio-store
serverName: cluster-example
storage:
size: 1Gi

View File

@ -36,12 +36,16 @@ func NewCmd() *cobra.Command {
} }
_ = viper.BindEnv("namespace", "NAMESPACE") _ = viper.BindEnv("namespace", "NAMESPACE")
_ = viper.BindEnv("barman-object-name", "BARMAN_OBJECT_NAME")
_ = viper.BindEnv("cluster-name", "CLUSTER_NAME") _ = viper.BindEnv("cluster-name", "CLUSTER_NAME")
_ = viper.BindEnv("pod-name", "POD_NAME") _ = viper.BindEnv("pod-name", "POD_NAME")
_ = viper.BindEnv("pgdata", "PGDATA") _ = viper.BindEnv("pgdata", "PGDATA")
_ = viper.BindEnv("spool-directory", "SPOOL_DIRECTORY") _ = viper.BindEnv("spool-directory", "SPOOL_DIRECTORY")
_ = viper.BindEnv("barman-object-name", "BARMAN_OBJECT_NAME")
_ = viper.BindEnv("server-name", "SERVER_NAME") _ = viper.BindEnv("server-name", "SERVER_NAME")
_ = viper.BindEnv("recovery-barman-object-name", "RECOVERY_BARMAN_OBJECT_NAME")
_ = viper.BindEnv("recovery-server-name", "RECOVERY_SERVER_NAME")
return cmd return cmd
} }

View File

@ -23,8 +23,11 @@ func NewCmd() *cobra.Command {
"cluster-name", "cluster-name",
"pod-name", "pod-name",
"spool-directory", "spool-directory",
"barman-object-name",
"server-name", // IMPORTANT: barman-object-name and server-name are not required
// to restore a cluster.
"recovery-barman-object-name",
"recovery-server-name",
} }
for _, k := range requiredSettings { for _, k := range requiredSettings {
@ -42,8 +45,12 @@ func NewCmd() *cobra.Command {
_ = viper.BindEnv("pod-name", "POD_NAME") _ = viper.BindEnv("pod-name", "POD_NAME")
_ = viper.BindEnv("pgdata", "PGDATA") _ = viper.BindEnv("pgdata", "PGDATA")
_ = viper.BindEnv("spool-directory", "SPOOL_DIRECTORY") _ = viper.BindEnv("spool-directory", "SPOOL_DIRECTORY")
_ = viper.BindEnv("barman-object-name", "BARMAN_OBJECT_NAME") _ = viper.BindEnv("barman-object-name", "BARMAN_OBJECT_NAME")
_ = viper.BindEnv("server-name", "SERVER_NAME") _ = viper.BindEnv("server-name", "SERVER_NAME")
_ = viper.BindEnv("recovery-barman-object-name", "RECOVERY_BARMAN_OBJECT_NAME")
_ = viper.BindEnv("recovery-server-name", "RECOVERY_SERVER_NAME")
return cmd return cmd
} }

View File

@ -24,15 +24,19 @@ import (
// WALServiceImplementation is the implementation of the WAL Service // WALServiceImplementation is the implementation of the WAL Service
type WALServiceImplementation struct { type WALServiceImplementation struct {
ServerName string wal.UnimplementedWALServer
BarmanObjectKey client.ObjectKey
ClusterObjectKey client.ObjectKey ClusterObjectKey client.ObjectKey
Client client.Client Client client.Client
InstanceName string InstanceName string
SpoolDirectory string SpoolDirectory string
PGDataPath string PGDataPath string
PGWALPath string PGWALPath string
wal.UnimplementedWALServer
BarmanObjectKey client.ObjectKey
ServerName string
RecoveryBarmanObjectKey client.ObjectKey
RecoveryServerName string
} }
// GetCapabilities implements the WALService interface // GetCapabilities implements the WALService interface
@ -123,8 +127,9 @@ func (w WALServiceImplementation) Restore(
ctx context.Context, ctx context.Context,
request *wal.WALRestoreRequest, request *wal.WALRestoreRequest,
) (*wal.WALRestoreResult, error) { ) (*wal.WALRestoreResult, error) {
contextLogger := log.FromContext(ctx) // TODO: build full paths
startTime := time.Now() walName := request.GetSourceWalName()
destinationPath := request.GetDestinationFileName()
var cluster cnpgv1.Cluster var cluster cnpgv1.Cluster
if err := w.Client.Get(ctx, w.ClusterObjectKey, &cluster); err != nil { if err := w.Client.Get(ctx, w.ClusterObjectKey, &cluster); err != nil {
@ -132,13 +137,45 @@ func (w WALServiceImplementation) Restore(
} }
var objectStore barmancloudv1.ObjectStore var objectStore barmancloudv1.ObjectStore
if err := w.Client.Get(ctx, w.BarmanObjectKey, &objectStore); err != nil { var serverName string
return nil, err
switch {
case cluster.IsReplica() && cluster.Status.CurrentPrimary == w.InstanceName:
// Designated primary on replica cluster, using recovery object store
serverName = w.RecoveryServerName
if err := w.Client.Get(ctx, w.RecoveryBarmanObjectKey, &objectStore); err != nil {
return nil, err
}
case cluster.Status.CurrentPrimary == "":
// Recovery from object store, using recovery object store
serverName = w.RecoveryServerName
if err := w.Client.Get(ctx, w.RecoveryBarmanObjectKey, &objectStore); err != nil {
return nil, err
}
default:
// Using cluster object store
serverName = w.ServerName
if err := w.Client.Get(ctx, w.BarmanObjectKey, &objectStore); err != nil {
return nil, err
}
} }
// TODO: build full paths return &wal.WALRestoreResult{}, w.restoreFromBarmanObjectStore(
walName := request.GetSourceWalName() ctx, &cluster, &objectStore, serverName, walName, destinationPath)
destinationPath := request.GetDestinationFileName() }
func (w WALServiceImplementation) restoreFromBarmanObjectStore(
ctx context.Context,
cluster *cnpgv1.Cluster,
objectStore *barmancloudv1.ObjectStore,
serverName string,
walName string,
destinationPath string,
) error {
contextLogger := log.FromContext(ctx)
startTime := time.Now()
barmanConfiguration := &objectStore.Spec.Configuration barmanConfiguration := &objectStore.Spec.Configuration
@ -151,37 +188,37 @@ func (w WALServiceImplementation) Restore(
os.Environ(), os.Environ(),
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("while getting recover credentials: %w", err) return fmt.Errorf("while getting recover credentials: %w", err)
} }
env = MergeEnv(env, credentialsEnv) env = MergeEnv(env, credentialsEnv)
options, err := barmanCommand.CloudWalRestoreOptions(ctx, barmanConfiguration, w.ServerName) options, err := barmanCommand.CloudWalRestoreOptions(ctx, barmanConfiguration, serverName)
if err != nil { if err != nil {
return nil, fmt.Errorf("while getting barman-cloud-wal-restore options: %w", err) return fmt.Errorf("while getting barman-cloud-wal-restore options: %w", err)
} }
// Create the restorer // Create the restorer
var walRestorer *barmanRestorer.WALRestorer var walRestorer *barmanRestorer.WALRestorer
if walRestorer, err = barmanRestorer.New(ctx, env, w.SpoolDirectory); err != nil { if walRestorer, err = barmanRestorer.New(ctx, env, w.SpoolDirectory); err != nil {
return nil, fmt.Errorf("while creating the restorer: %w", err) return fmt.Errorf("while creating the restorer: %w", err)
} }
// Step 1: check if this WAL file is not already in the spool // Step 1: check if this WAL file is not already in the spool
var wasInSpool bool var wasInSpool bool
if wasInSpool, err = walRestorer.RestoreFromSpool(walName, destinationPath); err != nil { if wasInSpool, err = walRestorer.RestoreFromSpool(walName, destinationPath); err != nil {
return nil, fmt.Errorf("while restoring a file from the spool directory: %w", err) return fmt.Errorf("while restoring a file from the spool directory: %w", err)
} }
if wasInSpool { if wasInSpool {
contextLogger.Info("Restored WAL file from spool (parallel)", contextLogger.Info("Restored WAL file from spool (parallel)",
"walName", walName, "walName", walName,
) )
return nil, nil return nil
} }
// We skip this step if streaming connection is not available // We skip this step if streaming connection is not available
if isStreamingAvailable(&cluster, w.InstanceName) { if isStreamingAvailable(cluster, w.InstanceName) {
if err := checkEndOfWALStreamFlag(walRestorer); err != nil { if err := checkEndOfWALStreamFlag(walRestorer); err != nil {
return nil, err return err
} }
} }
@ -194,7 +231,7 @@ func (w WALServiceImplementation) Restore(
if IsWALFile(walName) { if IsWALFile(walName) {
// If this is a regular WAL file, we try to prefetch // If this is a regular WAL file, we try to prefetch
if walFilesList, err = gatherWALFilesToRestore(walName, maxParallel); err != nil { if walFilesList, err = gatherWALFilesToRestore(walName, maxParallel); err != nil {
return nil, fmt.Errorf("while generating the list of WAL files to restore: %w", err) return fmt.Errorf("while generating the list of WAL files to restore: %w", err)
} }
} else { } else {
// This is not a regular WAL file, we fetch it directly // This is not a regular WAL file, we fetch it directly
@ -209,18 +246,18 @@ func (w WALServiceImplementation) Restore(
// is the one that PostgreSQL has requested to restore. // is the one that PostgreSQL has requested to restore.
// The failure has already been logged in walRestorer.RestoreList method // The failure has already been logged in walRestorer.RestoreList method
if walStatus[0].Err != nil { if walStatus[0].Err != nil {
return nil, walStatus[0].Err return walStatus[0].Err
} }
// We skip this step if streaming connection is not available // We skip this step if streaming connection is not available
endOfWALStream := isEndOfWALStream(walStatus) endOfWALStream := isEndOfWALStream(walStatus)
if isStreamingAvailable(&cluster, w.InstanceName) && endOfWALStream { if isStreamingAvailable(cluster, w.InstanceName) && endOfWALStream {
contextLogger.Info( contextLogger.Info(
"Set end-of-wal-stream flag as one of the WAL files to be prefetched was not found") "Set end-of-wal-stream flag as one of the WAL files to be prefetched was not found")
err = walRestorer.SetEndOfWALStream() err = walRestorer.SetEndOfWALStream()
if err != nil { if err != nil {
return nil, err return err
} }
} }
@ -241,7 +278,7 @@ func (w WALServiceImplementation) Restore(
"downloadTotalTime", time.Since(downloadStartTime), "downloadTotalTime", time.Since(downloadStartTime),
"totalTime", time.Since(startTime)) "totalTime", time.Since(startTime))
return &wal.WALRestoreResult{}, nil return nil
} }
// Status implements the WALService interface // Status implements the WALService interface

View File

@ -3,6 +3,7 @@ package client
import ( import (
"context" "context"
"fmt" "fmt"
"math"
"sync" "sync"
"time" "time"
@ -21,32 +22,41 @@ type cachedSecret struct {
// ExtendedClient is an extended client that is capable of caching multiple secrets without relying on informers // ExtendedClient is an extended client that is capable of caching multiple secrets without relying on informers
type ExtendedClient struct { type ExtendedClient struct {
client.Client client.Client
barmanObjectKey client.ObjectKey barmanObjectKeys []client.ObjectKey
cachedSecrets []*cachedSecret cachedSecrets []*cachedSecret
mux *sync.Mutex mux *sync.Mutex
ttl int ttl int
} }
// 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
func NewExtendedClient( func NewExtendedClient(
baseClient client.Client, baseClient client.Client,
objectStoreKey client.ObjectKey, objectStoreKeys []client.ObjectKey,
) client.Client { ) client.Client {
return &ExtendedClient{ return &ExtendedClient{
Client: baseClient, Client: baseClient,
barmanObjectKey: objectStoreKey, barmanObjectKeys: objectStoreKeys,
mux: &sync.Mutex{}, mux: &sync.Mutex{},
} }
} }
func (e *ExtendedClient) refreshTTL(ctx context.Context) error { func (e *ExtendedClient) refreshTTL(ctx context.Context) error {
var object v1.ObjectStore minTTL := math.MaxInt
if err := e.Get(ctx, e.barmanObjectKey, &object); err != nil {
return fmt.Errorf("failed to get the object store while refreshing the TTL parameter: %w", err) for _, key := range e.barmanObjectKeys {
var object v1.ObjectStore
if err := e.Get(ctx, key, &object); err != nil {
return fmt.Errorf("failed to get the object store while refreshing the TTL parameter: %w", err)
}
currentTTL := object.Spec.InstanceSidecarConfiguration.GetCacheTTL()
if currentTTL < minTTL {
minTTL = currentTTL
}
} }
e.ttl = object.Spec.InstanceSidecarConfiguration.GetCacheTTL() e.ttl = minTTL
return nil return nil
} }

View File

@ -55,7 +55,9 @@ var _ = Describe("ExtendedClient Get", func() {
baseClient := fake.NewClientBuilder(). baseClient := fake.NewClientBuilder().
WithScheme(scheme). WithScheme(scheme).
WithObjects(secretInClient, objectStore).Build() WithObjects(secretInClient, objectStore).Build()
extendedClient = NewExtendedClient(baseClient, client.ObjectKeyFromObject(objectStore)).(*ExtendedClient) extendedClient = NewExtendedClient(baseClient, []client.ObjectKey{
client.ObjectKeyFromObject(objectStore),
}).(*ExtendedClient)
}) })
It("returns secret from cache if not expired", func(ctx SpecContext) { It("returns secret from cache if not expired", func(ctx SpecContext) {

View File

@ -9,6 +9,7 @@ import (
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme" clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
@ -33,20 +34,16 @@ func Start(ctx context.Context) error {
setupLog := log.FromContext(ctx) setupLog := log.FromContext(ctx)
setupLog.Info("Starting barman cloud instance plugin") setupLog.Info("Starting barman cloud instance plugin")
namespace := viper.GetString("namespace") namespace := viper.GetString("namespace")
boName := viper.GetString("barman-object-name")
clusterName := viper.GetString("cluster-name") clusterName := viper.GetString("cluster-name")
podName := viper.GetString("pod-name") podName := viper.GetString("pod-name")
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ barmanObjectName := viper.GetString("barman-object-name")
recoveryBarmanObjectName := viper.GetString("recovery-barman-object-name")
controllerOptions := ctrl.Options{
Scheme: scheme, Scheme: scheme,
Cache: cache.Options{ Cache: cache.Options{
ByObject: map[client.Object]cache.ByObject{ ByObject: map[client.Object]cache.ByObject{
&barmancloudv1.ObjectStore{}: {
Field: fields.OneTermEqualSelector("metadata.name", boName),
Namespaces: map[string]cache.Config{
namespace: {},
},
},
&cnpgv1.Cluster{}: { &cnpgv1.Cluster{}: {
Field: fields.OneTermEqualSelector("metadata.name", clusterName), Field: fields.OneTermEqualSelector("metadata.name", clusterName),
Namespaces: map[string]cache.Config{ Namespaces: map[string]cache.Config{
@ -62,7 +59,23 @@ func Start(ctx context.Context) error {
}, },
}, },
}, },
}) }
if len(recoveryBarmanObjectName) == 0 {
controllerOptions.Cache.ByObject[&barmancloudv1.ObjectStore{}] = cache.ByObject{
Field: fields.OneTermEqualSelector("metadata.name", barmanObjectName),
Namespaces: map[string]cache.Config{
namespace: {},
},
}
} else {
controllerOptions.Client.Cache.DisableFor = append(
controllerOptions.Client.Cache.DisableFor,
&barmancloudv1.ObjectStore{},
)
}
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), controllerOptions)
if err != nil { if err != nil {
setupLog.Error(err, "unable to start manager") setupLog.Error(err, "unable to start manager")
return err return err
@ -70,23 +83,39 @@ func Start(ctx context.Context) error {
barmanObjectKey := client.ObjectKey{ barmanObjectKey := client.ObjectKey{
Namespace: namespace, Namespace: namespace,
Name: boName, Name: barmanObjectName,
}
recoveryBarmanObjectKey := client.ObjectKey{
Namespace: namespace,
Name: recoveryBarmanObjectName,
}
involvedObjectStores := make([]types.NamespacedName, 0, 2)
if len(barmanObjectName) > 0 {
involvedObjectStores = append(involvedObjectStores, barmanObjectKey)
}
if len(recoveryBarmanObjectName) > 0 {
involvedObjectStores = append(involvedObjectStores, recoveryBarmanObjectKey)
} }
if err := mgr.Add(&CNPGI{ if err := mgr.Add(&CNPGI{
Client: extendedclient.NewExtendedClient(mgr.GetClient(), barmanObjectKey), Client: extendedclient.NewExtendedClient(mgr.GetClient(), involvedObjectStores),
ClusterObjectKey: client.ObjectKey{ ClusterObjectKey: client.ObjectKey{
Namespace: namespace, Namespace: namespace,
Name: clusterName, Name: clusterName,
}, },
BarmanObjectKey: barmanObjectKey, InstanceName: podName,
ServerName: viper.GetString("server-name"),
InstanceName: podName,
// TODO: improve // TODO: improve
PGDataPath: viper.GetString("pgdata"), PGDataPath: viper.GetString("pgdata"),
PGWALPath: path.Join(viper.GetString("pgdata"), "pg_wal"), PGWALPath: path.Join(viper.GetString("pgdata"), "pg_wal"),
SpoolDirectory: viper.GetString("spool-directory"), SpoolDirectory: viper.GetString("spool-directory"),
PluginPath: viper.GetString("plugin-path"), PluginPath: viper.GetString("plugin-path"),
BarmanObjectKey: barmanObjectKey,
ServerName: viper.GetString("server-name"),
RecoveryBarmanObjectKey: recoveryBarmanObjectKey,
RecoveryServerName: viper.GetString("recovery-server-name"),
}); err != nil { }); err != nil {
setupLog.Error(err, "unable to create CNPGI runnable") setupLog.Error(err, "unable to create CNPGI runnable")
return err return err

View File

@ -15,8 +15,6 @@ import (
// CNPGI is the implementation of the PostgreSQL sidecar // CNPGI is the implementation of the PostgreSQL sidecar
type CNPGI struct { type CNPGI struct {
Client client.Client Client client.Client
BarmanObjectKey client.ObjectKey
ServerName string
ClusterObjectKey client.ObjectKey ClusterObjectKey client.ObjectKey
PGDataPath string PGDataPath string
PGWALPath string PGWALPath string
@ -24,20 +22,30 @@ type CNPGI struct {
// mutually exclusive with serverAddress // mutually exclusive with serverAddress
PluginPath string PluginPath string
InstanceName string InstanceName string
BarmanObjectKey client.ObjectKey
ServerName string
RecoveryBarmanObjectKey client.ObjectKey
RecoveryServerName string
} }
// Start starts the GRPC service // Start starts the GRPC service
func (c *CNPGI) Start(ctx context.Context) error { func (c *CNPGI) Start(ctx context.Context) error {
enrich := func(server *grpc.Server) error { enrich := func(server *grpc.Server) error {
wal.RegisterWALServer(server, common.WALServiceImplementation{ wal.RegisterWALServer(server, common.WALServiceImplementation{
BarmanObjectKey: c.BarmanObjectKey,
ClusterObjectKey: c.ClusterObjectKey, ClusterObjectKey: c.ClusterObjectKey,
ServerName: c.ServerName,
InstanceName: c.InstanceName, InstanceName: c.InstanceName,
Client: c.Client, Client: c.Client,
SpoolDirectory: c.SpoolDirectory, SpoolDirectory: c.SpoolDirectory,
PGDataPath: c.PGDataPath, PGDataPath: c.PGDataPath,
PGWALPath: c.PGWALPath, PGWALPath: c.PGWALPath,
BarmanObjectKey: c.BarmanObjectKey,
ServerName: c.ServerName,
RecoveryBarmanObjectKey: c.RecoveryBarmanObjectKey,
RecoveryServerName: c.RecoveryServerName,
}) })
backup.RegisterBackupServer(server, BackupServiceImplementation{ backup.RegisterBackupServer(server, BackupServiceImplementation{
Client: c.Client, Client: c.Client,

View File

@ -47,7 +47,7 @@ type PluginConfiguration struct {
BarmanObjectName string BarmanObjectName string
ServerName string ServerName string
RecoveryBarmanObjectName string RecoveryBarmanObjectName string
RecoveryBarmanServerName string RecoveryServerName string
} }
// NewFromCluster extracts the configuration from the cluster // NewFromCluster extracts the configuration from the cluster
@ -82,7 +82,7 @@ func NewFromCluster(cluster *cnpgv1.Cluster) *PluginConfiguration {
BarmanObjectName: helper.Parameters["barmanObjectName"], BarmanObjectName: helper.Parameters["barmanObjectName"],
ServerName: serverName, ServerName: serverName,
// used for restore/wal_restore // used for restore/wal_restore
RecoveryBarmanServerName: recoveryServerName, RecoveryServerName: recoveryServerName,
RecoveryBarmanObjectName: recoveryBarmanObjectName, RecoveryBarmanObjectName: recoveryBarmanObjectName,
} }

View File

@ -115,13 +115,6 @@ func reconcileJob(
return nil, nil return nil, nil
} }
// Since we're recovering from an existing object store,
// we set our primary object store name to the recovery one.
// This won't be needed anymore when wal-restore will be able
// to check two object stores
pluginConfiguration.BarmanObjectName = pluginConfiguration.RecoveryBarmanObjectName
pluginConfiguration.ServerName = pluginConfiguration.RecoveryBarmanServerName
var job batchv1.Job var job batchv1.Job
if err := decoder.DecodeObject( if err := decoder.DecodeObject(
request.GetObjectDefinition(), request.GetObjectDefinition(),
@ -219,14 +212,6 @@ func reconcilePodSpec(
Name: "CLUSTER_NAME", Name: "CLUSTER_NAME",
Value: cluster.Name, Value: cluster.Name,
}, },
{
Name: "BARMAN_OBJECT_NAME",
Value: cfg.BarmanObjectName,
},
{
Name: "SERVER_NAME",
Value: cfg.ServerName,
},
{ {
// TODO: should we really use this one? // TODO: should we really use this one?
// should we mount an emptyDir volume just for that? // should we mount an emptyDir volume just for that?
@ -235,6 +220,32 @@ func reconcilePodSpec(
}, },
} }
if len(cfg.BarmanObjectName) > 0 {
envs = append(envs,
corev1.EnvVar{
Name: "BARMAN_OBJECT_NAME",
Value: cfg.BarmanObjectName,
},
corev1.EnvVar{
Name: "SERVER_NAME",
Value: cfg.ServerName,
},
)
}
if len(cfg.RecoveryBarmanObjectName) > 0 {
envs = append(envs,
corev1.EnvVar{
Name: "RECOVERY_BARMAN_OBJECT_NAME",
Value: cfg.RecoveryBarmanObjectName,
},
corev1.EnvVar{
Name: "RECOVERY_SERVER_NAME",
Value: cfg.RecoveryServerName,
},
)
}
baseProbe := &corev1.Probe{ baseProbe := &corev1.Probe{
FailureThreshold: 3, FailureThreshold: 3,
ProbeHandler: corev1.ProbeHandler{ ProbeHandler: corev1.ProbeHandler{

View File

@ -15,7 +15,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1" barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config" "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/specs" "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/specs"
) )
@ -78,7 +77,6 @@ func (r ReconcilerImplementation) Pre(
var barmanObjects []barmancloudv1.ObjectStore var barmanObjects []barmancloudv1.ObjectStore
// this could be empty during recoveries
if pluginConfiguration.BarmanObjectName != "" { if pluginConfiguration.BarmanObjectName != "" {
var barmanObject barmancloudv1.ObjectStore var barmanObject barmancloudv1.ObjectStore
if err := r.Client.Get(ctx, client.ObjectKey{ if err := r.Client.Get(ctx, client.ObjectKey{
@ -86,7 +84,10 @@ func (r ReconcilerImplementation) Pre(
Name: pluginConfiguration.BarmanObjectName, Name: pluginConfiguration.BarmanObjectName,
}, &barmanObject); err != nil { }, &barmanObject); err != nil {
if apierrs.IsNotFound(err) { if apierrs.IsNotFound(err) {
contextLogger.Info("barman object configuration not found, requeuing") contextLogger.Info(
"barman object configuration not found, requeuing",
"name", pluginConfiguration.BarmanObjectName,
"namespace", cluster.Namespace)
return &reconciler.ReconcilerHooksResult{ return &reconciler.ReconcilerHooksResult{
Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_REQUEUE, Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_REQUEUE,
}, nil }, nil
@ -98,15 +99,26 @@ func (r ReconcilerImplementation) Pre(
barmanObjects = append(barmanObjects, barmanObject) barmanObjects = append(barmanObjects, barmanObject)
} }
if barmanObject, err := r.getRecoveryBarmanObject(ctx, &cluster); err != nil { if pluginConfiguration.RecoveryBarmanObjectName != "" {
if apierrs.IsNotFound(err) { var barmanObject barmancloudv1.ObjectStore
contextLogger.Info("barman recovery object configuration not found, requeuing") if err := r.Client.Get(ctx, client.ObjectKey{
return &reconciler.ReconcilerHooksResult{ Namespace: cluster.Namespace,
Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_REQUEUE, Name: pluginConfiguration.RecoveryBarmanObjectName,
}, nil }, &barmanObject); err != nil {
if apierrs.IsNotFound(err) {
contextLogger.Info(
"barman recovery object configuration not found, requeuing",
"name", pluginConfiguration.RecoveryBarmanObjectName,
"namespace", cluster.Namespace,
)
return &reconciler.ReconcilerHooksResult{
Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_REQUEUE,
}, nil
}
return nil, err
} }
} else if barmanObject != nil {
barmanObjects = append(barmanObjects, *barmanObject) barmanObjects = append(barmanObjects, barmanObject)
} }
var additionalSecretNames []string var additionalSecretNames []string
@ -124,30 +136,6 @@ func (r ReconcilerImplementation) Pre(
}, nil }, nil
} }
func (r ReconcilerImplementation) getRecoveryBarmanObject(
ctx context.Context,
cluster *cnpgv1.Cluster,
) (*barmancloudv1.ObjectStore, error) {
recoveryConfig := cluster.GetRecoverySourcePlugin()
if recoveryConfig != nil && recoveryConfig.Name == metadata.PluginName {
// TODO: refactor -> cnpg-i-machinery should be able to help us on getting
// the configuration for a recovery plugin
if recoveryObjectStore, ok := recoveryConfig.Parameters["barmanObjectName"]; ok {
var barmanObject barmancloudv1.ObjectStore
if err := r.Client.Get(ctx, client.ObjectKey{
Namespace: cluster.Namespace,
Name: recoveryObjectStore,
}, &barmanObject); err != nil {
return nil, err
}
return &barmanObject, nil
}
}
return nil, nil
}
// Post implements the reconciler interface // Post implements the reconciler interface
func (r ReconcilerImplementation) Post( func (r ReconcilerImplementation) Post(
_ context.Context, _ context.Context,

View File

@ -27,24 +27,10 @@ func BuildRole(
} }
secretsSet := stringset.New() secretsSet := stringset.New()
for _, barmanObject := range barmanObjects { barmanObjectsSet := stringset.New()
role.Rules = append(role.Rules, rbacv1.PolicyRule{
APIGroups: []string{
"barmancloud.cnpg.io",
},
Verbs: []string{
"get",
"watch",
"list",
},
Resources: []string{
"objectstores",
},
ResourceNames: []string{
barmanObject.Name,
},
})
for _, barmanObject := range barmanObjects {
barmanObjectsSet.Put(barmanObject.Name)
for _, secret := range CollectSecretNamesFromCredentials(&barmanObject.Spec.Configuration.BarmanCredentials) { for _, secret := range CollectSecretNamesFromCredentials(&barmanObject.Spec.Configuration.BarmanCredentials) {
secretsSet.Put(secret) secretsSet.Put(secret)
} }
@ -54,6 +40,21 @@ func BuildRole(
secretsSet.Put(secret) secretsSet.Put(secret)
} }
role.Rules = append(role.Rules, rbacv1.PolicyRule{
APIGroups: []string{
"barmancloud.cnpg.io",
},
Verbs: []string{
"get",
"watch",
"list",
},
Resources: []string{
"objectstores",
},
ResourceNames: barmanObjectsSet.ToSortedList(),
})
role.Rules = append(role.Rules, rbacv1.PolicyRule{ role.Rules = append(role.Rules, rbacv1.PolicyRule{
APIGroups: []string{ APIGroups: []string{
"", "",

View File

@ -32,7 +32,12 @@ func Start(ctx context.Context) error {
setupLog.Info("Starting barman cloud instance plugin") setupLog.Info("Starting barman cloud instance plugin")
namespace := viper.GetString("namespace") namespace := viper.GetString("namespace")
clusterName := viper.GetString("cluster-name") clusterName := viper.GetString("cluster-name")
boName := viper.GetString("barman-object-name")
recoveryBarmanObjectName := viper.GetString("recovery-barman-object-name")
recoveryServerName := viper.GetString("recovery-server-name")
barmanObjectName := viper.GetString("barman-object-name")
serverName := viper.GetString("server-name")
objs := map[client.Object]cache.ByObject{ objs := map[client.Object]cache.ByObject{
&cnpgv1.Cluster{}: { &cnpgv1.Cluster{}: {
@ -43,9 +48,9 @@ func Start(ctx context.Context) error {
}, },
} }
if boName != "" { if recoveryBarmanObjectName != "" {
objs[&barmancloudv1.ObjectStore{}] = cache.ByObject{ objs[&barmancloudv1.ObjectStore{}] = cache.ByObject{
Field: fields.OneTermEqualSelector("metadata.name", boName), Field: fields.OneTermEqualSelector("metadata.name", recoveryBarmanObjectName),
Namespaces: map[string]cache.Config{ Namespaces: map[string]cache.Config{
namespace: {}, namespace: {},
}, },
@ -74,10 +79,6 @@ func Start(ctx context.Context) error {
if err := mgr.Add(&CNPGI{ if err := mgr.Add(&CNPGI{
PluginPath: viper.GetString("plugin-path"), PluginPath: viper.GetString("plugin-path"),
SpoolDirectory: viper.GetString("spool-directory"), SpoolDirectory: viper.GetString("spool-directory"),
BarmanObjectKey: client.ObjectKey{
Namespace: namespace,
Name: boName,
},
ClusterObjectKey: client.ObjectKey{ ClusterObjectKey: client.ObjectKey{
Namespace: namespace, Namespace: namespace,
Name: clusterName, Name: clusterName,
@ -85,7 +86,18 @@ func Start(ctx context.Context) error {
Client: mgr.GetClient(), Client: mgr.GetClient(),
PGDataPath: viper.GetString("pgdata"), PGDataPath: viper.GetString("pgdata"),
InstanceName: viper.GetString("pod-name"), InstanceName: viper.GetString("pod-name"),
ServerName: viper.GetString("server-name"),
ServerName: serverName,
BarmanObjectKey: client.ObjectKey{
Namespace: namespace,
Name: barmanObjectName,
},
RecoveryServerName: recoveryServerName,
RecoveryBarmanObjectKey: client.ObjectKey{
Namespace: namespace,
Name: recoveryBarmanObjectName,
},
}); err != nil { }); err != nil {
setupLog.Error(err, "unable to create CNPGI runnable") setupLog.Error(err, "unable to create CNPGI runnable")
return err return err

View File

@ -43,9 +43,16 @@ const (
// JobHookImpl is the implementation of the restore job hooks // JobHookImpl is the implementation of the restore job hooks
type JobHookImpl struct { type JobHookImpl struct {
restore.UnimplementedRestoreJobHooksServer restore.UnimplementedRestoreJobHooksServer
Client client.Client
ClusterObjectKey client.ObjectKey Client client.Client
BackupToRestore client.ObjectKey ClusterObjectKey client.ObjectKey
BarmanObjectKey types.NamespacedName
ServerName string
RecoveryBarmanObjectKey types.NamespacedName
RecoveryServerName string
SpoolDirectory string SpoolDirectory string
PgDataPath string PgDataPath string
PgWalFolderToSymlink string PgWalFolderToSymlink string
@ -80,38 +87,17 @@ func (impl JobHookImpl) Restore(
return nil, err return nil, err
} }
recoveryPluginConfiguration := cluster.GetRecoverySourcePlugin()
var recoveryObjectStore barmancloudv1.ObjectStore var recoveryObjectStore barmancloudv1.ObjectStore
if err := impl.Client.Get(ctx, types.NamespacedName{ if err := impl.Client.Get(ctx, impl.RecoveryBarmanObjectKey, &recoveryObjectStore); err != nil {
Namespace: cluster.Namespace,
// TODO: refactor -> cnpg-i-machinery should be able to help us on getting
// the configuration for a recovery plugin
Name: recoveryPluginConfiguration.Parameters["barmanObjectName"],
}, &recoveryObjectStore); err != nil {
return nil, err return nil, err
} }
var targetObjectStoreName types.NamespacedName if impl.BarmanObjectKey.Name != "" {
for _, plugin := range cluster.Spec.Plugins { var targetObjectStore barmancloudv1.ObjectStore
if plugin.IsEnabled() && plugin.Name == metadata.PluginName { if err := impl.Client.Get(ctx, impl.BarmanObjectKey, &targetObjectStore); err != nil {
targetObjectStoreName = types.NamespacedName{
Namespace: cluster.Namespace,
Name: plugin.Parameters["barmanObjectName"],
}
}
}
var targetObjectStore barmancloudv1.ObjectStore
if targetObjectStoreName.Name != "" {
if err := impl.Client.Get(ctx, targetObjectStoreName, &targetObjectStore); err != nil {
return nil, err return nil, err
} }
}
// Before starting the restore we check if the archive destination is safe to use,
// otherwise we stop creating the cluster
if targetObjectStoreName.Name != "" {
if err := impl.checkBackupDestination(ctx, &cluster, &targetObjectStore.Spec.Configuration); err != nil { if err := impl.checkBackupDestination(ctx, &cluster, &targetObjectStore.Spec.Configuration); err != nil {
return nil, err return nil, err
} }
@ -123,6 +109,7 @@ func (impl JobHookImpl) Restore(
impl.Client, impl.Client,
&cluster, &cluster,
&recoveryObjectStore.Spec.Configuration, &recoveryObjectStore.Spec.Configuration,
impl.RecoveryServerName,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -353,30 +340,13 @@ func loadBackupObjectFromExternalCluster(
typedClient client.Client, typedClient client.Client,
cluster *cnpgv1.Cluster, cluster *cnpgv1.Cluster,
recoveryObjectStore *api.BarmanObjectStoreConfiguration, recoveryObjectStore *api.BarmanObjectStoreConfiguration,
serverName string,
) (*cnpgv1.Backup, []string, error) { ) (*cnpgv1.Backup, []string, error) {
contextLogger := log.FromContext(ctx) contextLogger := log.FromContext(ctx)
sourceName := cluster.Spec.Bootstrap.Recovery.Source
if sourceName == "" {
return nil, nil, fmt.Errorf("recovery source not specified")
}
server, found := cluster.ExternalCluster(sourceName)
if !found {
return nil, nil, fmt.Errorf("missing external cluster: %v", sourceName)
}
// TODO: document this, should this be in the helper?
var serverName string
if pluginServerName, ok := server.PluginConfiguration.Parameters["serverName"]; ok {
serverName = pluginServerName
} else {
serverName = server.Name
}
contextLogger.Info("Recovering from external cluster", contextLogger.Info("Recovering from external cluster",
"sourceName", sourceName, "serverName", serverName,
"serverName", serverName) "objectStore", recoveryObjectStore)
env, err := barmanCredentials.EnvSetRestoreCloudCredentials( env, err := barmanCredentials.EnvSetRestoreCloudCredentials(
ctx, ctx,

View File

@ -15,14 +15,20 @@ import (
// CNPGI is the implementation of the PostgreSQL sidecar // CNPGI is the implementation of the PostgreSQL sidecar
type CNPGI struct { type CNPGI struct {
PluginPath string PluginPath string
SpoolDirectory string SpoolDirectory string
BarmanObjectKey client.ObjectKey
BarmanObjectKey client.ObjectKey
ServerName string
RecoveryBarmanObjectKey client.ObjectKey
RecoveryServerName string
ClusterObjectKey client.ObjectKey ClusterObjectKey client.ObjectKey
Client client.Client
PGDataPath string Client client.Client
InstanceName string PGDataPath string
ServerName string InstanceName string
} }
// Start starts the GRPC service // Start starts the GRPC service
@ -32,14 +38,18 @@ func (c *CNPGI) Start(ctx context.Context) error {
enrich := func(server *grpc.Server) error { enrich := func(server *grpc.Server) error {
wal.RegisterWALServer(server, common.WALServiceImplementation{ wal.RegisterWALServer(server, common.WALServiceImplementation{
BarmanObjectKey: c.BarmanObjectKey,
ClusterObjectKey: c.ClusterObjectKey, ClusterObjectKey: c.ClusterObjectKey,
InstanceName: c.InstanceName, InstanceName: c.InstanceName,
Client: c.Client, Client: c.Client,
SpoolDirectory: c.SpoolDirectory, SpoolDirectory: c.SpoolDirectory,
PGDataPath: c.PGDataPath, PGDataPath: c.PGDataPath,
PGWALPath: path.Join(c.PGDataPath, "pg_wal"), PGWALPath: path.Join(c.PGDataPath, "pg_wal"),
ServerName: c.ServerName,
BarmanObjectKey: c.BarmanObjectKey,
ServerName: c.ServerName,
RecoveryBarmanObjectKey: c.RecoveryBarmanObjectKey,
RecoveryServerName: c.RecoveryServerName,
}) })
restore.RegisterRestoreJobHooksServer(server, &JobHookImpl{ restore.RegisterRestoreJobHooksServer(server, &JobHookImpl{
@ -48,6 +58,12 @@ func (c *CNPGI) Start(ctx context.Context) error {
SpoolDirectory: c.SpoolDirectory, SpoolDirectory: c.SpoolDirectory,
PgDataPath: c.PGDataPath, PgDataPath: c.PGDataPath,
PgWalFolderToSymlink: PgWalVolumePgWalPath, PgWalFolderToSymlink: PgWalVolumePgWalPath,
BarmanObjectKey: c.BarmanObjectKey,
ServerName: c.ServerName,
RecoveryBarmanObjectKey: c.RecoveryBarmanObjectKey,
RecoveryServerName: c.RecoveryServerName,
}) })
common.AddHealthCheck(server) common.AddHealthCheck(server)