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  }