github.com/pelicanplatform/pelican@v1.0.5/client/director_test.go (about)

     1  /***************************************************************
     2   *
     3   * Copyright (C) 2023, Pelican Project, Morgridge Institute for Research
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License"); you
     6   * may not use this file except in compliance with the License.  You may
     7   * obtain a copy of the License at
     8   *
     9   *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   ***************************************************************/
    18  
    19  package client
    20  
    21  import (
    22  	"bytes"
    23  	"github.com/stretchr/testify/assert"
    24  	"io"
    25  	"net/http"
    26  	"net/http/httptest"
    27  	"os"
    28  	"testing"
    29  
    30  	namespaces "github.com/pelicanplatform/pelican/namespaces"
    31  )
    32  
    33  func TestHeaderParser(t *testing.T) {
    34  	header1 := "namespace=/foo/bar, issuer = https://get-your-tokens.org, readhttps=False"
    35  	newMap1 := HeaderParser(header1)
    36  
    37  	assert.Equal(t, "/foo/bar", newMap1["namespace"])
    38  	assert.Equal(t, "https://get-your-tokens.org", newMap1["issuer"])
    39  	assert.Equal(t, "False", newMap1["readhttps"])
    40  
    41  	header2 := ""
    42  	newMap2 := HeaderParser(header2)
    43  	assert.Equal(t, map[string]string{}, newMap2)
    44  }
    45  
    46  func TestGetCachesFromDirectorResponse(t *testing.T) {
    47  	// Construct the Director's Response, comprising headers and a body
    48  	directorHeaders := make(map[string][]string)
    49  	directorHeaders["Link"] = []string{"<my-cache.edu:8443>; rel=\"duplicate\"; pri=1, <another-cache.edu:8443>; rel=\"duplicate\"; pri=2"}
    50  	directorBody := []byte(`{"key": "value"}`)
    51  
    52  	directorResponse := &http.Response{
    53  		StatusCode: 307,
    54  		Header:     directorHeaders,
    55  		Body:       io.NopCloser(bytes.NewReader(directorBody)),
    56  	}
    57  
    58  	// Call the function in question
    59  	caches, err := GetCachesFromDirectorResponse(directorResponse, true)
    60  
    61  	// Test for expected outputs
    62  	assert.NoError(t, err, "Error getting caches from the Director's response")
    63  
    64  	assert.Equal(t, "my-cache.edu:8443", caches[0].EndpointUrl)
    65  	assert.Equal(t, 1, caches[0].Priority)
    66  	assert.Equal(t, true, caches[0].AuthedReq)
    67  
    68  	assert.Equal(t, "another-cache.edu:8443", caches[1].EndpointUrl)
    69  	assert.Equal(t, 2, caches[1].Priority)
    70  	assert.Equal(t, true, caches[1].AuthedReq)
    71  }
    72  
    73  func TestCreateNsFromDirectorResp(t *testing.T) {
    74  	//Craft the Director's response
    75  	directorHeaders := make(map[string][]string)
    76  	directorHeaders["Link"] = []string{"<my-cache.edu:8443>; rel=\"duplicate\"; pri=1, <another-cache.edu:8443>; rel=\"duplicate\"; pri=2"}
    77  	directorHeaders["X-Pelican-Namespace"] = []string{"namespace=/foo/bar, readhttps=True, require-token=True"}
    78  	directorHeaders["X-Pelican-Authorization"] = []string{"issuer=https://get-your-tokens.org, base-path=/foo/bar"}
    79  	directorBody := []byte(`{"key": "value"}`)
    80  
    81  	directorResponse := &http.Response{
    82  		StatusCode: 307,
    83  		Header:     directorHeaders,
    84  		Body:       io.NopCloser(bytes.NewReader(directorBody)),
    85  	}
    86  
    87  	// Create a namespace instance to test against
    88  	cache1 := namespaces.DirectorCache{
    89  		EndpointUrl: "my-cache.edu:8443",
    90  		Priority:    1,
    91  		AuthedReq:   true,
    92  	}
    93  	cache2 := namespaces.DirectorCache{
    94  		EndpointUrl: "another-cache.edu:8443",
    95  		Priority:    2,
    96  		AuthedReq:   true,
    97  	}
    98  
    99  	caches := []namespaces.DirectorCache{}
   100  	caches = append(caches, cache1)
   101  	caches = append(caches, cache2)
   102  
   103  	constructedNamespace := &namespaces.Namespace{
   104  		SortedDirectorCaches: caches,
   105  		Path:                 "/foo/bar",
   106  		Issuer:               "https://get-your-tokens.org",
   107  		ReadHTTPS:            true,
   108  		UseTokenOnRead:       true,
   109  	}
   110  
   111  	// Call the function in question
   112  	ns, err := CreateNsFromDirectorResp(directorResponse)
   113  
   114  	// Test for expected outputs
   115  	assert.NoError(t, err, "Error creating Namespace from Director response")
   116  
   117  	assert.Equal(t, constructedNamespace.SortedDirectorCaches, ns.SortedDirectorCaches)
   118  	assert.Equal(t, constructedNamespace.Path, ns.Path)
   119  	assert.Equal(t, constructedNamespace.Issuer, ns.Issuer)
   120  	assert.Equal(t, constructedNamespace.ReadHTTPS, ns.ReadHTTPS)
   121  	assert.Equal(t, constructedNamespace.UseTokenOnRead, ns.UseTokenOnRead)
   122  }
   123  
   124  func TestNewTransferDetailsUsingDirector(t *testing.T) {
   125  	os.Setenv("http_proxy", "http://proxy.edu:3128")
   126  
   127  	// Construct the input caches
   128  	// Cache with http
   129  	nonAuthCache := namespaces.DirectorCache{
   130  		ResourceName: "mycache",
   131  		EndpointUrl:  "my-cache-url:8000",
   132  		Priority:     99,
   133  		AuthedReq:    false,
   134  	}
   135  
   136  	// Cache with https
   137  	authCache := namespaces.DirectorCache{
   138  		ResourceName: "mycache",
   139  		EndpointUrl:  "my-cache-url:8443",
   140  		Priority:     99,
   141  		AuthedReq:    true,
   142  	}
   143  
   144  	// Case 1: cache with http
   145  
   146  	transfers := NewTransferDetailsUsingDirector(nonAuthCache, TransferDetailsOptions{nonAuthCache.AuthedReq, ""})
   147  	assert.Equal(t, 2, len(transfers))
   148  	assert.Equal(t, "my-cache-url:8000", transfers[0].Url.Host)
   149  	assert.Equal(t, "http", transfers[0].Url.Scheme)
   150  	assert.Equal(t, true, transfers[0].Proxy)
   151  	assert.Equal(t, 2, len(transfers))
   152  	assert.Equal(t, "my-cache-url:8000", transfers[1].Url.Host)
   153  	assert.Equal(t, "http", transfers[1].Url.Scheme)
   154  	assert.Equal(t, false, transfers[1].Proxy)
   155  
   156  	// Case 2: cache with https
   157  	transfers = NewTransferDetailsUsingDirector(authCache, TransferDetailsOptions{authCache.AuthedReq, ""})
   158  	assert.Equal(t, 1, len(transfers))
   159  	assert.Equal(t, "my-cache-url:8443", transfers[0].Url.Host)
   160  	assert.Equal(t, "https", transfers[0].Url.Scheme)
   161  	assert.Equal(t, false, transfers[0].Proxy)
   162  
   163  	// Case 3: cache without port with http
   164  	nonAuthCache.EndpointUrl = "my-cache-url"
   165  	transfers = NewTransferDetailsUsingDirector(nonAuthCache, TransferDetailsOptions{nonAuthCache.AuthedReq, ""})
   166  	assert.Equal(t, 2, len(transfers))
   167  	assert.Equal(t, "my-cache-url:8000", transfers[0].Url.Host)
   168  	assert.Equal(t, "http", transfers[0].Url.Scheme)
   169  	assert.Equal(t, true, transfers[0].Proxy)
   170  	assert.Equal(t, "my-cache-url:8000", transfers[1].Url.Host)
   171  	assert.Equal(t, "http", transfers[1].Url.Scheme)
   172  	assert.Equal(t, false, transfers[1].Proxy)
   173  
   174  	// Case 4. cache without port with https
   175  	authCache.EndpointUrl = "my-cache-url"
   176  	transfers = NewTransferDetailsUsingDirector(authCache, TransferDetailsOptions{authCache.AuthedReq, ""})
   177  	assert.Equal(t, 2, len(transfers))
   178  	assert.Equal(t, "my-cache-url:8444", transfers[0].Url.Host)
   179  	assert.Equal(t, "https", transfers[0].Url.Scheme)
   180  	assert.Equal(t, false, transfers[0].Proxy)
   181  	assert.Equal(t, "my-cache-url:8443", transfers[1].Url.Host)
   182  	assert.Equal(t, "https", transfers[1].Url.Scheme)
   183  	assert.Equal(t, false, transfers[1].Proxy)
   184  }
   185  
   186  func TestQueryDirector(t *testing.T) {
   187  	// Construct a local server that we can poke with QueryDirector
   188  	expectedLocation := "http://redirect.com"
   189  	handler := func(w http.ResponseWriter, r *http.Request) {
   190  		w.Header().Set("Location", expectedLocation)
   191  		w.WriteHeader(http.StatusTemporaryRedirect)
   192  	}
   193  	server := httptest.NewServer(http.HandlerFunc(handler))
   194  	defer server.Close()
   195  
   196  	// Call QueryDirector with the test server URL and a source path
   197  	actualResp, err := QueryDirector("/foo/bar", server.URL)
   198  	if err != nil {
   199  		t.Fatal(err)
   200  	}
   201  
   202  	// Check the Location header
   203  	actualLocation := actualResp.Header.Get("Location")
   204  	if actualLocation != expectedLocation {
   205  		t.Errorf("Expected Location header %q, but got %q", expectedLocation, actualLocation)
   206  	}
   207  
   208  	// Check the HTTP status code
   209  	if actualResp.StatusCode != http.StatusTemporaryRedirect {
   210  		t.Errorf("Expected HTTP status code %d, but got %d", http.StatusFound, actualResp.StatusCode)
   211  	}
   212  }