Skip to content

Commit c9605c7

Browse files
VAULT-36947: Support force unloading a snapshot (#8740) (#9036)
* portion of changes for autoloading * add test checking for panic * add endpoint for force unloading * separate method for force unload * changelog * don't redefine constants Co-authored-by: miagilepner <[email protected]>
1 parent 5d632ef commit c9605c7

File tree

5 files changed

+47
-7
lines changed

5 files changed

+47
-7
lines changed

api/sys_raft.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,10 +503,27 @@ func (c *Sys) RaftUnloadSnapshot(snapID string) (*Secret, error) {
503503
// RaftUnloadSnapshotWithContext unloads a snapshot from the raft cluster.
504504
// It accepts a snapshot ID to identify the snapshot to be unloaded.
505505
func (c *Sys) RaftUnloadSnapshotWithContext(ctx context.Context, snapID string) (*Secret, error) {
506+
return c.raftUnloadSnapshotWithContext(ctx, snapID, false)
507+
}
508+
509+
// RaftForceUnloadSnapshot wraps RaftForceUnloadSnapshotWithContext using context.Background.
510+
func (c *Sys) RaftForceUnloadSnapshot(snapID string) (*Secret, error) {
511+
return c.RaftForceUnloadSnapshotWithContext(context.Background(), snapID)
512+
}
513+
514+
// RaftForceUnloadSnapshotWithContext forcefully unloads the given snapshot
515+
func (c *Sys) RaftForceUnloadSnapshotWithContext(ctx context.Context, snapID string) (*Secret, error) {
516+
return c.raftUnloadSnapshotWithContext(ctx, snapID, true)
517+
}
518+
519+
func (c *Sys) raftUnloadSnapshotWithContext(ctx context.Context, snapID string, force bool) (*Secret, error) {
506520
ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
507521
defer cancelFunc()
508522

509523
r := c.c.NewRequest(http.MethodDelete, "/v1/sys/storage/raft/snapshot-load/"+snapID)
524+
if force {
525+
r.Params.Set("force", "true")
526+
}
510527

511528
resp, err := c.c.rawRequestWithContext(ctx, r)
512529
if err != nil {

changelog/_8740.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
```release-note:improvement
2+
core/snapshot-load (enterprise): Add a `force` query parameter to the `DELETE sys/storage/raft/snapshot-load/{snapshot_id}` endpoint to allow for forced deletion of snapshots. This is useful when the snapshot is in a state that prevents normal deletion, such as being in the process of loading.
3+
```
4+
```release-note:improvement
5+
cli (enterprise): Add a `-force` flag to `vault operator raft snapshot unload` command to force deletion of a loaded snapshot.
6+
```

physical/raft/snapshot.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ func (s *BoltSnapshotSink) writeBoltDBFile() error {
364364
defer close(s.doneWritingCh)
365365
defer boltDB.Close()
366366

367-
err := loadSnapshot(boltDB, s.logger, reader, nil, false)
367+
err := loadSnapshot(context.Background(), boltDB, s.logger, reader, nil, false)
368368
if err != nil {
369369
s.writeError = err
370370
return
@@ -503,8 +503,8 @@ func snapshotName(term, index uint64) string {
503503
// FSM. The caller is responsible for closing the reader.
504504
// If pathsToFilter is not nil, the function will filter out any keys that are
505505
// found in the pathsToFilter tree.
506-
func LoadReadOnlySnapshot(fsm *FSM, snapshotFile io.ReadCloser, filterKey func(key string) bool, logger log.Logger) error {
507-
return loadSnapshot(fsm.db, logger, snapshotFile, filterKey, true)
506+
func LoadReadOnlySnapshot(ctx context.Context, fsm *FSM, snapshotFile io.ReadCloser, filterKey func(key string) bool, logger log.Logger) error {
507+
return loadSnapshot(ctx, fsm.db, logger, snapshotFile, filterKey, true)
508508
}
509509

510510
// loadSnapshot loads a snapshot from a file into the supplied boltDB database.
@@ -514,7 +514,7 @@ func LoadReadOnlySnapshot(fsm *FSM, snapshotFile io.ReadCloser, filterKey func(k
514514
// to 1.0.
515515
// If pathsToFilter is not nil, the function will filter out any keys that are
516516
// found in the pathsToFilter tree.
517-
func loadSnapshot(db *bolt.DB, logger log.Logger, snapshotFile io.ReadCloser, filterKey func(key string) bool, readOnly bool) error {
517+
func loadSnapshot(ctx context.Context, db *bolt.DB, logger log.Logger, snapshotFile io.ReadCloser, filterKey func(key string) bool, readOnly bool) error {
518518
// The delimited reader will parse full proto messages from the snapshot data.
519519
protoReader := NewDelimitedReader(snapshotFile, math.MaxInt32)
520520
defer protoReader.Close()
@@ -524,6 +524,11 @@ func loadSnapshot(db *bolt.DB, logger log.Logger, snapshotFile io.ReadCloser, fi
524524
entry := new(pb.StorageEntry)
525525
for !done {
526526
err := db.Update(func(tx *bolt.Tx) error {
527+
select {
528+
case <-ctx.Done():
529+
return fmt.Errorf("context canceled while loading snapshot: %w", ctx.Err())
530+
default:
531+
}
527532
b, err := tx.CreateBucketIfNotExists(dataBucketName)
528533
if readOnly {
529534
b.FillPercent = 1.0

physical/raft/snapshot_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -994,7 +994,7 @@ func TestLoadReadOnlySnapshot(t *testing.T) {
994994
// Create an FSM to load the snapshot data into.
995995
fsm, err := NewFSM(dir, "test-fsm", logger)
996996

997-
err = LoadReadOnlySnapshot(fsm, snapshotFile, toExclude, logger)
997+
err = LoadReadOnlySnapshot(context.Background(), fsm, snapshotFile, toExclude, logger)
998998
require.NoError(t, err)
999999
value, err := fsm.Get(context.Background(), "/path/to/exclude/1")
10001000
require.NoError(t, err)

vault/snapshots/source.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ func (m *manualUploadSource) Type(_ context.Context) string {
3030
return "manual"
3131
}
3232

33-
func (m *manualUploadSource) ReadCloser(_ context.Context) (io.ReadCloser, error) {
34-
return m.r, nil
33+
func (m *manualUploadSource) ReadCloser(ctx context.Context) (io.ReadCloser, error) {
34+
return &ctxAwareReadCloser{ctx, m.r}, nil
35+
}
36+
37+
type ctxAwareReadCloser struct {
38+
ctx context.Context
39+
io.ReadCloser
40+
}
41+
42+
func (c *ctxAwareReadCloser) Read(p []byte) (n int, err error) {
43+
if c.ctx.Err() != nil {
44+
return 0, c.ctx.Err()
45+
}
46+
return c.ReadCloser.Read(p)
3547
}

0 commit comments

Comments
 (0)