Skip to content

Commit edef022

Browse files
committed
Added a CLI command to take a single DB backup
Signed-off-by: Danny Zaken <dannyzaken@gmail.com>
1 parent da00325 commit edef022

3 files changed

Lines changed: 108 additions & 11 deletions

File tree

pkg/cnpg/cnpg.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,7 @@ func GetCnpgBackupObj(namespace string, name string) *cnpgv1.Backup {
620620
Name: name,
621621
Namespace: namespace,
622622
},
623+
Spec: cnpgv1.BackupSpec{},
623624
}
624625
return cnpgBackup
625626
}

pkg/system/db_reconciler.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,14 @@ const (
3232

3333
// ReconcileCNPGCluster reconciles the CNPG cluster
3434
// There are several cases to handle:
35-
// 1. Reconciling a fresh install - No CNPG cluster and no previous DB to import from
35+
// 1. Reconciling a fresh install - No CNPG cluster and no DBRecovery configuration
3636
// - In this case we need to create a new empty CNPG cluster
3737
// - Create a new CNPG image catalog
3838
// - Create a new CNPG cluster
3939
//
40-
// 2. Reconciling an upgrade from a version with a standalone DB - No CNPG cluster and DB statefulset exists
41-
// - In this case we need to create a new CNPG cluster and import the DB from the previous statefulset
42-
// - Import is done by providing externalCluster details in the CNPG cluster spec (https://cloudnative-pg.io/documentation/1.25/database_import/#the-microservice-type)
43-
// - After Import is completed, cleanup the old DB resources. For now we only scale down the standalone DB pod to 0 replicas
44-
// - All other pods (core, endpoints) should be stopped before starting the import
40+
// 2. Reconciling recovery from a snapshot - CNPG cluster was deleted by the user to initiate a recovery from a snapshot
41+
// - In this case we need to create a new CNPG cluster and set the bootstrap configuration to recover from the snapshot
42+
// - All other pods (core, endpoints) should be stopped before starting the recovery
4543
//
4644
// 3. Reconciling an existing CNPG cluster with no standalone DB - CNPG cluster exists and DB statefulset does not exist
4745
// - If the major version is the same, check if the DB image is changed and update the ImageCatalog
@@ -149,8 +147,11 @@ func (r *Reconciler) reconcileDBCluster() error {
149147

150148
// Apply changes to the cluster resources. create or update the modified cluster
151149
if r.CNPGCluster.UID == "" {
150+
// If the cluster CR has not been created yet (UID = ""), it means one of two options.:
151+
// 1. The cluster is being created for the first time
152+
// 2. The cluster is being recovered from a snapshot
152153

153-
// Cluster resource is missing. Check if noobaa CR has a recovery configuration.
154+
// Check if noobaa CR has a recovery configuration.
154155
if r.NooBaa.Spec.DBSpec.DBRecovery == nil {
155156
// No recovery configuration found, set bootstrap configuration to init a new DB
156157
if r.CNPGCluster.Spec.Bootstrap == nil {
@@ -205,11 +206,11 @@ func (r *Reconciler) reconcileDBCluster() error {
205206
r.cnpgLog("cluster spec is changed, updating cluster. diff: %v", diff)
206207

207208
currentDBClusterStatus := r.NooBaa.Status.DBStatus.DBClusterStatus
208-
// avoid updating a cluster that is being created or imported.
209+
// avoid updating a cluster that is being created.
209210
// We might want to consider allowing this somehow for supportability (through annotation or something)
210-
if currentDBClusterStatus == nbv1.DBClusterStatusCreating || currentDBClusterStatus == nbv1.DBClusterStatusImporting {
211-
r.cnpgLog("the cluster spec was changed but the cluster creation or import is still in progress, skipping update")
212-
return fmt.Errorf("cluster creation or import is still in progress, skipping update")
211+
if currentDBClusterStatus == nbv1.DBClusterStatusCreating || currentDBClusterStatus == nbv1.DBClusterStatusRecovering {
212+
r.cnpgLog("the cluster spec was changed but the cluster creation is still in progress, skipping update")
213+
return fmt.Errorf("cluster creation is still in progress, skipping update")
213214
}
214215

215216
r.cnpgLog("cluster spec is changed, updating cluster")
@@ -444,6 +445,7 @@ func (r *Reconciler) reconcileScheduledBackup() error {
444445
scheduledBackup.Spec.Cluster.Name = r.CNPGCluster.Name
445446
scheduledBackup.Spec.Method = cnpgv1.BackupMethodVolumeSnapshot
446447
scheduledBackup.Spec.Online = &offlineBackup
448+
scheduledBackup.Spec.Target = cnpgv1.BackupTargetStandby
447449
if scheduledBackup.Status.LastScheduleTime != nil {
448450
r.NooBaa.Status.DBStatus.BackupStatus.LastBackupTime = scheduledBackup.Status.LastScheduleTime
449451
}

pkg/system/system.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"strings"
1313
"time"
1414

15+
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
16+
storagesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1"
1517
nbv1 "github.com/noobaa/noobaa-operator/v5/pkg/apis/noobaa/v1alpha1"
1618
"github.com/noobaa/noobaa-operator/v5/pkg/bundle"
1719
"github.com/noobaa/noobaa-operator/v5/pkg/cnpg"
@@ -47,6 +49,7 @@ func Cmd() *cobra.Command {
4749
CmdCreate(),
4850
CmdDelete(),
4951
CmdStatus(),
52+
CmdDBBackup(),
5053
CmdSetDebugLevel(),
5154
CmdList(),
5255
CmdReconcile(),
@@ -118,6 +121,18 @@ func CmdSetDebugLevel() *cobra.Command {
118121
return cmd
119122
}
120123

124+
// CmdDBBackup returns a CLI command
125+
func CmdDBBackup() *cobra.Command {
126+
cmd := &cobra.Command{
127+
Use: "db-backup",
128+
Short: "Create a volume snapshot backup of the database",
129+
Run: RunDBBackup,
130+
Args: cobra.NoArgs,
131+
}
132+
cmd.Flags().String("name", "", "A custom name for the backup")
133+
return cmd
134+
}
135+
121136
// CmdReconcile returns a CLI command
122137
func CmdReconcile() *cobra.Command {
123138
cmd := &cobra.Command{
@@ -891,6 +906,85 @@ func RunSetDebugLevel(cmd *cobra.Command, args []string) {
891906
fmt.Println("Debug level is not persistent and is only effective for the currently running core and endpoints pods")
892907
}
893908

909+
// RunDBBackup runs a CLI command
910+
func RunDBBackup(cmd *cobra.Command, args []string) {
911+
log := util.Logger()
912+
913+
sys := &nbv1.NooBaa{
914+
TypeMeta: metav1.TypeMeta{Kind: "NooBaa"},
915+
ObjectMeta: metav1.ObjectMeta{
916+
Name: options.SystemName,
917+
Namespace: options.Namespace,
918+
},
919+
}
920+
921+
if !util.KubeCheck(sys) {
922+
log.Fatalf("❌ System %q not found", options.SystemName)
923+
}
924+
925+
if sys.Spec.DBSpec == nil {
926+
log.Fatalf("❌ The system is not configured with a CNPG cluster")
927+
}
928+
929+
if sys.Spec.DBSpec.DBBackup == nil || sys.Spec.DBSpec.DBBackup.VolumeSnapshot == nil {
930+
log.Fatalf("❌ The system is not configured with a volume snapshot backup")
931+
}
932+
933+
volumeSnapshotClass := sys.Spec.DBSpec.DBBackup.VolumeSnapshot.VolumeSnapshotClass
934+
if volumeSnapshotClass == "" {
935+
log.Fatalf("❌ The system is not configured with a volume snapshot class")
936+
}
937+
938+
backupName, _ := cmd.Flags().GetString("name")
939+
if backupName == "" {
940+
backupName = sys.Name + pgClusterSuffix + "-backup-" + time.Now().Format("20060102150405")
941+
}
942+
943+
offlineBackup := false
944+
backup := cnpg.GetCnpgBackupObj(sys.Namespace, backupName)
945+
backup.Spec.Cluster.Name = sys.Name + pgClusterSuffix
946+
backup.Spec.Method = cnpgv1.BackupMethodVolumeSnapshot
947+
backup.Spec.Online = &offlineBackup
948+
backup.Spec.Target = cnpgv1.BackupTargetStandby
949+
950+
if !util.KubeCreateFailExisting(backup) {
951+
log.Fatalf("❌ Backup %s failed to create", backupName)
952+
}
953+
954+
log.Printf("✅ Backup object %s created successfully. Waiting for the volume snapshot to be created...\n", backupName)
955+
log.Printf("You can monitor the backup status with the following command:\n")
956+
log.Printf("kubectl -n %s get backups.postgresql.cnpg.noobaa.io %s", sys.Namespace, backupName)
957+
958+
// Create a context with timeout for polling
959+
pollTimeout := 5 * time.Minute
960+
pollCtx, cancel := context.WithTimeout(context.Background(), pollTimeout)
961+
defer cancel()
962+
963+
// wait for the volume snapshot to be created
964+
interval := time.Duration(3)
965+
err := wait.PollUntilContextCancel(pollCtx, interval*time.Second, true, func(ctx context.Context) (bool, error) {
966+
volumeSnapshot := &storagesnapshotv1.VolumeSnapshot{}
967+
volumeSnapshotErr := util.KubeClient().Get(util.Context(),
968+
client.ObjectKey{Namespace: sys.Namespace, Name: backupName},
969+
volumeSnapshot)
970+
if errors.IsNotFound(volumeSnapshotErr) {
971+
log.Printf("⏳ Volume snapshot %s not found yet", backupName)
972+
return false, nil
973+
}
974+
if volumeSnapshotErr != nil {
975+
return false, volumeSnapshotErr
976+
}
977+
return true, nil
978+
})
979+
if err != nil {
980+
log.Fatalf("❌ Failed to wait for volume snapshot %s: %s", backupName, err)
981+
}
982+
983+
log.Printf("✅ Volume snapshot %s created successfully\n", backupName)
984+
log.Printf("You can view the volume snapshot with the following command:\n")
985+
log.Printf("kubectl -n %s get volumesnapshots.snapshot.storage.k8s.io %s", sys.Namespace, backupName)
986+
}
987+
894988
// RunReconcile runs a CLI command
895989
func RunReconcile(cmd *cobra.Command, args []string) {
896990
log := util.Logger()

0 commit comments

Comments
 (0)