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 }