diff --git a/pkg/server/sandbox_run_test.go b/pkg/server/sandbox_run_test.go index 217db5aa6..39ff714e8 100644 --- a/pkg/server/sandbox_run_test.go +++ b/pkg/server/sandbox_run_test.go @@ -23,20 +23,22 @@ import ( "syscall" "testing" + "github.com/containerd/containerd/api/services/execution" + rootfsapi "github.com/containerd/containerd/api/services/rootfs" + imagedigest "github.com/opencontainers/go-digest" + imagespec "github.com/opencontainers/image-spec/specs-go/v1" + runtimespec "github.com/opencontainers/runtime-spec/specs-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/net/context" + runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1" - "github.com/containerd/containerd/api/services/execution" - runtimespec "github.com/opencontainers/runtime-spec/specs-go" - + "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" - - runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1" ) -func getRunPodSandboxTestData() (*runtime.PodSandboxConfig, func(*testing.T, string, *runtimespec.Spec)) { +func getRunPodSandboxTestData() (*runtime.PodSandboxConfig, *imagespec.ImageConfig, func(*testing.T, string, *runtimespec.Spec)) { config := &runtime.PodSandboxConfig{ Metadata: &runtime.PodSandboxMetadata{ Name: "test-name", @@ -52,20 +54,31 @@ func getRunPodSandboxTestData() (*runtime.PodSandboxConfig, func(*testing.T, str CgroupParent: "/test/cgroup/parent", }, } + imageConfig := &imagespec.ImageConfig{ + Env: []string{"a=b", "c=d"}, + Entrypoint: []string{"/pause"}, + Cmd: []string{"forever"}, + WorkingDir: "/workspace", + } specCheck := func(t *testing.T, id string, spec *runtimespec.Spec) { assert.Equal(t, "test-hostname", spec.Hostname) assert.Equal(t, getCgroupsPath("/test/cgroup/parent", id), spec.Linux.CgroupsPath) assert.Equal(t, relativeRootfsPath, spec.Root.Path) assert.Equal(t, true, spec.Root.Readonly) + assert.Contains(t, spec.Process.Env, "a=b", "c=d") + assert.Equal(t, []string{"/pause", "forever"}, spec.Process.Args) + assert.Equal(t, "/workspace", spec.Process.Cwd) } - return config, specCheck + return config, imageConfig, specCheck } func TestGenerateSandboxContainerSpec(t *testing.T) { testID := "test-id" for desc, test := range map[string]struct { - configChange func(*runtime.PodSandboxConfig) - specCheck func(*testing.T, *runtimespec.Spec) + configChange func(*runtime.PodSandboxConfig) + imageConfigChange func(*imagespec.ImageConfig) + specCheck func(*testing.T, *runtimespec.Spec) + expectErr bool }{ "spec should reflect original config": { specCheck: func(t *testing.T, spec *runtimespec.Spec) { @@ -106,14 +119,36 @@ func TestGenerateSandboxContainerSpec(t *testing.T) { }) }, }, + "should return error when entrypoint is empty": { + imageConfigChange: func(c *imagespec.ImageConfig) { + c.Entrypoint = nil + }, + expectErr: true, + }, + "should return error when env is invalid ": { + imageConfigChange: func(c *imagespec.ImageConfig) { + c.Env = []string{"a"} + }, + expectErr: true, + }, } { t.Logf("TestCase %q", desc) c := newTestCRIContainerdService() - config, specCheck := getRunPodSandboxTestData() + config, imageConfig, specCheck := getRunPodSandboxTestData() if test.configChange != nil { test.configChange(config) } - spec := c.generateSandboxContainerSpec(testID, config) + if test.imageConfigChange != nil { + test.imageConfigChange(imageConfig) + } + spec, err := c.generateSandboxContainerSpec(testID, config, imageConfig) + if test.expectErr { + assert.Error(t, err) + assert.Nil(t, spec) + continue + } + assert.NoError(t, err) + assert.NotNil(t, spec) specCheck(t, testID, spec) if test.specCheck != nil { test.specCheck(t, spec) @@ -122,8 +157,9 @@ func TestGenerateSandboxContainerSpec(t *testing.T) { } func TestRunPodSandbox(t *testing.T) { - config, specCheck := getRunPodSandboxTestData() + config, imageConfig, specCheck := getRunPodSandboxTestData() c := newTestCRIContainerdService() + fakeRootfsClient := c.rootfsService.(*servertesting.FakeRootfsClient) fakeExecutionClient := c.containerService.(*servertesting.FakeExecutionClient) fakeCNIPlugin := c.netPlugin.(*servertesting.FakeCNIPlugin) fakeOS := c.os.(*ostesting.FakeOS) @@ -140,6 +176,17 @@ func TestRunPodSandbox(t *testing.T) { assert.Equal(t, os.FileMode(0700), perm) return nopReadWriteCloser{}, nil } + testChainID := imagedigest.Digest("test-sandbox-chain-id") + imageMetadata := metadata.ImageMetadata{ + ID: testSandboxImage, + ChainID: testChainID.String(), + Config: imageConfig, + } + // Insert sandbox image metadata. + assert.NoError(t, c.imageMetadataStore.Create(imageMetadata)) + // Insert fake chainID + fakeRootfsClient.SetFakeChainIDs([]imagedigest.Digest{testChainID}) + expectRootfsClientCalls := []string{"prepare"} expectExecutionClientCalls := []string{"create", "start"} res, err := c.RunPodSandbox(context.Background(), &runtime.RunPodSandboxRequest{Config: config}) @@ -155,13 +202,24 @@ 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, expectRootfsClientCalls, fakeRootfsClient.GetCalledNames(), "expect rootfs functions should be called") + calls := fakeRootfsClient.GetCalledDetails() + prepareOpts := calls[0].Argument.(*rootfsapi.PrepareRequest) + assert.Equal(t, &rootfsapi.PrepareRequest{ + Name: id, + ChainID: testChainID, + Readonly: true, + }, prepareOpts, "prepare request should be correct") + assert.Equal(t, expectExecutionClientCalls, fakeExecutionClient.GetCalledNames(), "expect containerd functions should be called") - calls := fakeExecutionClient.GetCalledDetails() + 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. assert.Equal(t, stdout, createOpts.Stdout, "stdout pipe should be passed to containerd") assert.Equal(t, stderr, createOpts.Stderr, "stderr pipe should be passed to containerd") + mountsResp, err := fakeRootfsClient.Mounts(context.Background(), &rootfsapi.MountsRequest{Name: id}) + assert.NoError(t, err) + assert.Equal(t, mountsResp.Mounts, createOpts.Rootfs, "rootfs mount should be correct") spec := &runtimespec.Spec{} assert.NoError(t, json.Unmarshal(createOpts.Spec.Value, spec)) t.Logf("oci spec check") diff --git a/pkg/server/service_test.go b/pkg/server/service_test.go index 5b6bffcae..8b73f6fd4 100644 --- a/pkg/server/service_test.go +++ b/pkg/server/service_test.go @@ -32,6 +32,8 @@ import ( ostesting "github.com/kubernetes-incubator/cri-containerd/pkg/os/testing" "github.com/kubernetes-incubator/cri-containerd/pkg/registrar" servertesting "github.com/kubernetes-incubator/cri-containerd/pkg/server/testing" + imagedigest "github.com/opencontainers/go-digest" + imagespec "github.com/opencontainers/image-spec/specs-go/v1" runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1" ) @@ -43,20 +45,28 @@ func (nopReadWriteCloser) Read(p []byte) (n int, err error) { return 0, io.EOF func (nopReadWriteCloser) Write(p []byte) (n int, err error) { return 0, io.ErrShortWrite } func (nopReadWriteCloser) Close() error { return nil } -const testRootDir = "/test/rootfs" +const ( + testRootDir = "/test/rootfs" + // Use an image id as test sandbox image to avoid image name resolve. + // TODO(random-liu): Change this to image name after we have complete image + // management unit test framework. + testSandboxImage = "sha256:c75bebcdd211f41b3a460c7bf82970ed6c75acaab9cd4c9a4e125b03ca113798" +) // newTestCRIContainerdService creates a fake criContainerdService for test. func newTestCRIContainerdService() *criContainerdService { return &criContainerdService{ os: ostesting.NewFakeOS(), rootDir: testRootDir, - containerService: servertesting.NewFakeExecutionClient(), + sandboxImage: testSandboxImage, sandboxStore: metadata.NewSandboxStore(store.NewMetadataStore()), imageMetadataStore: metadata.NewImageMetadataStore(store.NewMetadataStore()), sandboxNameIndex: registrar.NewRegistrar(), sandboxIDIndex: truncindex.NewTruncIndex(nil), containerStore: metadata.NewContainerStore(store.NewMetadataStore()), containerNameIndex: registrar.NewRegistrar(), + containerService: servertesting.NewFakeExecutionClient(), + rootfsService: servertesting.NewFakeRootfsClient(), netPlugin: servertesting.NewFakeCNIPlugin(), } } @@ -65,11 +75,21 @@ func newTestCRIContainerdService() *criContainerdService { func TestSandboxOperations(t *testing.T) { c := newTestCRIContainerdService() fake := c.containerService.(*servertesting.FakeExecutionClient) + fakeRootfsClient := c.rootfsService.(*servertesting.FakeRootfsClient) 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 } + // Insert sandbox image metadata. + assert.NoError(t, c.imageMetadataStore.Create(metadata.ImageMetadata{ + ID: testSandboxImage, + ChainID: "test-chain-id", + Config: &imagespec.ImageConfig{Entrypoint: []string{"/pause"}}, + })) + // Insert fake chainID + fakeRootfsClient.SetFakeChainIDs([]imagedigest.Digest{imagedigest.Digest("test-chain-id")}) + config := &runtime.PodSandboxConfig{ Metadata: &runtime.PodSandboxMetadata{ Name: "test-name", diff --git a/pkg/server/testing/fake_rootfs_client.go b/pkg/server/testing/fake_rootfs_client.go index f9bea8f57..da10522a7 100644 --- a/pkg/server/testing/fake_rootfs_client.go +++ b/pkg/server/testing/fake_rootfs_client.go @@ -169,7 +169,11 @@ func (f *FakeRootfsClient) Prepare(ctx context.Context, prepareOpts *rootfs.Prep if ok { return nil, fmt.Errorf("mounts already exist") } - f.MountList[prepareOpts.Name] = []*mount.Mount{} + f.MountList[prepareOpts.Name] = []*mount.Mount{{ + Type: "bind", + Source: prepareOpts.Name, + // TODO(random-liu): Fake options based on Readonly option. + }} return &rootfs.MountResponse{ Mounts: f.MountList[prepareOpts.Name], }, nil