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