123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848 |
- /*
- Copyright 2017 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package apiserver
- import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "path"
- "reflect"
- "strconv"
- "strings"
- "testing"
- "time"
- apps "k8s.io/api/apps/v1"
- v1 "k8s.io/api/core/v1"
- apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
- apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
- "k8s.io/apiextensions-apiserver/test/integration/fixtures"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- "k8s.io/apimachinery/pkg/api/meta"
- metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
- metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
- "k8s.io/apimachinery/pkg/fields"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/runtime/serializer/protobuf"
- "k8s.io/apimachinery/pkg/runtime/serializer/streaming"
- "k8s.io/apimachinery/pkg/types"
- "k8s.io/apimachinery/pkg/watch"
- "k8s.io/apiserver/pkg/features"
- utilfeature "k8s.io/apiserver/pkg/util/feature"
- "k8s.io/client-go/dynamic"
- clientset "k8s.io/client-go/kubernetes"
- "k8s.io/client-go/metadata"
- restclient "k8s.io/client-go/rest"
- "k8s.io/client-go/tools/pager"
- featuregatetesting "k8s.io/component-base/featuregate/testing"
- "k8s.io/klog"
- "k8s.io/kubernetes/pkg/master"
- "k8s.io/kubernetes/test/integration/framework"
- )
- func setup(t *testing.T, groupVersions ...schema.GroupVersion) (*httptest.Server, clientset.Interface, framework.CloseFunc) {
- return setupWithResources(t, groupVersions, nil)
- }
- func setupWithOptions(t *testing.T, opts *framework.MasterConfigOptions, groupVersions ...schema.GroupVersion) (*httptest.Server, clientset.Interface, framework.CloseFunc) {
- return setupWithResourcesWithOptions(t, opts, groupVersions, nil)
- }
- func setupWithResources(t *testing.T, groupVersions []schema.GroupVersion, resources []schema.GroupVersionResource) (*httptest.Server, clientset.Interface, framework.CloseFunc) {
- return setupWithResourcesWithOptions(t, &framework.MasterConfigOptions{}, groupVersions, resources)
- }
- func setupWithResourcesWithOptions(t *testing.T, opts *framework.MasterConfigOptions, groupVersions []schema.GroupVersion, resources []schema.GroupVersionResource) (*httptest.Server, clientset.Interface, framework.CloseFunc) {
- masterConfig := framework.NewIntegrationTestMasterConfigWithOptions(opts)
- if len(groupVersions) > 0 || len(resources) > 0 {
- resourceConfig := master.DefaultAPIResourceConfigSource()
- resourceConfig.EnableVersions(groupVersions...)
- resourceConfig.EnableResources(resources...)
- masterConfig.ExtraConfig.APIResourceConfigSource = resourceConfig
- }
- masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig()
- _, s, closeFn := framework.RunAMaster(masterConfig)
- clientSet, err := clientset.NewForConfig(&restclient.Config{Host: s.URL})
- if err != nil {
- t.Fatalf("Error in create clientset: %v", err)
- }
- return s, clientSet, closeFn
- }
- func verifyStatusCode(t *testing.T, verb, URL, body string, expectedStatusCode int) {
- // We dont use the typed Go client to send this request to be able to verify the response status code.
- bodyBytes := bytes.NewReader([]byte(body))
- req, err := http.NewRequest(verb, URL, bodyBytes)
- if err != nil {
- t.Fatalf("unexpected error: %v in sending req with verb: %s, URL: %s and body: %s", err, verb, URL, body)
- }
- transport := http.DefaultTransport
- klog.Infof("Sending request: %v", req)
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Fatalf("unexpected error: %v in req: %v", err, req)
- }
- defer resp.Body.Close()
- b, _ := ioutil.ReadAll(resp.Body)
- if resp.StatusCode != expectedStatusCode {
- t.Errorf("Expected status %v, but got %v", expectedStatusCode, resp.StatusCode)
- t.Errorf("Body: %v", string(b))
- }
- }
- func newRS(namespace string) *apps.ReplicaSet {
- return &apps.ReplicaSet{
- TypeMeta: metav1.TypeMeta{
- Kind: "ReplicaSet",
- APIVersion: "apps/v1",
- },
- ObjectMeta: metav1.ObjectMeta{
- Namespace: namespace,
- GenerateName: "apiserver-test",
- },
- Spec: apps.ReplicaSetSpec{
- Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": "test"}},
- Template: v1.PodTemplateSpec{
- ObjectMeta: metav1.ObjectMeta{
- Labels: map[string]string{"name": "test"},
- },
- Spec: v1.PodSpec{
- Containers: []v1.Container{
- {
- Name: "fake-name",
- Image: "fakeimage",
- },
- },
- },
- },
- },
- }
- }
- var cascDel = `
- {
- "kind": "DeleteOptions",
- "apiVersion": "v1",
- "orphanDependents": false
- }
- `
- func Test4xxStatusCodeInvalidPatch(t *testing.T) {
- _, client, closeFn := setup(t)
- defer closeFn()
- obj := []byte(`{
- "apiVersion": "apps/v1",
- "kind": "Deployment",
- "metadata": {
- "name": "deployment",
- "labels": {"app": "nginx"}
- },
- "spec": {
- "selector": {
- "matchLabels": {
- "app": "nginx"
- }
- },
- "template": {
- "metadata": {
- "labels": {
- "app": "nginx"
- }
- },
- "spec": {
- "containers": [{
- "name": "nginx",
- "image": "nginx:latest"
- }]
- }
- }
- }
- }`)
- resp, err := client.CoreV1().RESTClient().Post().
- AbsPath("/apis/apps/v1").
- Namespace("default").
- Resource("deployments").
- Body(obj).Do(context.TODO()).Get()
- if err != nil {
- t.Fatalf("Failed to create object: %v: %v", err, resp)
- }
- result := client.CoreV1().RESTClient().Patch(types.MergePatchType).
- AbsPath("/apis/apps/v1").
- Namespace("default").
- Resource("deployments").
- Name("deployment").
- Body([]byte(`{"metadata":{"annotations":{"foo":["bar"]}}}`)).Do(context.TODO())
- var statusCode int
- result.StatusCode(&statusCode)
- if statusCode != 422 {
- t.Fatalf("Expected status code to be 422, got %v (%#v)", statusCode, result)
- }
- result = client.CoreV1().RESTClient().Patch(types.StrategicMergePatchType).
- AbsPath("/apis/apps/v1").
- Namespace("default").
- Resource("deployments").
- Name("deployment").
- Body([]byte(`{"metadata":{"annotations":{"foo":["bar"]}}}`)).Do(context.TODO())
- result.StatusCode(&statusCode)
- if statusCode != 422 {
- t.Fatalf("Expected status code to be 422, got %v (%#v)", statusCode, result)
- }
- }
- // Tests that the apiserver returns 202 status code as expected.
- func Test202StatusCode(t *testing.T) {
- s, clientSet, closeFn := setup(t)
- defer closeFn()
- ns := framework.CreateTestingNamespace("status-code", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- rsClient := clientSet.AppsV1().ReplicaSets(ns.Name)
- // 1. Create the resource without any finalizer and then delete it without setting DeleteOptions.
- // Verify that server returns 200 in this case.
- rs, err := rsClient.Create(context.TODO(), newRS(ns.Name), metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("Failed to create rs: %v", err)
- }
- verifyStatusCode(t, "DELETE", s.URL+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), "", 200)
- // 2. Create the resource with a finalizer so that the resource is not immediately deleted and then delete it without setting DeleteOptions.
- // Verify that the apiserver still returns 200 since DeleteOptions.OrphanDependents is not set.
- rs = newRS(ns.Name)
- rs.ObjectMeta.Finalizers = []string{"kube.io/dummy-finalizer"}
- rs, err = rsClient.Create(context.TODO(), rs, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("Failed to create rs: %v", err)
- }
- verifyStatusCode(t, "DELETE", s.URL+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), "", 200)
- // 3. Create the resource and then delete it with DeleteOptions.OrphanDependents=false.
- // Verify that the server still returns 200 since the resource is immediately deleted.
- rs = newRS(ns.Name)
- rs, err = rsClient.Create(context.TODO(), rs, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("Failed to create rs: %v", err)
- }
- verifyStatusCode(t, "DELETE", s.URL+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), cascDel, 200)
- // 4. Create the resource with a finalizer so that the resource is not immediately deleted and then delete it with DeleteOptions.OrphanDependents=false.
- // Verify that the server returns 202 in this case.
- rs = newRS(ns.Name)
- rs.ObjectMeta.Finalizers = []string{"kube.io/dummy-finalizer"}
- rs, err = rsClient.Create(context.TODO(), rs, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("Failed to create rs: %v", err)
- }
- verifyStatusCode(t, "DELETE", s.URL+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), cascDel, 202)
- }
- func TestListResourceVersion0(t *testing.T) {
- var testcases = []struct {
- name string
- watchCacheEnabled bool
- }{
- {
- name: "watchCacheOn",
- watchCacheEnabled: true,
- },
- {
- name: "watchCacheOff",
- watchCacheEnabled: false,
- },
- }
- for _, tc := range testcases {
- t.Run(tc.name, func(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIListChunking, true)()
- etcdOptions := framework.DefaultEtcdOptions()
- etcdOptions.EnableWatchCache = tc.watchCacheEnabled
- s, clientSet, closeFn := setupWithOptions(t, &framework.MasterConfigOptions{EtcdOptions: etcdOptions})
- defer closeFn()
- ns := framework.CreateTestingNamespace("list-paging", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- rsClient := clientSet.AppsV1().ReplicaSets(ns.Name)
- for i := 0; i < 10; i++ {
- rs := newRS(ns.Name)
- rs.Name = fmt.Sprintf("test-%d", i)
- if _, err := rsClient.Create(context.TODO(), rs, metav1.CreateOptions{}); err != nil {
- t.Fatal(err)
- }
- }
- pagerFn := func(opts metav1.ListOptions) (runtime.Object, error) {
- return rsClient.List(context.TODO(), opts)
- }
- p := pager.New(pager.SimplePageFunc(pagerFn))
- p.PageSize = 3
- listObj, _, err := p.List(context.Background(), metav1.ListOptions{ResourceVersion: "0"})
- if err != nil {
- t.Fatalf("Unexpected list error: %v", err)
- }
- items, err := meta.ExtractList(listObj)
- if err != nil {
- t.Fatalf("Failed to extract list from %v", listObj)
- }
- if len(items) != 10 {
- t.Errorf("Expected list size of 10 but got %d", len(items))
- }
- })
- }
- }
- func TestAPIListChunking(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIListChunking, true)()
- s, clientSet, closeFn := setup(t)
- defer closeFn()
- ns := framework.CreateTestingNamespace("list-paging", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- rsClient := clientSet.AppsV1().ReplicaSets(ns.Name)
- for i := 0; i < 4; i++ {
- rs := newRS(ns.Name)
- rs.Name = fmt.Sprintf("test-%d", i)
- if _, err := rsClient.Create(context.TODO(), rs, metav1.CreateOptions{}); err != nil {
- t.Fatal(err)
- }
- }
- calls := 0
- firstRV := ""
- p := &pager.ListPager{
- PageSize: 1,
- PageFn: pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
- calls++
- list, err := rsClient.List(context.TODO(), opts)
- if err != nil {
- return nil, err
- }
- if calls == 1 {
- firstRV = list.ResourceVersion
- }
- if calls == 2 {
- rs := newRS(ns.Name)
- rs.Name = "test-5"
- if _, err := rsClient.Create(context.TODO(), rs, metav1.CreateOptions{}); err != nil {
- t.Fatal(err)
- }
- }
- return list, err
- }),
- }
- listObj, _, err := p.List(context.Background(), metav1.ListOptions{})
- if err != nil {
- t.Fatal(err)
- }
- if calls != 4 {
- t.Errorf("unexpected list invocations: %d", calls)
- }
- list := listObj.(metav1.ListInterface)
- if len(list.GetContinue()) != 0 {
- t.Errorf("unexpected continue: %s", list.GetContinue())
- }
- if list.GetResourceVersion() != firstRV {
- t.Errorf("unexpected resource version: %s instead of %s", list.GetResourceVersion(), firstRV)
- }
- var names []string
- if err := meta.EachListItem(listObj, func(obj runtime.Object) error {
- rs := obj.(*apps.ReplicaSet)
- names = append(names, rs.Name)
- return nil
- }); err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(names, []string{"test-0", "test-1", "test-2", "test-3"}) {
- t.Errorf("unexpected items: %#v", list)
- }
- }
- func makeSecret(name string) *v1.Secret {
- return &v1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- },
- Data: map[string][]byte{
- "key": []byte("value"),
- },
- }
- }
- func TestNameInFieldSelector(t *testing.T) {
- s, clientSet, closeFn := setup(t)
- defer closeFn()
- numNamespaces := 3
- for i := 0; i < 3; i++ {
- ns := framework.CreateTestingNamespace(fmt.Sprintf("ns%d", i), s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- _, err := clientSet.CoreV1().Secrets(ns.Name).Create(context.TODO(), makeSecret("foo"), metav1.CreateOptions{})
- if err != nil {
- t.Errorf("Couldn't create secret: %v", err)
- }
- _, err = clientSet.CoreV1().Secrets(ns.Name).Create(context.TODO(), makeSecret("bar"), metav1.CreateOptions{})
- if err != nil {
- t.Errorf("Couldn't create secret: %v", err)
- }
- }
- testcases := []struct {
- namespace string
- selector string
- expectedSecrets int
- }{
- {
- namespace: "",
- selector: "metadata.name=foo",
- expectedSecrets: numNamespaces,
- },
- {
- namespace: "",
- selector: "metadata.name=foo,metadata.name=bar",
- expectedSecrets: 0,
- },
- {
- namespace: "",
- selector: "metadata.name=foo,metadata.namespace=ns1",
- expectedSecrets: 1,
- },
- {
- namespace: "ns1",
- selector: "metadata.name=foo,metadata.namespace=ns1",
- expectedSecrets: 1,
- },
- {
- namespace: "ns1",
- selector: "metadata.name=foo,metadata.namespace=ns2",
- expectedSecrets: 0,
- },
- {
- namespace: "ns1",
- selector: "metadata.name=foo,metadata.namespace=",
- expectedSecrets: 0,
- },
- }
- for _, tc := range testcases {
- opts := metav1.ListOptions{
- FieldSelector: tc.selector,
- }
- secrets, err := clientSet.CoreV1().Secrets(tc.namespace).List(context.TODO(), opts)
- if err != nil {
- t.Errorf("%s: Unexpected error: %v", tc.selector, err)
- }
- if len(secrets.Items) != tc.expectedSecrets {
- t.Errorf("%s: Unexpected number of secrets: %d, expected: %d", tc.selector, len(secrets.Items), tc.expectedSecrets)
- }
- }
- }
- type callWrapper struct {
- nested http.RoundTripper
- req *http.Request
- resp *http.Response
- err error
- }
- func (w *callWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
- w.req = req
- resp, err := w.nested.RoundTrip(req)
- w.resp = resp
- w.err = err
- return resp, err
- }
- func TestMetadataClient(t *testing.T) {
- tearDown, config, _, err := fixtures.StartDefaultServer(t)
- if err != nil {
- t.Fatal(err)
- }
- defer tearDown()
- s, clientset, closeFn := setup(t)
- defer closeFn()
- apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- dynamicClient, err := dynamic.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- fooCRD := &apiextensionsv1beta1.CustomResourceDefinition{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foos.cr.bar.com",
- },
- Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
- Group: "cr.bar.com",
- Version: "v1",
- Scope: apiextensionsv1beta1.NamespaceScoped,
- Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
- Plural: "foos",
- Kind: "Foo",
- },
- Subresources: &apiextensionsv1beta1.CustomResourceSubresources{
- Status: &apiextensionsv1beta1.CustomResourceSubresourceStatus{},
- },
- },
- }
- fooCRD, err = fixtures.CreateNewCustomResourceDefinition(fooCRD, apiExtensionClient, dynamicClient)
- if err != nil {
- t.Fatal(err)
- }
- crdGVR := schema.GroupVersionResource{Group: fooCRD.Spec.Group, Version: fooCRD.Spec.Version, Resource: "foos"}
- testcases := []struct {
- name string
- want func(*testing.T)
- }{
- {
- name: "list, get, patch, and delete via metadata client",
- want: func(t *testing.T) {
- ns := "metadata-builtin"
- 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{})
- if err != nil {
- t.Fatalf("unable to create service: %v", err)
- }
- cfg := metadata.ConfigFor(&restclient.Config{Host: s.URL})
- wrapper := &callWrapper{}
- cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
- wrapper.nested = rt
- return wrapper
- })
- client := metadata.NewForConfigOrDie(cfg).Resource(v1.SchemeGroupVersion.WithResource("services"))
- items, err := client.Namespace(ns).List(metav1.ListOptions{})
- if err != nil {
- t.Fatal(err)
- }
- if items.ResourceVersion == "" {
- t.Fatalf("unexpected items: %#v", items)
- }
- if len(items.Items) != 1 {
- t.Fatalf("unexpected list: %#v", items)
- }
- if item := items.Items[0]; item.Name != "test-1" || item.UID != svc.UID || item.Annotations["foo"] != "bar" {
- t.Fatalf("unexpected object: %#v", item)
- }
- if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
- t.Fatalf("unexpected response: %#v", wrapper.resp)
- }
- wrapper.resp = nil
- item, err := client.Namespace(ns).Get("test-1", metav1.GetOptions{})
- if err != nil {
- t.Fatal(err)
- }
- if item.ResourceVersion == "" || item.UID != svc.UID || item.Annotations["foo"] != "bar" {
- t.Fatalf("unexpected object: %#v", item)
- }
- if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
- t.Fatalf("unexpected response: %#v", wrapper.resp)
- }
- item, err = client.Namespace(ns).Patch("test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"foo":"baz"}}}`), metav1.PatchOptions{})
- if err != nil {
- t.Fatal(err)
- }
- if item.Annotations["foo"] != "baz" {
- t.Fatalf("unexpected object: %#v", item)
- }
- if err := client.Namespace(ns).Delete("test-1", &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &item.UID}}); err != nil {
- t.Fatal(err)
- }
- if _, err := client.Namespace(ns).Get("test-1", metav1.GetOptions{}); !apierrors.IsNotFound(err) {
- t.Fatal(err)
- }
- },
- },
- {
- name: "list, get, patch, and delete via metadata client on a CRD",
- want: func(t *testing.T) {
- ns := "metadata-crd"
- crclient := dynamicClient.Resource(crdGVR).Namespace(ns)
- 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-1",
- "annotations": map[string]interface{}{
- "foo": "bar",
- },
- },
- },
- }, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- cfg := metadata.ConfigFor(config)
- wrapper := &callWrapper{}
- cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
- wrapper.nested = rt
- return wrapper
- })
- client := metadata.NewForConfigOrDie(cfg).Resource(crdGVR)
- items, err := client.Namespace(ns).List(metav1.ListOptions{})
- if err != nil {
- t.Fatal(err)
- }
- if items.ResourceVersion == "" {
- t.Fatalf("unexpected items: %#v", items)
- }
- if len(items.Items) != 1 {
- t.Fatalf("unexpected list: %#v", items)
- }
- if item := items.Items[0]; item.Name != "test-1" || item.UID != cr.GetUID() || item.Annotations["foo"] != "bar" {
- t.Fatalf("unexpected object: %#v", item)
- }
- if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
- t.Fatalf("unexpected response: %#v", wrapper.resp)
- }
- wrapper.resp = nil
- item, err := client.Namespace(ns).Get("test-1", metav1.GetOptions{})
- if err != nil {
- t.Fatal(err)
- }
- if item.ResourceVersion == "" || item.UID != cr.GetUID() || item.Annotations["foo"] != "bar" {
- t.Fatalf("unexpected object: %#v", item)
- }
- if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
- t.Fatalf("unexpected response: %#v", wrapper.resp)
- }
- item, err = client.Namespace(ns).Patch("test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"foo":"baz"}}}`), metav1.PatchOptions{})
- if err != nil {
- t.Fatal(err)
- }
- if item.Annotations["foo"] != "baz" {
- t.Fatalf("unexpected object: %#v", item)
- }
- if err := client.Namespace(ns).Delete("test-1", &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &item.UID}}); err != nil {
- t.Fatal(err)
- }
- if _, err := client.Namespace(ns).Get("test-1", metav1.GetOptions{}); !apierrors.IsNotFound(err) {
- t.Fatal(err)
- }
- },
- },
- {
- name: "watch via metadata client",
- want: func(t *testing.T) {
- ns := "metadata-watch"
- 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{})
- if err != nil {
- t.Fatalf("unable to create service: %v", err)
- }
- if _, err := clientset.CoreV1().Services(ns).Patch(context.TODO(), "test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to patch cr: %v", err)
- }
- cfg := metadata.ConfigFor(&restclient.Config{Host: s.URL})
- wrapper := &callWrapper{}
- cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
- wrapper.nested = rt
- return wrapper
- })
- client := metadata.NewForConfigOrDie(cfg).Resource(v1.SchemeGroupVersion.WithResource("services"))
- w, err := client.Namespace(ns).Watch(metav1.ListOptions{ResourceVersion: svc.ResourceVersion, Watch: true})
- if err != nil {
- t.Fatal(err)
- }
- defer w.Stop()
- var r watch.Event
- select {
- case evt, ok := <-w.ResultChan():
- if !ok {
- t.Fatal("watch closed")
- }
- r = evt
- case <-time.After(5 * time.Second):
- t.Fatal("no watch event in 5 seconds, bug")
- }
- if r.Type != watch.Modified {
- t.Fatalf("unexpected watch: %#v", r)
- }
- item, ok := r.Object.(*metav1.PartialObjectMetadata)
- if !ok {
- t.Fatalf("unexpected object: %T", item)
- }
- if item.ResourceVersion == "" || item.Name != "test-2" || item.UID != svc.UID || item.Annotations["test"] != "1" {
- t.Fatalf("unexpected object: %#v", item)
- }
- if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf;stream=watch" {
- t.Fatalf("unexpected response: %#v", wrapper.resp)
- }
- },
- },
- {
- name: "watch via metadata client on a CRD",
- want: func(t *testing.T) {
- ns := "metadata-watch-crd"
- crclient := dynamicClient.Resource(crdGVR).Namespace(ns)
- 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",
- "annotations": map[string]interface{}{
- "foo": "bar",
- },
- },
- },
- }, metav1.CreateOptions{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- cfg := metadata.ConfigFor(config)
- client := metadata.NewForConfigOrDie(cfg).Resource(crdGVR)
- patched, err := client.Namespace(ns).Patch("test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{})
- if err != nil {
- t.Fatal(err)
- }
- if patched.GetResourceVersion() == cr.GetResourceVersion() {
- t.Fatalf("Patch did not modify object: %#v", patched)
- }
- wrapper := &callWrapper{}
- cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
- wrapper.nested = rt
- return wrapper
- })
- client = metadata.NewForConfigOrDie(cfg).Resource(crdGVR)
- w, err := client.Namespace(ns).Watch(metav1.ListOptions{ResourceVersion: cr.GetResourceVersion(), Watch: true})
- if err != nil {
- t.Fatal(err)
- }
- defer w.Stop()
- var r watch.Event
- select {
- case evt, ok := <-w.ResultChan():
- if !ok {
- t.Fatal("watch closed")
- }
- r = evt
- case <-time.After(5 * time.Second):
- t.Fatal("no watch event in 5 seconds, bug")
- }
- if r.Type != watch.Modified {
- t.Fatalf("unexpected watch: %#v", r)
- }
- item, ok := r.Object.(*metav1.PartialObjectMetadata)
- if !ok {
- t.Fatalf("unexpected object: %T", item)
- }
- if item.ResourceVersion == "" || item.Name != "test-2" || item.UID != cr.GetUID() || item.Annotations["test"] != "1" {
- t.Fatalf("unexpected object: %#v", item)
- }
- if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf;stream=watch" {
- t.Fatalf("unexpected response: %#v", wrapper.resp)
- }
- },
- },
- }
- for i := range testcases {
- tc := testcases[i]
- t.Run(tc.name, func(t *testing.T) {
- tc.want(t)
- })
- }
- }
- func TestAPICRDProtobuf(t *testing.T) {
- testNamespace := "test-api-crd-protobuf"
- tearDown, config, _, err := fixtures.StartDefaultServer(t)
- if err != nil {
- t.Fatal(err)
- }
- defer tearDown()
- s, _, closeFn := setup(t)
- defer closeFn()
- apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- dynamicClient, err := dynamic.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- fooCRD := &apiextensionsv1beta1.CustomResourceDefinition{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foos.cr.bar.com",
- },
- Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
- Group: "cr.bar.com",
- Version: "v1",
- Scope: apiextensionsv1beta1.NamespaceScoped,
- Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
- Plural: "foos",
- Kind: "Foo",
- },
- Subresources: &apiextensionsv1beta1.CustomResourceSubresources{Status: &apiextensionsv1beta1.CustomResourceSubresourceStatus{}},
- },
- }
- fooCRD, err = fixtures.CreateNewCustomResourceDefinition(fooCRD, apiExtensionClient, dynamicClient)
- if err != nil {
- t.Fatal(err)
- }
- crdGVR := schema.GroupVersionResource{Group: fooCRD.Spec.Group, Version: fooCRD.Spec.Version, Resource: "foos"}
- crclient := dynamicClient.Resource(crdGVR).Namespace(testNamespace)
- testcases := []struct {
- name string
- accept string
- subresource string
- object func(*testing.T) (metav1.Object, string, string)
- wantErr func(*testing.T, error)
- wantBody func(*testing.T, io.Reader)
- }{
- {
- name: "server returns 406 when asking for protobuf for CRDs, which dynamic client does not support",
- accept: "application/vnd.kubernetes.protobuf",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- if _, err := crclient.Patch("test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to patch cr: %v", err)
- }
- return cr, crdGVR.Group, "foos"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- status := err.(apierrors.APIStatus).Status()
- data, _ := json.MarshalIndent(status, "", " ")
- // because the dynamic client only has a json serializer, the client processing of the error cannot
- // turn the response into something meaningful, so we verify that fallback handling works correctly
- if !apierrors.IsUnexpectedServerError(err) {
- t.Fatal(string(data))
- }
- if status.Message != "the server was unable to respond with a content type that the client supports (get foos.cr.bar.com test-1)" {
- t.Fatal(string(data))
- }
- },
- },
- {
- name: "server returns JSON when asking for protobuf and json for CRDs",
- accept: "application/vnd.kubernetes.protobuf,application/json",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- if _, err := crclient.Patch("test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to patch cr: %v", err)
- }
- return cr, crdGVR.Group, "foos"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- obj := &unstructured.Unstructured{}
- if err := json.NewDecoder(w).Decode(obj); err != nil {
- t.Fatal(err)
- }
- v, ok, err := unstructured.NestedInt64(obj.UnstructuredContent(), "spec", "field")
- if !ok || err != nil {
- data, _ := json.MarshalIndent(obj.UnstructuredContent(), "", " ")
- t.Fatalf("err=%v ok=%t json=%s", err, ok, string(data))
- }
- if v != 1 {
- t.Fatalf("unexpected body: %#v", obj.UnstructuredContent())
- }
- },
- },
- {
- name: "server returns 406 when asking for protobuf for CRDs status, which dynamic client does not support",
- accept: "application/vnd.kubernetes.protobuf",
- subresource: "status",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- if _, err := crclient.Patch("test-3", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"3"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to patch cr: %v", err)
- }
- return cr, crdGVR.Group, "foos"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- status := err.(apierrors.APIStatus).Status()
- data, _ := json.MarshalIndent(status, "", " ")
- // because the dynamic client only has a json serializer, the client processing of the error cannot
- // turn the response into something meaningful, so we verify that fallback handling works correctly
- if !apierrors.IsUnexpectedServerError(err) {
- t.Fatal(string(data))
- }
- if status.Message != "the server was unable to respond with a content type that the client supports (get foos.cr.bar.com test-3)" {
- t.Fatal(string(data))
- }
- },
- },
- {
- name: "server returns JSON when asking for protobuf and json for CRDs status",
- accept: "application/vnd.kubernetes.protobuf,application/json",
- subresource: "status",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- if _, err := crclient.Patch("test-4", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"4"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to patch cr: %v", err)
- }
- return cr, crdGVR.Group, "foos"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- obj := &unstructured.Unstructured{}
- if err := json.NewDecoder(w).Decode(obj); err != nil {
- t.Fatal(err)
- }
- v, ok, err := unstructured.NestedInt64(obj.UnstructuredContent(), "spec", "field")
- if !ok || err != nil {
- data, _ := json.MarshalIndent(obj.UnstructuredContent(), "", " ")
- t.Fatalf("err=%v ok=%t json=%s", err, ok, string(data))
- }
- if v != 1 {
- t.Fatalf("unexpected body: %#v", obj.UnstructuredContent())
- }
- },
- },
- }
- for i := range testcases {
- tc := testcases[i]
- t.Run(tc.name, func(t *testing.T) {
- obj, group, resource := tc.object(t)
- cfg := dynamic.ConfigFor(config)
- if len(group) == 0 {
- cfg = dynamic.ConfigFor(&restclient.Config{Host: s.URL})
- cfg.APIPath = "/api"
- } else {
- cfg.APIPath = "/apis"
- }
- cfg.GroupVersion = &schema.GroupVersion{Group: group, Version: "v1"}
- client, err := restclient.RESTClientFor(cfg)
- if err != nil {
- t.Fatal(err)
- }
- rv, _ := strconv.Atoi(obj.GetResourceVersion())
- if rv < 1 {
- rv = 1
- }
- w, err := client.Get().
- Resource(resource).NamespaceIfScoped(obj.GetNamespace(), len(obj.GetNamespace()) > 0).Name(obj.GetName()).SubResource(tc.subresource).
- SetHeader("Accept", tc.accept).
- Stream(context.TODO())
- if (tc.wantErr != nil) != (err != nil) {
- t.Fatalf("unexpected error: %v", err)
- }
- if tc.wantErr != nil {
- tc.wantErr(t, err)
- return
- }
- if err != nil {
- t.Fatal(err)
- }
- defer w.Close()
- tc.wantBody(t, w)
- })
- }
- }
- func TestTransform(t *testing.T) {
- testNamespace := "test-transform"
- tearDown, config, _, err := fixtures.StartDefaultServer(t)
- if err != nil {
- t.Fatal(err)
- }
- defer tearDown()
- s, clientset, closeFn := setup(t)
- defer closeFn()
- apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- dynamicClient, err := dynamic.NewForConfig(config)
- if err != nil {
- t.Fatal(err)
- }
- fooCRD := &apiextensionsv1beta1.CustomResourceDefinition{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foos.cr.bar.com",
- },
- Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
- Group: "cr.bar.com",
- Version: "v1",
- Scope: apiextensionsv1beta1.NamespaceScoped,
- Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
- Plural: "foos",
- Kind: "Foo",
- },
- },
- }
- fooCRD, err = fixtures.CreateNewCustomResourceDefinition(fooCRD, apiExtensionClient, dynamicClient)
- if err != nil {
- t.Fatal(err)
- }
- crdGVR := schema.GroupVersionResource{Group: fooCRD.Spec.Group, Version: fooCRD.Spec.Version, Resource: "foos"}
- crclient := dynamicClient.Resource(crdGVR).Namespace(testNamespace)
- testcases := []struct {
- name string
- accept string
- includeObject metav1.IncludeObjectPolicy
- object func(*testing.T) (metav1.Object, string, string)
- wantErr func(*testing.T, error)
- wantBody func(*testing.T, io.Reader)
- }{
- {
- name: "v1beta1 verify columns on cluster scoped resources",
- accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "default", Namespace: ""}, "", "namespaces"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectTableWatchEvents(t, 1, 3, metav1.IncludeMetadata, json.NewDecoder(w))
- },
- },
- {
- name: "v1beta1 verify columns on CRDs in json",
- accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- if _, err := crclient.Patch("test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to patch cr: %v", err)
- }
- return cr, crdGVR.Group, "foos"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectTableWatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w))
- },
- },
- {
- name: "v1beta1 verify columns on CRDs in json;stream=watch",
- accept: "application/json;stream=watch;as=Table;g=meta.k8s.io;v=v1beta1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- if _, err := crclient.Patch("test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to patch cr: %v", err)
- }
- return cr, crdGVR.Group, "foos"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectTableWatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w))
- },
- },
- {
- name: "v1beta1 verify columns on CRDs in yaml",
- accept: "application/yaml;as=Table;g=meta.k8s.io;v=v1beta1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- if _, err := crclient.Patch("test-3", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to patch cr: %v", err)
- }
- return cr, crdGVR.Group, "foos"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- // TODO: this should be a more specific error
- if err.Error() != "only the following media types are accepted: application/json;stream=watch, application/vnd.kubernetes.protobuf;stream=watch" {
- t.Fatal(err)
- }
- },
- },
- {
- name: "v1beta1 verify columns on services",
- accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create service: %v", err)
- }
- if _, err := clientset.CoreV1().Services(testNamespace).Patch(context.TODO(), svc.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to update service: %v", err)
- }
- return svc, "", "services"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectTableWatchEvents(t, 2, 7, metav1.IncludeMetadata, json.NewDecoder(w))
- },
- },
- {
- name: "v1beta1 verify columns on services with no object",
- accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
- includeObject: metav1.IncludeNone,
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create object: %v", err)
- }
- if _, err := clientset.CoreV1().Services(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to update object: %v", err)
- }
- return obj, "", "services"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectTableWatchEvents(t, 2, 7, metav1.IncludeNone, json.NewDecoder(w))
- },
- },
- {
- name: "v1beta1 verify columns on services with full object",
- accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
- includeObject: metav1.IncludeObject,
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create object: %v", err)
- }
- if _, err := clientset.CoreV1().Services(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to update object: %v", err)
- }
- return obj, "", "services"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- objects := expectTableWatchEvents(t, 2, 7, metav1.IncludeObject, json.NewDecoder(w))
- var svc v1.Service
- if err := json.Unmarshal(objects[1], &svc); err != nil {
- t.Fatal(err)
- }
- if svc.Annotations["test"] != "1" || svc.Spec.Ports[0].Port != 1000 {
- t.Fatalf("unexpected object: %#v", svc)
- }
- },
- },
- {
- name: "v1beta1 verify partial metadata object on config maps",
- accept: "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1beta1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create object: %v", err)
- }
- if _, err := clientset.CoreV1().ConfigMaps(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to update object: %v", err)
- }
- return obj, "", "configmaps"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectPartialObjectMetaEvents(t, json.NewDecoder(w), "0", "1")
- },
- },
- {
- name: "v1beta1 verify partial metadata object on config maps in protobuf",
- accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1beta1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create object: %v", err)
- }
- if _, err := clientset.CoreV1().ConfigMaps(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to update object: %v", err)
- }
- return obj, "", "configmaps"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectPartialObjectMetaEventsProtobuf(t, w, "0", "1")
- },
- },
- {
- name: "v1beta1 verify partial metadata object on CRDs in protobuf",
- accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1beta1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- if _, err := crclient.Patch("test-4", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to patch cr: %v", err)
- }
- return cr, crdGVR.Group, "foos"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectPartialObjectMetaEventsProtobuf(t, w, "0", "1")
- },
- },
- {
- name: "v1beta1 verify error on unsupported mimetype protobuf for table conversion",
- accept: "application/vnd.kubernetes.protobuf;as=Table;g=meta.k8s.io;v=v1beta1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- // TODO: this should be a more specific error
- if err.Error() != "only the following media types are accepted: application/json, application/yaml, application/vnd.kubernetes.protobuf" {
- t.Fatal(err)
- }
- },
- },
- {
- name: "verify error on invalid mimetype - bad version",
- accept: "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1alpha1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- },
- },
- {
- name: "v1beta1 verify error on invalid mimetype - bad group",
- accept: "application/json;as=PartialObjectMetadata;g=k8s.io;v=v1beta1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- },
- },
- {
- name: "v1beta1 verify error on invalid mimetype - bad kind",
- accept: "application/json;as=PartialObject;g=meta.k8s.io;v=v1beta1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- },
- },
- {
- name: "v1beta1 verify error on invalid mimetype - missing kind",
- accept: "application/json;g=meta.k8s.io;v=v1beta1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- },
- },
- {
- name: "v1beta1 verify error on invalid transform parameter",
- accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
- includeObject: metav1.IncludeObjectPolicy("unrecognized"),
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsBadRequest(err) || !strings.Contains(err.Error(), `Invalid value: "unrecognized": must be 'Metadata', 'Object', 'None', or empty`) {
- t.Fatal(err)
- }
- },
- },
- {
- name: "v1 verify columns on cluster scoped resources",
- accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "default", Namespace: ""}, "", "namespaces"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectTableV1WatchEvents(t, 1, 3, metav1.IncludeMetadata, json.NewDecoder(w))
- },
- },
- {
- name: "v1 verify columns on CRDs in json",
- accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- if _, err := crclient.Patch("test-5", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to patch cr: %v", err)
- }
- return cr, crdGVR.Group, "foos"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectTableV1WatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w))
- },
- },
- {
- name: "v1 verify columns on CRDs in json;stream=watch",
- accept: "application/json;stream=watch;as=Table;g=meta.k8s.io;v=v1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- if _, err := crclient.Patch("test-6", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to patch cr: %v", err)
- }
- return cr, crdGVR.Group, "foos"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectTableV1WatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w))
- },
- },
- {
- name: "v1 verify columns on CRDs in yaml",
- accept: "application/yaml;as=Table;g=meta.k8s.io;v=v1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- if _, err := crclient.Patch("test-7", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to patch cr: %v", err)
- }
- return cr, crdGVR.Group, "foos"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- // TODO: this should be a more specific error
- if err.Error() != "only the following media types are accepted: application/json;stream=watch, application/vnd.kubernetes.protobuf;stream=watch" {
- t.Fatal(err)
- }
- },
- },
- {
- name: "v1 verify columns on services",
- accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create service: %v", err)
- }
- if _, err := clientset.CoreV1().Services(testNamespace).Patch(context.TODO(), svc.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to update service: %v", err)
- }
- return svc, "", "services"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectTableV1WatchEvents(t, 2, 7, metav1.IncludeMetadata, json.NewDecoder(w))
- },
- },
- {
- name: "v1 verify columns on services with no object",
- accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
- includeObject: metav1.IncludeNone,
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create object: %v", err)
- }
- if _, err := clientset.CoreV1().Services(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to update object: %v", err)
- }
- return obj, "", "services"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectTableV1WatchEvents(t, 2, 7, metav1.IncludeNone, json.NewDecoder(w))
- },
- },
- {
- name: "v1 verify columns on services with full object",
- accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
- includeObject: metav1.IncludeObject,
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create object: %v", err)
- }
- if _, err := clientset.CoreV1().Services(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to update object: %v", err)
- }
- return obj, "", "services"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- objects := expectTableV1WatchEvents(t, 2, 7, metav1.IncludeObject, json.NewDecoder(w))
- var svc v1.Service
- if err := json.Unmarshal(objects[1], &svc); err != nil {
- t.Fatal(err)
- }
- if svc.Annotations["test"] != "1" || svc.Spec.Ports[0].Port != 1000 {
- t.Fatalf("unexpected object: %#v", svc)
- }
- },
- },
- {
- name: "v1 verify partial metadata object on config maps",
- accept: "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create object: %v", err)
- }
- if _, err := clientset.CoreV1().ConfigMaps(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to update object: %v", err)
- }
- return obj, "", "configmaps"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectPartialObjectMetaV1Events(t, json.NewDecoder(w), "0", "1")
- },
- },
- {
- name: "v1 verify partial metadata object on config maps in protobuf",
- accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create object: %v", err)
- }
- if _, err := clientset.CoreV1().ConfigMaps(testNamespace).Patch(context.TODO(), obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to update object: %v", err)
- }
- return obj, "", "configmaps"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectPartialObjectMetaV1EventsProtobuf(t, w, "0", "1")
- },
- },
- {
- name: "v1 verify partial metadata object on CRDs in protobuf",
- accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- 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{})
- if err != nil {
- t.Fatalf("unable to create cr: %v", err)
- }
- if _, err := crclient.Patch(cr.GetName(), types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
- t.Fatalf("unable to patch cr: %v", err)
- }
- return cr, crdGVR.Group, "foos"
- },
- wantBody: func(t *testing.T, w io.Reader) {
- expectPartialObjectMetaV1EventsProtobuf(t, w, "0", "1")
- },
- },
- {
- name: "v1 verify error on unsupported mimetype protobuf for table conversion",
- accept: "application/vnd.kubernetes.protobuf;as=Table;g=meta.k8s.io;v=v1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- // TODO: this should be a more specific error
- if err.Error() != "only the following media types are accepted: application/json, application/yaml, application/vnd.kubernetes.protobuf" {
- t.Fatal(err)
- }
- },
- },
- {
- name: "v1 verify error on invalid mimetype - bad group",
- accept: "application/json;as=PartialObjectMetadata;g=k8s.io;v=v1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- },
- },
- {
- name: "v1 verify error on invalid mimetype - bad kind",
- accept: "application/json;as=PartialObject;g=meta.k8s.io;v=v1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- },
- },
- {
- name: "v1 verify error on invalid mimetype - only meta kinds accepted",
- accept: "application/json;as=Service;g=;v=v1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- },
- },
- {
- name: "v1 verify error on invalid mimetype - missing kind",
- accept: "application/json;g=meta.k8s.io;v=v1",
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsNotAcceptable(err) {
- t.Fatal(err)
- }
- },
- },
- {
- name: "v1 verify error on invalid transform parameter",
- accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
- includeObject: metav1.IncludeObjectPolicy("unrecognized"),
- object: func(t *testing.T) (metav1.Object, string, string) {
- return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
- },
- wantErr: func(t *testing.T, err error) {
- if !apierrors.IsBadRequest(err) || !strings.Contains(err.Error(), `Invalid value: "unrecognized": must be 'Metadata', 'Object', 'None', or empty`) {
- t.Fatal(err)
- }
- },
- },
- }
- for i := range testcases {
- tc := testcases[i]
- t.Run(tc.name, func(t *testing.T) {
- obj, group, resource := tc.object(t)
- cfg := dynamic.ConfigFor(config)
- if len(group) == 0 {
- cfg = dynamic.ConfigFor(&restclient.Config{Host: s.URL})
- cfg.APIPath = "/api"
- } else {
- cfg.APIPath = "/apis"
- }
- cfg.GroupVersion = &schema.GroupVersion{Group: group, Version: "v1"}
- client, err := restclient.RESTClientFor(cfg)
- if err != nil {
- t.Fatal(err)
- }
- rv, _ := strconv.Atoi(obj.GetResourceVersion())
- if rv < 1 {
- rv = 1
- }
- w, err := client.Get().
- Resource(resource).NamespaceIfScoped(obj.GetNamespace(), len(obj.GetNamespace()) > 0).
- SetHeader("Accept", tc.accept).
- VersionedParams(&metav1.ListOptions{
- ResourceVersion: strconv.Itoa(rv - 1),
- Watch: true,
- FieldSelector: fields.OneTermEqualSelector("metadata.name", obj.GetName()).String(),
- }, metav1.ParameterCodec).
- Param("includeObject", string(tc.includeObject)).
- Stream(context.TODO())
- if (tc.wantErr != nil) != (err != nil) {
- t.Fatalf("unexpected error: %v", err)
- }
- if tc.wantErr != nil {
- tc.wantErr(t, err)
- return
- }
- if err != nil {
- t.Fatal(err)
- }
- defer w.Close()
- tc.wantBody(t, w)
- })
- }
- }
- func expectTableWatchEvents(t *testing.T, count, columns int, policy metav1.IncludeObjectPolicy, d *json.Decoder) [][]byte {
- t.Helper()
- var objects [][]byte
- for i := 0; i < count; i++ {
- var evt metav1.WatchEvent
- if err := d.Decode(&evt); err != nil {
- t.Fatal(err)
- }
- var table metav1beta1.Table
- if err := json.Unmarshal(evt.Object.Raw, &table); err != nil {
- t.Fatal(err)
- }
- if i == 0 {
- if len(table.ColumnDefinitions) != columns {
- t.Fatalf("Got unexpected columns on first watch event: %d vs %#v", columns, table.ColumnDefinitions)
- }
- } else {
- if len(table.ColumnDefinitions) != 0 {
- t.Fatalf("Expected no columns on second watch event: %#v", table.ColumnDefinitions)
- }
- }
- if len(table.Rows) != 1 {
- t.Fatalf("Invalid rows: %#v", table.Rows)
- }
- row := table.Rows[0]
- if len(row.Cells) != columns {
- t.Fatalf("Invalid row width: %#v", row.Cells)
- }
- switch policy {
- case metav1.IncludeMetadata:
- var meta metav1beta1.PartialObjectMetadata
- if err := json.Unmarshal(row.Object.Raw, &meta); err != nil {
- t.Fatalf("expected partial object: %v", err)
- }
- partialObj := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1beta1"}
- if meta.TypeMeta != partialObj {
- t.Fatalf("expected partial object: %#v", meta)
- }
- case metav1.IncludeNone:
- if len(row.Object.Raw) != 0 {
- t.Fatalf("Expected no object: %s", string(row.Object.Raw))
- }
- case metav1.IncludeObject:
- if len(row.Object.Raw) == 0 {
- t.Fatalf("Expected object: %s", string(row.Object.Raw))
- }
- objects = append(objects, row.Object.Raw)
- }
- }
- return objects
- }
- func expectPartialObjectMetaEvents(t *testing.T, d *json.Decoder, values ...string) {
- t.Helper()
- for i, value := range values {
- var evt metav1.WatchEvent
- if err := d.Decode(&evt); err != nil {
- t.Fatal(err)
- }
- var meta metav1beta1.PartialObjectMetadata
- if err := json.Unmarshal(evt.Object.Raw, &meta); err != nil {
- t.Fatal(err)
- }
- typeMeta := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1beta1"}
- if meta.TypeMeta != typeMeta {
- t.Fatalf("expected partial object: %#v", meta)
- }
- if meta.Annotations["test"] != value {
- t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"])
- }
- }
- }
- func expectPartialObjectMetaEventsProtobuf(t *testing.T, r io.Reader, values ...string) {
- scheme := runtime.NewScheme()
- metav1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
- rs := protobuf.NewRawSerializer(scheme, scheme)
- d := streaming.NewDecoder(
- protobuf.LengthDelimitedFramer.NewFrameReader(ioutil.NopCloser(r)),
- rs,
- )
- ds := metainternalversionscheme.Codecs.UniversalDeserializer()
- for i, value := range values {
- var evt metav1.WatchEvent
- if _, _, err := d.Decode(nil, &evt); err != nil {
- t.Fatal(err)
- }
- obj, gvk, err := ds.Decode(evt.Object.Raw, nil, nil)
- if err != nil {
- t.Fatal(err)
- }
- meta, ok := obj.(*metav1beta1.PartialObjectMetadata)
- if !ok {
- t.Fatalf("unexpected watch object %T", obj)
- }
- expected := &schema.GroupVersionKind{Kind: "PartialObjectMetadata", Version: "v1beta1", Group: "meta.k8s.io"}
- if !reflect.DeepEqual(expected, gvk) {
- t.Fatalf("expected partial object: %#v", meta)
- }
- if meta.Annotations["test"] != value {
- t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"])
- }
- }
- }
- func expectTableV1WatchEvents(t *testing.T, count, columns int, policy metav1.IncludeObjectPolicy, d *json.Decoder) [][]byte {
- t.Helper()
- var objects [][]byte
- for i := 0; i < count; i++ {
- var evt metav1.WatchEvent
- if err := d.Decode(&evt); err != nil {
- t.Fatal(err)
- }
- var table metav1.Table
- if err := json.Unmarshal(evt.Object.Raw, &table); err != nil {
- t.Fatal(err)
- }
- if i == 0 {
- if len(table.ColumnDefinitions) != columns {
- t.Fatalf("Got unexpected columns on first watch event: %d vs %#v", columns, table.ColumnDefinitions)
- }
- } else {
- if len(table.ColumnDefinitions) != 0 {
- t.Fatalf("Expected no columns on second watch event: %#v", table.ColumnDefinitions)
- }
- }
- if len(table.Rows) != 1 {
- t.Fatalf("Invalid rows: %#v", table.Rows)
- }
- row := table.Rows[0]
- if len(row.Cells) != columns {
- t.Fatalf("Invalid row width: %#v", row.Cells)
- }
- switch policy {
- case metav1.IncludeMetadata:
- var meta metav1.PartialObjectMetadata
- if err := json.Unmarshal(row.Object.Raw, &meta); err != nil {
- t.Fatalf("expected partial object: %v", err)
- }
- partialObj := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1"}
- if meta.TypeMeta != partialObj {
- t.Fatalf("expected partial object: %#v", meta)
- }
- case metav1.IncludeNone:
- if len(row.Object.Raw) != 0 {
- t.Fatalf("Expected no object: %s", string(row.Object.Raw))
- }
- case metav1.IncludeObject:
- if len(row.Object.Raw) == 0 {
- t.Fatalf("Expected object: %s", string(row.Object.Raw))
- }
- objects = append(objects, row.Object.Raw)
- }
- }
- return objects
- }
- func expectPartialObjectMetaV1Events(t *testing.T, d *json.Decoder, values ...string) {
- t.Helper()
- for i, value := range values {
- var evt metav1.WatchEvent
- if err := d.Decode(&evt); err != nil {
- t.Fatal(err)
- }
- var meta metav1.PartialObjectMetadata
- if err := json.Unmarshal(evt.Object.Raw, &meta); err != nil {
- t.Fatal(err)
- }
- typeMeta := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1"}
- if meta.TypeMeta != typeMeta {
- t.Fatalf("expected partial object: %#v", meta)
- }
- if meta.Annotations["test"] != value {
- t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"])
- }
- }
- }
- func expectPartialObjectMetaV1EventsProtobuf(t *testing.T, r io.Reader, values ...string) {
- scheme := runtime.NewScheme()
- metav1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
- rs := protobuf.NewRawSerializer(scheme, scheme)
- d := streaming.NewDecoder(
- protobuf.LengthDelimitedFramer.NewFrameReader(ioutil.NopCloser(r)),
- rs,
- )
- ds := metainternalversionscheme.Codecs.UniversalDeserializer()
- for i, value := range values {
- var evt metav1.WatchEvent
- if _, _, err := d.Decode(nil, &evt); err != nil {
- t.Fatal(err)
- }
- obj, gvk, err := ds.Decode(evt.Object.Raw, nil, nil)
- if err != nil {
- t.Fatal(err)
- }
- meta, ok := obj.(*metav1.PartialObjectMetadata)
- if !ok {
- t.Fatalf("unexpected watch object %T", obj)
- }
- expected := &schema.GroupVersionKind{Kind: "PartialObjectMetadata", Version: "v1", Group: "meta.k8s.io"}
- if !reflect.DeepEqual(expected, gvk) {
- t.Fatalf("expected partial object: %#v", meta)
- }
- if meta.Annotations["test"] != value {
- t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"])
- }
- }
- }
|