apply_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  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. "encoding/json"
  17. "fmt"
  18. "io/ioutil"
  19. "net/http"
  20. "os"
  21. "testing"
  22. "github.com/ghodss/yaml"
  23. "github.com/spf13/cobra"
  24. "k8s.io/kubernetes/pkg/api"
  25. "k8s.io/kubernetes/pkg/api/annotations"
  26. kubeerr "k8s.io/kubernetes/pkg/api/errors"
  27. "k8s.io/kubernetes/pkg/api/meta"
  28. "k8s.io/kubernetes/pkg/api/unversioned"
  29. "k8s.io/kubernetes/pkg/client/unversioned/fake"
  30. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  31. "k8s.io/kubernetes/pkg/runtime"
  32. )
  33. func TestApplyExtraArgsFail(t *testing.T) {
  34. buf := bytes.NewBuffer([]byte{})
  35. f, _, _, _ := NewAPIFactory()
  36. c := NewCmdApply(f, buf)
  37. if validateApplyArgs(c, []string{"rc"}) == nil {
  38. t.Fatalf("unexpected non-error")
  39. }
  40. }
  41. func validateApplyArgs(cmd *cobra.Command, args []string) error {
  42. if len(args) != 0 {
  43. return cmdutil.UsageError(cmd, "Unexpected args: %v", args)
  44. }
  45. return nil
  46. }
  47. const (
  48. filenameRC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.yaml"
  49. filenameSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/service.yaml"
  50. filenameRCSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-service.yaml"
  51. )
  52. func readBytesFromFile(t *testing.T, filename string) []byte {
  53. file, err := os.Open(filename)
  54. if err != nil {
  55. t.Fatal(err)
  56. }
  57. data, err := ioutil.ReadAll(file)
  58. if err != nil {
  59. t.Fatal(err)
  60. }
  61. return data
  62. }
  63. func readReplicationControllerFromFile(t *testing.T, filename string) *api.ReplicationController {
  64. data := readBytesFromFile(t, filename)
  65. rc := api.ReplicationController{}
  66. // TODO(jackgr): Replace with a call to testapi.Codec().Decode().
  67. if err := yaml.Unmarshal(data, &rc); err != nil {
  68. t.Fatal(err)
  69. }
  70. return &rc
  71. }
  72. func readServiceFromFile(t *testing.T, filename string) *api.Service {
  73. data := readBytesFromFile(t, filename)
  74. svc := api.Service{}
  75. // TODO(jackgr): Replace with a call to testapi.Codec().Decode().
  76. if err := yaml.Unmarshal(data, &svc); err != nil {
  77. t.Fatal(err)
  78. }
  79. return &svc
  80. }
  81. func annotateRuntimeObject(t *testing.T, originalObj, currentObj runtime.Object, kind string) (string, []byte) {
  82. originalAccessor, err := meta.Accessor(originalObj)
  83. if err != nil {
  84. t.Fatal(err)
  85. }
  86. originalLabels := originalAccessor.GetLabels()
  87. originalLabels["DELETE_ME"] = "DELETE_ME"
  88. originalAccessor.SetLabels(originalLabels)
  89. original, err := json.Marshal(originalObj)
  90. if err != nil {
  91. t.Fatal(err)
  92. }
  93. currentAccessor, err := meta.Accessor(currentObj)
  94. if err != nil {
  95. t.Fatal(err)
  96. }
  97. currentAnnotations := currentAccessor.GetAnnotations()
  98. if currentAnnotations == nil {
  99. currentAnnotations = make(map[string]string)
  100. }
  101. currentAnnotations[annotations.LastAppliedConfigAnnotation] = string(original)
  102. currentAccessor.SetAnnotations(currentAnnotations)
  103. current, err := json.Marshal(currentObj)
  104. if err != nil {
  105. t.Fatal(err)
  106. }
  107. return currentAccessor.GetName(), current
  108. }
  109. func readAndAnnotateReplicationController(t *testing.T, filename string) (string, []byte) {
  110. rc1 := readReplicationControllerFromFile(t, filename)
  111. rc2 := readReplicationControllerFromFile(t, filename)
  112. return annotateRuntimeObject(t, rc1, rc2, "ReplicationController")
  113. }
  114. func readAndAnnotateService(t *testing.T, filename string) (string, []byte) {
  115. svc1 := readServiceFromFile(t, filename)
  116. svc2 := readServiceFromFile(t, filename)
  117. return annotateRuntimeObject(t, svc1, svc2, "Service")
  118. }
  119. func validatePatchApplication(t *testing.T, req *http.Request) {
  120. patch, err := ioutil.ReadAll(req.Body)
  121. if err != nil {
  122. t.Fatal(err)
  123. }
  124. patchMap := map[string]interface{}{}
  125. if err := json.Unmarshal(patch, &patchMap); err != nil {
  126. t.Fatal(err)
  127. }
  128. annotationsMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"})
  129. if _, ok := annotationsMap[annotations.LastAppliedConfigAnnotation]; !ok {
  130. t.Fatalf("patch does not contain annotation:\n%s\n", patch)
  131. }
  132. labelMap := walkMapPath(t, patchMap, []string{"metadata", "labels"})
  133. if deleteMe, ok := labelMap["DELETE_ME"]; !ok || deleteMe != nil {
  134. t.Fatalf("patch does not remove deleted key: DELETE_ME:\n%s\n", patch)
  135. }
  136. }
  137. func walkMapPath(t *testing.T, start map[string]interface{}, path []string) map[string]interface{} {
  138. finish := start
  139. for i := 0; i < len(path); i++ {
  140. var ok bool
  141. finish, ok = finish[path[i]].(map[string]interface{})
  142. if !ok {
  143. t.Fatalf("key:%s of path:%v not found in map:%v", path[i], path, start)
  144. }
  145. }
  146. return finish
  147. }
  148. func TestApplyObject(t *testing.T) {
  149. initTestErrorHandler(t)
  150. nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
  151. pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
  152. f, tf, _, ns := NewAPIFactory()
  153. tf.Printer = &testPrinter{}
  154. tf.Client = &fake.RESTClient{
  155. NegotiatedSerializer: ns,
  156. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  157. switch p, m := req.URL.Path, req.Method; {
  158. case p == pathRC && m == "GET":
  159. bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
  160. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
  161. case p == pathRC && m == "PATCH":
  162. validatePatchApplication(t, req)
  163. bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
  164. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
  165. default:
  166. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  167. return nil, nil
  168. }
  169. }),
  170. }
  171. tf.Namespace = "test"
  172. buf := bytes.NewBuffer([]byte{})
  173. cmd := NewCmdApply(f, buf)
  174. cmd.Flags().Set("filename", filenameRC)
  175. cmd.Flags().Set("output", "name")
  176. cmd.Run(cmd, []string{})
  177. // uses the name from the file, not the response
  178. expectRC := "replicationcontroller/" + nameRC + "\n"
  179. if buf.String() != expectRC {
  180. t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC)
  181. }
  182. }
  183. func TestApplyRetry(t *testing.T) {
  184. initTestErrorHandler(t)
  185. nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
  186. pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
  187. firstPatch := true
  188. retry := false
  189. getCount := 0
  190. f, tf, _, ns := NewAPIFactory()
  191. tf.Printer = &testPrinter{}
  192. tf.Client = &fake.RESTClient{
  193. NegotiatedSerializer: ns,
  194. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  195. switch p, m := req.URL.Path, req.Method; {
  196. case p == pathRC && m == "GET":
  197. getCount++
  198. bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
  199. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
  200. case p == pathRC && m == "PATCH":
  201. if firstPatch {
  202. firstPatch = false
  203. statusErr := kubeerr.NewConflict(unversioned.GroupResource{Group: "", Resource: "rc"}, "test-rc", fmt.Errorf("the object has been modified. Please apply at first."))
  204. bodyBytes, _ := json.Marshal(statusErr)
  205. bodyErr := ioutil.NopCloser(bytes.NewReader(bodyBytes))
  206. return &http.Response{StatusCode: http.StatusConflict, Header: defaultHeader(), Body: bodyErr}, nil
  207. }
  208. retry = true
  209. validatePatchApplication(t, req)
  210. bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
  211. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
  212. default:
  213. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  214. return nil, nil
  215. }
  216. }),
  217. }
  218. tf.Namespace = "test"
  219. buf := bytes.NewBuffer([]byte{})
  220. cmd := NewCmdApply(f, buf)
  221. cmd.Flags().Set("filename", filenameRC)
  222. cmd.Flags().Set("output", "name")
  223. cmd.Run(cmd, []string{})
  224. if !retry || getCount != 2 {
  225. t.Fatalf("apply didn't retry when get conflict error")
  226. }
  227. // uses the name from the file, not the response
  228. expectRC := "replicationcontroller/" + nameRC + "\n"
  229. if buf.String() != expectRC {
  230. t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC)
  231. }
  232. }
  233. func TestApplyNonExistObject(t *testing.T) {
  234. nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
  235. pathRC := "/namespaces/test/replicationcontrollers"
  236. pathNameRC := pathRC + "/" + nameRC
  237. f, tf, _, ns := NewAPIFactory()
  238. tf.Printer = &testPrinter{}
  239. tf.Client = &fake.RESTClient{
  240. NegotiatedSerializer: ns,
  241. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  242. switch p, m := req.URL.Path, req.Method; {
  243. case p == pathNameRC && m == "GET":
  244. return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader(nil))}, nil
  245. case p == pathRC && m == "POST":
  246. bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
  247. return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: bodyRC}, nil
  248. default:
  249. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  250. return nil, nil
  251. }
  252. }),
  253. }
  254. tf.Namespace = "test"
  255. buf := bytes.NewBuffer([]byte{})
  256. cmd := NewCmdApply(f, buf)
  257. cmd.Flags().Set("filename", filenameRC)
  258. cmd.Flags().Set("output", "name")
  259. cmd.Run(cmd, []string{})
  260. // uses the name from the file, not the response
  261. expectRC := "replicationcontroller/" + nameRC + "\n"
  262. if buf.String() != expectRC {
  263. t.Errorf("unexpected output: %s\nexpected: %s", buf.String(), expectRC)
  264. }
  265. }
  266. func TestApplyMultipleObjectsAsList(t *testing.T) {
  267. testApplyMultipleObjects(t, true)
  268. }
  269. func TestApplyMultipleObjectsAsFiles(t *testing.T) {
  270. testApplyMultipleObjects(t, false)
  271. }
  272. func testApplyMultipleObjects(t *testing.T, asList bool) {
  273. nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
  274. pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
  275. nameSVC, currentSVC := readAndAnnotateService(t, filenameSVC)
  276. pathSVC := "/namespaces/test/services/" + nameSVC
  277. f, tf, _, ns := NewAPIFactory()
  278. tf.Printer = &testPrinter{}
  279. tf.Client = &fake.RESTClient{
  280. NegotiatedSerializer: ns,
  281. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  282. switch p, m := req.URL.Path, req.Method; {
  283. case p == pathRC && m == "GET":
  284. bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
  285. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
  286. case p == pathRC && m == "PATCH":
  287. validatePatchApplication(t, req)
  288. bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
  289. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
  290. case p == pathSVC && m == "GET":
  291. bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC))
  292. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil
  293. case p == pathSVC && m == "PATCH":
  294. validatePatchApplication(t, req)
  295. bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC))
  296. return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil
  297. default:
  298. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  299. return nil, nil
  300. }
  301. }),
  302. }
  303. tf.Namespace = "test"
  304. buf := bytes.NewBuffer([]byte{})
  305. cmd := NewCmdApply(f, buf)
  306. if asList {
  307. cmd.Flags().Set("filename", filenameRCSVC)
  308. } else {
  309. cmd.Flags().Set("filename", filenameRC)
  310. cmd.Flags().Set("filename", filenameSVC)
  311. }
  312. cmd.Flags().Set("output", "name")
  313. cmd.Run(cmd, []string{})
  314. // Names should come from the REST response, NOT the files
  315. expectRC := "replicationcontroller/" + nameRC + "\n"
  316. expectSVC := "service/" + nameSVC + "\n"
  317. // Test both possible orders since output is non-deterministic.
  318. expectOne := expectRC + expectSVC
  319. expectTwo := expectSVC + expectRC
  320. if buf.String() != expectOne && buf.String() != expectTwo {
  321. t.Fatalf("unexpected output: %s\nexpected: %s OR %s", buf.String(), expectOne, expectTwo)
  322. }
  323. }