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