...

Source file src/k8s.io/kubernetes/test/integration/apiserver/apiserver_test.go

Documentation: k8s.io/kubernetes/test/integration/apiserver

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package apiserver
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io"
    25  	"net"
    26  	"net/http"
    27  	"path"
    28  	"reflect"
    29  	"strconv"
    30  	"strings"
    31  	"sync"
    32  	"testing"
    33  	"time"
    34  
    35  	apps "k8s.io/api/apps/v1"
    36  	v1 "k8s.io/api/core/v1"
    37  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    38  	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    39  	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
    40  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    41  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    42  	"k8s.io/apimachinery/pkg/api/meta"
    43  	metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
    44  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    45  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    46  	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
    47  	"k8s.io/apimachinery/pkg/fields"
    48  	"k8s.io/apimachinery/pkg/runtime"
    49  	"k8s.io/apimachinery/pkg/runtime/schema"
    50  	"k8s.io/apimachinery/pkg/runtime/serializer/protobuf"
    51  	"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
    52  	"k8s.io/apimachinery/pkg/types"
    53  	"k8s.io/apimachinery/pkg/util/uuid"
    54  	"k8s.io/apimachinery/pkg/util/wait"
    55  	"k8s.io/apimachinery/pkg/watch"
    56  	"k8s.io/apiserver/pkg/endpoints/handlers"
    57  	"k8s.io/apiserver/pkg/storage/storagebackend"
    58  	"k8s.io/client-go/dynamic"
    59  	clientset "k8s.io/client-go/kubernetes"
    60  	appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
    61  	"k8s.io/client-go/metadata"
    62  	restclient "k8s.io/client-go/rest"
    63  	"k8s.io/client-go/tools/pager"
    64  	"k8s.io/klog/v2"
    65  	"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
    66  	kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
    67  	"k8s.io/kubernetes/pkg/controlplane"
    68  	"k8s.io/kubernetes/test/integration"
    69  	"k8s.io/kubernetes/test/integration/etcd"
    70  	"k8s.io/kubernetes/test/integration/framework"
    71  	"k8s.io/kubernetes/test/utils/ktesting"
    72  )
    73  
    74  func setup(t *testing.T, groupVersions ...schema.GroupVersion) (context.Context, clientset.Interface, *restclient.Config, framework.TearDownFunc) {
    75  	return setupWithResources(t, groupVersions, nil)
    76  }
    77  
    78  func setupWithResources(t *testing.T, groupVersions []schema.GroupVersion, resources []schema.GroupVersionResource) (context.Context, clientset.Interface /* TODO (pohly): return ktesting.TContext */, *restclient.Config, framework.TearDownFunc) {
    79  	tCtx := ktesting.Init(t)
    80  
    81  	client, config, teardown := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
    82  		ModifyServerConfig: func(config *controlplane.Config) {
    83  			if len(groupVersions) > 0 || len(resources) > 0 {
    84  				resourceConfig := controlplane.DefaultAPIResourceConfigSource()
    85  				resourceConfig.EnableVersions(groupVersions...)
    86  				resourceConfig.EnableResources(resources...)
    87  				config.ExtraConfig.APIResourceConfigSource = resourceConfig
    88  			}
    89  		},
    90  	})
    91  
    92  	newTeardown := func() {
    93  		tCtx.Cancel("tearing down apiserver")
    94  		teardown()
    95  	}
    96  
    97  	return tCtx, client, config, newTeardown
    98  }
    99  
   100  func verifyStatusCode(t *testing.T, transport http.RoundTripper, verb, URL, body string, expectedStatusCode int) {
   101  	// We don't use the typed Go client to send this request to be able to verify the response status code.
   102  	bodyBytes := bytes.NewReader([]byte(body))
   103  	req, err := http.NewRequest(verb, URL, bodyBytes)
   104  	if err != nil {
   105  		t.Fatalf("unexpected error: %v in sending req with verb: %s, URL: %s and body: %s", err, verb, URL, body)
   106  	}
   107  	klog.Infof("Sending request: %v", req)
   108  	resp, err := transport.RoundTrip(req)
   109  	if err != nil {
   110  		t.Fatalf("unexpected error: %v in req: %v", err, req)
   111  	}
   112  	defer resp.Body.Close()
   113  	b, _ := io.ReadAll(resp.Body)
   114  	if resp.StatusCode != expectedStatusCode {
   115  		t.Errorf("Expected status %v, but got %v", expectedStatusCode, resp.StatusCode)
   116  		t.Errorf("Body: %v", string(b))
   117  	}
   118  }
   119  
   120  func newRS(namespace string) *apps.ReplicaSet {
   121  	return &apps.ReplicaSet{
   122  		TypeMeta: metav1.TypeMeta{
   123  			Kind:       "ReplicaSet",
   124  			APIVersion: "apps/v1",
   125  		},
   126  		ObjectMeta: metav1.ObjectMeta{
   127  			Namespace:    namespace,
   128  			GenerateName: "apiserver-test",
   129  		},
   130  		Spec: apps.ReplicaSetSpec{
   131  			Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": "test"}},
   132  			Template: v1.PodTemplateSpec{
   133  				ObjectMeta: metav1.ObjectMeta{
   134  					Labels: map[string]string{"name": "test"},
   135  				},
   136  				Spec: v1.PodSpec{
   137  					Containers: []v1.Container{
   138  						{
   139  							Name:  "fake-name",
   140  							Image: "fakeimage",
   141  						},
   142  					},
   143  				},
   144  			},
   145  		},
   146  	}
   147  }
   148  
   149  var cascDel = `
   150  {
   151    "kind": "DeleteOptions",
   152    "apiVersion": "v1",
   153    "orphanDependents": false
   154  }
   155  `
   156  
   157  func Test4xxStatusCodeInvalidPatch(t *testing.T) {
   158  	ctx, client, _, tearDownFn := setup(t)
   159  	defer tearDownFn()
   160  
   161  	obj := []byte(`{
   162  		"apiVersion": "apps/v1",
   163  		"kind": "Deployment",
   164  		"metadata": {
   165  			"name": "deployment",
   166  			"labels": {"app": "nginx"}
   167                  },
   168  		"spec": {
   169  			"selector": {
   170  				"matchLabels": {
   171  					 "app": "nginx"
   172  				}
   173  			},
   174  			"template": {
   175  				"metadata": {
   176  					"labels": {
   177  						"app": "nginx"
   178  					}
   179  				},
   180  				"spec": {
   181  					"containers": [{
   182  						"name":  "nginx",
   183  						"image": "nginx:latest"
   184  					}]
   185  				}
   186  			}
   187  		}
   188  	}`)
   189  
   190  	resp, err := client.CoreV1().RESTClient().Post().
   191  		AbsPath("/apis/apps/v1").
   192  		Namespace("default").
   193  		Resource("deployments").
   194  		Body(obj).Do(ctx).Get()
   195  	if err != nil {
   196  		t.Fatalf("Failed to create object: %v: %v", err, resp)
   197  	}
   198  	result := client.CoreV1().RESTClient().Patch(types.MergePatchType).
   199  		AbsPath("/apis/apps/v1").
   200  		Namespace("default").
   201  		Resource("deployments").
   202  		Name("deployment").
   203  		Body([]byte(`{"metadata":{"annotations":{"foo":["bar"]}}}`)).Do(ctx)
   204  	var statusCode int
   205  	result.StatusCode(&statusCode)
   206  	if statusCode != 422 {
   207  		t.Fatalf("Expected status code to be 422, got %v (%#v)", statusCode, result)
   208  	}
   209  	result = client.CoreV1().RESTClient().Patch(types.StrategicMergePatchType).
   210  		AbsPath("/apis/apps/v1").
   211  		Namespace("default").
   212  		Resource("deployments").
   213  		Name("deployment").
   214  		Body([]byte(`{"metadata":{"annotations":{"foo":["bar"]}}}`)).Do(ctx)
   215  	result.StatusCode(&statusCode)
   216  	if statusCode != 422 {
   217  		t.Fatalf("Expected status code to be 422, got %v (%#v)", statusCode, result)
   218  	}
   219  }
   220  
   221  func TestCacheControl(t *testing.T) {
   222  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
   223  	defer server.TearDownFn()
   224  
   225  	rt, err := restclient.TransportFor(server.ClientConfig)
   226  	if err != nil {
   227  		t.Fatal(err)
   228  	}
   229  
   230  	paths := []string{
   231  		// untyped
   232  		"/",
   233  		// health
   234  		"/healthz",
   235  		// openapi
   236  		"/openapi/v2",
   237  		// discovery
   238  		"/api",
   239  		"/api/v1",
   240  		"/apis",
   241  		"/apis/apps",
   242  		"/apis/apps/v1",
   243  		// apis
   244  		"/api/v1/namespaces",
   245  		"/apis/apps/v1/deployments",
   246  	}
   247  	for _, path := range paths {
   248  		t.Run(path, func(t *testing.T) {
   249  			req, err := http.NewRequest("GET", server.ClientConfig.Host+path, nil)
   250  			if err != nil {
   251  				t.Fatal(err)
   252  			}
   253  			resp, err := rt.RoundTrip(req)
   254  			if err != nil {
   255  				t.Fatal(err)
   256  			}
   257  			defer resp.Body.Close()
   258  			cc := resp.Header.Get("Cache-Control")
   259  			if !strings.Contains(cc, "private") {
   260  				t.Errorf("expected private cache-control, got %q", cc)
   261  			}
   262  		})
   263  	}
   264  }
   265  
   266  // Tests that the apiserver returns HSTS headers as expected.
   267  func TestHSTS(t *testing.T) {
   268  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--strict-transport-security-directives=max-age=31536000,includeSubDomains"}, framework.SharedEtcd())
   269  	defer server.TearDownFn()
   270  
   271  	rt, err := restclient.TransportFor(server.ClientConfig)
   272  	if err != nil {
   273  		t.Fatal(err)
   274  	}
   275  
   276  	paths := []string{
   277  		// untyped
   278  		"/",
   279  		// health
   280  		"/healthz",
   281  		// openapi
   282  		"/openapi/v2",
   283  		// discovery
   284  		"/api",
   285  		"/api/v1",
   286  		"/apis",
   287  		"/apis/apps",
   288  		"/apis/apps/v1",
   289  		// apis
   290  		"/api/v1/namespaces",
   291  		"/apis/apps/v1/deployments",
   292  	}
   293  	for _, path := range paths {
   294  		t.Run(path, func(t *testing.T) {
   295  			req, err := http.NewRequest("GET", server.ClientConfig.Host+path, nil)
   296  			if err != nil {
   297  				t.Fatal(err)
   298  			}
   299  			resp, err := rt.RoundTrip(req)
   300  			if err != nil {
   301  				t.Fatal(err)
   302  			}
   303  			defer resp.Body.Close()
   304  			cc := resp.Header.Get("Strict-Transport-Security")
   305  			if !strings.Contains(cc, "max-age=31536000; includeSubDomains") {
   306  				t.Errorf("expected max-age=31536000; includeSubDomains, got %q", cc)
   307  			}
   308  		})
   309  	}
   310  }
   311  
   312  // Tests that the apiserver returns 202 status code as expected.
   313  func Test202StatusCode(t *testing.T) {
   314  	ctx, clientSet, kubeConfig, tearDownFn := setup(t)
   315  	defer tearDownFn()
   316  
   317  	transport, err := restclient.TransportFor(kubeConfig)
   318  	if err != nil {
   319  		t.Fatal(err)
   320  	}
   321  
   322  	ns := framework.CreateNamespaceOrDie(clientSet, "status-code", t)
   323  	defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
   324  
   325  	rsClient := clientSet.AppsV1().ReplicaSets(ns.Name)
   326  
   327  	// 1. Create the resource without any finalizer and then delete it without setting DeleteOptions.
   328  	// Verify that server returns 200 in this case.
   329  	rs, err := rsClient.Create(ctx, newRS(ns.Name), metav1.CreateOptions{})
   330  	if err != nil {
   331  		t.Fatalf("Failed to create rs: %v", err)
   332  	}
   333  	verifyStatusCode(t, transport, "DELETE", kubeConfig.Host+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), "", 200)
   334  
   335  	// 2. Create the resource with a finalizer so that the resource is not immediately deleted and then delete it without setting DeleteOptions.
   336  	// Verify that the apiserver still returns 200 since DeleteOptions.OrphanDependents is not set.
   337  	rs = newRS(ns.Name)
   338  	rs.ObjectMeta.Finalizers = []string{"kube.io/dummy-finalizer"}
   339  	rs, err = rsClient.Create(ctx, rs, metav1.CreateOptions{})
   340  	if err != nil {
   341  		t.Fatalf("Failed to create rs: %v", err)
   342  	}
   343  	verifyStatusCode(t, transport, "DELETE", kubeConfig.Host+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), "", 200)
   344  
   345  	// 3. Create the resource and then delete it with DeleteOptions.OrphanDependents=false.
   346  	// Verify that the server still returns 200 since the resource is immediately deleted.
   347  	rs = newRS(ns.Name)
   348  	rs, err = rsClient.Create(ctx, rs, metav1.CreateOptions{})
   349  	if err != nil {
   350  		t.Fatalf("Failed to create rs: %v", err)
   351  	}
   352  	verifyStatusCode(t, transport, "DELETE", kubeConfig.Host+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), cascDel, 200)
   353  
   354  	// 4. Create the resource with a finalizer so that the resource is not immediately deleted and then delete it with DeleteOptions.OrphanDependents=false.
   355  	// Verify that the server returns 202 in this case.
   356  	rs = newRS(ns.Name)
   357  	rs.ObjectMeta.Finalizers = []string{"kube.io/dummy-finalizer"}
   358  	rs, err = rsClient.Create(ctx, rs, metav1.CreateOptions{})
   359  	if err != nil {
   360  		t.Fatalf("Failed to create rs: %v", err)
   361  	}
   362  	verifyStatusCode(t, transport, "DELETE", kubeConfig.Host+path.Join("/apis/apps/v1/namespaces", ns.Name, "replicasets", rs.Name), cascDel, 202)
   363  }
   364  
   365  var (
   366  	invalidContinueToken        = "invalidContinueToken"
   367  	invalidResourceVersion      = "invalid"
   368  	invalidResourceVersionMatch = metav1.ResourceVersionMatch("InvalidMatch")
   369  )
   370  
   371  // TestListOptions ensures that list works as expected for valid and invalid combinations of limit, continue,
   372  // resourceVersion and resourceVersionMatch.
   373  func TestListOptions(t *testing.T) {
   374  
   375  	for _, watchCacheEnabled := range []bool{true, false} {
   376  		t.Run(fmt.Sprintf("watchCacheEnabled=%t", watchCacheEnabled), func(t *testing.T) {
   377  			tCtx := ktesting.Init(t)
   378  
   379  			var storageTransport *storagebackend.TransportConfig
   380  			clientSet, _, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
   381  				ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
   382  					opts.Etcd.EnableWatchCache = watchCacheEnabled
   383  					storageTransport = &opts.Etcd.StorageConfig.Transport
   384  				},
   385  			})
   386  			defer tearDownFn()
   387  
   388  			ns := framework.CreateNamespaceOrDie(clientSet, "list-options", t)
   389  			defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
   390  
   391  			rsClient := clientSet.AppsV1().ReplicaSets(ns.Name)
   392  
   393  			var compactedRv, oldestUncompactedRv string
   394  			for i := 0; i < 15; i++ {
   395  				rs := newRS(ns.Name)
   396  				rs.Name = fmt.Sprintf("test-%d", i)
   397  				created, err := rsClient.Create(tCtx, rs, metav1.CreateOptions{})
   398  				if err != nil {
   399  					t.Fatal(err)
   400  				}
   401  				if i == 0 {
   402  					compactedRv = created.ResourceVersion // We compact this first resource version below
   403  				}
   404  				// delete the first 5, and then compact them
   405  				if i < 5 {
   406  					var zero int64
   407  					if err := rsClient.Delete(tCtx, rs.Name, metav1.DeleteOptions{GracePeriodSeconds: &zero}); err != nil {
   408  						t.Fatal(err)
   409  					}
   410  					oldestUncompactedRv = created.ResourceVersion
   411  				}
   412  			}
   413  
   414  			// compact some of the revision history in etcd so we can test "too old" resource versions
   415  			rawClient, kvClient, err := integration.GetEtcdClients(*storageTransport)
   416  			if err != nil {
   417  				t.Fatal(err)
   418  			}
   419  			// kvClient is a wrapper around rawClient and to avoid leaking goroutines we need to
   420  			// close the client (which we can do by closing rawClient).
   421  			defer rawClient.Close()
   422  
   423  			revision, err := strconv.Atoi(oldestUncompactedRv)
   424  			if err != nil {
   425  				t.Fatal(err)
   426  			}
   427  			_, err = kvClient.Compact(tCtx, int64(revision))
   428  			if err != nil {
   429  				t.Fatal(err)
   430  			}
   431  
   432  			listObj, err := rsClient.List(tCtx, metav1.ListOptions{
   433  				Limit: 6,
   434  			})
   435  			if err != nil {
   436  				t.Fatalf("unexpected error: %v", err)
   437  			}
   438  			validContinueToken := listObj.Continue
   439  
   440  			// test all combinations of these, for both watch cache enabled and disabled:
   441  			limits := []int64{0, 6}
   442  			continueTokens := []string{"", validContinueToken, invalidContinueToken}
   443  			rvs := []string{"", "0", compactedRv, invalidResourceVersion}
   444  			rvMatches := []metav1.ResourceVersionMatch{
   445  				"",
   446  				metav1.ResourceVersionMatchNotOlderThan,
   447  				metav1.ResourceVersionMatchExact,
   448  				invalidResourceVersionMatch,
   449  			}
   450  
   451  			for _, limit := range limits {
   452  				for _, continueToken := range continueTokens {
   453  					for _, rv := range rvs {
   454  						for _, rvMatch := range rvMatches {
   455  							rvName := ""
   456  							switch rv {
   457  							case "":
   458  								rvName = "empty"
   459  							case "0":
   460  								rvName = "0"
   461  							case compactedRv:
   462  								rvName = "compacted"
   463  							case invalidResourceVersion:
   464  								rvName = "invalid"
   465  							default:
   466  								rvName = "unknown"
   467  							}
   468  
   469  							continueName := ""
   470  							switch continueToken {
   471  							case "":
   472  								continueName = "empty"
   473  							case validContinueToken:
   474  								continueName = "valid"
   475  							case invalidContinueToken:
   476  								continueName = "invalid"
   477  							default:
   478  								continueName = "unknown"
   479  							}
   480  
   481  							name := fmt.Sprintf("limit=%d continue=%s rv=%s rvMatch=%s", limit, continueName, rvName, rvMatch)
   482  							t.Run(name, func(t *testing.T) {
   483  								opts := metav1.ListOptions{
   484  									ResourceVersion:      rv,
   485  									ResourceVersionMatch: rvMatch,
   486  									Continue:             continueToken,
   487  									Limit:                limit,
   488  								}
   489  								testListOptionsCase(t, rsClient, watchCacheEnabled, opts, compactedRv)
   490  							})
   491  						}
   492  					}
   493  				}
   494  			}
   495  		})
   496  	}
   497  }
   498  
   499  func testListOptionsCase(t *testing.T, rsClient appsv1.ReplicaSetInterface, watchCacheEnabled bool, opts metav1.ListOptions, compactedRv string) {
   500  	listObj, err := rsClient.List(context.Background(), opts)
   501  
   502  	// check for expected validation errors
   503  	if opts.ResourceVersion == "" && opts.ResourceVersionMatch != "" {
   504  		if err == nil || !strings.Contains(err.Error(), "resourceVersionMatch is forbidden unless resourceVersion is provided") {
   505  			t.Fatalf("expected forbidden error, but got: %v", err)
   506  		}
   507  		return
   508  	}
   509  	if opts.Continue != "" && opts.ResourceVersionMatch != "" {
   510  		if err == nil || !strings.Contains(err.Error(), "resourceVersionMatch is forbidden when continue is provided") {
   511  			t.Fatalf("expected forbidden error, but got: %v", err)
   512  		}
   513  		return
   514  	}
   515  	if opts.ResourceVersionMatch == invalidResourceVersionMatch {
   516  		if err == nil || !strings.Contains(err.Error(), "supported values") {
   517  			t.Fatalf("expected not supported error, but got: %v", err)
   518  		}
   519  		return
   520  	}
   521  	if opts.ResourceVersionMatch == metav1.ResourceVersionMatchExact && opts.ResourceVersion == "0" {
   522  		if err == nil || !strings.Contains(err.Error(), "resourceVersionMatch \"exact\" is forbidden for resourceVersion \"0\"") {
   523  			t.Fatalf("expected forbidden error, but got: %v", err)
   524  		}
   525  		return
   526  	}
   527  	if opts.Continue == invalidContinueToken {
   528  		if err == nil || !strings.Contains(err.Error(), "continue key is not valid") {
   529  			t.Fatalf("expected continue key not valid error, but got: %v", err)
   530  		}
   531  		return
   532  	}
   533  	// Should not be allowed for any resource version, but tightening the validation would be a breaking change
   534  	if opts.Continue != "" && !(opts.ResourceVersion == "" || opts.ResourceVersion == "0") {
   535  		if err == nil || !strings.Contains(err.Error(), "specifying resource version is not allowed when using continue") {
   536  			t.Fatalf("expected not allowed error, but got: %v", err)
   537  		}
   538  		return
   539  	}
   540  	if opts.ResourceVersion == invalidResourceVersion {
   541  		if err == nil || !strings.Contains(err.Error(), "Invalid value") {
   542  			t.Fatalf("expecting invalid value error, but got: %v", err)
   543  		}
   544  		return
   545  	}
   546  
   547  	// Check for too old errors
   548  	isExact := opts.ResourceVersionMatch == metav1.ResourceVersionMatchExact
   549  	// Legacy corner cases that can be avoided by using an explicit resourceVersionMatch value
   550  	// see https://kubernetes.io/docs/reference/using-api/api-concepts/#the-resourceversion-parameter
   551  	isLegacyExact := opts.Limit > 0 && opts.ResourceVersionMatch == ""
   552  
   553  	if opts.ResourceVersion == compactedRv && (isExact || isLegacyExact) {
   554  		if err == nil || !strings.Contains(err.Error(), "The resourceVersion for the provided list is too old") {
   555  			t.Fatalf("expected too old error, but got: %v", err)
   556  		}
   557  		return
   558  	}
   559  
   560  	// test successful responses
   561  	if err != nil {
   562  		t.Fatalf("unexpected error: %v", err)
   563  	}
   564  	items, err := meta.ExtractList(listObj)
   565  	if err != nil {
   566  		t.Fatalf("Failed to extract list from %v", listObj)
   567  	}
   568  	count := int64(len(items))
   569  
   570  	// Cacher.GetList defines this for logic to decide if the watch cache is skipped. We need to know it to know if
   571  	// the limit is respected when testing here.
   572  	hasContinuation := len(opts.Continue) > 0
   573  	hasLimit := opts.Limit > 0 && opts.ResourceVersion != "0"
   574  	skipWatchCache := opts.ResourceVersion == "" || hasContinuation || hasLimit || isExact
   575  	usingWatchCache := watchCacheEnabled && !skipWatchCache
   576  
   577  	if usingWatchCache { // watch cache does not respect limit and is not used for continue
   578  		if count != 10 {
   579  			t.Errorf("Expected list size to be 10 but got %d", count) // limit is ignored if watch cache is hit
   580  		}
   581  		return
   582  	}
   583  
   584  	if opts.Continue != "" {
   585  		if count != 4 {
   586  			t.Errorf("Expected list size of 4 but got %d", count)
   587  		}
   588  		return
   589  	}
   590  	if opts.Limit > 0 {
   591  		if count != opts.Limit {
   592  			t.Errorf("Expected list size to be limited to %d but got %d", opts.Limit, count)
   593  		}
   594  		return
   595  	}
   596  	if count != 10 {
   597  		t.Errorf("Expected list size to be 10 but got %d", count)
   598  	}
   599  }
   600  
   601  func TestListResourceVersion0(t *testing.T) {
   602  	var testcases = []struct {
   603  		name              string
   604  		watchCacheEnabled bool
   605  	}{
   606  		{
   607  			name:              "watchCacheOn",
   608  			watchCacheEnabled: true,
   609  		},
   610  		{
   611  			name:              "watchCacheOff",
   612  			watchCacheEnabled: false,
   613  		},
   614  	}
   615  
   616  	for _, tc := range testcases {
   617  		t.Run(tc.name, func(t *testing.T) {
   618  			tCtx := ktesting.Init(t)
   619  
   620  			clientSet, _, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
   621  				ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
   622  					opts.Etcd.EnableWatchCache = tc.watchCacheEnabled
   623  				},
   624  			})
   625  			defer tearDownFn()
   626  
   627  			ns := framework.CreateNamespaceOrDie(clientSet, "list-paging", t)
   628  			defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
   629  
   630  			rsClient := clientSet.AppsV1().ReplicaSets(ns.Name)
   631  
   632  			for i := 0; i < 10; i++ {
   633  				rs := newRS(ns.Name)
   634  				rs.Name = fmt.Sprintf("test-%d", i)
   635  				if _, err := rsClient.Create(tCtx, rs, metav1.CreateOptions{}); err != nil {
   636  					t.Fatal(err)
   637  				}
   638  			}
   639  
   640  			if tc.watchCacheEnabled {
   641  				// poll until the watch cache has the full list in memory
   642  				err := wait.PollImmediate(time.Second, wait.ForeverTestTimeout, func() (bool, error) {
   643  					list, err := clientSet.AppsV1().ReplicaSets(ns.Name).List(tCtx, metav1.ListOptions{ResourceVersion: "0"})
   644  					if err != nil {
   645  						return false, err
   646  					}
   647  					return len(list.Items) == 10, nil
   648  				})
   649  				if err != nil {
   650  					t.Fatalf("error waiting for watch cache to observe the full list: %v", err)
   651  				}
   652  			}
   653  
   654  			pagerFn := func(opts metav1.ListOptions) (runtime.Object, error) {
   655  				return rsClient.List(tCtx, opts)
   656  			}
   657  
   658  			p := pager.New(pager.SimplePageFunc(pagerFn))
   659  			p.PageSize = 3
   660  			listObj, _, err := p.List(tCtx, metav1.ListOptions{ResourceVersion: "0"})
   661  			if err != nil {
   662  				t.Fatalf("Unexpected list error: %v", err)
   663  			}
   664  			items, err := meta.ExtractList(listObj)
   665  			if err != nil {
   666  				t.Fatalf("Failed to extract list from %v", listObj)
   667  			}
   668  			if len(items) != 10 {
   669  				t.Errorf("Expected list size of 10 but got %d", len(items))
   670  			}
   671  		})
   672  	}
   673  }
   674  
   675  func TestAPIListChunking(t *testing.T) {
   676  	ctx, clientSet, _, tearDownFn := setup(t)
   677  	defer tearDownFn()
   678  
   679  	ns := framework.CreateNamespaceOrDie(clientSet, "list-paging", t)
   680  	defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
   681  
   682  	rsClient := clientSet.AppsV1().ReplicaSets(ns.Name)
   683  
   684  	for i := 0; i < 4; i++ {
   685  		rs := newRS(ns.Name)
   686  		rs.Name = fmt.Sprintf("test-%d", i)
   687  		if _, err := rsClient.Create(ctx, rs, metav1.CreateOptions{}); err != nil {
   688  			t.Fatal(err)
   689  		}
   690  	}
   691  
   692  	calls := 0
   693  	firstRV := ""
   694  	p := &pager.ListPager{
   695  		PageSize: 1,
   696  		PageFn: pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
   697  			calls++
   698  			list, err := rsClient.List(ctx, opts)
   699  			if err != nil {
   700  				return nil, err
   701  			}
   702  			if calls == 1 {
   703  				firstRV = list.ResourceVersion
   704  			}
   705  			if calls == 2 {
   706  				rs := newRS(ns.Name)
   707  				rs.Name = "test-5"
   708  				if _, err := rsClient.Create(ctx, rs, metav1.CreateOptions{}); err != nil {
   709  					t.Fatal(err)
   710  				}
   711  			}
   712  			return list, err
   713  		}),
   714  	}
   715  	listObj, _, err := p.List(ctx, metav1.ListOptions{})
   716  	if err != nil {
   717  		t.Fatal(err)
   718  	}
   719  	if calls != 4 {
   720  		t.Errorf("unexpected list invocations: %d", calls)
   721  	}
   722  	list := listObj.(metav1.ListInterface)
   723  	if len(list.GetContinue()) != 0 {
   724  		t.Errorf("unexpected continue: %s", list.GetContinue())
   725  	}
   726  	if list.GetResourceVersion() != firstRV {
   727  		t.Errorf("unexpected resource version: %s instead of %s", list.GetResourceVersion(), firstRV)
   728  	}
   729  	var names []string
   730  	if err := meta.EachListItem(listObj, func(obj runtime.Object) error {
   731  		rs := obj.(*apps.ReplicaSet)
   732  		names = append(names, rs.Name)
   733  		return nil
   734  	}); err != nil {
   735  		t.Fatal(err)
   736  	}
   737  	if !reflect.DeepEqual(names, []string{"test-0", "test-1", "test-2", "test-3"}) {
   738  		t.Errorf("unexpected items: %#v", list)
   739  	}
   740  }
   741  
   742  func TestAPIListChunkingWithLabelSelector(t *testing.T) {
   743  	ctx, clientSet, _, tearDownFn := setup(t)
   744  	defer tearDownFn()
   745  
   746  	ns := framework.CreateNamespaceOrDie(clientSet, "list-paging-with-label-selector", t)
   747  	defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
   748  
   749  	rsClient := clientSet.AppsV1().ReplicaSets(ns.Name)
   750  
   751  	for i := 0; i < 10; i++ {
   752  		rs := newRS(ns.Name)
   753  		rs.Name = fmt.Sprintf("test-%d", i)
   754  		odd := i%2 != 0
   755  		rs.Labels = map[string]string{"odd-index": strconv.FormatBool(odd)}
   756  		if _, err := rsClient.Create(ctx, rs, metav1.CreateOptions{}); err != nil {
   757  			t.Fatal(err)
   758  		}
   759  	}
   760  
   761  	calls := 0
   762  	firstRV := ""
   763  	p := &pager.ListPager{
   764  		PageSize: 1,
   765  		PageFn: pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
   766  			calls++
   767  			list, err := rsClient.List(ctx, opts)
   768  			if err != nil {
   769  				return nil, err
   770  			}
   771  			if calls == 1 {
   772  				firstRV = list.ResourceVersion
   773  			}
   774  			return list, err
   775  		}),
   776  	}
   777  	listObj, _, err := p.List(ctx, metav1.ListOptions{LabelSelector: "odd-index=true", Limit: 3})
   778  	if err != nil {
   779  		t.Fatal(err)
   780  	}
   781  	if calls != 2 {
   782  		t.Errorf("unexpected list invocations: %d", calls)
   783  	}
   784  	list := listObj.(metav1.ListInterface)
   785  	if len(list.GetContinue()) != 0 {
   786  		t.Errorf("unexpected continue: %s", list.GetContinue())
   787  	}
   788  	if list.GetResourceVersion() != firstRV {
   789  		t.Errorf("unexpected resource version: %s instead of %s", list.GetResourceVersion(), firstRV)
   790  	}
   791  	var names []string
   792  	if err := meta.EachListItem(listObj, func(obj runtime.Object) error {
   793  		rs := obj.(*apps.ReplicaSet)
   794  		names = append(names, rs.Name)
   795  		return nil
   796  	}); err != nil {
   797  		t.Fatal(err)
   798  	}
   799  	if !reflect.DeepEqual(names, []string{"test-1", "test-3", "test-5", "test-7", "test-9"}) {
   800  		t.Errorf("unexpected items: %#v", list)
   801  	}
   802  }
   803  
   804  func makeSecret(name string) *v1.Secret {
   805  	return &v1.Secret{
   806  		ObjectMeta: metav1.ObjectMeta{
   807  			Name: name,
   808  		},
   809  		Data: map[string][]byte{
   810  			"key": []byte("value"),
   811  		},
   812  	}
   813  }
   814  
   815  func TestNameInFieldSelector(t *testing.T) {
   816  	ctx, clientSet, _, tearDownFn := setup(t)
   817  	defer tearDownFn()
   818  
   819  	numNamespaces := 3
   820  	for i := 0; i < 3; i++ {
   821  		ns := framework.CreateNamespaceOrDie(clientSet, fmt.Sprintf("ns%d", i), t)
   822  		defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
   823  
   824  		_, err := clientSet.CoreV1().Secrets(ns.Name).Create(ctx, makeSecret("foo"), metav1.CreateOptions{})
   825  		if err != nil {
   826  			t.Errorf("Couldn't create secret: %v", err)
   827  		}
   828  		_, err = clientSet.CoreV1().Secrets(ns.Name).Create(ctx, makeSecret("bar"), metav1.CreateOptions{})
   829  		if err != nil {
   830  			t.Errorf("Couldn't create secret: %v", err)
   831  		}
   832  	}
   833  
   834  	testcases := []struct {
   835  		namespace       string
   836  		selector        string
   837  		expectedSecrets int
   838  	}{
   839  		{
   840  			namespace:       "",
   841  			selector:        "metadata.name=foo",
   842  			expectedSecrets: numNamespaces,
   843  		},
   844  		{
   845  			namespace:       "",
   846  			selector:        "metadata.name=foo,metadata.name=bar",
   847  			expectedSecrets: 0,
   848  		},
   849  		{
   850  			namespace:       "",
   851  			selector:        "metadata.name=foo,metadata.namespace=ns1",
   852  			expectedSecrets: 1,
   853  		},
   854  		{
   855  			namespace:       "ns1",
   856  			selector:        "metadata.name=foo,metadata.namespace=ns1",
   857  			expectedSecrets: 1,
   858  		},
   859  		{
   860  			namespace:       "ns1",
   861  			selector:        "metadata.name=foo,metadata.namespace=ns2",
   862  			expectedSecrets: 0,
   863  		},
   864  		{
   865  			namespace:       "ns1",
   866  			selector:        "metadata.name=foo,metadata.namespace=",
   867  			expectedSecrets: 0,
   868  		},
   869  	}
   870  
   871  	for _, tc := range testcases {
   872  		opts := metav1.ListOptions{
   873  			FieldSelector: tc.selector,
   874  		}
   875  		secrets, err := clientSet.CoreV1().Secrets(tc.namespace).List(ctx, opts)
   876  		if err != nil {
   877  			t.Errorf("%s: Unexpected error: %v", tc.selector, err)
   878  		}
   879  		if len(secrets.Items) != tc.expectedSecrets {
   880  			t.Errorf("%s: Unexpected number of secrets: %d, expected: %d", tc.selector, len(secrets.Items), tc.expectedSecrets)
   881  		}
   882  	}
   883  }
   884  
   885  type callWrapper struct {
   886  	nested http.RoundTripper
   887  	req    *http.Request
   888  	resp   *http.Response
   889  	err    error
   890  }
   891  
   892  func (w *callWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
   893  	w.req = req
   894  	resp, err := w.nested.RoundTrip(req)
   895  	w.resp = resp
   896  	w.err = err
   897  	return resp, err
   898  }
   899  
   900  func TestMetadataClient(t *testing.T) {
   901  	tearDown, config, _, err := fixtures.StartDefaultServer(t)
   902  	if err != nil {
   903  		t.Fatal(err)
   904  	}
   905  	defer tearDown()
   906  
   907  	ctx, clientset, kubeConfig, tearDownFn := setup(t)
   908  	defer tearDownFn()
   909  
   910  	apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
   911  	if err != nil {
   912  		t.Fatal(err)
   913  	}
   914  
   915  	dynamicClient, err := dynamic.NewForConfig(config)
   916  	if err != nil {
   917  		t.Fatal(err)
   918  	}
   919  
   920  	fooCRD := &apiextensionsv1.CustomResourceDefinition{
   921  		ObjectMeta: metav1.ObjectMeta{
   922  			Name: "foos.cr.bar.com",
   923  		},
   924  		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   925  			Group: "cr.bar.com",
   926  			Scope: apiextensionsv1.NamespaceScoped,
   927  			Names: apiextensionsv1.CustomResourceDefinitionNames{
   928  				Plural: "foos",
   929  				Kind:   "Foo",
   930  			},
   931  			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   932  				{
   933  					Name:    "v1",
   934  					Served:  true,
   935  					Storage: true,
   936  					Schema:  fixtures.AllowAllSchema(),
   937  					Subresources: &apiextensionsv1.CustomResourceSubresources{
   938  						Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
   939  					},
   940  				},
   941  			},
   942  		},
   943  	}
   944  	fooCRD, err = fixtures.CreateNewV1CustomResourceDefinition(fooCRD, apiExtensionClient, dynamicClient)
   945  	if err != nil {
   946  		t.Fatal(err)
   947  	}
   948  	crdGVR := schema.GroupVersionResource{Group: fooCRD.Spec.Group, Version: fooCRD.Spec.Versions[0].Name, Resource: "foos"}
   949  
   950  	testcases := []struct {
   951  		name string
   952  		want func(*testing.T)
   953  	}{
   954  		{
   955  			name: "list, get, patch, and delete via metadata client",
   956  			want: func(t *testing.T) {
   957  				ns := "metadata-builtin"
   958  				namespace := framework.CreateNamespaceOrDie(clientset, ns, t)
   959  				defer framework.DeleteNamespaceOrDie(clientset, namespace, t)
   960  
   961  				svc, err := clientset.CoreV1().Services(ns).Create(ctx, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-1", Annotations: map[string]string{"foo": "bar"}}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
   962  				if err != nil {
   963  					t.Fatalf("unable to create service: %v", err)
   964  				}
   965  
   966  				cfg := metadata.ConfigFor(kubeConfig)
   967  				wrapper := &callWrapper{}
   968  				cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
   969  					wrapper.nested = rt
   970  					return wrapper
   971  				})
   972  
   973  				client := metadata.NewForConfigOrDie(cfg).Resource(v1.SchemeGroupVersion.WithResource("services"))
   974  				items, err := client.Namespace(ns).List(ctx, metav1.ListOptions{})
   975  				if err != nil {
   976  					t.Fatal(err)
   977  				}
   978  				if items.ResourceVersion == "" {
   979  					t.Fatalf("unexpected items: %#v", items)
   980  				}
   981  				if len(items.Items) != 1 {
   982  					t.Fatalf("unexpected list: %#v", items)
   983  				}
   984  				if item := items.Items[0]; item.Name != "test-1" || item.UID != svc.UID || item.Annotations["foo"] != "bar" {
   985  					t.Fatalf("unexpected object: %#v", item)
   986  				}
   987  
   988  				if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
   989  					t.Fatalf("unexpected response: %#v", wrapper.resp)
   990  				}
   991  				wrapper.resp = nil
   992  
   993  				item, err := client.Namespace(ns).Get(ctx, "test-1", metav1.GetOptions{})
   994  				if err != nil {
   995  					t.Fatal(err)
   996  				}
   997  				if item.ResourceVersion == "" || item.UID != svc.UID || item.Annotations["foo"] != "bar" {
   998  					t.Fatalf("unexpected object: %#v", item)
   999  				}
  1000  				if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
  1001  					t.Fatalf("unexpected response: %#v", wrapper.resp)
  1002  				}
  1003  
  1004  				item, err = client.Namespace(ns).Patch(ctx, "test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"foo":"baz"}}}`), metav1.PatchOptions{})
  1005  				if err != nil {
  1006  					t.Fatal(err)
  1007  				}
  1008  				if item.Annotations["foo"] != "baz" {
  1009  					t.Fatalf("unexpected object: %#v", item)
  1010  				}
  1011  
  1012  				if err := client.Namespace(ns).Delete(ctx, "test-1", metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &item.UID}}); err != nil {
  1013  					t.Fatal(err)
  1014  				}
  1015  
  1016  				if _, err := client.Namespace(ns).Get(ctx, "test-1", metav1.GetOptions{}); !apierrors.IsNotFound(err) {
  1017  					t.Fatal(err)
  1018  				}
  1019  			},
  1020  		},
  1021  		{
  1022  			name: "list, get, patch, and delete via metadata client on a CRD",
  1023  			want: func(t *testing.T) {
  1024  				ns := "metadata-crd"
  1025  				crclient := dynamicClient.Resource(crdGVR).Namespace(ns)
  1026  				cr, err := crclient.Create(ctx, &unstructured.Unstructured{
  1027  					Object: map[string]interface{}{
  1028  						"apiVersion": "cr.bar.com/v1",
  1029  						"kind":       "Foo",
  1030  						"spec":       map[string]interface{}{"field": 1},
  1031  						"metadata": map[string]interface{}{
  1032  							"name": "test-1",
  1033  							"annotations": map[string]interface{}{
  1034  								"foo": "bar",
  1035  							},
  1036  						},
  1037  					},
  1038  				}, metav1.CreateOptions{})
  1039  				if err != nil {
  1040  					t.Fatalf("unable to create cr: %v", err)
  1041  				}
  1042  
  1043  				cfg := metadata.ConfigFor(config)
  1044  				wrapper := &callWrapper{}
  1045  				cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
  1046  					wrapper.nested = rt
  1047  					return wrapper
  1048  				})
  1049  
  1050  				client := metadata.NewForConfigOrDie(cfg).Resource(crdGVR)
  1051  				items, err := client.Namespace(ns).List(ctx, metav1.ListOptions{})
  1052  				if err != nil {
  1053  					t.Fatal(err)
  1054  				}
  1055  				if items.ResourceVersion == "" {
  1056  					t.Fatalf("unexpected items: %#v", items)
  1057  				}
  1058  				if len(items.Items) != 1 {
  1059  					t.Fatalf("unexpected list: %#v", items)
  1060  				}
  1061  				if item := items.Items[0]; item.Name != "test-1" || item.UID != cr.GetUID() || item.Annotations["foo"] != "bar" {
  1062  					t.Fatalf("unexpected object: %#v", item)
  1063  				}
  1064  
  1065  				if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
  1066  					t.Fatalf("unexpected response: %#v", wrapper.resp)
  1067  				}
  1068  				wrapper.resp = nil
  1069  
  1070  				item, err := client.Namespace(ns).Get(ctx, "test-1", metav1.GetOptions{})
  1071  				if err != nil {
  1072  					t.Fatal(err)
  1073  				}
  1074  				if item.ResourceVersion == "" || item.UID != cr.GetUID() || item.Annotations["foo"] != "bar" {
  1075  					t.Fatalf("unexpected object: %#v", item)
  1076  				}
  1077  				if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
  1078  					t.Fatalf("unexpected response: %#v", wrapper.resp)
  1079  				}
  1080  
  1081  				item, err = client.Namespace(ns).Patch(ctx, "test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"foo":"baz"}}}`), metav1.PatchOptions{})
  1082  				if err != nil {
  1083  					t.Fatal(err)
  1084  				}
  1085  				if item.Annotations["foo"] != "baz" {
  1086  					t.Fatalf("unexpected object: %#v", item)
  1087  				}
  1088  
  1089  				if err := client.Namespace(ns).Delete(ctx, "test-1", metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &item.UID}}); err != nil {
  1090  					t.Fatal(err)
  1091  				}
  1092  				if _, err := client.Namespace(ns).Get(ctx, "test-1", metav1.GetOptions{}); !apierrors.IsNotFound(err) {
  1093  					t.Fatal(err)
  1094  				}
  1095  			},
  1096  		},
  1097  		{
  1098  			name: "watch via metadata client",
  1099  			want: func(t *testing.T) {
  1100  				ns := "metadata-watch"
  1101  				namespace := framework.CreateNamespaceOrDie(clientset, ns, t)
  1102  				defer framework.DeleteNamespaceOrDie(clientset, namespace, t)
  1103  
  1104  				svc, err := clientset.CoreV1().Services(ns).Create(ctx, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-2", Annotations: map[string]string{"foo": "bar"}}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  1105  				if err != nil {
  1106  					t.Fatalf("unable to create service: %v", err)
  1107  				}
  1108  				if _, err := clientset.CoreV1().Services(ns).Patch(ctx, "test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1109  					t.Fatalf("unable to patch cr: %v", err)
  1110  				}
  1111  
  1112  				cfg := metadata.ConfigFor(kubeConfig)
  1113  				wrapper := &callWrapper{}
  1114  				cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
  1115  					wrapper.nested = rt
  1116  					return wrapper
  1117  				})
  1118  
  1119  				client := metadata.NewForConfigOrDie(cfg).Resource(v1.SchemeGroupVersion.WithResource("services"))
  1120  				w, err := client.Namespace(ns).Watch(ctx, metav1.ListOptions{ResourceVersion: svc.ResourceVersion, Watch: true})
  1121  				if err != nil {
  1122  					t.Fatal(err)
  1123  				}
  1124  				defer w.Stop()
  1125  				var r watch.Event
  1126  				select {
  1127  				case evt, ok := <-w.ResultChan():
  1128  					if !ok {
  1129  						t.Fatal("watch closed")
  1130  					}
  1131  					r = evt
  1132  				case <-time.After(5 * time.Second):
  1133  					t.Fatal("no watch event in 5 seconds, bug")
  1134  				}
  1135  				if r.Type != watch.Modified {
  1136  					t.Fatalf("unexpected watch: %#v", r)
  1137  				}
  1138  				item, ok := r.Object.(*metav1.PartialObjectMetadata)
  1139  				if !ok {
  1140  					t.Fatalf("unexpected object: %T", item)
  1141  				}
  1142  				if item.ResourceVersion == "" || item.Name != "test-2" || item.UID != svc.UID || item.Annotations["test"] != "1" {
  1143  					t.Fatalf("unexpected object: %#v", item)
  1144  				}
  1145  
  1146  				if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf;stream=watch" {
  1147  					t.Fatalf("unexpected response: %#v", wrapper.resp)
  1148  				}
  1149  			},
  1150  		},
  1151  
  1152  		{
  1153  			name: "watch via metadata client on a CRD",
  1154  			want: func(t *testing.T) {
  1155  				ns := "metadata-watch-crd"
  1156  				crclient := dynamicClient.Resource(crdGVR).Namespace(ns)
  1157  				cr, err := crclient.Create(ctx, &unstructured.Unstructured{
  1158  					Object: map[string]interface{}{
  1159  						"apiVersion": "cr.bar.com/v1",
  1160  						"kind":       "Foo",
  1161  						"spec":       map[string]interface{}{"field": 1},
  1162  						"metadata": map[string]interface{}{
  1163  							"name": "test-2",
  1164  							"annotations": map[string]interface{}{
  1165  								"foo": "bar",
  1166  							},
  1167  						},
  1168  					},
  1169  				}, metav1.CreateOptions{})
  1170  				if err != nil {
  1171  					t.Fatalf("unable to create cr: %v", err)
  1172  				}
  1173  
  1174  				cfg := metadata.ConfigFor(config)
  1175  				client := metadata.NewForConfigOrDie(cfg).Resource(crdGVR)
  1176  
  1177  				patched, err := client.Namespace(ns).Patch(ctx, "test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{})
  1178  				if err != nil {
  1179  					t.Fatal(err)
  1180  				}
  1181  				if patched.GetResourceVersion() == cr.GetResourceVersion() {
  1182  					t.Fatalf("Patch did not modify object: %#v", patched)
  1183  				}
  1184  
  1185  				wrapper := &callWrapper{}
  1186  				cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
  1187  					wrapper.nested = rt
  1188  					return wrapper
  1189  				})
  1190  				client = metadata.NewForConfigOrDie(cfg).Resource(crdGVR)
  1191  
  1192  				w, err := client.Namespace(ns).Watch(ctx, metav1.ListOptions{ResourceVersion: cr.GetResourceVersion(), Watch: true})
  1193  				if err != nil {
  1194  					t.Fatal(err)
  1195  				}
  1196  				defer w.Stop()
  1197  				var r watch.Event
  1198  				select {
  1199  				case evt, ok := <-w.ResultChan():
  1200  					if !ok {
  1201  						t.Fatal("watch closed")
  1202  					}
  1203  					r = evt
  1204  				case <-time.After(5 * time.Second):
  1205  					t.Fatal("no watch event in 5 seconds, bug")
  1206  				}
  1207  				if r.Type != watch.Modified {
  1208  					t.Fatalf("unexpected watch: %#v", r)
  1209  				}
  1210  				item, ok := r.Object.(*metav1.PartialObjectMetadata)
  1211  				if !ok {
  1212  					t.Fatalf("unexpected object: %T", item)
  1213  				}
  1214  				if item.ResourceVersion == "" || item.Name != "test-2" || item.UID != cr.GetUID() || item.Annotations["test"] != "1" {
  1215  					t.Fatalf("unexpected object: %#v", item)
  1216  				}
  1217  
  1218  				if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf;stream=watch" {
  1219  					t.Fatalf("unexpected response: %#v", wrapper.resp)
  1220  				}
  1221  			},
  1222  		},
  1223  	}
  1224  
  1225  	for i := range testcases {
  1226  		tc := testcases[i]
  1227  		t.Run(tc.name, func(t *testing.T) {
  1228  			tc.want(t)
  1229  		})
  1230  	}
  1231  }
  1232  
  1233  func TestAPICRDProtobuf(t *testing.T) {
  1234  	testNamespace := "test-api-crd-protobuf"
  1235  	tearDown, config, _, err := fixtures.StartDefaultServer(t)
  1236  	if err != nil {
  1237  		t.Fatal(err)
  1238  	}
  1239  	defer tearDown()
  1240  
  1241  	ctx, _, kubeConfig, tearDownFn := setup(t)
  1242  	defer tearDownFn()
  1243  
  1244  	apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
  1245  	if err != nil {
  1246  		t.Fatal(err)
  1247  	}
  1248  
  1249  	dynamicClient, err := dynamic.NewForConfig(config)
  1250  	if err != nil {
  1251  		t.Fatal(err)
  1252  	}
  1253  
  1254  	fooCRD := &apiextensionsv1.CustomResourceDefinition{
  1255  		ObjectMeta: metav1.ObjectMeta{
  1256  			Name: "foos.cr.bar.com",
  1257  		},
  1258  		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  1259  			Group: "cr.bar.com",
  1260  			Scope: apiextensionsv1.NamespaceScoped,
  1261  			Names: apiextensionsv1.CustomResourceDefinitionNames{
  1262  				Plural: "foos",
  1263  				Kind:   "Foo",
  1264  			},
  1265  			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  1266  				{
  1267  					Name:         "v1",
  1268  					Served:       true,
  1269  					Storage:      true,
  1270  					Schema:       fixtures.AllowAllSchema(),
  1271  					Subresources: &apiextensionsv1.CustomResourceSubresources{Status: &apiextensionsv1.CustomResourceSubresourceStatus{}},
  1272  				},
  1273  			},
  1274  		},
  1275  	}
  1276  	fooCRD, err = fixtures.CreateNewV1CustomResourceDefinition(fooCRD, apiExtensionClient, dynamicClient)
  1277  	if err != nil {
  1278  		t.Fatal(err)
  1279  	}
  1280  	crdGVR := schema.GroupVersionResource{Group: fooCRD.Spec.Group, Version: fooCRD.Spec.Versions[0].Name, Resource: "foos"}
  1281  	crclient := dynamicClient.Resource(crdGVR).Namespace(testNamespace)
  1282  
  1283  	testcases := []struct {
  1284  		name        string
  1285  		accept      string
  1286  		subresource string
  1287  		object      func(*testing.T) (metav1.Object, string, string)
  1288  		wantErr     func(*testing.T, error)
  1289  		wantBody    func(*testing.T, io.Reader)
  1290  	}{
  1291  		{
  1292  			name:   "server returns 406 when asking for protobuf for CRDs, which dynamic client does not support",
  1293  			accept: "application/vnd.kubernetes.protobuf",
  1294  			object: func(t *testing.T) (metav1.Object, string, string) {
  1295  				cr, err := crclient.Create(ctx, &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-1"}}}, metav1.CreateOptions{})
  1296  				if err != nil {
  1297  					t.Fatalf("unable to create cr: %v", err)
  1298  				}
  1299  				if _, err := crclient.Patch(ctx, "test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1300  					t.Fatalf("unable to patch cr: %v", err)
  1301  				}
  1302  				return cr, crdGVR.Group, "foos"
  1303  			},
  1304  			wantErr: func(t *testing.T, err error) {
  1305  				if !apierrors.IsNotAcceptable(err) {
  1306  					t.Fatal(err)
  1307  				}
  1308  				status := err.(apierrors.APIStatus).Status()
  1309  				data, _ := json.MarshalIndent(status, "", "  ")
  1310  				// because the dynamic client only has a json serializer, the client processing of the error cannot
  1311  				// turn the response into something meaningful, so we verify that fallback handling works correctly
  1312  				if !apierrors.IsUnexpectedServerError(err) {
  1313  					t.Fatal(string(data))
  1314  				}
  1315  				if status.Message != "the server was unable to respond with a content type that the client supports (get foos.cr.bar.com test-1)" {
  1316  					t.Fatal(string(data))
  1317  				}
  1318  			},
  1319  		},
  1320  		{
  1321  			name:   "server returns JSON when asking for protobuf and json for CRDs",
  1322  			accept: "application/vnd.kubernetes.protobuf,application/json",
  1323  			object: func(t *testing.T) (metav1.Object, string, string) {
  1324  				cr, err := crclient.Create(ctx, &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{})
  1325  				if err != nil {
  1326  					t.Fatalf("unable to create cr: %v", err)
  1327  				}
  1328  				if _, err := crclient.Patch(ctx, "test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1329  					t.Fatalf("unable to patch cr: %v", err)
  1330  				}
  1331  				return cr, crdGVR.Group, "foos"
  1332  			},
  1333  			wantBody: func(t *testing.T, w io.Reader) {
  1334  				obj := &unstructured.Unstructured{}
  1335  				if err := json.NewDecoder(w).Decode(obj); err != nil {
  1336  					t.Fatal(err)
  1337  				}
  1338  				v, ok, err := unstructured.NestedInt64(obj.UnstructuredContent(), "spec", "field")
  1339  				if !ok || err != nil {
  1340  					data, _ := json.MarshalIndent(obj.UnstructuredContent(), "", "  ")
  1341  					t.Fatalf("err=%v ok=%t json=%s", err, ok, string(data))
  1342  				}
  1343  				if v != 1 {
  1344  					t.Fatalf("unexpected body: %#v", obj.UnstructuredContent())
  1345  				}
  1346  			},
  1347  		},
  1348  		{
  1349  			name:        "server returns 406 when asking for protobuf for CRDs status, which dynamic client does not support",
  1350  			accept:      "application/vnd.kubernetes.protobuf",
  1351  			subresource: "status",
  1352  			object: func(t *testing.T) (metav1.Object, string, string) {
  1353  				cr, err := crclient.Create(ctx, &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-3"}}}, metav1.CreateOptions{})
  1354  				if err != nil {
  1355  					t.Fatalf("unable to create cr: %v", err)
  1356  				}
  1357  				if _, err := crclient.Patch(ctx, "test-3", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"3"}}}`), metav1.PatchOptions{}); err != nil {
  1358  					t.Fatalf("unable to patch cr: %v", err)
  1359  				}
  1360  				return cr, crdGVR.Group, "foos"
  1361  			},
  1362  			wantErr: func(t *testing.T, err error) {
  1363  				if !apierrors.IsNotAcceptable(err) {
  1364  					t.Fatal(err)
  1365  				}
  1366  				status := err.(apierrors.APIStatus).Status()
  1367  				data, _ := json.MarshalIndent(status, "", "  ")
  1368  				// because the dynamic client only has a json serializer, the client processing of the error cannot
  1369  				// turn the response into something meaningful, so we verify that fallback handling works correctly
  1370  				if !apierrors.IsUnexpectedServerError(err) {
  1371  					t.Fatal(string(data))
  1372  				}
  1373  				if status.Message != "the server was unable to respond with a content type that the client supports (get foos.cr.bar.com test-3)" {
  1374  					t.Fatal(string(data))
  1375  				}
  1376  			},
  1377  		},
  1378  		{
  1379  			name:        "server returns JSON when asking for protobuf and json for CRDs status",
  1380  			accept:      "application/vnd.kubernetes.protobuf,application/json",
  1381  			subresource: "status",
  1382  			object: func(t *testing.T) (metav1.Object, string, string) {
  1383  				cr, err := crclient.Create(ctx, &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{})
  1384  				if err != nil {
  1385  					t.Fatalf("unable to create cr: %v", err)
  1386  				}
  1387  				if _, err := crclient.Patch(ctx, "test-4", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"4"}}}`), metav1.PatchOptions{}); err != nil {
  1388  					t.Fatalf("unable to patch cr: %v", err)
  1389  				}
  1390  				return cr, crdGVR.Group, "foos"
  1391  			},
  1392  			wantBody: func(t *testing.T, w io.Reader) {
  1393  				obj := &unstructured.Unstructured{}
  1394  				if err := json.NewDecoder(w).Decode(obj); err != nil {
  1395  					t.Fatal(err)
  1396  				}
  1397  				v, ok, err := unstructured.NestedInt64(obj.UnstructuredContent(), "spec", "field")
  1398  				if !ok || err != nil {
  1399  					data, _ := json.MarshalIndent(obj.UnstructuredContent(), "", "  ")
  1400  					t.Fatalf("err=%v ok=%t json=%s", err, ok, string(data))
  1401  				}
  1402  				if v != 1 {
  1403  					t.Fatalf("unexpected body: %#v", obj.UnstructuredContent())
  1404  				}
  1405  			},
  1406  		},
  1407  	}
  1408  
  1409  	for i := range testcases {
  1410  		tc := testcases[i]
  1411  		t.Run(tc.name, func(t *testing.T) {
  1412  			obj, group, resource := tc.object(t)
  1413  
  1414  			cfg := dynamic.ConfigFor(config)
  1415  			if len(group) == 0 {
  1416  				cfg = dynamic.ConfigFor(kubeConfig)
  1417  				cfg.APIPath = "/api"
  1418  			} else {
  1419  				cfg.APIPath = "/apis"
  1420  			}
  1421  			cfg.GroupVersion = &schema.GroupVersion{Group: group, Version: "v1"}
  1422  			client, err := restclient.RESTClientFor(cfg)
  1423  			if err != nil {
  1424  				t.Fatal(err)
  1425  			}
  1426  
  1427  			w, err := client.Get().
  1428  				Resource(resource).NamespaceIfScoped(obj.GetNamespace(), len(obj.GetNamespace()) > 0).Name(obj.GetName()).SubResource(tc.subresource).
  1429  				SetHeader("Accept", tc.accept).
  1430  				Stream(ctx)
  1431  			if (tc.wantErr != nil) != (err != nil) {
  1432  				t.Fatalf("unexpected error: %v", err)
  1433  			}
  1434  			if tc.wantErr != nil {
  1435  				tc.wantErr(t, err)
  1436  				return
  1437  			}
  1438  			if err != nil {
  1439  				t.Fatal(err)
  1440  			}
  1441  			defer w.Close()
  1442  			tc.wantBody(t, w)
  1443  		})
  1444  	}
  1445  }
  1446  
  1447  func TestGetSubresourcesAsTables(t *testing.T) {
  1448  	testNamespace := "test-transform"
  1449  	tearDown, config, _, err := fixtures.StartDefaultServer(t)
  1450  	if err != nil {
  1451  		t.Fatal(err)
  1452  	}
  1453  	defer tearDown()
  1454  
  1455  	ctx, clientset, kubeConfig, tearDownFn := setup(t)
  1456  	defer tearDownFn()
  1457  
  1458  	ns := framework.CreateNamespaceOrDie(clientset, testNamespace, t)
  1459  	defer framework.DeleteNamespaceOrDie(clientset, ns, t)
  1460  
  1461  	apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
  1462  	if err != nil {
  1463  		t.Fatal(err)
  1464  	}
  1465  
  1466  	dynamicClient, err := dynamic.NewForConfig(config)
  1467  	if err != nil {
  1468  		t.Fatal(err)
  1469  	}
  1470  
  1471  	fooWithSubresourceCRD := &apiextensionsv1.CustomResourceDefinition{
  1472  		ObjectMeta: metav1.ObjectMeta{
  1473  			Name: "foosubs.cr.bar.com",
  1474  		},
  1475  		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  1476  			Group: "cr.bar.com",
  1477  			Scope: apiextensionsv1.NamespaceScoped,
  1478  			Names: apiextensionsv1.CustomResourceDefinitionNames{
  1479  				Plural: "foosubs",
  1480  				Kind:   "FooSub",
  1481  			},
  1482  			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  1483  				{
  1484  					Name:    "v1",
  1485  					Served:  true,
  1486  					Storage: true,
  1487  					Schema: &apiextensionsv1.CustomResourceValidation{
  1488  						OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1489  							Type: "object",
  1490  							Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1491  								"spec": {
  1492  									Type: "object",
  1493  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1494  										"replicas": {
  1495  											Type: "integer",
  1496  										},
  1497  									},
  1498  								},
  1499  								"status": {
  1500  									Type: "object",
  1501  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1502  										"replicas": {
  1503  											Type: "integer",
  1504  										},
  1505  									},
  1506  								},
  1507  							},
  1508  						},
  1509  					},
  1510  					Subresources: &apiextensionsv1.CustomResourceSubresources{
  1511  						Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
  1512  						Scale: &apiextensionsv1.CustomResourceSubresourceScale{
  1513  							SpecReplicasPath:   ".spec.replicas",
  1514  							StatusReplicasPath: ".status.replicas",
  1515  						},
  1516  					},
  1517  				},
  1518  			},
  1519  		},
  1520  	}
  1521  
  1522  	fooWithSubresourceCRD, err = fixtures.CreateNewV1CustomResourceDefinition(fooWithSubresourceCRD, apiExtensionClient, dynamicClient)
  1523  	if err != nil {
  1524  		t.Fatal(err)
  1525  	}
  1526  	subresourcesCrdGVR := schema.GroupVersionResource{Group: fooWithSubresourceCRD.Spec.Group, Version: fooWithSubresourceCRD.Spec.Versions[0].Name, Resource: "foosubs"}
  1527  	subresourcesCrclient := dynamicClient.Resource(subresourcesCrdGVR).Namespace(testNamespace)
  1528  
  1529  	testcases := []struct {
  1530  		name        string
  1531  		accept      string
  1532  		object      func(*testing.T) (metav1.Object, string, string)
  1533  		subresource string
  1534  	}{
  1535  		{
  1536  			name:   "v1 verify status subresource returns a table for CRDs",
  1537  			accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
  1538  			object: func(t *testing.T) (metav1.Object, string, string) {
  1539  				cr, err := subresourcesCrclient.Create(ctx, &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "FooSub", "metadata": map[string]interface{}{"name": "test-1"}, "spec": map[string]interface{}{"replicas": 2}}}, metav1.CreateOptions{})
  1540  				if err != nil {
  1541  					t.Fatalf("unable to create cr: %v", err)
  1542  				}
  1543  				return cr, subresourcesCrdGVR.Group, "foosubs"
  1544  			},
  1545  			subresource: "status",
  1546  		},
  1547  		{
  1548  			name:   "v1 verify scale subresource returns a table for CRDs",
  1549  			accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
  1550  			object: func(t *testing.T) (metav1.Object, string, string) {
  1551  				cr, err := subresourcesCrclient.Create(ctx, &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "FooSub", "metadata": map[string]interface{}{"name": "test-2"}, "spec": map[string]interface{}{"replicas": 2}}}, metav1.CreateOptions{})
  1552  				if err != nil {
  1553  					t.Fatalf("unable to create cr: %v", err)
  1554  				}
  1555  				return cr, subresourcesCrdGVR.Group, "foosubs"
  1556  			},
  1557  			subresource: "scale",
  1558  		},
  1559  		{
  1560  			name:   "verify status subresource returns a table for replicationcontrollers",
  1561  			accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
  1562  			object: func(t *testing.T) (metav1.Object, string, string) {
  1563  				rc := &v1.ReplicationController{
  1564  					ObjectMeta: metav1.ObjectMeta{
  1565  						Name: "replicationcontroller-1",
  1566  					},
  1567  					Spec: v1.ReplicationControllerSpec{
  1568  						Replicas: int32Ptr(2),
  1569  						Selector: map[string]string{
  1570  							"label": "test-label",
  1571  						},
  1572  						Template: &v1.PodTemplateSpec{
  1573  							ObjectMeta: metav1.ObjectMeta{
  1574  								Labels: map[string]string{
  1575  									"label": "test-label",
  1576  								},
  1577  							},
  1578  							Spec: v1.PodSpec{
  1579  								Containers: []v1.Container{
  1580  									{Name: "test-name", Image: "nonexistant-image"},
  1581  								},
  1582  							},
  1583  						},
  1584  					},
  1585  				}
  1586  				rc, err := clientset.CoreV1().ReplicationControllers(testNamespace).Create(ctx, rc, metav1.CreateOptions{})
  1587  				if err != nil {
  1588  					t.Fatalf("unable to create replicationcontroller: %v", err)
  1589  				}
  1590  				return rc, "", "replicationcontrollers"
  1591  			},
  1592  			subresource: "status",
  1593  		},
  1594  		{
  1595  			name:   "verify scale subresource returns a table for replicationcontrollers",
  1596  			accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
  1597  			object: func(t *testing.T) (metav1.Object, string, string) {
  1598  				rc := &v1.ReplicationController{
  1599  					ObjectMeta: metav1.ObjectMeta{
  1600  						Name: "replicationcontroller-2",
  1601  					},
  1602  					Spec: v1.ReplicationControllerSpec{
  1603  						Replicas: int32Ptr(2),
  1604  						Selector: map[string]string{
  1605  							"label": "test-label",
  1606  						},
  1607  						Template: &v1.PodTemplateSpec{
  1608  							ObjectMeta: metav1.ObjectMeta{
  1609  								Labels: map[string]string{
  1610  									"label": "test-label",
  1611  								},
  1612  							},
  1613  							Spec: v1.PodSpec{
  1614  								Containers: []v1.Container{
  1615  									{Name: "test-name", Image: "nonexistant-image"},
  1616  								},
  1617  							},
  1618  						},
  1619  					},
  1620  				}
  1621  				rc, err := clientset.CoreV1().ReplicationControllers(testNamespace).Create(ctx, rc, metav1.CreateOptions{})
  1622  				if err != nil {
  1623  					t.Fatalf("unable to create replicationcontroller: %v", err)
  1624  				}
  1625  				return rc, "", "replicationcontrollers"
  1626  			},
  1627  			subresource: "scale",
  1628  		},
  1629  	}
  1630  
  1631  	for i := range testcases {
  1632  		tc := testcases[i]
  1633  		t.Run(tc.name, func(t *testing.T) {
  1634  			obj, group, resource := tc.object(t)
  1635  
  1636  			cfg := dynamic.ConfigFor(config)
  1637  			if len(group) == 0 {
  1638  				cfg = dynamic.ConfigFor(kubeConfig)
  1639  				cfg.APIPath = "/api"
  1640  			} else {
  1641  				cfg.APIPath = "/apis"
  1642  			}
  1643  			cfg.GroupVersion = &schema.GroupVersion{Group: group, Version: "v1"}
  1644  
  1645  			client, err := restclient.RESTClientFor(cfg)
  1646  			if err != nil {
  1647  				t.Fatal(err)
  1648  			}
  1649  
  1650  			res := client.Get().
  1651  				Resource(resource).NamespaceIfScoped(obj.GetNamespace(), len(obj.GetNamespace()) > 0).
  1652  				SetHeader("Accept", tc.accept).
  1653  				Name(obj.GetName()).
  1654  				SubResource(tc.subresource).
  1655  				Do(ctx)
  1656  
  1657  			resObj, err := res.Get()
  1658  			if err != nil {
  1659  				t.Fatalf("failed to retrieve object from response: %v", err)
  1660  			}
  1661  			actualKind := resObj.GetObjectKind().GroupVersionKind().Kind
  1662  			if actualKind != "Table" {
  1663  				t.Fatalf("Expected Kind 'Table', got '%v'", actualKind)
  1664  			}
  1665  		})
  1666  	}
  1667  }
  1668  
  1669  func TestTransform(t *testing.T) {
  1670  	testNamespace := "test-transform"
  1671  	tearDown, config, _, err := fixtures.StartDefaultServer(t)
  1672  	if err != nil {
  1673  		t.Fatal(err)
  1674  	}
  1675  	defer tearDown()
  1676  
  1677  	ctx, clientset, kubeConfig, tearDownFn := setup(t)
  1678  	defer tearDownFn()
  1679  
  1680  	ns := framework.CreateNamespaceOrDie(clientset, testNamespace, t)
  1681  	defer framework.DeleteNamespaceOrDie(clientset, ns, t)
  1682  
  1683  	apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
  1684  	if err != nil {
  1685  		t.Fatal(err)
  1686  	}
  1687  
  1688  	dynamicClient, err := dynamic.NewForConfig(config)
  1689  	if err != nil {
  1690  		t.Fatal(err)
  1691  	}
  1692  
  1693  	fooCRD := &apiextensionsv1.CustomResourceDefinition{
  1694  		ObjectMeta: metav1.ObjectMeta{
  1695  			Name: "foos.cr.bar.com",
  1696  		},
  1697  		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  1698  			Group: "cr.bar.com",
  1699  			Scope: apiextensionsv1.NamespaceScoped,
  1700  			Names: apiextensionsv1.CustomResourceDefinitionNames{
  1701  				Plural: "foos",
  1702  				Kind:   "Foo",
  1703  			},
  1704  			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  1705  				{
  1706  					Name:    "v1",
  1707  					Served:  true,
  1708  					Storage: true,
  1709  					Schema:  fixtures.AllowAllSchema(),
  1710  				},
  1711  			},
  1712  		},
  1713  	}
  1714  	fooCRD, err = fixtures.CreateNewV1CustomResourceDefinition(fooCRD, apiExtensionClient, dynamicClient)
  1715  	if err != nil {
  1716  		t.Fatal(err)
  1717  	}
  1718  	crdGVR := schema.GroupVersionResource{Group: fooCRD.Spec.Group, Version: fooCRD.Spec.Versions[0].Name, Resource: "foos"}
  1719  	crclient := dynamicClient.Resource(crdGVR).Namespace(testNamespace)
  1720  
  1721  	previousList, err := crclient.List(ctx, metav1.ListOptions{})
  1722  	if err != nil {
  1723  		t.Fatalf("failed to list CRs before test: %v", err)
  1724  	}
  1725  	previousRV := previousList.GetResourceVersion()
  1726  
  1727  	testcases := []struct {
  1728  		name          string
  1729  		accept        string
  1730  		includeObject metav1.IncludeObjectPolicy
  1731  		object        func(*testing.T) (metav1.Object, string, string)
  1732  		wantErr       func(*testing.T, error)
  1733  		wantBody      func(*testing.T, io.Reader)
  1734  	}{
  1735  		{
  1736  			name:   "v1beta1 verify columns on cluster scoped resources",
  1737  			accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
  1738  			object: func(t *testing.T) (metav1.Object, string, string) {
  1739  				return &metav1.ObjectMeta{Name: "default", Namespace: ""}, "", "namespaces"
  1740  			},
  1741  			wantBody: func(t *testing.T, w io.Reader) {
  1742  				expectTableWatchEvents(t, 1, 3, metav1.IncludeMetadata, json.NewDecoder(w))
  1743  			},
  1744  		},
  1745  		{
  1746  			name:   "v1beta1 verify columns on CRDs in json",
  1747  			accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
  1748  			object: func(t *testing.T) (metav1.Object, string, string) {
  1749  				cr, err := crclient.Create(ctx, &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-1"}}}, metav1.CreateOptions{})
  1750  				if err != nil {
  1751  					t.Fatalf("unable to create cr: %v", err)
  1752  				}
  1753  				if _, err := crclient.Patch(ctx, "test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1754  					t.Fatalf("unable to patch cr: %v", err)
  1755  				}
  1756  				return cr, crdGVR.Group, "foos"
  1757  			},
  1758  			wantBody: func(t *testing.T, w io.Reader) {
  1759  				expectTableWatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w))
  1760  			},
  1761  		},
  1762  		{
  1763  			name:   "v1beta1 verify columns on CRDs in json;stream=watch",
  1764  			accept: "application/json;stream=watch;as=Table;g=meta.k8s.io;v=v1beta1",
  1765  			object: func(t *testing.T) (metav1.Object, string, string) {
  1766  				cr, err := crclient.Create(ctx, &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-2"}}}, metav1.CreateOptions{})
  1767  				if err != nil {
  1768  					t.Fatalf("unable to create cr: %v", err)
  1769  				}
  1770  				if _, err := crclient.Patch(ctx, "test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1771  					t.Fatalf("unable to patch cr: %v", err)
  1772  				}
  1773  				return cr, crdGVR.Group, "foos"
  1774  			},
  1775  			wantBody: func(t *testing.T, w io.Reader) {
  1776  				expectTableWatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w))
  1777  			},
  1778  		},
  1779  		{
  1780  			name:   "v1beta1 verify columns on CRDs in yaml",
  1781  			accept: "application/yaml;as=Table;g=meta.k8s.io;v=v1beta1",
  1782  			object: func(t *testing.T) (metav1.Object, string, string) {
  1783  				cr, err := crclient.Create(ctx, &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-3"}}}, metav1.CreateOptions{})
  1784  				if err != nil {
  1785  					t.Fatalf("unable to create cr: %v", err)
  1786  				}
  1787  				if _, err := crclient.Patch(ctx, "test-3", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1788  					t.Fatalf("unable to patch cr: %v", err)
  1789  				}
  1790  				return cr, crdGVR.Group, "foos"
  1791  			},
  1792  			wantErr: func(t *testing.T, err error) {
  1793  				if !apierrors.IsNotAcceptable(err) {
  1794  					t.Fatal(err)
  1795  				}
  1796  				// TODO: this should be a more specific error
  1797  				if err.Error() != "only the following media types are accepted: application/json;stream=watch, application/vnd.kubernetes.protobuf;stream=watch" {
  1798  					t.Fatal(err)
  1799  				}
  1800  			},
  1801  		},
  1802  		{
  1803  			name:   "v1beta1 verify columns on services",
  1804  			accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
  1805  			object: func(t *testing.T) (metav1.Object, string, string) {
  1806  				svc, err := clientset.CoreV1().Services(testNamespace).Create(ctx, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-1"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  1807  				if err != nil {
  1808  					t.Fatalf("unable to create service: %v", err)
  1809  				}
  1810  				if _, err := clientset.CoreV1().Services(testNamespace).Patch(ctx, svc.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1811  					t.Fatalf("unable to update service: %v", err)
  1812  				}
  1813  				return svc, "", "services"
  1814  			},
  1815  			wantBody: func(t *testing.T, w io.Reader) {
  1816  				expectTableWatchEvents(t, 2, 7, metav1.IncludeMetadata, json.NewDecoder(w))
  1817  			},
  1818  		},
  1819  		{
  1820  			name:          "v1beta1 verify columns on services with no object",
  1821  			accept:        "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
  1822  			includeObject: metav1.IncludeNone,
  1823  			object: func(t *testing.T) (metav1.Object, string, string) {
  1824  				obj, err := clientset.CoreV1().Services(testNamespace).Create(ctx, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-2"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  1825  				if err != nil {
  1826  					t.Fatalf("unable to create object: %v", err)
  1827  				}
  1828  				if _, err := clientset.CoreV1().Services(testNamespace).Patch(ctx, obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1829  					t.Fatalf("unable to update object: %v", err)
  1830  				}
  1831  				return obj, "", "services"
  1832  			},
  1833  			wantBody: func(t *testing.T, w io.Reader) {
  1834  				expectTableWatchEvents(t, 2, 7, metav1.IncludeNone, json.NewDecoder(w))
  1835  			},
  1836  		},
  1837  		{
  1838  			name:          "v1beta1 verify columns on services with full object",
  1839  			accept:        "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
  1840  			includeObject: metav1.IncludeObject,
  1841  			object: func(t *testing.T) (metav1.Object, string, string) {
  1842  				obj, err := clientset.CoreV1().Services(testNamespace).Create(ctx, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-3"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  1843  				if err != nil {
  1844  					t.Fatalf("unable to create object: %v", err)
  1845  				}
  1846  				if _, err := clientset.CoreV1().Services(testNamespace).Patch(ctx, obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1847  					t.Fatalf("unable to update object: %v", err)
  1848  				}
  1849  				return obj, "", "services"
  1850  			},
  1851  			wantBody: func(t *testing.T, w io.Reader) {
  1852  				objects := expectTableWatchEvents(t, 2, 7, metav1.IncludeObject, json.NewDecoder(w))
  1853  				var svc v1.Service
  1854  				if err := json.Unmarshal(objects[1], &svc); err != nil {
  1855  					t.Fatal(err)
  1856  				}
  1857  				if svc.Annotations["test"] != "1" || svc.Spec.Ports[0].Port != 1000 {
  1858  					t.Fatalf("unexpected object: %#v", svc)
  1859  				}
  1860  			},
  1861  		},
  1862  		{
  1863  			name:   "v1beta1 verify partial metadata object on config maps",
  1864  			accept: "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1beta1",
  1865  			object: func(t *testing.T) (metav1.Object, string, string) {
  1866  				obj, err := clientset.CoreV1().ConfigMaps(testNamespace).Create(ctx, &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-1", Annotations: map[string]string{"test": "0"}}}, metav1.CreateOptions{})
  1867  				if err != nil {
  1868  					t.Fatalf("unable to create object: %v", err)
  1869  				}
  1870  				if _, err := clientset.CoreV1().ConfigMaps(testNamespace).Patch(ctx, obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1871  					t.Fatalf("unable to update object: %v", err)
  1872  				}
  1873  				return obj, "", "configmaps"
  1874  			},
  1875  			wantBody: func(t *testing.T, w io.Reader) {
  1876  				expectPartialObjectMetaEvents(t, json.NewDecoder(w), "0", "1")
  1877  			},
  1878  		},
  1879  		{
  1880  			name:   "v1beta1 verify partial metadata object on config maps in protobuf",
  1881  			accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1beta1",
  1882  			object: func(t *testing.T) (metav1.Object, string, string) {
  1883  				obj, err := clientset.CoreV1().ConfigMaps(testNamespace).Create(ctx, &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-2", Annotations: map[string]string{"test": "0"}}}, metav1.CreateOptions{})
  1884  				if err != nil {
  1885  					t.Fatalf("unable to create object: %v", err)
  1886  				}
  1887  				if _, err := clientset.CoreV1().ConfigMaps(testNamespace).Patch(ctx, obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1888  					t.Fatalf("unable to update object: %v", err)
  1889  				}
  1890  				return obj, "", "configmaps"
  1891  			},
  1892  			wantBody: func(t *testing.T, w io.Reader) {
  1893  				expectPartialObjectMetaEventsProtobuf(t, w, "0", "1")
  1894  			},
  1895  		},
  1896  		{
  1897  			name:   "v1beta1 verify partial metadata object on CRDs in protobuf",
  1898  			accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1beta1",
  1899  			object: func(t *testing.T) (metav1.Object, string, string) {
  1900  				cr, err := crclient.Create(ctx, &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{})
  1901  				if err != nil {
  1902  					t.Fatalf("unable to create cr: %v", err)
  1903  				}
  1904  				if _, err := crclient.Patch(ctx, "test-4", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  1905  					t.Fatalf("unable to patch cr: %v", err)
  1906  				}
  1907  				return cr, crdGVR.Group, "foos"
  1908  			},
  1909  			wantBody: func(t *testing.T, w io.Reader) {
  1910  				expectPartialObjectMetaEventsProtobuf(t, w, "0", "1")
  1911  			},
  1912  		},
  1913  		{
  1914  			name:   "v1beta1 verify error on unsupported mimetype protobuf for table conversion",
  1915  			accept: "application/vnd.kubernetes.protobuf;as=Table;g=meta.k8s.io;v=v1beta1",
  1916  			object: func(t *testing.T) (metav1.Object, string, string) {
  1917  				return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1918  			},
  1919  			wantErr: func(t *testing.T, err error) {
  1920  				if !apierrors.IsNotAcceptable(err) {
  1921  					t.Fatal(err)
  1922  				}
  1923  				// TODO: this should be a more specific error
  1924  				if err.Error() != "only the following media types are accepted: application/json, application/yaml, application/vnd.kubernetes.protobuf" {
  1925  					t.Fatal(err)
  1926  				}
  1927  			},
  1928  		},
  1929  		{
  1930  			name:   "verify error on invalid mimetype - bad version",
  1931  			accept: "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1alpha1",
  1932  			object: func(t *testing.T) (metav1.Object, string, string) {
  1933  				return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1934  			},
  1935  			wantErr: func(t *testing.T, err error) {
  1936  				if !apierrors.IsNotAcceptable(err) {
  1937  					t.Fatal(err)
  1938  				}
  1939  			},
  1940  		},
  1941  		{
  1942  			name:   "v1beta1 verify error on invalid mimetype - bad group",
  1943  			accept: "application/json;as=PartialObjectMetadata;g=k8s.io;v=v1beta1",
  1944  			object: func(t *testing.T) (metav1.Object, string, string) {
  1945  				return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1946  			},
  1947  			wantErr: func(t *testing.T, err error) {
  1948  				if !apierrors.IsNotAcceptable(err) {
  1949  					t.Fatal(err)
  1950  				}
  1951  			},
  1952  		},
  1953  		{
  1954  			name:   "v1beta1 verify error on invalid mimetype - bad kind",
  1955  			accept: "application/json;as=PartialObject;g=meta.k8s.io;v=v1beta1",
  1956  			object: func(t *testing.T) (metav1.Object, string, string) {
  1957  				return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1958  			},
  1959  			wantErr: func(t *testing.T, err error) {
  1960  				if !apierrors.IsNotAcceptable(err) {
  1961  					t.Fatal(err)
  1962  				}
  1963  			},
  1964  		},
  1965  		{
  1966  			name:   "v1beta1 verify error on invalid mimetype - missing kind",
  1967  			accept: "application/json;g=meta.k8s.io;v=v1beta1",
  1968  			object: func(t *testing.T) (metav1.Object, string, string) {
  1969  				return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1970  			},
  1971  			wantErr: func(t *testing.T, err error) {
  1972  				if !apierrors.IsNotAcceptable(err) {
  1973  					t.Fatal(err)
  1974  				}
  1975  			},
  1976  		},
  1977  		{
  1978  			name:          "v1beta1 verify error on invalid transform parameter",
  1979  			accept:        "application/json;as=Table;g=meta.k8s.io;v=v1beta1",
  1980  			includeObject: metav1.IncludeObjectPolicy("unrecognized"),
  1981  			object: func(t *testing.T) (metav1.Object, string, string) {
  1982  				return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  1983  			},
  1984  			wantErr: func(t *testing.T, err error) {
  1985  				if !apierrors.IsBadRequest(err) || !strings.Contains(err.Error(), `Invalid value: "unrecognized": must be 'Metadata', 'Object', 'None', or empty`) {
  1986  					t.Fatal(err)
  1987  				}
  1988  			},
  1989  		},
  1990  
  1991  		{
  1992  			name:   "v1 verify columns on cluster scoped resources",
  1993  			accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
  1994  			object: func(t *testing.T) (metav1.Object, string, string) {
  1995  				return &metav1.ObjectMeta{Name: "default", Namespace: ""}, "", "namespaces"
  1996  			},
  1997  			wantBody: func(t *testing.T, w io.Reader) {
  1998  				expectTableV1WatchEvents(t, 1, 3, metav1.IncludeMetadata, json.NewDecoder(w))
  1999  			},
  2000  		},
  2001  		{
  2002  			name:   "v1 verify columns on CRDs in json",
  2003  			accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
  2004  			object: func(t *testing.T) (metav1.Object, string, string) {
  2005  				cr, err := crclient.Create(ctx, &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-5"}}}, metav1.CreateOptions{})
  2006  				if err != nil {
  2007  					t.Fatalf("unable to create cr: %v", err)
  2008  				}
  2009  				if _, err := crclient.Patch(ctx, "test-5", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  2010  					t.Fatalf("unable to patch cr: %v", err)
  2011  				}
  2012  				return cr, crdGVR.Group, "foos"
  2013  			},
  2014  			wantBody: func(t *testing.T, w io.Reader) {
  2015  				expectTableV1WatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w))
  2016  			},
  2017  		},
  2018  		{
  2019  			name:   "v1 verify columns on CRDs in json;stream=watch",
  2020  			accept: "application/json;stream=watch;as=Table;g=meta.k8s.io;v=v1",
  2021  			object: func(t *testing.T) (metav1.Object, string, string) {
  2022  				cr, err := crclient.Create(ctx, &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-6"}}}, metav1.CreateOptions{})
  2023  				if err != nil {
  2024  					t.Fatalf("unable to create cr: %v", err)
  2025  				}
  2026  				if _, err := crclient.Patch(ctx, "test-6", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  2027  					t.Fatalf("unable to patch cr: %v", err)
  2028  				}
  2029  				return cr, crdGVR.Group, "foos"
  2030  			},
  2031  			wantBody: func(t *testing.T, w io.Reader) {
  2032  				expectTableV1WatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w))
  2033  			},
  2034  		},
  2035  		{
  2036  			name:   "v1 verify columns on CRDs in yaml",
  2037  			accept: "application/yaml;as=Table;g=meta.k8s.io;v=v1",
  2038  			object: func(t *testing.T) (metav1.Object, string, string) {
  2039  				cr, err := crclient.Create(ctx, &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-7"}}}, metav1.CreateOptions{})
  2040  				if err != nil {
  2041  					t.Fatalf("unable to create cr: %v", err)
  2042  				}
  2043  				if _, err := crclient.Patch(ctx, "test-7", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  2044  					t.Fatalf("unable to patch cr: %v", err)
  2045  				}
  2046  				return cr, crdGVR.Group, "foos"
  2047  			},
  2048  			wantErr: func(t *testing.T, err error) {
  2049  				if !apierrors.IsNotAcceptable(err) {
  2050  					t.Fatal(err)
  2051  				}
  2052  				// TODO: this should be a more specific error
  2053  				if err.Error() != "only the following media types are accepted: application/json;stream=watch, application/vnd.kubernetes.protobuf;stream=watch" {
  2054  					t.Fatal(err)
  2055  				}
  2056  			},
  2057  		},
  2058  		{
  2059  			name:   "v1 verify columns on services",
  2060  			accept: "application/json;as=Table;g=meta.k8s.io;v=v1",
  2061  			object: func(t *testing.T) (metav1.Object, string, string) {
  2062  				svc, err := clientset.CoreV1().Services(testNamespace).Create(ctx, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-5"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  2063  				if err != nil {
  2064  					t.Fatalf("unable to create service: %v", err)
  2065  				}
  2066  				if _, err := clientset.CoreV1().Services(testNamespace).Patch(ctx, svc.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  2067  					t.Fatalf("unable to update service: %v", err)
  2068  				}
  2069  				return svc, "", "services"
  2070  			},
  2071  			wantBody: func(t *testing.T, w io.Reader) {
  2072  				expectTableV1WatchEvents(t, 2, 7, metav1.IncludeMetadata, json.NewDecoder(w))
  2073  			},
  2074  		},
  2075  		{
  2076  			name:          "v1 verify columns on services with no object",
  2077  			accept:        "application/json;as=Table;g=meta.k8s.io;v=v1",
  2078  			includeObject: metav1.IncludeNone,
  2079  			object: func(t *testing.T) (metav1.Object, string, string) {
  2080  				obj, err := clientset.CoreV1().Services(testNamespace).Create(ctx, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-6"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  2081  				if err != nil {
  2082  					t.Fatalf("unable to create object: %v", err)
  2083  				}
  2084  				if _, err := clientset.CoreV1().Services(testNamespace).Patch(ctx, obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  2085  					t.Fatalf("unable to update object: %v", err)
  2086  				}
  2087  				return obj, "", "services"
  2088  			},
  2089  			wantBody: func(t *testing.T, w io.Reader) {
  2090  				expectTableV1WatchEvents(t, 2, 7, metav1.IncludeNone, json.NewDecoder(w))
  2091  			},
  2092  		},
  2093  		{
  2094  			name:          "v1 verify columns on services with full object",
  2095  			accept:        "application/json;as=Table;g=meta.k8s.io;v=v1",
  2096  			includeObject: metav1.IncludeObject,
  2097  			object: func(t *testing.T) (metav1.Object, string, string) {
  2098  				obj, err := clientset.CoreV1().Services(testNamespace).Create(ctx, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-7"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}, metav1.CreateOptions{})
  2099  				if err != nil {
  2100  					t.Fatalf("unable to create object: %v", err)
  2101  				}
  2102  				if _, err := clientset.CoreV1().Services(testNamespace).Patch(ctx, obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  2103  					t.Fatalf("unable to update object: %v", err)
  2104  				}
  2105  				return obj, "", "services"
  2106  			},
  2107  			wantBody: func(t *testing.T, w io.Reader) {
  2108  				objects := expectTableV1WatchEvents(t, 2, 7, metav1.IncludeObject, json.NewDecoder(w))
  2109  				var svc v1.Service
  2110  				if err := json.Unmarshal(objects[1], &svc); err != nil {
  2111  					t.Fatal(err)
  2112  				}
  2113  				if svc.Annotations["test"] != "1" || svc.Spec.Ports[0].Port != 1000 {
  2114  					t.Fatalf("unexpected object: %#v", svc)
  2115  				}
  2116  			},
  2117  		},
  2118  		{
  2119  			name:   "v1 verify partial metadata object on config maps",
  2120  			accept: "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1",
  2121  			object: func(t *testing.T) (metav1.Object, string, string) {
  2122  				obj, err := clientset.CoreV1().ConfigMaps(testNamespace).Create(ctx, &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-3", Annotations: map[string]string{"test": "0"}}}, metav1.CreateOptions{})
  2123  				if err != nil {
  2124  					t.Fatalf("unable to create object: %v", err)
  2125  				}
  2126  				if _, err := clientset.CoreV1().ConfigMaps(testNamespace).Patch(ctx, obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  2127  					t.Fatalf("unable to update object: %v", err)
  2128  				}
  2129  				return obj, "", "configmaps"
  2130  			},
  2131  			wantBody: func(t *testing.T, w io.Reader) {
  2132  				expectPartialObjectMetaV1Events(t, json.NewDecoder(w), "0", "1")
  2133  			},
  2134  		},
  2135  		{
  2136  			name:   "v1 verify partial metadata object on config maps in protobuf",
  2137  			accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1",
  2138  			object: func(t *testing.T) (metav1.Object, string, string) {
  2139  				obj, err := clientset.CoreV1().ConfigMaps(testNamespace).Create(ctx, &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-4", Annotations: map[string]string{"test": "0"}}}, metav1.CreateOptions{})
  2140  				if err != nil {
  2141  					t.Fatalf("unable to create object: %v", err)
  2142  				}
  2143  				if _, err := clientset.CoreV1().ConfigMaps(testNamespace).Patch(ctx, obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  2144  					t.Fatalf("unable to update object: %v", err)
  2145  				}
  2146  				return obj, "", "configmaps"
  2147  			},
  2148  			wantBody: func(t *testing.T, w io.Reader) {
  2149  				expectPartialObjectMetaV1EventsProtobuf(t, w, "0", "1")
  2150  			},
  2151  		},
  2152  		{
  2153  			name:   "v1 verify partial metadata object on CRDs in protobuf",
  2154  			accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1",
  2155  			object: func(t *testing.T) (metav1.Object, string, string) {
  2156  				cr, err := crclient.Create(ctx, &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{})
  2157  				if err != nil {
  2158  					t.Fatalf("unable to create cr: %v", err)
  2159  				}
  2160  				if _, err := crclient.Patch(ctx, cr.GetName(), types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil {
  2161  					t.Fatalf("unable to patch cr: %v", err)
  2162  				}
  2163  				return cr, crdGVR.Group, "foos"
  2164  			},
  2165  			wantBody: func(t *testing.T, w io.Reader) {
  2166  				expectPartialObjectMetaV1EventsProtobuf(t, w, "0", "1")
  2167  			},
  2168  		},
  2169  		{
  2170  			name:   "v1 verify error on unsupported mimetype protobuf for table conversion",
  2171  			accept: "application/vnd.kubernetes.protobuf;as=Table;g=meta.k8s.io;v=v1",
  2172  			object: func(t *testing.T) (metav1.Object, string, string) {
  2173  				return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  2174  			},
  2175  			wantErr: func(t *testing.T, err error) {
  2176  				if !apierrors.IsNotAcceptable(err) {
  2177  					t.Fatal(err)
  2178  				}
  2179  				// TODO: this should be a more specific error
  2180  				if err.Error() != "only the following media types are accepted: application/json, application/yaml, application/vnd.kubernetes.protobuf" {
  2181  					t.Fatal(err)
  2182  				}
  2183  			},
  2184  		},
  2185  		{
  2186  			name:   "v1 verify error on invalid mimetype - bad group",
  2187  			accept: "application/json;as=PartialObjectMetadata;g=k8s.io;v=v1",
  2188  			object: func(t *testing.T) (metav1.Object, string, string) {
  2189  				return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  2190  			},
  2191  			wantErr: func(t *testing.T, err error) {
  2192  				if !apierrors.IsNotAcceptable(err) {
  2193  					t.Fatal(err)
  2194  				}
  2195  			},
  2196  		},
  2197  		{
  2198  			name:   "v1 verify error on invalid mimetype - bad kind",
  2199  			accept: "application/json;as=PartialObject;g=meta.k8s.io;v=v1",
  2200  			object: func(t *testing.T) (metav1.Object, string, string) {
  2201  				return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  2202  			},
  2203  			wantErr: func(t *testing.T, err error) {
  2204  				if !apierrors.IsNotAcceptable(err) {
  2205  					t.Fatal(err)
  2206  				}
  2207  			},
  2208  		},
  2209  		{
  2210  			name:   "v1 verify error on invalid mimetype - only meta kinds accepted",
  2211  			accept: "application/json;as=Service;g=;v=v1",
  2212  			object: func(t *testing.T) (metav1.Object, string, string) {
  2213  				return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  2214  			},
  2215  			wantErr: func(t *testing.T, err error) {
  2216  				if !apierrors.IsNotAcceptable(err) {
  2217  					t.Fatal(err)
  2218  				}
  2219  			},
  2220  		},
  2221  		{
  2222  			name:   "v1 verify error on invalid mimetype - missing kind",
  2223  			accept: "application/json;g=meta.k8s.io;v=v1",
  2224  			object: func(t *testing.T) (metav1.Object, string, string) {
  2225  				return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  2226  			},
  2227  			wantErr: func(t *testing.T, err error) {
  2228  				if !apierrors.IsNotAcceptable(err) {
  2229  					t.Fatal(err)
  2230  				}
  2231  			},
  2232  		},
  2233  		{
  2234  			name:          "v1 verify error on invalid transform parameter",
  2235  			accept:        "application/json;as=Table;g=meta.k8s.io;v=v1",
  2236  			includeObject: metav1.IncludeObjectPolicy("unrecognized"),
  2237  			object: func(t *testing.T) (metav1.Object, string, string) {
  2238  				return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services"
  2239  			},
  2240  			wantErr: func(t *testing.T, err error) {
  2241  				if !apierrors.IsBadRequest(err) || !strings.Contains(err.Error(), `Invalid value: "unrecognized": must be 'Metadata', 'Object', 'None', or empty`) {
  2242  					t.Fatal(err)
  2243  				}
  2244  			},
  2245  		},
  2246  	}
  2247  
  2248  	for i := range testcases {
  2249  		tc := testcases[i]
  2250  		t.Run(tc.name, func(t *testing.T) {
  2251  			obj, group, resource := tc.object(t)
  2252  
  2253  			cfg := dynamic.ConfigFor(config)
  2254  			if len(group) == 0 {
  2255  				cfg = dynamic.ConfigFor(kubeConfig)
  2256  				cfg.APIPath = "/api"
  2257  			} else {
  2258  				cfg.APIPath = "/apis"
  2259  			}
  2260  			cfg.GroupVersion = &schema.GroupVersion{Group: group, Version: "v1"}
  2261  
  2262  			client, err := restclient.RESTClientFor(cfg)
  2263  			if err != nil {
  2264  				t.Fatal(err)
  2265  			}
  2266  
  2267  			var rv string
  2268  			if obj.GetResourceVersion() == "" || obj.GetResourceVersion() == "0" {
  2269  				// no object was created in the preamble to the test, so get recent data
  2270  				rv = "0"
  2271  			} else {
  2272  				// we created an object, and need to list+watch from some time before the creation to see it
  2273  				rv = previousRV
  2274  			}
  2275  
  2276  			timeoutCtx, timeoutCancel := context.WithTimeout(ctx, wait.ForeverTestTimeout)
  2277  			t.Cleanup(func() {
  2278  				timeoutCancel()
  2279  			})
  2280  			w, err := client.Get().
  2281  				Resource(resource).NamespaceIfScoped(obj.GetNamespace(), len(obj.GetNamespace()) > 0).
  2282  				SetHeader("Accept", tc.accept).
  2283  				VersionedParams(&metav1.ListOptions{
  2284  					ResourceVersion: rv,
  2285  					Watch:           true,
  2286  					FieldSelector:   fields.OneTermEqualSelector("metadata.name", obj.GetName()).String(),
  2287  				}, metav1.ParameterCodec).
  2288  				Param("includeObject", string(tc.includeObject)).
  2289  				Stream(timeoutCtx)
  2290  			if (tc.wantErr != nil) != (err != nil) {
  2291  				t.Fatalf("unexpected error: %v", err)
  2292  			}
  2293  			if tc.wantErr != nil {
  2294  				tc.wantErr(t, err)
  2295  				return
  2296  			}
  2297  			if err != nil {
  2298  				t.Fatal(err)
  2299  			}
  2300  			defer w.Close()
  2301  			tc.wantBody(t, w)
  2302  		})
  2303  	}
  2304  }
  2305  
  2306  func TestWatchTransformCaching(t *testing.T) {
  2307  	ctx, clientSet, _, tearDownFn := setup(t)
  2308  	defer tearDownFn()
  2309  
  2310  	ns := framework.CreateNamespaceOrDie(clientSet, "watch-transform", t)
  2311  	defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
  2312  
  2313  	list, err := clientSet.CoreV1().ConfigMaps(ns.Name).List(ctx, metav1.ListOptions{})
  2314  	if err != nil {
  2315  		t.Fatalf("Failed to list objects: %v", err)
  2316  	}
  2317  
  2318  	timeout := 30 * time.Second
  2319  	listOptions := &metav1.ListOptions{
  2320  		ResourceVersion: list.ResourceVersion,
  2321  		Watch:           true,
  2322  	}
  2323  
  2324  	wMeta, err := clientSet.CoreV1().RESTClient().Get().
  2325  		AbsPath("/api/v1/namespaces/watch-transform/configmaps").
  2326  		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1beta1").
  2327  		VersionedParams(listOptions, metav1.ParameterCodec).
  2328  		Timeout(timeout).
  2329  		Stream(ctx)
  2330  	if err != nil {
  2331  		t.Fatalf("Failed to start meta watch: %v", err)
  2332  	}
  2333  	defer wMeta.Close()
  2334  
  2335  	wTableIncludeMeta, err := clientSet.CoreV1().RESTClient().Get().
  2336  		AbsPath("/api/v1/namespaces/watch-transform/configmaps").
  2337  		SetHeader("Accept", "application/json;as=Table;g=meta.k8s.io;v=v1beta1").
  2338  		VersionedParams(listOptions, metav1.ParameterCodec).
  2339  		Param("includeObject", string(metav1.IncludeMetadata)).
  2340  		Timeout(timeout).
  2341  		Stream(ctx)
  2342  	if err != nil {
  2343  		t.Fatalf("Failed to start table meta watch: %v", err)
  2344  	}
  2345  	defer wTableIncludeMeta.Close()
  2346  
  2347  	wTableIncludeObject, err := clientSet.CoreV1().RESTClient().Get().
  2348  		AbsPath("/api/v1/namespaces/watch-transform/configmaps").
  2349  		SetHeader("Accept", "application/json;as=Table;g=meta.k8s.io;v=v1beta1").
  2350  		VersionedParams(listOptions, metav1.ParameterCodec).
  2351  		Param("includeObject", string(metav1.IncludeObject)).
  2352  		Timeout(timeout).
  2353  		Stream(ctx)
  2354  	if err != nil {
  2355  		t.Fatalf("Failed to start table object watch: %v", err)
  2356  	}
  2357  	defer wTableIncludeObject.Close()
  2358  
  2359  	wTableIncludeObjectFiltered, err := clientSet.CoreV1().RESTClient().Get().
  2360  		AbsPath("/api/v1/namespaces/watch-transform/configmaps").
  2361  		SetHeader("Accept", "application/json;as=Table;g=meta.k8s.io;v=v1beta1").
  2362  		VersionedParams(listOptions, metav1.ParameterCodec).
  2363  		Param("includeObject", string(metav1.IncludeObject)).
  2364  		Param("labelSelector", "foo=bar").
  2365  		Timeout(timeout).
  2366  		Stream(ctx)
  2367  	if err != nil {
  2368  		t.Fatalf("Failed to start table object watch: %v", err)
  2369  	}
  2370  	defer wTableIncludeObjectFiltered.Close()
  2371  
  2372  	configMap, err := clientSet.CoreV1().ConfigMaps("watch-transform").Create(ctx, &v1.ConfigMap{
  2373  		ObjectMeta: metav1.ObjectMeta{Name: "test1"},
  2374  		Data: map[string]string{
  2375  			"foo": "bar",
  2376  		},
  2377  	}, metav1.CreateOptions{})
  2378  	if err != nil {
  2379  		t.Fatalf("Failed to create a configMap: %v", err)
  2380  	}
  2381  
  2382  	listOptionsDelayed := &metav1.ListOptions{
  2383  		ResourceVersion: configMap.ResourceVersion,
  2384  		Watch:           true,
  2385  	}
  2386  	wTableIncludeObjectDelayed, err := clientSet.CoreV1().RESTClient().Get().
  2387  		AbsPath("/api/v1/namespaces/watch-transform/configmaps").
  2388  		SetHeader("Accept", "application/json;as=Table;g=meta.k8s.io;v=v1beta1").
  2389  		VersionedParams(listOptionsDelayed, metav1.ParameterCodec).
  2390  		Param("includeObject", string(metav1.IncludeObject)).
  2391  		Timeout(timeout).
  2392  		Stream(ctx)
  2393  	if err != nil {
  2394  		t.Fatalf("Failed to start table object watch: %v", err)
  2395  	}
  2396  	defer wTableIncludeObjectDelayed.Close()
  2397  
  2398  	configMap2, err := clientSet.CoreV1().ConfigMaps("watch-transform").Create(ctx, &v1.ConfigMap{
  2399  		ObjectMeta: metav1.ObjectMeta{Name: "test2"},
  2400  		Data: map[string]string{
  2401  			"foo": "bar",
  2402  		},
  2403  	}, metav1.CreateOptions{})
  2404  	if err != nil {
  2405  		t.Fatalf("Failed to create a second configMap: %v", err)
  2406  	}
  2407  
  2408  	// Now update both configmaps so that filtering watch can observe them.
  2409  	// This is needed to validate whether events caching done by apiserver
  2410  	// distinguished objects by type.
  2411  
  2412  	configMapUpdated, err := clientSet.CoreV1().ConfigMaps("watch-transform").Update(ctx, &v1.ConfigMap{
  2413  		ObjectMeta: metav1.ObjectMeta{Name: "test1", Labels: map[string]string{"foo": "bar"}},
  2414  		Data: map[string]string{
  2415  			"foo": "baz",
  2416  		},
  2417  	}, metav1.UpdateOptions{})
  2418  	if err != nil {
  2419  		t.Fatalf("Failed to update a configMap: %v", err)
  2420  	}
  2421  
  2422  	configMap2Updated, err := clientSet.CoreV1().ConfigMaps("watch-transform").Update(ctx, &v1.ConfigMap{
  2423  		ObjectMeta: metav1.ObjectMeta{Name: "test2", Labels: map[string]string{"foo": "bar"}},
  2424  		Data: map[string]string{
  2425  			"foo": "baz",
  2426  		},
  2427  	}, metav1.UpdateOptions{})
  2428  	if err != nil {
  2429  		t.Fatalf("Failed to update a second configMap: %v", err)
  2430  	}
  2431  
  2432  	metaChecks := []partialObjectMetadataCheck{
  2433  		func(res *metav1beta1.PartialObjectMetadata) {
  2434  			if !apiequality.Semantic.DeepEqual(configMap.ObjectMeta, res.ObjectMeta) {
  2435  				t.Errorf("expected object: %#v, got: %#v", configMap.ObjectMeta, res.ObjectMeta)
  2436  			}
  2437  		},
  2438  		func(res *metav1beta1.PartialObjectMetadata) {
  2439  			if !apiequality.Semantic.DeepEqual(configMap2.ObjectMeta, res.ObjectMeta) {
  2440  				t.Errorf("expected object: %#v, got: %#v", configMap2.ObjectMeta, res.ObjectMeta)
  2441  			}
  2442  		},
  2443  		func(res *metav1beta1.PartialObjectMetadata) {
  2444  			if !apiequality.Semantic.DeepEqual(configMapUpdated.ObjectMeta, res.ObjectMeta) {
  2445  				t.Errorf("expected object: %#v, got: %#v", configMapUpdated.ObjectMeta, res.ObjectMeta)
  2446  			}
  2447  		},
  2448  		func(res *metav1beta1.PartialObjectMetadata) {
  2449  			if !apiequality.Semantic.DeepEqual(configMap2Updated.ObjectMeta, res.ObjectMeta) {
  2450  				t.Errorf("expected object: %#v, got: %#v", configMap2Updated.ObjectMeta, res.ObjectMeta)
  2451  			}
  2452  		},
  2453  	}
  2454  	expectPartialObjectMetaEventsProtobufChecks(t, wMeta, metaChecks)
  2455  
  2456  	tableMetaCheck := func(expected *v1.ConfigMap, got []byte) {
  2457  		var obj metav1.PartialObjectMetadata
  2458  		if err := json.Unmarshal(got, &obj); err != nil {
  2459  			t.Fatal(err)
  2460  		}
  2461  		if !apiequality.Semantic.DeepEqual(expected.ObjectMeta, obj.ObjectMeta) {
  2462  			t.Errorf("expected object: %#v, got: %#v", expected, obj)
  2463  		}
  2464  	}
  2465  
  2466  	objectMetas := expectTableWatchEvents(t, 4, 3, metav1.IncludeMetadata, json.NewDecoder(wTableIncludeMeta))
  2467  	tableMetaCheck(configMap, objectMetas[0])
  2468  	tableMetaCheck(configMap2, objectMetas[1])
  2469  	tableMetaCheck(configMapUpdated, objectMetas[2])
  2470  	tableMetaCheck(configMap2Updated, objectMetas[3])
  2471  
  2472  	tableObjectCheck := func(expectedType watch.EventType, expectedObj *v1.ConfigMap, got streamedEvent) {
  2473  		var obj *v1.ConfigMap
  2474  		if err := json.Unmarshal(got.rawObject, &obj); err != nil {
  2475  			t.Fatal(err)
  2476  		}
  2477  		if expectedType != watch.EventType(got.eventType) {
  2478  			t.Errorf("expected type: %#v, got: %#v", expectedType, got.eventType)
  2479  		}
  2480  		obj.TypeMeta = metav1.TypeMeta{}
  2481  		if !apiequality.Semantic.DeepEqual(expectedObj, obj) {
  2482  			t.Errorf("expected object: %#v, got: %#v", expectedObj, obj)
  2483  		}
  2484  	}
  2485  
  2486  	objects := expectTableWatchEventsWithTypes(t, 4, 3, metav1.IncludeObject, json.NewDecoder(wTableIncludeObject))
  2487  	tableObjectCheck(watch.Added, configMap, objects[0])
  2488  	tableObjectCheck(watch.Added, configMap2, objects[1])
  2489  	tableObjectCheck(watch.Modified, configMapUpdated, objects[2])
  2490  	tableObjectCheck(watch.Modified, configMap2Updated, objects[3])
  2491  
  2492  	filteredObjects := expectTableWatchEventsWithTypes(t, 2, 3, metav1.IncludeObject, json.NewDecoder(wTableIncludeObjectFiltered))
  2493  	tableObjectCheck(watch.Added, configMapUpdated, filteredObjects[0])
  2494  	tableObjectCheck(watch.Added, configMap2Updated, filteredObjects[1])
  2495  
  2496  	delayedObjects := expectTableWatchEventsWithTypes(t, 3, 3, metav1.IncludeObject, json.NewDecoder(wTableIncludeObjectDelayed))
  2497  	tableObjectCheck(watch.Added, configMap2, delayedObjects[0])
  2498  	tableObjectCheck(watch.Modified, configMapUpdated, delayedObjects[1])
  2499  	tableObjectCheck(watch.Modified, configMap2Updated, delayedObjects[2])
  2500  }
  2501  
  2502  func expectTableWatchEvents(t *testing.T, count, columns int, policy metav1.IncludeObjectPolicy, d *json.Decoder) [][]byte {
  2503  	events := expectTableWatchEventsWithTypes(t, count, columns, policy, d)
  2504  	var objects [][]byte
  2505  	for _, event := range events {
  2506  		objects = append(objects, event.rawObject)
  2507  	}
  2508  	return objects
  2509  }
  2510  
  2511  type streamedEvent struct {
  2512  	eventType string
  2513  	rawObject []byte
  2514  }
  2515  
  2516  func expectTableWatchEventsWithTypes(t *testing.T, count, columns int, policy metav1.IncludeObjectPolicy, d *json.Decoder) []streamedEvent {
  2517  	t.Helper()
  2518  
  2519  	var events []streamedEvent
  2520  
  2521  	for i := 0; i < count; i++ {
  2522  		var evt metav1.WatchEvent
  2523  		if err := d.Decode(&evt); err != nil {
  2524  			t.Fatal(err)
  2525  		}
  2526  
  2527  		var table metav1beta1.Table
  2528  		if err := json.Unmarshal(evt.Object.Raw, &table); err != nil {
  2529  			t.Fatal(err)
  2530  		}
  2531  		if i == 0 {
  2532  			if len(table.ColumnDefinitions) != columns {
  2533  				t.Fatalf("Got unexpected columns on first watch event: %d vs %#v", columns, table.ColumnDefinitions)
  2534  			}
  2535  		} else {
  2536  			if len(table.ColumnDefinitions) != 0 {
  2537  				t.Fatalf("Expected no columns on second watch event: %#v", table.ColumnDefinitions)
  2538  			}
  2539  		}
  2540  		if len(table.Rows) != 1 {
  2541  			t.Fatalf("Invalid rows: %#v", table.Rows)
  2542  		}
  2543  		row := table.Rows[0]
  2544  		if len(row.Cells) != columns {
  2545  			t.Fatalf("Invalid row width: %#v", row.Cells)
  2546  		}
  2547  		switch policy {
  2548  		case metav1.IncludeMetadata:
  2549  			var meta metav1beta1.PartialObjectMetadata
  2550  			if err := json.Unmarshal(row.Object.Raw, &meta); err != nil {
  2551  				t.Fatalf("expected partial object: %v", err)
  2552  			}
  2553  			partialObj := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1beta1"}
  2554  			if meta.TypeMeta != partialObj {
  2555  				t.Fatalf("expected partial object: %#v", meta)
  2556  			}
  2557  			events = append(events, streamedEvent{eventType: evt.Type, rawObject: row.Object.Raw})
  2558  		case metav1.IncludeNone:
  2559  			if len(row.Object.Raw) != 0 {
  2560  				t.Fatalf("Expected no object: %s", string(row.Object.Raw))
  2561  			}
  2562  		case metav1.IncludeObject:
  2563  			if len(row.Object.Raw) == 0 {
  2564  				t.Fatalf("Expected object: %s", string(row.Object.Raw))
  2565  			}
  2566  			events = append(events, streamedEvent{eventType: evt.Type, rawObject: row.Object.Raw})
  2567  		}
  2568  	}
  2569  	return events
  2570  }
  2571  
  2572  func expectPartialObjectMetaEvents(t *testing.T, d *json.Decoder, values ...string) {
  2573  	t.Helper()
  2574  
  2575  	for i, value := range values {
  2576  		var evt metav1.WatchEvent
  2577  		if err := d.Decode(&evt); err != nil {
  2578  			t.Fatal(err)
  2579  		}
  2580  		var meta metav1beta1.PartialObjectMetadata
  2581  		if err := json.Unmarshal(evt.Object.Raw, &meta); err != nil {
  2582  			t.Fatal(err)
  2583  		}
  2584  		typeMeta := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1beta1"}
  2585  		if meta.TypeMeta != typeMeta {
  2586  			t.Fatalf("expected partial object: %#v", meta)
  2587  		}
  2588  		if meta.Annotations["test"] != value {
  2589  			t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"])
  2590  		}
  2591  	}
  2592  }
  2593  
  2594  type partialObjectMetadataCheck func(*metav1beta1.PartialObjectMetadata)
  2595  
  2596  func expectPartialObjectMetaEventsProtobuf(t *testing.T, r io.Reader, values ...string) {
  2597  	checks := []partialObjectMetadataCheck{}
  2598  	for i, value := range values {
  2599  		i, value := i, value
  2600  		checks = append(checks, func(meta *metav1beta1.PartialObjectMetadata) {
  2601  			if meta.Annotations["test"] != value {
  2602  				t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"])
  2603  			}
  2604  		})
  2605  	}
  2606  	expectPartialObjectMetaEventsProtobufChecks(t, r, checks)
  2607  }
  2608  
  2609  func expectPartialObjectMetaEventsProtobufChecks(t *testing.T, r io.Reader, checks []partialObjectMetadataCheck) {
  2610  	scheme := runtime.NewScheme()
  2611  	metav1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
  2612  	rs := protobuf.NewRawSerializer(scheme, scheme)
  2613  	d := streaming.NewDecoder(
  2614  		protobuf.LengthDelimitedFramer.NewFrameReader(io.NopCloser(r)),
  2615  		rs,
  2616  	)
  2617  	ds := metainternalversionscheme.Codecs.UniversalDeserializer()
  2618  
  2619  	for _, check := range checks {
  2620  		var evt metav1.WatchEvent
  2621  		if _, _, err := d.Decode(nil, &evt); err != nil {
  2622  			t.Fatal(err)
  2623  		}
  2624  		obj, gvk, err := ds.Decode(evt.Object.Raw, nil, nil)
  2625  		if err != nil {
  2626  			t.Fatal(err)
  2627  		}
  2628  		meta, ok := obj.(*metav1beta1.PartialObjectMetadata)
  2629  		if !ok {
  2630  			t.Fatalf("unexpected watch object %T", obj)
  2631  		}
  2632  		expected := &schema.GroupVersionKind{Kind: "PartialObjectMetadata", Version: "v1beta1", Group: "meta.k8s.io"}
  2633  		if !reflect.DeepEqual(expected, gvk) {
  2634  			t.Fatalf("expected partial object: %#v", meta)
  2635  		}
  2636  		check(meta)
  2637  	}
  2638  }
  2639  
  2640  func expectTableV1WatchEvents(t *testing.T, count, columns int, policy metav1.IncludeObjectPolicy, d *json.Decoder) [][]byte {
  2641  	t.Helper()
  2642  
  2643  	var objects [][]byte
  2644  
  2645  	for i := 0; i < count; i++ {
  2646  		var evt metav1.WatchEvent
  2647  		if err := d.Decode(&evt); err != nil {
  2648  			t.Fatal(err)
  2649  		}
  2650  		var table metav1.Table
  2651  		if err := json.Unmarshal(evt.Object.Raw, &table); err != nil {
  2652  			t.Fatal(err)
  2653  		}
  2654  		if i == 0 {
  2655  			if len(table.ColumnDefinitions) != columns {
  2656  				t.Fatalf("Got unexpected columns on first watch event: %d vs %#v", columns, table.ColumnDefinitions)
  2657  			}
  2658  		} else {
  2659  			if len(table.ColumnDefinitions) != 0 {
  2660  				t.Fatalf("Expected no columns on second watch event: %#v", table.ColumnDefinitions)
  2661  			}
  2662  		}
  2663  		if len(table.Rows) != 1 {
  2664  			t.Fatalf("Invalid rows: %#v", table.Rows)
  2665  		}
  2666  		row := table.Rows[0]
  2667  		if len(row.Cells) != columns {
  2668  			t.Fatalf("Invalid row width: %#v", row.Cells)
  2669  		}
  2670  		switch policy {
  2671  		case metav1.IncludeMetadata:
  2672  			var meta metav1.PartialObjectMetadata
  2673  			if err := json.Unmarshal(row.Object.Raw, &meta); err != nil {
  2674  				t.Fatalf("expected partial object: %v", err)
  2675  			}
  2676  			partialObj := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1"}
  2677  			if meta.TypeMeta != partialObj {
  2678  				t.Fatalf("expected partial object: %#v", meta)
  2679  			}
  2680  		case metav1.IncludeNone:
  2681  			if len(row.Object.Raw) != 0 {
  2682  				t.Fatalf("Expected no object: %s", string(row.Object.Raw))
  2683  			}
  2684  		case metav1.IncludeObject:
  2685  			if len(row.Object.Raw) == 0 {
  2686  				t.Fatalf("Expected object: %s", string(row.Object.Raw))
  2687  			}
  2688  			objects = append(objects, row.Object.Raw)
  2689  		}
  2690  	}
  2691  	return objects
  2692  }
  2693  
  2694  func expectPartialObjectMetaV1Events(t *testing.T, d *json.Decoder, values ...string) {
  2695  	t.Helper()
  2696  
  2697  	for i, value := range values {
  2698  		var evt metav1.WatchEvent
  2699  		if err := d.Decode(&evt); err != nil {
  2700  			t.Fatal(err)
  2701  		}
  2702  		var meta metav1.PartialObjectMetadata
  2703  		if err := json.Unmarshal(evt.Object.Raw, &meta); err != nil {
  2704  			t.Fatal(err)
  2705  		}
  2706  		typeMeta := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1"}
  2707  		if meta.TypeMeta != typeMeta {
  2708  			t.Fatalf("expected partial object: %#v", meta)
  2709  		}
  2710  		if meta.Annotations["test"] != value {
  2711  			t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"])
  2712  		}
  2713  	}
  2714  }
  2715  
  2716  func expectPartialObjectMetaV1EventsProtobuf(t *testing.T, r io.Reader, values ...string) {
  2717  	scheme := runtime.NewScheme()
  2718  	metav1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
  2719  	rs := protobuf.NewRawSerializer(scheme, scheme)
  2720  	d := streaming.NewDecoder(
  2721  		protobuf.LengthDelimitedFramer.NewFrameReader(io.NopCloser(r)),
  2722  		rs,
  2723  	)
  2724  	ds := metainternalversionscheme.Codecs.UniversalDeserializer()
  2725  
  2726  	for i, value := range values {
  2727  		var evt metav1.WatchEvent
  2728  		if _, _, err := d.Decode(nil, &evt); err != nil {
  2729  			t.Fatal(err)
  2730  		}
  2731  		obj, gvk, err := ds.Decode(evt.Object.Raw, nil, nil)
  2732  		if err != nil {
  2733  			t.Fatal(err)
  2734  		}
  2735  		meta, ok := obj.(*metav1.PartialObjectMetadata)
  2736  		if !ok {
  2737  			t.Fatalf("unexpected watch object %T", obj)
  2738  		}
  2739  		expected := &schema.GroupVersionKind{Kind: "PartialObjectMetadata", Version: "v1", Group: "meta.k8s.io"}
  2740  		if !reflect.DeepEqual(expected, gvk) {
  2741  			t.Fatalf("expected partial object: %#v", meta)
  2742  		}
  2743  		if meta.Annotations["test"] != value {
  2744  			t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"])
  2745  		}
  2746  	}
  2747  }
  2748  
  2749  func TestClientsetShareTransport(t *testing.T) {
  2750  	var counter int
  2751  	var mu sync.Mutex
  2752  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
  2753  	defer server.TearDownFn()
  2754  
  2755  	dialFn := func(ctx context.Context, network, address string) (net.Conn, error) {
  2756  		mu.Lock()
  2757  		counter++
  2758  		mu.Unlock()
  2759  		return (&net.Dialer{}).DialContext(ctx, network, address)
  2760  	}
  2761  	server.ClientConfig.Dial = dialFn
  2762  	client := clientset.NewForConfigOrDie(server.ClientConfig)
  2763  	// create test namespace
  2764  	_, err := client.CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{
  2765  		ObjectMeta: metav1.ObjectMeta{
  2766  			Name: "test-creation",
  2767  		},
  2768  	}, metav1.CreateOptions{})
  2769  	if err != nil {
  2770  		t.Fatalf("failed to create test ns: %v", err)
  2771  	}
  2772  	// List different objects
  2773  	result := client.CoreV1().RESTClient().Get().AbsPath("/healthz").Do(context.TODO())
  2774  	_, err = result.Raw()
  2775  	if err != nil {
  2776  		t.Fatal(err)
  2777  	}
  2778  	_, _, err = client.Discovery().ServerGroupsAndResources()
  2779  	if err != nil {
  2780  		t.Fatal(err)
  2781  	}
  2782  	n, err := client.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
  2783  	if err != nil {
  2784  		t.Fatal(err)
  2785  	}
  2786  	t.Logf("Listed %d namespaces on the cluster", len(n.Items))
  2787  	p, err := client.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
  2788  	if err != nil {
  2789  		t.Fatal(err)
  2790  	}
  2791  	t.Logf("Listed %d pods on the cluster", len(p.Items))
  2792  	e, err := client.DiscoveryV1().EndpointSlices("").List(context.TODO(), metav1.ListOptions{})
  2793  	if err != nil {
  2794  		t.Fatal(err)
  2795  	}
  2796  	t.Logf("Listed %d endpoint slices on the cluster", len(e.Items))
  2797  	d, err := client.AppsV1().Deployments("").List(context.TODO(), metav1.ListOptions{})
  2798  	if err != nil {
  2799  		t.Fatal(err)
  2800  	}
  2801  	t.Logf("Listed %d deployments on the cluster", len(d.Items))
  2802  
  2803  	if counter != 1 {
  2804  		t.Fatalf("expected only one connection, created %d connections", counter)
  2805  	}
  2806  }
  2807  
  2808  func TestDedupOwnerReferences(t *testing.T) {
  2809  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
  2810  	defer server.TearDownFn()
  2811  	etcd.CreateTestCRDs(t, apiextensionsclient.NewForConfigOrDie(server.ClientConfig), false, etcd.GetCustomResourceDefinitionData()[0])
  2812  
  2813  	b := &bytes.Buffer{}
  2814  	warningWriter := restclient.NewWarningWriter(b, restclient.WarningWriterOptions{})
  2815  	server.ClientConfig.WarningHandler = warningWriter
  2816  	client := clientset.NewForConfigOrDie(server.ClientConfig)
  2817  	dynamicClient := dynamic.NewForConfigOrDie(server.ClientConfig)
  2818  
  2819  	ns := "test-dedup-owner-references"
  2820  	// create test namespace
  2821  	_, err := client.CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{
  2822  		ObjectMeta: metav1.ObjectMeta{
  2823  			Name: ns,
  2824  		},
  2825  	}, metav1.CreateOptions{})
  2826  	if err != nil {
  2827  		t.Fatalf("failed to create test ns: %v", err)
  2828  	}
  2829  
  2830  	// some fake owner references
  2831  	fakeRefA := metav1.OwnerReference{
  2832  		APIVersion: "v1",
  2833  		Kind:       "ConfigMap",
  2834  		Name:       "fake-configmap",
  2835  		UID:        uuid.NewUUID(),
  2836  	}
  2837  	fakeRefB := metav1.OwnerReference{
  2838  		APIVersion: "v1",
  2839  		Kind:       "Node",
  2840  		Name:       "fake-node",
  2841  		UID:        uuid.NewUUID(),
  2842  	}
  2843  	fakeRefC := metav1.OwnerReference{
  2844  		APIVersion: "cr.bar.com/v1",
  2845  		Kind:       "Foo",
  2846  		Name:       "fake-foo",
  2847  		UID:        uuid.NewUUID(),
  2848  	}
  2849  
  2850  	tcs := []struct {
  2851  		gvr  schema.GroupVersionResource
  2852  		kind string
  2853  	}{
  2854  		{
  2855  			gvr: schema.GroupVersionResource{
  2856  				Group:    "",
  2857  				Version:  "v1",
  2858  				Resource: "configmaps",
  2859  			},
  2860  			kind: "ConfigMap",
  2861  		},
  2862  		{
  2863  			gvr: schema.GroupVersionResource{
  2864  				Group:    "cr.bar.com",
  2865  				Version:  "v1",
  2866  				Resource: "foos",
  2867  			},
  2868  			kind: "Foo",
  2869  		},
  2870  	}
  2871  
  2872  	for i, tc := range tcs {
  2873  		t.Run(tc.gvr.String(), func(t *testing.T) {
  2874  			previousWarningCount := i * 3
  2875  			c := &dependentClient{
  2876  				t:      t,
  2877  				client: dynamicClient.Resource(tc.gvr).Namespace(ns),
  2878  				gvr:    tc.gvr,
  2879  				kind:   tc.kind,
  2880  			}
  2881  			klog.Infof("creating dependent with duplicate owner references")
  2882  			dependent := c.createDependentWithOwners([]metav1.OwnerReference{fakeRefA, fakeRefA})
  2883  			assertManagedFields(t, dependent)
  2884  			expectedWarning := fmt.Sprintf(handlers.DuplicateOwnerReferencesWarningFormat, fakeRefA.UID)
  2885  			assertOwnerReferences(t, dependent, []metav1.OwnerReference{fakeRefA})
  2886  			assertWarningCount(t, warningWriter, previousWarningCount+1)
  2887  			assertWarningMessage(t, b, expectedWarning)
  2888  
  2889  			klog.Infof("updating dependent with duplicate owner references")
  2890  			dependent = c.updateDependentWithOwners(dependent, []metav1.OwnerReference{fakeRefA, fakeRefA})
  2891  			assertManagedFields(t, dependent)
  2892  			assertOwnerReferences(t, dependent, []metav1.OwnerReference{fakeRefA})
  2893  			assertWarningCount(t, warningWriter, previousWarningCount+2)
  2894  			assertWarningMessage(t, b, expectedWarning)
  2895  
  2896  			klog.Infof("patching dependent with duplicate owner reference")
  2897  			dependent = c.patchDependentWithOwner(dependent, fakeRefA)
  2898  			// TODO: currently a patch request that duplicates owner references can still
  2899  			// wipe out managed fields. Note that this happens to built-in resources but
  2900  			// not custom resources. In future we should either dedup before writing manage
  2901  			// fields, or stop deduping and reject the request.
  2902  			// assertManagedFields(t, dependent)
  2903  			expectedPatchWarning := fmt.Sprintf(handlers.DuplicateOwnerReferencesAfterMutatingAdmissionWarningFormat, fakeRefA.UID)
  2904  			assertOwnerReferences(t, dependent, []metav1.OwnerReference{fakeRefA})
  2905  			assertWarningCount(t, warningWriter, previousWarningCount+3)
  2906  			assertWarningMessage(t, b, expectedPatchWarning)
  2907  
  2908  			klog.Infof("updating dependent with different owner references")
  2909  			dependent = c.updateDependentWithOwners(dependent, []metav1.OwnerReference{fakeRefA, fakeRefB})
  2910  			assertOwnerReferences(t, dependent, []metav1.OwnerReference{fakeRefA, fakeRefB})
  2911  			assertWarningCount(t, warningWriter, previousWarningCount+3)
  2912  			assertWarningMessage(t, b, "")
  2913  
  2914  			klog.Infof("patching dependent with different owner references")
  2915  			dependent = c.patchDependentWithOwner(dependent, fakeRefC)
  2916  			assertOwnerReferences(t, dependent, []metav1.OwnerReference{fakeRefA, fakeRefB, fakeRefC})
  2917  			assertWarningCount(t, warningWriter, previousWarningCount+3)
  2918  			assertWarningMessage(t, b, "")
  2919  
  2920  			klog.Infof("deleting dependent")
  2921  			c.deleteDependent()
  2922  			assertWarningCount(t, warningWriter, previousWarningCount+3)
  2923  			assertWarningMessage(t, b, "")
  2924  		})
  2925  	}
  2926  	// cleanup
  2927  	if err := client.CoreV1().Namespaces().Delete(context.TODO(), ns, metav1.DeleteOptions{}); err != nil {
  2928  		t.Fatalf("failed to delete test ns: %v", err)
  2929  	}
  2930  }
  2931  
  2932  type dependentClient struct {
  2933  	t      *testing.T
  2934  	client dynamic.ResourceInterface
  2935  	gvr    schema.GroupVersionResource
  2936  	kind   string
  2937  }
  2938  
  2939  func (c *dependentClient) createDependentWithOwners(refs []metav1.OwnerReference) *unstructured.Unstructured {
  2940  	obj := &unstructured.Unstructured{}
  2941  	obj.SetName("dependent")
  2942  	obj.SetOwnerReferences(refs)
  2943  	obj.SetKind(c.kind)
  2944  	obj.SetAPIVersion(fmt.Sprintf("%s/%s", c.gvr.Group, c.gvr.Version))
  2945  	obj, err := c.client.Create(context.TODO(), obj, metav1.CreateOptions{})
  2946  	if err != nil {
  2947  		c.t.Fatalf("failed to create dependent with owner references %v: %v", refs, err)
  2948  	}
  2949  	return obj
  2950  }
  2951  
  2952  func (c *dependentClient) updateDependentWithOwners(obj *unstructured.Unstructured, refs []metav1.OwnerReference) *unstructured.Unstructured {
  2953  	obj.SetOwnerReferences(refs)
  2954  	obj, err := c.client.Update(context.TODO(), obj, metav1.UpdateOptions{})
  2955  	if err != nil {
  2956  		c.t.Fatalf("failed to update dependent with owner references %v: %v", refs, err)
  2957  	}
  2958  	return obj
  2959  }
  2960  
  2961  func (c *dependentClient) patchDependentWithOwner(obj *unstructured.Unstructured, ref metav1.OwnerReference) *unstructured.Unstructured {
  2962  	patch := []byte(fmt.Sprintf(`[{"op":"add","path":"/metadata/ownerReferences/-","value":{"apiVersion":"%v", "kind": "%v", "name": "%v", "uid": "%v"}}]`, ref.APIVersion, ref.Kind, ref.Name, ref.UID))
  2963  	obj, err := c.client.Patch(context.TODO(), obj.GetName(), types.JSONPatchType, patch, metav1.PatchOptions{})
  2964  	if err != nil {
  2965  		c.t.Fatalf("failed to append owner reference to dependent with owner reference %v, patch %v: %v",
  2966  			ref, patch, err)
  2967  	}
  2968  	return obj
  2969  }
  2970  
  2971  func (c *dependentClient) deleteDependent() {
  2972  	if err := c.client.Delete(context.TODO(), "dependent", metav1.DeleteOptions{}); err != nil {
  2973  		c.t.Fatalf("failed to delete dependent: %v", err)
  2974  	}
  2975  }
  2976  
  2977  type warningCounter interface {
  2978  	WarningCount() int
  2979  }
  2980  
  2981  func assertOwnerReferences(t *testing.T, obj *unstructured.Unstructured, refs []metav1.OwnerReference) {
  2982  	if !reflect.DeepEqual(obj.GetOwnerReferences(), refs) {
  2983  		t.Errorf("unexpected owner references, expected: %v, got: %v", refs, obj.GetOwnerReferences())
  2984  	}
  2985  }
  2986  
  2987  func assertWarningCount(t *testing.T, counter warningCounter, expected int) {
  2988  	if counter.WarningCount() != expected {
  2989  		t.Errorf("unexpected warning count, expected: %v, got: %v", expected, counter.WarningCount())
  2990  	}
  2991  }
  2992  
  2993  func assertWarningMessage(t *testing.T, b *bytes.Buffer, expected string) {
  2994  	defer b.Reset()
  2995  	actual := b.String()
  2996  	if len(expected) == 0 && len(actual) != 0 {
  2997  		t.Errorf("unexpected warning message, expected no warning, got: %v", actual)
  2998  	}
  2999  	if len(expected) == 0 {
  3000  		return
  3001  	}
  3002  	if !strings.Contains(actual, expected) {
  3003  		t.Errorf("unexpected warning message, expected: %v, got: %v", expected, actual)
  3004  	}
  3005  }
  3006  
  3007  func assertManagedFields(t *testing.T, obj *unstructured.Unstructured) {
  3008  	if len(obj.GetManagedFields()) == 0 {
  3009  		t.Errorf("unexpected empty managed fields in object: %v", obj)
  3010  	}
  3011  }
  3012  
  3013  func int32Ptr(i int32) *int32 {
  3014  	return &i
  3015  }
  3016  

View as plain text