docker_container_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package dockershim
  14. import (
  15. "context"
  16. "fmt"
  17. "path/filepath"
  18. "strings"
  19. "testing"
  20. "time"
  21. dockertypes "github.com/docker/docker/api/types"
  22. "github.com/stretchr/testify/assert"
  23. "github.com/stretchr/testify/require"
  24. runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
  25. containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
  26. )
  27. // A helper to create a basic config.
  28. func makeContainerConfig(sConfig *runtimeapi.PodSandboxConfig, name, image string, attempt uint32, labels, annotations map[string]string) *runtimeapi.ContainerConfig {
  29. return &runtimeapi.ContainerConfig{
  30. Metadata: &runtimeapi.ContainerMetadata{
  31. Name: name,
  32. Attempt: attempt,
  33. },
  34. Image: &runtimeapi.ImageSpec{Image: image},
  35. Labels: labels,
  36. Annotations: annotations,
  37. }
  38. }
  39. func getTestCTX() context.Context {
  40. return context.Background()
  41. }
  42. // TestListContainers creates several containers and then list them to check
  43. // whether the correct metadatas, states, and labels are returned.
  44. func TestListContainers(t *testing.T) {
  45. ds, _, fakeClock := newTestDockerService()
  46. podName, namespace := "foo", "bar"
  47. containerName, image := "sidecar", "logger"
  48. configs := []*runtimeapi.ContainerConfig{}
  49. sConfigs := []*runtimeapi.PodSandboxConfig{}
  50. for i := 0; i < 3; i++ {
  51. s := makeSandboxConfig(fmt.Sprintf("%s%d", podName, i),
  52. fmt.Sprintf("%s%d", namespace, i), fmt.Sprintf("%d", i), 0)
  53. labels := map[string]string{"abc.xyz": fmt.Sprintf("label%d", i)}
  54. annotations := map[string]string{"foo.bar.baz": fmt.Sprintf("annotation%d", i)}
  55. c := makeContainerConfig(s, fmt.Sprintf("%s%d", containerName, i),
  56. fmt.Sprintf("%s:v%d", image, i), uint32(i), labels, annotations)
  57. sConfigs = append(sConfigs, s)
  58. configs = append(configs, c)
  59. }
  60. expected := []*runtimeapi.Container{}
  61. state := runtimeapi.ContainerState_CONTAINER_RUNNING
  62. var createdAt int64 = fakeClock.Now().UnixNano()
  63. for i := range configs {
  64. // We don't care about the sandbox id; pass a bogus one.
  65. sandboxID := fmt.Sprintf("sandboxid%d", i)
  66. req := &runtimeapi.CreateContainerRequest{PodSandboxId: sandboxID, Config: configs[i], SandboxConfig: sConfigs[i]}
  67. createResp, err := ds.CreateContainer(getTestCTX(), req)
  68. require.NoError(t, err)
  69. id := createResp.ContainerId
  70. _, err = ds.StartContainer(getTestCTX(), &runtimeapi.StartContainerRequest{ContainerId: id})
  71. require.NoError(t, err)
  72. imageRef := "" // FakeDockerClient doesn't populate ImageRef yet.
  73. // Prepend to the expected list because ListContainers returns
  74. // the most recent containers first.
  75. expected = append([]*runtimeapi.Container{{
  76. Metadata: configs[i].Metadata,
  77. Id: id,
  78. PodSandboxId: sandboxID,
  79. State: state,
  80. CreatedAt: createdAt,
  81. Image: configs[i].Image,
  82. ImageRef: imageRef,
  83. Labels: configs[i].Labels,
  84. Annotations: configs[i].Annotations,
  85. }}, expected...)
  86. }
  87. listResp, err := ds.ListContainers(getTestCTX(), &runtimeapi.ListContainersRequest{})
  88. require.NoError(t, err)
  89. assert.Len(t, listResp.Containers, len(expected))
  90. assert.Equal(t, expected, listResp.Containers)
  91. }
  92. // TestContainerStatus tests the basic lifecycle operations and verify that
  93. // the status returned reflects the operations performed.
  94. func TestContainerStatus(t *testing.T) {
  95. ds, fDocker, fClock := newTestDockerService()
  96. sConfig := makeSandboxConfig("foo", "bar", "1", 0)
  97. labels := map[string]string{"abc.xyz": "foo"}
  98. annotations := map[string]string{"foo.bar.baz": "abc"}
  99. imageName := "iamimage"
  100. config := makeContainerConfig(sConfig, "pause", imageName, 0, labels, annotations)
  101. var defaultTime time.Time
  102. dt := defaultTime.UnixNano()
  103. ct, st, ft := dt, dt, dt
  104. state := runtimeapi.ContainerState_CONTAINER_CREATED
  105. imageRef := DockerImageIDPrefix + imageName
  106. // The following variables are not set in FakeDockerClient.
  107. exitCode := int32(0)
  108. var reason, message string
  109. expected := &runtimeapi.ContainerStatus{
  110. State: state,
  111. CreatedAt: ct,
  112. StartedAt: st,
  113. FinishedAt: ft,
  114. Metadata: config.Metadata,
  115. Image: config.Image,
  116. ImageRef: imageRef,
  117. ExitCode: exitCode,
  118. Reason: reason,
  119. Message: message,
  120. Mounts: []*runtimeapi.Mount{},
  121. Labels: config.Labels,
  122. Annotations: config.Annotations,
  123. }
  124. fDocker.InjectImages([]dockertypes.ImageSummary{{ID: imageName}})
  125. // Create the container.
  126. fClock.SetTime(time.Now().Add(-1 * time.Hour))
  127. expected.CreatedAt = fClock.Now().UnixNano()
  128. const sandboxId = "sandboxid"
  129. req := &runtimeapi.CreateContainerRequest{PodSandboxId: sandboxId, Config: config, SandboxConfig: sConfig}
  130. createResp, err := ds.CreateContainer(getTestCTX(), req)
  131. require.NoError(t, err)
  132. id := createResp.ContainerId
  133. // Check internal labels
  134. c, err := fDocker.InspectContainer(id)
  135. require.NoError(t, err)
  136. assert.Equal(t, c.Config.Labels[containerTypeLabelKey], containerTypeLabelContainer)
  137. assert.Equal(t, c.Config.Labels[sandboxIDLabelKey], sandboxId)
  138. // Set the id manually since we don't know the id until it's created.
  139. expected.Id = id
  140. assert.NoError(t, err)
  141. resp, err := ds.ContainerStatus(getTestCTX(), &runtimeapi.ContainerStatusRequest{ContainerId: id})
  142. require.NoError(t, err)
  143. assert.Equal(t, expected, resp.Status)
  144. // Advance the clock and start the container.
  145. fClock.SetTime(time.Now())
  146. expected.StartedAt = fClock.Now().UnixNano()
  147. expected.State = runtimeapi.ContainerState_CONTAINER_RUNNING
  148. _, err = ds.StartContainer(getTestCTX(), &runtimeapi.StartContainerRequest{ContainerId: id})
  149. require.NoError(t, err)
  150. resp, err = ds.ContainerStatus(getTestCTX(), &runtimeapi.ContainerStatusRequest{ContainerId: id})
  151. require.NoError(t, err)
  152. assert.Equal(t, expected, resp.Status)
  153. // Advance the clock and stop the container.
  154. fClock.SetTime(time.Now().Add(1 * time.Hour))
  155. expected.FinishedAt = fClock.Now().UnixNano()
  156. expected.State = runtimeapi.ContainerState_CONTAINER_EXITED
  157. expected.Reason = "Completed"
  158. _, err = ds.StopContainer(getTestCTX(), &runtimeapi.StopContainerRequest{ContainerId: id, Timeout: int64(0)})
  159. assert.NoError(t, err)
  160. resp, err = ds.ContainerStatus(getTestCTX(), &runtimeapi.ContainerStatusRequest{ContainerId: id})
  161. require.NoError(t, err)
  162. assert.Equal(t, expected, resp.Status)
  163. // Remove the container.
  164. _, err = ds.RemoveContainer(getTestCTX(), &runtimeapi.RemoveContainerRequest{ContainerId: id})
  165. require.NoError(t, err)
  166. resp, err = ds.ContainerStatus(getTestCTX(), &runtimeapi.ContainerStatusRequest{ContainerId: id})
  167. assert.Error(t, err, fmt.Sprintf("status of container: %+v", resp))
  168. }
  169. // TestContainerLogPath tests the container log creation logic.
  170. func TestContainerLogPath(t *testing.T) {
  171. ds, fDocker, _ := newTestDockerService()
  172. podLogPath := "/pod/1"
  173. containerLogPath := "0"
  174. kubeletContainerLogPath := filepath.Join(podLogPath, containerLogPath)
  175. sConfig := makeSandboxConfig("foo", "bar", "1", 0)
  176. sConfig.LogDirectory = podLogPath
  177. config := makeContainerConfig(sConfig, "pause", "iamimage", 0, nil, nil)
  178. config.LogPath = containerLogPath
  179. const sandboxId = "sandboxid"
  180. req := &runtimeapi.CreateContainerRequest{PodSandboxId: sandboxId, Config: config, SandboxConfig: sConfig}
  181. createResp, err := ds.CreateContainer(getTestCTX(), req)
  182. require.NoError(t, err)
  183. id := createResp.ContainerId
  184. // Check internal container log label
  185. c, err := fDocker.InspectContainer(id)
  186. assert.NoError(t, err)
  187. assert.Equal(t, c.Config.Labels[containerLogPathLabelKey], kubeletContainerLogPath)
  188. // Set docker container log path
  189. dockerContainerLogPath := "/docker/container/log"
  190. c.LogPath = dockerContainerLogPath
  191. // Verify container log symlink creation
  192. fakeOS := ds.os.(*containertest.FakeOS)
  193. fakeOS.SymlinkFn = func(oldname, newname string) error {
  194. assert.Equal(t, dockerContainerLogPath, oldname)
  195. assert.Equal(t, kubeletContainerLogPath, newname)
  196. return nil
  197. }
  198. _, err = ds.StartContainer(getTestCTX(), &runtimeapi.StartContainerRequest{ContainerId: id})
  199. require.NoError(t, err)
  200. _, err = ds.StopContainer(getTestCTX(), &runtimeapi.StopContainerRequest{ContainerId: id, Timeout: int64(0)})
  201. require.NoError(t, err)
  202. // Verify container log symlink deletion
  203. // symlink is also tentatively deleted at startup
  204. _, err = ds.RemoveContainer(getTestCTX(), &runtimeapi.RemoveContainerRequest{ContainerId: id})
  205. require.NoError(t, err)
  206. assert.Equal(t, []string{kubeletContainerLogPath, kubeletContainerLogPath}, fakeOS.Removes)
  207. }
  208. // TestContainerCreationConflict tests the logic to work around docker container
  209. // creation naming conflict bug.
  210. func TestContainerCreationConflict(t *testing.T) {
  211. sConfig := makeSandboxConfig("foo", "bar", "1", 0)
  212. config := makeContainerConfig(sConfig, "pause", "iamimage", 0, map[string]string{}, map[string]string{})
  213. containerName := makeContainerName(sConfig, config)
  214. const sandboxId = "sandboxid"
  215. const containerId = "containerid"
  216. conflictError := fmt.Errorf("Error response from daemon: Conflict. The name \"/%s\" is already in use by container %q. You have to remove (or rename) that container to be able to reuse that name.",
  217. containerName, containerId)
  218. noContainerError := fmt.Errorf("Error response from daemon: No such container: %s", containerId)
  219. randomError := fmt.Errorf("random error")
  220. for desc, test := range map[string]struct {
  221. createError error
  222. removeError error
  223. expectError error
  224. expectCalls []string
  225. expectFields int
  226. }{
  227. "no create error": {
  228. expectCalls: []string{"create"},
  229. expectFields: 6,
  230. },
  231. "random create error": {
  232. createError: randomError,
  233. expectError: randomError,
  234. expectCalls: []string{"create"},
  235. },
  236. "conflict create error with successful remove": {
  237. createError: conflictError,
  238. expectError: conflictError,
  239. expectCalls: []string{"create", "remove"},
  240. },
  241. "conflict create error with random remove error": {
  242. createError: conflictError,
  243. removeError: randomError,
  244. expectError: conflictError,
  245. expectCalls: []string{"create", "remove"},
  246. },
  247. "conflict create error with no such container remove error": {
  248. createError: conflictError,
  249. removeError: noContainerError,
  250. expectCalls: []string{"create", "remove", "create"},
  251. expectFields: 7,
  252. },
  253. } {
  254. t.Logf("TestCase: %s", desc)
  255. ds, fDocker, _ := newTestDockerService()
  256. if test.createError != nil {
  257. fDocker.InjectError("create", test.createError)
  258. }
  259. if test.removeError != nil {
  260. fDocker.InjectError("remove", test.removeError)
  261. }
  262. req := &runtimeapi.CreateContainerRequest{PodSandboxId: sandboxId, Config: config, SandboxConfig: sConfig}
  263. createResp, err := ds.CreateContainer(getTestCTX(), req)
  264. require.Equal(t, test.expectError, err)
  265. assert.NoError(t, fDocker.AssertCalls(test.expectCalls))
  266. if err == nil {
  267. c, err := fDocker.InspectContainer(createResp.ContainerId)
  268. assert.NoError(t, err)
  269. assert.Len(t, strings.Split(c.Name, nameDelimiter), test.expectFields)
  270. }
  271. }
  272. }