Skip to content

Commit f1ee4b9

Browse files
pooknullhors
andauthored
K8SPXC-1144: mark backups as PITR unready in the storage (#2261)
* K8SPXC-1144: mark backups as PITR unready in the storage https://perconadev.atlassian.net/browse/K8SPXC-1144 * remove comment * small fix * address comments --------- Co-authored-by: Viacheslav Sarzhan <[email protected]>
1 parent d0d50b6 commit f1ee4b9

File tree

10 files changed

+127
-26
lines changed

10 files changed

+127
-26
lines changed

cmd/pitr/collector/collector.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,8 @@ type BackupAzure struct {
119119
}
120120

121121
const (
122-
lastSetFilePrefix string = "last-binlog-set-" // filename prefix for object where the last binlog set will stored
123-
gtidPostfix string = "-gtid-set" // filename postfix for files with GTID set
124-
timelinePath string = "/tmp/pitr-timeline" // path to file with timeline
122+
lastSetFilePrefix string = "last-binlog-set-" // filename prefix for object where the last binlog set will be stored
123+
gtidPostfix string = "-gtid-set" // filename postfix for files with GTID set
125124
)
126125

127126
func New(ctx context.Context, c Config) (*Collector, error) {
@@ -320,11 +319,12 @@ func (c *Collector) filterBinLogs(ctx context.Context, logs []pxc.Binlog, lastBi
320319
}
321320

322321
func createGapFile(gtidSet pxc.GTIDSet) error {
323-
p := "/tmp/gap-detected"
322+
p := naming.GapDetected
324323
f, err := os.Create(p)
325324
if err != nil {
326325
return errors.Wrapf(err, "create %s", p)
327326
}
327+
defer f.Close()
328328

329329
_, err = f.WriteString(gtidSet.Raw())
330330
if err != nil {
@@ -346,10 +346,11 @@ func fileExists(name string) (bool, error) {
346346
}
347347

348348
func createTimelineFile(firstTs string) error {
349-
f, err := os.Create(timelinePath)
349+
f, err := os.Create(naming.TimelinePath)
350350
if err != nil {
351-
return errors.Wrapf(err, "create %s", timelinePath)
351+
return errors.Wrapf(err, "create %s", naming.TimelinePath)
352352
}
353+
defer f.Close()
353354

354355
_, err = f.WriteString(firstTs)
355356
if err != nil {
@@ -360,9 +361,9 @@ func createTimelineFile(firstTs string) error {
360361
}
361362

362363
func updateTimelineFile(lastTs string) error {
363-
f, err := os.OpenFile(timelinePath, os.O_RDWR, 0o644)
364+
f, err := os.OpenFile(naming.TimelinePath, os.O_RDWR, 0o644)
364365
if err != nil {
365-
return errors.Wrapf(err, "open %s", timelinePath)
366+
return errors.Wrapf(err, "open %s", naming.TimelinePath)
366367
}
367368
defer f.Close()
368369

@@ -373,7 +374,7 @@ func updateTimelineFile(lastTs string) error {
373374
}
374375

375376
if err := scanner.Err(); err != nil {
376-
return errors.Wrapf(err, "scan %s", timelinePath)
377+
return errors.Wrapf(err, "scan %s", naming.TimelinePath)
377378
}
378379

379380
if len(lines) > 1 {
@@ -383,11 +384,11 @@ func updateTimelineFile(lastTs string) error {
383384
}
384385

385386
if _, err := f.Seek(0, 0); err != nil {
386-
return errors.Wrapf(err, "seek %s", timelinePath)
387+
return errors.Wrapf(err, "seek %s", naming.TimelinePath)
387388
}
388389

389390
if err := f.Truncate(0); err != nil {
390-
return errors.Wrapf(err, "truncate %s", timelinePath)
391+
return errors.Wrapf(err, "truncate %s", naming.TimelinePath)
391392
}
392393

393394
_, err = f.WriteString(strings.Join(lines, "\n"))
@@ -575,7 +576,7 @@ func (c *Collector) CollectBinLogs(ctx context.Context) error {
575576
return nil
576577
}
577578

578-
if exists, err := fileExists(timelinePath); !exists && err == nil {
579+
if exists, err := fileExists(naming.TimelinePath); !exists && err == nil {
579580
firstTs, err := c.db.GetBinLogFirstTimestamp(ctx, binlogList[0].Name)
580581
if err != nil {
581582
return errors.Wrap(err, "get first timestamp")
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: pxc.percona.com/v1
2+
kind: PerconaXtraDBClusterRestore
3+
metadata:
4+
name: restore-on-pitr-minio-gap-bs
5+
spec:
6+
pxcCluster: pitr-gap-errors
7+
pitr:
8+
type: latest
9+
backupSource:
10+
storageName: "minio-binlogs"
11+
backupSource:
12+
destination: #destination
13+
s3:
14+
bucket: operator-testing
15+
credentialsSecret: minio-secret
16+
endpointUrl: https://minio-service.#namespace:9000/
17+
region: us-east-1
18+
verifyTLS: false
19+

e2e-tests/pitr-gap-errors/run

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,21 @@ check_binlog_gap_restore() {
136136
exit 1
137137
fi
138138
kubectl_bin delete -f "$test_dir/conf/${restore}.yaml"
139+
elif [ "$type" == "backup-source" ]; then
140+
local restore=restore-on-pitr-minio-gap-bs
141+
destination=$(kubectl_bin get pxc-backup on-pitr-minio-gap -o jsonpath='{.status.destination}')
142+
cat "${test_dir}/conf/${restore}.yaml" \
143+
| $sed -e "s~#destination~${destination}~" \
144+
| $sed -e "s~minio-service.#namespace~minio-service.${namespace}~" \
145+
| kubectl_bin apply -f -
146+
147+
wait_backup_restore "${restore}" "Failed"
148+
local backup_error=$(kubectl_bin get pxc-restore ${restore} -ojsonpath='{.status.comments}' | grep -c "Backup doesn't guarantee consistent recovery with PITR. Annotate PerconaXtraDBClusterRestore with percona.com/unsafe-pitr to force it.")
149+
if [[ $backup_error -eq 0 ]]; then
150+
echo "ERROR: Backup does not have pitr-not-ready file in the storage."
151+
exit 1
152+
fi
153+
kubectl_bin delete -f "$test_dir/conf/${restore}.yaml"
139154
elif [ "$type" == "force" ]; then
140155
local restore=restore-on-pitr-minio-gap-force
141156
kubectl_bin apply -f "$test_dir/conf/${restore}.yaml"
@@ -354,6 +369,7 @@ main() {
354369
run_backup "$cluster" "on-pitr-minio-gap"
355370
create_binlog_gap
356371
check_binlog_gap_error
372+
check_binlog_gap_restore "backup-source"
357373
check_binlog_gap_restore "error"
358374
check_binlog_gap_restore "force"
359375
check_binlog_gap_restore "no-pitr"

pkg/controller/pxc/controller.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
"github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app"
4242
"github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app/statefulset"
4343
"github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/backup"
44+
"github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/backup/storage"
4445
"github.com/percona/percona-xtradb-cluster-operator/pkg/util"
4546
"github.com/percona/percona-xtradb-cluster-operator/pkg/version"
4647
)
@@ -462,7 +463,7 @@ func (r *ReconcilePerconaXtraDBCluster) Reconcile(ctx context.Context, request r
462463
return reconcile.Result{}, err
463464
}
464465

465-
err = backup.CheckPITRErrors(ctx, r.client, r.clientcmd, o)
466+
err = backup.CheckPITRErrors(ctx, r.client, r.clientcmd, o, storage.NewClient)
466467
if err != nil {
467468
return reconcile.Result{}, err
468469
}

pkg/controller/pxcrestore/controller.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import (
88
"github.com/pkg/errors"
99
batchv1 "k8s.io/api/batch/v1"
1010
k8serrors "k8s.io/apimachinery/pkg/api/errors"
11-
"k8s.io/apimachinery/pkg/api/meta"
12-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1311
"k8s.io/apimachinery/pkg/runtime"
1412
"k8s.io/apimachinery/pkg/types"
1513
k8sretry "k8s.io/client-go/util/retry"
@@ -266,17 +264,22 @@ func (r *ReconcilePerconaXtraDBClusterRestore) reconcileStateNew(ctx context.Con
266264
}
267265

268266
if cr.Spec.PITR != nil {
269-
if err := backup.CheckPITRErrors(ctx, r.client, r.clientcmd, cluster); err != nil {
267+
if err := backup.CheckPITRErrors(ctx, r.client, r.clientcmd, cluster, r.newStorageClientFunc); err != nil {
270268
return reconcile.Result{}, err
271269
}
272270

273271
annotations := cr.GetAnnotations()
274272
_, unsafePITR := annotations[api.AnnotationUnsafePITR]
275-
cond := meta.FindStatusCondition(bcp.Status.Conditions, api.BackupConditionPITRReady)
276-
if cond != nil && cond.Status == metav1.ConditionFalse && !unsafePITR {
277-
cr.Status.Comments = fmt.Sprintf("Backup doesn't guarantee consistent recovery with PITR. Annotate PerconaXtraDBClusterRestore with %s to force it.", api.AnnotationUnsafePITR)
278-
cr.Status.State = api.RestoreFailed
279-
return reconcile.Result{}, nil
273+
if !unsafePITR {
274+
ready, err := r.isPITRReady(ctx, cluster, bcp)
275+
if err != nil {
276+
return reconcile.Result{}, errors.Wrap(err, "is pitr ready")
277+
}
278+
if !ready {
279+
cr.Status.Comments = fmt.Sprintf("Backup doesn't guarantee consistent recovery with PITR. Annotate PerconaXtraDBClusterRestore with %s to force it.", api.AnnotationUnsafePITR)
280+
cr.Status.State = api.RestoreFailed
281+
return reconcile.Result{}, nil
282+
}
280283
}
281284
}
282285

pkg/controller/pxcrestore/util.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ import (
77
"github.com/pkg/errors"
88
batchv1 "k8s.io/api/batch/v1"
99
corev1 "k8s.io/api/core/v1"
10+
"k8s.io/apimachinery/pkg/api/meta"
1011
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1112
"k8s.io/apimachinery/pkg/types"
1213
"sigs.k8s.io/controller-runtime/pkg/client"
1314

1415
api "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1"
16+
"github.com/percona/percona-xtradb-cluster-operator/pkg/naming"
17+
"github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/backup/storage"
1518
)
1619

1720
func getBackup(ctx context.Context, cl client.Client, cr *api.PerconaXtraDBClusterRestore) (*api.PerconaXtraDBClusterBackup, error) {
@@ -95,3 +98,32 @@ func isJobFinished(checkJob *batchv1.Job) (bool, error) {
9598
}
9699
return false, nil
97100
}
101+
102+
func (r *ReconcilePerconaXtraDBClusterRestore) isPITRReady(ctx context.Context, cluster *api.PerconaXtraDBCluster, bcp *api.PerconaXtraDBClusterBackup) (bool, error) {
103+
cond := meta.FindStatusCondition(bcp.Status.Conditions, api.BackupConditionPITRReady)
104+
if cond != nil && cond.Status == metav1.ConditionFalse {
105+
return false, nil
106+
}
107+
108+
opts, err := storage.GetOptionsFromBackup(ctx, r.client, cluster, bcp)
109+
if err != nil {
110+
return false, errors.Wrap(err, "failed to get storage options")
111+
}
112+
113+
stg, err := r.newStorageClientFunc(ctx, opts)
114+
if err != nil {
115+
return false, errors.Wrap(err, "new storage")
116+
}
117+
118+
filepath := bcp.Status.Destination.BackupName() + "." + naming.PITRNotReady
119+
objReader, err := stg.GetObject(ctx, filepath)
120+
if err == nil {
121+
objReader.Close()
122+
return false, nil
123+
}
124+
if errors.Is(err, storage.ErrObjectNotFound) {
125+
return true, nil
126+
}
127+
128+
return false, errors.Wrap(err, "get pitr-not-ready file from storage")
129+
}

pkg/naming/filepaths.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,10 @@ const (
55
BackupStorageCAFileDirectory = "/etc/s3/certs"
66
BackupStorageCAFileName = "ca.crt"
77
)
8+
9+
const (
10+
PITRNotReady = "pitr-not-ready"
11+
GapDetected = "/tmp/gap-detected"
12+
TimelinePath = "/tmp/pitr-timeline" // path to file with timeline
13+
LatestBackupPath = "/tmp/latest-backup"
14+
)

pkg/pxc/app/binlogcollector/binlog-collector.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ var GapFileNotFound = errors.New("gap file not found")
378378

379379
func RemoveGapFile(c *clientcmd.Client, pod *corev1.Pod) error {
380380
stderrBuf := &bytes.Buffer{}
381-
err := c.Exec(pod, "pitr", []string{"/bin/bash", "-c", "rm /tmp/gap-detected"}, nil, nil, stderrBuf, false)
381+
err := c.Exec(pod, "pitr", []string{"/bin/bash", "-c", "rm " + naming.GapDetected}, nil, nil, stderrBuf, false)
382382
if err != nil {
383383
if strings.Contains(stderrBuf.String(), "No such file or directory") {
384384
return GapFileNotFound
@@ -424,5 +424,4 @@ func InvalidateCache(
424424
"file", gtidCacheKey)
425425

426426
return stg.DeleteObject(ctx, gtidCacheKey)
427-
428427
}

pkg/pxc/backup/pitr.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import (
2121
api "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1"
2222
"github.com/percona/percona-xtradb-cluster-operator/pkg/naming"
2323
"github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app/binlogcollector"
24+
"github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/backup/storage"
2425
)
2526

26-
func CheckPITRErrors(ctx context.Context, cl client.Client, clcmd *clientcmd.Client, cr *api.PerconaXtraDBCluster) error {
27+
func CheckPITRErrors(ctx context.Context, cl client.Client, clcmd *clientcmd.Client, cr *api.PerconaXtraDBCluster, storageFunc storage.NewClientFunc) error {
2728
log := logf.FromContext(ctx)
2829

2930
if cr.Spec.Backup == nil || !cr.Spec.Backup.PITR.Enabled {
@@ -64,7 +65,7 @@ func CheckPITRErrors(ctx context.Context, cl client.Client, clcmd *clientcmd.Cli
6465
stdoutBuf := &bytes.Buffer{}
6566
stderrBuf := &bytes.Buffer{}
6667

67-
err = clcmd.Exec(collectorPod, "pitr", []string{"/bin/bash", "-c", "cat /tmp/gap-detected || true"}, nil, stdoutBuf, stderrBuf, false)
68+
err = clcmd.Exec(collectorPod, "pitr", []string{"/bin/bash", "-c", "cat " + naming.GapDetected + " || true"}, nil, stdoutBuf, stderrBuf, false)
6869
if err != nil {
6970
return errors.Wrapf(err, "exec binlog collector pod %s", collectorPod.Name)
7071
}
@@ -85,6 +86,10 @@ func CheckPITRErrors(ctx context.Context, cl client.Client, clcmd *clientcmd.Cli
8586
}
8687
meta.SetStatusCondition(&backup.Status.Conditions, condition)
8788

89+
if err := addPitrNotReadyFileToBackup(ctx, cl, cr, backup, storageFunc); err != nil {
90+
return errors.Wrap(err, "add pitr-not-ready file")
91+
}
92+
8893
if err := cl.Status().Update(ctx, backup); err != nil {
8994
return errors.Wrap(err, "update backup status")
9095
}
@@ -164,6 +169,24 @@ func UpdatePITRTimeline(ctx context.Context, cl client.Client, clcmd *clientcmd.
164169
return nil
165170
}
166171

172+
func addPitrNotReadyFileToBackup(ctx context.Context, cl client.Client, cr *api.PerconaXtraDBCluster, backup *api.PerconaXtraDBClusterBackup, storageFunc storage.NewClientFunc) error {
173+
opts, err := storage.GetOptionsFromBackup(ctx, cl, cr, backup)
174+
if err != nil {
175+
return errors.Wrap(err, "failed to get storage options")
176+
}
177+
scli, err := storageFunc(ctx, opts)
178+
if err != nil {
179+
return errors.Wrap(err, "failed to create storage client")
180+
}
181+
182+
filepath := backup.Status.Destination.BackupName() + "." + naming.PITRNotReady
183+
if err = scli.PutObject(ctx, filepath, bytes.NewBuffer([]byte{}), 0); err != nil {
184+
return errors.Wrap(err, "put pitr-not-ready object")
185+
}
186+
187+
return nil
188+
}
189+
167190
var ErrNoBackups = errors.New("No backups found")
168191

169192
func getLatestSuccessfulBackup(ctx context.Context, cl client.Client, cr *api.PerconaXtraDBCluster) (*api.PerconaXtraDBClusterBackup, error) {

pkg/pxc/backup/storage/storage.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ func (s *S3) GetObject(ctx context.Context, objectName string) (io.ReadCloser, e
135135

136136
// minio client returns error only on Read() method, so we need to call it to see if object exists
137137
_, err = oldObj.Read([]byte{})
138-
if err != nil {
138+
if err != nil && err != io.EOF {
139139
if minio.ToErrorResponse(errors.Cause(err)).Code == "NoSuchKey" {
140140
return nil, ErrObjectNotFound
141141
}

0 commit comments

Comments
 (0)