Merge pull request #974 from skriss/backup-describer
backup describer: show snapshot summary by default, details optionallypull/981/head
commit
6cf3519c3a
|
@ -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
|
||||
|
|
|
@ -30,6 +30,7 @@ type DownloadTargetKind string
|
|||
const (
|
||||
DownloadTargetKindBackupLog DownloadTargetKind = "BackupLog"
|
||||
DownloadTargetKindBackupContents DownloadTargetKind = "BackupContents"
|
||||
DownloadTargetKindBackupVolumeSnapshots DownloadTargetKind = "BackupVolumeSnapshots"
|
||||
DownloadTargetKindRestoreLog DownloadTargetKind = "RestoreLog"
|
||||
DownloadTargetKindRestoreResults DownloadTargetKind = "RestoreResults"
|
||||
)
|
||||
|
|
|
@ -34,7 +34,7 @@ import (
|
|||
func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
||||
var (
|
||||
listOptions metav1.ListOptions
|
||||
volumeDetails bool
|
||||
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
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ import (
|
|||
func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
||||
var (
|
||||
listOptions metav1.ListOptions
|
||||
volumeDetails bool
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
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", 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)
|
||||
}
|
||||
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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue