k8s.io/kubernetes@v1.29.3/pkg/volume/flexvolume/probe.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  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  	"sync"
    25  
    26  	"github.com/fsnotify/fsnotify"
    27  	"k8s.io/apimachinery/pkg/util/errors"
    28  	"k8s.io/klog/v2"
    29  	utilfs "k8s.io/kubernetes/pkg/util/filesystem"
    30  	"k8s.io/kubernetes/pkg/volume"
    31  	"k8s.io/utils/exec"
    32  	utilstrings "k8s.io/utils/strings"
    33  )
    34  
    35  type flexVolumeProber struct {
    36  	mutex          sync.Mutex
    37  	pluginDir      string         // Flexvolume driver directory
    38  	runner         exec.Interface // Interface to use for execing flex calls
    39  	watcher        utilfs.FSWatcher
    40  	factory        PluginFactory
    41  	fs             utilfs.Filesystem
    42  	probeAllNeeded bool
    43  	eventsMap      map[string]volume.ProbeOperation // the key is the driver directory path, the value is the corresponding operation
    44  }
    45  
    46  // GetDynamicPluginProber creates dynamic plugin prober
    47  func GetDynamicPluginProber(pluginDir string, runner exec.Interface) volume.DynamicPluginProber {
    48  	return &flexVolumeProber{
    49  		pluginDir: pluginDir,
    50  		watcher:   utilfs.NewFsnotifyWatcher(),
    51  		factory:   pluginFactory{},
    52  		runner:    runner,
    53  		fs:        &utilfs.DefaultFs{},
    54  	}
    55  }
    56  
    57  func (prober *flexVolumeProber) Init() error {
    58  	prober.testAndSetProbeAllNeeded(true)
    59  	prober.eventsMap = map[string]volume.ProbeOperation{}
    60  
    61  	if err := prober.createPluginDir(); err != nil {
    62  		return err
    63  	}
    64  	if err := prober.initWatcher(); err != nil {
    65  		return err
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  // If probeAllNeeded is true, probe all pluginDir
    72  // else probe events in eventsMap
    73  func (prober *flexVolumeProber) Probe() (events []volume.ProbeEvent, err error) {
    74  	if prober.probeAllNeeded {
    75  		prober.testAndSetProbeAllNeeded(false)
    76  		return prober.probeAll()
    77  	}
    78  
    79  	return prober.probeMap()
    80  }
    81  
    82  func (prober *flexVolumeProber) probeMap() (events []volume.ProbeEvent, err error) {
    83  	// TODO use a concurrent map to avoid Locking the entire map
    84  	prober.mutex.Lock()
    85  	defer prober.mutex.Unlock()
    86  	probeEvents := []volume.ProbeEvent{}
    87  	allErrs := []error{}
    88  	for driverDirPathAbs, op := range prober.eventsMap {
    89  		driverDirName := filepath.Base(driverDirPathAbs) // e.g. driverDirName = vendor~cifs
    90  		probeEvent, pluginErr := prober.newProbeEvent(driverDirName, op)
    91  		if pluginErr != nil {
    92  			allErrs = append(allErrs, pluginErr)
    93  			continue
    94  		}
    95  		probeEvents = append(probeEvents, probeEvent)
    96  
    97  		delete(prober.eventsMap, driverDirPathAbs)
    98  	}
    99  	return probeEvents, errors.NewAggregate(allErrs)
   100  }
   101  
   102  func (prober *flexVolumeProber) probeAll() (events []volume.ProbeEvent, err error) {
   103  	probeEvents := []volume.ProbeEvent{}
   104  	allErrs := []error{}
   105  	files, err := prober.fs.ReadDir(prober.pluginDir)
   106  	if err != nil {
   107  		return nil, fmt.Errorf("error reading the Flexvolume directory: %s", err)
   108  	}
   109  	for _, f := range files {
   110  		// only directories with names that do not begin with '.' are counted as plugins
   111  		// and pluginDir/dirname/dirname should be an executable
   112  		// unless dirname contains '~' for escaping namespace
   113  		// e.g. dirname = vendor~cifs
   114  		// then, executable will be pluginDir/dirname/cifs
   115  		if f.IsDir() && filepath.Base(f.Name())[0] != '.' {
   116  			probeEvent, pluginErr := prober.newProbeEvent(f.Name(), volume.ProbeAddOrUpdate)
   117  			if pluginErr != nil {
   118  				allErrs = append(allErrs, pluginErr)
   119  				continue
   120  			}
   121  			probeEvents = append(probeEvents, probeEvent)
   122  		}
   123  	}
   124  	return probeEvents, errors.NewAggregate(allErrs)
   125  }
   126  
   127  func (prober *flexVolumeProber) newProbeEvent(driverDirName string, op volume.ProbeOperation) (volume.ProbeEvent, error) {
   128  	probeEvent := volume.ProbeEvent{
   129  		Op: op,
   130  	}
   131  	if op == volume.ProbeAddOrUpdate {
   132  		plugin, pluginErr := prober.factory.NewFlexVolumePlugin(prober.pluginDir, driverDirName, prober.runner)
   133  		if pluginErr != nil {
   134  			pluginErr = fmt.Errorf(
   135  				"error creating Flexvolume plugin from directory %s, skipping. Error: %s",
   136  				driverDirName, pluginErr)
   137  			return probeEvent, pluginErr
   138  		}
   139  		probeEvent.Plugin = plugin
   140  		probeEvent.PluginName = plugin.GetPluginName()
   141  	} else if op == volume.ProbeRemove {
   142  		driverName := utilstrings.UnescapeQualifiedName(driverDirName)
   143  		probeEvent.PluginName = driverName
   144  
   145  	} else {
   146  		return probeEvent, fmt.Errorf("Unknown Operation on directory: %s. ", driverDirName)
   147  	}
   148  	return probeEvent, nil
   149  }
   150  
   151  func (prober *flexVolumeProber) handleWatchEvent(event fsnotify.Event) error {
   152  	// event.Name is the watched path.
   153  	if filepath.Base(event.Name)[0] == '.' {
   154  		// Ignore files beginning with '.'
   155  		return nil
   156  	}
   157  
   158  	eventPathAbs, err := filepath.Abs(event.Name)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	parentPathAbs := filepath.Dir(eventPathAbs)
   163  	pluginDirAbs, err := filepath.Abs(prober.pluginDir)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	// event of pluginDirAbs
   169  	if eventPathAbs == pluginDirAbs {
   170  		// If the Flexvolume plugin directory is removed, need to recreate it
   171  		// in order to keep it under watch.
   172  		if event.Has(fsnotify.Remove) {
   173  			if err := prober.createPluginDir(); err != nil {
   174  				return err
   175  			}
   176  			if err := prober.addWatchRecursive(pluginDirAbs); err != nil {
   177  				return err
   178  			}
   179  		}
   180  		return nil
   181  	}
   182  
   183  	// watch newly added subdirectories inside a driver directory
   184  	if event.Has(fsnotify.Create) {
   185  		if err := prober.addWatchRecursive(eventPathAbs); err != nil {
   186  			return err
   187  		}
   188  	}
   189  
   190  	eventRelPathToPluginDir, err := filepath.Rel(pluginDirAbs, eventPathAbs)
   191  	if err != nil {
   192  		return err
   193  	}
   194  
   195  	// event inside specific driver dir
   196  	if len(eventRelPathToPluginDir) > 0 {
   197  		driverDirName := strings.Split(eventRelPathToPluginDir, string(os.PathSeparator))[0]
   198  		driverDirAbs := filepath.Join(pluginDirAbs, driverDirName)
   199  		// executable is removed, will trigger ProbeRemove event
   200  		if event.Has(fsnotify.Remove) && (eventRelPathToPluginDir == getExecutablePathRel(driverDirName) || parentPathAbs == pluginDirAbs) {
   201  			prober.updateEventsMap(driverDirAbs, volume.ProbeRemove)
   202  		} else {
   203  			prober.updateEventsMap(driverDirAbs, volume.ProbeAddOrUpdate)
   204  		}
   205  	}
   206  
   207  	return nil
   208  }
   209  
   210  // getExecutableName returns the executableName of a flex plugin
   211  func getExecutablePathRel(driverDirName string) string {
   212  	parts := strings.Split(driverDirName, "~")
   213  	return filepath.Join(driverDirName, parts[len(parts)-1])
   214  }
   215  
   216  func (prober *flexVolumeProber) updateEventsMap(eventDirAbs string, op volume.ProbeOperation) {
   217  	prober.mutex.Lock()
   218  	defer prober.mutex.Unlock()
   219  	if prober.probeAllNeeded {
   220  		return
   221  	}
   222  	prober.eventsMap[eventDirAbs] = op
   223  }
   224  
   225  // Recursively adds to watch all directories inside and including the file specified by the given filename.
   226  // If the file is a symlink to a directory, it will watch the symlink but not any of the subdirectories.
   227  //
   228  // Each file or directory change triggers two events: one from the watch on itself, another from the watch
   229  // on its parent directory.
   230  func (prober *flexVolumeProber) addWatchRecursive(filename string) error {
   231  	addWatch := func(path string, info os.FileInfo, err error) error {
   232  		if err == nil && info.IsDir() {
   233  			if err := prober.watcher.AddWatch(path); err != nil {
   234  				klog.Errorf("Error recursively adding watch: %v", err)
   235  			}
   236  		}
   237  		return nil
   238  	}
   239  	return prober.fs.Walk(filename, addWatch)
   240  }
   241  
   242  // Creates a new filesystem watcher and adds watches for the plugin directory
   243  // and all of its subdirectories.
   244  func (prober *flexVolumeProber) initWatcher() error {
   245  	err := prober.watcher.Init(func(event fsnotify.Event) {
   246  		if err := prober.handleWatchEvent(event); err != nil {
   247  			klog.Errorf("Flexvolume prober watch: %s", err)
   248  		}
   249  	}, func(err error) {
   250  		klog.Errorf("Received an error from watcher: %s", err)
   251  	})
   252  	if err != nil {
   253  		return fmt.Errorf("error initializing watcher: %s", err)
   254  	}
   255  
   256  	if err := prober.addWatchRecursive(prober.pluginDir); err != nil {
   257  		return fmt.Errorf("error adding watch on Flexvolume directory: %s", err)
   258  	}
   259  
   260  	prober.watcher.Run()
   261  
   262  	return nil
   263  }
   264  
   265  // Creates the plugin directory, if it doesn't already exist.
   266  func (prober *flexVolumeProber) createPluginDir() error {
   267  	if _, err := prober.fs.Stat(prober.pluginDir); os.IsNotExist(err) {
   268  		klog.Warningf("Flexvolume plugin directory at %s does not exist. Recreating.", prober.pluginDir)
   269  		err := prober.fs.MkdirAll(prober.pluginDir, 0755)
   270  		if err != nil {
   271  			return fmt.Errorf("error (re-)creating driver directory: %s", err)
   272  		}
   273  	}
   274  
   275  	return nil
   276  }
   277  
   278  func (prober *flexVolumeProber) testAndSetProbeAllNeeded(newval bool) (oldval bool) {
   279  	prober.mutex.Lock()
   280  	defer prober.mutex.Unlock()
   281  	oldval, prober.probeAllNeeded = prober.probeAllNeeded, newval
   282  	return
   283  }