client_windows.go 14 KB

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