github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/tests/mq_protocol_tests/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 "context" 18 "database/sql" 19 "fmt" 20 "os" 21 "os/exec" 22 23 "github.com/integralist/go-findroot/find" 24 "github.com/pingcap/errors" 25 "github.com/pingcap/log" 26 cerrors "github.com/pingcap/tiflow/pkg/errors" 27 "github.com/pingcap/tiflow/pkg/retry" 28 "go.uber.org/zap" 29 ) 30 31 // DockerComposeOperator represent a docker compose 32 type DockerComposeOperator struct { 33 FileName string 34 Controller string 35 HealthChecker func() error 36 ExecEnv []string 37 } 38 39 // Setup brings up a docker-compose service 40 func (d *DockerComposeOperator) Setup() { 41 cmd := exec.Command("docker-compose", "-f", d.FileName, "up", "-d") 42 cmd.Env = os.Environ() 43 cmd.Env = append(cmd.Env, d.ExecEnv...) 44 runCmdHandleError(cmd) 45 err := waitTiDBStarted(UpstreamDSN) 46 if err != nil { 47 log.Fatal("ping upstream database but not receive a pong", zap.Error(err)) 48 } 49 err = waitTiDBStarted(DownstreamDSN) 50 if err != nil { 51 log.Fatal("ping downstream database but not receive a pong", zap.Error(err)) 52 } 53 d.WaitClusterStarted() 54 } 55 56 // WaitClusterStarted waits the cluster is started and ready 57 func (d *DockerComposeOperator) WaitClusterStarted() { 58 if d.HealthChecker != nil { 59 check := func() error { 60 err := d.HealthChecker() 61 if err != nil { 62 log.Error("check failed", zap.Error(err)) 63 } 64 return err 65 } 66 err := retry.Do(context.Background(), check, 67 retry.WithBackoffBaseDelay(1000), 68 retry.WithBackoffMaxDelay(60*1000), 69 retry.WithMaxTries(120), 70 retry.WithIsRetryableErr(cerrors.IsRetryableError)) 71 if err != nil { 72 log.Fatal("Docker service health check failed after max retries", 73 zap.Error(err)) 74 } 75 } 76 } 77 78 // RestartComponents restarts a docker-compose service 79 func (d *DockerComposeOperator) RestartComponents(names ...string) { 80 for _, name := range names { 81 cmd := exec.Command("docker-compose", "-f", d.FileName, "rm", "-sf", name) 82 cmd.Env = os.Environ() 83 cmd.Env = append(cmd.Env, d.ExecEnv...) 84 runCmdHandleError(cmd) 85 } 86 cmd := exec.Command("docker-compose", "-f", d.FileName, "up", "-d") 87 cmd.Env = os.Environ() 88 cmd.Env = append(cmd.Env, d.ExecEnv...) 89 runCmdHandleError(cmd) 90 } 91 92 func waitTiDBStarted(dsn string) error { 93 return retry.Do(context.Background(), func() error { 94 upstream, err := sql.Open("mysql", dsn) 95 if err != nil { 96 return errors.Trace(err) 97 } 98 defer upstream.Close() 99 err = upstream.Ping() 100 if err != nil { 101 return errors.Trace(err) 102 } 103 return nil 104 }, retry.WithBackoffBaseDelay(1000), retry.WithBackoffMaxDelay(60*1000), retry.WithMaxTries(60), retry.WithIsRetryableErr(cerrors.IsRetryableError)) 105 } 106 107 func runCmdHandleError(cmd *exec.Cmd) []byte { 108 log.Info("Start executing command", zap.String("cmd", cmd.String())) 109 bytes, err := cmd.Output() 110 if err, ok := err.(*exec.ExitError); ok { 111 log.Info("Running command failed", zap.ByteString("stderr", err.Stderr)) 112 } 113 114 if err != nil { 115 log.Fatal("Running command failed", 116 zap.Error(err), 117 zap.String("command", cmd.String()), 118 zap.ByteString("output", bytes)) 119 } 120 121 log.Info("Finished executing command", zap.String("cmd", cmd.String()), zap.ByteString("output", bytes)) 122 return bytes 123 } 124 125 // CdcHealthCheck check cdc cluster health. 126 func CdcHealthCheck(cdcContainer, pdEndpoint string) error { 127 _, err := execInController(cdcContainer, 128 fmt.Sprintf("/cdc cli --pd=\"%s\" changefeed list", pdEndpoint)) 129 return err 130 } 131 132 // execInController provides a way to execute commands inside a container in the service 133 func execInController(controller, shellCmd string) ([]byte, error) { 134 log.Info("Start executing in the Controller container", 135 zap.String("shellCmd", shellCmd), zap.String("container", controller)) 136 cmd := exec.Command("docker", "exec", controller, "sh", "-c", shellCmd) 137 defer log.Info("Finished executing in the Controller container", 138 zap.String("shellCmd", shellCmd), zap.String("container", controller)) 139 return cmd.Output() 140 } 141 142 // DumpStdout dumps all container logs 143 func (d *DockerComposeOperator) DumpStdout() error { 144 log.Info("Dumping container logs") 145 cmd := exec.Command("docker-compose", "-f", d.FileName, "logs", "-t") 146 st, err := find.Repo() 147 if err != nil { 148 log.Fatal("Could not find git repo root", zap.Error(err)) 149 } 150 f, err := os.Create(st.Path + "/deployments/ticdc/docker-compose/logs/stdout.log") 151 if err != nil { 152 return errors.AddStack(err) 153 } 154 defer f.Close() 155 cmd.Stdout = f 156 err = cmd.Run() 157 if err != nil { 158 return errors.AddStack(err) 159 } 160 161 return nil 162 } 163 164 // TearDown terminates a docker-compose service and remove all volumes 165 func (d *DockerComposeOperator) TearDown() { 166 log.Info("Start tearing down docker-compose services") 167 cmd := exec.Command("docker-compose", "-f", d.FileName, "down", "-v") 168 runCmdHandleError(cmd) 169 log.Info("Finished tearing down docker-compose services") 170 } 171 172 // ExecInController provides a way to execute commands inside a container in the service 173 func (d *DockerComposeOperator) ExecInController(shellCmd string) ([]byte, error) { 174 return execInController(d.Controller, shellCmd) 175 }