github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/pfsagentd/swift_proxy_emulator_test.go (about)

     1  // Copyright (c) 2015-2021, NVIDIA CORPORATION.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package main
     5  
     6  import (
     7  	"context"
     8  	"io/ioutil"
     9  	"net"
    10  	"net/http"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/swiftstack/ProxyFS/conf"
    18  )
    19  
    20  const (
    21  	testAuthToken           = "AUTH_tkTestToken"
    22  	testJrpcResponseBufSize = 1024 * 1024
    23  )
    24  
    25  type testSwiftProxyEmulatorGlobalsStruct struct {
    26  	sync.WaitGroup
    27  	t                   *testing.T
    28  	ramswiftNoAuthURL   string
    29  	proxyfsdJrpcTCPAddr *net.TCPAddr
    30  	jrpcResponsePool    *sync.Pool
    31  	httpClient          *http.Client
    32  	httpServer          *http.Server
    33  }
    34  
    35  var testSwiftProxyEmulatorGlobals testSwiftProxyEmulatorGlobalsStruct
    36  
    37  func startSwiftProxyEmulator(t *testing.T, confMap conf.ConfMap) {
    38  	var (
    39  		err                      error
    40  		infoResponse             *http.Response
    41  		jrpcServerIPAddr         string
    42  		jrpcServerTCPPort        uint16
    43  		swiftClientNoAuthIPAddr  string
    44  		swiftClientNoAuthTCPPort uint16
    45  		whoAmI                   string
    46  	)
    47  
    48  	testSwiftProxyEmulatorGlobals.t = t
    49  
    50  	swiftClientNoAuthIPAddr, err = confMap.FetchOptionValueString("SwiftClient", "NoAuthIPAddr")
    51  	if nil != err {
    52  		t.Fatal(err)
    53  	}
    54  
    55  	swiftClientNoAuthTCPPort, err = confMap.FetchOptionValueUint16("SwiftClient", "NoAuthTCPPort")
    56  	if nil != err {
    57  		t.Fatal(err)
    58  	}
    59  
    60  	testSwiftProxyEmulatorGlobals.ramswiftNoAuthURL = "http://" + net.JoinHostPort(swiftClientNoAuthIPAddr, strconv.FormatUint(uint64(swiftClientNoAuthTCPPort), 10))
    61  
    62  	whoAmI, err = confMap.FetchOptionValueString("Cluster", "WhoAmI")
    63  	if nil != err {
    64  		t.Fatal(err)
    65  	}
    66  
    67  	jrpcServerIPAddr, err = confMap.FetchOptionValueString("Peer:"+whoAmI, "PrivateIPAddr")
    68  	if nil != err {
    69  		t.Fatal(err)
    70  	}
    71  
    72  	jrpcServerTCPPort, err = confMap.FetchOptionValueUint16("JSONRPCServer", "TCPPort")
    73  	if nil != err {
    74  		t.Fatal(err)
    75  	}
    76  
    77  	testSwiftProxyEmulatorGlobals.proxyfsdJrpcTCPAddr, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(jrpcServerIPAddr, strconv.FormatUint(uint64(jrpcServerTCPPort), 10)))
    78  	if nil != err {
    79  		t.Fatal(err)
    80  	}
    81  
    82  	testSwiftProxyEmulatorGlobals.httpClient = &http.Client{}
    83  
    84  	testSwiftProxyEmulatorGlobals.httpServer = &http.Server{
    85  		Addr:    testSwiftProxyAddr,
    86  		Handler: &testSwiftProxyEmulatorGlobals,
    87  	}
    88  
    89  	testSwiftProxyEmulatorGlobals.jrpcResponsePool = &sync.Pool{
    90  		New: func() (bufAsInterface interface{}) {
    91  			var (
    92  				bufAsByteSlice []byte
    93  			)
    94  
    95  			bufAsByteSlice = make([]byte, testJrpcResponseBufSize)
    96  
    97  			bufAsInterface = bufAsByteSlice
    98  
    99  			return
   100  		},
   101  	}
   102  
   103  	testSwiftProxyEmulatorGlobals.Add(1)
   104  
   105  	go func() {
   106  		_ = testSwiftProxyEmulatorGlobals.httpServer.ListenAndServe()
   107  
   108  		testSwiftProxyEmulatorGlobals.Done()
   109  	}()
   110  
   111  	for {
   112  		infoResponse, err = http.Get("http://" + testSwiftProxyAddr + "/info")
   113  		if nil == err {
   114  			break
   115  		}
   116  
   117  		time.Sleep(testDaemonStartPollInterval)
   118  	}
   119  
   120  	if http.StatusOK != infoResponse.StatusCode {
   121  		t.Fatalf("GET /info from ServeHTTP() got unexpected status %s", infoResponse.Status)
   122  	}
   123  }
   124  
   125  func stopSwiftProxyEmulator() {
   126  	var (
   127  		err error
   128  	)
   129  
   130  	err = testSwiftProxyEmulatorGlobals.httpServer.Shutdown(context.Background())
   131  	if nil != err {
   132  		testSwiftProxyEmulatorGlobals.t.Fatalf("testSwiftProxyEmulatorGlobals.httpServer.Shutdown() failed: %v", err)
   133  	}
   134  
   135  	testSwiftProxyEmulatorGlobals.Wait()
   136  
   137  	testSwiftProxyEmulatorGlobals.jrpcResponsePool = nil
   138  }
   139  
   140  func (dummy *testSwiftProxyEmulatorGlobalsStruct) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) {
   141  	// Handle the GET of/on info & AuthURL cases
   142  
   143  	if http.MethodGet == request.Method {
   144  		switch request.URL.Path {
   145  		case "/info":
   146  			doInfo(responseWriter)
   147  			return
   148  		case "/auth/v1.0":
   149  			doAuth(responseWriter, request)
   150  			return
   151  		default:
   152  			// Fall through to normal processing
   153  		}
   154  	}
   155  
   156  	// Reject unauthorized requests
   157  
   158  	if request.Header.Get("X-Auth-Token") != testAuthToken {
   159  		responseWriter.WriteHeader(http.StatusUnauthorized)
   160  		return
   161  	}
   162  
   163  	// Branch off to individual request method handlers
   164  
   165  	switch request.Method {
   166  	case http.MethodGet:
   167  		doGET(responseWriter, request)
   168  	case http.MethodPut:
   169  		doPUT(responseWriter, request)
   170  	case "PROXYFS":
   171  		doRPC(responseWriter, request)
   172  	default:
   173  		responseWriter.WriteHeader(http.StatusMethodNotAllowed)
   174  	}
   175  }
   176  
   177  func doInfo(authResponseWriter http.ResponseWriter) {
   178  	var (
   179  		err            error
   180  		getBuf         []byte
   181  		noAuthResponse *http.Response
   182  	)
   183  
   184  	noAuthResponse, err = http.Get(testSwiftProxyEmulatorGlobals.ramswiftNoAuthURL + "/info")
   185  	if nil != err {
   186  		testSwiftProxyEmulatorGlobals.t.Fatalf("GET of /info from ramswift failed: %v", err)
   187  	}
   188  
   189  	if http.StatusOK != noAuthResponse.StatusCode {
   190  		testSwiftProxyEmulatorGlobals.t.Fatalf("GET of /info from ramswift returned bad status: %v", noAuthResponse.Status)
   191  	}
   192  
   193  	getBuf, err = ioutil.ReadAll(noAuthResponse.Body)
   194  	if nil != err {
   195  		testSwiftProxyEmulatorGlobals.t.Fatalf("GET of /info returned unreadable Body: %v", err)
   196  	}
   197  
   198  	err = noAuthResponse.Body.Close()
   199  	if nil != err {
   200  		testSwiftProxyEmulatorGlobals.t.Fatalf("GET of /info returned uncloseable Body: %v", err)
   201  	}
   202  
   203  	authResponseWriter.WriteHeader(http.StatusOK)
   204  	_, _ = authResponseWriter.Write(getBuf)
   205  }
   206  
   207  func doAuth(authResponseWriter http.ResponseWriter, authRequest *http.Request) {
   208  	if authRequest.Header.Get("X-Auth-User") != testAuthUser {
   209  		authResponseWriter.WriteHeader(http.StatusUnauthorized)
   210  		return
   211  	}
   212  	if authRequest.Header.Get("X-Auth-Key") != testAuthKey {
   213  		authResponseWriter.WriteHeader(http.StatusUnauthorized)
   214  		return
   215  	}
   216  	authResponseWriter.Header().Add("X-Auth-Token", testAuthToken)
   217  	authResponseWriter.Header().Add("X-Storage-Url", "http://"+testSwiftProxyAddr+"/v1/"+testAccountName)
   218  	authResponseWriter.WriteHeader(http.StatusOK)
   219  }
   220  
   221  // doGET proxies the GET over to ramswift
   222  //
   223  func doGET(authResponseWriter http.ResponseWriter, authRequest *http.Request) {
   224  	var (
   225  		contentRangeHeader string
   226  		contentTypeHeader  string
   227  		err                error
   228  		getBuf             []byte
   229  		hostHeader         string
   230  		noAuthPath         string
   231  		noAuthRequest      *http.Request
   232  		noAuthResponse     *http.Response
   233  		noAuthStatusCode   int
   234  		rangeHeader        string
   235  	)
   236  
   237  	if !strings.HasPrefix(authRequest.URL.Path, "/proxyfs/"+testAccountName) {
   238  		authResponseWriter.WriteHeader(http.StatusNotFound)
   239  		return
   240  	}
   241  
   242  	noAuthPath = strings.Replace(authRequest.URL.Path, "proxyfs", "v1", 1)
   243  
   244  	noAuthRequest, err = http.NewRequest("GET", testSwiftProxyEmulatorGlobals.ramswiftNoAuthURL+noAuthPath, nil)
   245  	if nil != err {
   246  		authResponseWriter.WriteHeader(http.StatusBadRequest)
   247  		return
   248  	}
   249  
   250  	hostHeader = authRequest.Header.Get("Host")
   251  	if "" != hostHeader {
   252  		noAuthRequest.Header.Add("Host", hostHeader)
   253  	}
   254  
   255  	rangeHeader = authRequest.Header.Get("Range")
   256  	if "" != rangeHeader {
   257  		noAuthRequest.Header.Add("Range", rangeHeader)
   258  	}
   259  
   260  	noAuthResponse, err = testSwiftProxyEmulatorGlobals.httpClient.Do(noAuthRequest)
   261  	if nil != err {
   262  		authResponseWriter.WriteHeader(http.StatusBadRequest)
   263  		return
   264  	}
   265  
   266  	noAuthStatusCode = noAuthResponse.StatusCode
   267  
   268  	if (http.StatusOK != noAuthStatusCode) && (http.StatusPartialContent != noAuthStatusCode) {
   269  		_ = noAuthResponse.Body.Close()
   270  		authResponseWriter.WriteHeader(noAuthStatusCode)
   271  		return
   272  	}
   273  
   274  	getBuf, err = ioutil.ReadAll(noAuthResponse.Body)
   275  	if nil != err {
   276  		_ = noAuthResponse.Body.Close()
   277  		authResponseWriter.WriteHeader(http.StatusBadRequest)
   278  		return
   279  	}
   280  
   281  	err = noAuthResponse.Body.Close()
   282  	if nil != err {
   283  		authResponseWriter.WriteHeader(http.StatusBadRequest)
   284  		return
   285  	}
   286  
   287  	contentTypeHeader = noAuthResponse.Header.Get("Content-Type")
   288  	if "" != contentTypeHeader {
   289  		authResponseWriter.Header().Add("Content-Type", contentTypeHeader)
   290  	}
   291  
   292  	contentRangeHeader = noAuthResponse.Header.Get("Content-Range")
   293  	if "" != contentRangeHeader {
   294  		authResponseWriter.Header().Add("Content-Range", contentRangeHeader)
   295  	}
   296  
   297  	authResponseWriter.WriteHeader(noAuthStatusCode)
   298  
   299  	_, _ = authResponseWriter.Write(getBuf)
   300  }
   301  
   302  // doPUT proxies the GET over to ramswift
   303  //
   304  func doPUT(authResponseWriter http.ResponseWriter, authRequest *http.Request) {
   305  	var (
   306  		err            error
   307  		hostHeader     string
   308  		noAuthPath     string
   309  		noAuthRequest  *http.Request
   310  		noAuthResponse *http.Response
   311  	)
   312  
   313  	if !strings.HasPrefix(authRequest.URL.Path, "/proxyfs/"+testAccountName) {
   314  		_ = authRequest.Body.Close()
   315  		authResponseWriter.WriteHeader(http.StatusNotFound)
   316  		return
   317  	}
   318  
   319  	noAuthPath = strings.Replace(authRequest.URL.Path, "proxyfs", "v1", 1)
   320  
   321  	noAuthRequest, err = http.NewRequest("PUT", testSwiftProxyEmulatorGlobals.ramswiftNoAuthURL+noAuthPath, authRequest.Body)
   322  	if nil != err {
   323  		_ = authRequest.Body.Close()
   324  		authResponseWriter.WriteHeader(http.StatusBadRequest)
   325  		return
   326  	}
   327  
   328  	hostHeader = authRequest.Header.Get("Host")
   329  	if "" != hostHeader {
   330  		noAuthRequest.Header.Add("Host", hostHeader)
   331  	}
   332  
   333  	noAuthResponse, err = testSwiftProxyEmulatorGlobals.httpClient.Do(noAuthRequest)
   334  	if nil != err {
   335  		_ = authRequest.Body.Close()
   336  		authResponseWriter.WriteHeader(http.StatusBadRequest)
   337  		return
   338  	}
   339  
   340  	err = authRequest.Body.Close()
   341  	if nil != err {
   342  		authResponseWriter.WriteHeader(http.StatusBadRequest)
   343  		return
   344  	}
   345  
   346  	authResponseWriter.WriteHeader(noAuthResponse.StatusCode)
   347  }
   348  
   349  // doRPC proxies the payload as a JSON RPC request over to proxyfsd
   350  //
   351  func doRPC(responseWriter http.ResponseWriter, request *http.Request) {
   352  	var (
   353  		err             error
   354  		jrpcResponseBuf []byte
   355  		jrpcResponseLen int
   356  		jrpcRequestBuf  []byte
   357  		tcpConn         *net.TCPConn
   358  	)
   359  
   360  	if !strings.HasPrefix(request.URL.Path, "/proxyfs/"+testAccountName) {
   361  		_ = request.Body.Close()
   362  		responseWriter.WriteHeader(http.StatusNotFound)
   363  		return
   364  	}
   365  
   366  	jrpcRequestBuf, err = ioutil.ReadAll(request.Body)
   367  	_ = request.Body.Close()
   368  	if nil != err {
   369  		responseWriter.WriteHeader(http.StatusBadRequest)
   370  		return
   371  	}
   372  
   373  	if request.Header.Get("Content-Type") != "application/json" {
   374  		responseWriter.WriteHeader(http.StatusBadRequest)
   375  		return
   376  	}
   377  
   378  	tcpConn, err = net.DialTCP("tcp", nil, testSwiftProxyEmulatorGlobals.proxyfsdJrpcTCPAddr)
   379  	if nil != err {
   380  		responseWriter.WriteHeader(http.StatusServiceUnavailable)
   381  		return
   382  	}
   383  
   384  	_, err = tcpConn.Write(jrpcRequestBuf)
   385  	if nil != err {
   386  		_ = tcpConn.Close()
   387  		responseWriter.WriteHeader(http.StatusServiceUnavailable)
   388  		return
   389  	}
   390  
   391  	jrpcResponseBuf = testSwiftProxyEmulatorGlobals.jrpcResponsePool.Get().([]byte)
   392  
   393  	jrpcResponseLen, err = tcpConn.Read(jrpcResponseBuf)
   394  	if nil != err {
   395  		_ = tcpConn.Close()
   396  		responseWriter.WriteHeader(http.StatusServiceUnavailable)
   397  		return
   398  	}
   399  
   400  	err = tcpConn.Close()
   401  	if nil != err {
   402  		responseWriter.WriteHeader(http.StatusServiceUnavailable)
   403  		return
   404  	}
   405  
   406  	responseWriter.Header().Add("Content-Type", "application/json")
   407  	responseWriter.WriteHeader(http.StatusOK)
   408  	_, _ = responseWriter.Write(jrpcResponseBuf[:jrpcResponseLen])
   409  
   410  	testSwiftProxyEmulatorGlobals.jrpcResponsePool.Put(jrpcResponseBuf)
   411  }