2019-11-16 00:31:59 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package trust_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"go.uber.org/zap/zaptest"
|
|
|
|
|
2019-12-27 11:48:47 +00:00
|
|
|
"storj.io/common/storj"
|
|
|
|
"storj.io/common/testcontext"
|
2019-11-16 00:31:59 +00:00
|
|
|
"storj.io/storj/storagenode/trust"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestNewList(t *testing.T) {
|
|
|
|
ctx := testcontext.New(t)
|
|
|
|
defer ctx.Cleanup()
|
|
|
|
|
|
|
|
log := zaptest.NewLogger(t)
|
|
|
|
|
|
|
|
cache := newTestCache(t, ctx.Dir(), nil)
|
|
|
|
|
|
|
|
for _, tt := range []struct {
|
|
|
|
name string
|
|
|
|
log *zap.Logger
|
|
|
|
cache *trust.Cache
|
|
|
|
err string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "missing logger",
|
|
|
|
cache: cache,
|
|
|
|
err: "trust: logger cannot be nil",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "missing cache",
|
|
|
|
log: log,
|
|
|
|
err: "trust: cache cannot be nil",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "success",
|
|
|
|
log: log,
|
|
|
|
cache: cache,
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
tt := tt // quiet linting
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
list, err := trust.NewList(tt.log, nil, nil, tt.cache)
|
|
|
|
if tt.err != "" {
|
|
|
|
require.EqualError(t, err, tt.err)
|
|
|
|
require.Nil(t, list)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, list)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestListAgainstSpec(t *testing.T) {
|
|
|
|
ctx := testcontext.New(t)
|
|
|
|
defer ctx.Cleanup()
|
|
|
|
|
|
|
|
idReplacer := regexp.MustCompile(`^(\d)(@.*)$`)
|
|
|
|
fixURL := func(s string) string {
|
|
|
|
m := idReplacer.FindStringSubmatch(s)
|
|
|
|
if m == nil {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
return makeTestID(m[1][0]).String() + m[2]
|
|
|
|
}
|
|
|
|
|
|
|
|
makeNodeURL := func(s string) storj.NodeURL {
|
|
|
|
u, err := storj.ParseNodeURL(fixURL(s))
|
|
|
|
require.NoError(t, err)
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
makeSatelliteURL := func(s string) trust.SatelliteURL {
|
|
|
|
u, err := trust.ParseSatelliteURL(fixURL(s))
|
|
|
|
require.NoError(t, err)
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
makeEntry := func(s string, authoritative bool) trust.Entry {
|
|
|
|
return trust.Entry{
|
|
|
|
SatelliteURL: makeSatelliteURL(s),
|
|
|
|
Authoritative: authoritative,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fileSource := &fakeSource{
|
|
|
|
name: "file:///path/to/some/trusted-satellites.txt",
|
|
|
|
static: true,
|
|
|
|
entries: []trust.Entry{
|
|
|
|
makeEntry("1@bar.test:7777", true),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
fooSource := &fakeSource{
|
|
|
|
name: "https://foo.test/trusted-satellites",
|
|
|
|
static: false,
|
|
|
|
entries: []trust.Entry{
|
|
|
|
makeEntry("2@f.foo.test:7777", true),
|
|
|
|
makeEntry("2@buz.test:7777", false),
|
|
|
|
makeEntry("2@qiz.test:7777", false),
|
|
|
|
makeEntry("5@ohno.test:7777", false),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
barSource := &fakeSource{
|
|
|
|
name: "https://bar.test/trusted-satellites",
|
|
|
|
static: false,
|
|
|
|
entries: []trust.Entry{
|
|
|
|
makeEntry("3@f.foo.test:7777", false),
|
|
|
|
makeEntry("3@bar.test:7777", true),
|
|
|
|
makeEntry("3@baz.test:7777", false),
|
|
|
|
makeEntry("3@buz.test:7777", false),
|
|
|
|
makeEntry("3@quz.test:7777", false),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
bazSource := &fakeSource{
|
|
|
|
name: "https://baz.test/trusted-satellites",
|
|
|
|
static: false,
|
|
|
|
entries: []trust.Entry{
|
|
|
|
makeEntry("4@baz.test:7777", true),
|
|
|
|
makeEntry("4@qiz.test:7777", false),
|
|
|
|
makeEntry("4@subdomain.quz.test:7777", false),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
fixedSource := &fakeSource{
|
|
|
|
name: "0@f.foo.test:7777",
|
|
|
|
static: true,
|
|
|
|
entries: []trust.Entry{
|
|
|
|
makeEntry("0@f.foo.test:7777", true),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
rules := trust.Rules{
|
|
|
|
trust.NewHostExcluder("quz.test"),
|
|
|
|
trust.NewURLExcluder(makeSatelliteURL("2@qiz.test:7777")),
|
|
|
|
trust.NewIDExcluder(makeTestID('5')),
|
|
|
|
}
|
|
|
|
|
|
|
|
cache := newTestCache(t, ctx.Dir(), nil)
|
|
|
|
|
|
|
|
sources := []trust.Source{
|
|
|
|
fileSource,
|
|
|
|
fooSource,
|
|
|
|
barSource,
|
|
|
|
bazSource,
|
|
|
|
fixedSource,
|
|
|
|
}
|
|
|
|
|
|
|
|
log := zaptest.NewLogger(t)
|
|
|
|
list, err := trust.NewList(log, sources, rules, cache)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
urls, err := list.FetchURLs(context.Background())
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
t.Logf("0@ = %s", makeTestID('0'))
|
|
|
|
t.Logf("1@ = %s", makeTestID('1'))
|
|
|
|
t.Logf("2@ = %s", makeTestID('2'))
|
|
|
|
t.Logf("3@ = %s", makeTestID('3'))
|
|
|
|
t.Logf("4@ = %s", makeTestID('4'))
|
|
|
|
t.Logf("5@ = %s", makeTestID('5'))
|
|
|
|
|
|
|
|
require.Equal(t, []storj.NodeURL{
|
|
|
|
makeNodeURL("1@bar.test:7777"),
|
|
|
|
makeNodeURL("2@f.foo.test:7777"),
|
|
|
|
makeNodeURL("2@buz.test:7777"),
|
|
|
|
makeNodeURL("4@baz.test:7777"),
|
|
|
|
makeNodeURL("4@qiz.test:7777"),
|
|
|
|
}, urls)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestListCacheInteraction(t *testing.T) {
|
|
|
|
entry1 := trust.Entry{
|
|
|
|
SatelliteURL: trust.SatelliteURL{
|
|
|
|
Host: "host1",
|
|
|
|
Port: 7777,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
url1 := entry1.SatelliteURL.NodeURL()
|
|
|
|
|
|
|
|
entry2 := trust.Entry{
|
|
|
|
SatelliteURL: trust.SatelliteURL{
|
|
|
|
Host: "host2",
|
|
|
|
Port: 7777,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
url2 := entry2.SatelliteURL.NodeURL()
|
|
|
|
|
|
|
|
makeNormal := func(entries ...trust.Entry) *fakeSource {
|
|
|
|
return &fakeSource{
|
|
|
|
name: "normal",
|
|
|
|
static: false,
|
|
|
|
entries: entries,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
makeFixed := func(entries ...trust.Entry) *fakeSource {
|
|
|
|
return &fakeSource{
|
|
|
|
name: "static",
|
|
|
|
static: true,
|
|
|
|
entries: entries,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
badNormal := &fakeSource{
|
|
|
|
name: "normal",
|
|
|
|
static: false,
|
|
|
|
err: errors.New("ohno"),
|
|
|
|
}
|
|
|
|
|
|
|
|
badFixed := &fakeSource{
|
|
|
|
name: "static",
|
|
|
|
static: true,
|
|
|
|
err: errors.New("ohno"),
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range []struct {
|
|
|
|
name string
|
|
|
|
sources []trust.Source
|
|
|
|
cacheBefore map[string][]trust.Entry
|
|
|
|
cacheAfter map[string][]trust.Entry
|
|
|
|
urls []storj.NodeURL
|
|
|
|
killCacheEarly bool
|
|
|
|
err string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "entries are cached for normal sources",
|
|
|
|
sources: []trust.Source{makeNormal(entry1)},
|
|
|
|
urls: []storj.NodeURL{url1},
|
|
|
|
cacheAfter: map[string][]trust.Entry{
|
|
|
|
"normal": {entry1},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "entries are not cached for static sources",
|
|
|
|
sources: []trust.Source{makeFixed(entry1)},
|
|
|
|
urls: []storj.NodeURL{url1},
|
|
|
|
cacheAfter: map[string][]trust.Entry{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "entries are updated on success for normal sources",
|
|
|
|
sources: []trust.Source{makeNormal(entry2)},
|
|
|
|
cacheBefore: map[string][]trust.Entry{
|
|
|
|
"normal": {entry1},
|
|
|
|
},
|
|
|
|
urls: []storj.NodeURL{url2},
|
|
|
|
cacheAfter: map[string][]trust.Entry{
|
|
|
|
"normal": {entry2},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "fetch fails if no cached entry on failure for normal source",
|
|
|
|
sources: []trust.Source{badNormal},
|
|
|
|
err: `trust: failed to fetch from source "normal": ohno`,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "cached entries are used on failure for normal sources",
|
|
|
|
sources: []trust.Source{badNormal},
|
|
|
|
cacheBefore: map[string][]trust.Entry{
|
|
|
|
"normal": {entry1},
|
|
|
|
},
|
|
|
|
urls: []storj.NodeURL{url1},
|
|
|
|
cacheAfter: map[string][]trust.Entry{
|
|
|
|
"normal": {entry1},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "fetch fails on failure for static source",
|
|
|
|
sources: []trust.Source{badFixed},
|
|
|
|
err: `trust: failed to fetch from source "static": ohno`,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "failure to save cache is not fatal",
|
|
|
|
sources: []trust.Source{makeNormal(entry1)},
|
|
|
|
urls: []storj.NodeURL{url1},
|
|
|
|
killCacheEarly: true,
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
tt := tt // quiet linting
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
ctx := testcontext.New(t)
|
|
|
|
defer ctx.Cleanup()
|
|
|
|
|
|
|
|
cache := newTestCache(t, ctx.Dir(), tt.cacheBefore)
|
|
|
|
|
|
|
|
log := zaptest.NewLogger(t)
|
|
|
|
list, err := trust.NewList(log, tt.sources, nil, cache)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
if tt.killCacheEarly {
|
|
|
|
require.NoError(t, os.Remove(cache.Path()))
|
|
|
|
}
|
|
|
|
|
|
|
|
urls, err := list.FetchURLs(context.Background())
|
|
|
|
if tt.err != "" {
|
|
|
|
require.EqualError(t, err, tt.err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, tt.urls, urls)
|
|
|
|
|
|
|
|
if !tt.killCacheEarly {
|
|
|
|
cacheAfter, err := trust.LoadCacheData(cache.Path())
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, &trust.CacheData{Entries: tt.cacheAfter}, cacheAfter)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newTestCache(t *testing.T, dir string, entries map[string][]trust.Entry) *trust.Cache {
|
|
|
|
cachePath := filepath.Join(dir, "cache.json")
|
|
|
|
|
|
|
|
err := trust.SaveCacheData(cachePath, &trust.CacheData{
|
|
|
|
Entries: entries,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
cache, err := trust.LoadCache(cachePath)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
return cache
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeSource struct {
|
|
|
|
name string
|
|
|
|
static bool
|
|
|
|
entries []trust.Entry
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *fakeSource) String() string {
|
|
|
|
return s.name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *fakeSource) Static() bool {
|
|
|
|
return s.static
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *fakeSource) FetchEntries(context.Context) ([]trust.Entry, error) {
|
|
|
|
return s.entries, s.err
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeTestID(x byte) storj.NodeID {
|
|
|
|
var id storj.NodeID
|
|
|
|
copy(id[:], bytes.Repeat([]byte{x}, len(id)))
|
|
|
|
return storj.NewVersionedID(id, storj.IDVersions[storj.V0])
|
|
|
|
}
|