enable incrementals with feature flags (#1903)

## Description

Allows the usage of incrementals via feature-
flag controls.  Feature flags can be enabled
on a per-flag basis in either the cli (the flag
is hidden from users, normally) or through the
sdk by directly toggling the flag property
in the control.Options.

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

- [x]  No 

## Type of change

- [x] 🌻 Feature

## Issue(s)

* #1901

## Test Plan

- [x] 💪 Manual
This commit is contained in:
Keepers 2022-12-21 14:31:48 -07:00 committed by GitHub
parent a45aeda4a2
commit 5b6b60de60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 106 additions and 38 deletions

View File

@ -106,6 +106,7 @@ func addExchangeCommands(cmd *cobra.Command) *cobra.Command {
switch cmd.Use {
case createCommand:
c, fs = utils.AddCommand(cmd, exchangeCreateCmd())
options.AddFeatureFlags(cmd, options.ExchangeIncrementals())
c.Use = c.Use + " " + exchangeServiceCommandCreateUseSuffix
c.Example = exchangeServiceCommandCreateExamples

View File

@ -2,10 +2,34 @@ package options
import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/alcionai/corso/src/pkg/control"
)
// Control produces the control options based on the user's flags.
func Control() control.Options {
opt := control.Defaults()
if fastFail {
opt.FailFast = true
}
if noStats {
opt.DisableMetrics = true
}
if exchangeIncrementals {
opt.EnabledFeatures.ExchangeIncrementals = true
}
return opt
}
// ---------------------------------------------------------------------------
// Operations Flags
// ---------------------------------------------------------------------------
var (
fastFail bool
noStats bool
@ -25,17 +49,32 @@ func AddGlobalOperationFlags(cmd *cobra.Command) {
fs.BoolVar(&noStats, "no-stats", false, "disable anonymous usage statistics gathering")
}
// Control produces the control options based on the user's flags.
func Control() control.Options {
opt := control.Defaults()
// ---------------------------------------------------------------------------
// Feature Flags
// ---------------------------------------------------------------------------
if fastFail {
opt.FailFast = true
var exchangeIncrementals bool
type exposeFeatureFlag func(*pflag.FlagSet)
// AddFeatureFlags adds CLI flags for each exposed feature flags to the
// persistent flag set within the command.
func AddFeatureFlags(cmd *cobra.Command, effs ...exposeFeatureFlag) {
fs := cmd.PersistentFlags()
for _, fflag := range effs {
fflag(fs)
}
}
// Adds the '--exchange-incrementals' cli flag which, when set, enables
// incrementals data retrieval for exchange backups.
func ExchangeIncrementals() func(*pflag.FlagSet) {
return func(fs *pflag.FlagSet) {
fs.BoolVar(
&exchangeIncrementals,
"exchange-incrementals",
false,
"Enable incremental data retrieval in Exchange backups.")
cobra.CheckErr(fs.MarkHidden("exchange-incrementals"))
}
if noStats {
opt.DisableMetrics = true
}
return opt
}

View File

@ -360,11 +360,9 @@ func FetchContactIDsFromDirectory(
Contacts().
Delta()
// TODO(rkeepers): Awaiting full integration of incremental support, else this
// will cause unexpected behavior/errors.
// if len(oldDelta) > 0 {
// builder = msuser.NewUsersItemContactFoldersItemContactsDeltaRequestBuilder(oldDelta, gs.Adapter())
// }
if len(oldDelta) > 0 {
builder = msuser.NewItemContactFoldersItemContactsDeltaRequestBuilder(oldDelta, gs.Adapter())
}
for {
resp, err := builder.Get(ctx, options)
@ -434,11 +432,9 @@ func FetchMessageIDsFromDirectory(
Messages().
Delta()
// TODO(rkeepers): Awaiting full integration of incremental support, else this
// will cause unexpected behavior/errors.
// if len(oldDelta) > 0 {
// builder = msuser.NewUsersItemMailFoldersItemMessagesDeltaRequestBuilder(oldDelta, gs.Adapter())
// }
if len(oldDelta) > 0 {
builder = msuser.NewItemMailFoldersItemMessagesDeltaRequestBuilder(oldDelta, gs.Adapter())
}
for {
resp, err := builder.Get(ctx, options)

View File

@ -129,8 +129,9 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
}()
oc := selectorToOwnersCats(op.Selectors)
srm := shouldRetrieveMetadata(op.Selectors, op.Options)
mans, mdColls, err := produceManifestsAndMetadata(ctx, op.kopia, op.store, oc, tenantID)
mans, mdColls, err := produceManifestsAndMetadata(ctx, op.kopia, op.store, oc, tenantID, srm)
if err != nil {
opStats.readErr = errors.Wrap(err, "connecting to M365")
return opStats.readErr
@ -142,7 +143,7 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
return opStats.readErr
}
cs, err := produceBackupDataCollections(ctx, gc, op.Selectors, mdColls, control.Options{})
cs, err := produceBackupDataCollections(ctx, gc, op.Selectors, mdColls, op.Options)
if err != nil {
opStats.readErr = errors.Wrap(err, "retrieving data to backup")
return opStats.readErr
@ -175,6 +176,12 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
return err
}
// checker to see if conditions are correct for retrieving metadata like delta tokens
// and previous paths.
func shouldRetrieveMetadata(sel selectors.Selector, opts control.Options) bool {
return opts.EnabledFeatures.ExchangeIncrementals && sel.Service == selectors.ServiceExchange
}
// calls kopia to retrieve prior backup manifests, metadata collections to supply backup heuristics.
func produceManifestsAndMetadata(
ctx context.Context,
@ -182,6 +189,7 @@ func produceManifestsAndMetadata(
sw *store.Wrapper,
oc *kopia.OwnersCats,
tenantID string,
getMetadata bool,
) ([]*kopia.ManifestEntry, []data.Collection, error) {
complete, closer := observe.MessageWithCompletion("Fetching backup heuristics:")
defer func() {
@ -203,6 +211,10 @@ func produceManifestsAndMetadata(
return nil, nil, err
}
if !getMetadata {
return ms, nil, nil
}
for _, man := range ms {
if len(man.IncompleteReason) > 0 {
continue

View File

@ -4,9 +4,25 @@ import (
"github.com/alcionai/corso/src/internal/common"
)
const (
defaultRestoreLocation = "Corso_Restore_"
)
// Options holds the optional configurations for a process
type Options struct {
Collision CollisionPolicy `json:"-"`
DisableMetrics bool `json:"disableMetrics"`
FailFast bool `json:"failFast"`
EnabledFeatures FeatureFlags `json:"enabledFeatures"`
}
// Defaults provides an Options with the default values set.
func Defaults() Options {
return Options{
FailFast: true,
EnabledFeatures: FeatureFlags{},
}
}
// ---------------------------------------------------------------------------
// Restore Item Collision Policy
// ---------------------------------------------------------------------------
// CollisionPolicy describes how the datalayer behaves in case of a collision.
type CollisionPolicy int
@ -19,19 +35,13 @@ const (
Replace
)
// Options holds the optional configurations for a process
type Options struct {
Collision CollisionPolicy `json:"-"`
DisableMetrics bool `json:"disableMetrics"`
FailFast bool `json:"failFast"`
}
// ---------------------------------------------------------------------------
// Restore Destination
// ---------------------------------------------------------------------------
// Defaults provides an Options with the default values set.
func Defaults() Options {
return Options{
FailFast: true,
}
}
const (
defaultRestoreLocation = "Corso_Restore_"
)
// RestoreDestination is a POD that contains an override of the resource owner
// to restore data under and the name of the root of the restored container
@ -51,3 +61,13 @@ func DefaultRestoreDestination(timeFormat common.TimeFormat) RestoreDestination
ContainerName: defaultRestoreLocation + common.FormatNow(timeFormat),
}
}
// ---------------------------------------------------------------------------
// Feature Flags
// ---------------------------------------------------------------------------
type FeatureFlags struct {
// ExchangeIncrementals allow for re-use of delta links when backing up
// exchange data, reducing the amount of data pulled from graph.
ExchangeIncrementals bool `json:"incrementals,omitempty"`
}