mirror of https://github.com/k3s-io/k3s
232 lines
7.3 KiB
Go
232 lines
7.3 KiB
Go
/*
|
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
|
|
|
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 apiserver
|
|
|
|
import (
|
|
"compress/gzip"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
|
"golang.org/x/net/websocket"
|
|
)
|
|
|
|
func TestProxy(t *testing.T) {
|
|
table := []struct {
|
|
method string
|
|
path string
|
|
reqBody string
|
|
respBody string
|
|
respContentType string
|
|
reqNamespace string
|
|
}{
|
|
{"GET", "/some/dir", "", "answer", "text/css", "default"},
|
|
{"GET", "/some/dir", "", "<html><head></head><body>answer</body></html>", "text/html", "default"},
|
|
{"POST", "/some/other/dir", "question", "answer", "text/css", "default"},
|
|
{"PUT", "/some/dir/id", "different question", "answer", "text/css", "default"},
|
|
{"DELETE", "/some/dir/id", "", "ok", "text/css", "default"},
|
|
{"GET", "/some/dir/id", "", "answer", "text/css", "other"},
|
|
{"GET", "/trailing/slash/", "", "answer", "text/css", "default"},
|
|
{"GET", "/", "", "answer", "text/css", "default"},
|
|
}
|
|
|
|
for _, item := range table {
|
|
proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
gotBody, err := ioutil.ReadAll(req.Body)
|
|
if err != nil {
|
|
t.Errorf("%v - unexpected error %v", item.method, err)
|
|
}
|
|
if e, a := item.reqBody, string(gotBody); e != a {
|
|
t.Errorf("%v - expected %v, got %v", item.method, e, a)
|
|
}
|
|
if e, a := item.path, req.URL.Path; e != a {
|
|
t.Errorf("%v - expected %v, got %v", item.method, e, a)
|
|
}
|
|
w.Header().Set("Content-Type", item.respContentType)
|
|
var out io.Writer = w
|
|
if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
|
|
// The proxier can ask for gzip'd data; we need to provide it with that
|
|
// in order to test our processing of that data.
|
|
w.Header().Set("Content-Encoding", "gzip")
|
|
gzw := gzip.NewWriter(w)
|
|
out = gzw
|
|
defer gzw.Close()
|
|
}
|
|
fmt.Fprint(out, item.respBody)
|
|
}))
|
|
defer proxyServer.Close()
|
|
|
|
serverURL, _ := url.Parse(proxyServer.URL)
|
|
simpleStorage := &SimpleRESTStorage{
|
|
errors: map[string]error{},
|
|
resourceLocation: serverURL,
|
|
expectedResourceNamespace: item.reqNamespace,
|
|
}
|
|
|
|
namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
|
|
namespaceServer := httptest.NewServer(namespaceHandler)
|
|
defer namespaceServer.Close()
|
|
legacyNamespaceHandler := handle(map[string]rest.Storage{"foo": simpleStorage})
|
|
legacyNamespaceServer := httptest.NewServer(legacyNamespaceHandler)
|
|
defer legacyNamespaceServer.Close()
|
|
|
|
// test each supported URL pattern for finding the redirection resource in the proxy in a particular namespace
|
|
serverPatterns := []struct {
|
|
server *httptest.Server
|
|
proxyTestPattern string
|
|
}{
|
|
{namespaceServer, "/api/version2/proxy/namespaces/" + item.reqNamespace + "/foo/id" + item.path},
|
|
{legacyNamespaceServer, "/api/version/proxy/foo/id" + item.path + "?namespace=" + item.reqNamespace},
|
|
}
|
|
|
|
for _, serverPattern := range serverPatterns {
|
|
server := serverPattern.server
|
|
proxyTestPattern := serverPattern.proxyTestPattern
|
|
req, err := http.NewRequest(
|
|
item.method,
|
|
server.URL+proxyTestPattern,
|
|
strings.NewReader(item.reqBody),
|
|
)
|
|
if err != nil {
|
|
t.Errorf("%v - unexpected error %v", item.method, err)
|
|
continue
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Errorf("%v - unexpected error %v", item.method, err)
|
|
continue
|
|
}
|
|
gotResp, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Errorf("%v - unexpected error %v", item.method, err)
|
|
}
|
|
resp.Body.Close()
|
|
if e, a := item.respBody, string(gotResp); e != a {
|
|
t.Errorf("%v - expected %v, got %v. url: %#v", item.method, e, a, req.URL)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProxyUpgrade(t *testing.T) {
|
|
backendServer := httptest.NewServer(websocket.Handler(func(ws *websocket.Conn) {
|
|
defer ws.Close()
|
|
body := make([]byte, 5)
|
|
ws.Read(body)
|
|
ws.Write([]byte("hello " + string(body)))
|
|
}))
|
|
defer backendServer.Close()
|
|
|
|
serverURL, _ := url.Parse(backendServer.URL)
|
|
simpleStorage := &SimpleRESTStorage{
|
|
errors: map[string]error{},
|
|
resourceLocation: serverURL,
|
|
expectedResourceNamespace: "myns",
|
|
}
|
|
|
|
namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
|
|
|
|
server := httptest.NewServer(namespaceHandler)
|
|
defer server.Close()
|
|
|
|
ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/api/version2/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/")
|
|
if err != nil {
|
|
t.Fatalf("websocket dial err: %s", err)
|
|
}
|
|
defer ws.Close()
|
|
|
|
if _, err := ws.Write([]byte("world")); err != nil {
|
|
t.Fatalf("write err: %s", err)
|
|
}
|
|
|
|
response := make([]byte, 20)
|
|
n, err := ws.Read(response)
|
|
if err != nil {
|
|
t.Fatalf("read err: %s", err)
|
|
}
|
|
if e, a := "hello world", string(response[0:n]); e != a {
|
|
t.Fatalf("expected '%#v', got '%#v'", e, a)
|
|
}
|
|
}
|
|
|
|
func TestRedirectOnMissingTrailingSlash(t *testing.T) {
|
|
table := []struct {
|
|
// The requested path
|
|
path string
|
|
// The path requested on the proxy server.
|
|
proxyServerPath string
|
|
// query string
|
|
query string
|
|
}{
|
|
{"/trailing/slash/", "/trailing/slash/", ""},
|
|
{"/", "/", "test1=value1&test2=value2"},
|
|
// "/" should be added at the end.
|
|
{"", "/", "test1=value1&test2=value2"},
|
|
// "/" should not be added at a non-root path.
|
|
{"/some/path", "/some/path", ""},
|
|
}
|
|
|
|
for _, item := range table {
|
|
proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
if req.URL.Path != item.proxyServerPath {
|
|
t.Errorf("Unexpected request on path: %s, expected path: %s, item: %v", req.URL.Path, item.proxyServerPath, item)
|
|
}
|
|
if req.URL.RawQuery != item.query {
|
|
t.Errorf("Unexpected query on url: %s, expected: %s", req.URL.RawQuery, item.query)
|
|
}
|
|
}))
|
|
defer proxyServer.Close()
|
|
|
|
serverURL, _ := url.Parse(proxyServer.URL)
|
|
simpleStorage := &SimpleRESTStorage{
|
|
errors: map[string]error{},
|
|
resourceLocation: serverURL,
|
|
expectedResourceNamespace: "ns",
|
|
}
|
|
|
|
handler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
|
|
server := httptest.NewServer(handler)
|
|
defer server.Close()
|
|
|
|
proxyTestPattern := "/api/version2/proxy/namespaces/ns/foo/id" + item.path
|
|
req, err := http.NewRequest(
|
|
"GET",
|
|
server.URL+proxyTestPattern+"?"+item.query,
|
|
strings.NewReader(""),
|
|
)
|
|
if err != nil {
|
|
t.Errorf("unexpected error %v", err)
|
|
continue
|
|
}
|
|
// Note: We are using a default client here, that follows redirects.
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Errorf("unexpected error %v", err)
|
|
continue
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Errorf("Unexpected errorCode: %v, expected: 200. Response: %v, item: %v", resp.StatusCode, resp, item)
|
|
}
|
|
}
|
|
}
|