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