util_test.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. /*
  2. Copyright 2015 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 pod
  14. import (
  15. "reflect"
  16. "strings"
  17. "testing"
  18. "time"
  19. "github.com/stretchr/testify/assert"
  20. v1 "k8s.io/api/core/v1"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/util/intstr"
  23. "k8s.io/apimachinery/pkg/util/sets"
  24. "k8s.io/apimachinery/pkg/util/validation/field"
  25. utilfeature "k8s.io/apiserver/pkg/util/feature"
  26. featuregatetesting "k8s.io/component-base/featuregate/testing"
  27. "k8s.io/kubernetes/pkg/features"
  28. )
  29. func TestFindPort(t *testing.T) {
  30. testCases := []struct {
  31. name string
  32. containers []v1.Container
  33. port intstr.IntOrString
  34. expected int
  35. pass bool
  36. }{{
  37. name: "valid int, no ports",
  38. containers: []v1.Container{{}},
  39. port: intstr.FromInt(93),
  40. expected: 93,
  41. pass: true,
  42. }, {
  43. name: "valid int, with ports",
  44. containers: []v1.Container{{Ports: []v1.ContainerPort{{
  45. Name: "",
  46. ContainerPort: 11,
  47. Protocol: "TCP",
  48. }, {
  49. Name: "p",
  50. ContainerPort: 22,
  51. Protocol: "TCP",
  52. }}}},
  53. port: intstr.FromInt(93),
  54. expected: 93,
  55. pass: true,
  56. }, {
  57. name: "valid str, no ports",
  58. containers: []v1.Container{{}},
  59. port: intstr.FromString("p"),
  60. expected: 0,
  61. pass: false,
  62. }, {
  63. name: "valid str, one ctr with ports",
  64. containers: []v1.Container{{Ports: []v1.ContainerPort{{
  65. Name: "",
  66. ContainerPort: 11,
  67. Protocol: "UDP",
  68. }, {
  69. Name: "p",
  70. ContainerPort: 22,
  71. Protocol: "TCP",
  72. }, {
  73. Name: "q",
  74. ContainerPort: 33,
  75. Protocol: "TCP",
  76. }}}},
  77. port: intstr.FromString("q"),
  78. expected: 33,
  79. pass: true,
  80. }, {
  81. name: "valid str, two ctr with ports",
  82. containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
  83. Name: "",
  84. ContainerPort: 11,
  85. Protocol: "UDP",
  86. }, {
  87. Name: "p",
  88. ContainerPort: 22,
  89. Protocol: "TCP",
  90. }, {
  91. Name: "q",
  92. ContainerPort: 33,
  93. Protocol: "TCP",
  94. }}}},
  95. port: intstr.FromString("q"),
  96. expected: 33,
  97. pass: true,
  98. }, {
  99. name: "valid str, two ctr with same port",
  100. containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
  101. Name: "",
  102. ContainerPort: 11,
  103. Protocol: "UDP",
  104. }, {
  105. Name: "p",
  106. ContainerPort: 22,
  107. Protocol: "TCP",
  108. }, {
  109. Name: "q",
  110. ContainerPort: 22,
  111. Protocol: "TCP",
  112. }}}},
  113. port: intstr.FromString("q"),
  114. expected: 22,
  115. pass: true,
  116. }, {
  117. name: "valid str, invalid protocol",
  118. containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
  119. Name: "a",
  120. ContainerPort: 11,
  121. Protocol: "snmp",
  122. },
  123. }}},
  124. port: intstr.FromString("a"),
  125. expected: 0,
  126. pass: false,
  127. }, {
  128. name: "valid hostPort",
  129. containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
  130. Name: "a",
  131. ContainerPort: 11,
  132. HostPort: 81,
  133. Protocol: "TCP",
  134. },
  135. }}},
  136. port: intstr.FromString("a"),
  137. expected: 11,
  138. pass: true,
  139. },
  140. {
  141. name: "invalid hostPort",
  142. containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
  143. Name: "a",
  144. ContainerPort: 11,
  145. HostPort: -1,
  146. Protocol: "TCP",
  147. },
  148. }}},
  149. port: intstr.FromString("a"),
  150. expected: 11,
  151. pass: true,
  152. //this should fail but passes.
  153. },
  154. {
  155. name: "invalid ContainerPort",
  156. containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
  157. Name: "a",
  158. ContainerPort: -1,
  159. Protocol: "TCP",
  160. },
  161. }}},
  162. port: intstr.FromString("a"),
  163. expected: -1,
  164. pass: true,
  165. //this should fail but passes
  166. },
  167. {
  168. name: "HostIP Address",
  169. containers: []v1.Container{{}, {Ports: []v1.ContainerPort{{
  170. Name: "a",
  171. ContainerPort: 11,
  172. HostIP: "192.168.1.1",
  173. Protocol: "TCP",
  174. },
  175. }}},
  176. port: intstr.FromString("a"),
  177. expected: 11,
  178. pass: true,
  179. },
  180. }
  181. for _, tc := range testCases {
  182. port, err := FindPort(&v1.Pod{Spec: v1.PodSpec{Containers: tc.containers}},
  183. &v1.ServicePort{Protocol: "TCP", TargetPort: tc.port})
  184. if err != nil && tc.pass {
  185. t.Errorf("unexpected error for %s: %v", tc.name, err)
  186. }
  187. if err == nil && !tc.pass {
  188. t.Errorf("unexpected non-error for %s: %d", tc.name, port)
  189. }
  190. if port != tc.expected {
  191. t.Errorf("wrong result for %s: expected %d, got %d", tc.name, tc.expected, port)
  192. }
  193. }
  194. }
  195. func TestVisitContainers(t *testing.T) {
  196. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
  197. testCases := []struct {
  198. description string
  199. haveSpec *v1.PodSpec
  200. wantNames []string
  201. }{
  202. {
  203. "empty podspec",
  204. &v1.PodSpec{},
  205. []string{},
  206. },
  207. {
  208. "regular containers",
  209. &v1.PodSpec{
  210. Containers: []v1.Container{
  211. {Name: "c1"},
  212. {Name: "c2"},
  213. },
  214. },
  215. []string{"c1", "c2"},
  216. },
  217. {
  218. "init containers",
  219. &v1.PodSpec{
  220. InitContainers: []v1.Container{
  221. {Name: "i1"},
  222. {Name: "i2"},
  223. },
  224. },
  225. []string{"i1", "i2"},
  226. },
  227. {
  228. "regular and init containers",
  229. &v1.PodSpec{
  230. Containers: []v1.Container{
  231. {Name: "c1"},
  232. {Name: "c2"},
  233. },
  234. InitContainers: []v1.Container{
  235. {Name: "i1"},
  236. {Name: "i2"},
  237. },
  238. },
  239. []string{"i1", "i2", "c1", "c2"},
  240. },
  241. {
  242. "ephemeral containers",
  243. &v1.PodSpec{
  244. Containers: []v1.Container{
  245. {Name: "c1"},
  246. {Name: "c2"},
  247. },
  248. EphemeralContainers: []v1.EphemeralContainer{
  249. {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}},
  250. },
  251. },
  252. []string{"c1", "c2", "e1"},
  253. },
  254. {
  255. "all container types",
  256. &v1.PodSpec{
  257. Containers: []v1.Container{
  258. {Name: "c1"},
  259. {Name: "c2"},
  260. },
  261. InitContainers: []v1.Container{
  262. {Name: "i1"},
  263. {Name: "i2"},
  264. },
  265. EphemeralContainers: []v1.EphemeralContainer{
  266. {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}},
  267. {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2"}},
  268. },
  269. },
  270. []string{"i1", "i2", "c1", "c2", "e1", "e2"},
  271. },
  272. {
  273. "dropping fields",
  274. &v1.PodSpec{
  275. Containers: []v1.Container{
  276. {Name: "c1"},
  277. {Name: "c2", SecurityContext: &v1.SecurityContext{}},
  278. },
  279. InitContainers: []v1.Container{
  280. {Name: "i1"},
  281. {Name: "i2", SecurityContext: &v1.SecurityContext{}},
  282. },
  283. EphemeralContainers: []v1.EphemeralContainer{
  284. {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}},
  285. {EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2", SecurityContext: &v1.SecurityContext{}}},
  286. },
  287. },
  288. []string{"i1", "i2", "c1", "c2", "e1", "e2"},
  289. },
  290. }
  291. for _, tc := range testCases {
  292. gotNames := []string{}
  293. VisitContainers(tc.haveSpec, func(c *v1.Container) bool {
  294. gotNames = append(gotNames, c.Name)
  295. if c.SecurityContext != nil {
  296. c.SecurityContext = nil
  297. }
  298. return true
  299. })
  300. if !reflect.DeepEqual(gotNames, tc.wantNames) {
  301. t.Errorf("VisitContainers() for test case %q visited containers %q, wanted to visit %q", tc.description, gotNames, tc.wantNames)
  302. }
  303. for _, c := range tc.haveSpec.Containers {
  304. if c.SecurityContext != nil {
  305. t.Errorf("VisitContainers() for test case %q: got SecurityContext %#v for container %v, wanted nil", tc.description, c.SecurityContext, c.Name)
  306. }
  307. }
  308. for _, c := range tc.haveSpec.InitContainers {
  309. if c.SecurityContext != nil {
  310. t.Errorf("VisitContainers() for test case %q: got SecurityContext %#v for init container %v, wanted nil", tc.description, c.SecurityContext, c.Name)
  311. }
  312. }
  313. for _, c := range tc.haveSpec.EphemeralContainers {
  314. if c.SecurityContext != nil {
  315. t.Errorf("VisitContainers() for test case %q: got SecurityContext %#v for ephemeral container %v, wanted nil", tc.description, c.SecurityContext, c.Name)
  316. }
  317. }
  318. }
  319. }
  320. func TestPodSecrets(t *testing.T) {
  321. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
  322. // Stub containing all possible secret references in a pod.
  323. // The names of the referenced secrets match struct paths detected by reflection.
  324. pod := &v1.Pod{
  325. Spec: v1.PodSpec{
  326. Containers: []v1.Container{{
  327. EnvFrom: []v1.EnvFromSource{{
  328. SecretRef: &v1.SecretEnvSource{
  329. LocalObjectReference: v1.LocalObjectReference{
  330. Name: "Spec.Containers[*].EnvFrom[*].SecretRef"}}}},
  331. Env: []v1.EnvVar{{
  332. ValueFrom: &v1.EnvVarSource{
  333. SecretKeyRef: &v1.SecretKeySelector{
  334. LocalObjectReference: v1.LocalObjectReference{
  335. Name: "Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef"}}}}}}},
  336. ImagePullSecrets: []v1.LocalObjectReference{{
  337. Name: "Spec.ImagePullSecrets"}},
  338. InitContainers: []v1.Container{{
  339. EnvFrom: []v1.EnvFromSource{{
  340. SecretRef: &v1.SecretEnvSource{
  341. LocalObjectReference: v1.LocalObjectReference{
  342. Name: "Spec.InitContainers[*].EnvFrom[*].SecretRef"}}}},
  343. Env: []v1.EnvVar{{
  344. ValueFrom: &v1.EnvVarSource{
  345. SecretKeyRef: &v1.SecretKeySelector{
  346. LocalObjectReference: v1.LocalObjectReference{
  347. Name: "Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef"}}}}}}},
  348. Volumes: []v1.Volume{{
  349. VolumeSource: v1.VolumeSource{
  350. AzureFile: &v1.AzureFileVolumeSource{
  351. SecretName: "Spec.Volumes[*].VolumeSource.AzureFile.SecretName"}}}, {
  352. VolumeSource: v1.VolumeSource{
  353. CephFS: &v1.CephFSVolumeSource{
  354. SecretRef: &v1.LocalObjectReference{
  355. Name: "Spec.Volumes[*].VolumeSource.CephFS.SecretRef"}}}}, {
  356. VolumeSource: v1.VolumeSource{
  357. Cinder: &v1.CinderVolumeSource{
  358. SecretRef: &v1.LocalObjectReference{
  359. Name: "Spec.Volumes[*].VolumeSource.Cinder.SecretRef"}}}}, {
  360. VolumeSource: v1.VolumeSource{
  361. FlexVolume: &v1.FlexVolumeSource{
  362. SecretRef: &v1.LocalObjectReference{
  363. Name: "Spec.Volumes[*].VolumeSource.FlexVolume.SecretRef"}}}}, {
  364. VolumeSource: v1.VolumeSource{
  365. Projected: &v1.ProjectedVolumeSource{
  366. Sources: []v1.VolumeProjection{{
  367. Secret: &v1.SecretProjection{
  368. LocalObjectReference: v1.LocalObjectReference{
  369. Name: "Spec.Volumes[*].VolumeSource.Projected.Sources[*].Secret"}}}}}}}, {
  370. VolumeSource: v1.VolumeSource{
  371. RBD: &v1.RBDVolumeSource{
  372. SecretRef: &v1.LocalObjectReference{
  373. Name: "Spec.Volumes[*].VolumeSource.RBD.SecretRef"}}}}, {
  374. VolumeSource: v1.VolumeSource{
  375. Secret: &v1.SecretVolumeSource{
  376. SecretName: "Spec.Volumes[*].VolumeSource.Secret.SecretName"}}}, {
  377. VolumeSource: v1.VolumeSource{
  378. Secret: &v1.SecretVolumeSource{
  379. SecretName: "Spec.Volumes[*].VolumeSource.Secret"}}}, {
  380. VolumeSource: v1.VolumeSource{
  381. ScaleIO: &v1.ScaleIOVolumeSource{
  382. SecretRef: &v1.LocalObjectReference{
  383. Name: "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef"}}}}, {
  384. VolumeSource: v1.VolumeSource{
  385. ISCSI: &v1.ISCSIVolumeSource{
  386. SecretRef: &v1.LocalObjectReference{
  387. Name: "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef"}}}}, {
  388. VolumeSource: v1.VolumeSource{
  389. StorageOS: &v1.StorageOSVolumeSource{
  390. SecretRef: &v1.LocalObjectReference{
  391. Name: "Spec.Volumes[*].VolumeSource.StorageOS.SecretRef"}}}}, {
  392. VolumeSource: v1.VolumeSource{
  393. CSI: &v1.CSIVolumeSource{
  394. NodePublishSecretRef: &v1.LocalObjectReference{
  395. Name: "Spec.Volumes[*].VolumeSource.CSI.NodePublishSecretRef"}}}}},
  396. EphemeralContainers: []v1.EphemeralContainer{{
  397. EphemeralContainerCommon: v1.EphemeralContainerCommon{
  398. EnvFrom: []v1.EnvFromSource{{
  399. SecretRef: &v1.SecretEnvSource{
  400. LocalObjectReference: v1.LocalObjectReference{
  401. Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].SecretRef"}}}},
  402. Env: []v1.EnvVar{{
  403. ValueFrom: &v1.EnvVarSource{
  404. SecretKeyRef: &v1.SecretKeySelector{
  405. LocalObjectReference: v1.LocalObjectReference{
  406. Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.SecretKeyRef"}}}}}}}},
  407. },
  408. }
  409. extractedNames := sets.NewString()
  410. VisitPodSecretNames(pod, func(name string) bool {
  411. extractedNames.Insert(name)
  412. return true
  413. })
  414. // excludedSecretPaths holds struct paths to fields with "secret" in the name that are not actually references to secret API objects
  415. excludedSecretPaths := sets.NewString(
  416. "Spec.Volumes[*].VolumeSource.CephFS.SecretFile",
  417. )
  418. // expectedSecretPaths holds struct paths to fields with "secret" in the name that are references to secret API objects.
  419. // every path here should be represented as an example in the Pod stub above, with the secret name set to the path.
  420. expectedSecretPaths := sets.NewString(
  421. "Spec.Containers[*].EnvFrom[*].SecretRef",
  422. "Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef",
  423. "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].SecretRef",
  424. "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.SecretKeyRef",
  425. "Spec.ImagePullSecrets",
  426. "Spec.InitContainers[*].EnvFrom[*].SecretRef",
  427. "Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef",
  428. "Spec.Volumes[*].VolumeSource.AzureFile.SecretName",
  429. "Spec.Volumes[*].VolumeSource.CephFS.SecretRef",
  430. "Spec.Volumes[*].VolumeSource.Cinder.SecretRef",
  431. "Spec.Volumes[*].VolumeSource.FlexVolume.SecretRef",
  432. "Spec.Volumes[*].VolumeSource.Projected.Sources[*].Secret",
  433. "Spec.Volumes[*].VolumeSource.RBD.SecretRef",
  434. "Spec.Volumes[*].VolumeSource.Secret",
  435. "Spec.Volumes[*].VolumeSource.Secret.SecretName",
  436. "Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef",
  437. "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef",
  438. "Spec.Volumes[*].VolumeSource.StorageOS.SecretRef",
  439. "Spec.Volumes[*].VolumeSource.CSI.NodePublishSecretRef",
  440. )
  441. secretPaths := collectResourcePaths(t, "secret", nil, "", reflect.TypeOf(&v1.Pod{}))
  442. secretPaths = secretPaths.Difference(excludedSecretPaths)
  443. if missingPaths := expectedSecretPaths.Difference(secretPaths); len(missingPaths) > 0 {
  444. t.Logf("Missing expected secret paths:\n%s", strings.Join(missingPaths.List(), "\n"))
  445. t.Error("Missing expected secret paths. Verify VisitPodSecretNames() is correctly finding the missing paths, then correct expectedSecretPaths")
  446. }
  447. if extraPaths := secretPaths.Difference(expectedSecretPaths); len(extraPaths) > 0 {
  448. t.Logf("Extra secret paths:\n%s", strings.Join(extraPaths.List(), "\n"))
  449. t.Error("Extra fields with 'secret' in the name found. Verify VisitPodSecretNames() is including these fields if appropriate, then correct expectedSecretPaths")
  450. }
  451. if missingNames := expectedSecretPaths.Difference(extractedNames); len(missingNames) > 0 {
  452. t.Logf("Missing expected secret names:\n%s", strings.Join(missingNames.List(), "\n"))
  453. t.Error("Missing expected secret names. Verify the pod stub above includes these references, then verify VisitPodSecretNames() is correctly finding the missing names")
  454. }
  455. if extraNames := extractedNames.Difference(expectedSecretPaths); len(extraNames) > 0 {
  456. t.Logf("Extra secret names:\n%s", strings.Join(extraNames.List(), "\n"))
  457. t.Error("Extra secret names extracted. Verify VisitPodSecretNames() is correctly extracting secret names")
  458. }
  459. }
  460. // collectResourcePaths traverses the object, computing all the struct paths that lead to fields with resourcename in the name.
  461. func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, name string, tp reflect.Type) sets.String {
  462. resourcename = strings.ToLower(resourcename)
  463. resourcePaths := sets.NewString()
  464. if tp.Kind() == reflect.Ptr {
  465. resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...)
  466. return resourcePaths
  467. }
  468. if strings.Contains(strings.ToLower(name), resourcename) {
  469. resourcePaths.Insert(path.String())
  470. }
  471. switch tp.Kind() {
  472. case reflect.Ptr:
  473. resourcePaths.Insert(collectResourcePaths(t, resourcename, path, name, tp.Elem()).List()...)
  474. case reflect.Struct:
  475. // ObjectMeta is generic and therefore should never have a field with a specific resource's name;
  476. // it contains cycles so it's easiest to just skip it.
  477. if name == "ObjectMeta" {
  478. break
  479. }
  480. for i := 0; i < tp.NumField(); i++ {
  481. field := tp.Field(i)
  482. resourcePaths.Insert(collectResourcePaths(t, resourcename, path.Child(field.Name), field.Name, field.Type).List()...)
  483. }
  484. case reflect.Interface:
  485. t.Errorf("cannot find %s fields in interface{} field %s", resourcename, path.String())
  486. case reflect.Map:
  487. resourcePaths.Insert(collectResourcePaths(t, resourcename, path.Key("*"), "", tp.Elem()).List()...)
  488. case reflect.Slice:
  489. resourcePaths.Insert(collectResourcePaths(t, resourcename, path.Key("*"), "", tp.Elem()).List()...)
  490. default:
  491. // all primitive types
  492. }
  493. return resourcePaths
  494. }
  495. func TestPodConfigmaps(t *testing.T) {
  496. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
  497. // Stub containing all possible ConfigMap references in a pod.
  498. // The names of the referenced ConfigMaps match struct paths detected by reflection.
  499. pod := &v1.Pod{
  500. Spec: v1.PodSpec{
  501. Containers: []v1.Container{{
  502. EnvFrom: []v1.EnvFromSource{{
  503. ConfigMapRef: &v1.ConfigMapEnvSource{
  504. LocalObjectReference: v1.LocalObjectReference{
  505. Name: "Spec.Containers[*].EnvFrom[*].ConfigMapRef"}}}},
  506. Env: []v1.EnvVar{{
  507. ValueFrom: &v1.EnvVarSource{
  508. ConfigMapKeyRef: &v1.ConfigMapKeySelector{
  509. LocalObjectReference: v1.LocalObjectReference{
  510. Name: "Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}},
  511. EphemeralContainers: []v1.EphemeralContainer{{
  512. EphemeralContainerCommon: v1.EphemeralContainerCommon{
  513. EnvFrom: []v1.EnvFromSource{{
  514. ConfigMapRef: &v1.ConfigMapEnvSource{
  515. LocalObjectReference: v1.LocalObjectReference{
  516. Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].ConfigMapRef"}}}},
  517. Env: []v1.EnvVar{{
  518. ValueFrom: &v1.EnvVarSource{
  519. ConfigMapKeyRef: &v1.ConfigMapKeySelector{
  520. LocalObjectReference: v1.LocalObjectReference{
  521. Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}}},
  522. InitContainers: []v1.Container{{
  523. EnvFrom: []v1.EnvFromSource{{
  524. ConfigMapRef: &v1.ConfigMapEnvSource{
  525. LocalObjectReference: v1.LocalObjectReference{
  526. Name: "Spec.InitContainers[*].EnvFrom[*].ConfigMapRef"}}}},
  527. Env: []v1.EnvVar{{
  528. ValueFrom: &v1.EnvVarSource{
  529. ConfigMapKeyRef: &v1.ConfigMapKeySelector{
  530. LocalObjectReference: v1.LocalObjectReference{
  531. Name: "Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}},
  532. Volumes: []v1.Volume{{
  533. VolumeSource: v1.VolumeSource{
  534. Projected: &v1.ProjectedVolumeSource{
  535. Sources: []v1.VolumeProjection{{
  536. ConfigMap: &v1.ConfigMapProjection{
  537. LocalObjectReference: v1.LocalObjectReference{
  538. Name: "Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap"}}}}}}}, {
  539. VolumeSource: v1.VolumeSource{
  540. ConfigMap: &v1.ConfigMapVolumeSource{
  541. LocalObjectReference: v1.LocalObjectReference{
  542. Name: "Spec.Volumes[*].VolumeSource.ConfigMap"}}}}},
  543. },
  544. }
  545. extractedNames := sets.NewString()
  546. VisitPodConfigmapNames(pod, func(name string) bool {
  547. extractedNames.Insert(name)
  548. return true
  549. })
  550. // expectedPaths holds struct paths to fields with "ConfigMap" in the name that are references to ConfigMap API objects.
  551. // every path here should be represented as an example in the Pod stub above, with the ConfigMap name set to the path.
  552. expectedPaths := sets.NewString(
  553. "Spec.Containers[*].EnvFrom[*].ConfigMapRef",
  554. "Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef",
  555. "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].ConfigMapRef",
  556. "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.ConfigMapKeyRef",
  557. "Spec.InitContainers[*].EnvFrom[*].ConfigMapRef",
  558. "Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef",
  559. "Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap",
  560. "Spec.Volumes[*].VolumeSource.ConfigMap",
  561. )
  562. collectPaths := collectResourcePaths(t, "ConfigMap", nil, "", reflect.TypeOf(&v1.Pod{}))
  563. if missingPaths := expectedPaths.Difference(collectPaths); len(missingPaths) > 0 {
  564. t.Logf("Missing expected paths:\n%s", strings.Join(missingPaths.List(), "\n"))
  565. t.Error("Missing expected paths. Verify VisitPodConfigmapNames() is correctly finding the missing paths, then correct expectedPaths")
  566. }
  567. if extraPaths := collectPaths.Difference(expectedPaths); len(extraPaths) > 0 {
  568. t.Logf("Extra paths:\n%s", strings.Join(extraPaths.List(), "\n"))
  569. t.Error("Extra fields with resource in the name found. Verify VisitPodConfigmapNames() is including these fields if appropriate, then correct expectedPaths")
  570. }
  571. if missingNames := expectedPaths.Difference(extractedNames); len(missingNames) > 0 {
  572. t.Logf("Missing expected names:\n%s", strings.Join(missingNames.List(), "\n"))
  573. t.Error("Missing expected names. Verify the pod stub above includes these references, then verify VisitPodConfigmapNames() is correctly finding the missing names")
  574. }
  575. if extraNames := extractedNames.Difference(expectedPaths); len(extraNames) > 0 {
  576. t.Logf("Extra names:\n%s", strings.Join(extraNames.List(), "\n"))
  577. t.Error("Extra names extracted. Verify VisitPodConfigmapNames() is correctly extracting resource names")
  578. }
  579. }
  580. func newPod(now metav1.Time, ready bool, beforeSec int) *v1.Pod {
  581. conditionStatus := v1.ConditionFalse
  582. if ready {
  583. conditionStatus = v1.ConditionTrue
  584. }
  585. return &v1.Pod{
  586. Status: v1.PodStatus{
  587. Conditions: []v1.PodCondition{
  588. {
  589. Type: v1.PodReady,
  590. LastTransitionTime: metav1.NewTime(now.Time.Add(-1 * time.Duration(beforeSec) * time.Second)),
  591. Status: conditionStatus,
  592. },
  593. },
  594. },
  595. }
  596. }
  597. func TestIsPodAvailable(t *testing.T) {
  598. now := metav1.Now()
  599. tests := []struct {
  600. pod *v1.Pod
  601. minReadySeconds int32
  602. expected bool
  603. }{
  604. {
  605. pod: newPod(now, false, 0),
  606. minReadySeconds: 0,
  607. expected: false,
  608. },
  609. {
  610. pod: newPod(now, true, 0),
  611. minReadySeconds: 1,
  612. expected: false,
  613. },
  614. {
  615. pod: newPod(now, true, 0),
  616. minReadySeconds: 0,
  617. expected: true,
  618. },
  619. {
  620. pod: newPod(now, true, 51),
  621. minReadySeconds: 50,
  622. expected: true,
  623. },
  624. }
  625. for i, test := range tests {
  626. isAvailable := IsPodAvailable(test.pod, test.minReadySeconds, now)
  627. if isAvailable != test.expected {
  628. t.Errorf("[tc #%d] expected available pod: %t, got: %t", i, test.expected, isAvailable)
  629. }
  630. }
  631. }
  632. func TestGetContainerStatus(t *testing.T) {
  633. type ExpectedStruct struct {
  634. status v1.ContainerStatus
  635. exists bool
  636. }
  637. tests := []struct {
  638. status []v1.ContainerStatus
  639. name string
  640. expected ExpectedStruct
  641. desc string
  642. }{
  643. {
  644. status: []v1.ContainerStatus{{Name: "test1", Ready: false, Image: "image1"}, {Name: "test2", Ready: true, Image: "image1"}},
  645. name: "test1",
  646. expected: ExpectedStruct{status: v1.ContainerStatus{Name: "test1", Ready: false, Image: "image1"}, exists: true},
  647. desc: "retrieve ContainerStatus with Name=\"test1\"",
  648. },
  649. {
  650. status: []v1.ContainerStatus{{Name: "test2", Ready: false, Image: "image2"}},
  651. name: "test1",
  652. expected: ExpectedStruct{status: v1.ContainerStatus{}, exists: false},
  653. desc: "no matching ContainerStatus with Name=\"test1\"",
  654. },
  655. {
  656. status: []v1.ContainerStatus{{Name: "test3", Ready: false, Image: "image3"}},
  657. name: "",
  658. expected: ExpectedStruct{status: v1.ContainerStatus{}, exists: false},
  659. desc: "retrieve an empty ContainerStatus with container name empty",
  660. },
  661. {
  662. status: nil,
  663. name: "",
  664. expected: ExpectedStruct{status: v1.ContainerStatus{}, exists: false},
  665. desc: "retrieve an empty ContainerStatus with status nil",
  666. },
  667. }
  668. for _, test := range tests {
  669. resultStatus, exists := GetContainerStatus(test.status, test.name)
  670. assert.Equal(t, test.expected.status, resultStatus, "GetContainerStatus: "+test.desc)
  671. assert.Equal(t, test.expected.exists, exists, "GetContainerStatus: "+test.desc)
  672. resultStatus = GetExistingContainerStatus(test.status, test.name)
  673. assert.Equal(t, test.expected.status, resultStatus, "GetExistingContainerStatus: "+test.desc)
  674. }
  675. }
  676. func TestUpdatePodCondition(t *testing.T) {
  677. time := metav1.Now()
  678. podStatus := v1.PodStatus{
  679. Conditions: []v1.PodCondition{
  680. {
  681. Type: v1.PodReady,
  682. Status: v1.ConditionTrue,
  683. Reason: "successfully",
  684. Message: "sync pod successfully",
  685. LastProbeTime: time,
  686. LastTransitionTime: metav1.NewTime(time.Add(1000)),
  687. },
  688. },
  689. }
  690. tests := []struct {
  691. status *v1.PodStatus
  692. conditions v1.PodCondition
  693. expected bool
  694. desc string
  695. }{
  696. {
  697. status: &podStatus,
  698. conditions: v1.PodCondition{
  699. Type: v1.PodReady,
  700. Status: v1.ConditionTrue,
  701. Reason: "successfully",
  702. Message: "sync pod successfully",
  703. LastProbeTime: time,
  704. LastTransitionTime: metav1.NewTime(time.Add(1000))},
  705. expected: false,
  706. desc: "all equal, no update",
  707. },
  708. {
  709. status: &podStatus,
  710. conditions: v1.PodCondition{
  711. Type: v1.PodScheduled,
  712. Status: v1.ConditionTrue,
  713. Reason: "successfully",
  714. Message: "sync pod successfully",
  715. LastProbeTime: time,
  716. LastTransitionTime: metav1.NewTime(time.Add(1000))},
  717. expected: true,
  718. desc: "not equal Type, should get updated",
  719. },
  720. {
  721. status: &podStatus,
  722. conditions: v1.PodCondition{
  723. Type: v1.PodReady,
  724. Status: v1.ConditionFalse,
  725. Reason: "successfully",
  726. Message: "sync pod successfully",
  727. LastProbeTime: time,
  728. LastTransitionTime: metav1.NewTime(time.Add(1000))},
  729. expected: true,
  730. desc: "not equal Status, should get updated",
  731. },
  732. }
  733. for _, test := range tests {
  734. resultStatus := UpdatePodCondition(test.status, &test.conditions)
  735. assert.Equal(t, test.expected, resultStatus, test.desc)
  736. }
  737. }
  738. // TestGetPodPriority tests GetPodPriority function.
  739. func TestGetPodPriority(t *testing.T) {
  740. p := int32(20)
  741. tests := []struct {
  742. name string
  743. pod *v1.Pod
  744. expectedPriority int32
  745. }{
  746. {
  747. name: "no priority pod resolves to static default priority",
  748. pod: &v1.Pod{
  749. Spec: v1.PodSpec{Containers: []v1.Container{
  750. {Name: "container", Image: "image"}},
  751. },
  752. },
  753. expectedPriority: 0,
  754. },
  755. {
  756. name: "pod with priority resolves correctly",
  757. pod: &v1.Pod{
  758. Spec: v1.PodSpec{Containers: []v1.Container{
  759. {Name: "container", Image: "image"}},
  760. Priority: &p,
  761. },
  762. },
  763. expectedPriority: p,
  764. },
  765. }
  766. for _, test := range tests {
  767. if GetPodPriority(test.pod) != test.expectedPriority {
  768. t.Errorf("expected pod priority: %v, got %v", test.expectedPriority, GetPodPriority(test.pod))
  769. }
  770. }
  771. }