Skip to content

Commit

Permalink
fix: Switch Quicksilver mock in tests to use httpmock responder
Browse files Browse the repository at this point in the history
  • Loading branch information
tarangv committed Aug 19, 2023
1 parent 512c877 commit 79333b2
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 90 deletions.
47 changes: 22 additions & 25 deletions boltrouter/bolt_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const (

// SelectBoltEndpoint selects a bolt endpoint from BoltVars.BoltEndpoints from the passed in requMethod.
// This method will err if not endpoints were selected.
func (br *BoltRouter) SelectBoltEndpoint(ctx context.Context, reqMethod string) (*url.URL, error) {
func (br *BoltRouter) SelectBoltEndpoint(reqMethod string) (*url.URL, error) {
preferredOrder := br.getPreferredEndpointOrder(reqMethod)
boltEndpoints := br.boltVars.BoltInfo.Get()

Expand Down Expand Up @@ -134,7 +134,7 @@ func (br *BoltRouter) GetCleanerStatus() (bool, error) {
return cleanerOnBool, nil
}

// select initial request destination based on cluster_health_metrics and client_behavior_params
// SelectInitialRequestTarget based on cluster_health_metrics and client_behavior_params
func (br *BoltRouter) SelectInitialRequestTarget() (target string, reason string, err error) {
boltInfo := br.boltVars.BoltInfo.Get()

Expand All @@ -154,32 +154,29 @@ func (br *BoltRouter) SelectInitialRequestTarget() (target string, reason string
return "", "", fmt.Errorf("could not cast boltHealthy to bool")
}

if clusterHealthyBool {
clientBehaviorParams, ok := clientBehaviorParams.(map[string]interface{})
if !ok {
return "", "", fmt.Errorf("could not cast clientBehaviorParams to map[string]interface{}")
}
if !clusterHealthyBool {
return "s3", "cluster unhealthy", nil
}

crunchTrafficPercent, ok := clientBehaviorParams["crunch_traffic_percent"].(string)
if !ok {
return "", "", fmt.Errorf("could not cast crunchTrafficPercent to string")
}
params, ok := clientBehaviorParams.(map[string]interface{})
if !ok {
return "", "", fmt.Errorf("could not cast clientBehaviorParams to map[string]interface{}")
}

crunchTrafficPercentInt, err := strconv.Atoi(crunchTrafficPercent)
if err != nil {
return "", "", fmt.Errorf("could not cast crunchTrafficPercent to int")
}
crunchTrafficPercent, ok := params["crunch_traffic_percent"].(string)
if !ok {
return "", "", fmt.Errorf("could not cast crunchTrafficPercent to string")
}

totalWeight := 1000
r := rand.New(rand.NewSource(time.Now().UnixNano()))
rnd := r.Intn(totalWeight)
crunchTrafficPercentInt, err := strconv.Atoi(crunchTrafficPercent)
if err != nil {
return "", "", fmt.Errorf("could not parse crunchTrafficPercent to int. %v", err)
}

if rnd < (crunchTrafficPercentInt * totalWeight / 100) {
return "bolt", "traffic splitting", nil
} else {
return "s3", "traffic splitting", nil
}
} else {
return "s3", "cluster unhealthy", nil
// Randomly select bolt with crunchTrafficPercentInt % probability.
if rand.Intn(100) < crunchTrafficPercentInt {
return "bolt", "traffic splitting", nil
}

return "s3", "traffic splitting", nil
}
6 changes: 3 additions & 3 deletions boltrouter/bolt_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
func TestGetBoltInfo(t *testing.T) {
ctx := context.Background()
logger := zaptest.NewLogger(t)
SetupQuickSilverMock(t, ctx, true, map[string]interface{}{"crunch_traffic_percent": "50.0"}, true, logger)
SetupQuickSilverMock(t, ctx, true, map[string]interface{}{"crunch_traffic_percent": "50"}, true, logger)

testCases := []struct {
name string
Expand All @@ -38,7 +38,7 @@ func TestGetBoltInfo(t *testing.T) {
func TestSelectBoltEndpoint(t *testing.T) {
ctx := context.Background()
logger := zaptest.NewLogger(t)
SetupQuickSilverMock(t, ctx, true, map[string]interface{}{"crunch_traffic_percent": "60.0"}, true, logger)
SetupQuickSilverMock(t, ctx, true, map[string]interface{}{"crunch_traffic_percent": "60"}, true, logger)

testCases := []struct {
name string
Expand All @@ -60,7 +60,7 @@ func TestSelectBoltEndpoint(t *testing.T) {
err = br.RefreshBoltInfo(ctx)
require.NoError(t, err)

endpoint, err := br.SelectBoltEndpoint(ctx, tt.httpMethod)
endpoint, err := br.SelectBoltEndpoint(tt.httpMethod)
require.NoError(t, err)
require.Contains(t, tt.expected, endpoint.Hostname())
})
Expand Down
2 changes: 1 addition & 1 deletion boltrouter/bolt_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (br *BoltRouter) NewBoltRequest(ctx context.Context, logger *zap.Logger, re
return nil, fmt.Errorf("could not make signed aws head request: %w", err)
}

BoltURL, err := br.SelectBoltEndpoint(ctx, req.Method)
BoltURL, err := br.SelectBoltEndpoint(req.Method)
if err != nil {
return nil, err
}
Expand Down
58 changes: 16 additions & 42 deletions boltrouter/bolt_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestBoltRequest(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
br, err := NewBoltRouter(ctx, logger, tt.cfg)
require.NoError(t, err)
br.RefreshBoltInfo(ctx)
require.NoError(t, br.RefreshBoltInfo(ctx))
boltVars := br.boltVars
body := strings.NewReader(randomdata.Paragraph())

Expand Down Expand Up @@ -80,16 +80,8 @@ func TestBoltRequestFailover(t *testing.T) {
func(req *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(500, "SERVER ERROR"), fmt.Errorf("s3 error")
})
httpmock.RegisterResponder("GET", fmt.Sprintf("https://%s/bolt/test.projectn.co", endpoint),
func(req *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(500, "SERVER ERROR"), fmt.Errorf("bolt error")
})
}

httpmock.RegisterResponder("GET", "https://s3.us-west-2.amazonaws.com/test.projectn.co",
func(req *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(200, "OK"), nil
})
httpmock.RegisterResponder("GET", "https://bolt.s3.us-west-2.amazonaws.com/test.projectn.co",
func(req *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(200, "OK"), nil
Expand All @@ -99,17 +91,10 @@ func TestBoltRequestFailover(t *testing.T) {
logger := zaptest.NewLogger(t)
SetupQuickSilverMock(t, ctx, true, make(map[string]interface{}), true, logger)

boltVars, err := GetBoltVars(ctx, logger)
br, err := NewBoltRouter(ctx, logger, Config{Failover: false})
require.NoError(t, err)
br := &BoltRouter{
config: Config{
Failover: false,
},
boltHttpClient: &http.Client{},
standardHttpClient: &http.Client{},
boltVars: boltVars,
}
br.RefreshBoltInfo(ctx)
require.NoError(t, br.RefreshBoltInfo(ctx))
overrideCrunchTrafficPct(br, "100")

body := strings.NewReader(randomdata.Paragraph())
req, err := http.NewRequest(http.MethodGet, "test.projectn.co", body)
Expand All @@ -125,13 +110,12 @@ func TestBoltRequestFailover(t *testing.T) {
require.False(t, failover, err)

br.config.Failover = true
br.RefreshBoltInfo(ctx)
boltReq, err = br.NewBoltRequest(ctx, logger, req)
require.NoError(t, err)
require.NotNil(t, boltReq)

_, failover, analytics, err := br.DoRequest(logger, boltReq)
// failover is enabled so we should get a successful response by failing over to s3
// failover is enabled, so we should get a successful response by failing over to s3
require.NoError(t, err)
require.True(t, failover, err)
require.NotZero(t, analytics.AwsRequestResponseStatusCode)
Expand All @@ -149,16 +133,8 @@ func TestBoltRequestPanic(t *testing.T) {
func(req *http.Request) (*http.Response, error) {
panic("Simulated panic during request")
})
httpmock.RegisterResponder("GET", fmt.Sprintf("https://%s/bolt/test.projectn.co", endpoint),
func(req *http.Request) (*http.Response, error) {
panic("Simulated panic during request")
})
}

httpmock.RegisterResponder("GET", "https://s3.us-west-2.amazonaws.com/test.projectn.co",
func(req *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(200, "OK"), nil
})
httpmock.RegisterResponder("GET", "https://bolt.s3.us-west-2.amazonaws.com/test.projectn.co",
func(req *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(200, "OK"), nil
Expand All @@ -168,17 +144,11 @@ func TestBoltRequestPanic(t *testing.T) {
logger := zaptest.NewLogger(t)
SetupQuickSilverMock(t, ctx, true, make(map[string]interface{}), true, logger)

boltVars, err := GetBoltVars(ctx, logger)
br, err := NewBoltRouter(ctx, logger, Config{Failover: false})
require.NoError(t, err)
br := &BoltRouter{
config: Config{
Failover: false,
},
boltHttpClient: &http.Client{},
standardHttpClient: &http.Client{},
boltVars: boltVars,
}
br.RefreshBoltInfo(ctx)
require.NoError(t, br.RefreshBoltInfo(ctx))

overrideCrunchTrafficPct(br, "100")

body := strings.NewReader(randomdata.Paragraph())
req, err := http.NewRequest(http.MethodGet, "test.projectn.co", body)
Expand All @@ -192,15 +162,19 @@ func TestBoltRequestPanic(t *testing.T) {
_, _, _, err = br.DoRequest(logger, boltReq)
require.Error(t, err)
// no failover in config so we should get an error
require.Contains(t, err.Error(), "panic occurred during Bolt request")
require.Contains(t, err.Error(), "panic")

br.config.Failover = true
br.RefreshBoltInfo(ctx)
boltReq, err = br.NewBoltRequest(ctx, logger, req)
require.NoError(t, err)
require.NotNil(t, boltReq)

_, _, _, err = br.DoRequest(logger, boltReq)
// failover is enabled so we should get a successful response by failing over to s3
// failover is enabled, so we should get a successful response by failing over to s3
require.NoError(t, err)
}

func overrideCrunchTrafficPct(br *BoltRouter, pct string) {
m := br.boltVars.BoltInfo.Get()
m["client_behavior_params"].(map[string]interface{})["crunch_traffic_percent"] = pct
}
17 changes: 10 additions & 7 deletions boltrouter/bolt_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@ func NewBoltRouter(ctx context.Context, logger *zap.Logger, cfg Config) (*BoltRo
return nil, fmt.Errorf("could not get BoltVars: %w", err)
}

// custom transport is needed to allow certificate validation from bolt hostname
customTransport := http.DefaultTransport.(*http.Transport).Clone()
customTransport.TLSClientConfig = &tls.Config{
ServerName: boltVars.BoltHostname.Get(),
}
boltHttpClient := http.Client{
Timeout: time.Duration(90) * time.Second,
Transport: customTransport,
Timeout: time.Duration(90) * time.Second,
}

// custom transport is needed to allow certificate validation from bolt hostname
if tp, ok := http.DefaultTransport.(*http.Transport); ok {
customTransport := tp.Clone()
customTransport.TLSClientConfig = &tls.Config{
ServerName: boltVars.BoltHostname.Get(),
}
boltHttpClient.Transport = customTransport
}

standardHttpClient := http.Client{
Expand Down
35 changes: 23 additions & 12 deletions boltrouter/main_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package boltrouter

import (
"bytes"
"context"
"encoding/json"
"github.com/jarcoal/httpmock"
"net/http"
"net/http/httptest"
"os"
Expand Down Expand Up @@ -33,7 +35,7 @@ var (

defaultClientBehaviorParams = map[string]interface{}{
"cleaner_on": "true",
"crunch_traffic_percent": "20.0",
"crunch_traffic_percent": "20",
}

quicksilverResponse = map[string]interface{}{
Expand All @@ -44,7 +46,10 @@ var (
}
)

func NewQuicksilverMock(t *testing.T, clusterHealthy bool, clientBehaviorParams map[string]interface{}, intelligentQS bool) *httptest.Server {
func NewQuicksilverMock(t *testing.T, clusterHealthy bool, clientBehaviorParams map[string]interface{}, intelligentQS bool) string {
httpmock.Activate()
t.Cleanup(httpmock.DeactivateAndReset)

// Reset quicksilverResponse to default
delete(quicksilverResponse, "client_behavior_params")
delete(quicksilverResponse, "cluster_healthy")
Expand All @@ -57,22 +62,28 @@ func NewQuicksilverMock(t *testing.T, clusterHealthy bool, clientBehaviorParams
}
}

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sc := http.StatusOK
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(sc)
err := json.NewEncoder(w).Encode(quicksilverResponse)
require.NoError(t, err)
}))
// This URL will where sidekick will call to get the quicksilver response
url := "https://quicksilver.us-west-2.com/services/bolt/"
httpmock.RegisterResponder("GET", url,
func(req *http.Request) (*http.Response, error) {
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(quicksilverResponse)
require.NoError(t, err)

resp := httpmock.NewStringResponse(http.StatusOK, buf.String())
resp.Header.Set("Content-Type", "application/json")

return resp, nil
})

return server
return url
}

func SetupQuickSilverMock(t *testing.T, ctx context.Context, clusterHealthy bool, clientBehaviorParams map[string]interface{}, intelligentQS bool, logger *zap.Logger) {
quicksilver := NewQuicksilverMock(t, clusterHealthy, clientBehaviorParams, intelligentQS)
quicksilverURL := NewQuicksilverMock(t, clusterHealthy, clientBehaviorParams, intelligentQS)
boltVars, err := GetBoltVars(ctx, logger)
require.NoError(t, err)
boltVars.QuicksilverURL.Set(quicksilver.URL)
boltVars.QuicksilverURL.Set(quicksilverURL)
}

type TestS3Client struct {
Expand Down

0 comments on commit 79333b2

Please sign in to comment.