mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
Merge pull request #84692 from smarterclayton/protocol_errors
Fix watch negotiation when using a non-default mime type in the client
This commit is contained in:
commit
c28921f248
@ -412,7 +412,6 @@ staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook
|
|||||||
staging/src/k8s.io/cli-runtime/pkg/genericclioptions
|
staging/src/k8s.io/cli-runtime/pkg/genericclioptions
|
||||||
staging/src/k8s.io/cli-runtime/pkg/printers
|
staging/src/k8s.io/cli-runtime/pkg/printers
|
||||||
staging/src/k8s.io/cli-runtime/pkg/resource
|
staging/src/k8s.io/cli-runtime/pkg/resource
|
||||||
staging/src/k8s.io/client-go/deprecated-dynamic
|
|
||||||
staging/src/k8s.io/client-go/discovery
|
staging/src/k8s.io/client-go/discovery
|
||||||
staging/src/k8s.io/client-go/discovery/fake
|
staging/src/k8s.io/client-go/discovery/fake
|
||||||
staging/src/k8s.io/client-go/dynamic
|
staging/src/k8s.io/client-go/dynamic
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||||
@ -232,11 +233,11 @@ func TestStream(t *testing.T) {
|
|||||||
server := httptest.NewServer(fakeServer(t, requestReceived, name, exec, testCase.Stdin, testCase.Stdout, testCase.Stderr, testCase.Error, testCase.Tty, testCase.MessageCount, testCase.ServerProtocols))
|
server := httptest.NewServer(fakeServer(t, requestReceived, name, exec, testCase.Stdin, testCase.Stdout, testCase.Stderr, testCase.Error, testCase.Tty, testCase.MessageCount, testCase.ServerProtocols))
|
||||||
|
|
||||||
url, _ := url.ParseRequestURI(server.URL)
|
url, _ := url.ParseRequestURI(server.URL)
|
||||||
config := restclient.ContentConfig{
|
config := restclient.ClientContentConfig{
|
||||||
GroupVersion: &schema.GroupVersion{Group: "x"},
|
GroupVersion: schema.GroupVersion{Group: "x"},
|
||||||
NegotiatedSerializer: legacyscheme.Codecs,
|
Negotiator: runtime.NewClientNegotiator(legacyscheme.Codecs.WithoutConversion(), schema.GroupVersion{Group: "x"}),
|
||||||
}
|
}
|
||||||
c, err := restclient.NewRESTClient(url, "", config, -1, -1, nil, nil)
|
c, err := restclient.NewRESTClient(url, "", config, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create a client: %v", err)
|
t.Fatalf("failed to create a client: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ func TestRunAccessCheck(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||||
@ -197,7 +197,7 @@ func TestRunAccessList(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
|
@ -551,7 +551,7 @@ func TestBadTar(t *testing.T) {
|
|||||||
|
|
||||||
func TestCopyToPod(t *testing.T) {
|
func TestCopyToPod(t *testing.T) {
|
||||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
@ -621,7 +621,7 @@ func TestCopyToPod(t *testing.T) {
|
|||||||
|
|
||||||
func TestCopyToPodNoPreserve(t *testing.T) {
|
func TestCopyToPodNoPreserve(t *testing.T) {
|
||||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
|
@ -50,6 +50,7 @@ go_library(
|
|||||||
"helper.go",
|
"helper.go",
|
||||||
"interfaces.go",
|
"interfaces.go",
|
||||||
"mapper.go",
|
"mapper.go",
|
||||||
|
"negotiate.go",
|
||||||
"register.go",
|
"register.go",
|
||||||
"scheme.go",
|
"scheme.go",
|
||||||
"scheme_builder.go",
|
"scheme_builder.go",
|
||||||
|
@ -155,6 +155,28 @@ type NegotiatedSerializer interface {
|
|||||||
DecoderToVersion(serializer Decoder, gv GroupVersioner) Decoder
|
DecoderToVersion(serializer Decoder, gv GroupVersioner) Decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientNegotiator handles turning an HTTP content type into the appropriate encoder.
|
||||||
|
// Use NewClientNegotiator or NewVersionedClientNegotiator to create this interface from
|
||||||
|
// a NegotiatedSerializer.
|
||||||
|
type ClientNegotiator interface {
|
||||||
|
// Encoder returns the appropriate encoder for the provided contentType (e.g. application/json)
|
||||||
|
// and any optional mediaType parameters (e.g. pretty=1), or an error. If no serializer is found
|
||||||
|
// a NegotiateError will be returned. The current client implementations consider params to be
|
||||||
|
// optional modifiers to the contentType and will ignore unrecognized parameters.
|
||||||
|
Encoder(contentType string, params map[string]string) (Encoder, error)
|
||||||
|
// Decoder returns the appropriate decoder for the provided contentType (e.g. application/json)
|
||||||
|
// and any optional mediaType parameters (e.g. pretty=1), or an error. If no serializer is found
|
||||||
|
// a NegotiateError will be returned. The current client implementations consider params to be
|
||||||
|
// optional modifiers to the contentType and will ignore unrecognized parameters.
|
||||||
|
Decoder(contentType string, params map[string]string) (Decoder, error)
|
||||||
|
// StreamDecoder returns the appropriate stream decoder for the provided contentType (e.g.
|
||||||
|
// application/json) and any optional mediaType parameters (e.g. pretty=1), or an error. If no
|
||||||
|
// serializer is found a NegotiateError will be returned. The Serializer and Framer will always
|
||||||
|
// be returned if a Decoder is returned. The current client implementations consider params to be
|
||||||
|
// optional modifiers to the contentType and will ignore unrecognized parameters.
|
||||||
|
StreamDecoder(contentType string, params map[string]string) (Decoder, Serializer, Framer, error)
|
||||||
|
}
|
||||||
|
|
||||||
// StorageSerializer is an interface used for obtaining encoders, decoders, and serializers
|
// StorageSerializer is an interface used for obtaining encoders, decoders, and serializers
|
||||||
// that can read and write data at rest. This would commonly be used by client tools that must
|
// that can read and write data at rest. This would commonly be used by client tools that must
|
||||||
// read files, or server side storage interfaces that persist restful objects.
|
// read files, or server side storage interfaces that persist restful objects.
|
||||||
|
146
staging/src/k8s.io/apimachinery/pkg/runtime/negotiate.go
Normal file
146
staging/src/k8s.io/apimachinery/pkg/runtime/negotiate.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NegotiateError is returned when a ClientNegotiator is unable to locate
|
||||||
|
// a serializer for the requested operation.
|
||||||
|
type NegotiateError struct {
|
||||||
|
ContentType string
|
||||||
|
Stream bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e NegotiateError) Error() string {
|
||||||
|
if e.Stream {
|
||||||
|
return fmt.Sprintf("no stream serializers registered for %s", e.ContentType)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("no serializers registered for %s", e.ContentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientNegotiator struct {
|
||||||
|
serializer NegotiatedSerializer
|
||||||
|
encode, decode GroupVersioner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *clientNegotiator) Encoder(contentType string, params map[string]string) (Encoder, error) {
|
||||||
|
// TODO: `pretty=1` is handled in NegotiateOutputMediaType, consider moving it to this method
|
||||||
|
// if client negotiators truly need to use it
|
||||||
|
mediaTypes := n.serializer.SupportedMediaTypes()
|
||||||
|
info, ok := SerializerInfoForMediaType(mediaTypes, contentType)
|
||||||
|
if !ok {
|
||||||
|
if len(contentType) != 0 || len(mediaTypes) == 0 {
|
||||||
|
return nil, NegotiateError{ContentType: contentType}
|
||||||
|
}
|
||||||
|
info = mediaTypes[0]
|
||||||
|
}
|
||||||
|
return n.serializer.EncoderForVersion(info.Serializer, n.encode), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *clientNegotiator) Decoder(contentType string, params map[string]string) (Decoder, error) {
|
||||||
|
mediaTypes := n.serializer.SupportedMediaTypes()
|
||||||
|
info, ok := SerializerInfoForMediaType(mediaTypes, contentType)
|
||||||
|
if !ok {
|
||||||
|
if len(contentType) != 0 || len(mediaTypes) == 0 {
|
||||||
|
return nil, NegotiateError{ContentType: contentType}
|
||||||
|
}
|
||||||
|
info = mediaTypes[0]
|
||||||
|
}
|
||||||
|
return n.serializer.DecoderToVersion(info.Serializer, n.decode), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *clientNegotiator) StreamDecoder(contentType string, params map[string]string) (Decoder, Serializer, Framer, error) {
|
||||||
|
mediaTypes := n.serializer.SupportedMediaTypes()
|
||||||
|
info, ok := SerializerInfoForMediaType(mediaTypes, contentType)
|
||||||
|
if !ok {
|
||||||
|
if len(contentType) != 0 || len(mediaTypes) == 0 {
|
||||||
|
return nil, nil, nil, NegotiateError{ContentType: contentType, Stream: true}
|
||||||
|
}
|
||||||
|
info = mediaTypes[0]
|
||||||
|
}
|
||||||
|
if info.StreamSerializer == nil {
|
||||||
|
return nil, nil, nil, NegotiateError{ContentType: info.MediaType, Stream: true}
|
||||||
|
}
|
||||||
|
return n.serializer.DecoderToVersion(info.Serializer, n.decode), info.StreamSerializer.Serializer, info.StreamSerializer.Framer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClientNegotiator will attempt to retrieve the appropriate encoder, decoder, or
|
||||||
|
// stream decoder for a given content type. Does not perform any conversion, but will
|
||||||
|
// encode the object to the desired group, version, and kind. Use when creating a client.
|
||||||
|
func NewClientNegotiator(serializer NegotiatedSerializer, gv schema.GroupVersion) ClientNegotiator {
|
||||||
|
return &clientNegotiator{
|
||||||
|
serializer: serializer,
|
||||||
|
encode: gv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInternalClientNegotiator applies the default client rules for connecting to a Kubernetes apiserver
|
||||||
|
// where objects are converted to gv prior to sending and decoded to their internal representation prior
|
||||||
|
// to retrieval.
|
||||||
|
//
|
||||||
|
// DEPRECATED: Internal clients are deprecated and will be removed in a future Kubernetes release.
|
||||||
|
func NewInternalClientNegotiator(serializer NegotiatedSerializer, gv schema.GroupVersion) ClientNegotiator {
|
||||||
|
decode := schema.GroupVersions{
|
||||||
|
{
|
||||||
|
Group: gv.Group,
|
||||||
|
Version: APIVersionInternal,
|
||||||
|
},
|
||||||
|
// always include the legacy group as a decoding target to handle non-error `Status` return types
|
||||||
|
{
|
||||||
|
Group: "",
|
||||||
|
Version: APIVersionInternal,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return &clientNegotiator{
|
||||||
|
encode: gv,
|
||||||
|
decode: decode,
|
||||||
|
serializer: serializer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSimpleClientNegotiator will negotiate for a single serializer. This should only be used
|
||||||
|
// for testing or when the caller is taking responsibility for setting the GVK on encoded objects.
|
||||||
|
func NewSimpleClientNegotiator(info SerializerInfo, gv schema.GroupVersion) ClientNegotiator {
|
||||||
|
return &clientNegotiator{
|
||||||
|
serializer: &simpleNegotiatedSerializer{info: info},
|
||||||
|
encode: gv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type simpleNegotiatedSerializer struct {
|
||||||
|
info SerializerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSimpleNegotiatedSerializer(info SerializerInfo) NegotiatedSerializer {
|
||||||
|
return &simpleNegotiatedSerializer{info: info}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *simpleNegotiatedSerializer) SupportedMediaTypes() []SerializerInfo {
|
||||||
|
return []SerializerInfo{n.info}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *simpleNegotiatedSerializer) EncoderForVersion(e Encoder, _ GroupVersioner) Encoder {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *simpleNegotiatedSerializer) DecoderToVersion(d Decoder, _gv GroupVersioner) Decoder {
|
||||||
|
return d
|
||||||
|
}
|
@ -34,7 +34,6 @@ import (
|
|||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@ -659,38 +658,20 @@ func TestWatchHTTPDynamicClientErrors(t *testing.T) {
|
|||||||
watchServer.ServeHTTP(w, req)
|
watchServer.ServeHTTP(w, req)
|
||||||
}))
|
}))
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
defer s.CloseClientConnections()
|
||||||
|
|
||||||
client := dynamic.NewForConfigOrDie(&restclient.Config{
|
client := dynamic.NewForConfigOrDie(&restclient.Config{
|
||||||
Host: s.URL,
|
Host: s.URL,
|
||||||
APIPath: "/" + prefix,
|
APIPath: "/" + prefix,
|
||||||
}).Resource(newGroupVersion.WithResource("simple"))
|
}).Resource(newGroupVersion.WithResource("simple"))
|
||||||
|
|
||||||
w, err := client.Watch(metav1.ListOptions{})
|
_, err := client.Watch(metav1.ListOptions{})
|
||||||
if err != nil {
|
if err == nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if err.Error() != "no stream serializers registered for testcase/json" {
|
||||||
errStatus := errors.NewInternalError(fmt.Errorf("we got an error")).Status()
|
t.Fatalf("unexpected error: %v", err)
|
||||||
watcher.Error(&errStatus)
|
|
||||||
watcher.Stop()
|
|
||||||
|
|
||||||
got := <-w.ResultChan()
|
|
||||||
if got.Type != watch.Error {
|
|
||||||
t.Fatalf("unexpected watch type: %#v", got)
|
|
||||||
}
|
}
|
||||||
obj, ok := got.Object.(*unstructured.Unstructured)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("not the correct object type: %#v", got)
|
|
||||||
}
|
|
||||||
|
|
||||||
status := &metav1.Status{}
|
|
||||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), status); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if status.Kind != "Status" || status.APIVersion != "v1" || status.Code != 500 || status.Status != "Failure" || !strings.Contains(status.Message, "we got an error") {
|
|
||||||
t.Fatalf("error: %#v", status)
|
|
||||||
}
|
|
||||||
t.Logf("status: %#v", status)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWatchHTTPTimeout(t *testing.T) {
|
func TestWatchHTTPTimeout(t *testing.T) {
|
||||||
|
@ -592,7 +592,7 @@ func TestHelperReplace(t *testing.T) {
|
|||||||
Req: expectPut,
|
Req: expectPut,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.Name, func(t *testing.T) {
|
t.Run(tt.Name, func(t *testing.T) {
|
||||||
client := &fake.RESTClient{
|
client := &fake.RESTClient{
|
||||||
GroupVersion: corev1GV,
|
GroupVersion: corev1GV,
|
||||||
@ -607,24 +607,24 @@ func TestHelperReplace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
_, err := modifier.Replace(tt.Namespace, "foo", tt.Overwrite, tt.Object)
|
_, err := modifier.Replace(tt.Namespace, "foo", tt.Overwrite, tt.Object)
|
||||||
if (err != nil) != tt.Err {
|
if (err != nil) != tt.Err {
|
||||||
t.Errorf("%d: unexpected error: %t %v", i, tt.Err, err)
|
t.Fatalf("unexpected error: %t %v", tt.Err, err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if tt.Req != nil && !tt.Req(tt.ExpectPath, client.Req) {
|
if tt.Req != nil && (client.Req == nil || !tt.Req(tt.ExpectPath, client.Req)) {
|
||||||
t.Errorf("%d: unexpected request: %#v", i, client.Req)
|
t.Fatalf("unexpected request: %#v", client.Req)
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(client.Req.Body)
|
body, err := ioutil.ReadAll(client.Req.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%d: unexpected error: %#v", i, err)
|
t.Fatalf("unexpected error: %#v", err)
|
||||||
}
|
}
|
||||||
expect := []byte{}
|
expect := []byte{}
|
||||||
if tt.ExpectObject != nil {
|
if tt.ExpectObject != nil {
|
||||||
expect = []byte(runtime.EncodeOrDie(corev1Codec, tt.ExpectObject))
|
expect = []byte(runtime.EncodeOrDie(corev1Codec, tt.ExpectObject))
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(expect, body) {
|
if !reflect.DeepEqual(expect, body) {
|
||||||
t.Errorf("%d: unexpected body: %s", i, string(body))
|
t.Fatalf("unexpected body: %s", string(body))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ filegroup(
|
|||||||
name = "all-srcs",
|
name = "all-srcs",
|
||||||
srcs = [
|
srcs = [
|
||||||
":package-srcs",
|
":package-srcs",
|
||||||
"//staging/src/k8s.io/client-go/deprecated-dynamic:all-srcs",
|
|
||||||
"//staging/src/k8s.io/client-go/discovery:all-srcs",
|
"//staging/src/k8s.io/client-go/discovery:all-srcs",
|
||||||
"//staging/src/k8s.io/client-go/dynamic:all-srcs",
|
"//staging/src/k8s.io/client-go/dynamic:all-srcs",
|
||||||
"//staging/src/k8s.io/client-go/examples/create-update-delete-deployment:all-srcs",
|
"//staging/src/k8s.io/client-go/examples/create-update-delete-deployment:all-srcs",
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = [
|
|
||||||
"client.go",
|
|
||||||
"client_pool.go",
|
|
||||||
],
|
|
||||||
importmap = "k8s.io/kubernetes/vendor/k8s.io/client-go/deprecated-dynamic",
|
|
||||||
importpath = "k8s.io/client-go/deprecated-dynamic",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
deps = [
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["client_test.go"],
|
|
||||||
embed = [":go_default_library"],
|
|
||||||
deps = [
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/rest/watch:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Package dynamic provides a client interface to arbitrary Kubernetes
|
|
||||||
// APIs that exposes common high level operations and exposes common
|
|
||||||
// metadata.
|
|
||||||
package deprecated_dynamic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
|
||||||
"k8s.io/client-go/dynamic"
|
|
||||||
restclient "k8s.io/client-go/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Interface is a Kubernetes client that allows you to access metadata
|
|
||||||
// and manipulate metadata of a Kubernetes API group.
|
|
||||||
type Interface interface {
|
|
||||||
// Resource returns an API interface to the specified resource for this client's
|
|
||||||
// group and version. If resource is not a namespaced resource, then namespace
|
|
||||||
// is ignored. The ResourceInterface inherits the parameter codec of this client.
|
|
||||||
Resource(resource *metav1.APIResource, namespace string) ResourceInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceInterface is an API interface to a specific resource under a
|
|
||||||
// dynamic client.
|
|
||||||
type ResourceInterface interface {
|
|
||||||
// List returns a list of objects for this resource.
|
|
||||||
List(opts metav1.ListOptions) (runtime.Object, error)
|
|
||||||
// Get gets the resource with the specified name.
|
|
||||||
Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error)
|
|
||||||
// Delete deletes the resource with the specified name.
|
|
||||||
Delete(name string, opts *metav1.DeleteOptions) error
|
|
||||||
// DeleteCollection deletes a collection of objects.
|
|
||||||
DeleteCollection(deleteOptions *metav1.DeleteOptions, listOptions metav1.ListOptions) error
|
|
||||||
// Create creates the provided resource.
|
|
||||||
Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error)
|
|
||||||
// Update updates the provided resource.
|
|
||||||
Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error)
|
|
||||||
// Watch returns a watch.Interface that watches the resource.
|
|
||||||
Watch(opts metav1.ListOptions) (watch.Interface, error)
|
|
||||||
// Patch patches the provided resource.
|
|
||||||
Patch(name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client is a Kubernetes client that allows you to access metadata
|
|
||||||
// and manipulate metadata of a Kubernetes API group, and implements Interface.
|
|
||||||
type Client struct {
|
|
||||||
version schema.GroupVersion
|
|
||||||
delegate dynamic.Interface
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient returns a new client based on the passed in config. The
|
|
||||||
// codec is ignored, as the dynamic client uses it's own codec.
|
|
||||||
func NewClient(conf *restclient.Config, version schema.GroupVersion) (*Client, error) {
|
|
||||||
delegate, err := dynamic.NewForConfig(conf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Client{version: version, delegate: delegate}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resource returns an API interface to the specified resource for this client's
|
|
||||||
// group and version. If resource is not a namespaced resource, then namespace
|
|
||||||
// is ignored. The ResourceInterface inherits the parameter codec of c.
|
|
||||||
func (c *Client) Resource(resource *metav1.APIResource, namespace string) ResourceInterface {
|
|
||||||
resourceTokens := strings.SplitN(resource.Name, "/", 2)
|
|
||||||
subresources := []string{}
|
|
||||||
if len(resourceTokens) > 1 {
|
|
||||||
subresources = strings.Split(resourceTokens[1], "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(namespace) == 0 {
|
|
||||||
return oldResourceShim(c.delegate.Resource(c.version.WithResource(resourceTokens[0])), subresources)
|
|
||||||
}
|
|
||||||
return oldResourceShim(c.delegate.Resource(c.version.WithResource(resourceTokens[0])).Namespace(namespace), subresources)
|
|
||||||
}
|
|
||||||
|
|
||||||
// the old interfaces used the wrong type for lists. this fixes that
|
|
||||||
func oldResourceShim(in dynamic.ResourceInterface, subresources []string) ResourceInterface {
|
|
||||||
return oldResourceShimType{ResourceInterface: in, subresources: subresources}
|
|
||||||
}
|
|
||||||
|
|
||||||
type oldResourceShimType struct {
|
|
||||||
dynamic.ResourceInterface
|
|
||||||
subresources []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s oldResourceShimType) Create(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
|
||||||
return s.ResourceInterface.Create(obj, metav1.CreateOptions{}, s.subresources...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s oldResourceShimType) Update(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
|
||||||
return s.ResourceInterface.Update(obj, metav1.UpdateOptions{}, s.subresources...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s oldResourceShimType) Delete(name string, opts *metav1.DeleteOptions) error {
|
|
||||||
return s.ResourceInterface.Delete(name, opts, s.subresources...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s oldResourceShimType) Get(name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) {
|
|
||||||
return s.ResourceInterface.Get(name, opts, s.subresources...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s oldResourceShimType) List(opts metav1.ListOptions) (runtime.Object, error) {
|
|
||||||
return s.ResourceInterface.List(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s oldResourceShimType) Patch(name string, pt types.PatchType, data []byte) (*unstructured.Unstructured, error) {
|
|
||||||
return s.ResourceInterface.Patch(name, pt, data, metav1.PatchOptions{}, s.subresources...)
|
|
||||||
}
|
|
@ -1,122 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package deprecated_dynamic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
restclient "k8s.io/client-go/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ClientPool manages a pool of dynamic clients.
|
|
||||||
type ClientPool interface {
|
|
||||||
// ClientForGroupVersionResource returns a client configured for the specified groupVersionResource.
|
|
||||||
// Resource may be empty.
|
|
||||||
ClientForGroupVersionResource(resource schema.GroupVersionResource) (Interface, error)
|
|
||||||
// ClientForGroupVersionKind returns a client configured for the specified groupVersionKind.
|
|
||||||
// Kind may be empty.
|
|
||||||
ClientForGroupVersionKind(kind schema.GroupVersionKind) (Interface, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIPathResolverFunc knows how to convert a groupVersion to its API path. The Kind field is
|
|
||||||
// optional.
|
|
||||||
type APIPathResolverFunc func(kind schema.GroupVersionKind) string
|
|
||||||
|
|
||||||
// LegacyAPIPathResolverFunc can resolve paths properly with the legacy API.
|
|
||||||
func LegacyAPIPathResolverFunc(kind schema.GroupVersionKind) string {
|
|
||||||
if len(kind.Group) == 0 {
|
|
||||||
return "/api"
|
|
||||||
}
|
|
||||||
return "/apis"
|
|
||||||
}
|
|
||||||
|
|
||||||
// clientPoolImpl implements ClientPool and caches clients for the resource group versions
|
|
||||||
// is asked to retrieve. This type is thread safe.
|
|
||||||
type clientPoolImpl struct {
|
|
||||||
lock sync.RWMutex
|
|
||||||
config *restclient.Config
|
|
||||||
clients map[schema.GroupVersion]*Client
|
|
||||||
apiPathResolverFunc APIPathResolverFunc
|
|
||||||
mapper meta.RESTMapper
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClientPool returns a ClientPool from the specified config. It reuses clients for the same
|
|
||||||
// group version. It is expected this type may be wrapped by specific logic that special cases certain
|
|
||||||
// resources or groups.
|
|
||||||
func NewClientPool(config *restclient.Config, mapper meta.RESTMapper, apiPathResolverFunc APIPathResolverFunc) ClientPool {
|
|
||||||
confCopy := *config
|
|
||||||
|
|
||||||
return &clientPoolImpl{
|
|
||||||
config: &confCopy,
|
|
||||||
clients: map[schema.GroupVersion]*Client{},
|
|
||||||
apiPathResolverFunc: apiPathResolverFunc,
|
|
||||||
mapper: mapper,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instantiates a new dynamic client pool with the given config.
|
|
||||||
func NewDynamicClientPool(cfg *restclient.Config) ClientPool {
|
|
||||||
// restMapper is not needed when using LegacyAPIPathResolverFunc
|
|
||||||
emptyMapper := meta.MultiRESTMapper{}
|
|
||||||
return NewClientPool(cfg, emptyMapper, LegacyAPIPathResolverFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientForGroupVersionResource uses the provided RESTMapper to identify the appropriate resource. Resource may
|
|
||||||
// be empty. If no matching kind is found the underlying client for that group is still returned.
|
|
||||||
func (c *clientPoolImpl) ClientForGroupVersionResource(resource schema.GroupVersionResource) (Interface, error) {
|
|
||||||
kinds, err := c.mapper.KindsFor(resource)
|
|
||||||
if err != nil {
|
|
||||||
if meta.IsNoMatchError(err) {
|
|
||||||
return c.ClientForGroupVersionKind(schema.GroupVersionKind{Group: resource.Group, Version: resource.Version})
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c.ClientForGroupVersionKind(kinds[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientForGroupVersion returns a client for the specified groupVersion, creates one if none exists. Kind
|
|
||||||
// in the GroupVersionKind may be empty.
|
|
||||||
func (c *clientPoolImpl) ClientForGroupVersionKind(kind schema.GroupVersionKind) (Interface, error) {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
gv := kind.GroupVersion()
|
|
||||||
|
|
||||||
// do we have a client already configured?
|
|
||||||
if existingClient, found := c.clients[gv]; found {
|
|
||||||
return existingClient, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// avoid changing the original config
|
|
||||||
confCopy := *c.config
|
|
||||||
conf := &confCopy
|
|
||||||
|
|
||||||
// we need to set the api path based on group version, if no group, default to legacy path
|
|
||||||
conf.APIPath = c.apiPathResolverFunc(kind)
|
|
||||||
|
|
||||||
// we need to make a client
|
|
||||||
conf.GroupVersion = &gv
|
|
||||||
|
|
||||||
dynamicClient, err := NewClient(conf, gv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.clients[gv] = dynamicClient
|
|
||||||
return dynamicClient, nil
|
|
||||||
}
|
|
@ -1,624 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package deprecated_dynamic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
|
||||||
restclient "k8s.io/client-go/rest"
|
|
||||||
restclientwatch "k8s.io/client-go/rest/watch"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getJSON(version, kind, name string) []byte {
|
|
||||||
return []byte(fmt.Sprintf(`{"apiVersion": %q, "kind": %q, "metadata": {"name": %q}}`, version, kind, name))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getListJSON(version, kind string, items ...[]byte) []byte {
|
|
||||||
json := fmt.Sprintf(`{"apiVersion": %q, "kind": %q, "items": [%s]}`,
|
|
||||||
version, kind, bytes.Join(items, []byte(",")))
|
|
||||||
return []byte(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getObject(version, kind, name string) *unstructured.Unstructured {
|
|
||||||
return &unstructured.Unstructured{
|
|
||||||
Object: map[string]interface{}{
|
|
||||||
"apiVersion": version,
|
|
||||||
"kind": kind,
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getClientServer(gv *schema.GroupVersion, h func(http.ResponseWriter, *http.Request)) (Interface, *httptest.Server, error) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(h))
|
|
||||||
cl, err := NewClient(&restclient.Config{
|
|
||||||
Host: srv.URL,
|
|
||||||
ContentConfig: restclient.ContentConfig{GroupVersion: gv},
|
|
||||||
}, *gv)
|
|
||||||
if err != nil {
|
|
||||||
srv.Close()
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return cl, srv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
|
||||||
tcs := []struct {
|
|
||||||
name string
|
|
||||||
namespace string
|
|
||||||
path string
|
|
||||||
resp []byte
|
|
||||||
want *unstructured.UnstructuredList
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "normal_list",
|
|
||||||
path: "/apis/gtest/vtest/rtest",
|
|
||||||
resp: getListJSON("vTest", "rTestList",
|
|
||||||
getJSON("vTest", "rTest", "item1"),
|
|
||||||
getJSON("vTest", "rTest", "item2")),
|
|
||||||
want: &unstructured.UnstructuredList{
|
|
||||||
Object: map[string]interface{}{
|
|
||||||
"apiVersion": "vTest",
|
|
||||||
"kind": "rTestList",
|
|
||||||
},
|
|
||||||
Items: []unstructured.Unstructured{
|
|
||||||
*getObject("vTest", "rTest", "item1"),
|
|
||||||
*getObject("vTest", "rTest", "item2"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "namespaced_list",
|
|
||||||
namespace: "nstest",
|
|
||||||
path: "/apis/gtest/vtest/namespaces/nstest/rtest",
|
|
||||||
resp: getListJSON("vTest", "rTestList",
|
|
||||||
getJSON("vTest", "rTest", "item1"),
|
|
||||||
getJSON("vTest", "rTest", "item2")),
|
|
||||||
want: &unstructured.UnstructuredList{
|
|
||||||
Object: map[string]interface{}{
|
|
||||||
"apiVersion": "vTest",
|
|
||||||
"kind": "rTestList",
|
|
||||||
},
|
|
||||||
Items: []unstructured.Unstructured{
|
|
||||||
*getObject("vTest", "rTest", "item1"),
|
|
||||||
*getObject("vTest", "rTest", "item2"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range tcs {
|
|
||||||
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
|
||||||
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
|
|
||||||
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "GET" {
|
|
||||||
t.Errorf("List(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Path != tc.path {
|
|
||||||
t.Errorf("List(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
|
|
||||||
w.Write(tc.resp)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when creating client: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
got, err := cl.Resource(resource, tc.namespace).List(metav1.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when listing %q: %v", tc.name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, tc.want) {
|
|
||||||
t.Errorf("List(%q) want: %v\ngot: %v", tc.name, tc.want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
|
||||||
tcs := []struct {
|
|
||||||
resource string
|
|
||||||
namespace string
|
|
||||||
name string
|
|
||||||
path string
|
|
||||||
resp []byte
|
|
||||||
want *unstructured.Unstructured
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
resource: "rtest",
|
|
||||||
name: "normal_get",
|
|
||||||
path: "/apis/gtest/vtest/rtest/normal_get",
|
|
||||||
resp: getJSON("vTest", "rTest", "normal_get"),
|
|
||||||
want: getObject("vTest", "rTest", "normal_get"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resource: "rtest",
|
|
||||||
namespace: "nstest",
|
|
||||||
name: "namespaced_get",
|
|
||||||
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_get",
|
|
||||||
resp: getJSON("vTest", "rTest", "namespaced_get"),
|
|
||||||
want: getObject("vTest", "rTest", "namespaced_get"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resource: "rtest/srtest",
|
|
||||||
name: "normal_subresource_get",
|
|
||||||
path: "/apis/gtest/vtest/rtest/normal_subresource_get/srtest",
|
|
||||||
resp: getJSON("vTest", "srTest", "normal_subresource_get"),
|
|
||||||
want: getObject("vTest", "srTest", "normal_subresource_get"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resource: "rtest/srtest",
|
|
||||||
namespace: "nstest",
|
|
||||||
name: "namespaced_subresource_get",
|
|
||||||
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_get/srtest",
|
|
||||||
resp: getJSON("vTest", "srTest", "namespaced_subresource_get"),
|
|
||||||
want: getObject("vTest", "srTest", "namespaced_subresource_get"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range tcs {
|
|
||||||
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
|
||||||
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
|
|
||||||
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "GET" {
|
|
||||||
t.Errorf("Get(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Path != tc.path {
|
|
||||||
t.Errorf("Get(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
|
|
||||||
w.Write(tc.resp)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when creating client: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
got, err := cl.Resource(resource, tc.namespace).Get(tc.name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when getting %q: %v", tc.name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, tc.want) {
|
|
||||||
t.Errorf("Get(%q) want: %v\ngot: %v", tc.name, tc.want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDelete(t *testing.T) {
|
|
||||||
background := metav1.DeletePropagationBackground
|
|
||||||
uid := types.UID("uid")
|
|
||||||
|
|
||||||
statusOK := &metav1.Status{
|
|
||||||
TypeMeta: metav1.TypeMeta{Kind: "Status"},
|
|
||||||
Status: metav1.StatusSuccess,
|
|
||||||
}
|
|
||||||
tcs := []struct {
|
|
||||||
namespace string
|
|
||||||
name string
|
|
||||||
path string
|
|
||||||
deleteOptions *metav1.DeleteOptions
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "normal_delete",
|
|
||||||
path: "/apis/gtest/vtest/rtest/normal_delete",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
namespace: "nstest",
|
|
||||||
name: "namespaced_delete",
|
|
||||||
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_delete",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
namespace: "nstest",
|
|
||||||
name: "namespaced_delete_with_options",
|
|
||||||
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_delete_with_options",
|
|
||||||
deleteOptions: &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &uid}, PropagationPolicy: &background},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range tcs {
|
|
||||||
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
|
||||||
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
|
|
||||||
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "DELETE" {
|
|
||||||
t.Errorf("Delete(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Path != tc.path {
|
|
||||||
t.Errorf("Delete(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
|
|
||||||
unstructured.UnstructuredJSONScheme.Encode(statusOK, w)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when creating client: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
err = cl.Resource(resource, tc.namespace).Delete(tc.name, tc.deleteOptions)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when deleting %q: %v", tc.name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteCollection(t *testing.T) {
|
|
||||||
statusOK := &metav1.Status{
|
|
||||||
TypeMeta: metav1.TypeMeta{Kind: "Status"},
|
|
||||||
Status: metav1.StatusSuccess,
|
|
||||||
}
|
|
||||||
tcs := []struct {
|
|
||||||
namespace string
|
|
||||||
name string
|
|
||||||
path string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "normal_delete_collection",
|
|
||||||
path: "/apis/gtest/vtest/rtest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
namespace: "nstest",
|
|
||||||
name: "namespaced_delete_collection",
|
|
||||||
path: "/apis/gtest/vtest/namespaces/nstest/rtest",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range tcs {
|
|
||||||
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
|
||||||
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
|
|
||||||
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "DELETE" {
|
|
||||||
t.Errorf("DeleteCollection(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Path != tc.path {
|
|
||||||
t.Errorf("DeleteCollection(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
|
|
||||||
unstructured.UnstructuredJSONScheme.Encode(statusOK, w)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when creating client: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
err = cl.Resource(resource, tc.namespace).DeleteCollection(nil, metav1.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when deleting collection %q: %v", tc.name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
|
||||||
tcs := []struct {
|
|
||||||
resource string
|
|
||||||
name string
|
|
||||||
namespace string
|
|
||||||
obj *unstructured.Unstructured
|
|
||||||
path string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
resource: "rtest",
|
|
||||||
name: "normal_create",
|
|
||||||
path: "/apis/gtest/vtest/rtest",
|
|
||||||
obj: getObject("gtest/vTest", "rTest", "normal_create"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resource: "rtest",
|
|
||||||
name: "namespaced_create",
|
|
||||||
namespace: "nstest",
|
|
||||||
path: "/apis/gtest/vtest/namespaces/nstest/rtest",
|
|
||||||
obj: getObject("gtest/vTest", "rTest", "namespaced_create"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range tcs {
|
|
||||||
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
|
||||||
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
|
|
||||||
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "POST" {
|
|
||||||
t.Errorf("Create(%q) got HTTP method %s. wanted POST", tc.name, r.Method)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Path != tc.path {
|
|
||||||
t.Errorf("Create(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
|
|
||||||
data, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Create(%q) unexpected error reading body: %v", tc.name, err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(data)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when creating client: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
got, err := cl.Resource(resource, tc.namespace).Create(tc.obj)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when creating %q: %v", tc.name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, tc.obj) {
|
|
||||||
t.Errorf("Create(%q) want: %v\ngot: %v", tc.name, tc.obj, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
|
||||||
tcs := []struct {
|
|
||||||
resource string
|
|
||||||
name string
|
|
||||||
namespace string
|
|
||||||
obj *unstructured.Unstructured
|
|
||||||
path string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
resource: "rtest",
|
|
||||||
name: "normal_update",
|
|
||||||
path: "/apis/gtest/vtest/rtest/normal_update",
|
|
||||||
obj: getObject("gtest/vTest", "rTest", "normal_update"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resource: "rtest",
|
|
||||||
name: "namespaced_update",
|
|
||||||
namespace: "nstest",
|
|
||||||
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_update",
|
|
||||||
obj: getObject("gtest/vTest", "rTest", "namespaced_update"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resource: "rtest/srtest",
|
|
||||||
name: "normal_subresource_update",
|
|
||||||
path: "/apis/gtest/vtest/rtest/normal_update/srtest",
|
|
||||||
obj: getObject("gtest/vTest", "srTest", "normal_update"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resource: "rtest/srtest",
|
|
||||||
name: "namespaced_subresource_update",
|
|
||||||
namespace: "nstest",
|
|
||||||
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_update/srtest",
|
|
||||||
obj: getObject("gtest/vTest", "srTest", "namespaced_update"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range tcs {
|
|
||||||
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
|
||||||
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
|
|
||||||
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "PUT" {
|
|
||||||
t.Errorf("Update(%q) got HTTP method %s. wanted PUT", tc.name, r.Method)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Path != tc.path {
|
|
||||||
t.Errorf("Update(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
|
|
||||||
data, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Update(%q) unexpected error reading body: %v", tc.name, err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(data)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when creating client: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
got, err := cl.Resource(resource, tc.namespace).Update(tc.obj)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when updating %q: %v", tc.name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, tc.obj) {
|
|
||||||
t.Errorf("Update(%q) want: %v\ngot: %v", tc.name, tc.obj, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWatch(t *testing.T) {
|
|
||||||
tcs := []struct {
|
|
||||||
name string
|
|
||||||
namespace string
|
|
||||||
events []watch.Event
|
|
||||||
path string
|
|
||||||
query string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "normal_watch",
|
|
||||||
path: "/apis/gtest/vtest/rtest",
|
|
||||||
query: "watch=true",
|
|
||||||
events: []watch.Event{
|
|
||||||
{Type: watch.Added, Object: getObject("gtest/vTest", "rTest", "normal_watch")},
|
|
||||||
{Type: watch.Modified, Object: getObject("gtest/vTest", "rTest", "normal_watch")},
|
|
||||||
{Type: watch.Deleted, Object: getObject("gtest/vTest", "rTest", "normal_watch")},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "namespaced_watch",
|
|
||||||
namespace: "nstest",
|
|
||||||
path: "/apis/gtest/vtest/namespaces/nstest/rtest",
|
|
||||||
query: "watch=true",
|
|
||||||
events: []watch.Event{
|
|
||||||
{Type: watch.Added, Object: getObject("gtest/vTest", "rTest", "namespaced_watch")},
|
|
||||||
{Type: watch.Modified, Object: getObject("gtest/vTest", "rTest", "namespaced_watch")},
|
|
||||||
{Type: watch.Deleted, Object: getObject("gtest/vTest", "rTest", "namespaced_watch")},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range tcs {
|
|
||||||
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
|
||||||
resource := &metav1.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0}
|
|
||||||
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "GET" {
|
|
||||||
t.Errorf("Watch(%q) got HTTP method %s. wanted GET", tc.name, r.Method)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Path != tc.path {
|
|
||||||
t.Errorf("Watch(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
|
|
||||||
}
|
|
||||||
if r.URL.RawQuery != tc.query {
|
|
||||||
t.Errorf("Watch(%q) got query %s. wanted %s", tc.name, r.URL.RawQuery, tc.query)
|
|
||||||
}
|
|
||||||
|
|
||||||
codec := unstructured.UnstructuredJSONScheme
|
|
||||||
enc := restclientwatch.NewEncoder(streaming.NewEncoder(w, codec), codec)
|
|
||||||
for _, e := range tc.events {
|
|
||||||
enc.Encode(&e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when creating client: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
watcher, err := cl.Resource(resource, tc.namespace).Watch(metav1.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when watching %q: %v", tc.name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, want := range tc.events {
|
|
||||||
got := <-watcher.ResultChan()
|
|
||||||
if !reflect.DeepEqual(got, want) {
|
|
||||||
t.Errorf("Watch(%q) want: %v\ngot: %v", tc.name, want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPatch(t *testing.T) {
|
|
||||||
tcs := []struct {
|
|
||||||
resource string
|
|
||||||
name string
|
|
||||||
namespace string
|
|
||||||
patch []byte
|
|
||||||
want *unstructured.Unstructured
|
|
||||||
path string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
resource: "rtest",
|
|
||||||
name: "normal_patch",
|
|
||||||
path: "/apis/gtest/vtest/rtest/normal_patch",
|
|
||||||
patch: getJSON("gtest/vTest", "rTest", "normal_patch"),
|
|
||||||
want: getObject("gtest/vTest", "rTest", "normal_patch"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resource: "rtest",
|
|
||||||
name: "namespaced_patch",
|
|
||||||
namespace: "nstest",
|
|
||||||
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_patch",
|
|
||||||
patch: getJSON("gtest/vTest", "rTest", "namespaced_patch"),
|
|
||||||
want: getObject("gtest/vTest", "rTest", "namespaced_patch"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resource: "rtest/srtest",
|
|
||||||
name: "normal_subresource_patch",
|
|
||||||
path: "/apis/gtest/vtest/rtest/normal_subresource_patch/srtest",
|
|
||||||
patch: getJSON("gtest/vTest", "srTest", "normal_subresource_patch"),
|
|
||||||
want: getObject("gtest/vTest", "srTest", "normal_subresource_patch"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resource: "rtest/srtest",
|
|
||||||
name: "namespaced_subresource_patch",
|
|
||||||
namespace: "nstest",
|
|
||||||
path: "/apis/gtest/vtest/namespaces/nstest/rtest/namespaced_subresource_patch/srtest",
|
|
||||||
patch: getJSON("gtest/vTest", "srTest", "namespaced_subresource_patch"),
|
|
||||||
want: getObject("gtest/vTest", "srTest", "namespaced_subresource_patch"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range tcs {
|
|
||||||
gv := &schema.GroupVersion{Group: "gtest", Version: "vtest"}
|
|
||||||
resource := &metav1.APIResource{Name: tc.resource, Namespaced: len(tc.namespace) != 0}
|
|
||||||
cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "PATCH" {
|
|
||||||
t.Errorf("Patch(%q) got HTTP method %s. wanted PATCH", tc.name, r.Method)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Path != tc.path {
|
|
||||||
t.Errorf("Patch(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
content := r.Header.Get("Content-Type")
|
|
||||||
if content != string(types.StrategicMergePatchType) {
|
|
||||||
t.Errorf("Patch(%q) got Content-Type %s. wanted %s", tc.name, content, types.StrategicMergePatchType)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Patch(%q) unexpected error reading body: %v", tc.name, err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Write(data)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when creating client: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
got, err := cl.Resource(resource, tc.namespace).Patch(tc.name, types.StrategicMergePatchType, tc.patch)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error when patching %q: %v", tc.name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, tc.want) {
|
|
||||||
t.Errorf("Patch(%q) want: %v\ngot: %v", tc.name, tc.want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -40,8 +40,6 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/versioning:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
||||||
|
@ -537,6 +537,8 @@ func TestWatch(t *testing.T) {
|
|||||||
t.Errorf("Watch(%q) got query %s. wanted %s", tc.name, r.URL.RawQuery, tc.query)
|
t.Errorf("Watch(%q) got query %s. wanted %s", tc.name, r.URL.RawQuery, tc.query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
enc := restclientwatch.NewEncoder(streaming.NewEncoder(w, unstructured.UnstructuredJSONScheme), unstructured.UnstructuredJSONScheme)
|
enc := restclientwatch.NewEncoder(streaming.NewEncoder(w, unstructured.UnstructuredJSONScheme), unstructured.UnstructuredJSONScheme)
|
||||||
for _, e := range tc.events {
|
for _, e := range tc.events {
|
||||||
enc.Encode(&e)
|
enc.Encode(&e)
|
||||||
|
@ -18,11 +18,11 @@ package dynamic
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer/versioning"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var watchScheme = runtime.NewScheme()
|
var watchScheme = runtime.NewScheme()
|
||||||
@ -41,37 +41,6 @@ func init() {
|
|||||||
metav1.AddToGroupVersion(deleteScheme, versionV1)
|
metav1.AddToGroupVersion(deleteScheme, versionV1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var watchJsonSerializerInfo = runtime.SerializerInfo{
|
|
||||||
MediaType: "application/json",
|
|
||||||
MediaTypeType: "application",
|
|
||||||
MediaTypeSubType: "json",
|
|
||||||
EncodesAsText: true,
|
|
||||||
Serializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, false),
|
|
||||||
PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, true),
|
|
||||||
StreamSerializer: &runtime.StreamSerializerInfo{
|
|
||||||
EncodesAsText: true,
|
|
||||||
Serializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, false),
|
|
||||||
Framer: json.Framer,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// watchNegotiatedSerializer is used to read the wrapper of the watch stream
|
|
||||||
type watchNegotiatedSerializer struct{}
|
|
||||||
|
|
||||||
var watchNegotiatedSerializerInstance = watchNegotiatedSerializer{}
|
|
||||||
|
|
||||||
func (s watchNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
|
|
||||||
return []runtime.SerializerInfo{watchJsonSerializerInfo}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s watchNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
|
|
||||||
return versioning.NewDefaultingCodecForScheme(watchScheme, encoder, nil, gv, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s watchNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
|
|
||||||
return versioning.NewDefaultingCodecForScheme(watchScheme, nil, decoder, nil, gv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// basicNegotiatedSerializer is used to handle discovery and error handling serialization
|
// basicNegotiatedSerializer is used to handle discovery and error handling serialization
|
||||||
type basicNegotiatedSerializer struct{}
|
type basicNegotiatedSerializer struct{}
|
||||||
|
|
||||||
@ -82,8 +51,8 @@ func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInf
|
|||||||
MediaTypeType: "application",
|
MediaTypeType: "application",
|
||||||
MediaTypeSubType: "json",
|
MediaTypeSubType: "json",
|
||||||
EncodesAsText: true,
|
EncodesAsText: true,
|
||||||
Serializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, false),
|
Serializer: json.NewSerializer(json.DefaultMetaFactory, unstructuredCreater{basicScheme}, unstructuredTyper{basicScheme}, false),
|
||||||
PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, true),
|
PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, unstructuredCreater{basicScheme}, unstructuredTyper{basicScheme}, true),
|
||||||
StreamSerializer: &runtime.StreamSerializerInfo{
|
StreamSerializer: &runtime.StreamSerializerInfo{
|
||||||
EncodesAsText: true,
|
EncodesAsText: true,
|
||||||
Serializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, false),
|
Serializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, false),
|
||||||
@ -94,9 +63,46 @@ func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInf
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s basicNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
|
func (s basicNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
|
||||||
return versioning.NewDefaultingCodecForScheme(watchScheme, encoder, nil, gv, nil)
|
return runtime.WithVersionEncoder{
|
||||||
|
Version: gv,
|
||||||
|
Encoder: encoder,
|
||||||
|
ObjectTyper: unstructuredTyper{basicScheme},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s basicNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
|
func (s basicNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
|
||||||
return versioning.NewDefaultingCodecForScheme(watchScheme, nil, decoder, nil, gv)
|
return decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
type unstructuredCreater struct {
|
||||||
|
nested runtime.ObjectCreater
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c unstructuredCreater) New(kind schema.GroupVersionKind) (runtime.Object, error) {
|
||||||
|
out, err := c.nested.New(kind)
|
||||||
|
if err == nil {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
out = &unstructured.Unstructured{}
|
||||||
|
out.GetObjectKind().SetGroupVersionKind(kind)
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type unstructuredTyper struct {
|
||||||
|
nested runtime.ObjectTyper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t unstructuredTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
|
||||||
|
kinds, unversioned, err := t.nested.ObjectKinds(obj)
|
||||||
|
if err == nil {
|
||||||
|
return kinds, unversioned, nil
|
||||||
|
}
|
||||||
|
if _, ok := obj.(runtime.Unstructured); ok && !obj.GetObjectKind().GroupVersionKind().Empty() {
|
||||||
|
return []schema.GroupVersionKind{obj.GetObjectKind().GroupVersionKind()}, false, nil
|
||||||
|
}
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t unstructuredTyper) Recognizes(gvk schema.GroupVersionKind) bool {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
@ -18,14 +18,12 @@ package dynamic
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
@ -282,31 +280,10 @@ func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.Uns
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
|
func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
|
||||||
internalGV := schema.GroupVersions{
|
|
||||||
{Group: c.resource.Group, Version: runtime.APIVersionInternal},
|
|
||||||
// always include the legacy group as a decoding target to handle non-error `Status` return types
|
|
||||||
{Group: "", Version: runtime.APIVersionInternal},
|
|
||||||
}
|
|
||||||
s := &rest.Serializers{
|
|
||||||
Encoder: watchNegotiatedSerializerInstance.EncoderForVersion(watchJsonSerializerInfo.Serializer, c.resource.GroupVersion()),
|
|
||||||
Decoder: watchNegotiatedSerializerInstance.DecoderToVersion(watchJsonSerializerInfo.Serializer, internalGV),
|
|
||||||
|
|
||||||
RenegotiatedDecoder: func(contentType string, params map[string]string) (runtime.Decoder, error) {
|
|
||||||
return watchNegotiatedSerializerInstance.DecoderToVersion(watchJsonSerializerInfo.Serializer, internalGV), nil
|
|
||||||
},
|
|
||||||
StreamingSerializer: watchJsonSerializerInfo.StreamSerializer.Serializer,
|
|
||||||
Framer: watchJsonSerializerInfo.StreamSerializer.Framer,
|
|
||||||
}
|
|
||||||
|
|
||||||
wrappedDecoderFn := func(body io.ReadCloser) streaming.Decoder {
|
|
||||||
framer := s.Framer.NewFrameReader(body)
|
|
||||||
return streaming.NewDecoder(framer, s.StreamingSerializer)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.Watch = true
|
opts.Watch = true
|
||||||
return c.client.client.Get().AbsPath(c.makeURLSegments("")...).
|
return c.client.client.Get().AbsPath(c.makeURLSegments("")...).
|
||||||
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
|
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
|
||||||
WatchWithSpecificDecoders(wrappedDecoderFn, unstructured.UnstructuredJSONScheme)
|
Watch()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) {
|
func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) {
|
||||||
|
@ -317,7 +317,6 @@ func (c *metadataResourceClient) List(opts metav1.ListOptions) (*metav1.PartialO
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("incoming object is incorrect type %T", obj)
|
return nil, fmt.Errorf("incoming object is incorrect type %T", obj)
|
||||||
}
|
}
|
||||||
fmt.Printf("DEBUG: %#v\n", inputList)
|
|
||||||
|
|
||||||
list := &metav1.PartialObjectMetadataList{
|
list := &metav1.PartialObjectMetadataList{
|
||||||
ListMeta: inputList.ListMeta,
|
ListMeta: inputList.ListMeta,
|
||||||
|
@ -17,8 +17,6 @@ limitations under the License.
|
|||||||
package rest
|
package rest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"mime"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@ -51,6 +49,28 @@ type Interface interface {
|
|||||||
APIVersion() schema.GroupVersion
|
APIVersion() schema.GroupVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientContentConfig controls how RESTClient communicates with the server.
|
||||||
|
//
|
||||||
|
// TODO: ContentConfig will be updated to accept a Negotiator instead of a
|
||||||
|
// NegotiatedSerializer and NegotiatedSerializer will be removed.
|
||||||
|
type ClientContentConfig struct {
|
||||||
|
// AcceptContentTypes specifies the types the client will accept and is optional.
|
||||||
|
// If not set, ContentType will be used to define the Accept header
|
||||||
|
AcceptContentTypes string
|
||||||
|
// ContentType specifies the wire format used to communicate with the server.
|
||||||
|
// This value will be set as the Accept header on requests made to the server if
|
||||||
|
// AcceptContentTypes is not set, and as the default content type on any object
|
||||||
|
// sent to the server. If not set, "application/json" is used.
|
||||||
|
ContentType string
|
||||||
|
// GroupVersion is the API version to talk to. Must be provided when initializing
|
||||||
|
// a RESTClient directly. When initializing a Client, will be set with the default
|
||||||
|
// code version. This is used as the default group version for VersionedParams.
|
||||||
|
GroupVersion schema.GroupVersion
|
||||||
|
// Negotiator is used for obtaining encoders and decoders for multiple
|
||||||
|
// supported media types.
|
||||||
|
Negotiator runtime.ClientNegotiator
|
||||||
|
}
|
||||||
|
|
||||||
// RESTClient imposes common Kubernetes API conventions on a set of resource paths.
|
// RESTClient imposes common Kubernetes API conventions on a set of resource paths.
|
||||||
// The baseURL is expected to point to an HTTP or HTTPS path that is the parent
|
// The baseURL is expected to point to an HTTP or HTTPS path that is the parent
|
||||||
// of one or more resources. The server should return a decodable API resource
|
// of one or more resources. The server should return a decodable API resource
|
||||||
@ -64,34 +84,27 @@ type RESTClient struct {
|
|||||||
// versionedAPIPath is a path segment connecting the base URL to the resource root
|
// versionedAPIPath is a path segment connecting the base URL to the resource root
|
||||||
versionedAPIPath string
|
versionedAPIPath string
|
||||||
|
|
||||||
// contentConfig is the information used to communicate with the server.
|
// content describes how a RESTClient encodes and decodes responses.
|
||||||
contentConfig ContentConfig
|
content ClientContentConfig
|
||||||
|
|
||||||
// serializers contain all serializers for underlying content type.
|
|
||||||
serializers Serializers
|
|
||||||
|
|
||||||
// creates BackoffManager that is passed to requests.
|
// creates BackoffManager that is passed to requests.
|
||||||
createBackoffMgr func() BackoffManager
|
createBackoffMgr func() BackoffManager
|
||||||
|
|
||||||
// TODO extract this into a wrapper interface via the RESTClient interface in kubectl.
|
// rateLimiter is shared among all requests created by this client unless specifically
|
||||||
Throttle flowcontrol.RateLimiter
|
// overridden.
|
||||||
|
rateLimiter flowcontrol.RateLimiter
|
||||||
|
|
||||||
// Set specific behavior of the client. If not set http.DefaultClient will be used.
|
// Set specific behavior of the client. If not set http.DefaultClient will be used.
|
||||||
Client *http.Client
|
Client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
type Serializers struct {
|
|
||||||
Encoder runtime.Encoder
|
|
||||||
Decoder runtime.Decoder
|
|
||||||
StreamingSerializer runtime.Serializer
|
|
||||||
Framer runtime.Framer
|
|
||||||
RenegotiatedDecoder func(contentType string, params map[string]string) (runtime.Decoder, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRESTClient creates a new RESTClient. This client performs generic REST functions
|
// NewRESTClient creates a new RESTClient. This client performs generic REST functions
|
||||||
// such as Get, Put, Post, and Delete on specified paths. Codec controls encoding and
|
// such as Get, Put, Post, and Delete on specified paths.
|
||||||
// decoding of responses from the server.
|
func NewRESTClient(baseURL *url.URL, versionedAPIPath string, config ClientContentConfig, rateLimiter flowcontrol.RateLimiter, client *http.Client) (*RESTClient, error) {
|
||||||
func NewRESTClient(baseURL *url.URL, versionedAPIPath string, config ContentConfig, maxQPS float32, maxBurst int, rateLimiter flowcontrol.RateLimiter, client *http.Client) (*RESTClient, error) {
|
if len(config.ContentType) == 0 {
|
||||||
|
config.ContentType = "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
base := *baseURL
|
base := *baseURL
|
||||||
if !strings.HasSuffix(base.Path, "/") {
|
if !strings.HasSuffix(base.Path, "/") {
|
||||||
base.Path += "/"
|
base.Path += "/"
|
||||||
@ -99,31 +112,14 @@ func NewRESTClient(baseURL *url.URL, versionedAPIPath string, config ContentConf
|
|||||||
base.RawQuery = ""
|
base.RawQuery = ""
|
||||||
base.Fragment = ""
|
base.Fragment = ""
|
||||||
|
|
||||||
if config.GroupVersion == nil {
|
|
||||||
config.GroupVersion = &schema.GroupVersion{}
|
|
||||||
}
|
|
||||||
if len(config.ContentType) == 0 {
|
|
||||||
config.ContentType = "application/json"
|
|
||||||
}
|
|
||||||
serializers, err := createSerializers(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var throttle flowcontrol.RateLimiter
|
|
||||||
if maxQPS > 0 && rateLimiter == nil {
|
|
||||||
throttle = flowcontrol.NewTokenBucketRateLimiter(maxQPS, maxBurst)
|
|
||||||
} else if rateLimiter != nil {
|
|
||||||
throttle = rateLimiter
|
|
||||||
}
|
|
||||||
return &RESTClient{
|
return &RESTClient{
|
||||||
base: &base,
|
base: &base,
|
||||||
versionedAPIPath: versionedAPIPath,
|
versionedAPIPath: versionedAPIPath,
|
||||||
contentConfig: config,
|
content: config,
|
||||||
serializers: *serializers,
|
|
||||||
createBackoffMgr: readExpBackoffConfig,
|
createBackoffMgr: readExpBackoffConfig,
|
||||||
Throttle: throttle,
|
rateLimiter: rateLimiter,
|
||||||
Client: client,
|
|
||||||
|
Client: client,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +128,7 @@ func (c *RESTClient) GetRateLimiter() flowcontrol.RateLimiter {
|
|||||||
if c == nil {
|
if c == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return c.Throttle
|
return c.rateLimiter
|
||||||
}
|
}
|
||||||
|
|
||||||
// readExpBackoffConfig handles the internal logic of determining what the
|
// readExpBackoffConfig handles the internal logic of determining what the
|
||||||
@ -153,58 +149,6 @@ func readExpBackoffConfig() BackoffManager {
|
|||||||
time.Duration(backoffDurationInt)*time.Second)}
|
time.Duration(backoffDurationInt)*time.Second)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// createSerializers creates all necessary serializers for given contentType.
|
|
||||||
// TODO: the negotiated serializer passed to this method should probably return
|
|
||||||
// serializers that control decoding and versioning without this package
|
|
||||||
// being aware of the types. Depends on whether RESTClient must deal with
|
|
||||||
// generic infrastructure.
|
|
||||||
func createSerializers(config ContentConfig) (*Serializers, error) {
|
|
||||||
mediaTypes := config.NegotiatedSerializer.SupportedMediaTypes()
|
|
||||||
contentType := config.ContentType
|
|
||||||
mediaType, _, err := mime.ParseMediaType(contentType)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("the content type specified in the client configuration is not recognized: %v", err)
|
|
||||||
}
|
|
||||||
info, ok := runtime.SerializerInfoForMediaType(mediaTypes, mediaType)
|
|
||||||
if !ok {
|
|
||||||
if len(contentType) != 0 || len(mediaTypes) == 0 {
|
|
||||||
return nil, fmt.Errorf("no serializers registered for %s", contentType)
|
|
||||||
}
|
|
||||||
info = mediaTypes[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
internalGV := schema.GroupVersions{
|
|
||||||
{
|
|
||||||
Group: config.GroupVersion.Group,
|
|
||||||
Version: runtime.APIVersionInternal,
|
|
||||||
},
|
|
||||||
// always include the legacy group as a decoding target to handle non-error `Status` return types
|
|
||||||
{
|
|
||||||
Group: "",
|
|
||||||
Version: runtime.APIVersionInternal,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &Serializers{
|
|
||||||
Encoder: config.NegotiatedSerializer.EncoderForVersion(info.Serializer, *config.GroupVersion),
|
|
||||||
Decoder: config.NegotiatedSerializer.DecoderToVersion(info.Serializer, internalGV),
|
|
||||||
|
|
||||||
RenegotiatedDecoder: func(contentType string, params map[string]string) (runtime.Decoder, error) {
|
|
||||||
info, ok := runtime.SerializerInfoForMediaType(mediaTypes, contentType)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("serializer for %s not registered", contentType)
|
|
||||||
}
|
|
||||||
return config.NegotiatedSerializer.DecoderToVersion(info.Serializer, internalGV), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if info.StreamSerializer != nil {
|
|
||||||
s.StreamingSerializer = info.StreamSerializer.Serializer
|
|
||||||
s.Framer = info.StreamSerializer.Framer
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verb begins a request with a verb (GET, POST, PUT, DELETE).
|
// Verb begins a request with a verb (GET, POST, PUT, DELETE).
|
||||||
//
|
//
|
||||||
// Example usage of RESTClient's request building interface:
|
// Example usage of RESTClient's request building interface:
|
||||||
@ -219,12 +163,7 @@ func createSerializers(config ContentConfig) (*Serializers, error) {
|
|||||||
// list, ok := resp.(*api.PodList)
|
// list, ok := resp.(*api.PodList)
|
||||||
//
|
//
|
||||||
func (c *RESTClient) Verb(verb string) *Request {
|
func (c *RESTClient) Verb(verb string) *Request {
|
||||||
backoff := c.createBackoffMgr()
|
return NewRequest(c).Verb(verb)
|
||||||
|
|
||||||
if c.Client == nil {
|
|
||||||
return NewRequest(nil, verb, c.base, c.versionedAPIPath, c.contentConfig, c.serializers, backoff, c.Throttle, 0)
|
|
||||||
}
|
|
||||||
return NewRequest(c.Client, verb, c.base, c.versionedAPIPath, c.contentConfig, c.serializers, backoff, c.Throttle, c.Client.Timeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post begins a POST request. Short for c.Verb("POST").
|
// Post begins a POST request. Short for c.Verb("POST").
|
||||||
@ -254,5 +193,5 @@ func (c *RESTClient) Delete() *Request {
|
|||||||
|
|
||||||
// APIVersion returns the APIVersion this RESTClient is expected to use.
|
// APIVersion returns the APIVersion this RESTClient is expected to use.
|
||||||
func (c *RESTClient) APIVersion() schema.GroupVersion {
|
func (c *RESTClient) APIVersion() schema.GroupVersion {
|
||||||
return *c.contentConfig.GroupVersion
|
return c.content.GroupVersion
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ import (
|
|||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
v1beta1 "k8s.io/api/extensions/v1beta1"
|
v1beta1 "k8s.io/api/extensions/v1beta1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -57,12 +57,14 @@ func TestSerializer(t *testing.T) {
|
|||||||
NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
|
NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
|
||||||
}
|
}
|
||||||
|
|
||||||
serializer, err := createSerializers(contentConfig)
|
n := runtime.NewClientNegotiator(contentConfig.NegotiatedSerializer, gv)
|
||||||
|
d, err := n.Decoder("application/json", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// bytes based on actual return from API server when encoding an "unversioned" object
|
// bytes based on actual return from API server when encoding an "unversioned" object
|
||||||
obj, err := runtime.Decode(serializer.Decoder, []byte(`{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Success"}`))
|
obj, err := runtime.Decode(d, []byte(`{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Success"}`))
|
||||||
t.Log(obj)
|
t.Log(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -269,6 +269,9 @@ type ContentConfig struct {
|
|||||||
GroupVersion *schema.GroupVersion
|
GroupVersion *schema.GroupVersion
|
||||||
// NegotiatedSerializer is used for obtaining encoders and decoders for multiple
|
// NegotiatedSerializer is used for obtaining encoders and decoders for multiple
|
||||||
// supported media types.
|
// supported media types.
|
||||||
|
//
|
||||||
|
// TODO: NegotiatedSerializer will be phased out as internal clients are removed
|
||||||
|
// from Kubernetes.
|
||||||
NegotiatedSerializer runtime.NegotiatedSerializer
|
NegotiatedSerializer runtime.NegotiatedSerializer
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,14 +286,6 @@ func RESTClientFor(config *Config) (*RESTClient, error) {
|
|||||||
if config.NegotiatedSerializer == nil {
|
if config.NegotiatedSerializer == nil {
|
||||||
return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")
|
return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")
|
||||||
}
|
}
|
||||||
qps := config.QPS
|
|
||||||
if config.QPS == 0.0 {
|
|
||||||
qps = DefaultQPS
|
|
||||||
}
|
|
||||||
burst := config.Burst
|
|
||||||
if config.Burst == 0 {
|
|
||||||
burst = DefaultBurst
|
|
||||||
}
|
|
||||||
|
|
||||||
baseURL, versionedAPIPath, err := defaultServerUrlFor(config)
|
baseURL, versionedAPIPath, err := defaultServerUrlFor(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -310,7 +305,33 @@ func RESTClientFor(config *Config) (*RESTClient, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewRESTClient(baseURL, versionedAPIPath, config.ContentConfig, qps, burst, config.RateLimiter, httpClient)
|
rateLimiter := config.RateLimiter
|
||||||
|
if rateLimiter == nil {
|
||||||
|
qps := config.QPS
|
||||||
|
if config.QPS == 0.0 {
|
||||||
|
qps = DefaultQPS
|
||||||
|
}
|
||||||
|
burst := config.Burst
|
||||||
|
if config.Burst == 0 {
|
||||||
|
burst = DefaultBurst
|
||||||
|
}
|
||||||
|
if qps > 0 {
|
||||||
|
rateLimiter = flowcontrol.NewTokenBucketRateLimiter(qps, burst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var gv schema.GroupVersion
|
||||||
|
if config.GroupVersion != nil {
|
||||||
|
gv = *config.GroupVersion
|
||||||
|
}
|
||||||
|
clientContent := ClientContentConfig{
|
||||||
|
AcceptContentTypes: config.AcceptContentTypes,
|
||||||
|
ContentType: config.ContentType,
|
||||||
|
GroupVersion: gv,
|
||||||
|
Negotiator: runtime.NewClientNegotiator(config.NegotiatedSerializer, gv),
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewRESTClient(baseURL, versionedAPIPath, clientContent, rateLimiter, httpClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnversionedRESTClientFor is the same as RESTClientFor, except that it allows
|
// UnversionedRESTClientFor is the same as RESTClientFor, except that it allows
|
||||||
@ -338,13 +359,33 @@ func UnversionedRESTClientFor(config *Config) (*RESTClient, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
versionConfig := config.ContentConfig
|
rateLimiter := config.RateLimiter
|
||||||
if versionConfig.GroupVersion == nil {
|
if rateLimiter == nil {
|
||||||
v := metav1.SchemeGroupVersion
|
qps := config.QPS
|
||||||
versionConfig.GroupVersion = &v
|
if config.QPS == 0.0 {
|
||||||
|
qps = DefaultQPS
|
||||||
|
}
|
||||||
|
burst := config.Burst
|
||||||
|
if config.Burst == 0 {
|
||||||
|
burst = DefaultBurst
|
||||||
|
}
|
||||||
|
if qps > 0 {
|
||||||
|
rateLimiter = flowcontrol.NewTokenBucketRateLimiter(qps, burst)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewRESTClient(baseURL, versionedAPIPath, versionConfig, config.QPS, config.Burst, config.RateLimiter, httpClient)
|
gv := metav1.SchemeGroupVersion
|
||||||
|
if config.GroupVersion != nil {
|
||||||
|
gv = *config.GroupVersion
|
||||||
|
}
|
||||||
|
clientContent := ClientContentConfig{
|
||||||
|
AcceptContentTypes: config.AcceptContentTypes,
|
||||||
|
ContentType: config.ContentType,
|
||||||
|
GroupVersion: gv,
|
||||||
|
Negotiator: runtime.NewClientNegotiator(config.NegotiatedSerializer, gv),
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewRESTClient(baseURL, versionedAPIPath, clientContent, rateLimiter, httpClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetKubernetesDefaults sets default values on the provided client config for accessing the
|
// SetKubernetesDefaults sets default values on the provided client config for accessing the
|
||||||
|
@ -155,6 +155,59 @@ func TestRESTClientRequires(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRESTClientLimiter(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
Name string
|
||||||
|
Config Config
|
||||||
|
Limiter flowcontrol.RateLimiter
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Config: Config{},
|
||||||
|
Limiter: flowcontrol.NewTokenBucketRateLimiter(5, 10),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: Config{QPS: 10},
|
||||||
|
Limiter: flowcontrol.NewTokenBucketRateLimiter(10, 10),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: Config{QPS: -1},
|
||||||
|
Limiter: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: Config{
|
||||||
|
RateLimiter: flowcontrol.NewTokenBucketRateLimiter(11, 12),
|
||||||
|
},
|
||||||
|
Limiter: flowcontrol.NewTokenBucketRateLimiter(11, 12),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run("Versioned_"+testCase.Name, func(t *testing.T) {
|
||||||
|
config := testCase.Config
|
||||||
|
config.Host = "127.0.0.1"
|
||||||
|
config.ContentConfig = ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs}
|
||||||
|
client, err := RESTClientFor(&config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) {
|
||||||
|
t.Fatalf("unexpected rate limiter: %#v", client.rateLimiter)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("Unversioned_"+testCase.Name, func(t *testing.T) {
|
||||||
|
config := testCase.Config
|
||||||
|
config.Host = "127.0.0.1"
|
||||||
|
config.ContentConfig = ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs}
|
||||||
|
client, err := UnversionedRESTClientFor(&config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) {
|
||||||
|
t.Fatalf("unexpected rate limiter: %#v", client.rateLimiter)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type fakeLimiter struct {
|
type fakeLimiter struct {
|
||||||
FakeSaturation float64
|
FakeSaturation float64
|
||||||
FakeQPS float32
|
FakeQPS float32
|
||||||
|
@ -29,6 +29,8 @@ import (
|
|||||||
"k8s.io/client-go/util/flowcontrol"
|
"k8s.io/client-go/util/flowcontrol"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CreateHTTPClient creates an http.Client that will invoke the provided roundTripper func
|
||||||
|
// when a request is made.
|
||||||
func CreateHTTPClient(roundTripper func(*http.Request) (*http.Response, error)) *http.Client {
|
func CreateHTTPClient(roundTripper func(*http.Request) (*http.Response, error)) *http.Client {
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Transport: roundTripperFunc(roundTripper),
|
Transport: roundTripperFunc(roundTripper),
|
||||||
@ -41,40 +43,49 @@ func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||||||
return f(req)
|
return f(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RESTClient provides a fake RESTClient interface.
|
// RESTClient provides a fake RESTClient interface. It is used to mock network
|
||||||
|
// interactions via a rest.Request, or to make them via the provided Client to
|
||||||
|
// a specific server.
|
||||||
type RESTClient struct {
|
type RESTClient struct {
|
||||||
Client *http.Client
|
|
||||||
NegotiatedSerializer runtime.NegotiatedSerializer
|
NegotiatedSerializer runtime.NegotiatedSerializer
|
||||||
GroupVersion schema.GroupVersion
|
GroupVersion schema.GroupVersion
|
||||||
VersionedAPIPath string
|
VersionedAPIPath string
|
||||||
|
|
||||||
Req *http.Request
|
// Err is returned when any request would be made to the server. If Err is set,
|
||||||
|
// Req will not be recorded, Resp will not be returned, and Client will not be
|
||||||
|
// invoked.
|
||||||
|
Err error
|
||||||
|
// Req is set to the last request that was executed (had the methods Do/DoRaw) invoked.
|
||||||
|
Req *http.Request
|
||||||
|
// If Client is specified, the client will be invoked instead of returning Resp if
|
||||||
|
// Err is not set.
|
||||||
|
Client *http.Client
|
||||||
|
// Resp is returned to the caller after Req is recorded, unless Err or Client are set.
|
||||||
Resp *http.Response
|
Resp *http.Response
|
||||||
Err error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RESTClient) Get() *restclient.Request {
|
func (c *RESTClient) Get() *restclient.Request {
|
||||||
return c.request("GET")
|
return c.Verb("GET")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RESTClient) Put() *restclient.Request {
|
func (c *RESTClient) Put() *restclient.Request {
|
||||||
return c.request("PUT")
|
return c.Verb("PUT")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RESTClient) Patch(pt types.PatchType) *restclient.Request {
|
func (c *RESTClient) Patch(pt types.PatchType) *restclient.Request {
|
||||||
return c.request("PATCH").SetHeader("Content-Type", string(pt))
|
return c.Verb("PATCH").SetHeader("Content-Type", string(pt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RESTClient) Post() *restclient.Request {
|
func (c *RESTClient) Post() *restclient.Request {
|
||||||
return c.request("POST")
|
return c.Verb("POST")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RESTClient) Delete() *restclient.Request {
|
func (c *RESTClient) Delete() *restclient.Request {
|
||||||
return c.request("DELETE")
|
return c.Verb("DELETE")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RESTClient) Verb(verb string) *restclient.Request {
|
func (c *RESTClient) Verb(verb string) *restclient.Request {
|
||||||
return c.request(verb)
|
return c.Request().Verb(verb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RESTClient) APIVersion() schema.GroupVersion {
|
func (c *RESTClient) APIVersion() schema.GroupVersion {
|
||||||
@ -85,28 +96,17 @@ func (c *RESTClient) GetRateLimiter() flowcontrol.RateLimiter {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RESTClient) request(verb string) *restclient.Request {
|
func (c *RESTClient) Request() *restclient.Request {
|
||||||
config := restclient.ContentConfig{
|
config := restclient.ClientContentConfig{
|
||||||
ContentType: runtime.ContentTypeJSON,
|
ContentType: runtime.ContentTypeJSON,
|
||||||
GroupVersion: &c.GroupVersion,
|
GroupVersion: c.GroupVersion,
|
||||||
NegotiatedSerializer: c.NegotiatedSerializer,
|
Negotiator: runtime.NewClientNegotiator(c.NegotiatedSerializer, c.GroupVersion),
|
||||||
}
|
}
|
||||||
|
return restclient.NewRequestWithClient(&url.URL{Scheme: "https", Host: "localhost"}, c.VersionedAPIPath, config, CreateHTTPClient(c.do))
|
||||||
ns := c.NegotiatedSerializer
|
|
||||||
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
|
||||||
serializers := restclient.Serializers{
|
|
||||||
// TODO this was hardcoded before, but it doesn't look right
|
|
||||||
Encoder: ns.EncoderForVersion(info.Serializer, c.GroupVersion),
|
|
||||||
Decoder: ns.DecoderToVersion(info.Serializer, c.GroupVersion),
|
|
||||||
}
|
|
||||||
if info.StreamSerializer != nil {
|
|
||||||
serializers.StreamingSerializer = info.StreamSerializer.Serializer
|
|
||||||
serializers.Framer = info.StreamSerializer.Framer
|
|
||||||
}
|
|
||||||
return restclient.NewRequest(c, verb, &url.URL{Host: "localhost"}, c.VersionedAPIPath, config, serializers, nil, nil, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RESTClient) Do(req *http.Request) (*http.Response, error) {
|
// do is invoked when a Request() created by this client is executed.
|
||||||
|
func (c *RESTClient) do(req *http.Request) (*http.Response, error) {
|
||||||
if c.Err != nil {
|
if c.Err != nil {
|
||||||
return nil, c.Err
|
return nil, c.Err
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,8 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// longThrottleLatency defines threshold for logging requests. All requests being
|
// longThrottleLatency defines threshold for logging requests. All requests being
|
||||||
// throttle for more than longThrottleLatency will be logged.
|
// throttled (via the provided rateLimiter) for more than longThrottleLatency will
|
||||||
|
// be logged.
|
||||||
longThrottleLatency = 50 * time.Millisecond
|
longThrottleLatency = 50 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -74,19 +75,20 @@ func (r *RequestConstructionError) Error() string {
|
|||||||
return fmt.Sprintf("request construction error: '%v'", r.Err)
|
return fmt.Sprintf("request construction error: '%v'", r.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var noBackoff = &NoBackoff{}
|
||||||
|
|
||||||
// Request allows for building up a request to a server in a chained fashion.
|
// Request allows for building up a request to a server in a chained fashion.
|
||||||
// Any errors are stored until the end of your call, so you only have to
|
// Any errors are stored until the end of your call, so you only have to
|
||||||
// check once.
|
// check once.
|
||||||
type Request struct {
|
type Request struct {
|
||||||
// required
|
c *RESTClient
|
||||||
client HTTPClient
|
|
||||||
verb string
|
|
||||||
|
|
||||||
baseURL *url.URL
|
rateLimiter flowcontrol.RateLimiter
|
||||||
content ContentConfig
|
backoff BackoffManager
|
||||||
serializers Serializers
|
timeout time.Duration
|
||||||
|
|
||||||
// generic components accessible via method setters
|
// generic components accessible via method setters
|
||||||
|
verb string
|
||||||
pathPrefix string
|
pathPrefix string
|
||||||
subpath string
|
subpath string
|
||||||
params url.Values
|
params url.Values
|
||||||
@ -98,7 +100,6 @@ type Request struct {
|
|||||||
resource string
|
resource string
|
||||||
resourceName string
|
resourceName string
|
||||||
subresource string
|
subresource string
|
||||||
timeout time.Duration
|
|
||||||
|
|
||||||
// output
|
// output
|
||||||
err error
|
err error
|
||||||
@ -106,42 +107,63 @@ type Request struct {
|
|||||||
|
|
||||||
// This is only used for per-request timeouts, deadlines, and cancellations.
|
// This is only used for per-request timeouts, deadlines, and cancellations.
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
||||||
backoffMgr BackoffManager
|
|
||||||
throttle flowcontrol.RateLimiter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequest creates a new request helper object for accessing runtime.Objects on a server.
|
// NewRequest creates a new request helper object for accessing runtime.Objects on a server.
|
||||||
func NewRequest(client HTTPClient, verb string, baseURL *url.URL, versionedAPIPath string, content ContentConfig, serializers Serializers, backoff BackoffManager, throttle flowcontrol.RateLimiter, timeout time.Duration) *Request {
|
func NewRequest(c *RESTClient) *Request {
|
||||||
|
var backoff BackoffManager
|
||||||
|
if c.createBackoffMgr != nil {
|
||||||
|
backoff = c.createBackoffMgr()
|
||||||
|
}
|
||||||
if backoff == nil {
|
if backoff == nil {
|
||||||
klog.V(2).Infof("Not implementing request backoff strategy.")
|
backoff = noBackoff
|
||||||
backoff = &NoBackoff{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pathPrefix := "/"
|
var pathPrefix string
|
||||||
if baseURL != nil {
|
if c.base != nil {
|
||||||
pathPrefix = path.Join(pathPrefix, baseURL.Path)
|
pathPrefix = path.Join("/", c.base.Path, c.versionedAPIPath)
|
||||||
|
} else {
|
||||||
|
pathPrefix = path.Join("/", c.versionedAPIPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var timeout time.Duration
|
||||||
|
if c.Client != nil {
|
||||||
|
timeout = c.Client.Timeout
|
||||||
|
}
|
||||||
|
|
||||||
r := &Request{
|
r := &Request{
|
||||||
client: client,
|
c: c,
|
||||||
verb: verb,
|
rateLimiter: c.rateLimiter,
|
||||||
baseURL: baseURL,
|
backoff: backoff,
|
||||||
pathPrefix: path.Join(pathPrefix, versionedAPIPath),
|
|
||||||
content: content,
|
|
||||||
serializers: serializers,
|
|
||||||
backoffMgr: backoff,
|
|
||||||
throttle: throttle,
|
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
|
pathPrefix: pathPrefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case len(content.AcceptContentTypes) > 0:
|
case len(c.content.AcceptContentTypes) > 0:
|
||||||
r.SetHeader("Accept", content.AcceptContentTypes)
|
r.SetHeader("Accept", c.content.AcceptContentTypes)
|
||||||
case len(content.ContentType) > 0:
|
case len(c.content.ContentType) > 0:
|
||||||
r.SetHeader("Accept", content.ContentType+", */*")
|
r.SetHeader("Accept", c.content.ContentType+", */*")
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRequestWithClient creates a Request with an embedded RESTClient for use in test scenarios.
|
||||||
|
func NewRequestWithClient(base *url.URL, versionedAPIPath string, content ClientContentConfig, client *http.Client) *Request {
|
||||||
|
return NewRequest(&RESTClient{
|
||||||
|
base: base,
|
||||||
|
versionedAPIPath: versionedAPIPath,
|
||||||
|
content: content,
|
||||||
|
Client: client,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verb sets the verb this request will use.
|
||||||
|
func (r *Request) Verb(verb string) *Request {
|
||||||
|
r.verb = verb
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
// Prefix adds segments to the relative beginning to the request path. These
|
// Prefix adds segments to the relative beginning to the request path. These
|
||||||
// items will be placed before the optional Namespace, Resource, or Name sections.
|
// items will be placed before the optional Namespace, Resource, or Name sections.
|
||||||
// Setting AbsPath will clear any previously set Prefix segments
|
// Setting AbsPath will clear any previously set Prefix segments
|
||||||
@ -184,17 +206,17 @@ func (r *Request) Resource(resource string) *Request {
|
|||||||
// or defaults to the stub implementation if nil is provided
|
// or defaults to the stub implementation if nil is provided
|
||||||
func (r *Request) BackOff(manager BackoffManager) *Request {
|
func (r *Request) BackOff(manager BackoffManager) *Request {
|
||||||
if manager == nil {
|
if manager == nil {
|
||||||
r.backoffMgr = &NoBackoff{}
|
r.backoff = &NoBackoff{}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
r.backoffMgr = manager
|
r.backoff = manager
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throttle receives a rate-limiter and sets or replaces an existing request limiter
|
// Throttle receives a rate-limiter and sets or replaces an existing request limiter
|
||||||
func (r *Request) Throttle(limiter flowcontrol.RateLimiter) *Request {
|
func (r *Request) Throttle(limiter flowcontrol.RateLimiter) *Request {
|
||||||
r.throttle = limiter
|
r.rateLimiter = limiter
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,8 +294,8 @@ func (r *Request) AbsPath(segments ...string) *Request {
|
|||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
r.pathPrefix = path.Join(r.baseURL.Path, path.Join(segments...))
|
r.pathPrefix = path.Join(r.c.base.Path, path.Join(segments...))
|
||||||
if len(segments) == 1 && (len(r.baseURL.Path) > 1 || len(segments[0]) > 1) && strings.HasSuffix(segments[0], "/") {
|
if len(segments) == 1 && (len(r.c.base.Path) > 1 || len(segments[0]) > 1) && strings.HasSuffix(segments[0], "/") {
|
||||||
// preserve any trailing slashes for legacy behavior
|
// preserve any trailing slashes for legacy behavior
|
||||||
r.pathPrefix += "/"
|
r.pathPrefix += "/"
|
||||||
}
|
}
|
||||||
@ -317,7 +339,7 @@ func (r *Request) Param(paramName, s string) *Request {
|
|||||||
// VersionedParams will not write query parameters that have omitempty set and are empty. If a
|
// VersionedParams will not write query parameters that have omitempty set and are empty. If a
|
||||||
// parameter has already been set it is appended to (Params and VersionedParams are additive).
|
// parameter has already been set it is appended to (Params and VersionedParams are additive).
|
||||||
func (r *Request) VersionedParams(obj runtime.Object, codec runtime.ParameterCodec) *Request {
|
func (r *Request) VersionedParams(obj runtime.Object, codec runtime.ParameterCodec) *Request {
|
||||||
return r.SpecificallyVersionedParams(obj, codec, *r.content.GroupVersion)
|
return r.SpecificallyVersionedParams(obj, codec, r.c.content.GroupVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Request) SpecificallyVersionedParams(obj runtime.Object, codec runtime.ParameterCodec, version schema.GroupVersion) *Request {
|
func (r *Request) SpecificallyVersionedParams(obj runtime.Object, codec runtime.ParameterCodec, version schema.GroupVersion) *Request {
|
||||||
@ -397,14 +419,19 @@ func (r *Request) Body(obj interface{}) *Request {
|
|||||||
if reflect.ValueOf(t).IsNil() {
|
if reflect.ValueOf(t).IsNil() {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
data, err := runtime.Encode(r.serializers.Encoder, t)
|
encoder, err := r.c.content.Negotiator.Encoder(r.c.content.ContentType, nil)
|
||||||
|
if err != nil {
|
||||||
|
r.err = err
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
data, err := runtime.Encode(encoder, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.err = err
|
r.err = err
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
glogBody("Request Body", data)
|
glogBody("Request Body", data)
|
||||||
r.body = bytes.NewReader(data)
|
r.body = bytes.NewReader(data)
|
||||||
r.SetHeader("Content-Type", r.content.ContentType)
|
r.SetHeader("Content-Type", r.c.content.ContentType)
|
||||||
default:
|
default:
|
||||||
r.err = fmt.Errorf("unknown type used for body: %+v", obj)
|
r.err = fmt.Errorf("unknown type used for body: %+v", obj)
|
||||||
}
|
}
|
||||||
@ -433,8 +460,8 @@ func (r *Request) URL() *url.URL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
finalURL := &url.URL{}
|
finalURL := &url.URL{}
|
||||||
if r.baseURL != nil {
|
if r.c.base != nil {
|
||||||
*finalURL = *r.baseURL
|
*finalURL = *r.c.base
|
||||||
}
|
}
|
||||||
finalURL.Path = p
|
finalURL.Path = p
|
||||||
|
|
||||||
@ -468,8 +495,8 @@ func (r Request) finalURLTemplate() url.URL {
|
|||||||
segments := strings.Split(r.URL().Path, "/")
|
segments := strings.Split(r.URL().Path, "/")
|
||||||
groupIndex := 0
|
groupIndex := 0
|
||||||
index := 0
|
index := 0
|
||||||
if r.URL() != nil && r.baseURL != nil && strings.Contains(r.URL().Path, r.baseURL.Path) {
|
if r.URL() != nil && r.c.base != nil && strings.Contains(r.URL().Path, r.c.base.Path) {
|
||||||
groupIndex += len(strings.Split(r.baseURL.Path, "/"))
|
groupIndex += len(strings.Split(r.c.base.Path, "/"))
|
||||||
}
|
}
|
||||||
if groupIndex >= len(segments) {
|
if groupIndex >= len(segments) {
|
||||||
return *url
|
return *url
|
||||||
@ -522,16 +549,16 @@ func (r Request) finalURLTemplate() url.URL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Request) tryThrottle() error {
|
func (r *Request) tryThrottle() error {
|
||||||
if r.throttle == nil {
|
if r.rateLimiter == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
var err error
|
var err error
|
||||||
if r.ctx != nil {
|
if r.ctx != nil {
|
||||||
err = r.throttle.Wait(r.ctx)
|
err = r.rateLimiter.Wait(r.ctx)
|
||||||
} else {
|
} else {
|
||||||
r.throttle.Accept()
|
r.rateLimiter.Accept()
|
||||||
}
|
}
|
||||||
|
|
||||||
if latency := time.Since(now); latency > longThrottleLatency {
|
if latency := time.Since(now); latency > longThrottleLatency {
|
||||||
@ -544,27 +571,11 @@ func (r *Request) tryThrottle() error {
|
|||||||
// Watch attempts to begin watching the requested location.
|
// Watch attempts to begin watching the requested location.
|
||||||
// Returns a watch.Interface, or an error.
|
// Returns a watch.Interface, or an error.
|
||||||
func (r *Request) Watch() (watch.Interface, error) {
|
func (r *Request) Watch() (watch.Interface, error) {
|
||||||
return r.WatchWithSpecificDecoders(
|
|
||||||
func(body io.ReadCloser) streaming.Decoder {
|
|
||||||
framer := r.serializers.Framer.NewFrameReader(body)
|
|
||||||
return streaming.NewDecoder(framer, r.serializers.StreamingSerializer)
|
|
||||||
},
|
|
||||||
r.serializers.Decoder,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchWithSpecificDecoders attempts to begin watching the requested location with a *different* decoder.
|
|
||||||
// Turns out that you want one "standard" decoder for the watch event and one "personal" decoder for the content
|
|
||||||
// Returns a watch.Interface, or an error.
|
|
||||||
func (r *Request) WatchWithSpecificDecoders(wrapperDecoderFn func(io.ReadCloser) streaming.Decoder, embeddedDecoder runtime.Decoder) (watch.Interface, error) {
|
|
||||||
// We specifically don't want to rate limit watches, so we
|
// We specifically don't want to rate limit watches, so we
|
||||||
// don't use r.throttle here.
|
// don't use r.rateLimiter here.
|
||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return nil, r.err
|
return nil, r.err
|
||||||
}
|
}
|
||||||
if r.serializers.Framer == nil {
|
|
||||||
return nil, fmt.Errorf("watching resources is not possible with this client (content-type: %s)", r.content.ContentType)
|
|
||||||
}
|
|
||||||
|
|
||||||
url := r.URL().String()
|
url := r.URL().String()
|
||||||
req, err := http.NewRequest(r.verb, url, r.body)
|
req, err := http.NewRequest(r.verb, url, r.body)
|
||||||
@ -575,18 +586,18 @@ func (r *Request) WatchWithSpecificDecoders(wrapperDecoderFn func(io.ReadCloser)
|
|||||||
req = req.WithContext(r.ctx)
|
req = req.WithContext(r.ctx)
|
||||||
}
|
}
|
||||||
req.Header = r.headers
|
req.Header = r.headers
|
||||||
client := r.client
|
client := r.c.Client
|
||||||
if client == nil {
|
if client == nil {
|
||||||
client = http.DefaultClient
|
client = http.DefaultClient
|
||||||
}
|
}
|
||||||
r.backoffMgr.Sleep(r.backoffMgr.CalculateBackoff(r.URL()))
|
r.backoff.Sleep(r.backoff.CalculateBackoff(r.URL()))
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
updateURLMetrics(r, resp, err)
|
updateURLMetrics(r, resp, err)
|
||||||
if r.baseURL != nil {
|
if r.c.base != nil {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.backoffMgr.UpdateBackoff(r.baseURL, err, 0)
|
r.backoff.UpdateBackoff(r.c.base, err, 0)
|
||||||
} else {
|
} else {
|
||||||
r.backoffMgr.UpdateBackoff(r.baseURL, err, resp.StatusCode)
|
r.backoff.UpdateBackoff(r.c.base, err, resp.StatusCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -604,9 +615,22 @@ func (r *Request) WatchWithSpecificDecoders(wrapperDecoderFn func(io.ReadCloser)
|
|||||||
}
|
}
|
||||||
return nil, fmt.Errorf("for request %s, got status: %v", url, resp.StatusCode)
|
return nil, fmt.Errorf("for request %s, got status: %v", url, resp.StatusCode)
|
||||||
}
|
}
|
||||||
wrapperDecoder := wrapperDecoderFn(resp.Body)
|
|
||||||
|
contentType := resp.Header.Get("Content-Type")
|
||||||
|
mediaType, params, err := mime.ParseMediaType(contentType)
|
||||||
|
if err != nil {
|
||||||
|
klog.V(4).Infof("Unexpected content type from the server: %q: %v", contentType, err)
|
||||||
|
}
|
||||||
|
objectDecoder, streamingSerializer, framer, err := r.c.content.Negotiator.StreamDecoder(mediaType, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
frameReader := framer.NewFrameReader(resp.Body)
|
||||||
|
watchEventDecoder := streaming.NewDecoder(frameReader, streamingSerializer)
|
||||||
|
|
||||||
return watch.NewStreamWatcher(
|
return watch.NewStreamWatcher(
|
||||||
restclientwatch.NewDecoder(wrapperDecoder, embeddedDecoder),
|
restclientwatch.NewDecoder(watchEventDecoder, objectDecoder),
|
||||||
// use 500 to indicate that the cause of the error is unknown - other error codes
|
// use 500 to indicate that the cause of the error is unknown - other error codes
|
||||||
// are more specific to HTTP interactions, and set a reason
|
// are more specific to HTTP interactions, and set a reason
|
||||||
errors.NewClientErrorReporter(http.StatusInternalServerError, r.verb, "ClientWatchDecoding"),
|
errors.NewClientErrorReporter(http.StatusInternalServerError, r.verb, "ClientWatchDecoding"),
|
||||||
@ -617,8 +641,8 @@ func (r *Request) WatchWithSpecificDecoders(wrapperDecoderFn func(io.ReadCloser)
|
|||||||
// It also handles corner cases for incomplete/invalid request data.
|
// It also handles corner cases for incomplete/invalid request data.
|
||||||
func updateURLMetrics(req *Request, resp *http.Response, err error) {
|
func updateURLMetrics(req *Request, resp *http.Response, err error) {
|
||||||
url := "none"
|
url := "none"
|
||||||
if req.baseURL != nil {
|
if req.c.base != nil {
|
||||||
url = req.baseURL.Host
|
url = req.c.base.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errors can be arbitrary strings. Unbound label cardinality is not suitable for a metric
|
// Errors can be arbitrary strings. Unbound label cardinality is not suitable for a metric
|
||||||
@ -656,18 +680,18 @@ func (r *Request) Stream() (io.ReadCloser, error) {
|
|||||||
req = req.WithContext(r.ctx)
|
req = req.WithContext(r.ctx)
|
||||||
}
|
}
|
||||||
req.Header = r.headers
|
req.Header = r.headers
|
||||||
client := r.client
|
client := r.c.Client
|
||||||
if client == nil {
|
if client == nil {
|
||||||
client = http.DefaultClient
|
client = http.DefaultClient
|
||||||
}
|
}
|
||||||
r.backoffMgr.Sleep(r.backoffMgr.CalculateBackoff(r.URL()))
|
r.backoff.Sleep(r.backoff.CalculateBackoff(r.URL()))
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
updateURLMetrics(r, resp, err)
|
updateURLMetrics(r, resp, err)
|
||||||
if r.baseURL != nil {
|
if r.c.base != nil {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.backoffMgr.UpdateBackoff(r.URL(), err, 0)
|
r.backoff.UpdateBackoff(r.URL(), err, 0)
|
||||||
} else {
|
} else {
|
||||||
r.backoffMgr.UpdateBackoff(r.URL(), err, resp.StatusCode)
|
r.backoff.UpdateBackoff(r.URL(), err, resp.StatusCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -738,7 +762,7 @@ func (r *Request) request(fn func(*http.Request, *http.Response)) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := r.client
|
client := r.c.Client
|
||||||
if client == nil {
|
if client == nil {
|
||||||
client = http.DefaultClient
|
client = http.DefaultClient
|
||||||
}
|
}
|
||||||
@ -765,11 +789,11 @@ func (r *Request) request(fn func(*http.Request, *http.Response)) error {
|
|||||||
}
|
}
|
||||||
req.Header = r.headers
|
req.Header = r.headers
|
||||||
|
|
||||||
r.backoffMgr.Sleep(r.backoffMgr.CalculateBackoff(r.URL()))
|
r.backoff.Sleep(r.backoff.CalculateBackoff(r.URL()))
|
||||||
if retries > 0 {
|
if retries > 0 {
|
||||||
// We are retrying the request that we already send to apiserver
|
// We are retrying the request that we already send to apiserver
|
||||||
// at least once before.
|
// at least once before.
|
||||||
// This request should also be throttled with the client-internal throttler.
|
// This request should also be throttled with the client-internal rate limiter.
|
||||||
if err := r.tryThrottle(); err != nil {
|
if err := r.tryThrottle(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -777,9 +801,9 @@ func (r *Request) request(fn func(*http.Request, *http.Response)) error {
|
|||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
updateURLMetrics(r, resp, err)
|
updateURLMetrics(r, resp, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.backoffMgr.UpdateBackoff(r.URL(), err, 0)
|
r.backoff.UpdateBackoff(r.URL(), err, 0)
|
||||||
} else {
|
} else {
|
||||||
r.backoffMgr.UpdateBackoff(r.URL(), err, resp.StatusCode)
|
r.backoff.UpdateBackoff(r.URL(), err, resp.StatusCode)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// "Connection reset by peer" is usually a transient error.
|
// "Connection reset by peer" is usually a transient error.
|
||||||
@ -822,7 +846,7 @@ func (r *Request) request(fn func(*http.Request, *http.Response)) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v", seconds, retries, url)
|
klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v", seconds, retries, url)
|
||||||
r.backoffMgr.Sleep(time.Duration(seconds) * time.Second)
|
r.backoff.Sleep(time.Duration(seconds) * time.Second)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
fn(req, resp)
|
fn(req, resp)
|
||||||
@ -908,14 +932,18 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu
|
|||||||
glogBody("Response Body", body)
|
glogBody("Response Body", body)
|
||||||
|
|
||||||
// verify the content type is accurate
|
// verify the content type is accurate
|
||||||
|
var decoder runtime.Decoder
|
||||||
contentType := resp.Header.Get("Content-Type")
|
contentType := resp.Header.Get("Content-Type")
|
||||||
decoder := r.serializers.Decoder
|
if len(contentType) == 0 {
|
||||||
if len(contentType) > 0 && (decoder == nil || (len(r.content.ContentType) > 0 && contentType != r.content.ContentType)) {
|
contentType = r.c.content.ContentType
|
||||||
|
}
|
||||||
|
if len(contentType) > 0 {
|
||||||
|
var err error
|
||||||
mediaType, params, err := mime.ParseMediaType(contentType)
|
mediaType, params, err := mime.ParseMediaType(contentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Result{err: errors.NewInternalError(err)}
|
return Result{err: errors.NewInternalError(err)}
|
||||||
}
|
}
|
||||||
decoder, err = r.serializers.RenegotiatedDecoder(mediaType, params)
|
decoder, err = r.c.content.Negotiator.Decoder(mediaType, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if we fail to negotiate a decoder, treat this as an unstructured error
|
// if we fail to negotiate a decoder, treat this as an unstructured error
|
||||||
switch {
|
switch {
|
||||||
@ -1035,7 +1063,7 @@ func (r *Request) newUnstructuredResponseError(body []byte, isTextResponse bool,
|
|||||||
}
|
}
|
||||||
var groupResource schema.GroupResource
|
var groupResource schema.GroupResource
|
||||||
if len(r.resource) > 0 {
|
if len(r.resource) > 0 {
|
||||||
groupResource.Group = r.content.GroupVersion.Group
|
groupResource.Group = r.c.content.GroupVersion.Group
|
||||||
groupResource.Resource = r.resource
|
groupResource.Resource = r.resource
|
||||||
}
|
}
|
||||||
return errors.NewGenericServerResponse(
|
return errors.NewGenericServerResponse(
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -239,7 +239,7 @@ func TestAttach(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||||
@ -341,7 +341,7 @@ func TestAttachWarnings(t *testing.T) {
|
|||||||
streams, _, _, bufErr := genericclioptions.NewTestIOStreams()
|
streams, _, _, bufErr := genericclioptions.NewTestIOStreams()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -71,7 +70,7 @@ func TestCreateClusterRoleBinding(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||||
encoder := ns.EncoderForVersion(info.Serializer, groupVersion)
|
encoder := ns.EncoderForVersion(info.Serializer, groupVersion)
|
||||||
@ -79,6 +78,7 @@ func TestCreateClusterRoleBinding(t *testing.T) {
|
|||||||
|
|
||||||
tf.Client = &ClusterRoleBindingRESTClient{
|
tf.Client = &ClusterRoleBindingRESTClient{
|
||||||
RESTClient: &fake.RESTClient{
|
RESTClient: &fake.RESTClient{
|
||||||
|
GroupVersion: schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1beta1"},
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
@ -129,19 +129,5 @@ type ClusterRoleBindingRESTClient struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClusterRoleBindingRESTClient) Post() *restclient.Request {
|
func (c *ClusterRoleBindingRESTClient) Post() *restclient.Request {
|
||||||
config := restclient.ContentConfig{
|
return c.RESTClient.Verb("POST")
|
||||||
ContentType: runtime.ContentTypeJSON,
|
|
||||||
NegotiatedSerializer: c.NegotiatedSerializer,
|
|
||||||
}
|
|
||||||
|
|
||||||
info, _ := runtime.SerializerInfoForMediaType(c.NegotiatedSerializer.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
|
||||||
serializers := restclient.Serializers{
|
|
||||||
Encoder: c.NegotiatedSerializer.EncoderForVersion(info.Serializer, schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1beta1"}),
|
|
||||||
Decoder: c.NegotiatedSerializer.DecoderToVersion(info.Serializer, schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1beta1"}),
|
|
||||||
}
|
|
||||||
if info.StreamSerializer != nil {
|
|
||||||
serializers.StreamingSerializer = info.StreamSerializer.Serializer
|
|
||||||
serializers.Framer = info.StreamSerializer.Framer
|
|
||||||
}
|
|
||||||
return restclient.NewRequest(c, "POST", &url.URL{Host: "localhost"}, c.VersionedAPIPath, config, serializers, nil, nil, 0)
|
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ func TestCreateConfigMap(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||||
|
@ -91,7 +91,7 @@ func TestCreateDeployment(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
fakeDiscovery := "{\"kind\":\"APIResourceList\",\"apiVersion\":\"v1\",\"groupVersion\":\"apps/v1\",\"resources\":[{\"name\":\"deployments\",\"singularName\":\"\",\"namespaced\":true,\"kind\":\"Deployment\",\"verbs\":[\"create\",\"delete\",\"deletecollection\",\"get\",\"list\",\"patch\",\"update\",\"watch\"],\"shortNames\":[\"deploy\"],\"categories\":[\"all\"]}]}"
|
fakeDiscovery := "{\"kind\":\"APIResourceList\",\"apiVersion\":\"v1\",\"groupVersion\":\"apps/v1\",\"resources\":[{\"name\":\"deployments\",\"singularName\":\"\",\"namespaced\":true,\"kind\":\"Deployment\",\"verbs\":[\"create\",\"delete\",\"deletecollection\",\"get\",\"list\",\"patch\",\"update\",\"watch\"],\"shortNames\":[\"deploy\"],\"categories\":[\"all\"]}]}"
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
@ -121,7 +121,7 @@ func TestCreateDeploymentNoImage(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
fakeDiscovery := "{\"kind\":\"APIResourceList\",\"apiVersion\":\"v1\",\"groupVersion\":\"apps/v1\",\"resources\":[{\"name\":\"deployments\",\"singularName\":\"\",\"namespaced\":true,\"kind\":\"Deployment\",\"verbs\":[\"create\",\"delete\",\"deletecollection\",\"get\",\"list\",\"patch\",\"update\",\"watch\"],\"shortNames\":[\"deploy\"],\"categories\":[\"all\"]}]}"
|
fakeDiscovery := "{\"kind\":\"APIResourceList\",\"apiVersion\":\"v1\",\"groupVersion\":\"apps/v1\",\"resources\":[{\"name\":\"deployments\",\"singularName\":\"\",\"namespaced\":true,\"kind\":\"Deployment\",\"verbs\":[\"create\",\"delete\",\"deletecollection\",\"get\",\"list\",\"patch\",\"update\",\"watch\"],\"shortNames\":[\"deploy\"],\"categories\":[\"all\"]}]}"
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
|
@ -35,7 +35,7 @@ func TestCreateNamespace(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||||
|
@ -35,7 +35,7 @@ func TestCreatePdb(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: schema.GroupVersion{Group: "policy", Version: "v1beta1"},
|
GroupVersion: schema.GroupVersion{Group: "policy", Version: "v1beta1"},
|
||||||
|
@ -35,7 +35,7 @@ func TestCreatePriorityClass(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory()
|
tf := cmdtesting.NewTestFactory()
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: schema.GroupVersion{Group: "scheduling.k8s.io", Version: "v1beta1"},
|
GroupVersion: schema.GroupVersion{Group: "scheduling.k8s.io", Version: "v1beta1"},
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -73,7 +72,7 @@ func TestCreateRoleBinding(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||||
encoder := ns.EncoderForVersion(info.Serializer, groupVersion)
|
encoder := ns.EncoderForVersion(info.Serializer, groupVersion)
|
||||||
@ -81,6 +80,7 @@ func TestCreateRoleBinding(t *testing.T) {
|
|||||||
|
|
||||||
tf.Client = &RoleBindingRESTClient{
|
tf.Client = &RoleBindingRESTClient{
|
||||||
RESTClient: &fake.RESTClient{
|
RESTClient: &fake.RESTClient{
|
||||||
|
GroupVersion: groupVersion,
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
@ -125,20 +125,5 @@ type RoleBindingRESTClient struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *RoleBindingRESTClient) Post() *restclient.Request {
|
func (c *RoleBindingRESTClient) Post() *restclient.Request {
|
||||||
config := restclient.ContentConfig{
|
return c.RESTClient.Verb("POST")
|
||||||
ContentType: runtime.ContentTypeJSON,
|
|
||||||
GroupVersion: &groupVersion,
|
|
||||||
NegotiatedSerializer: c.NegotiatedSerializer,
|
|
||||||
}
|
|
||||||
|
|
||||||
info, _ := runtime.SerializerInfoForMediaType(c.NegotiatedSerializer.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
|
||||||
serializers := restclient.Serializers{
|
|
||||||
Encoder: c.NegotiatedSerializer.EncoderForVersion(info.Serializer, groupVersion),
|
|
||||||
Decoder: c.NegotiatedSerializer.DecoderToVersion(info.Serializer, groupVersion),
|
|
||||||
}
|
|
||||||
if info.StreamSerializer != nil {
|
|
||||||
serializers.StreamingSerializer = info.StreamSerializer.Serializer
|
|
||||||
serializers.Framer = info.StreamSerializer.Framer
|
|
||||||
}
|
|
||||||
return restclient.NewRequest(c, "POST", &url.URL{Host: "localhost"}, c.VersionedAPIPath, config, serializers, nil, nil, 0)
|
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ func TestCreateSecretGeneric(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||||
@ -72,7 +72,7 @@ func TestCreateSecretDockerRegistry(t *testing.T) {
|
|||||||
secretObject.Name = "my-secret"
|
secretObject.Name = "my-secret"
|
||||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||||
|
@ -35,7 +35,7 @@ func TestCreateServiceAccount(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||||
|
@ -160,7 +160,7 @@ func TestCordon(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
newNode := &corev1.Node{}
|
newNode := &corev1.Node{}
|
||||||
updated := false
|
updated := false
|
||||||
@ -738,7 +738,7 @@ func TestDrain(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||||
|
@ -135,7 +135,7 @@ func TestPodAndContainer(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
@ -205,7 +205,7 @@ func TestExec(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
|
||||||
|
@ -602,7 +602,7 @@ func TestRunExposeService(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: schema.GroupVersion{Version: "v1"},
|
GroupVersion: schema.GroupVersion{Version: "v1"},
|
||||||
|
@ -77,7 +77,7 @@ func testPortForward(t *testing.T, flags map[string]string, args []string) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
VersionedAPIPath: "/api/v1",
|
VersionedAPIPath: "/api/v1",
|
||||||
|
@ -68,7 +68,7 @@ go_test(
|
|||||||
srcs = ["rollout_pause_test.go"],
|
srcs = ["rollout_pause_test.go"],
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
deps = [
|
deps = [
|
||||||
"//staging/src/k8s.io/api/extensions/v1beta1:go_default_library",
|
"//staging/src/k8s.io/api/apps/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||||
|
@ -20,10 +20,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
@ -34,23 +33,24 @@ import (
|
|||||||
"k8s.io/kubectl/pkg/scheme"
|
"k8s.io/kubectl/pkg/scheme"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rolloutPauseGroupVersionEncoder = schema.GroupVersion{Group: "extensions", Version: "v1beta1"}
|
var rolloutPauseGroupVersionEncoder = schema.GroupVersion{Group: "apps", Version: "v1"}
|
||||||
var rolloutPauseGroupVersionDecoder = schema.GroupVersion{Group: "extensions", Version: "v1beta1"}
|
var rolloutPauseGroupVersionDecoder = schema.GroupVersion{Group: "apps", Version: "v1"}
|
||||||
|
|
||||||
func TestRolloutPause(t *testing.T) {
|
func TestRolloutPause(t *testing.T) {
|
||||||
deploymentName := "deployment/nginx-deployment"
|
deploymentName := "deployment/nginx-deployment"
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||||
|
|
||||||
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||||
encoder := ns.EncoderForVersion(info.Serializer, rolloutPauseGroupVersionEncoder)
|
encoder := ns.EncoderForVersion(info.Serializer, rolloutPauseGroupVersionEncoder)
|
||||||
tf.Client = &RolloutPauseRESTClient{
|
tf.Client = &RolloutPauseRESTClient{
|
||||||
RESTClient: &fake.RESTClient{
|
RESTClient: &fake.RESTClient{
|
||||||
|
GroupVersion: rolloutPauseGroupVersionEncoder,
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/namespaces/test/deployments/nginx-deployment" && (m == "GET" || m == "PATCH"):
|
case p == "/namespaces/test/deployments/nginx-deployment" && (m == "GET" || m == "PATCH"):
|
||||||
responseDeployment := &extensionsv1beta1.Deployment{}
|
responseDeployment := &appsv1.Deployment{}
|
||||||
responseDeployment.Name = deploymentName
|
responseDeployment.Name = deploymentName
|
||||||
body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(encoder, responseDeployment))))
|
body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(encoder, responseDeployment))))
|
||||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
|
||||||
@ -66,7 +66,7 @@ func TestRolloutPause(t *testing.T) {
|
|||||||
cmd := NewCmdRolloutPause(tf, streams)
|
cmd := NewCmdRolloutPause(tf, streams)
|
||||||
|
|
||||||
cmd.Run(cmd, []string{deploymentName})
|
cmd.Run(cmd, []string{deploymentName})
|
||||||
expectedOutput := "deployment.extensions/" + deploymentName + " paused\n"
|
expectedOutput := "deployment.apps/" + deploymentName + " paused\n"
|
||||||
if buf.String() != expectedOutput {
|
if buf.String() != expectedOutput {
|
||||||
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
|
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
|
||||||
}
|
}
|
||||||
@ -77,39 +77,9 @@ type RolloutPauseRESTClient struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *RolloutPauseRESTClient) Get() *restclient.Request {
|
func (c *RolloutPauseRESTClient) Get() *restclient.Request {
|
||||||
config := restclient.ContentConfig{
|
return c.RESTClient.Verb("GET")
|
||||||
ContentType: runtime.ContentTypeJSON,
|
|
||||||
GroupVersion: &rolloutPauseGroupVersionEncoder,
|
|
||||||
NegotiatedSerializer: c.NegotiatedSerializer,
|
|
||||||
}
|
|
||||||
|
|
||||||
info, _ := runtime.SerializerInfoForMediaType(c.NegotiatedSerializer.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
|
||||||
serializers := restclient.Serializers{
|
|
||||||
Encoder: c.NegotiatedSerializer.EncoderForVersion(info.Serializer, rolloutPauseGroupVersionEncoder),
|
|
||||||
Decoder: c.NegotiatedSerializer.DecoderToVersion(info.Serializer, rolloutPauseGroupVersionDecoder),
|
|
||||||
}
|
|
||||||
if info.StreamSerializer != nil {
|
|
||||||
serializers.StreamingSerializer = info.StreamSerializer.Serializer
|
|
||||||
serializers.Framer = info.StreamSerializer.Framer
|
|
||||||
}
|
|
||||||
return restclient.NewRequest(c, "GET", &url.URL{Host: "localhost"}, c.VersionedAPIPath, config, serializers, nil, nil, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RolloutPauseRESTClient) Patch(pt types.PatchType) *restclient.Request {
|
func (c *RolloutPauseRESTClient) Patch(pt types.PatchType) *restclient.Request {
|
||||||
config := restclient.ContentConfig{
|
return c.RESTClient.Verb("PATCH")
|
||||||
ContentType: runtime.ContentTypeJSON,
|
|
||||||
GroupVersion: &rolloutPauseGroupVersionEncoder,
|
|
||||||
NegotiatedSerializer: c.NegotiatedSerializer,
|
|
||||||
}
|
|
||||||
|
|
||||||
info, _ := runtime.SerializerInfoForMediaType(c.NegotiatedSerializer.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
|
||||||
serializers := restclient.Serializers{
|
|
||||||
Encoder: c.NegotiatedSerializer.EncoderForVersion(info.Serializer, rolloutPauseGroupVersionEncoder),
|
|
||||||
Decoder: c.NegotiatedSerializer.DecoderToVersion(info.Serializer, rolloutPauseGroupVersionDecoder),
|
|
||||||
}
|
|
||||||
if info.StreamSerializer != nil {
|
|
||||||
serializers.StreamingSerializer = info.StreamSerializer.Serializer
|
|
||||||
serializers.Framer = info.StreamSerializer.Framer
|
|
||||||
}
|
|
||||||
return restclient.NewRequest(c, "PATCH", &url.URL{Host: "localhost"}, c.VersionedAPIPath, config, serializers, nil, nil, 0)
|
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ func TestRunArgsFollowDashRules(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
GroupVersion: corev1.SchemeGroupVersion,
|
GroupVersion: corev1.SchemeGroupVersion,
|
||||||
@ -327,7 +327,7 @@ func TestGenerateService(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
@ -505,8 +505,9 @@ func TestRunValidations(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
_, _, codec := cmdtesting.NewExternalScheme()
|
_, _, codec := cmdtesting.NewExternalScheme()
|
||||||
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: scheme.Codecs,
|
NegotiatedSerializer: ns,
|
||||||
Resp: &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, cmdtesting.NewInternalType("", "", ""))},
|
Resp: &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, cmdtesting.NewInternalType("", "", ""))},
|
||||||
}
|
}
|
||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
|
@ -243,7 +243,7 @@ func TestTaint(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
|
@ -53,7 +53,7 @@ func TestTopNodeAllMetrics(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
@ -109,7 +109,7 @@ func TestTopNodeAllMetricsCustomDefaults(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
@ -172,7 +172,7 @@ func TestTopNodeWithNameMetrics(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
@ -238,7 +238,7 @@ func TestTopNodeWithLabelSelectorMetrics(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
@ -292,7 +292,7 @@ func TestTopNodeAllMetricsFromMetricsServer(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
@ -359,7 +359,7 @@ func TestTopNodeWithNameMetricsFromMetricsServer(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
@ -436,7 +436,7 @@ func TestTopNodeWithLabelSelectorMetricsFromMetricsServer(t *testing.T) {
|
|||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
|
@ -176,7 +176,7 @@ func TestTopPod(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory().WithNamespace(testNS)
|
tf := cmdtesting.NewTestFactory().WithNamespace(testNS)
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
@ -323,7 +323,7 @@ func TestTopPodWithMetricsServer(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory().WithNamespace(testNS)
|
tf := cmdtesting.NewTestFactory().WithNamespace(testNS)
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
@ -527,7 +527,7 @@ func TestTopPodCustomDefaults(t *testing.T) {
|
|||||||
tf := cmdtesting.NewTestFactory().WithNamespace(testNS)
|
tf := cmdtesting.NewTestFactory().WithNamespace(testNS)
|
||||||
defer tf.Cleanup()
|
defer tf.Cleanup()
|
||||||
|
|
||||||
ns := scheme.Codecs
|
ns := scheme.Codecs.WithoutConversion()
|
||||||
|
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
|
@ -21,6 +21,7 @@ go_library(
|
|||||||
"garbage_collector.go",
|
"garbage_collector.go",
|
||||||
"generated_clientset.go",
|
"generated_clientset.go",
|
||||||
"namespace.go",
|
"namespace.go",
|
||||||
|
"protocol.go",
|
||||||
"resource_quota.go",
|
"resource_quota.go",
|
||||||
"table_conversion.go",
|
"table_conversion.go",
|
||||||
"watch.go",
|
"watch.go",
|
||||||
|
80
test/e2e/apimachinery/protocol.go
Normal file
80
test/e2e/apimachinery/protocol.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package apimachinery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
g "github.com/onsi/ginkgo"
|
||||||
|
o "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = SIGDescribe("client-go should negotiate", func() {
|
||||||
|
f := framework.NewDefaultFramework("protocol")
|
||||||
|
f.SkipNamespaceCreation = true
|
||||||
|
|
||||||
|
for _, s := range []string{
|
||||||
|
"application/json",
|
||||||
|
"application/vnd.kubernetes.protobuf",
|
||||||
|
"application/vnd.kubernetes.protobuf,application/json",
|
||||||
|
"application/json,application/vnd.kubernetes.protobuf",
|
||||||
|
} {
|
||||||
|
accept := s
|
||||||
|
g.It(fmt.Sprintf("watch and report errors with accept %q", accept), func() {
|
||||||
|
cfg, err := framework.LoadConfig()
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
|
cfg.AcceptContentTypes = accept
|
||||||
|
|
||||||
|
c := kubernetes.NewForConfigOrDie(cfg)
|
||||||
|
svcs, err := c.CoreV1().Services("default").Get("kubernetes", metav1.GetOptions{})
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
rv, err := strconv.Atoi(svcs.ResourceVersion)
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
w, err := c.CoreV1().Services("default").Watch(metav1.ListOptions{ResourceVersion: strconv.Itoa(rv - 1)})
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
defer w.Stop()
|
||||||
|
|
||||||
|
evt, ok := <-w.ResultChan()
|
||||||
|
o.Expect(ok).To(o.BeTrue())
|
||||||
|
switch evt.Type {
|
||||||
|
case watch.Added, watch.Modified:
|
||||||
|
// this is allowed
|
||||||
|
case watch.Error:
|
||||||
|
err := errors.FromObject(evt.Object)
|
||||||
|
if errors.IsGone(err) {
|
||||||
|
// this is allowed, since the kubernetes object could be very old
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if errors.IsUnexpectedObjectError(err) {
|
||||||
|
g.Fail(fmt.Sprintf("unexpected object, wanted v1.Status: %#v", evt.Object))
|
||||||
|
}
|
||||||
|
g.Fail(fmt.Sprintf("unexpected error: %#v", evt.Object))
|
||||||
|
default:
|
||||||
|
g.Fail(fmt.Sprintf("unexpected type %s: %#v", evt.Type, evt.Object))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
@ -54,7 +54,6 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/authentication/serviceaccount:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
|
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
|
"//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/util/workqueue:go_default_library",
|
"//staging/src/k8s.io/client-go/util/workqueue:go_default_library",
|
||||||
|
@ -27,7 +27,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
restclient "k8s.io/client-go/rest"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
"k8s.io/client-go/util/flowcontrol"
|
"k8s.io/client-go/util/flowcontrol"
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
@ -77,9 +77,12 @@ var _ = SIGDescribe("Service endpoints latency", func() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Turn off rate limiting--it interferes with our measurements.
|
// Turn off rate limiting--it interferes with our measurements.
|
||||||
oldThrottle := f.ClientSet.CoreV1().RESTClient().GetRateLimiter()
|
cfg, err := framework.LoadConfig()
|
||||||
f.ClientSet.CoreV1().RESTClient().(*restclient.RESTClient).Throttle = flowcontrol.NewFakeAlwaysRateLimiter()
|
if err != nil {
|
||||||
defer func() { f.ClientSet.CoreV1().RESTClient().(*restclient.RESTClient).Throttle = oldThrottle }()
|
framework.Failf("Unable to load config: %v", err)
|
||||||
|
}
|
||||||
|
cfg.RateLimiter = flowcontrol.NewFakeAlwaysRateLimiter()
|
||||||
|
f.ClientSet = kubernetes.NewForConfigOrDie(cfg)
|
||||||
|
|
||||||
failing := sets.NewString()
|
failing := sets.NewString()
|
||||||
d, err := runServiceLatencies(f, parallelTrials, totalTrials, acceptableFailureRatio)
|
d, err := runServiceLatencies(f, parallelTrials, totalTrials, acceptableFailureRatio)
|
||||||
|
Loading…
Reference in New Issue
Block a user