Create CLI command for maintenance (#3226)
Creates hidden CLI command to run maintenance. Flag names and descriptions can be updated if something else would fit better --- #### Does this PR need a docs update or release note? - [ ] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [ ] ⛔ No #### Type of change - [x] 🌻 Feature - [ ] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Issue(s) * #3077 #### Test Plan - [x] 💪 Manual - [x] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
965427d491
commit
089a96d437
@ -1,12 +1,21 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/alcionai/corso/src/cli/print"
|
||||
"github.com/alcionai/corso/src/cli/utils"
|
||||
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
initCommand = "init"
|
||||
connectCommand = "connect"
|
||||
initCommand = "init"
|
||||
connectCommand = "connect"
|
||||
maintenanceCommand = "maintenance"
|
||||
)
|
||||
|
||||
var repoCommands = []func(cmd *cobra.Command) *cobra.Command{
|
||||
@ -18,15 +27,24 @@ func AddCommands(cmd *cobra.Command) {
|
||||
var (
|
||||
// Get new instances so that setting the context during tests works
|
||||
// properly.
|
||||
repoCmd = repoCmd()
|
||||
initCmd = initCmd()
|
||||
connectCmd = connectCmd()
|
||||
repoCmd = repoCmd()
|
||||
initCmd = initCmd()
|
||||
connectCmd = connectCmd()
|
||||
maintenanceCmd = maintenanceCmd()
|
||||
)
|
||||
|
||||
cmd.AddCommand(repoCmd)
|
||||
repoCmd.AddCommand(initCmd)
|
||||
repoCmd.AddCommand(connectCmd)
|
||||
|
||||
utils.AddCommand(
|
||||
repoCmd,
|
||||
maintenanceCmd,
|
||||
utils.HideCommand(),
|
||||
utils.MarkPreReleaseCommand())
|
||||
utils.AddMaintenanceModeFlag(maintenanceCmd)
|
||||
utils.AddForceMaintenanceFlag(maintenanceCmd)
|
||||
|
||||
for _, addRepoTo := range repoCommands {
|
||||
addRepoTo(initCmd)
|
||||
addRepoTo(connectCmd)
|
||||
@ -84,3 +102,65 @@ func connectCmd() *cobra.Command {
|
||||
func handleConnectCmd(cmd *cobra.Command, args []string) error {
|
||||
return cmd.Help()
|
||||
}
|
||||
|
||||
func maintenanceCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: maintenanceCommand,
|
||||
Short: "Run maintenance on an existing repository",
|
||||
Long: `Run maintenance on an existing repository to optimize performance and storage use`,
|
||||
RunE: handleMaintenanceCmd,
|
||||
Args: cobra.NoArgs,
|
||||
}
|
||||
}
|
||||
|
||||
func handleMaintenanceCmd(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
t, err := getMaintenanceType(utils.MaintenanceModeFV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, _, err := utils.GetAccountAndConnect(ctx)
|
||||
if err != nil {
|
||||
return print.Only(ctx, err)
|
||||
}
|
||||
|
||||
defer utils.CloseRepo(ctx, r)
|
||||
|
||||
m, err := r.NewMaintenance(
|
||||
ctx,
|
||||
repository.Maintenance{
|
||||
Type: t,
|
||||
Safety: repository.FullMaintenanceSafety,
|
||||
Force: utils.ForceMaintenanceFV,
|
||||
})
|
||||
if err != nil {
|
||||
return print.Only(ctx, err)
|
||||
}
|
||||
|
||||
err = m.Run(ctx)
|
||||
if err != nil {
|
||||
return print.Only(ctx, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMaintenanceType(t string) (repository.MaintenanceType, error) {
|
||||
res, ok := repository.StringToMaintenanceType[t]
|
||||
if !ok {
|
||||
modes := maps.Keys(repository.StringToMaintenanceType)
|
||||
allButLast := []string{}
|
||||
|
||||
for i := 0; i < len(modes)-1; i++ {
|
||||
allButLast = append(allButLast, string(modes[i]))
|
||||
}
|
||||
|
||||
valuesStr := strings.Join(allButLast, ", ") + " or " + string(modes[len(modes)-1])
|
||||
|
||||
return res, clues.New(t + " is an unrecognized maintenance mode; must be one of " + valuesStr)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
41
src/cli/repo/repo_test.go
Normal file
41
src/cli/repo/repo_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/tester"
|
||||
)
|
||||
|
||||
type RepoUnitSuite struct {
|
||||
tester.Suite
|
||||
}
|
||||
|
||||
func TestRepoUnitSuite(t *testing.T) {
|
||||
suite.Run(t, &RepoUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||
}
|
||||
|
||||
func (suite *RepoUnitSuite) TestAddRepoCommands() {
|
||||
t := suite.T()
|
||||
cmd := &cobra.Command{}
|
||||
|
||||
AddCommands(cmd)
|
||||
|
||||
var found bool
|
||||
|
||||
// This is the repo command.
|
||||
repoCmds := cmd.Commands()
|
||||
require.Len(t, repoCmds, 1)
|
||||
|
||||
for _, c := range repoCmds[0].Commands() {
|
||||
if c.Use == maintenanceCommand {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, found, "looking for maintenance command")
|
||||
}
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||
"github.com/alcionai/corso/src/pkg/path"
|
||||
"github.com/alcionai/corso/src/pkg/selectors"
|
||||
)
|
||||
@ -37,6 +38,9 @@ var (
|
||||
|
||||
// for selection of data by category. eg: `--data email,contacts`
|
||||
CategoryDataFV []string
|
||||
|
||||
MaintenanceModeFV string
|
||||
ForceMaintenanceFV bool
|
||||
)
|
||||
|
||||
// common flag names (eg: FN)
|
||||
@ -59,6 +63,10 @@ const (
|
||||
FileCreatedBeforeFN = "file-created-before"
|
||||
FileModifiedAfterFN = "file-modified-after"
|
||||
FileModifiedBeforeFN = "file-modified-before"
|
||||
|
||||
// Maintenance stuff.
|
||||
MaintenanceModeFN = "mode"
|
||||
ForceMaintenanceFN = "force"
|
||||
)
|
||||
|
||||
// well-known flag values
|
||||
@ -168,6 +176,30 @@ func AddSiteFlag(cmd *cobra.Command) {
|
||||
"Backup data by site URL; accepts '"+Wildcard+"' to select all sites.")
|
||||
}
|
||||
|
||||
func AddMaintenanceModeFlag(cmd *cobra.Command) {
|
||||
fs := cmd.Flags()
|
||||
fs.StringVar(
|
||||
&MaintenanceModeFV,
|
||||
MaintenanceModeFN,
|
||||
repository.CompleteMaintenance.String(),
|
||||
"Type of maintenance operation to run. Pass '"+
|
||||
repository.MetadataMaintenance.String()+"' to run a faster maintenance "+
|
||||
"that does minimal clean-up and optimization. Pass '"+
|
||||
repository.CompleteMaintenance.String()+"' to fully compact existing "+
|
||||
"data and delete unused data.")
|
||||
cobra.CheckErr(fs.MarkHidden(MaintenanceModeFN))
|
||||
}
|
||||
|
||||
func AddForceMaintenanceFlag(cmd *cobra.Command) {
|
||||
fs := cmd.Flags()
|
||||
fs.BoolVar(
|
||||
&ForceMaintenanceFV,
|
||||
ForceMaintenanceFN,
|
||||
false,
|
||||
"Force maintenance. Caution: user must ensure this is not run concurrently on a single repo")
|
||||
cobra.CheckErr(fs.MarkHidden(ForceMaintenanceFN))
|
||||
}
|
||||
|
||||
type PopulatedFlags map[string]struct{}
|
||||
|
||||
func (fs PopulatedFlags) populate(pf *pflag.Flag) {
|
||||
|
||||
@ -28,13 +28,15 @@ const (
|
||||
tenantIDDeprecated = "m365_tenant_hash_deprecated"
|
||||
|
||||
// Event Keys
|
||||
CorsoStart = "Corso Start"
|
||||
RepoInit = "Repo Init"
|
||||
RepoConnect = "Repo Connect"
|
||||
BackupStart = "Backup Start"
|
||||
BackupEnd = "Backup End"
|
||||
RestoreStart = "Restore Start"
|
||||
RestoreEnd = "Restore End"
|
||||
CorsoStart = "Corso Start"
|
||||
RepoInit = "Repo Init"
|
||||
RepoConnect = "Repo Connect"
|
||||
BackupStart = "Backup Start"
|
||||
BackupEnd = "Backup End"
|
||||
RestoreStart = "Restore Start"
|
||||
RestoreEnd = "Restore End"
|
||||
MaintenanceStart = "Maintenance Start"
|
||||
MaintenanceEnd = "Maintenance End"
|
||||
|
||||
// Event Data Keys
|
||||
BackupCreateTime = "backup_creation_time"
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"github.com/alcionai/clues"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/crash"
|
||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||
"github.com/alcionai/corso/src/internal/events"
|
||||
"github.com/alcionai/corso/src/internal/kopia"
|
||||
"github.com/alcionai/corso/src/internal/stats"
|
||||
@ -49,20 +50,45 @@ func (op *MaintenanceOperation) Run(ctx context.Context) (err error) {
|
||||
if crErr := crash.Recovery(ctx, recover(), "maintenance"); crErr != nil {
|
||||
err = crErr
|
||||
}
|
||||
|
||||
// TODO(ashmrtn): Send success/failure usage stat?
|
||||
|
||||
op.Results.CompletedAt = time.Now()
|
||||
}()
|
||||
|
||||
op.Results.StartedAt = time.Now()
|
||||
|
||||
// TODO(ashmrtn): Send usage statistics?
|
||||
op.bus.Event(
|
||||
ctx,
|
||||
events.MaintenanceStart,
|
||||
map[string]any{
|
||||
events.StartTime: op.Results.StartedAt,
|
||||
})
|
||||
|
||||
err = op.operation.kopia.RepoMaintenance(ctx, op.mOpts)
|
||||
defer func() {
|
||||
op.bus.Event(
|
||||
ctx,
|
||||
events.MaintenanceEnd,
|
||||
map[string]any{
|
||||
events.StartTime: op.Results.StartedAt,
|
||||
events.Duration: op.Results.CompletedAt.Sub(op.Results.StartedAt),
|
||||
events.EndTime: dttm.Format(op.Results.CompletedAt),
|
||||
events.Status: op.Status.String(),
|
||||
events.Resources: op.mOpts.Type.String(),
|
||||
})
|
||||
}()
|
||||
|
||||
return op.do(ctx)
|
||||
}
|
||||
|
||||
func (op *MaintenanceOperation) do(ctx context.Context) error {
|
||||
defer func() {
|
||||
op.Results.CompletedAt = time.Now()
|
||||
}()
|
||||
|
||||
err := op.operation.kopia.RepoMaintenance(ctx, op.mOpts)
|
||||
if err != nil {
|
||||
op.Status = Failed
|
||||
return clues.Wrap(err, "running maintenance operation")
|
||||
}
|
||||
|
||||
op.Status = Completed
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -26,6 +26,11 @@ const (
|
||||
MetadataMaintenance // metadata
|
||||
)
|
||||
|
||||
var StringToMaintenanceType = map[string]MaintenanceType{
|
||||
CompleteMaintenance.String(): CompleteMaintenance,
|
||||
MetadataMaintenance.String(): MetadataMaintenance,
|
||||
}
|
||||
|
||||
type MaintenanceSafety int
|
||||
|
||||
// Can't be reordered as we rely on iota for numbering.
|
||||
@ -33,5 +38,9 @@ type MaintenanceSafety int
|
||||
//go:generate stringer -type=MaintenanceSafety -linecomment
|
||||
const (
|
||||
FullMaintenanceSafety MaintenanceSafety = iota
|
||||
//nolint:lll
|
||||
// Use only if there's no other kopia instances accessing the repo and the
|
||||
// storage backend is strongly consistent.
|
||||
// https://github.com/kopia/kopia/blob/f9de453efc198b6e993af8922f953a7e5322dc5f/repo/maintenance/maintenance_safety.go#L42
|
||||
NoMaintenanceSafety
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user