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