kuberuntime_container_linux_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. // +build linux
  2. /*
  3. Copyright 2018 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package kuberuntime
  15. import (
  16. "reflect"
  17. "testing"
  18. "github.com/google/go-cmp/cmp"
  19. cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
  20. "github.com/stretchr/testify/assert"
  21. v1 "k8s.io/api/core/v1"
  22. "k8s.io/apimachinery/pkg/api/resource"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. utilfeature "k8s.io/apiserver/pkg/util/feature"
  25. featuregatetesting "k8s.io/component-base/featuregate/testing"
  26. runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
  27. "k8s.io/kubernetes/pkg/features"
  28. kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
  29. )
  30. func makeExpectedConfig(m *kubeGenericRuntimeManager, pod *v1.Pod, containerIndex int) *runtimeapi.ContainerConfig {
  31. container := &pod.Spec.Containers[containerIndex]
  32. podIP := ""
  33. restartCount := 0
  34. opts, _, _ := m.runtimeHelper.GenerateRunContainerOptions(pod, container, podIP, []string{podIP})
  35. containerLogsPath := buildContainerLogsPath(container.Name, restartCount)
  36. restartCountUint32 := uint32(restartCount)
  37. envs := make([]*runtimeapi.KeyValue, len(opts.Envs))
  38. expectedConfig := &runtimeapi.ContainerConfig{
  39. Metadata: &runtimeapi.ContainerMetadata{
  40. Name: container.Name,
  41. Attempt: restartCountUint32,
  42. },
  43. Image: &runtimeapi.ImageSpec{Image: container.Image},
  44. Command: container.Command,
  45. Args: []string(nil),
  46. WorkingDir: container.WorkingDir,
  47. Labels: newContainerLabels(container, pod),
  48. Annotations: newContainerAnnotations(container, pod, restartCount, opts),
  49. Devices: makeDevices(opts),
  50. Mounts: m.makeMounts(opts, container),
  51. LogPath: containerLogsPath,
  52. Stdin: container.Stdin,
  53. StdinOnce: container.StdinOnce,
  54. Tty: container.TTY,
  55. Linux: m.generateLinuxContainerConfig(container, pod, new(int64), "", nil),
  56. Envs: envs,
  57. }
  58. return expectedConfig
  59. }
  60. func TestGenerateContainerConfig(t *testing.T) {
  61. _, imageService, m, err := createTestRuntimeManager()
  62. assert.NoError(t, err)
  63. runAsUser := int64(1000)
  64. runAsGroup := int64(2000)
  65. pod := &v1.Pod{
  66. ObjectMeta: metav1.ObjectMeta{
  67. UID: "12345678",
  68. Name: "bar",
  69. Namespace: "new",
  70. },
  71. Spec: v1.PodSpec{
  72. Containers: []v1.Container{
  73. {
  74. Name: "foo",
  75. Image: "busybox",
  76. ImagePullPolicy: v1.PullIfNotPresent,
  77. Command: []string{"testCommand"},
  78. WorkingDir: "testWorkingDir",
  79. SecurityContext: &v1.SecurityContext{
  80. RunAsUser: &runAsUser,
  81. RunAsGroup: &runAsGroup,
  82. },
  83. },
  84. },
  85. },
  86. }
  87. expectedConfig := makeExpectedConfig(m, pod, 0)
  88. containerConfig, _, err := m.generateContainerConfig(&pod.Spec.Containers[0], pod, 0, "", pod.Spec.Containers[0].Image, []string{}, nil)
  89. assert.NoError(t, err)
  90. assert.Equal(t, expectedConfig, containerConfig, "generate container config for kubelet runtime v1.")
  91. assert.Equal(t, runAsUser, containerConfig.GetLinux().GetSecurityContext().GetRunAsUser().GetValue(), "RunAsUser should be set")
  92. assert.Equal(t, runAsGroup, containerConfig.GetLinux().GetSecurityContext().GetRunAsGroup().GetValue(), "RunAsGroup should be set")
  93. runAsRoot := int64(0)
  94. runAsNonRootTrue := true
  95. podWithContainerSecurityContext := &v1.Pod{
  96. ObjectMeta: metav1.ObjectMeta{
  97. UID: "12345678",
  98. Name: "bar",
  99. Namespace: "new",
  100. },
  101. Spec: v1.PodSpec{
  102. Containers: []v1.Container{
  103. {
  104. Name: "foo",
  105. Image: "busybox",
  106. ImagePullPolicy: v1.PullIfNotPresent,
  107. Command: []string{"testCommand"},
  108. WorkingDir: "testWorkingDir",
  109. SecurityContext: &v1.SecurityContext{
  110. RunAsNonRoot: &runAsNonRootTrue,
  111. RunAsUser: &runAsRoot,
  112. },
  113. },
  114. },
  115. },
  116. }
  117. _, _, err = m.generateContainerConfig(&podWithContainerSecurityContext.Spec.Containers[0], podWithContainerSecurityContext, 0, "", podWithContainerSecurityContext.Spec.Containers[0].Image, []string{}, nil)
  118. assert.Error(t, err)
  119. imageID, _ := imageService.PullImage(&runtimeapi.ImageSpec{Image: "busybox"}, nil, nil)
  120. image, _ := imageService.ImageStatus(&runtimeapi.ImageSpec{Image: imageID})
  121. image.Uid = nil
  122. image.Username = "test"
  123. podWithContainerSecurityContext.Spec.Containers[0].SecurityContext.RunAsUser = nil
  124. podWithContainerSecurityContext.Spec.Containers[0].SecurityContext.RunAsNonRoot = &runAsNonRootTrue
  125. _, _, err = m.generateContainerConfig(&podWithContainerSecurityContext.Spec.Containers[0], podWithContainerSecurityContext, 0, "", podWithContainerSecurityContext.Spec.Containers[0].Image, []string{}, nil)
  126. assert.Error(t, err, "RunAsNonRoot should fail for non-numeric username")
  127. }
  128. func TestGetHugepageLimitsFromResources(t *testing.T) {
  129. var baseHugepage []*runtimeapi.HugepageLimit
  130. // For each page size, limit to 0.
  131. for _, pageSize := range cgroupfs.HugePageSizes {
  132. baseHugepage = append(baseHugepage, &runtimeapi.HugepageLimit{
  133. PageSize: pageSize,
  134. Limit: uint64(0),
  135. })
  136. }
  137. tests := []struct {
  138. name string
  139. resources v1.ResourceRequirements
  140. expected []*runtimeapi.HugepageLimit
  141. }{
  142. {
  143. name: "Success2MB",
  144. resources: v1.ResourceRequirements{
  145. Limits: v1.ResourceList{
  146. "hugepages-2Mi": resource.MustParse("2Mi"),
  147. },
  148. },
  149. expected: []*runtimeapi.HugepageLimit{
  150. {
  151. PageSize: "2MB",
  152. Limit: 2097152,
  153. },
  154. },
  155. },
  156. {
  157. name: "Success1GB",
  158. resources: v1.ResourceRequirements{
  159. Limits: v1.ResourceList{
  160. "hugepages-1Gi": resource.MustParse("2Gi"),
  161. },
  162. },
  163. expected: []*runtimeapi.HugepageLimit{
  164. {
  165. PageSize: "1GB",
  166. Limit: 2147483648,
  167. },
  168. },
  169. },
  170. {
  171. name: "Skip2MB",
  172. resources: v1.ResourceRequirements{
  173. Limits: v1.ResourceList{
  174. "hugepages-2MB": resource.MustParse("2Mi"),
  175. },
  176. },
  177. expected: []*runtimeapi.HugepageLimit{
  178. {
  179. PageSize: "2MB",
  180. Limit: 0,
  181. },
  182. },
  183. },
  184. {
  185. name: "Skip1GB",
  186. resources: v1.ResourceRequirements{
  187. Limits: v1.ResourceList{
  188. "hugepages-1GB": resource.MustParse("2Gi"),
  189. },
  190. },
  191. expected: []*runtimeapi.HugepageLimit{
  192. {
  193. PageSize: "1GB",
  194. Limit: 0,
  195. },
  196. },
  197. },
  198. {
  199. name: "Success2MBand1GB",
  200. resources: v1.ResourceRequirements{
  201. Limits: v1.ResourceList{
  202. v1.ResourceName(v1.ResourceCPU): resource.MustParse("0"),
  203. "hugepages-2Mi": resource.MustParse("2Mi"),
  204. "hugepages-1Gi": resource.MustParse("2Gi"),
  205. },
  206. },
  207. expected: []*runtimeapi.HugepageLimit{
  208. {
  209. PageSize: "2MB",
  210. Limit: 2097152,
  211. },
  212. {
  213. PageSize: "1GB",
  214. Limit: 2147483648,
  215. },
  216. },
  217. },
  218. {
  219. name: "Skip2MBand1GB",
  220. resources: v1.ResourceRequirements{
  221. Limits: v1.ResourceList{
  222. v1.ResourceName(v1.ResourceCPU): resource.MustParse("0"),
  223. "hugepages-2MB": resource.MustParse("2Mi"),
  224. "hugepages-1GB": resource.MustParse("2Gi"),
  225. },
  226. },
  227. expected: []*runtimeapi.HugepageLimit{
  228. {
  229. PageSize: "2MB",
  230. Limit: 0,
  231. },
  232. {
  233. PageSize: "1GB",
  234. Limit: 0,
  235. },
  236. },
  237. },
  238. }
  239. for _, test := range tests {
  240. // Validate if machine supports hugepage size that used in test case.
  241. machineHugepageSupport := true
  242. for _, hugepageLimit := range test.expected {
  243. hugepageSupport := false
  244. for _, pageSize := range cgroupfs.HugePageSizes {
  245. if pageSize == hugepageLimit.PageSize {
  246. hugepageSupport = true
  247. break
  248. }
  249. }
  250. if !hugepageSupport {
  251. machineHugepageSupport = false
  252. break
  253. }
  254. }
  255. // Case of machine can't support hugepage size
  256. if !machineHugepageSupport {
  257. continue
  258. }
  259. expectedHugepages := baseHugepage
  260. for _, hugepage := range test.expected {
  261. for _, expectedHugepage := range expectedHugepages {
  262. if expectedHugepage.PageSize == hugepage.PageSize {
  263. expectedHugepage.Limit = hugepage.Limit
  264. }
  265. }
  266. }
  267. results := GetHugepageLimitsFromResources(test.resources)
  268. if !reflect.DeepEqual(expectedHugepages, results) {
  269. t.Errorf("%s test failed. Expected %v but got %v", test.name, expectedHugepages, results)
  270. }
  271. for _, hugepage := range baseHugepage {
  272. hugepage.Limit = uint64(0)
  273. }
  274. }
  275. }
  276. func TestGenerateLinuxContainerConfigNamespaces(t *testing.T) {
  277. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
  278. _, _, m, err := createTestRuntimeManager()
  279. if err != nil {
  280. t.Fatalf("error creating test RuntimeManager: %v", err)
  281. }
  282. for _, tc := range []struct {
  283. name string
  284. pod *v1.Pod
  285. target *kubecontainer.ContainerID
  286. want *runtimeapi.NamespaceOption
  287. }{
  288. {
  289. "Default namespaces",
  290. &v1.Pod{
  291. Spec: v1.PodSpec{
  292. Containers: []v1.Container{
  293. {Name: "test"},
  294. },
  295. },
  296. },
  297. nil,
  298. &runtimeapi.NamespaceOption{
  299. Pid: runtimeapi.NamespaceMode_CONTAINER,
  300. },
  301. },
  302. {
  303. "PID Namespace POD",
  304. &v1.Pod{
  305. Spec: v1.PodSpec{
  306. Containers: []v1.Container{
  307. {Name: "test"},
  308. },
  309. ShareProcessNamespace: &[]bool{true}[0],
  310. },
  311. },
  312. nil,
  313. &runtimeapi.NamespaceOption{
  314. Pid: runtimeapi.NamespaceMode_POD,
  315. },
  316. },
  317. {
  318. "PID Namespace TARGET",
  319. &v1.Pod{
  320. Spec: v1.PodSpec{
  321. Containers: []v1.Container{
  322. {Name: "test"},
  323. },
  324. },
  325. },
  326. &kubecontainer.ContainerID{Type: "docker", ID: "really-long-id-string"},
  327. &runtimeapi.NamespaceOption{
  328. Pid: runtimeapi.NamespaceMode_TARGET,
  329. TargetId: "really-long-id-string",
  330. },
  331. },
  332. } {
  333. t.Run(tc.name, func(t *testing.T) {
  334. got := m.generateLinuxContainerConfig(&tc.pod.Spec.Containers[0], tc.pod, nil, "", tc.target)
  335. if diff := cmp.Diff(tc.want, got.SecurityContext.NamespaceOptions); diff != "" {
  336. t.Errorf("%v: diff (-want +got):\n%v", t.Name(), diff)
  337. }
  338. })
  339. }
  340. }