run_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  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 run
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io/ioutil"
  18. "net/http"
  19. "os"
  20. "reflect"
  21. "strings"
  22. "testing"
  23. "github.com/spf13/cobra"
  24. corev1 "k8s.io/api/core/v1"
  25. apiequality "k8s.io/apimachinery/pkg/api/equality"
  26. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  27. "k8s.io/apimachinery/pkg/runtime"
  28. "k8s.io/apimachinery/pkg/util/intstr"
  29. "k8s.io/cli-runtime/pkg/genericclioptions"
  30. restclient "k8s.io/client-go/rest"
  31. "k8s.io/client-go/rest/fake"
  32. "k8s.io/kubernetes/pkg/kubectl/cmd/delete"
  33. cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
  34. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  35. "k8s.io/kubernetes/pkg/kubectl/scheme"
  36. "k8s.io/kubernetes/pkg/kubectl/util/i18n"
  37. )
  38. func TestGetRestartPolicy(t *testing.T) {
  39. tests := []struct {
  40. input string
  41. interactive bool
  42. expected corev1.RestartPolicy
  43. expectErr bool
  44. }{
  45. {
  46. input: "",
  47. expected: corev1.RestartPolicyAlways,
  48. },
  49. {
  50. input: "",
  51. interactive: true,
  52. expected: corev1.RestartPolicyOnFailure,
  53. },
  54. {
  55. input: string(corev1.RestartPolicyAlways),
  56. interactive: true,
  57. expected: corev1.RestartPolicyAlways,
  58. },
  59. {
  60. input: string(corev1.RestartPolicyNever),
  61. interactive: true,
  62. expected: corev1.RestartPolicyNever,
  63. },
  64. {
  65. input: string(corev1.RestartPolicyAlways),
  66. expected: corev1.RestartPolicyAlways,
  67. },
  68. {
  69. input: string(corev1.RestartPolicyNever),
  70. expected: corev1.RestartPolicyNever,
  71. },
  72. {
  73. input: "foo",
  74. expectErr: true,
  75. },
  76. }
  77. for _, test := range tests {
  78. cmd := &cobra.Command{}
  79. cmd.Flags().String("restart", "", i18n.T("dummy restart flag)"))
  80. cmd.Flags().Lookup("restart").Value.Set(test.input)
  81. policy, err := getRestartPolicy(cmd, test.interactive)
  82. if test.expectErr && err == nil {
  83. t.Error("unexpected non-error")
  84. }
  85. if !test.expectErr && err != nil {
  86. t.Errorf("unexpected error: %v", err)
  87. }
  88. if !test.expectErr && policy != test.expected {
  89. t.Errorf("expected: %s, saw: %s (%s:%v)", test.expected, policy, test.input, test.interactive)
  90. }
  91. }
  92. }
  93. func TestGetEnv(t *testing.T) {
  94. test := struct {
  95. input []string
  96. expected []string
  97. }{
  98. input: []string{"a=b", "c=d"},
  99. expected: []string{"a=b", "c=d"},
  100. }
  101. cmd := &cobra.Command{}
  102. cmd.Flags().StringSlice("env", test.input, "")
  103. envStrings := cmdutil.GetFlagStringSlice(cmd, "env")
  104. if len(envStrings) != 2 || !reflect.DeepEqual(envStrings, test.expected) {
  105. t.Errorf("expected: %s, saw: %s", test.expected, envStrings)
  106. }
  107. }
  108. func TestRunArgsFollowDashRules(t *testing.T) {
  109. one := int32(1)
  110. rc := &corev1.ReplicationController{
  111. ObjectMeta: metav1.ObjectMeta{Name: "rc1", Namespace: "test", ResourceVersion: "18"},
  112. Spec: corev1.ReplicationControllerSpec{
  113. Replicas: &one,
  114. },
  115. }
  116. tests := []struct {
  117. args []string
  118. argsLenAtDash int
  119. expectError bool
  120. name string
  121. }{
  122. {
  123. args: []string{},
  124. argsLenAtDash: -1,
  125. expectError: true,
  126. name: "empty",
  127. },
  128. {
  129. args: []string{"foo"},
  130. argsLenAtDash: -1,
  131. expectError: false,
  132. name: "no cmd",
  133. },
  134. {
  135. args: []string{"foo", "sleep"},
  136. argsLenAtDash: -1,
  137. expectError: false,
  138. name: "cmd no dash",
  139. },
  140. {
  141. args: []string{"foo", "sleep"},
  142. argsLenAtDash: 1,
  143. expectError: false,
  144. name: "cmd has dash",
  145. },
  146. {
  147. args: []string{"foo", "sleep"},
  148. argsLenAtDash: 0,
  149. expectError: true,
  150. name: "no name",
  151. },
  152. }
  153. for _, test := range tests {
  154. t.Run(test.name, func(t *testing.T) {
  155. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  156. defer tf.Cleanup()
  157. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  158. ns := scheme.Codecs
  159. tf.Client = &fake.RESTClient{
  160. GroupVersion: corev1.SchemeGroupVersion,
  161. NegotiatedSerializer: ns,
  162. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  163. if req.URL.Path == "/namespaces/test/replicationcontrollers" {
  164. return &http.Response{StatusCode: 201, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, rc)}, nil
  165. }
  166. return &http.Response{
  167. StatusCode: http.StatusOK,
  168. Body: ioutil.NopCloser(bytes.NewBuffer([]byte("{}"))),
  169. }, nil
  170. }),
  171. }
  172. tf.ClientConfigVal = &restclient.Config{}
  173. cmd := NewCmdRun(tf, genericclioptions.NewTestIOStreamsDiscard())
  174. cmd.Flags().Set("image", "nginx")
  175. cmd.Flags().Set("generator", "run/v1")
  176. printFlags := genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme)
  177. printer, err := printFlags.ToPrinter()
  178. if err != nil {
  179. t.Errorf("unexpected error: %v", err)
  180. return
  181. }
  182. deleteFlags := delete.NewDeleteFlags("to use to replace the resource.")
  183. opts := &RunOptions{
  184. PrintFlags: printFlags,
  185. DeleteOptions: deleteFlags.ToOptions(nil, genericclioptions.NewTestIOStreamsDiscard()),
  186. IOStreams: genericclioptions.NewTestIOStreamsDiscard(),
  187. Image: "nginx",
  188. Generator: "run/v1",
  189. PrintObj: func(obj runtime.Object) error {
  190. return printer.PrintObj(obj, os.Stdout)
  191. },
  192. Recorder: genericclioptions.NoopRecorder{},
  193. ArgsLenAtDash: test.argsLenAtDash,
  194. }
  195. err = opts.Run(tf, cmd, test.args)
  196. if test.expectError && err == nil {
  197. t.Errorf("unexpected non-error (%s)", test.name)
  198. }
  199. if !test.expectError && err != nil {
  200. t.Errorf("unexpected error: %v (%s)", err, test.name)
  201. }
  202. })
  203. }
  204. }
  205. func TestGenerateService(t *testing.T) {
  206. tests := []struct {
  207. name string
  208. port string
  209. args []string
  210. serviceGenerator string
  211. params map[string]interface{}
  212. expectErr bool
  213. service corev1.Service
  214. expectPOST bool
  215. }{
  216. {
  217. name: "basic",
  218. port: "80",
  219. args: []string{"foo"},
  220. serviceGenerator: "service/v2",
  221. params: map[string]interface{}{
  222. "name": "foo",
  223. },
  224. expectErr: false,
  225. service: corev1.Service{
  226. TypeMeta: metav1.TypeMeta{
  227. Kind: "Service",
  228. APIVersion: "v1",
  229. },
  230. ObjectMeta: metav1.ObjectMeta{
  231. Name: "foo",
  232. },
  233. Spec: corev1.ServiceSpec{
  234. Ports: []corev1.ServicePort{
  235. {
  236. Port: 80,
  237. Protocol: "TCP",
  238. TargetPort: intstr.FromInt(80),
  239. },
  240. },
  241. Selector: map[string]string{
  242. "run": "foo",
  243. },
  244. },
  245. },
  246. expectPOST: true,
  247. },
  248. {
  249. name: "custom labels",
  250. port: "80",
  251. args: []string{"foo"},
  252. serviceGenerator: "service/v2",
  253. params: map[string]interface{}{
  254. "name": "foo",
  255. "labels": "app=bar",
  256. },
  257. expectErr: false,
  258. service: corev1.Service{
  259. TypeMeta: metav1.TypeMeta{
  260. Kind: "Service",
  261. APIVersion: "v1",
  262. },
  263. ObjectMeta: metav1.ObjectMeta{
  264. Name: "foo",
  265. Labels: map[string]string{"app": "bar"},
  266. },
  267. Spec: corev1.ServiceSpec{
  268. Ports: []corev1.ServicePort{
  269. {
  270. Port: 80,
  271. Protocol: "TCP",
  272. TargetPort: intstr.FromInt(80),
  273. },
  274. },
  275. Selector: map[string]string{
  276. "app": "bar",
  277. },
  278. },
  279. },
  280. expectPOST: true,
  281. },
  282. {
  283. expectErr: true,
  284. name: "missing port",
  285. expectPOST: false,
  286. },
  287. {
  288. name: "dry-run",
  289. port: "80",
  290. args: []string{"foo"},
  291. serviceGenerator: "service/v2",
  292. params: map[string]interface{}{
  293. "name": "foo",
  294. },
  295. expectErr: false,
  296. expectPOST: false,
  297. },
  298. }
  299. for _, test := range tests {
  300. t.Run(test.name, func(t *testing.T) {
  301. sawPOST := false
  302. tf := cmdtesting.NewTestFactory()
  303. defer tf.Cleanup()
  304. codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
  305. ns := scheme.Codecs
  306. tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
  307. tf.Client = &fake.RESTClient{
  308. GroupVersion: corev1.SchemeGroupVersion,
  309. NegotiatedSerializer: ns,
  310. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  311. switch p, m := req.URL.Path, req.Method; {
  312. case test.expectPOST && m == "POST" && p == "/namespaces/test/services":
  313. sawPOST = true
  314. body := cmdtesting.ObjBody(codec, &test.service)
  315. data, err := ioutil.ReadAll(req.Body)
  316. if err != nil {
  317. t.Fatalf("unexpected error: %v", err)
  318. }
  319. defer req.Body.Close()
  320. svc := &corev1.Service{}
  321. if err := runtime.DecodeInto(codec, data, svc); err != nil {
  322. t.Fatalf("unexpected error: %v", err)
  323. }
  324. // Copy things that are defaulted by the system
  325. test.service.Annotations = svc.Annotations
  326. if !apiequality.Semantic.DeepEqual(&test.service, svc) {
  327. t.Errorf("expected:\n%v\nsaw:\n%v\n", &test.service, svc)
  328. }
  329. return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil
  330. default:
  331. t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req)
  332. return nil, fmt.Errorf("unexpected request")
  333. }
  334. }),
  335. }
  336. printFlags := genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme)
  337. printer, err := printFlags.ToPrinter()
  338. if err != nil {
  339. t.Errorf("unexpected error: %v", err)
  340. return
  341. }
  342. ioStreams, _, buff, _ := genericclioptions.NewTestIOStreams()
  343. deleteFlags := delete.NewDeleteFlags("to use to replace the resource.")
  344. opts := &RunOptions{
  345. PrintFlags: printFlags,
  346. DeleteOptions: deleteFlags.ToOptions(nil, genericclioptions.NewTestIOStreamsDiscard()),
  347. IOStreams: ioStreams,
  348. Port: test.port,
  349. Recorder: genericclioptions.NoopRecorder{},
  350. PrintObj: func(obj runtime.Object) error {
  351. return printer.PrintObj(obj, buff)
  352. },
  353. }
  354. cmd := &cobra.Command{}
  355. cmd.Flags().Bool(cmdutil.ApplyAnnotationsFlag, false, "")
  356. cmd.Flags().Bool("record", false, "Record current kubectl command in the resource annotation. If set to false, do not record the command. If set to true, record the command. If not set, default to updating the existing annotation value only if one already exists.")
  357. addRunFlags(cmd, opts)
  358. if !test.expectPOST {
  359. opts.DryRun = true
  360. }
  361. if len(test.port) > 0 {
  362. cmd.Flags().Set("port", test.port)
  363. test.params["port"] = test.port
  364. }
  365. _, err = opts.generateService(tf, cmd, test.serviceGenerator, test.params, "test")
  366. if test.expectErr {
  367. if err == nil {
  368. t.Error("unexpected non-error")
  369. }
  370. return
  371. }
  372. if err != nil {
  373. t.Errorf("unexpected error: %v", err)
  374. }
  375. if test.expectPOST != sawPOST {
  376. t.Errorf("expectPost: %v, sawPost: %v", test.expectPOST, sawPOST)
  377. }
  378. })
  379. }
  380. }
  381. func TestRunValidations(t *testing.T) {
  382. tests := []struct {
  383. name string
  384. args []string
  385. flags map[string]string
  386. expectedErr string
  387. }{
  388. {
  389. name: "test missing name error",
  390. expectedErr: "NAME is required",
  391. },
  392. {
  393. name: "test missing --image error",
  394. args: []string{"test"},
  395. expectedErr: "--image is required",
  396. },
  397. {
  398. name: "test invalid image name error",
  399. args: []string{"test"},
  400. flags: map[string]string{
  401. "image": "#",
  402. },
  403. expectedErr: "Invalid image name",
  404. },
  405. {
  406. name: "test stdin replicas value",
  407. args: []string{"test"},
  408. flags: map[string]string{
  409. "image": "busybox",
  410. "stdin": "true",
  411. "replicas": "2",
  412. },
  413. expectedErr: "stdin requires that replicas is 1",
  414. },
  415. {
  416. name: "test rm errors when used on non-attached containers",
  417. args: []string{"test"},
  418. flags: map[string]string{
  419. "image": "busybox",
  420. "rm": "true",
  421. },
  422. expectedErr: "rm should only be used for attached containers",
  423. },
  424. {
  425. name: "test error on attached containers options",
  426. args: []string{"test"},
  427. flags: map[string]string{
  428. "image": "busybox",
  429. "attach": "true",
  430. "dry-run": "true",
  431. },
  432. expectedErr: "can't be used with attached containers options",
  433. },
  434. {
  435. name: "test error on attached containers options, with value from stdin",
  436. args: []string{"test"},
  437. flags: map[string]string{
  438. "image": "busybox",
  439. "stdin": "true",
  440. "dry-run": "true",
  441. },
  442. expectedErr: "can't be used with attached containers options",
  443. },
  444. {
  445. name: "test error on attached containers options, with value from stdin and tty",
  446. args: []string{"test"},
  447. flags: map[string]string{
  448. "image": "busybox",
  449. "tty": "true",
  450. "stdin": "true",
  451. "dry-run": "true",
  452. },
  453. expectedErr: "can't be used with attached containers options",
  454. },
  455. {
  456. name: "test error when tty=true and no stdin provided",
  457. args: []string{"test"},
  458. flags: map[string]string{
  459. "image": "busybox",
  460. "tty": "true",
  461. },
  462. expectedErr: "stdin is required for containers with -t/--tty",
  463. },
  464. }
  465. for _, test := range tests {
  466. t.Run(test.name, func(t *testing.T) {
  467. tf := cmdtesting.NewTestFactory().WithNamespace("test")
  468. defer tf.Cleanup()
  469. _, _, codec := cmdtesting.NewExternalScheme()
  470. tf.Client = &fake.RESTClient{
  471. NegotiatedSerializer: scheme.Codecs,
  472. Resp: &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, cmdtesting.NewInternalType("", "", ""))},
  473. }
  474. tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
  475. streams, _, _, bufErr := genericclioptions.NewTestIOStreams()
  476. cmdutil.BehaviorOnFatal(func(str string, code int) {
  477. bufErr.Write([]byte(str))
  478. })
  479. cmd := NewCmdRun(tf, streams)
  480. for flagName, flagValue := range test.flags {
  481. cmd.Flags().Set(flagName, flagValue)
  482. }
  483. cmd.Run(cmd, test.args)
  484. var err error
  485. if bufErr.Len() > 0 {
  486. err = fmt.Errorf("%v", bufErr.String())
  487. }
  488. if err != nil && len(test.expectedErr) > 0 {
  489. if !strings.Contains(err.Error(), test.expectedErr) {
  490. t.Errorf("unexpected error: %v", err)
  491. }
  492. }
  493. })
  494. }
  495. }