corso/src/pkg/backup/backup.go
Keepers 8dfb00f308
add fault errors streamstore (#2731)
Adds a new streamstore controller for fault.Errors. This provides large scale, extensible file storage for fault errors to be persisted, much like we do for backup details.

---

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🌻 Feature

#### Issue(s)

* #2708

#### Test Plan

- [x] 💚 E2E
2023-03-17 01:07:07 +00:00

233 lines
5.4 KiB
Go

package backup
import (
"context"
"fmt"
"time"
"github.com/alcionai/corso/src/cli/print"
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/model"
"github.com/alcionai/corso/src/internal/stats"
"github.com/alcionai/corso/src/internal/version"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/selectors"
)
// Backup represents the result of a backup operation
type Backup struct {
model.BaseModel
CreationTime time.Time `json:"creationTime"`
// SnapshotID is the kopia snapshot ID
SnapshotID string `json:"snapshotID"`
// Reference to the details and fault errors storage location.
// Used to read backup.Details and fault.Errors from the streamstore.
StreamStoreID string `json:"streamStoreID"`
// Status of the operation, eg: completed, failed, etc
Status string `json:"status"`
// Selector used in this operation
Selector selectors.Selector `json:"selectors"`
// Version represents the version of the backup format
Version int `json:"version"`
FailFast bool `json:"failFast"`
// the quantity of errors, both hard failure and recoverable.
ErrorCount int `json:"errorCount"`
// the non-recoverable failure message, only populated if one occurred.
Failure string `json:"failure"`
// stats are embedded so that the values appear as top-level properties
stats.ReadWrites
stats.StartAndEndTime
stats.SkippedCounts
// **Deprecated**
// Reference to the backup details storage location.
// Used to read backup.Details from the streamstore.
DetailsID string `json:"detailsID"`
}
// interface compliance checks
var _ print.Printable = &Backup{}
func New(
snapshotID, streamStoreID, status string,
id model.StableID,
selector selectors.Selector,
rw stats.ReadWrites,
se stats.StartAndEndTime,
errs *fault.Bus,
) *Backup {
var (
ee = errs.Errors()
// TODO: count errData.Items(), not all recovered errors.
errCount = len(ee.Recovered)
failMsg string
malware, notFound, otherSkips int
)
if ee.Failure != nil {
failMsg = ee.Failure.Msg
errCount++
}
for _, s := range ee.Skipped {
switch true {
case s.HasCause(fault.SkipMalware):
malware++
case s.HasCause(fault.SkipNotFound):
notFound++
default:
otherSkips++
}
}
return &Backup{
BaseModel: model.BaseModel{
ID: id,
Tags: map[string]string{
model.ServiceTag: selector.PathService().String(),
},
},
Version: version.Backup,
SnapshotID: snapshotID,
StreamStoreID: streamStoreID,
CreationTime: time.Now(),
Status: status,
Selector: selector,
FailFast: errs.FailFast(),
ErrorCount: errCount,
Failure: failMsg,
ReadWrites: rw,
StartAndEndTime: se,
SkippedCounts: stats.SkippedCounts{
TotalSkippedItems: len(ee.Skipped),
SkippedMalware: malware,
},
}
}
// --------------------------------------------------------------------------------
// CLI Output
// --------------------------------------------------------------------------------
// Print writes the Backup to StdOut, in the format requested by the caller.
func (b Backup) Print(ctx context.Context) {
print.Item(ctx, b)
}
// PrintAll writes the slice of Backups to StdOut, in the format requested by the caller.
func PrintAll(ctx context.Context, bs []*Backup) {
if len(bs) == 0 {
print.Info(ctx, "No backups available")
return
}
ps := []print.Printable{}
for _, b := range bs {
ps = append(ps, print.Printable(b))
}
print.All(ctx, ps...)
}
type Printable struct {
ID model.StableID `json:"id"`
ErrorCount int `json:"errorCount"`
StartedAt time.Time `json:"started at"`
Status string `json:"status"`
Version string `json:"version"`
BytesRead int64 `json:"bytesRead"`
BytesUploaded int64 `json:"bytesUploaded"`
Owner string `json:"owner"`
}
// MinimumPrintable reduces the Backup to its minimally printable details.
func (b Backup) MinimumPrintable() any {
return Printable{
ID: b.ID,
ErrorCount: b.ErrorCount,
StartedAt: b.StartedAt,
Status: b.Status,
Version: "0",
BytesRead: b.BytesRead,
BytesUploaded: b.BytesUploaded,
Owner: b.Selector.DiscreteOwner,
}
}
// Headers returns the human-readable names of properties in a Backup
// for printing out to a terminal in a columnar display.
func (b Backup) Headers() []string {
return []string{
"Started At",
"ID",
"Status",
"Resource Owner",
}
}
// Values returns the values matching the Headers list for printing
// out to a terminal in a columnar display.
func (b Backup) Values() []string {
var (
status = b.Status
errCount = b.ErrorCount
)
if errCount+b.TotalSkippedItems > 0 {
status += (" (")
}
if errCount > 0 {
status += fmt.Sprintf("%d errors", errCount)
}
if errCount > 0 && b.TotalSkippedItems > 0 {
status += ", "
}
if b.TotalSkippedItems > 0 {
status += fmt.Sprintf("%d skipped", b.TotalSkippedItems)
if b.SkippedMalware+b.SkippedNotFound > 0 {
status += ": "
}
}
if b.SkippedMalware > 0 {
status += fmt.Sprintf("%d malware", b.SkippedMalware)
if b.SkippedNotFound > 0 {
status += ", "
}
}
if b.SkippedNotFound > 0 {
status += fmt.Sprintf("%d not found", b.SkippedNotFound)
}
if errCount+b.TotalSkippedItems > 0 {
status += (")")
}
return []string{
common.FormatTabularDisplayTime(b.StartedAt),
string(b.ID),
status,
b.Selector.DiscreteOwner,
}
}