Merge pull request #3868 from awprice/issue-3850
Improve host fallback behaviour in docker remote
This commit is contained in:
commit
566121485b
@ -95,41 +95,49 @@ func (r dockerFetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.R
|
|||||||
images.MediaTypeDockerSchema1Manifest,
|
images.MediaTypeDockerSchema1Manifest,
|
||||||
ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex:
|
ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex:
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
for _, host := range r.hosts {
|
for _, host := range r.hosts {
|
||||||
req := r.request(host, http.MethodGet, "manifests", desc.Digest.String())
|
req := r.request(host, http.MethodGet, "manifests", desc.Digest.String())
|
||||||
|
|
||||||
rc, err := r.open(ctx, req, desc.MediaType, offset)
|
rc, err := r.open(ctx, req, desc.MediaType, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errdefs.IsNotFound(err) {
|
// Store the error for referencing later
|
||||||
continue // try another host
|
if firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
}
|
}
|
||||||
|
continue // try another host
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc, nil
|
return rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil, firstErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally use blobs endpoints
|
// Finally use blobs endpoints
|
||||||
|
var firstErr error
|
||||||
for _, host := range r.hosts {
|
for _, host := range r.hosts {
|
||||||
req := r.request(host, http.MethodGet, "blobs", desc.Digest.String())
|
req := r.request(host, http.MethodGet, "blobs", desc.Digest.String())
|
||||||
|
|
||||||
rc, err := r.open(ctx, req, desc.MediaType, offset)
|
rc, err := r.open(ctx, req, desc.MediaType, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errdefs.IsNotFound(err) {
|
// Store the error for referencing later
|
||||||
continue // try another host
|
if firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
}
|
}
|
||||||
|
continue // try another host
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc, nil
|
return rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.Wrapf(errdefs.ErrNotFound,
|
if errdefs.IsNotFound(firstErr) {
|
||||||
|
firstErr = errors.Wrapf(errdefs.ErrNotFound,
|
||||||
"could not fetch content descriptor %v (%v) from remote",
|
"could not fetch content descriptor %v (%v) from remote",
|
||||||
desc.Digest, desc.MediaType)
|
desc.Digest, desc.MediaType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, firstErr
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -286,7 +286,11 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
|
|||||||
if errors.Cause(err) == ErrInvalidAuthorization {
|
if errors.Cause(err) == ErrInvalidAuthorization {
|
||||||
err = errors.Wrapf(err, "pull access denied, repository does not exist or may require authorization")
|
err = errors.Wrapf(err, "pull access denied, repository does not exist or may require authorization")
|
||||||
}
|
}
|
||||||
return "", ocispec.Descriptor{}, err
|
// Store the error for referencing later
|
||||||
|
if lastErr == nil {
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
continue // try another host
|
||||||
}
|
}
|
||||||
resp.Body.Close() // don't care about body contents.
|
resp.Body.Close() // don't care about body contents.
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/containerd/remotes"
|
"github.com/containerd/containerd/remotes"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
@ -192,6 +193,93 @@ func TestBadTokenResolver(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHostFailureFallbackResolver(t *testing.T) {
|
||||||
|
sf := func(h http.Handler) (string, ResolverOptions, func()) {
|
||||||
|
s := httptest.NewServer(h)
|
||||||
|
base := s.URL[7:] // strip "http://"
|
||||||
|
|
||||||
|
options := ResolverOptions{}
|
||||||
|
createHost := func(host string) RegistryHost {
|
||||||
|
return RegistryHost{
|
||||||
|
Client: &http.Client{
|
||||||
|
// Set the timeout so we timeout waiting for the non-responsive HTTP server
|
||||||
|
Timeout: 500 * time.Millisecond,
|
||||||
|
},
|
||||||
|
Host: host,
|
||||||
|
Scheme: "http",
|
||||||
|
Path: "/v2",
|
||||||
|
Capabilities: HostCapabilityPull | HostCapabilityResolve | HostCapabilityPush,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an unstarted HTTP server. We use this to generate a random port.
|
||||||
|
notRunning := httptest.NewUnstartedServer(nil)
|
||||||
|
notRunningBase := notRunning.Listener.Addr().String()
|
||||||
|
|
||||||
|
// Override hosts with two hosts
|
||||||
|
options.Hosts = func(host string) ([]RegistryHost, error) {
|
||||||
|
return []RegistryHost{
|
||||||
|
createHost(notRunningBase), // This host IS running, but with a non-responsive HTTP server
|
||||||
|
createHost(base), // This host IS running
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return base, options, s.Close
|
||||||
|
}
|
||||||
|
|
||||||
|
runBasicTest(t, "testname", sf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHostTLSFailureFallbackResolver(t *testing.T) {
|
||||||
|
sf := func(h http.Handler) (string, ResolverOptions, func()) {
|
||||||
|
// Start up two servers
|
||||||
|
server := httptest.NewServer(h)
|
||||||
|
httpBase := server.URL[7:] // strip "http://"
|
||||||
|
|
||||||
|
tlsServer := httptest.NewUnstartedServer(h)
|
||||||
|
tlsServer.StartTLS()
|
||||||
|
httpsBase := tlsServer.URL[8:] // strip "https://"
|
||||||
|
|
||||||
|
capool := x509.NewCertPool()
|
||||||
|
cert, _ := x509.ParseCertificate(tlsServer.TLS.Certificates[0].Certificate[0])
|
||||||
|
capool.AddCert(cert)
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
RootCAs: capool,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
options := ResolverOptions{}
|
||||||
|
createHost := func(host string) RegistryHost {
|
||||||
|
return RegistryHost{
|
||||||
|
Client: client,
|
||||||
|
Host: host,
|
||||||
|
Scheme: "https",
|
||||||
|
Path: "/v2",
|
||||||
|
Capabilities: HostCapabilityPull | HostCapabilityResolve | HostCapabilityPush,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override hosts with two hosts
|
||||||
|
options.Hosts = func(host string) ([]RegistryHost, error) {
|
||||||
|
return []RegistryHost{
|
||||||
|
createHost(httpBase), // This host is serving plain HTTP
|
||||||
|
createHost(httpsBase), // This host is serving TLS
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpBase, options, func() {
|
||||||
|
server.Close()
|
||||||
|
tlsServer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runBasicTest(t, "testname", sf)
|
||||||
|
}
|
||||||
|
|
||||||
func withTokenServer(th http.Handler, creds func(string) (string, string, error)) func(h http.Handler) (string, ResolverOptions, func()) {
|
func withTokenServer(th http.Handler, creds func(string) (string, string, error)) func(h http.Handler) (string, ResolverOptions, func()) {
|
||||||
return func(h http.Handler) (string, ResolverOptions, func()) {
|
return func(h http.Handler) (string, ResolverOptions, func()) {
|
||||||
s := httptest.NewUnstartedServer(th)
|
s := httptest.NewUnstartedServer(th)
|
||||||
|
Loading…
Reference in New Issue
Block a user