Skip to content

Commit 00973af

Browse files
committed
fix: snapshot issues (#574)
1 parent f2f3e92 commit 00973af

File tree

12 files changed

+306
-36
lines changed

12 files changed

+306
-36
lines changed

engine/internal/provision/mode_local_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (m mockFSManager) CreateSnapshot(_, _ string) (snapshotName string, err err
8383
return "", nil
8484
}
8585

86-
func (m mockFSManager) DestroySnapshot(_ string) (err error) {
86+
func (m mockFSManager) DestroySnapshot(_ string, _ thinclones.DestroyOptions) (err error) {
8787
return nil
8888
}
8989

@@ -146,6 +146,10 @@ func (m mockFSManager) ListAllBranches() ([]models.BranchEntity, error) {
146146
return nil, nil
147147
}
148148

149+
func (m mockFSManager) GetSnapshotProperties(_ string) (thinclones.SnapshotProperties, error) {
150+
return thinclones.SnapshotProperties{}, nil
151+
}
152+
149153
func (m mockFSManager) AddBranchProp(_, _ string) error {
150154
return nil
151155
}
@@ -202,10 +206,18 @@ func (m mockFSManager) DeleteRootProp(_, _ string) error {
202206
return nil
203207
}
204208

205-
func (m mockFSManager) HasDependentEntity(_ string) error {
209+
func (m mockFSManager) HasDependentEntity(_ string) ([]string, error) {
210+
return nil, nil
211+
}
212+
213+
func (m mockFSManager) KeepRelation(_ string) error {
206214
return nil
207215
}
208216

217+
func (m mockFSManager) FindBranchBySnapshot(snapshot string) (string, error) {
218+
return "", nil
219+
}
220+
209221
func TestBuildPoolEntry(t *testing.T) {
210222
testCases := []struct {
211223
pool *resources.Pool

engine/internal/provision/pool/manager.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ type StateReporter interface {
4545
// Snapshotter describes methods of snapshot management.
4646
type Snapshotter interface {
4747
CreateSnapshot(poolSuffix, dataStateAt string) (snapshotName string, err error)
48-
DestroySnapshot(snapshotName string) (err error)
48+
DestroySnapshot(snapshotName string, options thinclones.DestroyOptions) (err error)
4949
CleanupSnapshots(retentionLimit int) ([]string, error)
5050
SnapshotList() []resources.Snapshot
5151
RefreshSnapshotList()
@@ -67,6 +67,7 @@ type Branching interface {
6767
Move(baseSnap, currentSnap, target string) error
6868
SetMountpoint(path, branch string) error
6969
Rename(oldName, branch string) error
70+
GetSnapshotProperties(snapshotName string) (thinclones.SnapshotProperties, error)
7071
AddBranchProp(branch, snapshotName string) error
7172
DeleteBranchProp(branch, snapshotName string) error
7273
DeleteChildProp(childSnapshot, snapshotName string) error
@@ -75,7 +76,9 @@ type Branching interface {
7576
SetDSA(dsa, snapshotName string) error
7677
SetMessage(message, snapshotName string) error
7778
Reset(snapshotID string, options thinclones.ResetOptions) error
78-
HasDependentEntity(snapshotName string) error
79+
HasDependentEntity(snapshotName string) ([]string, error)
80+
KeepRelation(snapshotName string) error
81+
FindBranchBySnapshot(snapshot string) (string, error)
7982
}
8083

8184
// Pooler describes methods for Pool providing.

engine/internal/provision/thinclones/lvm/lvmanager.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func (m *LVManager) CreateSnapshot(_, _ string) (string, error) {
9999
}
100100

101101
// DestroySnapshot is not supported in LVM mode.
102-
func (m *LVManager) DestroySnapshot(_ string) error {
102+
func (m *LVManager) DestroySnapshot(_ string, _ thinclones.DestroyOptions) error {
103103
log.Msg("Destroying a snapshot is not supported in LVM mode. Skip the operation.")
104104

105105
return nil
@@ -205,6 +205,13 @@ func (m *LVManager) ListAllBranches() ([]models.BranchEntity, error) {
205205
return nil, nil
206206
}
207207

208+
// GetSnapshotProperties get custom snapshot properties.
209+
func (m *LVManager) GetSnapshotProperties(_ string) (thinclones.SnapshotProperties, error) {
210+
log.Msg("GetSnapshotProperties is not supported for LVM. Skip the operation")
211+
212+
return thinclones.SnapshotProperties{}, nil
213+
}
214+
208215
// AddBranchProp adds branch to snapshot property.
209216
func (m *LVManager) AddBranchProp(_, _ string) error {
210217
log.Msg("AddBranchProp is not supported for LVM. Skip the operation")
@@ -297,8 +304,22 @@ func (m *LVManager) Move(_, _, _ string) error {
297304
}
298305

299306
// HasDependentEntity checks if snapshot has dependent entities.
300-
func (m *LVManager) HasDependentEntity(_ string) error {
307+
func (m *LVManager) HasDependentEntity(_ string) ([]string, error) {
301308
log.Msg("HasDependentEntity is not supported for LVM. Skip the operation")
302309

310+
return nil, nil
311+
}
312+
313+
// KeepRelation keeps relation between adjacent snapshots.
314+
func (m *LVManager) KeepRelation(_ string) error {
315+
log.Msg("KeepRelation is not supported for LVM. Skip the operation")
316+
303317
return nil
304318
}
319+
320+
// FindBranchBySnapshot finds the branch which the snapshot belongs to.
321+
func (m *LVManager) FindBranchBySnapshot(_ string) (string, error) {
322+
log.Msg("KeepRelation is not supported for LVM. Skip the operation")
323+
324+
return "", nil
325+
}

engine/internal/provision/thinclones/manager.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,19 @@ func NewSnapshotExistsError(name string) *SnapshotExistsError {
2929
func (e *SnapshotExistsError) Error() string {
3030
return fmt.Sprintf(`snapshot %s already exists`, e.name)
3131
}
32+
33+
// DestroyOptions provides options for destroy commands.
34+
type DestroyOptions struct {
35+
Force bool
36+
}
37+
38+
// SnapshotProperties describe custom properties of the dataset.
39+
type SnapshotProperties struct {
40+
Name string
41+
Parent string
42+
Child string
43+
Branch string
44+
Root string
45+
DataStateAt string
46+
Message string
47+
}

engine/internal/provision/thinclones/zfs/branching.go

Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package zfs
77
import (
88
"bytes"
99
"encoding/base64"
10+
"errors"
1011
"fmt"
1112
"strings"
1213

@@ -410,6 +411,43 @@ func unwindField(field string) []string {
410411
return items
411412
}
412413

414+
// GetSnapshotProperties get custom snapshot properties.
415+
func (m *Manager) GetSnapshotProperties(snapshotName string) (thinclones.SnapshotProperties, error) {
416+
strFields := bytes.TrimRight(bytes.Repeat([]byte(`%s,`), len(repoFields)), ",")
417+
418+
// Get ZFS snapshot (-t) with options (-o) without output headers (-H) filtered by snapshot.
419+
format := `zfs list -H -t snapshot -o ` + string(strFields) + ` %s`
420+
421+
args := append(repoFields, snapshotName)
422+
423+
out, err := m.runner.Run(fmt.Sprintf(format, args...))
424+
if err != nil {
425+
log.Dbg(out)
426+
427+
return thinclones.SnapshotProperties{}, err
428+
}
429+
430+
fields := strings.Fields(strings.TrimSpace(out))
431+
432+
if len(fields) != len(repoFields) {
433+
log.Dbg("Retrieved fields values:", fields)
434+
435+
return thinclones.SnapshotProperties{}, errors.New("some snapshot properties could not be retrieved")
436+
}
437+
438+
properties := thinclones.SnapshotProperties{
439+
Name: strings.Trim(fields[0], empty),
440+
Parent: strings.Trim(fields[1], empty),
441+
Child: strings.Trim(fields[2], empty),
442+
Branch: strings.Trim(fields[3], empty),
443+
Root: strings.Trim(fields[4], empty),
444+
DataStateAt: strings.Trim(fields[5], empty),
445+
Message: decodeCommitMessage(fields[6]),
446+
}
447+
448+
return properties, nil
449+
}
450+
413451
// AddBranchProp adds branch to snapshot property.
414452
func (m *Manager) AddBranchProp(branch, snapshotName string) error {
415453
return m.addToSet(branchProp, snapshotName, branch)
@@ -464,37 +502,100 @@ func (m *Manager) SetMessage(message, snapshotName string) error {
464502
}
465503

466504
// HasDependentEntity gets the root property of the snapshot.
467-
func (m *Manager) HasDependentEntity(snapshotName string) error {
505+
func (m *Manager) HasDependentEntity(snapshotName string) ([]string, error) {
468506
root, err := m.getProperty(rootProp, snapshotName)
469507
if err != nil {
470-
return fmt.Errorf("failed to check root property: %w", err)
508+
return nil, fmt.Errorf("failed to check root property: %w", err)
471509
}
472510

473511
if root != "" {
474-
return fmt.Errorf("snapshot has dependent branches: %s", root)
512+
return nil, fmt.Errorf("snapshot has dependent branches: %s", root)
475513
}
476514

477515
child, err := m.getProperty(childProp, snapshotName)
478516
if err != nil {
479-
return fmt.Errorf("failed to check snapshot child property: %w", err)
517+
return nil, fmt.Errorf("failed to check snapshot child property: %w", err)
480518
}
481519

482520
if child != "" {
483-
return fmt.Errorf("snapshot has dependent snapshots: %s", child)
521+
log.Warn(fmt.Sprintf("snapshot %s has dependent snapshots: %s", snapshotName, child))
484522
}
485523

486524
clones, err := m.checkDependentClones(snapshotName)
487525
if err != nil {
488-
return fmt.Errorf("failed to check dependent clones: %w", err)
526+
return nil, fmt.Errorf("failed to check dependent clones: %w", err)
489527
}
490528

491-
if len(clones) != 0 {
492-
return fmt.Errorf("snapshot has dependent clones: %s", clones)
529+
dependentClones := strings.Split(clones, ",")
530+
531+
// Check clones of dependent snapshots.
532+
if child != "" {
533+
// TODO: limit the max level of recursion.
534+
childClones, err := m.HasDependentEntity(child)
535+
if err != nil {
536+
return nil, fmt.Errorf("failed to check dependent clones of dependent snapshots: %w", err)
537+
}
538+
539+
dependentClones = append(dependentClones, childClones...)
540+
}
541+
542+
return dependentClones, nil
543+
}
544+
545+
// KeepRelation keeps relation between adjacent snapshots.
546+
func (m *Manager) KeepRelation(snapshotName string) error {
547+
child, err := m.getProperty(childProp, snapshotName)
548+
if err != nil {
549+
return fmt.Errorf("failed to check snapshot child property: %w", err)
550+
}
551+
552+
parent, err := m.getProperty(parentProp, snapshotName)
553+
if err != nil {
554+
return fmt.Errorf("failed to check snapshot parent property: %w", err)
555+
}
556+
557+
if parent != "" {
558+
if err := m.DeleteChildProp(snapshotName, parent); err != nil {
559+
return fmt.Errorf("failed to delete child: %w", err)
560+
}
561+
562+
if err := m.addChild(parent, child); err != nil {
563+
return fmt.Errorf("failed to add child: %w", err)
564+
}
565+
}
566+
567+
if child != "" {
568+
if err := m.setParent(parent, child); err != nil {
569+
return fmt.Errorf("failed to set parent: %w", err)
570+
}
493571
}
494572

495573
return nil
496574
}
497575

576+
// FindBranchBySnapshot finds the branch which the snapshot belongs to.
577+
func (m *Manager) FindBranchBySnapshot(snapshot string) (string, error) {
578+
branch, err := m.getProperty(branchProp, snapshot)
579+
if err != nil {
580+
return "", err
581+
}
582+
583+
if branch != "" {
584+
return branch, nil
585+
}
586+
587+
child, err := m.getProperty(childProp, snapshot)
588+
if err != nil {
589+
return "", fmt.Errorf("failed to check snapshot child property: %w", err)
590+
}
591+
592+
if child != "" {
593+
return m.FindBranchBySnapshot(child)
594+
}
595+
596+
return "", nil
597+
}
598+
498599
func (m *Manager) addToSet(property, snapshot, value string) error {
499600
original, err := m.getProperty(property, snapshot)
500601
if err != nil {

engine/internal/provision/thinclones/zfs/zfs.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,13 +366,19 @@ func getSnapshotName(pool, dataStateAt string) string {
366366
}
367367

368368
// DestroySnapshot destroys the snapshot.
369-
func (m *Manager) DestroySnapshot(snapshotName string) error {
369+
func (m *Manager) DestroySnapshot(snapshotName string, opts thinclones.DestroyOptions) error {
370370
rel, err := m.detectBranching(snapshotName)
371371
if err != nil {
372372
return fmt.Errorf("failed to inspect snapshot properties: %w", err)
373373
}
374374

375-
cmd := fmt.Sprintf("zfs destroy %s", snapshotName)
375+
flags := ""
376+
377+
if opts.Force {
378+
flags = "-R"
379+
}
380+
381+
cmd := fmt.Sprintf("zfs destroy %s %s", flags, snapshotName)
376382

377383
if _, err := m.runner.Run(cmd); err != nil {
378384
return fmt.Errorf("failed to run command: %w", err)
@@ -473,6 +479,8 @@ func (m *Manager) CleanupSnapshots(retentionLimit int) ([]string, error) {
473479

474480
out, err := m.runner.Run(cleanupCmd)
475481
if err != nil {
482+
log.Dbg(out)
483+
476484
return nil, errors.Wrap(err, "failed to clean up snapshots")
477485
}
478486

@@ -485,6 +493,7 @@ func (m *Manager) CleanupSnapshots(retentionLimit int) ([]string, error) {
485493

486494
func (m *Manager) getBusySnapshotList(clonesOutput string) []string {
487495
systemClones, userClones := make(map[string]string), make(map[string]struct{})
496+
branchingSnapshots := []string{}
488497

489498
userClonePrefix := m.config.Pool.Name + "/"
490499

@@ -495,6 +504,13 @@ func (m *Manager) getBusySnapshotList(clonesOutput string) []string {
495504
continue
496505
}
497506

507+
// Keep the user-defined snapshot and the snapshot it is based on.
508+
if strings.HasPrefix(cloneLine[0], userClonePrefix+"branch") {
509+
branchingSnapshots = append(branchingSnapshots, cloneLine[0], cloneLine[1])
510+
511+
continue
512+
}
513+
498514
//nolint:lll
499515
if cloneName, _ := strings.CutPrefix(cloneLine[0], userClonePrefix); strings.HasPrefix(cloneLine[0], userClonePrefix) && !strings.Contains(cloneName, m.config.PreSnapshotSuffix) {
500516
origin := cloneLine[1]
@@ -514,9 +530,13 @@ func (m *Manager) getBusySnapshotList(clonesOutput string) []string {
514530
busySnapshots := make([]string, 0, len(userClones))
515531

516532
for userClone := range userClones {
517-
busySnapshots = append(busySnapshots, systemClones[userClone])
533+
if systemClones[userClone] != "" {
534+
busySnapshots = append(busySnapshots, systemClones[userClone])
535+
}
518536
}
519537

538+
busySnapshots = append(busySnapshots, branchingSnapshots...)
539+
520540
return busySnapshots
521541
}
522542

0 commit comments

Comments
 (0)