1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

GT-523 Async backup (#1460)

This commit is contained in:
jwierzbo 2023-11-08 11:21:22 +01:00 committed by GitHub
parent f101f07937
commit 27d448a4e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 533 additions and 99 deletions

View file

@ -74,6 +74,14 @@ jobs:
exit 0
fi
make vulncheck
- run:
name: verify-generated docs
command: |
if [ -z "$CIRCLE_PULL_REQUEST" ]; then
echo "This is not a pull request. Skipping..."
exit 0
fi
make verify-generated
environment:
GO111MODULES: off

View file

@ -3,6 +3,7 @@
## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A)
- (Documentation) Improvements and fixes for rendered documentation (GH pages)
- (Feature) License Manager
- (Improvement) Use Async mode for backup creation
## [1.2.35](https://github.com/arangodb/kube-arangodb/tree/1.2.35) (2023-11-06)
- (Maintenance) Update go-driver to v1.6.0, update IsNotFound() checks

View file

@ -62,7 +62,8 @@ covers individual newer features separately.
[START_INJECT]: # (featuresCommunityTable)
| Feature | Operator Version | Introduced | ArangoDB Version | ArangoDB Edition | State | Enabled | Flag | Remarks |
|:------------------------------------------------------------------------------|:-----------------|:-----------|:-----------------|:----------------------|:-------------|:--------|:------------------------------------------------------|:-----------------------------------------------------------------------------------|
|:------------------------------------------------------------------------------|:-----------------|:-----------|:-----------------|:----------------------|:-------------|:--------|:------------------------------------------------------|:--------------------------------------------------------------------------------------|
| Create backups asynchronously | 1.2.35 | 1.2.35 | >= 3.8.0 | Community, Enterprise | Production | False | --deployment.feature.async-backup-creation | Create backups asynchronously to avoid blocking the operator and reaching the timeout |
| Enforced ResignLeadership | 1.2.34 | 1.2.34 | >= 3.8.0 | Community, Enterprise | Production | True | --deployment.feature.enforced-resign-leadership | Enforce ResignLeadership and ensure that Leaders are moved from restarted DBServer |
| Copy resources spec to init containers | 1.2.33 | 1.2.33 | >= 3.8.0 | Community, Enterprise | Production | True | --deployment.feature.init-containers-copy-resources | Copy resources spec to built-in init containers if they are not specified |
| [Rebalancer V2](docs/features/rebalancer_v2.md) | 1.2.31 | 1.2.31 | >= 3.10.0 | Community, Enterprise | Alpha | False | --deployment.feature.rebalancer-v2 | N/A |

View file

@ -284,7 +284,7 @@ func createClient(endpoints []string, certCA *x509.CertPool, auth connection.Aut
conf := connection.HttpConfiguration{
Authentication: auth,
ContentType: contentType,
Endpoint: connection.NewEndpoints(endpoints...),
Endpoint: connection.NewRoundRobinEndpoints(endpoints),
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certCA,

View file

@ -91,7 +91,7 @@ This field is **immutable**: can't be changed after backup creation
### .spec.options.timeout: number
Timeout for Backup creation request in seconds.
Timeout for Backup creation request in seconds. Works only when AsyncBackupCreation feature is set to false.
Default Value: `30`
@ -197,13 +197,13 @@ Uploaded Determines if ArangoBackup has been uploaded
Message for the state this object is in.
[Code Reference](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/backup/v1/backup_state.go#L86)
[Code Reference](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/backup/v1/backup_state.go#L88)
### .status.progress.jobID: string
JobID ArangoDB job ID for uploading or downloading
[Code Reference](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/backup/v1/backup_state.go#L109)
[Code Reference](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/backup/v1/backup_state.go#L111)
### .status.progress.progress: string
@ -214,7 +214,7 @@ Example:
90%
```
[Code Reference](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/backup/v1/backup_state.go#L112)
[Code Reference](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/backup/v1/backup_state.go#L114)
### .status.state: string
@ -235,5 +235,5 @@ Possible Values:
* Failed - state for failure
* Unavailable - state when Backup is not available on the ArangoDB. It can happen in case of upgrades, node restarts etc.
[Code Reference](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/backup/v1/backup_state.go#L80)
[Code Reference](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/backup/v1/backup_state.go#L82)

View file

@ -78,7 +78,7 @@ This field is **immutable**: can't be changed after backup creation
### .spec.template.options.timeout: number
Timeout for Backup creation request in seconds.
Timeout for Backup creation request in seconds. Works only when AsyncBackupCreation feature is set to false.
Default Value: `30`

View file

@ -1,17 +1,8 @@
## List of Community Edition features
| Feature | Operator Version | Introduced | ArangoDB Version | ArangoDB Edition | State | Enabled | Flag | Remarks |
|:-----------------------------------------------------|:-----------------|:-----------|:-----------------|:-----------------|:-----------|:--------|:-----|:----------------------------------------------------------------------------|
| AgencyCache | 1.2.30 | 1.2.30 | >= 3.8.0 | Enterprise | Production | True | N/A | Enable Agency Cache mechanism in the Operator (Increase limit of the nodes) |
| Member Maintenance Support | 1.2.25 | 1.2.16 | >= 3.8.0 | Enterprise | Production | True | N/A | Enable Member Maintenance during planned restarts |
| [Rebalancer](rebalancer.md) | 1.2.15 | 1.2.5 | >= 3.8.0 | Enterprise | Production | True | N/A | N/A |
| [TopologyAwareness](../design/topology_awareness.md) | 1.2.4 | 1.2.4 | >= 3.8.0 | Enterprise | Production | True | N/A | N/A |
## List of Enterprise Edition features
| Feature | Operator Version | Introduced | ArangoDB Version | ArangoDB Edition | State | Enabled | Flag | Remarks |
|:----------------------------------------------------------------|:-----------------|:-----------|:-----------------|:----------------------|:-------------|:--------|:------------------------------------------------------|:-----------------------------------------------------------------------------------|
|:----------------------------------------------------------------|:-----------------|:-----------|:-----------------|:----------------------|:-------------|:--------|:------------------------------------------------------|:--------------------------------------------------------------------------------------|
| Create backups asynchronously | 1.2.35 | 1.2.35 | >= 3.8.0 | Community, Enterprise | Production | False | --deployment.feature.async-backup-creation | Create backups asynchronously to avoid blocking the operator and reaching the timeout |
| Enforced ResignLeadership | 1.2.34 | 1.2.34 | >= 3.8.0 | Community, Enterprise | Production | True | --deployment.feature.enforced-resign-leadership | Enforce ResignLeadership and ensure that Leaders are moved from restarted DBServer |
| Copy resources spec to init containers | 1.2.33 | 1.2.33 | >= 3.8.0 | Community, Enterprise | Production | True | --deployment.feature.init-containers-copy-resources | Copy resources spec to built-in init containers if they are not specified |
| [Rebalancer V2](rebalancer_v2.md) | 1.2.31 | 1.2.31 | >= 3.10.0 | Community, Enterprise | Alpha | False | --deployment.feature.rebalancer-v2 | N/A |
@ -39,3 +30,13 @@
| Volume Resizing | 0.3.11 | 0.3.10 | >= 3.8.0 | Community, Enterprise | Production | True | N/A | N/A |
## List of Enterprise Edition features
| Feature | Operator Version | Introduced | ArangoDB Version | ArangoDB Edition | State | Enabled | Flag | Remarks |
|:-----------------------------------------------------|:-----------------|:-----------|:-----------------|:-----------------|:-----------|:--------|:-----|:----------------------------------------------------------------------------|
| AgencyCache | 1.2.30 | 1.2.30 | >= 3.8.0 | Enterprise | Production | True | N/A | Enable Agency Cache mechanism in the Operator (Increase limit of the nodes) |
| Member Maintenance Support | 1.2.25 | 1.2.16 | >= 3.8.0 | Enterprise | Production | True | N/A | Enable Member Maintenance during planned restarts |
| [Rebalancer](rebalancer.md) | 1.2.15 | 1.2.5 | >= 3.8.0 | Enterprise | Production | True | N/A | N/A |
| [TopologyAwareness](../design/topology_awareness.md) | 1.2.4 | 1.2.4 | >= 3.8.0 | Enterprise | Production | True | N/A | N/A |

View file

@ -2,9 +2,9 @@
## Overview
ArangoDB as of 3.10.0 provide Cluster Rebalancer functionality via [api](https://www.arangodb.com/docs/stable/http/cluster.html#rebalance).
ArangoDB as of 3.10.0 provides Cluster Rebalancer functionality via [api](https://www.arangodb.com/docs/stable/http/cluster.html#rebalance).
Operator will use above functionality to check shard movement plan and enforce it on the Cluster.
Operator will use the above functionality to check shard movement plan and enforce it on the Cluster.
## How to use

6
go.mod
View file

@ -25,8 +25,8 @@ require (
github.com/arangodb-helper/go-certificates v0.0.0-20180821055445-9fca24fc2680
github.com/arangodb-helper/go-helper v0.4.2
github.com/arangodb/arangosync-client v0.9.0
github.com/arangodb/go-driver v1.6.0
github.com/arangodb/go-driver/v2 v2.0.0-20230616090327-3b9171814ae4
github.com/arangodb/go-driver v1.6.1
github.com/arangodb/go-driver/v2 v2.0.3
github.com/arangodb/go-upgrade-rules v0.0.0-20180809110947-031b4774ff21
//github.com/arangodb/rebalancer v0.1.1
//github.com/arangodb/go-agency-helper v0.3.0
@ -130,4 +130,6 @@ require (
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
github.com/dchest/siphash v1.2.2 // indirect
github.com/kkdai/maglev v0.2.0 // indirect
)

10
go.sum
View file

@ -81,8 +81,13 @@ github.com/arangodb/go-agency-helper v0.4.0/go.mod h1:IMzQ1JilLu764DgFQ1qh21jPEz
github.com/arangodb/go-driver v1.2.1/go.mod h1:zdDkJJnCj8DAkfbtIjIXnsTrWIiy6VhP3Vy14p+uQeY=
github.com/arangodb/go-driver v1.6.0 h1:NFWj/idqXZxhFVueihMSI2R9NotNIsgvNfM/xmpekb4=
github.com/arangodb/go-driver v1.6.0/go.mod h1:HQmdGkvNMVBTE3SIPSQ8T/ZddC6iwNsfMR+dDJQxIsI=
github.com/arangodb/go-driver v1.6.1-0.20231025111232-5473b2e39799/go.mod h1:F1fFEduYiEUxeb+JioB6aMqPt0CRUcgtSwGZHhNeeLg=
github.com/arangodb/go-driver v1.6.1 h1:bnhrpbA4U1NU13JOWs5sWWYMtQwdjKT0+jkl8dSndyY=
github.com/arangodb/go-driver v1.6.1/go.mod h1:ywucwwi34GBxxXFWw/ym+7/66//L4K9abxk/sFJro2k=
github.com/arangodb/go-driver/v2 v2.0.0-20230616090327-3b9171814ae4 h1:LpIIPBcrbZ/mVDG2ioZN92Pbgb5HQ2Vnqj/UaQAyN4E=
github.com/arangodb/go-driver/v2 v2.0.0-20230616090327-3b9171814ae4/go.mod h1:bnCd24JXOgd5rpG8uwSpHPsF6NCcH4m6rKtKh1EJ7Lo=
github.com/arangodb/go-driver/v2 v2.0.3 h1:B/tKSgf4KSiLN0biqcH8Tm/Dj8nZdP5Lia2/xdy8KD0=
github.com/arangodb/go-driver/v2 v2.0.3/go.mod h1:iibpBwIQbE4uejDFCvLqwCVfgE72F51ZEehPme+BAug=
github.com/arangodb/go-upgrade-rules v0.0.0-20180809110947-031b4774ff21 h1:+W7D5ttxi/Ygh/39vialtypE23p9KI7P0J2qtoqUV4w=
github.com/arangodb/go-upgrade-rules v0.0.0-20180809110947-031b4774ff21/go.mod h1:RkPIG6JJ2pcJUoymc18NxAJGraZd+iAEVnOTDjZey/w=
github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e h1:Xg+hGrY2LcQBbxd0ZFdbGSyRKTYMZCfBbw/pMJFOk1g=
@ -155,6 +160,8 @@ github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/siphash v1.2.2 h1:9DFz8tQwl9pTVt5iok/9zKyzA1Q6bRGiF3HPiEEVr9I=
github.com/dchest/siphash v1.2.2/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9 h1:74lLNRzvsdIlkTgfDSMuaPjBr4cf6k7pwQQANm/yLKU=
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@ -199,6 +206,7 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@ -384,6 +392,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/maglev v0.2.0 h1:w6DCW0kAA6fstZqXkrBrlgIC3jeIRXkjOYea/m6EK/Y=
github.com/kkdai/maglev v0.2.0/go.mod h1:d+mt8Lmt3uqi9aRb/BnPjzD0fy+ETs1vVXiGRnqHVZ4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=

View file

@ -227,3 +227,10 @@ features:
releases:
- operatorVersion: 1.2.34
state: Production
- name: Create backups asynchronously
enabled: false
remarks: Create backups asynchronously to avoid blocking the operator and reaching the timeout
flag: --deployment.feature.async-backup-creation
releases:
- operatorVersion: 1.2.35
state: Production

View file

@ -42,13 +42,13 @@ func Test_GenerateFeaturesIndex(t *testing.T) {
const basePath = "docs/features"
write(t, out, "## List of Community Edition features\n")
section, err := GenerateReadmeFeatures(root, basePath, true)
section, err := GenerateReadmeFeatures(root, basePath, false)
require.NoError(t, err)
write(t, out, section)
write(t, out, "\n")
write(t, out, "## List of Enterprise Edition features\n")
section, err = GenerateReadmeFeatures(root, basePath, false)
section, err = GenerateReadmeFeatures(root, basePath, true)
require.NoError(t, err)
write(t, out, section)
write(t, out, "\n")

View file

@ -55,7 +55,7 @@ type ArangoBackupSpecDeployment struct {
}
type ArangoBackupSpecOptions struct {
// Timeout for Backup creation request in seconds.
// Timeout for Backup creation request in seconds. Works only when AsyncBackupCreation feature is set to false.
// +doc/immutable: can't be changed after backup creation
// +doc/default: 30
Timeout *float32 `json:"timeout,omitempty"`

View file

@ -34,6 +34,7 @@ const (
ArangoBackupStateDownloadError state.State = "DownloadError"
ArangoBackupStateDownloading state.State = "Downloading"
ArangoBackupStateCreate state.State = "Create"
ArangoBackupStateCreating state.State = "Creating"
ArangoBackupStateCreateError state.State = "CreateError"
ArangoBackupStateUpload state.State = "Upload"
ArangoBackupStateUploading state.State = "Uploading"
@ -51,7 +52,8 @@ var ArangoBackupStateMap = state.Map{
ArangoBackupStateDownload: {ArangoBackupStateDownloading, ArangoBackupStateFailed, ArangoBackupStateDownloadError},
ArangoBackupStateDownloading: {ArangoBackupStateReady, ArangoBackupStateFailed, ArangoBackupStateDownloadError},
ArangoBackupStateDownloadError: {ArangoBackupStatePending, ArangoBackupStateFailed},
ArangoBackupStateCreate: {ArangoBackupStateReady, ArangoBackupStateFailed, ArangoBackupStateCreateError},
ArangoBackupStateCreate: {ArangoBackupStateReady, ArangoBackupStateFailed, ArangoBackupStateCreateError, ArangoBackupStateCreating},
ArangoBackupStateCreating: {ArangoBackupStateReady, ArangoBackupStateFailed, ArangoBackupStateCreateError},
ArangoBackupStateCreateError: {ArangoBackupStateFailed, ArangoBackupStateCreate},
ArangoBackupStateUpload: {ArangoBackupStateUploading, ArangoBackupStateFailed, ArangoBackupStateDeleted, ArangoBackupStateUploadError},
ArangoBackupStateUploading: {ArangoBackupStateReady, ArangoBackupStateFailed, ArangoBackupStateUploadError},

View file

@ -0,0 +1,38 @@
//
// DISCLAIMER
//
// Copyright 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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package features
func init() {
registerFeature(asyncBackupCreation)
}
var asyncBackupCreation = &feature{
name: "async-backup-creation",
description: "Create backups asynchronously to avoid blocking the operator and reaching the timeout",
version: "3.7.0",
enterpriseRequired: false,
enabledByDefault: false,
}
// AsyncBackupCreation returns mode for backup creation (sync/async).
func AsyncBackupCreation() Feature {
return asyncBackupCreation
}

View file

@ -175,7 +175,7 @@ func (ib *imagesBuilder) fetchArangoDBImageIDAndVersion(ctx context.Context, cac
}
// Try fetching the ArangoDB version
client, err := arangod.CreateArangodImageIDClient(ctx, ib.APIObject, pod.Status.PodIP)
client, err := arangod.CreateArangodImageIDClient(ctx, ib.APIObject, pod.Status.PodIP, false)
if err != nil {
log.Err(err).Warn("Failed to create Image ID Pod client")
return true, nil

View file

@ -117,7 +117,7 @@ func (a actionBackupRestore) restoreAsync(ctx context.Context, backup *backupApi
a.actionCtx.Add(actionBackupRestoreLocalJobID, id, true)
a.actionCtx.Add(actionBackupRestoreLocalBackupName, backup.GetName(), true)
// Async request has been send
// Async request has been sent
return false, nil
} else {
return false, errors.Wrapf(err, "Unknown restore error")

View file

@ -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.
@ -59,6 +59,11 @@ type ArangoBackupCreateResponse struct {
// ArangoBackupClient interface with backup functionality for database
type ArangoBackupClient interface {
Create() (ArangoBackupCreateResponse, error)
// CreateAsync creates a new backup asynchronously and returns the job ID in the error
// pass empty string to create a new backup
CreateAsync(jobID string) (ArangoBackupCreateResponse, error)
Get(driver.BackupID) (driver.BackupMeta, error)
Upload(driver.BackupID) (driver.BackupTransferJobID, error)

View file

@ -48,7 +48,7 @@ type arangoClientBackupImpl struct {
func newArangoClientBackupFactory(handler *handler) ArangoClientFactory {
return func(deployment *database.ArangoDeployment, backup *backupApi.ArangoBackup) (ArangoBackupClient, error) {
ctx := context.Background()
client, err := arangod.CreateArangodDatabaseClient(ctx, handler.kubeClient.CoreV1(), deployment, false)
client, err := arangod.CreateArangodDatabaseClient(ctx, handler.kubeClient.CoreV1(), deployment, false, true)
if err != nil {
return nil, err
}
@ -109,6 +109,43 @@ func (ac *arangoClientBackupImpl) Create() (ArangoBackupCreateResponse, error) {
}, nil
}
func (ac *arangoClientBackupImpl) CreateAsync(jobID string) (ArangoBackupCreateResponse, error) {
dt := globals.GetGlobalTimeouts().BackupArangoClientTimeout().Get()
co := driver.BackupCreateOptions{}
if opt := ac.backup.Spec.Options; opt != nil {
if allowInconsistent := opt.AllowInconsistent; allowInconsistent != nil {
co.AllowInconsistent = *allowInconsistent
}
}
ctx, cancel := context.WithTimeout(context.Background(), dt)
defer cancel()
if jobID == "" {
ctx = driver.WithAsync(ctx)
} else {
ctx = driver.WithAsyncID(ctx, jobID)
}
id, resp, err := ac.driver.Backup().Create(ctx, &co)
if err != nil {
return ArangoBackupCreateResponse{}, err
}
// Now ask for the version
meta, err := ac.Get(id)
if err != nil {
return ArangoBackupCreateResponse{}, err
}
return ArangoBackupCreateResponse{
PotentiallyInconsistent: resp.PotentiallyInconsistent,
BackupMeta: meta,
}, nil
}
func (ac *arangoClientBackupImpl) Get(backupID driver.BackupID) (driver.BackupMeta, error) {
ctx, cancel := globals.GetGlobalTimeouts().BackupArangoClientTimeout().WithTimeout(context.Background())
defer cancel()
@ -217,11 +254,11 @@ func (ac *arangoClientBackupImpl) Progress(jobID driver.BackupTransferJobID) (Ar
case "":
completedCount++
default:
return ArangoBackupProgress{}, errors.Newf("Unknown transfere status: %s", status.Status)
return ArangoBackupProgress{}, errors.Newf("Unknown transfer status: %s", status.Status)
}
}
// Check if all defined servers are completed and total number of files is greater than 0 (there is at least 1 file per server)
// Check if all defined servers are completed and the total number of files is greater than 0 (there is at least 1 file per server)
ret.Completed = completedCount == len(report.DBServers) && total > 0
if total != 0 {
ret.Progress = (100 * done) / total

View file

@ -21,12 +21,14 @@
package backup
import (
"strconv"
"sync"
"time"
"k8s.io/apimachinery/pkg/util/uuid"
"github.com/arangodb/go-driver"
"github.com/arangodb/go-driver/util/connection/wrappers/async"
backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1"
database "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
@ -70,6 +72,7 @@ type mockArangoClientBackupState struct {
backups map[driver.BackupID]driver.BackupMeta
progresses map[driver.BackupTransferJobID]ArangoBackupProgress
createDone bool
errors mockErrorsArangoClientBackup
}
@ -227,6 +230,21 @@ func (m *mockArangoClientBackup) Create() (ArangoBackupCreateResponse, error) {
}, nil
}
func (m *mockArangoClientBackup) CreateAsync(jobID string) (ArangoBackupCreateResponse, error) {
if m.state.errors.createError != nil {
return ArangoBackupCreateResponse{}, m.state.errors.createError
}
if m.state.createDone {
return m.Create()
}
if jobID == "" {
return ArangoBackupCreateResponse{}, async.NewErrorAsyncJobInProgress(strconv.Itoa(util.Rand().Int()))
}
return ArangoBackupCreateResponse{}, async.NewErrorAsyncJobInProgress(jobID)
}
func (m *mockArangoClientBackup) getIDs() []string {
ret := make([]string, 0, len(m.state.backups))

View file

@ -75,9 +75,9 @@ func newObjectSet(state state.State) (*backupApi.ArangoBackup, *database.ArangoD
namespace := string(uuid.NewUUID())
obj := newArangoBackup(name, namespace, name, state)
deployment := newArangoDeployment(namespace, name)
arangoDeployment := newArangoDeployment(namespace, name)
return obj, deployment
return obj, arangoDeployment
}
func newItem(o operation.Operation, namespace, name string) operation.Item {

View file

@ -22,6 +22,7 @@ package backup
import (
"context"
"errors"
"fmt"
"sync"
"time"
@ -44,7 +45,7 @@ import (
"github.com/arangodb/kube-arangodb/pkg/operatorV2/event"
"github.com/arangodb/kube-arangodb/pkg/operatorV2/operation"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
adbErrors "github.com/arangodb/kube-arangodb/pkg/util/errors"
)
var logger = logging.Global().RegisterAndGetLogger("backup-operator", logging.Info)
@ -226,7 +227,7 @@ func (h *handler) getDeploymentMutex(namespace, deployment string) *sync.Mutex {
}
func (h *handler) Handle(item operation.Item) error {
// Get Backup object. It also cover NotFound case
// Get Backup object. It also covers NotFound case
b, err := h.client.BackupV1().ArangoBackups(item.Namespace).Get(context.Background(), item.Name, meta.GetOptions{})
if err != nil {
if apiErrors.IsNotFound(err) {
@ -261,7 +262,7 @@ func (h *handler) Handle(item operation.Item) error {
return nil
}
// Create lock per namespace to ensure that we are not using 2 goroutines in same time
// Create lock per namespace to ensure that we are not using two goroutines in same time
lock := h.getDeploymentMutex(b.Namespace, b.Spec.Deployment.Name)
lock.Lock()
defer lock.Unlock()
@ -298,7 +299,8 @@ func (h *handler) Handle(item operation.Item) error {
cError := switchError(err)
if _, ok := cError.(temporaryError); ok {
var temporaryError temporaryError
if errors.As(cError, &temporaryError) {
return cError
}
@ -368,7 +370,7 @@ func (h *handler) processArangoBackup(backup *backupApi.ArangoBackup) (*backupAp
return f(h, backup)
}
return nil, errors.Newf("state %s is not supported", backup.Status.State)
return nil, adbErrors.Newf("state %s is not supported", backup.Status.State)
}
func (h *handler) CanBeHandled(item operation.Item) bool {

View file

@ -33,6 +33,7 @@ var (
backupApi.ArangoBackupStatePending: statePendingHandler,
backupApi.ArangoBackupStateScheduled: stateScheduledHandler,
backupApi.ArangoBackupStateCreate: stateCreateHandler,
backupApi.ArangoBackupStateCreating: stateCreatingHandler,
backupApi.ArangoBackupStateCreateError: stateCreateErrorHandler,
backupApi.ArangoBackupStateUpload: stateUploadHandler,
backupApi.ArangoBackupStateUploading: stateUploadingHandler,

View file

@ -22,8 +22,10 @@ package backup
import (
"github.com/arangodb/go-driver"
"github.com/arangodb/go-driver/util/connection/wrappers/async"
backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1"
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
)
func stateCreateHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) {
@ -37,6 +39,14 @@ func stateCreateHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.
return nil, newTemporaryError(err)
}
if features.AsyncBackupCreation().Enabled() {
return asyncBackup(client, backup)
} else {
return syncBackup(client, backup)
}
}
func syncBackup(client ArangoBackupClient, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) {
response, err := client.Create()
if err != nil {
return wrapUpdateStatus(backup,
@ -51,8 +61,7 @@ func stateCreateHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.
if err != nil {
if driver.IsNotFoundGeneral(err) {
return wrapUpdateStatus(backup,
updateStatusState(backupApi.ArangoBackupStateFailed,
"backup is not present after creation"),
updateStatusState(backupApi.ArangoBackupStateFailed, "backup is not present after creation"),
cleanStatusJob(),
)
}
@ -67,3 +76,32 @@ func stateCreateHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.
cleanBackOff(),
)
}
func asyncBackup(client ArangoBackupClient, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) {
_, err := client.CreateAsync("")
if err == nil {
return wrapUpdateStatus(backup,
updateStatusState(backupApi.ArangoBackupStateCreateError, "Start Async backup failed - no jobID response"),
cleanStatusJob(),
updateStatusAvailable(false),
addBackOff(backup.Spec),
)
}
jobID, isAsyncId := async.IsAsyncJobInProgress(err)
if !isAsyncId {
return wrapUpdateStatus(backup,
updateStatusState(backupApi.ArangoBackupStateCreateError, "Create backup failed with error: %s", err.Error()),
cleanStatusJob(),
updateStatusAvailable(false),
addBackOff(backup.Spec),
)
}
return wrapUpdateStatus(backup,
updateStatusState(backupApi.ArangoBackupStateCreating, ""),
updateStatusJob(jobID, "0%"),
updateStatusAvailable(false),
)
}

View file

@ -28,16 +28,25 @@ import (
"github.com/arangodb/go-driver"
backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1"
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
"github.com/arangodb/kube-arangodb/pkg/operatorV2/operation"
"github.com/arangodb/kube-arangodb/pkg/util"
)
func Test_State_Create_Common(t *testing.T) {
*features.AsyncBackupCreation().EnabledPointer() = false
wrapperUndefinedDeployment(t, backupApi.ArangoBackupStateCreate)
wrapperConnectionIssues(t, backupApi.ArangoBackupStateCreate)
}
func Test_State_Create_Common_Async(t *testing.T) {
*features.AsyncBackupCreation().EnabledPointer() = true
wrapperUndefinedDeployment(t, backupApi.ArangoBackupStateCreate)
wrapperConnectionIssues(t, backupApi.ArangoBackupStateCreate)
}
func Test_State_Create_Success(t *testing.T) {
*features.AsyncBackupCreation().EnabledPointer() = false
// Arrange
handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{})
@ -62,7 +71,27 @@ func Test_State_Create_Success(t *testing.T) {
compareBackupMeta(t, backupMeta, newObj)
}
func Test_State_Create_Success_Async(t *testing.T) {
*features.AsyncBackupCreation().EnabledPointer() = true
// Arrange
handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{})
obj, deployment := newObjectSet(backupApi.ArangoBackupStateCreate)
// Act
createArangoDeployment(t, handler, deployment)
createArangoBackup(t, handler, obj)
require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj)))
// Assert
newObj := refreshArangoBackup(t, handler, obj)
checkBackup(t, newObj, backupApi.ArangoBackupStateCreating, false)
}
func Test_State_Create_SuccessForced(t *testing.T) {
*features.AsyncBackupCreation().EnabledPointer() = false
// Arrange
handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{})
@ -93,6 +122,8 @@ func Test_State_Create_SuccessForced(t *testing.T) {
}
func Test_State_Create_Upload(t *testing.T) {
*features.AsyncBackupCreation().EnabledPointer() = false
// Arrange
handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{})
@ -121,10 +152,34 @@ func Test_State_Create_Upload(t *testing.T) {
}
func Test_State_Create_CreateError(t *testing.T) {
*features.AsyncBackupCreation().EnabledPointer() = false
// Arrange
error := newFatalErrorf("error")
handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{
createError: error,
createError: newFatalErrorf("error"),
})
obj, deployment := newObjectSet(backupApi.ArangoBackupStateCreate)
// 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.ArangoBackupStateCreateError)
require.Nil(t, newObj.Status.Backup)
require.False(t, newObj.Status.Available)
}
func Test_State_Create_CreateError_Async(t *testing.T) {
*features.AsyncBackupCreation().EnabledPointer() = true
// Arrange
handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{
createError: newFatalErrorf("error"),
})
obj, deployment := newObjectSet(backupApi.ArangoBackupStateCreate)

View file

@ -0,0 +1,83 @@
//
// DISCLAIMER
//
// 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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package backup
import (
"github.com/arangodb/go-driver"
"github.com/arangodb/go-driver/util/connection/wrappers/async"
backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1"
)
func stateCreatingHandler(h *handler, backup *backupApi.ArangoBackup) (*backupApi.ArangoBackupStatus, error) {
deployment, err := h.getArangoDeploymentObject(backup)
if err != nil {
return nil, err
}
client, err := h.arangoClientFactory(deployment, backup)
if err != nil {
return nil, newTemporaryError(err)
}
if backup.Status.Progress == nil {
return nil, newFatalErrorf("missing field .status.progress")
}
response, err := client.CreateAsync(backup.Status.Progress.JobID)
if err != nil {
_, isAsyncId := async.IsAsyncJobInProgress(err)
if isAsyncId {
return wrapUpdateStatus(backup,
updateStatusState(backupApi.ArangoBackupStateCreating, ""),
updateStatusAvailable(false),
updateStatusJob(backup.Status.Progress.JobID, "50%"),
)
}
return wrapUpdateStatus(backup,
updateStatusState(backupApi.ArangoBackupStateCreateError, "Create backup failed with error: %s", err),
cleanStatusJob(),
updateStatusAvailable(false),
addBackOff(backup.Spec),
)
}
backupMeta, err := client.Get(response.ID)
if err != nil {
if driver.IsNotFoundGeneral(err) {
return wrapUpdateStatus(backup,
updateStatusState(backupApi.ArangoBackupStateFailed, "backup is not present after creation"),
cleanStatusJob(),
)
}
return nil, newFatalError(err)
}
return wrapUpdateStatus(backup,
updateStatusState(backupApi.ArangoBackupStateReady, ""),
cleanStatusJob(),
updateStatusAvailable(true),
updateStatusBackup(backupMeta),
cleanBackOff(),
)
}

View file

@ -0,0 +1,119 @@
//
// DISCLAIMER
//
// 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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package backup
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/arangodb/go-driver"
backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1"
"github.com/arangodb/kube-arangodb/pkg/operatorV2/operation"
)
func Test_State_Creating_Common(t *testing.T) {
wrapperUndefinedDeployment(t, backupApi.ArangoBackupStateCreating)
wrapperConnectionIssues(t, backupApi.ArangoBackupStateCreating)
}
func Test_State_Creating_Success(t *testing.T) {
// Arrange
handler, mock := newErrorsFakeHandler(mockErrorsArangoClientBackup{})
obj, deployment := newObjectSet(backupApi.ArangoBackupStateCreating)
obj.Status.Progress = &backupApi.ArangoBackupProgress{
JobID: "jobID",
}
// Act
createArangoDeployment(t, handler, deployment)
createArangoBackup(t, handler, obj)
t.Run("Create in progress, then done", func(t *testing.T) {
require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj)))
// Assert
newObj := refreshArangoBackup(t, handler, obj)
checkBackup(t, newObj, backupApi.ArangoBackupStateCreating, false)
require.NotNil(t, newObj.Status.Progress)
require.Equal(t, fmt.Sprintf("%d%%", 50), newObj.Status.Progress.Progress)
require.Equal(t, obj.Status.Progress.JobID, newObj.Status.Progress.JobID)
mock.state.createDone = true
require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj)))
// Assert
newObj = refreshArangoBackup(t, handler, obj)
checkBackup(t, newObj, backupApi.ArangoBackupStateReady, true)
require.Nil(t, newObj.Status.Progress)
backups := mock.getIDs()
require.Len(t, backups, 1)
backupMeta, err := mock.Get(driver.BackupID(backups[0]))
require.NoError(t, err)
compareBackupMeta(t, backupMeta, newObj)
})
}
func Test_State_Creating_Failed(t *testing.T) {
// Arrange
handler, _ := newErrorsFakeHandler(mockErrorsArangoClientBackup{
createError: driver.ArangoError{
Code: 400,
},
})
obj, deployment := newObjectSet(backupApi.ArangoBackupStateCreating)
obj.Status.Progress = &backupApi.ArangoBackupProgress{
JobID: "jobID",
}
// Act
createArangoDeployment(t, handler, deployment)
createArangoBackup(t, handler, obj)
t.Run("Create Backup returns error", func(t *testing.T) {
require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj)))
// Create error state should be set
newObj := refreshArangoBackup(t, handler, obj)
checkBackup(t, newObj, backupApi.ArangoBackupStateCreateError, false)
require.Nil(t, newObj.Status.Progress)
require.NoError(t, handler.Handle(newItemFromBackup(operation.Update, obj)))
// No retry - state should change to failed
newObj = refreshArangoBackup(t, handler, obj)
checkBackup(t, newObj, backupApi.ArangoBackupStateFailed, false)
require.Nil(t, newObj.Status.Progress)
})
}

View file

@ -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.
@ -30,9 +30,10 @@ import (
typedCore "k8s.io/client-go/kubernetes/typed/core/v1"
driver "github.com/arangodb/go-driver"
"github.com/arangodb/go-driver"
"github.com/arangodb/go-driver/http"
"github.com/arangodb/go-driver/jwt"
"github.com/arangodb/go-driver/util/connection/wrappers/async"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/apis/shared"
@ -114,10 +115,10 @@ var (
)
// CreateArangodClient creates a go-driver client for a specific member in the given group.
func CreateArangodClient(ctx context.Context, cli typedCore.CoreV1Interface, apiObject *api.ArangoDeployment, group api.ServerGroup, id string) (driver.Client, error) {
func CreateArangodClient(ctx context.Context, cli typedCore.CoreV1Interface, apiObject *api.ArangoDeployment, group api.ServerGroup, id string, asyncSupport bool) (driver.Client, error) {
// Create connection
dnsName := k8sutil.CreatePodDNSNameWithDomain(apiObject, apiObject.GetAcceptedSpec().ClusterDomain, group.AsRole(), id)
c, err := createArangodClientForDNSName(ctx, cli, apiObject, dnsName, false)
c, err := createArangodClientForDNSName(ctx, cli, apiObject, dnsName, false, asyncSupport)
if err != nil {
return nil, errors.WithStack(err)
}
@ -125,10 +126,10 @@ func CreateArangodClient(ctx context.Context, cli typedCore.CoreV1Interface, api
}
// CreateArangodDatabaseClient creates a go-driver client for accessing the entire cluster (or single server).
func CreateArangodDatabaseClient(ctx context.Context, cli typedCore.CoreV1Interface, apiObject *api.ArangoDeployment, shortTimeout bool) (driver.Client, error) {
func CreateArangodDatabaseClient(ctx context.Context, cli typedCore.CoreV1Interface, apiObject *api.ArangoDeployment, shortTimeout bool, asyncSupport bool) (driver.Client, error) {
// Create connection
dnsName := k8sutil.CreateDatabaseClientServiceDNSNameWithDomain(apiObject, apiObject.GetAcceptedSpec().ClusterDomain)
c, err := createArangodClientForDNSName(ctx, cli, apiObject, dnsName, shortTimeout)
c, err := createArangodClientForDNSName(ctx, cli, apiObject, dnsName, shortTimeout, asyncSupport)
if err != nil {
return nil, errors.WithStack(err)
}
@ -137,9 +138,9 @@ func CreateArangodDatabaseClient(ctx context.Context, cli typedCore.CoreV1Interf
// CreateArangodImageIDClient creates a go-driver client for an ArangoDB instance
// running in an Image-ID pod.
func CreateArangodImageIDClient(ctx context.Context, deployment k8sutil.APIObject, ip string) (driver.Client, error) {
func CreateArangodImageIDClient(ctx context.Context, deployment k8sutil.APIObject, ip string, asyncSupport bool) (driver.Client, error) {
// Create connection
c, err := createArangodClientForDNSName(ctx, nil, nil, ip, false)
c, err := createArangodClientForDNSName(ctx, nil, nil, ip, false, asyncSupport)
if err != nil {
return nil, errors.WithStack(err)
}
@ -147,7 +148,7 @@ func CreateArangodImageIDClient(ctx context.Context, deployment k8sutil.APIObjec
}
// CreateArangodClientForDNSName creates a go-driver client for a given DNS name.
func createArangodClientForDNSName(ctx context.Context, cli typedCore.CoreV1Interface, apiObject *api.ArangoDeployment, dnsName string, shortTimeout bool) (driver.Client, error) {
func createArangodClientForDNSName(ctx context.Context, cli typedCore.CoreV1Interface, apiObject *api.ArangoDeployment, dnsName string, shortTimeout bool, asyncSupport bool) (driver.Client, error) {
connConfig := createArangodHTTPConfigForDNSNames(apiObject, []string{dnsName}, shortTimeout)
// TODO deal with TLS with proper CA checking
conn, err := http.NewConnection(connConfig)
@ -155,6 +156,11 @@ func createArangodClientForDNSName(ctx context.Context, cli typedCore.CoreV1Inte
return nil, errors.WithStack(err)
}
if asyncSupport {
// Wrap connection with async wrapper
conn = async.NewConnectionAsyncWrapper(conn)
}
// Create client
config := driver.ClientConfig{
Connection: conn,