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  }