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 { switch cmd.Use {
case createCommand: case createCommand:
c, fs = utils.AddCommand(cmd, exchangeCreateCmd()) c, fs = utils.AddCommand(cmd, exchangeCreateCmd())
options.AddFeatureFlags(cmd, options.ExchangeIncrementals())
c.Use = c.Use + " " + exchangeServiceCommandCreateUseSuffix c.Use = c.Use + " " + exchangeServiceCommandCreateUseSuffix
c.Example = exchangeServiceCommandCreateExamples c.Example = exchangeServiceCommandCreateExamples

View File

@ -2,10 +2,34 @@ package options
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/alcionai/corso/src/pkg/control" "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 ( var (
fastFail bool fastFail bool
noStats bool noStats bool
@ -25,17 +49,32 @@ func AddGlobalOperationFlags(cmd *cobra.Command) {
fs.BoolVar(&noStats, "no-stats", false, "disable anonymous usage statistics gathering") 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 { // Feature Flags
opt := control.Defaults() // ---------------------------------------------------------------------------
if fastFail { var exchangeIncrementals bool
opt.FailFast = true
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(). Contacts().
Delta() Delta()
// TODO(rkeepers): Awaiting full integration of incremental support, else this if len(oldDelta) > 0 {
// will cause unexpected behavior/errors. builder = msuser.NewItemContactFoldersItemContactsDeltaRequestBuilder(oldDelta, gs.Adapter())
// if len(oldDelta) > 0 { }
// builder = msuser.NewUsersItemContactFoldersItemContactsDeltaRequestBuilder(oldDelta, gs.Adapter())
// }
for { for {
resp, err := builder.Get(ctx, options) resp, err := builder.Get(ctx, options)
@ -434,11 +432,9 @@ func FetchMessageIDsFromDirectory(
Messages(). Messages().
Delta() Delta()
// TODO(rkeepers): Awaiting full integration of incremental support, else this if len(oldDelta) > 0 {
// will cause unexpected behavior/errors. builder = msuser.NewItemMailFoldersItemMessagesDeltaRequestBuilder(oldDelta, gs.Adapter())
// if len(oldDelta) > 0 { }
// builder = msuser.NewUsersItemMailFoldersItemMessagesDeltaRequestBuilder(oldDelta, gs.Adapter())
// }
for { for {
resp, err := builder.Get(ctx, options) 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) 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 { if err != nil {
opStats.readErr = errors.Wrap(err, "connecting to M365") opStats.readErr = errors.Wrap(err, "connecting to M365")
return opStats.readErr return opStats.readErr
@ -142,7 +143,7 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
return opStats.readErr 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 { if err != nil {
opStats.readErr = errors.Wrap(err, "retrieving data to backup") opStats.readErr = errors.Wrap(err, "retrieving data to backup")
return opStats.readErr return opStats.readErr
@ -175,6 +176,12 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
return err 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. // calls kopia to retrieve prior backup manifests, metadata collections to supply backup heuristics.
func produceManifestsAndMetadata( func produceManifestsAndMetadata(
ctx context.Context, ctx context.Context,
@ -182,6 +189,7 @@ func produceManifestsAndMetadata(
sw *store.Wrapper, sw *store.Wrapper,
oc *kopia.OwnersCats, oc *kopia.OwnersCats,
tenantID string, tenantID string,
getMetadata bool,
) ([]*kopia.ManifestEntry, []data.Collection, error) { ) ([]*kopia.ManifestEntry, []data.Collection, error) {
complete, closer := observe.MessageWithCompletion("Fetching backup heuristics:") complete, closer := observe.MessageWithCompletion("Fetching backup heuristics:")
defer func() { defer func() {
@ -203,6 +211,10 @@ func produceManifestsAndMetadata(
return nil, nil, err return nil, nil, err
} }
if !getMetadata {
return ms, nil, nil
}
for _, man := range ms { for _, man := range ms {
if len(man.IncompleteReason) > 0 { if len(man.IncompleteReason) > 0 {
continue continue

View File

@ -4,9 +4,25 @@ import (
"github.com/alcionai/corso/src/internal/common" "github.com/alcionai/corso/src/internal/common"
) )
const ( // Options holds the optional configurations for a process
defaultRestoreLocation = "Corso_Restore_" 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. // CollisionPolicy describes how the datalayer behaves in case of a collision.
type CollisionPolicy int type CollisionPolicy int
@ -19,19 +35,13 @@ const (
Replace Replace
) )
// Options holds the optional configurations for a process // ---------------------------------------------------------------------------
type Options struct { // Restore Destination
Collision CollisionPolicy `json:"-"` // ---------------------------------------------------------------------------
DisableMetrics bool `json:"disableMetrics"`
FailFast bool `json:"failFast"`
}
// Defaults provides an Options with the default values set. const (
func Defaults() Options { defaultRestoreLocation = "Corso_Restore_"
return Options{ )
FailFast: true,
}
}
// RestoreDestination is a POD that contains an override of the resource owner // 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 // 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), 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"`
}