Browse code

Push/pull errors improvement and cleanup

Several improvements to error handling:

- Introduce ImageConfigPullError type, wrapping errors related to
downloading the image configuration blob in schema2. This allows for a
more descriptive error message to be seen by the end user.

- Change some logrus.Debugf calls that display errors to logrus.Errorf.
Add log lines in the push/pull fallback cases to make sure the errors
leading to the fallback are shown.

- Move error-related types and functions which are only used by the
distribution package out of the registry package.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>

Aaron Lehmann authored on 2016/02/12 07:08:49
Showing 8 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,102 @@
0
+package distribution
1
+
2
+import (
3
+	"net/url"
4
+	"strings"
5
+	"syscall"
6
+
7
+	"github.com/docker/distribution/registry/api/errcode"
8
+	"github.com/docker/distribution/registry/api/v2"
9
+	"github.com/docker/distribution/registry/client"
10
+	"github.com/docker/docker/distribution/xfer"
11
+)
12
+
13
+// ErrNoSupport is an error type used for errors indicating that an operation
14
+// is not supported. It encapsulates a more specific error.
15
+type ErrNoSupport struct{ Err error }
16
+
17
+func (e ErrNoSupport) Error() string {
18
+	if e.Err == nil {
19
+		return "not supported"
20
+	}
21
+	return e.Err.Error()
22
+}
23
+
24
+// fallbackError wraps an error that can possibly allow fallback to a different
25
+// endpoint.
26
+type fallbackError struct {
27
+	// err is the error being wrapped.
28
+	err error
29
+	// confirmedV2 is set to true if it was confirmed that the registry
30
+	// supports the v2 protocol. This is used to limit fallbacks to the v1
31
+	// protocol.
32
+	confirmedV2 bool
33
+}
34
+
35
+// Error renders the FallbackError as a string.
36
+func (f fallbackError) Error() string {
37
+	return f.err.Error()
38
+}
39
+
40
+// shouldV2Fallback returns true if this error is a reason to fall back to v1.
41
+func shouldV2Fallback(err errcode.Error) bool {
42
+	switch err.Code {
43
+	case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown:
44
+		return true
45
+	}
46
+	return false
47
+}
48
+
49
+// continueOnError returns true if we should fallback to the next endpoint
50
+// as a result of this error.
51
+func continueOnError(err error) bool {
52
+	switch v := err.(type) {
53
+	case errcode.Errors:
54
+		if len(v) == 0 {
55
+			return true
56
+		}
57
+		return continueOnError(v[0])
58
+	case ErrNoSupport:
59
+		return continueOnError(v.Err)
60
+	case errcode.Error:
61
+		return shouldV2Fallback(v)
62
+	case *client.UnexpectedHTTPResponseError:
63
+		return true
64
+	case ImageConfigPullError:
65
+		return false
66
+	case error:
67
+		return !strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error()))
68
+	}
69
+	// let's be nice and fallback if the error is a completely
70
+	// unexpected one.
71
+	// If new errors have to be handled in some way, please
72
+	// add them to the switch above.
73
+	return true
74
+}
75
+
76
+// retryOnError wraps the error in xfer.DoNotRetry if we should not retry the
77
+// operation after this error.
78
+func retryOnError(err error) error {
79
+	switch v := err.(type) {
80
+	case errcode.Errors:
81
+		return retryOnError(v[0])
82
+	case errcode.Error:
83
+		switch v.Code {
84
+		case errcode.ErrorCodeUnauthorized, errcode.ErrorCodeUnsupported, errcode.ErrorCodeDenied:
85
+			return xfer.DoNotRetry{Err: err}
86
+		}
87
+	case *url.Error:
88
+		return retryOnError(v.Err)
89
+	case *client.UnexpectedHTTPResponseError:
90
+		return xfer.DoNotRetry{Err: err}
91
+	case error:
92
+		if strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error())) {
93
+			return xfer.DoNotRetry{Err: err}
94
+		}
95
+	}
96
+	// let's be nice and fallback if the error is a completely
97
+	// unexpected one.
98
+	// If new errors have to be handled in some way, please
99
+	// add them to the switch above.
100
+	return err
101
+}
... ...
@@ -136,7 +136,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
136 136
 				}
137 137
 			}
138 138
 			if fallback {
139
-				if _, ok := err.(registry.ErrNoSupport); !ok {
139
+				if _, ok := err.(ErrNoSupport); !ok {
140 140
 					// Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
141 141
 					discardNoSupportErrors = true
142 142
 					// append subsequent errors
... ...
@@ -147,9 +147,10 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
147 147
 					// append subsequent errors
148 148
 					lastErr = err
149 149
 				}
150
+				logrus.Errorf("Attempting next endpoint for pull after error: %v", err)
150 151
 				continue
151 152
 			}
152
-			logrus.Debugf("Not continuing with error: %v", err)
153
+			logrus.Errorf("Not continuing with pull after error: %v", err)
153 154
 			return err
154 155
 		}
155 156
 
... ...
@@ -38,7 +38,7 @@ type v1Puller struct {
38 38
 func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error {
39 39
 	if _, isCanonical := ref.(reference.Canonical); isCanonical {
40 40
 		// Allowing fallback, because HTTPS v1 is before HTTP v2
41
-		return fallbackError{err: registry.ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}}
41
+		return fallbackError{err: ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}}
42 42
 	}
43 43
 
44 44
 	tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name)
... ...
@@ -35,6 +35,17 @@ import (
35 35
 
36 36
 var errRootFSMismatch = errors.New("layers from manifest don't match image configuration")
37 37
 
38
+// ImageConfigPullError is an error pulling the image config blob
39
+// (only applies to schema2).
40
+type ImageConfigPullError struct {
41
+	Err error
42
+}
43
+
44
+// Error returns the error string for ImageConfigPullError.
45
+func (e ImageConfigPullError) Error() string {
46
+	return "error pulling image configuration: " + e.Err.Error()
47
+}
48
+
38 49
 type v2Puller struct {
39 50
 	V2MetadataService *metadata.V2MetadataService
40 51
 	endpoint          registry.APIEndpoint
... ...
@@ -58,8 +69,8 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
58 58
 		if _, ok := err.(fallbackError); ok {
59 59
 			return err
60 60
 		}
61
-		if registry.ContinueOnError(err) {
62
-			logrus.Debugf("Error trying v2 registry: %v", err)
61
+		if continueOnError(err) {
62
+			logrus.Errorf("Error trying v2 registry: %v", err)
63 63
 			return fallbackError{err: err, confirmedV2: p.confirmedV2}
64 64
 		}
65 65
 	}
... ...
@@ -170,7 +181,7 @@ func (ld *v2LayerDescriptor) Download(ctx context.Context, progressOutput progre
170 170
 
171 171
 	layerDownload, err := blobs.Open(ctx, ld.digest)
172 172
 	if err != nil {
173
-		logrus.Debugf("Error initiating layer download: %v", err)
173
+		logrus.Errorf("Error initiating layer download: %v", err)
174 174
 		if err == distribution.ErrBlobUnknown {
175 175
 			return nil, 0, xfer.DoNotRetry{Err: err}
176 176
 		}
... ...
@@ -280,12 +291,12 @@ func (ld *v2LayerDescriptor) truncateDownloadFile() error {
280 280
 	ld.verifier = nil
281 281
 
282 282
 	if _, err := ld.tmpFile.Seek(0, os.SEEK_SET); err != nil {
283
-		logrus.Debugf("error seeking to beginning of download file: %v", err)
283
+		logrus.Errorf("error seeking to beginning of download file: %v", err)
284 284
 		return err
285 285
 	}
286 286
 
287 287
 	if err := ld.tmpFile.Truncate(0); err != nil {
288
-		logrus.Debugf("error truncating download file: %v", err)
288
+		logrus.Errorf("error truncating download file: %v", err)
289 289
 		return err
290 290
 	}
291 291
 
... ...
@@ -484,7 +495,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
484 484
 	go func() {
485 485
 		configJSON, err := p.pullSchema2ImageConfig(ctx, target.Digest)
486 486
 		if err != nil {
487
-			errChan <- err
487
+			errChan <- ImageConfigPullError{Err: err}
488 488
 			cancel()
489 489
 			return
490 490
 		}
... ...
@@ -704,12 +715,12 @@ func allowV1Fallback(err error) error {
704 704
 	switch v := err.(type) {
705 705
 	case errcode.Errors:
706 706
 		if len(v) != 0 {
707
-			if v0, ok := v[0].(errcode.Error); ok && registry.ShouldV2Fallback(v0) {
707
+			if v0, ok := v[0].(errcode.Error); ok && shouldV2Fallback(v0) {
708 708
 				return fallbackError{err: err, confirmedV2: false}
709 709
 			}
710 710
 		}
711 711
 	case errcode.Error:
712
-		if registry.ShouldV2Fallback(v) {
712
+		if shouldV2Fallback(v) {
713 713
 			return fallbackError{err: err, confirmedV2: false}
714 714
 		}
715 715
 	case *url.Error:
... ...
@@ -144,11 +144,12 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
144 144
 					confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
145 145
 					err = fallbackErr.err
146 146
 					lastErr = err
147
+					logrus.Errorf("Attempting next endpoint for push after error: %v", err)
147 148
 					continue
148 149
 				}
149 150
 			}
150 151
 
151
-			logrus.Debugf("Not continuing with error: %v", err)
152
+			logrus.Errorf("Not continuing with push after error: %v", err)
152 153
 			return err
153 154
 		}
154 155
 
... ...
@@ -68,7 +68,7 @@ func (p *v2Pusher) Push(ctx context.Context) (err error) {
68 68
 	}
69 69
 
70 70
 	if err = p.pushV2Repository(ctx); err != nil {
71
-		if registry.ContinueOnError(err) {
71
+		if continueOnError(err) {
72 72
 			return fallbackError{err: err, confirmedV2: p.pushState.confirmedV2}
73 73
 		}
74 74
 	}
... ...
@@ -6,38 +6,19 @@ import (
6 6
 	"net/http"
7 7
 	"net/url"
8 8
 	"strings"
9
-	"syscall"
10 9
 	"time"
11 10
 
12 11
 	"github.com/docker/distribution"
13 12
 	distreference "github.com/docker/distribution/reference"
14
-	"github.com/docker/distribution/registry/api/errcode"
15 13
 	"github.com/docker/distribution/registry/client"
16 14
 	"github.com/docker/distribution/registry/client/auth"
17 15
 	"github.com/docker/distribution/registry/client/transport"
18
-	"github.com/docker/docker/distribution/xfer"
19 16
 	"github.com/docker/docker/dockerversion"
20 17
 	"github.com/docker/docker/registry"
21 18
 	"github.com/docker/engine-api/types"
22 19
 	"golang.org/x/net/context"
23 20
 )
24 21
 
25
-// fallbackError wraps an error that can possibly allow fallback to a different
26
-// endpoint.
27
-type fallbackError struct {
28
-	// err is the error being wrapped.
29
-	err error
30
-	// confirmedV2 is set to true if it was confirmed that the registry
31
-	// supports the v2 protocol. This is used to limit fallbacks to the v1
32
-	// protocol.
33
-	confirmedV2 bool
34
-}
35
-
36
-// Error renders the FallbackError as a string.
37
-func (f fallbackError) Error() string {
38
-	return f.err.Error()
39
-}
40
-
41 22
 type dumbCredentialStore struct {
42 23
 	auth *types.AuthConfig
43 24
 }
... ...
@@ -141,30 +122,3 @@ func (th *existingTokenHandler) AuthorizeRequest(req *http.Request, params map[s
141 141
 	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", th.token))
142 142
 	return nil
143 143
 }
144
-
145
-// retryOnError wraps the error in xfer.DoNotRetry if we should not retry the
146
-// operation after this error.
147
-func retryOnError(err error) error {
148
-	switch v := err.(type) {
149
-	case errcode.Errors:
150
-		return retryOnError(v[0])
151
-	case errcode.Error:
152
-		switch v.Code {
153
-		case errcode.ErrorCodeUnauthorized, errcode.ErrorCodeUnsupported, errcode.ErrorCodeDenied:
154
-			return xfer.DoNotRetry{Err: err}
155
-		}
156
-	case *url.Error:
157
-		return retryOnError(v.Err)
158
-	case *client.UnexpectedHTTPResponseError:
159
-		return xfer.DoNotRetry{Err: err}
160
-	case error:
161
-		if strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error())) {
162
-			return xfer.DoNotRetry{Err: err}
163
-		}
164
-	}
165
-	// let's be nice and fallback if the error is a completely
166
-	// unexpected one.
167
-	// If new errors have to be handled in some way, please
168
-	// add them to the switch above.
169
-	return err
170
-}
... ...
@@ -13,13 +13,9 @@ import (
13 13
 	"path/filepath"
14 14
 	"runtime"
15 15
 	"strings"
16
-	"syscall"
17 16
 	"time"
18 17
 
19 18
 	"github.com/Sirupsen/logrus"
20
-	"github.com/docker/distribution/registry/api/errcode"
21
-	"github.com/docker/distribution/registry/api/v2"
22
-	"github.com/docker/distribution/registry/client"
23 19
 	"github.com/docker/distribution/registry/client/transport"
24 20
 	"github.com/docker/go-connections/tlsconfig"
25 21
 )
... ...
@@ -169,51 +165,6 @@ func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Reque
169 169
 	return nil
170 170
 }
171 171
 
172
-// ShouldV2Fallback returns true if this error is a reason to fall back to v1.
173
-func ShouldV2Fallback(err errcode.Error) bool {
174
-	switch err.Code {
175
-	case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown:
176
-		return true
177
-	}
178
-	return false
179
-}
180
-
181
-// ErrNoSupport is an error type used for errors indicating that an operation
182
-// is not supported. It encapsulates a more specific error.
183
-type ErrNoSupport struct{ Err error }
184
-
185
-func (e ErrNoSupport) Error() string {
186
-	if e.Err == nil {
187
-		return "not supported"
188
-	}
189
-	return e.Err.Error()
190
-}
191
-
192
-// ContinueOnError returns true if we should fallback to the next endpoint
193
-// as a result of this error.
194
-func ContinueOnError(err error) bool {
195
-	switch v := err.(type) {
196
-	case errcode.Errors:
197
-		if len(v) == 0 {
198
-			return true
199
-		}
200
-		return ContinueOnError(v[0])
201
-	case ErrNoSupport:
202
-		return ContinueOnError(v.Err)
203
-	case errcode.Error:
204
-		return ShouldV2Fallback(v)
205
-	case *client.UnexpectedHTTPResponseError:
206
-		return true
207
-	case error:
208
-		return !strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error()))
209
-	}
210
-	// let's be nice and fallback if the error is a completely
211
-	// unexpected one.
212
-	// If new errors have to be handled in some way, please
213
-	// add them to the switch above.
214
-	return true
215
-}
216
-
217 172
 // NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
218 173
 // default TLS configuration.
219 174
 func NewTransport(tlsConfig *tls.Config) *http.Transport {