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  }