123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- // +build linux
- /*
- Copyright 2014 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 cni
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "math/rand"
- "net"
- "os"
- "path"
- "reflect"
- "testing"
- "text/template"
- types020 "github.com/containernetworking/cni/pkg/types/020"
- "github.com/stretchr/testify/mock"
- "k8s.io/api/core/v1"
- clientset "k8s.io/client-go/kubernetes"
- utiltesting "k8s.io/client-go/util/testing"
- kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
- kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
- containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
- "k8s.io/kubernetes/pkg/kubelet/dockershim/network"
- "k8s.io/kubernetes/pkg/kubelet/dockershim/network/cni/testing"
- "k8s.io/kubernetes/pkg/kubelet/dockershim/network/hostport"
- networktest "k8s.io/kubernetes/pkg/kubelet/dockershim/network/testing"
- "k8s.io/utils/exec"
- fakeexec "k8s.io/utils/exec/testing"
- )
- // Returns .in file path, .out file path, and .env file path
- func installPluginUnderTest(t *testing.T, testBinDir, testConfDir, testDataDir, binName string, confName string) (string, string, string) {
- for _, dir := range []string{testBinDir, testConfDir, testDataDir} {
- err := os.MkdirAll(dir, 0777)
- if err != nil {
- t.Fatalf("Failed to create test plugin dir %s: %v", dir, err)
- }
- }
- confFile := path.Join(testConfDir, confName+".conf")
- f, err := os.Create(confFile)
- if err != nil {
- t.Fatalf("Failed to install plugin %s: %v", confFile, err)
- }
- networkConfig := fmt.Sprintf(`{ "name": "%s", "type": "%s", "capabilities": {"portMappings": true, "bandwidth": true, "ipRanges": true} }`, confName, binName)
- _, err = f.WriteString(networkConfig)
- if err != nil {
- t.Fatalf("Failed to write network config file (%v)", err)
- }
- f.Close()
- pluginExec := path.Join(testBinDir, binName)
- f, err = os.Create(pluginExec)
- const execScriptTempl = `#!/usr/bin/env bash
- cat > {{.InputFile}}
- env > {{.OutputEnv}}
- echo "%@" >> {{.OutputEnv}}
- export $(echo ${CNI_ARGS} | sed 's/;/ /g') &> /dev/null
- mkdir -p {{.OutputDir}} &> /dev/null
- echo -n "$CNI_COMMAND $CNI_NETNS $K8S_POD_NAMESPACE $K8S_POD_NAME $K8S_POD_INFRA_CONTAINER_ID" >& {{.OutputFile}}
- echo -n "{ \"ip4\": { \"ip\": \"10.1.0.23/24\" } }"
- `
- inputFile := path.Join(testDataDir, binName+".in")
- outputFile := path.Join(testDataDir, binName+".out")
- envFile := path.Join(testDataDir, binName+".env")
- execTemplateData := &map[string]interface{}{
- "InputFile": inputFile,
- "OutputFile": outputFile,
- "OutputEnv": envFile,
- "OutputDir": testDataDir,
- }
- tObj := template.Must(template.New("test").Parse(execScriptTempl))
- buf := &bytes.Buffer{}
- if err := tObj.Execute(buf, *execTemplateData); err != nil {
- t.Fatalf("Error in executing script template - %v", err)
- }
- execScript := buf.String()
- _, err = f.WriteString(execScript)
- if err != nil {
- t.Fatalf("Failed to write plugin exec - %v", err)
- }
- err = f.Chmod(0777)
- if err != nil {
- t.Fatalf("Failed to set exec perms on plugin")
- }
- f.Close()
- return inputFile, outputFile, envFile
- }
- func tearDownPlugin(tmpDir string) {
- err := os.RemoveAll(tmpDir)
- if err != nil {
- fmt.Printf("Error in cleaning up test: %v", err)
- }
- }
- type fakeNetworkHost struct {
- networktest.FakePortMappingGetter
- kubeClient clientset.Interface
- pods []*containertest.FakePod
- }
- func NewFakeHost(kubeClient clientset.Interface, pods []*containertest.FakePod, ports map[string][]*hostport.PortMapping) *fakeNetworkHost {
- host := &fakeNetworkHost{
- networktest.FakePortMappingGetter{PortMaps: ports},
- kubeClient,
- pods,
- }
- return host
- }
- func (fnh *fakeNetworkHost) GetPodByName(name, namespace string) (*v1.Pod, bool) {
- return nil, false
- }
- func (fnh *fakeNetworkHost) GetKubeClient() clientset.Interface {
- return fnh.kubeClient
- }
- func (fnh *fakeNetworkHost) GetNetNS(containerID string) (string, error) {
- for _, fp := range fnh.pods {
- for _, c := range fp.Pod.Containers {
- if c.ID.ID == containerID {
- return fp.NetnsPath, nil
- }
- }
- }
- return "", fmt.Errorf("container %q not found", containerID)
- }
- func (fnh *fakeNetworkHost) SupportsLegacyFeatures() bool {
- return true
- }
- func TestCNIPlugin(t *testing.T) {
- // install some random plugin
- netName := fmt.Sprintf("test%d", rand.Intn(1000))
- binName := fmt.Sprintf("test_vendor%d", rand.Intn(1000))
- podIP := "10.0.0.2"
- podIPOutput := fmt.Sprintf("4: eth0 inet %s/24 scope global dynamic eth0\\ valid_lft forever preferred_lft forever", podIP)
- fakeCmds := []fakeexec.FakeCommandAction{
- func(cmd string, args ...string) exec.Cmd {
- return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{
- CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
- func() ([]byte, error) {
- return []byte(podIPOutput), nil
- },
- },
- }, cmd, args...)
- },
- }
- fexec := &fakeexec.FakeExec{
- CommandScript: fakeCmds,
- LookPathFunc: func(file string) (string, error) {
- return fmt.Sprintf("/fake-bin/%s", file), nil
- },
- }
- mockLoCNI := &mock_cni.MockCNI{}
- // TODO mock for the test plugin too
- tmpDir := utiltesting.MkTmpdirOrDie("cni-test")
- testConfDir := path.Join(tmpDir, "etc", "cni", "net.d")
- testBinDir := path.Join(tmpDir, "opt", "cni", "bin")
- testDataDir := path.Join(tmpDir, "output")
- defer tearDownPlugin(tmpDir)
- inputFile, outputFile, outputEnv := installPluginUnderTest(t, testBinDir, testConfDir, testDataDir, binName, netName)
- containerID := kubecontainer.ContainerID{Type: "test", ID: "test_infra_container"}
- pods := []*containertest.FakePod{{
- Pod: &kubecontainer.Pod{
- Containers: []*kubecontainer.Container{
- {ID: containerID},
- },
- },
- NetnsPath: "/proc/12345/ns/net",
- }}
- plugins := ProbeNetworkPlugins(testConfDir, []string{testBinDir})
- if len(plugins) != 1 {
- t.Fatalf("Expected only one network plugin, got %d", len(plugins))
- }
- if plugins[0].Name() != "cni" {
- t.Fatalf("Expected CNI network plugin, got %q", plugins[0].Name())
- }
- cniPlugin, ok := plugins[0].(*cniNetworkPlugin)
- if !ok {
- t.Fatalf("Not a CNI network plugin!")
- }
- cniPlugin.execer = fexec
- cniPlugin.loNetwork.CNIConfig = mockLoCNI
- mockLoCNI.On("AddNetworkList", cniPlugin.loNetwork.NetworkConfig, mock.AnythingOfType("*libcni.RuntimeConf")).Return(&types020.Result{IP4: &types020.IPConfig{IP: net.IPNet{IP: []byte{127, 0, 0, 1}}}}, nil)
- // Check that status returns an error
- if err := cniPlugin.Status(); err == nil {
- t.Fatalf("cniPlugin returned non-err with no podCidr")
- }
- cniPlugin.Event(network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE, map[string]interface{}{
- network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE_DETAIL_CIDR: "10.0.2.0/24",
- })
- if err := cniPlugin.Status(); err != nil {
- t.Fatalf("unexpected status err: %v", err)
- }
- ports := map[string][]*hostport.PortMapping{
- containerID.ID: {
- {
- Name: "name",
- HostPort: 8008,
- ContainerPort: 80,
- Protocol: "UDP",
- HostIP: "0.0.0.0",
- },
- },
- }
- fakeHost := NewFakeHost(nil, pods, ports)
- plug, err := network.InitNetworkPlugin(plugins, "cni", fakeHost, kubeletconfig.HairpinNone, "10.0.0.0/8", network.UseDefaultMTU)
- if err != nil {
- t.Fatalf("Failed to select the desired plugin: %v", err)
- }
- bandwidthAnnotation := make(map[string]string)
- bandwidthAnnotation["kubernetes.io/ingress-bandwidth"] = "1M"
- bandwidthAnnotation["kubernetes.io/egress-bandwidth"] = "1M"
- // Set up the pod
- err = plug.SetUpPod("podNamespace", "podName", containerID, bandwidthAnnotation, nil)
- if err != nil {
- t.Errorf("Expected nil: %v", err)
- }
- eo, eerr := ioutil.ReadFile(outputEnv)
- output, err := ioutil.ReadFile(outputFile)
- if err != nil || eerr != nil {
- t.Errorf("Failed to read output file %s: %v (env %s err %v)", outputFile, err, eo, eerr)
- }
- expectedOutput := "ADD /proc/12345/ns/net podNamespace podName test_infra_container"
- if string(output) != expectedOutput {
- t.Errorf("Mismatch in expected output for setup hook. Expected '%s', got '%s'", expectedOutput, string(output))
- }
- // Verify the correct network configuration was passed
- inputConfig := struct {
- RuntimeConfig struct {
- PortMappings []map[string]interface{} `json:"portMappings"`
- Bandwidth map[string]interface{} `json:"bandwidth"`
- IpRanges [][]map[string]interface{} `json:"ipRanges"`
- } `json:"runtimeConfig"`
- }{}
- inputBytes, inerr := ioutil.ReadFile(inputFile)
- parseerr := json.Unmarshal(inputBytes, &inputConfig)
- if inerr != nil || parseerr != nil {
- t.Errorf("failed to parse reported cni input config %s: (%v %v)", inputFile, inerr, parseerr)
- }
- expectedMappings := []map[string]interface{}{
- // hah, golang always unmarshals unstructured json numbers as float64
- {"hostPort": 8008.0, "containerPort": 80.0, "protocol": "udp", "hostIP": "0.0.0.0"},
- }
- if !reflect.DeepEqual(inputConfig.RuntimeConfig.PortMappings, expectedMappings) {
- t.Errorf("mismatch in expected port mappings. expected %v got %v", expectedMappings, inputConfig.RuntimeConfig.PortMappings)
- }
- expectedBandwidth := map[string]interface{}{
- "ingressRate": 1000000.0, "egressRate": 1000000.0,
- "ingressBurst": 2147483647.0, "egressBurst": 2147483647.0,
- }
- if !reflect.DeepEqual(inputConfig.RuntimeConfig.Bandwidth, expectedBandwidth) {
- t.Errorf("mismatch in expected bandwidth. expected %v got %v", expectedBandwidth, inputConfig.RuntimeConfig.Bandwidth)
- }
- expectedIpRange := [][]map[string]interface{}{
- {
- {"subnet": "10.0.2.0/24"},
- },
- }
- if !reflect.DeepEqual(inputConfig.RuntimeConfig.IpRanges, expectedIpRange) {
- t.Errorf("mismatch in expected ipRange. expected %v got %v", expectedIpRange, inputConfig.RuntimeConfig.IpRanges)
- }
- // Get its IP address
- status, err := plug.GetPodNetworkStatus("podNamespace", "podName", containerID)
- if err != nil {
- t.Errorf("Failed to read pod network status: %v", err)
- }
- if status.IP.String() != podIP {
- t.Errorf("Expected pod IP %q but got %q", podIP, status.IP.String())
- }
- // Tear it down
- err = plug.TearDownPod("podNamespace", "podName", containerID)
- if err != nil {
- t.Errorf("Expected nil: %v", err)
- }
- output, err = ioutil.ReadFile(outputFile)
- expectedOutput = "DEL /proc/12345/ns/net podNamespace podName test_infra_container"
- if string(output) != expectedOutput {
- t.Errorf("Mismatch in expected output for setup hook. Expected '%s', got '%s'", expectedOutput, string(output))
- }
- mockLoCNI.AssertExpectations(t)
- }
- func TestLoNetNonNil(t *testing.T) {
- if conf := getLoNetwork(nil); conf == nil {
- t.Error("Expected non-nil lo network")
- }
- }
|