docker_cli_build_unix_test.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. // +build !windows
  2. package main
  3. import (
  4. "bufio"
  5. "bytes"
  6. "encoding/json"
  7. "io/ioutil"
  8. "os"
  9. "os/exec"
  10. "path/filepath"
  11. "regexp"
  12. "strings"
  13. "syscall"
  14. "time"
  15. "github.com/docker/docker/integration-cli/checker"
  16. "github.com/docker/docker/integration-cli/cli"
  17. "github.com/docker/docker/integration-cli/cli/build"
  18. "github.com/docker/docker/internal/test/fakecontext"
  19. "github.com/docker/go-units"
  20. "github.com/go-check/check"
  21. "gotest.tools/assert"
  22. "gotest.tools/icmd"
  23. )
  24. func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *testing.T) {
  25. testRequires(c, cpuCfsQuota)
  26. name := "testbuildresourceconstraints"
  27. buildLabel := "DockerSuite.TestBuildResourceConstraintsAreUsed"
  28. ctx := fakecontext.New(c, "", fakecontext.WithDockerfile(`
  29. FROM hello-world:frozen
  30. RUN ["/hello"]
  31. `))
  32. cli.Docker(
  33. cli.Args("build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpuset-mems=0", "--cpu-shares=100", "--cpu-quota=8000", "--ulimit", "nofile=42", "--label="+buildLabel, "-t", name, "."),
  34. cli.InDir(ctx.Dir),
  35. ).Assert(c, icmd.Success)
  36. out := cli.DockerCmd(c, "ps", "-lq", "--filter", "label="+buildLabel).Combined()
  37. cID := strings.TrimSpace(out)
  38. type hostConfig struct {
  39. Memory int64
  40. MemorySwap int64
  41. CpusetCpus string
  42. CpusetMems string
  43. CPUShares int64
  44. CPUQuota int64
  45. Ulimits []*units.Ulimit
  46. }
  47. cfg := inspectFieldJSON(c, cID, "HostConfig")
  48. var c1 hostConfig
  49. err := json.Unmarshal([]byte(cfg), &c1)
  50. assert.Assert(c, err == nil, check.Commentf(cfg))
  51. assert.Equal(c, c1.Memory, int64(64*1024*1024), check.Commentf("resource constraints not set properly for Memory"))
  52. assert.Equal(c, c1.MemorySwap, int64(-1), check.Commentf("resource constraints not set properly for MemorySwap"))
  53. assert.Equal(c, c1.CpusetCpus, "0", check.Commentf("resource constraints not set properly for CpusetCpus"))
  54. assert.Equal(c, c1.CpusetMems, "0", check.Commentf("resource constraints not set properly for CpusetMems"))
  55. assert.Equal(c, c1.CPUShares, int64(100), check.Commentf("resource constraints not set properly for CPUShares"))
  56. assert.Equal(c, c1.CPUQuota, int64(8000), check.Commentf("resource constraints not set properly for CPUQuota"))
  57. assert.Equal(c, c1.Ulimits[0].Name, "nofile", check.Commentf("resource constraints not set properly for Ulimits"))
  58. assert.Equal(c, c1.Ulimits[0].Hard, int64(42), check.Commentf("resource constraints not set properly for Ulimits"))
  59. // Make sure constraints aren't saved to image
  60. cli.DockerCmd(c, "run", "--name=test", name)
  61. cfg = inspectFieldJSON(c, "test", "HostConfig")
  62. var c2 hostConfig
  63. err = json.Unmarshal([]byte(cfg), &c2)
  64. assert.Assert(c, err == nil, check.Commentf(cfg))
  65. assert.Assert(c, c2.Memory != int64(64*1024*1024), check.Commentf("resource leaked from build for Memory"))
  66. assert.Assert(c, c2.MemorySwap != int64(-1), check.Commentf("resource leaked from build for MemorySwap"))
  67. assert.Assert(c, c2.CpusetCpus != "0", check.Commentf("resource leaked from build for CpusetCpus"))
  68. assert.Assert(c, c2.CpusetMems != "0", check.Commentf("resource leaked from build for CpusetMems"))
  69. assert.Assert(c, c2.CPUShares != int64(100), check.Commentf("resource leaked from build for CPUShares"))
  70. assert.Assert(c, c2.CPUQuota != int64(8000), check.Commentf("resource leaked from build for CPUQuota"))
  71. assert.Assert(c, c2.Ulimits == nil, check.Commentf("resource leaked from build for Ulimits"))
  72. }
  73. func (s *DockerSuite) TestBuildAddChangeOwnership(c *testing.T) {
  74. testRequires(c, DaemonIsLinux)
  75. name := "testbuildaddown"
  76. ctx := func() *fakecontext.Fake {
  77. dockerfile := `
  78. FROM busybox
  79. ADD foo /bar/
  80. RUN [ $(stat -c %U:%G "/bar") = 'root:root' ]
  81. RUN [ $(stat -c %U:%G "/bar/foo") = 'root:root' ]
  82. `
  83. tmpDir, err := ioutil.TempDir("", "fake-context")
  84. assert.NilError(c, err)
  85. testFile, err := os.Create(filepath.Join(tmpDir, "foo"))
  86. if err != nil {
  87. c.Fatalf("failed to create foo file: %v", err)
  88. }
  89. defer testFile.Close()
  90. icmd.RunCmd(icmd.Cmd{
  91. Command: []string{"chown", "daemon:daemon", "foo"},
  92. Dir: tmpDir,
  93. }).Assert(c, icmd.Success)
  94. if err := ioutil.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644); err != nil {
  95. c.Fatalf("failed to open destination dockerfile: %v", err)
  96. }
  97. return fakecontext.New(c, tmpDir)
  98. }()
  99. defer ctx.Close()
  100. buildImageSuccessfully(c, name, build.WithExternalBuildContext(ctx))
  101. }
  102. // Test that an infinite sleep during a build is killed if the client disconnects.
  103. // This test is fairly hairy because there are lots of ways to race.
  104. // Strategy:
  105. // * Monitor the output of docker events starting from before
  106. // * Run a 1-year-long sleep from a docker build.
  107. // * When docker events sees container start, close the "docker build" command
  108. // * Wait for docker events to emit a dying event.
  109. //
  110. // TODO(buildkit): this test needs to be rewritten for buildkit.
  111. // It has been manually tested positive. Confirmed issue: docker build output parsing.
  112. // Potential issue: newEventObserver uses docker events, which is not hooked up to buildkit.
  113. func (s *DockerSuite) TestBuildCancellationKillsSleep(c *testing.T) {
  114. testRequires(c, DaemonIsLinux, TODOBuildkit)
  115. name := "testbuildcancellation"
  116. observer, err := newEventObserver(c)
  117. assert.NilError(c, err)
  118. err = observer.Start()
  119. assert.NilError(c, err)
  120. defer observer.Stop()
  121. // (Note: one year, will never finish)
  122. ctx := fakecontext.New(c, "", fakecontext.WithDockerfile("FROM busybox\nRUN sleep 31536000"))
  123. defer ctx.Close()
  124. buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".")
  125. buildCmd.Dir = ctx.Dir
  126. stdoutBuild, err := buildCmd.StdoutPipe()
  127. assert.NilError(c, err)
  128. if err := buildCmd.Start(); err != nil {
  129. c.Fatalf("failed to run build: %s", err)
  130. }
  131. // always clean up
  132. defer func() {
  133. buildCmd.Process.Kill()
  134. buildCmd.Wait()
  135. }()
  136. matchCID := regexp.MustCompile("Running in (.+)")
  137. scanner := bufio.NewScanner(stdoutBuild)
  138. outputBuffer := new(bytes.Buffer)
  139. var buildID string
  140. for scanner.Scan() {
  141. line := scanner.Text()
  142. outputBuffer.WriteString(line)
  143. outputBuffer.WriteString("\n")
  144. if matches := matchCID.FindStringSubmatch(line); len(matches) > 0 {
  145. buildID = matches[1]
  146. break
  147. }
  148. }
  149. if buildID == "" {
  150. c.Fatalf("Unable to find build container id in build output:\n%s", outputBuffer.String())
  151. }
  152. testActions := map[string]chan bool{
  153. "start": make(chan bool, 1),
  154. "die": make(chan bool, 1),
  155. }
  156. matcher := matchEventLine(buildID, "container", testActions)
  157. processor := processEventMatch(testActions)
  158. go observer.Match(matcher, processor)
  159. select {
  160. case <-time.After(10 * time.Second):
  161. observer.CheckEventError(c, buildID, "start", matcher)
  162. case <-testActions["start"]:
  163. // ignore, done
  164. }
  165. // Send a kill to the `docker build` command.
  166. // Causes the underlying build to be cancelled due to socket close.
  167. if err := buildCmd.Process.Kill(); err != nil {
  168. c.Fatalf("error killing build command: %s", err)
  169. }
  170. // Get the exit status of `docker build`, check it exited because killed.
  171. if err := buildCmd.Wait(); err != nil && !isKilled(err) {
  172. c.Fatalf("wait failed during build run: %T %s", err, err)
  173. }
  174. select {
  175. case <-time.After(10 * time.Second):
  176. observer.CheckEventError(c, buildID, "die", matcher)
  177. case <-testActions["die"]:
  178. // ignore, done
  179. }
  180. }
  181. func isKilled(err error) bool {
  182. if exitErr, ok := err.(*exec.ExitError); ok {
  183. status, ok := exitErr.Sys().(syscall.WaitStatus)
  184. if !ok {
  185. return false
  186. }
  187. // status.ExitStatus() is required on Windows because it does not
  188. // implement Signal() nor Signaled(). Just check it had a bad exit
  189. // status could mean it was killed (and in tests we do kill)
  190. return (status.Signaled() && status.Signal() == os.Kill) || status.ExitStatus() != 0
  191. }
  192. return false
  193. }