1// Copyright (C) 2019 Storj Labs, Inc. 2// See LICENSE for copying information. 3 4package trust_test 5 6import ( 7 "bytes" 8 "context" 9 "errors" 10 "os" 11 "path/filepath" 12 "regexp" 13 "testing" 14 15 "github.com/stretchr/testify/require" 16 "go.uber.org/zap" 17 "go.uber.org/zap/zaptest" 18 19 "storj.io/common/storj" 20 "storj.io/common/testcontext" 21 "storj.io/storj/storagenode/trust" 22) 23 24func TestNewList(t *testing.T) { 25 ctx := testcontext.New(t) 26 defer ctx.Cleanup() 27 28 log := zaptest.NewLogger(t) 29 30 cache := newTestCache(t, ctx.Dir(), nil) 31 32 for _, tt := range []struct { 33 name string 34 log *zap.Logger 35 cache *trust.Cache 36 err string 37 }{ 38 { 39 name: "missing logger", 40 cache: cache, 41 err: "trust: logger cannot be nil", 42 }, 43 { 44 name: "missing cache", 45 log: log, 46 err: "trust: cache cannot be nil", 47 }, 48 { 49 name: "success", 50 log: log, 51 cache: cache, 52 }, 53 } { 54 tt := tt // quiet linting 55 t.Run(tt.name, func(t *testing.T) { 56 list, err := trust.NewList(tt.log, nil, nil, tt.cache) 57 if tt.err != "" { 58 require.EqualError(t, err, tt.err) 59 require.Nil(t, list) 60 return 61 } 62 require.NoError(t, err) 63 require.NotNil(t, list) 64 }) 65 } 66} 67 68func TestListAgainstSpec(t *testing.T) { 69 ctx := testcontext.New(t) 70 defer ctx.Cleanup() 71 72 idReplacer := regexp.MustCompile(`^(\d)(@.*)$`) 73 fixURL := func(s string) string { 74 m := idReplacer.FindStringSubmatch(s) 75 if m == nil { 76 return s 77 } 78 return makeTestID(m[1][0]).String() + m[2] 79 } 80 81 makeNodeURL := func(s string) storj.NodeURL { 82 u, err := storj.ParseNodeURL(fixURL(s)) 83 require.NoError(t, err) 84 return u 85 } 86 87 makeSatelliteURL := func(s string) trust.SatelliteURL { 88 u, err := trust.ParseSatelliteURL(fixURL(s)) 89 require.NoError(t, err) 90 return u 91 } 92 93 makeEntry := func(s string, authoritative bool) trust.Entry { 94 return trust.Entry{ 95 SatelliteURL: makeSatelliteURL(s), 96 Authoritative: authoritative, 97 } 98 } 99 100 fileSource := &fakeSource{ 101 name: "file:///path/to/some/trusted-satellites.txt", 102 static: true, 103 entries: []trust.Entry{ 104 makeEntry("1@bar.test:7777", true), 105 }, 106 } 107 108 fooSource := &fakeSource{ 109 name: "https://foo.test/trusted-satellites", 110 static: false, 111 entries: []trust.Entry{ 112 makeEntry("2@f.foo.test:7777", true), 113 makeEntry("2@buz.test:7777", false), 114 makeEntry("2@qiz.test:7777", false), 115 makeEntry("5@ohno.test:7777", false), 116 }, 117 } 118 119 barSource := &fakeSource{ 120 name: "https://bar.test/trusted-satellites", 121 static: false, 122 entries: []trust.Entry{ 123 makeEntry("3@f.foo.test:7777", false), 124 makeEntry("3@bar.test:7777", true), 125 makeEntry("3@baz.test:7777", false), 126 makeEntry("3@buz.test:7777", false), 127 makeEntry("3@quz.test:7777", false), 128 }, 129 } 130 131 bazSource := &fakeSource{ 132 name: "https://baz.test/trusted-satellites", 133 static: false, 134 entries: []trust.Entry{ 135 makeEntry("4@baz.test:7777", true), 136 makeEntry("4@qiz.test:7777", false), 137 makeEntry("4@subdomain.quz.test:7777", false), 138 }, 139 } 140 141 fixedSource := &fakeSource{ 142 name: "0@f.foo.test:7777", 143 static: true, 144 entries: []trust.Entry{ 145 makeEntry("0@f.foo.test:7777", true), 146 }, 147 } 148 149 rules := trust.Rules{ 150 trust.NewHostExcluder("quz.test"), 151 trust.NewURLExcluder(makeSatelliteURL("2@qiz.test:7777")), 152 trust.NewIDExcluder(makeTestID('5')), 153 } 154 155 cache := newTestCache(t, ctx.Dir(), nil) 156 157 sources := []trust.Source{ 158 fileSource, 159 fooSource, 160 barSource, 161 bazSource, 162 fixedSource, 163 } 164 165 log := zaptest.NewLogger(t) 166 list, err := trust.NewList(log, sources, rules, cache) 167 require.NoError(t, err) 168 169 urls, err := list.FetchURLs(context.Background()) 170 require.NoError(t, err) 171 172 t.Logf("0@ = %s", makeTestID('0')) 173 t.Logf("1@ = %s", makeTestID('1')) 174 t.Logf("2@ = %s", makeTestID('2')) 175 t.Logf("3@ = %s", makeTestID('3')) 176 t.Logf("4@ = %s", makeTestID('4')) 177 t.Logf("5@ = %s", makeTestID('5')) 178 179 require.Equal(t, []storj.NodeURL{ 180 makeNodeURL("1@bar.test:7777"), 181 makeNodeURL("2@f.foo.test:7777"), 182 makeNodeURL("2@buz.test:7777"), 183 makeNodeURL("4@baz.test:7777"), 184 makeNodeURL("4@qiz.test:7777"), 185 }, urls) 186} 187 188func TestListCacheInteraction(t *testing.T) { 189 entry1 := trust.Entry{ 190 SatelliteURL: trust.SatelliteURL{ 191 Host: "host1", 192 Port: 7777, 193 }, 194 } 195 url1 := entry1.SatelliteURL.NodeURL() 196 197 entry2 := trust.Entry{ 198 SatelliteURL: trust.SatelliteURL{ 199 Host: "host2", 200 Port: 7777, 201 }, 202 } 203 url2 := entry2.SatelliteURL.NodeURL() 204 205 makeNormal := func(entries ...trust.Entry) *fakeSource { 206 return &fakeSource{ 207 name: "normal", 208 static: false, 209 entries: entries, 210 } 211 } 212 213 makeFixed := func(entries ...trust.Entry) *fakeSource { 214 return &fakeSource{ 215 name: "static", 216 static: true, 217 entries: entries, 218 } 219 } 220 221 badNormal := &fakeSource{ 222 name: "normal", 223 static: false, 224 err: errors.New("ohno"), 225 } 226 227 badFixed := &fakeSource{ 228 name: "static", 229 static: true, 230 err: errors.New("ohno"), 231 } 232 233 for _, tt := range []struct { 234 name string 235 sources []trust.Source 236 cacheBefore map[string][]trust.Entry 237 cacheAfter map[string][]trust.Entry 238 urls []storj.NodeURL 239 killCacheEarly bool 240 err string 241 }{ 242 { 243 name: "entries are cached for normal sources", 244 sources: []trust.Source{makeNormal(entry1)}, 245 urls: []storj.NodeURL{url1}, 246 cacheAfter: map[string][]trust.Entry{ 247 "normal": {entry1}, 248 }, 249 }, 250 { 251 name: "entries are not cached for static sources", 252 sources: []trust.Source{makeFixed(entry1)}, 253 urls: []storj.NodeURL{url1}, 254 cacheAfter: map[string][]trust.Entry{}, 255 }, 256 { 257 name: "entries are updated on success for normal sources", 258 sources: []trust.Source{makeNormal(entry2)}, 259 cacheBefore: map[string][]trust.Entry{ 260 "normal": {entry1}, 261 }, 262 urls: []storj.NodeURL{url2}, 263 cacheAfter: map[string][]trust.Entry{ 264 "normal": {entry2}, 265 }, 266 }, 267 { 268 name: "fetch fails if no cached entry on failure for normal source", 269 sources: []trust.Source{badNormal}, 270 err: `trust: failed to fetch from source "normal": ohno`, 271 }, 272 { 273 name: "cached entries are used on failure for normal sources", 274 sources: []trust.Source{badNormal}, 275 cacheBefore: map[string][]trust.Entry{ 276 "normal": {entry1}, 277 }, 278 urls: []storj.NodeURL{url1}, 279 cacheAfter: map[string][]trust.Entry{ 280 "normal": {entry1}, 281 }, 282 }, 283 { 284 name: "fetch fails on failure for static source", 285 sources: []trust.Source{badFixed}, 286 err: `trust: failed to fetch from source "static": ohno`, 287 }, 288 { 289 name: "failure to save cache is not fatal", 290 sources: []trust.Source{makeNormal(entry1)}, 291 urls: []storj.NodeURL{url1}, 292 killCacheEarly: true, 293 }, 294 } { 295 tt := tt // quiet linting 296 t.Run(tt.name, func(t *testing.T) { 297 ctx := testcontext.New(t) 298 defer ctx.Cleanup() 299 300 cache := newTestCache(t, ctx.Dir(), tt.cacheBefore) 301 302 log := zaptest.NewLogger(t) 303 list, err := trust.NewList(log, tt.sources, nil, cache) 304 require.NoError(t, err) 305 306 if tt.killCacheEarly { 307 require.NoError(t, os.Remove(cache.Path())) 308 } 309 310 urls, err := list.FetchURLs(context.Background()) 311 if tt.err != "" { 312 require.EqualError(t, err, tt.err) 313 return 314 } 315 require.NoError(t, err) 316 require.Equal(t, tt.urls, urls) 317 318 if !tt.killCacheEarly { 319 cacheAfter, err := trust.LoadCacheData(cache.Path()) 320 require.NoError(t, err) 321 require.Equal(t, &trust.CacheData{Entries: tt.cacheAfter}, cacheAfter) 322 } 323 }) 324 } 325} 326 327func newTestCache(t *testing.T, dir string, entries map[string][]trust.Entry) *trust.Cache { 328 cachePath := filepath.Join(dir, "cache.json") 329 330 err := trust.SaveCacheData(cachePath, &trust.CacheData{ 331 Entries: entries, 332 }) 333 require.NoError(t, err) 334 335 cache, err := trust.LoadCache(cachePath) 336 require.NoError(t, err) 337 338 return cache 339} 340 341type fakeSource struct { 342 name string 343 static bool 344 entries []trust.Entry 345 err error 346} 347 348func (s *fakeSource) String() string { 349 return s.name 350} 351 352func (s *fakeSource) Static() bool { 353 return s.static 354} 355 356func (s *fakeSource) FetchEntries(context.Context) ([]trust.Entry, error) { 357 return s.entries, s.err 358} 359 360func makeTestID(x byte) storj.NodeID { 361 var id storj.NodeID 362 copy(id[:], bytes.Repeat([]byte{x}, len(id))) 363 return storj.NewVersionedID(id, storj.IDVersions[storj.V0]) 364} 365