github.com/FUSIONFoundation/efsn@v3.6.2-0.20200916075423-dbb5dd5d2cc7+incompatible/swarm/network_test.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package swarm 18 19 import ( 20 "context" 21 "flag" 22 "fmt" 23 "io/ioutil" 24 "math/rand" 25 "os" 26 "sync" 27 "sync/atomic" 28 "testing" 29 "time" 30 31 "github.com/FusionFoundation/efsn/crypto" 32 "github.com/FusionFoundation/efsn/log" 33 "github.com/FusionFoundation/efsn/node" 34 "github.com/FusionFoundation/efsn/p2p/discover" 35 "github.com/FusionFoundation/efsn/p2p/simulations/adapters" 36 "github.com/FusionFoundation/efsn/swarm/api" 37 "github.com/FusionFoundation/efsn/swarm/network/simulation" 38 "github.com/FusionFoundation/efsn/swarm/storage" 39 colorable "github.com/mattn/go-colorable" 40 ) 41 42 var ( 43 loglevel = flag.Int("loglevel", 2, "verbosity of logs") 44 longrunning = flag.Bool("longrunning", false, "do run long-running tests") 45 waitKademlia = flag.Bool("waitkademlia", false, "wait for healthy kademlia before checking files availability") 46 ) 47 48 func init() { 49 rand.Seed(time.Now().UnixNano()) 50 51 flag.Parse() 52 53 log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) 54 } 55 56 // TestSwarmNetwork runs a series of test simulations with 57 // static and dynamic Swarm nodes in network simulation, by 58 // uploading files to every node and retrieving them. 59 func TestSwarmNetwork(t *testing.T) { 60 for _, tc := range []struct { 61 name string 62 steps []testSwarmNetworkStep 63 options *testSwarmNetworkOptions 64 disabled bool 65 }{ 66 { 67 name: "10_nodes", 68 steps: []testSwarmNetworkStep{ 69 { 70 nodeCount: 10, 71 }, 72 }, 73 options: &testSwarmNetworkOptions{ 74 Timeout: 45 * time.Second, 75 }, 76 }, 77 { 78 name: "10_nodes_skip_check", 79 steps: []testSwarmNetworkStep{ 80 { 81 nodeCount: 10, 82 }, 83 }, 84 options: &testSwarmNetworkOptions{ 85 Timeout: 45 * time.Second, 86 SkipCheck: true, 87 }, 88 }, 89 { 90 name: "50_nodes", 91 steps: []testSwarmNetworkStep{ 92 { 93 nodeCount: 50, 94 }, 95 }, 96 options: &testSwarmNetworkOptions{ 97 Timeout: 3 * time.Minute, 98 }, 99 disabled: !*longrunning, 100 }, 101 { 102 name: "50_nodes_skip_check", 103 steps: []testSwarmNetworkStep{ 104 { 105 nodeCount: 50, 106 }, 107 }, 108 options: &testSwarmNetworkOptions{ 109 Timeout: 3 * time.Minute, 110 SkipCheck: true, 111 }, 112 disabled: !*longrunning, 113 }, 114 { 115 name: "inc_node_count", 116 steps: []testSwarmNetworkStep{ 117 { 118 nodeCount: 2, 119 }, 120 { 121 nodeCount: 5, 122 }, 123 { 124 nodeCount: 10, 125 }, 126 }, 127 options: &testSwarmNetworkOptions{ 128 Timeout: 90 * time.Second, 129 }, 130 disabled: !*longrunning, 131 }, 132 { 133 name: "dec_node_count", 134 steps: []testSwarmNetworkStep{ 135 { 136 nodeCount: 10, 137 }, 138 { 139 nodeCount: 6, 140 }, 141 { 142 nodeCount: 3, 143 }, 144 }, 145 options: &testSwarmNetworkOptions{ 146 Timeout: 90 * time.Second, 147 }, 148 disabled: !*longrunning, 149 }, 150 { 151 name: "dec_inc_node_count", 152 steps: []testSwarmNetworkStep{ 153 { 154 nodeCount: 5, 155 }, 156 { 157 nodeCount: 3, 158 }, 159 { 160 nodeCount: 10, 161 }, 162 }, 163 options: &testSwarmNetworkOptions{ 164 Timeout: 90 * time.Second, 165 }, 166 }, 167 { 168 name: "inc_dec_node_count", 169 steps: []testSwarmNetworkStep{ 170 { 171 nodeCount: 3, 172 }, 173 { 174 nodeCount: 5, 175 }, 176 { 177 nodeCount: 25, 178 }, 179 { 180 nodeCount: 10, 181 }, 182 { 183 nodeCount: 4, 184 }, 185 }, 186 options: &testSwarmNetworkOptions{ 187 Timeout: 5 * time.Minute, 188 }, 189 disabled: !*longrunning, 190 }, 191 { 192 name: "inc_dec_node_count_skip_check", 193 steps: []testSwarmNetworkStep{ 194 { 195 nodeCount: 3, 196 }, 197 { 198 nodeCount: 5, 199 }, 200 { 201 nodeCount: 25, 202 }, 203 { 204 nodeCount: 10, 205 }, 206 { 207 nodeCount: 4, 208 }, 209 }, 210 options: &testSwarmNetworkOptions{ 211 Timeout: 5 * time.Minute, 212 SkipCheck: true, 213 }, 214 disabled: !*longrunning, 215 }, 216 } { 217 if tc.disabled { 218 continue 219 } 220 t.Run(tc.name, func(t *testing.T) { 221 testSwarmNetwork(t, tc.options, tc.steps...) 222 }) 223 } 224 } 225 226 // testSwarmNetworkStep is the configuration 227 // for the state of the simulation network. 228 type testSwarmNetworkStep struct { 229 // number of swarm nodes that must be in the Up state 230 nodeCount int 231 } 232 233 // file represents the file uploaded on a particular node. 234 type file struct { 235 addr storage.Address 236 data string 237 nodeID discover.NodeID 238 } 239 240 // check represents a reference to a file that is retrieved 241 // from a particular node. 242 type check struct { 243 key string 244 nodeID discover.NodeID 245 } 246 247 // testSwarmNetworkOptions contains optional parameters for running 248 // testSwarmNetwork. 249 type testSwarmNetworkOptions struct { 250 Timeout time.Duration 251 SkipCheck bool 252 } 253 254 // testSwarmNetwork is a helper function used for testing different 255 // static and dynamic Swarm network simulations. 256 // It is responsible for: 257 // - Setting up a Swarm network simulation, and updates the number of nodes within the network on every step according to steps. 258 // - Uploading a unique file to every node on every step. 259 // - May wait for Kademlia on every node to be healthy. 260 // - Checking if a file is retrievable from all nodes. 261 func testSwarmNetwork(t *testing.T, o *testSwarmNetworkOptions, steps ...testSwarmNetworkStep) { 262 if o == nil { 263 o = new(testSwarmNetworkOptions) 264 } 265 266 sim := simulation.New(map[string]simulation.ServiceFunc{ 267 "swarm": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { 268 config := api.NewConfig() 269 270 dir, err := ioutil.TempDir("", "swarm-network-test-node") 271 if err != nil { 272 return nil, nil, err 273 } 274 cleanup = func() { 275 err := os.RemoveAll(dir) 276 if err != nil { 277 log.Error("cleaning up swarm temp dir", "err", err) 278 } 279 } 280 281 config.Path = dir 282 283 privkey, err := crypto.GenerateKey() 284 if err != nil { 285 return nil, cleanup, err 286 } 287 288 config.Init(privkey) 289 config.DeliverySkipCheck = o.SkipCheck 290 config.Port = "" 291 292 swarm, err := NewSwarm(config, nil) 293 if err != nil { 294 return nil, cleanup, err 295 } 296 bucket.Store(simulation.BucketKeyKademlia, swarm.bzz.Hive.Kademlia) 297 log.Info("new swarm", "bzzKey", config.BzzKey, "baseAddr", fmt.Sprintf("%x", swarm.bzz.BaseAddr())) 298 return swarm, cleanup, nil 299 }, 300 }) 301 defer sim.Close() 302 303 ctx := context.Background() 304 if o.Timeout > 0 { 305 var cancel context.CancelFunc 306 ctx, cancel = context.WithTimeout(ctx, o.Timeout) 307 defer cancel() 308 } 309 310 files := make([]file, 0) 311 312 for i, step := range steps { 313 log.Debug("test sync step", "n", i+1, "nodes", step.nodeCount) 314 315 change := step.nodeCount - len(sim.UpNodeIDs()) 316 317 if change > 0 { 318 _, err := sim.AddNodesAndConnectChain(change) 319 if err != nil { 320 t.Fatal(err) 321 } 322 } else if change < 0 { 323 _, err := sim.StopRandomNodes(-change) 324 if err != nil { 325 t.Fatal(err) 326 } 327 } else { 328 t.Logf("step %v: no change in nodes", i) 329 continue 330 } 331 332 var checkStatusM sync.Map 333 var nodeStatusM sync.Map 334 var totalFoundCount uint64 335 336 result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { 337 nodeIDs := sim.UpNodeIDs() 338 shuffle(len(nodeIDs), func(i, j int) { 339 nodeIDs[i], nodeIDs[j] = nodeIDs[j], nodeIDs[i] 340 }) 341 for _, id := range nodeIDs { 342 key, data, err := uploadFile(sim.Service("swarm", id).(*Swarm)) 343 if err != nil { 344 return err 345 } 346 log.Trace("file uploaded", "node", id, "key", key.String()) 347 files = append(files, file{ 348 addr: key, 349 data: data, 350 nodeID: id, 351 }) 352 } 353 354 if *waitKademlia { 355 if _, err := sim.WaitTillHealthy(ctx, 2); err != nil { 356 return err 357 } 358 } 359 360 // File retrieval check is repeated until all uploaded files are retrieved from all nodes 361 // or until the timeout is reached. 362 for { 363 if retrieve(sim, files, &checkStatusM, &nodeStatusM, &totalFoundCount) == 0 { 364 return nil 365 } 366 } 367 }) 368 369 if result.Error != nil { 370 t.Fatal(result.Error) 371 } 372 log.Debug("done: test sync step", "n", i+1, "nodes", step.nodeCount) 373 } 374 } 375 376 // uploadFile, uploads a short file to the swarm instance 377 // using the api.Put method. 378 func uploadFile(swarm *Swarm) (storage.Address, string, error) { 379 b := make([]byte, 8) 380 _, err := rand.Read(b) 381 if err != nil { 382 return nil, "", err 383 } 384 // File data is very short, but it is ensured that its 385 // uniqueness is very certain. 386 data := fmt.Sprintf("test content %s %x", time.Now().Round(0), b) 387 ctx := context.TODO() 388 k, wait, err := swarm.api.Put(ctx, data, "text/plain", false) 389 if err != nil { 390 return nil, "", err 391 } 392 if wait != nil { 393 err = wait(ctx) 394 } 395 return k, data, err 396 } 397 398 // retrieve is the function that is used for checking the availability of 399 // uploaded files in testSwarmNetwork test helper function. 400 func retrieve( 401 sim *simulation.Simulation, 402 files []file, 403 checkStatusM *sync.Map, 404 nodeStatusM *sync.Map, 405 totalFoundCount *uint64, 406 ) (missing uint64) { 407 shuffle(len(files), func(i, j int) { 408 files[i], files[j] = files[j], files[i] 409 }) 410 411 var totalWg sync.WaitGroup 412 errc := make(chan error) 413 414 nodeIDs := sim.UpNodeIDs() 415 416 totalCheckCount := len(nodeIDs) * len(files) 417 418 for _, id := range nodeIDs { 419 if _, ok := nodeStatusM.Load(id); ok { 420 continue 421 } 422 start := time.Now() 423 var checkCount uint64 424 var foundCount uint64 425 426 totalWg.Add(1) 427 428 var wg sync.WaitGroup 429 430 swarm := sim.Service("swarm", id).(*Swarm) 431 for _, f := range files { 432 433 checkKey := check{ 434 key: f.addr.String(), 435 nodeID: id, 436 } 437 if n, ok := checkStatusM.Load(checkKey); ok && n.(int) == 0 { 438 continue 439 } 440 441 checkCount++ 442 wg.Add(1) 443 go func(f file, id discover.NodeID) { 444 defer wg.Done() 445 446 log.Debug("api get: check file", "node", id.String(), "key", f.addr.String(), "total files found", atomic.LoadUint64(totalFoundCount)) 447 448 r, _, _, _, err := swarm.api.Get(context.TODO(), api.NOOPDecrypt, f.addr, "/") 449 if err != nil { 450 errc <- fmt.Errorf("api get: node %s, key %s, kademlia %s: %v", id, f.addr, swarm.bzz.Hive, err) 451 return 452 } 453 d, err := ioutil.ReadAll(r) 454 if err != nil { 455 errc <- fmt.Errorf("api get: read response: node %s, key %s: kademlia %s: %v", id, f.addr, swarm.bzz.Hive, err) 456 return 457 } 458 data := string(d) 459 if data != f.data { 460 errc <- fmt.Errorf("file contend missmatch: node %s, key %s, expected %q, got %q", id, f.addr, f.data, data) 461 return 462 } 463 checkStatusM.Store(checkKey, 0) 464 atomic.AddUint64(&foundCount, 1) 465 log.Info("api get: file found", "node", id.String(), "key", f.addr.String(), "content", data, "files found", atomic.LoadUint64(&foundCount)) 466 }(f, id) 467 } 468 469 go func(id discover.NodeID) { 470 defer totalWg.Done() 471 wg.Wait() 472 473 atomic.AddUint64(totalFoundCount, foundCount) 474 475 if foundCount == checkCount { 476 log.Info("all files are found for node", "id", id.String(), "duration", time.Since(start)) 477 nodeStatusM.Store(id, 0) 478 return 479 } 480 log.Debug("files missing for node", "id", id.String(), "check", checkCount, "found", foundCount) 481 }(id) 482 483 } 484 485 go func() { 486 totalWg.Wait() 487 close(errc) 488 }() 489 490 var errCount int 491 for err := range errc { 492 if err != nil { 493 errCount++ 494 } 495 log.Warn(err.Error()) 496 } 497 498 log.Info("check stats", "total check count", totalCheckCount, "total files found", atomic.LoadUint64(totalFoundCount), "total errors", errCount) 499 500 return uint64(totalCheckCount) - atomic.LoadUint64(totalFoundCount) 501 } 502 503 // Backported from stdlib https://golang.org/src/math/rand/rand.go?s=11175:11215#L333 504 // 505 // Replace with rand.Shuffle from go 1.10 when go 1.9 support is dropped. 506 // 507 // shuffle pseudo-randomizes the order of elements. 508 // n is the number of elements. Shuffle panics if n < 0. 509 // swap swaps the elements with indexes i and j. 510 func shuffle(n int, swap func(i, j int)) { 511 if n < 0 { 512 panic("invalid argument to Shuffle") 513 } 514 515 // Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 516 // Shuffle really ought not be called with n that doesn't fit in 32 bits. 517 // Not only will it take a very long time, but with 2³¹! possible permutations, 518 // there's no way that any PRNG can have a big enough internal state to 519 // generate even a minuscule percentage of the possible permutations. 520 // Nevertheless, the right API signature accepts an int n, so handle it as best we can. 521 i := n - 1 522 for ; i > 1<<31-1-1; i-- { 523 j := int(rand.Int63n(int64(i + 1))) 524 swap(i, j) 525 } 526 for ; i > 0; i-- { 527 j := int(rand.Int31n(int32(i + 1))) 528 swap(i, j) 529 } 530 }