diff --git a/pkg/kubelet/container/testing/fake_runtime.go b/pkg/kubelet/container/testing/fake_runtime.go index bd2b29d91c..1873512fa5 100644 --- a/pkg/kubelet/container/testing/fake_runtime.go +++ b/pkg/kubelet/container/testing/fake_runtime.go @@ -118,6 +118,11 @@ func (f *FakeRuntime) ClearCalls() { f.StatusErr = nil } +// UpdatePodCIDR fulfills the cri interface. +func (f *FakeRuntime) UpdatePodCIDR(c string) error { + return nil +} + func (f *FakeRuntime) assertList(expect []string, test []string) error { if !reflect.DeepEqual(expect, test) { return fmt.Errorf("expected %#v, got %#v", expect, test) diff --git a/pkg/kubelet/container/testing/runtime_mock.go b/pkg/kubelet/container/testing/runtime_mock.go index a559ff0a31..b00ef98c68 100644 --- a/pkg/kubelet/container/testing/runtime_mock.go +++ b/pkg/kubelet/container/testing/runtime_mock.go @@ -153,3 +153,8 @@ func (r *Mock) ImageStats() (*ImageStats, error) { args := r.Called() return args.Get(0).(*ImageStats), args.Error(1) } + +// UpdatePodCIDR fulfills the cri interface. +func (r *Mock) UpdatePodCIDR(c string) error { + return nil +} diff --git a/pkg/kubelet/dockershim/BUILD b/pkg/kubelet/dockershim/BUILD index fcc0428a79..672e10c132 100644 --- a/pkg/kubelet/dockershim/BUILD +++ b/pkg/kubelet/dockershim/BUILD @@ -27,11 +27,15 @@ go_library( tags = ["automanaged"], deps = [ "//pkg/api:go_default_library", + "//pkg/apis/componentconfig:go_default_library", "//pkg/kubelet/api:go_default_library", "//pkg/kubelet/api/v1alpha1/runtime:go_default_library", "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/dockertools:go_default_library", "//pkg/kubelet/leaky:go_default_library", + "//pkg/kubelet/network:go_default_library", + "//pkg/kubelet/network/cni:go_default_library", + "//pkg/kubelet/network/kubenet:go_default_library", "//pkg/kubelet/qos:go_default_library", "//pkg/kubelet/server/streaming:go_default_library", "//pkg/kubelet/types:go_default_library", @@ -63,12 +67,16 @@ go_test( deps = [ "//pkg/api:go_default_library", "//pkg/kubelet/api/v1alpha1/runtime:go_default_library", + "//pkg/kubelet/container:go_default_library", "//pkg/kubelet/container/testing:go_default_library", "//pkg/kubelet/dockertools:go_default_library", + "//pkg/kubelet/network:go_default_library", + "//pkg/kubelet/network/mock_network:go_default_library", "//pkg/kubelet/types:go_default_library", "//pkg/security/apparmor:go_default_library", "//pkg/util/clock:go_default_library", "//vendor:github.com/docker/engine-api/types", + "//vendor:github.com/golang/mock/gomock", "//vendor:github.com/stretchr/testify/assert", ], ) diff --git a/pkg/kubelet/dockershim/docker_sandbox_test.go b/pkg/kubelet/dockershim/docker_sandbox_test.go index 9070098811..baaadcd86b 100644 --- a/pkg/kubelet/dockershim/docker_sandbox_test.go +++ b/pkg/kubelet/dockershim/docker_sandbox_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" runtimeApi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/types" ) @@ -99,12 +100,13 @@ func TestSandboxStatus(t *testing.T) { state := runtimeApi.PodSandBoxState_READY ct := int64(0) + hostNetwork := false expected := &runtimeApi.PodSandboxStatus{ State: &state, CreatedAt: &ct, Metadata: config.Metadata, Network: &runtimeApi.PodSandboxNetworkStatus{Ip: &fakeIP}, - Linux: &runtimeApi.LinuxPodSandboxStatus{Namespaces: &runtimeApi.Namespace{Network: &fakeNS}}, + Linux: &runtimeApi.LinuxPodSandboxStatus{Namespaces: &runtimeApi.Namespace{Network: &fakeNS, Options: &runtimeApi.NamespaceOption{HostNetwork: &hostNetwork}}}, Labels: labels, Annotations: annotations, } @@ -138,3 +140,57 @@ func TestSandboxStatus(t *testing.T) { status, err = ds.PodSandboxStatus(id) assert.Error(t, err, fmt.Sprintf("status of sandbox: %+v", status)) } + +// TestNetworkPluginInvocation checks that the right SetUpPod and TearDownPod +// calls are made when we run/stop a sandbox. +func TestNetworkPluginInvocation(t *testing.T) { + ds, _, _ := newTestDockerService() + mockPlugin := newTestNetworkPlugin(t) + ds.networkPlugin = mockPlugin + defer mockPlugin.Finish() + + name := "foo0" + ns := "bar0" + c := makeSandboxConfigWithLabelsAndAnnotations( + name, ns, "0", 0, + map[string]string{"label": name}, + map[string]string{"annotation": ns}, + ) + cID := kubecontainer.ContainerID{Type: runtimeName, ID: fmt.Sprintf("/%v", makeSandboxName(c))} + + setup := mockPlugin.EXPECT().SetUpPod(ns, name, cID) + // StopPodSandbox performs a lookup on status to figure out if the sandbox + // is running with hostnetworking, as all its given is the ID. + mockPlugin.EXPECT().GetPodNetworkStatus(ns, name, cID) + mockPlugin.EXPECT().TearDownPod(ns, name, cID).After(setup) + + _, err := ds.RunPodSandbox(c) + assert.NoError(t, err) + err = ds.StopPodSandbox(cID.ID) + assert.NoError(t, err) +} + +// TestHostNetworkPluginInvocation checks that *no* SetUp/TearDown calls happen +// for host network sandboxes. +func TestHostNetworkPluginInvocation(t *testing.T) { + ds, _, _ := newTestDockerService() + mockPlugin := newTestNetworkPlugin(t) + ds.networkPlugin = mockPlugin + defer mockPlugin.Finish() + + name := "foo0" + ns := "bar0" + c := makeSandboxConfigWithLabelsAndAnnotations( + name, ns, "0", 0, + map[string]string{"label": name}, + map[string]string{"annotation": ns}, + ) + hostNetwork := true + c.Linux = &runtimeApi.LinuxPodSandboxConfig{NamespaceOptions: &runtimeApi.NamespaceOption{HostNetwork: &hostNetwork}} + cID := kubecontainer.ContainerID{Type: runtimeName, ID: fmt.Sprintf("/%v", makeSandboxName(c))} + + // No calls to network plugin are expected + _, err := ds.RunPodSandbox(c) + assert.NoError(t, err) + assert.NoError(t, ds.StopPodSandbox(cID.ID)) +} diff --git a/pkg/kubelet/dockershim/docker_service_test.go b/pkg/kubelet/dockershim/docker_service_test.go index 2784f99848..3314c176fd 100644 --- a/pkg/kubelet/dockershim/docker_service_test.go +++ b/pkg/kubelet/dockershim/docker_service_test.go @@ -17,15 +17,25 @@ limitations under the License. package dockershim import ( + "github.com/golang/mock/gomock" + "testing" "time" containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" "k8s.io/kubernetes/pkg/kubelet/dockertools" + "k8s.io/kubernetes/pkg/kubelet/network" + "k8s.io/kubernetes/pkg/kubelet/network/mock_network" "k8s.io/kubernetes/pkg/util/clock" ) +// newTestNetworkPlugin returns a mock plugin that implements network.NetworkPlugin +func newTestNetworkPlugin(t *testing.T) *mock_network.MockNetworkPlugin { + ctrl := gomock.NewController(t) + return mock_network.NewMockNetworkPlugin(ctrl) +} + func newTestDockerService() (*dockerService, *dockertools.FakeDockerClient, *clock.FakeClock) { fakeClock := clock.NewFakeClock(time.Time{}) c := dockertools.NewFakeDockerClientWithClock(fakeClock) - return &dockerService{client: c, os: &containertest.FakeOS{}}, c, fakeClock + return &dockerService{client: c, os: &containertest.FakeOS{}, networkPlugin: &network.NoopNetworkPlugin{}}, c, fakeClock } diff --git a/pkg/kubelet/network/cni/cni_test.go b/pkg/kubelet/network/cni/cni_test.go index 41fd278608..8aeed7a93f 100644 --- a/pkg/kubelet/network/cni/cni_test.go +++ b/pkg/kubelet/network/cni/cni_test.go @@ -138,6 +138,14 @@ func (fnh *fakeNetworkHost) GetRuntime() kubecontainer.Runtime { return fnh.runtime } +func (fnh *fakeNetworkHost) GetNetNS(containerID string) (string, error) { + return fnh.GetRuntime().GetNetNS(kubecontainer.ContainerID{Type: "test", ID: containerID}) +} + +func (fnh *fakeNetworkHost) SupportsLegacyFeatures() bool { + return true +} + func TestCNIPlugin(t *testing.T) { // install some random plugin pluginName := fmt.Sprintf("test%d", rand.Intn(1000)) diff --git a/pkg/kubelet/network/kubenet/kubenet_linux_test.go b/pkg/kubelet/network/kubenet/kubenet_linux_test.go index e3d129b06c..08cd7d5a90 100644 --- a/pkg/kubelet/network/kubenet/kubenet_linux_test.go +++ b/pkg/kubelet/network/kubenet/kubenet_linux_test.go @@ -229,4 +229,40 @@ func TestGenerateMacAddress(t *testing.T) { } } +// TestInvocationWithoutRuntime invokes the plugin without a runtime. +// This is how kubenet is invoked from the cri. +func TestTearDownWithoutRuntime(t *testing.T) { + fhost := nettest.NewFakeHost(nil) + fhost.Legacy = false + fhost.Runtime = nil + mockcni := &mock_cni.MockCNI{} + + fexec := &exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{}, + LookPathFunc: func(file string) (string, error) { + return fmt.Sprintf("/fake-bin/%s", file), nil + }, + } + + kubenet := newFakeKubenetPlugin(map[kubecontainer.ContainerID]string{}, fexec, fhost) + kubenet.cniConfig = mockcni + kubenet.iptables = ipttest.NewFake() + + details := make(map[string]interface{}) + details[network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE_DETAIL_CIDR] = "10.0.0.1/24" + kubenet.Event(network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE, details) + + existingContainerID := kubecontainer.BuildContainerID("docker", "123") + kubenet.podIPs[existingContainerID] = "10.0.0.1" + + mockcni.On("DelNetwork", mock.AnythingOfType("*libcni.NetworkConfig"), mock.AnythingOfType("*libcni.RuntimeConf")).Return(nil) + + if err := kubenet.TearDownPod("namespace", "name", existingContainerID); err != nil { + t.Fatalf("Unexpected error in TearDownPod: %v", err) + } + // Assert that the CNI DelNetwork made it through and we didn't crash + // without a runtime. + mockcni.AssertExpectations(t) +} + //TODO: add unit test for each implementation of network plugin interface diff --git a/pkg/kubelet/network/mock_network/network_plugins.go b/pkg/kubelet/network/mock_network/network_plugins.go index 71e50530d4..a56bf25231 100644 --- a/pkg/kubelet/network/mock_network/network_plugins.go +++ b/pkg/kubelet/network/mock_network/network_plugins.go @@ -55,6 +55,10 @@ func (_m *MockNetworkPlugin) Capabilities() sets.Int { return ret0 } +func (_m *MockNetworkPlugin) Finish() { + _m.ctrl.Finish() +} + func (_mr *_MockNetworkPluginRecorder) Capabilities() *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "Capabilities") } diff --git a/pkg/kubelet/network/testing/fake_host.go b/pkg/kubelet/network/testing/fake_host.go index 3eb1d1672f..a650be1c2e 100644 --- a/pkg/kubelet/network/testing/fake_host.go +++ b/pkg/kubelet/network/testing/fake_host.go @@ -27,11 +27,14 @@ import ( ) type fakeNetworkHost struct { + fakeNamespaceGetter kubeClient clientset.Interface + Legacy bool + Runtime *containertest.FakeRuntime } func NewFakeHost(kubeClient clientset.Interface) *fakeNetworkHost { - host := &fakeNetworkHost{kubeClient: kubeClient} + host := &fakeNetworkHost{kubeClient: kubeClient, Legacy: true, Runtime: &containertest.FakeRuntime{}} return host } @@ -44,5 +47,17 @@ func (fnh *fakeNetworkHost) GetKubeClient() clientset.Interface { } func (nh *fakeNetworkHost) GetRuntime() kubecontainer.Runtime { - return &containertest.FakeRuntime{} + return nh.Runtime +} + +func (nh *fakeNetworkHost) SupportsLegacyFeatures() bool { + return nh.Legacy +} + +type fakeNamespaceGetter struct { + ns string +} + +func (nh *fakeNamespaceGetter) GetNetNS(containerID string) (string, error) { + return nh.ns, nil } diff --git a/pkg/kubelet/networks.go b/pkg/kubelet/networks.go index cef6703e2a..74cfcb6427 100644 --- a/pkg/kubelet/networks.go +++ b/pkg/kubelet/networks.go @@ -63,7 +63,7 @@ type criNetworkHost struct { // Any network plugin invoked by a cri must implement NamespaceGetter // to talk directly to the runtime instead. func (c *criNetworkHost) GetNetNS(containerID string) (string, error) { - return c.kubelet.GetRuntime().GetNetNS(kubecontainer.ContainerID{"", containerID}) + return c.kubelet.GetRuntime().GetNetNS(kubecontainer.ContainerID{Type: "", ID: containerID}) } // noOpLegacyHost implements the network.LegacyHost interface for the remote