* Enable line width linter Set to 120 which should be long enough to not be annoying but keep things from getting "too long." Adding to get rid of the subjectiveness of what is "too long." Tabs count as a single character.
171 lines
4.4 KiB
Go
171 lines
4.4 KiB
Go
package operations
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/alcionai/corso/internal/connector"
|
|
"github.com/alcionai/corso/internal/connector/support"
|
|
"github.com/alcionai/corso/internal/data"
|
|
"github.com/alcionai/corso/internal/kopia"
|
|
"github.com/alcionai/corso/internal/model"
|
|
"github.com/alcionai/corso/internal/stats"
|
|
"github.com/alcionai/corso/pkg/account"
|
|
"github.com/alcionai/corso/pkg/backup"
|
|
"github.com/alcionai/corso/pkg/backup/details"
|
|
"github.com/alcionai/corso/pkg/control"
|
|
"github.com/alcionai/corso/pkg/selectors"
|
|
"github.com/alcionai/corso/pkg/store"
|
|
)
|
|
|
|
// BackupOperation wraps an operation with backup-specific props.
|
|
type BackupOperation struct {
|
|
operation
|
|
|
|
Results BackupResults `json:"results"`
|
|
Selectors selectors.Selector `json:"selectors"`
|
|
Version string `json:"version"`
|
|
|
|
account account.Account
|
|
}
|
|
|
|
// BackupResults aggregate the details of the result of the operation.
|
|
type BackupResults struct {
|
|
stats.ReadWrites
|
|
stats.StartAndEndTime
|
|
BackupID model.StableID `json:"backupID"`
|
|
}
|
|
|
|
// NewBackupOperation constructs and validates a backup operation.
|
|
func NewBackupOperation(
|
|
ctx context.Context,
|
|
opts control.Options,
|
|
kw *kopia.Wrapper,
|
|
sw *store.Wrapper,
|
|
acct account.Account,
|
|
selector selectors.Selector,
|
|
) (BackupOperation, error) {
|
|
op := BackupOperation{
|
|
operation: newOperation(opts, kw, sw),
|
|
Selectors: selector,
|
|
Version: "v0",
|
|
account: acct,
|
|
}
|
|
if err := op.validate(); err != nil {
|
|
return BackupOperation{}, err
|
|
}
|
|
|
|
return op, nil
|
|
}
|
|
|
|
func (op BackupOperation) validate() error {
|
|
return op.operation.validate()
|
|
}
|
|
|
|
// aggregates stats from the backup.Run().
|
|
// primarily used so that the defer can take in a
|
|
// pointer wrapping the values, while those values
|
|
// get populated asynchronously.
|
|
type backupStats struct {
|
|
k *kopia.BackupStats
|
|
gc *support.ConnectorOperationStatus
|
|
readErr, writeErr error
|
|
}
|
|
|
|
// Run begins a synchronous backup operation.
|
|
func (op *BackupOperation) Run(ctx context.Context) (err error) {
|
|
// TODO: persist initial state of backupOperation in modelstore
|
|
|
|
// persist operation results to the model store on exit
|
|
var (
|
|
opStats backupStats
|
|
backupDetails *details.Details
|
|
)
|
|
defer func() {
|
|
op.persistResults(time.Now(), &opStats)
|
|
|
|
err = op.createBackupModels(ctx, opStats.k.SnapshotID, backupDetails)
|
|
if err != nil {
|
|
opStats.writeErr = err
|
|
// todo: ^ we're not persisting this yet, except for the error shown to the user.
|
|
}
|
|
}()
|
|
|
|
// retrieve data from the producer
|
|
gc, err := connector.NewGraphConnector(op.account)
|
|
if err != nil {
|
|
opStats.readErr = err
|
|
return errors.Wrap(err, "connecting to graph api")
|
|
}
|
|
|
|
var cs []data.Collection
|
|
cs, err = gc.ExchangeDataCollection(ctx, op.Selectors)
|
|
if err != nil {
|
|
opStats.readErr = err
|
|
return errors.Wrap(err, "retrieving service data")
|
|
}
|
|
|
|
// hand the results to the consumer
|
|
opStats.k, backupDetails, err = op.kopia.BackupCollections(ctx, cs)
|
|
if err != nil {
|
|
opStats.writeErr = err
|
|
return errors.Wrap(err, "backing up service data")
|
|
}
|
|
opStats.gc = gc.AwaitStatus()
|
|
|
|
return err
|
|
}
|
|
|
|
// writes the results metrics to the operation results.
|
|
// later stored in the manifest using createBackupModels.
|
|
func (op *BackupOperation) persistResults(
|
|
started time.Time,
|
|
opStats *backupStats,
|
|
) {
|
|
op.Status = Completed
|
|
if opStats.k.TotalFileCount == 0 && (opStats.readErr != nil || opStats.writeErr != nil) {
|
|
op.Status = Failed
|
|
}
|
|
|
|
op.Results.ReadErrors = opStats.readErr
|
|
op.Results.WriteErrors = opStats.writeErr
|
|
|
|
if opStats.gc != nil {
|
|
op.Results.ItemsRead = opStats.gc.Successful
|
|
}
|
|
if opStats.k != nil {
|
|
op.Results.ItemsWritten = opStats.k.TotalFileCount
|
|
}
|
|
|
|
op.Results.StartedAt = started
|
|
op.Results.CompletedAt = time.Now()
|
|
}
|
|
|
|
// stores the operation details, results, and selectors in the backup manifest.
|
|
func (op *BackupOperation) createBackupModels(
|
|
ctx context.Context,
|
|
snapID string,
|
|
backupDetails *details.Details,
|
|
) error {
|
|
err := op.store.Put(ctx, model.BackupDetailsSchema, &backupDetails.DetailsModel)
|
|
if err != nil {
|
|
return errors.Wrap(err, "creating backupdetails model")
|
|
}
|
|
|
|
b := backup.New(
|
|
snapID, string(backupDetails.ModelStoreID), op.Status.String(),
|
|
op.Selectors,
|
|
op.Results.ReadWrites,
|
|
op.Results.StartAndEndTime,
|
|
)
|
|
err = op.store.Put(ctx, model.BackupSchema, b)
|
|
if err != nil {
|
|
return errors.Wrap(err, "creating backup model")
|
|
}
|
|
op.Results.BackupID = b.ID
|
|
|
|
return nil
|
|
}
|