From 84be1176e8b9e91f2fbb24fac68bb9c0c5f51009 Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 5 Jan 2023 15:02:09 +0700 Subject: [PATCH 01/12] implement docker executor and unit test --- yoda/executor/docker.go | 140 ++++++++++++++++++++++----------- yoda/executor/docker_test.go | 61 ++++++-------- yoda/executor/executor.go | 70 +++++++++++++---- yoda/executor/executor_test.go | 39 +++++++-- 4 files changed, 206 insertions(+), 104 deletions(-) diff --git a/yoda/executor/docker.go b/yoda/executor/docker.go index 53a2c1a44..173630dec 100644 --- a/yoda/executor/docker.go +++ b/yoda/executor/docker.go @@ -3,72 +3,122 @@ package executor import ( "bytes" "context" - "io" - "io/ioutil" - "os" + "encoding/base64" + "fmt" + "net/url" "os/exec" - "path/filepath" + "strconv" "time" - "github.com/google/shlex" - - "github.com/bandprotocol/chain/v2/x/oracle/types" + "github.com/levigross/grequests" ) type DockerExec struct { - image string - timeout time.Duration + image string + name string + timeout time.Duration + portLists chan string + maxTry int } -func NewDockerExec(image string, timeout time.Duration) *DockerExec { - return &DockerExec{image: image, timeout: timeout} +func NewDockerExec(image string, timeout time.Duration, maxTry int, startPort int, endPort int) *DockerExec { + ctx := context.Background() + portLists := make(chan string, 10) + name := "docker-runtime-executor-" + for i := startPort; i <= endPort; i++ { + port := strconv.Itoa(i) + StartContainer(name, ctx, port, image) + portLists <- port + } + + return &DockerExec{image: image, name: name, timeout: timeout, portLists: portLists, maxTry: maxTry} } -func (e *DockerExec) Exec(code []byte, arg string, env interface{}) (ExecResult, error) { - // TODO: Handle env if we are to revive Docker - dir, err := ioutil.TempDir("/tmp", "executor") - if err != nil { - return ExecResult{}, err - } - defer os.RemoveAll(dir) - err = ioutil.WriteFile(filepath.Join(dir, "exec"), code, 0777) - if err != nil { - return ExecResult{}, err - } - name := filepath.Base(dir) - args, err := shlex.Split(arg) - if err != nil { - return ExecResult{}, err - } +func StartContainer(name string, ctx context.Context, port string, image string) error { + exec.Command("docker", "kill", name+port).Run() dockerArgs := append([]string{ "run", "--rm", - "-v", dir + ":/scratch:ro", - "--name", name, - e.image, - "/scratch/exec", - }, args...) - ctx, cancel := context.WithTimeout(context.Background(), e.timeout) - defer cancel() + "--name", name + port, + "-p", port + ":5000", + "--memory=512m", + image, + }) + cmd := exec.CommandContext(ctx, "docker", dockerArgs...) var buf bytes.Buffer cmd.Stdout = &buf cmd.Stderr = &buf - err = cmd.Run() - if ctx.Err() == context.DeadlineExceeded { - exec.Command("docker", "kill", name).Start() - return ExecResult{}, ErrExecutionimeout - } - exitCode := uint32(0) + err := cmd.Start() + return err +} + +func (e *DockerExec) PostRequest( + code []byte, + arg string, + env interface{}, + name string, + ctx context.Context, + port string, +) (ExecResult, error) { + executable := base64.StdEncoding.EncodeToString(code) + resp, err := grequests.Post( + "http://127.0.0.1:"+port, + &grequests.RequestOptions{ + Headers: map[string]string{ + "Content-Type": "application/json", + }, + JSON: map[string]interface{}{ + "executable": executable, + "calldata": arg, + "timeout": e.timeout.Milliseconds(), + "env": env, + }, + RequestTimeout: e.timeout, + }, + ) + if err != nil { - if exitError, ok := err.(*exec.ExitError); ok { - exitCode = uint32(exitError.ExitCode()) - } else { + urlErr, ok := err.(*url.Error) + if !ok || !urlErr.Timeout() { return ExecResult{}, err } + // Return timeout code + return ExecResult{Output: []byte{}, Code: 111}, nil } - output, err := ioutil.ReadAll(io.LimitReader(&buf, int64(types.DefaultMaxReportDataSize))) + + if !resp.Ok { + return ExecResult{}, ErrRestNotOk + } + + r := externalExecutionResponse{} + err = resp.JSON(&r) + if err != nil { return ExecResult{}, err } - return ExecResult{Output: output, Code: exitCode}, nil + + go func() { + StartContainer(name, ctx, port, e.image) + e.portLists <- port + }() + if r.Returncode == 0 { + return ExecResult{Output: []byte(r.Stdout), Code: 0, Version: r.Version}, nil + } else { + return ExecResult{Output: []byte(r.Stderr), Code: r.Returncode, Version: r.Version}, nil + } +} + +func (e *DockerExec) Exec(code []byte, arg string, env interface{}) (ExecResult, error) { + ctx := context.Background() + port := <-e.portLists + errs := []error{} + for i := 0; i < e.maxTry; i++ { + execResult, err := e.PostRequest(code, arg, env, e.name, ctx, port) + if err == nil { + return execResult, err + } + errs = append(errs, err) + time.Sleep(500 * time.Millisecond) + } + return ExecResult{}, fmt.Errorf(ErrReachMaxTry.Error()+", tried errors: %#q", errs) } diff --git a/yoda/executor/docker_test.go b/yoda/executor/docker_test.go index 2797e5324..5b3db4c58 100644 --- a/yoda/executor/docker_test.go +++ b/yoda/executor/docker_test.go @@ -2,48 +2,31 @@ package executor import ( "testing" -) - -func TestDockerSuccess(t *testing.T) { - // TODO: Enable test when CI has docker installed. - // e := NewDockerExec("bandprotocol/runtime:1.0.2", 10*time.Second) - // res, err := e.Exec([]byte(`#!/usr/bin/env python3 - // import json - // import urllib.request - // import sys + "time" - // BINANCE_URL = "https://api.binance.com/api/v1/depth?symbol={}USDT&limit=5" - - // def make_json_request(url): - // req = urllib.request.Request(url) - // req.add_header( - // "User-Agent", - // "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", - // ) - // return json.loads(urllib.request.urlopen(req).read()) - - // def main(symbol): - // res = make_json_request(BINANCE_URL.format(symbol)) - // bid = float(res["bids"][0][0]) - // ask = float(res["asks"][0][0]) - // return (bid + ask) / 2 + "github.com/stretchr/testify/require" +) - // if __name__ == "__main__": - // try: - // print(main(*sys.argv[1:])) - // except Exception as e: - // print(str(e), file=sys.stderr) - // sys.exit(1) - // `), "BTC") - // fmt.Println(string(res.Output), res.Code, err) - // require.True(t, false) +func SetupDockerTest(t *testing.T) { } -func TestDockerLongStdout(t *testing.T) { +func TestDockerSuccess(t *testing.T) { // TODO: Enable test when CI has docker installed. - // e := NewDockerExec("bandprotocol/runtime:1.0.2", 10*time.Second) - // res, err := e.Exec([]byte(`#!/usr/bin/env python3 - // print("A"*1000)`), "BTC") - // fmt.Println(string(res.Output), res.Code, err) - // require.True(t, false) + // Prerequisite: please build docker image before running test + e := NewDockerExec("python-docker", 120*time.Second, 1, 5000, 5009) + for i := 0; i < 20; i++ { + res, err := e.Exec([]byte( + "#!/usr/bin/env python3\nimport os\nimport sys\nprint(sys.argv[1], os.getenv('BAND_CHAIN_ID'))", + ), "TEST_ARG", map[string]interface{}{ + "BAND_CHAIN_ID": "test-chain-id", + "BAND_VALIDATOR": "test-validator", + "BAND_REQUEST_ID": "test-request-id", + "BAND_EXTERNAL_ID": "test-external-id", + "BAND_REPORTER": "test-reporter", + "BAND_SIGNATURE": "test-signature", + }) + require.Equal(t, []byte("TEST_ARG test-chain-id\n"), res.Output) + require.Equal(t, uint32(0), res.Code) + require.NoError(t, err) + } } diff --git a/yoda/executor/executor.go b/yoda/executor/executor.go index 86251e5c3..ea68a6d35 100644 --- a/yoda/executor/executor.go +++ b/yoda/executor/executor.go @@ -4,17 +4,21 @@ import ( "errors" "fmt" "net/url" + "strconv" "strings" "time" ) const ( - flagQueryTimeout = "timeout" + flagQueryTimeout = "timeout" + flagQueryMaxTry = "maxTry" + flagQueryPortRange = "portRange" ) var ( ErrExecutionimeout = errors.New("execution timeout") ErrRestNotOk = errors.New("rest return non 2XX response") + ErrReachMaxTry = errors.New("execution reach max try") ) type ExecResult struct { @@ -33,7 +37,7 @@ var testProgram []byte = []byte( // NewExecutor returns executor by name and executor URL func NewExecutor(executor string) (exec Executor, err error) { - name, base, timeout, err := parseExecutor(executor) + name, base, timeout, maxTry, startPort, endPort, err := parseExecutor(executor) if err != nil { return nil, err } @@ -41,7 +45,13 @@ func NewExecutor(executor string) (exec Executor, err error) { case "rest": exec = NewRestExec(base, timeout) case "docker": - return nil, fmt.Errorf("Docker executor is currently not supported") + if endPort <= startPort { + return nil, fmt.Errorf("portRange invalid: startPort: %d, endPort: %d", startPort, endPort) + } + if maxTry < 1 { + return nil, fmt.Errorf("maxTry invalid: %d", maxTry) + } + exec = NewDockerExec(base, timeout, maxTry, startPort, endPort) default: return nil, fmt.Errorf("Invalid executor name: %s, base: %s", name, base) } @@ -68,29 +78,61 @@ func NewExecutor(executor string) (exec Executor, err error) { return exec, nil } -// parseExecutor splits the executor string in the form of "name:base?timeout=" into parts. -func parseExecutor(executorStr string) (name string, base string, timeout time.Duration, err error) { +// parseExecutor splits the executor string in the form of "name:base?timeout=&maxTry=&portRange=" into parts. +func parseExecutor( + executorStr string, +) (name string, base string, timeout time.Duration, maxTry int, startPort int, endPort int, err error) { executor := strings.SplitN(executorStr, ":", 2) if len(executor) != 2 { - return "", "", 0, fmt.Errorf("Invalid executor, cannot parse executor: %s", executorStr) + return "", "", 0, 0, 0, 0, fmt.Errorf("Invalid executor, cannot parse executor: %s", executorStr) } u, err := url.Parse(executor[1]) if err != nil { - return "", "", 0, fmt.Errorf("Invalid url, cannot parse %s to url with error: %s", executor[1], err.Error()) + return "", "", 0, 0, 0, 0, fmt.Errorf( + "Invalid url, cannot parse %s to url with error: %s", + executor[1], + err.Error(), + ) } query := u.Query() timeoutStr := query.Get(flagQueryTimeout) if timeoutStr == "" { - return "", "", 0, fmt.Errorf("Invalid timeout, executor requires query timeout") + return "", "", 0, 0, 0, 0, fmt.Errorf("Invalid timeout, executor requires query timeout") } - // Remove timeout from query because we need to return `base` - query.Del(flagQueryTimeout) - u.RawQuery = query.Encode() - timeout, err = time.ParseDuration(timeoutStr) if err != nil { - return "", "", 0, fmt.Errorf("Invalid timeout, cannot parse duration with error: %s", err.Error()) + return "", "", 0, 0, 0, 0, fmt.Errorf("Invalid timeout, cannot parse duration with error: %s", err.Error()) + } + + maxTryStr := query.Get(flagQueryMaxTry) + if maxTryStr == "" { + maxTryStr = "1" + } + maxTry, err = strconv.Atoi(maxTryStr) + if err != nil { + return "", "", 0, 0, 0, 0, fmt.Errorf("Invalid maxTry, cannot parse integer with error: %s", err.Error()) + } + + portRangeStr := query.Get(flagQueryPortRange) + ports := strings.SplitN(portRangeStr, "-", 2) + if len(ports) != 2 { + ports = []string{"0", "0"} } - return executor[0], u.String(), timeout, nil + startPort, err = strconv.Atoi(ports[0]) + if err != nil { + return "", "", 0, 0, 0, 0, fmt.Errorf("Invalid portRange, cannot parse integer with error: %s", err.Error()) + } + endPort, err = strconv.Atoi(ports[1]) + if err != nil { + return "", "", 0, 0, 0, 0, fmt.Errorf("Invalid portRange, cannot parse integer with error: %s", err.Error()) + } + + // Remove timeout from query because we need to return `base` + query.Del(flagQueryTimeout) + query.Del(flagQueryMaxTry) + query.Del(flagQueryPortRange) + + u.RawQuery = query.Encode() + return executor[0], u.String(), timeout, maxTry, startPort, endPort, nil } diff --git a/yoda/executor/executor_test.go b/yoda/executor/executor_test.go index 44672f74e..1fe78c027 100644 --- a/yoda/executor/executor_test.go +++ b/yoda/executor/executor_test.go @@ -8,19 +8,26 @@ import ( ) func TestParseExecutor(t *testing.T) { - name, url, timeout, err := parseExecutor("beeb:www.beebprotocol.com?timeout=3s") + name, url, timeout, maxTry, startPort, endPort, err := parseExecutor("beeb:www.beebprotocol.com?timeout=3s") require.Equal(t, name, "beeb") require.Equal(t, timeout, 3*time.Second) require.Equal(t, url, "www.beebprotocol.com") + require.Equal(t, maxTry, 1) + require.Equal(t, startPort, 0) + require.Equal(t, endPort, 0) require.NoError(t, err) - name, url, timeout, err = parseExecutor("beeb2:www.beeb.com/anna/kondanna?timeout=300ms") + name, url, timeout, _, _, _, err = parseExecutor( + "beeb2:www.beeb.com/anna/kondanna?timeout=300ms", + ) require.Equal(t, name, "beeb2") require.Equal(t, timeout, 300*time.Millisecond) require.Equal(t, url, "www.beeb.com/anna/kondanna") require.NoError(t, err) - name, url, timeout, err = parseExecutor("beeb3:https://bandprotocol.com/gg/gg2/bandchain?timeout=1s300ms") + name, url, timeout, _, _, _, err = parseExecutor( + "beeb3:https://bandprotocol.com/gg/gg2/bandchain?timeout=1s300ms", + ) require.Equal(t, name, "beeb3") require.Equal(t, timeout, 1*time.Second+300*time.Millisecond) require.Equal(t, url, "https://bandprotocol.com/gg/gg2/bandchain") @@ -28,16 +35,36 @@ func TestParseExecutor(t *testing.T) { } func TestParseExecutorWithoutRawQuery(t *testing.T) { - _, _, _, err := parseExecutor("beeb:www.beebprotocol.com") + _, _, _, _, _, _, err := parseExecutor("beeb:www.beebprotocol.com") require.EqualError(t, err, "Invalid timeout, executor requires query timeout") } func TestParseExecutorInvalidExecutorError(t *testing.T) { - _, _, _, err := parseExecutor("beeb") + _, _, _, _, _, _, err := parseExecutor("beeb") require.EqualError(t, err, "Invalid executor, cannot parse executor: beeb") } func TestParseExecutorInvalidTimeoutError(t *testing.T) { - _, _, _, err := parseExecutor("beeb:www.beebprotocol.com?timeout=beeb") + _, _, _, _, _, _, err := parseExecutor("beeb:www.beebprotocol.com?timeout=beeb") require.EqualError(t, err, "Invalid timeout, cannot parse duration with error: time: invalid duration \"beeb\"") } + +func TestExecuteDockerExecutorSuccess(t *testing.T) { + e, err := NewExecutor("docker:python-docker-2?timeout=120s&maxTry=5&portRange=5000-5009") + require.NoError(t, err) + for i := 0; i < 20; i++ { + res, err := e.Exec([]byte( + "#!/usr/bin/env python3\nimport os\nimport sys\nprint(sys.argv[1], os.getenv('BAND_CHAIN_ID'))", + ), "TEST_ARG", map[string]interface{}{ + "BAND_CHAIN_ID": "test-chain-id", + "BAND_VALIDATOR": "test-validator", + "BAND_REQUEST_ID": "test-request-id", + "BAND_EXTERNAL_ID": "test-external-id", + "BAND_REPORTER": "test-reporter", + "BAND_SIGNATURE": "test-signature", + }) + require.Equal(t, []byte("TEST_ARG test-chain-id\n"), res.Output) + require.Equal(t, uint32(0), res.Code) + require.NoError(t, err) + } +} From 68a4655df68549daca88aecc98a01c8a0955d6ca Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 5 Jan 2023 15:26:21 +0700 Subject: [PATCH 02/12] add warning comment and change docker to use docker in docker hub --- yoda/executor/docker.go | 3 ++- yoda/executor/executor.go | 1 + yoda/executor/executor_test.go | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/yoda/executor/docker.go b/yoda/executor/docker.go index 173630dec..8b3287944 100644 --- a/yoda/executor/docker.go +++ b/yoda/executor/docker.go @@ -13,6 +13,7 @@ import ( "github.com/levigross/grequests" ) +// Only use in testnet. No intensive testing, use at your own risk type DockerExec struct { image string name string @@ -62,7 +63,7 @@ func (e *DockerExec) PostRequest( ) (ExecResult, error) { executable := base64.StdEncoding.EncodeToString(code) resp, err := grequests.Post( - "http://127.0.0.1:"+port, + "http://localhost:"+port, &grequests.RequestOptions{ Headers: map[string]string{ "Content-Type": "application/json", diff --git a/yoda/executor/executor.go b/yoda/executor/executor.go index ea68a6d35..7ff8563c8 100644 --- a/yoda/executor/executor.go +++ b/yoda/executor/executor.go @@ -45,6 +45,7 @@ func NewExecutor(executor string) (exec Executor, err error) { case "rest": exec = NewRestExec(base, timeout) case "docker": + // Only use in testnet. No intensive testing, use at your own risk if endPort <= startPort { return nil, fmt.Errorf("portRange invalid: startPort: %d, endPort: %d", startPort, endPort) } diff --git a/yoda/executor/executor_test.go b/yoda/executor/executor_test.go index 1fe78c027..b4ea0695a 100644 --- a/yoda/executor/executor_test.go +++ b/yoda/executor/executor_test.go @@ -50,7 +50,9 @@ func TestParseExecutorInvalidTimeoutError(t *testing.T) { } func TestExecuteDockerExecutorSuccess(t *testing.T) { - e, err := NewExecutor("docker:python-docker-2?timeout=120s&maxTry=5&portRange=5000-5009") + e, err := NewExecutor( + "docker:ongartbandprotocol/band-testing:python-runtime?timeout=120s&maxTry=5&portRange=5000-5009", + ) require.NoError(t, err) for i := 0; i < 20; i++ { res, err := e.Exec([]byte( From 66a61402ec2f3ae473d43b87301e8eee0a80b4b1 Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 5 Jan 2023 15:31:31 +0700 Subject: [PATCH 03/12] change maxTry in testcases --- yoda/executor/docker_test.go | 2 +- yoda/executor/executor_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/yoda/executor/docker_test.go b/yoda/executor/docker_test.go index 5b3db4c58..7aee467f8 100644 --- a/yoda/executor/docker_test.go +++ b/yoda/executor/docker_test.go @@ -13,7 +13,7 @@ func SetupDockerTest(t *testing.T) { func TestDockerSuccess(t *testing.T) { // TODO: Enable test when CI has docker installed. // Prerequisite: please build docker image before running test - e := NewDockerExec("python-docker", 120*time.Second, 1, 5000, 5009) + e := NewDockerExec("python-docker", 120*time.Second, 10, 5000, 5009) for i := 0; i < 20; i++ { res, err := e.Exec([]byte( "#!/usr/bin/env python3\nimport os\nimport sys\nprint(sys.argv[1], os.getenv('BAND_CHAIN_ID'))", diff --git a/yoda/executor/executor_test.go b/yoda/executor/executor_test.go index b4ea0695a..6f8d53c15 100644 --- a/yoda/executor/executor_test.go +++ b/yoda/executor/executor_test.go @@ -51,7 +51,7 @@ func TestParseExecutorInvalidTimeoutError(t *testing.T) { func TestExecuteDockerExecutorSuccess(t *testing.T) { e, err := NewExecutor( - "docker:ongartbandprotocol/band-testing:python-runtime?timeout=120s&maxTry=5&portRange=5000-5009", + "docker:ongartbandprotocol/band-testing:python-runtime?timeout=120s&maxTry=10&portRange=5000-5009", ) require.NoError(t, err) for i := 0; i < 20; i++ { From 2a8de0737a2289cec66061096f208c426ab25df2 Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 5 Jan 2023 15:40:05 +0700 Subject: [PATCH 04/12] change image in unit test --- yoda/executor/docker_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yoda/executor/docker_test.go b/yoda/executor/docker_test.go index 7aee467f8..e92f6500d 100644 --- a/yoda/executor/docker_test.go +++ b/yoda/executor/docker_test.go @@ -13,7 +13,7 @@ func SetupDockerTest(t *testing.T) { func TestDockerSuccess(t *testing.T) { // TODO: Enable test when CI has docker installed. // Prerequisite: please build docker image before running test - e := NewDockerExec("python-docker", 120*time.Second, 10, 5000, 5009) + e := NewDockerExec("ongartbandprotocol/band-testing", 120*time.Second, 10, 5000, 5009) for i := 0; i < 20; i++ { res, err := e.Exec([]byte( "#!/usr/bin/env python3\nimport os\nimport sys\nprint(sys.argv[1], os.getenv('BAND_CHAIN_ID'))", From 2555da27138ffdcceefbe1597335d827f7914436 Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 5 Jan 2023 15:45:28 +0700 Subject: [PATCH 05/12] fix image typo --- yoda/executor/docker_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yoda/executor/docker_test.go b/yoda/executor/docker_test.go index e92f6500d..8a8d00798 100644 --- a/yoda/executor/docker_test.go +++ b/yoda/executor/docker_test.go @@ -13,7 +13,7 @@ func SetupDockerTest(t *testing.T) { func TestDockerSuccess(t *testing.T) { // TODO: Enable test when CI has docker installed. // Prerequisite: please build docker image before running test - e := NewDockerExec("ongartbandprotocol/band-testing", 120*time.Second, 10, 5000, 5009) + e := NewDockerExec("ongartbandprotocol/band-testing:python-runtime", 120*time.Second, 10, 5000, 5009) for i := 0; i < 20; i++ { res, err := e.Exec([]byte( "#!/usr/bin/env python3\nimport os\nimport sys\nprint(sys.argv[1], os.getenv('BAND_CHAIN_ID'))", From ddf61047d3199431925c1c498b817b1e00dfab78 Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 5 Jan 2023 16:42:57 +0700 Subject: [PATCH 06/12] change maxTry to 100 --- yoda/executor/docker_test.go | 2 +- yoda/executor/executor_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/yoda/executor/docker_test.go b/yoda/executor/docker_test.go index 8a8d00798..735c7d563 100644 --- a/yoda/executor/docker_test.go +++ b/yoda/executor/docker_test.go @@ -13,7 +13,7 @@ func SetupDockerTest(t *testing.T) { func TestDockerSuccess(t *testing.T) { // TODO: Enable test when CI has docker installed. // Prerequisite: please build docker image before running test - e := NewDockerExec("ongartbandprotocol/band-testing:python-runtime", 120*time.Second, 10, 5000, 5009) + e := NewDockerExec("ongartbandprotocol/band-testing:python-runtime", 120*time.Second, 100, 5000, 5009) for i := 0; i < 20; i++ { res, err := e.Exec([]byte( "#!/usr/bin/env python3\nimport os\nimport sys\nprint(sys.argv[1], os.getenv('BAND_CHAIN_ID'))", diff --git a/yoda/executor/executor_test.go b/yoda/executor/executor_test.go index 6f8d53c15..1b00a3a25 100644 --- a/yoda/executor/executor_test.go +++ b/yoda/executor/executor_test.go @@ -51,7 +51,7 @@ func TestParseExecutorInvalidTimeoutError(t *testing.T) { func TestExecuteDockerExecutorSuccess(t *testing.T) { e, err := NewExecutor( - "docker:ongartbandprotocol/band-testing:python-runtime?timeout=120s&maxTry=10&portRange=5000-5009", + "docker:ongartbandprotocol/band-testing:python-runtime?timeout=120s&maxTry=100&portRange=5000-5009", ) require.NoError(t, err) for i := 0; i < 20; i++ { From 08e4a19d830a125669916febf1a71a5c268db852 Mon Sep 17 00:00:00 2001 From: colmazia Date: Fri, 6 Jan 2023 12:44:18 +0700 Subject: [PATCH 07/12] fix channel size bug --- yoda/executor/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yoda/executor/docker.go b/yoda/executor/docker.go index 8b3287944..93db137ce 100644 --- a/yoda/executor/docker.go +++ b/yoda/executor/docker.go @@ -24,7 +24,7 @@ type DockerExec struct { func NewDockerExec(image string, timeout time.Duration, maxTry int, startPort int, endPort int) *DockerExec { ctx := context.Background() - portLists := make(chan string, 10) + portLists := make(chan string, endPort-startPort+1) name := "docker-runtime-executor-" for i := startPort; i <= endPort; i++ { port := strconv.Itoa(i) From 64fe43bb156ad603aeee8b685bc2ffc3e4781578 Mon Sep 17 00:00:00 2001 From: colmazia Date: Wed, 11 Jan 2023 17:25:33 +0700 Subject: [PATCH 08/12] change container kill to restart --- yoda/executor/docker.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/yoda/executor/docker.go b/yoda/executor/docker.go index 93db137ce..b654f6266 100644 --- a/yoda/executor/docker.go +++ b/yoda/executor/docker.go @@ -99,7 +99,12 @@ func (e *DockerExec) PostRequest( } go func() { - StartContainer(name, ctx, port, e.image) + // StartContainer(name, ctx, port, e.image) + cmd := exec.CommandContext(ctx, "docker", "restart", name+port) + err := cmd.Run() + for err != nil { + err = StartContainer(name, ctx, port, e.image) + } e.portLists <- port }() if r.Returncode == 0 { From bbf65db454a8b547d1406fe532898a95bca411e7 Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 12 Jan 2023 13:55:44 +0700 Subject: [PATCH 09/12] remove --rm and context --- yoda/executor/docker.go | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/yoda/executor/docker.go b/yoda/executor/docker.go index b654f6266..cff301384 100644 --- a/yoda/executor/docker.go +++ b/yoda/executor/docker.go @@ -23,29 +23,28 @@ type DockerExec struct { } func NewDockerExec(image string, timeout time.Duration, maxTry int, startPort int, endPort int) *DockerExec { - ctx := context.Background() portLists := make(chan string, endPort-startPort+1) name := "docker-runtime-executor-" for i := startPort; i <= endPort; i++ { port := strconv.Itoa(i) - StartContainer(name, ctx, port, image) + StartContainer(name, port, image) portLists <- port } return &DockerExec{image: image, name: name, timeout: timeout, portLists: portLists, maxTry: maxTry} } -func StartContainer(name string, ctx context.Context, port string, image string) error { +func StartContainer(name string, port string, image string) error { exec.Command("docker", "kill", name+port).Run() dockerArgs := append([]string{ - "run", "--rm", + "run", "--name", name + port, "-p", port + ":5000", "--memory=512m", image, }) - cmd := exec.CommandContext(ctx, "docker", dockerArgs...) + cmd := exec.CommandContext(context.Background(), "docker", dockerArgs...) var buf bytes.Buffer cmd.Stdout = &buf cmd.Stderr = &buf @@ -58,7 +57,6 @@ func (e *DockerExec) PostRequest( arg string, env interface{}, name string, - ctx context.Context, port string, ) (ExecResult, error) { executable := base64.StdEncoding.EncodeToString(code) @@ -99,11 +97,11 @@ func (e *DockerExec) PostRequest( } go func() { - // StartContainer(name, ctx, port, e.image) - cmd := exec.CommandContext(ctx, "docker", "restart", name+port) + // StartContainer(name, port, e.image) + cmd := exec.CommandContext(context.Background(), "docker", "restart", name+port) err := cmd.Run() for err != nil { - err = StartContainer(name, ctx, port, e.image) + err = StartContainer(name, port, e.image) } e.portLists <- port }() @@ -115,11 +113,10 @@ func (e *DockerExec) PostRequest( } func (e *DockerExec) Exec(code []byte, arg string, env interface{}) (ExecResult, error) { - ctx := context.Background() port := <-e.portLists errs := []error{} for i := 0; i < e.maxTry; i++ { - execResult, err := e.PostRequest(code, arg, env, e.name, ctx, port) + execResult, err := e.PostRequest(code, arg, env, e.name, port) if err == nil { return execResult, err } From c2aeda5b89ecdf2ace2323b047b8cb11169aae7b Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 12 Jan 2023 14:34:15 +0700 Subject: [PATCH 10/12] always restart --- yoda/executor/docker.go | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/yoda/executor/docker.go b/yoda/executor/docker.go index cff301384..0260b004c 100644 --- a/yoda/executor/docker.go +++ b/yoda/executor/docker.go @@ -35,20 +35,23 @@ func NewDockerExec(image string, timeout time.Duration, maxTry int, startPort in } func StartContainer(name string, port string, image string) error { - exec.Command("docker", "kill", name+port).Run() - dockerArgs := append([]string{ - "run", - "--name", name + port, - "-p", port + ":5000", - "--memory=512m", - image, - }) + err := exec.Command("docker", "restart", name+port).Run() + if err != nil { + dockerArgs := append([]string{ + "run", + "--name", name + port, + "-p", port + ":5000", + "--restart=always", + "--memory=512m", + image, + }) - cmd := exec.CommandContext(context.Background(), "docker", dockerArgs...) - var buf bytes.Buffer - cmd.Stdout = &buf - cmd.Stderr = &buf - err := cmd.Start() + cmd := exec.CommandContext(context.Background(), "docker", dockerArgs...) + var buf bytes.Buffer + cmd.Stdout = &buf + cmd.Stderr = &buf + err = cmd.Start() + } return err } @@ -98,8 +101,7 @@ func (e *DockerExec) PostRequest( go func() { // StartContainer(name, port, e.image) - cmd := exec.CommandContext(context.Background(), "docker", "restart", name+port) - err := cmd.Run() + err := exec.Command("docker", "restart", name+port).Run() for err != nil { err = StartContainer(name, port, e.image) } From e62278b32ebbe5e9ddd1590b6607303673cc1109 Mon Sep 17 00:00:00 2001 From: colmazia Date: Fri, 13 Jan 2023 15:47:49 +0700 Subject: [PATCH 11/12] change executor to 1 container --- yoda/executor/docker.go | 37 +++++++++++++++++----------------- yoda/executor/docker_test.go | 2 +- yoda/executor/executor.go | 2 +- yoda/executor/executor_test.go | 2 +- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/yoda/executor/docker.go b/yoda/executor/docker.go index 0260b004c..f9f97fa0c 100644 --- a/yoda/executor/docker.go +++ b/yoda/executor/docker.go @@ -15,23 +15,24 @@ import ( // Only use in testnet. No intensive testing, use at your own risk type DockerExec struct { - image string - name string - timeout time.Duration - portLists chan string - maxTry int + image string + name string + timeout time.Duration + // portLists chan string + port string + maxTry int } func NewDockerExec(image string, timeout time.Duration, maxTry int, startPort int, endPort int) *DockerExec { - portLists := make(chan string, endPort-startPort+1) + // portLists := make(chan string, endPort-startPort+1) name := "docker-runtime-executor-" for i := startPort; i <= endPort; i++ { port := strconv.Itoa(i) StartContainer(name, port, image) - portLists <- port + // portLists <- port } - return &DockerExec{image: image, name: name, timeout: timeout, portLists: portLists, maxTry: maxTry} + return &DockerExec{image: image, name: name, timeout: timeout, port: strconv.Itoa(startPort), maxTry: maxTry} } func StartContainer(name string, port string, image string) error { @@ -99,14 +100,14 @@ func (e *DockerExec) PostRequest( return ExecResult{}, err } - go func() { - // StartContainer(name, port, e.image) - err := exec.Command("docker", "restart", name+port).Run() - for err != nil { - err = StartContainer(name, port, e.image) - } - e.portLists <- port - }() + // go func() { + // // StartContainer(name, port, e.image) + // err := exec.Command("docker", "restart", name+port).Run() + // for err != nil { + // err = StartContainer(name, port, e.image) + // } + // e.portLists <- port + // }() if r.Returncode == 0 { return ExecResult{Output: []byte(r.Stdout), Code: 0, Version: r.Version}, nil } else { @@ -115,10 +116,10 @@ func (e *DockerExec) PostRequest( } func (e *DockerExec) Exec(code []byte, arg string, env interface{}) (ExecResult, error) { - port := <-e.portLists + // port := <-e.portLists errs := []error{} for i := 0; i < e.maxTry; i++ { - execResult, err := e.PostRequest(code, arg, env, e.name, port) + execResult, err := e.PostRequest(code, arg, env, e.name, e.port) if err == nil { return execResult, err } diff --git a/yoda/executor/docker_test.go b/yoda/executor/docker_test.go index 735c7d563..1ef5a1125 100644 --- a/yoda/executor/docker_test.go +++ b/yoda/executor/docker_test.go @@ -13,7 +13,7 @@ func SetupDockerTest(t *testing.T) { func TestDockerSuccess(t *testing.T) { // TODO: Enable test when CI has docker installed. // Prerequisite: please build docker image before running test - e := NewDockerExec("ongartbandprotocol/band-testing:python-runtime", 120*time.Second, 100, 5000, 5009) + e := NewDockerExec("ongartbandprotocol/docker-executor:0.2.0", 12*time.Second, 100, 30001, 30001) for i := 0; i < 20; i++ { res, err := e.Exec([]byte( "#!/usr/bin/env python3\nimport os\nimport sys\nprint(sys.argv[1], os.getenv('BAND_CHAIN_ID'))", diff --git a/yoda/executor/executor.go b/yoda/executor/executor.go index 7ff8563c8..6f40460c4 100644 --- a/yoda/executor/executor.go +++ b/yoda/executor/executor.go @@ -46,7 +46,7 @@ func NewExecutor(executor string) (exec Executor, err error) { exec = NewRestExec(base, timeout) case "docker": // Only use in testnet. No intensive testing, use at your own risk - if endPort <= startPort { + if endPort < startPort { return nil, fmt.Errorf("portRange invalid: startPort: %d, endPort: %d", startPort, endPort) } if maxTry < 1 { diff --git a/yoda/executor/executor_test.go b/yoda/executor/executor_test.go index 1b00a3a25..b538f1388 100644 --- a/yoda/executor/executor_test.go +++ b/yoda/executor/executor_test.go @@ -51,7 +51,7 @@ func TestParseExecutorInvalidTimeoutError(t *testing.T) { func TestExecuteDockerExecutorSuccess(t *testing.T) { e, err := NewExecutor( - "docker:ongartbandprotocol/band-testing:python-runtime?timeout=120s&maxTry=100&portRange=5000-5009", + "docker:ongartbandprotocol/band-testing:python-runtime-2?timeout=12s&maxTry=100&portRange=40001-40001", ) require.NoError(t, err) for i := 0; i < 20; i++ { From fe80ab0103f57b075fdb6eaa763c42cb1274a762 Mon Sep 17 00:00:00 2001 From: colmazia Date: Tue, 24 Jan 2023 12:45:53 +0700 Subject: [PATCH 12/12] change docker image version --- yoda/executor/executor_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yoda/executor/executor_test.go b/yoda/executor/executor_test.go index b538f1388..ffa8541d7 100644 --- a/yoda/executor/executor_test.go +++ b/yoda/executor/executor_test.go @@ -51,10 +51,10 @@ func TestParseExecutorInvalidTimeoutError(t *testing.T) { func TestExecuteDockerExecutorSuccess(t *testing.T) { e, err := NewExecutor( - "docker:ongartbandprotocol/band-testing:python-runtime-2?timeout=12s&maxTry=100&portRange=40001-40001", + "docker:ongartbandprotocol/docker-executor:0.2.7?timeout=12s&maxTry=100&portRange=40001-40001", ) require.NoError(t, err) - for i := 0; i < 20; i++ { + for i := 0; i < 100; i++ { res, err := e.Exec([]byte( "#!/usr/bin/env python3\nimport os\nimport sys\nprint(sys.argv[1], os.getenv('BAND_CHAIN_ID'))", ), "TEST_ARG", map[string]interface{}{