diff --git a/cmd/cri-containerd/cri_containerd.go b/cmd/cri-containerd/cri_containerd.go index 646500fe3..d0738fc4d 100644 --- a/cmd/cri-containerd/cri_containerd.go +++ b/cmd/cri-containerd/cri_containerd.go @@ -42,8 +42,8 @@ func main() { if err != nil { glog.Exitf("Failed to connect containerd endpoint %q: %v", o.ContainerdEndpoint, err) } - glog.V(2).Infof("Run cri-containerd grpc server on socket %q", o.SocketPath) + glog.V(2).Infof("Run cri-containerd grpc server on socket %q", o.SocketPath) service, err := server.NewCRIContainerdService(conn, o.RootDir, o.NetworkPluginBinDir, o.NetworkPluginConfDir) if err != nil { glog.Exitf("Failed to create CRI containerd service %+v: %v", o, err) diff --git a/pkg/os/testing/fake_os.go b/pkg/os/testing/fake_os.go index 9edc89f43..25aa4de4a 100644 --- a/pkg/os/testing/fake_os.go +++ b/pkg/os/testing/fake_os.go @@ -19,6 +19,7 @@ package testing import ( "io" "os" + "sync" "golang.org/x/net/context" @@ -29,20 +30,64 @@ import ( // If a member of the form `*Fn` is set, that function will be called in place // of the real call. type FakeOS struct { + sync.Mutex MkdirAllFn func(string, os.FileMode) error RemoveAllFn func(string) error OpenFifoFn func(context.Context, string, int, os.FileMode) (io.ReadWriteCloser, error) + StatFn func(name string) (os.FileInfo, error) + errors map[string]error } var _ osInterface.OS = &FakeOS{} +// getError get error for call +func (f *FakeOS) getError(op string) error { + f.Lock() + defer f.Unlock() + err, ok := f.errors[op] + if ok { + delete(f.errors, op) + return err + } + return nil +} + +// InjectError inject error for call +func (f *FakeOS) InjectError(fn string, err error) { + f.Lock() + defer f.Unlock() + f.errors[fn] = err +} + +// InjectErrors inject errors for calls +func (f *FakeOS) InjectErrors(errs map[string]error) { + f.Lock() + defer f.Unlock() + for fn, err := range errs { + f.errors[fn] = err + } +} + +// ClearErrors clear errors for call +func (f *FakeOS) ClearErrors() { + f.Lock() + defer f.Unlock() + f.errors = make(map[string]error) +} + // NewFakeOS creates a FakeOS. func NewFakeOS() *FakeOS { - return &FakeOS{} + return &FakeOS{ + errors: make(map[string]error), + } } // MkdirAll is a fake call that invokes MkdirAllFn or just returns nil. func (f *FakeOS) MkdirAll(path string, perm os.FileMode) error { + if err := f.getError("MkdirAll"); err != nil { + return err + } + if f.MkdirAllFn != nil { return f.MkdirAllFn(path, perm) } @@ -51,6 +96,10 @@ func (f *FakeOS) MkdirAll(path string, perm os.FileMode) error { // RemoveAll is a fake call that invokes RemoveAllFn or just returns nil. func (f *FakeOS) RemoveAll(path string) error { + if err := f.getError("RemoveAll"); err != nil { + return err + } + if f.RemoveAllFn != nil { return f.RemoveAllFn(path) } @@ -59,8 +108,24 @@ func (f *FakeOS) RemoveAll(path string) error { // OpenFifo is a fake call that invokes OpenFifoFn or just returns nil. func (f *FakeOS) OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) { + if err := f.getError("OpenFifo"); err != nil { + return nil, err + } + if f.OpenFifoFn != nil { return f.OpenFifoFn(ctx, fn, flag, perm) } return nil, nil } + +// Stat is a fake call that invokes Stat or just return nil. +func (f *FakeOS) Stat(name string) (os.FileInfo, error) { + if err := f.getError("Stat"); err != nil { + return nil, err + } + + if f.StatFn != nil { + return f.StatFn(name) + } + return nil, nil +} diff --git a/pkg/server/sandbox_run.go b/pkg/server/sandbox_run.go index 135dd7434..dc24e9aed 100644 --- a/pkg/server/sandbox_run.go +++ b/pkg/server/sandbox_run.go @@ -164,7 +164,7 @@ func (c *criContainerdService) RunPodSandbox(ctx context.Context, r *runtime.Run defer func() { if retErr != nil { // Cleanup the sandbox container if an error is returned. - if _, err := c.containerService.Delete(ctx, &execution.DeleteRequest{ID: id}); err != nil { + if _, err = c.containerService.Delete(ctx, &execution.DeleteRequest{ID: id}); err != nil { glog.Errorf("Failed to delete sandbox container %q: %v", id, err) } diff --git a/pkg/server/sandbox_run_test.go b/pkg/server/sandbox_run_test.go index 3eaffd31c..668fabb9d 100644 --- a/pkg/server/sandbox_run_test.go +++ b/pkg/server/sandbox_run_test.go @@ -124,7 +124,8 @@ func TestGenerateSandboxContainerSpec(t *testing.T) { func TestRunPodSandbox(t *testing.T) { config, specCheck := getRunPodSandboxTestData() c := newTestCRIContainerdService() - fake := c.containerService.(*servertesting.FakeExecutionClient) + fakeExecutionClient := c.containerService.(*servertesting.FakeExecutionClient) + fakeCNIPlugin := c.netPlugin.(*servertesting.FakeCNIPlugin) fakeOS := c.os.(*ostesting.FakeOS) var dirs []string var pipes []string @@ -139,7 +140,7 @@ func TestRunPodSandbox(t *testing.T) { assert.Equal(t, os.FileMode(0700), perm) return nopReadWriteCloser{}, nil } - expectCalls := []string{"create", "start"} + expectExecutionClientCalls := []string{"create", "start"} res, err := c.RunPodSandbox(context.Background(), &runtime.RunPodSandboxRequest{Config: config}) assert.NoError(t, err) @@ -154,8 +155,8 @@ func TestRunPodSandbox(t *testing.T) { assert.Contains(t, pipes, stdout, "sandbox stdout pipe should be created") assert.Contains(t, pipes, stderr, "sandbox stderr pipe should be created") - assert.Equal(t, expectCalls, fake.GetCalledNames(), "expect containerd functions should be called") - calls := fake.GetCalledDetails() + assert.Equal(t, expectExecutionClientCalls, fakeExecutionClient.GetCalledNames(), "expect containerd functions should be called") + calls := fakeExecutionClient.GetCalledDetails() createOpts := calls[0].Argument.(*execution.CreateRequest) assert.Equal(t, id, createOpts.ID, "create id should be correct") // TODO(random-liu): Test rootfs mount when image management part is integrated. @@ -177,7 +178,7 @@ func TestRunPodSandbox(t *testing.T) { assert.Equal(t, config, meta.Config, "metadata config should be correct") // TODO(random-liu): [P2] Add clock interface and use fake clock. assert.NotZero(t, meta.CreatedAt, "metadata CreatedAt should be set") - info, err := fake.Info(context.Background(), &execution.InfoRequest{ID: id}) + info, err := fakeExecutionClient.Info(context.Background(), &execution.InfoRequest{ID: id}) assert.NoError(t, err) pid := info.Pid assert.Equal(t, meta.NetNS, getNetworkNamespace(pid), "metadata network namespace should be correct") @@ -185,6 +186,13 @@ func TestRunPodSandbox(t *testing.T) { gotID, err := c.sandboxIDIndex.Get(id) assert.NoError(t, err) assert.Equal(t, id, gotID, "sandbox id should be indexed") + + expectedCNICalls := []string{"SetUpPod"} + assert.Equal(t, expectedCNICalls, fakeCNIPlugin.GetCalledNames(), "expect SetUpPod should be called") + calls = fakeCNIPlugin.GetCalledDetails() + pluginArgument := calls[0].Argument.(servertesting.CNIPluginArgument) + expectedPluginArgument := servertesting.CNIPluginArgument{meta.NetNS, config.GetMetadata().GetNamespace(), config.GetMetadata().GetName(), id} + assert.Equal(t, expectedPluginArgument, pluginArgument, "SetUpPod should be called with correct arguments") } // TODO(random-liu): [P1] Add unit test for different error cases to make sure diff --git a/pkg/server/sandbox_status_test.go b/pkg/server/sandbox_status_test.go index 2f253e572..2a93b7dcb 100644 --- a/pkg/server/sandbox_status_test.go +++ b/pkg/server/sandbox_status_test.go @@ -35,7 +35,11 @@ import ( // Variables used in the following test. -const sandboxStatusTestID = "test-id" +const ( + sandboxStatusTestID = "test-id" + sandboxStatusTestIP = "10.10.10.10" + sandboxStatusTestNetNS = "test-netns" +) func getSandboxStatusTestData() (*metadata.SandboxMetadata, *runtime.PodSandboxStatus) { config := &runtime.PodSandboxConfig{ @@ -65,15 +69,17 @@ func getSandboxStatusTestData() (*metadata.SandboxMetadata, *runtime.PodSandboxS Name: "test-name", Config: config, CreatedAt: createdAt, + NetNS: sandboxStatusTestNetNS, } expectedStatus := &runtime.PodSandboxStatus{ Id: sandboxStatusTestID, Metadata: config.GetMetadata(), CreatedAt: createdAt, - Network: &runtime.PodSandboxNetworkStatus{}, + Network: &runtime.PodSandboxNetworkStatus{Ip: ""}, Linux: &runtime.LinuxPodSandboxStatus{ Namespaces: &runtime.Namespace{ + Network: sandboxStatusTestNetNS, Options: &runtime.NamespaceOption{ HostNetwork: true, HostPid: false, @@ -95,7 +101,7 @@ func TestToCRISandboxStatus(t *testing.T) { }{ "ready sandbox should have network namespace": { state: runtime.PodSandboxState_SANDBOX_READY, - expectNetNS: "test-netns", + expectNetNS: sandboxStatusTestNetNS, }, "not ready sandbox should not have network namespace": { state: runtime.PodSandboxState_SANDBOX_NOTREADY, @@ -103,10 +109,10 @@ func TestToCRISandboxStatus(t *testing.T) { }, } { metadata, expect := getSandboxStatusTestData() - metadata.NetNS = "test-netns" - status := toCRISandboxStatus(metadata, test.state) + status := toCRISandboxStatus(metadata, test.state, sandboxStatusTestIP) expect.Linux.Namespaces.Network = test.expectNetNS expect.State = test.state + expect.Network.Ip = sandboxStatusTestIP assert.Equal(t, expect, status, desc) } } @@ -116,14 +122,18 @@ func TestPodSandboxStatus(t *testing.T) { sandboxContainers []container.Container injectMetadata bool injectErr error + injectIP bool + injectCNIErr error expectState runtime.PodSandboxState expectErr bool expectCalls []string + expectedCNICalls []string }{ "sandbox status without metadata": { - injectMetadata: false, - expectErr: true, - expectCalls: []string{}, + injectMetadata: false, + expectErr: true, + expectCalls: []string{}, + expectedCNICalls: []string{}, }, "sandbox status with running sandbox container": { sandboxContainers: []container.Container{{ @@ -131,9 +141,10 @@ func TestPodSandboxStatus(t *testing.T) { Pid: 1, Status: container.Status_RUNNING, }}, - injectMetadata: true, - expectState: runtime.PodSandboxState_SANDBOX_READY, - expectCalls: []string{"info"}, + injectMetadata: true, + expectState: runtime.PodSandboxState_SANDBOX_READY, + expectCalls: []string{"info"}, + expectedCNICalls: []string{"GetContainerNetworkStatus"}, }, "sandbox status with stopped sandbox container": { sandboxContainers: []container.Container{{ @@ -141,15 +152,17 @@ func TestPodSandboxStatus(t *testing.T) { Pid: 1, Status: container.Status_STOPPED, }}, - injectMetadata: true, - expectState: runtime.PodSandboxState_SANDBOX_NOTREADY, - expectCalls: []string{"info"}, + injectMetadata: true, + expectState: runtime.PodSandboxState_SANDBOX_NOTREADY, + expectCalls: []string{"info"}, + expectedCNICalls: []string{"GetContainerNetworkStatus"}, }, "sandbox status with non-existing sandbox container": { sandboxContainers: []container.Container{}, injectMetadata: true, expectState: runtime.PodSandboxState_SANDBOX_NOTREADY, expectCalls: []string{"info"}, + expectedCNICalls: []string{"GetContainerNetworkStatus"}, }, "sandbox status with arbitrary error": { sandboxContainers: []container.Container{{ @@ -157,16 +170,44 @@ func TestPodSandboxStatus(t *testing.T) { Pid: 1, Status: container.Status_RUNNING, }}, - injectMetadata: true, - injectErr: errors.New("arbitrary error"), - expectErr: true, - expectCalls: []string{"info"}, + injectMetadata: true, + expectState: runtime.PodSandboxState_SANDBOX_READY, + injectErr: errors.New("arbitrary error"), + expectErr: true, + expectCalls: []string{"info"}, + expectedCNICalls: []string{}, + }, + "sandbox status with IP address": { + sandboxContainers: []container.Container{{ + ID: sandboxStatusTestID, + Pid: 1, + Status: container.Status_RUNNING, + }}, + injectMetadata: true, + expectState: runtime.PodSandboxState_SANDBOX_READY, + expectCalls: []string{"info"}, + injectIP: true, + expectedCNICalls: []string{"GetContainerNetworkStatus"}, + }, + "sandbox status with GetContainerNetworkStatus returns error": { + sandboxContainers: []container.Container{{ + ID: sandboxStatusTestID, + Pid: 1, + Status: container.Status_RUNNING, + }}, + injectMetadata: true, + expectState: runtime.PodSandboxState_SANDBOX_READY, + expectCalls: []string{"info"}, + expectedCNICalls: []string{"GetContainerNetworkStatus"}, + injectCNIErr: errors.New("get container network status error"), }, } { t.Logf("TestCase %q", desc) metadata, expect := getSandboxStatusTestData() + expect.Network.Ip = "" c := newTestCRIContainerdService() fake := c.containerService.(*servertesting.FakeExecutionClient) + fakeCNIPlugin := c.netPlugin.(*servertesting.FakeCNIPlugin) fake.SetFakeContainers(test.sandboxContainers) if test.injectMetadata { assert.NoError(t, c.sandboxIDIndex.Add(metadata.ID)) @@ -175,18 +216,31 @@ func TestPodSandboxStatus(t *testing.T) { if test.injectErr != nil { fake.InjectError("info", test.injectErr) } + if test.injectCNIErr != nil { + fakeCNIPlugin.InjectError("GetContainerNetworkStatus", test.injectCNIErr) + } + if test.injectIP { + fakeCNIPlugin.SetFakePodNetwork(metadata.NetNS, metadata.Config.GetMetadata().GetNamespace(), + metadata.Config.GetMetadata().GetName(), sandboxStatusTestID, sandboxStatusTestIP) + expect.Network.Ip = sandboxStatusTestIP + } res, err := c.PodSandboxStatus(context.Background(), &runtime.PodSandboxStatusRequest{ PodSandboxId: sandboxStatusTestID, }) assert.Equal(t, test.expectCalls, fake.GetCalledNames()) + assert.Equal(t, test.expectedCNICalls, fakeCNIPlugin.GetCalledNames()) if test.expectErr { assert.Error(t, err) assert.Nil(t, res) continue } + assert.NoError(t, err) require.NotNil(t, res) expect.State = test.expectState + if expect.State == runtime.PodSandboxState_SANDBOX_NOTREADY { + expect.Linux.Namespaces.Network = "" + } assert.Equal(t, expect, res.GetStatus()) } } diff --git a/pkg/server/sandbox_stop_test.go b/pkg/server/sandbox_stop_test.go index 7efabb823..c8fa03152 100644 --- a/pkg/server/sandbox_stop_test.go +++ b/pkg/server/sandbox_stop_test.go @@ -17,6 +17,8 @@ limitations under the License. package server import ( + "errors" + "os" "testing" "github.com/stretchr/testify/assert" @@ -30,6 +32,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime" "github.com/kubernetes-incubator/cri-containerd/pkg/metadata" + ostesting "github.com/kubernetes-incubator/cri-containerd/pkg/os/testing" servertesting "github.com/kubernetes-incubator/cri-containerd/pkg/server/testing" ) @@ -38,6 +41,13 @@ func TestStopPodSandbox(t *testing.T) { testSandbox := metadata.SandboxMetadata{ ID: testID, Name: "test-name", + Config: &runtime.PodSandboxConfig{ + Metadata: &runtime.PodSandboxMetadata{ + Name: "test-name", + Uid: "test-uid", + Namespace: "test-ns", + }}, + NetNS: "test-netns", } testContainer := container.Container{ ID: testID, @@ -49,38 +59,71 @@ func TestStopPodSandbox(t *testing.T) { sandboxContainers []container.Container injectSandbox bool injectErr error + injectStatErr error + injectCNIErr error expectErr bool expectCalls []string + expectedCNICalls []string }{ "stop non-existing sandbox": { - injectSandbox: false, - expectErr: true, - expectCalls: []string{}, + injectSandbox: false, + expectErr: true, + expectCalls: []string{}, + expectedCNICalls: []string{}, }, "stop sandbox with sandbox container": { sandboxContainers: []container.Container{testContainer}, injectSandbox: true, expectErr: false, expectCalls: []string{"delete"}, + expectedCNICalls: []string{"TearDownPod"}, }, "stop sandbox with sandbox container not exist error": { sandboxContainers: []container.Container{}, injectSandbox: true, // Inject error to make sure fake execution client returns error. - injectErr: grpc.Errorf(codes.Unknown, containerd.ErrContainerNotExist.Error()), - expectErr: false, - expectCalls: []string{"delete"}, + injectErr: grpc.Errorf(codes.Unknown, containerd.ErrContainerNotExist.Error()), + expectErr: false, + expectCalls: []string{"delete"}, + expectedCNICalls: []string{"TearDownPod"}, }, "stop sandbox with with arbitrary error": { - injectSandbox: true, - injectErr: grpc.Errorf(codes.Unknown, "arbitrary error"), - expectErr: true, - expectCalls: []string{"delete"}, + injectSandbox: true, + injectErr: grpc.Errorf(codes.Unknown, "arbitrary error"), + expectErr: true, + expectCalls: []string{"delete"}, + expectedCNICalls: []string{"TearDownPod"}, + }, + "stop sandbox with Stat returns arbitrary error": { + sandboxContainers: []container.Container{testContainer}, + injectSandbox: true, + expectErr: true, + injectStatErr: errors.New("arbitrary error"), + expectCalls: []string{}, + expectedCNICalls: []string{}, + }, + "stop sandbox with Stat returns not exist error": { + sandboxContainers: []container.Container{testContainer}, + injectSandbox: true, + expectErr: false, + expectCalls: []string{"delete"}, + injectStatErr: os.ErrNotExist, + expectedCNICalls: []string{}, + }, + "stop sandbox with TearDownPod fails": { + sandboxContainers: []container.Container{testContainer}, + injectSandbox: true, + expectErr: true, + expectedCNICalls: []string{"TearDownPod"}, + injectCNIErr: errors.New("arbitrary error"), + expectCalls: []string{}, }, } { t.Logf("TestCase %q", desc) c := newTestCRIContainerdService() fake := c.containerService.(*servertesting.FakeExecutionClient) + fakeCNIPlugin := c.netPlugin.(*servertesting.FakeCNIPlugin) + fakeOS := c.os.(*ostesting.FakeOS) fake.SetFakeContainers(test.sandboxContainers) if test.injectSandbox { @@ -90,6 +133,14 @@ func TestStopPodSandbox(t *testing.T) { if test.injectErr != nil { fake.InjectError("delete", test.injectErr) } + if test.injectCNIErr != nil { + fakeCNIPlugin.InjectError("TearDownPod", test.injectCNIErr) + } + if test.injectStatErr != nil { + fakeOS.InjectError("Stat", test.injectStatErr) + } + fakeCNIPlugin.SetFakePodNetwork(testSandbox.NetNS, testSandbox.Config.GetMetadata().GetNamespace(), + testSandbox.Config.GetMetadata().GetName(), testID, sandboxStatusTestIP) res, err := c.StopPodSandbox(context.Background(), &runtime.StopPodSandboxRequest{ PodSandboxId: testID, @@ -102,5 +153,6 @@ func TestStopPodSandbox(t *testing.T) { assert.NotNil(t, res) } assert.Equal(t, test.expectCalls, fake.GetCalledNames()) + assert.Equal(t, test.expectedCNICalls, fakeCNIPlugin.GetCalledNames()) } } diff --git a/pkg/server/service_test.go b/pkg/server/service_test.go index c753815dd..b43153e2c 100644 --- a/pkg/server/service_test.go +++ b/pkg/server/service_test.go @@ -57,6 +57,7 @@ func newTestCRIContainerdService() *criContainerdService { sandboxIDIndex: truncindex.NewTruncIndex(nil), containerStore: metadata.NewContainerStore(store.NewMetadataStore()), containerNameIndex: registrar.NewRegistrar(), + netPlugin: servertesting.NewFakeCNIPlugin(), } } @@ -65,6 +66,7 @@ func TestSandboxOperations(t *testing.T) { c := newTestCRIContainerdService() fake := c.containerService.(*servertesting.FakeExecutionClient) fakeOS := c.os.(*ostesting.FakeOS) + fakeCNIPlugin := c.netPlugin.(*servertesting.FakeCNIPlugin) fakeOS.OpenFifoFn = func(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) { return nopReadWriteCloser{}, nil } @@ -89,6 +91,7 @@ func TestSandboxOperations(t *testing.T) { t.Logf("should be able to get pod sandbox status") info, err := fake.Info(context.Background(), &execution.InfoRequest{ID: id}) + netns := getNetworkNamespace(info.Pid) assert.NoError(t, err) expectSandboxStatus := &runtime.PodSandboxStatus{ Id: id, @@ -97,7 +100,7 @@ func TestSandboxOperations(t *testing.T) { Network: &runtime.PodSandboxNetworkStatus{}, Linux: &runtime.LinuxPodSandboxStatus{ Namespaces: &runtime.Namespace{ - Network: getNetworkNamespace(info.Pid), + Network: netns, Options: &runtime.NamespaceOption{ HostNetwork: false, HostPid: false, @@ -113,6 +116,9 @@ func TestSandboxOperations(t *testing.T) { require.NotNil(t, statusRes) status := statusRes.GetStatus() expectSandboxStatus.CreatedAt = status.GetCreatedAt() + ip, err := fakeCNIPlugin.GetContainerNetworkStatus(netns, config.GetMetadata().GetNamespace(), config.GetMetadata().GetName(), id) + assert.NoError(t, err) + expectSandboxStatus.Network.Ip = ip assert.Equal(t, expectSandboxStatus, status) t.Logf("should be able to list pod sandboxes") diff --git a/pkg/server/testing/fake_cni_plugin.go b/pkg/server/testing/fake_cni_plugin.go new file mode 100644 index 000000000..a41ba7339 --- /dev/null +++ b/pkg/server/testing/fake_cni_plugin.go @@ -0,0 +1,181 @@ +/* +Copyright 2017 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 testing + +import ( + "fmt" + "math/rand" + "strconv" + "sync" + "time" + + "github.com/kubernetes-incubator/cri-o/pkg/ocicni" +) + +// CNIPluginArgument is arguments used to call CNI related functions. +type CNIPluginArgument struct { + NetnsPath string + Namespace string + Name string + ContainerID string +} + +// FakeCNIPlugin is a fake plugin used for test. +type FakeCNIPlugin struct { + sync.Mutex + called []CalledDetail + errors map[string]error + IPMap map[CNIPluginArgument]string +} + +// getError get error for call +func (f *FakeCNIPlugin) getError(op string) error { + err, ok := f.errors[op] + if ok { + delete(f.errors, op) + return err + } + return nil +} + +// InjectError inject error for call +func (f *FakeCNIPlugin) InjectError(fn string, err error) { + f.Lock() + defer f.Unlock() + f.errors[fn] = err +} + +// InjectErrors inject errors for calls +func (f *FakeCNIPlugin) InjectErrors(errs map[string]error) { + f.Lock() + defer f.Unlock() + for fn, err := range errs { + f.errors[fn] = err + } +} + +// ClearErrors clear errors for call +func (f *FakeCNIPlugin) ClearErrors() { + f.Lock() + defer f.Unlock() + f.errors = make(map[string]error) +} + +func (f *FakeCNIPlugin) appendCalled(name string, argument interface{}) { + call := CalledDetail{Name: name, Argument: argument} + f.called = append(f.called, call) +} + +// GetCalledNames get names of call +func (f *FakeCNIPlugin) GetCalledNames() []string { + f.Lock() + defer f.Unlock() + names := []string{} + for _, detail := range f.called { + names = append(names, detail.Name) + } + return names +} + +// GetCalledDetails get detail of each call. +func (f *FakeCNIPlugin) GetCalledDetails() []CalledDetail { + f.Lock() + defer f.Unlock() + // Copy the list and return. + return append([]CalledDetail{}, f.called...) +} + +// SetFakePodNetwork sets the given IP for given arguments. +func (f *FakeCNIPlugin) SetFakePodNetwork(netnsPath string, namespace string, name string, containerID string, ip string) { + f.Lock() + defer f.Unlock() + arg := CNIPluginArgument{netnsPath, namespace, name, containerID} + f.IPMap[arg] = ip +} + +// NewFakeCNIPlugin create a FakeCNIPlugin. +func NewFakeCNIPlugin() ocicni.CNIPlugin { + return &FakeCNIPlugin{ + errors: make(map[string]error), + IPMap: make(map[CNIPluginArgument]string), + } +} + +// Name return the name of fake CNI plugin. +func (f *FakeCNIPlugin) Name() string { + return "fake-CNI-plugin" +} + +// SetUpPod setup the network of PodSandbox. +func (f *FakeCNIPlugin) SetUpPod(netnsPath string, namespace string, name string, containerID string) error { + f.Lock() + defer f.Unlock() + arg := CNIPluginArgument{netnsPath, namespace, name, containerID} + f.appendCalled("SetUpPod", arg) + if err := f.getError("SetUpPod"); err != nil { + return err + } + f.IPMap[arg] = generateIP() + return nil +} + +// TearDownPod teardown the network of PodSandbox. +func (f *FakeCNIPlugin) TearDownPod(netnsPath string, namespace string, name string, containerID string) error { + f.Lock() + defer f.Unlock() + arg := CNIPluginArgument{netnsPath, namespace, name, containerID} + f.appendCalled("TearDownPod", arg) + if err := f.getError("TearDownPod"); err != nil { + return err + } + _, ok := f.IPMap[arg] + if !ok { + return fmt.Errorf("failed to find the IP") + } + delete(f.IPMap, arg) + return nil +} + +// GetContainerNetworkStatus get the status of network. +func (f *FakeCNIPlugin) GetContainerNetworkStatus(netnsPath string, namespace string, name string, containerID string) (string, error) { + f.Lock() + defer f.Unlock() + arg := CNIPluginArgument{netnsPath, namespace, name, containerID} + f.appendCalled("GetContainerNetworkStatus", arg) + if err := f.getError("GetContainerNetworkStatus"); err != nil { + return "", err + } + ip, ok := f.IPMap[arg] + if !ok { + return "", fmt.Errorf("failed to find the IP") + } + return ip, nil +} + +// Status get the status of the plugin. +func (f *FakeCNIPlugin) Status() error { + return nil +} + +func generateIP() string { + rand.Seed(time.Now().Unix()) + p1 := strconv.Itoa(rand.Intn(266)) + p2 := strconv.Itoa(rand.Intn(266)) + p3 := strconv.Itoa(rand.Intn(266)) + p4 := strconv.Itoa(rand.Intn(266)) + return p1 + "." + p2 + "." + p3 + "." + p4 +}