client_windows.go 20 KB

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