client_windows.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. package libcontainerd
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "syscall"
  10. "golang.org/x/net/context"
  11. "github.com/Microsoft/hcsshim"
  12. "github.com/Sirupsen/logrus"
  13. "github.com/opencontainers/runtime-spec/specs-go"
  14. )
  15. type client struct {
  16. clientCommon
  17. // Platform specific properties below here (none presently on Windows)
  18. }
  19. // Win32 error codes that are used for various workarounds
  20. // These really should be ALL_CAPS to match golangs syscall library and standard
  21. // Win32 error conventions, but golint insists on CamelCase.
  22. const (
  23. CoEClassstring = syscall.Errno(0x800401F3) // Invalid class string
  24. ErrorNoNetwork = syscall.Errno(1222) // The network is not present or not started
  25. ErrorBadPathname = syscall.Errno(161) // The specified path is invalid
  26. ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object
  27. )
  28. // defaultOwner is a tag passed to HCS to allow it to differentiate between
  29. // container creator management stacks. We hard code "docker" in the case
  30. // of docker.
  31. const defaultOwner = "docker"
  32. // Create is the entrypoint to create a container from a spec, and if successfully
  33. // created, start it too. Table below shows the fields required for HCS JSON calling parameters,
  34. // where if not populated, is omitted.
  35. // +-----------------+--------------------------------------------+---------------------------------------------------+
  36. // | | Isolation=Process | Isolation=Hyper-V |
  37. // +-----------------+--------------------------------------------+---------------------------------------------------+
  38. // | VolumePath | \\?\\Volume{GUIDa} | |
  39. // | LayerFolderPath | %root%\windowsfilter\containerID | %root%\windowsfilter\containerID (servicing only) |
  40. // | Layers[] | ID=GUIDb;Path=%root%\windowsfilter\layerID | ID=GUIDb;Path=%root%\windowsfilter\layerID |
  41. // | SandboxPath | | %root%\windowsfilter |
  42. // | HvRuntime | | ImagePath=%root%\BaseLayerID\UtilityVM |
  43. // +-----------------+--------------------------------------------+---------------------------------------------------+
  44. //
  45. // Isolation=Process example:
  46. //
  47. // {
  48. // "SystemType": "Container",
  49. // "Name": "5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776",
  50. // "Owner": "docker",
  51. // "IsDummy": false,
  52. // "VolumePath": "\\\\\\\\?\\\\Volume{66d1ef4c-7a00-11e6-8948-00155ddbef9d}",
  53. // "IgnoreFlushesDuringBoot": true,
  54. // "LayerFolderPath": "C:\\\\control\\\\windowsfilter\\\\5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776",
  55. // "Layers": [{
  56. // "ID": "18955d65-d45a-557b-bf1c-49d6dfefc526",
  57. // "Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c"
  58. // }],
  59. // "HostName": "5e0055c814a6",
  60. // "MappedDirectories": [],
  61. // "HvPartition": false,
  62. // "EndpointList": ["eef2649d-bb17-4d53-9937-295a8efe6f2c"],
  63. // "Servicing": false
  64. //}
  65. //
  66. // Isolation=Hyper-V example:
  67. //
  68. //{
  69. // "SystemType": "Container",
  70. // "Name": "475c2c58933b72687a88a441e7e0ca4bd72d76413c5f9d5031fee83b98f6045d",
  71. // "Owner": "docker",
  72. // "IsDummy": false,
  73. // "IgnoreFlushesDuringBoot": true,
  74. // "Layers": [{
  75. // "ID": "18955d65-d45a-557b-bf1c-49d6dfefc526",
  76. // "Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c"
  77. // }],
  78. // "HostName": "475c2c58933b",
  79. // "MappedDirectories": [],
  80. // "SandboxPath": "C:\\\\control\\\\windowsfilter",
  81. // "HvPartition": true,
  82. // "EndpointList": ["e1bb1e61-d56f-405e-b75d-fd520cefa0cb"],
  83. // "HvRuntime": {
  84. // "ImagePath": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c\\\\UtilityVM"
  85. // },
  86. // "Servicing": false
  87. //}
  88. func (clnt *client) Create(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, options ...CreateOption) error {
  89. clnt.lock(containerID)
  90. defer clnt.unlock(containerID)
  91. logrus.Debugln("libcontainerd: client.Create() with spec", spec)
  92. configuration := &hcsshim.ContainerConfig{
  93. SystemType: "Container",
  94. Name: containerID,
  95. Owner: defaultOwner,
  96. IgnoreFlushesDuringBoot: false,
  97. HostName: spec.Hostname,
  98. HvPartition: false,
  99. }
  100. if spec.Windows.Resources != nil {
  101. if spec.Windows.Resources.CPU != nil {
  102. if spec.Windows.Resources.CPU.Shares != nil {
  103. configuration.ProcessorWeight = uint64(*spec.Windows.Resources.CPU.Shares)
  104. }
  105. if spec.Windows.Resources.CPU.Percent != nil {
  106. configuration.ProcessorMaximum = int64(*spec.Windows.Resources.CPU.Percent) * 100 // ProcessorMaximum is a value between 1 and 10000
  107. }
  108. }
  109. if spec.Windows.Resources.Memory != nil {
  110. if spec.Windows.Resources.Memory.Limit != nil {
  111. configuration.MemoryMaximumInMB = int64(*spec.Windows.Resources.Memory.Limit) / 1024 / 1024
  112. }
  113. }
  114. if spec.Windows.Resources.Storage != nil {
  115. if spec.Windows.Resources.Storage.Bps != nil {
  116. configuration.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps
  117. }
  118. if spec.Windows.Resources.Storage.Iops != nil {
  119. configuration.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops
  120. }
  121. }
  122. }
  123. var layerOpt *LayerOption
  124. for _, option := range options {
  125. if s, ok := option.(*ServicingOption); ok {
  126. configuration.Servicing = s.IsServicing
  127. continue
  128. }
  129. if f, ok := option.(*FlushOption); ok {
  130. configuration.IgnoreFlushesDuringBoot = f.IgnoreFlushesDuringBoot
  131. continue
  132. }
  133. if h, ok := option.(*HyperVIsolationOption); ok {
  134. configuration.HvPartition = h.IsHyperV
  135. configuration.SandboxPath = h.SandboxPath
  136. continue
  137. }
  138. if l, ok := option.(*LayerOption); ok {
  139. layerOpt = l
  140. }
  141. if n, ok := option.(*NetworkEndpointsOption); ok {
  142. configuration.EndpointList = n.Endpoints
  143. configuration.AllowUnqualifiedDNSQuery = n.AllowUnqualifiedDNSQuery
  144. continue
  145. }
  146. if c, ok := option.(*CredentialsOption); ok {
  147. configuration.Credentials = c.Credentials
  148. continue
  149. }
  150. }
  151. // We must have a layer option with at least one path
  152. if layerOpt == nil || layerOpt.LayerPaths == nil {
  153. return fmt.Errorf("no layer option or paths were supplied to the runtime")
  154. }
  155. if configuration.HvPartition {
  156. // Find the upper-most utility VM image, since the utility VM does not
  157. // use layering in RS1.
  158. // TODO @swernli/jhowardmsft at some point post RS1 this may be re-locatable.
  159. var uvmImagePath string
  160. for _, path := range layerOpt.LayerPaths {
  161. fullPath := filepath.Join(path, "UtilityVM")
  162. _, err := os.Stat(fullPath)
  163. if err == nil {
  164. uvmImagePath = fullPath
  165. break
  166. }
  167. if !os.IsNotExist(err) {
  168. return err
  169. }
  170. }
  171. if uvmImagePath == "" {
  172. return errors.New("utility VM image could not be found")
  173. }
  174. configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: uvmImagePath}
  175. } else {
  176. configuration.VolumePath = spec.Root.Path
  177. }
  178. configuration.LayerFolderPath = layerOpt.LayerFolderPath
  179. for _, layerPath := range layerOpt.LayerPaths {
  180. _, filename := filepath.Split(layerPath)
  181. g, err := hcsshim.NameToGuid(filename)
  182. if err != nil {
  183. return err
  184. }
  185. configuration.Layers = append(configuration.Layers, hcsshim.Layer{
  186. ID: g.ToString(),
  187. Path: layerPath,
  188. })
  189. }
  190. // Add the mounts (volumes, bind mounts etc) to the structure
  191. mds := make([]hcsshim.MappedDir, len(spec.Mounts))
  192. for i, mount := range spec.Mounts {
  193. mds[i] = hcsshim.MappedDir{
  194. HostPath: mount.Source,
  195. ContainerPath: mount.Destination,
  196. ReadOnly: false,
  197. }
  198. for _, o := range mount.Options {
  199. if strings.ToLower(o) == "ro" {
  200. mds[i].ReadOnly = true
  201. }
  202. }
  203. }
  204. configuration.MappedDirectories = mds
  205. hcsContainer, err := hcsshim.CreateContainer(containerID, configuration)
  206. if err != nil {
  207. return err
  208. }
  209. // Construct a container object for calling start on it.
  210. container := &container{
  211. containerCommon: containerCommon{
  212. process: process{
  213. processCommon: processCommon{
  214. containerID: containerID,
  215. client: clnt,
  216. friendlyName: InitFriendlyName,
  217. },
  218. commandLine: strings.Join(spec.Process.Args, " "),
  219. },
  220. processes: make(map[string]*process),
  221. },
  222. ociSpec: spec,
  223. hcsContainer: hcsContainer,
  224. }
  225. container.options = options
  226. for _, option := range options {
  227. if err := option.Apply(container); err != nil {
  228. logrus.Errorf("libcontainerd: %v", err)
  229. }
  230. }
  231. // Call start, and if it fails, delete the container from our
  232. // internal structure, start will keep HCS in sync by deleting the
  233. // container there.
  234. logrus.Debugf("libcontainerd: Create() id=%s, Calling start()", containerID)
  235. if err := container.start(); err != nil {
  236. clnt.deleteContainer(containerID)
  237. return err
  238. }
  239. logrus.Debugf("libcontainerd: Create() id=%s completed successfully", containerID)
  240. return nil
  241. }
  242. // AddProcess is the handler for adding a process to an already running
  243. // container. It's called through docker exec.
  244. func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendlyName string, procToAdd Process) error {
  245. clnt.lock(containerID)
  246. defer clnt.unlock(containerID)
  247. container, err := clnt.getContainer(containerID)
  248. if err != nil {
  249. return err
  250. }
  251. // Note we always tell HCS to
  252. // create stdout as it's required regardless of '-i' or '-t' options, so that
  253. // docker can always grab the output through logs. We also tell HCS to always
  254. // create stdin, even if it's not used - it will be closed shortly. Stderr
  255. // is only created if it we're not -t.
  256. createProcessParms := hcsshim.ProcessConfig{
  257. EmulateConsole: procToAdd.Terminal,
  258. CreateStdInPipe: true,
  259. CreateStdOutPipe: true,
  260. CreateStdErrPipe: !procToAdd.Terminal,
  261. }
  262. createProcessParms.ConsoleSize[0] = uint(procToAdd.ConsoleSize.Height)
  263. createProcessParms.ConsoleSize[1] = uint(procToAdd.ConsoleSize.Width)
  264. // Take working directory from the process to add if it is defined,
  265. // otherwise take from the first process.
  266. if procToAdd.Cwd != "" {
  267. createProcessParms.WorkingDirectory = procToAdd.Cwd
  268. } else {
  269. createProcessParms.WorkingDirectory = container.ociSpec.Process.Cwd
  270. }
  271. // Configure the environment for the process
  272. createProcessParms.Environment = setupEnvironmentVariables(procToAdd.Env)
  273. createProcessParms.CommandLine = strings.Join(procToAdd.Args, " ")
  274. logrus.Debugf("libcontainerd: commandLine: %s", createProcessParms.CommandLine)
  275. // Start the command running in the container.
  276. var stdout, stderr io.ReadCloser
  277. var stdin io.WriteCloser
  278. newProcess, err := container.hcsContainer.CreateProcess(&createProcessParms)
  279. if err != nil {
  280. logrus.Errorf("libcontainerd: AddProcess(%s) CreateProcess() failed %s", containerID, err)
  281. return err
  282. }
  283. pid := newProcess.Pid()
  284. stdin, stdout, stderr, err = newProcess.Stdio()
  285. if err != nil {
  286. logrus.Errorf("libcontainerd: %s getting std pipes failed %s", containerID, err)
  287. return err
  288. }
  289. iopipe := &IOPipe{Terminal: procToAdd.Terminal}
  290. iopipe.Stdin = createStdInCloser(stdin, newProcess)
  291. // Convert io.ReadClosers to io.Readers
  292. if stdout != nil {
  293. iopipe.Stdout = openReaderFromPipe(stdout)
  294. }
  295. if stderr != nil {
  296. iopipe.Stderr = openReaderFromPipe(stderr)
  297. }
  298. proc := &process{
  299. processCommon: processCommon{
  300. containerID: containerID,
  301. friendlyName: processFriendlyName,
  302. client: clnt,
  303. systemPid: uint32(pid),
  304. },
  305. commandLine: createProcessParms.CommandLine,
  306. hcsProcess: newProcess,
  307. }
  308. // Add the process to the container's list of processes
  309. container.processes[processFriendlyName] = proc
  310. // Make sure the lock is not held while calling back into the daemon
  311. clnt.unlock(containerID)
  312. // Tell the engine to attach streams back to the client
  313. if err := clnt.backend.AttachStreams(processFriendlyName, *iopipe); err != nil {
  314. clnt.lock(containerID)
  315. return err
  316. }
  317. // Lock again so that the defer unlock doesn't fail. (I really don't like this code)
  318. clnt.lock(containerID)
  319. // Spin up a go routine waiting for exit to handle cleanup
  320. go container.waitExit(proc, false)
  321. return nil
  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. // Get the container as we need it to get the container handle.
  332. clnt.lock(containerID)
  333. defer clnt.unlock(containerID)
  334. if cont, err = clnt.getContainer(containerID); err != nil {
  335. return err
  336. }
  337. cont.manualStopRequested = true
  338. logrus.Debugf("libcontainerd: Signal() containerID=%s sig=%d pid=%d", containerID, sig, cont.systemPid)
  339. if syscall.Signal(sig) == syscall.SIGKILL {
  340. // Terminate the compute system
  341. if err := cont.hcsContainer.Terminate(); err != nil {
  342. if !hcsshim.IsPending(err) {
  343. logrus.Errorf("libcontainerd: failed to terminate %s - %q", containerID, err)
  344. }
  345. }
  346. } else {
  347. // Terminate Process
  348. if err := cont.hcsProcess.Kill(); err != nil && !hcsshim.IsAlreadyStopped(err) {
  349. // ignore errors
  350. logrus.Warnf("libcontainerd: failed to terminate pid %d in %s: %q", cont.systemPid, containerID, err)
  351. }
  352. }
  353. return nil
  354. }
  355. // While Linux has support for the full range of signals, signals aren't really implemented on Windows.
  356. // We try to terminate the specified process whatever signal is requested.
  357. func (clnt *client) SignalProcess(containerID string, processFriendlyName string, sig int) error {
  358. clnt.lock(containerID)
  359. defer clnt.unlock(containerID)
  360. cont, err := clnt.getContainer(containerID)
  361. if err != nil {
  362. return err
  363. }
  364. for _, p := range cont.processes {
  365. if p.friendlyName == processFriendlyName {
  366. return p.hcsProcess.Kill()
  367. }
  368. }
  369. return fmt.Errorf("SignalProcess could not find process %s in %s", processFriendlyName, containerID)
  370. }
  371. // Resize handles a CLI event to resize an interactive docker run or docker exec
  372. // window.
  373. func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error {
  374. // Get the libcontainerd container object
  375. clnt.lock(containerID)
  376. defer clnt.unlock(containerID)
  377. cont, err := clnt.getContainer(containerID)
  378. if err != nil {
  379. return err
  380. }
  381. h, w := uint16(height), uint16(width)
  382. if processFriendlyName == InitFriendlyName {
  383. logrus.Debugln("libcontainerd: resizing systemPID in", containerID, cont.process.systemPid)
  384. return cont.process.hcsProcess.ResizeConsole(w, h)
  385. }
  386. for _, p := range cont.processes {
  387. if p.friendlyName == processFriendlyName {
  388. logrus.Debugln("libcontainerd: resizing exec'd process", containerID, p.systemPid)
  389. return p.hcsProcess.ResizeConsole(w, h)
  390. }
  391. }
  392. return fmt.Errorf("Resize could not find containerID %s to resize", containerID)
  393. }
  394. // Pause handles pause requests for containers
  395. func (clnt *client) Pause(containerID string) error {
  396. return errors.New("Windows: Containers cannot be paused")
  397. }
  398. // Resume handles resume requests for containers
  399. func (clnt *client) Resume(containerID string) error {
  400. return errors.New("Windows: Containers cannot be paused")
  401. }
  402. // Stats handles stats requests for containers
  403. func (clnt *client) Stats(containerID string) (*Stats, error) {
  404. // Get the libcontainerd container object
  405. clnt.lock(containerID)
  406. defer clnt.unlock(containerID)
  407. container, err := clnt.getContainer(containerID)
  408. if err != nil {
  409. return nil, err
  410. }
  411. s, err := container.hcsContainer.Statistics()
  412. if err != nil {
  413. return nil, err
  414. }
  415. st := Stats(s)
  416. return &st, nil
  417. }
  418. // Restore is the handler for restoring a container
  419. func (clnt *client) Restore(containerID string, unusedOnWindows ...CreateOption) error {
  420. // TODO Windows: Implement this. For now, just tell the backend the container exited.
  421. logrus.Debugf("libcontainerd: Restore(%s)", containerID)
  422. return clnt.backend.StateChanged(containerID, StateInfo{
  423. CommonStateInfo: CommonStateInfo{
  424. State: StateExit,
  425. ExitCode: 1 << 31,
  426. }})
  427. }
  428. // GetPidsForContainer returns a list of process IDs running in a container.
  429. // Although implemented, this is not used in Windows.
  430. func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) {
  431. var pids []int
  432. clnt.lock(containerID)
  433. defer clnt.unlock(containerID)
  434. cont, err := clnt.getContainer(containerID)
  435. if err != nil {
  436. return nil, err
  437. }
  438. // Add the first process
  439. pids = append(pids, int(cont.containerCommon.systemPid))
  440. // And add all the exec'd processes
  441. for _, p := range cont.processes {
  442. pids = append(pids, int(p.processCommon.systemPid))
  443. }
  444. return pids, nil
  445. }
  446. // Summary returns a summary of the processes running in a container.
  447. // This is present in Windows to support docker top. In linux, the
  448. // engine shells out to ps to get process information. On Windows, as
  449. // the containers could be Hyper-V containers, they would not be
  450. // visible on the container host. However, libcontainerd does have
  451. // that information.
  452. func (clnt *client) Summary(containerID string) ([]Summary, error) {
  453. // Get the libcontainerd container object
  454. clnt.lock(containerID)
  455. defer clnt.unlock(containerID)
  456. container, err := clnt.getContainer(containerID)
  457. if err != nil {
  458. return nil, err
  459. }
  460. p, err := container.hcsContainer.ProcessList()
  461. if err != nil {
  462. return nil, err
  463. }
  464. pl := make([]Summary, len(p))
  465. for i := range p {
  466. pl[i] = Summary(p[i])
  467. }
  468. return pl, nil
  469. }
  470. // UpdateResources updates resources for a running container.
  471. func (clnt *client) UpdateResources(containerID string, resources Resources) error {
  472. // Updating resource isn't supported on Windows
  473. // but we should return nil for enabling updating container
  474. return nil
  475. }
  476. func (clnt *client) CreateCheckpoint(containerID string, checkpointID string, checkpointDir string, exit bool) error {
  477. return errors.New("Windows: Containers do not support checkpoints")
  478. }
  479. func (clnt *client) DeleteCheckpoint(containerID string, checkpointID string, checkpointDir string) error {
  480. return errors.New("Windows: Containers do not support checkpoints")
  481. }
  482. func (clnt *client) ListCheckpoints(containerID string, checkpointDir string) (*Checkpoints, error) {
  483. return nil, errors.New("Windows: Containers do not support checkpoints")
  484. }