Skip to content

Commit f9d61c7

Browse files
committed
Unit tests for recorder
Unit tests for recorder
1 parent 6580918 commit f9d61c7

File tree

3 files changed

+168
-6
lines changed

3 files changed

+168
-6
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
kubernetes-backup
88
dist/*
99
katafygio
10+
profile.cov

pkg/recorder/recorder.go

+15-6
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ import (
1010
"sync"
1111
"time"
1212

13+
"github.com/spf13/afero"
14+
1315
"github.com/bpineau/katafygio/config"
1416
"github.com/bpineau/katafygio/pkg/event"
1517
)
1618

19+
var appFs = afero.NewOsFs()
20+
1721
// activeFiles will contain a list of active (present in cluster) objets; we'll
1822
// use that to periodically find and garbage collect stale objets in the git repos
1923
// (ie. if some objects were delete from cluster while katafygio was not running).
@@ -41,7 +45,7 @@ func New(config *config.KfConfig, events event.Notifier) *Listener {
4145
// Start receive events and saves them to disk as files
4246
func (w *Listener) Start() *Listener {
4347
w.config.Logger.Info("Starting event recorder")
44-
err := os.MkdirAll(filepath.Clean(w.config.LocalDir), 0700)
48+
err := appFs.MkdirAll(filepath.Clean(w.config.LocalDir), 0700)
4549
if err != nil {
4650
panic(fmt.Sprintf("Can't create directory %s: %v", w.config.LocalDir, err))
4751
}
@@ -114,7 +118,7 @@ func (w *Listener) remove(file string) error {
114118
w.activesLock.Lock()
115119
delete(w.actives, file)
116120
w.activesLock.Unlock()
117-
return os.Remove(filepath.Clean(file))
121+
return appFs.Remove(filepath.Clean(file))
118122
}
119123

120124
func (w *Listener) relativePath(file string) string {
@@ -131,7 +135,7 @@ func (w *Listener) save(file string, data string) error {
131135

132136
dir := filepath.Clean(filepath.Dir(file))
133137

134-
err := os.MkdirAll(dir, 0700)
138+
err := appFs.MkdirAll(dir, 0700)
135139
if err != nil {
136140
return fmt.Errorf("can't create local directory %s: %v", dir, err)
137141
}
@@ -140,7 +144,7 @@ func (w *Listener) save(file string, data string) error {
140144
w.actives[w.relativePath(file)] = true
141145
w.activesLock.Unlock()
142146

143-
f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
147+
f, err := appFs.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
144148
if err != nil {
145149
return fmt.Errorf("failed to create %s on disk: %v", file, err)
146150
}
@@ -163,7 +167,7 @@ func (w *Listener) deleteObsoleteFiles() {
163167
defer w.activesLock.RUnlock()
164168
root := filepath.Clean(w.config.LocalDir)
165169

166-
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
170+
err := afero.Walk(appFs, root, func(path string, info os.FileInfo, err error) error {
167171
if info.IsDir() {
168172
return nil
169173
}
@@ -177,7 +181,12 @@ func (w *Listener) deleteObsoleteFiles() {
177181
return nil
178182
}
179183

180-
return os.Remove(filepath.Clean(path))
184+
w.config.Logger.Debugf("Removing %s from disk", path)
185+
if !w.config.DryRun {
186+
return appFs.Remove(filepath.Clean(path))
187+
}
188+
189+
return nil
181190
})
182191

183192
if err != nil {

pkg/recorder/recorder_test.go

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package recorder
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/spf13/afero"
8+
9+
"github.com/bpineau/katafygio/config"
10+
"github.com/bpineau/katafygio/pkg/event"
11+
"github.com/bpineau/katafygio/pkg/log"
12+
)
13+
14+
func newNotif(action event.Action, key string) *event.Notification {
15+
return &event.Notification{
16+
Action: action,
17+
Key: key,
18+
Kind: "foo",
19+
Object: "bar",
20+
}
21+
}
22+
23+
func TestRecorder(t *testing.T) {
24+
appFs = afero.NewMemMapFs()
25+
26+
evt := event.New()
27+
28+
conf := &config.KfConfig{
29+
Logger: log.New("info", "", "test"),
30+
LocalDir: "/tmp/ktest", // fake dir (in memory fs provided by Afero)
31+
ResyncIntv: 60,
32+
}
33+
34+
rec := New(conf, evt).Start()
35+
36+
evt.Send(newNotif(event.Upsert, "foo1"))
37+
evt.Send(newNotif(event.Upsert, "foo2"))
38+
evt.Send(newNotif(event.Delete, "foo1"))
39+
40+
rec.Stop() // to flush ongoing fs operations
41+
42+
exist, _ := afero.Exists(appFs, conf.LocalDir+"/foo-foo2.yaml")
43+
if !exist {
44+
t.Error("foo-foo2.yaml should exist; upsert event didn't propagate")
45+
}
46+
47+
exist, _ = afero.Exists(appFs, conf.LocalDir+"/foo-foo1.yaml")
48+
if exist {
49+
t.Error("foo-foo1.yaml shouldn't exist, delete event didn't propagate")
50+
}
51+
52+
rogue := conf.LocalDir + "/roguefile.yaml"
53+
_ = afero.WriteFile(appFs, rogue, []byte{42}, 0600)
54+
_ = afero.WriteFile(appFs, rogue+".txt", []byte{42}, 0600)
55+
rec.deleteObsoleteFiles()
56+
57+
exist, _ = afero.Exists(appFs, rogue)
58+
if exist {
59+
t.Errorf("%s file should have been garbage collected", rogue)
60+
}
61+
62+
exist, _ = afero.Exists(appFs, rogue+".txt")
63+
if !exist {
64+
t.Errorf("garbage collection should only touch .yaml files")
65+
}
66+
}
67+
68+
func TestDryRunRecorder(t *testing.T) {
69+
appFs = afero.NewMemMapFs()
70+
71+
conf := &config.KfConfig{
72+
Logger: log.New("info", "", "test"),
73+
LocalDir: "/tmp/ktest",
74+
ResyncIntv: 60,
75+
}
76+
77+
conf.DryRun = true
78+
dryevt := event.New()
79+
dryrec := New(conf, dryevt).Start()
80+
dryevt.Send(newNotif(event.Upsert, "foo3"))
81+
dryevt.Send(newNotif(event.Upsert, "foo4"))
82+
dryevt.Send(newNotif(event.Delete, "foo4"))
83+
dryrec.Stop()
84+
85+
exist, _ := afero.Exists(appFs, conf.LocalDir+"/foo-foo3.yaml")
86+
if exist {
87+
t.Error("foo-foo3.yaml was created but we're in dry-run mode")
88+
}
89+
90+
rogue := conf.LocalDir + "/roguefile.yaml"
91+
_ = afero.WriteFile(appFs, rogue, []byte{42}, 0600)
92+
dryrec.deleteObsoleteFiles()
93+
94+
exist, _ = afero.Exists(appFs, rogue)
95+
if !exist {
96+
t.Errorf("garbage collection shouldn't remove files in dry-run mode")
97+
}
98+
}
99+
100+
// testing behavior on fs errors (we shouldn't block the program)
101+
func TestFailingFSRecorder(t *testing.T) {
102+
appFs = afero.NewMemMapFs()
103+
104+
evt := event.New()
105+
106+
conf := &config.KfConfig{
107+
Logger: log.New("info", "", "test"),
108+
LocalDir: "/tmp/ktest", // fake dir (in memory fs provided by Afero)
109+
ResyncIntv: 60,
110+
}
111+
112+
rec := New(conf, evt).Start()
113+
114+
_ = afero.WriteFile(appFs, conf.LocalDir+"/foo.yaml", []byte{42}, 0600)
115+
116+
// switching to failing (read-only) filesystem
117+
appFs = afero.NewReadOnlyFs(appFs)
118+
119+
err := rec.save("foo", "bar")
120+
if err == nil {
121+
t.Error("save should return an error in case of failure")
122+
}
123+
124+
// shouldn't panic in case of failures
125+
rec.deleteObsoleteFiles()
126+
127+
// shouldn't block (the controllers event loop will retry anyway)
128+
ch := make(chan struct{})
129+
go func() {
130+
evt.Send(newNotif(event.Upsert, "foo3"))
131+
evt.Send(newNotif(event.Upsert, "foo4"))
132+
ch <- struct{}{}
133+
}()
134+
135+
select {
136+
case <-ch:
137+
case <-time.After(5 * time.Second):
138+
t.Error("recorder shouldn't block in case of fs failure")
139+
}
140+
141+
// back to normal operations
142+
rec.Stop() // just to flush ongoing ops before switch filesystem
143+
appFs = afero.NewMemMapFs()
144+
rec.Start()
145+
evt.Send(newNotif(event.Upsert, "foo2"))
146+
rec.Stop() // flush ongoing ops
147+
148+
exist, _ := afero.Exists(appFs, conf.LocalDir+"/foo-foo2.yaml")
149+
if !exist {
150+
t.Error("foo-foo2.yaml should exist; recorder should recover from fs failures")
151+
}
152+
}

0 commit comments

Comments
 (0)