github.com/adnan-c/fabric_e2e_couchdb@v0.6.1-preview.0.20170228180935-21ce6b23cf91/core/container/dockercontroller/dockercontroller.go (about) 1 /* 2 Copyright IBM Corp. 2016 All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package dockercontroller 18 19 import ( 20 "bytes" 21 "fmt" 22 "io" 23 "strings" 24 "time" 25 26 "bufio" 27 28 "github.com/fsouza/go-dockerclient" 29 container "github.com/hyperledger/fabric/core/container/api" 30 "github.com/hyperledger/fabric/core/container/ccintf" 31 cutil "github.com/hyperledger/fabric/core/container/util" 32 "github.com/op/go-logging" 33 "github.com/spf13/viper" 34 "golang.org/x/net/context" 35 ) 36 37 var ( 38 dockerLogger = logging.MustGetLogger("dockercontroller") 39 hostConfig *docker.HostConfig 40 ) 41 42 //DockerVM is a vm. It is identified by an image id 43 type DockerVM struct { 44 id string 45 } 46 47 func getDockerHostConfig() *docker.HostConfig { 48 if hostConfig != nil { 49 return hostConfig 50 } 51 dockerKey := func(key string) string { 52 return "vm.docker.hostConfig." + key 53 } 54 getInt64 := func(key string) int64 { 55 defer func() { 56 if err := recover(); err != nil { 57 dockerLogger.Warningf("load vm.docker.hostConfig.%s failed, error: %v", key, err) 58 } 59 }() 60 n := viper.GetInt(dockerKey(key)) 61 return int64(n) 62 } 63 64 var logConfig docker.LogConfig 65 err := viper.UnmarshalKey(dockerKey("LogConfig"), &logConfig) 66 if err != nil { 67 dockerLogger.Warningf("load docker HostConfig.LogConfig failed, error: %s", err.Error()) 68 } 69 networkMode := viper.GetString(dockerKey("NetworkMode")) 70 if networkMode == "" { 71 networkMode = "host" 72 } 73 dockerLogger.Debugf("docker container hostconfig NetworkMode: %s", networkMode) 74 75 hostConfig = &docker.HostConfig{ 76 CapAdd: viper.GetStringSlice(dockerKey("CapAdd")), 77 CapDrop: viper.GetStringSlice(dockerKey("CapDrop")), 78 79 DNS: viper.GetStringSlice(dockerKey("Dns")), 80 DNSSearch: viper.GetStringSlice(dockerKey("DnsSearch")), 81 ExtraHosts: viper.GetStringSlice(dockerKey("ExtraHosts")), 82 NetworkMode: networkMode, 83 IpcMode: viper.GetString(dockerKey("IpcMode")), 84 PidMode: viper.GetString(dockerKey("PidMode")), 85 UTSMode: viper.GetString(dockerKey("UTSMode")), 86 LogConfig: logConfig, 87 88 ReadonlyRootfs: viper.GetBool(dockerKey("ReadonlyRootfs")), 89 SecurityOpt: viper.GetStringSlice(dockerKey("SecurityOpt")), 90 CgroupParent: viper.GetString(dockerKey("CgroupParent")), 91 Memory: getInt64("Memory"), 92 MemorySwap: getInt64("MemorySwap"), 93 MemorySwappiness: getInt64("MemorySwappiness"), 94 OOMKillDisable: viper.GetBool(dockerKey("OomKillDisable")), 95 CPUShares: getInt64("CpuShares"), 96 CPUSet: viper.GetString(dockerKey("Cpuset")), 97 CPUSetCPUs: viper.GetString(dockerKey("CpusetCPUs")), 98 CPUSetMEMs: viper.GetString(dockerKey("CpusetMEMs")), 99 CPUQuota: getInt64("CpuQuota"), 100 CPUPeriod: getInt64("CpuPeriod"), 101 BlkioWeight: getInt64("BlkioWeight"), 102 } 103 104 return hostConfig 105 } 106 107 func (vm *DockerVM) createContainer(ctxt context.Context, client *docker.Client, imageID string, containerID string, args []string, env []string, attachStdout bool) error { 108 config := docker.Config{Cmd: args, Image: imageID, Env: env, AttachStdout: attachStdout, AttachStderr: attachStdout} 109 copts := docker.CreateContainerOptions{Name: containerID, Config: &config, HostConfig: getDockerHostConfig()} 110 dockerLogger.Debugf("Create container: %s", containerID) 111 _, err := client.CreateContainer(copts) 112 if err != nil { 113 return err 114 } 115 dockerLogger.Debugf("Created container: %s", imageID) 116 return nil 117 } 118 119 func (vm *DockerVM) deployImage(client *docker.Client, ccid ccintf.CCID, args []string, env []string, reader io.Reader) error { 120 id, _ := vm.GetVMName(ccid) 121 outputbuf := bytes.NewBuffer(nil) 122 opts := docker.BuildImageOptions{ 123 Name: id, 124 Pull: false, 125 InputStream: reader, 126 OutputStream: outputbuf, 127 } 128 129 if err := client.BuildImage(opts); err != nil { 130 dockerLogger.Errorf("Error building images: %s", err) 131 dockerLogger.Errorf("Image Output:\n********************\n%s\n********************", outputbuf.String()) 132 return err 133 } 134 135 dockerLogger.Debugf("Created image: %s", id) 136 137 return nil 138 } 139 140 //Deploy use the reader containing targz to create a docker image 141 //for docker inputbuf is tar reader ready for use by docker.Client 142 //the stream from end client to peer could directly be this tar stream 143 //talk to docker daemon using docker Client and build the image 144 func (vm *DockerVM) Deploy(ctxt context.Context, ccid ccintf.CCID, args []string, env []string, reader io.Reader) error { 145 client, err := cutil.NewDockerClient() 146 switch err { 147 case nil: 148 if err = vm.deployImage(client, ccid, args, env, reader); err != nil { 149 return err 150 } 151 default: 152 return fmt.Errorf("Error creating docker client: %s", err) 153 } 154 return nil 155 } 156 157 //Start starts a container using a previously created docker image 158 func (vm *DockerVM) Start(ctxt context.Context, ccid ccintf.CCID, args []string, env []string, builder container.BuildSpecFactory) error { 159 imageID, _ := vm.GetVMName(ccid) 160 client, err := cutil.NewDockerClient() 161 if err != nil { 162 dockerLogger.Debugf("start - cannot create client %s", err) 163 return err 164 } 165 166 containerID := strings.Replace(imageID, ":", "_", -1) 167 attachStdout := viper.GetBool("vm.docker.attachStdout") 168 169 //stop,force remove if necessary 170 dockerLogger.Debugf("Cleanup container %s", containerID) 171 vm.stopInternal(ctxt, client, containerID, 0, false, false) 172 173 dockerLogger.Debugf("Start container %s", containerID) 174 err = vm.createContainer(ctxt, client, imageID, containerID, args, env, attachStdout) 175 if err != nil { 176 //if image not found try to create image and retry 177 if err == docker.ErrNoSuchImage { 178 if builder != nil { 179 dockerLogger.Debugf("start-could not find image ...attempt to recreate image %s", err) 180 181 reader, err := builder() 182 if err != nil { 183 dockerLogger.Errorf("Error creating image builder: %s", err) 184 } 185 186 if err = vm.deployImage(client, ccid, args, env, reader); err != nil { 187 return err 188 } 189 190 dockerLogger.Debug("start-recreated image successfully") 191 if err = vm.createContainer(ctxt, client, imageID, containerID, args, env, attachStdout); err != nil { 192 dockerLogger.Errorf("start-could not recreate container post recreate image: %s", err) 193 return err 194 } 195 } else { 196 dockerLogger.Errorf("start-could not find image: %s", err) 197 return err 198 } 199 } else { 200 dockerLogger.Errorf("start-could not recreate container %s", err) 201 return err 202 } 203 } 204 205 if attachStdout { 206 // Launch a few go-threads to manage output streams from the container. 207 // They will be automatically destroyed when the container exits 208 attached := make(chan struct{}) 209 r, w := io.Pipe() 210 211 go func() { 212 // AttachToContainer will fire off a message on the "attached" channel once the 213 // attachment completes, and then block until the container is terminated. 214 err = client.AttachToContainer(docker.AttachToContainerOptions{ 215 Container: containerID, 216 OutputStream: w, 217 ErrorStream: w, 218 Logs: true, 219 Stdout: true, 220 Stderr: true, 221 Stream: true, 222 Success: attached, 223 }) 224 225 // If we get here, the container has terminated. Send a signal on the pipe 226 // so that downstream may clean up appropriately 227 _ = w.CloseWithError(err) 228 }() 229 230 go func() { 231 // Block here until the attachment completes or we timeout 232 select { 233 case <-attached: 234 // successful attach 235 case <-time.After(10 * time.Second): 236 dockerLogger.Errorf("Timeout while attaching to IO channel in container %s", containerID) 237 return 238 } 239 240 // Acknowledge the attachment? This was included in the gist I followed 241 // (http://bit.ly/2jBrCtM). Not sure it's actually needed but it doesn't 242 // appear to hurt anything. 243 attached <- struct{}{} 244 245 // Establish a buffer for our IO channel so that we may do readline-style 246 // ingestion of the IO, one log entry per line 247 is := bufio.NewReader(r) 248 249 // Acquire a custom logger for our chaincode, inheriting the level from the peer 250 containerLogger := logging.MustGetLogger(containerID) 251 logging.SetLevel(logging.GetLevel("peer"), containerID) 252 253 for { 254 // Loop forever dumping lines of text into the containerLogger 255 // until the pipe is closed 256 line, err := is.ReadString('\n') 257 if err != nil { 258 switch err { 259 case io.EOF: 260 dockerLogger.Infof("Container %s has closed its IO channel", containerID) 261 default: 262 dockerLogger.Errorf("Error reading container output: %s", err) 263 } 264 265 return 266 } 267 268 containerLogger.Info(line) 269 } 270 }() 271 } 272 273 // start container with HostConfig was deprecated since v1.10 and removed in v1.2 274 err = client.StartContainer(containerID, nil) 275 if err != nil { 276 dockerLogger.Errorf("start-could not start container %s", err) 277 return err 278 } 279 280 dockerLogger.Debugf("Started container %s", containerID) 281 return nil 282 } 283 284 //Stop stops a running chaincode 285 func (vm *DockerVM) Stop(ctxt context.Context, ccid ccintf.CCID, timeout uint, dontkill bool, dontremove bool) error { 286 id, _ := vm.GetVMName(ccid) 287 client, err := cutil.NewDockerClient() 288 if err != nil { 289 dockerLogger.Debugf("stop - cannot create client %s", err) 290 return err 291 } 292 id = strings.Replace(id, ":", "_", -1) 293 294 err = vm.stopInternal(ctxt, client, id, timeout, dontkill, dontremove) 295 296 return err 297 } 298 299 func (vm *DockerVM) stopInternal(ctxt context.Context, client *docker.Client, id string, timeout uint, dontkill bool, dontremove bool) error { 300 err := client.StopContainer(id, timeout) 301 if err != nil { 302 dockerLogger.Debugf("Stop container %s(%s)", id, err) 303 } else { 304 dockerLogger.Debugf("Stopped container %s", id) 305 } 306 if !dontkill { 307 err = client.KillContainer(docker.KillContainerOptions{ID: id}) 308 if err != nil { 309 dockerLogger.Debugf("Kill container %s (%s)", id, err) 310 } else { 311 dockerLogger.Debugf("Killed container %s", id) 312 } 313 } 314 if !dontremove { 315 err = client.RemoveContainer(docker.RemoveContainerOptions{ID: id, Force: true}) 316 if err != nil { 317 dockerLogger.Debugf("Remove container %s (%s)", id, err) 318 } else { 319 dockerLogger.Debugf("Removed container %s", id) 320 } 321 } 322 return err 323 } 324 325 //Destroy destroys an image 326 func (vm *DockerVM) Destroy(ctxt context.Context, ccid ccintf.CCID, force bool, noprune bool) error { 327 id, _ := vm.GetVMName(ccid) 328 client, err := cutil.NewDockerClient() 329 if err != nil { 330 dockerLogger.Errorf("destroy-cannot create client %s", err) 331 return err 332 } 333 id = strings.Replace(id, ":", "_", -1) 334 335 err = client.RemoveImageExtended(id, docker.RemoveImageOptions{Force: force, NoPrune: noprune}) 336 337 if err != nil { 338 dockerLogger.Errorf("error while destroying image: %s", err) 339 } else { 340 dockerLogger.Debug("Destroyed image %s", id) 341 } 342 343 return err 344 } 345 346 //GetVMName generates the docker image from peer information given the hashcode. This is needed to 347 //keep image name's unique in a single host, multi-peer environment (such as a development environment) 348 func (vm *DockerVM) GetVMName(ccid ccintf.CCID) (string, error) { 349 name := ccid.GetName() 350 351 if ccid.NetworkID != "" { 352 return fmt.Sprintf("%s-%s-%s", ccid.NetworkID, ccid.PeerID, name), nil 353 } else if ccid.PeerID != "" { 354 return fmt.Sprintf("%s-%s", ccid.PeerID, name), nil 355 } else { 356 return name, nil 357 } 358 }