mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-14 18:53:35 +00:00
Fresh dep ensure
This commit is contained in:
41
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/BUILD
generated
vendored
41
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/BUILD
generated
vendored
@ -1,27 +1,36 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"example_handler.go",
|
||||
"example_plugin.go",
|
||||
"plugin_watcher.go",
|
||||
"types.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/kubelet/apis/pluginregistration/v1alpha1:go_default_library",
|
||||
"//pkg/kubelet/apis/pluginregistration/v1:go_default_library",
|
||||
"//pkg/kubelet/util/pluginwatcher/example_plugin_apis/v1beta1:go_default_library",
|
||||
"//pkg/kubelet/util/pluginwatcher/example_plugin_apis/v1beta2:go_default_library",
|
||||
"//pkg/util/filesystem:go_default_library",
|
||||
"//vendor/github.com/fsnotify/fsnotify:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/pkg/errors:go_default_library",
|
||||
"//vendor/golang.org/x/net/context:go_default_library",
|
||||
"//vendor/google.golang.org/grpc:go_default_library",
|
||||
"//vendor/k8s.io/klog:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["plugin_watcher_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/kubelet/apis/pluginregistration/v1:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
||||
"//vendor/k8s.io/klog:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@ -42,17 +51,3 @@ filegroup(
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["plugin_watcher_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/kubelet/apis/pluginregistration/v1alpha1:go_default_library",
|
||||
"//pkg/kubelet/util/pluginwatcher/example_plugin_apis/v1beta1:go_default_library",
|
||||
"//pkg/kubelet/util/pluginwatcher/example_plugin_apis/v1beta2:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
||||
"//vendor/golang.org/x/net/context:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
],
|
||||
)
|
||||
|
29
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/README
generated
vendored
29
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/README
generated
vendored
@ -1,29 +0,0 @@
|
||||
This folder contains a utility, pluginwatcher, for Kubelet to register
|
||||
different types of node-level plugins such as device plugins or CSI plugins.
|
||||
It discovers plugins by monitoring inotify events under the directory returned by
|
||||
kubelet.getPluginsDir(). Lets refer this directory as PluginsSockDir.
|
||||
For any discovered plugin, pluginwatcher issues Registration.GetInfo grpc call
|
||||
to get plugin type, name and supported service API versions. For any registered plugin type,
|
||||
pluginwatcher calls the registered callback function with the received plugin
|
||||
name, supported service API versions, and the full socket path. The Kubelet
|
||||
component that receives this callback can acknowledge or reject the plugin
|
||||
according to its own logic, and use the socket path to establish its service
|
||||
communication with any API version supported by the plugin.
|
||||
|
||||
Here are the general rules that Kubelet plugin developers should follow:
|
||||
- Run as 'root' user. Currently creating socket under PluginsSockDir, a root owned directory, requires
|
||||
plugin process to be running as 'root'.
|
||||
- Implements the Registration service specified in
|
||||
pkg/kubelet/apis/pluginregistration/v*/api.proto.
|
||||
- The plugin name sent during Registration.GetInfo grpc should be unique
|
||||
for the given plugin type (CSIPlugin or DevicePlugin).
|
||||
- The socket path needs to be unique and doesn't conflict with the path chosen
|
||||
by any other potential plugins. Currently we only support flat fs namespace
|
||||
under PluginsSockDir but will soon support recursive inotify watch for
|
||||
hierarchical socket paths.
|
||||
- A plugin should clean up its own socket upon exiting or when a new instance
|
||||
comes up. A plugin should NOT remove any sockets belonging to other plugins.
|
||||
- A plugin should make sure it has service ready for any supported service API
|
||||
version listed in the PluginInfo.
|
||||
- For an example plugin implementation, take a look at example_plugin.go
|
||||
included in this directory.
|
79
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/README.md
generated
vendored
Normal file
79
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/README.md
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
# Plugin Registration Service
|
||||
|
||||
This folder contains a utility, pluginwatcher, for Kubelet to register
|
||||
different types of node-level plugins such as device plugins or CSI plugins.
|
||||
It discovers plugins by monitoring inotify events under the directory returned by
|
||||
kubelet.getPluginsDir(). We will refer to this directory as PluginsDir.
|
||||
|
||||
Plugins are expected to implement the gRPC registration service specified in
|
||||
pkg/kubelet/apis/pluginregistration/v*/api.proto.
|
||||
|
||||
## Plugin Discovery
|
||||
|
||||
The pluginwatcher service will discover plugins in the PluginDir when they
|
||||
place a socket in that directory or, at Kubelet start if the socket is already
|
||||
there.
|
||||
|
||||
This socket filename should not start with a '.' as it will be ignored.
|
||||
|
||||
|
||||
## gRPC Service Lifecycle
|
||||
|
||||
For any discovered plugin, kubelet will issue a Registration.GetInfo gRPC call
|
||||
to get plugin type, name, endpoint and supported service API versions.
|
||||
|
||||
Kubelet will then go through a plugin initialization phase where it will issue
|
||||
Plugin specific calls (e.g: DevicePlugin::GetDevicePluginOptions).
|
||||
|
||||
Once Kubelet determines that it is ready to use your plugin it will issue a
|
||||
Registration.NotifyRegistrationStatus gRPC call.
|
||||
|
||||
If the plugin removes its socket from the PluginDir this will be interpreted
|
||||
as a plugin Deregistration
|
||||
|
||||
|
||||
## gRPC Service Overview
|
||||
|
||||
Here are the general rules that Kubelet plugin developers should follow:
|
||||
- Run plugin as 'root' user. Currently creating socket under PluginsDir, a root owned
|
||||
directory, requires plugin process to be running as 'root'.
|
||||
|
||||
- The plugin name sent during Registration.GetInfo grpc should be unique
|
||||
for the given plugin type (CSIPlugin or DevicePlugin).
|
||||
|
||||
- The socket path needs to be unique within one directory, in normal case,
|
||||
each plugin type has its own sub directory, but the design does support socket file
|
||||
under any sub directory of PluginSockDir.
|
||||
|
||||
- A plugin should clean up its own socket upon exiting or when a new instance
|
||||
comes up. A plugin should NOT remove any sockets belonging to other plugins.
|
||||
|
||||
- A plugin should make sure it has service ready for any supported service API
|
||||
version listed in the PluginInfo.
|
||||
|
||||
- For an example plugin implementation, take a look at example_plugin.go
|
||||
included in this directory.
|
||||
|
||||
|
||||
# Kubelet Interface
|
||||
|
||||
For any kubelet components using the pluginwatcher module, you will need to
|
||||
implement the PluginHandler interface defined in the types.go file.
|
||||
|
||||
The interface is documented and the implementations are registered with the
|
||||
pluginwatcher module in kubelet.go by calling AddHandler(pluginType, handler).
|
||||
|
||||
|
||||
The lifecycle follows a simple state machine:
|
||||
|
||||
Validate -> Register -> DeRegister
|
||||
^ +
|
||||
| |
|
||||
+--------- +
|
||||
|
||||
The pluginwatcher calls the functions with the received plugin name, supported
|
||||
service API versions and the endpoint to call the plugin on.
|
||||
|
||||
The Kubelet component that receives this callback can acknowledge or reject
|
||||
the plugin according to its own logic, and use the socket path to establish
|
||||
its service communication with any API version supported by the plugin.
|
154
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/example_handler.go
generated
vendored
Normal file
154
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/example_handler.go
generated
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"k8s.io/klog"
|
||||
|
||||
v1beta1 "k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/example_plugin_apis/v1beta1"
|
||||
v1beta2 "k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/example_plugin_apis/v1beta2"
|
||||
)
|
||||
|
||||
type exampleHandler struct {
|
||||
SupportedVersions []string
|
||||
ExpectedNames map[string]int
|
||||
|
||||
eventChans map[string]chan examplePluginEvent // map[pluginName]eventChan
|
||||
|
||||
m sync.Mutex
|
||||
count int
|
||||
|
||||
permitDeprecatedDir bool
|
||||
}
|
||||
|
||||
type examplePluginEvent int
|
||||
|
||||
const (
|
||||
exampleEventValidate examplePluginEvent = 0
|
||||
exampleEventRegister examplePluginEvent = 1
|
||||
exampleEventDeRegister examplePluginEvent = 2
|
||||
exampleEventError examplePluginEvent = 3
|
||||
)
|
||||
|
||||
// NewExampleHandler provide a example handler
|
||||
func NewExampleHandler(supportedVersions []string, permitDeprecatedDir bool) *exampleHandler {
|
||||
return &exampleHandler{
|
||||
SupportedVersions: supportedVersions,
|
||||
ExpectedNames: make(map[string]int),
|
||||
|
||||
eventChans: make(map[string]chan examplePluginEvent),
|
||||
permitDeprecatedDir: permitDeprecatedDir,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exampleHandler) ValidatePlugin(pluginName string, endpoint string, versions []string, foundInDeprecatedDir bool) error {
|
||||
if foundInDeprecatedDir && !p.permitDeprecatedDir {
|
||||
return fmt.Errorf("device plugin socket was found in a directory that is no longer supported and this test does not permit plugins from deprecated dir")
|
||||
}
|
||||
|
||||
p.SendEvent(pluginName, exampleEventValidate)
|
||||
|
||||
n, ok := p.DecreasePluginCount(pluginName)
|
||||
if !ok && n > 0 {
|
||||
return fmt.Errorf("pluginName('%s') wasn't expected (count is %d)", pluginName, n)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(versions, p.SupportedVersions) {
|
||||
return fmt.Errorf("versions('%v') != supported versions('%v')", versions, p.SupportedVersions)
|
||||
}
|
||||
|
||||
// this handler expects non-empty endpoint as an example
|
||||
if len(endpoint) == 0 {
|
||||
return errors.New("expecting non empty endpoint")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *exampleHandler) RegisterPlugin(pluginName, endpoint string, versions []string) error {
|
||||
p.SendEvent(pluginName, exampleEventRegister)
|
||||
|
||||
// Verifies the grpcServer is ready to serve services.
|
||||
_, conn, err := dial(endpoint, time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed dialing endpoint (%s): %v", endpoint, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// The plugin handler should be able to use any listed service API version.
|
||||
v1beta1Client := v1beta1.NewExampleClient(conn)
|
||||
v1beta2Client := v1beta2.NewExampleClient(conn)
|
||||
|
||||
// Tests v1beta1 GetExampleInfo
|
||||
_, err = v1beta1Client.GetExampleInfo(context.Background(), &v1beta1.ExampleRequest{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed GetExampleInfo for v1beta2Client(%s): %v", endpoint, err)
|
||||
}
|
||||
|
||||
// Tests v1beta1 GetExampleInfo
|
||||
_, err = v1beta2Client.GetExampleInfo(context.Background(), &v1beta2.ExampleRequest{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed GetExampleInfo for v1beta2Client(%s): %v", endpoint, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *exampleHandler) DeRegisterPlugin(pluginName string) {
|
||||
p.SendEvent(pluginName, exampleEventDeRegister)
|
||||
}
|
||||
|
||||
func (p *exampleHandler) EventChan(pluginName string) chan examplePluginEvent {
|
||||
return p.eventChans[pluginName]
|
||||
}
|
||||
|
||||
func (p *exampleHandler) SendEvent(pluginName string, event examplePluginEvent) {
|
||||
klog.V(2).Infof("Sending %v for plugin %s over chan %v", event, pluginName, p.eventChans[pluginName])
|
||||
p.eventChans[pluginName] <- event
|
||||
}
|
||||
|
||||
func (p *exampleHandler) AddPluginName(pluginName string) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
v, ok := p.ExpectedNames[pluginName]
|
||||
if !ok {
|
||||
p.eventChans[pluginName] = make(chan examplePluginEvent)
|
||||
v = 1
|
||||
}
|
||||
|
||||
p.ExpectedNames[pluginName] = v
|
||||
}
|
||||
|
||||
func (p *exampleHandler) DecreasePluginCount(pluginName string) (old int, ok bool) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
v, ok := p.ExpectedNames[pluginName]
|
||||
if !ok {
|
||||
v = -1
|
||||
}
|
||||
|
||||
return v, ok
|
||||
}
|
87
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/example_plugin.go
generated
vendored
87
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/example_plugin.go
generated
vendored
@ -17,31 +17,31 @@ limitations under the License.
|
||||
package pluginwatcher
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"k8s.io/klog"
|
||||
|
||||
registerapi "k8s.io/kubernetes/pkg/kubelet/apis/pluginregistration/v1alpha1"
|
||||
registerapi "k8s.io/kubernetes/pkg/kubelet/apis/pluginregistration/v1"
|
||||
v1beta1 "k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/example_plugin_apis/v1beta1"
|
||||
v1beta2 "k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/example_plugin_apis/v1beta2"
|
||||
)
|
||||
|
||||
const (
|
||||
PluginName = "example-plugin"
|
||||
PluginType = "example-plugin-type"
|
||||
)
|
||||
|
||||
// examplePlugin is a sample plugin to work with plugin watcher
|
||||
type examplePlugin struct {
|
||||
grpcServer *grpc.Server
|
||||
wg sync.WaitGroup
|
||||
registrationStatus chan registerapi.RegistrationStatus // for testing
|
||||
endpoint string // for testing
|
||||
pluginName string
|
||||
pluginType string
|
||||
versions []string
|
||||
}
|
||||
|
||||
type pluginServiceV1Beta1 struct {
|
||||
@ -49,7 +49,7 @@ type pluginServiceV1Beta1 struct {
|
||||
}
|
||||
|
||||
func (s *pluginServiceV1Beta1) GetExampleInfo(ctx context.Context, rqt *v1beta1.ExampleRequest) (*v1beta1.ExampleResponse, error) {
|
||||
glog.Infof("GetExampleInfo v1beta1field: %s", rqt.V1Beta1Field)
|
||||
klog.Infof("GetExampleInfo v1beta1field: %s", rqt.V1Beta1Field)
|
||||
return &v1beta1.ExampleResponse{}, nil
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@ type pluginServiceV1Beta2 struct {
|
||||
}
|
||||
|
||||
func (s *pluginServiceV1Beta2) GetExampleInfo(ctx context.Context, rqt *v1beta2.ExampleRequest) (*v1beta2.ExampleResponse, error) {
|
||||
glog.Infof("GetExampleInfo v1beta2_field: %s", rqt.V1Beta2Field)
|
||||
klog.Infof("GetExampleInfo v1beta2_field: %s", rqt.V1Beta2Field)
|
||||
return &v1beta2.ExampleResponse{}, nil
|
||||
}
|
||||
|
||||
@ -76,49 +76,64 @@ func NewExamplePlugin() *examplePlugin {
|
||||
}
|
||||
|
||||
// NewTestExamplePlugin returns an initialized examplePlugin instance for testing
|
||||
func NewTestExamplePlugin(endpoint string) *examplePlugin {
|
||||
func NewTestExamplePlugin(pluginName string, pluginType string, endpoint string, advertisedVersions ...string) *examplePlugin {
|
||||
return &examplePlugin{
|
||||
registrationStatus: make(chan registerapi.RegistrationStatus),
|
||||
pluginName: pluginName,
|
||||
pluginType: pluginType,
|
||||
endpoint: endpoint,
|
||||
versions: advertisedVersions,
|
||||
registrationStatus: make(chan registerapi.RegistrationStatus),
|
||||
}
|
||||
}
|
||||
|
||||
// GetInfo is the RPC invoked by plugin watcher
|
||||
func (e *examplePlugin) GetInfo(ctx context.Context, req *registerapi.InfoRequest) (*registerapi.PluginInfo, error) {
|
||||
return ®isterapi.PluginInfo{
|
||||
Type: PluginType,
|
||||
Name: PluginName,
|
||||
Type: e.pluginType,
|
||||
Name: e.pluginName,
|
||||
Endpoint: e.endpoint,
|
||||
SupportedVersions: []string{"v1beta1", "v1beta2"},
|
||||
SupportedVersions: e.versions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *examplePlugin) NotifyRegistrationStatus(ctx context.Context, status *registerapi.RegistrationStatus) (*registerapi.RegistrationStatusResponse, error) {
|
||||
klog.Errorf("Registration is: %v\n", status)
|
||||
|
||||
if e.registrationStatus != nil {
|
||||
e.registrationStatus <- *status
|
||||
}
|
||||
if !status.PluginRegistered {
|
||||
glog.Errorf("Registration failed: %s\n", status.Error)
|
||||
}
|
||||
|
||||
return ®isterapi.RegistrationStatusResponse{}, nil
|
||||
}
|
||||
|
||||
// Serve starts example plugin grpc server
|
||||
func (e *examplePlugin) Serve(socketPath string) error {
|
||||
glog.Infof("starting example server at: %s\n", socketPath)
|
||||
lis, err := net.Listen("unix", socketPath)
|
||||
// Serve starts a pluginwatcher server and one or more of the plugin services
|
||||
func (e *examplePlugin) Serve(services ...string) error {
|
||||
klog.Infof("starting example server at: %s\n", e.endpoint)
|
||||
lis, err := net.Listen("unix", e.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
glog.Infof("example server started at: %s\n", socketPath)
|
||||
|
||||
klog.Infof("example server started at: %s\n", e.endpoint)
|
||||
e.grpcServer = grpc.NewServer()
|
||||
|
||||
// Registers kubelet plugin watcher api.
|
||||
registerapi.RegisterRegistrationServer(e.grpcServer, e)
|
||||
// Registers services for both v1beta1 and v1beta2 versions.
|
||||
v1beta1 := &pluginServiceV1Beta1{server: e}
|
||||
v1beta1.RegisterService()
|
||||
v1beta2 := &pluginServiceV1Beta2{server: e}
|
||||
v1beta2.RegisterService()
|
||||
|
||||
for _, service := range services {
|
||||
switch service {
|
||||
case "v1beta1":
|
||||
v1beta1 := &pluginServiceV1Beta1{server: e}
|
||||
v1beta1.RegisterService()
|
||||
break
|
||||
case "v1beta2":
|
||||
v1beta2 := &pluginServiceV1Beta2{server: e}
|
||||
v1beta2.RegisterService()
|
||||
break
|
||||
default:
|
||||
return fmt.Errorf("Unsupported service: '%s'", service)
|
||||
}
|
||||
}
|
||||
|
||||
// Starts service
|
||||
e.wg.Add(1)
|
||||
@ -126,25 +141,33 @@ func (e *examplePlugin) Serve(socketPath string) error {
|
||||
defer e.wg.Done()
|
||||
// Blocking call to accept incoming connections.
|
||||
if err := e.grpcServer.Serve(lis); err != nil {
|
||||
glog.Errorf("example server stopped serving: %v", err)
|
||||
klog.Errorf("example server stopped serving: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *examplePlugin) Stop() error {
|
||||
glog.Infof("Stopping example server\n")
|
||||
klog.Infof("Stopping example server at: %s\n", e.endpoint)
|
||||
|
||||
e.grpcServer.Stop()
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
e.wg.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-c:
|
||||
return nil
|
||||
break
|
||||
case <-time.After(time.Second):
|
||||
glog.Errorf("Timed out on waiting for stop completion")
|
||||
return fmt.Errorf("Timed out on waiting for stop completion")
|
||||
return errors.New("Timed out on waiting for stop completion")
|
||||
}
|
||||
|
||||
if err := os.Remove(e.endpoint); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,11 +1,5 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
filegroup(
|
||||
name = "go_default_library_protos",
|
||||
srcs = ["api.proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["api.pb.go"],
|
||||
|
@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by protoc-gen-gogo.
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: api.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package v1beta1 is a generated protocol buffer package.
|
||||
@ -225,24 +224,6 @@ func (m *ExampleResponse) MarshalTo(dAtA []byte) (int, error) {
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func encodeFixed64Api(dAtA []byte, offset int, v uint64) int {
|
||||
dAtA[offset] = uint8(v)
|
||||
dAtA[offset+1] = uint8(v >> 8)
|
||||
dAtA[offset+2] = uint8(v >> 16)
|
||||
dAtA[offset+3] = uint8(v >> 24)
|
||||
dAtA[offset+4] = uint8(v >> 32)
|
||||
dAtA[offset+5] = uint8(v >> 40)
|
||||
dAtA[offset+6] = uint8(v >> 48)
|
||||
dAtA[offset+7] = uint8(v >> 56)
|
||||
return offset + 8
|
||||
}
|
||||
func encodeFixed32Api(dAtA []byte, offset int, v uint32) int {
|
||||
dAtA[offset] = uint8(v)
|
||||
dAtA[offset+1] = uint8(v >> 8)
|
||||
dAtA[offset+2] = uint8(v >> 16)
|
||||
dAtA[offset+3] = uint8(v >> 24)
|
||||
return offset + 4
|
||||
}
|
||||
func encodeVarintApi(dAtA []byte, offset int, v uint64) int {
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
|
@ -1,11 +1,5 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
filegroup(
|
||||
name = "go_default_library_protos",
|
||||
srcs = ["api.proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["api.pb.go"],
|
||||
|
@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by protoc-gen-gogo.
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: api.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package v1beta2 is a generated protocol buffer package.
|
||||
@ -226,24 +225,6 @@ func (m *ExampleResponse) MarshalTo(dAtA []byte) (int, error) {
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func encodeFixed64Api(dAtA []byte, offset int, v uint64) int {
|
||||
dAtA[offset] = uint8(v)
|
||||
dAtA[offset+1] = uint8(v >> 8)
|
||||
dAtA[offset+2] = uint8(v >> 16)
|
||||
dAtA[offset+3] = uint8(v >> 24)
|
||||
dAtA[offset+4] = uint8(v >> 32)
|
||||
dAtA[offset+5] = uint8(v >> 40)
|
||||
dAtA[offset+6] = uint8(v >> 48)
|
||||
dAtA[offset+7] = uint8(v >> 56)
|
||||
return offset + 8
|
||||
}
|
||||
func encodeFixed32Api(dAtA []byte, offset int, v uint32) int {
|
||||
dAtA[offset] = uint8(v)
|
||||
dAtA[offset+1] = uint8(v >> 8)
|
||||
dAtA[offset+2] = uint8(v >> 16)
|
||||
dAtA[offset+3] = uint8(v >> 24)
|
||||
return offset + 4
|
||||
}
|
||||
func encodeVarintApi(dAtA []byte, offset int, v uint64) int {
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
|
520
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/plugin_watcher.go
generated
vendored
520
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/plugin_watcher.go
generated
vendored
@ -20,151 +20,74 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/golang/glog"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
registerapi "k8s.io/kubernetes/pkg/kubelet/apis/pluginregistration/v1alpha1"
|
||||
"k8s.io/klog"
|
||||
|
||||
registerapi "k8s.io/kubernetes/pkg/kubelet/apis/pluginregistration/v1"
|
||||
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
||||
)
|
||||
|
||||
// RegisterCallbackFn is the type of the callback function that handlers will provide
|
||||
type RegisterCallbackFn func(pluginName string, endpoint string, versions []string, socketPath string) (error, chan bool)
|
||||
|
||||
// Watcher is the plugin watcher
|
||||
type Watcher struct {
|
||||
path string
|
||||
handlers map[string]RegisterCallbackFn
|
||||
stopCh chan interface{}
|
||||
fs utilfs.Filesystem
|
||||
watcher *fsnotify.Watcher
|
||||
wg sync.WaitGroup
|
||||
mutex sync.Mutex
|
||||
path string
|
||||
deprecatedPath string
|
||||
stopCh chan interface{}
|
||||
fs utilfs.Filesystem
|
||||
fsWatcher *fsnotify.Watcher
|
||||
wg sync.WaitGroup
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// NewWatcher provides a new watcher
|
||||
func NewWatcher(sockDir string) Watcher {
|
||||
return Watcher{
|
||||
path: sockDir,
|
||||
handlers: make(map[string]RegisterCallbackFn),
|
||||
fs: &utilfs.DefaultFs{},
|
||||
// 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 {
|
||||
return &Watcher{
|
||||
path: sockDir,
|
||||
deprecatedPath: deprecatedSockDir,
|
||||
fs: &utilfs.DefaultFs{},
|
||||
|
||||
handlers: make(map[string]PluginHandler),
|
||||
plugins: make(map[string]pathInfo),
|
||||
pluginsPool: make(map[string]map[string]*sync.Mutex),
|
||||
}
|
||||
}
|
||||
|
||||
// AddHandler registers a callback to be invoked for a particular type of plugin
|
||||
func (w *Watcher) AddHandler(handlerType string, handlerCbkFn RegisterCallbackFn) {
|
||||
func (w *Watcher) AddHandler(pluginType string, handler PluginHandler) {
|
||||
w.mutex.Lock()
|
||||
defer w.mutex.Unlock()
|
||||
w.handlers[handlerType] = handlerCbkFn
|
||||
|
||||
w.handlers[pluginType] = handler
|
||||
}
|
||||
|
||||
// Creates the plugin directory, if it doesn't already exist.
|
||||
func (w *Watcher) createPluginDir() error {
|
||||
glog.V(4).Infof("Ensuring Plugin directory at %s ", w.path)
|
||||
if err := w.fs.MkdirAll(w.path, 0755); err != nil {
|
||||
return fmt.Errorf("error (re-)creating driver directory: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (w *Watcher) getHandler(pluginType string) (PluginHandler, bool) {
|
||||
w.mutex.Lock()
|
||||
defer w.mutex.Unlock()
|
||||
|
||||
// Walks through the plugin directory to discover any existing plugin sockets.
|
||||
func (w *Watcher) traversePluginDir() error {
|
||||
files, err := w.fs.ReadDir(w.path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading the plugin directory: %v", err)
|
||||
}
|
||||
for _, f := range files {
|
||||
// Currently only supports flat fs namespace under the plugin directory.
|
||||
// TODO: adds support for hierarchical fs namespace.
|
||||
if !f.IsDir() && filepath.Base(f.Name())[0] != '.' {
|
||||
go func(sockName string) {
|
||||
w.watcher.Events <- fsnotify.Event{
|
||||
Name: sockName,
|
||||
Op: fsnotify.Op(uint32(1)),
|
||||
}
|
||||
}(path.Join(w.path, f.Name()))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) init() error {
|
||||
if err := w.createPluginDir(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) registerPlugin(socketPath string) error {
|
||||
//TODO: Implement rate limiting to mitigate any DOS kind of attacks.
|
||||
glog.V(4).Infof("registerPlugin called for socketPath: %s", socketPath)
|
||||
client, conn, err := dial(socketPath)
|
||||
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()
|
||||
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)
|
||||
}
|
||||
if err := w.invokeRegistrationCallbackAtHandler(ctx, client, infoResp, socketPath); err != nil {
|
||||
return fmt.Errorf("failed to register plugin. Callback handler returned err: %v", err)
|
||||
}
|
||||
glog.V(4).Infof("Successfully registered plugin for plugin type: %s, name: %s, socket: %s", infoResp.Type, infoResp.Name, socketPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) invokeRegistrationCallbackAtHandler(ctx context.Context, client registerapi.RegistrationClient, infoResp *registerapi.PluginInfo, socketPath string) error {
|
||||
var handlerCbkFn RegisterCallbackFn
|
||||
var ok bool
|
||||
handlerCbkFn, ok = w.handlers[infoResp.Type]
|
||||
if !ok {
|
||||
if _, err := client.NotifyRegistrationStatus(ctx, ®isterapi.RegistrationStatus{
|
||||
PluginRegistered: false,
|
||||
Error: fmt.Sprintf("No handler found registered for plugin type: %s, socket: %s", infoResp.Type, socketPath),
|
||||
}); err != nil {
|
||||
glog.Errorf("Failed to send registration status at socket %s, err: %v", socketPath, err)
|
||||
}
|
||||
return fmt.Errorf("no handler found registered for plugin type: %s, socket: %s", infoResp.Type, socketPath)
|
||||
}
|
||||
|
||||
var versions []string
|
||||
for _, version := range infoResp.SupportedVersions {
|
||||
versions = append(versions, version)
|
||||
}
|
||||
// calls handler callback to verify registration request
|
||||
err, chanForAckOfNotification := handlerCbkFn(infoResp.Name, infoResp.Endpoint, versions, socketPath)
|
||||
if err != nil {
|
||||
if _, err := client.NotifyRegistrationStatus(ctx, ®isterapi.RegistrationStatus{
|
||||
PluginRegistered: false,
|
||||
Error: fmt.Sprintf("Plugin registration failed with err: %v", err),
|
||||
}); err != nil {
|
||||
glog.Errorf("Failed to send registration status at socket %s, err: %v", socketPath, err)
|
||||
}
|
||||
chanForAckOfNotification <- false
|
||||
return fmt.Errorf("plugin registration failed with err: %v", err)
|
||||
}
|
||||
|
||||
if _, err := client.NotifyRegistrationStatus(ctx, ®isterapi.RegistrationStatus{
|
||||
PluginRegistered: true,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to send registration status at socket %s, err: %v", socketPath, err)
|
||||
}
|
||||
chanForAckOfNotification <- true
|
||||
return nil
|
||||
h, ok := w.handlers[pluginType]
|
||||
return h, ok
|
||||
}
|
||||
|
||||
// Start watches for the creation of plugin sockets at the path
|
||||
func (w *Watcher) Start() error {
|
||||
glog.V(2).Infof("Plugin Watcher Start at %s", w.path)
|
||||
klog.V(2).Infof("Plugin Watcher Start at %s", w.path)
|
||||
w.stopCh = make(chan interface{})
|
||||
|
||||
// Creating the directory to be watched if it doesn't exist yet,
|
||||
@ -173,80 +96,339 @@ func (w *Watcher) Start() error {
|
||||
return err
|
||||
}
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
fsWatcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start plugin watcher, err: %v", err)
|
||||
}
|
||||
|
||||
if err := watcher.Add(w.path); err != nil {
|
||||
watcher.Close()
|
||||
return fmt.Errorf("failed to start plugin watcher, err: %v", err)
|
||||
}
|
||||
|
||||
w.watcher = watcher
|
||||
|
||||
if err := w.traversePluginDir(); err != nil {
|
||||
watcher.Close()
|
||||
return fmt.Errorf("failed to traverse plugin socket path, err: %v", err)
|
||||
return fmt.Errorf("failed to start plugin fsWatcher, err: %v", err)
|
||||
}
|
||||
w.fsWatcher = fsWatcher
|
||||
|
||||
w.wg.Add(1)
|
||||
go func(watcher *fsnotify.Watcher) {
|
||||
go func(fsWatcher *fsnotify.Watcher) {
|
||||
defer w.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
if event.Op&fsnotify.Create == fsnotify.Create {
|
||||
go func(eventName string) {
|
||||
err := w.registerPlugin(eventName)
|
||||
if err != nil {
|
||||
glog.Errorf("Plugin %s registration failed with error: %v", eventName, err)
|
||||
}
|
||||
}(event.Name)
|
||||
}
|
||||
continue
|
||||
case err := <-watcher.Errors:
|
||||
case event := <-fsWatcher.Events:
|
||||
//TODO: Handle errors by taking corrective measures
|
||||
|
||||
w.wg.Add(1)
|
||||
func() {
|
||||
defer w.wg.Done()
|
||||
|
||||
if event.Op&fsnotify.Create == fsnotify.Create {
|
||||
err := w.handleCreateEvent(event)
|
||||
if err != nil {
|
||||
klog.Errorf("error %v when handling create event: %s", err, event)
|
||||
}
|
||||
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
||||
err := w.handleDeleteEvent(event)
|
||||
if err != nil {
|
||||
klog.Errorf("error %v when handling delete event: %s", err, event)
|
||||
}
|
||||
}
|
||||
return
|
||||
}()
|
||||
continue
|
||||
case err := <-fsWatcher.Errors:
|
||||
if err != nil {
|
||||
glog.Errorf("Watcher received error: %v", err)
|
||||
klog.Errorf("fsWatcher received error: %v", err)
|
||||
}
|
||||
continue
|
||||
|
||||
case <-w.stopCh:
|
||||
watcher.Close()
|
||||
break
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}(watcher)
|
||||
}(fsWatcher)
|
||||
|
||||
// Traverse plugin dir after starting the plugin processing goroutine
|
||||
if err := w.traversePluginDir(w.path); err != nil {
|
||||
w.Stop()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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(10 * time.Second):
|
||||
case <-time.After(11 * time.Second):
|
||||
return fmt.Errorf("timeout on stopping watcher")
|
||||
}
|
||||
|
||||
w.fsWatcher.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cleanup cleans the path by removing sockets
|
||||
func (w *Watcher) Cleanup() error {
|
||||
return os.RemoveAll(w.path)
|
||||
func (w *Watcher) init() error {
|
||||
klog.V(4).Infof("Ensuring Plugin directory at %s ", w.path)
|
||||
|
||||
if err := w.fs.MkdirAll(w.path, 0755); err != nil {
|
||||
return fmt.Errorf("error (re-)creating root %s: %v", w.path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Walks through the plugin directory discover any existing plugin sockets.
|
||||
// Goroutines started here will be waited for in Stop() before cleaning up.
|
||||
// Ignore all errors except root dir not being walkable
|
||||
func (w *Watcher) traversePluginDir(dir string) error {
|
||||
return w.fs.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
if path == dir {
|
||||
return fmt.Errorf("error accessing path: %s error: %v", path, err)
|
||||
}
|
||||
|
||||
klog.Errorf("error accessing path: %s error: %v", path, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch mode := info.Mode(); {
|
||||
case mode.IsDir():
|
||||
if w.containsBlacklistedDir(path) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
if err := w.fsWatcher.Add(path); err != nil {
|
||||
return fmt.Errorf("failed to watch %s, err: %v", path, err)
|
||||
}
|
||||
case mode&os.ModeSocket != 0:
|
||||
w.wg.Add(1)
|
||||
go func() {
|
||||
defer w.wg.Done()
|
||||
w.fsWatcher.Events <- fsnotify.Event{
|
||||
Name: path,
|
||||
Op: fsnotify.Create,
|
||||
}
|
||||
}()
|
||||
default:
|
||||
klog.V(5).Infof("Ignoring file %s with mode %v", path, mode)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Handle filesystem notify event.
|
||||
// Files names:
|
||||
// - MUST NOT start with a '.'
|
||||
func (w *Watcher) handleCreateEvent(event fsnotify.Event) error {
|
||||
klog.V(6).Infof("Handling create event: %v", event)
|
||||
|
||||
if w.containsBlacklistedDir(event.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
fi, err := os.Stat(event.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stat file %s failed: %v", event.Name, err)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(fi.Name(), ".") {
|
||||
klog.V(5).Infof("Ignoring file (starts with '.'): %s", fi.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
if fi.Mode()&os.ModeSocket == 0 {
|
||||
klog.V(5).Infof("Ignoring non socket file %s", fi.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
return w.handlePluginRegistration(event.Name)
|
||||
}
|
||||
|
||||
return w.traversePluginDir(event.Name)
|
||||
}
|
||||
|
||||
func (w *Watcher) handlePluginRegistration(socketPath string) error {
|
||||
//TODO: Implement rate limiting to mitigate any DOS kind of attacks.
|
||||
client, conn, err := dial(socketPath, 10*time.Second)
|
||||
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()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
handler, ok := w.handlers[infoResp.Type]
|
||||
if !ok {
|
||||
return w.notifyPlugin(client, false, fmt.Sprintf("no handler registered for plugin type: %s at socket %s", infoResp.Type, socketPath))
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
foundInDeprecatedDir := w.foundInDeprecatedDir(socketPath)
|
||||
|
||||
// calls handler callback to verify registration request
|
||||
if err := handler.ValidatePlugin(infoResp.Name, infoResp.Endpoint, infoResp.SupportedVersions, foundInDeprecatedDir); err != nil {
|
||||
return w.notifyPlugin(client, false, fmt.Sprintf("plugin validation failed with err: %v", err))
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
if err := handler.RegisterPlugin(infoResp.Name, infoResp.Endpoint, infoResp.SupportedVersions); err != nil {
|
||||
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 {
|
||||
return fmt.Errorf("failed to send registration status at socket %s, err: %v", socketPath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) handleDeleteEvent(event fsnotify.Event) error {
|
||||
klog.V(6).Infof("Handling delete event: %v", event)
|
||||
|
||||
plugin, ok := w.getPlugin(event.Name)
|
||||
if !ok {
|
||||
klog.V(5).Infof("could not find plugin for deleted file %s", event.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
klog.V(2).Infof("A newer plugin watcher has been registered for plugin %v, dropping DeRegister call", plugin)
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
klog.V(2).Infof("DeRegistering plugin %v at path %s", plugin, event.Name)
|
||||
w.deRegisterPlugin(event.Name, plugin.pluginType, plugin.pluginName)
|
||||
h.DeRegisterPlugin(plugin.pluginName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) registerPlugin(socketPath, pluginType, pluginName string) {
|
||||
w.mutex.Lock()
|
||||
defer w.mutex.Unlock()
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
delete(w.plugins, path)
|
||||
break
|
||||
}
|
||||
|
||||
w.plugins[socketPath] = pathInfo{
|
||||
pluginType: pluginType,
|
||||
pluginName: pluginName,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) deRegisterPlugin(socketPath, pluginType, pluginName string) {
|
||||
w.mutex.Lock()
|
||||
defer w.mutex.Unlock()
|
||||
|
||||
delete(w.plugins, socketPath)
|
||||
delete(w.pluginsPool[pluginType], pluginName)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if _, ok := w.pluginsPool[pluginType][pluginName]; !ok {
|
||||
w.pluginsPool[pluginType][pluginName] = &sync.Mutex{}
|
||||
}
|
||||
|
||||
return w.pluginsPool[pluginType][pluginName]
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Dial establishes the gRPC communication with the picked up plugin socket. https://godoc.org/google.golang.org/grpc#Dial
|
||||
func dial(unixSocketPath string) (registerapi.RegistrationClient, *grpc.ClientConn, error) {
|
||||
c, err := grpc.Dial(unixSocketPath, grpc.WithInsecure(), grpc.WithBlock(),
|
||||
grpc.WithTimeout(10*time.Second),
|
||||
func dial(unixSocketPath string, timeout time.Duration) (registerapi.RegistrationClient, *grpc.ClientConn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
c, err := grpc.DialContext(ctx, unixSocketPath, grpc.WithInsecure(), grpc.WithBlock(),
|
||||
grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
|
||||
return net.DialTimeout("unix", addr, timeout)
|
||||
}),
|
||||
@ -258,3 +440,27 @@ func dial(unixSocketPath string) (registerapi.RegistrationClient, *grpc.ClientCo
|
||||
|
||||
return registerapi.NewRegistrationClient(c), c, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
614
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/plugin_watcher_test.go
generated
vendored
614
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/plugin_watcher_test.go
generated
vendored
@ -17,204 +17,464 @@ limitations under the License.
|
||||
package pluginwatcher
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
registerapi "k8s.io/kubernetes/pkg/kubelet/apis/pluginregistration/v1alpha1"
|
||||
v1beta1 "k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/example_plugin_apis/v1beta1"
|
||||
v1beta2 "k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/example_plugin_apis/v1beta2"
|
||||
"k8s.io/klog"
|
||||
registerapi "k8s.io/kubernetes/pkg/kubelet/apis/pluginregistration/v1"
|
||||
)
|
||||
|
||||
func TestExamplePlugin(t *testing.T) {
|
||||
socketDir, err := ioutil.TempDir("", "plugin_test")
|
||||
require.NoError(t, err)
|
||||
socketPath := socketDir + "/plugin.sock"
|
||||
w := NewWatcher(socketDir)
|
||||
var (
|
||||
socketDir string
|
||||
deprecatedSocketDir string
|
||||
|
||||
testCases := []struct {
|
||||
description string
|
||||
expectedEndpoint string
|
||||
returnErr error
|
||||
}{
|
||||
{
|
||||
description: "Successfully register plugin through inotify",
|
||||
expectedEndpoint: "",
|
||||
returnErr: nil,
|
||||
},
|
||||
{
|
||||
description: "Successfully register plugin through inotify and got expected optional endpoint",
|
||||
expectedEndpoint: "dummyEndpoint",
|
||||
returnErr: nil,
|
||||
},
|
||||
{
|
||||
description: "Fails registration because endpoint is expected to be non-empty",
|
||||
expectedEndpoint: "dummyEndpoint",
|
||||
returnErr: fmt.Errorf("empty endpoint received"),
|
||||
},
|
||||
{
|
||||
description: "Successfully register plugin through inotify after plugin restarts",
|
||||
expectedEndpoint: "",
|
||||
returnErr: nil,
|
||||
},
|
||||
{
|
||||
description: "Fails registration with conflicting plugin name",
|
||||
expectedEndpoint: "",
|
||||
returnErr: fmt.Errorf("conflicting plugin name"),
|
||||
},
|
||||
{
|
||||
description: "Successfully register plugin during initial traverse after plugin watcher restarts",
|
||||
expectedEndpoint: "",
|
||||
returnErr: nil,
|
||||
},
|
||||
{
|
||||
description: "Fails registration with conflicting plugin name during initial traverse after plugin watcher restarts",
|
||||
expectedEndpoint: "",
|
||||
returnErr: fmt.Errorf("conflicting plugin name"),
|
||||
},
|
||||
supportedVersions = []string{"v1beta1", "v1beta2"}
|
||||
)
|
||||
|
||||
func init() {
|
||||
var logLevel string
|
||||
|
||||
klog.InitFlags(flag.CommandLine)
|
||||
flag.Set("alsologtostderr", fmt.Sprintf("%t", true))
|
||||
flag.StringVar(&logLevel, "logLevel", "6", "test")
|
||||
flag.Lookup("v").Value.Set(logLevel)
|
||||
|
||||
d, err := ioutil.TempDir("", "plugin_test")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Could not create a temp directory: %s", d))
|
||||
}
|
||||
|
||||
callbackCount := struct {
|
||||
mutex sync.Mutex
|
||||
count int32
|
||||
}{}
|
||||
w.AddHandler(PluginType, func(name string, endpoint string, versions []string, sockPath string) (error, chan bool) {
|
||||
callbackCount.mutex.Lock()
|
||||
localCount := callbackCount.count
|
||||
callbackCount.count = callbackCount.count + 1
|
||||
callbackCount.mutex.Unlock()
|
||||
|
||||
require.True(t, localCount <= int32((len(testCases)-1)))
|
||||
require.Equal(t, PluginName, name, "Plugin name mismatched!!")
|
||||
retError := testCases[localCount].returnErr
|
||||
if retError == nil || retError.Error() != "empty endpoint received" {
|
||||
require.Equal(t, testCases[localCount].expectedEndpoint, endpoint, "Unexpected endpoint")
|
||||
} else {
|
||||
require.NotEqual(t, testCases[localCount].expectedEndpoint, endpoint, "Unexpected endpoint")
|
||||
}
|
||||
|
||||
require.Equal(t, []string{"v1beta1", "v1beta2"}, versions, "Plugin version mismatched!!")
|
||||
// Verifies the grpcServer is ready to serve services.
|
||||
_, conn, err := dial(sockPath)
|
||||
require.Nil(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
// The plugin handler should be able to use any listed service API version.
|
||||
v1beta1Client := v1beta1.NewExampleClient(conn)
|
||||
v1beta2Client := v1beta2.NewExampleClient(conn)
|
||||
|
||||
// Tests v1beta1 GetExampleInfo
|
||||
_, err = v1beta1Client.GetExampleInfo(context.Background(), &v1beta1.ExampleRequest{})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Tests v1beta1 GetExampleInfo
|
||||
_, err = v1beta2Client.GetExampleInfo(context.Background(), &v1beta2.ExampleRequest{})
|
||||
//atomic.AddInt32(&callbackCount, 1)
|
||||
chanForAckOfNotification := make(chan bool)
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-chanForAckOfNotification:
|
||||
close(chanForAckOfNotification)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("Timed out while waiting for notification ack")
|
||||
}
|
||||
}()
|
||||
return retError, chanForAckOfNotification
|
||||
})
|
||||
require.NoError(t, w.Start())
|
||||
|
||||
p := NewTestExamplePlugin("")
|
||||
require.NoError(t, p.Serve(socketPath))
|
||||
require.True(t, waitForPluginRegistrationStatus(t, p.registrationStatus))
|
||||
|
||||
require.NoError(t, p.Stop())
|
||||
|
||||
p = NewTestExamplePlugin("dummyEndpoint")
|
||||
require.NoError(t, p.Serve(socketPath))
|
||||
require.True(t, waitForPluginRegistrationStatus(t, p.registrationStatus))
|
||||
|
||||
require.NoError(t, p.Stop())
|
||||
|
||||
p = NewTestExamplePlugin("")
|
||||
require.NoError(t, p.Serve(socketPath))
|
||||
require.False(t, waitForPluginRegistrationStatus(t, p.registrationStatus))
|
||||
|
||||
// Trying to start a plugin service at the same socket path should fail
|
||||
// with "bind: address already in use"
|
||||
require.NotNil(t, p.Serve(socketPath))
|
||||
|
||||
// grpcServer.Stop() will remove the socket and starting plugin service
|
||||
// at the same path again should succeeds and trigger another callback.
|
||||
require.NoError(t, p.Stop())
|
||||
p = NewTestExamplePlugin("")
|
||||
go func() {
|
||||
require.Nil(t, p.Serve(socketPath))
|
||||
}()
|
||||
require.True(t, waitForPluginRegistrationStatus(t, p.registrationStatus))
|
||||
|
||||
// Starting another plugin with the same name got verification error.
|
||||
p2 := NewTestExamplePlugin("")
|
||||
socketPath2 := socketDir + "/plugin2.sock"
|
||||
go func() {
|
||||
require.NoError(t, p2.Serve(socketPath2))
|
||||
}()
|
||||
require.False(t, waitForPluginRegistrationStatus(t, p2.registrationStatus))
|
||||
|
||||
// Restarts plugin watcher should traverse the socket directory and issues a
|
||||
// callback for every existing socket.
|
||||
require.NoError(t, w.Stop())
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- w.Start()
|
||||
}()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
var pStatus string
|
||||
var p2Status string
|
||||
go func() {
|
||||
pStatus = strconv.FormatBool(waitForPluginRegistrationStatus(t, p.registrationStatus))
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
p2Status = strconv.FormatBool(waitForPluginRegistrationStatus(t, p2.registrationStatus))
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
expectedSet := sets.NewString()
|
||||
expectedSet.Insert("true", "false")
|
||||
actualSet := sets.NewString()
|
||||
actualSet.Insert(pStatus, p2Status)
|
||||
|
||||
require.Equal(t, expectedSet, actualSet)
|
||||
|
||||
select {
|
||||
case err = <-errCh:
|
||||
require.NoError(t, err)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("Timed out while waiting for watcher start")
|
||||
|
||||
d2, err := ioutil.TempDir("", "deprecated_plugin_test")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Could not create a temp directory: %s", d))
|
||||
}
|
||||
|
||||
require.NoError(t, w.Stop())
|
||||
err = w.Cleanup()
|
||||
require.NoError(t, err)
|
||||
socketDir = d
|
||||
deprecatedSocketDir = d2
|
||||
}
|
||||
|
||||
func waitForPluginRegistrationStatus(t *testing.T, statusCh chan registerapi.RegistrationStatus) bool {
|
||||
func cleanup(t *testing.T) {
|
||||
require.NoError(t, os.RemoveAll(socketDir))
|
||||
require.NoError(t, os.RemoveAll(deprecatedSocketDir))
|
||||
os.MkdirAll(socketDir, 0755)
|
||||
os.MkdirAll(deprecatedSocketDir, 0755)
|
||||
}
|
||||
|
||||
func TestPluginRegistration(t *testing.T) {
|
||||
defer cleanup(t)
|
||||
|
||||
hdlr := NewExampleHandler(supportedVersions, false /* permitDeprecatedDir */)
|
||||
w := newWatcherWithHandler(t, hdlr, false /* testDeprecatedDir */)
|
||||
defer func() { require.NoError(t, w.Stop()) }()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
socketPath := fmt.Sprintf("%s/plugin-%d.sock", socketDir, i)
|
||||
pluginName := fmt.Sprintf("example-plugin-%d", i)
|
||||
|
||||
hdlr.AddPluginName(pluginName)
|
||||
|
||||
p := NewTestExamplePlugin(pluginName, registerapi.DevicePlugin, socketPath, supportedVersions...)
|
||||
require.NoError(t, p.Serve("v1beta1", "v1beta2"))
|
||||
|
||||
require.True(t, waitForEvent(t, exampleEventValidate, hdlr.EventChan(p.pluginName)))
|
||||
require.True(t, waitForEvent(t, exampleEventRegister, hdlr.EventChan(p.pluginName)))
|
||||
|
||||
require.True(t, waitForPluginRegistrationStatus(t, p.registrationStatus))
|
||||
|
||||
require.NoError(t, p.Stop())
|
||||
require.True(t, waitForEvent(t, exampleEventDeRegister, hdlr.EventChan(p.pluginName)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginRegistrationDeprecated(t *testing.T) {
|
||||
defer cleanup(t)
|
||||
|
||||
hdlr := NewExampleHandler(supportedVersions, true /* permitDeprecatedDir */)
|
||||
w := newWatcherWithHandler(t, hdlr, true /* testDeprecatedDir */)
|
||||
defer func() { require.NoError(t, w.Stop()) }()
|
||||
|
||||
// Test plugins in deprecated dir
|
||||
for i := 0; i < 10; i++ {
|
||||
endpoint := fmt.Sprintf("%s/dep-plugin-%d.sock", deprecatedSocketDir, i)
|
||||
pluginName := fmt.Sprintf("dep-example-plugin-%d", i)
|
||||
|
||||
hdlr.AddPluginName(pluginName)
|
||||
|
||||
p := NewTestExamplePlugin(pluginName, registerapi.DevicePlugin, endpoint, supportedVersions...)
|
||||
require.NoError(t, p.Serve("v1beta1", "v1beta2"))
|
||||
|
||||
require.True(t, waitForEvent(t, exampleEventValidate, hdlr.EventChan(p.pluginName)))
|
||||
require.True(t, waitForEvent(t, exampleEventRegister, hdlr.EventChan(p.pluginName)))
|
||||
|
||||
require.True(t, waitForPluginRegistrationStatus(t, p.registrationStatus))
|
||||
|
||||
require.NoError(t, p.Stop())
|
||||
require.True(t, waitForEvent(t, exampleEventDeRegister, hdlr.EventChan(p.pluginName)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginReRegistration(t *testing.T) {
|
||||
defer cleanup(t)
|
||||
|
||||
pluginName := fmt.Sprintf("example-plugin")
|
||||
hdlr := NewExampleHandler(supportedVersions, false /* permitDeprecatedDir */)
|
||||
|
||||
w := newWatcherWithHandler(t, hdlr, false /* testDeprecatedDir */)
|
||||
defer func() { require.NoError(t, w.Stop()) }()
|
||||
|
||||
plugins := make([]*examplePlugin, 10)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
socketPath := fmt.Sprintf("%s/plugin-%d.sock", socketDir, i)
|
||||
hdlr.AddPluginName(pluginName)
|
||||
|
||||
p := NewTestExamplePlugin(pluginName, registerapi.DevicePlugin, socketPath, supportedVersions...)
|
||||
require.NoError(t, p.Serve("v1beta1", "v1beta2"))
|
||||
|
||||
require.True(t, waitForEvent(t, exampleEventValidate, hdlr.EventChan(p.pluginName)))
|
||||
require.True(t, waitForEvent(t, exampleEventRegister, hdlr.EventChan(p.pluginName)))
|
||||
|
||||
require.True(t, waitForPluginRegistrationStatus(t, p.registrationStatus))
|
||||
|
||||
plugins[i] = p
|
||||
}
|
||||
|
||||
plugins[len(plugins)-1].Stop()
|
||||
require.True(t, waitForEvent(t, exampleEventDeRegister, hdlr.EventChan(pluginName)))
|
||||
|
||||
close(hdlr.EventChan(pluginName))
|
||||
for i := 0; i < len(plugins)-1; i++ {
|
||||
plugins[i].Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginRegistrationAtKubeletStart(t *testing.T) {
|
||||
defer cleanup(t)
|
||||
|
||||
hdlr := NewExampleHandler(supportedVersions, false /* permitDeprecatedDir */)
|
||||
plugins := make([]*examplePlugin, 10)
|
||||
|
||||
for i := 0; i < len(plugins); i++ {
|
||||
socketPath := fmt.Sprintf("%s/plugin-%d.sock", socketDir, i)
|
||||
pluginName := fmt.Sprintf("example-plugin-%d", i)
|
||||
hdlr.AddPluginName(pluginName)
|
||||
|
||||
p := NewTestExamplePlugin(pluginName, registerapi.DevicePlugin, socketPath, supportedVersions...)
|
||||
require.NoError(t, p.Serve("v1beta1", "v1beta2"))
|
||||
defer func(p *examplePlugin) { require.NoError(t, p.Stop()) }(p)
|
||||
|
||||
plugins[i] = p
|
||||
}
|
||||
|
||||
w := newWatcherWithHandler(t, hdlr, false /* testDeprecatedDir */)
|
||||
defer func() { require.NoError(t, w.Stop()) }()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < len(plugins); i++ {
|
||||
wg.Add(1)
|
||||
go func(p *examplePlugin) {
|
||||
defer wg.Done()
|
||||
|
||||
require.True(t, waitForEvent(t, exampleEventValidate, hdlr.EventChan(p.pluginName)))
|
||||
require.True(t, waitForEvent(t, exampleEventRegister, hdlr.EventChan(p.pluginName)))
|
||||
|
||||
require.True(t, waitForPluginRegistrationStatus(t, p.registrationStatus))
|
||||
}(plugins[i])
|
||||
}
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case status := <-statusCh:
|
||||
case <-c:
|
||||
return
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatalf("Timeout while waiting for the plugin registration status")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginRegistrationFailureWithUnsupportedVersion(t *testing.T) {
|
||||
defer cleanup(t)
|
||||
|
||||
pluginName := fmt.Sprintf("example-plugin")
|
||||
socketPath := socketDir + "/plugin.sock"
|
||||
|
||||
hdlr := NewExampleHandler(supportedVersions, false /* permitDeprecatedDir */)
|
||||
hdlr.AddPluginName(pluginName)
|
||||
|
||||
w := newWatcherWithHandler(t, hdlr, false /* testDeprecatedDir */)
|
||||
defer func() { require.NoError(t, w.Stop()) }()
|
||||
|
||||
// Advertise v1beta3 but don't serve anything else than the plugin service
|
||||
p := NewTestExamplePlugin(pluginName, registerapi.DevicePlugin, socketPath, "v1beta3")
|
||||
require.NoError(t, p.Serve())
|
||||
defer func() { require.NoError(t, p.Stop()) }()
|
||||
|
||||
require.True(t, waitForEvent(t, exampleEventValidate, hdlr.EventChan(p.pluginName)))
|
||||
require.False(t, waitForPluginRegistrationStatus(t, p.registrationStatus))
|
||||
}
|
||||
|
||||
func TestPlugiRegistrationFailureWithUnsupportedVersionAtKubeletStart(t *testing.T) {
|
||||
defer cleanup(t)
|
||||
|
||||
pluginName := fmt.Sprintf("example-plugin")
|
||||
socketPath := socketDir + "/plugin.sock"
|
||||
|
||||
// Advertise v1beta3 but don't serve anything else than the plugin service
|
||||
p := NewTestExamplePlugin(pluginName, registerapi.DevicePlugin, socketPath, "v1beta3")
|
||||
require.NoError(t, p.Serve())
|
||||
defer func() { require.NoError(t, p.Stop()) }()
|
||||
|
||||
hdlr := NewExampleHandler(supportedVersions, false /* permitDeprecatedDir */)
|
||||
hdlr.AddPluginName(pluginName)
|
||||
|
||||
w := newWatcherWithHandler(t, hdlr, false /* testDeprecatedDir */)
|
||||
defer func() { require.NoError(t, w.Stop()) }()
|
||||
|
||||
require.True(t, waitForEvent(t, exampleEventValidate, hdlr.EventChan(p.pluginName)))
|
||||
require.False(t, waitForPluginRegistrationStatus(t, p.registrationStatus))
|
||||
}
|
||||
|
||||
func waitForPluginRegistrationStatus(t *testing.T, statusChan chan registerapi.RegistrationStatus) bool {
|
||||
select {
|
||||
case status := <-statusChan:
|
||||
return status.PluginRegistered
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatalf("Timed out while waiting for registration status")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func waitForEvent(t *testing.T, expected examplePluginEvent, eventChan chan examplePluginEvent) bool {
|
||||
select {
|
||||
case event := <-eventChan:
|
||||
return event == expected
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatalf("Timed out while waiting for registration status %v", expected)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func newWatcherWithHandler(t *testing.T, hdlr PluginHandler, testDeprecatedDir bool) *Watcher {
|
||||
depSocketDir := ""
|
||||
if testDeprecatedDir {
|
||||
depSocketDir = deprecatedSocketDir
|
||||
}
|
||||
w := NewWatcher(socketDir, depSocketDir)
|
||||
|
||||
w.AddHandler(registerapi.DevicePlugin, hdlr)
|
||||
require.NoError(t, w.Start())
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
func TestFoundInDeprecatedDir(t *testing.T) {
|
||||
testCases := []struct {
|
||||
sockDir string
|
||||
deprecatedSockDir string
|
||||
socketPath string
|
||||
expectFoundInDeprecatedDir bool
|
||||
}{
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
socketPath: "/var/lib/kubelet/plugins_registry/mydriver.foo/csi.sock",
|
||||
expectFoundInDeprecatedDir: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
socketPath: "/var/lib/kubelet/plugins/mydriver.foo/csi.sock",
|
||||
expectFoundInDeprecatedDir: true,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
socketPath: "/var/lib/kubelet/plugins_registry",
|
||||
expectFoundInDeprecatedDir: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
socketPath: "/var/lib/kubelet/plugins",
|
||||
expectFoundInDeprecatedDir: true,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
socketPath: "/var/lib/kubelet/plugins/kubernetes.io",
|
||||
expectFoundInDeprecatedDir: true,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
socketPath: "/var/lib/kubelet/plugins/my.driver.com",
|
||||
expectFoundInDeprecatedDir: true,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
socketPath: "/var/lib/kubelet/plugins_registry",
|
||||
expectFoundInDeprecatedDir: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
socketPath: "/var/lib/kubelet/plugins_registry/kubernetes.io",
|
||||
expectFoundInDeprecatedDir: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
socketPath: "/var/lib/kubelet/plugins_registry/my.driver.com",
|
||||
expectFoundInDeprecatedDir: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
// Arrange & Act
|
||||
watcher := NewWatcher(tc.sockDir, tc.deprecatedSockDir)
|
||||
|
||||
actualFoundInDeprecatedDir := watcher.foundInDeprecatedDir(tc.socketPath)
|
||||
|
||||
// Assert
|
||||
if tc.expectFoundInDeprecatedDir != actualFoundInDeprecatedDir {
|
||||
t.Fatalf("expecting actualFoundInDeprecatedDir=%v, but got %v for testcase: %#v", tc.expectFoundInDeprecatedDir, actualFoundInDeprecatedDir, tc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsBlacklistedDir(t *testing.T) {
|
||||
testCases := []struct {
|
||||
sockDir string
|
||||
deprecatedSockDir string
|
||||
path string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins_registry/mydriver.foo/csi.sock",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins/mydriver.foo/csi.sock",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins_registry",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins/kubernetes.io",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins/kubernetes.io/csi.sock",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins/kubernetes.io/my.plugin/csi.sock",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins/kubernetes.io/",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins/my.driver.com",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins_registry",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins_registry/kubernetes.io",
|
||||
expected: false, // New (non-deprecated dir) has no blacklist
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins_registry/my.driver.com",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins/my-kubernetes.io-plugin",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins/my-kubernetes.io-plugin/csi.sock",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins/kubernetes.io-plugin",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins/kubernetes.io-plugin/csi.sock",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
sockDir: "/var/lib/kubelet/plugins_registry",
|
||||
deprecatedSockDir: "/var/lib/kubelet/plugins",
|
||||
path: "/var/lib/kubelet/plugins/kubernetes.io-plugin/",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
// Arrange & Act
|
||||
watcher := NewWatcher(tc.sockDir, tc.deprecatedSockDir)
|
||||
|
||||
actual := watcher.containsBlacklistedDir(tc.path)
|
||||
|
||||
// Assert
|
||||
if tc.expected != actual {
|
||||
t.Fatalf("expecting %v but got %v for testcase: %#v", tc.expected, actual, tc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
59
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/types.go
generated
vendored
Normal file
59
vendor/k8s.io/kubernetes/pkg/kubelet/util/pluginwatcher/types.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
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
|
||||
|
||||
// PluginHandler is an interface a client of the pluginwatcher API needs to implement in
|
||||
// order to consume plugins
|
||||
// The PluginHandler follows the simple following state machine:
|
||||
//
|
||||
// +--------------------------------------+
|
||||
// | ReRegistration |
|
||||
// | Socket created with same plugin name |
|
||||
// | |
|
||||
// | |
|
||||
// Socket Created v + Socket Deleted
|
||||
// +------------------> Validate +---------------------------> Register +------------------> DeRegister
|
||||
// + + +
|
||||
// | | |
|
||||
// | Error | Error |
|
||||
// | | |
|
||||
// v v v
|
||||
// Out Out Out
|
||||
//
|
||||
// The pluginwatcher module follows strictly and sequentially this state machine for each *plugin name*.
|
||||
// e.g: If you are Registering a plugin foo, you cannot get a DeRegister call for plugin foo
|
||||
// until the Register("foo") call returns. Nor will you get a Validate("foo", "Different endpoint", ...)
|
||||
// call until the Register("foo") call returns.
|
||||
//
|
||||
// ReRegistration: Socket created with same plugin name, usually for a plugin update
|
||||
// e.g: plugin with name foo registers at foo.com/foo-1.9.7 later a plugin with name foo
|
||||
// registers at foo.com/foo-1.9.9
|
||||
//
|
||||
// DeRegistration: When ReRegistration happens only the deletion of the new socket will trigger a DeRegister call
|
||||
|
||||
type PluginHandler interface {
|
||||
// Validate returns an error if the information provided by
|
||||
// the potential plugin is erroneous (unsupported version, ...)
|
||||
ValidatePlugin(pluginName string, endpoint string, versions []string, foundInDeprecatedDir bool) error
|
||||
// RegisterPlugin is called so that the plugin can be register by any
|
||||
// plugin consumer
|
||||
// Error encountered here can still be Notified to the plugin.
|
||||
RegisterPlugin(pluginName, endpoint string, versions []string) error
|
||||
// DeRegister is called once the pluginwatcher observes that the socket has
|
||||
// been deleted.
|
||||
DeRegisterPlugin(pluginName string)
|
||||
}
|
Reference in New Issue
Block a user