github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/integration/framework/docker_compose_op.go (about) 1 // Copyright 2020 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package framework 15 16 import ( 17 "database/sql" 18 "os" 19 "os/exec" 20 21 "github.com/pingcap/errors" 22 "github.com/pingcap/log" 23 cerrors "github.com/pingcap/ticdc/pkg/errors" 24 "github.com/pingcap/ticdc/pkg/retry" 25 "go.uber.org/zap" 26 "golang.org/x/net/context" 27 ) 28 29 // DockerComposeOperator represent a docker compose 30 type DockerComposeOperator struct { 31 FileName string 32 Controller string 33 HealthChecker func() error 34 ExecEnv []string 35 } 36 37 // Setup brings up a docker-compose service 38 func (d *DockerComposeOperator) Setup() { 39 cmd := exec.Command("docker-compose", "-f", d.FileName, "up", "-d") 40 cmd.Env = os.Environ() 41 cmd.Env = append(cmd.Env, d.ExecEnv...) 42 runCmdHandleError(cmd) 43 err := waitTiDBStarted(UpstreamDSN) 44 if err != nil { 45 log.Fatal("ping upstream database but not receive a pong", zap.Error(err)) 46 } 47 err = waitTiDBStarted(DownstreamDSN) 48 if err != nil { 49 log.Fatal("ping downstream database but not receive a pong", zap.Error(err)) 50 } 51 d.WaitClusterStarted() 52 } 53 54 // WaitClusterStarted waits the cluster is started and ready 55 func (d *DockerComposeOperator) WaitClusterStarted() { 56 if d.HealthChecker != nil { 57 err := retry.Do(context.Background(), d.HealthChecker, retry.WithBackoffBaseDelay(1000), retry.WithBackoffMaxDelay(60*1000), retry.WithMaxTries(120), retry.WithIsRetryableErr(cerrors.IsRetryableError)) 58 if err != nil { 59 log.Fatal("Docker service health check failed after max retries", zap.Error(err)) 60 } 61 } 62 } 63 64 // RestartComponents restarts a docker-compose service 65 func (d *DockerComposeOperator) RestartComponents(names ...string) { 66 for _, name := range names { 67 cmd := exec.Command("docker-compose", "-f", d.FileName, "rm", "-sf", name) 68 cmd.Env = os.Environ() 69 cmd.Env = append(cmd.Env, d.ExecEnv...) 70 runCmdHandleError(cmd) 71 } 72 cmd := exec.Command("docker-compose", "-f", d.FileName, "up", "-d") 73 cmd.Env = os.Environ() 74 cmd.Env = append(cmd.Env, d.ExecEnv...) 75 runCmdHandleError(cmd) 76 } 77 78 func waitTiDBStarted(dsn string) error { 79 return retry.Do(context.Background(), func() error { 80 upstream, err := sql.Open("mysql", dsn) 81 if err != nil { 82 return errors.Trace(err) 83 } 84 defer upstream.Close() 85 err = upstream.Ping() 86 if err != nil { 87 return errors.Trace(err) 88 } 89 return nil 90 }, retry.WithBackoffBaseDelay(1000), retry.WithBackoffMaxDelay(60*1000), retry.WithMaxTries(60), retry.WithIsRetryableErr(cerrors.IsRetryableError)) 91 } 92 93 func runCmdHandleError(cmd *exec.Cmd) []byte { 94 log.Info("Start executing command", zap.String("cmd", cmd.String())) 95 bytes, err := cmd.Output() 96 if err, ok := err.(*exec.ExitError); ok { 97 log.Info("Running command failed", zap.ByteString("stderr", err.Stderr)) 98 } 99 100 if err != nil { 101 log.Fatal("Running command failed", 102 zap.Error(err), 103 zap.String("command", cmd.String()), 104 zap.ByteString("output", bytes)) 105 } 106 107 log.Info("Finished executing command", zap.String("cmd", cmd.String()), zap.ByteString("output", bytes)) 108 return bytes 109 } 110 111 // DumpStdout dumps all container logs 112 func (d *DockerComposeOperator) DumpStdout() error { 113 log.Info("Dumping container logs") 114 cmd := exec.Command("docker-compose", "-f", d.FileName, "logs", "-t") 115 f, err := os.Create("../docker/logs/stdout.log") 116 if err != nil { 117 return errors.AddStack(err) 118 } 119 defer f.Close() 120 cmd.Stdout = f 121 err = cmd.Run() 122 if err != nil { 123 return errors.AddStack(err) 124 } 125 126 return nil 127 } 128 129 // TearDown terminates a docker-compose service and remove all volumes 130 func (d *DockerComposeOperator) TearDown() { 131 log.Info("Start tearing down docker-compose services") 132 cmd := exec.Command("docker-compose", "-f", d.FileName, "down", "-v") 133 runCmdHandleError(cmd) 134 log.Info("Finished tearing down docker-compose services") 135 } 136 137 // ExecInController provides a way to execute commands inside a container in the service 138 func (d *DockerComposeOperator) ExecInController(shellCmd string) ([]byte, error) { 139 log.Info("Start executing in the Controller container", zap.String("shellCmd", shellCmd)) 140 cmd := exec.Command("docker", "exec", d.Controller, "sh", "-c", shellCmd) 141 defer log.Info("Finished executing in the Controller container", zap.String("shellCmd", shellCmd)) 142 return cmd.Output() 143 }