mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 10:18: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 { | ||||
| 	sw := generator.NewSnippetWriter(w, c, "$", "$") | ||||
| 	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{}{ | ||||
| 		"group":               g.group, | ||||
| 		"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. | ||||
| 	sw := generator.NewSnippetWriter(w, c, "$", "$") | ||||
| 	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) | ||||
|  | ||||
|   | ||||
| @@ -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 { | ||||
| 	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 pkgSerializer = "k8s.io/apimachinery/pkg/runtime/serializer" | ||||
| 	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), | ||||
| 		"GroupVersion":        namer.IC(g.group) + namer.IC(g.version), | ||||
| 		"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"}), | ||||
| 		"PatchType":           c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/types", Name: "PatchType"}), | ||||
| 		"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 "transport" | ||||
| save "tools/metrics" | ||||
| 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/integer" | ||||
| 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" | ||||
| # TODO: make this test file not depending on pkg/client/unversioned | ||||
| 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" | ||||
| # remove this test because it imports the internal clientset | ||||
| 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 | ||||
|  | ||||
| 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 | ||||
|  | ||||
| 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/dynamic dynamic | ||||
| 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/unversioned/auth tools/auth | ||||
| mvfolder pkg/client/unversioned/clientcmd tools/clientcmd | ||||
| mvfolder pkg/client/unversioned/portforward tools/portforward | ||||
| mvfolder pkg/client/metrics tools/metrics | ||||
| mvfolder pkg/client/testing/core testing | ||||
| mvfolder pkg/client/testing/cache tools/cache/testing | ||||
| 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/watch" | ||||
| 	"k8s.io/client-go/pkg/api/v1" | ||||
| 	pathvalidation "k8s.io/client-go/pkg/api/validation/path" | ||||
| 	"k8s.io/client-go/pkg/util/flowcontrol" | ||||
| 	restclientwatch "k8s.io/client-go/rest/watch" | ||||
| 	"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) | ||||
| 		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) | ||||
| 		return r | ||||
| 	} | ||||
| @@ -199,7 +198,7 @@ func (r *Request) SubResource(subresources ...string) *Request { | ||||
| 		return r | ||||
| 	} | ||||
| 	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) | ||||
| 			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) | ||||
| 		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) | ||||
| 		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) | ||||
| 		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) | ||||
| 		return r | ||||
| 	} | ||||
| @@ -760,10 +759,11 @@ func (r *Request) Stream() (io.ReadCloser, error) { | ||||
| 		defer resp.Body.Close() | ||||
|  | ||||
| 		result := r.transformResponse(resp, req) | ||||
| 		if result.err != nil { | ||||
| 			return nil, result.err | ||||
| 		err := result.Error() | ||||
| 		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 | ||||
| } | ||||
|  | ||||
| // 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 { | ||||
| 		Request *Request | ||||
| 		Err     bool | ||||
| 		ErrFn   func(error) bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			Request: &Request{err: errors.New("bail")}, | ||||
| @@ -903,6 +904,26 @@ func TestRequestStream(t *testing.T) { | ||||
| 			}, | ||||
| 			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 { | ||||
| 		testCase.Request.backoffMgr = &NoBackoff{} | ||||
| @@ -914,6 +935,12 @@ func TestRequestStream(t *testing.T) { | ||||
| 		if hasErr && body != nil { | ||||
| 			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