154 lines
4.0 KiB
Go
154 lines
4.0 KiB
Go
package operations
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/kopia/kopia/repo/manifest"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/alcionai/corso/internal/connector"
|
|
"github.com/alcionai/corso/internal/connector/support"
|
|
"github.com/alcionai/corso/internal/kopia"
|
|
"github.com/alcionai/corso/internal/model"
|
|
"github.com/alcionai/corso/pkg/account"
|
|
"github.com/alcionai/corso/pkg/backup"
|
|
"github.com/alcionai/corso/pkg/selectors"
|
|
)
|
|
|
|
// RestoreOperation wraps an operation with restore-specific props.
|
|
type RestoreOperation struct {
|
|
operation
|
|
|
|
BackupID model.ID `json:"backupID"`
|
|
Results RestoreResults `json:"results"`
|
|
Selectors selectors.Selector `json:"selectors"` // todo: replace with Selectors
|
|
Version string `json:"version"`
|
|
|
|
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 Options,
|
|
kw *kopia.Wrapper,
|
|
ms *kopia.ModelStore,
|
|
acct account.Account,
|
|
backupID model.ID,
|
|
sel selectors.Selector,
|
|
) (RestoreOperation, error) {
|
|
op := RestoreOperation{
|
|
operation: newOperation(opts, kw, ms),
|
|
BackupID: backupID,
|
|
Selectors: sel,
|
|
Version: "v0",
|
|
account: acct,
|
|
}
|
|
if err := op.validate(); err != nil {
|
|
return RestoreOperation{}, err
|
|
}
|
|
|
|
return op, nil
|
|
}
|
|
|
|
func (op RestoreOperation) validate() error {
|
|
return op.operation.validate()
|
|
}
|
|
|
|
// aggregates stats from the restore.Run().
|
|
// primarily used so that the defer can take in a
|
|
// pointer wrapping the values, while those values
|
|
// get populated asynchronously.
|
|
type restoreStats struct {
|
|
cs []connector.DataCollection
|
|
gc *support.ConnectorOperationStatus
|
|
readErr, writeErr error
|
|
}
|
|
|
|
// Run begins a synchronous restore operation.
|
|
// todo (keepers): return stats block in first param.
|
|
func (op *RestoreOperation) Run(ctx context.Context) error {
|
|
// TODO: persist initial state of restoreOperation in modelstore
|
|
|
|
// persist operation results to the model store on exit
|
|
stats := restoreStats{}
|
|
defer op.persistResults(time.Now(), &stats)
|
|
|
|
// retrieve the restore point details
|
|
bu := backup.Backup{}
|
|
err := op.modelStore.Get(ctx, kopia.BackupModel, op.BackupID, &bu)
|
|
if err != nil {
|
|
stats.readErr = errors.Wrap(err, "retrieving restore point")
|
|
return stats.readErr
|
|
}
|
|
|
|
backup := backup.Details{}
|
|
err = op.modelStore.GetWithModelStoreID(ctx, kopia.BackupDetailsModel, manifest.ID(bu.DetailsID), &backup)
|
|
if err != nil {
|
|
stats.readErr = errors.Wrap(err, "retrieving restore point details")
|
|
return stats.readErr
|
|
}
|
|
|
|
er, err := op.Selectors.ToExchangeRestore()
|
|
if err != nil {
|
|
stats.readErr = err
|
|
return err
|
|
}
|
|
|
|
// format the details and retrieve the items from kopia
|
|
fds := er.FilterDetails(&backup)
|
|
dcs, err := op.kopia.RestoreMultipleItems(ctx, bu.SnapshotID, fds)
|
|
if err != nil {
|
|
stats.readErr = errors.Wrap(err, "retrieving service data")
|
|
return stats.readErr
|
|
}
|
|
stats.cs = dcs
|
|
|
|
// restore those collections using graph
|
|
gc, err := connector.NewGraphConnector(op.account)
|
|
if err != nil {
|
|
stats.writeErr = errors.Wrap(err, "connecting to graph api")
|
|
return stats.writeErr
|
|
}
|
|
|
|
if err := gc.RestoreMessages(ctx, dcs); err != nil {
|
|
stats.writeErr = errors.Wrap(err, "restoring service data")
|
|
return stats.writeErr
|
|
}
|
|
stats.gc = gc.Status()
|
|
|
|
op.Status = Successful
|
|
return nil
|
|
}
|
|
|
|
// writes the restoreOperation outcome to the modelStore.
|
|
func (op *RestoreOperation) persistResults(
|
|
started time.Time,
|
|
stats *restoreStats,
|
|
) {
|
|
op.Status = Successful
|
|
if stats.readErr != nil || stats.writeErr != nil {
|
|
op.Status = Failed
|
|
}
|
|
op.Results.ReadErrors = stats.readErr
|
|
op.Results.WriteErrors = stats.writeErr
|
|
|
|
op.Results.ItemsRead = len(stats.cs) // TODO: file count, not collection count
|
|
|
|
if stats.gc != nil {
|
|
op.Results.ItemsWritten = stats.gc.ObjectCount
|
|
}
|
|
|
|
op.Results.StartedAt = started
|
|
op.Results.CompletedAt = time.Now()
|
|
|
|
// TODO: persist operation to modelstore
|
|
}
|