vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

64
vendor/k8s.io/kubernetes/pkg/client/tests/BUILD generated vendored Normal file
View File

@ -0,0 +1,64 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"fake_client_test.go",
"listwatch_test.go",
"portfoward_test.go",
"remotecommand_test.go",
],
importpath = "k8s.io/kubernetes/pkg/client/tests",
library = ":go_default_library",
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/install:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/kubelet/server/portforward:go_default_library",
"//pkg/kubelet/server/remotecommand:go_default_library",
"//vendor/github.com/stretchr/testify/require:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/httpstream:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/remotecommand:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/client-go/tools/portforward:go_default_library",
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/k8s.io/client-go/transport/spdy:go_default_library",
"//vendor/k8s.io/client-go/util/testing:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["doc.go"],
importpath = "k8s.io/kubernetes/pkg/client/tests",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

18
vendor/k8s.io/kubernetes/pkg/client/tests/doc.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
/*
Copyright 2017 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.
*/
// This package runs tests against the client which require an internal client
package tests

View File

@ -0,0 +1,182 @@
/*
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 tests
import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/kubernetes/pkg/apis/core"
clientsetfake "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
_ "k8s.io/kubernetes/pkg/apis/core/install"
)
func TestFakeClientSetFiltering(t *testing.T) {
tc := clientsetfake.NewSimpleClientset(
testPod("nsA", "pod-1"),
testPod("nsB", "pod-2"),
testSA("nsA", "sa-1"),
testSA("nsA", "sa-2"),
testSA("nsB", "sa-1"),
testSA("nsB", "sa-2"),
testSA("nsB", "sa-3"),
)
saList1, err := tc.Core().ServiceAccounts("nsA").List(metav1.ListOptions{})
if err != nil {
t.Fatalf("ServiceAccounts.List: %s", err)
}
if actual, expected := len(saList1.Items), 2; expected != actual {
t.Fatalf("Expected %d records to match, got %d", expected, actual)
}
for _, sa := range saList1.Items {
if sa.Namespace != "nsA" {
t.Fatalf("Expected namespace %q; got %q", "nsA", sa.Namespace)
}
}
saList2, err := tc.Core().ServiceAccounts("nsB").List(metav1.ListOptions{})
if err != nil {
t.Fatalf("ServiceAccounts.List: %s", err)
}
if actual, expected := len(saList2.Items), 3; expected != actual {
t.Fatalf("Expected %d records to match, got %d", expected, actual)
}
for _, sa := range saList2.Items {
if sa.Namespace != "nsB" {
t.Fatalf("Expected namespace %q; got %q", "nsA", sa.Namespace)
}
}
pod1, err := tc.Core().Pods("nsA").Get("pod-1", metav1.GetOptions{})
if err != nil {
t.Fatalf("Pods.Get: %s", err)
}
if pod1 == nil {
t.Fatalf("Expected to find pod nsA/pod-1 but it wasn't found")
}
if pod1.Namespace != "nsA" || pod1.Name != "pod-1" {
t.Fatalf("Expected to find pod nsA/pod-1t, got %s/%s", pod1.Namespace, pod1.Name)
}
wrongPod, err := tc.Core().Pods("nsB").Get("pod-1", metav1.GetOptions{})
if err == nil {
t.Fatalf("Pods.Get: expected nsB/pod-1 not to match, but it matched %s/%s", wrongPod.Namespace, wrongPod.Name)
}
allPods, err := tc.Core().Pods(metav1.NamespaceAll).List(metav1.ListOptions{})
if err != nil {
t.Fatalf("Pods.List: %s", err)
}
if actual, expected := len(allPods.Items), 2; expected != actual {
t.Fatalf("Expected %d pods to match, got %d", expected, actual)
}
allSAs, err := tc.Core().ServiceAccounts(metav1.NamespaceAll).List(metav1.ListOptions{})
if err != nil {
t.Fatalf("ServiceAccounts.List: %s", err)
}
if actual, expected := len(allSAs.Items), 5; expected != actual {
t.Fatalf("Expected %d service accounts to match, got %d", expected, actual)
}
}
func TestFakeClientsetInheritsNamespace(t *testing.T) {
tc := clientsetfake.NewSimpleClientset(
testNamespace("nsA"),
testPod("nsA", "pod-1"),
)
_, err := tc.Core().Namespaces().Create(testNamespace("nsB"))
if err != nil {
t.Fatalf("Namespaces.Create: %s", err)
}
allNS, err := tc.Core().Namespaces().List(metav1.ListOptions{})
if err != nil {
t.Fatalf("Namespaces.List: %s", err)
}
if actual, expected := len(allNS.Items), 2; expected != actual {
t.Fatalf("Expected %d namespaces to match, got %d", expected, actual)
}
_, err = tc.Core().Pods("nsB").Create(testPod("", "pod-1"))
if err != nil {
t.Fatalf("Pods.Create nsB/pod-1: %s", err)
}
podB1, err := tc.Core().Pods("nsB").Get("pod-1", metav1.GetOptions{})
if err != nil {
t.Fatalf("Pods.Get nsB/pod-1: %s", err)
}
if podB1 == nil {
t.Fatalf("Expected to find pod nsB/pod-1 but it wasn't found")
}
if podB1.Namespace != "nsB" || podB1.Name != "pod-1" {
t.Fatalf("Expected to find pod nsB/pod-1t, got %s/%s", podB1.Namespace, podB1.Name)
}
_, err = tc.Core().Pods("nsA").Create(testPod("", "pod-1"))
if err == nil {
t.Fatalf("Expected Pods.Create to fail with already exists error")
}
_, err = tc.Core().Pods("nsA").Update(testPod("", "pod-1"))
if err != nil {
t.Fatalf("Pods.Update nsA/pod-1: %s", err)
}
_, err = tc.Core().Pods("nsA").Create(testPod("nsB", "pod-2"))
if err == nil {
t.Fatalf("Expected Pods.Create to fail with bad request from namespace mismtach")
}
if err.Error() != `request namespace does not match object namespace, request: "nsA" object: "nsB"` {
t.Fatalf("Expected Pods.Create error to provide object and request namespaces, got %q", err)
}
_, err = tc.Core().Pods("nsA").Update(testPod("", "pod-3"))
if err == nil {
t.Fatalf("Expected Pods.Update nsA/pod-3 to fail with not found error")
}
}
func testSA(ns, name string) *api.ServiceAccount {
return &api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
},
}
}
func testPod(ns, name string) *api.Pod {
return &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
},
}
}
func testNamespace(ns string) *api.Namespace {
return &api.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: ns,
},
}
}

View File

@ -0,0 +1,229 @@
/*
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 tests
import (
"net/http/httptest"
"net/url"
"testing"
"time"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
restclient "k8s.io/client-go/rest"
. "k8s.io/client-go/tools/cache"
utiltesting "k8s.io/client-go/util/testing"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
)
func parseSelectorOrDie(s string) fields.Selector {
selector, err := fields.ParseSelector(s)
if err != nil {
panic(err)
}
return selector
}
// buildQueryValues is a convenience function for knowing if a namespace should be in a query param or not
func buildQueryValues(query url.Values) url.Values {
v := url.Values{}
if query != nil {
for key, values := range query {
for _, value := range values {
v.Add(key, value)
}
}
}
return v
}
func buildLocation(resourcePath string, query url.Values) string {
return resourcePath + "?" + query.Encode()
}
func TestListWatchesCanList(t *testing.T) {
fieldSelectorQueryParamName := metav1.FieldSelectorQueryParam(legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String())
table := []struct {
location string
resource string
namespace string
fieldSelector fields.Selector
}{
// Node
{
location: testapi.Default.ResourcePath("nodes", metav1.NamespaceAll, ""),
resource: "nodes",
namespace: metav1.NamespaceAll,
fieldSelector: parseSelectorOrDie(""),
},
// pod with "assigned" field selector.
{
location: buildLocation(
testapi.Default.ResourcePath("pods", metav1.NamespaceAll, ""),
buildQueryValues(url.Values{fieldSelectorQueryParamName: []string{"spec.host="}})),
resource: "pods",
namespace: metav1.NamespaceAll,
fieldSelector: fields.Set{"spec.host": ""}.AsSelector(),
},
// pod in namespace "foo"
{
location: buildLocation(
testapi.Default.ResourcePath("pods", "foo", ""),
buildQueryValues(url.Values{fieldSelectorQueryParamName: []string{"spec.host="}})),
resource: "pods",
namespace: "foo",
fieldSelector: fields.Set{"spec.host": ""}.AsSelector(),
},
}
for _, item := range table {
handler := utiltesting.FakeHandler{
StatusCode: 500,
ResponseBody: "",
T: t,
}
server := httptest.NewServer(&handler)
defer server.Close()
client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}})
lw := NewListWatchFromClient(client.Core().RESTClient(), item.resource, item.namespace, item.fieldSelector)
lw.DisableChunking = true
// This test merely tests that the correct request is made.
lw.List(metav1.ListOptions{})
handler.ValidateRequest(t, item.location, "GET", nil)
}
}
func TestListWatchesCanWatch(t *testing.T) {
fieldSelectorQueryParamName := metav1.FieldSelectorQueryParam(legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String())
table := []struct {
rv string
location string
resource string
namespace string
fieldSelector fields.Selector
}{
// Node
{
location: buildLocation(
testapi.Default.ResourcePath("nodes", metav1.NamespaceAll, ""),
buildQueryValues(url.Values{"watch": []string{"true"}})),
rv: "",
resource: "nodes",
namespace: metav1.NamespaceAll,
fieldSelector: parseSelectorOrDie(""),
},
{
location: buildLocation(
testapi.Default.ResourcePath("nodes", metav1.NamespaceAll, ""),
buildQueryValues(url.Values{"resourceVersion": []string{"42"}, "watch": []string{"true"}})),
rv: "42",
resource: "nodes",
namespace: metav1.NamespaceAll,
fieldSelector: parseSelectorOrDie(""),
},
// pod with "assigned" field selector.
{
location: buildLocation(
testapi.Default.ResourcePath("pods", metav1.NamespaceAll, ""),
buildQueryValues(url.Values{fieldSelectorQueryParamName: []string{"spec.host="}, "resourceVersion": []string{"0"}, "watch": []string{"true"}})),
rv: "0",
resource: "pods",
namespace: metav1.NamespaceAll,
fieldSelector: fields.Set{"spec.host": ""}.AsSelector(),
},
// pod with namespace foo and assigned field selector
{
location: buildLocation(
testapi.Default.ResourcePath("pods", "foo", ""),
buildQueryValues(url.Values{fieldSelectorQueryParamName: []string{"spec.host="}, "resourceVersion": []string{"0"}, "watch": []string{"true"}})),
rv: "0",
resource: "pods",
namespace: "foo",
fieldSelector: fields.Set{"spec.host": ""}.AsSelector(),
},
}
for _, item := range table {
handler := utiltesting.FakeHandler{
StatusCode: 500,
ResponseBody: "",
T: t,
}
server := httptest.NewServer(&handler)
defer server.Close()
client := clientset.NewForConfigOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion}})
lw := NewListWatchFromClient(client.Core().RESTClient(), item.resource, item.namespace, item.fieldSelector)
// This test merely tests that the correct request is made.
lw.Watch(metav1.ListOptions{ResourceVersion: item.rv})
handler.ValidateRequest(t, item.location, "GET", nil)
}
}
type lw struct {
list runtime.Object
watch watch.Interface
}
func (w lw) List(options metav1.ListOptions) (runtime.Object, error) {
return w.list, nil
}
func (w lw) Watch(options metav1.ListOptions) (watch.Interface, error) {
return w.watch, nil
}
func TestListWatchUntil(t *testing.T) {
fw := watch.NewFake()
go func() {
var obj *v1.Pod
fw.Modify(obj)
}()
listwatch := lw{
list: &v1.PodList{Items: []v1.Pod{{}}},
watch: fw,
}
conditions := []watch.ConditionFunc{
func(event watch.Event) (bool, error) {
t.Logf("got %#v", event)
return event.Type == watch.Added, nil
},
func(event watch.Event) (bool, error) {
t.Logf("got %#v", event)
return event.Type == watch.Modified, nil
},
}
timeout := 10 * time.Second
lastEvent, err := ListWatchUntil(timeout, listwatch, conditions...)
if err != nil {
t.Fatalf("expected nil error, got %#v", err)
}
if lastEvent == nil {
t.Fatal("expected an event")
}
if lastEvent.Type != watch.Modified {
t.Fatalf("expected MODIFIED event type, got %v", lastEvent.Type)
}
if got, isPod := lastEvent.Object.(*v1.Pod); !isPod {
t.Fatalf("expected a pod event, got %#v", got)
}
}

View File

@ -0,0 +1,232 @@
/*
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 tests
import (
"bytes"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"sync"
"testing"
"time"
"k8s.io/apimachinery/pkg/types"
restclient "k8s.io/client-go/rest"
. "k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
"k8s.io/kubernetes/pkg/kubelet/server/portforward"
)
// fakePortForwarder simulates port forwarding for testing. It implements
// portforward.PortForwarder.
type fakePortForwarder struct {
lock sync.Mutex
// stores data expected from the stream per port
expected map[int32]string
// stores data received from the stream per port
received map[int32]string
// data to be sent to the stream per port
send map[int32]string
}
var _ portforward.PortForwarder = &fakePortForwarder{}
func (pf *fakePortForwarder) PortForward(name string, uid types.UID, port int32, stream io.ReadWriteCloser) error {
defer stream.Close()
// read from the client
received := make([]byte, len(pf.expected[port]))
n, err := stream.Read(received)
if err != nil {
return fmt.Errorf("error reading from client for port %d: %v", port, err)
}
if n != len(pf.expected[port]) {
return fmt.Errorf("unexpected length read from client for port %d: got %d, expected %d. data=%q", port, n, len(pf.expected[port]), string(received))
}
// store the received content
pf.lock.Lock()
pf.received[port] = string(received)
pf.lock.Unlock()
// send the hardcoded data to the client
io.Copy(stream, strings.NewReader(pf.send[port]))
return nil
}
// fakePortForwardServer creates an HTTP server that can handle port forwarding
// requests.
func fakePortForwardServer(t *testing.T, testName string, serverSends, expectedFromClient map[int32]string) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
pf := &fakePortForwarder{
expected: expectedFromClient,
received: make(map[int32]string),
send: serverSends,
}
portforward.ServePortForward(w, req, pf, "pod", "uid", nil, 0, 10*time.Second, portforward.SupportedProtocols)
for port, expected := range expectedFromClient {
actual, ok := pf.received[port]
if !ok {
t.Errorf("%s: server didn't receive any data for port %d", testName, port)
continue
}
if expected != actual {
t.Errorf("%s: server expected to receive %q, got %q for port %d", testName, expected, actual, port)
}
}
for port, actual := range pf.received {
if _, ok := expectedFromClient[port]; !ok {
t.Errorf("%s: server unexpectedly received %q for port %d", testName, actual, port)
}
}
})
}
func TestForwardPorts(t *testing.T) {
tests := map[string]struct {
ports []string
clientSends map[int32]string
serverSends map[int32]string
}{
"forward 1 port with no data either direction": {
ports: []string{"5000"},
},
"forward 2 ports with bidirectional data": {
ports: []string{"5001", "6000"},
clientSends: map[int32]string{
5001: "abcd",
6000: "ghij",
},
serverSends: map[int32]string{
5001: "1234",
6000: "5678",
},
},
}
for testName, test := range tests {
server := httptest.NewServer(fakePortForwardServer(t, testName, test.serverSends, test.clientSends))
transport, upgrader, err := spdy.RoundTripperFor(&restclient.Config{})
if err != nil {
t.Fatal(err)
}
url, _ := url.Parse(server.URL)
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", url)
stopChan := make(chan struct{}, 1)
readyChan := make(chan struct{})
pf, err := New(dialer, test.ports, stopChan, readyChan, os.Stdout, os.Stderr)
if err != nil {
t.Fatalf("%s: unexpected error calling New: %v", testName, err)
}
doneChan := make(chan error)
go func() {
doneChan <- pf.ForwardPorts()
}()
<-pf.Ready
for port, data := range test.clientSends {
clientConn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
if err != nil {
t.Errorf("%s: error dialing %d: %s", testName, port, err)
server.Close()
continue
}
defer clientConn.Close()
n, err := clientConn.Write([]byte(data))
if err != nil && err != io.EOF {
t.Errorf("%s: Error sending data '%s': %s", testName, data, err)
server.Close()
continue
}
if n == 0 {
t.Errorf("%s: unexpected write of 0 bytes", testName)
server.Close()
continue
}
b := make([]byte, 4)
n, err = clientConn.Read(b)
if err != nil && err != io.EOF {
t.Errorf("%s: Error reading data: %s", testName, err)
server.Close()
continue
}
if !bytes.Equal([]byte(test.serverSends[port]), b) {
t.Errorf("%s: expected to read '%s', got '%s'", testName, test.serverSends[port], b)
server.Close()
continue
}
}
// tell r.ForwardPorts to stop
close(stopChan)
// wait for r.ForwardPorts to actually return
err = <-doneChan
if err != nil {
t.Errorf("%s: unexpected error: %s", testName, err)
}
server.Close()
}
}
func TestForwardPortsReturnsErrorWhenAllBindsFailed(t *testing.T) {
server := httptest.NewServer(fakePortForwardServer(t, "allBindsFailed", nil, nil))
defer server.Close()
transport, upgrader, err := spdy.RoundTripperFor(&restclient.Config{})
if err != nil {
t.Fatal(err)
}
url, _ := url.Parse(server.URL)
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", url)
stopChan1 := make(chan struct{}, 1)
defer close(stopChan1)
readyChan1 := make(chan struct{})
pf1, err := New(dialer, []string{"5555"}, stopChan1, readyChan1, os.Stdout, os.Stderr)
if err != nil {
t.Fatalf("error creating pf1: %v", err)
}
go pf1.ForwardPorts()
<-pf1.Ready
stopChan2 := make(chan struct{}, 1)
readyChan2 := make(chan struct{})
pf2, err := New(dialer, []string{"5555"}, stopChan2, readyChan2, os.Stdout, os.Stderr)
if err != nil {
t.Fatalf("error creating pf2: %v", err)
}
if err := pf2.ForwardPorts(); err == nil {
t.Fatal("expected non-nil error for pf2.ForwardPorts")
}
}

View File

@ -0,0 +1,366 @@
/*
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 tests
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/httpstream"
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
restclient "k8s.io/client-go/rest"
remoteclient "k8s.io/client-go/tools/remotecommand"
"k8s.io/client-go/transport/spdy"
"k8s.io/kubernetes/pkg/api/testapi"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
)
type fakeExecutor struct {
t *testing.T
testName string
errorData string
stdoutData string
stderrData string
expectStdin bool
stdinReceived bytes.Buffer
tty bool
messageCount int
command []string
exec bool
}
func (ex *fakeExecutor) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remoteclient.TerminalSize, timeout time.Duration) error {
return ex.run(name, uid, container, cmd, in, out, err, tty)
}
func (ex *fakeExecutor) AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remoteclient.TerminalSize) error {
return ex.run(name, uid, container, nil, in, out, err, tty)
}
func (ex *fakeExecutor) run(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error {
ex.command = cmd
ex.tty = tty
if e, a := "pod", name; e != a {
ex.t.Errorf("%s: pod: expected %q, got %q", ex.testName, e, a)
}
if e, a := "uid", uid; e != string(a) {
ex.t.Errorf("%s: uid: expected %q, got %q", ex.testName, e, a)
}
if ex.exec {
if e, a := "ls /", strings.Join(ex.command, " "); e != a {
ex.t.Errorf("%s: command: expected %q, got %q", ex.testName, e, a)
}
} else {
if len(ex.command) > 0 {
ex.t.Errorf("%s: command: expected nothing, got %v", ex.testName, ex.command)
}
}
if len(ex.errorData) > 0 {
return errors.New(ex.errorData)
}
if len(ex.stdoutData) > 0 {
for i := 0; i < ex.messageCount; i++ {
fmt.Fprint(out, ex.stdoutData)
}
}
if len(ex.stderrData) > 0 {
for i := 0; i < ex.messageCount; i++ {
fmt.Fprint(err, ex.stderrData)
}
}
if ex.expectStdin {
io.Copy(&ex.stdinReceived, in)
}
return nil
}
func fakeServer(t *testing.T, testName string, exec bool, stdinData, stdoutData, stderrData, errorData string, tty bool, messageCount int, serverProtocols []string) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
executor := &fakeExecutor{
t: t,
testName: testName,
errorData: errorData,
stdoutData: stdoutData,
stderrData: stderrData,
expectStdin: len(stdinData) > 0,
tty: tty,
messageCount: messageCount,
exec: exec,
}
opts, err := remotecommand.NewOptions(req)
require.NoError(t, err)
if exec {
cmd := req.URL.Query()[api.ExecCommandParam]
remotecommand.ServeExec(w, req, executor, "pod", "uid", "container", cmd, opts, 0, 10*time.Second, serverProtocols)
} else {
remotecommand.ServeAttach(w, req, executor, "pod", "uid", "container", opts, 0, 10*time.Second, serverProtocols)
}
if e, a := strings.Repeat(stdinData, messageCount), executor.stdinReceived.String(); e != a {
t.Errorf("%s: stdin: expected %q, got %q", testName, e, a)
}
})
}
func TestStream(t *testing.T) {
testCases := []struct {
TestName string
Stdin string
Stdout string
Stderr string
Error string
Tty bool
MessageCount int
ClientProtocols []string
ServerProtocols []string
}{
{
TestName: "error",
Error: "bail",
Stdout: "a",
ClientProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
ServerProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
},
{
TestName: "in/out/err",
Stdin: "a",
Stdout: "b",
Stderr: "c",
MessageCount: 100,
ClientProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
ServerProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
},
{
TestName: "in/out/tty",
Stdin: "a",
Stdout: "b",
Tty: true,
MessageCount: 100,
ClientProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
ServerProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
},
{
// 1.0 kubectl, 1.0 kubelet
TestName: "unversioned client, unversioned server",
Stdout: "b",
Stderr: "c",
MessageCount: 1,
ClientProtocols: []string{},
ServerProtocols: []string{},
},
{
// 1.0 kubectl, 1.1+ kubelet
TestName: "unversioned client, versioned server",
Stdout: "b",
Stderr: "c",
MessageCount: 1,
ClientProtocols: []string{},
ServerProtocols: []string{remotecommandconsts.StreamProtocolV2Name, remotecommandconsts.StreamProtocolV1Name},
},
{
// 1.1+ kubectl, 1.0 kubelet
TestName: "versioned client, unversioned server",
Stdout: "b",
Stderr: "c",
MessageCount: 1,
ClientProtocols: []string{remotecommandconsts.StreamProtocolV2Name, remotecommandconsts.StreamProtocolV1Name},
ServerProtocols: []string{},
},
}
for _, testCase := range testCases {
for _, exec := range []bool{true, false} {
var name string
if exec {
name = testCase.TestName + " (exec)"
} else {
name = testCase.TestName + " (attach)"
}
var (
streamIn io.Reader
streamOut, streamErr io.Writer
)
localOut := &bytes.Buffer{}
localErr := &bytes.Buffer{}
server := httptest.NewServer(fakeServer(t, name, exec, testCase.Stdin, testCase.Stdout, testCase.Stderr, testCase.Error, testCase.Tty, testCase.MessageCount, testCase.ServerProtocols))
url, _ := url.ParseRequestURI(server.URL)
config := restclient.ContentConfig{
GroupVersion: &schema.GroupVersion{Group: "x"},
NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
}
c, err := restclient.NewRESTClient(url, "", config, -1, -1, nil, nil)
if err != nil {
t.Fatalf("failed to create a client: %v", err)
}
req := c.Post().Resource("testing")
if exec {
req.Param("command", "ls")
req.Param("command", "/")
}
if len(testCase.Stdin) > 0 {
req.Param(api.ExecStdinParam, "1")
streamIn = strings.NewReader(strings.Repeat(testCase.Stdin, testCase.MessageCount))
}
if len(testCase.Stdout) > 0 {
req.Param(api.ExecStdoutParam, "1")
streamOut = localOut
}
if testCase.Tty {
req.Param(api.ExecTTYParam, "1")
} else if len(testCase.Stderr) > 0 {
req.Param(api.ExecStderrParam, "1")
streamErr = localErr
}
conf := &restclient.Config{
Host: server.URL,
}
transport, upgradeTransport, err := spdy.RoundTripperFor(conf)
if err != nil {
t.Errorf("%s: unexpected error: %v", name, err)
continue
}
e, err := remoteclient.NewSPDYExecutorForProtocols(transport, upgradeTransport, "POST", req.URL(), testCase.ClientProtocols...)
if err != nil {
t.Errorf("%s: unexpected error: %v", name, err)
continue
}
err = e.Stream(remoteclient.StreamOptions{
Stdin: streamIn,
Stdout: streamOut,
Stderr: streamErr,
Tty: testCase.Tty,
})
hasErr := err != nil
if len(testCase.Error) > 0 {
if !hasErr {
t.Errorf("%s: expected an error", name)
} else {
if e, a := testCase.Error, err.Error(); !strings.Contains(a, e) {
t.Errorf("%s: expected error stream read %q, got %q", name, e, a)
}
}
server.Close()
continue
}
if hasErr {
t.Errorf("%s: unexpected error: %v", name, err)
server.Close()
continue
}
if len(testCase.Stdout) > 0 {
if e, a := strings.Repeat(testCase.Stdout, testCase.MessageCount), localOut; e != a.String() {
t.Errorf("%s: expected stdout data %q, got %q", name, e, a)
}
}
if testCase.Stderr != "" {
if e, a := strings.Repeat(testCase.Stderr, testCase.MessageCount), localErr; e != a.String() {
t.Errorf("%s: expected stderr data %q, got %q", name, e, a)
}
}
server.Close()
}
}
}
type fakeUpgrader struct {
req *http.Request
resp *http.Response
conn httpstream.Connection
err, connErr error
checkResponse bool
called bool
t *testing.T
}
func (u *fakeUpgrader) RoundTrip(req *http.Request) (*http.Response, error) {
u.called = true
u.req = req
return u.resp, u.err
}
func (u *fakeUpgrader) NewConnection(resp *http.Response) (httpstream.Connection, error) {
if u.checkResponse && u.resp != resp {
u.t.Errorf("response objects passed did not match: %#v", resp)
}
return u.conn, u.connErr
}
type fakeConnection struct {
httpstream.Connection
}
// Dial is the common functionality between any stream based upgrader, regardless of protocol.
// This method ensures that someone can use a generic stream executor without being dependent
// on the core Kube client config behavior.
func TestDial(t *testing.T) {
upgrader := &fakeUpgrader{
t: t,
checkResponse: true,
conn: &fakeConnection{},
resp: &http.Response{
StatusCode: http.StatusSwitchingProtocols,
Body: ioutil.NopCloser(&bytes.Buffer{}),
},
}
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: upgrader}, "POST", &url.URL{Host: "something.com", Scheme: "https"})
conn, protocol, err := dialer.Dial("protocol1")
if err != nil {
t.Fatal(err)
}
if conn != upgrader.conn {
t.Errorf("unexpected connection: %#v", conn)
}
if !upgrader.called {
t.Errorf("request not called")
}
_ = protocol
}