From 7ddcecfd1cfc3d91ccbb989332ec4aaaa30e1537 Mon Sep 17 00:00:00 2001 From: CJ Cullen Date: Thu, 19 Mar 2015 16:14:13 -0700 Subject: [PATCH] Revert "Revert "[WIP] southbound networking hooks in kubelet"" --- cmd/kubelet/app/plugins.go | 13 ++ cmd/kubelet/app/server.go | 10 ++ pkg/kubelet/kubelet.go | 25 ++++ pkg/kubelet/kubelet_test.go | 2 + pkg/kubelet/network/exec/exec.go | 140 ++++++++++++++++++++ pkg/kubelet/network/exec/exec_test.go | 179 ++++++++++++++++++++++++++ pkg/kubelet/network/plugins.go | 122 ++++++++++++++++++ pkg/kubelet/network/plugins_test.go | 35 +++++ pkg/kubelet/network/testing.go | 42 ++++++ pkg/kubelet/networks.go | 36 ++++++ pkg/kubelet/runonce_test.go | 2 + 11 files changed, 606 insertions(+) create mode 100644 pkg/kubelet/network/exec/exec.go create mode 100644 pkg/kubelet/network/exec/exec_test.go create mode 100644 pkg/kubelet/network/plugins.go create mode 100644 pkg/kubelet/network/plugins_test.go create mode 100644 pkg/kubelet/network/testing.go create mode 100644 pkg/kubelet/networks.go diff --git a/cmd/kubelet/app/plugins.go b/cmd/kubelet/app/plugins.go index 130e72f153d..5d9f2576b75 100644 --- a/cmd/kubelet/app/plugins.go +++ b/cmd/kubelet/app/plugins.go @@ -20,6 +20,9 @@ package app import ( // Credential providers _ "github.com/GoogleCloudPlatform/kubernetes/pkg/credentialprovider/gcp" + // Network plugins + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/network" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/network/exec" // Volume plugins "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/empty_dir" @@ -44,3 +47,13 @@ func ProbeVolumePlugins() []volume.Plugin { return allPlugins } + +// ProbeNetworkPlugins collects all compiled-in plugins +func ProbeNetworkPlugins() []network.NetworkPlugin { + allPlugins := []network.NetworkPlugin{} + + // for each existing plugin, add to the list + allPlugins = append(allPlugins, exec.ProbeNetworkPlugins()...) + + return allPlugins +} diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index 062aec4a4c0..73da9984b81 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -33,6 +33,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/cadvisor" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/config" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/network" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume" "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -79,6 +80,7 @@ type KubeletServer struct { StreamingConnectionIdleTimeout time.Duration ImageGCHighThresholdPercent int ImageGCLowThresholdPercent int + NetworkPluginName string } // NewKubeletServer will create a new KubeletServer with default values. @@ -104,6 +106,7 @@ func NewKubeletServer() *KubeletServer { MasterServiceNamespace: api.NamespaceDefault, ImageGCHighThresholdPercent: 90, ImageGCLowThresholdPercent: 80, + NetworkPluginName: "", } } @@ -142,6 +145,7 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) { fs.DurationVar(&s.StreamingConnectionIdleTimeout, "streaming_connection_idle_timeout", 0, "Maximum time a streaming connection can be idle before the connection is automatically closed. Example: '5m'") fs.IntVar(&s.ImageGCHighThresholdPercent, "image_gc_high_threshold", s.ImageGCHighThresholdPercent, "The percent of disk usage after which image garbage collection is always run. Default: 90%%") fs.IntVar(&s.ImageGCLowThresholdPercent, "image_gc_low_threshold", s.ImageGCLowThresholdPercent, "The percent of disk usage before which image garbage collection is never run. Lowest disk usage to garbage collect to. Default: 80%%") + fs.StringVar(&s.NetworkPluginName, "network_plugin", s.NetworkPluginName, " The name of the network plugin to be invoked for various events in kubelet/pod lifecycle") } // Run runs the specified KubeletServer. This should never exit. @@ -200,6 +204,8 @@ func (s *KubeletServer) Run(_ []string) error { KubeClient: client, MasterServiceNamespace: s.MasterServiceNamespace, VolumePlugins: ProbeVolumePlugins(), + NetworkPlugins: ProbeNetworkPlugins(), + NetworkPluginName: s.NetworkPluginName, StreamingConnectionIdleTimeout: s.StreamingConnectionIdleTimeout, ImageGCPolicy: imageGCPolicy, } @@ -397,6 +403,8 @@ type KubeletConfig struct { Runonce bool MasterServiceNamespace string VolumePlugins []volume.Plugin + NetworkPlugins []network.NetworkPlugin + NetworkPluginName string StreamingConnectionIdleTimeout time.Duration Recorder record.EventRecorder TLSOptions *kubelet.TLSOptions @@ -437,6 +445,8 @@ func createAndInitKubelet(kc *KubeletConfig, pc *config.PodConfig) (*kubelet.Kub net.IP(kc.ClusterDNS), kc.MasterServiceNamespace, kc.VolumePlugins, + kc.NetworkPlugins, + kc.NetworkPluginName, kc.StreamingConnectionIdleTimeout, kc.Recorder, kc.CadvisorInterface, diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index a715811f011..32b251df6e1 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -42,6 +42,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/envvars" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/metrics" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/network" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" @@ -118,6 +119,8 @@ func NewMainKubelet( clusterDNS net.IP, masterServiceNamespace string, volumePlugins []volume.Plugin, + networkPlugins []network.NetworkPlugin, + networkPluginName string, streamingConnectionIdleTimeout time.Duration, recorder record.EventRecorder, cadvisorInterface cadvisor.Interface, @@ -220,6 +223,12 @@ func NewMainKubelet( return nil, err } + if plug, err := network.InitNetworkPlugin(networkPlugins, networkPluginName, &networkHost{klet}); err != nil { + return nil, err + } else { + klet.networkPlugin = plug + } + klet.podStatuses = make(map[string]api.PodStatus) klet.mirrorManager = newBasicMirrorManager(klet.kubeClient) @@ -301,6 +310,9 @@ type Kubelet struct { // Volume plugins. volumePluginMgr volume.PluginMgr + // Network plugin + networkPlugin network.NetworkPlugin + // probe runner holder prober probeHolder // container readiness state holder @@ -1357,6 +1369,11 @@ func (kl *Kubelet) syncPod(pod *api.Pod, hasMirrorPod bool, containersInPod dock } glog.Infof("Creating pod infra container for %q", podFullName) podInfraContainerID, err = kl.createPodInfraContainer(pod) + + // Call the networking plugin + if err == nil { + err = kl.networkPlugin.SetUpPod(pod.Namespace, pod.Name, podInfraContainerID) + } if err != nil { glog.Errorf("Failed to create pod infra container: %v; Skipping pod %q", err, podFullName) return err @@ -1548,6 +1565,14 @@ func (kl *Kubelet) SyncPods(allPods []api.Pod, podSyncTypes map[types.UID]metric pc := podContainer{podFullName, uid, containerName} _, ok := desiredContainers[pc] if err != nil || !ok { + // call the networking plugin for teardown + if containerName == dockertools.PodInfraContainerName { + name, namespace, _ := ParsePodFullName(podFullName) + err := kl.networkPlugin.TearDownPod(namespace, name, dockertools.DockerID(dockerContainers[ix].ID)) + if err != nil { + glog.Errorf("Network plugin pre-delete method returned an error: %v", err) + } + } glog.V(1).Infof("Killing unwanted container %+v", pc) err = kl.killContainer(dockerContainers[ix]) if err != nil { diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 0478c57af43..6698883eb9c 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -39,6 +39,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/cadvisor" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/metrics" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/network" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/host_path" "github.com/GoogleCloudPlatform/kubernetes/pkg/types" @@ -73,6 +74,7 @@ func newTestKubelet(t *testing.T) *TestKubelet { kubelet.kubeClient = fakeKubeClient kubelet.dockerPuller = &dockertools.FakeDockerPuller{} kubelet.hostname = "testnode" + kubelet.networkPlugin, _ = network.InitNetworkPlugin([]network.NetworkPlugin{}, "", network.NewFakeHost(nil)) if tempDir, err := ioutil.TempDir("/tmp", "kubelet_test."); err != nil { t.Fatalf("can't make a temp rootdir: %v", err) } else { diff --git a/pkg/kubelet/network/exec/exec.go b/pkg/kubelet/network/exec/exec.go new file mode 100644 index 00000000000..feddebb0c7b --- /dev/null +++ b/pkg/kubelet/network/exec/exec.go @@ -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/ +// 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 +// whereupon, 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/ +// 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 +} diff --git a/pkg/kubelet/network/exec/exec_test.go b/pkg/kubelet/network/exec/exec_test.go new file mode 100644 index 00000000000..2f753422802 --- /dev/null +++ b/pkg/kubelet/network/exec/exec_test.go @@ -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)) + } +} diff --git a/pkg/kubelet/network/plugins.go b/pkg/kubelet/network/plugins.go new file mode 100644 index 00000000000..993c7bffe96 --- /dev/null +++ b/pkg/kubelet/network/plugins.go @@ -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 +} diff --git a/pkg/kubelet/network/plugins_test.go b/pkg/kubelet/network/plugins_test.go new file mode 100644 index 00000000000..5c00ca288ed --- /dev/null +++ b/pkg/kubelet/network/plugins_test.go @@ -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()) + } +} diff --git a/pkg/kubelet/network/testing.go b/pkg/kubelet/network/testing.go new file mode 100644 index 00000000000..83c149c2dcb --- /dev/null +++ b/pkg/kubelet/network/testing.go @@ -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 +} diff --git a/pkg/kubelet/networks.go b/pkg/kubelet/networks.go new file mode 100644 index 00000000000..0a42599d03c --- /dev/null +++ b/pkg/kubelet/networks.go @@ -0,0 +1,36 @@ +/* +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 kubelet + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" +) + +// This just exports required functions from kubelet proper, for use by network +// plugins. +type networkHost struct { + kubelet *Kubelet +} + +func (nh *networkHost) GetPodByName(name, namespace string) (*api.Pod, bool) { + return nh.kubelet.GetPodByName(name, namespace) +} + +func (nh *networkHost) GetKubeClient() client.Interface { + return nh.kubelet.kubeClient +} diff --git a/pkg/kubelet/runonce_test.go b/pkg/kubelet/runonce_test.go index fc860127b79..55b3189d782 100644 --- a/pkg/kubelet/runonce_test.go +++ b/pkg/kubelet/runonce_test.go @@ -26,6 +26,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/cadvisor" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/network" docker "github.com/fsouza/go-dockerclient" cadvisorApi "github.com/google/cadvisor/info/v1" ) @@ -78,6 +79,7 @@ func TestRunOnce(t *testing.T) { cadvisor: cadvisor, } + kb.networkPlugin, _ = network.InitNetworkPlugin([]network.NetworkPlugin{}, "", network.NewFakeHost(nil)) if err := kb.setupDataDirs(); err != nil { t.Errorf("Failed to init data dirs: %v", err) }