Skip to content

Commit b2a8491

Browse files
restores wip
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
1 parent dd22dd0 commit b2a8491

6 files changed

Lines changed: 247 additions & 14 deletions

File tree

pkg/apis/psmdb/v1/perconaservermongodbrestore_types.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func (r *PerconaServerMongoDBRestore) SetDefaults() error {
101101
return nil
102102
}
103103

104-
func (r *PerconaServerMongoDBRestore) CheckFields() error {
104+
func (r *PerconaServerMongoDBRestore) CheckFields(backupType defs.BackupType) error {
105105
if len(r.Spec.ClusterName) == 0 {
106106
return fmt.Errorf("spec clusterName field is empty")
107107
}
@@ -119,7 +119,8 @@ func (r *PerconaServerMongoDBRestore) CheckFields() error {
119119
return errors.New("backupSource destination should use s3 protocol format")
120120
}
121121

122-
if len(r.Spec.StorageName) == 0 &&
122+
if backupType != defs.ExternalBackup &&
123+
len(r.Spec.StorageName) == 0 &&
123124
r.Spec.BackupSource.S3 == nil &&
124125
r.Spec.BackupSource.GCS == nil &&
125126
r.Spec.BackupSource.Azure == nil &&

pkg/controller/perconaservermongodbbackup/snapshot.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ func (b *snapshotBackups) Status(ctx context.Context, cl client.Client, cluster
236236
}
237237
}
238238

239+
status.LastTransition = &metav1.Time{
240+
Time: time.Unix(meta.LastTransitionTS, 0),
241+
}
242+
status.Type = cr.Spec.Type
239243
return status, nil
240244
}
241245

pkg/controller/perconaservermongodbrestore/physical.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func (r *ReconcilePerconaServerMongoDBRestore) reconcilePhysicalRestore(
8585
status.PITRTarget = ts
8686
}
8787

88-
if err := r.updatePBMConfigSecret(ctx, cr, cluster, bcp); err != nil {
88+
if err := r.updatePBMConfigSecret(ctx, cluster); err != nil {
8989
return status, errors.Wrap(err, "update PBM config secret")
9090
}
9191

@@ -398,9 +398,9 @@ func (r *ReconcilePerconaServerMongoDBRestore) updateStatefulSetForPhysicalResto
398398
cmd := []string{
399399
"bash", "-c",
400400
strings.Join([]string{
401-
"install -D /usr/bin/pbm /opt/percona/pbm",
402-
"install -D /usr/bin/pbm-agent /opt/percona/pbm-agent",
403-
"install -D /usr/bin/pbm-agent-entrypoint /opt/percona/pbm-agent-entrypoint",
401+
"install -D /usr/local/bin/pbm /opt/percona/pbm",
402+
"install -D /usr/local/bin/pbm-agent /opt/percona/pbm-agent",
403+
"install -D /usr/local/bin/pbm-agent-entrypoint /opt/percona/pbm-agent-entrypoint",
404404
}, " && "),
405405
}
406406
pbmInit := psmdb.EntrypointInitContainer(
@@ -813,9 +813,7 @@ func (r *ReconcilePerconaServerMongoDBRestore) checkIfReplsetsAreReadyForPhysica
813813

814814
func (r *ReconcilePerconaServerMongoDBRestore) updatePBMConfigSecret(
815815
ctx context.Context,
816-
cr *psmdbv1.PerconaServerMongoDBRestore,
817816
cluster *psmdbv1.PerconaServerMongoDB,
818-
bcp *psmdbv1.PerconaServerMongoDBBackup,
819817
) error {
820818
log := logf.FromContext(ctx)
821819

pkg/controller/perconaservermongodbrestore/psmdb_restore_controller.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,12 @@ func (r *ReconcilePerconaServerMongoDBRestore) Reconcile(ctx context.Context, re
132132
}
133133
}()
134134

135-
err = cr.CheckFields()
135+
bcp, err := r.getBackup(ctx, cr)
136+
if err != nil {
137+
return rr, errors.Wrap(err, "get backup")
138+
}
139+
140+
err = cr.CheckFields(bcp.PBMBackupType())
136141
if err != nil {
137142
return reconcile.Result{}, errors.Wrap(err, "fields check")
138143
}
@@ -163,11 +168,6 @@ func (r *ReconcilePerconaServerMongoDBRestore) Reconcile(ctx context.Context, re
163168
return rr, nil
164169
}
165170

166-
bcp, err := r.getBackup(ctx, cr)
167-
if err != nil {
168-
return rr, errors.Wrap(err, "get backup")
169-
}
170-
171171
var svr *version.ServerVersion
172172
svr, err = version.Server(r.clientcmd)
173173
if err != nil {
@@ -261,6 +261,12 @@ func (r *ReconcilePerconaServerMongoDBRestore) Reconcile(ctx context.Context, re
261261
if err != nil {
262262
return rr, errors.Wrap(err, "reconcile physical restore")
263263
}
264+
265+
case defs.ExternalBackup:
266+
status, err = r.reconcileExternalSnapshotRestore(ctx, cr, bcp, cluster)
267+
if err != nil {
268+
return rr, errors.Wrap(err, "reconcile external snapshot restore")
269+
}
264270
}
265271

266272
return rr, nil
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
package perconaservermongodbrestore
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"strings"
8+
9+
"github.com/percona/percona-backup-mongodb/pbm/defs"
10+
psmdbv1 "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1"
11+
"github.com/percona/percona-server-mongodb-operator/pkg/psmdb/backup"
12+
"github.com/pkg/errors"
13+
corev1 "k8s.io/api/core/v1"
14+
"k8s.io/apimachinery/pkg/types"
15+
"k8s.io/client-go/util/retry"
16+
logf "sigs.k8s.io/controller-runtime/pkg/log"
17+
)
18+
19+
func (r *ReconcilePerconaServerMongoDBRestore) reconcileExternalSnapshotRestore(
20+
ctx context.Context,
21+
cr *psmdbv1.PerconaServerMongoDBRestore,
22+
bcp *psmdbv1.PerconaServerMongoDBBackup,
23+
cluster *psmdbv1.PerconaServerMongoDB,
24+
) (psmdbv1.PerconaServerMongoDBRestoreStatus, error) {
25+
26+
switch cr.Status.State {
27+
case psmdbv1.RestoreStateNew:
28+
return r.reconcileSnapshotNew(cr)
29+
30+
case psmdbv1.RestoreStateWaiting:
31+
return r.reconcileSnapshotWaiting(ctx, cr, cluster)
32+
33+
case psmdbv1.RestoreStateRequested:
34+
return r.reconcileSnapshotRequested(ctx, cr, cluster)
35+
36+
case psmdbv1.RestoreStateRunning:
37+
return r.reconcileSnapshotRunning(ctx, cr, cluster)
38+
}
39+
40+
return cr.Status, nil
41+
}
42+
43+
func (r *ReconcilePerconaServerMongoDBRestore) reconcileSnapshotNew(
44+
restore *psmdbv1.PerconaServerMongoDBRestore,
45+
) (psmdbv1.PerconaServerMongoDBRestoreStatus, error) {
46+
status := restore.Status
47+
status.State = psmdbv1.RestoreStateWaiting
48+
return status, nil
49+
}
50+
51+
func (r *ReconcilePerconaServerMongoDBRestore) reconcileSnapshotWaiting(
52+
ctx context.Context,
53+
restore *psmdbv1.PerconaServerMongoDBRestore,
54+
cluster *psmdbv1.PerconaServerMongoDB,
55+
) (psmdbv1.PerconaServerMongoDBRestoreStatus, error) {
56+
log := logf.FromContext(ctx)
57+
58+
status := restore.Status
59+
if err := r.updatePBMConfigSecret(ctx, cluster); err != nil {
60+
return status, errors.Wrap(err, "update PBM config secret")
61+
}
62+
63+
// TODO: also create a new secret with encryption settings, which will be passed to `--db-config` flag.
64+
65+
if err := r.prepareStatefulSetsForPhysicalRestore(ctx, cluster); err != nil {
66+
return status, errors.Wrap(err, "prepare statefulsets for physical restore")
67+
}
68+
69+
sfsReady, err := r.checkIfStatefulSetsAreReadyForPhysicalRestore(ctx, cluster)
70+
if err != nil {
71+
return status, errors.Wrap(err, "check if statefulsets are ready for physical restore")
72+
}
73+
74+
if !sfsReady {
75+
log.Info("Waiting for statefulsets to be ready before restore", "ready", sfsReady)
76+
return status, nil
77+
}
78+
79+
replsets := cluster.Spec.Replsets
80+
if cluster.Spec.Sharding.Enabled {
81+
replsets = append(replsets, cluster.Spec.Sharding.ConfigsvrReplSet)
82+
}
83+
rs := replsets[0]
84+
85+
pod := corev1.Pod{}
86+
if err := r.client.Get(ctx, types.NamespacedName{Name: rs.PodName(cluster, 0), Namespace: cluster.Namespace}, &pod); err != nil {
87+
return status, errors.Wrap(err, "get pod")
88+
}
89+
90+
restoreCmd := []string{
91+
"/opt/percona/pbm", "restore",
92+
"--external", "--out", "json",
93+
}
94+
95+
// TODO: support replset remapping?
96+
97+
stdoutBuf := &bytes.Buffer{}
98+
stderrBuf := &bytes.Buffer{}
99+
err = retry.OnError(anotherOpBackoff, func(err error) bool {
100+
return strings.Contains(err.Error(), "another operation") ||
101+
strings.Contains(err.Error(), "unable to upgrade connection")
102+
}, func() error {
103+
log.Info("Starting restore", "command", restoreCmd, "pod", pod.Name)
104+
105+
stdoutBuf.Reset()
106+
stderrBuf.Reset()
107+
108+
err := r.clientcmd.Exec(ctx, &pod, "mongod", restoreCmd, nil, stdoutBuf, stderrBuf, false)
109+
if err != nil {
110+
log.Error(nil, "Restore failed to start", "pod", pod.Name, "stderr", stderrBuf.String(), "stdout", stdoutBuf.String())
111+
return errors.Wrapf(err, "start restore stderr: %s stdout: %s", stderrBuf.String(), stdoutBuf.String())
112+
}
113+
114+
log.Info("Restore started", "pod", pod.Name)
115+
116+
return nil
117+
})
118+
if err != nil {
119+
return status, err
120+
}
121+
122+
var out struct {
123+
Name string `json:"name"`
124+
Storage string `json:"storage"`
125+
}
126+
if err := json.Unmarshal(stdoutBuf.Bytes(), &out); err != nil {
127+
return status, errors.Wrap(err, "unmarshal PBM restore output")
128+
}
129+
130+
status.State = psmdbv1.RestoreStateRequested
131+
status.PBMname = out.Name
132+
return status, nil
133+
}
134+
135+
func (r *ReconcilePerconaServerMongoDBRestore) reconcileSnapshotRequested(
136+
ctx context.Context,
137+
restore *psmdbv1.PerconaServerMongoDBRestore,
138+
cluster *psmdbv1.PerconaServerMongoDB,
139+
) (psmdbv1.PerconaServerMongoDBRestoreStatus, error) {
140+
log := logf.FromContext(ctx)
141+
142+
replsets := cluster.Spec.Replsets
143+
if cluster.Spec.Sharding.Enabled {
144+
replsets = append(replsets, cluster.Spec.Sharding.ConfigsvrReplSet)
145+
}
146+
147+
status := restore.Status
148+
meta := backup.BackupMeta{}
149+
150+
stdoutBuf := &bytes.Buffer{}
151+
stderrBuf := &bytes.Buffer{}
152+
err := retry.OnError(retry.DefaultBackoff, func(err error) bool {
153+
return strings.Contains(err.Error(), "container is not created or running") ||
154+
strings.Contains(err.Error(), "error dialing backend: No agent available") ||
155+
strings.Contains(err.Error(), "unable to upgrade connection") ||
156+
strings.Contains(err.Error(), "unmarshal PBM describe-restore output")
157+
}, func() error {
158+
stdoutBuf.Reset()
159+
stderrBuf.Reset()
160+
161+
command := []string{
162+
"/opt/percona/pbm", "describe-restore", status.PBMname,
163+
"--config", "/etc/pbm/pbm_config.yaml",
164+
"--out", "json",
165+
}
166+
167+
pod := corev1.Pod{}
168+
if err := r.client.Get(ctx, types.NamespacedName{Name: replsets[0].PodName(cluster, 0), Namespace: cluster.Namespace}, &pod); err != nil {
169+
return errors.Wrap(err, "get pod")
170+
}
171+
172+
if err := r.clientcmd.Exec(ctx, &pod, "mongod", command, nil, stdoutBuf, stderrBuf, false); err != nil {
173+
return errors.Wrapf(err, "describe restore stderr: %s stdout: %s", stderrBuf.String(), stdoutBuf.String())
174+
}
175+
176+
return nil
177+
})
178+
if err != nil {
179+
return status, err
180+
}
181+
182+
if err := json.Unmarshal(stdoutBuf.Bytes(), &meta); err != nil {
183+
return status, errors.Wrap(err, "unmarshal PBM describe-restore output")
184+
}
185+
186+
if meta.Status != defs.StatusCopyReady {
187+
log.Info("Waiting for nodes to be copy ready", "status", meta.Status)
188+
return status, nil
189+
}
190+
191+
log.Info("Nodes are ready for snapshot restore", "status", meta.Status)
192+
status.State = psmdbv1.RestoreStateRunning
193+
return status, nil
194+
}
195+
196+
func (r *ReconcilePerconaServerMongoDBRestore) reconcileSnapshotRunning(
197+
ctx context.Context,
198+
restore *psmdbv1.PerconaServerMongoDBRestore,
199+
cluster *psmdbv1.PerconaServerMongoDB,
200+
) (psmdbv1.PerconaServerMongoDBRestoreStatus, error) {
201+
202+
// The exact steps that need to take place here:
203+
// 1. Scale down cfg and rs statefulsets and wait for it to be scaled down.
204+
// 2. Update the StatefulSet volumeClaimTemplates to use the snapshot as data source
205+
// 3. Update the StatefulSet pbm-agent arg as follows:
206+
// ```
207+
// pbm-agent restore-finish <restore_name> -c <pbm-config.yaml> --rs <rs_name> --node <node_name> --db-config <db-config.yaml>
208+
// ```
209+
// * rs: Specifed in MONGODB_REPLSET environment variable
210+
// * node_name: Specifed as $POD_NAME.$SERVICE_NAME-MONGODB_REPLSET.$NAMESPACE.svc.cluster.local
211+
// 4. Scale up cfg and rs statefulsets and wait
212+
// 5. Exec the following command to finish the restore:
213+
// ```
214+
// /opt/percona/pbm restore-finish <restore_name> -c <pbm-config.yaml>
215+
// ```
216+
// 6. Delete all statefulsets
217+
// 7. Add resync storage annotation
218+
return restore.Status, nil
219+
}

pkg/controller/perconaservermongodbrestore/validate.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ func (r *ReconcilePerconaServerMongoDBRestore) validate(ctx context.Context, cr
2727
return errors.Wrap(err, "get backup")
2828
}
2929

30+
if bcp.Spec.Type == defs.ExternalBackup {
31+
// TODO: should we check that snapshots exist?
32+
return nil
33+
}
34+
3035
if bcp.Status.Type != defs.LogicalBackup && cr.Spec.Selective != nil {
3136
return errors.New("`.spec.selective` field is supported only for logical backups")
3237
}

0 commit comments

Comments
 (0)