Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Change Log

## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A)
- (Feature) ArangoBackup create retries and MaxIterations limit

## [1.2.27](https://github.com/arangodb/kube-arangodb/tree/1.2.27) (2023-04-27)
- (Feature) Add InSync Cache
Expand Down
4 changes: 3 additions & 1 deletion pkg/apis/backup/v1/backup_spec_backoff.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -29,6 +29,8 @@ type ArangoBackupSpecBackOff struct {
MaxDelay *int `json:"max_delay,omitempty"`
// Iterations defines number of iterations before reaching MaxDelay. Default to 5
Iterations *int `json:"iterations,omitempty"`
// MaxIterations defines maximum number of iterations after backoff will be disabled. Default to nil (no limit)
MaxIterations *int `json:"max_iterations,omitempty"`
}

func (a *ArangoBackupSpecBackOff) GetMaxDelay() int {
Expand Down
6 changes: 4 additions & 2 deletions pkg/apis/backup/v1/backup_state.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -34,6 +34,7 @@ const (
ArangoBackupStateDownloadError state.State = "DownloadError"
ArangoBackupStateDownloading state.State = "Downloading"
ArangoBackupStateCreate state.State = "Create"
ArangoBackupStateCreateError state.State = "CreateError"
ArangoBackupStateUpload state.State = "Upload"
ArangoBackupStateUploading state.State = "Uploading"
ArangoBackupStateUploadError state.State = "UploadError"
Expand All @@ -50,7 +51,8 @@ var ArangoBackupStateMap = state.Map{
ArangoBackupStateDownload: {ArangoBackupStateDownloading, ArangoBackupStateFailed, ArangoBackupStateDownloadError},
ArangoBackupStateDownloading: {ArangoBackupStateReady, ArangoBackupStateFailed, ArangoBackupStateDownloadError},
ArangoBackupStateDownloadError: {ArangoBackupStatePending, ArangoBackupStateFailed},
ArangoBackupStateCreate: {ArangoBackupStateReady, ArangoBackupStateFailed},
ArangoBackupStateCreate: {ArangoBackupStateReady, ArangoBackupStateFailed, ArangoBackupStateCreateError},
ArangoBackupStateCreateError: {ArangoBackupStateFailed, ArangoBackupStateCreate},
ArangoBackupStateUpload: {ArangoBackupStateUploading, ArangoBackupStateFailed, ArangoBackupStateDeleted, ArangoBackupStateUploadError},
ArangoBackupStateUploading: {ArangoBackupStateReady, ArangoBackupStateFailed, ArangoBackupStateUploadError},
ArangoBackupStateUploadError: {ArangoBackupStateFailed, ArangoBackupStateReady},
Expand Down
14 changes: 13 additions & 1 deletion pkg/apis/backup/v1/backup_status_backoff.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -51,7 +51,19 @@ func (a *ArangoBackupStatusBackOff) GetNext() meta.Time {
return a.Next
}

func (a *ArangoBackupStatusBackOff) ShouldBackoff(spec *ArangoBackupSpecBackOff) bool {
return spec == nil || spec.MaxIterations == nil || a.GetIterations() < *spec.MaxIterations
}

func (a *ArangoBackupStatusBackOff) Backoff(spec *ArangoBackupSpecBackOff) *ArangoBackupStatusBackOff {
if !a.ShouldBackoff(spec) {
// Do not backoff anymore
return &ArangoBackupStatusBackOff{
Iterations: a.GetIterations(),
Next: a.GetNext(),
}
}

return &ArangoBackupStatusBackOff{
Iterations: a.GetIterations() + 1,
Next: meta.Time{Time: time.Now().Add(spec.Backoff(a.GetIterations()))},
Expand Down
21 changes: 20 additions & 1 deletion pkg/apis/backup/v1/backup_status_backoff_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -25,6 +25,8 @@ import (
"time"

"github.com/stretchr/testify/require"

"github.com/arangodb/kube-arangodb/pkg/util"
)

func TestArangoBackupStatusBackOff_Backoff(t *testing.T) {
Expand All @@ -37,4 +39,21 @@ func TestArangoBackupStatusBackOff_Backoff(t *testing.T) {
require.Equal(t, 1, n.GetIterations())
require.True(t, n.GetNext().After(time.Now().Add(time.Duration(9.9*float64(time.Second)))))
})

t.Run("Test MaxIterations", func(t *testing.T) {
var spec = &ArangoBackupSpecBackOff{
Iterations: util.NewInt(2),
MaxIterations: util.NewInt(3),
}
var status *ArangoBackupStatusBackOff

n := status.Backoff(spec)
require.Equal(t, 1, n.GetIterations())
require.True(t, n.ShouldBackoff(spec))

n.Iterations = 3
n = n.Backoff(spec)
require.Equal(t, 3, n.GetIterations())
require.False(t, n.ShouldBackoff(spec))
})
}
5 changes: 5 additions & 0 deletions pkg/apis/backup/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pkg/handlers/backup/state.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -33,6 +33,7 @@ var (
backupApi.ArangoBackupStatePending: statePendingHandler,
backupApi.ArangoBackupStateScheduled: stateScheduledHandler,
backupApi.ArangoBackupStateCreate: stateCreateHandler,
backupApi.ArangoBackupStateCreateError: stateCreateErrorHandler,
backupApi.ArangoBackupStateUpload: stateUploadHandler,
backupApi.ArangoBackupStateUploading: stateUploadingHandler,
backupApi.ArangoBackupStateUploadError: stateUploadErrorHandler,
Expand Down
31 changes: 29 additions & 2 deletions pkg/handlers/backup/state_create.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -21,6 +21,8 @@
package backup

import (
"time"

"github.com/arangodb/go-driver"

backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1"
Expand All @@ -39,7 +41,12 @@ func stateCreateHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.

response, err := client.Create()
if err != nil {
return nil, err
return wrapUpdateStatus(backup,
updateStatusState(backupApi.ArangoBackupStateCreateError, "Create failed with error: %s", err.Error()),
cleanStatusJob(),
updateStatusAvailable(false),
addBackOff(backup.Spec),
)
}

backupMeta, err := client.Get(response.ID)
Expand All @@ -59,5 +66,25 @@ func stateCreateHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.
updateStatusState(backupApi.ArangoBackupStateReady, ""),
updateStatusAvailable(true),
updateStatusBackup(backupMeta),
cleanBackOff(),
)
}

func stateCreateErrorHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) {
// no more retries - move to failed state
if !backup.Status.Backoff.ShouldBackoff(backup.Spec.Backoff) {
return wrapUpdateStatus(backup,
updateStatusState(backupApi.ArangoBackupStateFailed, "out of Create retries"),
cleanStatusJob())
}

// if we should retry - move to create state
if backup.Status.Backoff.ShouldBackoff(backup.Spec.Backoff) && !backup.Status.Backoff.GetNext().After(time.Now()) {
return wrapUpdateStatus(backup,
updateStatusState(backupApi.ArangoBackupStateCreate, ""),
cleanStatusJob())
}

// no ready to retry - wait (do not change state)
return wrapUpdateStatus(backup)
}
75 changes: 60 additions & 15 deletions pkg/handlers/backup/state_create_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -22,8 +22,10 @@ package backup

import (
"testing"
"time"

"github.com/stretchr/testify/require"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/arangodb/go-driver"

Expand Down Expand Up @@ -120,7 +122,7 @@ func Test_State_Create_Upload(t *testing.T) {
compareBackupMeta(t, backupMeta, newObj)
}

func Test_State_Create_CreateFailed(t *testing.T) {
func Test_State_Create_CreateError(t *testing.T) {
// Arrange
error := newFatalErrorf("error")
handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{
Expand All @@ -137,32 +139,75 @@ func Test_State_Create_CreateFailed(t *testing.T) {

// Assert
newObj := refreshArangoBackup(t, handler, obj)
require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateFailed)
require.Equal(t, newObj.Status.Message, createStateMessage(backupApi.ArangoBackupStateCreate, backupApi.ArangoBackupStateFailed, error.Error()))

require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateCreateError)
require.Nil(t, newObj.Status.Backup)

require.False(t, newObj.Status.Available)
}

func Test_State_Create_TemporaryCreateFailed(t *testing.T) {
func Test_State_CreateError_Retry(t *testing.T) {
// Arrange
error := newTemporaryErrorf("error")
handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{
createError: error,
})
handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{})

obj, deployment := newObjectSet(backupApi.ArangoBackupStateCreate)
obj, deployment := newObjectSet(backupApi.ArangoBackupStateCreateError)

backupMeta, err := mock.Create()
require.NoError(t, err)

obj.Status.Backup = &backupApi.ArangoBackupDetails{
ID: string(backupMeta.ID),
Version: backupMeta.Version,
CreationTimestamp: meta.Now(),
}

obj.Status.Time.Time = time.Now().Add(-2 * downloadDelay)

// Act
createArangoDeployment(t, handler, deployment)
createArangoBackup(t, handler, obj)

err := handler.Handle(newItemFromBackup(operation.Update, obj))
require.EqualError(t, err, error.Error())
require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj)))

// Assert
newObj := refreshArangoBackup(t, handler, obj)
require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateCreate)
require.False(t, newObj.Status.Available)
require.NotNil(t, newObj.Status.Backup)
require.Equal(t, obj.Status.Backup, newObj.Status.Backup)
}

func Test_State_CreateError_Transfer_To_Failed(t *testing.T) {
// Arrange
handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{})

require.Equal(t, obj.Status, newObj.Status)
obj, deployment := newObjectSet(backupApi.ArangoBackupStateCreateError)

backupMeta, err := mock.Create()
require.NoError(t, err)

obj.Status.Backup = &backupApi.ArangoBackupDetails{
ID: string(backupMeta.ID),
Version: backupMeta.Version,
CreationTimestamp: meta.Now(),
}
obj.Status.Backoff = &backupApi.ArangoBackupStatusBackOff{
Iterations: 2,
}

obj.Spec.Backoff = &backupApi.ArangoBackupSpecBackOff{
Iterations: util.NewInt(1),
MaxIterations: util.NewInt(2),
}

obj.Status.Time.Time = time.Now().Add(-2 * downloadDelay)

// Act
createArangoDeployment(t, handler, deployment)
createArangoBackup(t, handler, obj)

require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj)))

// Assert
newObj := refreshArangoBackup(t, handler, obj)
require.Equal(t, newObj.Status.State, backupApi.ArangoBackupStateFailed)
require.Equal(t, newObj.Status.Message, "out of Create retries")
}
14 changes: 12 additions & 2 deletions pkg/handlers/backup/state_uploaderror.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -27,13 +27,23 @@ import (
)

func stateUploadErrorHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) {
if backup.Spec.Upload == nil || !backup.Status.Backoff.GetNext().After(time.Now()) {
// no more retries - move to failed state
if !backup.Status.Backoff.ShouldBackoff(backup.Spec.Backoff) {
return wrapUpdateStatus(backup,
updateStatusState(backupApi.ArangoBackupStateFailed, "out of Upload retries"),
cleanStatusJob())
}

// if we should retry - move to ready state
if backup.Spec.Upload == nil ||
(backup.Status.Backoff.ShouldBackoff(backup.Spec.Backoff) && !backup.Status.Backoff.GetNext().After(time.Now())) {
return wrapUpdateStatus(backup,
updateStatusState(backupApi.ArangoBackupStateReady, ""),
cleanStatusJob(),
updateStatusAvailable(true))
}

// no ready to retry - wait (do not change state)
return wrapUpdateStatus(backup,
updateStatusAvailable(true))
}