storj/versioncontrol/peer_test.go

377 lines
9.3 KiB
Go
Raw Normal View History

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package versioncontrol_test
import (
"context"
"encoding/hex"
"io"
"net/http"
"reflect"
"strings"
"testing"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
"golang.org/x/sync/errgroup"
"storj.io/common/testcontext"
"storj.io/common/testrand"
"storj.io/storj/versioncontrol"
)
var rolloutErrScenarios = []struct {
name string
rollout versioncontrol.RolloutConfig
errContains string
}{
{
"short seed",
versioncontrol.RolloutConfig{
// 31 byte seed
Seed: "00000000000000000000000000000000000000000000000000000000000000",
Cursor: 0,
},
"invalid seed length:",
},
{
"long seed",
versioncontrol.RolloutConfig{
// 33 byte seed
Seed: "000000000000000000000000000000000000000000000000000000000000000000",
Cursor: 0,
},
"invalid seed length:",
},
{
"invalid seed",
versioncontrol.RolloutConfig{
// non-hex seed
Seed: "G000000000000000000000000000000000000000000000000000000000000000",
Cursor: 0,
},
"invalid seed:",
},
{
"negative cursor",
versioncontrol.RolloutConfig{
Seed: "0000000000000000000000000000000000000000000000000000000000000000",
Cursor: -1,
},
"invalid cursor percentage:",
},
{
"cursor too big",
versioncontrol.RolloutConfig{
Seed: "0000000000000000000000000000000000000000000000000000000000000000",
Cursor: 101,
},
"invalid cursor percentage:",
},
}
func TestPeerEndpoint(t *testing.T) {
minimumVersion := "v0.0.1"
suggestedVersion := "v0.0.2"
createURL := func(process, version string) string {
urlTmpl := "http://example.com/{version}/{process}_{os}_{arch}"
url := strings.Replace(urlTmpl, "{version}", version, 1)
url = strings.Replace(url, "{process}", process, 1)
return url
}
config := &versioncontrol.Config{
Address: "127.0.0.1:0",
Versions: versioncontrol.OldVersionConfig{
Satellite: minimumVersion,
Storagenode: minimumVersion,
Uplink: minimumVersion,
Gateway: minimumVersion,
Identity: minimumVersion,
},
Binary: versioncontrol.ProcessesConfig{
Storagenode: versioncontrol.ProcessConfig{
Minimum: versioncontrol.VersionConfig{
Version: minimumVersion,
URL: createURL("storagenode", minimumVersion),
},
Suggested: versioncontrol.VersionConfig{
Version: suggestedVersion,
URL: createURL("storagenode", suggestedVersion),
},
},
StoragenodeUpdater: versioncontrol.ProcessConfig{
Minimum: versioncontrol.VersionConfig{
Version: minimumVersion,
URL: createURL("storagenode-updater", minimumVersion),
},
Suggested: versioncontrol.VersionConfig{
Version: suggestedVersion,
URL: createURL("storagenode-updater", suggestedVersion),
},
},
Uplink: versioncontrol.ProcessConfig{
Minimum: versioncontrol.VersionConfig{
Version: minimumVersion,
URL: createURL("uplink", minimumVersion),
},
Suggested: versioncontrol.VersionConfig{
Version: suggestedVersion,
URL: createURL("uplink", suggestedVersion),
},
},
Gateway: versioncontrol.ProcessConfig{
Minimum: versioncontrol.VersionConfig{
Version: minimumVersion,
URL: createURL("gateway", minimumVersion),
},
Suggested: versioncontrol.VersionConfig{
Version: suggestedVersion,
URL: createURL("gateway", suggestedVersion),
},
},
Identity: versioncontrol.ProcessConfig{
Minimum: versioncontrol.VersionConfig{
Version: minimumVersion,
URL: createURL("identity", minimumVersion),
},
Suggested: versioncontrol.VersionConfig{
Version: suggestedVersion,
URL: createURL("identity", suggestedVersion),
},
},
},
}
log := zaptest.NewLogger(t)
peer, err := versioncontrol.New(log, config)
require.NoError(t, err)
require.NotNil(t, peer)
testCtx := testcontext.New(t)
ctx, cancel := context.WithCancel(testCtx)
var wg errgroup.Group
wg.Go(func() error {
return peer.Run(ctx)
})
defer testCtx.Check(peer.Close)
defer cancel()
baseURL := "http://" + peer.Addr()
t.Run("resolve process url", func(t *testing.T) {
queryTmpl := "processes/{service}/{version}/url?os={os}&arch={arch}"
urls := make(map[string]string)
for _, supportedBinary := range versioncontrol.SupportedBinaries {
splitted := strings.SplitN(supportedBinary, "_", 3)
service := splitted[0]
os := splitted[1]
arch := splitted[2]
for _, versionType := range []string{"minimum", "suggested"} {
query := strings.Replace(queryTmpl, "{service}", service, 1)
query = strings.Replace(query, "{version}", versionType, 1)
query = strings.Replace(query, "{os}", os, 1)
query = strings.Replace(query, "{arch}", arch, 1)
var url string
switch versionType {
case "minimum":
url = createURL(service, minimumVersion)
case "suggested":
url = createURL(service, suggestedVersion)
}
url = strings.Replace(url, "{os}", os, 1)
url = strings.Replace(url, "{arch}", arch, 1)
urls[query] = url
}
}
for query, url := range urls {
query, url := query, url
t.Run(query, func(t *testing.T) {
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/"+query, nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
b, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.NotNil(t, b)
require.NoError(t, resp.Body.Close())
require.Equal(t, url, string(b))
log.Debug(string(b))
})
}
})
}
func TestPeer_Run(t *testing.T) {
testVersion := "v0.0.1"
testServiceVersions := versioncontrol.OldVersionConfig{
Gateway: testVersion,
Identity: testVersion,
Satellite: testVersion,
Storagenode: testVersion,
Uplink: testVersion,
}
t.Run("random rollouts", func(t *testing.T) {
for i := 0; i < 100; i++ {
config := versioncontrol.Config{
Address: "127.0.0.1:0",
Versions: testServiceVersions,
Binary: validRandVersions(t),
}
peer, err := versioncontrol.New(zaptest.NewLogger(t), &config)
require.NoError(t, err)
require.NotNil(t, peer)
}
})
t.Run("empty rollout seed", func(t *testing.T) {
versionsType := reflect.TypeOf(versioncontrol.ProcessesConfig{})
fieldCount := versionsType.NumField()
// test invalid rollout for each binary
for i := 0; i < fieldCount; i++ {
versions := versioncontrol.ProcessesConfig{}
versionsValue := reflect.ValueOf(&versions)
field := versionsValue.Elem().Field(i)
binary := versioncontrol.ProcessConfig{
Rollout: versioncontrol.RolloutConfig{
Seed: "",
Cursor: 0,
},
}
field.Set(reflect.ValueOf(binary))
config := versioncontrol.Config{
Address: "127.0.0.1:0",
Versions: testServiceVersions,
Binary: versions,
}
peer, err := versioncontrol.New(zaptest.NewLogger(t), &config)
require.NoError(t, err)
require.NotNil(t, peer)
}
})
}
func TestPeer_Run_error(t *testing.T) {
for _, scenario := range rolloutErrScenarios {
scenario := scenario
t.Run(scenario.name, func(t *testing.T) {
versionsType := reflect.TypeOf(versioncontrol.ProcessesConfig{})
fieldCount := versionsType.NumField()
// test invalid rollout for each binary
for i := 0; i < fieldCount; i++ {
versions := versioncontrol.ProcessesConfig{}
versionsValue := reflect.ValueOf(&versions)
field := reflect.Indirect(versionsValue).Field(i)
binary := versioncontrol.ProcessConfig{
Rollout: scenario.rollout,
}
field.Set(reflect.ValueOf(binary))
config := versioncontrol.Config{
Address: "127.0.0.1:0",
Binary: versions,
}
peer, err := versioncontrol.New(zaptest.NewLogger(t), &config)
require.Nil(t, peer)
require.Error(t, err)
require.Contains(t, err.Error(), scenario.errContains)
}
})
}
}
func TestVersions_ValidateRollouts(t *testing.T) {
versions := validRandVersions(t)
err := versions.ValidateRollouts(zaptest.NewLogger(t))
require.NoError(t, err)
}
func TestRollout_Validate(t *testing.T) {
for i := 0; i < 100; i++ {
rollout := versioncontrol.RolloutConfig{
Seed: randSeedString(t),
Cursor: i,
}
err := rollout.Validate()
require.NoError(t, err)
}
}
func TestRollout_Validate_error(t *testing.T) {
for _, scenario := range rolloutErrScenarios {
scenario := scenario
t.Run(scenario.name, func(t *testing.T) {
err := scenario.rollout.Validate()
require.Error(t, err)
require.True(t, versioncontrol.RolloutErr.Has(err))
require.Contains(t, err.Error(), scenario.errContains)
})
}
}
func validRandVersions(t *testing.T) versioncontrol.ProcessesConfig {
t.Helper()
return versioncontrol.ProcessesConfig{
Satellite: versioncontrol.ProcessConfig{
Rollout: randRollout(t),
},
Storagenode: versioncontrol.ProcessConfig{
Rollout: randRollout(t),
},
Uplink: versioncontrol.ProcessConfig{
Rollout: randRollout(t),
},
Gateway: versioncontrol.ProcessConfig{
Rollout: randRollout(t),
},
Identity: versioncontrol.ProcessConfig{
Rollout: randRollout(t),
},
}
}
func randRollout(t *testing.T) versioncontrol.RolloutConfig {
t.Helper()
return versioncontrol.RolloutConfig{
Seed: randSeedString(t),
Cursor: testrand.Intn(101),
}
}
func randSeedString(t *testing.T) string {
t.Helper()
seed := make([]byte, 32)
testrand.Read(seed)
return hex.EncodeToString(seed)
}