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 }