github.com/hyperledger-labs/bdls@v2.1.1+incompatible/integration/runner/zookeeper.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package runner
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"io"
    13  	"net"
    14  	"os"
    15  	"sync"
    16  	"time"
    17  
    18  	docker "github.com/fsouza/go-dockerclient"
    19  	"github.com/pkg/errors"
    20  	"github.com/tedsuo/ifrit"
    21  )
    22  
    23  const ZooKeeperDefaultImage = "confluentinc/cp-zookeeper:5.3.1"
    24  
    25  type ZooKeeper struct {
    26  	Client         *docker.Client
    27  	Image          string
    28  	HostIP         string
    29  	HostPort       []int
    30  	ContainerPorts []docker.Port
    31  	Name           string
    32  	StartTimeout   time.Duration
    33  
    34  	NetworkName string
    35  	ClientPort  docker.Port
    36  	LeaderPort  docker.Port
    37  	PeerPort    docker.Port
    38  	ZooMyID     int
    39  	ZooServers  string
    40  
    41  	ErrorStream  io.Writer
    42  	OutputStream io.Writer
    43  
    44  	containerID      string
    45  	containerAddress string
    46  	address          string
    47  
    48  	mutex   sync.Mutex
    49  	stopped bool
    50  }
    51  
    52  func (z *ZooKeeper) Run(sigCh <-chan os.Signal, ready chan<- struct{}) error {
    53  	if z.Image == "" {
    54  		z.Image = ZooKeeperDefaultImage
    55  	}
    56  
    57  	if z.Name == "" {
    58  		z.Name = DefaultNamer()
    59  	}
    60  
    61  	if z.HostIP == "" {
    62  		z.HostIP = "127.0.0.1"
    63  	}
    64  
    65  	if z.ContainerPorts == nil {
    66  		if z.ClientPort == docker.Port("") {
    67  			z.ClientPort = docker.Port("2181/tcp")
    68  		}
    69  		if z.LeaderPort == docker.Port("") {
    70  			z.LeaderPort = docker.Port("3888/tcp")
    71  		}
    72  		if z.PeerPort == docker.Port("") {
    73  			z.PeerPort = docker.Port("2888/tcp")
    74  		}
    75  
    76  		z.ContainerPorts = []docker.Port{
    77  			z.ClientPort,
    78  			z.LeaderPort,
    79  			z.PeerPort,
    80  		}
    81  	}
    82  
    83  	if z.StartTimeout == 0 {
    84  		z.StartTimeout = DefaultStartTimeout
    85  	}
    86  
    87  	if z.ZooMyID == 0 {
    88  		z.ZooMyID = 1
    89  	}
    90  
    91  	if z.Client == nil {
    92  		client, err := docker.NewClientFromEnv()
    93  		if err != nil {
    94  			return err
    95  		}
    96  		z.Client = client
    97  	}
    98  
    99  	containerOptions := docker.CreateContainerOptions{
   100  		Name: z.Name,
   101  		HostConfig: &docker.HostConfig{
   102  			AutoRemove: true,
   103  		},
   104  		Config: &docker.Config{
   105  			Image: z.Image,
   106  			Env: []string{
   107  				fmt.Sprintf("ZOOKEEPER_MY_ID=%d", z.ZooMyID),
   108  				fmt.Sprintf("ZOOKEEPER_SERVERS=%s", z.ZooServers),
   109  				fmt.Sprintf("ZOOKEEPER_CLIENT_PORT=%s", z.ClientPort.Port()),
   110  			},
   111  		},
   112  	}
   113  
   114  	if z.NetworkName != "" {
   115  		nw, err := z.Client.NetworkInfo(z.NetworkName)
   116  		if err != nil {
   117  			return err
   118  		}
   119  
   120  		containerOptions.NetworkingConfig = &docker.NetworkingConfig{
   121  			EndpointsConfig: map[string]*docker.EndpointConfig{
   122  				z.NetworkName: {
   123  					NetworkID: nw.ID,
   124  				},
   125  			},
   126  		}
   127  	}
   128  
   129  	container, err := z.Client.CreateContainer(containerOptions)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	z.containerID = container.ID
   134  
   135  	err = z.Client.StartContainer(container.ID, nil)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	defer z.Stop()
   140  
   141  	container, err = z.Client.InspectContainer(container.ID)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	z.containerAddress = net.JoinHostPort(
   147  		container.NetworkSettings.IPAddress,
   148  		z.ContainerPorts[0].Port(),
   149  	)
   150  
   151  	streamCtx, streamCancel := context.WithCancel(context.Background())
   152  	defer streamCancel()
   153  	go z.streamLogs(streamCtx)
   154  
   155  	containerExit := z.wait()
   156  	ctx, cancel := context.WithTimeout(context.Background(), z.StartTimeout)
   157  	defer cancel()
   158  
   159  	select {
   160  	case <-ctx.Done():
   161  		return errors.Wrapf(ctx.Err(), "zookeeper in container %s did not start", z.containerID)
   162  	case <-containerExit:
   163  		return errors.New("container exited before ready")
   164  	default:
   165  		z.address = z.containerAddress
   166  	}
   167  
   168  	close(ready)
   169  
   170  	for {
   171  		select {
   172  		case err := <-containerExit:
   173  			return err
   174  		case <-sigCh:
   175  			if err := z.Stop(); err != nil {
   176  				return err
   177  			}
   178  		}
   179  	}
   180  }
   181  
   182  func (z *ZooKeeper) wait() <-chan error {
   183  	exitCh := make(chan error)
   184  	go func() {
   185  		exitCode, err := z.Client.WaitContainer(z.containerID)
   186  		if err == nil {
   187  			err = fmt.Errorf("zookeeper: process exited with %d", exitCode)
   188  		}
   189  		exitCh <- err
   190  	}()
   191  
   192  	return exitCh
   193  }
   194  
   195  func (z *ZooKeeper) streamLogs(ctx context.Context) error {
   196  	if z.ErrorStream == nil && z.OutputStream == nil {
   197  		return nil
   198  	}
   199  
   200  	logOptions := docker.LogsOptions{
   201  		Context:      ctx,
   202  		Container:    z.ContainerID(),
   203  		ErrorStream:  z.ErrorStream,
   204  		OutputStream: z.OutputStream,
   205  		Stderr:       z.ErrorStream != nil,
   206  		Stdout:       z.OutputStream != nil,
   207  		Follow:       true,
   208  	}
   209  	return z.Client.Logs(logOptions)
   210  }
   211  
   212  func (z *ZooKeeper) ContainerID() string {
   213  	return z.containerID
   214  }
   215  
   216  func (z *ZooKeeper) ContainerAddress() string {
   217  	return z.containerAddress
   218  }
   219  
   220  func (z *ZooKeeper) Start() error {
   221  	p := ifrit.Invoke(z)
   222  
   223  	select {
   224  	case <-p.Ready():
   225  		return nil
   226  	case err := <-p.Wait():
   227  		return err
   228  	}
   229  }
   230  
   231  func (z *ZooKeeper) Stop() error {
   232  	z.mutex.Lock()
   233  	if z.stopped {
   234  		z.mutex.Unlock()
   235  		return errors.Errorf("container %s already stopped", z.Name)
   236  	}
   237  	z.stopped = true
   238  	z.mutex.Unlock()
   239  
   240  	err := z.Client.StopContainer(z.containerID, 0)
   241  	if err != nil {
   242  		return err
   243  	}
   244  
   245  	_, err = z.Client.PruneVolumes(docker.PruneVolumesOptions{})
   246  	return err
   247  }