Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support cleanPolicy for backup CR #3002

Merged
merged 16 commits into from
Jul 24, 2020
Merged
Show file tree
Hide file tree
Changes from 11 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
17 changes: 9 additions & 8 deletions cmd/backup-manager/app/clean/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"fmt"
"io"
"os/exec"
"strings"

"k8s.io/klog"

Expand Down Expand Up @@ -67,14 +68,14 @@ func (bo *Options) cleanRemoteBackupData(bucket string, opts []string) error {
destBucket := util.NormalizeBucketURI(bucket)
args := util.ConstructArgs(constants.RcloneConfigArg, opts, "deletefile", destBucket, "")
output, err := exec.Command("rclone", args...).CombinedOutput()
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
if code := exitError.ExitCode(); code == 3 || code == 4 {
klog.Infof("cluster %s backup %s has already been deleted before", bo, bucket)
return nil
}
}
return fmt.Errorf("cluster %s, execute rclone deletefile command failed, output: %s, err: %v", bo, string(output), err)
if err != nil && !strings.Contains(string(output), "doesn't exist") {
return fmt.Errorf("cluster %s, execute rclone deletefile command to delete archive failed, output: %s, err: %v", bo, string(output), err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why changing this back?

Copy link
Contributor Author

@lichunzhu lichunzhu Jul 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because according to test, this error code is not accurate. When rclone returns dir doesn't exist error the exit code is 1. May the last time I forgot to use hack/local-up-operator.sh to update operator.

Copy link
Contributor

@cofyc cofyc Jul 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rclone delete is a better command in this scenario

https://rclone.org/commands/rclone_delete/

in my testing, it succeeds if no file found at the specified path

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It won't even return error. Should we use this command?
C.C. @DanielZhangQD

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's OK if the file can be removed if exist.

Copy link
Contributor Author

@lichunzhu lichunzhu Jul 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revise to rclone delete in 4e1d38f

}

args = util.ConstructArgs(constants.RcloneConfigArg, opts, "deletefile", fmt.Sprintf("%s.tmp", destBucket), "")
Copy link
Contributor

@cofyc cofyc Jul 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when will this *.tmp file be created?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After file is archived and uploaded to S3.

  1. upload archive file to *.tgz.tmp
  2. move *.tgz.tmp to *.tgz

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this is for backup with dumpling only.

output, err = exec.Command("rclone", args...).CombinedOutput()
if err != nil && !strings.Contains(string(output), "doesn't exist") {
return fmt.Errorf("cluster %s, execute rclone deletefile command to delete tmp file failed, output: %s, err: %v", bo, string(output), err)
}

klog.Infof("cluster %s backup %s was deleted successfully", bo, bucket)
Expand Down
18 changes: 17 additions & 1 deletion cmd/backup-manager/app/export/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,23 @@ func (bm *BackupManager) performBackup(backup *v1alpha1.Backup, db *sql.DB) erro

remotePath := strings.TrimPrefix(archiveBackupPath, constants.BackupRootPath+"/")
bucketURI := bm.getDestBucketURI(remotePath)
backup.Status.BackupPath = bucketURI
err = bm.StatusUpdater.Update(backup, &v1alpha1.BackupCondition{
Type: v1alpha1.BackupPrepare,
Status: corev1.ConditionTrue,
})
if err != nil {
errs = append(errs, err)
uerr := bm.StatusUpdater.Update(backup, &v1alpha1.BackupCondition{
Type: v1alpha1.BackupFailed,
Status: corev1.ConditionTrue,
Reason: "UpdateBackupPathFailed",
Message: err.Error(),
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why setting this condition if StatusUpdater.Update( fails?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it's meaningless to call StatusUpdater.Update again if the previous StatusUpdater.Update fails as the second call can fail too

our current architecture relies on our backup job to update Backup CR's status, the situation that the status may not be reported to the apiserver is unavoidable

does it cause problems when the StatusUpdater.Update call fails?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we omit this error, we might be unable to clean files on the Cloud when we delete this CR because BackupPath is not recorded.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but the bm.backupDataToRemote will not be run if reporting the backup path to the apiserver fails

(if this is not true, I think we should fix it. one more API call does not help)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So should we omit this error? What about BR?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we didn't omit the error, the job fails

errs = append(errs, uerr)
return errorutils.NewAggregate(errs)
}

err = bm.backupDataToRemote(archiveBackupPath, bucketURI, opts)
if err != nil {
errs = append(errs, err)
Expand All @@ -342,7 +359,6 @@ func (bm *BackupManager) performBackup(backup *v1alpha1.Backup, db *sql.DB) erro

finish := time.Now()

backup.Status.BackupPath = bucketURI
backup.Status.TimeStarted = metav1.Time{Time: started}
backup.Status.TimeCompleted = metav1.Time{Time: finish}
backup.Status.BackupSize = size
Expand Down
24 changes: 18 additions & 6 deletions docs/api-references/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,13 +267,15 @@ string
</tr>
<tr>
<td>
<code>cleanData</code></br>
<code>cleanPolicy</code></br>
<em>
bool
<a href="#cleanpolicytype">
CleanPolicyType
</a>
</em>
</td>
<td>
<p>CleanData denotes whether to clean backup data before the object is deleted from the cluster</p>
<p>CleanPolicy denotes whether to clean backup data when the object is deleted from the cluster, if not set, the backup data will be retained</p>
</td>
</tr>
</table>
Expand Down Expand Up @@ -2580,13 +2582,15 @@ string
</tr>
<tr>
<td>
<code>cleanData</code></br>
<code>cleanPolicy</code></br>
<em>
bool
<a href="#cleanpolicytype">
CleanPolicyType
</a>
</em>
</td>
<td>
<p>CleanData denotes whether to clean backup data before the object is deleted from the cluster</p>
<p>CleanPolicy denotes whether to clean backup data when the object is deleted from the cluster, if not set, the backup data will be retained</p>
</td>
</tr>
</tbody>
Expand Down Expand Up @@ -2965,6 +2969,14 @@ Optional: Defaults to range</p>
</tr>
</tbody>
</table>
<h3 id="cleanpolicytype">CleanPolicyType</h3>
<p>
(<em>Appears on:</em>
<a href="#backupspec">BackupSpec</a>)
</p>
<p>
<p>CleanPolicyType represents the clean policy of backup data in remote storage</p>
</p>
<h3 id="commonconfig">CommonConfig</h3>
<p>
(<em>Appears on:</em>
Expand Down
8 changes: 4 additions & 4 deletions manifests/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11616,8 +11616,8 @@ spec:
required:
- cluster
type: object
cleanData:
type: boolean
cleanPolicy:
type: string
dumpling:
properties:
options:
Expand Down Expand Up @@ -12517,8 +12517,8 @@ spec:
required:
- cluster
type: object
cleanData:
type: boolean
cleanPolicy:
type: string
dumpling:
properties:
options:
Expand Down
15 changes: 15 additions & 0 deletions pkg/apis/pingcap/v1alpha1/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,18 @@ func IsBackupClean(backup *Backup) bool {
_, condition := GetBackupCondition(&backup.Status, BackupClean)
return condition != nil && condition.Status == corev1.ConditionTrue
}

// IsCleanCandidate returns true if a Backup should be added to clean candidate according to cleanPolicy
func IsCleanCandidate(backup *Backup) bool {
switch backup.Spec.CleanPolicy {
case CleanPolicyTypeDelete, CleanPolicyTypeOnFailure:
return true
default:
return false
}
}

// NeedNotClean returns true if a Backup need not to be cleaned up according to cleanPolicy
func NeedNotClean(backup *Backup) bool {
return backup.Spec.CleanPolicy == CleanPolicyTypeOnFailure && !IsBackupFailed(backup)
}
6 changes: 3 additions & 3 deletions pkg/apis/pingcap/v1alpha1/openapi_generated.go

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

17 changes: 15 additions & 2 deletions pkg/apis/pingcap/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,19 @@ type TiDBAccessConfig struct {
TLSClientSecretName *string `json:"tlsClientSecretName,omitempty"`
}

// +k8s:openapi-gen=true
// CleanPolicyType represents the clean policy of backup data in remote storage
type CleanPolicyType string

const (
// CleanPolicyTypeRetain represents that the backup data in remote storage will be retained when the Backup CR is deleted
CleanPolicyTypeRetain CleanPolicyType = "Retain"
// CleanPolicyTypeOnFailure represents that the backup data in remote storage will be cleaned only for the failed backups when the Backup CR is deleted
CleanPolicyTypeOnFailure CleanPolicyType = "OnFailure"
// CleanPolicyTypeIfFailed represents that the backup data in remote storage will be cleaned when the Backup CR is deleted
CleanPolicyTypeDelete CleanPolicyType = "Delete"
)

// +k8s:openapi-gen=true
// BackupSpec contains the backup specification for a tidb cluster.
type BackupSpec struct {
Expand Down Expand Up @@ -1148,8 +1161,8 @@ type BackupSpec struct {
UseKMS bool `json:"useKMS,omitempty"`
// Specify service account of backup
ServiceAccount string `json:"serviceAccount,omitempty"`
// CleanData denotes whether to clean backup data before the object is deleted from the cluster
CleanData bool `json:"cleanData,omitempty"`
// CleanPolicy denotes whether to clean backup data when the object is deleted from the cluster, if not set, the backup data will be retained
CleanPolicy CleanPolicyType `json:"cleanPolicy,omitempty"`
}

// +k8s:openapi-gen=true
Expand Down
5 changes: 3 additions & 2 deletions pkg/backup/backup/backup_cleaner.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ func NewBackupCleaner(
}

func (bc *backupCleaner) Clean(backup *v1alpha1.Backup) error {
if backup.DeletionTimestamp == nil || !backup.Spec.CleanData {
// The backup object has not been deleted,do nothing
if backup.DeletionTimestamp == nil || !v1alpha1.IsCleanCandidate(backup) || v1alpha1.NeedNotClean(backup) {
// The backup object has not been deleted or we need to retain backup data,do nothing
return nil
}
ns := backup.GetNamespace()
Expand All @@ -80,6 +80,7 @@ func (bc *backupCleaner) Clean(backup *v1alpha1.Backup) error {
Status: corev1.ConditionTrue,
})
}

// not found clean job, create it
job, reason, err := bc.makeCleanJob(backup)
if err != nil {
Expand Down
9 changes: 7 additions & 2 deletions pkg/controller/backup/backup_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func (bc *defaultBackupControl) removeProtectionFinalizer(backup *v1alpha1.Backu
ns := backup.GetNamespace()
name := backup.GetName()

if backup.Spec.CleanData && isDeletionCandidate(backup) && v1alpha1.IsBackupClean(backup) {
if needToRemoveFinalizer(backup) {
backup.Finalizers = slice.RemoveString(backup.Finalizers, label.BackupProtectionFinalizer, nil)
_, err := bc.cli.PingcapV1alpha1().Backups(ns).Update(backup)
if err != nil {
Expand All @@ -98,7 +98,12 @@ func (bc *defaultBackupControl) removeProtectionFinalizer(backup *v1alpha1.Backu
}

func needToAddFinalizer(backup *v1alpha1.Backup) bool {
return backup.DeletionTimestamp == nil && backup.Spec.CleanData && !slice.ContainsString(backup.Finalizers, label.BackupProtectionFinalizer, nil)
return backup.DeletionTimestamp == nil && v1alpha1.IsCleanCandidate(backup) && !slice.ContainsString(backup.Finalizers, label.BackupProtectionFinalizer, nil)
}

func needToRemoveFinalizer(backup *v1alpha1.Backup) bool {
return v1alpha1.IsCleanCandidate(backup) && isDeletionCandidate(backup) &&
(v1alpha1.IsBackupClean(backup) || v1alpha1.NeedNotClean(backup))
}

func isDeletionCandidate(backup *v1alpha1.Backup) bool {
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/backup/backup_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ func newBackup() *v1alpha1.Backup {
},
StorageClassName: pointer.StringPtr("local-storage"),
StorageSize: "1Gi",
CleanData: true,
CleanPolicy: v1alpha1.CleanPolicyTypeDelete,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add tests for other values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will add tests later

},
}
}
2 changes: 1 addition & 1 deletion tests/pkg/fixture/fixture.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ func GetBackupCRDWithS3(tc *v1alpha1.TidbCluster, fromSecretName, brType string,
ClusterNamespace: tc.GetNamespace(),
SendCredToTikv: &sendCredToTikv,
},
CleanData: true,
CleanPolicy: v1alpha1.CleanPolicyTypeDelete,
},
}
if brType == DumperType {
Expand Down