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