kuberuntime_container_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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 kuberuntime
  14. import (
  15. "fmt"
  16. "path/filepath"
  17. "strings"
  18. "testing"
  19. "time"
  20. "github.com/google/go-cmp/cmp"
  21. "github.com/stretchr/testify/assert"
  22. "github.com/stretchr/testify/require"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. v1 "k8s.io/api/core/v1"
  25. utilfeature "k8s.io/apiserver/pkg/util/feature"
  26. featuregatetesting "k8s.io/component-base/featuregate/testing"
  27. runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
  28. "k8s.io/kubernetes/pkg/features"
  29. kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
  30. containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
  31. "k8s.io/kubernetes/pkg/kubelet/lifecycle"
  32. )
  33. // TestRemoveContainer tests removing the container and its corresponding container logs.
  34. func TestRemoveContainer(t *testing.T) {
  35. fakeRuntime, _, m, err := createTestRuntimeManager()
  36. require.NoError(t, err)
  37. pod := &v1.Pod{
  38. ObjectMeta: metav1.ObjectMeta{
  39. UID: "12345678",
  40. Name: "bar",
  41. Namespace: "new",
  42. },
  43. Spec: v1.PodSpec{
  44. Containers: []v1.Container{
  45. {
  46. Name: "foo",
  47. Image: "busybox",
  48. ImagePullPolicy: v1.PullIfNotPresent,
  49. },
  50. },
  51. },
  52. }
  53. // Create fake sandbox and container
  54. _, fakeContainers := makeAndSetFakePod(t, m, fakeRuntime, pod)
  55. assert.Equal(t, len(fakeContainers), 1)
  56. containerID := fakeContainers[0].Id
  57. fakeOS := m.osInterface.(*containertest.FakeOS)
  58. err = m.removeContainer(containerID)
  59. assert.NoError(t, err)
  60. // Verify container log is removed
  61. expectedContainerLogPath := filepath.Join(podLogsRootDirectory, "new_bar_12345678", "foo", "0.log")
  62. expectedContainerLogSymlink := legacyLogSymlink(containerID, "foo", "bar", "new")
  63. assert.Equal(t, fakeOS.Removes, []string{expectedContainerLogPath, expectedContainerLogSymlink})
  64. // Verify container is removed
  65. assert.Contains(t, fakeRuntime.Called, "RemoveContainer")
  66. containers, err := fakeRuntime.ListContainers(&runtimeapi.ContainerFilter{Id: containerID})
  67. assert.NoError(t, err)
  68. assert.Empty(t, containers)
  69. }
  70. // TestKillContainer tests killing the container in a Pod.
  71. func TestKillContainer(t *testing.T) {
  72. _, _, m, _ := createTestRuntimeManager()
  73. tests := []struct {
  74. caseName string
  75. pod *v1.Pod
  76. containerID kubecontainer.ContainerID
  77. containerName string
  78. reason string
  79. gracePeriodOverride int64
  80. succeed bool
  81. }{
  82. {
  83. caseName: "Failed to find container in pods, expect to return error",
  84. pod: &v1.Pod{
  85. ObjectMeta: metav1.ObjectMeta{UID: "pod1_id", Name: "pod1", Namespace: "default"},
  86. Spec: v1.PodSpec{Containers: []v1.Container{{Name: "empty_container"}}},
  87. },
  88. containerID: kubecontainer.ContainerID{Type: "docker", ID: "not_exist_container_id"},
  89. containerName: "not_exist_container",
  90. reason: "unknown reason",
  91. gracePeriodOverride: 0,
  92. succeed: false,
  93. },
  94. }
  95. for _, test := range tests {
  96. err := m.killContainer(test.pod, test.containerID, test.containerName, test.reason, &test.gracePeriodOverride)
  97. if test.succeed != (err == nil) {
  98. t.Errorf("%s: expected %v, got %v (%v)", test.caseName, test.succeed, (err == nil), err)
  99. }
  100. }
  101. }
  102. // TestToKubeContainerStatus tests the converting the CRI container status to
  103. // the internal type (i.e., toKubeContainerStatus()) for containers in
  104. // different states.
  105. func TestToKubeContainerStatus(t *testing.T) {
  106. cid := &kubecontainer.ContainerID{Type: "testRuntime", ID: "dummyid"}
  107. meta := &runtimeapi.ContainerMetadata{Name: "cname", Attempt: 3}
  108. imageSpec := &runtimeapi.ImageSpec{Image: "fimage"}
  109. var (
  110. createdAt int64 = 327
  111. startedAt int64 = 999
  112. finishedAt int64 = 1278
  113. )
  114. for desc, test := range map[string]struct {
  115. input *runtimeapi.ContainerStatus
  116. expected *kubecontainer.ContainerStatus
  117. }{
  118. "created container": {
  119. input: &runtimeapi.ContainerStatus{
  120. Id: cid.ID,
  121. Metadata: meta,
  122. Image: imageSpec,
  123. State: runtimeapi.ContainerState_CONTAINER_CREATED,
  124. CreatedAt: createdAt,
  125. },
  126. expected: &kubecontainer.ContainerStatus{
  127. ID: *cid,
  128. Image: imageSpec.Image,
  129. State: kubecontainer.ContainerStateCreated,
  130. CreatedAt: time.Unix(0, createdAt),
  131. },
  132. },
  133. "running container": {
  134. input: &runtimeapi.ContainerStatus{
  135. Id: cid.ID,
  136. Metadata: meta,
  137. Image: imageSpec,
  138. State: runtimeapi.ContainerState_CONTAINER_RUNNING,
  139. CreatedAt: createdAt,
  140. StartedAt: startedAt,
  141. },
  142. expected: &kubecontainer.ContainerStatus{
  143. ID: *cid,
  144. Image: imageSpec.Image,
  145. State: kubecontainer.ContainerStateRunning,
  146. CreatedAt: time.Unix(0, createdAt),
  147. StartedAt: time.Unix(0, startedAt),
  148. },
  149. },
  150. "exited container": {
  151. input: &runtimeapi.ContainerStatus{
  152. Id: cid.ID,
  153. Metadata: meta,
  154. Image: imageSpec,
  155. State: runtimeapi.ContainerState_CONTAINER_EXITED,
  156. CreatedAt: createdAt,
  157. StartedAt: startedAt,
  158. FinishedAt: finishedAt,
  159. ExitCode: int32(121),
  160. Reason: "GotKilled",
  161. Message: "The container was killed",
  162. },
  163. expected: &kubecontainer.ContainerStatus{
  164. ID: *cid,
  165. Image: imageSpec.Image,
  166. State: kubecontainer.ContainerStateExited,
  167. CreatedAt: time.Unix(0, createdAt),
  168. StartedAt: time.Unix(0, startedAt),
  169. FinishedAt: time.Unix(0, finishedAt),
  170. ExitCode: 121,
  171. Reason: "GotKilled",
  172. Message: "The container was killed",
  173. },
  174. },
  175. "unknown container": {
  176. input: &runtimeapi.ContainerStatus{
  177. Id: cid.ID,
  178. Metadata: meta,
  179. Image: imageSpec,
  180. State: runtimeapi.ContainerState_CONTAINER_UNKNOWN,
  181. CreatedAt: createdAt,
  182. StartedAt: startedAt,
  183. },
  184. expected: &kubecontainer.ContainerStatus{
  185. ID: *cid,
  186. Image: imageSpec.Image,
  187. State: kubecontainer.ContainerStateUnknown,
  188. CreatedAt: time.Unix(0, createdAt),
  189. StartedAt: time.Unix(0, startedAt),
  190. },
  191. },
  192. } {
  193. actual := toKubeContainerStatus(test.input, cid.Type)
  194. assert.Equal(t, test.expected, actual, desc)
  195. }
  196. }
  197. func TestLifeCycleHook(t *testing.T) {
  198. // Setup
  199. fakeRuntime, _, m, _ := createTestRuntimeManager()
  200. gracePeriod := int64(30)
  201. cID := kubecontainer.ContainerID{
  202. Type: "docker",
  203. ID: "foo",
  204. }
  205. testPod := &v1.Pod{
  206. ObjectMeta: metav1.ObjectMeta{
  207. Name: "bar",
  208. Namespace: "default",
  209. },
  210. Spec: v1.PodSpec{
  211. Containers: []v1.Container{
  212. {
  213. Name: "foo",
  214. Image: "busybox",
  215. ImagePullPolicy: v1.PullIfNotPresent,
  216. Command: []string{"testCommand"},
  217. WorkingDir: "testWorkingDir",
  218. },
  219. },
  220. },
  221. }
  222. cmdPostStart := &v1.Lifecycle{
  223. PostStart: &v1.Handler{
  224. Exec: &v1.ExecAction{
  225. Command: []string{"PostStartCMD"},
  226. },
  227. },
  228. }
  229. httpLifeCycle := &v1.Lifecycle{
  230. PreStop: &v1.Handler{
  231. HTTPGet: &v1.HTTPGetAction{
  232. Host: "testHost.com",
  233. Path: "/GracefulExit",
  234. },
  235. },
  236. }
  237. cmdLifeCycle := &v1.Lifecycle{
  238. PreStop: &v1.Handler{
  239. Exec: &v1.ExecAction{
  240. Command: []string{"PreStopCMD"},
  241. },
  242. },
  243. }
  244. fakeRunner := &containertest.FakeContainerCommandRunner{}
  245. fakeHTTP := &fakeHTTP{}
  246. lcHanlder := lifecycle.NewHandlerRunner(
  247. fakeHTTP,
  248. fakeRunner,
  249. nil)
  250. m.runner = lcHanlder
  251. // Configured and works as expected
  252. t.Run("PreStop-CMDExec", func(t *testing.T) {
  253. testPod.Spec.Containers[0].Lifecycle = cmdLifeCycle
  254. m.killContainer(testPod, cID, "foo", "testKill", &gracePeriod)
  255. if fakeRunner.Cmd[0] != cmdLifeCycle.PreStop.Exec.Command[0] {
  256. t.Errorf("CMD Prestop hook was not invoked")
  257. }
  258. })
  259. // Configured and working HTTP hook
  260. t.Run("PreStop-HTTPGet", func(t *testing.T) {
  261. defer func() { fakeHTTP.url = "" }()
  262. testPod.Spec.Containers[0].Lifecycle = httpLifeCycle
  263. m.killContainer(testPod, cID, "foo", "testKill", &gracePeriod)
  264. if !strings.Contains(fakeHTTP.url, httpLifeCycle.PreStop.HTTPGet.Host) {
  265. t.Errorf("HTTP Prestop hook was not invoked")
  266. }
  267. })
  268. // When there is no time to run PreStopHook
  269. t.Run("PreStop-NoTimeToRun", func(t *testing.T) {
  270. gracePeriodLocal := int64(0)
  271. testPod.DeletionGracePeriodSeconds = &gracePeriodLocal
  272. testPod.Spec.TerminationGracePeriodSeconds = &gracePeriodLocal
  273. m.killContainer(testPod, cID, "foo", "testKill", &gracePeriodLocal)
  274. if strings.Contains(fakeHTTP.url, httpLifeCycle.PreStop.HTTPGet.Host) {
  275. t.Errorf("HTTP Should not execute when gracePeriod is 0")
  276. }
  277. })
  278. // Post Start script
  279. t.Run("PostStart-CmdExe", func(t *testing.T) {
  280. // Fake all the things you need before trying to create a container
  281. fakeSandBox, _ := makeAndSetFakePod(t, m, fakeRuntime, testPod)
  282. fakeSandBoxConfig, _ := m.generatePodSandboxConfig(testPod, 0)
  283. testPod.Spec.Containers[0].Lifecycle = cmdPostStart
  284. testContainer := &testPod.Spec.Containers[0]
  285. fakePodStatus := &kubecontainer.PodStatus{
  286. ContainerStatuses: []*kubecontainer.ContainerStatus{
  287. {
  288. ID: kubecontainer.ContainerID{
  289. Type: "docker",
  290. ID: testContainer.Name,
  291. },
  292. Name: testContainer.Name,
  293. State: kubecontainer.ContainerStateCreated,
  294. CreatedAt: time.Unix(0, time.Now().Unix()),
  295. },
  296. },
  297. }
  298. // Now try to create a container, which should in turn invoke PostStart Hook
  299. _, err := m.startContainer(fakeSandBox.Id, fakeSandBoxConfig, containerStartSpec(testContainer), testPod, fakePodStatus, nil, "", []string{})
  300. if err != nil {
  301. t.Errorf("startContainer error =%v", err)
  302. }
  303. if fakeRunner.Cmd[0] != cmdPostStart.PostStart.Exec.Command[0] {
  304. t.Errorf("CMD PostStart hook was not invoked")
  305. }
  306. })
  307. }
  308. func TestStartSpec(t *testing.T) {
  309. podStatus := &kubecontainer.PodStatus{
  310. ContainerStatuses: []*kubecontainer.ContainerStatus{
  311. {
  312. ID: kubecontainer.ContainerID{
  313. Type: "docker",
  314. ID: "docker-something-something",
  315. },
  316. Name: "target",
  317. },
  318. },
  319. }
  320. for _, tc := range []struct {
  321. name string
  322. spec *startSpec
  323. want *kubecontainer.ContainerID
  324. }{
  325. {
  326. "Regular Container",
  327. containerStartSpec(&v1.Container{
  328. Name: "test",
  329. }),
  330. nil,
  331. },
  332. {
  333. "Ephemeral Container w/o Target",
  334. ephemeralContainerStartSpec(&v1.EphemeralContainer{
  335. EphemeralContainerCommon: v1.EphemeralContainerCommon{
  336. Name: "test",
  337. },
  338. }),
  339. nil,
  340. },
  341. {
  342. "Ephemeral Container w/ Target",
  343. ephemeralContainerStartSpec(&v1.EphemeralContainer{
  344. EphemeralContainerCommon: v1.EphemeralContainerCommon{
  345. Name: "test",
  346. },
  347. TargetContainerName: "target",
  348. }),
  349. &kubecontainer.ContainerID{
  350. Type: "docker",
  351. ID: "docker-something-something",
  352. },
  353. },
  354. } {
  355. t.Run(tc.name, func(t *testing.T) {
  356. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
  357. if got, err := tc.spec.getTargetID(podStatus); err != nil {
  358. t.Fatalf("%v: getTargetID got unexpected error: %v", t.Name(), err)
  359. } else if diff := cmp.Diff(tc.want, got); diff != "" {
  360. t.Errorf("%v: getTargetID got unexpected result. diff:\n%v", t.Name(), diff)
  361. }
  362. })
  363. // Test with feature disabled in self-contained section which can be removed when feature flag is removed.
  364. t.Run(fmt.Sprintf("%s (disabled)", tc.name), func(t *testing.T) {
  365. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, false)()
  366. if got, err := tc.spec.getTargetID(podStatus); err != nil {
  367. t.Fatalf("%v: getTargetID got unexpected error: %v", t.Name(), err)
  368. } else if got != nil {
  369. t.Errorf("%v: getTargetID got: %v, wanted nil", t.Name(), got)
  370. }
  371. })
  372. }
  373. }