diff --git a/graph/pull.go b/graph/pull.go index ce60047913..8cee94886b 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -3,6 +3,7 @@ package graph import ( "fmt" "io" + "strings" "github.com/Sirupsen/logrus" "github.com/docker/docker/cliconfig" @@ -87,12 +88,13 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf } var ( - lastErr error + // use a slice to append the error strings and return a joined string to caller + errors []string // discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport - // By default it is false, which means that if a ErrNoSupport error is encountered, it will be saved in lastErr. + // By default it is false, which means that if a ErrNoSupport error is encountered, it will be saved in errors. // As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of - // any subsequent ErrNoSupport errors in lastErr. + // any subsequent ErrNoSupport errors in errors. // It's needed for pull-by-digest on v1 endpoints: if there are only v1 endpoints configured, the error should be // returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant // error is the ones from v2 endpoints not v1. @@ -103,7 +105,7 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf puller, err := newPuller(s, endpoint, repoInfo, imagePullConfig, sf) if err != nil { - lastErr = err + errors = append(errors, err.Error()) continue } if fallback, err := puller.Pull(tag); err != nil { @@ -111,28 +113,35 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf if _, ok := err.(registry.ErrNoSupport); !ok { // Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors. discardNoSupportErrors = true - // save the current error - lastErr = err + // append subsequent errors + errors = append(errors, err.Error()) } else if !discardNoSupportErrors { // Save the ErrNoSupport error, because it's either the first error or all encountered errors // were also ErrNoSupport errors. - lastErr = err + // append subsequent errors + errors = append(errors, err.Error()) } continue } - logrus.Debugf("Not continuing with error: %v", err) - return err - + errors = append(errors, err.Error()) + logrus.Debugf("Not continuing with error: %v", fmt.Errorf(strings.Join(errors, "\n"))) + if len(errors) > 0 { + return fmt.Errorf(strings.Join(errors, "\n")) + } } s.eventsService.Log("pull", logName, "") return nil } - if lastErr == nil { - lastErr = fmt.Errorf("no endpoints found for %s", image) + if len(errors) == 0 { + return fmt.Errorf("no endpoints found for %s", image) } - return lastErr + + if len(errors) > 0 { + return fmt.Errorf(strings.Join(errors, "\n")) + } + return nil } // writeStatus writes a status message to out. If layersDownloaded is true, the diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index c25e54bb39..065099105b 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -1849,3 +1849,30 @@ func (s *DockerDaemonSuite) TestBridgeIPIsExcludedFromAllocatorPool(c *check.C) cont++ } } + +// Test daemon for no space left on device error +func (s *DockerDaemonSuite) TestDaemonNoSpaceleftOnDeviceError(c *check.C) { + // create a 2MiB image and mount it as graph root + cmd := exec.Command("dd", "of=/tmp/testfs.img", "bs=1M", "seek=2", "count=0") + if err := cmd.Run(); err != nil { + c.Fatalf("dd failed: %v", err) + } + cmd = exec.Command("mkfs.ext4", "-F", "/tmp/testfs.img") + if err := cmd.Run(); err != nil { + c.Fatalf("mkfs.ext4 failed: %v", err) + } + cmd = exec.Command("mkdir", "-p", "/tmp/testfs-mount") + if err := cmd.Run(); err != nil { + c.Fatalf("mkdir failed: %v", err) + } + cmd = exec.Command("mount", "-t", "ext4", "-no", "loop,rw", "/tmp/testfs.img", "/tmp/testfs-mount") + if err := cmd.Run(); err != nil { + c.Fatalf("mount failed: %v", err) + } + err := s.d.Start("--graph", "/tmp/testfs-mount") + c.Assert(err, check.IsNil) + + // pull a repository large enough to fill the mount point + out, err := s.d.Cmd("pull", "registry:2") + c.Assert(out, check.Not(check.Equals), 1, check.Commentf("no space left on device")) +} diff --git a/registry/registry.go b/registry/registry.go index e8eb478572..d018f922ac 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -13,6 +13,7 @@ import ( "path/filepath" "runtime" "strings" + "syscall" "time" "github.com/Sirupsen/logrus" @@ -219,6 +220,10 @@ func ContinueOnError(err error) bool { return shouldV2Fallback(v) case *client.UnexpectedHTTPResponseError: return true + case error: + if val := strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error())); val { + return false + } } // let's be nice and fallback if the error is a completely // unexpected one.