apiserver_test.go 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848
  1. /*
  2. Copyright 2017 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 apiserver
  14. import (
  15. "bytes"
  16. "context"
  17. "encoding/json"
  18. "fmt"
  19. "io"
  20. "io/ioutil"
  21. "net/http"
  22. "net/http/httptest"
  23. "path"
  24. "reflect"
  25. "strconv"
  26. "strings"
  27. "testing"
  28. "time"
  29. apps "k8s.io/api/apps/v1"
  30. v1 "k8s.io/api/core/v1"
  31. apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
  32. apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
  33. "k8s.io/apiextensions-apiserver/test/integration/fixtures"
  34. apierrors "k8s.io/apimachinery/pkg/api/errors"
  35. "k8s.io/apimachinery/pkg/api/meta"
  36. metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
  37. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  38. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  39. metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
  40. "k8s.io/apimachinery/pkg/fields"
  41. "k8s.io/apimachinery/pkg/runtime"
  42. "k8s.io/apimachinery/pkg/runtime/schema"
  43. "k8s.io/apimachinery/pkg/runtime/serializer/protobuf"
  44. "k8s.io/apimachinery/pkg/runtime/serializer/streaming"
  45. "k8s.io/apimachinery/pkg/types"
  46. "k8s.io/apimachinery/pkg/watch"
  47. "k8s.io/apiserver/pkg/features"
  48. utilfeature "k8s.io/apiserver/pkg/util/feature"
  49. "k8s.io/client-go/dynamic"
  50. clientset "k8s.io/client-go/kubernetes"
  51. "k8s.io/client-go/metadata"
  52. restclient "k8s.io/client-go/rest"
  53. "k8s.io/client-go/tools/pager"
  54. featuregatetesting "k8s.io/component-base/featuregate/testing"
  55. "k8s.io/klog"
  56. "k8s.io/kubernetes/pkg/master"
  57. "k8s.io/kubernetes/test/integration/framework"
  58. )
  59. func setup(t *testing.T, groupVersions ...schema.GroupVersion) (*httptest.Server, clientset.Interface, framework.CloseFunc) {
  60. return setupWithResources(t, groupVersions, nil)
  61. }
  62. func setupWithOptions(t *testing.T, opts *framework.MasterConfigOptions, groupVersions ...schema.GroupVersion) (*httptest.Server, clientset.Interface, framework.CloseFunc) {
  63. return setupWithResourcesWithOptions(t, opts, groupVersions, nil)
  64. }
  65. func setupWithResources(t *testing.T, groupVersions []schema.GroupVersion, resources []schema.GroupVersionResource) (*httptest.Server, clientset.Interface, framework.CloseFunc) {
  66. return setupWithResourcesWithOptions(t, &framework.MasterConfigOptions{}, groupVersions, resources)
  67. }
  68. func setupWithResourcesWithOptions(t *testing.T, opts *framework.MasterConfigOptions, groupVersions []schema.GroupVersion, resources []schema.GroupVersionResource) (*httptest.Server, clientset.Interface, framework.CloseFunc) {
  69. masterConfig := framework.NewIntegrationTestMasterConfigWithOptions(opts)
  70. if len(groupVersions) > 0 || len(resources) > 0 {
  71. resourceConfig := master.DefaultAPIResourceConfigSource()
  72. resourceConfig.EnableVersions(groupVersions...)
  73. resourceConfig.EnableResources(resources...)
  74. masterConfig.ExtraConfig.APIResourceConfigSource = resourceConfig
  75. }
  76. masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig()
  77. _, s, closeFn := framework.RunAMaster(masterConfig)
  78. clientSet, err := clientset.NewForConfig(&restclient.Config{Host: s.URL})
  79. if err != nil {
  80. t.Fatalf("Error in create clientset: %v", err)
  81. }
  82. return s, clientSet, closeFn
  83. }
  84. func verifyStatusCode(t *testing.T, verb, URL, body string, expectedStatusCode int) {
  85. // We dont use the typed Go client to send this request to be able to verify the response status code.
  86. bodyBytes := bytes.NewReader([]byte(body))
  87. req, err := http.NewRequest(verb, URL, bodyBytes)
  88. if err != nil {
  89. t.Fatalf("unexpected error: %v in sending req with verb: %s, URL: %s and body: %s", err, verb, URL, body)
  90. }
  91. transport := http.DefaultTransport
  92. klog.Infof("Sending request: %v", req)
  93. resp, err := transport.RoundTrip(req)
  94. if err != nil {
  95. t.Fatalf("unexpected error: %v in req: %v", err, req)
  96. }
  97. defer resp.Body.Close()
  98. b, _ := ioutil.ReadAll(resp.Body)
  99. if resp.StatusCode != expectedStatusCode {
  100. t.Errorf("Expected status %v, but got %v", expectedStatusCode, resp.StatusCode)
  101. t.Errorf("Body: %v", string(b))
  102. }
  103. }
  104. func newRS(namespace string) *apps.ReplicaSet {
  105. return &apps.ReplicaSet{
  106. TypeMeta: metav1.TypeMeta{
  107. Kind: "ReplicaSet",
  108. APIVersion: "apps/v1",
  109. },
  110. ObjectMeta: metav1.ObjectMeta{
  111. Namespace: namespace,
  112. GenerateName: "apiserver-test",
  113. },
  114. Spec: apps.ReplicaSetSpec{
  115. Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": "test"}},
  116. Template: v1.PodTemplateSpec{
  117. ObjectMeta: metav1.ObjectMeta{
  118. Labels: map[string]string{"name": "test"},
  119. },
  120. Spec: v1.PodSpec{
  121. Containers: []v1.Container{
  122. {
  123. Name: "fake-name",
  124. Image: "fakeimage",
  125. },
  126. },
  127. },
  128. },
  129. },
  130. }
  131. }
  132. var cascDel = `
  133. {
  134. "kind": "DeleteOptions",
  135. "apiVersion": "v1",
  136. "orphanDependents": false
  137. }
  138. `
  139. func Test4xxStatusCodeInvalidPatch(t *testing.T) {
  140. _, client, closeFn := setup(t)
  141. defer closeFn()
  142. obj := []byte(`{
  143. "apiVersion": "apps/v1",
  144. "kind": "Deployment",
  145. "metadata": {
  146. "name": "deployment",
  147. "labels": {"app": "nginx"}
  148. },
  149. "spec": {
  150. "selector": {
  151. "matchLabels": {
  152. "app": "nginx"
  153. }
  154. },
  155. "template": {
  156. "metadata": {
  157. "labels": {
  158. "app": "nginx"
  159. }
  160. },
  161. "spec": {
  162. "containers": [{
  163. "name": "nginx",
  164. "image": "nginx:latest"
  165. }]
  166. }
  167. }
  168. }
  169. }`)
  170. resp, err := client.CoreV1().RESTClient().Post().
  171. AbsPath("/apis/apps/v1").
  172. Namespace("default").
  173. Resource("deployments").
  174. Body(obj).Do(context.TODO()).Get()
  175. if err != nil {
  176. t.Fatalf("Failed to create object: %v: %v", err, resp)
  177. }
  178. result := client.CoreV1().RESTClient().Patch(types.MergePatchType).
  179. AbsPath("/apis/apps/v1").
  180. Namespace("default").
  181. Resource("deployments").
  182. Name("deployment").
  183. Body([]byte(`{"metadata":{"annotations":{"foo":["bar"]}}}`)).Do(context.TODO())
  184. var statusCode int
  185. result.StatusCode(&statusCode)
  186. if statusCode != 422 {
  187. t.Fatalf("Expected status code to be 422, got %v (%#v)", statusCode, result)
  188. }
  189. result = client.CoreV1().RESTClient().Patch(types.StrategicMergePatchType).
  190. AbsPath("/apis/apps/v1").
  191. Namespace("default").
  192. Resource("deployments").
  193. Name("deployment").
  194. Body([]byte(`{"metadata":{"annotations":{"foo":["bar"]}}}`)).Do(context.TODO())
  195. result.StatusCode(&statusCode)
  196. if statusCode != 422 {
  197. t.Fatalf("Expected status code to be 422, got %v (%#v)", statusCode, result)
  198. }
  199. }
  200. // Tests that the apiserver returns 202 status code as expected.
  201. func Test202StatusCode(t *testing.T) {
  202. s, clientSet, closeFn := setup(t)
  203. defer closeFn()
  204. ns := framework.CreateTestingNamespace("status-code", s, t)
  205. defer framework.DeleteTestingNamespace(ns, s, t)
  206. rsClient := clientSet.AppsV1().ReplicaSets(ns.Name)
  207. // 1. Create the resource without any finalizer and then delete it without setting DeleteOptions.
  208. // Verify that server returns 200 in this case.
  209. rs, err := rsClient.Create(context.TODO(), newRS(ns.Name), metav1.CreateOptions{})
  210. if err != nil {
  211. t.Fatalf("Failed to create rs: %v", err)
  212. }
  213. verifyStatusCode(t, "DELETE", s.URL+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), "", 200)
  214. // 2. Create the resource with a finalizer so that the resource is not immediately deleted and then delete it without setting DeleteOptions.
  215. // Verify that the apiserver still returns 200 since DeleteOptions.OrphanDependents is not set.
  216. rs = newRS(ns.Name)
  217. rs.ObjectMeta.Finalizers = []string{"kube.io/dummy-finalizer"}
  218. rs, err = rsClient.Create(context.TODO(), rs, metav1.CreateOptions{})
  219. if err != nil {
  220. t.Fatalf("Failed to create rs: %v", err)
  221. }
  222. verifyStatusCode(t, "DELETE", s.URL+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), "", 200)
  223. // 3. Create the resource and then delete it with DeleteOptions.OrphanDependents=false.
  224. // Verify that the server still returns 200 since the resource is immediately deleted.
  225. rs = newRS(ns.Name)
  226. rs, err = rsClient.Create(context.TODO(), rs, metav1.CreateOptions{})
  227. if err != nil {
  228. t.Fatalf("Failed to create rs: %v", err)
  229. }
  230. verifyStatusCode(t, "DELETE", s.URL+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), cascDel, 200)
  231. // 4. Create the resource with a finalizer so that the resource is not immediately deleted and then delete it with DeleteOptions.OrphanDependents=false.
  232. // Verify that the server returns 202 in this case.
  233. rs = newRS(ns.Name)
  234. rs.ObjectMeta.Finalizers = []string{"kube.io/dummy-finalizer"}
  235. rs, err = rsClient.Create(context.TODO(), rs, metav1.CreateOptions{})
  236. if err != nil {
  237. t.Fatalf("Failed to create rs: %v", err)
  238. }
  239. verifyStatusCode(t, "DELETE", s.URL+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), cascDel, 202)
  240. }
  241. func TestListResourceVersion0(t *testing.T) {
  242. var testcases = []struct {
  243. name string
  244. watchCacheEnabled bool
  245. }{
  246. {
  247. name: "watchCacheOn",
  248. watchCacheEnabled: true,
  249. },
  250. {
  251. name: "watchCacheOff",
  252. watchCacheEnabled: false,
  253. },
  254. }
  255. for _, tc := range testcases {
  256. t.Run(tc.name, func(t *testing.T) {
  257. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIListChunking, true)()
  258. etcdOptions := framework.DefaultEtcdOptions()
  259. etcdOptions.EnableWatchCache = tc.watchCacheEnabled
  260. s, clientSet, closeFn := setupWithOptions(t, &framework.MasterConfigOptions{EtcdOptions: etcdOptions})
  261. defer closeFn()
  262. ns := framework.CreateTestingNamespace("list-paging", s, t)
  263. defer framework.DeleteTestingNamespace(ns, s, t)
  264. rsClient := clientSet.AppsV1().ReplicaSets(ns.Name)
  265. for i := 0; i < 10; i++ {
  266. rs := newRS(ns.Name)
  267. rs.Name = fmt.Sprintf("test-%d", i)
  268. if _, err := rsClient.Create(context.TODO(), rs, metav1.CreateOptions{}); err != nil {
  269. t.Fatal(err)
  270. }
  271. }
  272. pagerFn := func(opts metav1.ListOptions) (runtime.Object, error) {
  273. return rsClient.List(context.TODO(), opts)
  274. }
  275. p := pager.New(pager.SimplePageFunc(pagerFn))
  276. p.PageSize = 3
  277. listObj, _, err := p.List(context.Background(), metav1.ListOptions{ResourceVersion: "0"})
  278. if err != nil {
  279. t.Fatalf("Unexpected list error: %v", err)
  280. }
  281. items, err := meta.ExtractList(listObj)
  282. if err != nil {
  283. t.Fatalf("Failed to extract list from %v", listObj)
  284. }
  285. if len(items) != 10 {
  286. t.Errorf("Expected list size of 10 but got %d", len(items))
  287. }
  288. })
  289. }
  290. }
  291. func TestAPIListChunking(t *testing.T) {
  292. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIListChunking, true)()
  293. s, clientSet, closeFn := setup(t)
  294. defer closeFn()
  295. ns := framework.CreateTestingNamespace("list-paging", s, t)
  296. defer framework.DeleteTestingNamespace(ns, s, t)
  297. rsClient := clientSet.AppsV1().ReplicaSets(ns.Name)
  298. for i := 0; i < 4; i++ {
  299. rs := newRS(ns.Name)
  300. rs.Name = fmt.Sprintf("test-%d", i)
  301. if _, err := rsClient.Create(context.TODO(), rs, metav1.CreateOptions{}); err != nil {
  302. t.Fatal(err)
  303. }
  304. }
  305. calls := 0
  306. firstRV := ""
  307. p := &pager.ListPager{
  308. PageSize: 1,
  309. PageFn: pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
  310. calls++
  311. list, err := rsClient.List(context.TODO(), opts)
  312. if err != nil {
  313. return nil, err
  314. }
  315. if calls == 1 {
  316. firstRV = list.ResourceVersion
  317. }
  318. if calls == 2 {
  319. rs := newRS(ns.Name)
  320. rs.Name = "test-5"
  321. if _, err := rsClient.Create(context.TODO(), rs, metav1.CreateOptions{}); err != nil {
  322. t.Fatal(err)
  323. }
  324. }
  325. return list, err
  326. }),
  327. }
  328. listObj, _, err := p.List(context.Background(), metav1.ListOptions{})
  329. if err != nil {
  330. t.Fatal(err)
  331. }
  332. if calls != 4 {
  333. t.Errorf("unexpected list invocations: %d", calls)
  334. }
  335. list := listObj.(metav1.ListInterface)
  336. if len(list.GetContinue()) != 0 {
  337. t.Errorf("unexpected continue: %s", list.GetContinue())
  338. }
  339. if list.GetResourceVersion() != firstRV {
  340. t.Errorf("unexpected resource version: %s instead of %s", list.GetResourceVersion(), firstRV)
  341. }
  342. var names []string
  343. if err := meta.EachListItem(listObj, func(obj runtime.Object) error {
  344. rs := obj.(*apps.ReplicaSet)
  345. names = append(names, rs.Name)
  346. return nil
  347. }); err != nil {
  348. t.Fatal(err)
  349. }
  350. if !reflect.DeepEqual(names, []string{"test-0", "test-1", "test-2", "test-3"}) {
  351. t.Errorf("unexpected items: %#v", list)
  352. }
  353. }
  354. func makeSecret(name string) *v1.Secret {
  355. return &v1.Secret{
  356. ObjectMeta: metav1.ObjectMeta{
  357. Name: name,
  358. },
  359. Data: map[string][]byte{
  360. "key": []byte("value"),
  361. },
  362. }
  363. }
  364. func TestNameInFieldSelector(t *testing.T) {
  365. s, clientSet, closeFn := setup(t)
  366. defer closeFn()
  367. numNamespaces := 3
  368. for i := 0; i < 3; i++ {
  369. ns := framework.CreateTestingNamespace(fmt.Sprintf("ns%d", i), s, t)
  370. defer framework.DeleteTestingNamespace(ns, s, t)
  371. _, err := clientSet.CoreV1().Secrets(ns.Name).Create(context.TODO(), makeSecret("foo"), metav1.CreateOptions{})
  372. if err != nil {
  373. t.Errorf("Couldn't create secret: %v", err)
  374. }
  375. _, err = clientSet.CoreV1().Secrets(ns.Name).Create(context.TODO(), makeSecret("bar"), metav1.CreateOptions{})
  376. if err != nil {
  377. t.Errorf("Couldn't create secret: %v", err)
  378. }
  379. }
  380. testcases := []struct {
  381. namespace string
  382. selector string
  383. expectedSecrets int
  384. }{
  385. {
  386. namespace: "",
  387. selector: "metadata.name=foo",
  388. expectedSecrets: numNamespaces,
  389. },
  390. {
  391. namespace: "",
  392. selector: "metadata.name=foo,metadata.name=bar",
  393. expectedSecrets: 0,
  394. },
  395. {
  396. namespace: "",
  397. selector: "metadata.name=foo,metadata.namespace=ns1",
  398. expectedSecrets: 1,
  399. },
  400. {
  401. namespace: "ns1",
  402. selector: "metadata.name=foo,metadata.namespace=ns1",
  403. expectedSecrets: 1,
  404. },
  405. {
  406. namespace: "ns1",
  407. selector: "metadata.name=foo,metadata.namespace=ns2",
  408. expectedSecrets: 0,
  409. },
  410. {
  411. namespace: "ns1",
  412. selector: "metadata.name=foo,metadata.namespace=",
  413. expectedSecrets: 0,
  414. },
  415. }
  416. for _, tc := range testcases {
  417. opts := metav1.ListOptions{
  418. FieldSelector: tc.selector,
  419. }
  420. secrets, err := clientSet.CoreV1().Secrets(tc.namespace).List(context.TODO(), opts)
  421. if err != nil {
  422. t.Errorf("%s: Unexpected error: %v", tc.selector, err)
  423. }
  424. if len(secrets.Items) != tc.expectedSecrets {
  425. t.Errorf("%s: Unexpected number of secrets: %d, expected: %d", tc.selector, len(secrets.Items), tc.expectedSecrets)
  426. }
  427. }
  428. }
  429. type callWrapper struct {
  430. nested http.RoundTripper
  431. req *http.Request
  432. resp *http.Response
  433. err error
  434. }
  435. func (w *callWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
  436. w.req = req
  437. resp, err := w.nested.RoundTrip(req)
  438. w.resp = resp
  439. w.err = err
  440. return resp, err
  441. }
  442. func TestMetadataClient(t *testing.T) {
  443. tearDown, config, _, err := fixtures.StartDefaultServer(t)
  444. if err != nil {
  445. t.Fatal(err)
  446. }
  447. defer tearDown()
  448. s, clientset, closeFn := setup(t)
  449. defer closeFn()
  450. apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
  451. if err != nil {
  452. t.Fatal(err)
  453. }
  454. dynamicClient, err := dynamic.NewForConfig(config)
  455. if err != nil {
  456. t.Fatal(err)
  457. }
  458. fooCRD := &apiextensionsv1beta1.CustomResourceDefinition{
  459. ObjectMeta: metav1.ObjectMeta{
  460. Name: "foos.cr.bar.com",
  461. },
  462. Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
  463. Group: "cr.bar.com",
  464. Version: "v1",
  465. Scope: apiextensionsv1beta1.NamespaceScoped,
  466. Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
  467. Plural: "foos",
  468. Kind: "Foo",
  469. },
  470. Subresources: &apiextensionsv1beta1.CustomResourceSubresources{
  471. Status: &apiextensionsv1beta1.CustomResourceSubresourceStatus{},
  472. },
  473. },
  474. }
  475. fooCRD, err = fixtures.CreateNewCustomResourceDefinition(fooCRD, apiExtensionClient, dynamicClient)
  476. if err != nil {
  477. t.Fatal(err)
  478. }
  479. crdGVR := schema.GroupVersionResource{Group: fooCRD.Spec.Group, Version: fooCRD.Spec.Version, Resource: "foos"}
  480. testcases := []struct {
  481. name string
  482. want func(*testing.T)
  483. }{
  484. {
  485. name: "list, get, patch, and delete via metadata client",
  486. want: func(t *testing.T) {
  487. ns := "metadata-builtin"
  488. svc, err := clientset.CoreV1().Services(ns).Create(context.TODO(), &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-1", Annotations: map[string]string{"foo": "bar"}}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  489. if err != nil {
  490. t.Fatalf("unable to create service: %v", err)
  491. }
  492. cfg := metadata.ConfigFor(&restclient.Config{Host: s.URL})
  493. wrapper := &callWrapper{}
  494. cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
  495. wrapper.nested = rt
  496. return wrapper
  497. })
  498. client := metadata.NewForConfigOrDie(cfg).Resource(v1.SchemeGroupVersion.WithResource("services"))
  499. items, err := client.Namespace(ns).List(metav1.ListOptions{})
  500. if err != nil {
  501. t.Fatal(err)
  502. }
  503. if items.ResourceVersion == "" {
  504. t.Fatalf("unexpected items: %#v", items)
  505. }
  506. if len(items.Items) != 1 {
  507. t.Fatalf("unexpected list: %#v", items)
  508. }
  509. if item := items.Items[0]; item.Name != "test-1" || item.UID != svc.UID || item.Annotations["foo"] != "bar" {
  510. t.Fatalf("unexpected object: %#v", item)
  511. }
  512. if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
  513. t.Fatalf("unexpected response: %#v", wrapper.resp)
  514. }
  515. wrapper.resp = nil
  516. item, err := client.Namespace(ns).Get("test-1", metav1.GetOptions{})
  517. if err != nil {
  518. t.Fatal(err)
  519. }
  520. if item.ResourceVersion == "" || item.UID != svc.UID || item.Annotations["foo"] != "bar" {
  521. t.Fatalf("unexpected object: %#v", item)
  522. }
  523. if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
  524. t.Fatalf("unexpected response: %#v", wrapper.resp)
  525. }
  526. item, err = client.Namespace(ns).Patch("test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"foo":"baz"}}}`), metav1.PatchOptions{})
  527. if err != nil {
  528. t.Fatal(err)
  529. }
  530. if item.Annotations["foo"] != "baz" {
  531. t.Fatalf("unexpected object: %#v", item)
  532. }
  533. if err := client.Namespace(ns).Delete("test-1", &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &item.UID}}); err != nil {
  534. t.Fatal(err)
  535. }
  536. if _, err := client.Namespace(ns).Get("test-1", metav1.GetOptions{}); !apierrors.IsNotFound(err) {
  537. t.Fatal(err)
  538. }
  539. },
  540. },
  541. {
  542. name: "list, get, patch, and delete via metadata client on a CRD",
  543. want: func(t *testing.T) {
  544. ns := "metadata-crd"
  545. crclient := dynamicClient.Resource(crdGVR).Namespace(ns)
  546. cr, err := crclient.Create(&unstructured.Unstructured{
  547. Object: map[string]interface{}{
  548. "apiVersion": "cr.bar.com/v1",
  549. "kind": "Foo",
  550. "spec": map[string]interface{}{"field": 1},
  551. "metadata": map[string]interface{}{
  552. "name": "test-1",
  553. "annotations": map[string]interface{}{
  554. "foo": "bar",
  555. },
  556. },
  557. },
  558. }, metav1.CreateOptions{})
  559. if err != nil {
  560. t.Fatalf("unable to create cr: %v", err)
  561. }
  562. cfg := metadata.ConfigFor(config)
  563. wrapper := &callWrapper{}
  564. cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
  565. wrapper.nested = rt
  566. return wrapper
  567. })
  568. client := metadata.NewForConfigOrDie(cfg).Resource(crdGVR)
  569. items, err := client.Namespace(ns).List(metav1.ListOptions{})
  570. if err != nil {
  571. t.Fatal(err)
  572. }
  573. if items.ResourceVersion == "" {
  574. t.Fatalf("unexpected items: %#v", items)
  575. }
  576. if len(items.Items) != 1 {
  577. t.Fatalf("unexpected list: %#v", items)
  578. }
  579. if item := items.Items[0]; item.Name != "test-1" || item.UID != cr.GetUID() || item.Annotations["foo"] != "bar" {
  580. t.Fatalf("unexpected object: %#v", item)
  581. }
  582. if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
  583. t.Fatalf("unexpected response: %#v", wrapper.resp)
  584. }
  585. wrapper.resp = nil
  586. item, err := client.Namespace(ns).Get("test-1", metav1.GetOptions{})
  587. if err != nil {
  588. t.Fatal(err)
  589. }
  590. if item.ResourceVersion == "" || item.UID != cr.GetUID() || item.Annotations["foo"] != "bar" {
  591. t.Fatalf("unexpected object: %#v", item)
  592. }
  593. if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
  594. t.Fatalf("unexpected response: %#v", wrapper.resp)
  595. }
  596. item, err = client.Namespace(ns).Patch("test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"foo":"baz"}}}`), metav1.PatchOptions{})
  597. if err != nil {
  598. t.Fatal(err)
  599. }
  600. if item.Annotations["foo"] != "baz" {
  601. t.Fatalf("unexpected object: %#v", item)
  602. }
  603. if err := client.Namespace(ns).Delete("test-1", &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &item.UID}}); err != nil {
  604. t.Fatal(err)
  605. }
  606. if _, err := client.Namespace(ns).Get("test-1", metav1.GetOptions{}); !apierrors.IsNotFound(err) {
  607. t.Fatal(err)
  608. }
  609. },
  610. },
  611. {
  612. name: "watch via metadata client",
  613. want: func(t *testing.T) {
  614. ns := "metadata-watch"
  615. svc, err := clientset.CoreV1().Services(ns).Create(context.TODO(), &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-2", Annotations: map[string]string{"foo": "bar"}}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  616. if err != nil {
  617. t.Fatalf("unable to create service: %v", err)
  618. }
  619. if _, err := clientset.CoreV1().Services(ns).Patch(context.TODO(), "test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  620. t.Fatalf("unable to patch cr: %v", err)
  621. }
  622. cfg := metadata.ConfigFor(&restclient.Config{Host: s.URL})
  623. wrapper := &callWrapper{}
  624. cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
  625. wrapper.nested = rt
  626. return wrapper
  627. })
  628. client := metadata.NewForConfigOrDie(cfg).Resource(v1.SchemeGroupVersion.WithResource("services"))
  629. w, err := client.Namespace(ns).Watch(metav1.ListOptions{ResourceVersion: svc.ResourceVersion, Watch: true})
  630. if err != nil {
  631. t.Fatal(err)
  632. }
  633. defer w.Stop()
  634. var r watch.Event
  635. select {
  636. case evt, ok := <-w.ResultChan():
  637. if !ok {
  638. t.Fatal("watch closed")
  639. }
  640. r = evt
  641. case <-time.After(5 * time.Second):
  642. t.Fatal("no watch event in 5 seconds, bug")
  643. }
  644. if r.Type != watch.Modified {
  645. t.Fatalf("unexpected watch: %#v", r)
  646. }
  647. item, ok := r.Object.(*metav1.PartialObjectMetadata)
  648. if !ok {
  649. t.Fatalf("unexpected object: %T", item)
  650. }
  651. if item.ResourceVersion == "" || item.Name != "test-2" || item.UID != svc.UID || item.Annotations["test"] != "1" {
  652. t.Fatalf("unexpected object: %#v", item)
  653. }
  654. if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf;stream=watch" {
  655. t.Fatalf("unexpected response: %#v", wrapper.resp)
  656. }
  657. },
  658. },
  659. {
  660. name: "watch via metadata client on a CRD",
  661. want: func(t *testing.T) {
  662. ns := "metadata-watch-crd"
  663. crclient := dynamicClient.Resource(crdGVR).Namespace(ns)
  664. cr, err := crclient.Create(&unstructured.Unstructured{
  665. Object: map[string]interface{}{
  666. "apiVersion": "cr.bar.com/v1",
  667. "kind": "Foo",
  668. "spec": map[string]interface{}{"field": 1},
  669. "metadata": map[string]interface{}{
  670. "name": "test-2",
  671. "annotations": map[string]interface{}{
  672. "foo": "bar",
  673. },
  674. },
  675. },
  676. }, metav1.CreateOptions{})
  677. if err != nil {
  678. t.Fatalf("unable to create cr: %v", err)
  679. }
  680. cfg := metadata.ConfigFor(config)
  681. client := metadata.NewForConfigOrDie(cfg).Resource(crdGVR)
  682. patched, err := client.Namespace(ns).Patch("test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{})
  683. if err != nil {
  684. t.Fatal(err)
  685. }
  686. if patched.GetResourceVersion() == cr.GetResourceVersion() {
  687. t.Fatalf("Patch did not modify object: %#v", patched)
  688. }
  689. wrapper := &callWrapper{}
  690. cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
  691. wrapper.nested = rt
  692. return wrapper
  693. })
  694. client = metadata.NewForConfigOrDie(cfg).Resource(crdGVR)
  695. w, err := client.Namespace(ns).Watch(metav1.ListOptions{ResourceVersion: cr.GetResourceVersion(), Watch: true})
  696. if err != nil {
  697. t.Fatal(err)
  698. }
  699. defer w.Stop()
  700. var r watch.Event
  701. select {
  702. case evt, ok := <-w.ResultChan():
  703. if !ok {
  704. t.Fatal("watch closed")
  705. }
  706. r = evt
  707. case <-time.After(5 * time.Second):
  708. t.Fatal("no watch event in 5 seconds, bug")
  709. }
  710. if r.Type != watch.Modified {
  711. t.Fatalf("unexpected watch: %#v", r)
  712. }
  713. item, ok := r.Object.(*metav1.PartialObjectMetadata)
  714. if !ok {
  715. t.Fatalf("unexpected object: %T", item)
  716. }
  717. if item.ResourceVersion == "" || item.Name != "test-2" || item.UID != cr.GetUID() || item.Annotations["test"] != "1" {
  718. t.Fatalf("unexpected object: %#v", item)
  719. }
  720. if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf;stream=watch" {
  721. t.Fatalf("unexpected response: %#v", wrapper.resp)
  722. }
  723. },
  724. },
  725. }
  726. for i := range testcases {
  727. tc := testcases[i]
  728. t.Run(tc.name, func(t *testing.T) {
  729. tc.want(t)
  730. })
  731. }
  732. }
  733. func TestAPICRDProtobuf(t *testing.T) {
  734. testNamespace := "test-api-crd-protobuf"
  735. tearDown, config, _, err := fixtures.StartDefaultServer(t)
  736. if err != nil {
  737. t.Fatal(err)
  738. }
  739. defer tearDown()
  740. s, _, closeFn := setup(t)
  741. defer closeFn()
  742. apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
  743. if err != nil {
  744. t.Fatal(err)
  745. }
  746. dynamicClient, err := dynamic.NewForConfig(config)
  747. if err != nil {
  748. t.Fatal(err)
  749. }
  750. fooCRD := &apiextensionsv1beta1.CustomResourceDefinition{
  751. ObjectMeta: metav1.ObjectMeta{
  752. Name: "foos.cr.bar.com",
  753. },
  754. Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
  755. Group: "cr.bar.com",
  756. Version: "v1",
  757. Scope: apiextensionsv1beta1.NamespaceScoped,
  758. Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
  759. Plural: "foos",
  760. Kind: "Foo",
  761. },
  762. Subresources: &apiextensionsv1beta1.CustomResourceSubresources{Status: &apiextensionsv1beta1.CustomResourceSubresourceStatus{}},
  763. },
  764. }
  765. fooCRD, err = fixtures.CreateNewCustomResourceDefinition(fooCRD, apiExtensionClient, dynamicClient)
  766. if err != nil {
  767. t.Fatal(err)
  768. }
  769. crdGVR := schema.GroupVersionResource{Group: fooCRD.Spec.Group, Version: fooCRD.Spec.Version, Resource: "foos"}
  770. crclient := dynamicClient.Resource(crdGVR).Namespace(testNamespace)
  771. testcases := []struct {
  772. name string
  773. accept string
  774. subresource string
  775. object func(*testing.T) (metav1.Object, string, string)
  776. wantErr func(*testing.T, error)
  777. wantBody func(*testing.T, io.Reader)
  778. }{
  779. {
  780. name: "server returns 406 when asking for protobuf for CRDs, which dynamic client does not support",
  781. accept: "application/vnd.kubernetes.protobuf",
  782. object: func(t *testing.T) (metav1.Object, string, string) {
  783. cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-1"}}}, metav1.CreateOptions{})
  784. if err != nil {
  785. t.Fatalf("unable to create cr: %v", err)
  786. }
  787. if _, err := crclient.Patch("test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  788. t.Fatalf("unable to patch cr: %v", err)
  789. }
  790. return cr, crdGVR.Group, "foos"
  791. },
  792. wantErr: func(t *testing.T, err error) {
  793. if !apierrors.IsNotAcceptable(err) {
  794. t.Fatal(err)
  795. }
  796. status := err.(apierrors.APIStatus).Status()
  797. data, _ := json.MarshalIndent(status, "", " ")
  798. // because the dynamic client only has a json serializer, the client processing of the error cannot
  799. // turn the response into something meaningful, so we verify that fallback handling works correctly
  800. if !apierrors.IsUnexpectedServerError(err) {
  801. t.Fatal(string(data))
  802. }
  803. if status.Message != "the server was unable to respond with a content type that the client supports (get foos.cr.bar.com test-1)" {
  804. t.Fatal(string(data))
  805. }
  806. },
  807. },
  808. {
  809. name: "server returns JSON when asking for protobuf and json for CRDs",
  810. accept: "application/vnd.kubernetes.protobuf,application/json",
  811. object: func(t *testing.T) (metav1.Object, string, string) {
  812. cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "spec": map[string]interface{}{"field": 1}, "metadata": map[string]interface{}{"name": "test-2"}}}, metav1.CreateOptions{})
  813. if err != nil {
  814. t.Fatalf("unable to create cr: %v", err)
  815. }
  816. if _, err := crclient.Patch("test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  817. t.Fatalf("unable to patch cr: %v", err)
  818. }
  819. return cr, crdGVR.Group, "foos"
  820. },
  821. wantBody: func(t *testing.T, w io.Reader) {
  822. obj := &unstructured.Unstructured{}
  823. if err := json.NewDecoder(w).Decode(obj); err != nil {
  824. t.Fatal(err)
  825. }
  826. v, ok, err := unstructured.NestedInt64(obj.UnstructuredContent(), "spec", "field")
  827. if !ok || err != nil {
  828. data, _ := json.MarshalIndent(obj.UnstructuredContent(), "", " ")
  829. t.Fatalf("err=%v ok=%t json=%s", err, ok, string(data))
  830. }
  831. if v != 1 {
  832. t.Fatalf("unexpected body: %#v", obj.UnstructuredContent())
  833. }
  834. },
  835. },
  836. {
  837. name: "server returns 406 when asking for protobuf for CRDs status, which dynamic client does not support",
  838. accept: "application/vnd.kubernetes.protobuf",
  839. subresource: "status",
  840. object: func(t *testing.T) (metav1.Object, string, string) {
  841. cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-3"}}}, metav1.CreateOptions{})
  842. if err != nil {
  843. t.Fatalf("unable to create cr: %v", err)
  844. }
  845. if _, err := crclient.Patch("test-3", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"3"}}}`), metav1.PatchOptions{}); err != nil {
  846. t.Fatalf("unable to patch cr: %v", err)
  847. }
  848. return cr, crdGVR.Group, "foos"
  849. },
  850. wantErr: func(t *testing.T, err error) {
  851. if !apierrors.IsNotAcceptable(err) {
  852. t.Fatal(err)
  853. }
  854. status := err.(apierrors.APIStatus).Status()
  855. data, _ := json.MarshalIndent(status, "", " ")
  856. // because the dynamic client only has a json serializer, the client processing of the error cannot
  857. // turn the response into something meaningful, so we verify that fallback handling works correctly
  858. if !apierrors.IsUnexpectedServerError(err) {
  859. t.Fatal(string(data))
  860. }
  861. if status.Message != "the server was unable to respond with a content type that the client supports (get foos.cr.bar.com test-3)" {
  862. t.Fatal(string(data))
  863. }
  864. },
  865. },
  866. {
  867. name: "server returns JSON when asking for protobuf and json for CRDs status",
  868. accept: "application/vnd.kubernetes.protobuf,application/json",
  869. subresource: "status",
  870. object: func(t *testing.T) (metav1.Object, string, string) {
  871. cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "spec": map[string]interface{}{"field": 1}, "metadata": map[string]interface{}{"name": "test-4"}}}, metav1.CreateOptions{})
  872. if err != nil {
  873. t.Fatalf("unable to create cr: %v", err)
  874. }
  875. if _, err := crclient.Patch("test-4", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"4"}}}`), metav1.PatchOptions{}); err != nil {
  876. t.Fatalf("unable to patch cr: %v", err)
  877. }
  878. return cr, crdGVR.Group, "foos"
  879. },
  880. wantBody: func(t *testing.T, w io.Reader) {
  881. obj := &unstructured.Unstructured{}
  882. if err := json.NewDecoder(w).Decode(obj); err != nil {
  883. t.Fatal(err)
  884. }
  885. v, ok, err := unstructured.NestedInt64(obj.UnstructuredContent(), "spec", "field")
  886. if !ok || err != nil {
  887. data, _ := json.MarshalIndent(obj.UnstructuredContent(), "", " ")
  888. t.Fatalf("err=%v ok=%t json=%s", err, ok, string(data))
  889. }
  890. if v != 1 {
  891. t.Fatalf("unexpected body: %#v", obj.UnstructuredContent())
  892. }
  893. },
  894. },
  895. }
  896. for i := range testcases {
  897. tc := testcases[i]
  898. t.Run(tc.name, func(t *testing.T) {
  899. obj, group, resource := tc.object(t)
  900. cfg := dynamic.ConfigFor(config)
  901. if len(group) == 0 {
  902. cfg = dynamic.ConfigFor(&restclient.Config{Host: s.URL})
  903. cfg.APIPath = "/api"
  904. } else {
  905. cfg.APIPath = "/apis"
  906. }
  907. cfg.GroupVersion = &schema.GroupVersion{Group: group, Version: "v1"}
  908. client, err := restclient.RESTClientFor(cfg)
  909. if err != nil {
  910. t.Fatal(err)
  911. }
  912. rv, _ := strconv.Atoi(obj.GetResourceVersion())
  913. if rv < 1 {
  914. rv = 1
  915. }
  916. w, err := client.Get().
  917. Resource(resource).NamespaceIfScoped(obj.GetNamespace(), len(obj.GetNamespace()) > 0).Name(obj.GetName()).SubResource(tc.subresource).
  918. SetHeader("Accept", tc.accept).
  919. Stream(context.TODO())
  920. if (tc.wantErr != nil) != (err != nil) {
  921. t.Fatalf("unexpected error: %v", err)
  922. }
  923. if tc.wantErr != nil {
  924. tc.wantErr(t, err)
  925. return
  926. }
  927. if err != nil {
  928. t.Fatal(err)
  929. }
  930. defer w.Close()
  931. tc.wantBody(t, w)
  932. })
  933. }
  934. }
  935. func TestTransform(t *testing.T) {
  936. testNamespace := "test-transform"
  937. tearDown, config, _, err := fixtures.StartDefaultServer(t)
  938. if err != nil {
  939. t.Fatal(err)
  940. }
  941. defer tearDown()
  942. s, clientset, closeFn := setup(t)
  943. defer closeFn()
  944. apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
  945. if err != nil {
  946. t.Fatal(err)
  947. }
  948. dynamicClient, err := dynamic.NewForConfig(config)
  949. if err != nil {
  950. t.Fatal(err)
  951. }
  952. fooCRD := &apiextensionsv1beta1.CustomResourceDefinition{
  953. ObjectMeta: metav1.ObjectMeta{
  954. Name: "foos.cr.bar.com",
  955. },
  956. Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
  957. Group: "cr.bar.com",
  958. Version: "v1",
  959. Scope: apiextensionsv1beta1.NamespaceScoped,
  960. Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
  961. Plural: "foos",
  962. Kind: "Foo",
  963. },
  964. },
  965. }
  966. fooCRD, err = fixtures.CreateNewCustomResourceDefinition(fooCRD, apiExtensionClient, dynamicClient)
  967. if err != nil {
  968. t.Fatal(err)
  969. }
  970. crdGVR := schema.GroupVersionResource{Group: fooCRD.Spec.Group, Version: fooCRD.Spec.Version, Resource: "foos"}
  971. crclient := dynamicClient.Resource(crdGVR).Namespace(testNamespace)
  972. testcases := []struct {
  973. name string
  974. accept string
  975. includeObject metav1.IncludeObjectPolicy
  976. object func(*testing.T) (metav1.Object, string, string)
  977. wantErr func(*testing.T, error)
  978. wantBody func(*testing.T, io.Reader)
  979. }{
  980. {
  981. name: "v1beta1 verify columns on cluster scoped resources",
  982. accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
  983. object: func(t *testing.T) (metav1.Object, string, string) {
  984. return &metav1.ObjectMeta{Name: "default", Namespace: ""}, "", "namespaces"
  985. },
  986. wantBody: func(t *testing.T, w io.Reader) {
  987. expectTableWatchEvents(t, 1, 3, metav1.IncludeMetadata, json.NewDecoder(w))
  988. },
  989. },
  990. {
  991. name: "v1beta1 verify columns on CRDs in json",
  992. accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
  993. object: func(t *testing.T) (metav1.Object, string, string) {
  994. cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-1"}}}, metav1.CreateOptions{})
  995. if err != nil {
  996. t.Fatalf("unable to create cr: %v", err)
  997. }
  998. if _, err := crclient.Patch("test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  999. t.Fatalf("unable to patch cr: %v", err)
  1000. }
  1001. return cr, crdGVR.Group, "foos"
  1002. },
  1003. wantBody: func(t *testing.T, w io.Reader) {
  1004. expectTableWatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w))
  1005. },
  1006. },
  1007. {
  1008. name: "v1beta1 verify columns on CRDs in json;stream=watch",
  1009. accept: "application/json;stream=watch;as=Table;g=meta.k8s.io;v=v1beta1",
  1010. object: func(t *testing.T) (metav1.Object, string, string) {
  1011. cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-2"}}}, metav1.CreateOptions{})
  1012. if err != nil {
  1013. t.Fatalf("unable to create cr: %v", err)
  1014. }
  1015. if _, err := crclient.Patch("test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1016. t.Fatalf("unable to patch cr: %v", err)
  1017. }
  1018. return cr, crdGVR.Group, "foos"
  1019. },
  1020. wantBody: func(t *testing.T, w io.Reader) {
  1021. expectTableWatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w))
  1022. },
  1023. },
  1024. {
  1025. name: "v1beta1 verify columns on CRDs in yaml",
  1026. accept: "application/yaml;as=Table;g=meta.k8s.io;v=v1beta1",
  1027. object: func(t *testing.T) (metav1.Object, string, string) {
  1028. cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-3"}}}, metav1.CreateOptions{})
  1029. if err != nil {
  1030. t.Fatalf("unable to create cr: %v", err)
  1031. }
  1032. if _, err := crclient.Patch("test-3", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1033. t.Fatalf("unable to patch cr: %v", err)
  1034. }
  1035. return cr, crdGVR.Group, "foos"
  1036. },
  1037. wantErr: func(t *testing.T, err error) {
  1038. if !apierrors.IsNotAcceptable(err) {
  1039. t.Fatal(err)
  1040. }
  1041. // TODO: this should be a more specific error
  1042. if err.Error() != "only the following media types are accepted: application/json;stream=watch, application/vnd.kubernetes.protobuf;stream=watch" {
  1043. t.Fatal(err)
  1044. }
  1045. },
  1046. },
  1047. {
  1048. name: "v1beta1 verify columns on services",
  1049. accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
  1050. object: func(t *testing.T) (metav1.Object, string, string) {
  1051. svc, err := clientset.CoreV1().Services(testNamespace).Create(context.TODO(), &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-1"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  1052. if err != nil {
  1053. t.Fatalf("unable to create service: %v", err)
  1054. }
  1055. if _, err := clientset.CoreV1().Services(testNamespace).Patch(context.TODO(), svc.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1056. t.Fatalf("unable to update service: %v", err)
  1057. }
  1058. return svc, "", "services"
  1059. },
  1060. wantBody: func(t *testing.T, w io.Reader) {
  1061. expectTableWatchEvents(t, 2, 7, metav1.IncludeMetadata, json.NewDecoder(w))
  1062. },
  1063. },
  1064. {
  1065. name: "v1beta1 verify columns on services with no object",
  1066. accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
  1067. includeObject: metav1.IncludeNone,
  1068. object: func(t *testing.T) (metav1.Object, string, string) {
  1069. obj, err := clientset.CoreV1().Services(testNamespace).Create(context.TODO(), &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-2"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  1070. if err != nil {
  1071. t.Fatalf("unable to create object: %v", err)
  1072. }
  1073. if _, err := clientset.CoreV1().Services(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1074. t.Fatalf("unable to update object: %v", err)
  1075. }
  1076. return obj, "", "services"
  1077. },
  1078. wantBody: func(t *testing.T, w io.Reader) {
  1079. expectTableWatchEvents(t, 2, 7, metav1.IncludeNone, json.NewDecoder(w))
  1080. },
  1081. },
  1082. {
  1083. name: "v1beta1 verify columns on services with full object",
  1084. accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
  1085. includeObject: metav1.IncludeObject,
  1086. object: func(t *testing.T) (metav1.Object, string, string) {
  1087. obj, err := clientset.CoreV1().Services(testNamespace).Create(context.TODO(), &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-3"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  1088. if err != nil {
  1089. t.Fatalf("unable to create object: %v", err)
  1090. }
  1091. if _, err := clientset.CoreV1().Services(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1092. t.Fatalf("unable to update object: %v", err)
  1093. }
  1094. return obj, "", "services"
  1095. },
  1096. wantBody: func(t *testing.T, w io.Reader) {
  1097. objects := expectTableWatchEvents(t, 2, 7, metav1.IncludeObject, json.NewDecoder(w))
  1098. var svc v1.Service
  1099. if err := json.Unmarshal(objects[1], &svc); err != nil {
  1100. t.Fatal(err)
  1101. }
  1102. if svc.Annotations["test"] != "1" || svc.Spec.Ports[0].Port != 1000 {
  1103. t.Fatalf("unexpected object: %#v", svc)
  1104. }
  1105. },
  1106. },
  1107. {
  1108. name: "v1beta1 verify partial metadata object on config maps",
  1109. accept: "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1beta1",
  1110. object: func(t *testing.T) (metav1.Object, string, string) {
  1111. obj, err := clientset.CoreV1().ConfigMaps(testNamespace).Create(context.TODO(), &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-1", Annotations: map[string]string{"test": "0"}}}, metav1.CreateOptions{})
  1112. if err != nil {
  1113. t.Fatalf("unable to create object: %v", err)
  1114. }
  1115. if _, err := clientset.CoreV1().ConfigMaps(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1116. t.Fatalf("unable to update object: %v", err)
  1117. }
  1118. return obj, "", "configmaps"
  1119. },
  1120. wantBody: func(t *testing.T, w io.Reader) {
  1121. expectPartialObjectMetaEvents(t, json.NewDecoder(w), "0", "1")
  1122. },
  1123. },
  1124. {
  1125. name: "v1beta1 verify partial metadata object on config maps in protobuf",
  1126. accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1beta1",
  1127. object: func(t *testing.T) (metav1.Object, string, string) {
  1128. obj, err := clientset.CoreV1().ConfigMaps(testNamespace).Create(context.TODO(), &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-2", Annotations: map[string]string{"test": "0"}}}, metav1.CreateOptions{})
  1129. if err != nil {
  1130. t.Fatalf("unable to create object: %v", err)
  1131. }
  1132. if _, err := clientset.CoreV1().ConfigMaps(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1133. t.Fatalf("unable to update object: %v", err)
  1134. }
  1135. return obj, "", "configmaps"
  1136. },
  1137. wantBody: func(t *testing.T, w io.Reader) {
  1138. expectPartialObjectMetaEventsProtobuf(t, w, "0", "1")
  1139. },
  1140. },
  1141. {
  1142. name: "v1beta1 verify partial metadata object on CRDs in protobuf",
  1143. accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1beta1",
  1144. object: func(t *testing.T) (metav1.Object, string, string) {
  1145. cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-4", "annotations": map[string]string{"test": "0"}}}}, metav1.CreateOptions{})
  1146. if err != nil {
  1147. t.Fatalf("unable to create cr: %v", err)
  1148. }
  1149. if _, err := crclient.Patch("test-4", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1150. t.Fatalf("unable to patch cr: %v", err)
  1151. }
  1152. return cr, crdGVR.Group, "foos"
  1153. },
  1154. wantBody: func(t *testing.T, w io.Reader) {
  1155. expectPartialObjectMetaEventsProtobuf(t, w, "0", "1")
  1156. },
  1157. },
  1158. {
  1159. name: "v1beta1 verify error on unsupported mimetype protobuf for table conversion",
  1160. accept: "application/vnd.kubernetes.protobuf;as=Table;g=meta.k8s.io;v=v1beta1",
  1161. object: func(t *testing.T) (metav1.Object, string, string) {
  1162. return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1163. },
  1164. wantErr: func(t *testing.T, err error) {
  1165. if !apierrors.IsNotAcceptable(err) {
  1166. t.Fatal(err)
  1167. }
  1168. // TODO: this should be a more specific error
  1169. if err.Error() != "only the following media types are accepted: application/json, application/yaml, application/vnd.kubernetes.protobuf" {
  1170. t.Fatal(err)
  1171. }
  1172. },
  1173. },
  1174. {
  1175. name: "verify error on invalid mimetype - bad version",
  1176. accept: "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1alpha1",
  1177. object: func(t *testing.T) (metav1.Object, string, string) {
  1178. return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1179. },
  1180. wantErr: func(t *testing.T, err error) {
  1181. if !apierrors.IsNotAcceptable(err) {
  1182. t.Fatal(err)
  1183. }
  1184. },
  1185. },
  1186. {
  1187. name: "v1beta1 verify error on invalid mimetype - bad group",
  1188. accept: "application/json;as=PartialObjectMetadata;g=k8s.io;v=v1beta1",
  1189. object: func(t *testing.T) (metav1.Object, string, string) {
  1190. return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1191. },
  1192. wantErr: func(t *testing.T, err error) {
  1193. if !apierrors.IsNotAcceptable(err) {
  1194. t.Fatal(err)
  1195. }
  1196. },
  1197. },
  1198. {
  1199. name: "v1beta1 verify error on invalid mimetype - bad kind",
  1200. accept: "application/json;as=PartialObject;g=meta.k8s.io;v=v1beta1",
  1201. object: func(t *testing.T) (metav1.Object, string, string) {
  1202. return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1203. },
  1204. wantErr: func(t *testing.T, err error) {
  1205. if !apierrors.IsNotAcceptable(err) {
  1206. t.Fatal(err)
  1207. }
  1208. },
  1209. },
  1210. {
  1211. name: "v1beta1 verify error on invalid mimetype - missing kind",
  1212. accept: "application/json;g=meta.k8s.io;v=v1beta1",
  1213. object: func(t *testing.T) (metav1.Object, string, string) {
  1214. return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1215. },
  1216. wantErr: func(t *testing.T, err error) {
  1217. if !apierrors.IsNotAcceptable(err) {
  1218. t.Fatal(err)
  1219. }
  1220. },
  1221. },
  1222. {
  1223. name: "v1beta1 verify error on invalid transform parameter",
  1224. accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
  1225. includeObject: metav1.IncludeObjectPolicy("unrecognized"),
  1226. object: func(t *testing.T) (metav1.Object, string, string) {
  1227. return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1228. },
  1229. wantErr: func(t *testing.T, err error) {
  1230. if !apierrors.IsBadRequest(err) || !strings.Contains(err.Error(), `Invalid value: "unrecognized": must be 'Metadata', 'Object', 'None', or empty`) {
  1231. t.Fatal(err)
  1232. }
  1233. },
  1234. },
  1235. {
  1236. name: "v1 verify columns on cluster scoped resources",
  1237. accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
  1238. object: func(t *testing.T) (metav1.Object, string, string) {
  1239. return &metav1.ObjectMeta{Name: "default", Namespace: ""}, "", "namespaces"
  1240. },
  1241. wantBody: func(t *testing.T, w io.Reader) {
  1242. expectTableV1WatchEvents(t, 1, 3, metav1.IncludeMetadata, json.NewDecoder(w))
  1243. },
  1244. },
  1245. {
  1246. name: "v1 verify columns on CRDs in json",
  1247. accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
  1248. object: func(t *testing.T) (metav1.Object, string, string) {
  1249. cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-5"}}}, metav1.CreateOptions{})
  1250. if err != nil {
  1251. t.Fatalf("unable to create cr: %v", err)
  1252. }
  1253. if _, err := crclient.Patch("test-5", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1254. t.Fatalf("unable to patch cr: %v", err)
  1255. }
  1256. return cr, crdGVR.Group, "foos"
  1257. },
  1258. wantBody: func(t *testing.T, w io.Reader) {
  1259. expectTableV1WatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w))
  1260. },
  1261. },
  1262. {
  1263. name: "v1 verify columns on CRDs in json;stream=watch",
  1264. accept: "application/json;stream=watch;as=Table;g=meta.k8s.io;v=v1",
  1265. object: func(t *testing.T) (metav1.Object, string, string) {
  1266. cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-6"}}}, metav1.CreateOptions{})
  1267. if err != nil {
  1268. t.Fatalf("unable to create cr: %v", err)
  1269. }
  1270. if _, err := crclient.Patch("test-6", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1271. t.Fatalf("unable to patch cr: %v", err)
  1272. }
  1273. return cr, crdGVR.Group, "foos"
  1274. },
  1275. wantBody: func(t *testing.T, w io.Reader) {
  1276. expectTableV1WatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w))
  1277. },
  1278. },
  1279. {
  1280. name: "v1 verify columns on CRDs in yaml",
  1281. accept: "application/yaml;as=Table;g=meta.k8s.io;v=v1",
  1282. object: func(t *testing.T) (metav1.Object, string, string) {
  1283. cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-7"}}}, metav1.CreateOptions{})
  1284. if err != nil {
  1285. t.Fatalf("unable to create cr: %v", err)
  1286. }
  1287. if _, err := crclient.Patch("test-7", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1288. t.Fatalf("unable to patch cr: %v", err)
  1289. }
  1290. return cr, crdGVR.Group, "foos"
  1291. },
  1292. wantErr: func(t *testing.T, err error) {
  1293. if !apierrors.IsNotAcceptable(err) {
  1294. t.Fatal(err)
  1295. }
  1296. // TODO: this should be a more specific error
  1297. if err.Error() != "only the following media types are accepted: application/json;stream=watch, application/vnd.kubernetes.protobuf;stream=watch" {
  1298. t.Fatal(err)
  1299. }
  1300. },
  1301. },
  1302. {
  1303. name: "v1 verify columns on services",
  1304. accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
  1305. object: func(t *testing.T) (metav1.Object, string, string) {
  1306. svc, err := clientset.CoreV1().Services(testNamespace).Create(context.TODO(), &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-5"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  1307. if err != nil {
  1308. t.Fatalf("unable to create service: %v", err)
  1309. }
  1310. if _, err := clientset.CoreV1().Services(testNamespace).Patch(context.TODO(), svc.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1311. t.Fatalf("unable to update service: %v", err)
  1312. }
  1313. return svc, "", "services"
  1314. },
  1315. wantBody: func(t *testing.T, w io.Reader) {
  1316. expectTableV1WatchEvents(t, 2, 7, metav1.IncludeMetadata, json.NewDecoder(w))
  1317. },
  1318. },
  1319. {
  1320. name: "v1 verify columns on services with no object",
  1321. accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
  1322. includeObject: metav1.IncludeNone,
  1323. object: func(t *testing.T) (metav1.Object, string, string) {
  1324. obj, err := clientset.CoreV1().Services(testNamespace).Create(context.TODO(), &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-6"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  1325. if err != nil {
  1326. t.Fatalf("unable to create object: %v", err)
  1327. }
  1328. if _, err := clientset.CoreV1().Services(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1329. t.Fatalf("unable to update object: %v", err)
  1330. }
  1331. return obj, "", "services"
  1332. },
  1333. wantBody: func(t *testing.T, w io.Reader) {
  1334. expectTableV1WatchEvents(t, 2, 7, metav1.IncludeNone, json.NewDecoder(w))
  1335. },
  1336. },
  1337. {
  1338. name: "v1 verify columns on services with full object",
  1339. accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
  1340. includeObject: metav1.IncludeObject,
  1341. object: func(t *testing.T) (metav1.Object, string, string) {
  1342. obj, err := clientset.CoreV1().Services(testNamespace).Create(context.TODO(), &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-7"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  1343. if err != nil {
  1344. t.Fatalf("unable to create object: %v", err)
  1345. }
  1346. if _, err := clientset.CoreV1().Services(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1347. t.Fatalf("unable to update object: %v", err)
  1348. }
  1349. return obj, "", "services"
  1350. },
  1351. wantBody: func(t *testing.T, w io.Reader) {
  1352. objects := expectTableV1WatchEvents(t, 2, 7, metav1.IncludeObject, json.NewDecoder(w))
  1353. var svc v1.Service
  1354. if err := json.Unmarshal(objects[1], &svc); err != nil {
  1355. t.Fatal(err)
  1356. }
  1357. if svc.Annotations["test"] != "1" || svc.Spec.Ports[0].Port != 1000 {
  1358. t.Fatalf("unexpected object: %#v", svc)
  1359. }
  1360. },
  1361. },
  1362. {
  1363. name: "v1 verify partial metadata object on config maps",
  1364. accept: "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1",
  1365. object: func(t *testing.T) (metav1.Object, string, string) {
  1366. obj, err := clientset.CoreV1().ConfigMaps(testNamespace).Create(context.TODO(), &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-3", Annotations: map[string]string{"test": "0"}}}, metav1.CreateOptions{})
  1367. if err != nil {
  1368. t.Fatalf("unable to create object: %v", err)
  1369. }
  1370. if _, err := clientset.CoreV1().ConfigMaps(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1371. t.Fatalf("unable to update object: %v", err)
  1372. }
  1373. return obj, "", "configmaps"
  1374. },
  1375. wantBody: func(t *testing.T, w io.Reader) {
  1376. expectPartialObjectMetaV1Events(t, json.NewDecoder(w), "0", "1")
  1377. },
  1378. },
  1379. {
  1380. name: "v1 verify partial metadata object on config maps in protobuf",
  1381. accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1",
  1382. object: func(t *testing.T) (metav1.Object, string, string) {
  1383. obj, err := clientset.CoreV1().ConfigMaps(testNamespace).Create(context.TODO(), &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-4", Annotations: map[string]string{"test": "0"}}}, metav1.CreateOptions{})
  1384. if err != nil {
  1385. t.Fatalf("unable to create object: %v", err)
  1386. }
  1387. if _, err := clientset.CoreV1().ConfigMaps(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1388. t.Fatalf("unable to update object: %v", err)
  1389. }
  1390. return obj, "", "configmaps"
  1391. },
  1392. wantBody: func(t *testing.T, w io.Reader) {
  1393. expectPartialObjectMetaV1EventsProtobuf(t, w, "0", "1")
  1394. },
  1395. },
  1396. {
  1397. name: "v1 verify partial metadata object on CRDs in protobuf",
  1398. accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1",
  1399. object: func(t *testing.T) (metav1.Object, string, string) {
  1400. cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-8", "annotations": map[string]string{"test": "0"}}}}, metav1.CreateOptions{})
  1401. if err != nil {
  1402. t.Fatalf("unable to create cr: %v", err)
  1403. }
  1404. if _, err := crclient.Patch(cr.GetName(), types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1405. t.Fatalf("unable to patch cr: %v", err)
  1406. }
  1407. return cr, crdGVR.Group, "foos"
  1408. },
  1409. wantBody: func(t *testing.T, w io.Reader) {
  1410. expectPartialObjectMetaV1EventsProtobuf(t, w, "0", "1")
  1411. },
  1412. },
  1413. {
  1414. name: "v1 verify error on unsupported mimetype protobuf for table conversion",
  1415. accept: "application/vnd.kubernetes.protobuf;as=Table;g=meta.k8s.io;v=v1",
  1416. object: func(t *testing.T) (metav1.Object, string, string) {
  1417. return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1418. },
  1419. wantErr: func(t *testing.T, err error) {
  1420. if !apierrors.IsNotAcceptable(err) {
  1421. t.Fatal(err)
  1422. }
  1423. // TODO: this should be a more specific error
  1424. if err.Error() != "only the following media types are accepted: application/json, application/yaml, application/vnd.kubernetes.protobuf" {
  1425. t.Fatal(err)
  1426. }
  1427. },
  1428. },
  1429. {
  1430. name: "v1 verify error on invalid mimetype - bad group",
  1431. accept: "application/json;as=PartialObjectMetadata;g=k8s.io;v=v1",
  1432. object: func(t *testing.T) (metav1.Object, string, string) {
  1433. return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1434. },
  1435. wantErr: func(t *testing.T, err error) {
  1436. if !apierrors.IsNotAcceptable(err) {
  1437. t.Fatal(err)
  1438. }
  1439. },
  1440. },
  1441. {
  1442. name: "v1 verify error on invalid mimetype - bad kind",
  1443. accept: "application/json;as=PartialObject;g=meta.k8s.io;v=v1",
  1444. object: func(t *testing.T) (metav1.Object, string, string) {
  1445. return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1446. },
  1447. wantErr: func(t *testing.T, err error) {
  1448. if !apierrors.IsNotAcceptable(err) {
  1449. t.Fatal(err)
  1450. }
  1451. },
  1452. },
  1453. {
  1454. name: "v1 verify error on invalid mimetype - only meta kinds accepted",
  1455. accept: "application/json;as=Service;g=;v=v1",
  1456. object: func(t *testing.T) (metav1.Object, string, string) {
  1457. return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1458. },
  1459. wantErr: func(t *testing.T, err error) {
  1460. if !apierrors.IsNotAcceptable(err) {
  1461. t.Fatal(err)
  1462. }
  1463. },
  1464. },
  1465. {
  1466. name: "v1 verify error on invalid mimetype - missing kind",
  1467. accept: "application/json;g=meta.k8s.io;v=v1",
  1468. object: func(t *testing.T) (metav1.Object, string, string) {
  1469. return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1470. },
  1471. wantErr: func(t *testing.T, err error) {
  1472. if !apierrors.IsNotAcceptable(err) {
  1473. t.Fatal(err)
  1474. }
  1475. },
  1476. },
  1477. {
  1478. name: "v1 verify error on invalid transform parameter",
  1479. accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
  1480. includeObject: metav1.IncludeObjectPolicy("unrecognized"),
  1481. object: func(t *testing.T) (metav1.Object, string, string) {
  1482. return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1483. },
  1484. wantErr: func(t *testing.T, err error) {
  1485. if !apierrors.IsBadRequest(err) || !strings.Contains(err.Error(), `Invalid value: "unrecognized": must be 'Metadata', 'Object', 'None', or empty`) {
  1486. t.Fatal(err)
  1487. }
  1488. },
  1489. },
  1490. }
  1491. for i := range testcases {
  1492. tc := testcases[i]
  1493. t.Run(tc.name, func(t *testing.T) {
  1494. obj, group, resource := tc.object(t)
  1495. cfg := dynamic.ConfigFor(config)
  1496. if len(group) == 0 {
  1497. cfg = dynamic.ConfigFor(&restclient.Config{Host: s.URL})
  1498. cfg.APIPath = "/api"
  1499. } else {
  1500. cfg.APIPath = "/apis"
  1501. }
  1502. cfg.GroupVersion = &schema.GroupVersion{Group: group, Version: "v1"}
  1503. client, err := restclient.RESTClientFor(cfg)
  1504. if err != nil {
  1505. t.Fatal(err)
  1506. }
  1507. rv, _ := strconv.Atoi(obj.GetResourceVersion())
  1508. if rv < 1 {
  1509. rv = 1
  1510. }
  1511. w, err := client.Get().
  1512. Resource(resource).NamespaceIfScoped(obj.GetNamespace(), len(obj.GetNamespace()) > 0).
  1513. SetHeader("Accept", tc.accept).
  1514. VersionedParams(&metav1.ListOptions{
  1515. ResourceVersion: strconv.Itoa(rv - 1),
  1516. Watch: true,
  1517. FieldSelector: fields.OneTermEqualSelector("metadata.name", obj.GetName()).String(),
  1518. }, metav1.ParameterCodec).
  1519. Param("includeObject", string(tc.includeObject)).
  1520. Stream(context.TODO())
  1521. if (tc.wantErr != nil) != (err != nil) {
  1522. t.Fatalf("unexpected error: %v", err)
  1523. }
  1524. if tc.wantErr != nil {
  1525. tc.wantErr(t, err)
  1526. return
  1527. }
  1528. if err != nil {
  1529. t.Fatal(err)
  1530. }
  1531. defer w.Close()
  1532. tc.wantBody(t, w)
  1533. })
  1534. }
  1535. }
  1536. func expectTableWatchEvents(t *testing.T, count, columns int, policy metav1.IncludeObjectPolicy, d *json.Decoder) [][]byte {
  1537. t.Helper()
  1538. var objects [][]byte
  1539. for i := 0; i < count; i++ {
  1540. var evt metav1.WatchEvent
  1541. if err := d.Decode(&evt); err != nil {
  1542. t.Fatal(err)
  1543. }
  1544. var table metav1beta1.Table
  1545. if err := json.Unmarshal(evt.Object.Raw, &table); err != nil {
  1546. t.Fatal(err)
  1547. }
  1548. if i == 0 {
  1549. if len(table.ColumnDefinitions) != columns {
  1550. t.Fatalf("Got unexpected columns on first watch event: %d vs %#v", columns, table.ColumnDefinitions)
  1551. }
  1552. } else {
  1553. if len(table.ColumnDefinitions) != 0 {
  1554. t.Fatalf("Expected no columns on second watch event: %#v", table.ColumnDefinitions)
  1555. }
  1556. }
  1557. if len(table.Rows) != 1 {
  1558. t.Fatalf("Invalid rows: %#v", table.Rows)
  1559. }
  1560. row := table.Rows[0]
  1561. if len(row.Cells) != columns {
  1562. t.Fatalf("Invalid row width: %#v", row.Cells)
  1563. }
  1564. switch policy {
  1565. case metav1.IncludeMetadata:
  1566. var meta metav1beta1.PartialObjectMetadata
  1567. if err := json.Unmarshal(row.Object.Raw, &meta); err != nil {
  1568. t.Fatalf("expected partial object: %v", err)
  1569. }
  1570. partialObj := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1beta1"}
  1571. if meta.TypeMeta != partialObj {
  1572. t.Fatalf("expected partial object: %#v", meta)
  1573. }
  1574. case metav1.IncludeNone:
  1575. if len(row.Object.Raw) != 0 {
  1576. t.Fatalf("Expected no object: %s", string(row.Object.Raw))
  1577. }
  1578. case metav1.IncludeObject:
  1579. if len(row.Object.Raw) == 0 {
  1580. t.Fatalf("Expected object: %s", string(row.Object.Raw))
  1581. }
  1582. objects = append(objects, row.Object.Raw)
  1583. }
  1584. }
  1585. return objects
  1586. }
  1587. func expectPartialObjectMetaEvents(t *testing.T, d *json.Decoder, values ...string) {
  1588. t.Helper()
  1589. for i, value := range values {
  1590. var evt metav1.WatchEvent
  1591. if err := d.Decode(&evt); err != nil {
  1592. t.Fatal(err)
  1593. }
  1594. var meta metav1beta1.PartialObjectMetadata
  1595. if err := json.Unmarshal(evt.Object.Raw, &meta); err != nil {
  1596. t.Fatal(err)
  1597. }
  1598. typeMeta := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1beta1"}
  1599. if meta.TypeMeta != typeMeta {
  1600. t.Fatalf("expected partial object: %#v", meta)
  1601. }
  1602. if meta.Annotations["test"] != value {
  1603. t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"])
  1604. }
  1605. }
  1606. }
  1607. func expectPartialObjectMetaEventsProtobuf(t *testing.T, r io.Reader, values ...string) {
  1608. scheme := runtime.NewScheme()
  1609. metav1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
  1610. rs := protobuf.NewRawSerializer(scheme, scheme)
  1611. d := streaming.NewDecoder(
  1612. protobuf.LengthDelimitedFramer.NewFrameReader(ioutil.NopCloser(r)),
  1613. rs,
  1614. )
  1615. ds := metainternalversionscheme.Codecs.UniversalDeserializer()
  1616. for i, value := range values {
  1617. var evt metav1.WatchEvent
  1618. if _, _, err := d.Decode(nil, &evt); err != nil {
  1619. t.Fatal(err)
  1620. }
  1621. obj, gvk, err := ds.Decode(evt.Object.Raw, nil, nil)
  1622. if err != nil {
  1623. t.Fatal(err)
  1624. }
  1625. meta, ok := obj.(*metav1beta1.PartialObjectMetadata)
  1626. if !ok {
  1627. t.Fatalf("unexpected watch object %T", obj)
  1628. }
  1629. expected := &schema.GroupVersionKind{Kind: "PartialObjectMetadata", Version: "v1beta1", Group: "meta.k8s.io"}
  1630. if !reflect.DeepEqual(expected, gvk) {
  1631. t.Fatalf("expected partial object: %#v", meta)
  1632. }
  1633. if meta.Annotations["test"] != value {
  1634. t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"])
  1635. }
  1636. }
  1637. }
  1638. func expectTableV1WatchEvents(t *testing.T, count, columns int, policy metav1.IncludeObjectPolicy, d *json.Decoder) [][]byte {
  1639. t.Helper()
  1640. var objects [][]byte
  1641. for i := 0; i < count; i++ {
  1642. var evt metav1.WatchEvent
  1643. if err := d.Decode(&evt); err != nil {
  1644. t.Fatal(err)
  1645. }
  1646. var table metav1.Table
  1647. if err := json.Unmarshal(evt.Object.Raw, &table); err != nil {
  1648. t.Fatal(err)
  1649. }
  1650. if i == 0 {
  1651. if len(table.ColumnDefinitions) != columns {
  1652. t.Fatalf("Got unexpected columns on first watch event: %d vs %#v", columns, table.ColumnDefinitions)
  1653. }
  1654. } else {
  1655. if len(table.ColumnDefinitions) != 0 {
  1656. t.Fatalf("Expected no columns on second watch event: %#v", table.ColumnDefinitions)
  1657. }
  1658. }
  1659. if len(table.Rows) != 1 {
  1660. t.Fatalf("Invalid rows: %#v", table.Rows)
  1661. }
  1662. row := table.Rows[0]
  1663. if len(row.Cells) != columns {
  1664. t.Fatalf("Invalid row width: %#v", row.Cells)
  1665. }
  1666. switch policy {
  1667. case metav1.IncludeMetadata:
  1668. var meta metav1.PartialObjectMetadata
  1669. if err := json.Unmarshal(row.Object.Raw, &meta); err != nil {
  1670. t.Fatalf("expected partial object: %v", err)
  1671. }
  1672. partialObj := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1"}
  1673. if meta.TypeMeta != partialObj {
  1674. t.Fatalf("expected partial object: %#v", meta)
  1675. }
  1676. case metav1.IncludeNone:
  1677. if len(row.Object.Raw) != 0 {
  1678. t.Fatalf("Expected no object: %s", string(row.Object.Raw))
  1679. }
  1680. case metav1.IncludeObject:
  1681. if len(row.Object.Raw) == 0 {
  1682. t.Fatalf("Expected object: %s", string(row.Object.Raw))
  1683. }
  1684. objects = append(objects, row.Object.Raw)
  1685. }
  1686. }
  1687. return objects
  1688. }
  1689. func expectPartialObjectMetaV1Events(t *testing.T, d *json.Decoder, values ...string) {
  1690. t.Helper()
  1691. for i, value := range values {
  1692. var evt metav1.WatchEvent
  1693. if err := d.Decode(&evt); err != nil {
  1694. t.Fatal(err)
  1695. }
  1696. var meta metav1.PartialObjectMetadata
  1697. if err := json.Unmarshal(evt.Object.Raw, &meta); err != nil {
  1698. t.Fatal(err)
  1699. }
  1700. typeMeta := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1"}
  1701. if meta.TypeMeta != typeMeta {
  1702. t.Fatalf("expected partial object: %#v", meta)
  1703. }
  1704. if meta.Annotations["test"] != value {
  1705. t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"])
  1706. }
  1707. }
  1708. }
  1709. func expectPartialObjectMetaV1EventsProtobuf(t *testing.T, r io.Reader, values ...string) {
  1710. scheme := runtime.NewScheme()
  1711. metav1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
  1712. rs := protobuf.NewRawSerializer(scheme, scheme)
  1713. d := streaming.NewDecoder(
  1714. protobuf.LengthDelimitedFramer.NewFrameReader(ioutil.NopCloser(r)),
  1715. rs,
  1716. )
  1717. ds := metainternalversionscheme.Codecs.UniversalDeserializer()
  1718. for i, value := range values {
  1719. var evt metav1.WatchEvent
  1720. if _, _, err := d.Decode(nil, &evt); err != nil {
  1721. t.Fatal(err)
  1722. }
  1723. obj, gvk, err := ds.Decode(evt.Object.Raw, nil, nil)
  1724. if err != nil {
  1725. t.Fatal(err)
  1726. }
  1727. meta, ok := obj.(*metav1.PartialObjectMetadata)
  1728. if !ok {
  1729. t.Fatalf("unexpected watch object %T", obj)
  1730. }
  1731. expected := &schema.GroupVersionKind{Kind: "PartialObjectMetadata", Version: "v1", Group: "meta.k8s.io"}
  1732. if !reflect.DeepEqual(expected, gvk) {
  1733. t.Fatalf("expected partial object: %#v", meta)
  1734. }
  1735. if meta.Annotations["test"] != value {
  1736. t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"])
  1737. }
  1738. }
  1739. }