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
233 lines
5.4 KiB
Go
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,
|
|
}
|
|
}
|