Revert "Revert "[WIP] southbound networking hooks in kubelet""
This commit is contained in:
140
pkg/kubelet/network/exec/exec.go
Normal file
140
pkg/kubelet/network/exec/exec.go
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 exec scans and loads networking plugins that are installed
|
||||
// under /usr/libexec/kubernetes/kubelet-plugins/net/exec/
|
||||
// The layout convention for a plugin is:
|
||||
// plugin-name/ (plugins have to be directories first)
|
||||
// plugin-name/plugin-name (executable that will be called out, see Vendoring Note for more nuances)
|
||||
// plugin-name/<other-files>
|
||||
// where, 'executable' has the following requirements:
|
||||
// - should have exec permissions
|
||||
// - should give non-zero exit code on failure, and zero on sucess
|
||||
// - the arguments will be <action> <pod_namespace> <pod_name> <docker_id_of_infra_container>
|
||||
// whereupon, <action> will be one of:
|
||||
// - init, called when the kubelet loads the plugin
|
||||
// - setup, called after the infra container of a pod is
|
||||
// created, but before other containers of the pod are created
|
||||
// - teardown, called before the pod infra container is killed
|
||||
// As the executables are called, the file-descriptors stdin, stdout, stderr
|
||||
// remain open. The combined output of stdout/stderr is captured and logged.
|
||||
//
|
||||
// Note: If the pod infra container self-terminates (e.g. crashes or is killed),
|
||||
// the entire pod lifecycle will be restarted, but teardown will not be called.
|
||||
//
|
||||
// Vendoring Note:
|
||||
// Plugin Names can be vendored also. Use '~' as the escaped name for plugin directories.
|
||||
// And expect command line argument to call vendored plugins as 'vendor/pluginName'
|
||||
// e.g. pluginName = mysdn
|
||||
// vendorname = mycompany
|
||||
// then, plugin layout should be
|
||||
// mycompany~mysdn/
|
||||
// mycompany~mysdn/mysdn (this becomes the executable)
|
||||
// mycompany~mysdn/<other-files>
|
||||
// and, call the kubelet with '--network-plugin=mycompany/mysdn'
|
||||
package exec
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/network"
|
||||
utilexec "github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
type execNetworkPlugin struct {
|
||||
execName string
|
||||
execPath string
|
||||
host network.Host
|
||||
}
|
||||
|
||||
const (
|
||||
initCmd = "init"
|
||||
setUpCmd = "setup"
|
||||
tearDownCmd = "teardown"
|
||||
execDir = "/usr/libexec/kubernetes/kubelet-plugins/net/exec/"
|
||||
X_OK = 0x1
|
||||
)
|
||||
|
||||
func ProbeNetworkPlugins() []network.NetworkPlugin {
|
||||
return probeNetworkPluginsWithExecDir(execDir)
|
||||
}
|
||||
|
||||
func probeNetworkPluginsWithExecDir(pluginDir string) []network.NetworkPlugin {
|
||||
execPlugins := []network.NetworkPlugin{}
|
||||
|
||||
files, _ := ioutil.ReadDir(pluginDir)
|
||||
for _, f := range files {
|
||||
// only directories are counted as plugins
|
||||
// and pluginDir/dirname/dirname should be an executable
|
||||
// unless dirname contains '~' for escaping namespace
|
||||
// e.g. dirname = vendor~ipvlan
|
||||
// then, executable will be pluginDir/dirname/ipvlan
|
||||
if f.IsDir() {
|
||||
execPath := path.Join(pluginDir, f.Name())
|
||||
execPlugins = append(execPlugins, &execNetworkPlugin{execName: network.UnescapePluginName(f.Name()), execPath: execPath})
|
||||
}
|
||||
}
|
||||
return execPlugins
|
||||
}
|
||||
|
||||
func (plugin *execNetworkPlugin) Init(host network.Host) error {
|
||||
err := plugin.validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plugin.host = host
|
||||
// call the init script
|
||||
out, err := utilexec.New().Command(plugin.getExecutable(), initCmd).CombinedOutput()
|
||||
glog.V(5).Infof("Init 'exec' network plugin output: %s, %v", string(out), err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (plugin *execNetworkPlugin) getExecutable() string {
|
||||
parts := strings.Split(plugin.execName, "/")
|
||||
execName := parts[len(parts)-1]
|
||||
return path.Join(plugin.execPath, execName)
|
||||
}
|
||||
|
||||
func (plugin *execNetworkPlugin) Name() string {
|
||||
return plugin.execName
|
||||
}
|
||||
|
||||
func (plugin *execNetworkPlugin) validate() error {
|
||||
if syscall.Access(plugin.getExecutable(), X_OK) != nil {
|
||||
errStr := fmt.Sprintf("Invalid exec plugin. Executable '%s' does not have correct permissions.", plugin.execName)
|
||||
return errors.New(errStr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *execNetworkPlugin) SetUpPod(namespace string, name string, id dockertools.DockerID) error {
|
||||
out, err := utilexec.New().Command(plugin.getExecutable(), setUpCmd, namespace, name, string(id)).CombinedOutput()
|
||||
glog.V(5).Infof("SetUpPod 'exec' network plugin output: %s, %v", string(out), err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (plugin *execNetworkPlugin) TearDownPod(namespace string, name string, id dockertools.DockerID) error {
|
||||
out, err := utilexec.New().Command(plugin.getExecutable(), tearDownCmd, namespace, name, string(id)).CombinedOutput()
|
||||
glog.V(5).Infof("TearDownPod 'exec' network plugin output: %s, %v", string(out), err)
|
||||
return err
|
||||
}
|
179
pkg/kubelet/network/exec/exec_test.go
Normal file
179
pkg/kubelet/network/exec/exec_test.go
Normal file
@@ -0,0 +1,179 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 exec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/network"
|
||||
)
|
||||
|
||||
// The temp dir where test plugins will be stored.
|
||||
const testPluginPath = "/tmp/fake/plugins/net"
|
||||
|
||||
func installPluginUnderTest(t *testing.T, vendorName string, plugName string) {
|
||||
vendoredName := plugName
|
||||
if vendorName != "" {
|
||||
vendoredName = fmt.Sprintf("%s~%s", vendorName, plugName)
|
||||
}
|
||||
pluginDir := path.Join(testPluginPath, vendoredName)
|
||||
err := os.MkdirAll(pluginDir, 0777)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create plugin: %v", err)
|
||||
}
|
||||
pluginExec := path.Join(pluginDir, plugName)
|
||||
f, err := os.Create(pluginExec)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to install plugin")
|
||||
}
|
||||
err = f.Chmod(0777)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to set exec perms on plugin")
|
||||
}
|
||||
writeStr := fmt.Sprintf("#!/bin/bash\necho -n $@ &> %s", path.Join(pluginDir, plugName+".out"))
|
||||
_, err = f.WriteString(writeStr)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to write plugin exec")
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
func tearDownPlugin(plugName string) {
|
||||
err := os.RemoveAll(testPluginPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error in cleaning up test: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectPlugin(t *testing.T) {
|
||||
// install some random plugin under testPluginPath
|
||||
pluginName := fmt.Sprintf("test%d", rand.Intn(1000))
|
||||
defer tearDownPlugin(pluginName)
|
||||
installPluginUnderTest(t, "", pluginName)
|
||||
|
||||
plug, err := network.InitNetworkPlugin(probeNetworkPluginsWithExecDir(testPluginPath), pluginName, network.NewFakeHost(nil))
|
||||
if err != nil {
|
||||
t.Errorf("Failed to select the desired plugin: %v", err)
|
||||
}
|
||||
if plug.Name() != pluginName {
|
||||
t.Errorf("Wrong plugin selected, chose %s, got %s\n", pluginName, plug.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectVendoredPlugin(t *testing.T) {
|
||||
// install some random plugin under testPluginPath
|
||||
pluginName := fmt.Sprintf("test%d", rand.Intn(1000))
|
||||
defer tearDownPlugin(pluginName)
|
||||
vendor := "mycompany"
|
||||
installPluginUnderTest(t, vendor, pluginName)
|
||||
|
||||
vendoredPluginName := fmt.Sprintf("%s/%s", vendor, pluginName)
|
||||
plug, err := network.InitNetworkPlugin(probeNetworkPluginsWithExecDir(testPluginPath), vendoredPluginName, network.NewFakeHost(nil))
|
||||
if err != nil {
|
||||
t.Errorf("Failed to select the desired plugin: %v", err)
|
||||
}
|
||||
if plug.Name() != vendoredPluginName {
|
||||
t.Errorf("Wrong plugin selected, chose %s, got %s\n", vendoredPluginName, plug.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectWrongPlugin(t *testing.T) {
|
||||
// install some random plugin under testPluginPath
|
||||
pluginName := fmt.Sprintf("test%d", rand.Intn(1000))
|
||||
defer tearDownPlugin(pluginName)
|
||||
installPluginUnderTest(t, "", pluginName)
|
||||
|
||||
wrongPlugin := "abcd"
|
||||
plug, err := network.InitNetworkPlugin(probeNetworkPluginsWithExecDir(testPluginPath), wrongPlugin, network.NewFakeHost(nil))
|
||||
if plug != nil || err == nil {
|
||||
t.Errorf("Expected to see an error. Wrong plugin selected.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginValidation(t *testing.T) {
|
||||
pluginName := fmt.Sprintf("test%d", rand.Intn(1000))
|
||||
defer tearDownPlugin(pluginName)
|
||||
installPluginUnderTest(t, "", pluginName)
|
||||
|
||||
// modify the perms of the pluginExecutable
|
||||
f, err := os.Open(path.Join(testPluginPath, pluginName, pluginName))
|
||||
if err != nil {
|
||||
t.Errorf("Nil value expected.")
|
||||
}
|
||||
err = f.Chmod(0444)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to set perms on plugin exec")
|
||||
}
|
||||
f.Close()
|
||||
|
||||
_, err = network.InitNetworkPlugin(probeNetworkPluginsWithExecDir(testPluginPath), pluginName, network.NewFakeHost(nil))
|
||||
if err == nil {
|
||||
// we expected an error here because validation would have failed
|
||||
t.Errorf("Expected non-nil value.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginSetupHook(t *testing.T) {
|
||||
pluginName := fmt.Sprintf("test%d", rand.Intn(1000))
|
||||
defer tearDownPlugin(pluginName)
|
||||
installPluginUnderTest(t, "", pluginName)
|
||||
|
||||
plug, err := network.InitNetworkPlugin(probeNetworkPluginsWithExecDir(testPluginPath), pluginName, network.NewFakeHost(nil))
|
||||
|
||||
err = plug.SetUpPod("podNamespace", "podName", "dockerid2345")
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil: %v", err)
|
||||
}
|
||||
// check output of setup hook
|
||||
output, err := ioutil.ReadFile(path.Join(testPluginPath, pluginName, pluginName+".out"))
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil")
|
||||
}
|
||||
expectedOutput := "setup podNamespace podName dockerid2345"
|
||||
if string(output) != expectedOutput {
|
||||
t.Errorf("Mismatch in expected output for setup hook. Expected '%s', got '%s'", expectedOutput, string(output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginTearDownHook(t *testing.T) {
|
||||
pluginName := fmt.Sprintf("test%d", rand.Intn(1000))
|
||||
defer tearDownPlugin(pluginName)
|
||||
installPluginUnderTest(t, "", pluginName)
|
||||
|
||||
plug, err := network.InitNetworkPlugin(probeNetworkPluginsWithExecDir(testPluginPath), pluginName, network.NewFakeHost(nil))
|
||||
|
||||
err = plug.TearDownPod("podNamespace", "podName", "dockerid2345")
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil")
|
||||
}
|
||||
// check output of setup hook
|
||||
output, err := ioutil.ReadFile(path.Join(testPluginPath, pluginName, pluginName+".out"))
|
||||
if err != nil {
|
||||
t.Errorf("Expected nil")
|
||||
}
|
||||
expectedOutput := "teardown podNamespace podName dockerid2345"
|
||||
if string(output) != expectedOutput {
|
||||
t.Errorf("Mismatch in expected output for teardown hook. Expected '%s', got '%s'", expectedOutput, string(output))
|
||||
}
|
||||
}
|
122
pkg/kubelet/network/plugins.go
Normal file
122
pkg/kubelet/network/plugins.go
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const DefaultPluginName = "kubernetes.io/no-op"
|
||||
|
||||
// Plugin is an interface to network plugins for the kubelet
|
||||
type NetworkPlugin interface {
|
||||
// Init initializes the plugin. This will be called exactly once
|
||||
// before any other methods are called.
|
||||
Init(host Host) error
|
||||
|
||||
// Name returns the plugin's name. This will be used when searching
|
||||
// for a plugin by name, e.g.
|
||||
Name() string
|
||||
|
||||
// SetUpPod is the method called after the infra container of
|
||||
// the pod has been created but before the other containers of the
|
||||
// pod are launched.
|
||||
SetUpPod(namespace string, name string, podInfraContainerID dockertools.DockerID) error
|
||||
|
||||
// TearDownPod is the method called before a pod's infra container will be deleted
|
||||
TearDownPod(namespace string, name string, podInfraContainerID dockertools.DockerID) error
|
||||
}
|
||||
|
||||
// Host is an interface that plugins can use to access the kubelet.
|
||||
type Host interface {
|
||||
// Get the pod structure by its name, namespace
|
||||
GetPodByName(namespace, name string) (*api.Pod, bool)
|
||||
|
||||
// GetKubeClient returns a client interface
|
||||
GetKubeClient() client.Interface
|
||||
}
|
||||
|
||||
// InitNetworkPlugin inits the plugin that matches networkPluginName. Plugins must have unique names.
|
||||
func InitNetworkPlugin(plugins []NetworkPlugin, networkPluginName string, host Host) (NetworkPlugin, error) {
|
||||
if networkPluginName == "" {
|
||||
// default to the no_op plugin
|
||||
plug := &noopNetworkPlugin{}
|
||||
return plug, nil
|
||||
}
|
||||
|
||||
pluginMap := map[string]NetworkPlugin{}
|
||||
|
||||
allErrs := []error{}
|
||||
for _, plugin := range plugins {
|
||||
name := plugin.Name()
|
||||
if !util.IsQualifiedName(name) {
|
||||
allErrs = append(allErrs, fmt.Errorf("network plugin has invalid name: %#v", plugin))
|
||||
continue
|
||||
}
|
||||
|
||||
if _, found := pluginMap[name]; found {
|
||||
allErrs = append(allErrs, fmt.Errorf("network plugin %q was registered more than once", name))
|
||||
continue
|
||||
}
|
||||
pluginMap[name] = plugin
|
||||
}
|
||||
|
||||
chosenPlugin := pluginMap[networkPluginName]
|
||||
if chosenPlugin != nil {
|
||||
err := chosenPlugin.Init(host)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, fmt.Errorf("Network plugin %q failed init: %v", networkPluginName, err))
|
||||
} else {
|
||||
glog.V(1).Infof("Loaded network plugin %q", networkPluginName)
|
||||
}
|
||||
} else {
|
||||
allErrs = append(allErrs, fmt.Errorf("Network plugin %q not found.", networkPluginName))
|
||||
}
|
||||
|
||||
return chosenPlugin, errors.NewAggregate(allErrs)
|
||||
}
|
||||
|
||||
func UnescapePluginName(in string) string {
|
||||
return strings.Replace(in, "~", "/", -1)
|
||||
}
|
||||
|
||||
type noopNetworkPlugin struct {
|
||||
}
|
||||
|
||||
func (plugin *noopNetworkPlugin) Init(host Host) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *noopNetworkPlugin) Name() string {
|
||||
return DefaultPluginName
|
||||
}
|
||||
|
||||
func (plugin *noopNetworkPlugin) SetUpPod(namespace string, name string, id dockertools.DockerID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *noopNetworkPlugin) TearDownPod(namespace string, name string, id dockertools.DockerID) error {
|
||||
return nil
|
||||
}
|
35
pkg/kubelet/network/plugins_test.go
Normal file
35
pkg/kubelet/network/plugins_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 network
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSelectDefaultPlugin(t *testing.T) {
|
||||
all_plugins := []NetworkPlugin{}
|
||||
plug, err := InitNetworkPlugin(all_plugins, "", NewFakeHost(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error in selecting default plugin: %v", err)
|
||||
}
|
||||
if plug == nil {
|
||||
t.Fatalf("Failed to select the default plugin.")
|
||||
}
|
||||
if plug.Name() != DefaultPluginName {
|
||||
t.Errorf("Failed to select the default plugin. Expected %s. Got %s", DefaultPluginName, plug.Name())
|
||||
}
|
||||
}
|
42
pkg/kubelet/network/testing.go
Normal file
42
pkg/kubelet/network/testing.go
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 network
|
||||
|
||||
// helper for testing plugins
|
||||
// a fake host is created here that can be used by plugins for testing
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
)
|
||||
|
||||
type fakeNetworkHost struct {
|
||||
kubeClient client.Interface
|
||||
}
|
||||
|
||||
func NewFakeHost(kubeClient client.Interface) *fakeNetworkHost {
|
||||
host := &fakeNetworkHost{kubeClient: kubeClient}
|
||||
return host
|
||||
}
|
||||
|
||||
func (fnh *fakeNetworkHost) GetPodByName(name, namespace string) (*api.Pod, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (fnh *fakeNetworkHost) GetKubeClient() client.Interface {
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user