1package containerd
2
3import (
4	"context"
5	"io/ioutil"
6	"os"
7	"sync"
8	"testing"
9	"time"
10
11	"github.com/docker/docker/libcontainerd"
12	"github.com/opencontainers/runtime-spec/specs-go"
13	"github.com/pkg/errors"
14	"gotest.tools/assert"
15)
16
17func TestLifeCycle(t *testing.T) {
18	t.Parallel()
19
20	mock := newMockClient()
21	exec, cleanup := setupTest(t, mock, mock)
22	defer cleanup()
23
24	id := "test-create"
25	mock.simulateStartError(true, id)
26	err := exec.Create(id, specs.Spec{}, nil, nil)
27	assert.Assert(t, err != nil)
28	mock.simulateStartError(false, id)
29
30	err = exec.Create(id, specs.Spec{}, nil, nil)
31	assert.Assert(t, err)
32	running, _ := exec.IsRunning(id)
33	assert.Assert(t, running)
34
35	// create with the same ID
36	err = exec.Create(id, specs.Spec{}, nil, nil)
37	assert.Assert(t, err != nil)
38
39	mock.HandleExitEvent(id) // simulate a plugin that exits
40
41	err = exec.Create(id, specs.Spec{}, nil, nil)
42	assert.Assert(t, err)
43}
44
45func setupTest(t *testing.T, client Client, eh ExitHandler) (*Executor, func()) {
46	rootDir, err := ioutil.TempDir("", "test-daemon")
47	assert.Assert(t, err)
48	assert.Assert(t, client != nil)
49	assert.Assert(t, eh != nil)
50
51	return &Executor{
52			rootDir:     rootDir,
53			client:      client,
54			exitHandler: eh,
55		}, func() {
56			assert.Assert(t, os.RemoveAll(rootDir))
57		}
58}
59
60type mockClient struct {
61	mu           sync.Mutex
62	containers   map[string]bool
63	errorOnStart map[string]bool
64}
65
66func newMockClient() *mockClient {
67	return &mockClient{
68		containers:   make(map[string]bool),
69		errorOnStart: make(map[string]bool),
70	}
71}
72
73func (c *mockClient) Create(ctx context.Context, id string, _ *specs.Spec, _ interface{}) error {
74	c.mu.Lock()
75	defer c.mu.Unlock()
76
77	if _, ok := c.containers[id]; ok {
78		return errors.New("exists")
79	}
80
81	c.containers[id] = false
82	return nil
83}
84
85func (c *mockClient) Restore(ctx context.Context, id string, attachStdio libcontainerd.StdioCallback) (alive bool, pid int, err error) {
86	return false, 0, nil
87}
88
89func (c *mockClient) Status(ctx context.Context, id string) (libcontainerd.Status, error) {
90	c.mu.Lock()
91	defer c.mu.Unlock()
92
93	running, ok := c.containers[id]
94	if !ok {
95		return libcontainerd.StatusUnknown, errors.New("not found")
96	}
97	if running {
98		return libcontainerd.StatusRunning, nil
99	}
100	return libcontainerd.StatusStopped, nil
101}
102
103func (c *mockClient) Delete(ctx context.Context, id string) error {
104	c.mu.Lock()
105	defer c.mu.Unlock()
106	delete(c.containers, id)
107	return nil
108}
109
110func (c *mockClient) DeleteTask(ctx context.Context, id string) (uint32, time.Time, error) {
111	return 0, time.Time{}, nil
112}
113
114func (c *mockClient) Start(ctx context.Context, id, checkpointDir string, withStdin bool, attachStdio libcontainerd.StdioCallback) (pid int, err error) {
115	c.mu.Lock()
116	defer c.mu.Unlock()
117
118	if _, ok := c.containers[id]; !ok {
119		return 0, errors.New("not found")
120	}
121
122	if c.errorOnStart[id] {
123		return 0, errors.New("some startup error")
124	}
125	c.containers[id] = true
126	return 1, nil
127}
128
129func (c *mockClient) SignalProcess(ctx context.Context, containerID, processID string, signal int) error {
130	return nil
131}
132
133func (c *mockClient) simulateStartError(sim bool, id string) {
134	c.mu.Lock()
135	defer c.mu.Unlock()
136	if sim {
137		c.errorOnStart[id] = sim
138		return
139	}
140	delete(c.errorOnStart, id)
141}
142
143func (c *mockClient) HandleExitEvent(id string) error {
144	c.mu.Lock()
145	defer c.mu.Unlock()
146	delete(c.containers, id)
147	return nil
148}
149