Skip to content

Commit d384848

Browse files
authored
K8SPS-257: fix backup deletion in error state (#383)
https://jira.percona.com/browse/K8SPS-257
1 parent 2098af8 commit d384848

File tree

2 files changed

+179
-8
lines changed

2 files changed

+179
-8
lines changed

pkg/controller/psbackup/controller.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,9 @@ func (r *PerconaServerMySQLBackupReconciler) getBackupSource(ctx context.Context
337337
return source, nil
338338
}
339339

340-
func (r *PerconaServerMySQLBackupReconciler) checkFinalizers(ctx context.Context,
341-
cr *apiv1alpha1.PerconaServerMySQLBackup) {
340+
const finalizerDeleteBackup = "delete-backup"
341+
342+
func (r *PerconaServerMySQLBackupReconciler) checkFinalizers(ctx context.Context, cr *apiv1alpha1.PerconaServerMySQLBackup) {
342343
if cr.DeletionTimestamp == nil || cr.Status.State == apiv1alpha1.BackupStarting || cr.Status.State == apiv1alpha1.BackupRunning {
343344
return
344345
}
@@ -350,7 +351,8 @@ func (r *PerconaServerMySQLBackupReconciler) checkFinalizers(ctx context.Context
350351
}
351352
}()
352353

353-
if cr.Status.State == apiv1alpha1.BackupNew {
354+
switch cr.Status.State {
355+
case apiv1alpha1.BackupError, apiv1alpha1.BackupNew:
354356
cr.Finalizers = nil
355357
return
356358
}
@@ -359,7 +361,7 @@ func (r *PerconaServerMySQLBackupReconciler) checkFinalizers(ctx context.Context
359361
for _, finalizer := range cr.GetFinalizers() {
360362
var err error
361363
switch finalizer {
362-
case "delete-backup":
364+
case finalizerDeleteBackup:
363365
var ok bool
364366
ok, err = r.deleteBackup(ctx, cr)
365367
if !ok {
@@ -403,11 +405,11 @@ func (r *PerconaServerMySQLBackupReconciler) backupConfig(ctx context.Context, c
403405
}
404406
accessKey, ok := s.Data[secret.CredentialsAWSAccessKey]
405407
if !ok {
406-
return nil, errors.Errorf("no credentials for Azure in secret %s", nn.Name)
408+
return nil, errors.Errorf("no credentials for S3 in secret %s", nn.Name)
407409
}
408410
secretKey, ok := s.Data[secret.CredentialsAWSSecretKey]
409411
if !ok {
410-
return nil, errors.Errorf("no credentials for Azure in secret %s", nn.Name)
412+
return nil, errors.Errorf("no credentials for S3 in secret %s", nn.Name)
411413
}
412414
conf.S3.Bucket = s3.Bucket.Bucket()
413415
conf.S3.Region = s3.Region
@@ -424,11 +426,11 @@ func (r *PerconaServerMySQLBackupReconciler) backupConfig(ctx context.Context, c
424426
}
425427
accessKey, ok := s.Data[secret.CredentialsGCSAccessKey]
426428
if !ok {
427-
return nil, errors.Errorf("no credentials for Azure in secret %s", nn.Name)
429+
return nil, errors.Errorf("no credentials for GCS in secret %s", nn.Name)
428430
}
429431
secretKey, ok := s.Data[secret.CredentialsGCSSecretKey]
430432
if !ok {
431-
return nil, errors.Errorf("no credentials for Azure in secret %s", nn.Name)
433+
return nil, errors.Errorf("no credentials for GCS in secret %s", nn.Name)
432434
}
433435
conf.GCS.Bucket = gcs.Bucket.Bucket()
434436
conf.GCS.EndpointURL = gcs.EndpointURL

pkg/controller/psbackup/controller_test.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@ import (
55
"fmt"
66
"os"
77
"path/filepath"
8+
"reflect"
89
"testing"
10+
"time"
911

12+
batchv1 "k8s.io/api/batch/v1"
13+
corev1 "k8s.io/api/core/v1"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1015
"k8s.io/apimachinery/pkg/runtime"
1116
"k8s.io/apimachinery/pkg/types"
1217
"k8s.io/apimachinery/pkg/util/yaml"
@@ -16,6 +21,8 @@ import (
1621

1722
apiv1alpha1 "github.com/percona/percona-server-mysql-operator/api/v1alpha1"
1823
"github.com/percona/percona-server-mysql-operator/pkg/platform"
24+
"github.com/percona/percona-server-mysql-operator/pkg/secret"
25+
"github.com/percona/percona-server-mysql-operator/pkg/xtrabackup"
1926
)
2027

2128
func TestBackupStatusErrStateDesc(t *testing.T) {
@@ -125,6 +132,168 @@ func TestBackupStatusErrStateDesc(t *testing.T) {
125132
}
126133
}
127134

135+
func TestCheckFinalizers(t *testing.T) {
136+
ctx := context.Background()
137+
scheme := runtime.NewScheme()
138+
if err := clientgoscheme.AddToScheme(scheme); err != nil {
139+
t.Fatal(err, "failed to add client-go scheme")
140+
}
141+
if err := apiv1alpha1.AddToScheme(scheme); err != nil {
142+
t.Fatal(err, "failed to add apis scheme")
143+
}
144+
namespace := "some-namespace"
145+
cr, err := readDefaultCRBackup("some-name", namespace)
146+
if err != nil {
147+
t.Fatal(err, "failed to read default backup")
148+
}
149+
150+
tests := []struct {
151+
name string
152+
cr *apiv1alpha1.PerconaServerMySQLBackup
153+
expectedFinalizers []string
154+
finalizerJobFail bool
155+
}{
156+
{
157+
name: "without finalizers",
158+
cr: updateResource(cr.DeepCopy(), func(cr *apiv1alpha1.PerconaServerMySQLBackup) {
159+
cr.Finalizers = []string{}
160+
cr.Status.State = apiv1alpha1.BackupError
161+
}),
162+
expectedFinalizers: nil,
163+
},
164+
{
165+
name: "with finalizer and starting state",
166+
cr: updateResource(cr.DeepCopy(), func(cr *apiv1alpha1.PerconaServerMySQLBackup) {
167+
cr.Finalizers = []string{finalizerDeleteBackup}
168+
cr.Status.State = apiv1alpha1.BackupStarting
169+
}),
170+
expectedFinalizers: []string{finalizerDeleteBackup},
171+
},
172+
{
173+
name: "with finalizer and running state",
174+
cr: updateResource(cr.DeepCopy(), func(cr *apiv1alpha1.PerconaServerMySQLBackup) {
175+
cr.Finalizers = []string{finalizerDeleteBackup}
176+
cr.Status.State = apiv1alpha1.BackupRunning
177+
}),
178+
expectedFinalizers: []string{finalizerDeleteBackup},
179+
},
180+
{
181+
name: "with finalizer and error state",
182+
cr: updateResource(cr.DeepCopy(), func(cr *apiv1alpha1.PerconaServerMySQLBackup) {
183+
cr.Finalizers = []string{finalizerDeleteBackup}
184+
cr.Status.State = apiv1alpha1.BackupError
185+
}),
186+
expectedFinalizers: nil,
187+
},
188+
{
189+
name: "with finalizer and new state",
190+
cr: updateResource(cr.DeepCopy(), func(cr *apiv1alpha1.PerconaServerMySQLBackup) {
191+
cr.Finalizers = []string{finalizerDeleteBackup}
192+
cr.Status.State = apiv1alpha1.BackupNew
193+
}),
194+
expectedFinalizers: nil,
195+
},
196+
{
197+
name: "with failing finalizer and succeeded state",
198+
cr: updateResource(cr.DeepCopy(), func(cr *apiv1alpha1.PerconaServerMySQLBackup) {
199+
cr.Finalizers = []string{finalizerDeleteBackup}
200+
cr.Status.State = apiv1alpha1.BackupSucceeded
201+
}),
202+
finalizerJobFail: true,
203+
expectedFinalizers: []string{finalizerDeleteBackup},
204+
},
205+
{
206+
name: "with successful finalizer and succeeded state",
207+
cr: updateResource(cr.DeepCopy(), func(cr *apiv1alpha1.PerconaServerMySQLBackup) {
208+
cr.Finalizers = []string{finalizerDeleteBackup}
209+
cr.Status.State = apiv1alpha1.BackupSucceeded
210+
}),
211+
expectedFinalizers: []string{},
212+
},
213+
{
214+
name: "with successful finalizer, unknown finalizer and succeeded state",
215+
cr: updateResource(cr.DeepCopy(), func(cr *apiv1alpha1.PerconaServerMySQLBackup) {
216+
cr.Finalizers = []string{finalizerDeleteBackup, "unknown-finalizer"}
217+
cr.Status.State = apiv1alpha1.BackupSucceeded
218+
}),
219+
expectedFinalizers: []string{"unknown-finalizer"},
220+
},
221+
{
222+
name: "with failing finalizer and failed state",
223+
cr: updateResource(cr.DeepCopy(), func(cr *apiv1alpha1.PerconaServerMySQLBackup) {
224+
cr.Finalizers = []string{finalizerDeleteBackup}
225+
cr.Status.State = apiv1alpha1.BackupFailed
226+
}),
227+
finalizerJobFail: true,
228+
expectedFinalizers: []string{finalizerDeleteBackup},
229+
},
230+
{
231+
name: "with successful finalizer and failed state",
232+
cr: updateResource(cr.DeepCopy(), func(cr *apiv1alpha1.PerconaServerMySQLBackup) {
233+
cr.Finalizers = []string{finalizerDeleteBackup}
234+
cr.Status.State = apiv1alpha1.BackupFailed
235+
}),
236+
expectedFinalizers: []string{},
237+
},
238+
{
239+
name: "with successful finalizer, unknown finalizer and failed state",
240+
cr: updateResource(cr.DeepCopy(), func(cr *apiv1alpha1.PerconaServerMySQLBackup) {
241+
cr.Finalizers = []string{finalizerDeleteBackup, "unknown-finalizer"}
242+
cr.Status.State = apiv1alpha1.BackupFailed
243+
}),
244+
expectedFinalizers: []string{"unknown-finalizer"},
245+
},
246+
}
247+
248+
sec := &corev1.Secret{
249+
ObjectMeta: metav1.ObjectMeta{
250+
Name: "some-secret",
251+
Namespace: namespace,
252+
},
253+
Data: map[string][]byte{
254+
secret.CredentialsAWSAccessKey: []byte("access-key"),
255+
secret.CredentialsAWSSecretKey: []byte("secret-key"),
256+
},
257+
}
258+
storage := &apiv1alpha1.BackupStorageSpec{
259+
Type: apiv1alpha1.BackupStorageS3,
260+
S3: &apiv1alpha1.BackupStorageS3Spec{
261+
Bucket: "some-bucket",
262+
CredentialsSecret: "some-secret",
263+
},
264+
Labels: make(map[string]string),
265+
}
266+
267+
for _, tt := range tests {
268+
t.Run(tt.name, func(t *testing.T) {
269+
tt.cr.DeletionTimestamp = &metav1.Time{Time: time.Now()}
270+
tt.cr.Status.Storage = storage
271+
272+
job := xtrabackup.GetDeleteJob(tt.cr, new(xtrabackup.BackupConfig))
273+
cond := batchv1.JobCondition{
274+
Type: batchv1.JobComplete,
275+
Status: corev1.ConditionTrue,
276+
}
277+
if tt.finalizerJobFail {
278+
cond.Type = batchv1.JobFailed
279+
}
280+
job.Status.Conditions = append(job.Status.Conditions, cond)
281+
282+
cb := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.cr, sec, job)
283+
r := PerconaServerMySQLBackupReconciler{
284+
Client: cb.Build(),
285+
Scheme: scheme,
286+
ServerVersion: &platform.ServerVersion{Platform: platform.PlatformKubernetes},
287+
}
288+
289+
r.checkFinalizers(ctx, tt.cr)
290+
if !reflect.DeepEqual(tt.cr.Finalizers, tt.expectedFinalizers) {
291+
t.Fatalf("expected finalizers %v, got %v", tt.expectedFinalizers, tt.cr.Finalizers)
292+
}
293+
})
294+
}
295+
}
296+
128297
func readDefaultCR(name, namespace string) (*apiv1alpha1.PerconaServerMySQL, error) {
129298
data, err := os.ReadFile(filepath.Join("..", "..", "..", "deploy", "cr.yaml"))
130299
if err != nil {

0 commit comments

Comments
 (0)