mirror of
https://github.com/zrepl/zrepl.git
synced 2024-11-22 16:34:32 +01:00
206 lines
4.6 KiB
Go
206 lines
4.6 KiB
Go
package pruner
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/zrepl/zrepl/logger"
|
|
"github.com/zrepl/zrepl/pruning"
|
|
"github.com/zrepl/zrepl/replication/pdu"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type mockFS struct {
|
|
path string
|
|
snaps []string
|
|
}
|
|
|
|
func (m *mockFS) Filesystem() *pdu.Filesystem {
|
|
return &pdu.Filesystem{
|
|
Path: m.path,
|
|
}
|
|
}
|
|
|
|
func (m *mockFS) FilesystemVersions() []*pdu.FilesystemVersion {
|
|
versions := make([]*pdu.FilesystemVersion, len(m.snaps))
|
|
for i, v := range m.snaps {
|
|
versions[i] = &pdu.FilesystemVersion{
|
|
Type: pdu.FilesystemVersion_Snapshot,
|
|
Name: v,
|
|
Creation: pdu.FilesystemVersionCreation(time.Unix(0, 0)),
|
|
Guid: uint64(i),
|
|
}
|
|
}
|
|
return versions
|
|
}
|
|
|
|
type mockTarget struct {
|
|
fss []mockFS
|
|
destroyed map[string][]string
|
|
listVersionsErrs map[string][]error
|
|
listFilesystemsErr []error
|
|
destroyErrs map[string][]error
|
|
}
|
|
|
|
func (t *mockTarget) ListFilesystems(ctx context.Context) ([]*pdu.Filesystem, error) {
|
|
if len(t.listFilesystemsErr) > 0 {
|
|
e := t.listFilesystemsErr[0]
|
|
t.listFilesystemsErr = t.listFilesystemsErr[1:]
|
|
return nil, e
|
|
}
|
|
fss := make([]*pdu.Filesystem, len(t.fss))
|
|
for i := range fss {
|
|
fss[i] = t.fss[i].Filesystem()
|
|
}
|
|
return fss, nil
|
|
}
|
|
|
|
func (t *mockTarget) ListFilesystemVersions(ctx context.Context, fs string) ([]*pdu.FilesystemVersion, error) {
|
|
if len(t.listVersionsErrs[fs]) != 0 {
|
|
e := t.listVersionsErrs[fs][0]
|
|
t.listVersionsErrs[fs] = t.listVersionsErrs[fs][1:]
|
|
return nil, e
|
|
}
|
|
|
|
for _, mfs := range t.fss {
|
|
if mfs.path != fs {
|
|
continue
|
|
}
|
|
return mfs.FilesystemVersions(), nil
|
|
}
|
|
return nil, fmt.Errorf("filesystem %s does not exist", fs)
|
|
}
|
|
|
|
func (t *mockTarget) DestroySnapshots(ctx context.Context, req *pdu.DestroySnapshotsReq) (*pdu.DestroySnapshotsRes, error) {
|
|
fs, snaps := req.Filesystem, req.Snapshots
|
|
if len(t.destroyErrs[fs]) != 0 {
|
|
e := t.destroyErrs[fs][0]
|
|
t.destroyErrs[fs] = t.destroyErrs[fs][1:]
|
|
return nil, e
|
|
}
|
|
destroyed := t.destroyed[fs]
|
|
res := make([]*pdu.DestroySnapshotRes, len(snaps))
|
|
for i, s := range snaps {
|
|
destroyed = append(destroyed, s.Name)
|
|
res[i] = &pdu.DestroySnapshotRes{Error: "", Snapshot: s}
|
|
}
|
|
t.destroyed[fs] = destroyed
|
|
return &pdu.DestroySnapshotsRes{Results: res}, nil
|
|
}
|
|
|
|
type mockCursor struct {
|
|
snapname string
|
|
guid uint64
|
|
}
|
|
type mockHistory struct {
|
|
errs map[string][]error
|
|
cursors map[string]*mockCursor
|
|
}
|
|
|
|
func (r *mockHistory) ReplicationCursor(ctx context.Context, req *pdu.ReplicationCursorReq) (*pdu.ReplicationCursorRes, error) {
|
|
fs := req.Filesystem
|
|
if len(r.errs[fs]) > 0 {
|
|
e := r.errs[fs][0]
|
|
r.errs[fs] = r.errs[fs][1:]
|
|
return nil, e
|
|
}
|
|
return &pdu.ReplicationCursorRes{Result: &pdu.ReplicationCursorRes_Guid{Guid: 0}}, nil
|
|
}
|
|
|
|
type stubNetErr struct {
|
|
msg string
|
|
temporary, timeout bool
|
|
}
|
|
|
|
var _ net.Error = stubNetErr{}
|
|
|
|
func (e stubNetErr) Error() string {
|
|
return e.msg
|
|
}
|
|
|
|
func (e stubNetErr) Temporary() bool { return e.temporary }
|
|
|
|
func (e stubNetErr) Timeout() bool { return e.timeout }
|
|
|
|
func TestPruner_Prune(t *testing.T) {
|
|
|
|
var _ net.Error = &net.OpError{} // we use it below
|
|
target := &mockTarget{
|
|
listFilesystemsErr: []error{
|
|
stubNetErr{msg: "fakerror0", temporary: true},
|
|
},
|
|
listVersionsErrs: map[string][]error{
|
|
"zroot/foo": {
|
|
stubNetErr{msg: "fakeerror1", temporary: true},
|
|
stubNetErr{msg: "fakeerror2", temporary: true,},
|
|
},
|
|
},
|
|
destroyErrs: map[string][]error{
|
|
"zroot/baz": {
|
|
stubNetErr{msg: "fakeerror3", temporary: true}, // first error puts it back in the queue
|
|
stubNetErr{msg:"permanent error"}, // so it will be last when pruner gives up due to permanent err
|
|
},
|
|
},
|
|
destroyed: make(map[string][]string),
|
|
fss: []mockFS{
|
|
{
|
|
path: "zroot/foo",
|
|
snaps: []string{
|
|
"keep_a",
|
|
"keep_b",
|
|
"drop_c",
|
|
"keep_d",
|
|
},
|
|
},
|
|
{
|
|
path: "zroot/bar",
|
|
snaps: []string{
|
|
"keep_e",
|
|
"keep_f",
|
|
"drop_g",
|
|
},
|
|
},
|
|
{
|
|
path: "zroot/baz",
|
|
snaps: []string{
|
|
"keep_h",
|
|
"drop_i",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
history := &mockHistory{
|
|
errs: map[string][]error{
|
|
"zroot/foo": {
|
|
stubNetErr{msg: "fakeerror4", temporary: true},
|
|
},
|
|
},
|
|
}
|
|
|
|
keepRules := []pruning.KeepRule{pruning.MustKeepRegex("^keep", false)}
|
|
|
|
p := Pruner{
|
|
args: args{
|
|
ctx: WithLogger(context.Background(), logger.NewTestLogger(t)),
|
|
target: target,
|
|
receiver: history,
|
|
rules: keepRules,
|
|
retryWait: 10*time.Millisecond,
|
|
},
|
|
state: Plan,
|
|
}
|
|
p.Prune()
|
|
|
|
exp := map[string][]string{
|
|
"zroot/foo": {"drop_c"},
|
|
"zroot/bar": {"drop_g"},
|
|
}
|
|
|
|
assert.Equal(t, exp, target.destroyed)
|
|
|
|
//assert.Equal(t, map[string][]error{}, target.listVersionsErrs, "retried")
|
|
|
|
}
|