mirror of https://github.com/k3s-io/k3s
Merge pull request #51474 from verult/ProberTest
Automatic merge from submit-queue (batch tested with PRs 51805, 51725, 50925, 51474, 51638) Flexvolume dynamic plugin discovery: Prober unit tests and basic e2e test. **What this PR does / why we need it**: Tests for changes introduced in PR #50031 . As part of the prober unit test, I mocked filesystem, filesystem watch, and Flexvolume plugin initialization. Moved the filesystem event goroutine to watcher implementation. **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #51147 **Special notes for your reviewer**: First commit contains added functionality of the mock filesystem. Second commit is the refactor for moving mock filesystem into a common util directory. Third commit is the unit and e2e tests. **Release note**: ```release-note NONE ``` /release-note-none /sig storage /assign @saad-ali @liggitt /cc @mtaufen @chakri-nelluri @wongma7pull/6/head
commit
f07279ada2
|
@ -21,9 +21,9 @@ go_library(
|
||||||
"//pkg/kubelet/kubeletconfig/configfiles:go_default_library",
|
"//pkg/kubelet/kubeletconfig/configfiles:go_default_library",
|
||||||
"//pkg/kubelet/kubeletconfig/status:go_default_library",
|
"//pkg/kubelet/kubeletconfig/status:go_default_library",
|
||||||
"//pkg/kubelet/kubeletconfig/util/equal:go_default_library",
|
"//pkg/kubelet/kubeletconfig/util/equal:go_default_library",
|
||||||
"//pkg/kubelet/kubeletconfig/util/filesystem:go_default_library",
|
|
||||||
"//pkg/kubelet/kubeletconfig/util/log:go_default_library",
|
"//pkg/kubelet/kubeletconfig/util/log:go_default_library",
|
||||||
"//pkg/kubelet/kubeletconfig/util/panic:go_default_library",
|
"//pkg/kubelet/kubeletconfig/util/panic:go_default_library",
|
||||||
|
"//pkg/util/filesystem:go_default_library",
|
||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||||
|
@ -52,7 +52,6 @@ filegroup(
|
||||||
"//pkg/kubelet/kubeletconfig/util/codec:all-srcs",
|
"//pkg/kubelet/kubeletconfig/util/codec:all-srcs",
|
||||||
"//pkg/kubelet/kubeletconfig/util/equal:all-srcs",
|
"//pkg/kubelet/kubeletconfig/util/equal:all-srcs",
|
||||||
"//pkg/kubelet/kubeletconfig/util/files:all-srcs",
|
"//pkg/kubelet/kubeletconfig/util/files:all-srcs",
|
||||||
"//pkg/kubelet/kubeletconfig/util/filesystem:all-srcs",
|
|
||||||
"//pkg/kubelet/kubeletconfig/util/log:all-srcs",
|
"//pkg/kubelet/kubeletconfig/util/log:all-srcs",
|
||||||
"//pkg/kubelet/kubeletconfig/util/panic:all-srcs",
|
"//pkg/kubelet/kubeletconfig/util/panic:all-srcs",
|
||||||
"//pkg/kubelet/kubeletconfig/util/test:all-srcs",
|
"//pkg/kubelet/kubeletconfig/util/test:all-srcs",
|
||||||
|
|
|
@ -16,8 +16,8 @@ go_test(
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/kubelet/kubeletconfig/checkpoint:go_default_library",
|
"//pkg/kubelet/kubeletconfig/checkpoint:go_default_library",
|
||||||
"//pkg/kubelet/kubeletconfig/util/files:go_default_library",
|
"//pkg/kubelet/kubeletconfig/util/files:go_default_library",
|
||||||
"//pkg/kubelet/kubeletconfig/util/filesystem:go_default_library",
|
|
||||||
"//pkg/kubelet/kubeletconfig/util/test:go_default_library",
|
"//pkg/kubelet/kubeletconfig/util/test:go_default_library",
|
||||||
|
"//pkg/util/filesystem:go_default_library",
|
||||||
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
|
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
|
||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
@ -35,8 +35,8 @@ go_library(
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/kubelet/kubeletconfig/checkpoint:go_default_library",
|
"//pkg/kubelet/kubeletconfig/checkpoint:go_default_library",
|
||||||
"//pkg/kubelet/kubeletconfig/util/files:go_default_library",
|
"//pkg/kubelet/kubeletconfig/util/files:go_default_library",
|
||||||
"//pkg/kubelet/kubeletconfig/util/filesystem:go_default_library",
|
|
||||||
"//pkg/kubelet/kubeletconfig/util/log:go_default_library",
|
"//pkg/kubelet/kubeletconfig/util/log:go_default_library",
|
||||||
|
"//pkg/util/filesystem:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,8 @@ import (
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint"
|
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint"
|
||||||
utilfiles "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/files"
|
utilfiles "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/files"
|
||||||
utilfs "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/filesystem"
|
|
||||||
utillog "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log"
|
utillog "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log"
|
||||||
|
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -29,8 +29,8 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint"
|
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint"
|
||||||
utilfiles "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/files"
|
utilfiles "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/files"
|
||||||
utilfs "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/filesystem"
|
|
||||||
utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test"
|
utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test"
|
||||||
|
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
||||||
)
|
)
|
||||||
|
|
||||||
const testCheckpointsDir = "/test-checkpoints-dir"
|
const testCheckpointsDir = "/test-checkpoints-dir"
|
||||||
|
|
|
@ -13,7 +13,7 @@ go_library(
|
||||||
"//pkg/kubelet/apis/kubeletconfig:go_default_library",
|
"//pkg/kubelet/apis/kubeletconfig:go_default_library",
|
||||||
"//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library",
|
"//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library",
|
||||||
"//pkg/kubelet/kubeletconfig/util/codec:go_default_library",
|
"//pkg/kubelet/kubeletconfig/util/codec:go_default_library",
|
||||||
"//pkg/kubelet/kubeletconfig/util/filesystem:go_default_library",
|
"//pkg/util/filesystem:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -40,8 +40,8 @@ go_test(
|
||||||
"//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library",
|
"//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library",
|
||||||
"//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library",
|
"//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library",
|
||||||
"//pkg/kubelet/kubeletconfig/util/files:go_default_library",
|
"//pkg/kubelet/kubeletconfig/util/files:go_default_library",
|
||||||
"//pkg/kubelet/kubeletconfig/util/filesystem:go_default_library",
|
|
||||||
"//pkg/kubelet/kubeletconfig/util/test:go_default_library",
|
"//pkg/kubelet/kubeletconfig/util/test:go_default_library",
|
||||||
|
"//pkg/util/filesystem:go_default_library",
|
||||||
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
|
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||||
],
|
],
|
||||||
|
|
|
@ -24,7 +24,7 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig"
|
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig"
|
||||||
kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme"
|
kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme"
|
||||||
utilcodec "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/codec"
|
utilcodec "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/codec"
|
||||||
utilfs "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/filesystem"
|
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
||||||
)
|
)
|
||||||
|
|
||||||
const kubeletFile = "kubelet"
|
const kubeletFile = "kubelet"
|
||||||
|
|
|
@ -29,8 +29,8 @@ import (
|
||||||
kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme"
|
kubeletscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme"
|
||||||
kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1"
|
kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1"
|
||||||
utilfiles "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/files"
|
utilfiles "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/files"
|
||||||
utilfs "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/filesystem"
|
|
||||||
utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test"
|
utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test"
|
||||||
|
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addFile(fs utilfs.Filesystem, path string, file string) error {
|
func addFile(fs utilfs.Filesystem, path string, file string) error {
|
||||||
|
|
|
@ -31,9 +31,9 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint/store"
|
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint/store"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/configfiles"
|
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/configfiles"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/status"
|
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/status"
|
||||||
utilfs "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/filesystem"
|
|
||||||
utillog "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log"
|
utillog "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log"
|
||||||
utilpanic "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/panic"
|
utilpanic "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/panic"
|
||||||
|
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -8,7 +8,7 @@ load(
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = ["files.go"],
|
srcs = ["files.go"],
|
||||||
deps = ["//pkg/kubelet/kubeletconfig/util/filesystem:go_default_library"],
|
deps = ["//pkg/util/filesystem:go_default_library"],
|
||||||
)
|
)
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
utilfs "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/filesystem"
|
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultPerm = 0666
|
const defaultPerm = 0666
|
||||||
|
|
|
@ -19,6 +19,7 @@ filegroup(
|
||||||
"//pkg/util/ebtables:all-srcs",
|
"//pkg/util/ebtables:all-srcs",
|
||||||
"//pkg/util/env:all-srcs",
|
"//pkg/util/env:all-srcs",
|
||||||
"//pkg/util/file:all-srcs",
|
"//pkg/util/file:all-srcs",
|
||||||
|
"//pkg/util/filesystem:all-srcs",
|
||||||
"//pkg/util/flock:all-srcs",
|
"//pkg/util/flock:all-srcs",
|
||||||
"//pkg/util/goroutinemap:all-srcs",
|
"//pkg/util/goroutinemap:all-srcs",
|
||||||
"//pkg/util/hash:all-srcs",
|
"//pkg/util/hash:all-srcs",
|
||||||
|
|
|
@ -11,8 +11,12 @@ go_library(
|
||||||
"defaultfs.go",
|
"defaultfs.go",
|
||||||
"fakefs.go",
|
"fakefs.go",
|
||||||
"filesystem.go",
|
"filesystem.go",
|
||||||
|
"watcher.go",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/fsnotify/fsnotify:go_default_library",
|
||||||
|
"//vendor/github.com/spf13/afero:go_default_library",
|
||||||
],
|
],
|
||||||
deps = ["//vendor/github.com/spf13/afero:go_default_library"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
|
@ -19,12 +19,15 @@ package filesystem
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultFs implements Filesystem using same-named functions from "os" and "io/ioutil"
|
// DefaultFs implements Filesystem using same-named functions from "os" and "io/ioutil"
|
||||||
type DefaultFs struct{}
|
type DefaultFs struct{}
|
||||||
|
|
||||||
|
var _ Filesystem = DefaultFs{}
|
||||||
|
|
||||||
// Stat via os.Stat
|
// Stat via os.Stat
|
||||||
func (DefaultFs) Stat(name string) (os.FileInfo, error) {
|
func (DefaultFs) Stat(name string) (os.FileInfo, error) {
|
||||||
return os.Stat(name)
|
return os.Stat(name)
|
||||||
|
@ -54,12 +57,17 @@ func (DefaultFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
return os.Chtimes(name, atime, mtime)
|
return os.Chtimes(name, atime, mtime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFile via os.ReadFile
|
// RemoveAll via os.RemoveAll
|
||||||
|
func (DefaultFs) RemoveAll(path string) error {
|
||||||
|
return os.RemoveAll(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile via ioutil.ReadFile
|
||||||
func (DefaultFs) ReadFile(filename string) ([]byte, error) {
|
func (DefaultFs) ReadFile(filename string) ([]byte, error) {
|
||||||
return ioutil.ReadFile(filename)
|
return ioutil.ReadFile(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TempFile via os.TempFile
|
// TempFile via ioutil.TempFile
|
||||||
func (DefaultFs) TempFile(dir, prefix string) (File, error) {
|
func (DefaultFs) TempFile(dir, prefix string) (File, error) {
|
||||||
file, err := ioutil.TempFile(dir, prefix)
|
file, err := ioutil.TempFile(dir, prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -68,6 +76,16 @@ func (DefaultFs) TempFile(dir, prefix string) (File, error) {
|
||||||
return &defaultFile{file}, nil
|
return &defaultFile{file}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadDir via ioutil.ReadDir
|
||||||
|
func (DefaultFs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||||
|
return ioutil.ReadDir(dirname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk via filepath.Walk
|
||||||
|
func (DefaultFs) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||||
|
return filepath.Walk(root, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
// defaultFile implements File using same-named functions from "os"
|
// defaultFile implements File using same-named functions from "os"
|
||||||
type defaultFile struct {
|
type defaultFile struct {
|
||||||
file *os.File
|
file *os.File
|
|
@ -18,6 +18,7 @@ package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
@ -62,12 +63,12 @@ func (fs *fakeFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
return fs.a.Fs.Chtimes(name, atime, mtime)
|
return fs.a.Fs.Chtimes(name, atime, mtime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFile via afero.Fs.ReadFile
|
// ReadFile via afero.ReadFile
|
||||||
func (fs *fakeFs) ReadFile(filename string) ([]byte, error) {
|
func (fs *fakeFs) ReadFile(filename string) ([]byte, error) {
|
||||||
return fs.a.ReadFile(filename)
|
return fs.a.ReadFile(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TempFile via afero.Fs.TempFile
|
// TempFile via afero.TempFile
|
||||||
func (fs *fakeFs) TempFile(dir, prefix string) (File, error) {
|
func (fs *fakeFs) TempFile(dir, prefix string) (File, error) {
|
||||||
file, err := fs.a.TempFile(dir, prefix)
|
file, err := fs.a.TempFile(dir, prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -76,6 +77,21 @@ func (fs *fakeFs) TempFile(dir, prefix string) (File, error) {
|
||||||
return &fakeFile{file}, nil
|
return &fakeFile{file}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadDir via afero.ReadDir
|
||||||
|
func (fs *fakeFs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||||
|
return fs.a.ReadDir(dirname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk via afero.Walk
|
||||||
|
func (fs *fakeFs) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||||
|
return fs.a.Walk(root, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAll via afero.RemoveAll
|
||||||
|
func (fs *fakeFs) RemoveAll(path string) error {
|
||||||
|
return fs.a.RemoveAll(path)
|
||||||
|
}
|
||||||
|
|
||||||
// fakeFile implements File; for use with fakeFs
|
// fakeFile implements File; for use with fakeFs
|
||||||
type fakeFile struct {
|
type fakeFile struct {
|
||||||
file afero.File
|
file afero.File
|
|
@ -18,6 +18,7 @@ package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,10 +30,13 @@ type Filesystem interface {
|
||||||
Rename(oldpath, newpath string) error
|
Rename(oldpath, newpath string) error
|
||||||
MkdirAll(path string, perm os.FileMode) error
|
MkdirAll(path string, perm os.FileMode) error
|
||||||
Chtimes(name string, atime time.Time, mtime time.Time) error
|
Chtimes(name string, atime time.Time, mtime time.Time) error
|
||||||
|
RemoveAll(path string) error
|
||||||
|
|
||||||
// from "io/ioutil"
|
// from "io/ioutil"
|
||||||
ReadFile(filename string) ([]byte, error)
|
ReadFile(filename string) ([]byte, error)
|
||||||
TempFile(dir, prefix string) (File, error)
|
TempFile(dir, prefix string) (File, error)
|
||||||
|
ReadDir(dirname string) ([]os.FileInfo, error)
|
||||||
|
Walk(root string, walkFn filepath.WalkFunc) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// File is an interface that we can use to mock various filesystem operations typically
|
// File is an interface that we can use to mock various filesystem operations typically
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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 filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FSWatcher is a callback-based filesystem watcher abstraction for fsnotify.
|
||||||
|
type FSWatcher interface {
|
||||||
|
// Initializes the watcher with the given watch handlers.
|
||||||
|
// Called before all other methods.
|
||||||
|
Init(FSEventHandler, FSErrorHandler) error
|
||||||
|
|
||||||
|
// Starts listening for events and errors.
|
||||||
|
// When an event or error occurs, the corresponding handler is called.
|
||||||
|
Run()
|
||||||
|
|
||||||
|
// Add a filesystem path to watch
|
||||||
|
AddWatch(path string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FSEventHandler is called when a fsnotify event occurs.
|
||||||
|
type FSEventHandler func(event fsnotify.Event)
|
||||||
|
|
||||||
|
// FSErrorHandler is called when a fsnotify error occurs.
|
||||||
|
type FSErrorHandler func(err error)
|
||||||
|
|
||||||
|
type fsnotifyWatcher struct {
|
||||||
|
watcher *fsnotify.Watcher
|
||||||
|
eventHandler FSEventHandler
|
||||||
|
errorHandler FSErrorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ FSWatcher = &fsnotifyWatcher{}
|
||||||
|
|
||||||
|
// NewFsnotifyWatcher returns an implementation of FSWatcher that continuously listens for
|
||||||
|
// fsnotify events and calls the event handler as soon as an event is received.
|
||||||
|
func NewFsnotifyWatcher() FSWatcher {
|
||||||
|
return &fsnotifyWatcher{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fsnotifyWatcher) AddWatch(path string) error {
|
||||||
|
return w.watcher.Add(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fsnotifyWatcher) Init(eventHandler FSEventHandler, errorHandler FSErrorHandler) error {
|
||||||
|
var err error
|
||||||
|
w.watcher, err = fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.eventHandler = eventHandler
|
||||||
|
w.errorHandler = errorHandler
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fsnotifyWatcher) Run() {
|
||||||
|
go func() {
|
||||||
|
defer w.watcher.Close()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-w.watcher.Events:
|
||||||
|
if w.eventHandler != nil {
|
||||||
|
w.eventHandler(event)
|
||||||
|
}
|
||||||
|
case err := <-w.watcher.Errors:
|
||||||
|
if w.errorHandler != nil {
|
||||||
|
w.errorHandler(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ go_library(
|
||||||
"detacher.go",
|
"detacher.go",
|
||||||
"detacher-defaults.go",
|
"detacher-defaults.go",
|
||||||
"driver-call.go",
|
"driver-call.go",
|
||||||
|
"fake_watcher.go",
|
||||||
"mounter.go",
|
"mounter.go",
|
||||||
"mounter-defaults.go",
|
"mounter-defaults.go",
|
||||||
"plugin.go",
|
"plugin.go",
|
||||||
|
@ -25,6 +26,7 @@ go_library(
|
||||||
"volume.go",
|
"volume.go",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//pkg/util/filesystem:go_default_library",
|
||||||
"//pkg/util/mount:go_default_library",
|
"//pkg/util/mount:go_default_library",
|
||||||
"//pkg/util/strings:go_default_library",
|
"//pkg/util/strings:go_default_library",
|
||||||
"//pkg/volume:go_default_library",
|
"//pkg/volume:go_default_library",
|
||||||
|
@ -48,13 +50,17 @@ go_test(
|
||||||
"flexvolume_test.go",
|
"flexvolume_test.go",
|
||||||
"mounter_test.go",
|
"mounter_test.go",
|
||||||
"plugin_test.go",
|
"plugin_test.go",
|
||||||
|
"probe_test.go",
|
||||||
"unmounter_test.go",
|
"unmounter_test.go",
|
||||||
],
|
],
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
deps = [
|
deps = [
|
||||||
|
"//pkg/util/filesystem:go_default_library",
|
||||||
"//pkg/util/mount:go_default_library",
|
"//pkg/util/mount:go_default_library",
|
||||||
"//pkg/volume:go_default_library",
|
"//pkg/volume:go_default_library",
|
||||||
"//pkg/volume/testing:go_default_library",
|
"//pkg/volume/testing:go_default_library",
|
||||||
|
"//vendor/github.com/fsnotify/fsnotify:go_default_library",
|
||||||
|
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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 flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock filesystem watcher
|
||||||
|
type fakeWatcher struct {
|
||||||
|
watches []string // List of watches added by the prober, ordered from least recent to most recent.
|
||||||
|
eventHandler utilfs.FSEventHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ utilfs.FSWatcher = &fakeWatcher{}
|
||||||
|
|
||||||
|
func NewFakeWatcher() *fakeWatcher {
|
||||||
|
return &fakeWatcher{
|
||||||
|
watches: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fakeWatcher) Init(eventHandler utilfs.FSEventHandler, _ utilfs.FSErrorHandler) error {
|
||||||
|
w.eventHandler = eventHandler
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fakeWatcher) Run() { /* no-op */ }
|
||||||
|
|
||||||
|
func (w *fakeWatcher) AddWatch(path string) error {
|
||||||
|
w.watches = append(w.watches, path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Triggers a mock filesystem event.
|
||||||
|
func (w *fakeWatcher) TriggerEvent(op fsnotify.Op, filename string) {
|
||||||
|
w.eventHandler(fsnotify.Event{Op: op, Name: filename})
|
||||||
|
}
|
|
@ -29,6 +29,6 @@ func logPrefix(plugin *flexVolumePlugin) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (plugin *pluginDefaults) GetVolumeName(spec *volume.Spec) (string, error) {
|
func (plugin *pluginDefaults) GetVolumeName(spec *volume.Spec) (string, error) {
|
||||||
glog.Warning(logPrefix((*flexVolumePlugin)(plugin)), "using default GetVolumeName for volume ", spec.Name)
|
glog.Warning(logPrefix((*flexVolumePlugin)(plugin)), "using default GetVolumeName for volume ", spec.Name())
|
||||||
return spec.Name(), nil
|
return spec.Name(), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,13 @@ type flexVolumeAttachablePlugin struct {
|
||||||
var _ volume.AttachableVolumePlugin = &flexVolumeAttachablePlugin{}
|
var _ volume.AttachableVolumePlugin = &flexVolumeAttachablePlugin{}
|
||||||
var _ volume.PersistentVolumePlugin = &flexVolumePlugin{}
|
var _ volume.PersistentVolumePlugin = &flexVolumePlugin{}
|
||||||
|
|
||||||
func NewFlexVolumePlugin(pluginDir, name string) (volume.VolumePlugin, error) {
|
type PluginFactory interface {
|
||||||
|
NewFlexVolumePlugin(pluginDir, driverName string) (volume.VolumePlugin, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type pluginFactory struct{}
|
||||||
|
|
||||||
|
func (pluginFactory) NewFlexVolumePlugin(pluginDir, name string) (volume.VolumePlugin, error) {
|
||||||
execPath := path.Join(pluginDir, name)
|
execPath := path.Join(pluginDir, name)
|
||||||
|
|
||||||
driverName := utilstrings.UnescapePluginName(name)
|
driverName := utilstrings.UnescapePluginName(name)
|
||||||
|
|
|
@ -17,8 +17,6 @@ limitations under the License.
|
||||||
package flexvolume
|
package flexvolume
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
|
||||||
|
@ -31,15 +29,18 @@ import (
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"k8s.io/apimachinery/pkg/util/errors"
|
"k8s.io/apimachinery/pkg/util/errors"
|
||||||
|
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
||||||
)
|
)
|
||||||
|
|
||||||
type flexVolumeProber struct {
|
type flexVolumeProber struct {
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
pluginDir string // Flexvolume driver directory
|
pluginDir string // Flexvolume driver directory
|
||||||
watcher *fsnotify.Watcher
|
watcher utilfs.FSWatcher
|
||||||
probeNeeded bool // Must only read and write this through testAndSetProbeNeeded.
|
probeNeeded bool // Must only read and write this through testAndSetProbeNeeded.
|
||||||
lastUpdated time.Time // Last time probeNeeded was updated.
|
lastUpdated time.Time // Last time probeNeeded was updated.
|
||||||
watchEventCount int
|
watchEventCount int
|
||||||
|
factory PluginFactory
|
||||||
|
fs utilfs.Filesystem
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -50,7 +51,12 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetDynamicPluginProber(pluginDir string) volume.DynamicPluginProber {
|
func GetDynamicPluginProber(pluginDir string) volume.DynamicPluginProber {
|
||||||
return &flexVolumeProber{pluginDir: pluginDir}
|
return &flexVolumeProber{
|
||||||
|
pluginDir: pluginDir,
|
||||||
|
watcher: utilfs.NewFsnotifyWatcher(),
|
||||||
|
factory: pluginFactory{},
|
||||||
|
fs: &utilfs.DefaultFs{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (prober *flexVolumeProber) Init() error {
|
func (prober *flexVolumeProber) Init() error {
|
||||||
|
@ -64,20 +70,6 @@ func (prober *flexVolumeProber) Init() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer prober.watcher.Close()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event := <-prober.watcher.Events:
|
|
||||||
if err := prober.handleWatchEvent(event); err != nil {
|
|
||||||
glog.Errorf("Flexvolume prober watch: %s", err)
|
|
||||||
}
|
|
||||||
case err := <-prober.watcher.Errors:
|
|
||||||
glog.Errorf("Received an error from watcher: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +86,7 @@ func (prober *flexVolumeProber) Probe() (updated bool, plugins []volume.VolumePl
|
||||||
return false, nil, nil
|
return false, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(prober.pluginDir)
|
files, err := prober.fs.ReadDir(prober.pluginDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, fmt.Errorf("Error reading the Flexvolume directory: %s", err)
|
return false, nil, fmt.Errorf("Error reading the Flexvolume directory: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -108,7 +100,7 @@ func (prober *flexVolumeProber) Probe() (updated bool, plugins []volume.VolumePl
|
||||||
// e.g. dirname = vendor~cifs
|
// e.g. dirname = vendor~cifs
|
||||||
// then, executable will be pluginDir/dirname/cifs
|
// then, executable will be pluginDir/dirname/cifs
|
||||||
if f.IsDir() && filepath.Base(f.Name())[0] != '.' {
|
if f.IsDir() && filepath.Base(f.Name())[0] != '.' {
|
||||||
plugin, pluginErr := NewFlexVolumePlugin(prober.pluginDir, f.Name())
|
plugin, pluginErr := prober.factory.NewFlexVolumePlugin(prober.pluginDir, f.Name())
|
||||||
if pluginErr != nil {
|
if pluginErr != nil {
|
||||||
pluginErr = fmt.Errorf(
|
pluginErr = fmt.Errorf(
|
||||||
"Error creating Flexvolume plugin from directory %s, skipping. Error: %s",
|
"Error creating Flexvolume plugin from directory %s, skipping. Error: %s",
|
||||||
|
@ -144,7 +136,6 @@ func (prober *flexVolumeProber) handleWatchEvent(event fsnotify.Event) error {
|
||||||
// If the Flexvolume plugin directory is removed, need to recreate it
|
// If the Flexvolume plugin directory is removed, need to recreate it
|
||||||
// in order to keep it under watch.
|
// in order to keep it under watch.
|
||||||
if eventOpIs(event, fsnotify.Remove) && eventPathAbs == pluginDirAbs {
|
if eventOpIs(event, fsnotify.Remove) && eventPathAbs == pluginDirAbs {
|
||||||
glog.Warningf("Flexvolume plugin directory at %s is removed. Recreating.", pluginDirAbs)
|
|
||||||
if err := prober.createPluginDir(); err != nil {
|
if err := prober.createPluginDir(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -186,35 +177,43 @@ func (prober *flexVolumeProber) updateProbeNeeded() {
|
||||||
func (prober *flexVolumeProber) addWatchRecursive(filename string) error {
|
func (prober *flexVolumeProber) addWatchRecursive(filename string) error {
|
||||||
addWatch := func(path string, info os.FileInfo, err error) error {
|
addWatch := func(path string, info os.FileInfo, err error) error {
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
if err := prober.watcher.Add(path); err != nil {
|
if err := prober.watcher.AddWatch(path); err != nil {
|
||||||
glog.Errorf("Error recursively adding watch: %v", err)
|
glog.Errorf("Error recursively adding watch: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return prober.fs.Walk(filename, addWatch)
|
||||||
return filepath.Walk(filename, addWatch)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new filesystem watcher and adds watches for the plugin directory
|
// Creates a new filesystem watcher and adds watches for the plugin directory
|
||||||
// and all of its subdirectories.
|
// and all of its subdirectories.
|
||||||
func (prober *flexVolumeProber) initWatcher() error {
|
func (prober *flexVolumeProber) initWatcher() error {
|
||||||
var err error
|
err := prober.watcher.Init(func(event fsnotify.Event) {
|
||||||
if prober.watcher, err = fsnotify.NewWatcher(); err != nil {
|
if err := prober.handleWatchEvent(event); err != nil {
|
||||||
return fmt.Errorf("Error creating new watcher: %s", err)
|
glog.Errorf("Flexvolume prober watch: %s", err)
|
||||||
|
}
|
||||||
|
}, func(err error) {
|
||||||
|
glog.Errorf("Received an error from watcher: %s", err)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error initializing watcher: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = prober.addWatchRecursive(prober.pluginDir); err != nil {
|
if err := prober.addWatchRecursive(prober.pluginDir); err != nil {
|
||||||
return fmt.Errorf("Error adding watch on Flexvolume directory: %s", err)
|
return fmt.Errorf("Error adding watch on Flexvolume directory: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prober.watcher.Run()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates the plugin directory, if it doesn't already exist.
|
// Creates the plugin directory, if it doesn't already exist.
|
||||||
func (prober *flexVolumeProber) createPluginDir() error {
|
func (prober *flexVolumeProber) createPluginDir() error {
|
||||||
if _, err := os.Stat(prober.pluginDir); os.IsNotExist(err) {
|
if _, err := prober.fs.Stat(prober.pluginDir); os.IsNotExist(err) {
|
||||||
err := os.MkdirAll(prober.pluginDir, 0755)
|
glog.Warningf("Flexvolume plugin directory at %s does not exist. Recreating.", prober.pluginDir)
|
||||||
|
err := prober.fs.MkdirAll(prober.pluginDir, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error (re-)creating driver directory: %s", err)
|
return fmt.Errorf("Error (re-)creating driver directory: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,274 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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 flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
pluginDir = "/flexvolume"
|
||||||
|
driverName = "fake-driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Probes a driver installed before prober initialization.
|
||||||
|
func TestProberExistingDriverBeforeInit(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
driverPath, _, watcher, prober := initTestEnvironment(t)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
updated, plugins, err := prober.Probe()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// Probe occurs, 1 plugin should be returned, and 2 watches (pluginDir and all its
|
||||||
|
// current subdirectories) registered.
|
||||||
|
assert.True(t, updated)
|
||||||
|
assert.Equal(t, 1, len(plugins))
|
||||||
|
assert.Equal(t, pluginDir, watcher.watches[0])
|
||||||
|
assert.Equal(t, driverPath, watcher.watches[1])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Should no longer probe.
|
||||||
|
|
||||||
|
// Act
|
||||||
|
updated, plugins, err = prober.Probe()
|
||||||
|
// Assert
|
||||||
|
assert.False(t, updated)
|
||||||
|
assert.Equal(t, 0, len(plugins))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probes newly added drivers after prober is running.
|
||||||
|
func TestProberAddDriver(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
_, fs, watcher, prober := initTestEnvironment(t)
|
||||||
|
prober.Probe()
|
||||||
|
updated, _, _ := prober.Probe()
|
||||||
|
assert.False(t, updated)
|
||||||
|
|
||||||
|
// Call probe after a file is added. Should return true.
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
const driverName2 = "fake-driver2"
|
||||||
|
driverPath := path.Join(pluginDir, driverName2)
|
||||||
|
installDriver(driverName2, fs)
|
||||||
|
watcher.TriggerEvent(fsnotify.Create, driverPath)
|
||||||
|
watcher.TriggerEvent(fsnotify.Create, path.Join(driverPath, driverName2))
|
||||||
|
|
||||||
|
// Act
|
||||||
|
updated, plugins, err := prober.Probe()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.True(t, updated)
|
||||||
|
assert.Equal(t, 2, len(plugins)) // 1 existing, 1 newly added
|
||||||
|
assert.Equal(t, driverPath, watcher.watches[len(watcher.watches)-1]) // Checks most recent watch
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Call probe again, should return false.
|
||||||
|
|
||||||
|
// Act
|
||||||
|
updated, _, err = prober.Probe()
|
||||||
|
// Assert
|
||||||
|
assert.False(t, updated)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Call probe after a non-driver file is added in a subdirectory. Should return true.
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
fp := path.Join(driverPath, "dummyfile")
|
||||||
|
fs.Create(fp)
|
||||||
|
watcher.TriggerEvent(fsnotify.Create, fp)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
updated, plugins, err = prober.Probe()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.True(t, updated)
|
||||||
|
assert.Equal(t, 2, len(plugins)) // Number of plugins should not change.
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Call probe again, should return false.
|
||||||
|
// Act
|
||||||
|
updated, _, err = prober.Probe()
|
||||||
|
// Assert
|
||||||
|
assert.False(t, updated)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests the behavior when no drivers exist in the plugin directory.
|
||||||
|
func TestEmptyPluginDir(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
fs := utilfs.NewFakeFs()
|
||||||
|
watcher := NewFakeWatcher()
|
||||||
|
prober := &flexVolumeProber{
|
||||||
|
pluginDir: pluginDir,
|
||||||
|
watcher: watcher,
|
||||||
|
fs: fs,
|
||||||
|
factory: fakePluginFactory{error: false},
|
||||||
|
}
|
||||||
|
prober.Init()
|
||||||
|
|
||||||
|
// Act
|
||||||
|
updated, plugins, err := prober.Probe()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.True(t, updated)
|
||||||
|
assert.Equal(t, 0, len(plugins))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue an event to remove plugindir. New directory should still be watched.
|
||||||
|
func TestRemovePluginDir(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
driverPath, fs, watcher, _ := initTestEnvironment(t)
|
||||||
|
fs.RemoveAll(pluginDir)
|
||||||
|
watcher.TriggerEvent(fsnotify.Remove, path.Join(driverPath, driverName))
|
||||||
|
watcher.TriggerEvent(fsnotify.Remove, driverPath)
|
||||||
|
watcher.TriggerEvent(fsnotify.Remove, pluginDir)
|
||||||
|
|
||||||
|
// Act: The handler triggered by the above events should have already handled the event appropriately.
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.Equal(t, 3, len(watcher.watches)) // 2 from initial setup, 1 from new watch.
|
||||||
|
assert.Equal(t, pluginDir, watcher.watches[len(watcher.watches)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue multiple events and probe multiple times. Should give true, false, false...
|
||||||
|
func TestProberMultipleEvents(t *testing.T) {
|
||||||
|
const iterations = 5
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
_, fs, watcher, prober := initTestEnvironment(t)
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
newDriver := fmt.Sprintf("multi-event-driver%d", 1)
|
||||||
|
installDriver(newDriver, fs)
|
||||||
|
driverPath := path.Join(pluginDir, newDriver)
|
||||||
|
watcher.TriggerEvent(fsnotify.Create, driverPath)
|
||||||
|
watcher.TriggerEvent(fsnotify.Create, path.Join(driverPath, newDriver))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
updated, _, err := prober.Probe()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.True(t, updated)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
for i := 0; i < iterations-1; i++ {
|
||||||
|
updated, _, err = prober.Probe()
|
||||||
|
assert.False(t, updated)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When many events are triggered quickly in succession, events should stop triggering a probe update
|
||||||
|
// after a certain limit.
|
||||||
|
func TestProberRateLimit(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
driverPath, _, watcher, prober := initTestEnvironment(t)
|
||||||
|
for i := 0; i < watchEventLimit; i++ {
|
||||||
|
watcher.TriggerEvent(fsnotify.Write, path.Join(driverPath, driverName))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
updated, plugins, err := prober.Probe()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// The probe results should not be different from what it would be if none of the events
|
||||||
|
// are triggered.
|
||||||
|
assert.True(t, updated)
|
||||||
|
assert.Equal(t, 1, len(plugins))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
watcher.TriggerEvent(fsnotify.Write, path.Join(driverPath, driverName))
|
||||||
|
|
||||||
|
// Act
|
||||||
|
updated, _, err = prober.Probe()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// The last event is outside the event limit. Should not trigger a probe.
|
||||||
|
assert.False(t, updated)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProberError(t *testing.T) {
|
||||||
|
fs := utilfs.NewFakeFs()
|
||||||
|
watcher := NewFakeWatcher()
|
||||||
|
prober := &flexVolumeProber{
|
||||||
|
pluginDir: pluginDir,
|
||||||
|
watcher: watcher,
|
||||||
|
fs: fs,
|
||||||
|
factory: fakePluginFactory{error: true},
|
||||||
|
}
|
||||||
|
installDriver(driverName, fs)
|
||||||
|
prober.Init()
|
||||||
|
|
||||||
|
_, _, err := prober.Probe()
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Installs a mock driver (an empty file) in the mock fs.
|
||||||
|
func installDriver(driverName string, fs utilfs.Filesystem) {
|
||||||
|
driverPath := path.Join(pluginDir, driverName)
|
||||||
|
fs.MkdirAll(driverPath, 0666)
|
||||||
|
fs.Create(path.Join(driverPath, driverName))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializes mocks, installs a single driver in the mock fs, then initializes prober.
|
||||||
|
func initTestEnvironment(t *testing.T) (
|
||||||
|
driverPath string,
|
||||||
|
fs utilfs.Filesystem,
|
||||||
|
watcher *fakeWatcher,
|
||||||
|
prober volume.DynamicPluginProber) {
|
||||||
|
fs = utilfs.NewFakeFs()
|
||||||
|
watcher = NewFakeWatcher()
|
||||||
|
prober = &flexVolumeProber{
|
||||||
|
pluginDir: pluginDir,
|
||||||
|
watcher: watcher,
|
||||||
|
fs: fs,
|
||||||
|
factory: fakePluginFactory{error: false},
|
||||||
|
}
|
||||||
|
driverPath = path.Join(pluginDir, driverName)
|
||||||
|
installDriver(driverName, fs)
|
||||||
|
prober.Init()
|
||||||
|
|
||||||
|
assert.NotNilf(t, watcher.eventHandler,
|
||||||
|
"Expect watch event handler to be registered after prober init, but is not.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fake Flexvolume plugin
|
||||||
|
type fakePluginFactory struct {
|
||||||
|
error bool // Indicates whether an error should be returned.
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PluginFactory = fakePluginFactory{}
|
||||||
|
|
||||||
|
func (m fakePluginFactory) NewFlexVolumePlugin(_, driverName string) (volume.VolumePlugin, error) {
|
||||||
|
if m.error {
|
||||||
|
return nil, fmt.Errorf("Flexvolume plugin error")
|
||||||
|
}
|
||||||
|
// Dummy Flexvolume plugin. Prober never interacts with the plugin.
|
||||||
|
return &flexVolumePlugin{driverName: driverName}, nil
|
||||||
|
}
|
|
@ -63,10 +63,10 @@ func testFlexVolume(driver string, cs clientset.Interface, config framework.Volu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// installFlex installs the driver found at filePath on the node and restarts
|
// installFlex installs the driver found at filePath on the node, and restarts
|
||||||
// kubelet. If node is nil, installs on the master and restarts
|
// kubelet if 'restart' is true. If node is nil, installs on the master, and restarts
|
||||||
// controller-manager.
|
// controller-manager if 'restart' is true.
|
||||||
func installFlex(node *v1.Node, vendor, driver, filePath string) {
|
func installFlex(node *v1.Node, vendor, driver, filePath string, restart bool) {
|
||||||
flexDir := getFlexDir(node == nil, vendor, driver)
|
flexDir := getFlexDir(node == nil, vendor, driver)
|
||||||
flexFile := path.Join(flexDir, driver)
|
flexFile := path.Join(flexDir, driver)
|
||||||
|
|
||||||
|
@ -87,6 +87,10 @@ func installFlex(node *v1.Node, vendor, driver, filePath string) {
|
||||||
cmd = fmt.Sprintf("sudo chmod +x %s", flexFile)
|
cmd = fmt.Sprintf("sudo chmod +x %s", flexFile)
|
||||||
sshAndLog(cmd, host)
|
sshAndLog(cmd, host)
|
||||||
|
|
||||||
|
if !restart {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if node != nil {
|
if node != nil {
|
||||||
err := framework.RestartKubelet(host)
|
err := framework.RestartKubelet(host)
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
@ -170,7 +174,7 @@ var _ = SIGDescribe("Flexvolumes [Disruptive] [Feature:FlexVolume]", func() {
|
||||||
driverInstallAs := driver + "-" + suffix
|
driverInstallAs := driver + "-" + suffix
|
||||||
|
|
||||||
By(fmt.Sprintf("installing flexvolume %s on node %s as %s", path.Join(driverDir, driver), node.Name, driverInstallAs))
|
By(fmt.Sprintf("installing flexvolume %s on node %s as %s", path.Join(driverDir, driver), node.Name, driverInstallAs))
|
||||||
installFlex(&node, "k8s", driverInstallAs, path.Join(driverDir, driver))
|
installFlex(&node, "k8s", driverInstallAs, path.Join(driverDir, driver), true /* restart */)
|
||||||
|
|
||||||
testFlexVolume(driverInstallAs, cs, config, f, clean)
|
testFlexVolume(driverInstallAs, cs, config, f, clean)
|
||||||
|
|
||||||
|
@ -188,9 +192,9 @@ var _ = SIGDescribe("Flexvolumes [Disruptive] [Feature:FlexVolume]", func() {
|
||||||
driverInstallAs := driver + "-" + suffix
|
driverInstallAs := driver + "-" + suffix
|
||||||
|
|
||||||
By(fmt.Sprintf("installing flexvolume %s on node %s as %s", path.Join(driverDir, driver), node.Name, driverInstallAs))
|
By(fmt.Sprintf("installing flexvolume %s on node %s as %s", path.Join(driverDir, driver), node.Name, driverInstallAs))
|
||||||
installFlex(&node, "k8s", driverInstallAs, path.Join(driverDir, driver))
|
installFlex(&node, "k8s", driverInstallAs, path.Join(driverDir, driver), true /* restart */)
|
||||||
By(fmt.Sprintf("installing flexvolume %s on master as %s", path.Join(driverDir, driver), driverInstallAs))
|
By(fmt.Sprintf("installing flexvolume %s on master as %s", path.Join(driverDir, driver), driverInstallAs))
|
||||||
installFlex(nil, "k8s", driverInstallAs, path.Join(driverDir, driver))
|
installFlex(nil, "k8s", driverInstallAs, path.Join(driverDir, driver), true /* restart */)
|
||||||
|
|
||||||
testFlexVolume(driverInstallAs, cs, config, f, clean)
|
testFlexVolume(driverInstallAs, cs, config, f, clean)
|
||||||
|
|
||||||
|
@ -204,4 +208,22 @@ var _ = SIGDescribe("Flexvolumes [Disruptive] [Feature:FlexVolume]", func() {
|
||||||
By(fmt.Sprintf("uninstalling flexvolume %s from master", driverInstallAs))
|
By(fmt.Sprintf("uninstalling flexvolume %s from master", driverInstallAs))
|
||||||
uninstallFlex(nil, "k8s", driverInstallAs)
|
uninstallFlex(nil, "k8s", driverInstallAs)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should install plugin without kubelet restart", func() {
|
||||||
|
driver := "dummy"
|
||||||
|
driverInstallAs := driver + "-" + suffix
|
||||||
|
|
||||||
|
By(fmt.Sprintf("installing flexvolume %s on node %s as %s", path.Join(driverDir, driver), node.Name, driverInstallAs))
|
||||||
|
installFlex(&node, "k8s", driverInstallAs, path.Join(driverDir, driver), false /* restart */)
|
||||||
|
|
||||||
|
testFlexVolume(driverInstallAs, cs, config, f, clean)
|
||||||
|
|
||||||
|
By("waiting for flex client pod to terminate")
|
||||||
|
if err := f.WaitForPodTerminated(config.Prefix+"-client", ""); !apierrs.IsNotFound(err) {
|
||||||
|
framework.ExpectNoError(err, "Failed to wait client pod terminated: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
By(fmt.Sprintf("uninstalling flexvolume %s from node %s", driverInstallAs, node.Name))
|
||||||
|
uninstallFlex(&node, "k8s", driverInstallAs)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue