validation_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  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 validation
  14. import (
  15. "strings"
  16. "testing"
  17. "k8s.io/kubernetes/pkg/api"
  18. "k8s.io/kubernetes/pkg/api/unversioned"
  19. "k8s.io/kubernetes/pkg/apis/batch"
  20. "k8s.io/kubernetes/pkg/types"
  21. )
  22. func getValidManualSelector() *unversioned.LabelSelector {
  23. return &unversioned.LabelSelector{
  24. MatchLabels: map[string]string{"a": "b"},
  25. }
  26. }
  27. func getValidPodTemplateSpecForManual(selector *unversioned.LabelSelector) api.PodTemplateSpec {
  28. return api.PodTemplateSpec{
  29. ObjectMeta: api.ObjectMeta{
  30. Labels: selector.MatchLabels,
  31. },
  32. Spec: api.PodSpec{
  33. RestartPolicy: api.RestartPolicyOnFailure,
  34. DNSPolicy: api.DNSClusterFirst,
  35. Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
  36. },
  37. }
  38. }
  39. func getValidGeneratedSelector() *unversioned.LabelSelector {
  40. return &unversioned.LabelSelector{
  41. MatchLabels: map[string]string{"controller-uid": "1a2b3c", "job-name": "myjob"},
  42. }
  43. }
  44. func getValidPodTemplateSpecForGenerated(selector *unversioned.LabelSelector) api.PodTemplateSpec {
  45. return api.PodTemplateSpec{
  46. ObjectMeta: api.ObjectMeta{
  47. Labels: selector.MatchLabels,
  48. },
  49. Spec: api.PodSpec{
  50. RestartPolicy: api.RestartPolicyOnFailure,
  51. DNSPolicy: api.DNSClusterFirst,
  52. Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
  53. },
  54. }
  55. }
  56. func TestValidateJob(t *testing.T) {
  57. validManualSelector := getValidManualSelector()
  58. validPodTemplateSpecForManual := getValidPodTemplateSpecForManual(validManualSelector)
  59. validGeneratedSelector := getValidGeneratedSelector()
  60. validPodTemplateSpecForGenerated := getValidPodTemplateSpecForGenerated(validGeneratedSelector)
  61. successCases := map[string]batch.Job{
  62. "manual selector": {
  63. ObjectMeta: api.ObjectMeta{
  64. Name: "myjob",
  65. Namespace: api.NamespaceDefault,
  66. UID: types.UID("1a2b3c"),
  67. },
  68. Spec: batch.JobSpec{
  69. Selector: validManualSelector,
  70. ManualSelector: newBool(true),
  71. Template: validPodTemplateSpecForManual,
  72. },
  73. },
  74. "generated selector": {
  75. ObjectMeta: api.ObjectMeta{
  76. Name: "myjob",
  77. Namespace: api.NamespaceDefault,
  78. UID: types.UID("1a2b3c"),
  79. },
  80. Spec: batch.JobSpec{
  81. Selector: validGeneratedSelector,
  82. Template: validPodTemplateSpecForGenerated,
  83. },
  84. },
  85. }
  86. for k, v := range successCases {
  87. if errs := ValidateJob(&v); len(errs) != 0 {
  88. t.Errorf("expected success for %s: %v", k, errs)
  89. }
  90. }
  91. negative := int32(-1)
  92. negative64 := int64(-1)
  93. errorCases := map[string]batch.Job{
  94. "spec.parallelism:must be greater than or equal to 0": {
  95. ObjectMeta: api.ObjectMeta{
  96. Name: "myjob",
  97. Namespace: api.NamespaceDefault,
  98. UID: types.UID("1a2b3c"),
  99. },
  100. Spec: batch.JobSpec{
  101. Parallelism: &negative,
  102. Selector: validGeneratedSelector,
  103. Template: validPodTemplateSpecForGenerated,
  104. },
  105. },
  106. "spec.completions:must be greater than or equal to 0": {
  107. ObjectMeta: api.ObjectMeta{
  108. Name: "myjob",
  109. Namespace: api.NamespaceDefault,
  110. UID: types.UID("1a2b3c"),
  111. },
  112. Spec: batch.JobSpec{
  113. Completions: &negative,
  114. Selector: validGeneratedSelector,
  115. Template: validPodTemplateSpecForGenerated,
  116. },
  117. },
  118. "spec.activeDeadlineSeconds:must be greater than or equal to 0": {
  119. ObjectMeta: api.ObjectMeta{
  120. Name: "myjob",
  121. Namespace: api.NamespaceDefault,
  122. UID: types.UID("1a2b3c"),
  123. },
  124. Spec: batch.JobSpec{
  125. ActiveDeadlineSeconds: &negative64,
  126. Selector: validGeneratedSelector,
  127. Template: validPodTemplateSpecForGenerated,
  128. },
  129. },
  130. "spec.selector:Required value": {
  131. ObjectMeta: api.ObjectMeta{
  132. Name: "myjob",
  133. Namespace: api.NamespaceDefault,
  134. UID: types.UID("1a2b3c"),
  135. },
  136. Spec: batch.JobSpec{
  137. Template: validPodTemplateSpecForGenerated,
  138. },
  139. },
  140. "spec.template.metadata.labels: Invalid value: {\"y\":\"z\"}: `selector` does not match template `labels`": {
  141. ObjectMeta: api.ObjectMeta{
  142. Name: "myjob",
  143. Namespace: api.NamespaceDefault,
  144. UID: types.UID("1a2b3c"),
  145. },
  146. Spec: batch.JobSpec{
  147. Selector: validManualSelector,
  148. ManualSelector: newBool(true),
  149. Template: api.PodTemplateSpec{
  150. ObjectMeta: api.ObjectMeta{
  151. Labels: map[string]string{"y": "z"},
  152. },
  153. Spec: api.PodSpec{
  154. RestartPolicy: api.RestartPolicyOnFailure,
  155. DNSPolicy: api.DNSClusterFirst,
  156. Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
  157. },
  158. },
  159. },
  160. },
  161. "spec.template.metadata.labels: Invalid value: {\"controller-uid\":\"4d5e6f\"}: `selector` does not match template `labels`": {
  162. ObjectMeta: api.ObjectMeta{
  163. Name: "myjob",
  164. Namespace: api.NamespaceDefault,
  165. UID: types.UID("1a2b3c"),
  166. },
  167. Spec: batch.JobSpec{
  168. Selector: validManualSelector,
  169. ManualSelector: newBool(true),
  170. Template: api.PodTemplateSpec{
  171. ObjectMeta: api.ObjectMeta{
  172. Labels: map[string]string{"controller-uid": "4d5e6f"},
  173. },
  174. Spec: api.PodSpec{
  175. RestartPolicy: api.RestartPolicyOnFailure,
  176. DNSPolicy: api.DNSClusterFirst,
  177. Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
  178. },
  179. },
  180. },
  181. },
  182. "spec.template.spec.restartPolicy: Unsupported value": {
  183. ObjectMeta: api.ObjectMeta{
  184. Name: "myjob",
  185. Namespace: api.NamespaceDefault,
  186. UID: types.UID("1a2b3c"),
  187. },
  188. Spec: batch.JobSpec{
  189. Selector: validManualSelector,
  190. ManualSelector: newBool(true),
  191. Template: api.PodTemplateSpec{
  192. ObjectMeta: api.ObjectMeta{
  193. Labels: validManualSelector.MatchLabels,
  194. },
  195. Spec: api.PodSpec{
  196. RestartPolicy: api.RestartPolicyAlways,
  197. DNSPolicy: api.DNSClusterFirst,
  198. Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
  199. },
  200. },
  201. },
  202. },
  203. }
  204. for k, v := range errorCases {
  205. errs := ValidateJob(&v)
  206. if len(errs) == 0 {
  207. t.Errorf("expected failure for %s", k)
  208. } else {
  209. s := strings.Split(k, ":")
  210. err := errs[0]
  211. if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
  212. t.Errorf("unexpected error: %v, expected: %s", err, k)
  213. }
  214. }
  215. }
  216. }
  217. func TestValidateJobUpdateStatus(t *testing.T) {
  218. type testcase struct {
  219. old batch.Job
  220. update batch.Job
  221. }
  222. successCases := []testcase{
  223. {
  224. old: batch.Job{
  225. ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
  226. Status: batch.JobStatus{
  227. Active: 1,
  228. Succeeded: 2,
  229. Failed: 3,
  230. },
  231. },
  232. update: batch.Job{
  233. ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
  234. Status: batch.JobStatus{
  235. Active: 1,
  236. Succeeded: 1,
  237. Failed: 3,
  238. },
  239. },
  240. },
  241. }
  242. for _, successCase := range successCases {
  243. successCase.old.ObjectMeta.ResourceVersion = "1"
  244. successCase.update.ObjectMeta.ResourceVersion = "1"
  245. if errs := ValidateJobUpdateStatus(&successCase.update, &successCase.old); len(errs) != 0 {
  246. t.Errorf("expected success: %v", errs)
  247. }
  248. }
  249. errorCases := map[string]testcase{
  250. "[status.active: Invalid value: -1: must be greater than or equal to 0, status.succeeded: Invalid value: -2: must be greater than or equal to 0]": {
  251. old: batch.Job{
  252. ObjectMeta: api.ObjectMeta{
  253. Name: "abc",
  254. Namespace: api.NamespaceDefault,
  255. ResourceVersion: "10",
  256. },
  257. Status: batch.JobStatus{
  258. Active: 1,
  259. Succeeded: 2,
  260. Failed: 3,
  261. },
  262. },
  263. update: batch.Job{
  264. ObjectMeta: api.ObjectMeta{
  265. Name: "abc",
  266. Namespace: api.NamespaceDefault,
  267. ResourceVersion: "10",
  268. },
  269. Status: batch.JobStatus{
  270. Active: -1,
  271. Succeeded: -2,
  272. Failed: 3,
  273. },
  274. },
  275. },
  276. }
  277. for testName, errorCase := range errorCases {
  278. errs := ValidateJobUpdateStatus(&errorCase.update, &errorCase.old)
  279. if len(errs) == 0 {
  280. t.Errorf("expected failure: %s", testName)
  281. continue
  282. }
  283. if errs.ToAggregate().Error() != testName {
  284. t.Errorf("expected '%s' got '%s'", errs.ToAggregate().Error(), testName)
  285. }
  286. }
  287. }
  288. func TestValidateScheduledJob(t *testing.T) {
  289. validManualSelector := getValidManualSelector()
  290. validPodTemplateSpec := getValidPodTemplateSpecForGenerated(getValidGeneratedSelector())
  291. validPodTemplateSpec.Labels = map[string]string{}
  292. successCases := map[string]batch.ScheduledJob{
  293. "basic scheduled job": {
  294. ObjectMeta: api.ObjectMeta{
  295. Name: "myscheduledjob",
  296. Namespace: api.NamespaceDefault,
  297. UID: types.UID("1a2b3c"),
  298. },
  299. Spec: batch.ScheduledJobSpec{
  300. Schedule: "* * * * ?",
  301. ConcurrencyPolicy: batch.AllowConcurrent,
  302. JobTemplate: batch.JobTemplateSpec{
  303. Spec: batch.JobSpec{
  304. Template: validPodTemplateSpec,
  305. },
  306. },
  307. },
  308. },
  309. "non-standard scheduled": {
  310. ObjectMeta: api.ObjectMeta{
  311. Name: "myscheduledjob",
  312. Namespace: api.NamespaceDefault,
  313. UID: types.UID("1a2b3c"),
  314. },
  315. Spec: batch.ScheduledJobSpec{
  316. Schedule: "@hourly",
  317. ConcurrencyPolicy: batch.AllowConcurrent,
  318. JobTemplate: batch.JobTemplateSpec{
  319. Spec: batch.JobSpec{
  320. Template: validPodTemplateSpec,
  321. },
  322. },
  323. },
  324. },
  325. }
  326. for k, v := range successCases {
  327. if errs := ValidateScheduledJob(&v); len(errs) != 0 {
  328. t.Errorf("expected success for %s: %v", k, errs)
  329. }
  330. }
  331. negative := int32(-1)
  332. negative64 := int64(-1)
  333. errorCases := map[string]batch.ScheduledJob{
  334. "spec.schedule: Invalid value": {
  335. ObjectMeta: api.ObjectMeta{
  336. Name: "myscheduledjob",
  337. Namespace: api.NamespaceDefault,
  338. UID: types.UID("1a2b3c"),
  339. },
  340. Spec: batch.ScheduledJobSpec{
  341. Schedule: "error",
  342. ConcurrencyPolicy: batch.AllowConcurrent,
  343. JobTemplate: batch.JobTemplateSpec{
  344. Spec: batch.JobSpec{
  345. Template: validPodTemplateSpec,
  346. },
  347. },
  348. },
  349. },
  350. "spec.schedule: Required value": {
  351. ObjectMeta: api.ObjectMeta{
  352. Name: "myscheduledjob",
  353. Namespace: api.NamespaceDefault,
  354. UID: types.UID("1a2b3c"),
  355. },
  356. Spec: batch.ScheduledJobSpec{
  357. Schedule: "",
  358. ConcurrencyPolicy: batch.AllowConcurrent,
  359. JobTemplate: batch.JobTemplateSpec{
  360. Spec: batch.JobSpec{
  361. Template: validPodTemplateSpec,
  362. },
  363. },
  364. },
  365. },
  366. "spec.startingDeadlineSeconds:must be greater than or equal to 0": {
  367. ObjectMeta: api.ObjectMeta{
  368. Name: "myscheduledjob",
  369. Namespace: api.NamespaceDefault,
  370. UID: types.UID("1a2b3c"),
  371. },
  372. Spec: batch.ScheduledJobSpec{
  373. Schedule: "* * * * ?",
  374. ConcurrencyPolicy: batch.AllowConcurrent,
  375. StartingDeadlineSeconds: &negative64,
  376. JobTemplate: batch.JobTemplateSpec{
  377. Spec: batch.JobSpec{
  378. Template: validPodTemplateSpec,
  379. },
  380. },
  381. },
  382. },
  383. "spec.concurrencyPolicy: Required value": {
  384. ObjectMeta: api.ObjectMeta{
  385. Name: "myscheduledjob",
  386. Namespace: api.NamespaceDefault,
  387. UID: types.UID("1a2b3c"),
  388. },
  389. Spec: batch.ScheduledJobSpec{
  390. Schedule: "* * * * ?",
  391. JobTemplate: batch.JobTemplateSpec{
  392. Spec: batch.JobSpec{
  393. Template: validPodTemplateSpec,
  394. },
  395. },
  396. },
  397. },
  398. "spec.jobTemplate.spec.parallelism:must be greater than or equal to 0": {
  399. ObjectMeta: api.ObjectMeta{
  400. Name: "myscheduledjob",
  401. Namespace: api.NamespaceDefault,
  402. UID: types.UID("1a2b3c"),
  403. },
  404. Spec: batch.ScheduledJobSpec{
  405. Schedule: "* * * * ?",
  406. ConcurrencyPolicy: batch.AllowConcurrent,
  407. JobTemplate: batch.JobTemplateSpec{
  408. Spec: batch.JobSpec{
  409. Parallelism: &negative,
  410. Template: validPodTemplateSpec,
  411. },
  412. },
  413. },
  414. },
  415. "spec.jobTemplate.spec.completions:must be greater than or equal to 0": {
  416. ObjectMeta: api.ObjectMeta{
  417. Name: "myscheduledjob",
  418. Namespace: api.NamespaceDefault,
  419. UID: types.UID("1a2b3c"),
  420. },
  421. Spec: batch.ScheduledJobSpec{
  422. Schedule: "* * * * ?",
  423. ConcurrencyPolicy: batch.AllowConcurrent,
  424. JobTemplate: batch.JobTemplateSpec{
  425. Spec: batch.JobSpec{
  426. Completions: &negative,
  427. Template: validPodTemplateSpec,
  428. },
  429. },
  430. },
  431. },
  432. "spec.jobTemplate.spec.activeDeadlineSeconds:must be greater than or equal to 0": {
  433. ObjectMeta: api.ObjectMeta{
  434. Name: "myscheduledjob",
  435. Namespace: api.NamespaceDefault,
  436. UID: types.UID("1a2b3c"),
  437. },
  438. Spec: batch.ScheduledJobSpec{
  439. Schedule: "* * * * ?",
  440. ConcurrencyPolicy: batch.AllowConcurrent,
  441. JobTemplate: batch.JobTemplateSpec{
  442. Spec: batch.JobSpec{
  443. ActiveDeadlineSeconds: &negative64,
  444. Template: validPodTemplateSpec,
  445. },
  446. },
  447. },
  448. },
  449. "spec.jobTemplate.spec.selector: Invalid value: {\"matchLabels\":{\"a\":\"b\"}}: `selector` will be auto-generated": {
  450. ObjectMeta: api.ObjectMeta{
  451. Name: "myscheduledjob",
  452. Namespace: api.NamespaceDefault,
  453. UID: types.UID("1a2b3c"),
  454. },
  455. Spec: batch.ScheduledJobSpec{
  456. Schedule: "* * * * ?",
  457. ConcurrencyPolicy: batch.AllowConcurrent,
  458. JobTemplate: batch.JobTemplateSpec{
  459. Spec: batch.JobSpec{
  460. Selector: validManualSelector,
  461. Template: validPodTemplateSpec,
  462. },
  463. },
  464. },
  465. },
  466. "spec.jobTemplate.spec.manualSelector: Unsupported value": {
  467. ObjectMeta: api.ObjectMeta{
  468. Name: "myscheduledjob",
  469. Namespace: api.NamespaceDefault,
  470. UID: types.UID("1a2b3c"),
  471. },
  472. Spec: batch.ScheduledJobSpec{
  473. Schedule: "* * * * ?",
  474. ConcurrencyPolicy: batch.AllowConcurrent,
  475. JobTemplate: batch.JobTemplateSpec{
  476. Spec: batch.JobSpec{
  477. ManualSelector: newBool(true),
  478. Template: validPodTemplateSpec,
  479. },
  480. },
  481. },
  482. },
  483. "spec.jobTemplate.spec.template.spec.restartPolicy: Unsupported value": {
  484. ObjectMeta: api.ObjectMeta{
  485. Name: "myscheduledjob",
  486. Namespace: api.NamespaceDefault,
  487. UID: types.UID("1a2b3c"),
  488. },
  489. Spec: batch.ScheduledJobSpec{
  490. Schedule: "* * * * ?",
  491. ConcurrencyPolicy: batch.AllowConcurrent,
  492. JobTemplate: batch.JobTemplateSpec{
  493. Spec: batch.JobSpec{
  494. Template: api.PodTemplateSpec{
  495. Spec: api.PodSpec{
  496. RestartPolicy: api.RestartPolicyAlways,
  497. DNSPolicy: api.DNSClusterFirst,
  498. Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
  499. },
  500. },
  501. },
  502. },
  503. },
  504. },
  505. }
  506. for k, v := range errorCases {
  507. errs := ValidateScheduledJob(&v)
  508. if len(errs) == 0 {
  509. t.Errorf("expected failure for %s", k)
  510. } else {
  511. s := strings.Split(k, ":")
  512. err := errs[0]
  513. if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
  514. t.Errorf("unexpected error: %v, expected: %s", err, k)
  515. }
  516. }
  517. }
  518. }
  519. func newBool(val bool) *bool {
  520. p := new(bool)
  521. *p = val
  522. return p
  523. }