2015-01-08 20:41:38 +00:00
|
|
|
/*
|
2016-06-03 00:25:58 +00:00
|
|
|
Copyright 2015 The Kubernetes Authors.
|
2015-01-08 20:41:38 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2017-02-15 10:34:49 +00:00
|
|
|
package tests
|
2015-01-08 20:41:38 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2016-03-22 13:38:42 +00:00
|
|
|
"errors"
|
2015-07-07 15:31:23 +00:00
|
|
|
"fmt"
|
2015-08-31 17:23:47 +00:00
|
|
|
"io"
|
2015-09-27 00:00:39 +00:00
|
|
|
"io/ioutil"
|
2015-01-08 20:41:38 +00:00
|
|
|
"net/http"
|
2015-07-07 15:31:23 +00:00
|
|
|
"net/http/httptest"
|
|
|
|
"net/url"
|
2015-01-08 20:41:38 +00:00
|
|
|
"strings"
|
|
|
|
"testing"
|
2016-03-22 13:38:42 +00:00
|
|
|
"time"
|
2015-01-08 20:41:38 +00:00
|
|
|
|
2016-12-09 02:54:02 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
2017-01-11 14:09:48 +00:00
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
"k8s.io/apimachinery/pkg/types"
|
2017-01-24 14:43:13 +00:00
|
|
|
"k8s.io/apimachinery/pkg/util/httpstream"
|
2017-02-15 10:34:49 +00:00
|
|
|
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
2017-01-19 18:27:59 +00:00
|
|
|
restclient "k8s.io/client-go/rest"
|
2017-04-14 09:33:57 +00:00
|
|
|
remoteclient "k8s.io/client-go/tools/remotecommand"
|
2017-07-07 22:22:39 +00:00
|
|
|
"k8s.io/client-go/transport/spdy"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
2016-04-26 07:05:40 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api/testapi"
|
2016-03-22 13:38:42 +00:00
|
|
|
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
2015-01-08 20:41:38 +00:00
|
|
|
)
|
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
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
|
2016-02-01 21:59:39 +00:00
|
|
|
}
|
|
|
|
|
2017-02-15 10:34:49 +00:00
|
|
|
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 {
|
2016-03-22 13:38:42 +00:00
|
|
|
return ex.run(name, uid, container, cmd, in, out, err, tty)
|
2016-02-01 21:59:39 +00:00
|
|
|
}
|
|
|
|
|
2017-02-15 10:34:49 +00:00
|
|
|
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 {
|
2016-03-22 13:38:42 +00:00
|
|
|
return ex.run(name, uid, container, nil, in, out, err, tty)
|
|
|
|
}
|
2015-01-08 20:41:38 +00:00
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
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)
|
2015-10-22 00:42:40 +00:00
|
|
|
}
|
2016-03-22 13:38:42 +00:00
|
|
|
} else {
|
|
|
|
if len(ex.command) > 0 {
|
|
|
|
ex.t.Errorf("%s: command: expected nothing, got %v", ex.testName, ex.command)
|
2015-07-07 15:31:23 +00:00
|
|
|
}
|
2016-03-22 13:38:42 +00:00
|
|
|
}
|
2015-01-08 20:41:38 +00:00
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
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)
|
2015-07-07 15:31:23 +00:00
|
|
|
}
|
2016-03-22 13:38:42 +00:00
|
|
|
}
|
2015-01-08 20:41:38 +00:00
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
if len(ex.stderrData) > 0 {
|
|
|
|
for i := 0; i < ex.messageCount; i++ {
|
|
|
|
fmt.Fprint(err, ex.stderrData)
|
2015-07-07 15:31:23 +00:00
|
|
|
}
|
2016-03-22 13:38:42 +00:00
|
|
|
}
|
2015-01-08 20:41:38 +00:00
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
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,
|
2015-07-07 15:31:23 +00:00
|
|
|
}
|
2016-03-22 13:38:42 +00:00
|
|
|
|
2016-12-09 02:54:02 +00:00
|
|
|
opts, err := remotecommand.NewOptions(req)
|
|
|
|
require.NoError(t, err)
|
2016-03-22 13:38:42 +00:00
|
|
|
if exec {
|
2017-05-05 22:52:46 +00:00
|
|
|
cmd := req.URL.Query()[api.ExecCommandParam]
|
2016-12-09 02:54:02 +00:00
|
|
|
remotecommand.ServeExec(w, req, executor, "pod", "uid", "container", cmd, opts, 0, 10*time.Second, serverProtocols)
|
2016-03-22 13:38:42 +00:00
|
|
|
} else {
|
2016-12-09 02:54:02 +00:00
|
|
|
remotecommand.ServeAttach(w, req, executor, "pod", "uid", "container", opts, 0, 10*time.Second, serverProtocols)
|
2015-07-07 15:31:23 +00:00
|
|
|
}
|
2016-03-22 13:38:42 +00:00
|
|
|
|
|
|
|
if e, a := strings.Repeat(stdinData, messageCount), executor.stdinReceived.String(); e != a {
|
|
|
|
t.Errorf("%s: stdin: expected %q, got %q", testName, e, a)
|
2015-07-07 15:31:23 +00:00
|
|
|
}
|
|
|
|
})
|
2015-01-08 20:41:38 +00:00
|
|
|
}
|
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
func TestStream(t *testing.T) {
|
2015-01-08 20:41:38 +00:00
|
|
|
testCases := []struct {
|
2016-03-22 13:38:42 +00:00
|
|
|
TestName string
|
|
|
|
Stdin string
|
|
|
|
Stdout string
|
|
|
|
Stderr string
|
|
|
|
Error string
|
|
|
|
Tty bool
|
|
|
|
MessageCount int
|
|
|
|
ClientProtocols []string
|
|
|
|
ServerProtocols []string
|
2015-01-08 20:41:38 +00:00
|
|
|
}{
|
|
|
|
{
|
2016-03-22 13:38:42 +00:00
|
|
|
TestName: "error",
|
|
|
|
Error: "bail",
|
|
|
|
Stdout: "a",
|
2017-02-15 10:34:49 +00:00
|
|
|
ClientProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
|
|
|
|
ServerProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
|
2015-01-08 20:41:38 +00:00
|
|
|
},
|
|
|
|
{
|
2016-03-22 13:38:42 +00:00
|
|
|
TestName: "in/out/err",
|
|
|
|
Stdin: "a",
|
|
|
|
Stdout: "b",
|
|
|
|
Stderr: "c",
|
|
|
|
MessageCount: 100,
|
2017-02-15 10:34:49 +00:00
|
|
|
ClientProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
|
|
|
|
ServerProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
|
2015-01-08 20:41:38 +00:00
|
|
|
},
|
|
|
|
{
|
2016-03-22 13:38:42 +00:00
|
|
|
TestName: "in/out/tty",
|
|
|
|
Stdin: "a",
|
|
|
|
Stdout: "b",
|
|
|
|
Tty: true,
|
|
|
|
MessageCount: 100,
|
2017-02-15 10:34:49 +00:00
|
|
|
ClientProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
|
|
|
|
ServerProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
|
2015-01-08 20:41:38 +00:00
|
|
|
},
|
2015-07-29 23:19:09 +00:00
|
|
|
{
|
2016-03-22 13:38:42 +00:00
|
|
|
// 1.0 kubectl, 1.0 kubelet
|
|
|
|
TestName: "unversioned client, unversioned server",
|
|
|
|
Stdout: "b",
|
|
|
|
Stderr: "c",
|
|
|
|
MessageCount: 1,
|
|
|
|
ClientProtocols: []string{},
|
|
|
|
ServerProtocols: []string{},
|
2015-07-29 23:19:09 +00:00
|
|
|
},
|
|
|
|
{
|
2016-03-22 13:38:42 +00:00
|
|
|
// 1.0 kubectl, 1.1+ kubelet
|
|
|
|
TestName: "unversioned client, versioned server",
|
|
|
|
Stdout: "b",
|
|
|
|
Stderr: "c",
|
|
|
|
MessageCount: 1,
|
|
|
|
ClientProtocols: []string{},
|
2017-02-15 10:34:49 +00:00
|
|
|
ServerProtocols: []string{remotecommandconsts.StreamProtocolV2Name, remotecommandconsts.StreamProtocolV1Name},
|
2015-07-29 23:19:09 +00:00
|
|
|
},
|
|
|
|
{
|
2016-03-22 13:38:42 +00:00
|
|
|
// 1.1+ kubectl, 1.0 kubelet
|
|
|
|
TestName: "versioned client, unversioned server",
|
|
|
|
Stdout: "b",
|
|
|
|
Stderr: "c",
|
|
|
|
MessageCount: 1,
|
2017-02-15 10:34:49 +00:00
|
|
|
ClientProtocols: []string{remotecommandconsts.StreamProtocolV2Name, remotecommandconsts.StreamProtocolV1Name},
|
2016-03-22 13:38:42 +00:00
|
|
|
ServerProtocols: []string{},
|
2015-07-29 23:19:09 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
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)
|
2016-04-26 07:05:40 +00:00
|
|
|
config := restclient.ContentConfig{
|
2016-11-21 02:55:31 +00:00
|
|
|
GroupVersion: &schema.GroupVersion{Group: "x"},
|
2016-04-23 19:00:28 +00:00
|
|
|
NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
|
2016-04-26 07:05:40 +00:00
|
|
|
}
|
|
|
|
c, err := restclient.NewRESTClient(url, "", config, -1, -1, nil, nil)
|
2016-04-26 06:28:15 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to create a client: %v", err)
|
|
|
|
}
|
2016-03-22 13:38:42 +00:00
|
|
|
req := c.Post().Resource("testing")
|
|
|
|
|
|
|
|
if exec {
|
|
|
|
req.Param("command", "ls")
|
|
|
|
req.Param("command", "/")
|
|
|
|
}
|
2015-07-29 23:19:09 +00:00
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
if len(testCase.Stdin) > 0 {
|
|
|
|
req.Param(api.ExecStdinParam, "1")
|
|
|
|
streamIn = strings.NewReader(strings.Repeat(testCase.Stdin, testCase.MessageCount))
|
|
|
|
}
|
2015-07-29 23:19:09 +00:00
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
if len(testCase.Stdout) > 0 {
|
|
|
|
req.Param(api.ExecStdoutParam, "1")
|
|
|
|
streamOut = localOut
|
|
|
|
}
|
2015-07-29 23:19:09 +00:00
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
if testCase.Tty {
|
|
|
|
req.Param(api.ExecTTYParam, "1")
|
|
|
|
} else if len(testCase.Stderr) > 0 {
|
|
|
|
req.Param(api.ExecStderrParam, "1")
|
|
|
|
streamErr = localErr
|
|
|
|
}
|
2015-07-29 23:19:09 +00:00
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
conf := &restclient.Config{
|
|
|
|
Host: server.URL,
|
|
|
|
}
|
2017-07-07 22:22:39 +00:00
|
|
|
e, err := remoteclient.NewSPDYExecutorForProtocols(conf, "POST", req.URL(), testCase.ClientProtocols...)
|
2016-03-22 13:38:42 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("%s: unexpected error: %v", name, err)
|
|
|
|
continue
|
2015-07-29 23:19:09 +00:00
|
|
|
}
|
2017-02-15 10:34:49 +00:00
|
|
|
err = e.Stream(remoteclient.StreamOptions{
|
2017-07-07 22:22:39 +00:00
|
|
|
Stdin: streamIn,
|
|
|
|
Stdout: streamOut,
|
|
|
|
Stderr: streamErr,
|
|
|
|
Tty: testCase.Tty,
|
2016-04-18 16:54:44 +00:00
|
|
|
})
|
2016-03-22 13:38:42 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2015-07-29 23:19:09 +00:00
|
|
|
|
2016-04-21 11:50:55 +00:00
|
|
|
server.Close()
|
2016-03-22 13:38:42 +00:00
|
|
|
continue
|
|
|
|
}
|
2015-07-29 23:19:09 +00:00
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
if hasErr {
|
|
|
|
t.Errorf("%s: unexpected error: %v", name, err)
|
2016-04-21 11:50:55 +00:00
|
|
|
server.Close()
|
2016-03-22 13:38:42 +00:00
|
|
|
continue
|
|
|
|
}
|
2015-07-29 23:19:09 +00:00
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
if len(testCase.Stdout) > 0 {
|
|
|
|
if e, a := strings.Repeat(testCase.Stdout, testCase.MessageCount), localOut; e != a.String() {
|
2016-04-18 16:54:44 +00:00
|
|
|
t.Errorf("%s: expected stdout data %q, got %q", name, e, a)
|
2016-03-22 13:38:42 +00:00
|
|
|
}
|
2015-07-29 23:19:09 +00:00
|
|
|
}
|
|
|
|
|
2016-03-22 13:38:42 +00:00
|
|
|
if testCase.Stderr != "" {
|
|
|
|
if e, a := strings.Repeat(testCase.Stderr, testCase.MessageCount), localErr; e != a.String() {
|
2016-04-18 16:54:44 +00:00
|
|
|
t.Errorf("%s: expected stderr data %q, got %q", name, e, a)
|
2016-03-22 13:38:42 +00:00
|
|
|
}
|
2015-07-29 23:19:09 +00:00
|
|
|
}
|
|
|
|
|
2016-04-21 11:50:55 +00:00
|
|
|
server.Close()
|
2016-03-22 13:38:42 +00:00
|
|
|
}
|
2015-07-29 23:19:09 +00:00
|
|
|
}
|
|
|
|
}
|
2015-09-27 00:00:39 +00:00
|
|
|
|
|
|
|
type fakeUpgrader struct {
|
|
|
|
req *http.Request
|
|
|
|
resp *http.Response
|
|
|
|
conn httpstream.Connection
|
|
|
|
err, connErr error
|
|
|
|
checkResponse bool
|
2017-07-07 22:22:39 +00:00
|
|
|
called bool
|
2015-09-27 00:00:39 +00:00
|
|
|
|
|
|
|
t *testing.T
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *fakeUpgrader) RoundTrip(req *http.Request) (*http.Response, error) {
|
2017-07-07 22:22:39 +00:00
|
|
|
u.called = true
|
2015-09-27 00:00:39 +00:00
|
|
|
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{
|
2015-10-20 12:21:07 +00:00
|
|
|
StatusCode: http.StatusSwitchingProtocols,
|
2015-09-27 00:00:39 +00:00
|
|
|
Body: ioutil.NopCloser(&bytes.Buffer{}),
|
|
|
|
},
|
|
|
|
}
|
2017-07-07 22:22:39 +00:00
|
|
|
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: upgrader}, "POST", &url.URL{Host: "something.com", Scheme: "https"})
|
|
|
|
conn, protocol, err := dialer.Dial("protocol1")
|
2015-09-27 00:00:39 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if conn != upgrader.conn {
|
|
|
|
t.Errorf("unexpected connection: %#v", conn)
|
|
|
|
}
|
2017-07-07 22:22:39 +00:00
|
|
|
if !upgrader.called {
|
|
|
|
t.Errorf("request not called")
|
2015-09-27 00:00:39 +00:00
|
|
|
}
|
2015-10-20 12:21:07 +00:00
|
|
|
_ = protocol
|
2015-09-27 00:00:39 +00:00
|
|
|
}
|