exec_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  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 cmd
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io"
  18. "io/ioutil"
  19. "net/http"
  20. "net/url"
  21. "reflect"
  22. "strings"
  23. "testing"
  24. "github.com/spf13/cobra"
  25. "k8s.io/kubernetes/pkg/api"
  26. "k8s.io/kubernetes/pkg/api/testapi"
  27. "k8s.io/kubernetes/pkg/api/unversioned"
  28. "k8s.io/kubernetes/pkg/client/restclient"
  29. "k8s.io/kubernetes/pkg/client/unversioned/fake"
  30. "k8s.io/kubernetes/pkg/util/term"
  31. )
  32. type fakeRemoteExecutor struct {
  33. method string
  34. url *url.URL
  35. execErr error
  36. }
  37. func (f *fakeRemoteExecutor) Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue term.TerminalSizeQueue) error {
  38. f.method = method
  39. f.url = url
  40. return f.execErr
  41. }
  42. func TestPodAndContainer(t *testing.T) {
  43. tests := []struct {
  44. args []string
  45. argsLenAtDash int
  46. p *ExecOptions
  47. name string
  48. expectError bool
  49. expectedPod string
  50. expectedContainer string
  51. expectedArgs []string
  52. }{
  53. {
  54. p: &ExecOptions{},
  55. argsLenAtDash: -1,
  56. expectError: true,
  57. name: "empty",
  58. },
  59. {
  60. p: &ExecOptions{StreamOptions: StreamOptions{PodName: "foo"}},
  61. argsLenAtDash: -1,
  62. expectError: true,
  63. name: "no cmd",
  64. },
  65. {
  66. p: &ExecOptions{StreamOptions: StreamOptions{PodName: "foo", ContainerName: "bar"}},
  67. argsLenAtDash: -1,
  68. expectError: true,
  69. name: "no cmd, w/ container",
  70. },
  71. {
  72. p: &ExecOptions{StreamOptions: StreamOptions{PodName: "foo"}},
  73. args: []string{"cmd"},
  74. argsLenAtDash: -1,
  75. expectedPod: "foo",
  76. expectedArgs: []string{"cmd"},
  77. name: "pod in flags",
  78. },
  79. {
  80. p: &ExecOptions{},
  81. args: []string{"foo", "cmd"},
  82. argsLenAtDash: 0,
  83. expectError: true,
  84. name: "no pod, pod name is behind dash",
  85. },
  86. {
  87. p: &ExecOptions{},
  88. args: []string{"foo"},
  89. argsLenAtDash: -1,
  90. expectError: true,
  91. name: "no cmd, w/o flags",
  92. },
  93. {
  94. p: &ExecOptions{},
  95. args: []string{"foo", "cmd"},
  96. argsLenAtDash: -1,
  97. expectedPod: "foo",
  98. expectedArgs: []string{"cmd"},
  99. name: "cmd, w/o flags",
  100. },
  101. {
  102. p: &ExecOptions{},
  103. args: []string{"foo", "cmd"},
  104. argsLenAtDash: 1,
  105. expectedPod: "foo",
  106. expectedArgs: []string{"cmd"},
  107. name: "cmd, cmd is behind dash",
  108. },
  109. {
  110. p: &ExecOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
  111. args: []string{"foo", "cmd"},
  112. argsLenAtDash: -1,
  113. expectedPod: "foo",
  114. expectedContainer: "bar",
  115. expectedArgs: []string{"cmd"},
  116. name: "cmd, container in flag",
  117. },
  118. }
  119. for _, test := range tests {
  120. f, tf, _, ns := NewAPIFactory()
  121. tf.Client = &fake.RESTClient{
  122. NegotiatedSerializer: ns,
  123. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { return nil, nil }),
  124. }
  125. tf.Namespace = "test"
  126. tf.ClientConfig = &restclient.Config{}
  127. cmd := &cobra.Command{}
  128. options := test.p
  129. err := options.Complete(f, cmd, test.args, test.argsLenAtDash)
  130. if test.expectError && err == nil {
  131. t.Errorf("unexpected non-error (%s)", test.name)
  132. }
  133. if !test.expectError && err != nil {
  134. t.Errorf("unexpected error: %v (%s)", err, test.name)
  135. }
  136. if err != nil {
  137. continue
  138. }
  139. if options.PodName != test.expectedPod {
  140. t.Errorf("expected: %s, got: %s (%s)", test.expectedPod, options.PodName, test.name)
  141. }
  142. if options.ContainerName != test.expectedContainer {
  143. t.Errorf("expected: %s, got: %s (%s)", test.expectedContainer, options.ContainerName, test.name)
  144. }
  145. if !reflect.DeepEqual(test.expectedArgs, options.Command) {
  146. t.Errorf("expected: %v, got %v (%s)", test.expectedArgs, options.Command, test.name)
  147. }
  148. }
  149. }
  150. func TestExec(t *testing.T) {
  151. version := testapi.Default.GroupVersion().Version
  152. tests := []struct {
  153. name, version, podPath, execPath, container string
  154. pod *api.Pod
  155. execErr bool
  156. }{
  157. {
  158. name: "pod exec",
  159. version: version,
  160. podPath: "/api/" + version + "/namespaces/test/pods/foo",
  161. execPath: "/api/" + version + "/namespaces/test/pods/foo/exec",
  162. pod: execPod(),
  163. },
  164. {
  165. name: "pod exec error",
  166. version: version,
  167. podPath: "/api/" + version + "/namespaces/test/pods/foo",
  168. execPath: "/api/" + version + "/namespaces/test/pods/foo/exec",
  169. pod: execPod(),
  170. execErr: true,
  171. },
  172. }
  173. for _, test := range tests {
  174. f, tf, codec, ns := NewAPIFactory()
  175. tf.Client = &fake.RESTClient{
  176. NegotiatedSerializer: ns,
  177. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  178. switch p, m := req.URL.Path, req.Method; {
  179. case p == test.podPath && m == "GET":
  180. body := objBody(codec, test.pod)
  181. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
  182. default:
  183. // Ensures no GET is performed when deleting by name
  184. t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req)
  185. return nil, fmt.Errorf("unexpected request")
  186. }
  187. }),
  188. }
  189. tf.Namespace = "test"
  190. tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: test.version}}}
  191. bufOut := bytes.NewBuffer([]byte{})
  192. bufErr := bytes.NewBuffer([]byte{})
  193. bufIn := bytes.NewBuffer([]byte{})
  194. ex := &fakeRemoteExecutor{}
  195. if test.execErr {
  196. ex.execErr = fmt.Errorf("exec error")
  197. }
  198. params := &ExecOptions{
  199. StreamOptions: StreamOptions{
  200. PodName: "foo",
  201. ContainerName: "bar",
  202. In: bufIn,
  203. Out: bufOut,
  204. Err: bufErr,
  205. },
  206. Executor: ex,
  207. }
  208. cmd := &cobra.Command{}
  209. args := []string{"test", "command"}
  210. if err := params.Complete(f, cmd, args, -1); err != nil {
  211. t.Fatal(err)
  212. }
  213. err := params.Run()
  214. if test.execErr && err != ex.execErr {
  215. t.Errorf("%s: Unexpected exec error: %v", test.name, err)
  216. continue
  217. }
  218. if !test.execErr && err != nil {
  219. t.Errorf("%s: Unexpected error: %v", test.name, err)
  220. continue
  221. }
  222. if test.execErr {
  223. continue
  224. }
  225. if ex.url.Path != test.execPath {
  226. t.Errorf("%s: Did not get expected path for exec request", test.name)
  227. continue
  228. }
  229. if ex.method != "POST" {
  230. t.Errorf("%s: Did not get method for exec request: %s", test.name, ex.method)
  231. }
  232. }
  233. }
  234. func execPod() *api.Pod {
  235. return &api.Pod{
  236. ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
  237. Spec: api.PodSpec{
  238. RestartPolicy: api.RestartPolicyAlways,
  239. DNSPolicy: api.DNSClusterFirst,
  240. Containers: []api.Container{
  241. {
  242. Name: "bar",
  243. },
  244. },
  245. },
  246. Status: api.PodStatus{
  247. Phase: api.PodRunning,
  248. },
  249. }
  250. }
  251. func TestSetupTTY(t *testing.T) {
  252. stderr := &bytes.Buffer{}
  253. // test 1 - don't attach stdin
  254. o := &StreamOptions{
  255. // InterruptParent: ,
  256. Stdin: false,
  257. In: &bytes.Buffer{},
  258. Out: &bytes.Buffer{},
  259. Err: stderr,
  260. TTY: true,
  261. }
  262. tty := o.setupTTY()
  263. if o.In != nil {
  264. t.Errorf("don't attach stdin: o.In should be nil")
  265. }
  266. if tty.In != nil {
  267. t.Errorf("don't attach stdin: tty.In should be nil")
  268. }
  269. if o.TTY {
  270. t.Errorf("don't attach stdin: o.TTY should be false")
  271. }
  272. if tty.Raw {
  273. t.Errorf("don't attach stdin: tty.Raw should be false")
  274. }
  275. if len(stderr.String()) > 0 {
  276. t.Errorf("don't attach stdin: stderr wasn't empty: %s", stderr.String())
  277. }
  278. // tests from here on attach stdin
  279. // test 2 - don't request a TTY
  280. o.Stdin = true
  281. o.In = &bytes.Buffer{}
  282. o.TTY = false
  283. tty = o.setupTTY()
  284. if o.In == nil {
  285. t.Errorf("attach stdin, no TTY: o.In should not be nil")
  286. }
  287. if tty.In != o.In {
  288. t.Errorf("attach stdin, no TTY: tty.In should equal o.In")
  289. }
  290. if o.TTY {
  291. t.Errorf("attach stdin, no TTY: o.TTY should be false")
  292. }
  293. if tty.Raw {
  294. t.Errorf("attach stdin, no TTY: tty.Raw should be false")
  295. }
  296. if len(stderr.String()) > 0 {
  297. t.Errorf("attach stdin, no TTY: stderr wasn't empty: %s", stderr.String())
  298. }
  299. // test 3 - request a TTY, but stdin is not a terminal
  300. o.Stdin = true
  301. o.In = &bytes.Buffer{}
  302. o.Err = stderr
  303. o.TTY = true
  304. tty = o.setupTTY()
  305. if o.In == nil {
  306. t.Errorf("attach stdin, TTY, not a terminal: o.In should not be nil")
  307. }
  308. if tty.In != o.In {
  309. t.Errorf("attach stdin, TTY, not a terminal: tty.In should equal o.In")
  310. }
  311. if o.TTY {
  312. t.Errorf("attach stdin, TTY, not a terminal: o.TTY should be false")
  313. }
  314. if tty.Raw {
  315. t.Errorf("attach stdin, TTY, not a terminal: tty.Raw should be false")
  316. }
  317. if !strings.Contains(stderr.String(), "input is not a terminal") {
  318. t.Errorf("attach stdin, TTY, not a terminal: expected 'input is not a terminal' to stderr")
  319. }
  320. // test 4 - request a TTY, stdin is a terminal
  321. o.Stdin = true
  322. o.In = &bytes.Buffer{}
  323. stderr.Reset()
  324. o.TTY = true
  325. overrideStdin := ioutil.NopCloser(&bytes.Buffer{})
  326. overrideStdout := &bytes.Buffer{}
  327. overrideStderr := &bytes.Buffer{}
  328. o.overrideStreams = func() (io.ReadCloser, io.Writer, io.Writer) {
  329. return overrideStdin, overrideStdout, overrideStderr
  330. }
  331. o.isTerminalIn = func(tty term.TTY) bool {
  332. return true
  333. }
  334. tty = o.setupTTY()
  335. if o.In != overrideStdin {
  336. t.Errorf("attach stdin, TTY, is a terminal: o.In should equal overrideStdin")
  337. }
  338. if tty.In != o.In {
  339. t.Errorf("attach stdin, TTY, is a terminal: tty.In should equal o.In")
  340. }
  341. if !o.TTY {
  342. t.Errorf("attach stdin, TTY, is a terminal: o.TTY should be true")
  343. }
  344. if !tty.Raw {
  345. t.Errorf("attach stdin, TTY, is a terminal: tty.Raw should be true")
  346. }
  347. if len(stderr.String()) > 0 {
  348. t.Errorf("attach stdin, TTY, is a terminal: stderr wasn't empty: %s", stderr.String())
  349. }
  350. if o.Out != overrideStdout {
  351. t.Errorf("attach stdin, TTY, is a terminal: o.Out should equal overrideStdout")
  352. }
  353. if tty.Out != o.Out {
  354. t.Errorf("attach stdin, TTY, is a terminal: tty.Out should equal o.Out")
  355. }
  356. }