k8s.io/client-go@v0.22.2/tools/remotecommand/remotecommand_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package remotecommand
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"io"
    23  	"io/ioutil"
    24  	v1 "k8s.io/api/core/v1"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/util/httpstream"
    28  	"k8s.io/apimachinery/pkg/util/httpstream/spdy"
    29  	remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
    30  	"k8s.io/apimachinery/pkg/util/wait"
    31  	"k8s.io/client-go/rest"
    32  	"net/http"
    33  	"net/http/httptest"
    34  	"net/url"
    35  	"strings"
    36  	"testing"
    37  	"time"
    38  )
    39  
    40  type AttachFunc func(in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan TerminalSize) error
    41  type streamContext struct {
    42  	conn         io.Closer
    43  	stdinStream  io.ReadCloser
    44  	stdoutStream io.WriteCloser
    45  	stderrStream io.WriteCloser
    46  	writeStatus  func(status *apierrors.StatusError) error
    47  }
    48  
    49  type streamAndReply struct {
    50  	httpstream.Stream
    51  	replySent <-chan struct{}
    52  }
    53  
    54  type fakeMassiveDataPty struct{}
    55  
    56  func (s *fakeMassiveDataPty) Read(p []byte) (int, error) {
    57  	time.Sleep(time.Duration(1) * time.Second)
    58  	return copy(p, []byte{}), errors.New("client crashed after 1 second")
    59  }
    60  
    61  func (s *fakeMassiveDataPty) Write(p []byte) (int, error) {
    62  	time.Sleep(time.Duration(1) * time.Second)
    63  	return len(p), errors.New("return err")
    64  }
    65  
    66  func fakeMassiveDataAttacher(stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan TerminalSize) error {
    67  
    68  	copyDone := make(chan struct{}, 3)
    69  
    70  	if stdin == nil {
    71  		return errors.New("stdin is requested") // we need stdin to notice the conn break
    72  	}
    73  
    74  	go func() {
    75  		io.Copy(ioutil.Discard, stdin)
    76  		copyDone <- struct{}{}
    77  	}()
    78  
    79  	go func() {
    80  		if stdout == nil {
    81  			return
    82  		}
    83  		copyDone <- writeMassiveData(stdout)
    84  	}()
    85  
    86  	go func() {
    87  		if stderr == nil {
    88  			return
    89  		}
    90  		copyDone <- writeMassiveData(stderr)
    91  	}()
    92  
    93  	select {
    94  	case <-copyDone:
    95  		return nil
    96  	}
    97  }
    98  
    99  func writeMassiveData(stdStream io.Writer) struct{} { // write to stdin or stdout
   100  	for {
   101  		_, err := io.Copy(stdStream, strings.NewReader("something"))
   102  		if err != nil && err.Error() != "EOF" {
   103  			break
   104  		}
   105  	}
   106  	return struct{}{}
   107  }
   108  
   109  func TestSPDYExecutorStream(t *testing.T) {
   110  	tests := []struct {
   111  		name        string
   112  		options     StreamOptions
   113  		expectError string
   114  		attacher    AttachFunc
   115  	}{
   116  		{
   117  			name: "stdoutBlockTest",
   118  			options: StreamOptions{
   119  				Stdin:  &fakeMassiveDataPty{},
   120  				Stdout: &fakeMassiveDataPty{},
   121  			},
   122  			expectError: "",
   123  			attacher:    fakeMassiveDataAttacher,
   124  		},
   125  		{
   126  			name: "stderrBlockTest",
   127  			options: StreamOptions{
   128  				Stdin:  &fakeMassiveDataPty{},
   129  				Stderr: &fakeMassiveDataPty{},
   130  			},
   131  			expectError: "",
   132  			attacher:    fakeMassiveDataAttacher,
   133  		},
   134  	}
   135  
   136  	for _, test := range tests {
   137  		server := newTestHTTPServer(test.attacher, &test.options)
   138  
   139  		err := attach2Server(server.URL, test.options)
   140  		gotError := ""
   141  		if err != nil {
   142  			gotError = err.Error()
   143  		}
   144  		if test.expectError != gotError {
   145  			t.Errorf("%s: expected [%v], got [%v]", test.name, test.expectError, gotError)
   146  		}
   147  
   148  		server.Close()
   149  	}
   150  
   151  }
   152  
   153  func newTestHTTPServer(f AttachFunc, options *StreamOptions) *httptest.Server {
   154  	server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
   155  		ctx, err := createHTTPStreams(writer, request, options)
   156  		if err != nil {
   157  			return
   158  		}
   159  		defer ctx.conn.Close()
   160  
   161  		// handle input output
   162  		err = f(ctx.stdinStream, ctx.stdoutStream, ctx.stderrStream, false, nil)
   163  		if err != nil {
   164  			ctx.writeStatus(apierrors.NewInternalError(err))
   165  		} else {
   166  			ctx.writeStatus(&apierrors.StatusError{ErrStatus: metav1.Status{
   167  				Status: metav1.StatusSuccess,
   168  			}})
   169  		}
   170  	}))
   171  	return server
   172  }
   173  
   174  func attach2Server(rawURL string, options StreamOptions) error {
   175  	uri, _ := url.Parse(rawURL)
   176  	exec, err := NewSPDYExecutor(&rest.Config{Host: uri.Host}, "POST", uri)
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	e := make(chan error)
   182  	go func(e chan error) {
   183  		e <- exec.Stream(options)
   184  	}(e)
   185  	select {
   186  	case err := <-e:
   187  		return err
   188  	case <-time.After(wait.ForeverTestTimeout):
   189  		return errors.New("execute timeout")
   190  	}
   191  }
   192  
   193  // simplify createHttpStreams , only support StreamProtocolV4Name
   194  func createHTTPStreams(w http.ResponseWriter, req *http.Request, opts *StreamOptions) (*streamContext, error) {
   195  	_, err := httpstream.Handshake(req, w, []string{remotecommandconsts.StreamProtocolV4Name})
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  
   200  	upgrader := spdy.NewResponseUpgrader()
   201  	streamCh := make(chan streamAndReply)
   202  	conn := upgrader.UpgradeResponse(w, req, func(stream httpstream.Stream, replySent <-chan struct{}) error {
   203  		streamCh <- streamAndReply{Stream: stream, replySent: replySent}
   204  		return nil
   205  	})
   206  	ctx := &streamContext{
   207  		conn: conn,
   208  	}
   209  
   210  	// wait for stream
   211  	replyChan := make(chan struct{}, 4)
   212  	defer close(replyChan)
   213  	receivedStreams := 0
   214  	expectedStreams := 1
   215  	if opts.Stdout != nil {
   216  		expectedStreams++
   217  	}
   218  	if opts.Stdin != nil {
   219  		expectedStreams++
   220  	}
   221  	if opts.Stderr != nil {
   222  		expectedStreams++
   223  	}
   224  WaitForStreams:
   225  	for {
   226  		select {
   227  		case stream := <-streamCh:
   228  			streamType := stream.Headers().Get(v1.StreamType)
   229  			switch streamType {
   230  			case v1.StreamTypeError:
   231  				replyChan <- struct{}{}
   232  				ctx.writeStatus = v4WriteStatusFunc(stream)
   233  			case v1.StreamTypeStdout:
   234  				replyChan <- struct{}{}
   235  				ctx.stdoutStream = stream
   236  			case v1.StreamTypeStdin:
   237  				replyChan <- struct{}{}
   238  				ctx.stdinStream = stream
   239  			case v1.StreamTypeStderr:
   240  				replyChan <- struct{}{}
   241  				ctx.stderrStream = stream
   242  			default:
   243  				// add other stream ...
   244  				return nil, errors.New("unimplemented stream type")
   245  			}
   246  		case <-replyChan:
   247  			receivedStreams++
   248  			if receivedStreams == expectedStreams {
   249  				break WaitForStreams
   250  			}
   251  		}
   252  	}
   253  
   254  	return ctx, nil
   255  }
   256  
   257  func v4WriteStatusFunc(stream io.Writer) func(status *apierrors.StatusError) error {
   258  	return func(status *apierrors.StatusError) error {
   259  		bs, err := json.Marshal(status.Status())
   260  		if err != nil {
   261  			return err
   262  		}
   263  		_, err = stream.Write(bs)
   264  		return err
   265  	}
   266  }