Browse code

Merge pull request #17617 from askb/17168_pull_error_fix

Fix for #17168 issue

Alexander Morozov authored on 2015/11/18 09:10:44
Showing 3 changed files
... ...
@@ -3,6 +3,7 @@ package graph
3 3
 import (
4 4
 	"fmt"
5 5
 	"io"
6
+	"strings"
6 7
 
7 8
 	"github.com/Sirupsen/logrus"
8 9
 	"github.com/docker/docker/cliconfig"
... ...
@@ -87,12 +88,13 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf
87 87
 	}
88 88
 
89 89
 	var (
90
-		lastErr error
90
+		// use a slice to append the error strings and return a joined string to caller
91
+		errors []string
91 92
 
92 93
 		// discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport
93
-		// By default it is false, which means that if a ErrNoSupport error is encountered, it will be saved in lastErr.
94
+		// By default it is false, which means that if a ErrNoSupport error is encountered, it will be saved in errors.
94 95
 		// As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of
95
-		// any subsequent ErrNoSupport errors in lastErr.
96
+		// any subsequent ErrNoSupport errors in errors.
96 97
 		// It's needed for pull-by-digest on v1 endpoints: if there are only v1 endpoints configured, the error should be
97 98
 		// returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
98 99
 		// error is the ones from v2 endpoints not v1.
... ...
@@ -103,7 +105,7 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf
103 103
 
104 104
 		puller, err := newPuller(s, endpoint, repoInfo, imagePullConfig, sf)
105 105
 		if err != nil {
106
-			lastErr = err
106
+			errors = append(errors, err.Error())
107 107
 			continue
108 108
 		}
109 109
 		if fallback, err := puller.Pull(tag); err != nil {
... ...
@@ -111,28 +113,35 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf
111 111
 				if _, ok := err.(registry.ErrNoSupport); !ok {
112 112
 					// Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
113 113
 					discardNoSupportErrors = true
114
-					// save the current error
115
-					lastErr = err
114
+					// append subsequent errors
115
+					errors = append(errors, err.Error())
116 116
 				} else if !discardNoSupportErrors {
117 117
 					// Save the ErrNoSupport error, because it's either the first error or all encountered errors
118 118
 					// were also ErrNoSupport errors.
119
-					lastErr = err
119
+					// append subsequent errors
120
+					errors = append(errors, err.Error())
120 121
 				}
121 122
 				continue
122 123
 			}
123
-			logrus.Debugf("Not continuing with error: %v", err)
124
-			return err
125
-
124
+			errors = append(errors, err.Error())
125
+			logrus.Debugf("Not continuing with error: %v", fmt.Errorf(strings.Join(errors, "\n")))
126
+			if len(errors) > 0 {
127
+				return fmt.Errorf(strings.Join(errors, "\n"))
128
+			}
126 129
 		}
127 130
 
128 131
 		s.eventsService.Log("pull", logName, "")
129 132
 		return nil
130 133
 	}
131 134
 
132
-	if lastErr == nil {
133
-		lastErr = fmt.Errorf("no endpoints found for %s", image)
135
+	if len(errors) == 0 {
136
+		return fmt.Errorf("no endpoints found for %s", image)
137
+	}
138
+
139
+	if len(errors) > 0 {
140
+		return fmt.Errorf(strings.Join(errors, "\n"))
134 141
 	}
135
-	return lastErr
142
+	return nil
136 143
 }
137 144
 
138 145
 // writeStatus writes a status message to out. If layersDownloaded is true, the
... ...
@@ -1849,3 +1849,30 @@ func (s *DockerDaemonSuite) TestBridgeIPIsExcludedFromAllocatorPool(c *check.C)
1849 1849
 		cont++
1850 1850
 	}
1851 1851
 }
1852
+
1853
+// Test daemon for no space left on device error
1854
+func (s *DockerDaemonSuite) TestDaemonNoSpaceleftOnDeviceError(c *check.C) {
1855
+	// create a 2MiB image and mount it as graph root
1856
+	cmd := exec.Command("dd", "of=/tmp/testfs.img", "bs=1M", "seek=2", "count=0")
1857
+	if err := cmd.Run(); err != nil {
1858
+		c.Fatalf("dd failed: %v", err)
1859
+	}
1860
+	cmd = exec.Command("mkfs.ext4", "-F", "/tmp/testfs.img")
1861
+	if err := cmd.Run(); err != nil {
1862
+		c.Fatalf("mkfs.ext4 failed: %v", err)
1863
+	}
1864
+	cmd = exec.Command("mkdir", "-p", "/tmp/testfs-mount")
1865
+	if err := cmd.Run(); err != nil {
1866
+		c.Fatalf("mkdir failed: %v", err)
1867
+	}
1868
+	cmd = exec.Command("mount", "-t", "ext4", "-no", "loop,rw", "/tmp/testfs.img", "/tmp/testfs-mount")
1869
+	if err := cmd.Run(); err != nil {
1870
+		c.Fatalf("mount failed: %v", err)
1871
+	}
1872
+	err := s.d.Start("--graph", "/tmp/testfs-mount")
1873
+	c.Assert(err, check.IsNil)
1874
+
1875
+	// pull a repository large enough to fill the mount point
1876
+	out, err := s.d.Cmd("pull", "registry:2")
1877
+	c.Assert(out, check.Not(check.Equals), 1, check.Commentf("no space left on device"))
1878
+}
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"path/filepath"
14 14
 	"runtime"
15 15
 	"strings"
16
+	"syscall"
16 17
 	"time"
17 18
 
18 19
 	"github.com/Sirupsen/logrus"
... ...
@@ -219,6 +220,10 @@ func ContinueOnError(err error) bool {
219 219
 		return shouldV2Fallback(v)
220 220
 	case *client.UnexpectedHTTPResponseError:
221 221
 		return true
222
+	case error:
223
+		if val := strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error())); val {
224
+			return false
225
+		}
222 226
 	}
223 227
 	// let's be nice and fallback if the error is a completely
224 228
 	// unexpected one.