diff --git a/src/internal/operations/backup.go b/src/internal/operations/backup.go index 8930768d7..58ffd134c 100644 --- a/src/internal/operations/backup.go +++ b/src/internal/operations/backup.go @@ -13,26 +13,34 @@ import ( // BackupOperation wraps an operation with backup-specific props. type BackupOperation struct { operation - Version string + + Results BackupResults `json:"results"` + Targets []string `json:"selectors"` // todo: replace with Selectors + Version string `json:"version"` account account.Account +} - Targets []string // something for targets/filter/source/app&users/etc +// BackupResults aggregate the details of the result of the operation. +type BackupResults struct { + summary + metrics + // todo: RestorePoint RestorePoint } // NewBackupOperation constructs and validates a backup operation. func NewBackupOperation( ctx context.Context, - opts OperationOpts, + opts Options, kw *kopia.KopiaWrapper, acct account.Account, targets []string, ) (BackupOperation, error) { op := BackupOperation{ operation: newOperation(opts, kw), + Targets: targets, Version: "v0", account: acct, - Targets: targets, } if err := op.validate(); err != nil { return BackupOperation{}, err diff --git a/src/internal/operations/backup_test.go b/src/internal/operations/backup_test.go index c03bf5ea1..4cc2ac732 100644 --- a/src/internal/operations/backup_test.go +++ b/src/internal/operations/backup_test.go @@ -43,20 +43,20 @@ func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() { table := []struct { name string - opts operations.OperationOpts + opts operations.Options kw *kopia.KopiaWrapper acct account.Account targets []string errCheck assert.ErrorAssertionFunc }{ - {"good", operations.OperationOpts{}, kw, acct, nil, assert.NoError}, - {"missing kopia", operations.OperationOpts{}, nil, acct, nil, assert.Error}, + {"good", operations.Options{}, kw, acct, nil, assert.NoError}, + {"missing kopia", operations.Options{}, nil, acct, nil, assert.Error}, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { _, err := operations.NewBackupOperation( context.Background(), - operations.OperationOpts{}, + operations.Options{}, test.kw, test.acct, nil) diff --git a/src/internal/operations/operation.go b/src/internal/operations/operation.go index 0e35ca3cc..6be542ad5 100644 --- a/src/internal/operations/operation.go +++ b/src/internal/operations/operation.go @@ -4,6 +4,7 @@ import ( "time" "github.com/google/uuid" + multierror "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/alcionai/corso/internal/kopia" @@ -18,35 +19,38 @@ const ( Failed ) +// -------------------------------------------------------------------------------- +// Operation Core +// -------------------------------------------------------------------------------- + // An operation tracks the in-progress workload of some long-running process. // Specific processes (eg: backups, restores, etc) are expected to wrap operation // with process specific details. type operation struct { - ID uuid.UUID // system generated identifier - CreatedAt time.Time // datetime of the operation's creation + ID uuid.UUID `json:"id"` // system generated identifier + CreatedAt time.Time `json:"createdAt"` // datetime of the operation's creation + Options Options `json:"options"` + Status opStatus `json:"status"` - options OperationOpts - kopia *kopia.KopiaWrapper - - Status opStatus - Errors []error + kopia *kopia.KopiaWrapper } -// OperationOpts configure some parameters of the operation -type OperationOpts struct { +// Options configure some parameters of the operation +type Options struct { + // todo: collision handling + // todo: fast fail vs best attempt } func newOperation( - opts OperationOpts, + opts Options, kw *kopia.KopiaWrapper, ) operation { return operation{ ID: uuid.New(), CreatedAt: time.Now(), - options: opts, + Options: opts, kopia: kw, Status: InProgress, - Errors: []error{}, } } @@ -56,3 +60,22 @@ func (op operation) validate() error { } return nil } + +// -------------------------------------------------------------------------------- +// Results +// -------------------------------------------------------------------------------- + +// Summary tracks the total files touched and errors produced +// during an operation. +type summary struct { + ItemsRead int `json:"itemsRead,omitempty"` + ItemsWritten int `json:"itemsWritten,omitempty"` + ReadErrors multierror.Error `json:"readErrors,omitempty"` + WriteErrors multierror.Error `json:"writeErrors,omitempty"` +} + +// Metrics tracks performance details such as timing, throughput, etc. +type metrics struct { + StartedAt time.Time `json:"startedAt"` + CompletedAt time.Time `json:"completedAt"` +} diff --git a/src/internal/operations/operation_test.go b/src/internal/operations/operation_test.go index 32926ace9..465331065 100644 --- a/src/internal/operations/operation_test.go +++ b/src/internal/operations/operation_test.go @@ -19,8 +19,7 @@ func TestOperationSuite(t *testing.T) { func (suite *OperationSuite) TestNewOperation() { t := suite.T() - op := newOperation(OperationOpts{}, nil) - assert.NotNil(t, op.Errors) + op := newOperation(Options{}, nil) assert.NotNil(t, op.ID) } @@ -36,7 +35,7 @@ func (suite *OperationSuite) TestOperation_Validate() { } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - op := newOperation(OperationOpts{}, test.kw) + op := newOperation(Options{}, test.kw) test.errCheck(t, op.validate()) }) } diff --git a/src/internal/operations/restore.go b/src/internal/operations/restore.go index 535543d92..08343c50c 100644 --- a/src/internal/operations/restore.go +++ b/src/internal/operations/restore.go @@ -13,18 +13,25 @@ import ( // RestoreOperation wraps an operation with restore-specific props. type RestoreOperation struct { operation - Version string - restorePointID string - account account.Account + RestorePointID string `json:"restorePointID"` + Results RestoreResults `json:"results"` + Targets []string `json:"selectors"` // todo: replace with Selectors + Version string `json:"bersion"` - Targets []string // something for targets/filter/source/app&users/etc + account account.Account +} + +// RestoreResults aggregate the details of the results of the operation. +type RestoreResults struct { + summary + metrics } // NewRestoreOperation constructs and validates a restore operation. func NewRestoreOperation( ctx context.Context, - opts OperationOpts, + opts Options, kw *kopia.KopiaWrapper, acct account.Account, restorePointID string, @@ -32,10 +39,10 @@ func NewRestoreOperation( ) (RestoreOperation, error) { op := RestoreOperation{ operation: newOperation(opts, kw), + RestorePointID: restorePointID, + Targets: targets, Version: "v0", account: acct, - restorePointID: restorePointID, - Targets: targets, } if err := op.validate(); err != nil { return RestoreOperation{}, err @@ -51,7 +58,7 @@ func (op RestoreOperation) validate() error { // Run begins a synchronous restore operation. // todo (keepers): return stats block in first param. func (op *RestoreOperation) Run(ctx context.Context) error { - dc, err := op.kopia.RestoreSingleItem(ctx, op.restorePointID, op.Targets) + dc, err := op.kopia.RestoreSingleItem(ctx, op.RestorePointID, op.Targets) if err != nil { return errors.Wrap(err, "retrieving service data") } diff --git a/src/internal/operations/restore_test.go b/src/internal/operations/restore_test.go index 98d81d5ff..3dadbb90a 100644 --- a/src/internal/operations/restore_test.go +++ b/src/internal/operations/restore_test.go @@ -37,20 +37,20 @@ func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() { table := []struct { name string - opts operations.OperationOpts + opts operations.Options kw *kopia.KopiaWrapper acct account.Account targets []string errCheck assert.ErrorAssertionFunc }{ - {"good", operations.OperationOpts{}, kw, acct, nil, assert.NoError}, - {"missing kopia", operations.OperationOpts{}, nil, acct, nil, assert.Error}, + {"good", operations.Options{}, kw, acct, nil, assert.NoError}, + {"missing kopia", operations.Options{}, nil, acct, nil, assert.Error}, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { _, err := operations.NewRestoreOperation( context.Background(), - operations.OperationOpts{}, + operations.Options{}, test.kw, test.acct, "restore-point-id", diff --git a/src/pkg/repository/repository.go b/src/pkg/repository/repository.go index d7e833e03..0a9da6305 100644 --- a/src/pkg/repository/repository.go +++ b/src/pkg/repository/repository.go @@ -94,7 +94,7 @@ func (r *Repository) Close(ctx context.Context) error { func (r Repository) NewBackup(ctx context.Context, targets []string) (operations.BackupOperation, error) { return operations.NewBackupOperation( ctx, - operations.OperationOpts{}, + operations.Options{}, r.dataLayer, r.Account, targets) @@ -104,7 +104,7 @@ func (r Repository) NewBackup(ctx context.Context, targets []string) (operations func (r Repository) NewRestore(ctx context.Context, restorePointID string, targets []string) (operations.RestoreOperation, error) { return operations.NewRestoreOperation( ctx, - operations.OperationOpts{}, + operations.Options{}, r.dataLayer, r.Account, restorePointID,