2018-05-01 02:15:06 -04:00
|
|
|
/*
|
|
|
|
|
Copyright 2018 The Kubernetes Authors.
|
|
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package pluginwatcher
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"net"
|
|
|
|
|
"os"
|
2018-11-20 23:06:10 -05:00
|
|
|
"path/filepath"
|
2018-08-11 10:52:41 -04:00
|
|
|
"strings"
|
2018-05-01 02:15:06 -04:00
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/fsnotify/fsnotify"
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
"github.com/pkg/errors"
|
2018-05-01 02:15:06 -04:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
|
"google.golang.org/grpc"
|
2018-11-09 13:49:10 -05:00
|
|
|
"k8s.io/klog"
|
2018-08-11 10:52:41 -04:00
|
|
|
|
2018-11-02 19:19:27 -04:00
|
|
|
registerapi "k8s.io/kubernetes/pkg/kubelet/apis/pluginregistration/v1"
|
2018-05-01 02:15:06 -04:00
|
|
|
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Watcher is the plugin watcher
|
|
|
|
|
type Watcher struct {
|
2018-11-20 23:06:10 -05:00
|
|
|
path string
|
|
|
|
|
deprecatedPath string
|
|
|
|
|
stopCh chan interface{}
|
|
|
|
|
fs utilfs.Filesystem
|
|
|
|
|
fsWatcher *fsnotify.Watcher
|
|
|
|
|
wg sync.WaitGroup
|
2018-08-11 10:52:41 -04:00
|
|
|
|
|
|
|
|
mutex sync.Mutex
|
|
|
|
|
handlers map[string]PluginHandler
|
|
|
|
|
plugins map[string]pathInfo
|
|
|
|
|
pluginsPool map[string]map[string]*sync.Mutex // map[pluginType][pluginName]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type pathInfo struct {
|
|
|
|
|
pluginType string
|
|
|
|
|
pluginName string
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewWatcher provides a new watcher
|
2018-11-20 23:06:10 -05:00
|
|
|
// deprecatedSockDir refers to a pre-GA directory that was used by older plugins
|
|
|
|
|
// for socket registration. New plugins should not use this directory.
|
|
|
|
|
func NewWatcher(sockDir string, deprecatedSockDir string) *Watcher {
|
2018-08-11 10:52:41 -04:00
|
|
|
return &Watcher{
|
2018-11-20 23:06:10 -05:00
|
|
|
path: sockDir,
|
|
|
|
|
deprecatedPath: deprecatedSockDir,
|
|
|
|
|
fs: &utilfs.DefaultFs{},
|
2018-08-11 10:52:41 -04:00
|
|
|
|
|
|
|
|
handlers: make(map[string]PluginHandler),
|
|
|
|
|
plugins: make(map[string]pathInfo),
|
|
|
|
|
pluginsPool: make(map[string]map[string]*sync.Mutex),
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
func (w *Watcher) AddHandler(pluginType string, handler PluginHandler) {
|
2018-05-01 02:15:06 -04:00
|
|
|
w.mutex.Lock()
|
|
|
|
|
defer w.mutex.Unlock()
|
2018-08-11 10:52:41 -04:00
|
|
|
|
|
|
|
|
w.handlers[pluginType] = handler
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
func (w *Watcher) getHandler(pluginType string) (PluginHandler, bool) {
|
|
|
|
|
w.mutex.Lock()
|
|
|
|
|
defer w.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
h, ok := w.handlers[pluginType]
|
|
|
|
|
return h, ok
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start watches for the creation of plugin sockets at the path
|
|
|
|
|
func (w *Watcher) Start() error {
|
2018-11-09 13:49:10 -05:00
|
|
|
klog.V(2).Infof("Plugin Watcher Start at %s", w.path)
|
2018-08-11 10:52:41 -04:00
|
|
|
w.stopCh = make(chan interface{})
|
|
|
|
|
|
|
|
|
|
// Creating the directory to be watched if it doesn't exist yet,
|
|
|
|
|
// and walks through the directory to discover the existing plugins.
|
|
|
|
|
if err := w.init(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fsWatcher, err := fsnotify.NewWatcher()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to start plugin fsWatcher, err: %v", err)
|
|
|
|
|
}
|
|
|
|
|
w.fsWatcher = fsWatcher
|
|
|
|
|
|
|
|
|
|
w.wg.Add(1)
|
|
|
|
|
go func(fsWatcher *fsnotify.Watcher) {
|
|
|
|
|
defer w.wg.Done()
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case event := <-fsWatcher.Events:
|
|
|
|
|
//TODO: Handle errors by taking corrective measures
|
|
|
|
|
|
|
|
|
|
w.wg.Add(1)
|
2018-11-26 22:11:47 -05:00
|
|
|
func() {
|
2018-08-11 10:52:41 -04:00
|
|
|
defer w.wg.Done()
|
|
|
|
|
|
|
|
|
|
if event.Op&fsnotify.Create == fsnotify.Create {
|
|
|
|
|
err := w.handleCreateEvent(event)
|
|
|
|
|
if err != nil {
|
2018-11-09 13:49:10 -05:00
|
|
|
klog.Errorf("error %v when handling create event: %s", err, event)
|
2018-08-11 10:52:41 -04:00
|
|
|
}
|
|
|
|
|
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
|
|
|
|
err := w.handleDeleteEvent(event)
|
|
|
|
|
if err != nil {
|
2018-11-09 13:49:10 -05:00
|
|
|
klog.Errorf("error %v when handling delete event: %s", err, event)
|
2018-08-11 10:52:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}()
|
|
|
|
|
continue
|
|
|
|
|
case err := <-fsWatcher.Errors:
|
|
|
|
|
if err != nil {
|
2018-11-09 13:49:10 -05:00
|
|
|
klog.Errorf("fsWatcher received error: %v", err)
|
2018-08-11 10:52:41 -04:00
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
case <-w.stopCh:
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}(fsWatcher)
|
|
|
|
|
|
|
|
|
|
// Traverse plugin dir after starting the plugin processing goroutine
|
|
|
|
|
if err := w.traversePluginDir(w.path); err != nil {
|
|
|
|
|
w.Stop()
|
2018-11-20 23:06:10 -05:00
|
|
|
return fmt.Errorf("failed to traverse plugin socket path %q, err: %v", w.path, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Traverse deprecated plugin dir, if specified.
|
|
|
|
|
if len(w.deprecatedPath) != 0 {
|
|
|
|
|
if err := w.traversePluginDir(w.deprecatedPath); err != nil {
|
|
|
|
|
w.Stop()
|
|
|
|
|
return fmt.Errorf("failed to traverse deprecated plugin socket path %q, err: %v", w.deprecatedPath, err)
|
|
|
|
|
}
|
2018-08-11 10:52:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stop stops probing the creation of plugin sockets at the path
|
|
|
|
|
func (w *Watcher) Stop() error {
|
|
|
|
|
close(w.stopCh)
|
|
|
|
|
|
|
|
|
|
c := make(chan struct{})
|
|
|
|
|
go func() {
|
|
|
|
|
defer close(c)
|
|
|
|
|
w.wg.Wait()
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case <-c:
|
|
|
|
|
case <-time.After(11 * time.Second):
|
|
|
|
|
return fmt.Errorf("timeout on stopping watcher")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w.fsWatcher.Close()
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *Watcher) init() error {
|
2018-11-09 13:49:10 -05:00
|
|
|
klog.V(4).Infof("Ensuring Plugin directory at %s ", w.path)
|
2018-08-11 10:52:41 -04:00
|
|
|
|
2018-05-01 02:15:06 -04:00
|
|
|
if err := w.fs.MkdirAll(w.path, 0755); err != nil {
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
return fmt.Errorf("error (re-)creating root %s: %v", w.path, err)
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
|
2018-05-01 02:15:06 -04:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
// Walks through the plugin directory discover any existing plugin sockets.
|
2018-11-08 15:11:38 -05:00
|
|
|
// Goroutines started here will be waited for in Stop() before cleaning up.
|
2018-11-08 18:42:02 -05:00
|
|
|
// Ignore all errors except root dir not being walkable
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
func (w *Watcher) traversePluginDir(dir string) error {
|
|
|
|
|
return w.fs.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
|
|
|
if err != nil {
|
2018-11-08 18:42:02 -05:00
|
|
|
if path == dir {
|
|
|
|
|
return fmt.Errorf("error accessing path: %s error: %v", path, err)
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-10 07:53:25 -05:00
|
|
|
klog.Errorf("error accessing path: %s error: %v", path, err)
|
2018-11-08 15:11:38 -05:00
|
|
|
return nil
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch mode := info.Mode(); {
|
|
|
|
|
case mode.IsDir():
|
2018-11-20 23:06:10 -05:00
|
|
|
if w.containsBlacklistedDir(path) {
|
|
|
|
|
return filepath.SkipDir
|
|
|
|
|
}
|
|
|
|
|
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
if err := w.fsWatcher.Add(path); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to watch %s, err: %v", path, err)
|
|
|
|
|
}
|
|
|
|
|
case mode&os.ModeSocket != 0:
|
2018-11-08 15:10:08 -05:00
|
|
|
w.wg.Add(1)
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
go func() {
|
2018-11-08 15:10:08 -05:00
|
|
|
defer w.wg.Done()
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
w.fsWatcher.Events <- fsnotify.Event{
|
|
|
|
|
Name: path,
|
|
|
|
|
Op: fsnotify.Create,
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
}()
|
2018-08-11 10:52:41 -04:00
|
|
|
default:
|
2018-11-09 13:49:10 -05:00
|
|
|
klog.V(5).Infof("Ignoring file %s with mode %v", path, mode)
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
})
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
// Handle filesystem notify event.
|
2018-11-15 15:05:11 -05:00
|
|
|
// Files names:
|
|
|
|
|
// - MUST NOT start with a '.'
|
2018-08-11 10:52:41 -04:00
|
|
|
func (w *Watcher) handleCreateEvent(event fsnotify.Event) error {
|
2018-11-09 13:49:10 -05:00
|
|
|
klog.V(6).Infof("Handling create event: %v", event)
|
2018-08-11 10:52:41 -04:00
|
|
|
|
2018-11-20 23:06:10 -05:00
|
|
|
if w.containsBlacklistedDir(event.Name) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
fi, err := os.Stat(event.Name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("stat file %s failed: %v", event.Name, err)
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
2018-08-11 10:52:41 -04:00
|
|
|
|
|
|
|
|
if strings.HasPrefix(fi.Name(), ".") {
|
2018-11-15 15:05:11 -05:00
|
|
|
klog.V(5).Infof("Ignoring file (starts with '.'): %s", fi.Name())
|
2018-08-11 10:52:41 -04:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !fi.IsDir() {
|
2018-11-15 15:05:11 -05:00
|
|
|
if fi.Mode()&os.ModeSocket == 0 {
|
|
|
|
|
klog.V(5).Infof("Ignoring non socket file %s", fi.Name())
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
return w.handlePluginRegistration(event.Name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return w.traversePluginDir(event.Name)
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
func (w *Watcher) handlePluginRegistration(socketPath string) error {
|
2018-05-01 02:15:06 -04:00
|
|
|
//TODO: Implement rate limiting to mitigate any DOS kind of attacks.
|
2018-08-11 10:52:41 -04:00
|
|
|
client, conn, err := dial(socketPath, 10*time.Second)
|
2018-05-01 02:15:06 -04:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("dial failed at socket %s, err: %v", socketPath, err)
|
|
|
|
|
}
|
|
|
|
|
defer conn.Close()
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
|
|
|
defer cancel()
|
2018-08-11 10:52:41 -04:00
|
|
|
|
2018-05-01 02:15:06 -04:00
|
|
|
infoResp, err := client.GetInfo(ctx, ®isterapi.InfoRequest{})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to get plugin info using RPC GetInfo at socket %s, err: %v", socketPath, err)
|
|
|
|
|
}
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
handler, ok := w.handlers[infoResp.Type]
|
2018-05-01 02:15:06 -04:00
|
|
|
if !ok {
|
2018-08-11 10:52:41 -04:00
|
|
|
return w.notifyPlugin(client, false, fmt.Sprintf("no handler registered for plugin type: %s at socket %s", infoResp.Type, socketPath))
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
// ReRegistration: We want to handle multiple plugins registering at the same time with the same name sequentially.
|
|
|
|
|
// See the state machine for more information.
|
|
|
|
|
// This is done by using a Lock for each plugin with the same name and type
|
|
|
|
|
pool := w.getPluginPool(infoResp.Type, infoResp.Name)
|
|
|
|
|
|
|
|
|
|
pool.Lock()
|
|
|
|
|
defer pool.Unlock()
|
|
|
|
|
|
|
|
|
|
if infoResp.Endpoint == "" {
|
|
|
|
|
infoResp.Endpoint = socketPath
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
2018-08-11 10:52:41 -04:00
|
|
|
|
2018-11-20 23:06:10 -05:00
|
|
|
foundInDeprecatedDir := w.foundInDeprecatedDir(socketPath)
|
|
|
|
|
|
2018-05-01 02:15:06 -04:00
|
|
|
// calls handler callback to verify registration request
|
2018-11-20 23:06:10 -05:00
|
|
|
if err := handler.ValidatePlugin(infoResp.Name, infoResp.Endpoint, infoResp.SupportedVersions, foundInDeprecatedDir); err != nil {
|
2018-08-11 10:52:41 -04:00
|
|
|
return w.notifyPlugin(client, false, fmt.Sprintf("plugin validation failed with err: %v", err))
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
// We add the plugin to the pluginwatcher's map before calling a plugin consumer's Register handle
|
|
|
|
|
// so that if we receive a delete event during Register Plugin, we can process it as a DeRegister call.
|
|
|
|
|
w.registerPlugin(socketPath, infoResp.Type, infoResp.Name)
|
|
|
|
|
|
2018-11-20 23:06:10 -05:00
|
|
|
if err := handler.RegisterPlugin(infoResp.Name, infoResp.Endpoint, infoResp.SupportedVersions); err != nil {
|
2018-08-11 10:52:41 -04:00
|
|
|
return w.notifyPlugin(client, false, fmt.Sprintf("plugin registration failed with err: %v", err))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Notify is called after register to guarantee that even if notify throws an error Register will always be called after validate
|
|
|
|
|
if err := w.notifyPlugin(client, true, ""); err != nil {
|
2018-05-01 02:15:06 -04:00
|
|
|
return fmt.Errorf("failed to send registration status at socket %s, err: %v", socketPath, err)
|
|
|
|
|
}
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
|
2018-05-01 02:15:06 -04:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
func (w *Watcher) handleDeleteEvent(event fsnotify.Event) error {
|
2018-11-09 13:49:10 -05:00
|
|
|
klog.V(6).Infof("Handling delete event: %v", event)
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
plugin, ok := w.getPlugin(event.Name)
|
|
|
|
|
if !ok {
|
2018-11-15 15:05:11 -05:00
|
|
|
klog.V(5).Infof("could not find plugin for deleted file %s", event.Name)
|
|
|
|
|
return nil
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
// You should not get a Deregister call while registering a plugin
|
|
|
|
|
pool := w.getPluginPool(plugin.pluginType, plugin.pluginName)
|
|
|
|
|
|
|
|
|
|
pool.Lock()
|
|
|
|
|
defer pool.Unlock()
|
|
|
|
|
|
|
|
|
|
// ReRegisteration: When waiting for the lock a plugin with the same name (not socketPath) could have registered
|
|
|
|
|
// In that case, we don't want to issue a DeRegister call for that plugin
|
|
|
|
|
// When ReRegistering, the new plugin will have removed the current mapping (map[socketPath] = plugin) and replaced
|
|
|
|
|
// it with it's own socketPath.
|
|
|
|
|
if _, ok = w.getPlugin(event.Name); !ok {
|
2018-11-09 13:49:10 -05:00
|
|
|
klog.V(2).Infof("A newer plugin watcher has been registered for plugin %v, dropping DeRegister call", plugin)
|
2018-08-11 10:52:41 -04:00
|
|
|
return nil
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
h, ok := w.getHandler(plugin.pluginType)
|
|
|
|
|
if !ok {
|
|
|
|
|
return fmt.Errorf("could not find handler %s for plugin %s at path %s", plugin.pluginType, plugin.pluginName, event.Name)
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
}
|
|
|
|
|
|
2018-11-09 13:49:10 -05:00
|
|
|
klog.V(2).Infof("DeRegistering plugin %v at path %s", plugin, event.Name)
|
2018-08-11 10:52:41 -04:00
|
|
|
w.deRegisterPlugin(event.Name, plugin.pluginType, plugin.pluginName)
|
|
|
|
|
h.DeRegisterPlugin(plugin.pluginName)
|
|
|
|
|
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-30 20:07:58 -04:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
func (w *Watcher) registerPlugin(socketPath, pluginType, pluginName string) {
|
|
|
|
|
w.mutex.Lock()
|
|
|
|
|
defer w.mutex.Unlock()
|
2018-05-01 02:15:06 -04:00
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
// Reregistration case, if this plugin is already in the map, remove it
|
|
|
|
|
// This will prevent handleDeleteEvent to issue a DeRegister call
|
|
|
|
|
for path, info := range w.plugins {
|
|
|
|
|
if info.pluginType != pluginType || info.pluginName != pluginName {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2018-05-01 02:15:06 -04:00
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
delete(w.plugins, path)
|
|
|
|
|
break
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
w.plugins[socketPath] = pathInfo{
|
|
|
|
|
pluginType: pluginType,
|
|
|
|
|
pluginName: pluginName,
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
2018-08-11 10:52:41 -04:00
|
|
|
}
|
2018-05-01 02:15:06 -04:00
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
func (w *Watcher) deRegisterPlugin(socketPath, pluginType, pluginName string) {
|
|
|
|
|
w.mutex.Lock()
|
|
|
|
|
defer w.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
delete(w.plugins, socketPath)
|
|
|
|
|
delete(w.pluginsPool[pluginType], pluginName)
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
func (w *Watcher) getPlugin(socketPath string) (pathInfo, bool) {
|
|
|
|
|
w.mutex.Lock()
|
|
|
|
|
defer w.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
plugin, ok := w.plugins[socketPath]
|
|
|
|
|
return plugin, ok
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *Watcher) getPluginPool(pluginType, pluginName string) *sync.Mutex {
|
|
|
|
|
w.mutex.Lock()
|
|
|
|
|
defer w.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
if _, ok := w.pluginsPool[pluginType]; !ok {
|
|
|
|
|
w.pluginsPool[pluginType] = make(map[string]*sync.Mutex)
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
2018-08-11 10:52:41 -04:00
|
|
|
|
|
|
|
|
if _, ok := w.pluginsPool[pluginType][pluginName]; !ok {
|
|
|
|
|
w.pluginsPool[pluginType][pluginName] = &sync.Mutex{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return w.pluginsPool[pluginType][pluginName]
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
|
|
|
|
|
2018-08-11 10:52:41 -04:00
|
|
|
func (w *Watcher) notifyPlugin(client registerapi.RegistrationClient, registered bool, errStr string) error {
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
status := ®isterapi.RegistrationStatus{
|
|
|
|
|
PluginRegistered: registered,
|
|
|
|
|
Error: errStr,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, err := client.NotifyRegistrationStatus(ctx, status); err != nil {
|
|
|
|
|
return errors.Wrap(err, errStr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if errStr != "" {
|
|
|
|
|
return errors.New(errStr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
2018-05-01 02:15:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Dial establishes the gRPC communication with the picked up plugin socket. https://godoc.org/google.golang.org/grpc#Dial
|
2018-08-11 10:52:41 -04:00
|
|
|
func dial(unixSocketPath string, timeout time.Duration) (registerapi.RegistrationClient, *grpc.ClientConn, error) {
|
2018-08-23 22:18:21 -04:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
c, err := grpc.DialContext(ctx, unixSocketPath, grpc.WithInsecure(), grpc.WithBlock(),
|
2018-05-01 02:15:06 -04:00
|
|
|
grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
|
|
|
|
|
return net.DialTimeout("unix", addr, timeout)
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, fmt.Errorf("failed to dial socket %s, err: %v", unixSocketPath, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return registerapi.NewRegistrationClient(c), c, nil
|
|
|
|
|
}
|
2018-11-20 23:06:10 -05:00
|
|
|
|
|
|
|
|
// While deprecated dir is supported, to add extra protection around #69015
|
|
|
|
|
// we will explicitly blacklist kubernetes.io directory.
|
|
|
|
|
func (w *Watcher) containsBlacklistedDir(path string) bool {
|
|
|
|
|
return strings.HasPrefix(path, w.deprecatedPath+"/kubernetes.io/") ||
|
|
|
|
|
path == w.deprecatedPath+"/kubernetes.io"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *Watcher) foundInDeprecatedDir(socketPath string) bool {
|
|
|
|
|
if len(w.deprecatedPath) != 0 {
|
|
|
|
|
if socketPath == w.deprecatedPath {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deprecatedPath := w.deprecatedPath
|
|
|
|
|
if !strings.HasSuffix(deprecatedPath, "/") {
|
|
|
|
|
deprecatedPath = deprecatedPath + "/"
|
|
|
|
|
}
|
|
|
|
|
if strings.HasPrefix(socketPath, deprecatedPath) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|