github.com/akerouanton/docker@v1.11.0-rc3/libcontainerd/client_windows.go (about)

     1  package libcontainerd
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"path/filepath"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"syscall"
    13  	"time"
    14  
    15  	"github.com/Microsoft/hcsshim"
    16  	"github.com/Sirupsen/logrus"
    17  )
    18  
    19  type client struct {
    20  	clientCommon
    21  
    22  	// Platform specific properties below here (none presently on Windows)
    23  }
    24  
    25  // defaultContainerNAT is the default name of the container NAT device that is
    26  // preconfigured on the server. TODO Windows - Remove for TP5 support as not needed.
    27  const defaultContainerNAT = "ContainerNAT"
    28  
    29  // Win32 error codes that are used for various workarounds
    30  // These really should be ALL_CAPS to match golangs syscall library and standard
    31  // Win32 error conventions, but golint insists on CamelCase.
    32  const (
    33  	CoEClassstring     = syscall.Errno(0x800401F3) // Invalid class string
    34  	ErrorNoNetwork     = syscall.Errno(1222)       // The network is not present or not started
    35  	ErrorBadPathname   = syscall.Errno(161)        // The specified path is invalid
    36  	ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object
    37  )
    38  
    39  type layer struct {
    40  	ID   string
    41  	Path string
    42  }
    43  
    44  type defConfig struct {
    45  	DefFile string
    46  }
    47  
    48  type portBinding struct {
    49  	Protocol     string
    50  	InternalPort int
    51  	ExternalPort int
    52  }
    53  
    54  type natSettings struct {
    55  	Name         string
    56  	PortBindings []portBinding
    57  }
    58  
    59  type networkConnection struct {
    60  	NetworkName string
    61  	Nat         natSettings
    62  }
    63  type networkSettings struct {
    64  	MacAddress string
    65  }
    66  
    67  type device struct {
    68  	DeviceType string
    69  	Connection interface{}
    70  	Settings   interface{}
    71  }
    72  
    73  type mappedDir struct {
    74  	HostPath      string
    75  	ContainerPath string
    76  	ReadOnly      bool
    77  }
    78  
    79  // TODO Windows: @darrenstahlmsft Add ProcessorCount
    80  type containerInit struct {
    81  	SystemType              string      // HCS requires this to be hard-coded to "Container"
    82  	Name                    string      // Name of the container. We use the docker ID.
    83  	Owner                   string      // The management platform that created this container
    84  	IsDummy                 bool        // Used for development purposes.
    85  	VolumePath              string      // Windows volume path for scratch space
    86  	Devices                 []device    // Devices used by the container
    87  	IgnoreFlushesDuringBoot bool        // Optimization hint for container startup in Windows
    88  	LayerFolderPath         string      // Where the layer folders are located
    89  	Layers                  []layer     // List of storage layers
    90  	ProcessorWeight         uint64      `json:",omitempty"` // CPU Shares 0..10000 on Windows; where 0 will be omitted and HCS will default.
    91  	ProcessorMaximum        int64       `json:",omitempty"` // CPU maximum usage percent 1..100
    92  	StorageIOPSMaximum      uint64      `json:",omitempty"` // Maximum Storage IOPS
    93  	StorageBandwidthMaximum uint64      `json:",omitempty"` // Maximum Storage Bandwidth in bytes per second
    94  	StorageSandboxSize      uint64      `json:",omitempty"` // Size in bytes that the container system drive should be expanded to if smaller
    95  	MemoryMaximumInMB       int64       `json:",omitempty"` // Maximum memory available to the container in Megabytes
    96  	HostName                string      // Hostname
    97  	MappedDirectories       []mappedDir // List of mapped directories (volumes/mounts)
    98  	SandboxPath             string      // Location of unmounted sandbox (used for Hyper-V containers)
    99  	HvPartition             bool        // True if it a Hyper-V Container
   100  	EndpointList            []string    // List of networking endpoints to be attached to container
   101  }
   102  
   103  // defaultOwner is a tag passed to HCS to allow it to differentiate between
   104  // container creator management stacks. We hard code "docker" in the case
   105  // of docker.
   106  const defaultOwner = "docker"
   107  
   108  // Create is the entrypoint to create a container from a spec, and if successfully
   109  // created, start it too.
   110  func (clnt *client) Create(containerID string, spec Spec, options ...CreateOption) error {
   111  	logrus.Debugln("LCD client.Create() with spec", spec)
   112  
   113  	cu := &containerInit{
   114  		SystemType: "Container",
   115  		Name:       containerID,
   116  		Owner:      defaultOwner,
   117  
   118  		VolumePath:              spec.Root.Path,
   119  		IgnoreFlushesDuringBoot: spec.Windows.FirstStart,
   120  		LayerFolderPath:         spec.Windows.LayerFolder,
   121  		HostName:                spec.Hostname,
   122  	}
   123  
   124  	if spec.Windows.Networking != nil {
   125  		cu.EndpointList = spec.Windows.Networking.EndpointList
   126  	}
   127  
   128  	if spec.Windows.Resources != nil {
   129  		if spec.Windows.Resources.CPU != nil {
   130  			if spec.Windows.Resources.CPU.Shares != nil {
   131  				cu.ProcessorWeight = *spec.Windows.Resources.CPU.Shares
   132  			}
   133  			if spec.Windows.Resources.CPU.Percent != nil {
   134  				cu.ProcessorMaximum = *spec.Windows.Resources.CPU.Percent * 100 // ProcessorMaximum is a value between 1 and 10000
   135  			}
   136  		}
   137  		if spec.Windows.Resources.Memory != nil {
   138  			if spec.Windows.Resources.Memory.Limit != nil {
   139  				cu.MemoryMaximumInMB = *spec.Windows.Resources.Memory.Limit / 1024 / 1024
   140  			}
   141  		}
   142  		if spec.Windows.Resources.Storage != nil {
   143  			if spec.Windows.Resources.Storage.Bps != nil {
   144  				cu.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps
   145  			}
   146  			if spec.Windows.Resources.Storage.Iops != nil {
   147  				cu.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops
   148  			}
   149  			if spec.Windows.Resources.Storage.SandboxSize != nil {
   150  				cu.StorageSandboxSize = *spec.Windows.Resources.Storage.SandboxSize
   151  			}
   152  		}
   153  	}
   154  
   155  	cu.HvPartition = (spec.Windows.HvRuntime != nil)
   156  
   157  	// TODO Windows @jhowardmsft. FIXME post TP5.
   158  	//	if spec.Windows.HvRuntime != nil {
   159  	//		if spec.WIndows.HVRuntime.ImagePath != "" {
   160  	//			cu.TBD = spec.Windows.HvRuntime.ImagePath
   161  	//		}
   162  	//	}
   163  
   164  	if cu.HvPartition {
   165  		cu.SandboxPath = filepath.Dir(spec.Windows.LayerFolder)
   166  	} else {
   167  		cu.VolumePath = spec.Root.Path
   168  		cu.LayerFolderPath = spec.Windows.LayerFolder
   169  	}
   170  
   171  	for _, layerPath := range spec.Windows.LayerPaths {
   172  		_, filename := filepath.Split(layerPath)
   173  		g, err := hcsshim.NameToGuid(filename)
   174  		if err != nil {
   175  			return err
   176  		}
   177  		cu.Layers = append(cu.Layers, layer{
   178  			ID:   g.ToString(),
   179  			Path: layerPath,
   180  		})
   181  	}
   182  
   183  	// Add the mounts (volumes, bind mounts etc) to the structure
   184  	mds := make([]mappedDir, len(spec.Mounts))
   185  	for i, mount := range spec.Mounts {
   186  		mds[i] = mappedDir{
   187  			HostPath:      mount.Source,
   188  			ContainerPath: mount.Destination,
   189  			ReadOnly:      mount.Readonly}
   190  	}
   191  	cu.MappedDirectories = mds
   192  
   193  	// TODO Windows: vv START OF TP4 BLOCK OF CODE. REMOVE ONCE TP4 IS NO LONGER SUPPORTED
   194  	if hcsshim.IsTP4() &&
   195  		spec.Windows.Networking != nil &&
   196  		spec.Windows.Networking.Bridge != "" {
   197  		// Enumerate through the port bindings specified by the user and convert
   198  		// them into the internal structure matching the JSON blob that can be
   199  		// understood by the HCS.
   200  		var pbs []portBinding
   201  		for i, v := range spec.Windows.Networking.PortBindings {
   202  			proto := strings.ToUpper(i.Proto())
   203  			if proto != "TCP" && proto != "UDP" {
   204  				return fmt.Errorf("invalid protocol %s", i.Proto())
   205  			}
   206  
   207  			if len(v) > 1 {
   208  				return fmt.Errorf("Windows does not support more than one host port in NAT settings")
   209  			}
   210  
   211  			for _, v2 := range v {
   212  				var (
   213  					iPort, ePort int
   214  					err          error
   215  				)
   216  				if len(v2.HostIP) != 0 {
   217  					return fmt.Errorf("Windows does not support host IP addresses in NAT settings")
   218  				}
   219  				if ePort, err = strconv.Atoi(v2.HostPort); err != nil {
   220  					return fmt.Errorf("invalid container port %s: %s", v2.HostPort, err)
   221  				}
   222  				if iPort, err = strconv.Atoi(i.Port()); err != nil {
   223  					return fmt.Errorf("invalid internal port %s: %s", i.Port(), err)
   224  				}
   225  				if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 {
   226  					return fmt.Errorf("specified NAT port is not in allowed range")
   227  				}
   228  				pbs = append(pbs,
   229  					portBinding{ExternalPort: ePort,
   230  						InternalPort: iPort,
   231  						Protocol:     proto})
   232  			}
   233  		}
   234  
   235  		dev := device{
   236  			DeviceType: "Network",
   237  			Connection: &networkConnection{
   238  				NetworkName: spec.Windows.Networking.Bridge,
   239  				Nat: natSettings{
   240  					Name:         defaultContainerNAT,
   241  					PortBindings: pbs,
   242  				},
   243  			},
   244  		}
   245  
   246  		if spec.Windows.Networking.MacAddress != "" {
   247  			windowsStyleMAC := strings.Replace(
   248  				spec.Windows.Networking.MacAddress, ":", "-", -1)
   249  			dev.Settings = networkSettings{
   250  				MacAddress: windowsStyleMAC,
   251  			}
   252  		}
   253  		cu.Devices = append(cu.Devices, dev)
   254  	} else {
   255  		logrus.Debugln("No network interface")
   256  	}
   257  	// TODO Windows: ^^ END OF TP4 BLOCK OF CODE. REMOVE ONCE TP4 IS NO LONGER SUPPORTED
   258  
   259  	configurationb, err := json.Marshal(cu)
   260  	if err != nil {
   261  		return err
   262  	}
   263  
   264  	configuration := string(configurationb)
   265  
   266  	// TODO Windows TP5 timeframe. Remove when TP4 is no longer supported.
   267  	// The following a workaround for Windows TP4 which has a networking
   268  	// bug which fairly frequently returns an error. Back off and retry.
   269  	if !hcsshim.IsTP4() {
   270  		if err := hcsshim.CreateComputeSystem(containerID, configuration); err != nil {
   271  			return err
   272  		}
   273  	} else {
   274  		maxAttempts := 5
   275  		for i := 1; i <= maxAttempts; i++ {
   276  			err = hcsshim.CreateComputeSystem(containerID, configuration)
   277  			if err == nil {
   278  				break
   279  			}
   280  
   281  			if herr, ok := err.(*hcsshim.HcsError); ok {
   282  				if herr.Err != syscall.ERROR_NOT_FOUND && // Element not found
   283  					herr.Err != syscall.ERROR_FILE_NOT_FOUND && // The system cannot find the file specified
   284  					herr.Err != ErrorNoNetwork && // The network is not present or not started
   285  					herr.Err != ErrorBadPathname && // The specified path is invalid
   286  					herr.Err != CoEClassstring && // Invalid class string
   287  					herr.Err != ErrorInvalidObject { // The object identifier does not represent a valid object
   288  					logrus.Debugln("Failed to create temporary container ", err)
   289  					return err
   290  				}
   291  				logrus.Warnf("Invoking Windows TP4 retry hack (%d of %d)", i, maxAttempts-1)
   292  				time.Sleep(50 * time.Millisecond)
   293  			}
   294  		}
   295  	}
   296  
   297  	// Construct a container object for calling start on it.
   298  	container := &container{
   299  		containerCommon: containerCommon{
   300  			process: process{
   301  				processCommon: processCommon{
   302  					containerID:  containerID,
   303  					client:       clnt,
   304  					friendlyName: InitFriendlyName,
   305  				},
   306  				commandLine: strings.Join(spec.Process.Args, " "),
   307  			},
   308  			processes: make(map[string]*process),
   309  		},
   310  		ociSpec: spec,
   311  	}
   312  
   313  	container.options = options
   314  	for _, option := range options {
   315  		if err := option.Apply(container); err != nil {
   316  			logrus.Error(err)
   317  		}
   318  	}
   319  
   320  	// Call start, and if it fails, delete the container from our
   321  	// internal structure, and also keep HCS in sync by deleting the
   322  	// container there.
   323  	logrus.Debugf("Create() id=%s, Calling start()", containerID)
   324  	if err := container.start(); err != nil {
   325  		clnt.deleteContainer(containerID)
   326  		return err
   327  	}
   328  
   329  	logrus.Debugf("Create() id=%s completed successfully", containerID)
   330  	return nil
   331  
   332  }
   333  
   334  // AddProcess is the handler for adding a process to an already running
   335  // container. It's called through docker exec.
   336  func (clnt *client) AddProcess(containerID, processFriendlyName string, procToAdd Process) error {
   337  
   338  	clnt.lock(containerID)
   339  	defer clnt.unlock(containerID)
   340  	container, err := clnt.getContainer(containerID)
   341  	if err != nil {
   342  		return err
   343  	}
   344  
   345  	createProcessParms := hcsshim.CreateProcessParams{
   346  		EmulateConsole: procToAdd.Terminal,
   347  		ConsoleSize:    procToAdd.InitialConsoleSize,
   348  	}
   349  
   350  	// Take working directory from the process to add if it is defined,
   351  	// otherwise take from the first process.
   352  	if procToAdd.Cwd != "" {
   353  		createProcessParms.WorkingDirectory = procToAdd.Cwd
   354  	} else {
   355  		createProcessParms.WorkingDirectory = container.ociSpec.Process.Cwd
   356  	}
   357  
   358  	// Configure the environment for the process
   359  	createProcessParms.Environment = setupEnvironmentVariables(procToAdd.Env)
   360  	createProcessParms.CommandLine = strings.Join(procToAdd.Args, " ")
   361  
   362  	logrus.Debugf("commandLine: %s", createProcessParms.CommandLine)
   363  
   364  	// Start the command running in the container. Note we always tell HCS to
   365  	// create stdout as it's required regardless of '-i' or '-t' options, so that
   366  	// docker can always grab the output through logs. We also tell HCS to always
   367  	// create stdin, even if it's not used - it will be closed shortly. Stderr
   368  	// is only created if it we're not -t.
   369  	var stdout, stderr io.ReadCloser
   370  	var pid uint32
   371  	iopipe := &IOPipe{Terminal: procToAdd.Terminal}
   372  	pid, iopipe.Stdin, stdout, stderr, err = hcsshim.CreateProcessInComputeSystem(
   373  		containerID,
   374  		true,
   375  		true,
   376  		!procToAdd.Terminal,
   377  		createProcessParms)
   378  	if err != nil {
   379  		logrus.Errorf("AddProcess %s CreateProcessInComputeSystem() failed %s", containerID, err)
   380  		return err
   381  	}
   382  
   383  	// Convert io.ReadClosers to io.Readers
   384  	if stdout != nil {
   385  		iopipe.Stdout = openReaderFromPipe(stdout)
   386  	}
   387  	if stderr != nil {
   388  		iopipe.Stderr = openReaderFromPipe(stderr)
   389  	}
   390  
   391  	// Add the process to the containers list of processes
   392  	container.processes[processFriendlyName] =
   393  		&process{
   394  			processCommon: processCommon{
   395  				containerID:  containerID,
   396  				friendlyName: processFriendlyName,
   397  				client:       clnt,
   398  				systemPid:    pid,
   399  			},
   400  			commandLine: createProcessParms.CommandLine,
   401  		}
   402  
   403  	// Make sure the lock is not held while calling back into the daemon
   404  	clnt.unlock(containerID)
   405  
   406  	// Tell the engine to attach streams back to the client
   407  	if err := clnt.backend.AttachStreams(processFriendlyName, *iopipe); err != nil {
   408  		return err
   409  	}
   410  
   411  	// Lock again so that the defer unlock doesn't fail. (I really don't like this code)
   412  	clnt.lock(containerID)
   413  
   414  	// Spin up a go routine waiting for exit to handle cleanup
   415  	go container.waitExit(pid, processFriendlyName, false)
   416  
   417  	return nil
   418  }
   419  
   420  // Signal handles `docker stop` on Windows. While Linux has support for
   421  // the full range of signals, signals aren't really implemented on Windows.
   422  // We fake supporting regular stop and -9 to force kill.
   423  func (clnt *client) Signal(containerID string, sig int) error {
   424  	var (
   425  		cont *container
   426  		err  error
   427  	)
   428  
   429  	// Get the container as we need it to find the pid of the process.
   430  	clnt.lock(containerID)
   431  	defer clnt.unlock(containerID)
   432  	if cont, err = clnt.getContainer(containerID); err != nil {
   433  		return err
   434  	}
   435  
   436  	logrus.Debugf("lcd: Signal() containerID=%s sig=%d pid=%d", containerID, sig, cont.systemPid)
   437  	context := fmt.Sprintf("Signal: sig=%d pid=%d", sig, cont.systemPid)
   438  
   439  	if syscall.Signal(sig) == syscall.SIGKILL {
   440  		// Terminate the compute system
   441  		if err := hcsshim.TerminateComputeSystem(containerID, hcsshim.TimeoutInfinite, context); err != nil {
   442  			logrus.Errorf("Failed to terminate %s - %q", containerID, err)
   443  		}
   444  
   445  	} else {
   446  		// Terminate Process
   447  		if err = hcsshim.TerminateProcessInComputeSystem(containerID, cont.systemPid); err != nil {
   448  			logrus.Warnf("Failed to terminate pid %d in %s: %q", cont.systemPid, containerID, err)
   449  			// Ignore errors
   450  			err = nil
   451  		}
   452  
   453  		// Shutdown the compute system
   454  		if err := hcsshim.ShutdownComputeSystem(containerID, hcsshim.TimeoutInfinite, context); err != nil {
   455  			logrus.Errorf("Failed to shutdown %s - %q", containerID, err)
   456  		}
   457  	}
   458  	return nil
   459  }
   460  
   461  // Resize handles a CLI event to resize an interactive docker run or docker exec
   462  // window.
   463  func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error {
   464  	// Get the libcontainerd container object
   465  	clnt.lock(containerID)
   466  	defer clnt.unlock(containerID)
   467  	cont, err := clnt.getContainer(containerID)
   468  	if err != nil {
   469  		return err
   470  	}
   471  
   472  	if processFriendlyName == InitFriendlyName {
   473  		logrus.Debugln("Resizing systemPID in", containerID, cont.process.systemPid)
   474  		return hcsshim.ResizeConsoleInComputeSystem(containerID, cont.process.systemPid, height, width)
   475  	}
   476  
   477  	for _, p := range cont.processes {
   478  		if p.friendlyName == processFriendlyName {
   479  			logrus.Debugln("Resizing exec'd process", containerID, p.systemPid)
   480  			return hcsshim.ResizeConsoleInComputeSystem(containerID, p.systemPid, height, width)
   481  		}
   482  	}
   483  
   484  	return fmt.Errorf("Resize could not find containerID %s to resize", containerID)
   485  
   486  }
   487  
   488  // Pause handles pause requests for containers
   489  func (clnt *client) Pause(containerID string) error {
   490  	return errors.New("Windows: Containers cannot be paused")
   491  }
   492  
   493  // Resume handles resume requests for containers
   494  func (clnt *client) Resume(containerID string) error {
   495  	return errors.New("Windows: Containers cannot be paused")
   496  }
   497  
   498  // Stats handles stats requests for containers
   499  func (clnt *client) Stats(containerID string) (*Stats, error) {
   500  	return nil, errors.New("Windows: Stats not implemented")
   501  }
   502  
   503  // Restore is the handler for restoring a container
   504  func (clnt *client) Restore(containerID string, unusedOnWindows ...CreateOption) error {
   505  	// TODO Windows: Implement this. For now, just tell the backend the container exited.
   506  	logrus.Debugf("lcd Restore %s", containerID)
   507  	return clnt.backend.StateChanged(containerID, StateInfo{
   508  		State:    StateExit,
   509  		ExitCode: 1 << 31,
   510  	})
   511  }
   512  
   513  // GetPidsForContainer returns a list of process IDs running in a container.
   514  // Although implemented, this is not used in Windows.
   515  func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) {
   516  	var pids []int
   517  	clnt.lock(containerID)
   518  	defer clnt.unlock(containerID)
   519  	cont, err := clnt.getContainer(containerID)
   520  	if err != nil {
   521  		return nil, err
   522  	}
   523  
   524  	// Add the first process
   525  	pids = append(pids, int(cont.containerCommon.systemPid))
   526  	// And add all the exec'd processes
   527  	for _, p := range cont.processes {
   528  		pids = append(pids, int(p.processCommon.systemPid))
   529  	}
   530  	return pids, nil
   531  }
   532  
   533  // Summary returns a summary of the processes running in a container.
   534  // This is present in Windows to support docker top. In linux, the
   535  // engine shells out to ps to get process information. On Windows, as
   536  // the containers could be Hyper-V containers, they would not be
   537  // visible on the container host. However, libcontainerd does have
   538  // that information.
   539  func (clnt *client) Summary(containerID string) ([]Summary, error) {
   540  	var s []Summary
   541  	clnt.lock(containerID)
   542  	defer clnt.unlock(containerID)
   543  	cont, err := clnt.getContainer(containerID)
   544  	if err != nil {
   545  		return nil, err
   546  	}
   547  
   548  	// Add the first process
   549  	s = append(s, Summary{
   550  		Pid:     cont.containerCommon.systemPid,
   551  		Command: cont.ociSpec.Process.Args[0]})
   552  	// And add all the exec'd processes
   553  	for _, p := range cont.processes {
   554  		s = append(s, Summary{
   555  			Pid:     p.processCommon.systemPid,
   556  			Command: p.commandLine})
   557  	}
   558  	return s, nil
   559  
   560  }
   561  
   562  // UpdateResources updates resources for a running container.
   563  func (clnt *client) UpdateResources(containerID string, resources Resources) error {
   564  	// Updating resource isn't supported on Windows
   565  	// but we should return nil for enabling updating container
   566  	return nil
   567  }