github.com/uppal0016/docker_new@v0.0.0-20240123060250-1c98be13ac2c/libcontainerd/client_windows.go (about)

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