helper_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  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 resource
  14. import (
  15. "bytes"
  16. "errors"
  17. "io"
  18. "io/ioutil"
  19. "net/http"
  20. "reflect"
  21. "strings"
  22. "testing"
  23. "k8s.io/kubernetes/pkg/api"
  24. "k8s.io/kubernetes/pkg/api/testapi"
  25. apitesting "k8s.io/kubernetes/pkg/api/testing"
  26. "k8s.io/kubernetes/pkg/api/unversioned"
  27. "k8s.io/kubernetes/pkg/client/unversioned/fake"
  28. "k8s.io/kubernetes/pkg/labels"
  29. "k8s.io/kubernetes/pkg/runtime"
  30. )
  31. func objBody(obj runtime.Object) io.ReadCloser {
  32. return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), obj))))
  33. }
  34. func header() http.Header {
  35. header := http.Header{}
  36. header.Set("Content-Type", runtime.ContentTypeJSON)
  37. return header
  38. }
  39. // splitPath returns the segments for a URL path.
  40. func splitPath(path string) []string {
  41. path = strings.Trim(path, "/")
  42. if path == "" {
  43. return []string{}
  44. }
  45. return strings.Split(path, "/")
  46. }
  47. func TestHelperDelete(t *testing.T) {
  48. tests := []struct {
  49. Err bool
  50. Req func(*http.Request) bool
  51. Resp *http.Response
  52. HttpErr error
  53. }{
  54. {
  55. HttpErr: errors.New("failure"),
  56. Err: true,
  57. },
  58. {
  59. Resp: &http.Response{
  60. StatusCode: http.StatusNotFound,
  61. Header: header(),
  62. Body: objBody(&unversioned.Status{Status: unversioned.StatusFailure}),
  63. },
  64. Err: true,
  65. },
  66. {
  67. Resp: &http.Response{
  68. StatusCode: http.StatusOK,
  69. Header: header(),
  70. Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess}),
  71. },
  72. Req: func(req *http.Request) bool {
  73. if req.Method != "DELETE" {
  74. t.Errorf("unexpected method: %#v", req)
  75. return false
  76. }
  77. parts := splitPath(req.URL.Path)
  78. if len(parts) < 3 {
  79. t.Errorf("expected URL path to have 3 parts: %s", req.URL.Path)
  80. return false
  81. }
  82. if parts[1] != "bar" {
  83. t.Errorf("url doesn't contain namespace: %#v", req)
  84. return false
  85. }
  86. if parts[2] != "foo" {
  87. t.Errorf("url doesn't contain name: %#v", req)
  88. return false
  89. }
  90. return true
  91. },
  92. },
  93. }
  94. for _, test := range tests {
  95. client := &fake.RESTClient{
  96. NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
  97. Resp: test.Resp,
  98. Err: test.HttpErr,
  99. }
  100. modifier := &Helper{
  101. RESTClient: client,
  102. NamespaceScoped: true,
  103. }
  104. err := modifier.Delete("bar", "foo")
  105. if (err != nil) != test.Err {
  106. t.Errorf("unexpected error: %t %v", test.Err, err)
  107. }
  108. if err != nil {
  109. continue
  110. }
  111. if test.Req != nil && !test.Req(client.Req) {
  112. t.Errorf("unexpected request: %#v", client.Req)
  113. }
  114. }
  115. }
  116. func TestHelperCreate(t *testing.T) {
  117. expectPost := func(req *http.Request) bool {
  118. if req.Method != "POST" {
  119. t.Errorf("unexpected method: %#v", req)
  120. return false
  121. }
  122. parts := splitPath(req.URL.Path)
  123. if parts[1] != "bar" {
  124. t.Errorf("url doesn't contain namespace: %#v", req)
  125. return false
  126. }
  127. return true
  128. }
  129. tests := []struct {
  130. Resp *http.Response
  131. HttpErr error
  132. Modify bool
  133. Object runtime.Object
  134. ExpectObject runtime.Object
  135. Err bool
  136. Req func(*http.Request) bool
  137. }{
  138. {
  139. HttpErr: errors.New("failure"),
  140. Err: true,
  141. },
  142. {
  143. Resp: &http.Response{
  144. StatusCode: http.StatusNotFound,
  145. Header: header(),
  146. Body: objBody(&unversioned.Status{Status: unversioned.StatusFailure}),
  147. },
  148. Err: true,
  149. },
  150. {
  151. Resp: &http.Response{
  152. StatusCode: http.StatusOK,
  153. Header: header(),
  154. Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess}),
  155. },
  156. Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
  157. ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
  158. Req: expectPost,
  159. },
  160. {
  161. Modify: false,
  162. Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
  163. ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
  164. Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess})},
  165. Req: expectPost,
  166. },
  167. {
  168. Modify: true,
  169. Object: &api.Pod{
  170. ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"},
  171. Spec: apitesting.DeepEqualSafePodSpec(),
  172. },
  173. ExpectObject: &api.Pod{
  174. ObjectMeta: api.ObjectMeta{Name: "foo"},
  175. Spec: apitesting.DeepEqualSafePodSpec(),
  176. },
  177. Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess})},
  178. Req: expectPost,
  179. },
  180. }
  181. for i, test := range tests {
  182. client := &fake.RESTClient{
  183. NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
  184. Resp: test.Resp,
  185. Err: test.HttpErr,
  186. }
  187. modifier := &Helper{
  188. RESTClient: client,
  189. Versioner: testapi.Default.MetadataAccessor(),
  190. NamespaceScoped: true,
  191. }
  192. _, err := modifier.Create("bar", test.Modify, test.Object)
  193. if (err != nil) != test.Err {
  194. t.Errorf("%d: unexpected error: %t %v", i, test.Err, err)
  195. }
  196. if err != nil {
  197. continue
  198. }
  199. if test.Req != nil && !test.Req(client.Req) {
  200. t.Errorf("%d: unexpected request: %#v", i, client.Req)
  201. }
  202. body, err := ioutil.ReadAll(client.Req.Body)
  203. if err != nil {
  204. t.Fatalf("%d: unexpected error: %#v", i, err)
  205. }
  206. t.Logf("got body: %s", string(body))
  207. expect := []byte{}
  208. if test.ExpectObject != nil {
  209. expect = []byte(runtime.EncodeOrDie(testapi.Default.Codec(), test.ExpectObject))
  210. }
  211. if !reflect.DeepEqual(expect, body) {
  212. t.Errorf("%d: unexpected body: %s (expected %s)", i, string(body), string(expect))
  213. }
  214. }
  215. }
  216. func TestHelperGet(t *testing.T) {
  217. tests := []struct {
  218. Err bool
  219. Req func(*http.Request) bool
  220. Resp *http.Response
  221. HttpErr error
  222. }{
  223. {
  224. HttpErr: errors.New("failure"),
  225. Err: true,
  226. },
  227. {
  228. Resp: &http.Response{
  229. StatusCode: http.StatusNotFound,
  230. Header: header(),
  231. Body: objBody(&unversioned.Status{Status: unversioned.StatusFailure}),
  232. },
  233. Err: true,
  234. },
  235. {
  236. Resp: &http.Response{
  237. StatusCode: http.StatusOK,
  238. Header: header(),
  239. Body: objBody(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}),
  240. },
  241. Req: func(req *http.Request) bool {
  242. if req.Method != "GET" {
  243. t.Errorf("unexpected method: %#v", req)
  244. return false
  245. }
  246. parts := splitPath(req.URL.Path)
  247. if parts[1] != "bar" {
  248. t.Errorf("url doesn't contain namespace: %#v", req)
  249. return false
  250. }
  251. if parts[2] != "foo" {
  252. t.Errorf("url doesn't contain name: %#v", req)
  253. return false
  254. }
  255. return true
  256. },
  257. },
  258. }
  259. for _, test := range tests {
  260. client := &fake.RESTClient{
  261. NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
  262. Resp: test.Resp,
  263. Err: test.HttpErr,
  264. }
  265. modifier := &Helper{
  266. RESTClient: client,
  267. NamespaceScoped: true,
  268. }
  269. obj, err := modifier.Get("bar", "foo", false)
  270. if (err != nil) != test.Err {
  271. t.Errorf("unexpected error: %t %v", test.Err, err)
  272. }
  273. if err != nil {
  274. continue
  275. }
  276. if obj.(*api.Pod).Name != "foo" {
  277. t.Errorf("unexpected object: %#v", obj)
  278. }
  279. if test.Req != nil && !test.Req(client.Req) {
  280. t.Errorf("unexpected request: %#v", client.Req)
  281. }
  282. }
  283. }
  284. func TestHelperList(t *testing.T) {
  285. tests := []struct {
  286. Err bool
  287. Req func(*http.Request) bool
  288. Resp *http.Response
  289. HttpErr error
  290. }{
  291. {
  292. HttpErr: errors.New("failure"),
  293. Err: true,
  294. },
  295. {
  296. Resp: &http.Response{
  297. StatusCode: http.StatusNotFound,
  298. Header: header(),
  299. Body: objBody(&unversioned.Status{Status: unversioned.StatusFailure}),
  300. },
  301. Err: true,
  302. },
  303. {
  304. Resp: &http.Response{
  305. StatusCode: http.StatusOK,
  306. Header: header(),
  307. Body: objBody(&api.PodList{
  308. Items: []api.Pod{{
  309. ObjectMeta: api.ObjectMeta{Name: "foo"},
  310. },
  311. },
  312. }),
  313. },
  314. Req: func(req *http.Request) bool {
  315. if req.Method != "GET" {
  316. t.Errorf("unexpected method: %#v", req)
  317. return false
  318. }
  319. if req.URL.Path != "/namespaces/bar" {
  320. t.Errorf("url doesn't contain name: %#v", req.URL)
  321. return false
  322. }
  323. if req.URL.Query().Get(unversioned.LabelSelectorQueryParam(testapi.Default.GroupVersion().String())) != labels.SelectorFromSet(labels.Set{"foo": "baz"}).String() {
  324. t.Errorf("url doesn't contain query parameters: %#v", req.URL)
  325. return false
  326. }
  327. return true
  328. },
  329. },
  330. }
  331. for _, test := range tests {
  332. client := &fake.RESTClient{
  333. NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
  334. Resp: test.Resp,
  335. Err: test.HttpErr,
  336. }
  337. modifier := &Helper{
  338. RESTClient: client,
  339. NamespaceScoped: true,
  340. }
  341. obj, err := modifier.List("bar", testapi.Default.GroupVersion().String(), labels.SelectorFromSet(labels.Set{"foo": "baz"}), false)
  342. if (err != nil) != test.Err {
  343. t.Errorf("unexpected error: %t %v", test.Err, err)
  344. }
  345. if err != nil {
  346. continue
  347. }
  348. if obj.(*api.PodList).Items[0].Name != "foo" {
  349. t.Errorf("unexpected object: %#v", obj)
  350. }
  351. if test.Req != nil && !test.Req(client.Req) {
  352. t.Errorf("unexpected request: %#v", client.Req)
  353. }
  354. }
  355. }
  356. func TestHelperReplace(t *testing.T) {
  357. expectPut := func(path string, req *http.Request) bool {
  358. if req.Method != "PUT" {
  359. t.Errorf("unexpected method: %#v", req)
  360. return false
  361. }
  362. if req.URL.Path != path {
  363. t.Errorf("unexpected url: %v", req.URL)
  364. return false
  365. }
  366. return true
  367. }
  368. tests := []struct {
  369. Resp *http.Response
  370. HTTPClient *http.Client
  371. HttpErr error
  372. Overwrite bool
  373. Object runtime.Object
  374. Namespace string
  375. NamespaceScoped bool
  376. ExpectPath string
  377. ExpectObject runtime.Object
  378. Err bool
  379. Req func(string, *http.Request) bool
  380. }{
  381. {
  382. Namespace: "bar",
  383. NamespaceScoped: true,
  384. HttpErr: errors.New("failure"),
  385. Err: true,
  386. },
  387. {
  388. Namespace: "bar",
  389. NamespaceScoped: true,
  390. Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
  391. Resp: &http.Response{
  392. StatusCode: http.StatusNotFound,
  393. Header: header(),
  394. Body: objBody(&unversioned.Status{Status: unversioned.StatusFailure}),
  395. },
  396. Err: true,
  397. },
  398. {
  399. Namespace: "bar",
  400. NamespaceScoped: true,
  401. Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
  402. ExpectPath: "/namespaces/bar/foo",
  403. ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
  404. Resp: &http.Response{
  405. StatusCode: http.StatusOK,
  406. Header: header(),
  407. Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess}),
  408. },
  409. Req: expectPut,
  410. },
  411. // namespace scoped resource
  412. {
  413. Namespace: "bar",
  414. NamespaceScoped: true,
  415. Object: &api.Pod{
  416. ObjectMeta: api.ObjectMeta{Name: "foo"},
  417. Spec: apitesting.DeepEqualSafePodSpec(),
  418. },
  419. ExpectPath: "/namespaces/bar/foo",
  420. ExpectObject: &api.Pod{
  421. ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"},
  422. Spec: apitesting.DeepEqualSafePodSpec(),
  423. },
  424. Overwrite: true,
  425. HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  426. if req.Method == "PUT" {
  427. return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess})}, nil
  428. }
  429. return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
  430. }),
  431. Req: expectPut,
  432. },
  433. // cluster scoped resource
  434. {
  435. Object: &api.Node{
  436. ObjectMeta: api.ObjectMeta{Name: "foo"},
  437. },
  438. ExpectObject: &api.Node{
  439. ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"},
  440. },
  441. Overwrite: true,
  442. ExpectPath: "/foo",
  443. HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  444. if req.Method == "PUT" {
  445. return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess})}, nil
  446. }
  447. return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&api.Node{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
  448. }),
  449. Req: expectPut,
  450. },
  451. {
  452. Namespace: "bar",
  453. NamespaceScoped: true,
  454. Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
  455. ExpectPath: "/namespaces/bar/foo",
  456. ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
  457. Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess})},
  458. Req: expectPut,
  459. },
  460. }
  461. for i, test := range tests {
  462. client := &fake.RESTClient{
  463. Client: test.HTTPClient,
  464. NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
  465. Resp: test.Resp,
  466. Err: test.HttpErr,
  467. }
  468. modifier := &Helper{
  469. RESTClient: client,
  470. Versioner: testapi.Default.MetadataAccessor(),
  471. NamespaceScoped: test.NamespaceScoped,
  472. }
  473. _, err := modifier.Replace(test.Namespace, "foo", test.Overwrite, test.Object)
  474. if (err != nil) != test.Err {
  475. t.Errorf("%d: unexpected error: %t %v", i, test.Err, err)
  476. }
  477. if err != nil {
  478. continue
  479. }
  480. if test.Req != nil && !test.Req(test.ExpectPath, client.Req) {
  481. t.Errorf("%d: unexpected request: %#v", i, client.Req)
  482. }
  483. body, err := ioutil.ReadAll(client.Req.Body)
  484. if err != nil {
  485. t.Fatalf("%d: unexpected error: %#v", i, err)
  486. }
  487. expect := []byte{}
  488. if test.ExpectObject != nil {
  489. expect = []byte(runtime.EncodeOrDie(testapi.Default.Codec(), test.ExpectObject))
  490. }
  491. if !reflect.DeepEqual(expect, body) {
  492. t.Errorf("%d: unexpected body: %s", i, string(body))
  493. }
  494. }
  495. }