client_windows.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. package libcontainerd
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "path/filepath"
  8. "strings"
  9. "syscall"
  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. type layer struct {
  27. ID string
  28. Path string
  29. }
  30. type defConfig struct {
  31. DefFile string
  32. }
  33. type portBinding struct {
  34. Protocol string
  35. InternalPort int
  36. ExternalPort int
  37. }
  38. type natSettings struct {
  39. Name string
  40. PortBindings []portBinding
  41. }
  42. type networkConnection struct {
  43. NetworkName string
  44. Nat natSettings
  45. }
  46. type networkSettings struct {
  47. MacAddress string
  48. }
  49. type device struct {
  50. DeviceType string
  51. Connection interface{}
  52. Settings interface{}
  53. }
  54. type mappedDir struct {
  55. HostPath string
  56. ContainerPath string
  57. ReadOnly bool
  58. }
  59. type hvRuntime struct {
  60. ImagePath string `json:",omitempty"`
  61. }
  62. // TODO Windows: @darrenstahlmsft Add ProcessorCount
  63. type containerInit struct {
  64. SystemType string // HCS requires this to be hard-coded to "Container"
  65. Name string // Name of the container. We use the docker ID.
  66. Owner string // The management platform that created this container
  67. IsDummy bool // Used for development purposes.
  68. VolumePath string // Windows volume path for scratch space
  69. Devices []device // Devices used by the container
  70. IgnoreFlushesDuringBoot bool // Optimization hint for container startup in Windows
  71. LayerFolderPath string // Where the layer folders are located
  72. Layers []layer // List of storage layers
  73. ProcessorWeight uint64 `json:",omitempty"` // CPU Shares 0..10000 on Windows; where 0 will be omitted and HCS will default.
  74. ProcessorMaximum int64 `json:",omitempty"` // CPU maximum usage percent 1..100
  75. StorageIOPSMaximum uint64 `json:",omitempty"` // Maximum Storage IOPS
  76. StorageBandwidthMaximum uint64 `json:",omitempty"` // Maximum Storage Bandwidth in bytes per second
  77. StorageSandboxSize uint64 `json:",omitempty"` // Size in bytes that the container system drive should be expanded to if smaller
  78. MemoryMaximumInMB int64 `json:",omitempty"` // Maximum memory available to the container in Megabytes
  79. HostName string // Hostname
  80. MappedDirectories []mappedDir // List of mapped directories (volumes/mounts)
  81. SandboxPath string // Location of unmounted sandbox (used for Hyper-V containers)
  82. HvPartition bool // True if it a Hyper-V Container
  83. EndpointList []string // List of networking endpoints to be attached to container
  84. HvRuntime *hvRuntime // Hyper-V container settings
  85. Servicing bool // True if this container is for servicing
  86. }
  87. // defaultOwner is a tag passed to HCS to allow it to differentiate between
  88. // container creator management stacks. We hard code "docker" in the case
  89. // of docker.
  90. const defaultOwner = "docker"
  91. // Create is the entrypoint to create a container from a spec, and if successfully
  92. // created, start it too.
  93. func (clnt *client) Create(containerID string, spec Spec, options ...CreateOption) error {
  94. logrus.Debugln("LCD client.Create() with spec", spec)
  95. cu := &containerInit{
  96. SystemType: "Container",
  97. Name: containerID,
  98. Owner: defaultOwner,
  99. VolumePath: spec.Root.Path,
  100. IgnoreFlushesDuringBoot: spec.Windows.FirstStart,
  101. LayerFolderPath: spec.Windows.LayerFolder,
  102. HostName: spec.Hostname,
  103. }
  104. if spec.Windows.Networking != nil {
  105. cu.EndpointList = spec.Windows.Networking.EndpointList
  106. }
  107. if spec.Windows.Resources != nil {
  108. if spec.Windows.Resources.CPU != nil {
  109. if spec.Windows.Resources.CPU.Shares != nil {
  110. cu.ProcessorWeight = *spec.Windows.Resources.CPU.Shares
  111. }
  112. if spec.Windows.Resources.CPU.Percent != nil {
  113. cu.ProcessorMaximum = *spec.Windows.Resources.CPU.Percent * 100 // ProcessorMaximum is a value between 1 and 10000
  114. }
  115. }
  116. if spec.Windows.Resources.Memory != nil {
  117. if spec.Windows.Resources.Memory.Limit != nil {
  118. cu.MemoryMaximumInMB = *spec.Windows.Resources.Memory.Limit / 1024 / 1024
  119. }
  120. }
  121. if spec.Windows.Resources.Storage != nil {
  122. if spec.Windows.Resources.Storage.Bps != nil {
  123. cu.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps
  124. }
  125. if spec.Windows.Resources.Storage.Iops != nil {
  126. cu.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops
  127. }
  128. if spec.Windows.Resources.Storage.SandboxSize != nil {
  129. cu.StorageSandboxSize = *spec.Windows.Resources.Storage.SandboxSize
  130. }
  131. }
  132. }
  133. if spec.Windows.HvRuntime != nil {
  134. cu.HvPartition = true
  135. cu.HvRuntime = &hvRuntime{
  136. ImagePath: spec.Windows.HvRuntime.ImagePath,
  137. }
  138. }
  139. for _, option := range options {
  140. if s, ok := option.(*ServicingOption); ok {
  141. cu.Servicing = s.IsServicing
  142. break
  143. }
  144. }
  145. if cu.HvPartition {
  146. cu.SandboxPath = filepath.Dir(spec.Windows.LayerFolder)
  147. } else {
  148. cu.VolumePath = spec.Root.Path
  149. cu.LayerFolderPath = spec.Windows.LayerFolder
  150. }
  151. for _, layerPath := range spec.Windows.LayerPaths {
  152. _, filename := filepath.Split(layerPath)
  153. g, err := hcsshim.NameToGuid(filename)
  154. if err != nil {
  155. return err
  156. }
  157. cu.Layers = append(cu.Layers, layer{
  158. ID: g.ToString(),
  159. Path: layerPath,
  160. })
  161. }
  162. // Add the mounts (volumes, bind mounts etc) to the structure
  163. mds := make([]mappedDir, len(spec.Mounts))
  164. for i, mount := range spec.Mounts {
  165. mds[i] = mappedDir{
  166. HostPath: mount.Source,
  167. ContainerPath: mount.Destination,
  168. ReadOnly: mount.Readonly}
  169. }
  170. cu.MappedDirectories = mds
  171. configurationb, err := json.Marshal(cu)
  172. if err != nil {
  173. return err
  174. }
  175. // Create the compute system
  176. configuration := string(configurationb)
  177. if err := hcsshim.CreateComputeSystem(containerID, configuration); err != nil {
  178. return err
  179. }
  180. // Construct a container object for calling start on it.
  181. container := &container{
  182. containerCommon: containerCommon{
  183. process: process{
  184. processCommon: processCommon{
  185. containerID: containerID,
  186. client: clnt,
  187. friendlyName: InitFriendlyName,
  188. },
  189. commandLine: strings.Join(spec.Process.Args, " "),
  190. },
  191. processes: make(map[string]*process),
  192. },
  193. ociSpec: spec,
  194. }
  195. container.options = options
  196. for _, option := range options {
  197. if err := option.Apply(container); err != nil {
  198. logrus.Error(err)
  199. }
  200. }
  201. // Call start, and if it fails, delete the container from our
  202. // internal structure, and also keep HCS in sync by deleting the
  203. // container there.
  204. logrus.Debugf("Create() id=%s, Calling start()", containerID)
  205. if err := container.start(); err != nil {
  206. clnt.deleteContainer(containerID)
  207. return err
  208. }
  209. logrus.Debugf("Create() id=%s completed successfully", containerID)
  210. return nil
  211. }
  212. // AddProcess is the handler for adding a process to an already running
  213. // container. It's called through docker exec.
  214. func (clnt *client) AddProcess(containerID, processFriendlyName string, procToAdd Process) error {
  215. clnt.lock(containerID)
  216. defer clnt.unlock(containerID)
  217. container, err := clnt.getContainer(containerID)
  218. if err != nil {
  219. return err
  220. }
  221. createProcessParms := hcsshim.CreateProcessParams{
  222. EmulateConsole: procToAdd.Terminal,
  223. ConsoleSize: procToAdd.InitialConsoleSize,
  224. }
  225. // Take working directory from the process to add if it is defined,
  226. // otherwise take from the first process.
  227. if procToAdd.Cwd != "" {
  228. createProcessParms.WorkingDirectory = procToAdd.Cwd
  229. } else {
  230. createProcessParms.WorkingDirectory = container.ociSpec.Process.Cwd
  231. }
  232. // Configure the environment for the process
  233. createProcessParms.Environment = setupEnvironmentVariables(procToAdd.Env)
  234. createProcessParms.CommandLine = strings.Join(procToAdd.Args, " ")
  235. logrus.Debugf("commandLine: %s", createProcessParms.CommandLine)
  236. // Start the command running in the container. Note we always tell HCS to
  237. // create stdout as it's required regardless of '-i' or '-t' options, so that
  238. // docker can always grab the output through logs. We also tell HCS to always
  239. // create stdin, even if it's not used - it will be closed shortly. Stderr
  240. // is only created if it we're not -t.
  241. var stdout, stderr io.ReadCloser
  242. var pid uint32
  243. iopipe := &IOPipe{Terminal: procToAdd.Terminal}
  244. pid, iopipe.Stdin, stdout, stderr, err = hcsshim.CreateProcessInComputeSystem(
  245. containerID,
  246. true,
  247. true,
  248. !procToAdd.Terminal,
  249. createProcessParms)
  250. if err != nil {
  251. logrus.Errorf("AddProcess %s CreateProcessInComputeSystem() failed %s", containerID, err)
  252. return err
  253. }
  254. // Convert io.ReadClosers to io.Readers
  255. if stdout != nil {
  256. iopipe.Stdout = openReaderFromPipe(stdout)
  257. }
  258. if stderr != nil {
  259. iopipe.Stderr = openReaderFromPipe(stderr)
  260. }
  261. // Add the process to the containers list of processes
  262. container.processes[processFriendlyName] =
  263. &process{
  264. processCommon: processCommon{
  265. containerID: containerID,
  266. friendlyName: processFriendlyName,
  267. client: clnt,
  268. systemPid: pid,
  269. },
  270. commandLine: createProcessParms.CommandLine,
  271. }
  272. // Make sure the lock is not held while calling back into the daemon
  273. clnt.unlock(containerID)
  274. // Tell the engine to attach streams back to the client
  275. if err := clnt.backend.AttachStreams(processFriendlyName, *iopipe); err != nil {
  276. return err
  277. }
  278. // Lock again so that the defer unlock doesn't fail. (I really don't like this code)
  279. clnt.lock(containerID)
  280. // Spin up a go routine waiting for exit to handle cleanup
  281. go container.waitExit(pid, processFriendlyName, false)
  282. return nil
  283. }
  284. // Signal handles `docker stop` on Windows. While Linux has support for
  285. // the full range of signals, signals aren't really implemented on Windows.
  286. // We fake supporting regular stop and -9 to force kill.
  287. func (clnt *client) Signal(containerID string, sig int) error {
  288. var (
  289. cont *container
  290. err error
  291. )
  292. // Get the container as we need it to find the pid of the process.
  293. clnt.lock(containerID)
  294. defer clnt.unlock(containerID)
  295. if cont, err = clnt.getContainer(containerID); err != nil {
  296. return err
  297. }
  298. cont.manualStopRequested = true
  299. logrus.Debugf("lcd: Signal() containerID=%s sig=%d pid=%d", containerID, sig, cont.systemPid)
  300. context := fmt.Sprintf("Signal: sig=%d pid=%d", sig, cont.systemPid)
  301. if syscall.Signal(sig) == syscall.SIGKILL {
  302. // Terminate the compute system
  303. if err := hcsshim.TerminateComputeSystem(containerID, hcsshim.TimeoutInfinite, context); err != nil {
  304. logrus.Errorf("Failed to terminate %s - %q", containerID, err)
  305. }
  306. } else {
  307. // Terminate Process
  308. if err = hcsshim.TerminateProcessInComputeSystem(containerID, cont.systemPid); err != nil {
  309. logrus.Warnf("Failed to terminate pid %d in %s: %q", cont.systemPid, containerID, err)
  310. // Ignore errors
  311. err = nil
  312. }
  313. }
  314. return nil
  315. }
  316. // Resize handles a CLI event to resize an interactive docker run or docker exec
  317. // window.
  318. func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error {
  319. // Get the libcontainerd container object
  320. clnt.lock(containerID)
  321. defer clnt.unlock(containerID)
  322. cont, err := clnt.getContainer(containerID)
  323. if err != nil {
  324. return err
  325. }
  326. if processFriendlyName == InitFriendlyName {
  327. logrus.Debugln("Resizing systemPID in", containerID, cont.process.systemPid)
  328. return hcsshim.ResizeConsoleInComputeSystem(containerID, cont.process.systemPid, height, width)
  329. }
  330. for _, p := range cont.processes {
  331. if p.friendlyName == processFriendlyName {
  332. logrus.Debugln("Resizing exec'd process", containerID, p.systemPid)
  333. return hcsshim.ResizeConsoleInComputeSystem(containerID, p.systemPid, height, width)
  334. }
  335. }
  336. return fmt.Errorf("Resize could not find containerID %s to resize", containerID)
  337. }
  338. // Pause handles pause requests for containers
  339. func (clnt *client) Pause(containerID string) error {
  340. return errors.New("Windows: Containers cannot be paused")
  341. }
  342. // Resume handles resume requests for containers
  343. func (clnt *client) Resume(containerID string) error {
  344. return errors.New("Windows: Containers cannot be paused")
  345. }
  346. // Stats handles stats requests for containers
  347. func (clnt *client) Stats(containerID string) (*Stats, error) {
  348. return nil, errors.New("Windows: Stats not implemented")
  349. }
  350. // Restore is the handler for restoring a container
  351. func (clnt *client) Restore(containerID string, unusedOnWindows ...CreateOption) error {
  352. // TODO Windows: Implement this. For now, just tell the backend the container exited.
  353. logrus.Debugf("lcd Restore %s", containerID)
  354. return clnt.backend.StateChanged(containerID, StateInfo{
  355. CommonStateInfo: CommonStateInfo{
  356. State: StateExit,
  357. ExitCode: 1 << 31,
  358. }})
  359. }
  360. // GetPidsForContainer returns a list of process IDs running in a container.
  361. // Although implemented, this is not used in Windows.
  362. func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) {
  363. var pids []int
  364. clnt.lock(containerID)
  365. defer clnt.unlock(containerID)
  366. cont, err := clnt.getContainer(containerID)
  367. if err != nil {
  368. return nil, err
  369. }
  370. // Add the first process
  371. pids = append(pids, int(cont.containerCommon.systemPid))
  372. // And add all the exec'd processes
  373. for _, p := range cont.processes {
  374. pids = append(pids, int(p.processCommon.systemPid))
  375. }
  376. return pids, nil
  377. }
  378. // Summary returns a summary of the processes running in a container.
  379. // This is present in Windows to support docker top. In linux, the
  380. // engine shells out to ps to get process information. On Windows, as
  381. // the containers could be Hyper-V containers, they would not be
  382. // visible on the container host. However, libcontainerd does have
  383. // that information.
  384. func (clnt *client) Summary(containerID string) ([]Summary, error) {
  385. var s []Summary
  386. clnt.lock(containerID)
  387. defer clnt.unlock(containerID)
  388. cont, err := clnt.getContainer(containerID)
  389. if err != nil {
  390. return nil, err
  391. }
  392. // Add the first process
  393. s = append(s, Summary{
  394. Pid: cont.containerCommon.systemPid,
  395. Command: cont.ociSpec.Process.Args[0]})
  396. // And add all the exec'd processes
  397. for _, p := range cont.processes {
  398. s = append(s, Summary{
  399. Pid: p.processCommon.systemPid,
  400. Command: p.commandLine})
  401. }
  402. return s, nil
  403. }
  404. // UpdateResources updates resources for a running container.
  405. func (clnt *client) UpdateResources(containerID string, resources Resources) error {
  406. // Updating resource isn't supported on Windows
  407. // but we should return nil for enabling updating container
  408. return nil
  409. }