corso/src/cmd/longevity_test/longevity.go
ashmrtn f0425a0f9d
Add test to lookup deleted backup by opening old version of repo (#3925)
Make helper function that allows
* opening the repo at a previous point in time
* listing backups
* searching the list for backups in a given set

This will allow us to open the repo just prior to
the point in time at which backups were deleted
so we can ensure PITR is working right

This PR does not enable this functionality in
CI, it only adds the code for it

---

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

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [x] 🤖 Supportability/Tests
- [x] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

#### Issue(s)

* #3799

#### Test Plan

- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
2023-07-31 18:26:38 +00:00

172 lines
4.1 KiB
Go

package main
import (
"context"
"fmt"
"os"
"strconv"
"time"
"github.com/alcionai/clues"
"github.com/spf13/cobra"
"golang.org/x/exp/maps"
"github.com/alcionai/corso/src/cli/config"
"github.com/alcionai/corso/src/cli/utils"
"github.com/alcionai/corso/src/pkg/logger"
"github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/store"
)
// deleteBackups connects to the repository and deletes all backups for
// service that are at least deletionDays old. Returns the IDs of all backups
// that were deleted.
func deleteBackups(
ctx context.Context,
service path.ServiceType,
deletionDays int,
) ([]string, error) {
ctx = clues.Add(ctx, "cutoff_days", deletionDays)
r, _, _, _, err := utils.GetAccountAndConnect(ctx, service, nil)
if err != nil {
return nil, clues.Wrap(err, "connecting to account").WithClues(ctx)
}
defer r.Close(ctx)
backups, err := r.BackupsByTag(ctx, store.Service(service))
if err != nil {
return nil, clues.Wrap(err, "listing backups").WithClues(ctx)
}
var (
deleted []string
cutoff = time.Now().Add(-time.Hour * 24 * time.Duration(deletionDays))
)
for _, backup := range backups {
if backup.StartAndEndTime.CompletedAt.Before(cutoff) {
if err := r.DeleteBackup(ctx, backup.ID.String()); err != nil {
return nil, clues.Wrap(
err,
"deleting backup").
With("backup_id", backup.ID).
WithClues(ctx)
}
deleted = append(deleted, backup.ID.String())
logAndPrint(ctx, "Deleted backup %s", backup.ID.String())
}
}
return deleted, nil
}
// pitrListBackups connects to the repository at the given point in time and
// lists the backups for service. It then checks the list of backups contains
// the backups in backupIDs.
//
//nolint:unused
//lint:ignore U1000 Waiting for full support.
func pitrListBackups(
ctx context.Context,
service path.ServiceType,
pitr time.Time,
backupIDs []string,
) error {
if len(backupIDs) == 0 {
return nil
}
ctx = clues.Add(ctx, "pitr_time", pitr, "search_backups", backupIDs)
// TODO(ashmrtn): This may be moved into CLI layer at some point when we add
// flags for opening a repo at a point in time.
cfg, err := config.GetConfigRepoDetails(ctx, true, true, nil)
if err != nil {
return clues.Wrap(err, "getting config info")
}
opts := utils.ControlWithConfig(cfg)
opts.Repo.ViewTimestamp = &pitr
r, err := repository.Connect(ctx, cfg.Account, cfg.Storage, cfg.RepoID, opts)
if err != nil {
return clues.Wrap(err, "connecting to repo").WithClues(ctx)
}
defer r.Close(ctx)
backups, err := r.BackupsByTag(ctx, store.Service(service))
if err != nil {
return clues.Wrap(err, "listing backups").WithClues(ctx)
}
bups := map[string]struct{}{}
for _, backup := range backups {
bups[backup.ID.String()] = struct{}{}
}
ctx = clues.Add(ctx, "found_backups", maps.Keys(bups))
for _, backupID := range backupIDs {
if _, ok := bups[backupID]; !ok {
return clues.New("looking for backup").
With("search_backup_id", backupID).
WithClues(ctx)
}
}
return nil
}
func main() {
var (
service path.ServiceType
cc = cobra.Command{}
)
cc.SetContext(context.Background())
if err := config.InitFunc(&cc, []string{}); err != nil {
return
}
switch serviceName := os.Getenv("SERVICE"); serviceName {
case "exchange":
service = path.ExchangeService
case "onedrive":
service = path.OneDriveService
case "sharepoint":
service = path.SharePointService
default:
fatal(cc.Context(), "unknown service", nil)
}
ctx := clues.Add(cc.Context(), "service", service)
days, err := strconv.Atoi(os.Getenv("DELETION_DAYS"))
if err != nil {
fatal(ctx, "invalid number of days provided", nil)
}
_, err = deleteBackups(ctx, service, days)
if err != nil {
fatal(cc.Context(), "deleting backups", clues.Stack(err))
}
}
func fatal(ctx context.Context, msg string, err error) {
logger.CtxErr(ctx, err).Error("test failure: " + msg)
fmt.Println(msg+": ", err)
os.Exit(1)
}
func logAndPrint(ctx context.Context, tmpl string, vs ...any) {
logger.Ctx(ctx).Infof(tmpl, vs...)
fmt.Printf(tmpl+"\n", vs...)
}