gitlab.com/jokerrs1/Sia@v1.3.2/node/api/server_helpers_test.go (about) 1 package api 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "net" 8 "net/http" 9 "net/url" 10 "path/filepath" 11 "runtime/debug" 12 "strings" 13 "testing" 14 "time" 15 16 "github.com/NebulousLabs/Sia/build" 17 "github.com/NebulousLabs/Sia/crypto" 18 "github.com/NebulousLabs/Sia/modules" 19 "github.com/NebulousLabs/Sia/modules/consensus" 20 "github.com/NebulousLabs/Sia/modules/explorer" 21 "github.com/NebulousLabs/Sia/modules/gateway" 22 "github.com/NebulousLabs/Sia/modules/host" 23 "github.com/NebulousLabs/Sia/modules/miner" 24 "github.com/NebulousLabs/Sia/modules/renter" 25 "github.com/NebulousLabs/Sia/modules/transactionpool" 26 "github.com/NebulousLabs/Sia/modules/wallet" 27 "github.com/NebulousLabs/Sia/persist" 28 "github.com/NebulousLabs/Sia/types" 29 30 "github.com/NebulousLabs/errors" 31 "github.com/NebulousLabs/threadgroup" 32 ) 33 34 // A Server is a collection of siad modules that can be communicated with over 35 // an http api. 36 type Server struct { 37 api *API 38 apiServer *http.Server 39 listener net.Listener 40 requiredUserAgent string 41 tg threadgroup.ThreadGroup 42 } 43 44 // panicClose will close a Server, panicking if there is an error upon close. 45 func (srv *Server) panicClose() { 46 err := srv.Close() 47 if err != nil { 48 // Print the stack. 49 debug.PrintStack() 50 panic(err) 51 } 52 } 53 54 // Close closes the Server's listener, causing the HTTP server to shut down. 55 func (srv *Server) Close() error { 56 err := srv.listener.Close() 57 err = errors.Extend(err, srv.tg.Stop()) 58 59 // Safely close each module. 60 mods := []struct { 61 name string 62 c io.Closer 63 }{ 64 {"explorer", srv.api.explorer}, 65 {"host", srv.api.host}, 66 {"renter", srv.api.renter}, 67 {"miner", srv.api.miner}, 68 {"wallet", srv.api.wallet}, 69 {"tpool", srv.api.tpool}, 70 {"consensus", srv.api.cs}, 71 {"gateway", srv.api.gateway}, 72 } 73 for _, mod := range mods { 74 if mod.c != nil { 75 if closeErr := mod.c.Close(); closeErr != nil { 76 err = errors.Extend(err, fmt.Errorf("%v.Close failed: %v", mod.name, err)) 77 } 78 } 79 } 80 return errors.AddContext(err, "error while closing server") 81 } 82 83 // Serve listens for and handles API calls. It is a blocking function. 84 func (srv *Server) Serve() error { 85 err := srv.tg.Add() 86 if err != nil { 87 return errors.AddContext(err, "unable to initialize server") 88 } 89 defer srv.tg.Done() 90 91 // The server will run until an error is encountered or the listener is 92 // closed, via either the Close method or by signal handling. Closing the 93 // listener will result in the benign error handled below. 94 err = srv.apiServer.Serve(srv.listener) 95 if err != nil && !strings.HasSuffix(err.Error(), "use of closed network connection") { 96 return err 97 } 98 return nil 99 } 100 101 // NewServer creates a new API server from the provided modules. The API will 102 // require authentication using HTTP basic auth if the supplied password is not 103 // the empty string. Usernames are ignored for authentication. This type of 104 // authentication sends passwords in plaintext and should therefore only be 105 // used if the APIaddr is localhost. 106 func NewServer(APIaddr string, requiredUserAgent string, requiredPassword string, cs modules.ConsensusSet, e modules.Explorer, g modules.Gateway, h modules.Host, m modules.Miner, r modules.Renter, tp modules.TransactionPool, w modules.Wallet) (*Server, error) { 107 listener, err := net.Listen("tcp", APIaddr) 108 if err != nil { 109 return nil, err 110 } 111 112 api := New(requiredUserAgent, requiredPassword, cs, e, g, h, m, r, tp, w) 113 srv := &Server{ 114 api: api, 115 apiServer: &http.Server{ 116 Handler: api, 117 }, 118 listener: listener, 119 requiredUserAgent: requiredUserAgent, 120 } 121 return srv, nil 122 } 123 124 // serverTester contains a server and a set of channels for keeping all of the 125 // modules synchronized during testing. 126 type serverTester struct { 127 cs modules.ConsensusSet 128 explorer modules.Explorer 129 gateway modules.Gateway 130 host modules.Host 131 miner modules.TestMiner 132 renter modules.Renter 133 tpool modules.TransactionPool 134 wallet modules.Wallet 135 walletKey crypto.TwofishKey 136 137 server *Server 138 139 dir string 140 } 141 142 // assembleServerTester creates a bunch of modules and assembles them into a 143 // server tester, without creating any directories or mining any blocks. 144 func assembleServerTester(key crypto.TwofishKey, testdir string) (*serverTester, error) { 145 // assembleServerTester should not get called during short tests, as it 146 // takes a long time to run. 147 if testing.Short() { 148 panic("assembleServerTester called during short tests") 149 } 150 151 // Create the modules. 152 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 153 if err != nil { 154 return nil, err 155 } 156 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 157 if err != nil { 158 return nil, err 159 } 160 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 161 if err != nil { 162 return nil, err 163 } 164 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 165 if err != nil { 166 return nil, err 167 } 168 if !w.Encrypted() { 169 _, err = w.Encrypt(key) 170 if err != nil { 171 return nil, err 172 } 173 } 174 err = w.Unlock(key) 175 if err != nil { 176 return nil, err 177 } 178 m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) 179 if err != nil { 180 return nil, err 181 } 182 h, err := host.New(cs, tp, w, "localhost:0", filepath.Join(testdir, modules.HostDir)) 183 if err != nil { 184 return nil, err 185 } 186 r, err := renter.New(g, cs, w, tp, filepath.Join(testdir, modules.RenterDir)) 187 if err != nil { 188 return nil, err 189 } 190 srv, err := NewServer("localhost:0", "Sia-Agent", "", cs, nil, g, h, m, r, tp, w) 191 if err != nil { 192 return nil, err 193 } 194 195 // Assemble the serverTester. 196 st := &serverTester{ 197 cs: cs, 198 gateway: g, 199 host: h, 200 miner: m, 201 renter: r, 202 tpool: tp, 203 wallet: w, 204 walletKey: key, 205 206 server: srv, 207 208 dir: testdir, 209 } 210 211 // TODO: A more reasonable way of listening for server errors. 212 go func() { 213 listenErr := srv.Serve() 214 if listenErr != nil { 215 panic(listenErr) 216 } 217 }() 218 return st, nil 219 } 220 221 // assembleAuthenticatedServerTester creates a bunch of modules and assembles 222 // them into a server tester that requires authentication with the given 223 // requiredPassword. No directories are created and no blocks are mined. 224 func assembleAuthenticatedServerTester(requiredPassword string, key crypto.TwofishKey, testdir string) (*serverTester, error) { 225 // assembleAuthenticatedServerTester should not get called during short 226 // tests, as it takes a long time to run. 227 if testing.Short() { 228 panic("assembleServerTester called during short tests") 229 } 230 231 // Create the modules. 232 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 233 if err != nil { 234 return nil, err 235 } 236 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 237 if err != nil { 238 return nil, err 239 } 240 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 241 if err != nil { 242 return nil, err 243 } 244 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 245 if err != nil { 246 return nil, err 247 } 248 if !w.Encrypted() { 249 _, err = w.Encrypt(key) 250 if err != nil { 251 return nil, err 252 } 253 } 254 err = w.Unlock(key) 255 if err != nil { 256 return nil, err 257 } 258 m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) 259 if err != nil { 260 return nil, err 261 } 262 h, err := host.New(cs, tp, w, "localhost:0", filepath.Join(testdir, modules.HostDir)) 263 if err != nil { 264 return nil, err 265 } 266 r, err := renter.New(g, cs, w, tp, filepath.Join(testdir, modules.RenterDir)) 267 if err != nil { 268 return nil, err 269 } 270 srv, err := NewServer("localhost:0", "Sia-Agent", requiredPassword, cs, nil, g, h, m, r, tp, w) 271 if err != nil { 272 return nil, err 273 } 274 275 // Assemble the serverTester. 276 st := &serverTester{ 277 cs: cs, 278 gateway: g, 279 host: h, 280 miner: m, 281 renter: r, 282 tpool: tp, 283 wallet: w, 284 walletKey: key, 285 286 server: srv, 287 288 dir: testdir, 289 } 290 291 // TODO: A more reasonable way of listening for server errors. 292 go func() { 293 listenErr := srv.Serve() 294 if listenErr != nil { 295 panic(listenErr) 296 } 297 }() 298 return st, nil 299 } 300 301 // assembleExplorerServerTester creates all the explorer dependencies and 302 // explorer module without creating any directories. The user agent requirement 303 // is disabled. 304 func assembleExplorerServerTester(testdir string) (*serverTester, error) { 305 // assembleExplorerServerTester should not get called during short tests, 306 // as it takes a long time to run. 307 if testing.Short() { 308 panic("assembleServerTester called during short tests") 309 } 310 311 // Create the modules. 312 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 313 if err != nil { 314 return nil, err 315 } 316 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 317 if err != nil { 318 return nil, err 319 } 320 e, err := explorer.New(cs, filepath.Join(testdir, modules.ExplorerDir)) 321 if err != nil { 322 return nil, err 323 } 324 srv, err := NewServer("localhost:0", "", "", cs, e, g, nil, nil, nil, nil, nil) 325 if err != nil { 326 return nil, err 327 } 328 329 // Assemble the serverTester. 330 st := &serverTester{ 331 cs: cs, 332 explorer: e, 333 gateway: g, 334 335 server: srv, 336 337 dir: testdir, 338 } 339 340 // TODO: A more reasonable way of listening for server errors. 341 go func() { 342 listenErr := srv.Serve() 343 if listenErr != nil { 344 panic(listenErr) 345 } 346 }() 347 return st, nil 348 } 349 350 // blankServerTester creates a server tester object that is ready for testing, 351 // without mining any blocks. 352 func blankServerTester(name string) (*serverTester, error) { 353 // createServerTester is expensive, and therefore should not be called 354 // during short tests. 355 if testing.Short() { 356 panic("blankServerTester called during short tests") 357 } 358 359 // Create the server tester with key. 360 testdir := build.TempDir("api", name) 361 key := crypto.GenerateTwofishKey() 362 st, err := assembleServerTester(key, testdir) 363 if err != nil { 364 return nil, err 365 } 366 return st, nil 367 } 368 369 // createServerTester creates a server tester object that is ready for testing, 370 // including money in the wallet and all modules initialized. 371 func createServerTester(name string) (*serverTester, error) { 372 // createServerTester is expensive, and therefore should not be called 373 // during short tests. 374 if testing.Short() { 375 panic("createServerTester called during short tests") 376 } 377 378 // Create the testing directory. 379 testdir := build.TempDir("api", name) 380 381 key := crypto.GenerateTwofishKey() 382 st, err := assembleServerTester(key, testdir) 383 if err != nil { 384 return nil, err 385 } 386 387 // Mine blocks until the wallet has confirmed money. 388 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 389 _, err := st.miner.AddBlock() 390 if err != nil { 391 return nil, err 392 } 393 } 394 395 return st, nil 396 } 397 398 // createAuthenticatedServerTester creates an authenticated server tester 399 // object that is ready for testing, including money in the wallet and all 400 // modules initialized. 401 func createAuthenticatedServerTester(name string, password string) (*serverTester, error) { 402 // createAuthenticatedServerTester should not get called during short 403 // tests, as it takes a long time to run. 404 if testing.Short() { 405 panic("assembleServerTester called during short tests") 406 } 407 408 // Create the testing directory. 409 testdir := build.TempDir("authenticated-api", name) 410 411 key := crypto.GenerateTwofishKey() 412 st, err := assembleAuthenticatedServerTester(password, key, testdir) 413 if err != nil { 414 return nil, err 415 } 416 417 // Mine blocks until the wallet has confirmed money. 418 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 419 _, err := st.miner.AddBlock() 420 if err != nil { 421 return nil, err 422 } 423 } 424 425 return st, nil 426 } 427 428 // createExplorerServerTester creates a server tester object containing only 429 // the explorer and some presets that match standard explorer setups. 430 func createExplorerServerTester(name string) (*serverTester, error) { 431 testdir := build.TempDir("api", name) 432 st, err := assembleExplorerServerTester(testdir) 433 if err != nil { 434 return nil, err 435 } 436 return st, nil 437 } 438 439 // decodeError returns the api.Error from a API response. This method should 440 // only be called if the response's status code is non-2xx. The error returned 441 // may not be of type api.Error in the event of an error unmarshalling the 442 // JSON. 443 func decodeError(resp *http.Response) error { 444 var apiErr Error 445 err := json.NewDecoder(resp.Body).Decode(&apiErr) 446 if err != nil { 447 return err 448 } 449 return apiErr 450 } 451 452 // non2xx returns true for non-success HTTP status codes. 453 func non2xx(code int) bool { 454 return code < 200 || code > 299 455 } 456 457 // panicClose attempts to close a serverTester. If it fails, panic is called 458 // with the error. 459 func (st *serverTester) panicClose() { 460 st.server.panicClose() 461 } 462 463 // retry will retry a function multiple times until it returns 'nil'. It will 464 // sleep the specified duration between tries. If success is not achieved in the 465 // specified number of attempts, the final error is returned. 466 func retry(tries int, durationBetweenAttempts time.Duration, fn func() error) (err error) { 467 for i := 0; i < tries-1; i++ { 468 err = fn() 469 if err == nil { 470 return nil 471 } 472 time.Sleep(durationBetweenAttempts) 473 } 474 return fn() 475 } 476 477 // reloadedServerTester creates a server tester where all of the persistent 478 // data has been copied to a new folder and all of the modules re-initialized 479 // on the new folder. This gives an opportunity to see how modules will behave 480 // when they are relying on their persistent structures. 481 func (st *serverTester) reloadedServerTester() (*serverTester, error) { 482 // Copy the testing directory. 483 copiedDir := st.dir + " - " + persist.RandomSuffix() 484 err := build.CopyDir(st.dir, copiedDir) 485 if err != nil { 486 return nil, err 487 } 488 copyST, err := assembleServerTester(st.walletKey, copiedDir) 489 if err != nil { 490 return nil, err 491 } 492 return copyST, nil 493 } 494 495 // netAddress returns the NetAddress of the caller. 496 func (st *serverTester) netAddress() modules.NetAddress { 497 return st.server.api.gateway.Address() 498 } 499 500 // coinAddress returns a coin address that the caller is able to spend from. 501 func (st *serverTester) coinAddress() string { 502 var addr struct { 503 Address string 504 } 505 st.getAPI("/wallet/address", &addr) 506 return addr.Address 507 } 508 509 // acceptContracts instructs the host to begin accepting contracts. 510 func (st *serverTester) acceptContracts() error { 511 settingsValues := url.Values{} 512 settingsValues.Set("acceptingcontracts", "true") 513 return st.stdPostAPI("/host", settingsValues) 514 } 515 516 // setHostStorage adds a storage folder to the host. 517 func (st *serverTester) setHostStorage() error { 518 values := url.Values{} 519 values.Set("path", st.dir) 520 values.Set("size", "1048576") 521 return st.stdPostAPI("/host/storage/folders/add", values) 522 } 523 524 // announceHost announces the host, mines a block, and waits for the 525 // announcement to register. 526 func (st *serverTester) announceHost() error { 527 // Check how many hosts there are to begin with. 528 var hosts HostdbActiveGET 529 err := st.getAPI("/hostdb/active", &hosts) 530 if err != nil { 531 return err 532 } 533 initialHosts := len(hosts.Hosts) 534 535 // Set the host to be accepting contracts. 536 acceptingContractsValues := url.Values{} 537 acceptingContractsValues.Set("acceptingcontracts", "true") 538 err = st.stdPostAPI("/host", acceptingContractsValues) 539 if err != nil { 540 return build.ExtendErr("couldn't make an api call to the host:", err) 541 } 542 543 announceValues := url.Values{} 544 announceValues.Set("address", string(st.host.ExternalSettings().NetAddress)) 545 err = st.stdPostAPI("/host/announce", announceValues) 546 if err != nil { 547 return err 548 } 549 // mine block 550 _, err = st.miner.AddBlock() 551 if err != nil { 552 return err 553 } 554 // wait for announcement 555 err = st.getAPI("/hostdb/active", &hosts) 556 if err != nil { 557 return err 558 } 559 for i := 0; i < 50 && len(hosts.Hosts) <= initialHosts; i++ { 560 time.Sleep(100 * time.Millisecond) 561 err = st.getAPI("/hostdb/active", &hosts) 562 if err != nil { 563 return err 564 } 565 } 566 if len(hosts.Hosts) <= initialHosts { 567 return errors.New("host announcement not seen") 568 } 569 return nil 570 } 571 572 // getAPI makes an API call and decodes the response. 573 func (st *serverTester) getAPI(call string, obj interface{}) error { 574 resp, err := HttpGET("http://" + st.server.listener.Addr().String() + call) 575 if err != nil { 576 return err 577 } 578 defer resp.Body.Close() 579 580 if non2xx(resp.StatusCode) { 581 return decodeError(resp) 582 } 583 584 // Return early because there is no content to decode. 585 if resp.StatusCode == http.StatusNoContent { 586 return nil 587 } 588 589 // Decode the response into 'obj'. 590 err = json.NewDecoder(resp.Body).Decode(obj) 591 if err != nil { 592 return err 593 } 594 return nil 595 } 596 597 // postAPI makes an API call and decodes the response. 598 func (st *serverTester) postAPI(call string, values url.Values, obj interface{}) error { 599 resp, err := HttpPOST("http://"+st.server.listener.Addr().String()+call, values.Encode()) 600 if err != nil { 601 return err 602 } 603 defer resp.Body.Close() 604 605 if non2xx(resp.StatusCode) { 606 return decodeError(resp) 607 } 608 609 // Return early because there is no content to decode. 610 if resp.StatusCode == http.StatusNoContent { 611 return nil 612 } 613 614 // Decode the response into 'obj'. 615 err = json.NewDecoder(resp.Body).Decode(obj) 616 if err != nil { 617 return err 618 } 619 return nil 620 } 621 622 // stdGetAPI makes an API call and discards the response. 623 func (st *serverTester) stdGetAPI(call string) error { 624 resp, err := HttpGET("http://" + st.server.listener.Addr().String() + call) 625 if err != nil { 626 return err 627 } 628 defer resp.Body.Close() 629 630 if non2xx(resp.StatusCode) { 631 return decodeError(resp) 632 } 633 return nil 634 } 635 636 // stdGetAPIUA makes an API call with a custom user agent. 637 func (st *serverTester) stdGetAPIUA(call string, userAgent string) error { 638 req, err := http.NewRequest("GET", "http://"+st.server.listener.Addr().String()+call, nil) 639 if err != nil { 640 return err 641 } 642 req.Header.Set("User-Agent", userAgent) 643 resp, err := http.DefaultClient.Do(req) 644 if err != nil { 645 return err 646 } 647 defer resp.Body.Close() 648 649 if non2xx(resp.StatusCode) { 650 return decodeError(resp) 651 } 652 return nil 653 } 654 655 // stdPostAPI makes an API call and discards the response. 656 func (st *serverTester) stdPostAPI(call string, values url.Values) error { 657 resp, err := HttpPOST("http://"+st.server.listener.Addr().String()+call, values.Encode()) 658 if err != nil { 659 return err 660 } 661 defer resp.Body.Close() 662 663 if non2xx(resp.StatusCode) { 664 return decodeError(resp) 665 } 666 return nil 667 }