github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/e2e/runner.go (about)

     1  /*
     2   * Copyright (C) 2020 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU 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   * This program 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 General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package e2e
    19  
    20  import (
    21  	"bytes"
    22  	"encoding/json"
    23  	"fmt"
    24  	"net/http"
    25  	"regexp"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/magefile/mage/sh"
    30  	"github.com/pkg/errors"
    31  	"github.com/rs/zerolog/log"
    32  )
    33  
    34  // NewRunner returns e2e test runners instance
    35  func NewRunner(composeFiles []string, testEnv, services string) (runner *Runner, cleanup func()) {
    36  	fileArgs := make([]string, 0)
    37  	for _, f := range composeFiles {
    38  		fileArgs = append(fileArgs, "-f", f)
    39  	}
    40  	var args []string
    41  	args = append(args, fileArgs...)
    42  	args = append(args, "-p", testEnv)
    43  
    44  	runner = &Runner{
    45  		compose:    sh.RunCmd("docker-compose", args...),
    46  		composeOut: sh.OutCmd("docker-compose", args...),
    47  		testEnv:    testEnv,
    48  		services:   services,
    49  	}
    50  	return runner, runner.cleanup
    51  }
    52  
    53  // Runner is e2e tests runner responsible for starting test environment and running e2e tests.
    54  type Runner struct {
    55  	compose         func(args ...string) error
    56  	composeOut      func(args ...string) (string, error)
    57  	etherPassphrase string
    58  	testEnv         string
    59  	services        string
    60  }
    61  
    62  // Test starts given provider and consumer nodes and runs e2e tests.
    63  func (r *Runner) Test(providerHost string) (retErr error) {
    64  	services := strings.Split(r.services, ",")
    65  	if err := r.startProviderConsumerNodes(providerHost, services); err != nil {
    66  		retErr = errors.Wrap(err, "tests failed!")
    67  		return
    68  	}
    69  
    70  	defer func() {
    71  		if err := r.stopProviderConsumerNodes(providerHost, services); err != nil {
    72  			log.Err(err).Msg("Could not stop provider consumer nodes")
    73  		}
    74  
    75  		if retErr == nil { // check public IPs in logs only if all the tests succeeded
    76  			if err := r.checkPublicIPInLogs("myst-provider", "myst-consumer-wireguard"); err != nil {
    77  				retErr = errors.Wrap(err, "tests failed!")
    78  				return
    79  			}
    80  		}
    81  	}()
    82  
    83  	log.Info().Msg("Running tests for env: " + r.testEnv)
    84  
    85  	err := r.compose("run", "go-runner",
    86  		"/usr/local/bin/test", "-test.v",
    87  		"-provider.tequilapi-host", providerHost,
    88  		"-provider.tequilapi-port=4050",
    89  		"-consumer.tequilapi-port=4050",
    90  		"-consumer.services", r.services,
    91  	)
    92  
    93  	retErr = errors.Wrap(err, "tests failed!")
    94  	return
    95  }
    96  
    97  func (r *Runner) checkPublicIPInLogs(containers ...string) error {
    98  	regExps := []*regexp.Regexp{
    99  		regexp.MustCompile(`(^|[^0-9])(172\.30\.0\.2)($|[^0-9])`),
   100  		regexp.MustCompile(`(^|[^0-9])(172\.31\.0\.2)($|[^0-9])`),
   101  	}
   102  
   103  	for _, containerName := range containers {
   104  		output, err := r.composeOut("logs", containerName)
   105  		if err != nil {
   106  			log.Err(err).Msgf("Could not get logs of %s container", containerName)
   107  			continue
   108  		}
   109  
   110  		if len(output) == 0 {
   111  			log.Error().Msgf("Could not get logs of %s container. Empty data", containerName)
   112  			continue
   113  		}
   114  
   115  		for _, reg := range regExps {
   116  			if reg.MatchString(output) {
   117  				// it will be easier to locate the place if we print the output
   118  				log.Warn().Msgf("output from %s container's logs:\n%s", containerName, output)
   119  				return fmt.Errorf("found public IP address by regular expression %s in %s container's logs", reg.String(), containerName)
   120  			}
   121  		}
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  func (r *Runner) cleanup() {
   128  	log.Info().Msg("Cleaning up")
   129  
   130  	_ = r.compose("logs")
   131  	if err := r.compose("down", "--volumes", "--remove-orphans", "--timeout", "30"); err != nil {
   132  		log.Warn().Err(err).Msg("Cleanup error")
   133  	}
   134  }
   135  
   136  // Init starts provider and consumer node dependencies.
   137  func (r *Runner) Init() error {
   138  	log.Info().Msg("Starting other services")
   139  	if err := r.compose("pull"); err != nil {
   140  		return errors.Wrap(err, "could not pull images")
   141  	}
   142  
   143  	if err := r.compose("up", "-d", "broker", "ganache", "ganache2", "ipify", "morqa", "mongodb", "transactordatabase", "pilvytis-mock"); err != nil {
   144  		return errors.Wrap(err, "starting other services failed!")
   145  	}
   146  
   147  	log.Info().Msg("Starting discovery DB")
   148  	if err := r.compose("up", "-d", "db"); err != nil {
   149  		return errors.Wrap(err, "starting DB failed!")
   150  	}
   151  
   152  	log.Info().Msg("Starting discovery")
   153  	if err := r.compose("up", "-d", "discovery"); err != nil {
   154  		return errors.Wrap(err, "starting mysterium-api failed!")
   155  	}
   156  
   157  	log.Info().Msg("Starting httpmock")
   158  	if err := r.compose("up", "-d", "http-mock"); err != nil {
   159  		return errors.Wrap(err, "starting http-mock failed!")
   160  	}
   161  
   162  	log.Info().Msg("building runner")
   163  	if err := r.compose("build", "go-runner"); err != nil {
   164  		return fmt.Errorf("could not build go runner %w", err)
   165  	}
   166  
   167  	log.Info().Msg("Deploying contracts")
   168  	err := r.compose("run", "go-runner",
   169  		"/usr/local/bin/deployer",
   170  		"--keystore.directory=./keystore",
   171  		"--ether.address=0x354Bd098B4eF8c9E70B7F21BE2d455DF559705d7",
   172  		fmt.Sprintf("--ether.passphrase=%v", r.etherPassphrase),
   173  		"--geth.url=http://ganache:8545")
   174  	if err != nil {
   175  		return errors.Wrap(err, "failed to deploy contracts!")
   176  	}
   177  
   178  	log.Info().Msg("Deploying contracts to bc2")
   179  	err = r.compose("run", "go-runner",
   180  		"/usr/local/bin/deployer",
   181  		"--keystore.directory=./keystore",
   182  		"--ether.address=0x354Bd098B4eF8c9E70B7F21BE2d455DF559705d7",
   183  		fmt.Sprintf("--ether.passphrase=%v", r.etherPassphrase),
   184  		"--geth.url=ws://ganache2:8545")
   185  	if err != nil {
   186  		return errors.Wrap(err, "failed to deploy contracts!")
   187  	}
   188  
   189  	log.Info().Msg("Seeding http mock")
   190  	if err := seedHTTPMock(); err != nil {
   191  		return fmt.Errorf("could not seed http mock %w", err)
   192  	}
   193  
   194  	log.Info().Msg("Starting transactor")
   195  	if err := r.compose("up", "-d", "transactor", "transactor-sidecar"); err != nil {
   196  		return errors.Wrap(err, "starting transactor failed!")
   197  	}
   198  
   199  	log.Info().Msg("Building app images")
   200  	if err := r.compose("build"); err != nil {
   201  		return errors.Wrap(err, "building app images failed!")
   202  	}
   203  
   204  	return nil
   205  }
   206  
   207  func (r *Runner) startProviderConsumerNodes(providerHost string, services []string) error {
   208  	log.Info().Msg("Starting provider consumer containers")
   209  
   210  	args := []string{
   211  		"up",
   212  		"-d",
   213  		providerHost,
   214  	}
   215  
   216  	for i := range services {
   217  		args = append(args, fmt.Sprintf("myst-consumer-%v", services[i]))
   218  	}
   219  
   220  	if err := r.compose(args...); err != nil {
   221  		return errors.Wrap(err, "starting app containers failed!")
   222  	}
   223  	return nil
   224  }
   225  
   226  func (r *Runner) stopProviderConsumerNodes(providerHost string, services []string) error {
   227  	log.Info().Msg("Stopping provider consumer containers")
   228  
   229  	args := []string{
   230  		"stop",
   231  		providerHost,
   232  	}
   233  
   234  	for i := range services {
   235  		args = append(args, fmt.Sprintf("myst-consumer-%v", services[i]))
   236  	}
   237  
   238  	if err := r.compose(args...); err != nil {
   239  		return errors.Wrap(err, "stopping containers failed!")
   240  	}
   241  	return nil
   242  }
   243  
   244  func seedHTTPMock() error {
   245  	url := "http://localhost:9999/expectation"
   246  	method := "PUT"
   247  
   248  	client := &http.Client{
   249  		Timeout: time.Second * 10,
   250  	}
   251  
   252  	for _, v := range httpMockExpectations {
   253  		marshaled, err := json.Marshal(v)
   254  		if err != nil {
   255  			return err
   256  		}
   257  
   258  		req, err := http.NewRequest(method, url, bytes.NewReader(marshaled))
   259  		if err != nil {
   260  			return err
   261  		}
   262  
   263  		req.Header.Add("Content-Type", "application/json")
   264  		_, err = client.Do(req)
   265  		if err != nil {
   266  			return err
   267  		}
   268  	}
   269  
   270  	return nil
   271  }
   272  
   273  var httpMockExpectations = []HTTPMockExpectation{
   274  	{
   275  		HTTPRequest: HTTPRequest{
   276  			Method: "GET",
   277  			Path:   "/gecko/simple/price",
   278  		},
   279  		HTTPResponse: HTTPResponse{
   280  			StatusCode: http.StatusOK,
   281  			Headers: []Headers{
   282  				{
   283  					Name:   "Content-Type",
   284  					Values: []string{"application/json"},
   285  				},
   286  			},
   287  			Body: `{"mysterium":{"usd":1,"eur":1},"matic-network":{"usd":0.5,"eur":0.5},"ethereum":{"usd":0.00001,"eur":0.00001}}`,
   288  		},
   289  	},
   290  }
   291  
   292  // HTTPMockExpectation the expectation payload.
   293  type HTTPMockExpectation struct {
   294  	HTTPRequest  HTTPRequest  `json:"httpRequest"`
   295  	HTTPResponse HTTPResponse `json:"httpResponse"`
   296  }
   297  
   298  // HTTPRequest the http request properties for http mock.
   299  type HTTPRequest struct {
   300  	Method string `json:"method"`
   301  	Path   string `json:"path"`
   302  }
   303  
   304  // Headers the http response headers for http mock.
   305  type Headers struct {
   306  	Name   string   `json:"name"`
   307  	Values []string `json:"values"`
   308  }
   309  
   310  // HTTPResponse the http response for http mock.
   311  type HTTPResponse struct {
   312  	StatusCode int       `json:"statusCode"`
   313  	Headers    []Headers `json:"headers"`
   314  	Body       string    `json:"body"`
   315  }