mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +00:00 
			
		
		
		
	remove old restclient
This commit is contained in:
		| @@ -60,7 +60,7 @@ func (g *genFakeForGroup) Imports(c *generator.Context) (imports []string) { | |||||||
| func (g *genFakeForGroup) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { | func (g *genFakeForGroup) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { | ||||||
| 	sw := generator.NewSnippetWriter(w, c, "$", "$") | 	sw := generator.NewSnippetWriter(w, c, "$", "$") | ||||||
| 	const pkgTestingCore = "k8s.io/kubernetes/pkg/client/testing/core" | 	const pkgTestingCore = "k8s.io/kubernetes/pkg/client/testing/core" | ||||||
| 	const pkgRESTClient = "k8s.io/kubernetes/pkg/client/restclient" | 	const pkgRESTClient = "k8s.io/client-go/rest" | ||||||
| 	m := map[string]interface{}{ | 	m := map[string]interface{}{ | ||||||
| 		"group":               g.group, | 		"group":               g.group, | ||||||
| 		"GroupVersion":        namer.IC(g.group) + namer.IC(g.version), | 		"GroupVersion":        namer.IC(g.group) + namer.IC(g.version), | ||||||
|   | |||||||
| @@ -73,7 +73,7 @@ func (g *genClientset) GenerateType(c *generator.Context, t *types.Type, w io.Wr | |||||||
| 	// perhaps we can adapt the go2ild framework to this kind of usage. | 	// perhaps we can adapt the go2ild framework to this kind of usage. | ||||||
| 	sw := generator.NewSnippetWriter(w, c, "$", "$") | 	sw := generator.NewSnippetWriter(w, c, "$", "$") | ||||||
| 	const pkgDiscovery = "k8s.io/kubernetes/pkg/client/typed/discovery" | 	const pkgDiscovery = "k8s.io/kubernetes/pkg/client/typed/discovery" | ||||||
| 	const pkgRESTClient = "k8s.io/kubernetes/pkg/client/restclient" | 	const pkgRESTClient = "k8s.io/client-go/rest" | ||||||
|  |  | ||||||
| 	allGroups := clientgentypes.ToGroupVersionPackages(g.groups) | 	allGroups := clientgentypes.ToGroupVersionPackages(g.groups) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -57,7 +57,7 @@ func (g *genGroup) Imports(c *generator.Context) (imports []string) { | |||||||
|  |  | ||||||
| func (g *genGroup) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { | func (g *genGroup) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { | ||||||
| 	sw := generator.NewSnippetWriter(w, c, "$", "$") | 	sw := generator.NewSnippetWriter(w, c, "$", "$") | ||||||
| 	const pkgRESTClient = "k8s.io/kubernetes/pkg/client/restclient" | 	const pkgRESTClient = "k8s.io/client-go/rest" | ||||||
| 	const pkgAPI = "k8s.io/kubernetes/pkg/api" | 	const pkgAPI = "k8s.io/kubernetes/pkg/api" | ||||||
| 	const pkgSerializer = "k8s.io/apimachinery/pkg/runtime/serializer" | 	const pkgSerializer = "k8s.io/apimachinery/pkg/runtime/serializer" | ||||||
| 	const pkgUnversioned = "k8s.io/kubernetes/pkg/api/unversioned" | 	const pkgUnversioned = "k8s.io/kubernetes/pkg/api/unversioned" | ||||||
|   | |||||||
| @@ -84,7 +84,7 @@ func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w i | |||||||
| 		"Group":               namer.IC(g.group), | 		"Group":               namer.IC(g.group), | ||||||
| 		"GroupVersion":        namer.IC(g.group) + namer.IC(g.version), | 		"GroupVersion":        namer.IC(g.group) + namer.IC(g.version), | ||||||
| 		"watchInterface":      c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/watch", Name: "Interface"}), | 		"watchInterface":      c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/watch", Name: "Interface"}), | ||||||
| 		"RESTClientInterface": c.Universe.Type(types.Name{Package: "k8s.io/kubernetes/pkg/client/restclient", Name: "Interface"}), | 		"RESTClientInterface": c.Universe.Type(types.Name{Package: "k8s.io/client-go/rest", Name: "Interface"}), | ||||||
| 		"apiParameterCodec":   c.Universe.Type(types.Name{Package: "k8s.io/kubernetes/pkg/api", Name: "ParameterCodec"}), | 		"apiParameterCodec":   c.Universe.Type(types.Name{Package: "k8s.io/kubernetes/pkg/api", Name: "ParameterCodec"}), | ||||||
| 		"PatchType":           c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/types", Name: "PatchType"}), | 		"PatchType":           c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/types", Name: "PatchType"}), | ||||||
| 		"namespaced":          namespaced, | 		"namespaced":          namespaced, | ||||||
|   | |||||||
| @@ -1,100 +0,0 @@ | |||||||
| package(default_visibility = ["//visibility:public"]) |  | ||||||
|  |  | ||||||
| licenses(["notice"]) |  | ||||||
|  |  | ||||||
| load( |  | ||||||
|     "@io_bazel_rules_go//go:def.bzl", |  | ||||||
|     "go_library", |  | ||||||
|     "go_test", |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| go_library( |  | ||||||
|     name = "go_default_library", |  | ||||||
|     srcs = [ |  | ||||||
|         "client.go", |  | ||||||
|         "config.go", |  | ||||||
|         "plugin.go", |  | ||||||
|         "request.go", |  | ||||||
|         "transport.go", |  | ||||||
|         "url_utils.go", |  | ||||||
|         "urlbackoff.go", |  | ||||||
|         "versions.go", |  | ||||||
|     ], |  | ||||||
|     tags = ["automanaged"], |  | ||||||
|     deps = [ |  | ||||||
|         "//pkg/api:go_default_library", |  | ||||||
|         "//pkg/api/v1:go_default_library", |  | ||||||
|         "//pkg/api/validation/path:go_default_library", |  | ||||||
|         "//pkg/client/metrics:go_default_library", |  | ||||||
|         "//pkg/util/cert:go_default_library", |  | ||||||
|         "//pkg/version:go_default_library", |  | ||||||
|         "//vendor:github.com/golang/glog", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/api/errors", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/fields", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/labels", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime/schema", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime/serializer/streaming", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/types", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/util/net", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/util/sets", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", |  | ||||||
|         "//vendor:k8s.io/client-go/pkg/util/flowcontrol", |  | ||||||
|         "//vendor:k8s.io/client-go/rest/watch", |  | ||||||
|         "//vendor:k8s.io/client-go/tools/clientcmd/api", |  | ||||||
|         "//vendor:k8s.io/client-go/transport", |  | ||||||
|     ], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| go_test( |  | ||||||
|     name = "go_default_test", |  | ||||||
|     srcs = [ |  | ||||||
|         "client_test.go", |  | ||||||
|         "config_test.go", |  | ||||||
|         "plugin_test.go", |  | ||||||
|         "request_test.go", |  | ||||||
|         "url_utils_test.go", |  | ||||||
|         "urlbackoff_test.go", |  | ||||||
|     ], |  | ||||||
|     library = ":go_default_library", |  | ||||||
|     tags = ["automanaged"], |  | ||||||
|     deps = [ |  | ||||||
|         "//pkg/api:go_default_library", |  | ||||||
|         "//pkg/api/testapi:go_default_library", |  | ||||||
|         "//pkg/api/v1:go_default_library", |  | ||||||
|         "//pkg/util/httpstream:go_default_library", |  | ||||||
|         "//pkg/util/intstr:go_default_library", |  | ||||||
|         "//pkg/util/testing:go_default_library", |  | ||||||
|         "//vendor:github.com/google/gofuzz", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/api/errors", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/labels", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime/schema", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime/serializer/streaming", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/types", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/util/diff", |  | ||||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", |  | ||||||
|         "//vendor:k8s.io/client-go/pkg/util/clock", |  | ||||||
|         "//vendor:k8s.io/client-go/pkg/util/flowcontrol", |  | ||||||
|         "//vendor:k8s.io/client-go/rest/watch", |  | ||||||
|         "//vendor:k8s.io/client-go/tools/clientcmd/api", |  | ||||||
|     ], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| filegroup( |  | ||||||
|     name = "package-srcs", |  | ||||||
|     srcs = glob(["**"]), |  | ||||||
|     tags = ["automanaged"], |  | ||||||
|     visibility = ["//visibility:private"], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| filegroup( |  | ||||||
|     name = "all-srcs", |  | ||||||
|     srcs = [ |  | ||||||
|         ":package-srcs", |  | ||||||
|         "//pkg/client/restclient/fake:all-srcs", |  | ||||||
|     ], |  | ||||||
|     tags = ["automanaged"], |  | ||||||
| ) |  | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| reviewers: |  | ||||||
| - thockin |  | ||||||
| - smarterclayton |  | ||||||
| - caesarxuchao |  | ||||||
| - wojtek-t |  | ||||||
| - deads2k |  | ||||||
| - brendandburns |  | ||||||
| - liggitt |  | ||||||
| - nikhiljindal |  | ||||||
| - gmarek |  | ||||||
| - erictune |  | ||||||
| - sttts |  | ||||||
| - luxas |  | ||||||
| - dims |  | ||||||
| - errordeveloper |  | ||||||
| - hongchaodeng |  | ||||||
| - krousey |  | ||||||
| - resouer |  | ||||||
| - cjcullen |  | ||||||
| - rmmh |  | ||||||
| - lixiaobing10051267 |  | ||||||
| - asalkeld |  | ||||||
| - juanvallejo |  | ||||||
| - lojies |  | ||||||
| @@ -1,258 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2014 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 restclient |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"mime" |  | ||||||
| 	"net/http" |  | ||||||
| 	"net/url" |  | ||||||
| 	"os" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" |  | ||||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" |  | ||||||
| 	"k8s.io/apimachinery/pkg/types" |  | ||||||
| 	"k8s.io/client-go/pkg/util/flowcontrol" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	// Environment variables: Note that the duration should be long enough that the backoff |  | ||||||
| 	// persists for some reasonable time (i.e. 120 seconds).  The typical base might be "1". |  | ||||||
| 	envBackoffBase     = "KUBE_CLIENT_BACKOFF_BASE" |  | ||||||
| 	envBackoffDuration = "KUBE_CLIENT_BACKOFF_DURATION" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Interface captures the set of operations for generically interacting with Kubernetes REST apis. |  | ||||||
| type Interface interface { |  | ||||||
| 	GetRateLimiter() flowcontrol.RateLimiter |  | ||||||
| 	Verb(verb string) *Request |  | ||||||
| 	Post() *Request |  | ||||||
| 	Put() *Request |  | ||||||
| 	Patch(pt types.PatchType) *Request |  | ||||||
| 	Get() *Request |  | ||||||
| 	Delete() *Request |  | ||||||
| 	APIVersion() schema.GroupVersion |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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 |  | ||||||
| // of one or more resources.  The server should return a decodable API resource |  | ||||||
| // object, or an api.Status object which contains information about the reason for |  | ||||||
| // any failure. |  | ||||||
| // |  | ||||||
| // Most consumers should use client.New() to get a Kubernetes API client. |  | ||||||
| type RESTClient struct { |  | ||||||
| 	// base is the root URL for all invocations of the client |  | ||||||
| 	base *url.URL |  | ||||||
| 	// versionedAPIPath is a path segment connecting the base URL to the resource root |  | ||||||
| 	versionedAPIPath string |  | ||||||
|  |  | ||||||
| 	// contentConfig is the information used to communicate with the server. |  | ||||||
| 	contentConfig ContentConfig |  | ||||||
|  |  | ||||||
| 	// serializers contain all serializers for underlying content type. |  | ||||||
| 	serializers Serializers |  | ||||||
|  |  | ||||||
| 	// creates BackoffManager that is passed to requests. |  | ||||||
| 	createBackoffMgr func() BackoffManager |  | ||||||
|  |  | ||||||
| 	// TODO extract this into a wrapper interface via the RESTClient interface in kubectl. |  | ||||||
| 	Throttle flowcontrol.RateLimiter |  | ||||||
|  |  | ||||||
| 	// Set specific behavior of the client.  If not set http.DefaultClient will be used. |  | ||||||
| 	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 |  | ||||||
| // such as Get, Put, Post, and Delete on specified paths.  Codec controls encoding and |  | ||||||
| // decoding of responses from the server. |  | ||||||
| func NewRESTClient(baseURL *url.URL, versionedAPIPath string, config ContentConfig, maxQPS float32, maxBurst int, rateLimiter flowcontrol.RateLimiter, client *http.Client) (*RESTClient, error) { |  | ||||||
| 	base := *baseURL |  | ||||||
| 	if !strings.HasSuffix(base.Path, "/") { |  | ||||||
| 		base.Path += "/" |  | ||||||
| 	} |  | ||||||
| 	base.RawQuery = "" |  | ||||||
| 	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{ |  | ||||||
| 		base:             &base, |  | ||||||
| 		versionedAPIPath: versionedAPIPath, |  | ||||||
| 		contentConfig:    config, |  | ||||||
| 		serializers:      *serializers, |  | ||||||
| 		createBackoffMgr: readExpBackoffConfig, |  | ||||||
| 		Throttle:         throttle, |  | ||||||
| 		Client:           client, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // GetRateLimiter returns rate limier for a given client, or nil if it's called on a nil client |  | ||||||
| func (c *RESTClient) GetRateLimiter() flowcontrol.RateLimiter { |  | ||||||
| 	if c == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return c.Throttle |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // readExpBackoffConfig handles the internal logic of determining what the |  | ||||||
| // backoff policy is.  By default if no information is available, NoBackoff. |  | ||||||
| // TODO Generalize this see #17727 . |  | ||||||
| func readExpBackoffConfig() BackoffManager { |  | ||||||
| 	backoffBase := os.Getenv(envBackoffBase) |  | ||||||
| 	backoffDuration := os.Getenv(envBackoffDuration) |  | ||||||
|  |  | ||||||
| 	backoffBaseInt, errBase := strconv.ParseInt(backoffBase, 10, 64) |  | ||||||
| 	backoffDurationInt, errDuration := strconv.ParseInt(backoffDuration, 10, 64) |  | ||||||
| 	if errBase != nil || errDuration != nil { |  | ||||||
| 		return &NoBackoff{} |  | ||||||
| 	} |  | ||||||
| 	return &URLBackoff{ |  | ||||||
| 		Backoff: flowcontrol.NewBackOff( |  | ||||||
| 			time.Duration(backoffBaseInt)*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). |  | ||||||
| // |  | ||||||
| // Example usage of RESTClient's request building interface: |  | ||||||
| // c, err := NewRESTClient(...) |  | ||||||
| // if err != nil { ... } |  | ||||||
| // resp, err := c.Verb("GET"). |  | ||||||
| //  Path("pods"). |  | ||||||
| //  SelectorParam("labels", "area=staging"). |  | ||||||
| //  Timeout(10*time.Second). |  | ||||||
| //  Do() |  | ||||||
| // if err != nil { ... } |  | ||||||
| // list, ok := resp.(*api.PodList) |  | ||||||
| // |  | ||||||
| func (c *RESTClient) Verb(verb string) *Request { |  | ||||||
| 	backoff := c.createBackoffMgr() |  | ||||||
|  |  | ||||||
| 	if c.Client == nil { |  | ||||||
| 		return NewRequest(nil, verb, c.base, c.versionedAPIPath, c.contentConfig, c.serializers, backoff, c.Throttle) |  | ||||||
| 	} |  | ||||||
| 	return NewRequest(c.Client, verb, c.base, c.versionedAPIPath, c.contentConfig, c.serializers, backoff, c.Throttle) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Post begins a POST request. Short for c.Verb("POST"). |  | ||||||
| func (c *RESTClient) Post() *Request { |  | ||||||
| 	return c.Verb("POST") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Put begins a PUT request. Short for c.Verb("PUT"). |  | ||||||
| func (c *RESTClient) Put() *Request { |  | ||||||
| 	return c.Verb("PUT") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Patch begins a PATCH request. Short for c.Verb("Patch"). |  | ||||||
| func (c *RESTClient) Patch(pt types.PatchType) *Request { |  | ||||||
| 	return c.Verb("PATCH").SetHeader("Content-Type", string(pt)) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Get begins a GET request. Short for c.Verb("GET"). |  | ||||||
| func (c *RESTClient) Get() *Request { |  | ||||||
| 	return c.Verb("GET") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Delete begins a DELETE request. Short for c.Verb("DELETE"). |  | ||||||
| func (c *RESTClient) Delete() *Request { |  | ||||||
| 	return c.Verb("DELETE") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // APIVersion returns the APIVersion this RESTClient is expected to use. |  | ||||||
| func (c *RESTClient) APIVersion() schema.GroupVersion { |  | ||||||
| 	return *c.contentConfig.GroupVersion |  | ||||||
| } |  | ||||||
| @@ -1,340 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2014 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 restclient |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 	"net/http/httptest" |  | ||||||
| 	"net/url" |  | ||||||
| 	"os" |  | ||||||
| 	"reflect" |  | ||||||
| 	"testing" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"fmt" |  | ||||||
|  |  | ||||||
| 	"k8s.io/apimachinery/pkg/api/errors" |  | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |  | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" |  | ||||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" |  | ||||||
| 	"k8s.io/apimachinery/pkg/types" |  | ||||||
| 	"k8s.io/apimachinery/pkg/util/diff" |  | ||||||
| 	"k8s.io/kubernetes/pkg/api" |  | ||||||
| 	"k8s.io/kubernetes/pkg/api/testapi" |  | ||||||
| 	utiltesting "k8s.io/kubernetes/pkg/util/testing" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type TestParam struct { |  | ||||||
| 	actualError           error |  | ||||||
| 	expectingError        bool |  | ||||||
| 	actualCreated         bool |  | ||||||
| 	expCreated            bool |  | ||||||
| 	expStatus             *metav1.Status |  | ||||||
| 	testBody              bool |  | ||||||
| 	testBodyErrorIsNotNil bool |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // TestSerializer makes sure that you're always able to decode an unversioned API object |  | ||||||
| func TestSerializer(t *testing.T) { |  | ||||||
| 	contentConfig := ContentConfig{ |  | ||||||
| 		ContentType:          "application/json", |  | ||||||
| 		GroupVersion:         &schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}, |  | ||||||
| 		NegotiatedSerializer: api.Codecs, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	serializer, err := createSerializers(contentConfig) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
| 	// 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"}`)) |  | ||||||
| 	t.Log(obj) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestDoRequestSuccess(t *testing.T) { |  | ||||||
| 	testServer, fakeHandler, status := testServerEnv(t, 200) |  | ||||||
| 	defer testServer.Close() |  | ||||||
|  |  | ||||||
| 	c, err := restClient(testServer) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	body, err := c.Get().Prefix("test").Do().Raw() |  | ||||||
|  |  | ||||||
| 	testParam := TestParam{actualError: err, expectingError: false, expCreated: true, |  | ||||||
| 		expStatus: status, testBody: true, testBodyErrorIsNotNil: false} |  | ||||||
| 	validate(testParam, t, body, fakeHandler) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestDoRequestFailed(t *testing.T) { |  | ||||||
| 	status := &metav1.Status{ |  | ||||||
| 		Code:    http.StatusNotFound, |  | ||||||
| 		Status:  metav1.StatusFailure, |  | ||||||
| 		Reason:  metav1.StatusReasonNotFound, |  | ||||||
| 		Message: " \"\" not found", |  | ||||||
| 		Details: &metav1.StatusDetails{}, |  | ||||||
| 	} |  | ||||||
| 	expectedBody, _ := runtime.Encode(testapi.Default.Codec(), status) |  | ||||||
| 	fakeHandler := utiltesting.FakeHandler{ |  | ||||||
| 		StatusCode:   404, |  | ||||||
| 		ResponseBody: string(expectedBody), |  | ||||||
| 		T:            t, |  | ||||||
| 	} |  | ||||||
| 	testServer := httptest.NewServer(&fakeHandler) |  | ||||||
| 	defer testServer.Close() |  | ||||||
|  |  | ||||||
| 	c, err := restClient(testServer) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = c.Get().Do().Error() |  | ||||||
| 	if err == nil { |  | ||||||
| 		t.Errorf("unexpected non-error") |  | ||||||
| 	} |  | ||||||
| 	ss, ok := err.(errors.APIStatus) |  | ||||||
| 	if !ok { |  | ||||||
| 		t.Errorf("unexpected error type %v", err) |  | ||||||
| 	} |  | ||||||
| 	actual := ss.Status() |  | ||||||
| 	if !reflect.DeepEqual(status, &actual) { |  | ||||||
| 		t.Errorf("Unexpected mis-match: %s", diff.ObjectReflectDiff(status, &actual)) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestDoRawRequestFailed(t *testing.T) { |  | ||||||
| 	status := &metav1.Status{ |  | ||||||
| 		Code:    http.StatusNotFound, |  | ||||||
| 		Status:  metav1.StatusFailure, |  | ||||||
| 		Reason:  metav1.StatusReasonNotFound, |  | ||||||
| 		Message: "the server could not find the requested resource", |  | ||||||
| 		Details: &metav1.StatusDetails{ |  | ||||||
| 			Causes: []metav1.StatusCause{ |  | ||||||
| 				{Type: metav1.CauseTypeUnexpectedServerResponse, Message: "unknown"}, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	expectedBody, _ := runtime.Encode(testapi.Default.Codec(), status) |  | ||||||
| 	fakeHandler := utiltesting.FakeHandler{ |  | ||||||
| 		StatusCode:   404, |  | ||||||
| 		ResponseBody: string(expectedBody), |  | ||||||
| 		T:            t, |  | ||||||
| 	} |  | ||||||
| 	testServer := httptest.NewServer(&fakeHandler) |  | ||||||
| 	defer testServer.Close() |  | ||||||
|  |  | ||||||
| 	c, err := restClient(testServer) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	body, err := c.Get().Do().Raw() |  | ||||||
|  |  | ||||||
| 	if err == nil || body == nil { |  | ||||||
| 		t.Errorf("unexpected non-error: %#v", body) |  | ||||||
| 	} |  | ||||||
| 	ss, ok := err.(errors.APIStatus) |  | ||||||
| 	if !ok { |  | ||||||
| 		t.Errorf("unexpected error type %v", err) |  | ||||||
| 	} |  | ||||||
| 	actual := ss.Status() |  | ||||||
| 	if !reflect.DeepEqual(status, &actual) { |  | ||||||
| 		t.Errorf("Unexpected mis-match: %s", diff.ObjectReflectDiff(status, &actual)) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestDoRequestCreated(t *testing.T) { |  | ||||||
| 	testServer, fakeHandler, status := testServerEnv(t, 201) |  | ||||||
| 	defer testServer.Close() |  | ||||||
|  |  | ||||||
| 	c, err := restClient(testServer) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	created := false |  | ||||||
| 	body, err := c.Get().Prefix("test").Do().WasCreated(&created).Raw() |  | ||||||
|  |  | ||||||
| 	testParam := TestParam{actualError: err, expectingError: false, expCreated: true, |  | ||||||
| 		expStatus: status, testBody: false} |  | ||||||
| 	validate(testParam, t, body, fakeHandler) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestDoRequestNotCreated(t *testing.T) { |  | ||||||
| 	testServer, fakeHandler, expectedStatus := testServerEnv(t, 202) |  | ||||||
| 	defer testServer.Close() |  | ||||||
| 	c, err := restClient(testServer) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	created := false |  | ||||||
| 	body, err := c.Get().Prefix("test").Do().WasCreated(&created).Raw() |  | ||||||
| 	testParam := TestParam{actualError: err, expectingError: false, expCreated: false, |  | ||||||
| 		expStatus: expectedStatus, testBody: false} |  | ||||||
| 	validate(testParam, t, body, fakeHandler) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestDoRequestAcceptedNoContentReturned(t *testing.T) { |  | ||||||
| 	testServer, fakeHandler, _ := testServerEnv(t, 204) |  | ||||||
| 	defer testServer.Close() |  | ||||||
|  |  | ||||||
| 	c, err := restClient(testServer) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	created := false |  | ||||||
| 	body, err := c.Get().Prefix("test").Do().WasCreated(&created).Raw() |  | ||||||
| 	testParam := TestParam{actualError: err, expectingError: false, expCreated: false, |  | ||||||
| 		testBody: false} |  | ||||||
| 	validate(testParam, t, body, fakeHandler) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestBadRequest(t *testing.T) { |  | ||||||
| 	testServer, fakeHandler, _ := testServerEnv(t, 400) |  | ||||||
| 	defer testServer.Close() |  | ||||||
| 	c, err := restClient(testServer) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	created := false |  | ||||||
| 	body, err := c.Get().Prefix("test").Do().WasCreated(&created).Raw() |  | ||||||
| 	testParam := TestParam{actualError: err, expectingError: true, expCreated: false, |  | ||||||
| 		testBody: true} |  | ||||||
| 	validate(testParam, t, body, fakeHandler) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func validate(testParam TestParam, t *testing.T, body []byte, fakeHandler *utiltesting.FakeHandler) { |  | ||||||
| 	switch { |  | ||||||
| 	case testParam.expectingError && testParam.actualError == nil: |  | ||||||
| 		t.Errorf("Expected error") |  | ||||||
| 	case !testParam.expectingError && testParam.actualError != nil: |  | ||||||
| 		t.Error(testParam.actualError) |  | ||||||
| 	} |  | ||||||
| 	if !testParam.expCreated { |  | ||||||
| 		if testParam.actualCreated { |  | ||||||
| 			t.Errorf("Expected object not to be created") |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	statusOut, err := runtime.Decode(testapi.Default.Codec(), body) |  | ||||||
| 	if testParam.testBody { |  | ||||||
| 		if testParam.testBodyErrorIsNotNil { |  | ||||||
| 			if err == nil { |  | ||||||
| 				t.Errorf("Expected Error") |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if testParam.expStatus != nil { |  | ||||||
| 		if !reflect.DeepEqual(testParam.expStatus, statusOut) { |  | ||||||
| 			t.Errorf("Unexpected mis-match. Expected %#v.  Saw %#v", testParam.expStatus, statusOut) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	fakeHandler.ValidateRequest(t, "/"+api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()+"/test", "GET", nil) |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestHttpMethods(t *testing.T) { |  | ||||||
| 	testServer, _, _ := testServerEnv(t, 200) |  | ||||||
| 	defer testServer.Close() |  | ||||||
| 	c, _ := restClient(testServer) |  | ||||||
|  |  | ||||||
| 	request := c.Post() |  | ||||||
| 	if request == nil { |  | ||||||
| 		t.Errorf("Post : Object returned should not be nil") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	request = c.Get() |  | ||||||
| 	if request == nil { |  | ||||||
| 		t.Errorf("Get: Object returned should not be nil") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	request = c.Put() |  | ||||||
| 	if request == nil { |  | ||||||
| 		t.Errorf("Put : Object returned should not be nil") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	request = c.Delete() |  | ||||||
| 	if request == nil { |  | ||||||
| 		t.Errorf("Delete : Object returned should not be nil") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	request = c.Patch(types.JSONPatchType) |  | ||||||
| 	if request == nil { |  | ||||||
| 		t.Errorf("Patch : Object returned should not be nil") |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestCreateBackoffManager(t *testing.T) { |  | ||||||
|  |  | ||||||
| 	theUrl, _ := url.Parse("http://localhost") |  | ||||||
|  |  | ||||||
| 	// 1 second base backoff + duration of 2 seconds -> exponential backoff for requests. |  | ||||||
| 	os.Setenv(envBackoffBase, "1") |  | ||||||
| 	os.Setenv(envBackoffDuration, "2") |  | ||||||
| 	backoff := readExpBackoffConfig() |  | ||||||
| 	backoff.UpdateBackoff(theUrl, nil, 500) |  | ||||||
| 	backoff.UpdateBackoff(theUrl, nil, 500) |  | ||||||
| 	if backoff.CalculateBackoff(theUrl)/time.Second != 2 { |  | ||||||
| 		t.Errorf("Backoff env not working.") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// 0 duration -> no backoff. |  | ||||||
| 	os.Setenv(envBackoffBase, "1") |  | ||||||
| 	os.Setenv(envBackoffDuration, "0") |  | ||||||
| 	backoff.UpdateBackoff(theUrl, nil, 500) |  | ||||||
| 	backoff.UpdateBackoff(theUrl, nil, 500) |  | ||||||
| 	backoff = readExpBackoffConfig() |  | ||||||
| 	if backoff.CalculateBackoff(theUrl)/time.Second != 0 { |  | ||||||
| 		t.Errorf("Zero backoff duration, but backoff still occuring.") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// No env -> No backoff. |  | ||||||
| 	os.Setenv(envBackoffBase, "") |  | ||||||
| 	os.Setenv(envBackoffDuration, "") |  | ||||||
| 	backoff = readExpBackoffConfig() |  | ||||||
| 	backoff.UpdateBackoff(theUrl, nil, 500) |  | ||||||
| 	backoff.UpdateBackoff(theUrl, nil, 500) |  | ||||||
| 	if backoff.CalculateBackoff(theUrl)/time.Second != 0 { |  | ||||||
| 		t.Errorf("Backoff should have been 0.") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func testServerEnv(t *testing.T, statusCode int) (*httptest.Server, *utiltesting.FakeHandler, *metav1.Status) { |  | ||||||
| 	status := &metav1.Status{Status: fmt.Sprintf("%s", metav1.StatusSuccess)} |  | ||||||
| 	expectedBody, _ := runtime.Encode(testapi.Default.Codec(), status) |  | ||||||
| 	fakeHandler := utiltesting.FakeHandler{ |  | ||||||
| 		StatusCode:   statusCode, |  | ||||||
| 		ResponseBody: string(expectedBody), |  | ||||||
| 		T:            t, |  | ||||||
| 	} |  | ||||||
| 	testServer := httptest.NewServer(&fakeHandler) |  | ||||||
| 	return testServer, &fakeHandler, status |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func restClient(testServer *httptest.Server) (*RESTClient, error) { |  | ||||||
| 	c, err := RESTClientFor(&Config{ |  | ||||||
| 		Host: testServer.URL, |  | ||||||
| 		ContentConfig: ContentConfig{ |  | ||||||
| 			GroupVersion:         &api.Registry.GroupOrDie(api.GroupName).GroupVersion, |  | ||||||
| 			NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), |  | ||||||
| 		}, |  | ||||||
| 		Username: "user", |  | ||||||
| 		Password: "pass", |  | ||||||
| 	}) |  | ||||||
| 	return c, err |  | ||||||
| } |  | ||||||
| @@ -1,380 +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 restclient |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"net" |  | ||||||
| 	"net/http" |  | ||||||
| 	"os" |  | ||||||
| 	"path" |  | ||||||
| 	gruntime "runtime" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/golang/glog" |  | ||||||
|  |  | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |  | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" |  | ||||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" |  | ||||||
| 	"k8s.io/client-go/pkg/util/flowcontrol" |  | ||||||
| 	clientcmdapi "k8s.io/client-go/tools/clientcmd/api" |  | ||||||
| 	"k8s.io/kubernetes/pkg/api" |  | ||||||
| 	certutil "k8s.io/kubernetes/pkg/util/cert" |  | ||||||
| 	"k8s.io/kubernetes/pkg/version" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	DefaultQPS   float32 = 5.0 |  | ||||||
| 	DefaultBurst int     = 10 |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Config holds the common attributes that can be passed to a Kubernetes client on |  | ||||||
| // initialization. |  | ||||||
| type Config struct { |  | ||||||
| 	// Host must be a host string, a host:port pair, or a URL to the base of the apiserver. |  | ||||||
| 	// If a URL is given then the (optional) Path of that URL represents a prefix that must |  | ||||||
| 	// be appended to all request URIs used to access the apiserver. This allows a frontend |  | ||||||
| 	// proxy to easily relocate all of the apiserver endpoints. |  | ||||||
| 	Host string |  | ||||||
| 	// APIPath is a sub-path that points to an API root. |  | ||||||
| 	APIPath string |  | ||||||
| 	// Prefix is the sub path of the server. If not specified, the client will set |  | ||||||
| 	// a default value.  Use "/" to indicate the server root should be used |  | ||||||
| 	Prefix string |  | ||||||
|  |  | ||||||
| 	// ContentConfig contains settings that affect how objects are transformed when |  | ||||||
| 	// sent to the server. |  | ||||||
| 	ContentConfig |  | ||||||
|  |  | ||||||
| 	// Server requires Basic authentication |  | ||||||
| 	Username string |  | ||||||
| 	Password string |  | ||||||
|  |  | ||||||
| 	// Server requires Bearer authentication. This client will not attempt to use |  | ||||||
| 	// refresh tokens for an OAuth2 flow. |  | ||||||
| 	// TODO: demonstrate an OAuth2 compatible client. |  | ||||||
| 	BearerToken string |  | ||||||
|  |  | ||||||
| 	// Impersonate is the configuration that RESTClient will use for impersonation. |  | ||||||
| 	Impersonate ImpersonationConfig |  | ||||||
|  |  | ||||||
| 	// Server requires plugin-specified authentication. |  | ||||||
| 	AuthProvider *clientcmdapi.AuthProviderConfig |  | ||||||
|  |  | ||||||
| 	// Callback to persist config for AuthProvider. |  | ||||||
| 	AuthConfigPersister AuthProviderConfigPersister |  | ||||||
|  |  | ||||||
| 	// TLSClientConfig contains settings to enable transport layer security |  | ||||||
| 	TLSClientConfig |  | ||||||
|  |  | ||||||
| 	// Server should be accessed without verifying the TLS |  | ||||||
| 	// certificate. For testing only. |  | ||||||
| 	Insecure bool |  | ||||||
|  |  | ||||||
| 	// UserAgent is an optional field that specifies the caller of this request. |  | ||||||
| 	UserAgent string |  | ||||||
|  |  | ||||||
| 	// Transport may be used for custom HTTP behavior. This attribute may not |  | ||||||
| 	// be specified with the TLS client certificate options. Use WrapTransport |  | ||||||
| 	// for most client level operations. |  | ||||||
| 	Transport http.RoundTripper |  | ||||||
| 	// WrapTransport will be invoked for custom HTTP behavior after the underlying |  | ||||||
| 	// transport is initialized (either the transport created from TLSClientConfig, |  | ||||||
| 	// Transport, or http.DefaultTransport). The config may layer other RoundTrippers |  | ||||||
| 	// on top of the returned RoundTripper. |  | ||||||
| 	WrapTransport func(rt http.RoundTripper) http.RoundTripper |  | ||||||
|  |  | ||||||
| 	// QPS indicates the maximum QPS to the master from this client. |  | ||||||
| 	// If it's zero, the created RESTClient will use DefaultQPS: 5 |  | ||||||
| 	QPS float32 |  | ||||||
|  |  | ||||||
| 	// Maximum burst for throttle. |  | ||||||
| 	// If it's zero, the created RESTClient will use DefaultBurst: 10. |  | ||||||
| 	Burst int |  | ||||||
|  |  | ||||||
| 	// Rate limiter for limiting connections to the master from this client. If present overwrites QPS/Burst |  | ||||||
| 	RateLimiter flowcontrol.RateLimiter |  | ||||||
|  |  | ||||||
| 	// The maximum length of time to wait before giving up on a server request. A value of zero means no timeout. |  | ||||||
| 	Timeout time.Duration |  | ||||||
|  |  | ||||||
| 	// Version forces a specific version to be used (if registered) |  | ||||||
| 	// Do we need this? |  | ||||||
| 	// Version string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ImpersonationConfig has all the available impersonation options |  | ||||||
| type ImpersonationConfig struct { |  | ||||||
| 	// UserName is the username to impersonate on each request. |  | ||||||
| 	UserName string |  | ||||||
| 	// Groups are the groups to impersonate on each request. |  | ||||||
| 	Groups []string |  | ||||||
| 	// Extra is a free-form field which can be used to link some authentication information |  | ||||||
| 	// to authorization information.  This field allows you to impersonate it. |  | ||||||
| 	Extra map[string][]string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // TLSClientConfig contains settings to enable transport layer security |  | ||||||
| type TLSClientConfig struct { |  | ||||||
| 	// Server requires TLS client certificate authentication |  | ||||||
| 	CertFile string |  | ||||||
| 	// Server requires TLS client certificate authentication |  | ||||||
| 	KeyFile string |  | ||||||
| 	// Trusted root certificates for server |  | ||||||
| 	CAFile string |  | ||||||
|  |  | ||||||
| 	// CertData holds PEM-encoded bytes (typically read from a client certificate file). |  | ||||||
| 	// CertData takes precedence over CertFile |  | ||||||
| 	CertData []byte |  | ||||||
| 	// KeyData holds PEM-encoded bytes (typically read from a client certificate key file). |  | ||||||
| 	// KeyData takes precedence over KeyFile |  | ||||||
| 	KeyData []byte |  | ||||||
| 	// CAData holds PEM-encoded bytes (typically read from a root certificates bundle). |  | ||||||
| 	// CAData takes precedence over CAFile |  | ||||||
| 	CAData []byte |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type ContentConfig 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, 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. |  | ||||||
| 	GroupVersion *schema.GroupVersion |  | ||||||
| 	// NegotiatedSerializer is used for obtaining encoders and decoders for multiple |  | ||||||
| 	// supported media types. |  | ||||||
| 	NegotiatedSerializer runtime.NegotiatedSerializer |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config |  | ||||||
| // object. Note that a RESTClient may require fields that are optional when initializing a Client. |  | ||||||
| // A RESTClient created by this method is generic - it expects to operate on an API that follows |  | ||||||
| // the Kubernetes conventions, but may not be the Kubernetes API. |  | ||||||
| func RESTClientFor(config *Config) (*RESTClient, error) { |  | ||||||
| 	if config.GroupVersion == nil { |  | ||||||
| 		return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient") |  | ||||||
| 	} |  | ||||||
| 	if config.NegotiatedSerializer == nil { |  | ||||||
| 		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) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	transport, err := TransportFor(config) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var httpClient *http.Client |  | ||||||
| 	if transport != http.DefaultTransport { |  | ||||||
| 		httpClient = &http.Client{Transport: transport} |  | ||||||
| 		if config.Timeout > 0 { |  | ||||||
| 			httpClient.Timeout = config.Timeout |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return NewRESTClient(baseURL, versionedAPIPath, config.ContentConfig, qps, burst, config.RateLimiter, httpClient) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // UnversionedRESTClientFor is the same as RESTClientFor, except that it allows |  | ||||||
| // the config.Version to be empty. |  | ||||||
| func UnversionedRESTClientFor(config *Config) (*RESTClient, error) { |  | ||||||
| 	if config.NegotiatedSerializer == nil { |  | ||||||
| 		return nil, fmt.Errorf("NeogitatedSerializer is required when initializing a RESTClient") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	baseURL, versionedAPIPath, err := defaultServerUrlFor(config) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	transport, err := TransportFor(config) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var httpClient *http.Client |  | ||||||
| 	if transport != http.DefaultTransport { |  | ||||||
| 		httpClient = &http.Client{Transport: transport} |  | ||||||
| 		if config.Timeout > 0 { |  | ||||||
| 			httpClient.Timeout = config.Timeout |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	versionConfig := config.ContentConfig |  | ||||||
| 	if versionConfig.GroupVersion == nil { |  | ||||||
| 		v := metav1.SchemeGroupVersion |  | ||||||
| 		versionConfig.GroupVersion = &v |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return NewRESTClient(baseURL, versionedAPIPath, versionConfig, config.QPS, config.Burst, config.RateLimiter, httpClient) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SetKubernetesDefaults sets default values on the provided client config for accessing the |  | ||||||
| // Kubernetes API or returns an error if any of the defaults are impossible or invalid. |  | ||||||
| func SetKubernetesDefaults(config *Config) error { |  | ||||||
| 	if len(config.UserAgent) == 0 { |  | ||||||
| 		config.UserAgent = DefaultKubernetesUserAgent() |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // DefaultKubernetesUserAgent returns the default user agent that clients can use. |  | ||||||
| func DefaultKubernetesUserAgent() string { |  | ||||||
| 	commit := version.Get().GitCommit |  | ||||||
| 	if len(commit) > 7 { |  | ||||||
| 		commit = commit[:7] |  | ||||||
| 	} |  | ||||||
| 	if len(commit) == 0 { |  | ||||||
| 		commit = "unknown" |  | ||||||
| 	} |  | ||||||
| 	version := version.Get().GitVersion |  | ||||||
| 	seg := strings.SplitN(version, "-", 2) |  | ||||||
| 	version = seg[0] |  | ||||||
| 	return fmt.Sprintf("%s/%s (%s/%s) kubernetes/%s", path.Base(os.Args[0]), version, gruntime.GOOS, gruntime.GOARCH, commit) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // InClusterConfig returns a config object which uses the service account |  | ||||||
| // kubernetes gives to pods. It's intended for clients that expect to be |  | ||||||
| // running inside a pod running on kubernetes. It will return an error if |  | ||||||
| // called from a process not running in a kubernetes environment. |  | ||||||
| func InClusterConfig() (*Config, error) { |  | ||||||
| 	host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT") |  | ||||||
| 	if len(host) == 0 || len(port) == 0 { |  | ||||||
| 		return nil, fmt.Errorf("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountTokenKey) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	tlsClientConfig := TLSClientConfig{} |  | ||||||
| 	rootCAFile := "/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountRootCAKey |  | ||||||
| 	if _, err := certutil.NewPool(rootCAFile); err != nil { |  | ||||||
| 		glog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err) |  | ||||||
| 	} else { |  | ||||||
| 		tlsClientConfig.CAFile = rootCAFile |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return &Config{ |  | ||||||
| 		// TODO: switch to using cluster DNS. |  | ||||||
| 		Host:            "https://" + net.JoinHostPort(host, port), |  | ||||||
| 		BearerToken:     string(token), |  | ||||||
| 		TLSClientConfig: tlsClientConfig, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // IsConfigTransportTLS returns true if and only if the provided |  | ||||||
| // config will result in a protected connection to the server when it |  | ||||||
| // is passed to restclient.RESTClientFor().  Use to determine when to |  | ||||||
| // send credentials over the wire. |  | ||||||
| // |  | ||||||
| // Note: the Insecure flag is ignored when testing for this value, so MITM attacks are |  | ||||||
| // still possible. |  | ||||||
| func IsConfigTransportTLS(config Config) bool { |  | ||||||
| 	baseURL, _, err := defaultServerUrlFor(&config) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	return baseURL.Scheme == "https" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // LoadTLSFiles copies the data from the CertFile, KeyFile, and CAFile fields into the CertData, |  | ||||||
| // KeyData, and CAFile fields, or returns an error. If no error is returned, all three fields are |  | ||||||
| // either populated or were empty to start. |  | ||||||
| func LoadTLSFiles(c *Config) error { |  | ||||||
| 	var err error |  | ||||||
| 	c.CAData, err = dataFromSliceOrFile(c.CAData, c.CAFile) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	c.CertData, err = dataFromSliceOrFile(c.CertData, c.CertFile) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	c.KeyData, err = dataFromSliceOrFile(c.KeyData, c.KeyFile) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // dataFromSliceOrFile returns data from the slice (if non-empty), or from the file, |  | ||||||
| // or an error if an error occurred reading the file |  | ||||||
| func dataFromSliceOrFile(data []byte, file string) ([]byte, error) { |  | ||||||
| 	if len(data) > 0 { |  | ||||||
| 		return data, nil |  | ||||||
| 	} |  | ||||||
| 	if len(file) > 0 { |  | ||||||
| 		fileData, err := ioutil.ReadFile(file) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return []byte{}, err |  | ||||||
| 		} |  | ||||||
| 		return fileData, nil |  | ||||||
| 	} |  | ||||||
| 	return nil, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func AddUserAgent(config *Config, userAgent string) *Config { |  | ||||||
| 	fullUserAgent := DefaultKubernetesUserAgent() + "/" + userAgent |  | ||||||
| 	config.UserAgent = fullUserAgent |  | ||||||
| 	return config |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // AnonymousClientConfig returns a copy of the given config with all user credentials (cert/key, bearer token, and username/password) removed |  | ||||||
| func AnonymousClientConfig(config *Config) *Config { |  | ||||||
| 	// copy only known safe fields |  | ||||||
| 	return &Config{ |  | ||||||
| 		Host:          config.Host, |  | ||||||
| 		APIPath:       config.APIPath, |  | ||||||
| 		Prefix:        config.Prefix, |  | ||||||
| 		ContentConfig: config.ContentConfig, |  | ||||||
| 		TLSClientConfig: TLSClientConfig{ |  | ||||||
| 			CAFile: config.TLSClientConfig.CAFile, |  | ||||||
| 			CAData: config.TLSClientConfig.CAData, |  | ||||||
| 		}, |  | ||||||
| 		RateLimiter:   config.RateLimiter, |  | ||||||
| 		Insecure:      config.Insecure, |  | ||||||
| 		UserAgent:     config.UserAgent, |  | ||||||
| 		Transport:     config.Transport, |  | ||||||
| 		WrapTransport: config.WrapTransport, |  | ||||||
| 		QPS:           config.QPS, |  | ||||||
| 		Burst:         config.Burst, |  | ||||||
| 		Timeout:       config.Timeout, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,231 +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 restclient |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"io" |  | ||||||
| 	"net/http" |  | ||||||
| 	"reflect" |  | ||||||
| 	"strings" |  | ||||||
| 	"testing" |  | ||||||
|  |  | ||||||
| 	fuzz "github.com/google/gofuzz" |  | ||||||
|  |  | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" |  | ||||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" |  | ||||||
| 	"k8s.io/apimachinery/pkg/util/diff" |  | ||||||
| 	"k8s.io/client-go/pkg/util/flowcontrol" |  | ||||||
| 	clientcmdapi "k8s.io/client-go/tools/clientcmd/api" |  | ||||||
| 	"k8s.io/kubernetes/pkg/api" |  | ||||||
| 	"k8s.io/kubernetes/pkg/api/testapi" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func TestIsConfigTransportTLS(t *testing.T) { |  | ||||||
| 	testCases := []struct { |  | ||||||
| 		Config       *Config |  | ||||||
| 		TransportTLS bool |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			Config:       &Config{}, |  | ||||||
| 			TransportTLS: false, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			Config: &Config{ |  | ||||||
| 				Host: "https://localhost", |  | ||||||
| 			}, |  | ||||||
| 			TransportTLS: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			Config: &Config{ |  | ||||||
| 				Host: "localhost", |  | ||||||
| 				TLSClientConfig: TLSClientConfig{ |  | ||||||
| 					CertFile: "foo", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			TransportTLS: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			Config: &Config{ |  | ||||||
| 				Host: "///:://localhost", |  | ||||||
| 				TLSClientConfig: TLSClientConfig{ |  | ||||||
| 					CertFile: "foo", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			TransportTLS: false, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			Config: &Config{ |  | ||||||
| 				Host:     "1.2.3.4:567", |  | ||||||
| 				Insecure: true, |  | ||||||
| 			}, |  | ||||||
| 			TransportTLS: true, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	for _, testCase := range testCases { |  | ||||||
| 		if err := SetKubernetesDefaults(testCase.Config); err != nil { |  | ||||||
| 			t.Errorf("setting defaults failed for %#v: %v", testCase.Config, err) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		useTLS := IsConfigTransportTLS(*testCase.Config) |  | ||||||
| 		if testCase.TransportTLS != useTLS { |  | ||||||
| 			t.Errorf("expected %v for %#v", testCase.TransportTLS, testCase.Config) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestSetKubernetesDefaultsUserAgent(t *testing.T) { |  | ||||||
| 	config := &Config{} |  | ||||||
| 	if err := SetKubernetesDefaults(config); err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	if !strings.Contains(config.UserAgent, "kubernetes/") { |  | ||||||
| 		t.Errorf("no user agent set: %#v", config) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestRESTClientRequires(t *testing.T) { |  | ||||||
| 	if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{NegotiatedSerializer: testapi.Default.NegotiatedSerializer()}}); err == nil { |  | ||||||
| 		t.Errorf("unexpected non-error") |  | ||||||
| 	} |  | ||||||
| 	if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion}}); err == nil { |  | ||||||
| 		t.Errorf("unexpected non-error") |  | ||||||
| 	} |  | ||||||
| 	if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion, NegotiatedSerializer: testapi.Default.NegotiatedSerializer()}}); err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type fakeLimiter struct { |  | ||||||
| 	FakeSaturation float64 |  | ||||||
| 	FakeQPS        float32 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *fakeLimiter) TryAccept() bool { |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *fakeLimiter) Saturation() float64 { |  | ||||||
| 	return t.FakeSaturation |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *fakeLimiter) QPS() float32 { |  | ||||||
| 	return t.FakeQPS |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *fakeLimiter) Stop() {} |  | ||||||
|  |  | ||||||
| func (t *fakeLimiter) Accept() {} |  | ||||||
|  |  | ||||||
| type fakeCodec struct{} |  | ||||||
|  |  | ||||||
| func (c *fakeCodec) Decode([]byte, *schema.GroupVersionKind, runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { |  | ||||||
| 	return nil, nil, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *fakeCodec) Encode(obj runtime.Object, stream io.Writer) error { |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type fakeRoundTripper struct{} |  | ||||||
|  |  | ||||||
| func (r *fakeRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { |  | ||||||
| 	return nil, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var fakeWrapperFunc = func(http.RoundTripper) http.RoundTripper { |  | ||||||
| 	return &fakeRoundTripper{} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type fakeNegotiatedSerializer struct{} |  | ||||||
|  |  | ||||||
| func (n *fakeNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo { |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (n *fakeNegotiatedSerializer) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { |  | ||||||
| 	return &fakeCodec{} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (n *fakeNegotiatedSerializer) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { |  | ||||||
| 	return &fakeCodec{} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestAnonymousConfig(t *testing.T) { |  | ||||||
| 	f := fuzz.New().NilChance(0.0).NumElements(1, 1) |  | ||||||
| 	f.Funcs( |  | ||||||
| 		func(r *runtime.Codec, f fuzz.Continue) { |  | ||||||
| 			codec := &fakeCodec{} |  | ||||||
| 			f.Fuzz(codec) |  | ||||||
| 			*r = codec |  | ||||||
| 		}, |  | ||||||
| 		func(r *http.RoundTripper, f fuzz.Continue) { |  | ||||||
| 			roundTripper := &fakeRoundTripper{} |  | ||||||
| 			f.Fuzz(roundTripper) |  | ||||||
| 			*r = roundTripper |  | ||||||
| 		}, |  | ||||||
| 		func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) { |  | ||||||
| 			*fn = fakeWrapperFunc |  | ||||||
| 		}, |  | ||||||
| 		func(r *runtime.NegotiatedSerializer, f fuzz.Continue) { |  | ||||||
| 			serializer := &fakeNegotiatedSerializer{} |  | ||||||
| 			f.Fuzz(serializer) |  | ||||||
| 			*r = serializer |  | ||||||
| 		}, |  | ||||||
| 		func(r *flowcontrol.RateLimiter, f fuzz.Continue) { |  | ||||||
| 			limiter := &fakeLimiter{} |  | ||||||
| 			f.Fuzz(limiter) |  | ||||||
| 			*r = limiter |  | ||||||
| 		}, |  | ||||||
| 		// Authentication does not require fuzzer |  | ||||||
| 		func(r *AuthProviderConfigPersister, f fuzz.Continue) {}, |  | ||||||
| 		func(r *clientcmdapi.AuthProviderConfig, f fuzz.Continue) { |  | ||||||
| 			r.Config = map[string]string{} |  | ||||||
| 		}, |  | ||||||
| 	) |  | ||||||
| 	for i := 0; i < 20; i++ { |  | ||||||
| 		original := &Config{} |  | ||||||
| 		f.Fuzz(original) |  | ||||||
| 		actual := AnonymousClientConfig(original) |  | ||||||
| 		expected := *original |  | ||||||
|  |  | ||||||
| 		// this is the list of known security related fields, add to this list if a new field |  | ||||||
| 		// is added to Config, update AnonymousClientConfig to preserve the field otherwise. |  | ||||||
| 		expected.Impersonate = ImpersonationConfig{} |  | ||||||
| 		expected.BearerToken = "" |  | ||||||
| 		expected.Username = "" |  | ||||||
| 		expected.Password = "" |  | ||||||
| 		expected.AuthProvider = nil |  | ||||||
| 		expected.AuthConfigPersister = nil |  | ||||||
| 		expected.TLSClientConfig.CertData = nil |  | ||||||
| 		expected.TLSClientConfig.CertFile = "" |  | ||||||
| 		expected.TLSClientConfig.KeyData = nil |  | ||||||
| 		expected.TLSClientConfig.KeyFile = "" |  | ||||||
|  |  | ||||||
| 		// The DeepEqual cannot handle the func comparison, so we just verify if the |  | ||||||
| 		// function return the expected object. |  | ||||||
| 		if actual.WrapTransport == nil || !reflect.DeepEqual(expected.WrapTransport(nil), &fakeRoundTripper{}) { |  | ||||||
| 			t.Fatalf("AnonymousClientConfig dropped the WrapTransport field") |  | ||||||
| 		} else { |  | ||||||
| 			actual.WrapTransport = nil |  | ||||||
| 			expected.WrapTransport = nil |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if !reflect.DeepEqual(*actual, expected) { |  | ||||||
| 			t.Fatalf("AnonymousClientConfig dropped unexpected fields, identify whether they are security related or not: %s", diff.ObjectGoPrintDiff(expected, actual)) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,73 +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 restclient |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" |  | ||||||
| 	"sync" |  | ||||||
|  |  | ||||||
| 	"github.com/golang/glog" |  | ||||||
|  |  | ||||||
| 	clientcmdapi "k8s.io/client-go/tools/clientcmd/api" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type AuthProvider interface { |  | ||||||
| 	// WrapTransport allows the plugin to create a modified RoundTripper that |  | ||||||
| 	// attaches authorization headers (or other info) to requests. |  | ||||||
| 	WrapTransport(http.RoundTripper) http.RoundTripper |  | ||||||
| 	// Login allows the plugin to initialize its configuration. It must not |  | ||||||
| 	// require direct user interaction. |  | ||||||
| 	Login() error |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Factory generates an AuthProvider plugin. |  | ||||||
| //  clusterAddress is the address of the current cluster. |  | ||||||
| //  config is the initial configuration for this plugin. |  | ||||||
| //  persister allows the plugin to save updated configuration. |  | ||||||
| type Factory func(clusterAddress string, config map[string]string, persister AuthProviderConfigPersister) (AuthProvider, error) |  | ||||||
|  |  | ||||||
| // AuthProviderConfigPersister allows a plugin to persist configuration info |  | ||||||
| // for just itself. |  | ||||||
| type AuthProviderConfigPersister interface { |  | ||||||
| 	Persist(map[string]string) error |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // All registered auth provider plugins. |  | ||||||
| var pluginsLock sync.Mutex |  | ||||||
| var plugins = make(map[string]Factory) |  | ||||||
|  |  | ||||||
| func RegisterAuthProviderPlugin(name string, plugin Factory) error { |  | ||||||
| 	pluginsLock.Lock() |  | ||||||
| 	defer pluginsLock.Unlock() |  | ||||||
| 	if _, found := plugins[name]; found { |  | ||||||
| 		return fmt.Errorf("Auth Provider Plugin %q was registered twice", name) |  | ||||||
| 	} |  | ||||||
| 	glog.V(4).Infof("Registered Auth Provider Plugin %q", name) |  | ||||||
| 	plugins[name] = plugin |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func GetAuthProvider(clusterAddress string, apc *clientcmdapi.AuthProviderConfig, persister AuthProviderConfigPersister) (AuthProvider, error) { |  | ||||||
| 	pluginsLock.Lock() |  | ||||||
| 	defer pluginsLock.Unlock() |  | ||||||
| 	p, ok := plugins[apc.Name] |  | ||||||
| 	if !ok { |  | ||||||
| 		return nil, fmt.Errorf("No Auth Provider found for name %q", apc.Name) |  | ||||||
| 	} |  | ||||||
| 	return p(clusterAddress, apc.Config, persister) |  | ||||||
| } |  | ||||||
| @@ -1,311 +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 restclient |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" |  | ||||||
| 	"reflect" |  | ||||||
| 	"strconv" |  | ||||||
| 	"testing" |  | ||||||
|  |  | ||||||
| 	clientcmdapi "k8s.io/client-go/tools/clientcmd/api" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func TestAuthPluginWrapTransport(t *testing.T) { |  | ||||||
| 	if err := RegisterAuthProviderPlugin("pluginA", pluginAProvider); err != nil { |  | ||||||
| 		t.Errorf("Unexpected error: failed to register pluginA: %v", err) |  | ||||||
| 	} |  | ||||||
| 	if err := RegisterAuthProviderPlugin("pluginB", pluginBProvider); err != nil { |  | ||||||
| 		t.Errorf("Unexpected error: failed to register pluginB: %v", err) |  | ||||||
| 	} |  | ||||||
| 	if err := RegisterAuthProviderPlugin("pluginFail", pluginFailProvider); err != nil { |  | ||||||
| 		t.Errorf("Unexpected error: failed to register pluginFail: %v", err) |  | ||||||
| 	} |  | ||||||
| 	testCases := []struct { |  | ||||||
| 		useWrapTransport bool |  | ||||||
| 		plugin           string |  | ||||||
| 		expectErr        bool |  | ||||||
| 		expectPluginA    bool |  | ||||||
| 		expectPluginB    bool |  | ||||||
| 	}{ |  | ||||||
| 		{false, "", false, false, false}, |  | ||||||
| 		{false, "pluginA", false, true, false}, |  | ||||||
| 		{false, "pluginB", false, false, true}, |  | ||||||
| 		{false, "pluginFail", true, false, false}, |  | ||||||
| 		{false, "pluginUnknown", true, false, false}, |  | ||||||
| 	} |  | ||||||
| 	for i, tc := range testCases { |  | ||||||
| 		c := Config{} |  | ||||||
| 		if tc.useWrapTransport { |  | ||||||
| 			// Specify an existing WrapTransport in the config to make sure that |  | ||||||
| 			// plugins play nicely. |  | ||||||
| 			c.WrapTransport = func(rt http.RoundTripper) http.RoundTripper { |  | ||||||
| 				return &wrapTransport{rt} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if len(tc.plugin) != 0 { |  | ||||||
| 			c.AuthProvider = &clientcmdapi.AuthProviderConfig{Name: tc.plugin} |  | ||||||
| 		} |  | ||||||
| 		tConfig, err := c.TransportConfig() |  | ||||||
| 		if err != nil { |  | ||||||
| 			// Unknown/bad plugins are expected to fail here. |  | ||||||
| 			if !tc.expectErr { |  | ||||||
| 				t.Errorf("%d. Did not expect errors loading Auth Plugin: %q. Got: %v", i, tc.plugin, err) |  | ||||||
| 			} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		var fullyWrappedTransport http.RoundTripper |  | ||||||
| 		fullyWrappedTransport = &emptyTransport{} |  | ||||||
| 		if tConfig.WrapTransport != nil { |  | ||||||
| 			fullyWrappedTransport = tConfig.WrapTransport(&emptyTransport{}) |  | ||||||
| 		} |  | ||||||
| 		res, err := fullyWrappedTransport.RoundTrip(&http.Request{}) |  | ||||||
| 		if err != nil { |  | ||||||
| 			t.Errorf("%d. Unexpected error in RoundTrip: %v", i, err) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		hasWrapTransport := res.Header.Get("wrapTransport") == "Y" |  | ||||||
| 		hasPluginA := res.Header.Get("pluginA") == "Y" |  | ||||||
| 		hasPluginB := res.Header.Get("pluginB") == "Y" |  | ||||||
| 		if hasWrapTransport != tc.useWrapTransport { |  | ||||||
| 			t.Errorf("%d. Expected Existing config.WrapTransport: %t; Got: %t", i, tc.useWrapTransport, hasWrapTransport) |  | ||||||
| 		} |  | ||||||
| 		if hasPluginA != tc.expectPluginA { |  | ||||||
| 			t.Errorf("%d. Expected Plugin A: %t; Got: %t", i, tc.expectPluginA, hasPluginA) |  | ||||||
| 		} |  | ||||||
| 		if hasPluginB != tc.expectPluginB { |  | ||||||
| 			t.Errorf("%d. Expected Plugin B: %t; Got: %t", i, tc.expectPluginB, hasPluginB) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestAuthPluginPersist(t *testing.T) { |  | ||||||
| 	// register pluginA by a different name so we don't collide across tests. |  | ||||||
| 	if err := RegisterAuthProviderPlugin("pluginA2", pluginAProvider); err != nil { |  | ||||||
| 		t.Errorf("Unexpected error: failed to register pluginA: %v", err) |  | ||||||
| 	} |  | ||||||
| 	if err := RegisterAuthProviderPlugin("pluginPersist", pluginPersistProvider); err != nil { |  | ||||||
| 		t.Errorf("Unexpected error: failed to register pluginPersist: %v", err) |  | ||||||
| 	} |  | ||||||
| 	fooBarConfig := map[string]string{"foo": "bar"} |  | ||||||
| 	testCases := []struct { |  | ||||||
| 		plugin                       string |  | ||||||
| 		startingConfig               map[string]string |  | ||||||
| 		expectedConfigAfterLogin     map[string]string |  | ||||||
| 		expectedConfigAfterRoundTrip map[string]string |  | ||||||
| 	}{ |  | ||||||
| 		// non-persisting plugins should work fine without modifying config. |  | ||||||
| 		{"pluginA2", map[string]string{}, map[string]string{}, map[string]string{}}, |  | ||||||
| 		{"pluginA2", fooBarConfig, fooBarConfig, fooBarConfig}, |  | ||||||
| 		// plugins that persist config should be able to persist when they want. |  | ||||||
| 		{ |  | ||||||
| 			"pluginPersist", |  | ||||||
| 			map[string]string{}, |  | ||||||
| 			map[string]string{ |  | ||||||
| 				"login": "Y", |  | ||||||
| 			}, |  | ||||||
| 			map[string]string{ |  | ||||||
| 				"login":      "Y", |  | ||||||
| 				"roundTrips": "1", |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"pluginPersist", |  | ||||||
| 			map[string]string{ |  | ||||||
| 				"login":      "Y", |  | ||||||
| 				"roundTrips": "123", |  | ||||||
| 			}, |  | ||||||
| 			map[string]string{ |  | ||||||
| 				"login":      "Y", |  | ||||||
| 				"roundTrips": "123", |  | ||||||
| 			}, |  | ||||||
| 			map[string]string{ |  | ||||||
| 				"login":      "Y", |  | ||||||
| 				"roundTrips": "124", |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	for i, tc := range testCases { |  | ||||||
| 		cfg := &clientcmdapi.AuthProviderConfig{ |  | ||||||
| 			Name:   tc.plugin, |  | ||||||
| 			Config: tc.startingConfig, |  | ||||||
| 		} |  | ||||||
| 		persister := &inMemoryPersister{make(map[string]string)} |  | ||||||
| 		persister.Persist(tc.startingConfig) |  | ||||||
| 		plugin, err := GetAuthProvider("127.0.0.1", cfg, persister) |  | ||||||
| 		if err != nil { |  | ||||||
| 			t.Errorf("%d. Unexpected error: failed to get plugin %q: %v", i, tc.plugin, err) |  | ||||||
| 		} |  | ||||||
| 		if err := plugin.Login(); err != nil { |  | ||||||
| 			t.Errorf("%d. Unexpected error calling Login() w/ plugin %q: %v", i, tc.plugin, err) |  | ||||||
| 		} |  | ||||||
| 		// Make sure the plugin persisted what we expect after Login(). |  | ||||||
| 		if !reflect.DeepEqual(persister.savedConfig, tc.expectedConfigAfterLogin) { |  | ||||||
| 			t.Errorf("%d. Unexpected persisted config after calling %s.Login(): \nGot:\n%v\nExpected:\n%v", |  | ||||||
| 				i, tc.plugin, persister.savedConfig, tc.expectedConfigAfterLogin) |  | ||||||
| 		} |  | ||||||
| 		if _, err := plugin.WrapTransport(&emptyTransport{}).RoundTrip(&http.Request{}); err != nil { |  | ||||||
| 			t.Errorf("%d. Unexpected error round-tripping w/ plugin %q: %v", i, tc.plugin, err) |  | ||||||
| 		} |  | ||||||
| 		// Make sure the plugin persisted what we expect after RoundTrip(). |  | ||||||
| 		if !reflect.DeepEqual(persister.savedConfig, tc.expectedConfigAfterRoundTrip) { |  | ||||||
| 			t.Errorf("%d. Unexpected persisted config after calling %s.WrapTransport.RoundTrip(): \nGot:\n%v\nExpected:\n%v", |  | ||||||
| 				i, tc.plugin, persister.savedConfig, tc.expectedConfigAfterLogin) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // emptyTransport provides an empty http.Response with an initialized header |  | ||||||
| // to allow wrapping RoundTrippers to set header values. |  | ||||||
| type emptyTransport struct{} |  | ||||||
|  |  | ||||||
| func (*emptyTransport) RoundTrip(req *http.Request) (*http.Response, error) { |  | ||||||
| 	res := &http.Response{ |  | ||||||
| 		Header: make(map[string][]string), |  | ||||||
| 	} |  | ||||||
| 	return res, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // wrapTransport sets "wrapTransport" = "Y" on the response. |  | ||||||
| type wrapTransport struct { |  | ||||||
| 	rt http.RoundTripper |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (w *wrapTransport) RoundTrip(req *http.Request) (*http.Response, error) { |  | ||||||
| 	res, err := w.rt.RoundTrip(req) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	res.Header.Add("wrapTransport", "Y") |  | ||||||
| 	return res, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // wrapTransportA sets "pluginA" = "Y" on the response. |  | ||||||
| type wrapTransportA struct { |  | ||||||
| 	rt http.RoundTripper |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (w *wrapTransportA) RoundTrip(req *http.Request) (*http.Response, error) { |  | ||||||
| 	res, err := w.rt.RoundTrip(req) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	res.Header.Add("pluginA", "Y") |  | ||||||
| 	return res, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type pluginA struct{} |  | ||||||
|  |  | ||||||
| func (*pluginA) WrapTransport(rt http.RoundTripper) http.RoundTripper { |  | ||||||
| 	return &wrapTransportA{rt} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (*pluginA) Login() error { return nil } |  | ||||||
|  |  | ||||||
| func pluginAProvider(string, map[string]string, AuthProviderConfigPersister) (AuthProvider, error) { |  | ||||||
| 	return &pluginA{}, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // wrapTransportB sets "pluginB" = "Y" on the response. |  | ||||||
| type wrapTransportB struct { |  | ||||||
| 	rt http.RoundTripper |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (w *wrapTransportB) RoundTrip(req *http.Request) (*http.Response, error) { |  | ||||||
| 	res, err := w.rt.RoundTrip(req) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	res.Header.Add("pluginB", "Y") |  | ||||||
| 	return res, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type pluginB struct{} |  | ||||||
|  |  | ||||||
| func (*pluginB) WrapTransport(rt http.RoundTripper) http.RoundTripper { |  | ||||||
| 	return &wrapTransportB{rt} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (*pluginB) Login() error { return nil } |  | ||||||
|  |  | ||||||
| func pluginBProvider(string, map[string]string, AuthProviderConfigPersister) (AuthProvider, error) { |  | ||||||
| 	return &pluginB{}, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // pluginFailProvider simulates a registered AuthPlugin that fails to load. |  | ||||||
| func pluginFailProvider(string, map[string]string, AuthProviderConfigPersister) (AuthProvider, error) { |  | ||||||
| 	return nil, fmt.Errorf("Failed to load AuthProvider") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type inMemoryPersister struct { |  | ||||||
| 	savedConfig map[string]string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (i *inMemoryPersister) Persist(config map[string]string) error { |  | ||||||
| 	i.savedConfig = make(map[string]string) |  | ||||||
| 	for k, v := range config { |  | ||||||
| 		i.savedConfig[k] = v |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // wrapTransportPersist increments the "roundTrips" entry from the config when |  | ||||||
| // roundTrip is called. |  | ||||||
| type wrapTransportPersist struct { |  | ||||||
| 	rt        http.RoundTripper |  | ||||||
| 	config    map[string]string |  | ||||||
| 	persister AuthProviderConfigPersister |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (w *wrapTransportPersist) RoundTrip(req *http.Request) (*http.Response, error) { |  | ||||||
| 	roundTrips := 0 |  | ||||||
| 	if rtVal, ok := w.config["roundTrips"]; ok { |  | ||||||
| 		var err error |  | ||||||
| 		roundTrips, err = strconv.Atoi(rtVal) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	roundTrips++ |  | ||||||
| 	w.config["roundTrips"] = fmt.Sprintf("%d", roundTrips) |  | ||||||
| 	if err := w.persister.Persist(w.config); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return w.rt.RoundTrip(req) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type pluginPersist struct { |  | ||||||
| 	config    map[string]string |  | ||||||
| 	persister AuthProviderConfigPersister |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p *pluginPersist) WrapTransport(rt http.RoundTripper) http.RoundTripper { |  | ||||||
| 	return &wrapTransportPersist{rt, p.config, p.persister} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Login sets the config entry "login" to "Y". |  | ||||||
| func (p *pluginPersist) Login() error { |  | ||||||
| 	p.config["login"] = "Y" |  | ||||||
| 	p.persister.Persist(p.config) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func pluginPersistProvider(_ string, config map[string]string, persister AuthProviderConfigPersister) (AuthProvider, error) { |  | ||||||
| 	return &pluginPersist{config, persister}, nil |  | ||||||
| } |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,98 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2014 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 restclient |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"crypto/tls" |  | ||||||
| 	"net/http" |  | ||||||
|  |  | ||||||
| 	"k8s.io/client-go/transport" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // TLSConfigFor returns a tls.Config that will provide the transport level security defined |  | ||||||
| // by the provided Config. Will return nil if no transport level security is requested. |  | ||||||
| func TLSConfigFor(config *Config) (*tls.Config, error) { |  | ||||||
| 	cfg, err := config.TransportConfig() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return transport.TLSConfigFor(cfg) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // TransportFor returns an http.RoundTripper that will provide the authentication |  | ||||||
| // or transport level security defined by the provided Config. Will return the |  | ||||||
| // default http.DefaultTransport if no special case behavior is needed. |  | ||||||
| func TransportFor(config *Config) (http.RoundTripper, error) { |  | ||||||
| 	cfg, err := config.TransportConfig() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return transport.New(cfg) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // HTTPWrappersForConfig wraps a round tripper with any relevant layered behavior from the |  | ||||||
| // config. Exposed to allow more clients that need HTTP-like behavior but then must hijack |  | ||||||
| // the underlying connection (like WebSocket or HTTP2 clients). Pure HTTP clients should use |  | ||||||
| // the higher level TransportFor or RESTClientFor methods. |  | ||||||
| func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTripper, error) { |  | ||||||
| 	cfg, err := config.TransportConfig() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return transport.HTTPWrappersForConfig(cfg, rt) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // TransportConfig converts a client config to an appropriate transport config. |  | ||||||
| func (c *Config) TransportConfig() (*transport.Config, error) { |  | ||||||
| 	wt := c.WrapTransport |  | ||||||
| 	if c.AuthProvider != nil { |  | ||||||
| 		provider, err := GetAuthProvider(c.Host, c.AuthProvider, c.AuthConfigPersister) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		if wt != nil { |  | ||||||
| 			previousWT := wt |  | ||||||
| 			wt = func(rt http.RoundTripper) http.RoundTripper { |  | ||||||
| 				return provider.WrapTransport(previousWT(rt)) |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			wt = provider.WrapTransport |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return &transport.Config{ |  | ||||||
| 		UserAgent:     c.UserAgent, |  | ||||||
| 		Transport:     c.Transport, |  | ||||||
| 		WrapTransport: wt, |  | ||||||
| 		TLS: transport.TLSConfig{ |  | ||||||
| 			CAFile:   c.CAFile, |  | ||||||
| 			CAData:   c.CAData, |  | ||||||
| 			CertFile: c.CertFile, |  | ||||||
| 			CertData: c.CertData, |  | ||||||
| 			KeyFile:  c.KeyFile, |  | ||||||
| 			KeyData:  c.KeyData, |  | ||||||
| 			Insecure: c.Insecure, |  | ||||||
| 		}, |  | ||||||
| 		Username:    c.Username, |  | ||||||
| 		Password:    c.Password, |  | ||||||
| 		BearerToken: c.BearerToken, |  | ||||||
| 		Impersonate: transport.ImpersonationConfig{ |  | ||||||
| 			UserName: c.Impersonate.UserName, |  | ||||||
| 			Groups:   c.Impersonate.Groups, |  | ||||||
| 			Extra:    c.Impersonate.Extra, |  | ||||||
| 		}, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
| @@ -1,90 +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 restclient |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/url" |  | ||||||
| 	"path" |  | ||||||
|  |  | ||||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // DefaultServerURL converts a host, host:port, or URL string to the default base server API path |  | ||||||
| // to use with a Client at a given API version following the standard conventions for a |  | ||||||
| // Kubernetes API. |  | ||||||
| func DefaultServerURL(host, apiPath string, groupVersion schema.GroupVersion, defaultTLS bool) (*url.URL, string, error) { |  | ||||||
| 	if host == "" { |  | ||||||
| 		return nil, "", fmt.Errorf("host must be a URL or a host:port pair") |  | ||||||
| 	} |  | ||||||
| 	base := host |  | ||||||
| 	hostURL, err := url.Parse(base) |  | ||||||
| 	if err != nil || hostURL.Scheme == "" || hostURL.Host == "" { |  | ||||||
| 		scheme := "http://" |  | ||||||
| 		if defaultTLS { |  | ||||||
| 			scheme = "https://" |  | ||||||
| 		} |  | ||||||
| 		hostURL, err = url.Parse(scheme + base) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, "", err |  | ||||||
| 		} |  | ||||||
| 		if hostURL.Path != "" && hostURL.Path != "/" { |  | ||||||
| 			return nil, "", fmt.Errorf("host must be a URL or a host:port pair: %q", base) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// hostURL.Path is optional; a non-empty Path is treated as a prefix that is to be applied to |  | ||||||
| 	// all URIs used to access the host. this is useful when there's a proxy in front of the |  | ||||||
| 	// apiserver that has relocated the apiserver endpoints, forwarding all requests from, for |  | ||||||
| 	// example, /a/b/c to the apiserver. in this case the Path should be /a/b/c. |  | ||||||
| 	// |  | ||||||
| 	// if running without a frontend proxy (that changes the location of the apiserver), then |  | ||||||
| 	// hostURL.Path should be blank. |  | ||||||
| 	// |  | ||||||
| 	// versionedAPIPath, a path relative to baseURL.Path, points to a versioned API base |  | ||||||
| 	versionedAPIPath := path.Join("/", apiPath) |  | ||||||
|  |  | ||||||
| 	// Add the version to the end of the path |  | ||||||
| 	if len(groupVersion.Group) > 0 { |  | ||||||
| 		versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Group, groupVersion.Version) |  | ||||||
|  |  | ||||||
| 	} else { |  | ||||||
| 		versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Version) |  | ||||||
|  |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return hostURL, versionedAPIPath, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // defaultServerUrlFor is shared between IsConfigTransportTLS and RESTClientFor. It |  | ||||||
| // requires Host and Version to be set prior to being called. |  | ||||||
| func defaultServerUrlFor(config *Config) (*url.URL, string, error) { |  | ||||||
| 	// TODO: move the default to secure when the apiserver supports TLS by default |  | ||||||
| 	// config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA." |  | ||||||
| 	hasCA := len(config.CAFile) != 0 || len(config.CAData) != 0 |  | ||||||
| 	hasCert := len(config.CertFile) != 0 || len(config.CertData) != 0 |  | ||||||
| 	defaultTLS := hasCA || hasCert || config.Insecure |  | ||||||
| 	host := config.Host |  | ||||||
| 	if host == "" { |  | ||||||
| 		host = "localhost" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if config.GroupVersion != nil { |  | ||||||
| 		return DefaultServerURL(host, config.APIPath, *config.GroupVersion, defaultTLS) |  | ||||||
| 	} |  | ||||||
| 	return DefaultServerURL(host, config.APIPath, schema.GroupVersion{}, defaultTLS) |  | ||||||
| } |  | ||||||
| @@ -1,61 +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 restclient |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"path" |  | ||||||
| 	"testing" |  | ||||||
|  |  | ||||||
| 	"k8s.io/kubernetes/pkg/api" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func TestValidatesHostParameter(t *testing.T) { |  | ||||||
| 	testCases := []struct { |  | ||||||
| 		Host    string |  | ||||||
| 		APIPath string |  | ||||||
|  |  | ||||||
| 		URL string |  | ||||||
| 		Err bool |  | ||||||
| 	}{ |  | ||||||
| 		{"127.0.0.1", "", "http://127.0.0.1/" + api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version, false}, |  | ||||||
| 		{"127.0.0.1:8080", "", "http://127.0.0.1:8080/" + api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version, false}, |  | ||||||
| 		{"foo.bar.com", "", "http://foo.bar.com/" + api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version, false}, |  | ||||||
| 		{"http://host/prefix", "", "http://host/prefix/" + api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version, false}, |  | ||||||
| 		{"http://host", "", "http://host/" + api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version, false}, |  | ||||||
| 		{"http://host", "/", "http://host/" + api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version, false}, |  | ||||||
| 		{"http://host", "/other", "http://host/other/" + api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version, false}, |  | ||||||
| 		{"host/server", "", "", true}, |  | ||||||
| 	} |  | ||||||
| 	for i, testCase := range testCases { |  | ||||||
| 		u, versionedAPIPath, err := DefaultServerURL(testCase.Host, testCase.APIPath, api.Registry.GroupOrDie(api.GroupName).GroupVersion, false) |  | ||||||
| 		switch { |  | ||||||
| 		case err == nil && testCase.Err: |  | ||||||
| 			t.Errorf("expected error but was nil") |  | ||||||
| 			continue |  | ||||||
| 		case err != nil && !testCase.Err: |  | ||||||
| 			t.Errorf("unexpected error %v", err) |  | ||||||
| 			continue |  | ||||||
| 		case err != nil: |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		u.Path = path.Join(u.Path, versionedAPIPath) |  | ||||||
| 		if e, a := testCase.URL, u.String(); e != a { |  | ||||||
| 			t.Errorf("%d: expected host %s, got %s", i, e, a) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,107 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2015 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 restclient |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"net/url" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/golang/glog" |  | ||||||
| 	"k8s.io/apimachinery/pkg/util/sets" |  | ||||||
| 	"k8s.io/client-go/pkg/util/flowcontrol" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Set of resp. Codes that we backoff for. |  | ||||||
| // In general these should be errors that indicate a server is overloaded. |  | ||||||
| // These shouldn't be configured by any user, we set them based on conventions |  | ||||||
| // described in |  | ||||||
| var serverIsOverloadedSet = sets.NewInt(429) |  | ||||||
| var maxResponseCode = 499 |  | ||||||
|  |  | ||||||
| type BackoffManager interface { |  | ||||||
| 	UpdateBackoff(actualUrl *url.URL, err error, responseCode int) |  | ||||||
| 	CalculateBackoff(actualUrl *url.URL) time.Duration |  | ||||||
| 	Sleep(d time.Duration) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // URLBackoff struct implements the semantics on top of Backoff which |  | ||||||
| // we need for URL specific exponential backoff. |  | ||||||
| type URLBackoff struct { |  | ||||||
| 	// Uses backoff as underlying implementation. |  | ||||||
| 	Backoff *flowcontrol.Backoff |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NoBackoff is a stub implementation, can be used for mocking or else as a default. |  | ||||||
| type NoBackoff struct { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (n *NoBackoff) UpdateBackoff(actualUrl *url.URL, err error, responseCode int) { |  | ||||||
| 	// do nothing. |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (n *NoBackoff) CalculateBackoff(actualUrl *url.URL) time.Duration { |  | ||||||
| 	return 0 * time.Second |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (n *NoBackoff) Sleep(d time.Duration) { |  | ||||||
| 	time.Sleep(d) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Disable makes the backoff trivial, i.e., sets it to zero.  This might be used |  | ||||||
| // by tests which want to run 1000s of mock requests without slowing down. |  | ||||||
| func (b *URLBackoff) Disable() { |  | ||||||
| 	glog.V(4).Infof("Disabling backoff strategy") |  | ||||||
| 	b.Backoff = flowcontrol.NewBackOff(0*time.Second, 0*time.Second) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // baseUrlKey returns the key which urls will be mapped to. |  | ||||||
| // For example, 127.0.0.1:8080/api/v2/abcde -> 127.0.0.1:8080. |  | ||||||
| func (b *URLBackoff) baseUrlKey(rawurl *url.URL) string { |  | ||||||
| 	// Simple implementation for now, just the host. |  | ||||||
| 	// We may backoff specific paths (i.e. "pods") differentially |  | ||||||
| 	// in the future. |  | ||||||
| 	host, err := url.Parse(rawurl.String()) |  | ||||||
| 	if err != nil { |  | ||||||
| 		glog.V(4).Infof("Error extracting url: %v", rawurl) |  | ||||||
| 		panic("bad url!") |  | ||||||
| 	} |  | ||||||
| 	return host.Host |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // UpdateBackoff updates backoff metadata |  | ||||||
| func (b *URLBackoff) UpdateBackoff(actualUrl *url.URL, err error, responseCode int) { |  | ||||||
| 	// range for retry counts that we store is [0,13] |  | ||||||
| 	if responseCode > maxResponseCode || serverIsOverloadedSet.Has(responseCode) { |  | ||||||
| 		b.Backoff.Next(b.baseUrlKey(actualUrl), b.Backoff.Clock.Now()) |  | ||||||
| 		return |  | ||||||
| 	} else if responseCode >= 300 || err != nil { |  | ||||||
| 		glog.V(4).Infof("Client is returning errors: code %v, error %v", responseCode, err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	//If we got this far, there is no backoff required for this URL anymore. |  | ||||||
| 	b.Backoff.Reset(b.baseUrlKey(actualUrl)) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // CalculateBackoff takes a url and back's off exponentially, |  | ||||||
| // based on its knowledge of existing failures. |  | ||||||
| func (b *URLBackoff) CalculateBackoff(actualUrl *url.URL) time.Duration { |  | ||||||
| 	return b.Backoff.Get(b.baseUrlKey(actualUrl)) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *URLBackoff) Sleep(d time.Duration) { |  | ||||||
| 	b.Backoff.Clock.Sleep(d) |  | ||||||
| } |  | ||||||
| @@ -1,79 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2014 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 restclient |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"net/url" |  | ||||||
| 	"testing" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"k8s.io/client-go/pkg/util/flowcontrol" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func parse(raw string) *url.URL { |  | ||||||
| 	theUrl, _ := url.Parse(raw) |  | ||||||
| 	return theUrl |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestURLBackoffFunctionalityCollisions(t *testing.T) { |  | ||||||
| 	myBackoff := &URLBackoff{ |  | ||||||
| 		Backoff: flowcontrol.NewBackOff(1*time.Second, 60*time.Second), |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Add some noise and make sure backoff for a clean URL is zero. |  | ||||||
| 	myBackoff.UpdateBackoff(parse("http://100.200.300.400:8080"), nil, 500) |  | ||||||
|  |  | ||||||
| 	myBackoff.UpdateBackoff(parse("http://1.2.3.4:8080"), nil, 500) |  | ||||||
|  |  | ||||||
| 	if myBackoff.CalculateBackoff(parse("http://1.2.3.4:100")) > 0 { |  | ||||||
| 		t.Errorf("URLs are colliding in the backoff map!") |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // TestURLBackoffFunctionality generally tests the URLBackoff wrapper.  We avoid duplicating tests from backoff and request. |  | ||||||
| func TestURLBackoffFunctionality(t *testing.T) { |  | ||||||
| 	myBackoff := &URLBackoff{ |  | ||||||
| 		Backoff: flowcontrol.NewBackOff(1*time.Second, 60*time.Second), |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Now test that backoff increases, then recovers. |  | ||||||
| 	// 200 and 300 should both result in clearing the backoff. |  | ||||||
| 	// all others like 429 should result in increased backoff. |  | ||||||
| 	seconds := []int{0, |  | ||||||
| 		1, 2, 4, 8, 0, |  | ||||||
| 		1, 2} |  | ||||||
| 	returnCodes := []int{ |  | ||||||
| 		429, 500, 501, 502, 300, |  | ||||||
| 		500, 501, 502, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if len(seconds) != len(returnCodes) { |  | ||||||
| 		t.Fatalf("responseCode to backoff arrays should be the same length... sanity check failed.") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for i, sec := range seconds { |  | ||||||
| 		backoffSec := myBackoff.CalculateBackoff(parse("http://1.2.3.4:100")) |  | ||||||
| 		if backoffSec < time.Duration(sec)*time.Second || backoffSec > time.Duration(sec+5)*time.Second { |  | ||||||
| 			t.Errorf("Backoff out of range %v: %v %v", i, sec, backoffSec) |  | ||||||
| 		} |  | ||||||
| 		myBackoff.UpdateBackoff(parse("http://1.2.3.4:100/responseCodeForFuncTest"), nil, returnCodes[i]) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if myBackoff.CalculateBackoff(parse("http://1.2.3.4:100")) == 0 { |  | ||||||
| 		t.Errorf("The final return code %v should have resulted in a backoff ! ", returnCodes[7]) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,88 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2014 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 restclient |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" |  | ||||||
| 	"path" |  | ||||||
|  |  | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	legacyAPIPath  = "/api" |  | ||||||
| 	defaultAPIPath = "/apis" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // TODO: Is this obsoleted by the discovery client? |  | ||||||
|  |  | ||||||
| // ServerAPIVersions returns the GroupVersions supported by the API server. |  | ||||||
| // It creates a RESTClient based on the passed in config, but it doesn't rely |  | ||||||
| // on the Version and Codec of the config, because it uses AbsPath and |  | ||||||
| // takes the raw response. |  | ||||||
| func ServerAPIVersions(c *Config) (groupVersions []string, err error) { |  | ||||||
| 	transport, err := TransportFor(c) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	client := http.Client{Transport: transport} |  | ||||||
|  |  | ||||||
| 	configCopy := *c |  | ||||||
| 	configCopy.GroupVersion = nil |  | ||||||
| 	configCopy.APIPath = "" |  | ||||||
| 	baseURL, _, err := defaultServerUrlFor(&configCopy) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	// Get the groupVersions exposed at /api |  | ||||||
| 	originalPath := baseURL.Path |  | ||||||
| 	baseURL.Path = path.Join(originalPath, legacyAPIPath) |  | ||||||
| 	resp, err := client.Get(baseURL.String()) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	var v metav1.APIVersions |  | ||||||
| 	defer resp.Body.Close() |  | ||||||
| 	err = json.NewDecoder(resp.Body).Decode(&v) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	groupVersions = append(groupVersions, v.Versions...) |  | ||||||
| 	// Get the groupVersions exposed at /apis |  | ||||||
| 	baseURL.Path = path.Join(originalPath, defaultAPIPath) |  | ||||||
| 	resp2, err := client.Get(baseURL.String()) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	var apiGroupList metav1.APIGroupList |  | ||||||
| 	defer resp2.Body.Close() |  | ||||||
| 	err = json.NewDecoder(resp2.Body).Decode(&apiGroupList) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, g := range apiGroupList.Groups { |  | ||||||
| 		for _, gv := range g.Versions { |  | ||||||
| 			groupVersions = append(groupVersions, gv.GroupVersion) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return groupVersions, nil |  | ||||||
| } |  | ||||||
| @@ -1,46 +0,0 @@ | |||||||
| package(default_visibility = ["//visibility:public"]) |  | ||||||
|  |  | ||||||
| licenses(["notice"]) |  | ||||||
|  |  | ||||||
| load( |  | ||||||
|     "@io_bazel_rules_go//go:def.bzl", |  | ||||||
|     "go_library", |  | ||||||
|     "go_test", |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| go_library( |  | ||||||
|     name = "go_default_library", |  | ||||||
|     srcs = [ |  | ||||||
|         "cert.go", |  | ||||||
|         "csr.go", |  | ||||||
|         "io.go", |  | ||||||
|         "pem.go", |  | ||||||
|     ], |  | ||||||
|     tags = ["automanaged"], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| go_test( |  | ||||||
|     name = "go_default_test", |  | ||||||
|     srcs = ["csr_test.go"], |  | ||||||
|     data = [ |  | ||||||
|         "testdata/dontUseThisKey.pem", |  | ||||||
|     ], |  | ||||||
|     library = ":go_default_library", |  | ||||||
|     tags = ["automanaged"], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| filegroup( |  | ||||||
|     name = "package-srcs", |  | ||||||
|     srcs = glob(["**"]), |  | ||||||
|     tags = ["automanaged"], |  | ||||||
|     visibility = ["//visibility:private"], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| filegroup( |  | ||||||
|     name = "all-srcs", |  | ||||||
|     srcs = [ |  | ||||||
|         ":package-srcs", |  | ||||||
|         "//pkg/util/cert/triple:all-srcs", |  | ||||||
|     ], |  | ||||||
|     tags = ["automanaged"], |  | ||||||
| ) |  | ||||||
| @@ -1,207 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2014 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 cert |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"crypto/ecdsa" |  | ||||||
| 	"crypto/elliptic" |  | ||||||
| 	cryptorand "crypto/rand" |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"crypto/x509" |  | ||||||
| 	"crypto/x509/pkix" |  | ||||||
| 	"encoding/pem" |  | ||||||
| 	"fmt" |  | ||||||
| 	"math" |  | ||||||
| 	"math/big" |  | ||||||
| 	"net" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	rsaKeySize   = 2048 |  | ||||||
| 	duration365d = time.Hour * 24 * 365 |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Config containes the basic fields required for creating a certificate |  | ||||||
| type Config struct { |  | ||||||
| 	CommonName   string |  | ||||||
| 	Organization []string |  | ||||||
| 	AltNames     AltNames |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // AltNames contains the domain names and IP addresses that will be added |  | ||||||
| // to the API Server's x509 certificate SubAltNames field. The values will |  | ||||||
| // be passed directly to the x509.Certificate object. |  | ||||||
| type AltNames struct { |  | ||||||
| 	DNSNames []string |  | ||||||
| 	IPs      []net.IP |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewPrivateKey creates an RSA private key |  | ||||||
| func NewPrivateKey() (*rsa.PrivateKey, error) { |  | ||||||
| 	return rsa.GenerateKey(cryptorand.Reader, rsaKeySize) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewSelfSignedCACert creates a CA certificate |  | ||||||
| func NewSelfSignedCACert(cfg Config, key *rsa.PrivateKey) (*x509.Certificate, error) { |  | ||||||
| 	now := time.Now() |  | ||||||
| 	tmpl := x509.Certificate{ |  | ||||||
| 		SerialNumber: new(big.Int).SetInt64(0), |  | ||||||
| 		Subject: pkix.Name{ |  | ||||||
| 			CommonName:   cfg.CommonName, |  | ||||||
| 			Organization: cfg.Organization, |  | ||||||
| 		}, |  | ||||||
| 		NotBefore:             now.UTC(), |  | ||||||
| 		NotAfter:              now.Add(duration365d * 10).UTC(), |  | ||||||
| 		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, |  | ||||||
| 		BasicConstraintsValid: true, |  | ||||||
| 		IsCA: true, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &tmpl, &tmpl, key.Public(), key) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return x509.ParseCertificate(certDERBytes) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewSignedCert creates a signed certificate using the given CA certificate and key |  | ||||||
| func NewSignedCert(cfg Config, key *rsa.PrivateKey, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, error) { |  | ||||||
| 	serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	certTmpl := x509.Certificate{ |  | ||||||
| 		Subject: pkix.Name{ |  | ||||||
| 			CommonName:   cfg.CommonName, |  | ||||||
| 			Organization: cfg.Organization, |  | ||||||
| 		}, |  | ||||||
| 		DNSNames:     cfg.AltNames.DNSNames, |  | ||||||
| 		IPAddresses:  cfg.AltNames.IPs, |  | ||||||
| 		SerialNumber: serial, |  | ||||||
| 		NotBefore:    caCert.NotBefore, |  | ||||||
| 		NotAfter:     time.Now().Add(duration365d).UTC(), |  | ||||||
| 		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, |  | ||||||
| 		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, |  | ||||||
| 	} |  | ||||||
| 	certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &certTmpl, caCert, key.Public(), caKey) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return x509.ParseCertificate(certDERBytes) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // MakeEllipticPrivateKeyPEM creates an ECDSA private key |  | ||||||
| func MakeEllipticPrivateKeyPEM() ([]byte, error) { |  | ||||||
| 	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	derBytes, err := x509.MarshalECPrivateKey(privateKey) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	privateKeyPemBlock := &pem.Block{ |  | ||||||
| 		Type:  "EC PRIVATE KEY", |  | ||||||
| 		Bytes: derBytes, |  | ||||||
| 	} |  | ||||||
| 	return pem.EncodeToMemory(privateKeyPemBlock), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // GenerateSelfSignedCertKey creates a self-signed certificate and key for the given host. |  | ||||||
| // Host may be an IP or a DNS name |  | ||||||
| // You may also specify additional subject alt names (either ip or dns names) for the certificate |  | ||||||
| func GenerateSelfSignedCertKey(host string, alternateIPs []net.IP, alternateDNS []string) ([]byte, []byte, error) { |  | ||||||
| 	priv, err := rsa.GenerateKey(cryptorand.Reader, 2048) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	template := x509.Certificate{ |  | ||||||
| 		SerialNumber: big.NewInt(1), |  | ||||||
| 		Subject: pkix.Name{ |  | ||||||
| 			CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()), |  | ||||||
| 		}, |  | ||||||
| 		NotBefore: time.Now(), |  | ||||||
| 		NotAfter:  time.Now().Add(time.Hour * 24 * 365), |  | ||||||
|  |  | ||||||
| 		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, |  | ||||||
| 		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, |  | ||||||
| 		BasicConstraintsValid: true, |  | ||||||
| 		IsCA: true, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if ip := net.ParseIP(host); ip != nil { |  | ||||||
| 		template.IPAddresses = append(template.IPAddresses, ip) |  | ||||||
| 	} else { |  | ||||||
| 		template.DNSNames = append(template.DNSNames, host) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	template.IPAddresses = append(template.IPAddresses, alternateIPs...) |  | ||||||
| 	template.DNSNames = append(template.DNSNames, alternateDNS...) |  | ||||||
|  |  | ||||||
| 	derBytes, err := x509.CreateCertificate(cryptorand.Reader, &template, &template, &priv.PublicKey, priv) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Generate cert |  | ||||||
| 	certBuffer := bytes.Buffer{} |  | ||||||
| 	if err := pem.Encode(&certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Generate key |  | ||||||
| 	keyBuffer := bytes.Buffer{} |  | ||||||
| 	if err := pem.Encode(&keyBuffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil { |  | ||||||
| 		return nil, nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return certBuffer.Bytes(), keyBuffer.Bytes(), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // FormatBytesCert receives byte array certificate and formats in human-readable format |  | ||||||
| func FormatBytesCert(cert []byte) (string, error) { |  | ||||||
| 	block, _ := pem.Decode(cert) |  | ||||||
| 	c, err := x509.ParseCertificate(block.Bytes) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", fmt.Errorf("failed to parse certificate [%v]", err) |  | ||||||
| 	} |  | ||||||
| 	return FormatCert(c), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // FormatCert receives certificate and formats in human-readable format |  | ||||||
| func FormatCert(c *x509.Certificate) string { |  | ||||||
| 	var ips []string |  | ||||||
| 	for _, ip := range c.IPAddresses { |  | ||||||
| 		ips = append(ips, ip.String()) |  | ||||||
| 	} |  | ||||||
| 	altNames := append(ips, c.DNSNames...) |  | ||||||
| 	res := fmt.Sprintf( |  | ||||||
| 		"Issuer: CN=%s | Subject: CN=%s | CA: %t\n", |  | ||||||
| 		c.Issuer.CommonName, c.Subject.CommonName, c.IsCA, |  | ||||||
| 	) |  | ||||||
| 	res += fmt.Sprintf("Not before: %s Not After: %s", c.NotBefore, c.NotAfter) |  | ||||||
| 	if len(altNames) > 0 { |  | ||||||
| 		res += fmt.Sprintf("\nAlternate Names: %v", altNames) |  | ||||||
| 	} |  | ||||||
| 	return res |  | ||||||
| } |  | ||||||
| @@ -1,63 +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 cert |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	cryptorand "crypto/rand" |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"crypto/x509" |  | ||||||
| 	"crypto/x509/pkix" |  | ||||||
| 	"encoding/pem" |  | ||||||
| 	"net" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // MakeCSR generates a PEM-encoded CSR using the supplied private key, subject, and SANs. |  | ||||||
| // All key types that are implemented via crypto.Signer are supported (This includes *rsa.PrivateKey and *ecdsa.PrivateKey.) |  | ||||||
| func MakeCSR(privateKey interface{}, subject *pkix.Name, dnsSANs []string, ipSANs []net.IP) (csr []byte, err error) { |  | ||||||
| 	// Customize the signature for RSA keys, depending on the key size |  | ||||||
| 	var sigType x509.SignatureAlgorithm |  | ||||||
| 	if privateKey, ok := privateKey.(*rsa.PrivateKey); ok { |  | ||||||
| 		keySize := privateKey.N.BitLen() |  | ||||||
| 		switch { |  | ||||||
| 		case keySize >= 4096: |  | ||||||
| 			sigType = x509.SHA512WithRSA |  | ||||||
| 		case keySize >= 3072: |  | ||||||
| 			sigType = x509.SHA384WithRSA |  | ||||||
| 		default: |  | ||||||
| 			sigType = x509.SHA256WithRSA |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	template := &x509.CertificateRequest{ |  | ||||||
| 		Subject:            *subject, |  | ||||||
| 		SignatureAlgorithm: sigType, |  | ||||||
| 		DNSNames:           dnsSANs, |  | ||||||
| 		IPAddresses:        ipSANs, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	csr, err = x509.CreateCertificateRequest(cryptorand.Reader, template, privateKey) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	csrPemBlock := &pem.Block{ |  | ||||||
| 		Type:  "CERTIFICATE REQUEST", |  | ||||||
| 		Bytes: csr, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return pem.EncodeToMemory(csrPemBlock), nil |  | ||||||
| } |  | ||||||
| @@ -1,129 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2014 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 cert |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"crypto/x509" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // CanReadCertAndKey returns true if the certificate and key files already exists, |  | ||||||
| // otherwise returns false. If lost one of cert and key, returns error. |  | ||||||
| func CanReadCertAndKey(certPath, keyPath string) (bool, error) { |  | ||||||
| 	certReadable := canReadFile(certPath) |  | ||||||
| 	keyReadable := canReadFile(keyPath) |  | ||||||
|  |  | ||||||
| 	if certReadable == false && keyReadable == false { |  | ||||||
| 		return false, nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if certReadable == false { |  | ||||||
| 		return false, fmt.Errorf("error reading %s, certificate and key must be supplied as a pair", certPath) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if keyReadable == false { |  | ||||||
| 		return false, fmt.Errorf("error reading %s, certificate and key must be supplied as a pair", keyPath) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return true, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // If the file represented by path exists and |  | ||||||
| // readable, returns true otherwise returns false. |  | ||||||
| func canReadFile(path string) bool { |  | ||||||
| 	f, err := os.Open(path) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	defer f.Close() |  | ||||||
|  |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // WriteCert writes the pem-encoded certificate data to certPath. |  | ||||||
| // The certificate file will be created with file mode 0644. |  | ||||||
| // If the certificate file already exists, it will be overwritten. |  | ||||||
| // The parent directory of the certPath will be created as needed with file mode 0755. |  | ||||||
| func WriteCert(certPath string, data []byte) error { |  | ||||||
| 	if err := os.MkdirAll(filepath.Dir(certPath), os.FileMode(0755)); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	if err := ioutil.WriteFile(certPath, data, os.FileMode(0644)); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // WriteKey writes the pem-encoded key data to keyPath. |  | ||||||
| // The key file will be created with file mode 0600. |  | ||||||
| // If the key file already exists, it will be overwritten. |  | ||||||
| // The parent directory of the keyPath will be created as needed with file mode 0755. |  | ||||||
| func WriteKey(keyPath string, data []byte) error { |  | ||||||
| 	if err := os.MkdirAll(filepath.Dir(keyPath), os.FileMode(0755)); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	if err := ioutil.WriteFile(keyPath, data, os.FileMode(0600)); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewPool returns an x509.CertPool containing the certificates in the given PEM-encoded file. |  | ||||||
| // Returns an error if the file could not be read, a certificate could not be parsed, or if the file does not contain any certificates |  | ||||||
| func NewPool(filename string) (*x509.CertPool, error) { |  | ||||||
| 	certs, err := CertsFromFile(filename) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	pool := x509.NewCertPool() |  | ||||||
| 	for _, cert := range certs { |  | ||||||
| 		pool.AddCert(cert) |  | ||||||
| 	} |  | ||||||
| 	return pool, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // CertsFromFile returns the x509.Certificates contained in the given PEM-encoded file. |  | ||||||
| // Returns an error if the file could not be read, a certificate could not be parsed, or if the file does not contain any certificates |  | ||||||
| func CertsFromFile(file string) ([]*x509.Certificate, error) { |  | ||||||
| 	pemBlock, err := ioutil.ReadFile(file) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	certs, err := ParseCertsPEM(pemBlock) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error reading %s: %s", file, err) |  | ||||||
| 	} |  | ||||||
| 	return certs, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // PrivateKeyFromFile returns the private key in rsa.PrivateKey or ecdsa.PrivateKey format from a given PEM-encoded file. |  | ||||||
| // Returns an error if the file could not be read or if the private key could not be parsed. |  | ||||||
| func PrivateKeyFromFile(file string) (interface{}, error) { |  | ||||||
| 	pemBlock, err := ioutil.ReadFile(file) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	key, err := ParsePrivateKeyPEM(pemBlock) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("error reading %s: %v", file, err) |  | ||||||
| 	} |  | ||||||
| 	return key, nil |  | ||||||
| } |  | ||||||
| @@ -1,107 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2014 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 cert |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"crypto/rsa" |  | ||||||
| 	"crypto/x509" |  | ||||||
| 	"encoding/pem" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // EncodePublicKeyPEM returns PEM-endcode public data |  | ||||||
| func EncodePublicKeyPEM(key *rsa.PublicKey) ([]byte, error) { |  | ||||||
| 	der, err := x509.MarshalPKIXPublicKey(key) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return []byte{}, err |  | ||||||
| 	} |  | ||||||
| 	block := pem.Block{ |  | ||||||
| 		Type:  "PUBLIC KEY", |  | ||||||
| 		Bytes: der, |  | ||||||
| 	} |  | ||||||
| 	return pem.EncodeToMemory(&block), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // EncodePrivateKeyPEM returns PEM-encoded private key data |  | ||||||
| func EncodePrivateKeyPEM(key *rsa.PrivateKey) []byte { |  | ||||||
| 	block := pem.Block{ |  | ||||||
| 		Type:  "RSA PRIVATE KEY", |  | ||||||
| 		Bytes: x509.MarshalPKCS1PrivateKey(key), |  | ||||||
| 	} |  | ||||||
| 	return pem.EncodeToMemory(&block) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // EncodeCertPEM returns PEM-endcoded certificate data |  | ||||||
| func EncodeCertPEM(cert *x509.Certificate) []byte { |  | ||||||
| 	block := pem.Block{ |  | ||||||
| 		Type:  "CERTIFICATE", |  | ||||||
| 		Bytes: cert.Raw, |  | ||||||
| 	} |  | ||||||
| 	return pem.EncodeToMemory(&block) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ParsePrivateKeyPEM returns a private key parsed from a PEM block in the supplied data. |  | ||||||
| // Recognizes PEM blocks for "EC PRIVATE KEY" and "RSA PRIVATE KEY" |  | ||||||
| func ParsePrivateKeyPEM(keyData []byte) (interface{}, error) { |  | ||||||
| 	for { |  | ||||||
| 		var privateKeyPemBlock *pem.Block |  | ||||||
| 		privateKeyPemBlock, keyData = pem.Decode(keyData) |  | ||||||
| 		if privateKeyPemBlock == nil { |  | ||||||
| 			// we read all the PEM blocks and didn't recognize one |  | ||||||
| 			return nil, fmt.Errorf("no private key PEM block found") |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		switch privateKeyPemBlock.Type { |  | ||||||
| 		case "EC PRIVATE KEY": |  | ||||||
| 			return x509.ParseECPrivateKey(privateKeyPemBlock.Bytes) |  | ||||||
| 		case "RSA PRIVATE KEY": |  | ||||||
| 			return x509.ParsePKCS1PrivateKey(privateKeyPemBlock.Bytes) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ParseCertsPEM returns the x509.Certificates contained in the given PEM-encoded byte array |  | ||||||
| // Returns an error if a certificate could not be parsed, or if the data does not contain any certificates |  | ||||||
| func ParseCertsPEM(pemCerts []byte) ([]*x509.Certificate, error) { |  | ||||||
| 	ok := false |  | ||||||
| 	certs := []*x509.Certificate{} |  | ||||||
| 	for len(pemCerts) > 0 { |  | ||||||
| 		var block *pem.Block |  | ||||||
| 		block, pemCerts = pem.Decode(pemCerts) |  | ||||||
| 		if block == nil { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 		// Only use PEM "CERTIFICATE" blocks without extra headers |  | ||||||
| 		if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		cert, err := x509.ParseCertificate(block.Bytes) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return certs, err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		certs = append(certs, cert) |  | ||||||
| 		ok = true |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if !ok { |  | ||||||
| 		return certs, errors.New("could not read any certificates") |  | ||||||
| 	} |  | ||||||
| 	return certs, nil |  | ||||||
| } |  | ||||||
| @@ -1,38 +0,0 @@ | |||||||
| package(default_visibility = ["//visibility:public"]) |  | ||||||
|  |  | ||||||
| licenses(["notice"]) |  | ||||||
|  |  | ||||||
| load( |  | ||||||
|     "@io_bazel_rules_go//go:def.bzl", |  | ||||||
|     "go_library", |  | ||||||
|     "go_test", |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| go_library( |  | ||||||
|     name = "go_default_library", |  | ||||||
|     srcs = [ |  | ||||||
|         "fake_handler.go", |  | ||||||
|         "tmpdir.go", |  | ||||||
|     ], |  | ||||||
|     tags = ["automanaged"], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| go_test( |  | ||||||
|     name = "go_default_test", |  | ||||||
|     srcs = ["fake_handler_test.go"], |  | ||||||
|     library = ":go_default_library", |  | ||||||
|     tags = ["automanaged"], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| filegroup( |  | ||||||
|     name = "package-srcs", |  | ||||||
|     srcs = glob(["**"]), |  | ||||||
|     tags = ["automanaged"], |  | ||||||
|     visibility = ["//visibility:private"], |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| filegroup( |  | ||||||
|     name = "all-srcs", |  | ||||||
|     srcs = [":package-srcs"], |  | ||||||
|     tags = ["automanaged"], |  | ||||||
| ) |  | ||||||
| @@ -1,139 +0,0 @@ | |||||||
| /* |  | ||||||
| Copyright 2014 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 testing |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"net/http" |  | ||||||
| 	"net/url" |  | ||||||
| 	"reflect" |  | ||||||
| 	"sync" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // TestInterface is a simple interface providing Errorf, to make injection for |  | ||||||
| // testing easier (insert 'yo dawg' meme here). |  | ||||||
| type TestInterface interface { |  | ||||||
| 	Errorf(format string, args ...interface{}) |  | ||||||
| 	Logf(format string, args ...interface{}) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // LogInterface is a simple interface to allow injection of Logf to report serving errors. |  | ||||||
| type LogInterface interface { |  | ||||||
| 	Logf(format string, args ...interface{}) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // FakeHandler is to assist in testing HTTP requests. Notice that FakeHandler is |  | ||||||
| // not thread safe and you must not direct traffic to except for the request |  | ||||||
| // you want to test. You can do this by hiding it in an http.ServeMux. |  | ||||||
| type FakeHandler struct { |  | ||||||
| 	RequestReceived *http.Request |  | ||||||
| 	RequestBody     string |  | ||||||
| 	StatusCode      int |  | ||||||
| 	ResponseBody    string |  | ||||||
| 	// For logging - you can use a *testing.T |  | ||||||
| 	// This will keep log messages associated with the test. |  | ||||||
| 	T LogInterface |  | ||||||
|  |  | ||||||
| 	// Enforce "only one use" constraint. |  | ||||||
| 	lock           sync.Mutex |  | ||||||
| 	requestCount   int |  | ||||||
| 	hasBeenChecked bool |  | ||||||
|  |  | ||||||
| 	SkipRequestFn func(verb string, url url.URL) bool |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (f *FakeHandler) SetResponseBody(responseBody string) { |  | ||||||
| 	f.lock.Lock() |  | ||||||
| 	defer f.lock.Unlock() |  | ||||||
| 	f.ResponseBody = responseBody |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (f *FakeHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) { |  | ||||||
| 	f.lock.Lock() |  | ||||||
| 	defer f.lock.Unlock() |  | ||||||
|  |  | ||||||
| 	if f.SkipRequestFn != nil && f.SkipRequestFn(request.Method, *request.URL) { |  | ||||||
| 		response.Header().Set("Content-Type", "application/json") |  | ||||||
| 		response.WriteHeader(f.StatusCode) |  | ||||||
| 		response.Write([]byte(f.ResponseBody)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	f.requestCount++ |  | ||||||
| 	if f.hasBeenChecked { |  | ||||||
| 		panic("got request after having been validated") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	f.RequestReceived = request |  | ||||||
| 	response.Header().Set("Content-Type", "application/json") |  | ||||||
| 	response.WriteHeader(f.StatusCode) |  | ||||||
| 	response.Write([]byte(f.ResponseBody)) |  | ||||||
|  |  | ||||||
| 	bodyReceived, err := ioutil.ReadAll(request.Body) |  | ||||||
| 	if err != nil && f.T != nil { |  | ||||||
| 		f.T.Logf("Received read error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	f.RequestBody = string(bodyReceived) |  | ||||||
| 	if f.T != nil { |  | ||||||
| 		f.T.Logf("request body: %s", f.RequestBody) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (f *FakeHandler) ValidateRequestCount(t TestInterface, count int) bool { |  | ||||||
| 	ok := true |  | ||||||
| 	f.lock.Lock() |  | ||||||
| 	defer f.lock.Unlock() |  | ||||||
| 	if f.requestCount != count { |  | ||||||
| 		ok = false |  | ||||||
| 		t.Errorf("Expected %d call, but got %d. Only the last call is recorded and checked.", count, f.requestCount) |  | ||||||
| 	} |  | ||||||
| 	f.hasBeenChecked = true |  | ||||||
| 	return ok |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ValidateRequest verifies that FakeHandler received a request with expected path, method, and body. |  | ||||||
| func (f *FakeHandler) ValidateRequest(t TestInterface, expectedPath, expectedMethod string, body *string) { |  | ||||||
| 	f.lock.Lock() |  | ||||||
| 	defer f.lock.Unlock() |  | ||||||
| 	if f.requestCount != 1 { |  | ||||||
| 		t.Logf("Expected 1 call, but got %v. Only the last call is recorded and checked.", f.requestCount) |  | ||||||
| 	} |  | ||||||
| 	f.hasBeenChecked = true |  | ||||||
|  |  | ||||||
| 	expectURL, err := url.Parse(expectedPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("Couldn't parse %v as a URL.", expectedPath) |  | ||||||
| 	} |  | ||||||
| 	if f.RequestReceived == nil { |  | ||||||
| 		t.Errorf("Unexpected nil request received for %s", expectedPath) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if f.RequestReceived.URL.Path != expectURL.Path { |  | ||||||
| 		t.Errorf("Unexpected request path for request %#v, received: %q, expected: %q", f.RequestReceived, f.RequestReceived.URL.Path, expectURL.Path) |  | ||||||
| 	} |  | ||||||
| 	if e, a := expectURL.Query(), f.RequestReceived.URL.Query(); !reflect.DeepEqual(e, a) { |  | ||||||
| 		t.Errorf("Unexpected query for request %#v, received: %q, expected: %q", f.RequestReceived, a, e) |  | ||||||
| 	} |  | ||||||
| 	if f.RequestReceived.Method != expectedMethod { |  | ||||||
| 		t.Errorf("Unexpected method: %q, expected: %q", f.RequestReceived.Method, expectedMethod) |  | ||||||
| 	} |  | ||||||
| 	if body != nil { |  | ||||||
| 		if *body != f.RequestBody { |  | ||||||
| 			t.Errorf("Received body:\n%s\n Doesn't match expected body:\n%s", f.RequestBody, *body) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,44 +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 testing |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"os" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // MkTmpdir creates a temporary directory based upon the prefix passed in. |  | ||||||
| // If successful, it returns the temporary directory path. The directory can be |  | ||||||
| // deleted with a call to "os.RemoveAll(...)". |  | ||||||
| // In case of error, it'll return an empty string and the error. |  | ||||||
| func MkTmpdir(prefix string) (string, error) { |  | ||||||
| 	tmpDir, err := ioutil.TempDir(os.TempDir(), prefix) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 	return tmpDir, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // MkTmpdir does the same work as "MkTmpdir", except in case of |  | ||||||
| // errors, it'll trigger a panic. |  | ||||||
| func MkTmpdirOrDie(prefix string) string { |  | ||||||
| 	tmpDir, err := MkTmpdir(prefix) |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| 	return tmpDir |  | ||||||
| } |  | ||||||
| @@ -70,11 +70,16 @@ function save() { | |||||||
|  |  | ||||||
| # save everything for which the staging directory is the source of truth | # save everything for which the staging directory is the source of truth | ||||||
| save "transport" | save "transport" | ||||||
|  | save "tools/metrics" | ||||||
| save "tools/clientcmd/api" | save "tools/clientcmd/api" | ||||||
| save "rest/watch" | save "rest" | ||||||
|  | # remove the rest/fake until we're authoritative for it (need to update for registry) | ||||||
|  | rm -rf ${CLIENT_REPO_TEMP}/rest/fake | ||||||
|  | save "pkg/util/cert" | ||||||
| save "pkg/util/clock" | save "pkg/util/clock" | ||||||
| save "pkg/util/integer" |  | ||||||
| save "pkg/util/flowcontrol" | save "pkg/util/flowcontrol" | ||||||
|  | save "pkg/util/integer" | ||||||
|  | save "pkg/util/testing" | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -90,7 +95,7 @@ mkcp "/pkg/client/record" "/pkg/client" | |||||||
| mkcp "/pkg/client/cache" "/pkg/client" | mkcp "/pkg/client/cache" "/pkg/client" | ||||||
| # TODO: make this test file not depending on pkg/client/unversioned | # TODO: make this test file not depending on pkg/client/unversioned | ||||||
| rm "${CLIENT_REPO_TEMP}"/pkg/client/cache/listwatch_test.go | rm "${CLIENT_REPO_TEMP}"/pkg/client/cache/listwatch_test.go | ||||||
| mkcp "/pkg/client/restclient" "/pkg/client" | mkcp "/pkg/client/restclient/fake" "/pkg/client/restclient" | ||||||
| mkcp "/pkg/client/testing" "/pkg/client" | mkcp "/pkg/client/testing" "/pkg/client" | ||||||
| # remove this test because it imports the internal clientset | # remove this test because it imports the internal clientset | ||||||
| rm "${CLIENT_REPO_TEMP}"/pkg/client/testing/core/fake_test.go | rm "${CLIENT_REPO_TEMP}"/pkg/client/testing/core/fake_test.go | ||||||
| @@ -147,8 +152,6 @@ find "${CLIENT_REPO_TEMP}"/pkg/client/record -type f -name "*.go" -print0 | xarg | |||||||
| # gofmt the changed files | # gofmt the changed files | ||||||
|  |  | ||||||
| echo "rewrite conflicting Prometheus registration" | echo "rewrite conflicting Prometheus registration" | ||||||
| sed -i "s/request_latency_microseconds/request_latency_microseconds_copy/g" "${CLIENT_REPO_TEMP}"/pkg/client/metrics/metrics.go |  | ||||||
| sed -i "s/request_status_codes/request_status_codes_copy/g" "${CLIENT_REPO_TEMP}"/pkg/client/metrics/metrics.go |  | ||||||
| sed -i "s/kubernetes_build_info/kubernetes_build_info_copy/g" "${CLIENT_REPO_TEMP}"/pkg/version/version.go | sed -i "s/kubernetes_build_info/kubernetes_build_info_copy/g" "${CLIENT_REPO_TEMP}"/pkg/version/version.go | ||||||
|  |  | ||||||
| echo "rewrite proto names in proto.RegisterType" | echo "rewrite proto names in proto.RegisterType" | ||||||
| @@ -191,12 +194,11 @@ mvfolder "pkg/client/clientset_generated/${CLIENTSET}" kubernetes | |||||||
| mvfolder pkg/client/typed/discovery discovery | mvfolder pkg/client/typed/discovery discovery | ||||||
| mvfolder pkg/client/typed/dynamic dynamic | mvfolder pkg/client/typed/dynamic dynamic | ||||||
| mvfolder pkg/client/record tools/record | mvfolder pkg/client/record tools/record | ||||||
| mvfolder pkg/client/restclient rest | mvfolder pkg/client/restclient/fake rest/fake | ||||||
| mvfolder pkg/client/cache tools/cache | mvfolder pkg/client/cache tools/cache | ||||||
| mvfolder pkg/client/unversioned/auth tools/auth | mvfolder pkg/client/unversioned/auth tools/auth | ||||||
| mvfolder pkg/client/unversioned/clientcmd tools/clientcmd | mvfolder pkg/client/unversioned/clientcmd tools/clientcmd | ||||||
| mvfolder pkg/client/unversioned/portforward tools/portforward | mvfolder pkg/client/unversioned/portforward tools/portforward | ||||||
| mvfolder pkg/client/metrics tools/metrics |  | ||||||
| mvfolder pkg/client/testing/core testing | mvfolder pkg/client/testing/core testing | ||||||
| mvfolder pkg/client/testing/cache tools/cache/testing | mvfolder pkg/client/testing/cache tools/cache/testing | ||||||
| mvfolder cmd/kubeadm/app/apis/kubeadm pkg/apis/kubeadm | mvfolder cmd/kubeadm/app/apis/kubeadm pkg/apis/kubeadm | ||||||
|   | |||||||
							
								
								
									
										184
									
								
								staging/src/k8s.io/client-go/pkg/util/clock/clock_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								staging/src/k8s.io/client-go/pkg/util/clock/clock_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,184 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2015 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 clock | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestFakeClock(t *testing.T) { | ||||||
|  | 	startTime := time.Now() | ||||||
|  | 	tc := NewFakeClock(startTime) | ||||||
|  | 	tc.Step(time.Second) | ||||||
|  | 	now := tc.Now() | ||||||
|  | 	if now.Sub(startTime) != time.Second { | ||||||
|  | 		t.Errorf("input: %s now=%s gap=%s expected=%s", startTime, now, now.Sub(startTime), time.Second) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tt := tc.Now() | ||||||
|  | 	tc.SetTime(tt.Add(time.Hour)) | ||||||
|  | 	if tc.Now().Sub(tt) != time.Hour { | ||||||
|  | 		t.Errorf("input: %s now=%s gap=%s expected=%s", tt, tc.Now(), tc.Now().Sub(tt), time.Hour) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestFakeClockSleep(t *testing.T) { | ||||||
|  | 	startTime := time.Now() | ||||||
|  | 	tc := NewFakeClock(startTime) | ||||||
|  | 	tc.Sleep(time.Duration(1) * time.Hour) | ||||||
|  | 	now := tc.Now() | ||||||
|  | 	if now.Sub(startTime) != time.Hour { | ||||||
|  | 		t.Errorf("Fake sleep failed, expected time to advance by one hour, instead, its %v", now.Sub(startTime)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestFakeAfter(t *testing.T) { | ||||||
|  | 	tc := NewFakeClock(time.Now()) | ||||||
|  | 	if tc.HasWaiters() { | ||||||
|  | 		t.Errorf("unexpected waiter?") | ||||||
|  | 	} | ||||||
|  | 	oneSec := tc.After(time.Second) | ||||||
|  | 	if !tc.HasWaiters() { | ||||||
|  | 		t.Errorf("unexpected lack of waiter?") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	oneOhOneSec := tc.After(time.Second + time.Millisecond) | ||||||
|  | 	twoSec := tc.After(2 * time.Second) | ||||||
|  | 	select { | ||||||
|  | 	case <-oneSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	case <-oneOhOneSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	case <-twoSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	default: | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tc.Step(999 * time.Millisecond) | ||||||
|  | 	select { | ||||||
|  | 	case <-oneSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	case <-oneOhOneSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	case <-twoSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	default: | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tc.Step(time.Millisecond) | ||||||
|  | 	select { | ||||||
|  | 	case <-oneSec: | ||||||
|  | 		// Expected! | ||||||
|  | 	case <-oneOhOneSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	case <-twoSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	default: | ||||||
|  | 		t.Errorf("unexpected non-channel read") | ||||||
|  | 	} | ||||||
|  | 	tc.Step(time.Millisecond) | ||||||
|  | 	select { | ||||||
|  | 	case <-oneSec: | ||||||
|  | 		// should not double-trigger! | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	case <-oneOhOneSec: | ||||||
|  | 		// Expected! | ||||||
|  | 	case <-twoSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	default: | ||||||
|  | 		t.Errorf("unexpected non-channel read") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestFakeTick(t *testing.T) { | ||||||
|  | 	tc := NewFakeClock(time.Now()) | ||||||
|  | 	if tc.HasWaiters() { | ||||||
|  | 		t.Errorf("unexpected waiter?") | ||||||
|  | 	} | ||||||
|  | 	oneSec := tc.Tick(time.Second) | ||||||
|  | 	if !tc.HasWaiters() { | ||||||
|  | 		t.Errorf("unexpected lack of waiter?") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	oneOhOneSec := tc.Tick(time.Second + time.Millisecond) | ||||||
|  | 	twoSec := tc.Tick(2 * time.Second) | ||||||
|  | 	select { | ||||||
|  | 	case <-oneSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	case <-oneOhOneSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	case <-twoSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	default: | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tc.Step(999 * time.Millisecond) // t=.999 | ||||||
|  | 	select { | ||||||
|  | 	case <-oneSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	case <-oneOhOneSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	case <-twoSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	default: | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tc.Step(time.Millisecond) // t=1.000 | ||||||
|  | 	select { | ||||||
|  | 	case <-oneSec: | ||||||
|  | 		// Expected! | ||||||
|  | 	case <-oneOhOneSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	case <-twoSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	default: | ||||||
|  | 		t.Errorf("unexpected non-channel read") | ||||||
|  | 	} | ||||||
|  | 	tc.Step(time.Millisecond) // t=1.001 | ||||||
|  | 	select { | ||||||
|  | 	case <-oneSec: | ||||||
|  | 		// should not double-trigger! | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	case <-oneOhOneSec: | ||||||
|  | 		// Expected! | ||||||
|  | 	case <-twoSec: | ||||||
|  | 		t.Errorf("unexpected channel read") | ||||||
|  | 	default: | ||||||
|  | 		t.Errorf("unexpected non-channel read") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tc.Step(time.Second) // t=2.001 | ||||||
|  | 	tc.Step(time.Second) // t=3.001 | ||||||
|  | 	tc.Step(time.Second) // t=4.001 | ||||||
|  | 	tc.Step(time.Second) // t=5.001 | ||||||
|  |  | ||||||
|  | 	// The one second ticker should not accumulate ticks | ||||||
|  | 	accumulatedTicks := 0 | ||||||
|  | 	drained := false | ||||||
|  | 	for !drained { | ||||||
|  | 		select { | ||||||
|  | 		case <-oneSec: | ||||||
|  | 			accumulatedTicks++ | ||||||
|  | 		default: | ||||||
|  | 			drained = true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if accumulatedTicks != 1 { | ||||||
|  | 		t.Errorf("unexpected number of accumulated ticks: %d", accumulatedTicks) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,195 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2015 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 flowcontrol | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"k8s.io/kubernetes/pkg/util/clock" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestSlowBackoff(t *testing.T) { | ||||||
|  | 	id := "_idSlow" | ||||||
|  | 	tc := clock.NewFakeClock(time.Now()) | ||||||
|  | 	step := time.Second | ||||||
|  | 	maxDuration := 50 * step | ||||||
|  |  | ||||||
|  | 	b := NewFakeBackOff(step, maxDuration, tc) | ||||||
|  | 	cases := []time.Duration{0, 1, 2, 4, 8, 16, 32, 50, 50, 50} | ||||||
|  | 	for ix, c := range cases { | ||||||
|  | 		tc.Step(step) | ||||||
|  | 		w := b.Get(id) | ||||||
|  | 		if w != c*step { | ||||||
|  | 			t.Errorf("input: '%d': expected %s, got %s", ix, c*step, w) | ||||||
|  | 		} | ||||||
|  | 		b.Next(id, tc.Now()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//Now confirm that the Reset cancels backoff. | ||||||
|  | 	b.Next(id, tc.Now()) | ||||||
|  | 	b.Reset(id) | ||||||
|  | 	if b.Get(id) != 0 { | ||||||
|  | 		t.Errorf("Reset didn't clear the backoff.") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestBackoffReset(t *testing.T) { | ||||||
|  | 	id := "_idReset" | ||||||
|  | 	tc := clock.NewFakeClock(time.Now()) | ||||||
|  | 	step := time.Second | ||||||
|  | 	maxDuration := step * 5 | ||||||
|  | 	b := NewFakeBackOff(step, maxDuration, tc) | ||||||
|  | 	startTime := tc.Now() | ||||||
|  |  | ||||||
|  | 	// get to backoff = maxDuration | ||||||
|  | 	for i := 0; i <= int(maxDuration/step); i++ { | ||||||
|  | 		tc.Step(step) | ||||||
|  | 		b.Next(id, tc.Now()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// backoff should be capped at maxDuration | ||||||
|  | 	if !b.IsInBackOffSince(id, tc.Now()) { | ||||||
|  | 		t.Errorf("expected to be in Backoff got %s", b.Get(id)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	lastUpdate := tc.Now() | ||||||
|  | 	tc.Step(2*maxDuration + step) // time += 11s, 11 > 2*maxDuration | ||||||
|  | 	if b.IsInBackOffSince(id, lastUpdate) { | ||||||
|  | 		t.Errorf("expected to not be in Backoff after reset (start=%s, now=%s, lastUpdate=%s), got %s", startTime, tc.Now(), lastUpdate, b.Get(id)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestBackoffHightWaterMark(t *testing.T) { | ||||||
|  | 	id := "_idHiWaterMark" | ||||||
|  | 	tc := clock.NewFakeClock(time.Now()) | ||||||
|  | 	step := time.Second | ||||||
|  | 	maxDuration := 5 * step | ||||||
|  | 	b := NewFakeBackOff(step, maxDuration, tc) | ||||||
|  |  | ||||||
|  | 	// get to backoff = maxDuration | ||||||
|  | 	for i := 0; i <= int(maxDuration/step); i++ { | ||||||
|  | 		tc.Step(step) | ||||||
|  | 		b.Next(id, tc.Now()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// backoff high watermark expires after 2*maxDuration | ||||||
|  | 	tc.Step(maxDuration + step) | ||||||
|  | 	b.Next(id, tc.Now()) | ||||||
|  |  | ||||||
|  | 	if b.Get(id) != maxDuration { | ||||||
|  | 		t.Errorf("expected Backoff to stay at high watermark %s got %s", maxDuration, b.Get(id)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestBackoffGC(t *testing.T) { | ||||||
|  | 	id := "_idGC" | ||||||
|  | 	tc := clock.NewFakeClock(time.Now()) | ||||||
|  | 	step := time.Second | ||||||
|  | 	maxDuration := 5 * step | ||||||
|  |  | ||||||
|  | 	b := NewFakeBackOff(step, maxDuration, tc) | ||||||
|  |  | ||||||
|  | 	for i := 0; i <= int(maxDuration/step); i++ { | ||||||
|  | 		tc.Step(step) | ||||||
|  | 		b.Next(id, tc.Now()) | ||||||
|  | 	} | ||||||
|  | 	lastUpdate := tc.Now() | ||||||
|  | 	tc.Step(maxDuration + step) | ||||||
|  | 	b.GC() | ||||||
|  | 	_, found := b.perItemBackoff[id] | ||||||
|  | 	if !found { | ||||||
|  | 		t.Errorf("expected GC to skip entry, elapsed time=%s maxDuration=%s", tc.Now().Sub(lastUpdate), maxDuration) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tc.Step(maxDuration + step) | ||||||
|  | 	b.GC() | ||||||
|  | 	r, found := b.perItemBackoff[id] | ||||||
|  | 	if found { | ||||||
|  | 		t.Errorf("expected GC of entry after %s got entry %v", tc.Now().Sub(lastUpdate), r) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestIsInBackOffSinceUpdate(t *testing.T) { | ||||||
|  | 	id := "_idIsInBackOffSinceUpdate" | ||||||
|  | 	tc := clock.NewFakeClock(time.Now()) | ||||||
|  | 	step := time.Second | ||||||
|  | 	maxDuration := 10 * step | ||||||
|  | 	b := NewFakeBackOff(step, maxDuration, tc) | ||||||
|  | 	startTime := tc.Now() | ||||||
|  |  | ||||||
|  | 	cases := []struct { | ||||||
|  | 		tick      time.Duration | ||||||
|  | 		inBackOff bool | ||||||
|  | 		value     int | ||||||
|  | 	}{ | ||||||
|  | 		{tick: 0, inBackOff: false, value: 0}, | ||||||
|  | 		{tick: 1, inBackOff: false, value: 1}, | ||||||
|  | 		{tick: 2, inBackOff: true, value: 2}, | ||||||
|  | 		{tick: 3, inBackOff: false, value: 2}, | ||||||
|  | 		{tick: 4, inBackOff: true, value: 4}, | ||||||
|  | 		{tick: 5, inBackOff: true, value: 4}, | ||||||
|  | 		{tick: 6, inBackOff: true, value: 4}, | ||||||
|  | 		{tick: 7, inBackOff: false, value: 4}, | ||||||
|  | 		{tick: 8, inBackOff: true, value: 8}, | ||||||
|  | 		{tick: 9, inBackOff: true, value: 8}, | ||||||
|  | 		{tick: 10, inBackOff: true, value: 8}, | ||||||
|  | 		{tick: 11, inBackOff: true, value: 8}, | ||||||
|  | 		{tick: 12, inBackOff: true, value: 8}, | ||||||
|  | 		{tick: 13, inBackOff: true, value: 8}, | ||||||
|  | 		{tick: 14, inBackOff: true, value: 8}, | ||||||
|  | 		{tick: 15, inBackOff: false, value: 8}, | ||||||
|  | 		{tick: 16, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 17, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 18, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 19, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 20, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 21, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 22, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 23, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 24, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 25, inBackOff: false, value: 10}, | ||||||
|  | 		{tick: 26, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 27, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 28, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 29, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 30, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 31, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 32, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 33, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 34, inBackOff: true, value: 10}, | ||||||
|  | 		{tick: 35, inBackOff: false, value: 10}, | ||||||
|  | 		{tick: 56, inBackOff: false, value: 0}, | ||||||
|  | 		{tick: 57, inBackOff: false, value: 1}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, c := range cases { | ||||||
|  | 		tc.SetTime(startTime.Add(c.tick * step)) | ||||||
|  | 		if c.inBackOff != b.IsInBackOffSinceUpdate(id, tc.Now()) { | ||||||
|  | 			t.Errorf("expected IsInBackOffSinceUpdate %v got %v at tick %s", c.inBackOff, b.IsInBackOffSinceUpdate(id, tc.Now()), c.tick*step) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if c.inBackOff && (time.Duration(c.value)*step != b.Get(id)) { | ||||||
|  | 			t.Errorf("expected backoff value=%s got %s at tick %s", time.Duration(c.value)*step, b.Get(id), c.tick*step) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !c.inBackOff { | ||||||
|  | 			b.Next(id, tc.Now()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,177 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2014 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 flowcontrol | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"math" | ||||||
|  | 	"sync" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestMultithreadedThrottling(t *testing.T) { | ||||||
|  | 	// Bucket with 100QPS and no burst | ||||||
|  | 	r := NewTokenBucketRateLimiter(100, 1) | ||||||
|  |  | ||||||
|  | 	// channel to collect 100 tokens | ||||||
|  | 	taken := make(chan bool, 100) | ||||||
|  |  | ||||||
|  | 	// Set up goroutines to hammer the throttler | ||||||
|  | 	startCh := make(chan bool) | ||||||
|  | 	endCh := make(chan bool) | ||||||
|  | 	for i := 0; i < 10; i++ { | ||||||
|  | 		go func() { | ||||||
|  | 			// wait for the starting signal | ||||||
|  | 			<-startCh | ||||||
|  | 			for { | ||||||
|  | 				// get a token | ||||||
|  | 				r.Accept() | ||||||
|  | 				select { | ||||||
|  | 				// try to add it to the taken channel | ||||||
|  | 				case taken <- true: | ||||||
|  | 					continue | ||||||
|  | 				// if taken is full, notify and return | ||||||
|  | 				default: | ||||||
|  | 					endCh <- true | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// record wall time | ||||||
|  | 	startTime := time.Now() | ||||||
|  | 	// take the initial capacity so all tokens are the result of refill | ||||||
|  | 	r.Accept() | ||||||
|  | 	// start the thundering herd | ||||||
|  | 	close(startCh) | ||||||
|  | 	// wait for the first signal that we collected 100 tokens | ||||||
|  | 	<-endCh | ||||||
|  | 	// record wall time | ||||||
|  | 	endTime := time.Now() | ||||||
|  |  | ||||||
|  | 	// tolerate a 1% clock change because these things happen | ||||||
|  | 	if duration := endTime.Sub(startTime); duration < (time.Second * 99 / 100) { | ||||||
|  | 		// We shouldn't be able to get 100 tokens out of the bucket in less than 1 second of wall clock time, no matter what | ||||||
|  | 		t.Errorf("Expected it to take at least 1 second to get 100 tokens, took %v", duration) | ||||||
|  | 	} else { | ||||||
|  | 		t.Logf("Took %v to get 100 tokens", duration) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestBasicThrottle(t *testing.T) { | ||||||
|  | 	r := NewTokenBucketRateLimiter(1, 3) | ||||||
|  | 	for i := 0; i < 3; i++ { | ||||||
|  | 		if !r.TryAccept() { | ||||||
|  | 			t.Error("unexpected false accept") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if r.TryAccept() { | ||||||
|  | 		t.Error("unexpected true accept") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestIncrementThrottle(t *testing.T) { | ||||||
|  | 	r := NewTokenBucketRateLimiter(1, 1) | ||||||
|  | 	if !r.TryAccept() { | ||||||
|  | 		t.Error("unexpected false accept") | ||||||
|  | 	} | ||||||
|  | 	if r.TryAccept() { | ||||||
|  | 		t.Error("unexpected true accept") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Allow to refill | ||||||
|  | 	time.Sleep(2 * time.Second) | ||||||
|  |  | ||||||
|  | 	if !r.TryAccept() { | ||||||
|  | 		t.Error("unexpected false accept") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestThrottle(t *testing.T) { | ||||||
|  | 	r := NewTokenBucketRateLimiter(10, 5) | ||||||
|  |  | ||||||
|  | 	// Should consume 5 tokens immediately, then | ||||||
|  | 	// the remaining 11 should take at least 1 second (0.1s each) | ||||||
|  | 	expectedFinish := time.Now().Add(time.Second * 1) | ||||||
|  | 	for i := 0; i < 16; i++ { | ||||||
|  | 		r.Accept() | ||||||
|  | 	} | ||||||
|  | 	if time.Now().Before(expectedFinish) { | ||||||
|  | 		t.Error("rate limit was not respected, finished too early") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRateLimiterSaturation(t *testing.T) { | ||||||
|  | 	const e = 0.000001 | ||||||
|  | 	tests := []struct { | ||||||
|  | 		capacity int | ||||||
|  | 		take     int | ||||||
|  |  | ||||||
|  | 		expectedSaturation float64 | ||||||
|  | 	}{ | ||||||
|  | 		{1, 1, 1}, | ||||||
|  | 		{10, 3, 0.3}, | ||||||
|  | 	} | ||||||
|  | 	for i, tt := range tests { | ||||||
|  | 		rl := NewTokenBucketRateLimiter(1, tt.capacity) | ||||||
|  | 		for i := 0; i < tt.take; i++ { | ||||||
|  | 			rl.Accept() | ||||||
|  | 		} | ||||||
|  | 		if math.Abs(rl.Saturation()-tt.expectedSaturation) > e { | ||||||
|  | 			t.Fatalf("#%d: Saturation rate difference isn't within tolerable range\n want=%f, get=%f", | ||||||
|  | 				i, tt.expectedSaturation, rl.Saturation()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestAlwaysFake(t *testing.T) { | ||||||
|  | 	rl := NewFakeAlwaysRateLimiter() | ||||||
|  | 	if !rl.TryAccept() { | ||||||
|  | 		t.Error("TryAccept in AlwaysFake should return true.") | ||||||
|  | 	} | ||||||
|  | 	// If this will block the test will timeout | ||||||
|  | 	rl.Accept() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestNeverFake(t *testing.T) { | ||||||
|  | 	rl := NewFakeNeverRateLimiter() | ||||||
|  | 	if rl.TryAccept() { | ||||||
|  | 		t.Error("TryAccept in NeverFake should return false.") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	finished := false | ||||||
|  | 	wg := sync.WaitGroup{} | ||||||
|  | 	wg.Add(1) | ||||||
|  | 	go func() { | ||||||
|  | 		rl.Accept() | ||||||
|  | 		finished = true | ||||||
|  | 		wg.Done() | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	// Wait some time to make sure it never finished. | ||||||
|  | 	time.Sleep(time.Second) | ||||||
|  | 	if finished { | ||||||
|  | 		t.Error("Accept should block forever in NeverFake.") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	rl.Stop() | ||||||
|  | 	wg.Wait() | ||||||
|  | 	if !finished { | ||||||
|  | 		t.Error("Stop should make Accept unblock in NeverFake.") | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										244
									
								
								staging/src/k8s.io/client-go/pkg/util/integer/integer_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								staging/src/k8s.io/client-go/pkg/util/integer/integer_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,244 @@ | |||||||
|  | /* | ||||||
|  | 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 integer | ||||||
|  |  | ||||||
|  | import "testing" | ||||||
|  |  | ||||||
|  | func TestIntMax(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		nums        []int | ||||||
|  | 		expectedMax int | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int{-1, 0}, | ||||||
|  | 			expectedMax: 0, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int{-1, -2}, | ||||||
|  | 			expectedMax: -1, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int{0, 1}, | ||||||
|  | 			expectedMax: 1, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int{1, 2}, | ||||||
|  | 			expectedMax: 2, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i, test := range tests { | ||||||
|  | 		t.Logf("executing scenario %d", i) | ||||||
|  | 		if max := IntMax(test.nums[0], test.nums[1]); max != test.expectedMax { | ||||||
|  | 			t.Errorf("expected %v,  got %v", test.expectedMax, max) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestIntMin(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		nums        []int | ||||||
|  | 		expectedMin int | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int{-1, 0}, | ||||||
|  | 			expectedMin: -1, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int{-1, -2}, | ||||||
|  | 			expectedMin: -2, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int{0, 1}, | ||||||
|  | 			expectedMin: 0, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int{1, 2}, | ||||||
|  | 			expectedMin: 1, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i, test := range tests { | ||||||
|  | 		t.Logf("executing scenario %d", i) | ||||||
|  | 		if min := IntMin(test.nums[0], test.nums[1]); min != test.expectedMin { | ||||||
|  | 			t.Errorf("expected %v,  got %v", test.expectedMin, min) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestInt32Max(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		nums        []int32 | ||||||
|  | 		expectedMax int32 | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int32{-1, 0}, | ||||||
|  | 			expectedMax: 0, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int32{-1, -2}, | ||||||
|  | 			expectedMax: -1, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int32{0, 1}, | ||||||
|  | 			expectedMax: 1, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int32{1, 2}, | ||||||
|  | 			expectedMax: 2, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i, test := range tests { | ||||||
|  | 		t.Logf("executing scenario %d", i) | ||||||
|  | 		if max := Int32Max(test.nums[0], test.nums[1]); max != test.expectedMax { | ||||||
|  | 			t.Errorf("expected %v,  got %v", test.expectedMax, max) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestInt32Min(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		nums        []int32 | ||||||
|  | 		expectedMin int32 | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int32{-1, 0}, | ||||||
|  | 			expectedMin: -1, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int32{-1, -2}, | ||||||
|  | 			expectedMin: -2, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int32{0, 1}, | ||||||
|  | 			expectedMin: 0, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int32{1, 2}, | ||||||
|  | 			expectedMin: 1, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i, test := range tests { | ||||||
|  | 		t.Logf("executing scenario %d", i) | ||||||
|  | 		if min := Int32Min(test.nums[0], test.nums[1]); min != test.expectedMin { | ||||||
|  | 			t.Errorf("expected %v,  got %v", test.expectedMin, min) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestInt64Max(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		nums        []int64 | ||||||
|  | 		expectedMax int64 | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int64{-1, 0}, | ||||||
|  | 			expectedMax: 0, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int64{-1, -2}, | ||||||
|  | 			expectedMax: -1, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int64{0, 1}, | ||||||
|  | 			expectedMax: 1, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int64{1, 2}, | ||||||
|  | 			expectedMax: 2, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i, test := range tests { | ||||||
|  | 		t.Logf("executing scenario %d", i) | ||||||
|  | 		if max := Int64Max(test.nums[0], test.nums[1]); max != test.expectedMax { | ||||||
|  | 			t.Errorf("expected %v,  got %v", test.expectedMax, max) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestInt64Min(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		nums        []int64 | ||||||
|  | 		expectedMin int64 | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int64{-1, 0}, | ||||||
|  | 			expectedMin: -1, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int64{-1, -2}, | ||||||
|  | 			expectedMin: -2, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int64{0, 1}, | ||||||
|  | 			expectedMin: 0, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			nums:        []int64{1, 2}, | ||||||
|  | 			expectedMin: 1, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i, test := range tests { | ||||||
|  | 		t.Logf("executing scenario %d", i) | ||||||
|  | 		if min := Int64Min(test.nums[0], test.nums[1]); min != test.expectedMin { | ||||||
|  | 			t.Errorf("expected %v,  got %v", test.expectedMin, min) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRoundToInt32(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		num float64 | ||||||
|  | 		exp int32 | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			num: 5.5, | ||||||
|  | 			exp: 6, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			num: -3.7, | ||||||
|  | 			exp: -4, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			num: 3.49, | ||||||
|  | 			exp: 3, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			num: -7.9, | ||||||
|  | 			exp: -8, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			num: -4.499999, | ||||||
|  | 			exp: -4, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			num: 0, | ||||||
|  | 			exp: 0, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i, test := range tests { | ||||||
|  | 		t.Logf("executing scenario %d", i) | ||||||
|  | 		if got := RoundToInt32(test.num); got != test.exp { | ||||||
|  | 			t.Errorf("expected %d, got %d", test.exp, got) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -44,7 +44,6 @@ import ( | |||||||
| 	"k8s.io/apimachinery/pkg/util/sets" | 	"k8s.io/apimachinery/pkg/util/sets" | ||||||
| 	"k8s.io/apimachinery/pkg/watch" | 	"k8s.io/apimachinery/pkg/watch" | ||||||
| 	"k8s.io/client-go/pkg/api/v1" | 	"k8s.io/client-go/pkg/api/v1" | ||||||
| 	pathvalidation "k8s.io/client-go/pkg/api/validation/path" |  | ||||||
| 	"k8s.io/client-go/pkg/util/flowcontrol" | 	"k8s.io/client-go/pkg/util/flowcontrol" | ||||||
| 	restclientwatch "k8s.io/client-go/rest/watch" | 	restclientwatch "k8s.io/client-go/rest/watch" | ||||||
| 	"k8s.io/client-go/tools/metrics" | 	"k8s.io/client-go/tools/metrics" | ||||||
| @@ -179,7 +178,7 @@ func (r *Request) Resource(resource string) *Request { | |||||||
| 		r.err = fmt.Errorf("resource already set to %q, cannot change to %q", r.resource, resource) | 		r.err = fmt.Errorf("resource already set to %q, cannot change to %q", r.resource, resource) | ||||||
| 		return r | 		return r | ||||||
| 	} | 	} | ||||||
| 	if msgs := pathvalidation.IsValidPathSegmentName(resource); len(msgs) != 0 { | 	if msgs := IsValidPathSegmentName(resource); len(msgs) != 0 { | ||||||
| 		r.err = fmt.Errorf("invalid resource %q: %v", resource, msgs) | 		r.err = fmt.Errorf("invalid resource %q: %v", resource, msgs) | ||||||
| 		return r | 		return r | ||||||
| 	} | 	} | ||||||
| @@ -199,7 +198,7 @@ func (r *Request) SubResource(subresources ...string) *Request { | |||||||
| 		return r | 		return r | ||||||
| 	} | 	} | ||||||
| 	for _, s := range subresources { | 	for _, s := range subresources { | ||||||
| 		if msgs := pathvalidation.IsValidPathSegmentName(s); len(msgs) != 0 { | 		if msgs := IsValidPathSegmentName(s); len(msgs) != 0 { | ||||||
| 			r.err = fmt.Errorf("invalid subresource %q: %v", s, msgs) | 			r.err = fmt.Errorf("invalid subresource %q: %v", s, msgs) | ||||||
| 			return r | 			return r | ||||||
| 		} | 		} | ||||||
| @@ -221,7 +220,7 @@ func (r *Request) Name(resourceName string) *Request { | |||||||
| 		r.err = fmt.Errorf("resource name already set to %q, cannot change to %q", r.resourceName, resourceName) | 		r.err = fmt.Errorf("resource name already set to %q, cannot change to %q", r.resourceName, resourceName) | ||||||
| 		return r | 		return r | ||||||
| 	} | 	} | ||||||
| 	if msgs := pathvalidation.IsValidPathSegmentName(resourceName); len(msgs) != 0 { | 	if msgs := IsValidPathSegmentName(resourceName); len(msgs) != 0 { | ||||||
| 		r.err = fmt.Errorf("invalid resource name %q: %v", resourceName, msgs) | 		r.err = fmt.Errorf("invalid resource name %q: %v", resourceName, msgs) | ||||||
| 		return r | 		return r | ||||||
| 	} | 	} | ||||||
| @@ -238,7 +237,7 @@ func (r *Request) Namespace(namespace string) *Request { | |||||||
| 		r.err = fmt.Errorf("namespace already set to %q, cannot change to %q", r.namespace, namespace) | 		r.err = fmt.Errorf("namespace already set to %q, cannot change to %q", r.namespace, namespace) | ||||||
| 		return r | 		return r | ||||||
| 	} | 	} | ||||||
| 	if msgs := pathvalidation.IsValidPathSegmentName(namespace); len(msgs) != 0 { | 	if msgs := IsValidPathSegmentName(namespace); len(msgs) != 0 { | ||||||
| 		r.err = fmt.Errorf("invalid namespace %q: %v", namespace, msgs) | 		r.err = fmt.Errorf("invalid namespace %q: %v", namespace, msgs) | ||||||
| 		return r | 		return r | ||||||
| 	} | 	} | ||||||
| @@ -760,10 +759,11 @@ func (r *Request) Stream() (io.ReadCloser, error) { | |||||||
| 		defer resp.Body.Close() | 		defer resp.Body.Close() | ||||||
|  |  | ||||||
| 		result := r.transformResponse(resp, req) | 		result := r.transformResponse(resp, req) | ||||||
| 		if result.err != nil { | 		err := result.Error() | ||||||
| 			return nil, result.err | 		if err == nil { | ||||||
|  | 			err = fmt.Errorf("%d while accessing %v: %s", result.statusCode, url, string(result.body)) | ||||||
| 		} | 		} | ||||||
| 		return nil, fmt.Errorf("%d while accessing %v: %s", result.statusCode, url, string(result.body)) | 		return nil, err | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1197,3 +1197,49 @@ func (r Result) Error() error { | |||||||
| 	} | 	} | ||||||
| 	return r.err | 	return r.err | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // NameMayNotBe specifies strings that cannot be used as names specified as path segments (like the REST API or etcd store) | ||||||
|  | var NameMayNotBe = []string{".", ".."} | ||||||
|  |  | ||||||
|  | // NameMayNotContain specifies substrings that cannot be used in names specified as path segments (like the REST API or etcd store) | ||||||
|  | var NameMayNotContain = []string{"/", "%"} | ||||||
|  |  | ||||||
|  | // IsValidPathSegmentName validates the name can be safely encoded as a path segment | ||||||
|  | func IsValidPathSegmentName(name string) []string { | ||||||
|  | 	for _, illegalName := range NameMayNotBe { | ||||||
|  | 		if name == illegalName { | ||||||
|  | 			return []string{fmt.Sprintf(`may not be '%s'`, illegalName)} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var errors []string | ||||||
|  | 	for _, illegalContent := range NameMayNotContain { | ||||||
|  | 		if strings.Contains(name, illegalContent) { | ||||||
|  | 			errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return errors | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsValidPathSegmentPrefix validates the name can be used as a prefix for a name which will be encoded as a path segment | ||||||
|  | // It does not check for exact matches with disallowed names, since an arbitrary suffix might make the name valid | ||||||
|  | func IsValidPathSegmentPrefix(name string) []string { | ||||||
|  | 	var errors []string | ||||||
|  | 	for _, illegalContent := range NameMayNotContain { | ||||||
|  | 		if strings.Contains(name, illegalContent) { | ||||||
|  | 			errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return errors | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ValidatePathSegmentName validates the name can be safely encoded as a path segment | ||||||
|  | func ValidatePathSegmentName(name string, prefix bool) []string { | ||||||
|  | 	if prefix { | ||||||
|  | 		return IsValidPathSegmentPrefix(name) | ||||||
|  | 	} else { | ||||||
|  | 		return IsValidPathSegmentName(name) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -868,6 +868,7 @@ func TestRequestStream(t *testing.T) { | |||||||
| 	testCases := []struct { | 	testCases := []struct { | ||||||
| 		Request *Request | 		Request *Request | ||||||
| 		Err     bool | 		Err     bool | ||||||
|  | 		ErrFn   func(error) bool | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			Request: &Request{err: errors.New("bail")}, | 			Request: &Request{err: errors.New("bail")}, | ||||||
| @@ -903,6 +904,26 @@ func TestRequestStream(t *testing.T) { | |||||||
| 			}, | 			}, | ||||||
| 			Err: true, | 			Err: true, | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Request: &Request{ | ||||||
|  | 				client: clientFunc(func(req *http.Request) (*http.Response, error) { | ||||||
|  | 					return &http.Response{ | ||||||
|  | 						StatusCode: http.StatusBadRequest, | ||||||
|  | 						Body:       ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"a container name must be specified for pod kube-dns-v20-mz5cv, choose one of: [kubedns dnsmasq healthz]","reason":"BadRequest","code":400}`))), | ||||||
|  | 					}, nil | ||||||
|  | 				}), | ||||||
|  | 				content:     defaultContentConfig(), | ||||||
|  | 				serializers: defaultSerializers(), | ||||||
|  | 				baseURL:     &url.URL{}, | ||||||
|  | 			}, | ||||||
|  | 			Err: true, | ||||||
|  | 			ErrFn: func(err error) bool { | ||||||
|  | 				if err.Error() == "a container name must be specified for pod kube-dns-v20-mz5cv, choose one of: [kubedns dnsmasq healthz]" { | ||||||
|  | 					return true | ||||||
|  | 				} | ||||||
|  | 				return false | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 	for i, testCase := range testCases { | 	for i, testCase := range testCases { | ||||||
| 		testCase.Request.backoffMgr = &NoBackoff{} | 		testCase.Request.backoffMgr = &NoBackoff{} | ||||||
| @@ -914,6 +935,12 @@ func TestRequestStream(t *testing.T) { | |||||||
| 		if hasErr && body != nil { | 		if hasErr && body != nil { | ||||||
| 			t.Errorf("%d: body should be nil when error is returned", i) | 			t.Errorf("%d: body should be nil when error is returned", i) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if hasErr { | ||||||
|  | 			if testCase.ErrFn != nil && !testCase.ErrFn(err) { | ||||||
|  | 				t.Errorf("unexpected error: %v", err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 deads2k
					deads2k