Merge pull request #974 from skriss/backup-describer

backup describer: show snapshot summary by default, details optionally
pull/981/head
Nolan Brubaker 2018-10-23 12:58:54 -04:00 committed by GitHub
commit 6cf3519c3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 100 additions and 34 deletions

View File

@ -195,6 +195,14 @@ type BackupStatus struct {
// Completion time is recorded before uploading the backup object.
// The server's time is used for CompletionTimestamps
CompletionTimestamp metav1.Time `json:"completionTimestamp"`
// VolumeSnapshotsAttempted is the total number of attempted
// volume snapshots for this backup.
VolumeSnapshotsAttempted int `json:"volumeSnapshotsAttempted"`
// VolumeSnapshotsCompleted is the total number of successfully
// completed volume snapshots for this backup.
VolumeSnapshotsCompleted int `json:"volumeSnapshotsCompleted"`
}
// VolumeBackupInfo captures the required information about

View File

@ -28,10 +28,11 @@ type DownloadRequestSpec struct {
type DownloadTargetKind string
const (
DownloadTargetKindBackupLog DownloadTargetKind = "BackupLog"
DownloadTargetKindBackupContents DownloadTargetKind = "BackupContents"
DownloadTargetKindRestoreLog DownloadTargetKind = "RestoreLog"
DownloadTargetKindRestoreResults DownloadTargetKind = "RestoreResults"
DownloadTargetKindBackupLog DownloadTargetKind = "BackupLog"
DownloadTargetKindBackupContents DownloadTargetKind = "BackupContents"
DownloadTargetKindBackupVolumeSnapshots DownloadTargetKind = "BackupVolumeSnapshots"
DownloadTargetKindRestoreLog DownloadTargetKind = "RestoreLog"
DownloadTargetKindRestoreResults DownloadTargetKind = "RestoreResults"
)
// DownloadTarget is the specification for what kind of file to download, and the name of the

View File

@ -33,8 +33,8 @@ import (
func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
var (
listOptions metav1.ListOptions
volumeDetails bool
listOptions metav1.ListOptions
details bool
)
c := &cobra.Command{
@ -71,7 +71,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
fmt.Fprintf(os.Stderr, "error getting PodVolumeBackups for backup %s: %v\n", backup.Name, err)
}
s := output.DescribeBackup(&backup, deleteRequestList.Items, podVolumeBackupList.Items, volumeDetails)
s := output.DescribeBackup(&backup, deleteRequestList.Items, podVolumeBackupList.Items, details, arkClient)
if first {
first = false
fmt.Print(s)
@ -84,7 +84,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
}
c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "only show items matching this label selector")
c.Flags().BoolVar(&volumeDetails, "volume-details", volumeDetails, "display details of restic volume backups")
c.Flags().BoolVar(&details, "details", details, "display additional detail in the command output")
return c
}

View File

@ -32,8 +32,8 @@ import (
func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
var (
listOptions metav1.ListOptions
volumeDetails bool
listOptions metav1.ListOptions
details bool
)
c := &cobra.Command{
@ -64,7 +64,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
fmt.Fprintf(os.Stderr, "error getting PodVolumeRestores for restore %s: %v\n", restore.Name, err)
}
s := output.DescribeRestore(&restore, podvolumeRestoreList.Items, volumeDetails, arkClient)
s := output.DescribeRestore(&restore, podvolumeRestoreList.Items, details, arkClient)
if first {
first = false
fmt.Print(s)
@ -77,7 +77,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
}
c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "only show items matching this label selector")
c.Flags().BoolVar(&volumeDetails, "volume-details", volumeDetails, "display details of restic volume restores")
c.Flags().BoolVar(&details, "details", details, "display additional detail in the command output")
return c
}

View File

@ -17,16 +17,27 @@ limitations under the License.
package output
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"strings"
arkv1api "github.com/heptio/ark/pkg/apis/ark/v1"
"github.com/heptio/ark/pkg/cmd/util/downloadrequest"
clientset "github.com/heptio/ark/pkg/generated/clientset/versioned"
"github.com/heptio/ark/pkg/volume"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// DescribeBackup describes a backup in human-readable format.
func DescribeBackup(backup *arkv1api.Backup, deleteRequests []arkv1api.DeleteBackupRequest, podVolumeBackups []arkv1api.PodVolumeBackup, volumeDetails bool) string {
func DescribeBackup(
backup *arkv1api.Backup,
deleteRequests []arkv1api.DeleteBackupRequest,
podVolumeBackups []arkv1api.PodVolumeBackup,
details bool,
arkClient clientset.Interface,
) string {
return Describe(func(d *Describer) {
d.DescribeMetadata(backup.ObjectMeta)
@ -41,7 +52,7 @@ func DescribeBackup(backup *arkv1api.Backup, deleteRequests []arkv1api.DeleteBac
DescribeBackupSpec(d, backup.Spec)
d.Println()
DescribeBackupStatus(d, backup.Status)
DescribeBackupStatus(d, backup, details, arkClient)
if len(deleteRequests) > 0 {
d.Println()
@ -50,7 +61,7 @@ func DescribeBackup(backup *arkv1api.Backup, deleteRequests []arkv1api.DeleteBac
if len(podVolumeBackups) > 0 {
d.Println()
DescribePodVolumeBackups(d, podVolumeBackups, volumeDetails)
DescribePodVolumeBackups(d, podVolumeBackups, details)
}
})
}
@ -167,7 +178,9 @@ func DescribeBackupSpec(d *Describer, spec arkv1api.BackupSpec) {
}
// DescribeBackupStatus describes a backup status in human-readable format.
func DescribeBackupStatus(d *Describer, status arkv1api.BackupStatus) {
func DescribeBackupStatus(d *Describer, backup *arkv1api.Backup, details bool, arkClient clientset.Interface) {
status := backup.Status
d.Printf("Backup Format Version:\t%d\n", status.Version)
d.Println()
@ -197,22 +210,54 @@ func DescribeBackupStatus(d *Describer, status arkv1api.BackupStatus) {
}
d.Println()
if len(status.VolumeBackups) == 0 {
d.Printf("Persistent Volumes: <none included>\n")
} else {
if len(status.VolumeBackups) > 0 {
// pre-v0.10 backup
d.Printf("Persistent Volumes:\n")
for pvName, info := range status.VolumeBackups {
d.Printf("\t%s:\n", pvName)
d.Printf("\t\tSnapshot ID:\t%s\n", info.SnapshotID)
d.Printf("\t\tType:\t%s\n", info.Type)
d.Printf("\t\tAvailability Zone:\t%s\n", info.AvailabilityZone)
iops := "<N/A>"
if info.Iops != nil {
iops = fmt.Sprintf("%d", *info.Iops)
}
d.Printf("\t\tIOPS:\t%s\n", iops)
printSnapshot(d, pvName, info.SnapshotID, info.Type, info.AvailabilityZone, info.Iops)
}
return
}
if status.VolumeSnapshotsAttempted > 0 {
// v0.10+ backup
if !details {
d.Printf("Persistent Volumes:\t%d of %d snapshots completed successfully (specify --details for more information)\n", status.VolumeSnapshotsCompleted, status.VolumeSnapshotsAttempted)
return
}
buf := new(bytes.Buffer)
if err := downloadrequest.Stream(arkClient.ArkV1(), backup.Namespace, backup.Name, arkv1api.DownloadTargetKindBackupVolumeSnapshots, buf, downloadRequestTimeout); err != nil {
d.Printf("Persistent Volumes:\t<error getting volume snapshot info: %v>\n", err)
return
}
var snapshots []*volume.Snapshot
if err := json.NewDecoder(buf).Decode(&snapshots); err != nil {
d.Printf("Persistent Volumes:\t<error reading volume snapshot info: %v>\n", err)
return
}
d.Printf("Persistent Volumes:\n")
for _, snap := range snapshots {
printSnapshot(d, snap.Spec.PersistentVolumeName, snap.Status.ProviderSnapshotID, snap.Spec.VolumeType, snap.Spec.VolumeAZ, snap.Spec.VolumeIOPS)
}
return
}
d.Printf("Persistent Volumes: <none included>\n")
}
func printSnapshot(d *Describer, pvName, snapshotID, volumeType, volumeAZ string, iops *int64) {
d.Printf("\t%s:\n", pvName)
d.Printf("\t\tSnapshot ID:\t%s\n", snapshotID)
d.Printf("\t\tType:\t%s\n", volumeType)
d.Printf("\t\tAvailability Zone:\t%s\n", volumeAZ)
iopsString := "<N/A>"
if iops != nil {
iopsString = fmt.Sprintf("%d", *iops)
}
d.Printf("\t\tIOPS:\t%s\n", iopsString)
}
// DescribeDeleteBackupRequests describes delete backup requests in human-readable format.
@ -256,7 +301,7 @@ func DescribePodVolumeBackups(d *Describer, backups []arkv1api.PodVolumeBackup,
if details {
d.Printf("Restic Backups:\n")
} else {
d.Printf("Restic Backups (specify --volume-details for more information):\n")
d.Printf("Restic Backups (specify --details for more information):\n")
}
// separate backups by phase (combining <none> and New into a single group)

View File

@ -19,6 +19,7 @@ package output
import (
"fmt"
"os"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -32,6 +33,8 @@ import (
"github.com/heptio/ark/pkg/util/encode"
)
const downloadRequestTimeout = 30 * time.Second
// BindFlags defines a set of output-specific flags within the provided
// FlagSet.
func BindFlags(flags *pflag.FlagSet) {

View File

@ -21,7 +21,6 @@ import (
"encoding/json"
"sort"
"strings"
"time"
"github.com/heptio/ark/pkg/apis/ark/v1"
"github.com/heptio/ark/pkg/cmd/util/downloadrequest"
@ -29,7 +28,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func DescribeRestore(restore *v1.Restore, podVolumeRestores []v1.PodVolumeRestore, volumeDetails bool, arkClient clientset.Interface) string {
func DescribeRestore(restore *v1.Restore, podVolumeRestores []v1.PodVolumeRestore, details bool, arkClient clientset.Interface) string {
return Describe(func(d *Describer) {
d.DescribeMetadata(restore.ObjectMeta)
@ -100,7 +99,7 @@ func DescribeRestore(restore *v1.Restore, podVolumeRestores []v1.PodVolumeRestor
if len(podVolumeRestores) > 0 {
d.Println()
describePodVolumeRestores(d, podVolumeRestores, volumeDetails)
describePodVolumeRestores(d, podVolumeRestores, details)
}
})
}
@ -114,7 +113,7 @@ func describeRestoreResults(d *Describer, restore *v1.Restore, arkClient clients
var buf bytes.Buffer
var resultMap map[string]v1.RestoreResult
if err := downloadrequest.Stream(arkClient.ArkV1(), restore.Namespace, restore.Name, v1.DownloadTargetKindRestoreResults, &buf, 30*time.Second); err != nil {
if err := downloadrequest.Stream(arkClient.ArkV1(), restore.Namespace, restore.Name, v1.DownloadTargetKindRestoreResults, &buf, downloadRequestTimeout); err != nil {
d.Printf("Warnings:\t<error getting warnings: %v>\n\nErrors:\t<error getting errors: %v>\n", err, err)
return
}
@ -148,7 +147,7 @@ func describePodVolumeRestores(d *Describer, restores []v1.PodVolumeRestore, det
if details {
d.Printf("Restic Restores:\n")
} else {
d.Printf("Restic Restores (specify --volume-details for more information):\n")
d.Printf("Restic Restores (specify --details for more information):\n")
}
// separate restores by phase (combining <none> and New into a single group)

View File

@ -48,6 +48,7 @@ import (
"github.com/heptio/ark/pkg/util/encode"
kubeutil "github.com/heptio/ark/pkg/util/kube"
"github.com/heptio/ark/pkg/util/logging"
"github.com/heptio/ark/pkg/volume"
)
const backupVersion = 1
@ -398,6 +399,13 @@ func (c *backupController) runBackup(backup *pkgbackup.Request) error {
// Otherwise, the JSON file in object storage has a CompletionTimestamp of 'null'.
backup.Status.CompletionTimestamp.Time = c.clock.Now()
backup.Status.VolumeSnapshotsAttempted = len(backup.VolumeSnapshots)
for _, snap := range backup.VolumeSnapshots {
if snap.Status.Phase == volume.SnapshotPhaseCompleted {
backup.Status.VolumeSnapshotsCompleted++
}
}
errs = append(errs, persistBackup(backup, backupFile, logFile, backupStore, c.logger)...)
errs = append(errs, recordBackupMetrics(backup.Backup, backupFile, c.metrics))

View File

@ -349,6 +349,8 @@ func (s *objectBackupStore) GetDownloadURL(target arkv1api.DownloadTarget) (stri
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupContentsKey(target.Name), DownloadURLTTL)
case arkv1api.DownloadTargetKindBackupLog:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupLogKey(target.Name), DownloadURLTTL)
case arkv1api.DownloadTargetKindBackupVolumeSnapshots:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupVolumeSnapshotsKey(target.Name), DownloadURLTTL)
case arkv1api.DownloadTargetKindRestoreLog:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getRestoreLogKey(target.Name), DownloadURLTTL)
case arkv1api.DownloadTargetKindRestoreResults: