client_windows.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886
  1. package libcontainerd
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "os"
  9. "path"
  10. "path/filepath"
  11. "regexp"
  12. "strings"
  13. "syscall"
  14. "time"
  15. "golang.org/x/net/context"
  16. "github.com/Microsoft/hcsshim"
  17. opengcs "github.com/Microsoft/opengcs/client"
  18. "github.com/docker/docker/pkg/sysinfo"
  19. "github.com/docker/docker/pkg/system"
  20. specs "github.com/opencontainers/runtime-spec/specs-go"
  21. "github.com/sirupsen/logrus"
  22. )
  23. type client struct {
  24. clientCommon
  25. // Platform specific properties below here (none presently on Windows)
  26. }
  27. // Win32 error codes that are used for various workarounds
  28. // These really should be ALL_CAPS to match golangs syscall library and standard
  29. // Win32 error conventions, but golint insists on CamelCase.
  30. const (
  31. CoEClassstring = syscall.Errno(0x800401F3) // Invalid class string
  32. ErrorNoNetwork = syscall.Errno(1222) // The network is not present or not started
  33. ErrorBadPathname = syscall.Errno(161) // The specified path is invalid
  34. ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object
  35. )
  36. // defaultOwner is a tag passed to HCS to allow it to differentiate between
  37. // container creator management stacks. We hard code "docker" in the case
  38. // of docker.
  39. const defaultOwner = "docker"
  40. // Create is the entrypoint to create a container from a spec, and if successfully
  41. // created, start it too. Table below shows the fields required for HCS JSON calling parameters,
  42. // where if not populated, is omitted.
  43. // +-----------------+--------------------------------------------+---------------------------------------------------+
  44. // | | Isolation=Process | Isolation=Hyper-V |
  45. // +-----------------+--------------------------------------------+---------------------------------------------------+
  46. // | VolumePath | \\?\\Volume{GUIDa} | |
  47. // | LayerFolderPath | %root%\windowsfilter\containerID | %root%\windowsfilter\containerID (servicing only) |
  48. // | Layers[] | ID=GUIDb;Path=%root%\windowsfilter\layerID | ID=GUIDb;Path=%root%\windowsfilter\layerID |
  49. // | HvRuntime | | ImagePath=%root%\BaseLayerID\UtilityVM |
  50. // +-----------------+--------------------------------------------+---------------------------------------------------+
  51. //
  52. // Isolation=Process example:
  53. //
  54. // {
  55. // "SystemType": "Container",
  56. // "Name": "5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776",
  57. // "Owner": "docker",
  58. // "VolumePath": "\\\\\\\\?\\\\Volume{66d1ef4c-7a00-11e6-8948-00155ddbef9d}",
  59. // "IgnoreFlushesDuringBoot": true,
  60. // "LayerFolderPath": "C:\\\\control\\\\windowsfilter\\\\5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776",
  61. // "Layers": [{
  62. // "ID": "18955d65-d45a-557b-bf1c-49d6dfefc526",
  63. // "Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c"
  64. // }],
  65. // "HostName": "5e0055c814a6",
  66. // "MappedDirectories": [],
  67. // "HvPartition": false,
  68. // "EndpointList": ["eef2649d-bb17-4d53-9937-295a8efe6f2c"],
  69. // "Servicing": false
  70. //}
  71. //
  72. // Isolation=Hyper-V example:
  73. //
  74. //{
  75. // "SystemType": "Container",
  76. // "Name": "475c2c58933b72687a88a441e7e0ca4bd72d76413c5f9d5031fee83b98f6045d",
  77. // "Owner": "docker",
  78. // "IgnoreFlushesDuringBoot": true,
  79. // "Layers": [{
  80. // "ID": "18955d65-d45a-557b-bf1c-49d6dfefc526",
  81. // "Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c"
  82. // }],
  83. // "HostName": "475c2c58933b",
  84. // "MappedDirectories": [],
  85. // "HvPartition": true,
  86. // "EndpointList": ["e1bb1e61-d56f-405e-b75d-fd520cefa0cb"],
  87. // "DNSSearchList": "a.com,b.com,c.com",
  88. // "HvRuntime": {
  89. // "ImagePath": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c\\\\UtilityVM"
  90. // },
  91. // "Servicing": false
  92. //}
  93. func (clnt *client) Create(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
  94. clnt.lock(containerID)
  95. defer clnt.unlock(containerID)
  96. if b, err := json.Marshal(spec); err == nil {
  97. logrus.Debugln("libcontainerd: client.Create() with spec", string(b))
  98. }
  99. // spec.Linux must be nil for Windows containers, but spec.Windows will be filled in regardless of container platform.
  100. // This is a temporary workaround due to LCOW requiring layer folder paths, which are stored under spec.Windows.
  101. // TODO: @darrenstahlmsft fix this once the OCI spec is updated to support layer folder paths for LCOW
  102. if spec.Linux == nil {
  103. return clnt.createWindows(containerID, checkpoint, checkpointDir, spec, attachStdio, options...)
  104. }
  105. return clnt.createLinux(containerID, checkpoint, checkpointDir, spec, attachStdio, options...)
  106. }
  107. func (clnt *client) createWindows(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
  108. configuration := &hcsshim.ContainerConfig{
  109. SystemType: "Container",
  110. Name: containerID,
  111. Owner: defaultOwner,
  112. IgnoreFlushesDuringBoot: spec.Windows.IgnoreFlushesDuringBoot,
  113. HostName: spec.Hostname,
  114. HvPartition: false,
  115. Servicing: spec.Windows.Servicing,
  116. }
  117. if spec.Windows.Resources != nil {
  118. if spec.Windows.Resources.CPU != nil {
  119. if spec.Windows.Resources.CPU.Count != nil {
  120. // This check is being done here rather than in adaptContainerSettings
  121. // because we don't want to update the HostConfig in case this container
  122. // is moved to a host with more CPUs than this one.
  123. cpuCount := *spec.Windows.Resources.CPU.Count
  124. hostCPUCount := uint64(sysinfo.NumCPU())
  125. if cpuCount > hostCPUCount {
  126. logrus.Warnf("Changing requested CPUCount of %d to current number of processors, %d", cpuCount, hostCPUCount)
  127. cpuCount = hostCPUCount
  128. }
  129. configuration.ProcessorCount = uint32(cpuCount)
  130. }
  131. if spec.Windows.Resources.CPU.Shares != nil {
  132. configuration.ProcessorWeight = uint64(*spec.Windows.Resources.CPU.Shares)
  133. }
  134. if spec.Windows.Resources.CPU.Maximum != nil {
  135. configuration.ProcessorMaximum = int64(*spec.Windows.Resources.CPU.Maximum)
  136. }
  137. }
  138. if spec.Windows.Resources.Memory != nil {
  139. if spec.Windows.Resources.Memory.Limit != nil {
  140. configuration.MemoryMaximumInMB = int64(*spec.Windows.Resources.Memory.Limit) / 1024 / 1024
  141. }
  142. }
  143. if spec.Windows.Resources.Storage != nil {
  144. if spec.Windows.Resources.Storage.Bps != nil {
  145. configuration.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps
  146. }
  147. if spec.Windows.Resources.Storage.Iops != nil {
  148. configuration.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops
  149. }
  150. }
  151. }
  152. if spec.Windows.HyperV != nil {
  153. configuration.HvPartition = true
  154. }
  155. if spec.Windows.Network != nil {
  156. configuration.EndpointList = spec.Windows.Network.EndpointList
  157. configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery
  158. if spec.Windows.Network.DNSSearchList != nil {
  159. configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",")
  160. }
  161. configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
  162. }
  163. if cs, ok := spec.Windows.CredentialSpec.(string); ok {
  164. configuration.Credentials = cs
  165. }
  166. // We must have least two layers in the spec, the bottom one being a base image,
  167. // the top one being the RW layer.
  168. if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) < 2 {
  169. return fmt.Errorf("OCI spec is invalid - at least two LayerFolders must be supplied to the runtime")
  170. }
  171. // Strip off the top-most layer as that's passed in separately to HCS
  172. configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1]
  173. layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1]
  174. if configuration.HvPartition {
  175. // We don't currently support setting the utility VM image explicitly.
  176. // TODO @swernli/jhowardmsft circa RS3/4, this may be re-locatable.
  177. if spec.Windows.HyperV.UtilityVMPath != "" {
  178. return errors.New("runtime does not support an explicit utility VM path for Hyper-V containers")
  179. }
  180. // Find the upper-most utility VM image.
  181. var uvmImagePath string
  182. for _, path := range layerFolders {
  183. fullPath := filepath.Join(path, "UtilityVM")
  184. _, err := os.Stat(fullPath)
  185. if err == nil {
  186. uvmImagePath = fullPath
  187. break
  188. }
  189. if !os.IsNotExist(err) {
  190. return err
  191. }
  192. }
  193. if uvmImagePath == "" {
  194. return errors.New("utility VM image could not be found")
  195. }
  196. configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: uvmImagePath}
  197. if spec.Root.Path != "" {
  198. return errors.New("OCI spec is invalid - Root.Path must be omitted for a Hyper-V container")
  199. }
  200. } else {
  201. const volumeGUIDRegex = `^\\\\\?\\(Volume)\{{0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}\}\\$`
  202. if _, err := regexp.MatchString(volumeGUIDRegex, spec.Root.Path); err != nil {
  203. return fmt.Errorf(`OCI spec is invalid - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, spec.Root.Path)
  204. }
  205. // HCS API requires the trailing backslash to be removed
  206. configuration.VolumePath = spec.Root.Path[:len(spec.Root.Path)-1]
  207. }
  208. if spec.Root.Readonly {
  209. return errors.New(`OCI spec is invalid - Root.Readonly must not be set on Windows`)
  210. }
  211. for _, layerPath := range layerFolders {
  212. _, filename := filepath.Split(layerPath)
  213. g, err := hcsshim.NameToGuid(filename)
  214. if err != nil {
  215. return err
  216. }
  217. configuration.Layers = append(configuration.Layers, hcsshim.Layer{
  218. ID: g.ToString(),
  219. Path: layerPath,
  220. })
  221. }
  222. // Add the mounts (volumes, bind mounts etc) to the structure
  223. var mds []hcsshim.MappedDir
  224. var mps []hcsshim.MappedPipe
  225. for _, mount := range spec.Mounts {
  226. const pipePrefix = `\\.\pipe\`
  227. if mount.Type != "" {
  228. return fmt.Errorf("OCI spec is invalid - Mount.Type '%s' must not be set", mount.Type)
  229. }
  230. if strings.HasPrefix(mount.Destination, pipePrefix) {
  231. mp := hcsshim.MappedPipe{
  232. HostPath: mount.Source,
  233. ContainerPipeName: mount.Destination[len(pipePrefix):],
  234. }
  235. mps = append(mps, mp)
  236. } else {
  237. md := hcsshim.MappedDir{
  238. HostPath: mount.Source,
  239. ContainerPath: mount.Destination,
  240. ReadOnly: false,
  241. }
  242. for _, o := range mount.Options {
  243. if strings.ToLower(o) == "ro" {
  244. md.ReadOnly = true
  245. }
  246. }
  247. mds = append(mds, md)
  248. }
  249. }
  250. configuration.MappedDirectories = mds
  251. if len(mps) > 0 && system.GetOSVersion().Build < 16210 { // replace with Win10 RS3 build number at RTM
  252. return errors.New("named pipe mounts are not supported on this version of Windows")
  253. }
  254. configuration.MappedPipes = mps
  255. hcsContainer, err := hcsshim.CreateContainer(containerID, configuration)
  256. if err != nil {
  257. return err
  258. }
  259. // Construct a container object for calling start on it.
  260. container := &container{
  261. containerCommon: containerCommon{
  262. process: process{
  263. processCommon: processCommon{
  264. containerID: containerID,
  265. client: clnt,
  266. friendlyName: InitFriendlyName,
  267. },
  268. },
  269. processes: make(map[string]*process),
  270. },
  271. isWindows: true,
  272. ociSpec: spec,
  273. hcsContainer: hcsContainer,
  274. }
  275. container.options = options
  276. for _, option := range options {
  277. if err := option.Apply(container); err != nil {
  278. logrus.Errorf("libcontainerd: %v", err)
  279. }
  280. }
  281. // Call start, and if it fails, delete the container from our
  282. // internal structure, start will keep HCS in sync by deleting the
  283. // container there.
  284. logrus.Debugf("libcontainerd: createWindows() id=%s, Calling start()", containerID)
  285. if err := container.start(attachStdio); err != nil {
  286. clnt.deleteContainer(containerID)
  287. return err
  288. }
  289. logrus.Debugf("libcontainerd: createWindows() id=%s completed successfully", containerID)
  290. return nil
  291. }
  292. func (clnt *client) createLinux(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
  293. logrus.Debugf("libcontainerd: createLinux(): containerId %s ", containerID)
  294. var lcowOpt *LCOWOption
  295. for _, option := range options {
  296. if lcow, ok := option.(*LCOWOption); ok {
  297. lcowOpt = lcow
  298. }
  299. }
  300. if lcowOpt == nil || lcowOpt.Config == nil {
  301. return fmt.Errorf("lcow option must be supplied to the runtime")
  302. }
  303. configuration := &hcsshim.ContainerConfig{
  304. HvPartition: true,
  305. Name: containerID,
  306. SystemType: "container",
  307. ContainerType: "linux",
  308. Owner: defaultOwner,
  309. TerminateOnLastHandleClosed: true,
  310. }
  311. if lcowOpt.Config.ActualMode == opengcs.ModeActualVhdx {
  312. configuration.HvRuntime = &hcsshim.HvRuntime{
  313. ImagePath: lcowOpt.Config.Vhdx,
  314. BootSource: "Vhd",
  315. WritableBootSource: false,
  316. }
  317. } else {
  318. configuration.HvRuntime = &hcsshim.HvRuntime{
  319. ImagePath: lcowOpt.Config.KirdPath,
  320. LinuxKernelFile: lcowOpt.Config.KernelFile,
  321. LinuxInitrdFile: lcowOpt.Config.InitrdFile,
  322. LinuxBootParameters: lcowOpt.Config.BootParameters,
  323. }
  324. }
  325. if spec.Windows == nil {
  326. return fmt.Errorf("spec.Windows must not be nil for LCOW containers")
  327. }
  328. // We must have least one layer in the spec
  329. if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) == 0 {
  330. return fmt.Errorf("OCI spec is invalid - at least one LayerFolders must be supplied to the runtime")
  331. }
  332. // Strip off the top-most layer as that's passed in separately to HCS
  333. configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1]
  334. layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1]
  335. for _, layerPath := range layerFolders {
  336. _, filename := filepath.Split(layerPath)
  337. g, err := hcsshim.NameToGuid(filename)
  338. if err != nil {
  339. return err
  340. }
  341. configuration.Layers = append(configuration.Layers, hcsshim.Layer{
  342. ID: g.ToString(),
  343. Path: filepath.Join(layerPath, "layer.vhd"),
  344. })
  345. }
  346. if spec.Windows.Network != nil {
  347. configuration.EndpointList = spec.Windows.Network.EndpointList
  348. configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery
  349. if spec.Windows.Network.DNSSearchList != nil {
  350. configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",")
  351. }
  352. configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
  353. }
  354. // Add the mounts (volumes, bind mounts etc) to the structure. We have to do
  355. // some translation for both the mapped directories passed into HCS and in
  356. // the spec.
  357. //
  358. // For HCS, we only pass in the mounts from the spec which are type "bind".
  359. // Further, the "ContainerPath" field (which is a little mis-leadingly
  360. // named when it applies to the utility VM rather than the container in the
  361. // utility VM) is moved to under /tmp/gcs/<ID>/binds, where this is passed
  362. // by the caller through a 'uvmpath' option.
  363. //
  364. // We do similar translation for the mounts in the spec by stripping out
  365. // the uvmpath option, and translating the Source path to the location in the
  366. // utility VM calculated above.
  367. //
  368. // From inside the utility VM, you would see a 9p mount such as in the following
  369. // where a host folder has been mapped to /target. The line with /tmp/gcs/<ID>/binds
  370. // specifically:
  371. //
  372. // / # mount
  373. // rootfs on / type rootfs (rw,size=463736k,nr_inodes=115934)
  374. // proc on /proc type proc (rw,relatime)
  375. // sysfs on /sys type sysfs (rw,relatime)
  376. // udev on /dev type devtmpfs (rw,relatime,size=498100k,nr_inodes=124525,mode=755)
  377. // tmpfs on /run type tmpfs (rw,relatime)
  378. // cgroup on /sys/fs/cgroup type cgroup (rw,relatime,cpuset,cpu,cpuacct,blkio,memory,devices,freezer,net_cls,perf_event,net_prio,hugetlb,pids,rdma)
  379. // mqueue on /dev/mqueue type mqueue (rw,relatime)
  380. // devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000)
  381. // /binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target on /binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target type 9p (rw,sync,dirsync,relatime,trans=fd,rfdno=6,wfdno=6)
  382. // /dev/pmem0 on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0 type ext4 (ro,relatime,block_validity,delalloc,norecovery,barrier,dax,user_xattr,acl)
  383. // /dev/sda on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch type ext4 (rw,relatime,block_validity,delalloc,barrier,user_xattr,acl)
  384. // overlay on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/rootfs type overlay (rw,relatime,lowerdir=/tmp/base/:/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0,upperdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/upper,workdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/work)
  385. //
  386. // /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l
  387. // total 16
  388. // drwx------ 3 0 0 60 Sep 7 18:54 binds
  389. // -rw-r--r-- 1 0 0 3345 Sep 7 18:54 config.json
  390. // drwxr-xr-x 10 0 0 4096 Sep 6 17:26 layer0
  391. // drwxr-xr-x 1 0 0 4096 Sep 7 18:54 rootfs
  392. // drwxr-xr-x 5 0 0 4096 Sep 7 18:54 scratch
  393. //
  394. // /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l binds
  395. // total 0
  396. // drwxrwxrwt 2 0 0 4096 Sep 7 16:51 target
  397. mds := []hcsshim.MappedDir{}
  398. specMounts := []specs.Mount{}
  399. for _, mount := range spec.Mounts {
  400. specMount := mount
  401. if mount.Type == "bind" {
  402. // Strip out the uvmpath from the options
  403. updatedOptions := []string{}
  404. uvmPath := ""
  405. readonly := false
  406. for _, opt := range mount.Options {
  407. dropOption := false
  408. elements := strings.SplitN(opt, "=", 2)
  409. switch elements[0] {
  410. case "uvmpath":
  411. uvmPath = elements[1]
  412. dropOption = true
  413. case "rw":
  414. case "ro":
  415. readonly = true
  416. case "rbind":
  417. default:
  418. return fmt.Errorf("unsupported option %q", opt)
  419. }
  420. if !dropOption {
  421. updatedOptions = append(updatedOptions, opt)
  422. }
  423. }
  424. mount.Options = updatedOptions
  425. if uvmPath == "" {
  426. return fmt.Errorf("no uvmpath for bind mount %+v", mount)
  427. }
  428. md := hcsshim.MappedDir{
  429. HostPath: mount.Source,
  430. ContainerPath: path.Join(uvmPath, mount.Destination),
  431. CreateInUtilityVM: true,
  432. ReadOnly: readonly,
  433. }
  434. mds = append(mds, md)
  435. specMount.Source = path.Join(uvmPath, mount.Destination)
  436. }
  437. specMounts = append(specMounts, specMount)
  438. }
  439. configuration.MappedDirectories = mds
  440. hcsContainer, err := hcsshim.CreateContainer(containerID, configuration)
  441. if err != nil {
  442. return err
  443. }
  444. spec.Mounts = specMounts
  445. // Construct a container object for calling start on it.
  446. container := &container{
  447. containerCommon: containerCommon{
  448. process: process{
  449. processCommon: processCommon{
  450. containerID: containerID,
  451. client: clnt,
  452. friendlyName: InitFriendlyName,
  453. },
  454. },
  455. processes: make(map[string]*process),
  456. },
  457. ociSpec: spec,
  458. hcsContainer: hcsContainer,
  459. }
  460. container.options = options
  461. for _, option := range options {
  462. if err := option.Apply(container); err != nil {
  463. logrus.Errorf("libcontainerd: createLinux() %v", err)
  464. }
  465. }
  466. // Call start, and if it fails, delete the container from our
  467. // internal structure, start will keep HCS in sync by deleting the
  468. // container there.
  469. logrus.Debugf("libcontainerd: createLinux() id=%s, Calling start()", containerID)
  470. if err := container.start(attachStdio); err != nil {
  471. clnt.deleteContainer(containerID)
  472. return err
  473. }
  474. logrus.Debugf("libcontainerd: createLinux() id=%s completed successfully", containerID)
  475. return nil
  476. }
  477. // AddProcess is the handler for adding a process to an already running
  478. // container. It's called through docker exec. It returns the system pid of the
  479. // exec'd process.
  480. func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendlyName string, procToAdd Process, attachStdio StdioCallback) (int, error) {
  481. clnt.lock(containerID)
  482. defer clnt.unlock(containerID)
  483. container, err := clnt.getContainer(containerID)
  484. if err != nil {
  485. return -1, err
  486. }
  487. defer container.debugGCS()
  488. // Note we always tell HCS to
  489. // create stdout as it's required regardless of '-i' or '-t' options, so that
  490. // docker can always grab the output through logs. We also tell HCS to always
  491. // create stdin, even if it's not used - it will be closed shortly. Stderr
  492. // is only created if it we're not -t.
  493. createProcessParms := hcsshim.ProcessConfig{
  494. CreateStdInPipe: true,
  495. CreateStdOutPipe: true,
  496. CreateStdErrPipe: !procToAdd.Terminal,
  497. }
  498. if procToAdd.Terminal {
  499. createProcessParms.EmulateConsole = true
  500. if procToAdd.ConsoleSize != nil {
  501. createProcessParms.ConsoleSize[0] = uint(procToAdd.ConsoleSize.Height)
  502. createProcessParms.ConsoleSize[1] = uint(procToAdd.ConsoleSize.Width)
  503. }
  504. }
  505. // Take working directory from the process to add if it is defined,
  506. // otherwise take from the first process.
  507. if procToAdd.Cwd != "" {
  508. createProcessParms.WorkingDirectory = procToAdd.Cwd
  509. } else {
  510. createProcessParms.WorkingDirectory = container.ociSpec.Process.Cwd
  511. }
  512. // Configure the environment for the process
  513. createProcessParms.Environment = setupEnvironmentVariables(procToAdd.Env)
  514. if container.isWindows {
  515. createProcessParms.CommandLine = strings.Join(procToAdd.Args, " ")
  516. } else {
  517. createProcessParms.CommandArgs = procToAdd.Args
  518. }
  519. createProcessParms.User = procToAdd.User.Username
  520. logrus.Debugf("libcontainerd: commandLine: %s", createProcessParms.CommandLine)
  521. // Start the command running in the container.
  522. var stdout, stderr io.ReadCloser
  523. var stdin io.WriteCloser
  524. newProcess, err := container.hcsContainer.CreateProcess(&createProcessParms)
  525. if err != nil {
  526. logrus.Errorf("libcontainerd: AddProcess(%s) CreateProcess() failed %s", containerID, err)
  527. return -1, err
  528. }
  529. pid := newProcess.Pid()
  530. stdin, stdout, stderr, err = newProcess.Stdio()
  531. if err != nil {
  532. logrus.Errorf("libcontainerd: %s getting std pipes failed %s", containerID, err)
  533. return -1, err
  534. }
  535. iopipe := &IOPipe{Terminal: procToAdd.Terminal}
  536. iopipe.Stdin = createStdInCloser(stdin, newProcess)
  537. // Convert io.ReadClosers to io.Readers
  538. if stdout != nil {
  539. iopipe.Stdout = ioutil.NopCloser(&autoClosingReader{ReadCloser: stdout})
  540. }
  541. if stderr != nil {
  542. iopipe.Stderr = ioutil.NopCloser(&autoClosingReader{ReadCloser: stderr})
  543. }
  544. proc := &process{
  545. processCommon: processCommon{
  546. containerID: containerID,
  547. friendlyName: processFriendlyName,
  548. client: clnt,
  549. systemPid: uint32(pid),
  550. },
  551. hcsProcess: newProcess,
  552. }
  553. // Add the process to the container's list of processes
  554. container.processes[processFriendlyName] = proc
  555. // Tell the engine to attach streams back to the client
  556. if err := attachStdio(*iopipe); err != nil {
  557. return -1, err
  558. }
  559. // Spin up a go routine waiting for exit to handle cleanup
  560. go container.waitExit(proc, false)
  561. return pid, nil
  562. }
  563. // Signal handles `docker stop` on Windows. While Linux has support for
  564. // the full range of signals, signals aren't really implemented on Windows.
  565. // We fake supporting regular stop and -9 to force kill.
  566. func (clnt *client) Signal(containerID string, sig int) error {
  567. var (
  568. cont *container
  569. err error
  570. )
  571. // Get the container as we need it to get the container handle.
  572. clnt.lock(containerID)
  573. defer clnt.unlock(containerID)
  574. if cont, err = clnt.getContainer(containerID); err != nil {
  575. return err
  576. }
  577. cont.manualStopRequested = true
  578. logrus.Debugf("libcontainerd: Signal() containerID=%s sig=%d pid=%d", containerID, sig, cont.systemPid)
  579. if syscall.Signal(sig) == syscall.SIGKILL {
  580. // Terminate the compute system
  581. if err := cont.hcsContainer.Terminate(); err != nil {
  582. if !hcsshim.IsPending(err) {
  583. logrus.Errorf("libcontainerd: failed to terminate %s - %q", containerID, err)
  584. }
  585. }
  586. } else {
  587. // Shut down the container
  588. if err := cont.hcsContainer.Shutdown(); err != nil {
  589. if !hcsshim.IsPending(err) && !hcsshim.IsAlreadyStopped(err) {
  590. // ignore errors
  591. logrus.Warnf("libcontainerd: failed to shutdown container %s: %q", containerID, err)
  592. }
  593. }
  594. }
  595. return nil
  596. }
  597. // While Linux has support for the full range of signals, signals aren't really implemented on Windows.
  598. // We try to terminate the specified process whatever signal is requested.
  599. func (clnt *client) SignalProcess(containerID string, processFriendlyName string, sig int) error {
  600. clnt.lock(containerID)
  601. defer clnt.unlock(containerID)
  602. cont, err := clnt.getContainer(containerID)
  603. if err != nil {
  604. return err
  605. }
  606. for _, p := range cont.processes {
  607. if p.friendlyName == processFriendlyName {
  608. return p.hcsProcess.Kill()
  609. }
  610. }
  611. return fmt.Errorf("SignalProcess could not find process %s in %s", processFriendlyName, containerID)
  612. }
  613. // Resize handles a CLI event to resize an interactive docker run or docker exec
  614. // window.
  615. func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error {
  616. // Get the libcontainerd container object
  617. clnt.lock(containerID)
  618. defer clnt.unlock(containerID)
  619. cont, err := clnt.getContainer(containerID)
  620. if err != nil {
  621. return err
  622. }
  623. h, w := uint16(height), uint16(width)
  624. if processFriendlyName == InitFriendlyName {
  625. logrus.Debugln("libcontainerd: resizing systemPID in", containerID, cont.process.systemPid)
  626. return cont.process.hcsProcess.ResizeConsole(w, h)
  627. }
  628. for _, p := range cont.processes {
  629. if p.friendlyName == processFriendlyName {
  630. logrus.Debugln("libcontainerd: resizing exec'd process", containerID, p.systemPid)
  631. return p.hcsProcess.ResizeConsole(w, h)
  632. }
  633. }
  634. return fmt.Errorf("Resize could not find containerID %s to resize", containerID)
  635. }
  636. // Pause handles pause requests for containers
  637. func (clnt *client) Pause(containerID string) error {
  638. unlockContainer := true
  639. // Get the libcontainerd container object
  640. clnt.lock(containerID)
  641. defer func() {
  642. if unlockContainer {
  643. clnt.unlock(containerID)
  644. }
  645. }()
  646. container, err := clnt.getContainer(containerID)
  647. if err != nil {
  648. return err
  649. }
  650. if container.ociSpec.Windows.HyperV == nil {
  651. return errors.New("cannot pause Windows Server Containers")
  652. }
  653. err = container.hcsContainer.Pause()
  654. if err != nil {
  655. return err
  656. }
  657. // Unlock container before calling back into the daemon
  658. unlockContainer = false
  659. clnt.unlock(containerID)
  660. return clnt.backend.StateChanged(containerID, StateInfo{
  661. CommonStateInfo: CommonStateInfo{
  662. State: StatePause,
  663. }})
  664. }
  665. // Resume handles resume requests for containers
  666. func (clnt *client) Resume(containerID string) error {
  667. unlockContainer := true
  668. // Get the libcontainerd container object
  669. clnt.lock(containerID)
  670. defer func() {
  671. if unlockContainer {
  672. clnt.unlock(containerID)
  673. }
  674. }()
  675. container, err := clnt.getContainer(containerID)
  676. if err != nil {
  677. return err
  678. }
  679. // This should never happen, since Windows Server Containers cannot be paused
  680. if container.ociSpec.Windows.HyperV == nil {
  681. return errors.New("cannot resume Windows Server Containers")
  682. }
  683. err = container.hcsContainer.Resume()
  684. if err != nil {
  685. return err
  686. }
  687. // Unlock container before calling back into the daemon
  688. unlockContainer = false
  689. clnt.unlock(containerID)
  690. return clnt.backend.StateChanged(containerID, StateInfo{
  691. CommonStateInfo: CommonStateInfo{
  692. State: StateResume,
  693. }})
  694. }
  695. // Stats handles stats requests for containers
  696. func (clnt *client) Stats(containerID string) (*Stats, error) {
  697. // Get the libcontainerd container object
  698. clnt.lock(containerID)
  699. defer clnt.unlock(containerID)
  700. container, err := clnt.getContainer(containerID)
  701. if err != nil {
  702. return nil, err
  703. }
  704. s, err := container.hcsContainer.Statistics()
  705. if err != nil {
  706. return nil, err
  707. }
  708. st := Stats(s)
  709. return &st, nil
  710. }
  711. // Restore is the handler for restoring a container
  712. func (clnt *client) Restore(containerID string, _ StdioCallback, unusedOnWindows ...CreateOption) error {
  713. logrus.Debugf("libcontainerd: Restore(%s)", containerID)
  714. // TODO Windows: On RS1, a re-attach isn't possible.
  715. // However, there is a scenario in which there is an issue.
  716. // Consider a background container. The daemon dies unexpectedly.
  717. // HCS will still have the compute service alive and running.
  718. // For consistence, we call in to shoot it regardless if HCS knows about it
  719. // We explicitly just log a warning if the terminate fails.
  720. // Then we tell the backend the container exited.
  721. if hc, err := hcsshim.OpenContainer(containerID); err == nil {
  722. const terminateTimeout = time.Minute * 2
  723. err := hc.Terminate()
  724. if hcsshim.IsPending(err) {
  725. err = hc.WaitTimeout(terminateTimeout)
  726. } else if hcsshim.IsAlreadyStopped(err) {
  727. err = nil
  728. }
  729. if err != nil {
  730. logrus.Warnf("libcontainerd: failed to terminate %s on restore - %q", containerID, err)
  731. return err
  732. }
  733. }
  734. return clnt.backend.StateChanged(containerID, StateInfo{
  735. CommonStateInfo: CommonStateInfo{
  736. State: StateExit,
  737. ExitCode: 1 << 31,
  738. }})
  739. }
  740. // GetPidsForContainer returns a list of process IDs running in a container.
  741. // Not used on Windows.
  742. func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) {
  743. return nil, errors.New("not implemented on Windows")
  744. }
  745. // Summary returns a summary of the processes running in a container.
  746. // This is present in Windows to support docker top. In linux, the
  747. // engine shells out to ps to get process information. On Windows, as
  748. // the containers could be Hyper-V containers, they would not be
  749. // visible on the container host. However, libcontainerd does have
  750. // that information.
  751. func (clnt *client) Summary(containerID string) ([]Summary, error) {
  752. // Get the libcontainerd container object
  753. clnt.lock(containerID)
  754. defer clnt.unlock(containerID)
  755. container, err := clnt.getContainer(containerID)
  756. if err != nil {
  757. return nil, err
  758. }
  759. p, err := container.hcsContainer.ProcessList()
  760. if err != nil {
  761. return nil, err
  762. }
  763. pl := make([]Summary, len(p))
  764. for i := range p {
  765. pl[i] = Summary(p[i])
  766. }
  767. return pl, nil
  768. }
  769. // UpdateResources updates resources for a running container.
  770. func (clnt *client) UpdateResources(containerID string, resources Resources) error {
  771. // Updating resource isn't supported on Windows
  772. // but we should return nil for enabling updating container
  773. return nil
  774. }
  775. func (clnt *client) CreateCheckpoint(containerID string, checkpointID string, checkpointDir string, exit bool) error {
  776. return errors.New("Windows: Containers do not support checkpoints")
  777. }
  778. func (clnt *client) DeleteCheckpoint(containerID string, checkpointID string, checkpointDir string) error {
  779. return errors.New("Windows: Containers do not support checkpoints")
  780. }
  781. func (clnt *client) ListCheckpoints(containerID string, checkpointDir string) (*Checkpoints, error) {
  782. return nil, errors.New("Windows: Containers do not support checkpoints")
  783. }
  784. func (clnt *client) GetServerVersion(ctx context.Context) (*ServerVersion, error) {
  785. return &ServerVersion{}, nil
  786. }