1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package server
18
19import (
20	"testing"
21	"time"
22
23	"github.com/stretchr/testify/assert"
24	"github.com/stretchr/testify/require"
25	"golang.org/x/net/context"
26	runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
27
28	containerstore "github.com/containerd/containerd/pkg/cri/store/container"
29	sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
30)
31
32func TestToCRIContainer(t *testing.T) {
33	config := &runtime.ContainerConfig{
34		Metadata: &runtime.ContainerMetadata{
35			Name:    "test-name",
36			Attempt: 1,
37		},
38		Image:       &runtime.ImageSpec{Image: "test-image"},
39		Labels:      map[string]string{"a": "b"},
40		Annotations: map[string]string{"c": "d"},
41	}
42	createdAt := time.Now().UnixNano()
43	container, err := containerstore.NewContainer(
44		containerstore.Metadata{
45			ID:        "test-id",
46			Name:      "test-name",
47			SandboxID: "test-sandbox-id",
48			Config:    config,
49			ImageRef:  "test-image-ref",
50		},
51		containerstore.WithFakeStatus(
52			containerstore.Status{
53				Pid:        1234,
54				CreatedAt:  createdAt,
55				StartedAt:  time.Now().UnixNano(),
56				FinishedAt: time.Now().UnixNano(),
57				ExitCode:   1,
58				Reason:     "test-reason",
59				Message:    "test-message",
60			},
61		),
62	)
63	assert.NoError(t, err)
64	expect := &runtime.Container{
65		Id:           "test-id",
66		PodSandboxId: "test-sandbox-id",
67		Metadata:     config.GetMetadata(),
68		Image:        config.GetImage(),
69		ImageRef:     "test-image-ref",
70		State:        runtime.ContainerState_CONTAINER_EXITED,
71		CreatedAt:    createdAt,
72		Labels:       config.GetLabels(),
73		Annotations:  config.GetAnnotations(),
74	}
75	c := toCRIContainer(container)
76	assert.Equal(t, expect, c)
77}
78
79func TestFilterContainers(t *testing.T) {
80	c := newTestCRIService()
81
82	testContainers := []*runtime.Container{
83		{
84			Id:           "1",
85			PodSandboxId: "s-1",
86			Metadata:     &runtime.ContainerMetadata{Name: "name-1", Attempt: 1},
87			State:        runtime.ContainerState_CONTAINER_RUNNING,
88		},
89		{
90			Id:           "2",
91			PodSandboxId: "s-2",
92			Metadata:     &runtime.ContainerMetadata{Name: "name-2", Attempt: 2},
93			State:        runtime.ContainerState_CONTAINER_EXITED,
94			Labels:       map[string]string{"a": "b"},
95		},
96		{
97			Id:           "3",
98			PodSandboxId: "s-2",
99			Metadata:     &runtime.ContainerMetadata{Name: "name-2", Attempt: 3},
100			State:        runtime.ContainerState_CONTAINER_CREATED,
101			Labels:       map[string]string{"c": "d"},
102		},
103	}
104	for desc, test := range map[string]struct {
105		filter *runtime.ContainerFilter
106		expect []*runtime.Container
107	}{
108		"no filter": {
109			expect: testContainers,
110		},
111		"id filter": {
112			filter: &runtime.ContainerFilter{Id: "2"},
113			expect: []*runtime.Container{testContainers[1]},
114		},
115		"state filter": {
116			filter: &runtime.ContainerFilter{
117				State: &runtime.ContainerStateValue{
118					State: runtime.ContainerState_CONTAINER_EXITED,
119				},
120			},
121			expect: []*runtime.Container{testContainers[1]},
122		},
123		"label filter": {
124			filter: &runtime.ContainerFilter{
125				LabelSelector: map[string]string{"a": "b"},
126			},
127			expect: []*runtime.Container{testContainers[1]},
128		},
129		"sandbox id filter": {
130			filter: &runtime.ContainerFilter{PodSandboxId: "s-2"},
131			expect: []*runtime.Container{testContainers[1], testContainers[2]},
132		},
133		"mixed filter not matched": {
134			filter: &runtime.ContainerFilter{
135				Id:            "1",
136				PodSandboxId:  "s-2",
137				LabelSelector: map[string]string{"a": "b"},
138			},
139			expect: []*runtime.Container{},
140		},
141		"mixed filter matched": {
142			filter: &runtime.ContainerFilter{
143				PodSandboxId: "s-2",
144				State: &runtime.ContainerStateValue{
145					State: runtime.ContainerState_CONTAINER_CREATED,
146				},
147				LabelSelector: map[string]string{"c": "d"},
148			},
149			expect: []*runtime.Container{testContainers[2]},
150		},
151	} {
152		filtered := c.filterCRIContainers(testContainers, test.filter)
153		assert.Equal(t, test.expect, filtered, desc)
154	}
155}
156
157// containerForTest is a helper type for test.
158type containerForTest struct {
159	metadata containerstore.Metadata
160	status   containerstore.Status
161}
162
163func (c containerForTest) toContainer() (containerstore.Container, error) {
164	return containerstore.NewContainer(
165		c.metadata,
166		containerstore.WithFakeStatus(c.status),
167	)
168}
169
170func TestListContainers(t *testing.T) {
171	c := newTestCRIService()
172	sandboxesInStore := []sandboxstore.Sandbox{
173		sandboxstore.NewSandbox(
174			sandboxstore.Metadata{
175				ID:     "s-1abcdef1234",
176				Name:   "sandboxname-1",
177				Config: &runtime.PodSandboxConfig{Metadata: &runtime.PodSandboxMetadata{Name: "podname-1"}},
178			},
179			sandboxstore.Status{
180				State: sandboxstore.StateReady,
181			},
182		),
183		sandboxstore.NewSandbox(
184			sandboxstore.Metadata{
185				ID:     "s-2abcdef1234",
186				Name:   "sandboxname-2",
187				Config: &runtime.PodSandboxConfig{Metadata: &runtime.PodSandboxMetadata{Name: "podname-2"}},
188			},
189			sandboxstore.Status{
190				State: sandboxstore.StateNotReady,
191			},
192		),
193	}
194	createdAt := time.Now().UnixNano()
195	startedAt := time.Now().UnixNano()
196	finishedAt := time.Now().UnixNano()
197	containersInStore := []containerForTest{
198		{
199			metadata: containerstore.Metadata{
200				ID:        "c-1container",
201				Name:      "name-1",
202				SandboxID: "s-1abcdef1234",
203				Config:    &runtime.ContainerConfig{Metadata: &runtime.ContainerMetadata{Name: "name-1"}},
204			},
205			status: containerstore.Status{CreatedAt: createdAt},
206		},
207		{
208			metadata: containerstore.Metadata{
209				ID:        "c-2container",
210				Name:      "name-2",
211				SandboxID: "s-1abcdef1234",
212				Config:    &runtime.ContainerConfig{Metadata: &runtime.ContainerMetadata{Name: "name-2"}},
213			},
214			status: containerstore.Status{
215				CreatedAt: createdAt,
216				StartedAt: startedAt,
217			},
218		},
219		{
220			metadata: containerstore.Metadata{
221				ID:        "c-3container",
222				Name:      "name-3",
223				SandboxID: "s-1abcdef1234",
224				Config:    &runtime.ContainerConfig{Metadata: &runtime.ContainerMetadata{Name: "name-3"}},
225			},
226			status: containerstore.Status{
227				CreatedAt:  createdAt,
228				StartedAt:  startedAt,
229				FinishedAt: finishedAt,
230			},
231		},
232		{
233			metadata: containerstore.Metadata{
234				ID:        "c-4container",
235				Name:      "name-4",
236				SandboxID: "s-2abcdef1234",
237				Config:    &runtime.ContainerConfig{Metadata: &runtime.ContainerMetadata{Name: "name-4"}},
238			},
239			status: containerstore.Status{
240				CreatedAt: createdAt,
241			},
242		},
243	}
244
245	expectedContainers := []*runtime.Container{
246		{
247			Id:           "c-1container",
248			PodSandboxId: "s-1abcdef1234",
249			Metadata:     &runtime.ContainerMetadata{Name: "name-1"},
250			State:        runtime.ContainerState_CONTAINER_CREATED,
251			CreatedAt:    createdAt,
252		},
253		{
254			Id:           "c-2container",
255			PodSandboxId: "s-1abcdef1234",
256			Metadata:     &runtime.ContainerMetadata{Name: "name-2"},
257			State:        runtime.ContainerState_CONTAINER_RUNNING,
258			CreatedAt:    createdAt,
259		},
260		{
261			Id:           "c-3container",
262			PodSandboxId: "s-1abcdef1234",
263			Metadata:     &runtime.ContainerMetadata{Name: "name-3"},
264			State:        runtime.ContainerState_CONTAINER_EXITED,
265			CreatedAt:    createdAt,
266		},
267		{
268			Id:           "c-4container",
269			PodSandboxId: "s-2abcdef1234",
270			Metadata:     &runtime.ContainerMetadata{Name: "name-4"},
271			State:        runtime.ContainerState_CONTAINER_CREATED,
272			CreatedAt:    createdAt,
273		},
274	}
275
276	// Inject test sandbox metadata
277	for _, sb := range sandboxesInStore {
278		assert.NoError(t, c.sandboxStore.Add(sb))
279	}
280
281	// Inject test container metadata
282	for _, cntr := range containersInStore {
283		container, err := cntr.toContainer()
284		assert.NoError(t, err)
285		assert.NoError(t, c.containerStore.Add(container))
286	}
287
288	for testdesc, testdata := range map[string]struct {
289		filter *runtime.ContainerFilter
290		expect []*runtime.Container
291	}{
292		"test without filter": {
293			filter: &runtime.ContainerFilter{},
294			expect: expectedContainers,
295		},
296		"test filter by sandboxid": {
297			filter: &runtime.ContainerFilter{
298				PodSandboxId: "s-1abcdef1234",
299			},
300			expect: expectedContainers[:3],
301		},
302		"test filter by truncated sandboxid": {
303			filter: &runtime.ContainerFilter{
304				PodSandboxId: "s-1",
305			},
306			expect: expectedContainers[:3],
307		},
308		"test filter by containerid": {
309			filter: &runtime.ContainerFilter{
310				Id: "c-1container",
311			},
312			expect: expectedContainers[:1],
313		},
314		"test filter by truncated containerid": {
315			filter: &runtime.ContainerFilter{
316				Id: "c-1",
317			},
318			expect: expectedContainers[:1],
319		},
320		"test filter by containerid and sandboxid": {
321			filter: &runtime.ContainerFilter{
322				Id:           "c-1container",
323				PodSandboxId: "s-1abcdef1234",
324			},
325			expect: expectedContainers[:1],
326		},
327		"test filter by truncated containerid and truncated sandboxid": {
328			filter: &runtime.ContainerFilter{
329				Id:           "c-1",
330				PodSandboxId: "s-1",
331			},
332			expect: expectedContainers[:1],
333		},
334	} {
335		t.Logf("TestCase: %s", testdesc)
336		resp, err := c.ListContainers(context.Background(), &runtime.ListContainersRequest{Filter: testdata.filter})
337		assert.NoError(t, err)
338		require.NotNil(t, resp)
339		containers := resp.GetContainers()
340		assert.Len(t, containers, len(testdata.expect))
341		for _, cntr := range testdata.expect {
342			assert.Contains(t, containers, cntr)
343		}
344	}
345}
346