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