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  }