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