1/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package flexvolume
18
19import (
20	"fmt"
21	"path/filepath"
22	"strings"
23	"testing"
24
25	"github.com/fsnotify/fsnotify"
26	"github.com/stretchr/testify/assert"
27	utilfs "k8s.io/kubernetes/pkg/util/filesystem"
28	"k8s.io/kubernetes/pkg/volume"
29	"k8s.io/utils/exec"
30)
31
32const (
33	pluginDir  = "/flexvolume"
34	driverName = "fake-driver"
35)
36
37func assertPathSuffix(t *testing.T, dir1 string, dir2 string) {
38	assert.True(t, strings.HasSuffix(dir2, dir1))
39}
40
41// Probes a driver installed before prober initialization.
42func TestProberExistingDriverBeforeInit(t *testing.T) {
43	// Arrange
44	driverPath, _, watcher, prober := initTestEnvironment(t)
45
46	// Act
47	events, err := prober.Probe()
48
49	// Assert
50	// Probe occurs, 1 plugin should be returned, and 2 watches (pluginDir and all its
51	// current subdirectories) registered.
52	assert.Equal(t, 1, len(events))
53	assert.Equal(t, volume.ProbeAddOrUpdate, events[0].Op)
54	assertPathSuffix(t, pluginDir, watcher.watches[0])
55	assertPathSuffix(t, driverPath, watcher.watches[1])
56	assert.NoError(t, err)
57
58	// Should no longer probe.
59
60	// Act
61	events, err = prober.Probe()
62	// Assert
63	assert.Equal(t, 0, len(events))
64	assert.NoError(t, err)
65}
66
67// Probes newly added drivers after prober is running.
68func TestProberAddRemoveDriver(t *testing.T) {
69	// Arrange
70	_, fs, watcher, prober := initTestEnvironment(t)
71	prober.Probe()
72	events, err := prober.Probe()
73	assert.NoError(t, err)
74	assert.Equal(t, 0, len(events))
75
76	// Call probe after a file is added. Should return 1 event.
77
78	// add driver
79	const driverName2 = "fake-driver2"
80	driverPath := filepath.Join(pluginDir, driverName2)
81	executablePath := filepath.Join(driverPath, driverName2)
82	installDriver(driverName2, fs)
83	watcher.TriggerEvent(fsnotify.Create, driverPath)
84	watcher.TriggerEvent(fsnotify.Create, executablePath)
85
86	// Act
87	events, err = prober.Probe()
88
89	// Assert
90	assert.Equal(t, 1, len(events))
91	assert.Equal(t, volume.ProbeAddOrUpdate, events[0].Op)                   // 1 newly added
92	assertPathSuffix(t, driverPath, watcher.watches[len(watcher.watches)-1]) // Checks most recent watch
93	assert.NoError(t, err)
94
95	// Call probe again, should return 0 event.
96
97	// Act
98	events, err = prober.Probe()
99	// Assert
100	assert.Equal(t, 0, len(events))
101	assert.NoError(t, err)
102
103	// Call probe after a non-driver file is added in a subdirectory. should return 1 event.
104	fp := filepath.Join(driverPath, "dummyfile")
105	fs.Create(fp)
106	watcher.TriggerEvent(fsnotify.Create, fp)
107
108	// Act
109	events, err = prober.Probe()
110
111	// Assert
112	assert.Equal(t, 1, len(events))
113	assert.Equal(t, volume.ProbeAddOrUpdate, events[0].Op)
114	assert.NoError(t, err)
115
116	// Call probe again, should return 0 event.
117	// Act
118	events, err = prober.Probe()
119	// Assert
120	assert.Equal(t, 0, len(events))
121	assert.NoError(t, err)
122
123	// Call probe after a subdirectory is added in a driver directory. should return 1 event.
124	subdirPath := filepath.Join(driverPath, "subdir")
125	fs.Create(subdirPath)
126	watcher.TriggerEvent(fsnotify.Create, subdirPath)
127
128	// Act
129	events, err = prober.Probe()
130
131	// Assert
132	assert.Equal(t, 1, len(events))
133	assert.Equal(t, volume.ProbeAddOrUpdate, events[0].Op)
134	assert.NoError(t, err)
135
136	// Call probe again, should return 0 event.
137	// Act
138	events, err = prober.Probe()
139	// Assert
140	assert.Equal(t, 0, len(events))
141	assert.NoError(t, err)
142
143	// Call probe after a subdirectory is removed in a driver directory. should return 1 event.
144	fs.Remove(subdirPath)
145	watcher.TriggerEvent(fsnotify.Remove, subdirPath)
146
147	// Act
148	events, err = prober.Probe()
149
150	// Assert
151	assert.Equal(t, 1, len(events))
152	assert.Equal(t, volume.ProbeAddOrUpdate, events[0].Op)
153	assert.NoError(t, err)
154
155	// Call probe again, should return 0 event.
156	// Act
157	events, err = prober.Probe()
158	// Assert
159	assert.Equal(t, 0, len(events))
160	assert.NoError(t, err)
161
162	// Call probe after a driver executable and driver directory is remove. should return 1 event.
163	fs.Remove(executablePath)
164	fs.Remove(driverPath)
165	watcher.TriggerEvent(fsnotify.Remove, executablePath)
166	watcher.TriggerEvent(fsnotify.Remove, driverPath)
167	// Act and Assert: 1 ProbeRemove event
168	events, err = prober.Probe()
169	assert.Equal(t, 1, len(events))
170	assert.Equal(t, volume.ProbeRemove, events[0].Op)
171	assert.NoError(t, err)
172
173	// Act and Assert: 0 event
174	events, err = prober.Probe()
175	assert.Equal(t, 0, len(events))
176	assert.NoError(t, err)
177}
178
179// Tests the behavior when no drivers exist in the plugin directory.
180func TestEmptyPluginDir(t *testing.T) {
181	// Arrange
182	fs := utilfs.NewTempFs()
183	watcher := newFakeWatcher()
184	prober := &flexVolumeProber{
185		pluginDir: pluginDir,
186		watcher:   watcher,
187		fs:        fs,
188		factory:   fakePluginFactory{error: false},
189	}
190	prober.Init()
191
192	// Act
193	events, err := prober.Probe()
194
195	// Assert
196	assert.Equal(t, 0, len(events))
197	assert.NoError(t, err)
198}
199
200// Issue an event to remove plugindir. New directory should still be watched.
201func TestRemovePluginDir(t *testing.T) {
202	// Arrange
203	driverPath, fs, watcher, _ := initTestEnvironment(t)
204	fs.RemoveAll(pluginDir)
205	watcher.TriggerEvent(fsnotify.Remove, filepath.Join(driverPath, driverName))
206	watcher.TriggerEvent(fsnotify.Remove, driverPath)
207	watcher.TriggerEvent(fsnotify.Remove, pluginDir)
208
209	// Act: The handler triggered by the above events should have already handled the event appropriately.
210
211	// Assert
212	assert.Equal(t, 3, len(watcher.watches)) // 2 from initial setup, 1 from new watch.
213	assertPathSuffix(t, pluginDir, watcher.watches[len(watcher.watches)-1])
214}
215
216// Issue an event to remove plugindir. New directory should still be watched.
217func TestNestedDriverDir(t *testing.T) {
218	// Arrange
219	_, fs, watcher, _ := initTestEnvironment(t)
220	// Assert
221	assert.Equal(t, 2, len(watcher.watches)) // 2 from initial setup
222
223	// test add testDriverName
224	testDriverName := "testDriverName"
225	testDriverPath := filepath.Join(pluginDir, testDriverName)
226	fs.MkdirAll(testDriverPath, 0777)
227	watcher.TriggerEvent(fsnotify.Create, testDriverPath)
228	// Assert
229	assert.Equal(t, 3, len(watcher.watches)) // 2 from initial setup, 1 from new watch.
230	assertPathSuffix(t, testDriverPath, watcher.watches[len(watcher.watches)-1])
231
232	// test add nested subdir inside testDriverName
233	basePath := testDriverPath
234	for i := 0; i < 10; i++ {
235		subdirName := "subdirName"
236		subdirPath := filepath.Join(basePath, subdirName)
237		fs.MkdirAll(subdirPath, 0777)
238		watcher.TriggerEvent(fsnotify.Create, subdirPath)
239		// Assert
240		assert.Equal(t, 4+i, len(watcher.watches)) // 3 + newly added
241		assertPathSuffix(t, subdirPath, watcher.watches[len(watcher.watches)-1])
242		basePath = subdirPath
243	}
244}
245
246// Issue multiple events and probe multiple times.
247func TestProberMultipleEvents(t *testing.T) {
248	const iterations = 5
249
250	// Arrange
251	_, fs, watcher, prober := initTestEnvironment(t)
252	for i := 0; i < iterations; i++ {
253		newDriver := fmt.Sprintf("multi-event-driver%d", 1)
254		installDriver(newDriver, fs)
255		driverPath := filepath.Join(pluginDir, newDriver)
256		watcher.TriggerEvent(fsnotify.Create, driverPath)
257		watcher.TriggerEvent(fsnotify.Create, filepath.Join(driverPath, newDriver))
258	}
259
260	// Act
261	events, err := prober.Probe()
262
263	// Assert
264	assert.Equal(t, 2, len(events))
265	assert.Equal(t, volume.ProbeAddOrUpdate, events[0].Op)
266	assert.Equal(t, volume.ProbeAddOrUpdate, events[1].Op)
267	assert.NoError(t, err)
268	for i := 0; i < iterations-1; i++ {
269		events, err = prober.Probe()
270		assert.Equal(t, 0, len(events))
271		assert.NoError(t, err)
272	}
273}
274
275func TestProberError(t *testing.T) {
276	fs := utilfs.NewTempFs()
277	watcher := newFakeWatcher()
278	prober := &flexVolumeProber{
279		pluginDir: pluginDir,
280		watcher:   watcher,
281		fs:        fs,
282		factory:   fakePluginFactory{error: true},
283	}
284	installDriver(driverName, fs)
285	prober.Init()
286
287	_, err := prober.Probe()
288	assert.Error(t, err)
289}
290
291// Installs a mock driver (an empty file) in the mock fs.
292func installDriver(driverName string, fs utilfs.Filesystem) {
293	driverPath := filepath.Join(pluginDir, driverName)
294	fs.MkdirAll(driverPath, 0777)
295	fs.Create(filepath.Join(driverPath, driverName))
296}
297
298// Initializes mocks, installs a single driver in the mock fs, then initializes prober.
299func initTestEnvironment(t *testing.T) (
300	driverPath string,
301	fs utilfs.Filesystem,
302	watcher *fakeWatcher,
303	prober volume.DynamicPluginProber) {
304	fs = utilfs.NewTempFs()
305	watcher = newFakeWatcher()
306	prober = &flexVolumeProber{
307		pluginDir: pluginDir,
308		watcher:   watcher,
309		fs:        fs,
310		factory:   fakePluginFactory{error: false},
311	}
312	driverPath = filepath.Join(pluginDir, driverName)
313	installDriver(driverName, fs)
314	prober.Init()
315
316	assert.NotNilf(t, watcher.eventHandler,
317		"Expect watch event handler to be registered after prober init, but is not.")
318	return
319}
320
321// Fake Flexvolume plugin
322type fakePluginFactory struct {
323	error bool // Indicates whether an error should be returned.
324}
325
326var _ PluginFactory = fakePluginFactory{}
327
328func (m fakePluginFactory) NewFlexVolumePlugin(_, driverName string, _ exec.Interface) (volume.VolumePlugin, error) {
329	if m.error {
330		return nil, fmt.Errorf("Flexvolume plugin error")
331	}
332	// Dummy Flexvolume plugin. Prober never interacts with the plugin.
333	return &flexVolumePlugin{driverName: driverName}, nil
334}
335