github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/worker/transport/hijack_streamer_test.go (about)

     1  package transport_test
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"context"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"net"
    13  	"net/http"
    14  	"net/url"
    15  	"strings"
    16  	"time"
    17  
    18  	"code.cloudfoundry.org/garden"
    19  	"github.com/pf-qiu/concourse/v6/atc/db/dbfakes"
    20  	gconn "github.com/pf-qiu/concourse/v6/atc/worker/gclient/connection"
    21  	"github.com/pf-qiu/concourse/v6/atc/worker/transport"
    22  	"github.com/pf-qiu/concourse/v6/atc/worker/transport/transportfakes"
    23  	. "github.com/onsi/ginkgo"
    24  	. "github.com/onsi/gomega"
    25  	"github.com/tedsuo/rata"
    26  
    27  	"github.com/pf-qiu/concourse/v6/atc/db"
    28  	"github.com/concourse/retryhttp/retryhttpfakes"
    29  )
    30  
    31  var _ = Describe("hijackStreamer", func() {
    32  	var (
    33  		savedWorker          *dbfakes.FakeWorker
    34  		savedWorkerAddress   string
    35  		fakeDB               *transportfakes.FakeTransportDB
    36  		fakeRoundTripper     *transportfakes.FakeRoundTripper
    37  		fakeHijackableClient *retryhttpfakes.FakeHijackableClient
    38  		hijackStreamer       gconn.HijackStreamer
    39  		fakeRequestGenerator *transportfakes.FakeRequestGenerator
    40  		handler              string
    41  		params               rata.Params
    42  		query                url.Values
    43  		contentType          string
    44  	)
    45  	BeforeEach(func() {
    46  		savedWorkerAddress = "some-garden-addr"
    47  
    48  		savedWorker = new(dbfakes.FakeWorker)
    49  
    50  		savedWorker.GardenAddrReturns(&savedWorkerAddress)
    51  		savedWorker.ExpiresAtReturns(time.Now().Add(123 * time.Minute))
    52  		savedWorker.StateReturns(db.WorkerStateRunning)
    53  
    54  		fakeDB = new(transportfakes.FakeTransportDB)
    55  		fakeDB.GetWorkerReturns(savedWorker, true, nil)
    56  
    57  		fakeRequestGenerator = new(transportfakes.FakeRequestGenerator)
    58  
    59  		fakeRoundTripper = new(transportfakes.FakeRoundTripper)
    60  		fakeHijackableClient = new(retryhttpfakes.FakeHijackableClient)
    61  
    62  		hijackStreamer = &transport.WorkerHijackStreamer{
    63  			HttpClient:       &http.Client{Transport: fakeRoundTripper},
    64  			HijackableClient: fakeHijackableClient,
    65  			Req:              fakeRequestGenerator,
    66  		}
    67  
    68  		handler = "Ping"
    69  		params = map[string]string{"param1": "value1"}
    70  		contentType = "application/json"
    71  		query = map[string][]string{"key": []string{"some", "values"}}
    72  
    73  		request, err := http.NewRequest("POST", "http://example.url", strings.NewReader("some-request-body"))
    74  		Expect(err).NotTo(HaveOccurred())
    75  		fakeRequestGenerator.CreateRequestReturns(request, nil)
    76  	})
    77  
    78  	Describe("hijackStreamer #Stream", func() {
    79  		var (
    80  			body             io.Reader
    81  			actualReadCloser io.ReadCloser
    82  			streamErr        error
    83  			httpResp         http.Response
    84  			expectedString   string
    85  		)
    86  
    87  		BeforeEach(func() {
    88  			expectedString = "some-example-string"
    89  			body = strings.NewReader(expectedString)
    90  
    91  			fakeRoundTripper.RoundTripReturns(&httpResp, nil)
    92  		})
    93  
    94  		JustBeforeEach(func() {
    95  			actualReadCloser, streamErr = hijackStreamer.Stream(handler, body, params, query, contentType)
    96  		})
    97  
    98  		Context("when httpResponse is success", func() {
    99  			BeforeEach(func() {
   100  				httpResp = http.Response{StatusCode: http.StatusOK, Body: ioutil.NopCloser(body)}
   101  			})
   102  
   103  			It("returns response body", func() {
   104  				actualBodyBytes, err := ioutil.ReadAll(actualReadCloser)
   105  				Expect(err).ToNot(HaveOccurred())
   106  				Expect(expectedString).To(Equal(string(actualBodyBytes)))
   107  			})
   108  		})
   109  
   110  		Context("when httpResponse is not success", func() {
   111  			var fakeBody *transportfakes.FakeReadCloser
   112  
   113  			BeforeEach(func() {
   114  				fakeBody = new(transportfakes.FakeReadCloser)
   115  				httpResp = http.Response{StatusCode: http.StatusTeapot, Body: fakeBody}
   116  
   117  				bodyBuf, _ := json.Marshal(garden.Error{Err: errors.New("some-error")})
   118  				realBody := strings.NewReader(string(bodyBuf))
   119  				fakeBody.ReadStub = func(buf []byte) (int, error) {
   120  					Expect(fakeBody.CloseCallCount()).To(BeZero())
   121  					return realBody.Read(buf)
   122  				}
   123  			})
   124  
   125  			It("closes httpResp.Body and returns error", func() {
   126  				Expect(actualReadCloser).To(BeNil())
   127  				Expect(streamErr).To(HaveOccurred())
   128  				Expect(fakeBody.CloseCallCount()).To(Equal(1))
   129  				Expect(streamErr).To(MatchError("some-error"))
   130  			})
   131  		})
   132  
   133  		Context("when httpResponse is not success with bad response", func() {
   134  			var fakeBody *transportfakes.FakeReadCloser
   135  
   136  			BeforeEach(func() {
   137  				fakeBody = new(transportfakes.FakeReadCloser)
   138  				httpResp = http.Response{StatusCode: http.StatusTeapot, Body: fakeBody}
   139  
   140  				realBody := strings.NewReader("some-error")
   141  				fakeBody.ReadStub = func(buf []byte) (int, error) {
   142  					Expect(fakeBody.CloseCallCount()).To(BeZero())
   143  					return realBody.Read(buf)
   144  				}
   145  			})
   146  
   147  			It("closes httpResp.Body and returns bad response", func() {
   148  				Expect(actualReadCloser).To(BeNil())
   149  				Expect(streamErr).To(HaveOccurred())
   150  				Expect(fakeBody.CloseCallCount()).To(Equal(1))
   151  				Expect(streamErr).To(MatchError(fmt.Errorf("bad response: %s", errors.New("invalid character 's' looking for beginning of value"))))
   152  			})
   153  		})
   154  
   155  		Context("when httpResponse fails", func() {
   156  			BeforeEach(func() {
   157  				httpResp = http.Response{StatusCode: http.StatusTeapot, Body: ioutil.NopCloser(body)}
   158  			})
   159  
   160  			It("returns error", func() {
   161  				Expect(actualReadCloser).To(BeNil())
   162  				Expect(streamErr).To(HaveOccurred())
   163  			})
   164  
   165  			It("creates request with the right arguments", func() {
   166  				Expect(fakeRequestGenerator.CreateRequestCallCount()).To(Equal(1))
   167  				actualHandler, actualParams, actualBody := fakeRequestGenerator.CreateRequestArgsForCall(0)
   168  				Expect(actualHandler).To(Equal(handler))
   169  				Expect(actualParams).To(Equal(params))
   170  				Expect(actualBody).To(Equal(body))
   171  			})
   172  
   173  			It("httpClient makes the right request", func() {
   174  				expectedRequest, err := http.NewRequest("POST", "http://example.url", strings.NewReader("some-request-body"))
   175  				expectedRequest.Header.Add("Content-Type", "application/json")
   176  				expectedRequest.URL.RawQuery = "key=some&key=values"
   177  				Expect(err).NotTo(HaveOccurred())
   178  				Expect(fakeRoundTripper.RoundTripCallCount()).To(Equal(1))
   179  				actualRequest := fakeRoundTripper.RoundTripArgsForCall(0)
   180  				Expect(actualRequest.Method).To(Equal(expectedRequest.Method))
   181  				Expect(actualRequest.URL).To(Equal(expectedRequest.URL))
   182  				Expect(actualRequest.Header).To(Equal(expectedRequest.Header))
   183  
   184  				s, err := ioutil.ReadAll(actualRequest.Body)
   185  				Expect(err).NotTo(HaveOccurred())
   186  				Expect(string(s)).To(Equal("some-request-body"))
   187  			})
   188  
   189  		})
   190  	})
   191  
   192  	Describe("hijackStreamer #Hijack", func() {
   193  		var (
   194  			body                   io.Reader
   195  			hijackError            error
   196  			httpResp               http.Response
   197  			expectedString         string
   198  			actualHijackedConn     net.Conn
   199  			actualResponseReader   *bufio.Reader
   200  			fakeHijackCloser       *retryhttpfakes.FakeHijackCloser
   201  			expectedResponseReader *bufio.Reader
   202  			fakeHijackedConn       *retryhttpfakes.FakeConn
   203  		)
   204  
   205  		BeforeEach(func() {
   206  			expectedResponseReader = new(bufio.Reader)
   207  			fakeHijackedConn = new(retryhttpfakes.FakeConn)
   208  
   209  			expectedString = "some-example-string"
   210  			body = strings.NewReader(expectedString)
   211  			fakeHijackCloser = new(retryhttpfakes.FakeHijackCloser)
   212  		})
   213  
   214  		JustBeforeEach(func() {
   215  			actualHijackedConn, actualResponseReader, hijackError = hijackStreamer.Hijack(context.TODO(), handler, body, params, query, contentType)
   216  		})
   217  
   218  		Context("when request is successful", func() {
   219  			BeforeEach(func() {
   220  				fakeHijackableClient.DoReturns(&httpResp, fakeHijackCloser, nil)
   221  				httpResp = http.Response{StatusCode: http.StatusOK}
   222  				fakeHijackCloser.HijackReturns(fakeHijackedConn, expectedResponseReader)
   223  			})
   224  
   225  			It("returns success response and hijackCloser", func() {
   226  				Expect(hijackError).ToNot(HaveOccurred())
   227  				Expect(fakeHijackCloser.HijackCallCount()).To(Equal(1))
   228  				Expect(actualHijackedConn).To(Equal(fakeHijackedConn))
   229  				Expect(actualResponseReader).To(Equal(expectedResponseReader))
   230  			})
   231  		})
   232  
   233  		Context("when httpResponse is not success", func() {
   234  			var fakeBody *transportfakes.FakeReadCloser
   235  
   236  			BeforeEach(func() {
   237  				fakeHijackableClient.DoReturns(&httpResp, fakeHijackCloser, nil)
   238  				fakeBody = new(transportfakes.FakeReadCloser)
   239  				httpResp = http.Response{StatusCode: http.StatusTeapot, Body: fakeBody}
   240  
   241  				realBody := strings.NewReader("some-error")
   242  				fakeBody.ReadStub = func(buf []byte) (int, error) {
   243  					Expect(fakeBody.CloseCallCount()).To(BeZero())
   244  					return realBody.Read(buf)
   245  				}
   246  			})
   247  
   248  			It("closes httpResp.Body, hijackCloser and returns error", func() {
   249  				Expect(fakeHijackCloser).NotTo(BeNil())
   250  				Expect(hijackError).To(HaveOccurred())
   251  				Expect(fakeBody.CloseCallCount()).To(Equal(1))
   252  				Expect(hijackError).To(MatchError(fmt.Errorf("Backend error: Exit status: %d, message: %s", httpResp.StatusCode, "some-error")))
   253  				Expect(fakeHijackCloser.CloseCallCount()).To(Equal(1))
   254  			})
   255  		})
   256  
   257  		Context("when httpResponse fails with ExecutableNotFoundError", func() {
   258  			var fakeBody *transportfakes.FakeReadCloser
   259  			gerr := garden.ExecutableNotFoundError{Message: "sorry, couldn't find it"}
   260  
   261  			BeforeEach(func() {
   262  				fakeHijackableClient.DoReturns(&httpResp, fakeHijackCloser, nil)
   263  				fakeBody = new(transportfakes.FakeReadCloser)
   264  				httpResp = http.Response{StatusCode: http.StatusTeapot, Body: fakeBody}
   265  
   266  				jsonBody, _ := garden.Error{Err: gerr}.MarshalJSON()
   267  				realBody := bytes.NewReader(jsonBody)
   268  				fakeBody.ReadStub = func(buf []byte) (int, error) {
   269  					Expect(fakeBody.CloseCallCount()).To(BeZero())
   270  					return realBody.Read(buf)
   271  				}
   272  			})
   273  
   274  			It("closes httpResp.Body, hijackCloser and returns ExecutableNotFoundError", func() {
   275  				Expect(fakeHijackCloser).NotTo(BeNil())
   276  				Expect(hijackError).To(HaveOccurred())
   277  				Expect(fakeBody.CloseCallCount()).To(Equal(1))
   278  				Expect(hijackError).To(MatchError(gerr))
   279  				Expect(fakeHijackCloser.CloseCallCount()).To(Equal(1))
   280  			})
   281  		})
   282  
   283  		Context("when httpResponse is not success with bad response", func() {
   284  			var fakeBody *transportfakes.FakeReadCloser
   285  
   286  			BeforeEach(func() {
   287  				fakeHijackableClient.DoReturns(&httpResp, fakeHijackCloser, nil)
   288  				fakeBody = new(transportfakes.FakeReadCloser)
   289  				httpResp = http.Response{StatusCode: http.StatusTeapot, Body: fakeBody}
   290  
   291  				fakeBody.ReadStub = func(buf []byte) (int, error) {
   292  					Expect(fakeBody.CloseCallCount()).To(BeZero())
   293  					return 0, errors.New("error reading")
   294  				}
   295  			})
   296  
   297  			It("closes httpResp.Body and returns bad response", func() {
   298  				Expect(fakeHijackCloser).NotTo(BeNil())
   299  				Expect(hijackError).To(HaveOccurred())
   300  				Expect(fakeBody.CloseCallCount()).To(Equal(1))
   301  				Expect(hijackError).To(MatchError(fmt.Errorf("Backend error: Exit status: %d, error reading response body: %s", httpResp.StatusCode, "error reading")))
   302  				Expect(fakeHijackCloser.CloseCallCount()).To(Equal(1))
   303  			})
   304  		})
   305  
   306  		Context("when httpResponse fails", func() {
   307  			BeforeEach(func() {
   308  				fakeHijackableClient.DoReturns(nil, nil, errors.New("Request failed"))
   309  				httpResp = http.Response{StatusCode: http.StatusTeapot, Body: ioutil.NopCloser(body)}
   310  			})
   311  
   312  			It("returns error", func() {
   313  				Expect(hijackError).To(HaveOccurred())
   314  				Expect(actualHijackedConn).To(BeNil())
   315  				Expect(actualResponseReader).To(BeNil())
   316  			})
   317  
   318  			It("makes the right request", func() {
   319  				expectedRequest, err := http.NewRequest("POST", "http://example.url", strings.NewReader("some-request-body"))
   320  				expectedRequest.Header.Add("Content-Type", "application/json")
   321  				expectedRequest.URL.RawQuery = "key=some&key=values"
   322  				Expect(err).NotTo(HaveOccurred())
   323  				Expect(fakeHijackableClient.DoCallCount()).To(Equal(1))
   324  				actualRequest := fakeHijackableClient.DoArgsForCall(0)
   325  
   326  				Expect(actualRequest.Method).To(Equal(expectedRequest.Method))
   327  				Expect(actualRequest.URL).To(Equal(expectedRequest.URL))
   328  				Expect(actualRequest.Header).To(Equal(expectedRequest.Header))
   329  
   330  				s, err := ioutil.ReadAll(actualRequest.Body)
   331  				Expect(err).NotTo(HaveOccurred())
   332  				Expect(string(s)).To(Equal("some-request-body"))
   333  			})
   334  		})
   335  	})
   336  })