Merge pull request #10070 from lavalamp/e2eProxyFix

proxy e2e test improvements
This commit is contained in:
Maxwell Forbes 2015-06-25 13:15:03 -07:00
commit 5e748c1d47
13 changed files with 278 additions and 111 deletions

View File

@ -8,7 +8,7 @@
"containers": [
{
"name": "porter",
"image": "gcr.io/google_containers/porter:91d46193649807d1340b46797774d8b2",
"image": "gcr.io/google_containers/porter:59ad46ed2c56ba50fa7f1dc176c07c37",
"env": [
{
"name": "SERVE_PORT_80",

Binary file not shown.

View File

@ -33,7 +33,10 @@ const prefix = "SERVE_PORT_"
func main() {
for _, vk := range os.Environ() {
parts := strings.Split(vk, "=")
// Put everything before the first = sign in parts[0], and
// everything else in parts[1] (even if there are multiple =
// characters).
parts := strings.SplitN(vk, "=", 2)
key := parts[0]
value := parts[1]
if strings.HasPrefix(key, prefix) {

View File

@ -41,7 +41,7 @@ $ kubectl proxy --api-prefix=/k8s-api
```
--accept-hosts="^localhost$,^127\\.0\\.0\\.1$,^\\[::1\\]$": Regular expression for hosts that the proxy should accept.
--accept-paths="^/api/.*": Regular expression for paths that the proxy should accept.
--accept-paths="^/.*": Regular expression for paths that the proxy should accept.
--api-prefix="/api/": Prefix to serve the proxied API under.
--disable-filter=false: If true, disable request filtering in the proxy. This is dangerous, and can leave you vulnerable to XSRF attacks. Use with caution.
-h, --help=false: help for proxy
@ -84,6 +84,6 @@ $ kubectl proxy --api-prefix=/k8s-api
### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-06-11 03:49:29.837564354 +0000 UTC
###### Auto generated by spf13/cobra at 2015-06-23 19:00:28.69764897 +0000 UTC
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/kubectl_proxy.md?pixel)]()

View File

@ -43,7 +43,7 @@ The above lets you 'curl localhost:8001/custom/api/v1/pods'
Regular expression for hosts that the proxy should accept.
.PP
\fB\-\-accept\-paths\fP="^/api/.*"
\fB\-\-accept\-paths\fP="^/.*"
Regular expression for paths that the proxy should accept.
.PP

View File

@ -25,16 +25,54 @@ KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
source "${KUBE_ROOT}/hack/lib/init.sh"
source "${KUBE_ROOT}/hack/lib/test.sh"
# Stops the running kubectl proxy, if there is one.
function stop-proxy()
{
[[ -n "${PROXY_PID-}" ]] && kill "${PROXY_PID}" 1>&2 2>/dev/null
PROXY_PID=
}
# Starts "kubect proxy" to test the client proxy. You may pass options, e.g.
# --api-prefix.
function start-proxy()
{
stop-proxy
kube::log::status "Starting kubectl proxy"
# the --www and --www-prefix are just to make something definitely show up for
# wait_for_url to see.
kubectl proxy -p ${PROXY_PORT} --www=. --www-prefix=/healthz "$@" 1>&2 &
PROXY_PID=$!
kube::util::wait_for_url "http://127.0.0.1:${PROXY_PORT}/healthz" "kubectl proxy $@"
}
function cleanup()
{
[[ -n ${APISERVER_PID-} ]] && kill ${APISERVER_PID} 1>&2 2>/dev/null
[[ -n ${CTLRMGR_PID-} ]] && kill ${CTLRMGR_PID} 1>&2 2>/dev/null
[[ -n ${KUBELET_PID-} ]] && kill ${KUBELET_PID} 1>&2 2>/dev/null
[[ -n "${APISERVER_PID-}" ]] && kill "${APISERVER_PID}" 1>&2 2>/dev/null
[[ -n "${CTLRMGR_PID-}" ]] && kill "${CTLRMGR_PID}" 1>&2 2>/dev/null
[[ -n "${KUBELET_PID-}" ]] && kill "${KUBELET_PID}" 1>&2 2>/dev/null
stop-proxy
kube::etcd::cleanup
rm -rf "${KUBE_TEMP}"
kube::etcd::cleanup
rm -rf "${KUBE_TEMP}"
kube::log::status "Clean up complete"
kube::log::status "Clean up complete"
}
# Executes curl against the proxy. $1 is the path to use, $2 is the desired
# return code. Prints a helpful message on failure.
function check-curl-proxy-code()
{
local status
local -r address=$1
local -r desired=$2
local -r full_address="${PROXY_HOST}:${PROXY_PORT}${address}"
status=$(curl -w "%{http_code}" --silent --output /dev/null "${full_address}")
if [ "${status}" == "${desired}" ]; then
return 0
fi
echo "For address ${full_address}, got ${status} but wanted ${desired}"
return 1
}
trap cleanup EXIT SIGINT
@ -49,6 +87,8 @@ API_HOST=${API_HOST:-127.0.0.1}
KUBELET_PORT=${KUBELET_PORT:-10250}
KUBELET_HEALTHZ_PORT=${KUBELET_HEALTHZ_PORT:-10248}
CTLRMGR_PORT=${CTLRMGR_PORT:-10252}
PROXY_PORT=${PROXY_PORT:-8001}
PROXY_HOST=127.0.0.1 # kubectl only serves on localhost.
# Check kubectl
kube::log::status "Running kubectl with no options"
@ -146,6 +186,38 @@ for version in "${kube_api_versions[@]}"; do
# Passing no arguments to create is an error
! kubectl create
#######################
# kubectl local proxy #
#######################
# Make sure the UI can be proxied
start-proxy --api-prefix=/
check-curl-proxy-code /ui 301
check-curl-proxy-code /metrics 200
if [[ -n "${version}" ]]; then
check-curl-proxy-code /api/${version}/namespaces 200
fi
stop-proxy
# Default proxy locks you into the /api path (legacy behavior)
start-proxy
check-curl-proxy-code /ui 404
check-curl-proxy-code /metrics 404
check-curl-proxy-code /api/ui 404
if [[ -n "${version}" ]]; then
check-curl-proxy-code /api/${version}/namespaces 200
fi
stop-proxy
# Custom paths let you see everything.
start-proxy --api-prefix=/custom
check-curl-proxy-code /custom/ui 301
check-curl-proxy-code /custom/metrics 200
if [[ -n "${version}" ]]; then
check-curl-proxy-code /custom/api/${version}/namespaces 200
fi
stop-proxy
###########################
# POD creation / deletion #
###########################

View File

@ -20,6 +20,7 @@ import (
"crypto/tls"
"fmt"
"io"
"math/rand"
"net"
"net/http"
"net/http/httputil"
@ -55,6 +56,8 @@ type ProxyHandler struct {
}
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
proxyHandlerTraceID := rand.Int63()
var verb string
var apiResource string
var httpCode int
@ -108,7 +111,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
location, transport, err := redirector.ResourceLocation(ctx, id)
location, roundTripper, err := redirector.ResourceLocation(ctx, id)
if err != nil {
httplog.LogOf(req, w).Addf("Error getting ResourceLocation: %v", err)
status := errToAPIStatus(err)
@ -123,8 +126,11 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
// If we have a custom dialer, and no pre-existing transport, initialize it to use the dialer.
if transport == nil && r.dial != nil {
transport = &http.Transport{Dial: r.dial}
if roundTripper == nil && r.dial != nil {
glog.V(5).Infof("[%x: %v] making a dial-only transport...", proxyHandlerTraceID, req.URL)
roundTripper = &http.Transport{Dial: r.dial}
} else if roundTripper != nil {
glog.V(5).Infof("[%x: %v] using transport %T...", proxyHandlerTraceID, req.URL, roundTripper)
}
// Default to http
@ -158,7 +164,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// TODO convert this entire proxy to an UpgradeAwareProxy similar to
// https://github.com/openshift/origin/blob/master/pkg/util/httpproxy/upgradeawareproxy.go.
// That proxy needs to be modified to support multiple backends, not just 1.
if r.tryUpgrade(w, req, newReq, location, transport) {
if r.tryUpgrade(w, req, newReq, location, roundTripper) {
return
}
@ -175,19 +181,33 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
start := time.Now()
glog.V(4).Infof("[%x] Beginning proxy %s...", proxyHandlerTraceID, req.URL)
defer func() {
glog.V(4).Infof("[%x] Proxy %v finished %v.", proxyHandlerTraceID, req.URL, time.Now().Sub(start))
}()
proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: location.Scheme, Host: location.Host})
if transport == nil {
alreadyRewriting := false
if roundTripper != nil {
_, alreadyRewriting = roundTripper.(*proxyutil.Transport)
glog.V(5).Infof("[%x] Not making a reriting transport for proxy %s...", proxyHandlerTraceID, req.URL)
}
if !alreadyRewriting {
glog.V(5).Infof("[%x] making a transport for proxy %s...", proxyHandlerTraceID, req.URL)
prepend := path.Join(r.prefix, resource, id)
if len(namespace) > 0 {
prepend = path.Join(r.prefix, "namespaces", namespace, resource, id)
}
transport = &proxyutil.Transport{
Scheme: req.URL.Scheme,
Host: req.URL.Host,
PathPrepend: prepend,
pTransport := &proxyutil.Transport{
Scheme: req.URL.Scheme,
Host: req.URL.Host,
PathPrepend: prepend,
RoundTripper: roundTripper,
}
roundTripper = pTransport
}
proxy.Transport = transport
proxy.Transport = roundTripper
proxy.FlushInterval = 200 * time.Millisecond
proxy.ServeHTTP(w, newReq)
}

View File

@ -803,9 +803,9 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu
}
return Result{
body: body,
created: resp.StatusCode == http.StatusCreated,
codec: r.codec,
body: body,
statusCode: resp.StatusCode,
codec: r.codec,
}
}
@ -879,9 +879,9 @@ func retryAfterSeconds(resp *http.Response) (int, bool) {
// Result contains the result of calling Request.Do().
type Result struct {
body []byte
created bool
err error
body []byte
err error
statusCode int
codec runtime.Codec
}
@ -899,6 +899,13 @@ func (r Result) Get() (runtime.Object, error) {
return r.codec.Decode(r.body)
}
// StatusCode returns the HTTP status code of the request. (Only valid if no
// error was returned.)
func (r Result) StatusCode(statusCode *int) Result {
*statusCode = r.statusCode
return r
}
// Into stores the result into obj, if possible.
func (r Result) Into(obj runtime.Object) error {
if r.err != nil {
@ -910,7 +917,7 @@ func (r Result) Into(obj runtime.Object) error {
// WasCreated updates the provided bool pointer to whether the server returned
// 201 created or a different response.
func (r Result) WasCreated(wasCreated *bool) Result {
*wasCreated = r.created
*wasCreated = r.statusCode == http.StatusCreated
return r
}

View File

@ -274,7 +274,7 @@ func TestTransformResponse(t *testing.T) {
test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
}
result := r.transformResponse(test.Response, &http.Request{})
response, created, err := result.body, result.created, result.err
response, created, err := result.body, result.statusCode == http.StatusCreated, result.err
hasErr := err != nil
if hasErr != test.Error {
t.Errorf("%d: unexpected error: %t %v", i, test.Error, err)

View File

@ -31,7 +31,7 @@ import (
const (
DefaultHostAcceptRE = "^localhost$,^127\\.0\\.0\\.1$,^\\[::1\\]$"
DefaultPathAcceptRE = "^/api/.*"
DefaultPathAcceptRE = "^/.*"
DefaultPathRejectRE = "^/api/.*/exec,^/api/.*/run"
DefaultMethodRejectRE = "POST,PUT,PATCH"
)
@ -75,6 +75,7 @@ func MakeRegexpArrayOrDie(str string) []*regexp.Regexp {
func matchesRegexp(str string, regexps []*regexp.Regexp) bool {
for _, re := range regexps {
if re.MatchString(str) {
glog.V(6).Infof("%v matched %s", str, re)
return true
}
}
@ -83,17 +84,28 @@ func matchesRegexp(str string, regexps []*regexp.Regexp) bool {
func (f *FilterServer) accept(method, path, host string) bool {
if matchesRegexp(path, f.RejectPaths) {
glog.V(3).Infof("Filter rejecting %v %v %v", method, path, host)
return false
}
if matchesRegexp(method, f.RejectMethods) {
glog.V(3).Infof("Filter rejecting %v %v %v", method, path, host)
return false
}
if matchesRegexp(path, f.AcceptPaths) && matchesRegexp(host, f.AcceptHosts) {
glog.V(3).Infof("Filter accepting %v %v %v", method, path, host)
return true
}
glog.V(3).Infof("Filter rejecting %v %v %v", method, path, host)
return false
}
// Make a copy of f which passes requests along to the new delegate.
func (f *FilterServer) HandlerFor(delegate http.Handler) *FilterServer {
f2 := *f
f2.delegate = delegate
return &f2
}
func (f *FilterServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
host, _, _ := net.SplitHostPort(req.Host)
if f.accept(req.Method, req.URL.Path, host) {
@ -106,12 +118,12 @@ func (f *FilterServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// ProxyServer is a http.Handler which proxies Kubernetes APIs to remote API server.
type ProxyServer struct {
mux *http.ServeMux
httputil.ReverseProxy
handler http.Handler
}
// NewProxyServer creates and installs a new ProxyServer.
// It automatically registers the created ProxyServer to http.DefaultServeMux.
// 'filter', if non-nil, protects requests to the api only.
func NewProxyServer(filebase string, apiProxyPrefix string, staticPrefix string, filter *FilterServer, cfg *client.Config) (*ProxyServer, error) {
host := cfg.Host
if !strings.HasSuffix(host, "/") {
@ -121,46 +133,45 @@ func NewProxyServer(filebase string, apiProxyPrefix string, staticPrefix string,
if err != nil {
return nil, err
}
proxy := newProxyServer(target)
proxy := newProxy(target)
if proxy.Transport, err = client.TransportFor(cfg); err != nil {
return nil, err
}
var server http.Handler
if strings.HasPrefix(apiProxyPrefix, "/api") {
server = proxy
} else {
server = http.StripPrefix(apiProxyPrefix, proxy)
}
proxyServer := http.Handler(proxy)
if filter != nil {
filter.delegate = server
server = filter
proxyServer = filter.HandlerFor(proxyServer)
}
proxy.mux.Handle(apiProxyPrefix, server)
proxy.mux.Handle(staticPrefix, newFileHandler(staticPrefix, filebase))
return proxy, nil
if !strings.HasPrefix(apiProxyPrefix, "/api") {
proxyServer = stripLeaveSlash(apiProxyPrefix, proxyServer)
}
mux := http.NewServeMux()
mux.Handle(apiProxyPrefix, proxyServer)
if filebase != "" {
// Require user to explicitly request this behavior rather than
// serving their working directory by default.
mux.Handle(staticPrefix, newFileHandler(staticPrefix, filebase))
}
return &ProxyServer{handler: mux}, nil
}
// Serve starts the server (http.DefaultServeMux) on given port, loops forever.
func (s *ProxyServer) Serve(port int) error {
server := http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: s.mux,
Handler: s.handler,
}
return server.ListenAndServe()
}
func newProxyServer(target *url.URL) *ProxyServer {
func newProxy(target *url.URL) *httputil.ReverseProxy {
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
}
return &ProxyServer{
ReverseProxy: httputil.ReverseProxy{Director: director},
mux: http.NewServeMux(),
}
return &httputil.ReverseProxy{Director: director}
}
func newFileHandler(prefix, base string) http.Handler {
@ -178,3 +189,20 @@ func singleJoiningSlash(a, b string) string {
}
return a + b
}
// like http.StripPrefix, but always leaves an initial slash. (so that our
// regexps will work.)
func stripLeaveSlash(prefix string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
p := strings.TrimPrefix(req.URL.Path, prefix)
if len(p) >= len(req.URL.Path) {
http.NotFound(w, req)
return
}
if len(p) > 0 && p[:1] != "/" {
p = "/" + p
}
req.URL.Path = p
h.ServeHTTP(w, req)
})
}

View File

@ -98,10 +98,10 @@ func TestAccept(t *testing.T) {
acceptPaths: DefaultPathAcceptRE,
rejectPaths: DefaultPathRejectRE,
acceptHosts: DefaultHostAcceptRE,
path: "/foo/v1/pods",
path: "/ui",
host: "localhost",
method: "GET",
expectAccept: false,
expectAccept: true,
},
{
acceptPaths: DefaultPathAcceptRE,
@ -230,7 +230,7 @@ func TestAPIRequests(t *testing.T) {
// httptest.NewServer should always generate a valid URL.
target, _ := url.Parse(ts.URL)
proxy := newProxyServer(target)
proxy := newProxy(target)
tests := []struct{ method, body string }{
{"GET", ""},
@ -291,7 +291,7 @@ func TestPathHandling(t *testing.T) {
if err != nil {
t.Fatalf("%#v: %v", item, err)
}
pts := httptest.NewServer(p.mux)
pts := httptest.NewServer(p.handler)
defer pts.Close()
r, err := http.Get(pts.URL + item.reqPath)

View File

@ -73,6 +73,8 @@ type Transport struct {
Scheme string
Host string
PathPrepend string
http.RoundTripper
}
// RoundTrip implements the http.RoundTripper interface
@ -86,7 +88,11 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("X-Forwarded-Host", t.Host)
req.Header.Set("X-Forwarded-Proto", t.Scheme)
resp, err := http.DefaultTransport.RoundTrip(req)
rt := t.RoundTripper
if rt == nil {
rt = http.DefaultTransport
}
resp, err := rt.RoundTrip(req)
if err != nil {
message := fmt.Sprintf("Error: '%s'\nTrying to reach: '%v'", err.Error(), req.URL.String())

View File

@ -18,7 +18,9 @@ package e2e
import (
"fmt"
"net/http"
"strings"
"sync"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -37,52 +39,23 @@ var _ = Describe("Proxy", func() {
}
})
const (
// Try all the proxy tests this many times (to catch even rare flakes).
proxyAttempts = 20
// Only print this many characters of the response (to keep the logs
// legible).
maxDisplayBodyLen = 100
)
func proxyContext(version string) {
f := NewFramework("proxy")
prefix := "/api/" + version
It("should proxy logs on node with explicit kubelet port", func() {
node, err := pickNode(f.Client)
Expect(err).NotTo(HaveOccurred())
// AbsPath preserves the trailing '/'.
body, err := f.Client.Get().AbsPath(prefix + "/proxy/nodes/" + node + ":10250/logs/").Do().Raw()
if len(body) > 0 {
if len(body) > 100 {
body = body[:100]
body = append(body, '.', '.', '.')
}
Logf("Got: %s", body)
}
Expect(err).NotTo(HaveOccurred())
})
It("should proxy logs on node with explicit kubelet port", func() { nodeProxyTest(f, version, ":10250/logs/") })
It("should proxy logs on node", func() {
node, err := pickNode(f.Client)
Expect(err).NotTo(HaveOccurred())
body, err := f.Client.Get().AbsPath(prefix + "/proxy/nodes/" + node + "/logs/").Do().Raw()
if len(body) > 0 {
if len(body) > 100 {
body = body[:100]
body = append(body, '.', '.', '.')
}
Logf("Got: %s", body)
}
Expect(err).NotTo(HaveOccurred())
})
It("should proxy logs on node", func() { nodeProxyTest(f, version, "/logs/") })
It("should proxy to cadvisor", func() {
node, err := pickNode(f.Client)
Expect(err).NotTo(HaveOccurred())
body, err := f.Client.Get().AbsPath(prefix + "/proxy/nodes/" + node + ":4194/containers/").Do().Raw()
if len(body) > 0 {
if len(body) > 100 {
body = body[:100]
body = append(body, '.', '.', '.')
}
Logf("Got: %s", body)
}
Expect(err).NotTo(HaveOccurred())
})
It("should proxy to cadvisor", func() { nodeProxyTest(f, version, ":4194/containers/") })
It("should proxy through a service and a pod", func() {
labels := map[string]string{"proxy-service-target": "true"}
@ -118,13 +91,13 @@ func proxyContext(version string) {
pods := []*api.Pod{}
cfg := RCConfig{
Client: f.Client,
Image: "gcr.io/google_containers/porter:91d46193649807d1340b46797774d8b2",
Image: "gcr.io/google_containers/porter:59ad46ed2c56ba50fa7f1dc176c07c37",
Name: service.Name,
Namespace: f.Namespace.Name,
Replicas: 1,
PollInterval: time.Second,
Env: map[string]string{
"SERVE_PORT_80": "not accessible via service",
"SERVE_PORT_80": `<a href="/rewriteme">test</a>`,
"SERVE_PORT_160": "foo",
"SERVE_PORT_162": "bar",
},
@ -144,11 +117,11 @@ func proxyContext(version string) {
svcPrefix := prefix + "/proxy/namespaces/" + f.Namespace.Name + "/services/" + service.Name
podPrefix := prefix + "/proxy/namespaces/" + f.Namespace.Name + "/pods/" + pods[0].Name
expectations := map[string]string{
svcPrefix + ":portname1": "foo",
svcPrefix + ":portname2": "bar",
podPrefix + ":80": "not accessible via service",
podPrefix + ":160": "foo",
podPrefix + ":162": "bar",
svcPrefix + ":portname1/": "foo",
svcPrefix + ":portname2/": "bar",
podPrefix + ":80/": `<a href="` + podPrefix + `:80/rewriteme">test</a>`,
podPrefix + ":160/": "foo",
podPrefix + ":162/": "bar",
// TODO: below entries don't work, but I believe we should make them work.
// svcPrefix + ":80": "foo",
// svcPrefix + ":81": "bar",
@ -156,17 +129,38 @@ func proxyContext(version string) {
// podPrefix + ":dest2": "bar",
}
wg := sync.WaitGroup{}
errors := []string{}
for path, val := range expectations {
body, err := f.Client.Get().AbsPath(path).Do().Raw()
if err != nil {
errors = append(errors, fmt.Sprintf("path %v gave error %v", path, err))
continue
}
if e, a := val, string(body); e != a {
errors = append(errors, fmt.Sprintf("path %v: wanted %v, got %v", path, e, a))
errLock := sync.Mutex{}
recordError := func(s string) {
errLock.Lock()
defer errLock.Unlock()
errors = append(errors, s)
}
for i := 0; i < proxyAttempts; i++ {
for path, val := range expectations {
wg.Add(1)
go func(i int, path, val string) {
defer wg.Done()
body, status, d, err := doProxy(f, path)
if err != nil {
recordError(fmt.Sprintf("%v: path %v gave error: %v", i, path, err))
return
}
if status != http.StatusOK {
recordError(fmt.Sprintf("%v: path %v gave status: %v", i, path, status))
}
if e, a := val, string(body); e != a {
recordError(fmt.Sprintf("%v: path %v: wanted %v, got %v", i, path, e, a))
}
if d > 15*time.Second {
recordError(fmt.Sprintf("%v: path %v took %v > 15s", i, path, d))
}
}(i, path, val)
time.Sleep(150 * time.Millisecond)
}
}
wg.Wait()
if len(errors) != 0 {
Fail(strings.Join(errors, "\n"))
@ -174,6 +168,31 @@ func proxyContext(version string) {
})
}
func doProxy(f *Framework, path string) (body []byte, statusCode int, d time.Duration, err error) {
// About all of the proxy accesses in this file:
// * AbsPath is used because it preserves the trailing '/'.
// * Do().Raw() is used (instead of DoRaw()) because it will turn an
// error from apiserver proxy into an actual error, and there is no
// chance of the things we are talking to being confused for an error
// that apiserver would have emitted.
start := time.Now()
body, err = f.Client.Get().AbsPath(path).Do().StatusCode(&statusCode).Raw()
d = time.Since(start)
if len(body) > 0 {
Logf("%v: %s (%v; %v)", path, truncate(body, maxDisplayBodyLen), statusCode, d)
}
return
}
func truncate(b []byte, maxLen int) []byte {
if len(b) <= maxLen-3 {
return b
}
b2 := append([]byte(nil), b[:maxLen-3]...)
b2 = append(b2, '.', '.', '.')
return b2
}
func pickNode(c *client.Client) (string, error) {
nodes, err := c.Nodes().List(labels.Everything(), fields.Everything())
if err != nil {
@ -184,3 +203,15 @@ func pickNode(c *client.Client) (string, error) {
}
return nodes.Items[0].Name, nil
}
func nodeProxyTest(f *Framework, version, nodeDest string) {
prefix := "/api/" + version
node, err := pickNode(f.Client)
Expect(err).NotTo(HaveOccurred())
for i := 0; i < proxyAttempts; i++ {
_, status, d, err := doProxy(f, prefix+"/proxy/nodes/"+node+nodeDest)
Expect(err).NotTo(HaveOccurred())
Expect(status).To(Equal(http.StatusOK))
Expect(d).To(BeNumerically("<", 15*time.Second))
}
}