client_windows.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. package libcontainerd
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "path/filepath"
  7. "strings"
  8. "syscall"
  9. "github.com/Microsoft/hcsshim"
  10. "github.com/Sirupsen/logrus"
  11. )
  12. type client struct {
  13. clientCommon
  14. // Platform specific properties below here (none presently on Windows)
  15. }
  16. // Win32 error codes that are used for various workarounds
  17. // These really should be ALL_CAPS to match golangs syscall library and standard
  18. // Win32 error conventions, but golint insists on CamelCase.
  19. const (
  20. CoEClassstring = syscall.Errno(0x800401F3) // Invalid class string
  21. ErrorNoNetwork = syscall.Errno(1222) // The network is not present or not started
  22. ErrorBadPathname = syscall.Errno(161) // The specified path is invalid
  23. ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object
  24. )
  25. // defaultOwner is a tag passed to HCS to allow it to differentiate between
  26. // container creator management stacks. We hard code "docker" in the case
  27. // of docker.
  28. const defaultOwner = "docker"
  29. // Create is the entrypoint to create a container from a spec, and if successfully
  30. // created, start it too.
  31. func (clnt *client) Create(containerID string, spec Spec, options ...CreateOption) error {
  32. logrus.Debugln("LCD client.Create() with spec", spec)
  33. configuration := &hcsshim.ContainerConfig{
  34. SystemType: "Container",
  35. Name: containerID,
  36. Owner: defaultOwner,
  37. VolumePath: spec.Root.Path,
  38. IgnoreFlushesDuringBoot: spec.Windows.FirstStart,
  39. LayerFolderPath: spec.Windows.LayerFolder,
  40. HostName: spec.Hostname,
  41. }
  42. if spec.Windows.Networking != nil {
  43. configuration.EndpointList = spec.Windows.Networking.EndpointList
  44. }
  45. if spec.Windows.Resources != nil {
  46. if spec.Windows.Resources.CPU != nil {
  47. if spec.Windows.Resources.CPU.Shares != nil {
  48. configuration.ProcessorWeight = *spec.Windows.Resources.CPU.Shares
  49. }
  50. if spec.Windows.Resources.CPU.Percent != nil {
  51. configuration.ProcessorMaximum = *spec.Windows.Resources.CPU.Percent * 100 // ProcessorMaximum is a value between 1 and 10000
  52. }
  53. }
  54. if spec.Windows.Resources.Memory != nil {
  55. if spec.Windows.Resources.Memory.Limit != nil {
  56. configuration.MemoryMaximumInMB = *spec.Windows.Resources.Memory.Limit / 1024 / 1024
  57. }
  58. }
  59. if spec.Windows.Resources.Storage != nil {
  60. if spec.Windows.Resources.Storage.Bps != nil {
  61. configuration.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps
  62. }
  63. if spec.Windows.Resources.Storage.Iops != nil {
  64. configuration.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops
  65. }
  66. if spec.Windows.Resources.Storage.SandboxSize != nil {
  67. configuration.StorageSandboxSize = *spec.Windows.Resources.Storage.SandboxSize
  68. }
  69. }
  70. }
  71. if spec.Windows.HvRuntime != nil {
  72. configuration.VolumePath = "" // Always empty for Hyper-V containers
  73. configuration.HvPartition = true
  74. configuration.HvRuntime = &hcsshim.HvRuntime{
  75. ImagePath: spec.Windows.HvRuntime.ImagePath,
  76. }
  77. // Images with build version < 14350 don't support running with clone, but
  78. // Windows cannot automatically detect this. Explicitly block cloning in this
  79. // case.
  80. if build := buildFromVersion(spec.Platform.OSVersion); build > 0 && build < 14350 {
  81. configuration.HvRuntime.SkipTemplate = true
  82. }
  83. }
  84. if configuration.HvPartition {
  85. configuration.SandboxPath = filepath.Dir(spec.Windows.LayerFolder)
  86. } else {
  87. configuration.VolumePath = spec.Root.Path
  88. configuration.LayerFolderPath = spec.Windows.LayerFolder
  89. }
  90. for _, option := range options {
  91. if s, ok := option.(*ServicingOption); ok {
  92. configuration.Servicing = s.IsServicing
  93. break
  94. }
  95. }
  96. for _, layerPath := range spec.Windows.LayerPaths {
  97. _, filename := filepath.Split(layerPath)
  98. g, err := hcsshim.NameToGuid(filename)
  99. if err != nil {
  100. return err
  101. }
  102. configuration.Layers = append(configuration.Layers, hcsshim.Layer{
  103. ID: g.ToString(),
  104. Path: layerPath,
  105. })
  106. }
  107. // Add the mounts (volumes, bind mounts etc) to the structure
  108. mds := make([]hcsshim.MappedDir, len(spec.Mounts))
  109. for i, mount := range spec.Mounts {
  110. mds[i] = hcsshim.MappedDir{
  111. HostPath: mount.Source,
  112. ContainerPath: mount.Destination,
  113. ReadOnly: mount.Readonly}
  114. }
  115. configuration.MappedDirectories = mds
  116. hcsContainer, err := hcsshim.CreateContainer(containerID, configuration)
  117. if err != nil {
  118. return err
  119. }
  120. // Construct a container object for calling start on it.
  121. container := &container{
  122. containerCommon: containerCommon{
  123. process: process{
  124. processCommon: processCommon{
  125. containerID: containerID,
  126. client: clnt,
  127. friendlyName: InitFriendlyName,
  128. },
  129. commandLine: strings.Join(spec.Process.Args, " "),
  130. },
  131. processes: make(map[string]*process),
  132. },
  133. ociSpec: spec,
  134. hcsContainer: hcsContainer,
  135. }
  136. container.options = options
  137. for _, option := range options {
  138. if err := option.Apply(container); err != nil {
  139. logrus.Error(err)
  140. }
  141. }
  142. // Call start, and if it fails, delete the container from our
  143. // internal structure, start will keep HCS in sync by deleting the
  144. // container there.
  145. logrus.Debugf("Create() id=%s, Calling start()", containerID)
  146. if err := container.start(); err != nil {
  147. clnt.deleteContainer(containerID)
  148. return err
  149. }
  150. logrus.Debugf("Create() id=%s completed successfully", containerID)
  151. return nil
  152. }
  153. // AddProcess is the handler for adding a process to an already running
  154. // container. It's called through docker exec.
  155. func (clnt *client) AddProcess(containerID, processFriendlyName string, procToAdd Process) error {
  156. clnt.lock(containerID)
  157. defer clnt.unlock(containerID)
  158. container, err := clnt.getContainer(containerID)
  159. if err != nil {
  160. return err
  161. }
  162. // Note we always tell HCS to
  163. // create stdout as it's required regardless of '-i' or '-t' options, so that
  164. // docker can always grab the output through logs. We also tell HCS to always
  165. // create stdin, even if it's not used - it will be closed shortly. Stderr
  166. // is only created if it we're not -t.
  167. createProcessParms := hcsshim.ProcessConfig{
  168. EmulateConsole: procToAdd.Terminal,
  169. ConsoleSize: procToAdd.InitialConsoleSize,
  170. CreateStdInPipe: true,
  171. CreateStdOutPipe: true,
  172. CreateStdErrPipe: !procToAdd.Terminal,
  173. }
  174. // Take working directory from the process to add if it is defined,
  175. // otherwise take from the first process.
  176. if procToAdd.Cwd != "" {
  177. createProcessParms.WorkingDirectory = procToAdd.Cwd
  178. } else {
  179. createProcessParms.WorkingDirectory = container.ociSpec.Process.Cwd
  180. }
  181. // Configure the environment for the process
  182. createProcessParms.Environment = setupEnvironmentVariables(procToAdd.Env)
  183. createProcessParms.CommandLine = strings.Join(procToAdd.Args, " ")
  184. logrus.Debugf("commandLine: %s", createProcessParms.CommandLine)
  185. // Start the command running in the container.
  186. var stdout, stderr io.ReadCloser
  187. var stdin io.WriteCloser
  188. newProcess, err := container.hcsContainer.CreateProcess(&createProcessParms)
  189. if err != nil {
  190. logrus.Errorf("AddProcess %s CreateProcess() failed %s", containerID, err)
  191. return err
  192. }
  193. stdin, stdout, stderr, err = newProcess.Stdio()
  194. if err != nil {
  195. logrus.Errorf("%s getting std pipes failed %s", containerID, err)
  196. return err
  197. }
  198. iopipe := &IOPipe{Terminal: procToAdd.Terminal}
  199. iopipe.Stdin = createStdInCloser(stdin, newProcess)
  200. // TEMP: Work around Windows BS/DEL behavior.
  201. iopipe.Stdin = fixStdinBackspaceBehavior(iopipe.Stdin, container.ociSpec.Platform.OSVersion, procToAdd.Terminal)
  202. // Convert io.ReadClosers to io.Readers
  203. if stdout != nil {
  204. iopipe.Stdout = openReaderFromPipe(stdout)
  205. }
  206. if stderr != nil {
  207. iopipe.Stderr = openReaderFromPipe(stderr)
  208. }
  209. pid := newProcess.Pid()
  210. proc := &process{
  211. processCommon: processCommon{
  212. containerID: containerID,
  213. friendlyName: processFriendlyName,
  214. client: clnt,
  215. systemPid: uint32(pid),
  216. },
  217. commandLine: createProcessParms.CommandLine,
  218. hcsProcess: newProcess,
  219. }
  220. // Add the process to the container's list of processes
  221. container.processes[processFriendlyName] = proc
  222. // Make sure the lock is not held while calling back into the daemon
  223. clnt.unlock(containerID)
  224. // Tell the engine to attach streams back to the client
  225. if err := clnt.backend.AttachStreams(processFriendlyName, *iopipe); err != nil {
  226. return err
  227. }
  228. // Lock again so that the defer unlock doesn't fail. (I really don't like this code)
  229. clnt.lock(containerID)
  230. // Spin up a go routine waiting for exit to handle cleanup
  231. go container.waitExit(proc, false)
  232. return nil
  233. }
  234. // Signal handles `docker stop` on Windows. While Linux has support for
  235. // the full range of signals, signals aren't really implemented on Windows.
  236. // We fake supporting regular stop and -9 to force kill.
  237. func (clnt *client) Signal(containerID string, sig int) error {
  238. var (
  239. cont *container
  240. err error
  241. )
  242. // Get the container as we need it to find the pid of the process.
  243. clnt.lock(containerID)
  244. defer clnt.unlock(containerID)
  245. if cont, err = clnt.getContainer(containerID); err != nil {
  246. return err
  247. }
  248. cont.manualStopRequested = true
  249. logrus.Debugf("lcd: Signal() containerID=%s sig=%d pid=%d", containerID, sig, cont.systemPid)
  250. if syscall.Signal(sig) == syscall.SIGKILL {
  251. // Terminate the compute system
  252. if err := cont.hcsContainer.Terminate(); err != nil {
  253. if err != hcsshim.ErrVmcomputeOperationPending {
  254. logrus.Errorf("Failed to terminate %s - %q", containerID, err)
  255. }
  256. }
  257. } else {
  258. // Terminate Process
  259. if err := cont.hcsProcess.Kill(); err != nil {
  260. // ignore errors
  261. logrus.Warnf("Failed to terminate pid %d in %s: %q", cont.systemPid, containerID, err)
  262. }
  263. }
  264. return nil
  265. }
  266. // While Linux has support for the full range of signals, signals aren't really implemented on Windows.
  267. // We try to terminate the specified process whatever signal is requested.
  268. func (clnt *client) SignalProcess(containerID string, processFriendlyName string, sig int) error {
  269. clnt.lock(containerID)
  270. defer clnt.unlock(containerID)
  271. cont, err := clnt.getContainer(containerID)
  272. if err != nil {
  273. return err
  274. }
  275. for _, p := range cont.processes {
  276. if p.friendlyName == processFriendlyName {
  277. return hcsshim.TerminateProcessInComputeSystem(containerID, p.systemPid)
  278. }
  279. }
  280. return fmt.Errorf("SignalProcess could not find process %s in %s", processFriendlyName, containerID)
  281. }
  282. // Resize handles a CLI event to resize an interactive docker run or docker exec
  283. // window.
  284. func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error {
  285. // Get the libcontainerd container object
  286. clnt.lock(containerID)
  287. defer clnt.unlock(containerID)
  288. cont, err := clnt.getContainer(containerID)
  289. if err != nil {
  290. return err
  291. }
  292. h, w := uint16(height), uint16(width)
  293. if processFriendlyName == InitFriendlyName {
  294. logrus.Debugln("Resizing systemPID in", containerID, cont.process.systemPid)
  295. return cont.process.hcsProcess.ResizeConsole(w, h)
  296. }
  297. for _, p := range cont.processes {
  298. if p.friendlyName == processFriendlyName {
  299. logrus.Debugln("Resizing exec'd process", containerID, p.systemPid)
  300. return p.hcsProcess.ResizeConsole(w, h)
  301. }
  302. }
  303. return fmt.Errorf("Resize could not find containerID %s to resize", containerID)
  304. }
  305. // Pause handles pause requests for containers
  306. func (clnt *client) Pause(containerID string) error {
  307. return errors.New("Windows: Containers cannot be paused")
  308. }
  309. // Resume handles resume requests for containers
  310. func (clnt *client) Resume(containerID string) error {
  311. return errors.New("Windows: Containers cannot be paused")
  312. }
  313. // Stats handles stats requests for containers
  314. func (clnt *client) Stats(containerID string) (*Stats, error) {
  315. return nil, errors.New("Windows: Stats not implemented")
  316. }
  317. // Restore is the handler for restoring a container
  318. func (clnt *client) Restore(containerID string, unusedOnWindows ...CreateOption) error {
  319. // TODO Windows: Implement this. For now, just tell the backend the container exited.
  320. logrus.Debugf("lcd Restore %s", containerID)
  321. return clnt.backend.StateChanged(containerID, StateInfo{
  322. CommonStateInfo: CommonStateInfo{
  323. State: StateExit,
  324. ExitCode: 1 << 31,
  325. }})
  326. }
  327. // GetPidsForContainer returns a list of process IDs running in a container.
  328. // Although implemented, this is not used in Windows.
  329. func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) {
  330. var pids []int
  331. clnt.lock(containerID)
  332. defer clnt.unlock(containerID)
  333. cont, err := clnt.getContainer(containerID)
  334. if err != nil {
  335. return nil, err
  336. }
  337. // Add the first process
  338. pids = append(pids, int(cont.containerCommon.systemPid))
  339. // And add all the exec'd processes
  340. for _, p := range cont.processes {
  341. pids = append(pids, int(p.processCommon.systemPid))
  342. }
  343. return pids, nil
  344. }
  345. // Summary returns a summary of the processes running in a container.
  346. // This is present in Windows to support docker top. In linux, the
  347. // engine shells out to ps to get process information. On Windows, as
  348. // the containers could be Hyper-V containers, they would not be
  349. // visible on the container host. However, libcontainerd does have
  350. // that information.
  351. func (clnt *client) Summary(containerID string) ([]Summary, error) {
  352. var s []Summary
  353. clnt.lock(containerID)
  354. defer clnt.unlock(containerID)
  355. cont, err := clnt.getContainer(containerID)
  356. if err != nil {
  357. return nil, err
  358. }
  359. // Add the first process
  360. s = append(s, Summary{
  361. Pid: cont.containerCommon.systemPid,
  362. Command: cont.ociSpec.Process.Args[0]})
  363. // And add all the exec'd processes
  364. for _, p := range cont.processes {
  365. s = append(s, Summary{
  366. Pid: p.processCommon.systemPid,
  367. Command: p.commandLine})
  368. }
  369. return s, nil
  370. }
  371. // UpdateResources updates resources for a running container.
  372. func (clnt *client) UpdateResources(containerID string, resources Resources) error {
  373. // Updating resource isn't supported on Windows
  374. // but we should return nil for enabling updating container
  375. return nil
  376. }