strategy_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. /*
  2. Copyright 2014 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. "context"
  16. "net/http"
  17. "net/url"
  18. "reflect"
  19. "testing"
  20. "k8s.io/apimachinery/pkg/api/errors"
  21. "k8s.io/apimachinery/pkg/api/resource"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/fields"
  24. "k8s.io/apimachinery/pkg/labels"
  25. "k8s.io/apimachinery/pkg/runtime"
  26. "k8s.io/apimachinery/pkg/types"
  27. genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
  28. apitesting "k8s.io/kubernetes/pkg/api/testing"
  29. api "k8s.io/kubernetes/pkg/apis/core"
  30. "k8s.io/kubernetes/pkg/kubelet/client"
  31. // ensure types are installed
  32. _ "k8s.io/kubernetes/pkg/apis/core/install"
  33. )
  34. func TestMatchPod(t *testing.T) {
  35. testCases := []struct {
  36. in *api.Pod
  37. fieldSelector fields.Selector
  38. expectMatch bool
  39. }{
  40. {
  41. in: &api.Pod{
  42. Spec: api.PodSpec{NodeName: "nodeA"},
  43. },
  44. fieldSelector: fields.ParseSelectorOrDie("spec.nodeName=nodeA"),
  45. expectMatch: true,
  46. },
  47. {
  48. in: &api.Pod{
  49. Spec: api.PodSpec{NodeName: "nodeB"},
  50. },
  51. fieldSelector: fields.ParseSelectorOrDie("spec.nodeName=nodeA"),
  52. expectMatch: false,
  53. },
  54. {
  55. in: &api.Pod{
  56. Spec: api.PodSpec{RestartPolicy: api.RestartPolicyAlways},
  57. },
  58. fieldSelector: fields.ParseSelectorOrDie("spec.restartPolicy=Always"),
  59. expectMatch: true,
  60. },
  61. {
  62. in: &api.Pod{
  63. Spec: api.PodSpec{RestartPolicy: api.RestartPolicyAlways},
  64. },
  65. fieldSelector: fields.ParseSelectorOrDie("spec.restartPolicy=Never"),
  66. expectMatch: false,
  67. },
  68. {
  69. in: &api.Pod{
  70. Spec: api.PodSpec{SchedulerName: "scheduler1"},
  71. },
  72. fieldSelector: fields.ParseSelectorOrDie("spec.schedulerName=scheduler1"),
  73. expectMatch: true,
  74. },
  75. {
  76. in: &api.Pod{
  77. Spec: api.PodSpec{SchedulerName: "scheduler1"},
  78. },
  79. fieldSelector: fields.ParseSelectorOrDie("spec.schedulerName=scheduler2"),
  80. expectMatch: false,
  81. },
  82. {
  83. in: &api.Pod{
  84. Spec: api.PodSpec{ServiceAccountName: "serviceAccount1"},
  85. },
  86. fieldSelector: fields.ParseSelectorOrDie("spec.serviceAccountName=serviceAccount1"),
  87. expectMatch: true,
  88. },
  89. {
  90. in: &api.Pod{
  91. Spec: api.PodSpec{SchedulerName: "serviceAccount1"},
  92. },
  93. fieldSelector: fields.ParseSelectorOrDie("spec.serviceAccountName=serviceAccount2"),
  94. expectMatch: false,
  95. },
  96. {
  97. in: &api.Pod{
  98. Status: api.PodStatus{Phase: api.PodRunning},
  99. },
  100. fieldSelector: fields.ParseSelectorOrDie("status.phase=Running"),
  101. expectMatch: true,
  102. },
  103. {
  104. in: &api.Pod{
  105. Status: api.PodStatus{Phase: api.PodRunning},
  106. },
  107. fieldSelector: fields.ParseSelectorOrDie("status.phase=Pending"),
  108. expectMatch: false,
  109. },
  110. {
  111. in: &api.Pod{
  112. Status: api.PodStatus{
  113. PodIPs: []api.PodIP{
  114. {IP: "1.2.3.4"},
  115. },
  116. },
  117. },
  118. fieldSelector: fields.ParseSelectorOrDie("status.podIP=1.2.3.4"),
  119. expectMatch: true,
  120. },
  121. {
  122. in: &api.Pod{
  123. Status: api.PodStatus{
  124. PodIPs: []api.PodIP{
  125. {IP: "1.2.3.4"},
  126. },
  127. },
  128. },
  129. fieldSelector: fields.ParseSelectorOrDie("status.podIP=4.3.2.1"),
  130. expectMatch: false,
  131. },
  132. {
  133. in: &api.Pod{
  134. Status: api.PodStatus{NominatedNodeName: "node1"},
  135. },
  136. fieldSelector: fields.ParseSelectorOrDie("status.nominatedNodeName=node1"),
  137. expectMatch: true,
  138. },
  139. {
  140. in: &api.Pod{
  141. Status: api.PodStatus{NominatedNodeName: "node1"},
  142. },
  143. fieldSelector: fields.ParseSelectorOrDie("status.nominatedNodeName=node2"),
  144. expectMatch: false,
  145. },
  146. {
  147. in: &api.Pod{
  148. Status: api.PodStatus{
  149. PodIPs: []api.PodIP{
  150. {IP: "2001:db8::"},
  151. },
  152. },
  153. },
  154. fieldSelector: fields.ParseSelectorOrDie("status.podIP=2001:db8::"),
  155. expectMatch: true,
  156. },
  157. {
  158. in: &api.Pod{
  159. Status: api.PodStatus{
  160. PodIPs: []api.PodIP{
  161. {IP: "2001:db8::"},
  162. },
  163. },
  164. },
  165. fieldSelector: fields.ParseSelectorOrDie("status.podIP=2001:db7::"),
  166. expectMatch: false,
  167. },
  168. }
  169. for _, testCase := range testCases {
  170. m := MatchPod(labels.Everything(), testCase.fieldSelector)
  171. result, err := m.Matches(testCase.in)
  172. if err != nil {
  173. t.Errorf("Unexpected error %v", err)
  174. }
  175. if result != testCase.expectMatch {
  176. t.Errorf("Result %v, Expected %v, Selector: %v, Pod: %v", result, testCase.expectMatch, testCase.fieldSelector.String(), testCase.in)
  177. }
  178. }
  179. }
  180. func getResourceList(cpu, memory string) api.ResourceList {
  181. res := api.ResourceList{}
  182. if cpu != "" {
  183. res[api.ResourceCPU] = resource.MustParse(cpu)
  184. }
  185. if memory != "" {
  186. res[api.ResourceMemory] = resource.MustParse(memory)
  187. }
  188. return res
  189. }
  190. func getResourceRequirements(requests, limits api.ResourceList) api.ResourceRequirements {
  191. res := api.ResourceRequirements{}
  192. res.Requests = requests
  193. res.Limits = limits
  194. return res
  195. }
  196. func newContainer(name string, requests api.ResourceList, limits api.ResourceList) api.Container {
  197. return api.Container{
  198. Name: name,
  199. Resources: getResourceRequirements(requests, limits),
  200. }
  201. }
  202. func newPod(name string, containers []api.Container) *api.Pod {
  203. return &api.Pod{
  204. ObjectMeta: metav1.ObjectMeta{
  205. Name: name,
  206. },
  207. Spec: api.PodSpec{
  208. Containers: containers,
  209. },
  210. }
  211. }
  212. func TestGetPodQOS(t *testing.T) {
  213. testCases := []struct {
  214. pod *api.Pod
  215. expected api.PodQOSClass
  216. }{
  217. {
  218. pod: newPod("guaranteed", []api.Container{
  219. newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")),
  220. }),
  221. expected: api.PodQOSGuaranteed,
  222. },
  223. {
  224. pod: newPod("best-effort", []api.Container{
  225. newContainer("best-effort", getResourceList("", ""), getResourceList("", "")),
  226. }),
  227. expected: api.PodQOSBestEffort,
  228. },
  229. {
  230. pod: newPod("burstable", []api.Container{
  231. newContainer("burstable", getResourceList("100m", "100Mi"), getResourceList("", "")),
  232. }),
  233. expected: api.PodQOSBurstable,
  234. },
  235. }
  236. for id, testCase := range testCases {
  237. Strategy.PrepareForCreate(genericapirequest.NewContext(), testCase.pod)
  238. actual := testCase.pod.Status.QOSClass
  239. if actual != testCase.expected {
  240. t.Errorf("[%d]: invalid qos pod %s, expected: %s, actual: %s", id, testCase.pod.Name, testCase.expected, actual)
  241. }
  242. }
  243. }
  244. func TestCheckGracefulDelete(t *testing.T) {
  245. defaultGracePeriod := int64(30)
  246. tcs := []struct {
  247. in *api.Pod
  248. gracePeriod int64
  249. }{
  250. {
  251. in: &api.Pod{
  252. Spec: api.PodSpec{NodeName: "something"},
  253. Status: api.PodStatus{Phase: api.PodPending},
  254. },
  255. gracePeriod: defaultGracePeriod,
  256. },
  257. {
  258. in: &api.Pod{
  259. Spec: api.PodSpec{NodeName: "something"},
  260. Status: api.PodStatus{Phase: api.PodFailed},
  261. },
  262. gracePeriod: 0,
  263. },
  264. {
  265. in: &api.Pod{
  266. Spec: api.PodSpec{},
  267. Status: api.PodStatus{Phase: api.PodPending},
  268. },
  269. gracePeriod: 0,
  270. },
  271. {
  272. in: &api.Pod{
  273. Spec: api.PodSpec{},
  274. Status: api.PodStatus{Phase: api.PodSucceeded},
  275. },
  276. gracePeriod: 0,
  277. },
  278. {
  279. in: &api.Pod{
  280. Spec: api.PodSpec{},
  281. Status: api.PodStatus{},
  282. },
  283. gracePeriod: 0,
  284. },
  285. }
  286. for _, tc := range tcs {
  287. out := &metav1.DeleteOptions{GracePeriodSeconds: &defaultGracePeriod}
  288. Strategy.CheckGracefulDelete(genericapirequest.NewContext(), tc.in, out)
  289. if out.GracePeriodSeconds == nil {
  290. t.Errorf("out grace period was nil but supposed to be %v", tc.gracePeriod)
  291. }
  292. if *(out.GracePeriodSeconds) != tc.gracePeriod {
  293. t.Errorf("out grace period was %v but was expected to be %v", *out, tc.gracePeriod)
  294. }
  295. }
  296. }
  297. type mockPodGetter struct {
  298. pod *api.Pod
  299. }
  300. func (g mockPodGetter) Get(context.Context, string, *metav1.GetOptions) (runtime.Object, error) {
  301. return g.pod, nil
  302. }
  303. func TestCheckLogLocation(t *testing.T) {
  304. ctx := genericapirequest.NewDefaultContext()
  305. fakePodName := "test"
  306. tcs := []struct {
  307. name string
  308. in *api.Pod
  309. opts *api.PodLogOptions
  310. expectedErr error
  311. expectedTransport http.RoundTripper
  312. }{
  313. {
  314. name: "simple",
  315. in: &api.Pod{
  316. ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
  317. Spec: api.PodSpec{
  318. Containers: []api.Container{
  319. {Name: "mycontainer"},
  320. },
  321. NodeName: "foo",
  322. },
  323. Status: api.PodStatus{},
  324. },
  325. opts: &api.PodLogOptions{},
  326. expectedErr: nil,
  327. expectedTransport: fakeSecureRoundTripper,
  328. },
  329. {
  330. name: "insecure",
  331. in: &api.Pod{
  332. ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
  333. Spec: api.PodSpec{
  334. Containers: []api.Container{
  335. {Name: "mycontainer"},
  336. },
  337. NodeName: "foo",
  338. },
  339. Status: api.PodStatus{},
  340. },
  341. opts: &api.PodLogOptions{
  342. InsecureSkipTLSVerifyBackend: true,
  343. },
  344. expectedErr: nil,
  345. expectedTransport: fakeInsecureRoundTripper,
  346. },
  347. {
  348. name: "missing container",
  349. in: &api.Pod{
  350. ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
  351. Spec: api.PodSpec{},
  352. Status: api.PodStatus{},
  353. },
  354. opts: &api.PodLogOptions{},
  355. expectedErr: errors.NewBadRequest("a container name must be specified for pod test"),
  356. expectedTransport: nil,
  357. },
  358. {
  359. name: "choice of two containers",
  360. in: &api.Pod{
  361. ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
  362. Spec: api.PodSpec{
  363. Containers: []api.Container{
  364. {Name: "container1"},
  365. {Name: "container2"},
  366. },
  367. },
  368. Status: api.PodStatus{},
  369. },
  370. opts: &api.PodLogOptions{},
  371. expectedErr: errors.NewBadRequest("a container name must be specified for pod test, choose one of: [container1 container2]"),
  372. expectedTransport: nil,
  373. },
  374. {
  375. name: "initcontainers",
  376. in: &api.Pod{
  377. ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
  378. Spec: api.PodSpec{
  379. Containers: []api.Container{
  380. {Name: "container1"},
  381. {Name: "container2"},
  382. },
  383. InitContainers: []api.Container{
  384. {Name: "initcontainer1"},
  385. },
  386. },
  387. Status: api.PodStatus{},
  388. },
  389. opts: &api.PodLogOptions{},
  390. expectedErr: errors.NewBadRequest("a container name must be specified for pod test, choose one of: [container1 container2] or one of the init containers: [initcontainer1]"),
  391. expectedTransport: nil,
  392. },
  393. {
  394. name: "bad container",
  395. in: &api.Pod{
  396. ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
  397. Spec: api.PodSpec{
  398. Containers: []api.Container{
  399. {Name: "container1"},
  400. {Name: "container2"},
  401. },
  402. },
  403. Status: api.PodStatus{},
  404. },
  405. opts: &api.PodLogOptions{
  406. Container: "unknown",
  407. },
  408. expectedErr: errors.NewBadRequest("container unknown is not valid for pod test"),
  409. expectedTransport: nil,
  410. },
  411. {
  412. name: "good with two containers",
  413. in: &api.Pod{
  414. ObjectMeta: metav1.ObjectMeta{Name: fakePodName},
  415. Spec: api.PodSpec{
  416. Containers: []api.Container{
  417. {Name: "container1"},
  418. {Name: "container2"},
  419. },
  420. NodeName: "foo",
  421. },
  422. Status: api.PodStatus{},
  423. },
  424. opts: &api.PodLogOptions{
  425. Container: "container2",
  426. },
  427. expectedErr: nil,
  428. expectedTransport: fakeSecureRoundTripper,
  429. },
  430. }
  431. for _, tc := range tcs {
  432. t.Run(tc.name, func(t *testing.T) {
  433. getter := &mockPodGetter{tc.in}
  434. connectionGetter := &mockConnectionInfoGetter{&client.ConnectionInfo{
  435. Transport: fakeSecureRoundTripper,
  436. InsecureSkipTLSVerifyTransport: fakeInsecureRoundTripper,
  437. }}
  438. _, actualTransport, err := LogLocation(ctx, getter, connectionGetter, fakePodName, tc.opts)
  439. if !reflect.DeepEqual(err, tc.expectedErr) {
  440. t.Errorf("expected %v, got %v", tc.expectedErr, err)
  441. }
  442. if actualTransport != tc.expectedTransport {
  443. t.Errorf("expected %v, got %v", tc.expectedTransport, actualTransport)
  444. }
  445. })
  446. }
  447. }
  448. func TestSelectableFieldLabelConversions(t *testing.T) {
  449. apitesting.TestSelectableFieldLabelConversionsOfKind(t,
  450. "v1",
  451. "Pod",
  452. ToSelectableFields(&api.Pod{}),
  453. nil,
  454. )
  455. }
  456. type mockConnectionInfoGetter struct {
  457. info *client.ConnectionInfo
  458. }
  459. func (g mockConnectionInfoGetter) GetConnectionInfo(ctx context.Context, nodeName types.NodeName) (*client.ConnectionInfo, error) {
  460. return g.info, nil
  461. }
  462. func TestPortForwardLocation(t *testing.T) {
  463. ctx := genericapirequest.NewDefaultContext()
  464. tcs := []struct {
  465. in *api.Pod
  466. info *client.ConnectionInfo
  467. opts *api.PodPortForwardOptions
  468. expectedErr error
  469. expectedURL *url.URL
  470. }{
  471. {
  472. in: &api.Pod{
  473. Spec: api.PodSpec{},
  474. },
  475. opts: &api.PodPortForwardOptions{},
  476. expectedErr: errors.NewBadRequest("pod test does not have a host assigned"),
  477. },
  478. {
  479. in: &api.Pod{
  480. ObjectMeta: metav1.ObjectMeta{
  481. Namespace: "ns",
  482. Name: "pod1",
  483. },
  484. Spec: api.PodSpec{
  485. NodeName: "node1",
  486. },
  487. },
  488. info: &client.ConnectionInfo{},
  489. opts: &api.PodPortForwardOptions{},
  490. expectedURL: &url.URL{Host: ":", Path: "/portForward/ns/pod1"},
  491. },
  492. {
  493. in: &api.Pod{
  494. ObjectMeta: metav1.ObjectMeta{
  495. Namespace: "ns",
  496. Name: "pod1",
  497. },
  498. Spec: api.PodSpec{
  499. NodeName: "node1",
  500. },
  501. },
  502. info: &client.ConnectionInfo{},
  503. opts: &api.PodPortForwardOptions{Ports: []int32{80}},
  504. expectedURL: &url.URL{Host: ":", Path: "/portForward/ns/pod1", RawQuery: "port=80"},
  505. },
  506. }
  507. for _, tc := range tcs {
  508. getter := &mockPodGetter{tc.in}
  509. connectionGetter := &mockConnectionInfoGetter{tc.info}
  510. loc, _, err := PortForwardLocation(ctx, getter, connectionGetter, "test", tc.opts)
  511. if !reflect.DeepEqual(err, tc.expectedErr) {
  512. t.Errorf("expected %v, got %v", tc.expectedErr, err)
  513. }
  514. if !reflect.DeepEqual(loc, tc.expectedURL) {
  515. t.Errorf("expected %v, got %v", tc.expectedURL, loc)
  516. }
  517. }
  518. }
  519. func TestGetPodIP(t *testing.T) {
  520. testCases := []struct {
  521. name string
  522. pod *api.Pod
  523. expectedIP string
  524. }{
  525. {
  526. name: "nil pod",
  527. pod: nil,
  528. expectedIP: "",
  529. },
  530. {
  531. name: "no status object",
  532. pod: &api.Pod{
  533. ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
  534. Spec: api.PodSpec{},
  535. },
  536. expectedIP: "",
  537. },
  538. {
  539. name: "no pod ips",
  540. pod: &api.Pod{
  541. ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
  542. Spec: api.PodSpec{},
  543. Status: api.PodStatus{},
  544. },
  545. expectedIP: "",
  546. },
  547. {
  548. name: "empty list",
  549. pod: &api.Pod{
  550. ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
  551. Spec: api.PodSpec{},
  552. Status: api.PodStatus{
  553. PodIPs: []api.PodIP{},
  554. },
  555. },
  556. expectedIP: "",
  557. },
  558. {
  559. name: "1 ip",
  560. pod: &api.Pod{
  561. ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
  562. Spec: api.PodSpec{},
  563. Status: api.PodStatus{
  564. PodIPs: []api.PodIP{
  565. {IP: "10.0.0.10"},
  566. },
  567. },
  568. },
  569. expectedIP: "10.0.0.10",
  570. },
  571. {
  572. name: "multiple ips",
  573. pod: &api.Pod{
  574. ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod1"},
  575. Spec: api.PodSpec{},
  576. Status: api.PodStatus{
  577. PodIPs: []api.PodIP{
  578. {IP: "10.0.0.10"},
  579. {IP: "10.0.0.20"},
  580. },
  581. },
  582. },
  583. expectedIP: "10.0.0.10",
  584. },
  585. }
  586. for _, tc := range testCases {
  587. t.Run(tc.name, func(t *testing.T) {
  588. podIP := getPodIP(tc.pod)
  589. if podIP != tc.expectedIP {
  590. t.Errorf("expected pod ip:%v does not match actual %v", tc.expectedIP, podIP)
  591. }
  592. })
  593. }
  594. }
  595. type fakeTransport struct {
  596. val string
  597. }
  598. func (f fakeTransport) RoundTrip(*http.Request) (*http.Response, error) {
  599. return nil, nil
  600. }
  601. var (
  602. fakeSecureRoundTripper = fakeTransport{val: "secure"}
  603. fakeInsecureRoundTripper = fakeTransport{val: "insecure"}
  604. )