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  }