client_windows.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  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, checkpoint string, checkpointDir 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: false,
  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. }
  68. }
  69. if spec.Windows.HvRuntime != nil {
  70. configuration.VolumePath = "" // Always empty for Hyper-V containers
  71. configuration.HvPartition = true
  72. configuration.HvRuntime = &hcsshim.HvRuntime{
  73. ImagePath: spec.Windows.HvRuntime.ImagePath,
  74. }
  75. }
  76. if configuration.HvPartition {
  77. configuration.SandboxPath = filepath.Dir(spec.Windows.LayerFolder)
  78. } else {
  79. configuration.VolumePath = spec.Root.Path
  80. configuration.LayerFolderPath = spec.Windows.LayerFolder
  81. }
  82. for _, option := range options {
  83. if s, ok := option.(*ServicingOption); ok {
  84. configuration.Servicing = s.IsServicing
  85. continue
  86. }
  87. if s, ok := option.(*FlushOption); ok {
  88. configuration.IgnoreFlushesDuringBoot = s.IgnoreFlushesDuringBoot
  89. continue
  90. }
  91. }
  92. for _, layerPath := range spec.Windows.LayerPaths {
  93. _, filename := filepath.Split(layerPath)
  94. g, err := hcsshim.NameToGuid(filename)
  95. if err != nil {
  96. return err
  97. }
  98. configuration.Layers = append(configuration.Layers, hcsshim.Layer{
  99. ID: g.ToString(),
  100. Path: layerPath,
  101. })
  102. }
  103. // Add the mounts (volumes, bind mounts etc) to the structure
  104. mds := make([]hcsshim.MappedDir, len(spec.Mounts))
  105. for i, mount := range spec.Mounts {
  106. mds[i] = hcsshim.MappedDir{
  107. HostPath: mount.Source,
  108. ContainerPath: mount.Destination,
  109. ReadOnly: false,
  110. }
  111. for _, o := range mount.Options {
  112. if strings.ToLower(o) == "ro" {
  113. mds[i].ReadOnly = true
  114. }
  115. }
  116. }
  117. configuration.MappedDirectories = mds
  118. hcsContainer, err := hcsshim.CreateContainer(containerID, configuration)
  119. if err != nil {
  120. return err
  121. }
  122. // Construct a container object for calling start on it.
  123. container := &container{
  124. containerCommon: containerCommon{
  125. process: process{
  126. processCommon: processCommon{
  127. containerID: containerID,
  128. client: clnt,
  129. friendlyName: InitFriendlyName,
  130. },
  131. commandLine: strings.Join(spec.Process.Args, " "),
  132. },
  133. processes: make(map[string]*process),
  134. },
  135. ociSpec: spec,
  136. hcsContainer: hcsContainer,
  137. }
  138. container.options = options
  139. for _, option := range options {
  140. if err := option.Apply(container); err != nil {
  141. logrus.Errorf("libcontainerd: %v", err)
  142. }
  143. }
  144. // Call start, and if it fails, delete the container from our
  145. // internal structure, start will keep HCS in sync by deleting the
  146. // container there.
  147. logrus.Debugf("libcontainerd: Create() id=%s, Calling start()", containerID)
  148. if err := container.start(); err != nil {
  149. clnt.deleteContainer(containerID)
  150. return err
  151. }
  152. logrus.Debugf("libcontainerd: Create() id=%s completed successfully", containerID)
  153. return nil
  154. }
  155. // AddProcess is the handler for adding a process to an already running
  156. // container. It's called through docker exec.
  157. func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendlyName string, procToAdd Process) error {
  158. clnt.lock(containerID)
  159. defer clnt.unlock(containerID)
  160. container, err := clnt.getContainer(containerID)
  161. if err != nil {
  162. return err
  163. }
  164. // Note we always tell HCS to
  165. // create stdout as it's required regardless of '-i' or '-t' options, so that
  166. // docker can always grab the output through logs. We also tell HCS to always
  167. // create stdin, even if it's not used - it will be closed shortly. Stderr
  168. // is only created if it we're not -t.
  169. createProcessParms := hcsshim.ProcessConfig{
  170. EmulateConsole: procToAdd.Terminal,
  171. CreateStdInPipe: true,
  172. CreateStdOutPipe: true,
  173. CreateStdErrPipe: !procToAdd.Terminal,
  174. }
  175. createProcessParms.ConsoleSize[0] = int(procToAdd.ConsoleSize.Height)
  176. createProcessParms.ConsoleSize[1] = int(procToAdd.ConsoleSize.Width)
  177. // Take working directory from the process to add if it is defined,
  178. // otherwise take from the first process.
  179. if procToAdd.Cwd != "" {
  180. createProcessParms.WorkingDirectory = procToAdd.Cwd
  181. } else {
  182. createProcessParms.WorkingDirectory = container.ociSpec.Process.Cwd
  183. }
  184. // Configure the environment for the process
  185. createProcessParms.Environment = setupEnvironmentVariables(procToAdd.Env)
  186. createProcessParms.CommandLine = strings.Join(procToAdd.Args, " ")
  187. logrus.Debugf("libcontainerd: commandLine: %s", createProcessParms.CommandLine)
  188. // Start the command running in the container.
  189. var stdout, stderr io.ReadCloser
  190. var stdin io.WriteCloser
  191. newProcess, err := container.hcsContainer.CreateProcess(&createProcessParms)
  192. if err != nil {
  193. logrus.Errorf("libcontainerd: AddProcess(%s) CreateProcess() failed %s", containerID, err)
  194. return err
  195. }
  196. stdin, stdout, stderr, err = newProcess.Stdio()
  197. if err != nil {
  198. logrus.Errorf("libcontainerd: %s getting std pipes failed %s", containerID, err)
  199. return err
  200. }
  201. iopipe := &IOPipe{Terminal: procToAdd.Terminal}
  202. iopipe.Stdin = createStdInCloser(stdin, newProcess)
  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 !hcsshim.IsPending(err) {
  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 && !hcsshim.IsAlreadyStopped(err) {
  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 p.hcsProcess.Kill()
  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. // Get the libcontainerd container object
  317. clnt.lock(containerID)
  318. defer clnt.unlock(containerID)
  319. container, err := clnt.getContainer(containerID)
  320. if err != nil {
  321. return nil, err
  322. }
  323. s, err := container.hcsContainer.Statistics()
  324. if err != nil {
  325. return nil, err
  326. }
  327. st := Stats(s)
  328. return &st, nil
  329. }
  330. // Restore is the handler for restoring a container
  331. func (clnt *client) Restore(containerID string, unusedOnWindows ...CreateOption) error {
  332. // TODO Windows: Implement this. For now, just tell the backend the container exited.
  333. logrus.Debugf("libcontainerd: Restore(%s)", containerID)
  334. return clnt.backend.StateChanged(containerID, StateInfo{
  335. CommonStateInfo: CommonStateInfo{
  336. State: StateExit,
  337. ExitCode: 1 << 31,
  338. }})
  339. }
  340. // GetPidsForContainer returns a list of process IDs running in a container.
  341. // Although implemented, this is not used in Windows.
  342. func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) {
  343. var pids []int
  344. clnt.lock(containerID)
  345. defer clnt.unlock(containerID)
  346. cont, err := clnt.getContainer(containerID)
  347. if err != nil {
  348. return nil, err
  349. }
  350. // Add the first process
  351. pids = append(pids, int(cont.containerCommon.systemPid))
  352. // And add all the exec'd processes
  353. for _, p := range cont.processes {
  354. pids = append(pids, int(p.processCommon.systemPid))
  355. }
  356. return pids, nil
  357. }
  358. // Summary returns a summary of the processes running in a container.
  359. // This is present in Windows to support docker top. In linux, the
  360. // engine shells out to ps to get process information. On Windows, as
  361. // the containers could be Hyper-V containers, they would not be
  362. // visible on the container host. However, libcontainerd does have
  363. // that information.
  364. func (clnt *client) Summary(containerID string) ([]Summary, error) {
  365. // Get the libcontainerd container object
  366. clnt.lock(containerID)
  367. defer clnt.unlock(containerID)
  368. container, err := clnt.getContainer(containerID)
  369. if err != nil {
  370. return nil, err
  371. }
  372. p, err := container.hcsContainer.ProcessList()
  373. if err != nil {
  374. return nil, err
  375. }
  376. pl := make([]Summary, len(p))
  377. for i := range p {
  378. pl[i] = Summary(p[i])
  379. }
  380. return pl, nil
  381. }
  382. // UpdateResources updates resources for a running container.
  383. func (clnt *client) UpdateResources(containerID string, resources Resources) error {
  384. // Updating resource isn't supported on Windows
  385. // but we should return nil for enabling updating container
  386. return nil
  387. }
  388. func (clnt *client) CreateCheckpoint(containerID string, checkpointID string, checkpointDir string, exit bool) error {
  389. return errors.New("Windows: Containers do not support checkpoints")
  390. }
  391. func (clnt *client) DeleteCheckpoint(containerID string, checkpointID string, checkpointDir string) error {
  392. return errors.New("Windows: Containers do not support checkpoints")
  393. }
  394. func (clnt *client) ListCheckpoints(containerID string, checkpointDir string) (*Checkpoints, error) {
  395. return nil, errors.New("Windows: Containers do not support checkpoints")
  396. }