expose_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  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 expose
  14. import (
  15. "fmt"
  16. "net/http"
  17. "strings"
  18. "testing"
  19. corev1 "k8s.io/api/core/v1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/runtime"
  22. "k8s.io/apimachinery/pkg/runtime/schema"
  23. "k8s.io/apimachinery/pkg/util/intstr"
  24. "k8s.io/cli-runtime/pkg/genericclioptions"
  25. "k8s.io/client-go/rest/fake"
  26. cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
  27. "k8s.io/kubernetes/pkg/kubectl/scheme"
  28. )
  29. func TestRunExposeService(t *testing.T) {
  30. tests := []struct {
  31. name string
  32. args []string
  33. ns string
  34. calls map[string]string
  35. input runtime.Object
  36. flags map[string]string
  37. output runtime.Object
  38. expected string
  39. status int
  40. }{
  41. {
  42. name: "expose-service-from-service-no-selector-defined",
  43. args: []string{"service", "baz"},
  44. ns: "test",
  45. calls: map[string]string{
  46. "GET": "/namespaces/test/services/baz",
  47. "POST": "/namespaces/test/services",
  48. },
  49. input: &corev1.Service{
  50. ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
  51. Spec: corev1.ServiceSpec{
  52. Selector: map[string]string{"app": "go"},
  53. },
  54. },
  55. flags: map[string]string{"protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test"},
  56. output: &corev1.Service{
  57. ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
  58. Spec: corev1.ServiceSpec{
  59. Ports: []corev1.ServicePort{
  60. {
  61. Protocol: corev1.ProtocolUDP,
  62. Port: 14,
  63. TargetPort: intstr.FromInt(14),
  64. },
  65. },
  66. Selector: map[string]string{"app": "go"},
  67. },
  68. },
  69. expected: "service/foo exposed",
  70. status: 200,
  71. },
  72. {
  73. name: "expose-service-from-service",
  74. args: []string{"service", "baz"},
  75. ns: "test",
  76. calls: map[string]string{
  77. "GET": "/namespaces/test/services/baz",
  78. "POST": "/namespaces/test/services",
  79. },
  80. input: &corev1.Service{
  81. ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
  82. Spec: corev1.ServiceSpec{
  83. Selector: map[string]string{"app": "go"},
  84. },
  85. },
  86. flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test"},
  87. output: &corev1.Service{
  88. ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
  89. Spec: corev1.ServiceSpec{
  90. Ports: []corev1.ServicePort{
  91. {
  92. Protocol: corev1.ProtocolUDP,
  93. Port: 14,
  94. TargetPort: intstr.FromInt(14),
  95. },
  96. },
  97. Selector: map[string]string{"func": "stream"},
  98. },
  99. },
  100. expected: "service/foo exposed",
  101. status: 200,
  102. },
  103. {
  104. name: "no-name-passed-from-the-cli",
  105. args: []string{"service", "mayor"},
  106. ns: "default",
  107. calls: map[string]string{
  108. "GET": "/namespaces/default/services/mayor",
  109. "POST": "/namespaces/default/services",
  110. },
  111. input: &corev1.Service{
  112. ObjectMeta: metav1.ObjectMeta{Name: "mayor", Namespace: "default", ResourceVersion: "12"},
  113. Spec: corev1.ServiceSpec{
  114. Selector: map[string]string{"run": "this"},
  115. },
  116. },
  117. // No --name flag specified below. Service will use the rc's name passed via the 'default-name' parameter
  118. flags: map[string]string{"selector": "run=this", "port": "80", "labels": "runas=amayor"},
  119. output: &corev1.Service{
  120. ObjectMeta: metav1.ObjectMeta{Name: "mayor", Namespace: "", Labels: map[string]string{"runas": "amayor"}},
  121. Spec: corev1.ServiceSpec{
  122. Ports: []corev1.ServicePort{
  123. {
  124. Protocol: corev1.ProtocolTCP,
  125. Port: 80,
  126. TargetPort: intstr.FromInt(80),
  127. },
  128. },
  129. Selector: map[string]string{"run": "this"},
  130. },
  131. },
  132. expected: "service/mayor exposed",
  133. status: 200,
  134. },
  135. {
  136. name: "expose-service",
  137. args: []string{"service", "baz"},
  138. ns: "test",
  139. calls: map[string]string{
  140. "GET": "/namespaces/test/services/baz",
  141. "POST": "/namespaces/test/services",
  142. },
  143. input: &corev1.Service{
  144. ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
  145. Spec: corev1.ServiceSpec{
  146. Selector: map[string]string{"app": "go"},
  147. },
  148. },
  149. flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "type": "LoadBalancer", "dry-run": "true"},
  150. output: &corev1.Service{
  151. ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
  152. Spec: corev1.ServiceSpec{
  153. Ports: []corev1.ServicePort{
  154. {
  155. Protocol: corev1.ProtocolUDP,
  156. Port: 14,
  157. TargetPort: intstr.FromInt(14),
  158. },
  159. },
  160. Selector: map[string]string{"func": "stream"},
  161. Type: corev1.ServiceTypeLoadBalancer,
  162. },
  163. },
  164. status: 200,
  165. },
  166. {
  167. name: "expose-affinity-service",
  168. args: []string{"service", "baz"},
  169. ns: "test",
  170. calls: map[string]string{
  171. "GET": "/namespaces/test/services/baz",
  172. "POST": "/namespaces/test/services",
  173. },
  174. input: &corev1.Service{
  175. ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
  176. Spec: corev1.ServiceSpec{
  177. Selector: map[string]string{"app": "go"},
  178. },
  179. },
  180. flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "type": "LoadBalancer", "session-affinity": "ClientIP", "dry-run": "true"},
  181. output: &corev1.Service{
  182. ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
  183. Spec: corev1.ServiceSpec{
  184. Ports: []corev1.ServicePort{
  185. {
  186. Protocol: corev1.ProtocolUDP,
  187. Port: 14,
  188. TargetPort: intstr.FromInt(14),
  189. },
  190. },
  191. Selector: map[string]string{"func": "stream"},
  192. Type: corev1.ServiceTypeLoadBalancer,
  193. SessionAffinity: corev1.ServiceAffinityClientIP,
  194. },
  195. },
  196. status: 200,
  197. },
  198. {
  199. name: "expose-service-cluster-ip",
  200. args: []string{"service", "baz"},
  201. ns: "test",
  202. calls: map[string]string{
  203. "GET": "/namespaces/test/services/baz",
  204. "POST": "/namespaces/test/services",
  205. },
  206. input: &corev1.Service{
  207. ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
  208. Spec: corev1.ServiceSpec{
  209. Selector: map[string]string{"app": "go"},
  210. },
  211. },
  212. flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "10.10.10.10", "dry-run": "true"},
  213. output: &corev1.Service{
  214. ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
  215. Spec: corev1.ServiceSpec{
  216. Ports: []corev1.ServicePort{
  217. {
  218. Protocol: corev1.ProtocolUDP,
  219. Port: 14,
  220. TargetPort: intstr.FromInt(14),
  221. },
  222. },
  223. Selector: map[string]string{"func": "stream"},
  224. ClusterIP: "10.10.10.10",
  225. },
  226. },
  227. expected: "service /foo exposed",
  228. status: 200,
  229. },
  230. {
  231. name: "expose-headless-service",
  232. args: []string{"service", "baz"},
  233. ns: "test",
  234. calls: map[string]string{
  235. "GET": "/namespaces/test/services/baz",
  236. "POST": "/namespaces/test/services",
  237. },
  238. input: &corev1.Service{
  239. ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
  240. Spec: corev1.ServiceSpec{
  241. Selector: map[string]string{"app": "go"},
  242. },
  243. },
  244. flags: map[string]string{"selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "true"},
  245. output: &corev1.Service{
  246. ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
  247. Spec: corev1.ServiceSpec{
  248. Ports: []corev1.ServicePort{
  249. {
  250. Protocol: corev1.ProtocolUDP,
  251. Port: 14,
  252. TargetPort: intstr.FromInt(14),
  253. },
  254. },
  255. Selector: map[string]string{"func": "stream"},
  256. ClusterIP: corev1.ClusterIPNone,
  257. },
  258. },
  259. expected: "service/foo exposed",
  260. status: 200,
  261. },
  262. {
  263. name: "expose-headless-service-no-port",
  264. args: []string{"service", "baz"},
  265. ns: "test",
  266. calls: map[string]string{
  267. "GET": "/namespaces/test/services/baz",
  268. "POST": "/namespaces/test/services",
  269. },
  270. input: &corev1.Service{
  271. ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
  272. Spec: corev1.ServiceSpec{
  273. Selector: map[string]string{"app": "go"},
  274. },
  275. },
  276. flags: map[string]string{"selector": "func=stream", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "true"},
  277. output: &corev1.Service{
  278. ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
  279. Spec: corev1.ServiceSpec{
  280. Ports: []corev1.ServicePort{},
  281. Selector: map[string]string{"func": "stream"},
  282. ClusterIP: corev1.ClusterIPNone,
  283. },
  284. },
  285. expected: "service/foo exposed",
  286. status: 200,
  287. },
  288. {
  289. name: "expose-from-file",
  290. args: []string{},
  291. ns: "test",
  292. calls: map[string]string{
  293. "GET": "/namespaces/test/services/redis-master",
  294. "POST": "/namespaces/test/services",
  295. },
  296. input: &corev1.Service{
  297. ObjectMeta: metav1.ObjectMeta{Name: "redis-master", Namespace: "test", ResourceVersion: "12"},
  298. Spec: corev1.ServiceSpec{
  299. Selector: map[string]string{"app": "go"},
  300. },
  301. },
  302. flags: map[string]string{"filename": "../../../../test/e2e/testing-manifests/guestbook/redis-master-service.yaml", "selector": "func=stream", "protocol": "UDP", "port": "14", "name": "foo", "labels": "svc=test", "dry-run": "true"},
  303. output: &corev1.Service{
  304. ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{"svc": "test"}},
  305. Spec: corev1.ServiceSpec{
  306. Ports: []corev1.ServicePort{
  307. {
  308. Protocol: corev1.ProtocolUDP,
  309. Port: 14,
  310. TargetPort: intstr.FromInt(14),
  311. },
  312. },
  313. Selector: map[string]string{"func": "stream"},
  314. },
  315. },
  316. status: 200,
  317. },
  318. {
  319. name: "truncate-name",
  320. args: []string{"pod", "a-name-that-is-toooo-big-for-a-service-because-it-can-only-handle-63-characters"},
  321. ns: "test",
  322. calls: map[string]string{
  323. "GET": "/namespaces/test/pods/a-name-that-is-toooo-big-for-a-service-because-it-can-only-handle-63-characters",
  324. "POST": "/namespaces/test/services",
  325. },
  326. input: &corev1.Pod{
  327. ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
  328. },
  329. flags: map[string]string{"selector": "svc=frompod", "port": "90", "labels": "svc=frompod", "generator": "service/v2"},
  330. output: &corev1.Service{
  331. ObjectMeta: metav1.ObjectMeta{Name: "a-name-that-is-toooo-big-for-a-service-because-it-can-only-handle-63-characters", Namespace: "", Labels: map[string]string{"svc": "frompod"}},
  332. Spec: corev1.ServiceSpec{
  333. Ports: []corev1.ServicePort{
  334. {
  335. Protocol: corev1.ProtocolTCP,
  336. Port: 90,
  337. TargetPort: intstr.FromInt(90),
  338. },
  339. },
  340. Selector: map[string]string{"svc": "frompod"},
  341. },
  342. },
  343. expected: "service/a-name-that-is-toooo-big-for-a-service-because-it-can-only-hand exposed",
  344. status: 200,
  345. },
  346. {
  347. name: "expose-multiport-object",
  348. args: []string{"service", "foo"},
  349. ns: "test",
  350. calls: map[string]string{
  351. "GET": "/namespaces/test/services/foo",
  352. "POST": "/namespaces/test/services",
  353. },
  354. input: &corev1.Service{
  355. ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
  356. Spec: corev1.ServiceSpec{
  357. Ports: []corev1.ServicePort{
  358. {
  359. Protocol: corev1.ProtocolTCP,
  360. Port: 80,
  361. TargetPort: intstr.FromInt(80),
  362. },
  363. {
  364. Protocol: corev1.ProtocolTCP,
  365. Port: 443,
  366. TargetPort: intstr.FromInt(443),
  367. },
  368. },
  369. },
  370. },
  371. flags: map[string]string{"selector": "svc=fromfoo", "generator": "service/v2", "name": "fromfoo", "dry-run": "true"},
  372. output: &corev1.Service{
  373. ObjectMeta: metav1.ObjectMeta{Name: "fromfoo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
  374. Spec: corev1.ServiceSpec{
  375. Ports: []corev1.ServicePort{
  376. {
  377. Name: "port-1",
  378. Protocol: corev1.ProtocolTCP,
  379. Port: 80,
  380. TargetPort: intstr.FromInt(80),
  381. },
  382. {
  383. Name: "port-2",
  384. Protocol: corev1.ProtocolTCP,
  385. Port: 443,
  386. TargetPort: intstr.FromInt(443),
  387. },
  388. },
  389. Selector: map[string]string{"svc": "fromfoo"},
  390. },
  391. },
  392. status: 200,
  393. },
  394. {
  395. name: "expose-multiprotocol-object",
  396. args: []string{"service", "foo"},
  397. ns: "test",
  398. calls: map[string]string{
  399. "GET": "/namespaces/test/services/foo",
  400. "POST": "/namespaces/test/services",
  401. },
  402. input: &corev1.Service{
  403. ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
  404. Spec: corev1.ServiceSpec{
  405. Ports: []corev1.ServicePort{
  406. {
  407. Protocol: corev1.ProtocolTCP,
  408. Port: 80,
  409. TargetPort: intstr.FromInt(80),
  410. },
  411. {
  412. Protocol: corev1.ProtocolUDP,
  413. Port: 8080,
  414. TargetPort: intstr.FromInt(8080),
  415. },
  416. {
  417. Protocol: corev1.ProtocolUDP,
  418. Port: 8081,
  419. TargetPort: intstr.FromInt(8081),
  420. },
  421. {
  422. Protocol: corev1.ProtocolSCTP,
  423. Port: 8082,
  424. TargetPort: intstr.FromInt(8082),
  425. },
  426. },
  427. },
  428. },
  429. flags: map[string]string{"selector": "svc=fromfoo", "generator": "service/v2", "name": "fromfoo", "dry-run": "true"},
  430. output: &corev1.Service{
  431. ObjectMeta: metav1.ObjectMeta{Name: "fromfoo", Namespace: "", Labels: map[string]string{"svc": "multiport"}},
  432. Spec: corev1.ServiceSpec{
  433. Ports: []corev1.ServicePort{
  434. {
  435. Name: "port-1",
  436. Protocol: corev1.ProtocolTCP,
  437. Port: 80,
  438. TargetPort: intstr.FromInt(80),
  439. },
  440. {
  441. Name: "port-2",
  442. Protocol: corev1.ProtocolUDP,
  443. Port: 8080,
  444. TargetPort: intstr.FromInt(8080),
  445. },
  446. {
  447. Name: "port-3",
  448. Protocol: corev1.ProtocolUDP,
  449. Port: 8081,
  450. TargetPort: intstr.FromInt(8081),
  451. },
  452. {
  453. Name: "port-4",
  454. Protocol: corev1.ProtocolSCTP,
  455. Port: 8082,
  456. TargetPort: intstr.FromInt(8082),
  457. },
  458. },
  459. Selector: map[string]string{"svc": "fromfoo"},
  460. },
  461. },
  462. status: 200,
  463. },
  464. {
  465. name: "expose-service-from-service-no-selector-defined-sctp",
  466. args: []string{"service", "baz"},
  467. ns: "test",
  468. calls: map[string]string{
  469. "GET": "/namespaces/test/services/baz",
  470. "POST": "/namespaces/test/services",
  471. },
  472. input: &corev1.Service{
  473. ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
  474. Spec: corev1.ServiceSpec{
  475. Selector: map[string]string{"app": "go"},
  476. },
  477. },
  478. flags: map[string]string{"protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test"},
  479. output: &corev1.Service{
  480. ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
  481. Spec: corev1.ServiceSpec{
  482. Ports: []corev1.ServicePort{
  483. {
  484. Protocol: corev1.ProtocolSCTP,
  485. Port: 14,
  486. TargetPort: intstr.FromInt(14),
  487. },
  488. },
  489. Selector: map[string]string{"app": "go"},
  490. },
  491. },
  492. expected: "service/foo exposed",
  493. status: 200,
  494. },
  495. {
  496. name: "expose-service-from-service-sctp",
  497. args: []string{"service", "baz"},
  498. ns: "test",
  499. calls: map[string]string{
  500. "GET": "/namespaces/test/services/baz",
  501. "POST": "/namespaces/test/services",
  502. },
  503. input: &corev1.Service{
  504. ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
  505. Spec: corev1.ServiceSpec{
  506. Selector: map[string]string{"app": "go"},
  507. },
  508. },
  509. flags: map[string]string{"selector": "func=stream", "protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test"},
  510. output: &corev1.Service{
  511. ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
  512. Spec: corev1.ServiceSpec{
  513. Ports: []corev1.ServicePort{
  514. {
  515. Protocol: corev1.ProtocolSCTP,
  516. Port: 14,
  517. TargetPort: intstr.FromInt(14),
  518. },
  519. },
  520. Selector: map[string]string{"func": "stream"},
  521. },
  522. },
  523. expected: "service/foo exposed",
  524. status: 200,
  525. },
  526. {
  527. name: "expose-service-cluster-ip-sctp",
  528. args: []string{"service", "baz"},
  529. ns: "test",
  530. calls: map[string]string{
  531. "GET": "/namespaces/test/services/baz",
  532. "POST": "/namespaces/test/services",
  533. },
  534. input: &corev1.Service{
  535. ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
  536. Spec: corev1.ServiceSpec{
  537. Selector: map[string]string{"app": "go"},
  538. },
  539. },
  540. flags: map[string]string{"selector": "func=stream", "protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "10.10.10.10", "dry-run": "true"},
  541. output: &corev1.Service{
  542. ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
  543. Spec: corev1.ServiceSpec{
  544. Ports: []corev1.ServicePort{
  545. {
  546. Protocol: corev1.ProtocolSCTP,
  547. Port: 14,
  548. TargetPort: intstr.FromInt(14),
  549. },
  550. },
  551. Selector: map[string]string{"func": "stream"},
  552. ClusterIP: "10.10.10.10",
  553. },
  554. },
  555. expected: "service /foo exposed",
  556. status: 200,
  557. },
  558. {
  559. name: "expose-headless-service-sctp",
  560. args: []string{"service", "baz"},
  561. ns: "test",
  562. calls: map[string]string{
  563. "GET": "/namespaces/test/services/baz",
  564. "POST": "/namespaces/test/services",
  565. },
  566. input: &corev1.Service{
  567. ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
  568. Spec: corev1.ServiceSpec{
  569. Selector: map[string]string{"app": "go"},
  570. },
  571. },
  572. flags: map[string]string{"selector": "func=stream", "protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "true"},
  573. output: &corev1.Service{
  574. ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}},
  575. Spec: corev1.ServiceSpec{
  576. Ports: []corev1.ServicePort{
  577. {
  578. Protocol: corev1.ProtocolSCTP,
  579. Port: 14,
  580. TargetPort: intstr.FromInt(14),
  581. },
  582. },
  583. Selector: map[string]string{"func": "stream"},
  584. ClusterIP: corev1.ClusterIPNone,
  585. },
  586. },
  587. expected: "service/foo exposed",
  588. status: 200,
  589. },
  590. }
  591. for _, test := range tests {
  592. t.Run(test.name, func(t *testing.T) {
  593. tf := cmdtesting.NewTestFactory().WithNamespace(test.ns)
  594. defer tf.Cleanup()
  595. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  596. ns := scheme.Codecs
  597. tf.Client = &fake.RESTClient{
  598. GroupVersion: schema.GroupVersion{Version: "v1"},
  599. NegotiatedSerializer: ns,
  600. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  601. switch p, m := req.URL.Path, req.Method; {
  602. case p == test.calls[m] && m == "GET":
  603. return &http.Response{StatusCode: test.status, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, test.input)}, nil
  604. default:
  605. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  606. return nil, nil
  607. }
  608. }),
  609. }
  610. ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
  611. cmd := NewCmdExposeService(tf, ioStreams)
  612. cmd.SetOutput(buf)
  613. for flag, value := range test.flags {
  614. cmd.Flags().Set(flag, value)
  615. }
  616. cmd.Run(cmd, test.args)
  617. out := buf.String()
  618. if _, ok := test.flags["dry-run"]; ok {
  619. test.expected = fmt.Sprintf("service/%s exposed (dry run)", test.flags["name"])
  620. }
  621. if !strings.Contains(out, test.expected) {
  622. t.Errorf("%s: Unexpected output! Expected\n%s\ngot\n%s", test.name, test.expected, out)
  623. }
  624. })
  625. }
  626. }