Skip to content

Commit

Permalink
Return backup stats in backup action (#337)
Browse files Browse the repository at this point in the history
* Adding backup stats to return

Parsing the log from the backup execution to find the
size and file count.

* Adding empty file count test case

* Adding empty size test cases

* Fix backup-all and test checks

* Fix copy pasted comment

* Move regex compiles out of loops

PR suggestion

* Add slice bound checks on match

Per code review request, adding slice bound checks on
regex matches for SnapshotIDFromBackupLog and
SnapshotStatsFromBackupLog.

* Return struct instead of unnamed strings

Returning a struct that maps the parsed outputs
from the backup action, for readability and less
error prone.

* Debug log when can't parse stats

Adding a debug log when either file count or size come back empty
  • Loading branch information
redgoat650 authored and mergify[bot] committed Oct 8, 2019
1 parent 6050c5a commit e6ca11f
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 16 deletions.
40 changes: 29 additions & 11 deletions pkg/function/backup_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,15 @@ func (*backupDataFunc) Exec(ctx context.Context, tp param.TemplateParams, args m
if err != nil {
return nil, errors.Wrapf(err, "Failed to create Kubernetes client")
}
backupID, backupTag, err := backupData(ctx, cli, namespace, pod, container, backupArtifactPrefix, includePath, encryptionKey, tp)
backupOutputs, err := backupData(ctx, cli, namespace, pod, container, backupArtifactPrefix, includePath, encryptionKey, tp)
if err != nil {
return nil, errors.Wrapf(err, "Failed to backup data")
}
output := map[string]interface{}{
BackupDataOutputBackupID: backupID,
BackupDataOutputBackupTag: backupTag,
BackupDataOutputBackupID: backupOutputs.backupID,
BackupDataOutputBackupTag: backupOutputs.backupTag,
BackupDataStatsOutputFileCount: backupOutputs.fileCount,
BackupDataStatsOutputSize: backupOutputs.backupSize,
}
return output, nil
}
Expand All @@ -128,34 +130,50 @@ func (*backupDataFunc) RequiredArgs() []string {
BackupDataIncludePathArg, BackupDataBackupArtifactPrefixArg}
}

func backupData(ctx context.Context, cli kubernetes.Interface, namespace, pod, container, backupArtifactPrefix, includePath, encryptionKey string, tp param.TemplateParams) (string, string, error) {
type backupDataParsedOutput struct {
backupID string
backupTag string
fileCount string
backupSize string
}

func backupData(ctx context.Context, cli kubernetes.Interface, namespace, pod, container, backupArtifactPrefix, includePath, encryptionKey string, tp param.TemplateParams) (backupDataParsedOutput, error) {
pw, err := getPodWriter(cli, ctx, namespace, pod, container, tp.Profile)
if err != nil {
return "", "", err
return backupDataParsedOutput{}, err
}
defer cleanUpCredsFile(ctx, pw, namespace, pod, container)
if err = restic.GetOrCreateRepository(cli, namespace, pod, container, backupArtifactPrefix, encryptionKey, tp.Profile); err != nil {
return "", "", err
return backupDataParsedOutput{}, err
}

// Create backup and dump it on the object store
backupTag := rand.String(10)
cmd, err := restic.BackupCommandByTag(tp.Profile, backupArtifactPrefix, backupTag, includePath, encryptionKey)
if err != nil {
return "", "", err
return backupDataParsedOutput{}, err
}
stdout, stderr, err := kube.Exec(cli, namespace, pod, container, cmd, nil)
format.Log(pod, container, stdout)
format.Log(pod, container, stderr)
if err != nil {
return "", "", errors.Wrapf(err, "Failed to create and upload backup")
return backupDataParsedOutput{}, errors.Wrapf(err, "Failed to create and upload backup")
}
// Get the snapshot ID from log
backupID := restic.SnapshotIDFromBackupLog(stdout)
if backupID == "" {
return "", "", errors.New("Failed to parse the backup ID from logs")
}
return backupID, backupTag, nil
return backupDataParsedOutput{}, errors.New("Failed to parse the backup ID from logs")
}
// Get the file count and size of the backup from log
fileCount, backupSize := restic.SnapshotStatsFromBackupLog(stdout)
if fileCount == "" || backupSize == "" {
log.Debug("Could not parse backup stats from backup log")
}
return backupDataParsedOutput{
backupID: backupID,
backupTag: backupTag,
fileCount: fileCount,
backupSize: backupSize}, nil
}

func getPodWriter(cli kubernetes.Interface, ctx context.Context, namespace, podName, containerName string, profile *param.Profile) (*kube.PodWriter, error) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/function/backup_data_all.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ func backupDataAll(ctx context.Context, cli kubernetes.Interface, namespace stri
for _, pod := range ps {
go func(pod string, container string) {
ctx = field.Context(ctx, consts.PodNameKey, pod)
backupID, backupTag, err := backupData(ctx, cli, namespace, pod, container, fmt.Sprintf("%s/%s", backupArtifactPrefix, pod), includePath, encryptionKey, tp)
backupOutputs, err := backupData(ctx, cli, namespace, pod, container, fmt.Sprintf("%s/%s", backupArtifactPrefix, pod), includePath, encryptionKey, tp)
errChan <- errors.Wrapf(err, "Failed to backup data for pod %s", pod)
outChan <- BackupInfo{PodName: pod, BackupID: backupID, BackupTag: backupTag}
outChan <- BackupInfo{PodName: pod, BackupID: backupOutputs.backupID, BackupTag: backupOutputs.backupTag}
}(pod, container)
}
errs := make([]string, 0, len(ps))
Expand Down
4 changes: 4 additions & 0 deletions pkg/function/data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ func (s *DataSuite) TestBackupRestoreDeleteData(c *C) {
out := runAction(c, bp, "backup", tp)
c.Assert(out[BackupDataOutputBackupID].(string), Not(Equals), "")
c.Assert(out[BackupDataOutputBackupTag].(string), Not(Equals), "")
c.Check(out[BackupDataStatsOutputFileCount].(string), Not(Equals), "")
c.Check(out[BackupDataStatsOutputSize].(string), Not(Equals), "")

options := map[string]string{
BackupDataOutputBackupID: out[BackupDataOutputBackupID].(string),
Expand All @@ -339,6 +341,8 @@ func (s *DataSuite) TestBackupRestoreDataWithSnapshotID(c *C) {
out := runAction(c, bp, "backup", tp)
c.Assert(out[BackupDataOutputBackupID].(string), Not(Equals), "")
c.Assert(out[BackupDataOutputBackupTag].(string), Not(Equals), "")
c.Check(out[BackupDataStatsOutputFileCount].(string), Not(Equals), "")
c.Check(out[BackupDataStatsOutputSize].(string), Not(Equals), "")

options := map[string]string{
BackupDataOutputBackupID: out[BackupDataOutputBackupID].(string),
Expand Down
31 changes: 28 additions & 3 deletions pkg/restic/restic.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,17 +293,42 @@ func SnapshotIDFromBackupLog(output string) string {
return ""
}
logs := regexp.MustCompile("[\n]").Split(output, -1)
// Log should contain "snapshot ABC123 saved"
pattern := regexp.MustCompile(`snapshot\s(.*?)\ssaved$`)
for _, l := range logs {
// Log should contain "snapshot ABC123 saved"
pattern := regexp.MustCompile(`snapshot\s(.*?)\ssaved$`)
match := pattern.FindAllStringSubmatch(l, 1)
if match != nil {
return match[0][1]
if len(match) >= 1 && len(match[0]) >= 2 {
return match[0][1]
}
}
}
return ""
}

// SnapshotStatsFromBackupLog gets the Snapshot file count and size from Backup Command log
func SnapshotStatsFromBackupLog(output string) (fileCount string, backupSize string) {
if output == "" {
return "", ""
}
logs := regexp.MustCompile("[\n]").Split(output, -1)
// Log should contain "processed %d files, %.3f [Xi]B in mm:ss"
pattern := regexp.MustCompile(`processed\s([\d]+)\sfiles,\s([\d]+(\.[\d]+)?\s([TGMK]i)?B)\sin\s`)
for _, l := range logs {
match := pattern.FindAllStringSubmatch(l, 1)
if match != nil {
if len(match) >= 1 && len(match[0]) >= 3 {
// Expect in order:
// 0: entire match,
// 1: first submatch == file count,
// 2: second submatch == size string
return match[0][1], match[0][2]
}
}
}
return "", ""
}

// SnapshotStatsFromStatsLog gets the Snapshot Stats from Stats Command log
func SnapshotStatsFromStatsLog(output string) (string, string, string) {
if output == "" {
Expand Down
51 changes: 51 additions & 0 deletions pkg/restic/restic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,54 @@ func (s *ResticDataSuite) TestGetSnapshotStatsModeFromStatsLog(c *C) {
c.Assert(mode, Equals, tc.expected)
}
}

func (s *ResticDataSuite) TestGetSnapshotStatsFromBackupLog(c *C) {
for _, tc := range []struct {
log string
expectedfc string
expectedsize string
}{
{log: "processed 9 files, 11.235 KiB in 0:00", expectedfc: "9", expectedsize: "11.235 KiB"},
{log: "processed 9 files, 11 KiB in 0:00", expectedfc: "9", expectedsize: "11 KiB"},
{log: "processed 9 files, 11. KiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, . KiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, .111 KiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 0.111 KiB in 0:00", expectedfc: "9", expectedsize: "0.111 KiB"},
{log: "processed 9 files, 11.235 KiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed asdf files, 11.235 KiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 KiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "asdf 9 files, 11.235 KiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9,999,999 files, 11.235 KiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9.999 files, 11.235 KiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 KiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 KiB", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 KiB in", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 KiB in ", expectedfc: "9", expectedsize: "11.235 KiB"},
{log: "processed 9 files, 11.235 KiB in ", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 in ", expectedfc: "", expectedsize: ""},
{log: "processed 9 , 11.235 KiB in ", expectedfc: "", expectedsize: ""},
{log: "processed 9 files 11.235 KiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 B in 0:00", expectedfc: "9", expectedsize: "11.235 B"},
{log: "processed 9 files, 11.235 MiB in 0:00", expectedfc: "9", expectedsize: "11.235 MiB"},
{log: "processed 9 files, 11.235 GiB in 0:00", expectedfc: "9", expectedsize: "11.235 GiB"},
{log: "processed 9 files, 11.235 TiB in 0:00", expectedfc: "9", expectedsize: "11.235 TiB"},
{log: "processed 9 files, 11.235 PiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 asdf in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 iB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 KB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 MB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 GB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 TB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 PB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, 11.235 asdfB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed files, 11.235 asdfB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed files, 11.235 asdfB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, KiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, KiB in 0:00", expectedfc: "", expectedsize: ""},
{log: "processed 9 files, in 0:00", expectedfc: "", expectedsize: ""},
} {
fc, s := SnapshotStatsFromBackupLog(tc.log)
c.Check(fc, Equals, tc.expectedfc)
c.Check(s, Equals, tc.expectedsize)
}
}

0 comments on commit e6ca11f

Please sign in to comment.