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  }