Skip to content

Commit

Permalink
Merge pull request #2138 from yuwaMSFT2/master
Browse files Browse the repository at this point in the history
closes issue#2135 image pull returns 404 on manifest request if there is storage error
  • Loading branch information
stevvooe authored Jan 18, 2017
2 parents 954b4e8 + 0bb696c commit 0111f1e
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 4 deletions.
110 changes: 110 additions & 0 deletions registry/handlers/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package handlers
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
Expand All @@ -28,6 +29,8 @@ import (
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/api/v2"
storagedriver "github.com/docker/distribution/registry/storage/driver"
"github.com/docker/distribution/registry/storage/driver/factory"
_ "github.com/docker/distribution/registry/storage/driver/testdriver"
"github.com/docker/distribution/testutil"
"github.com/docker/libtrust"
Expand Down Expand Up @@ -815,6 +818,93 @@ func TestManifestAPI(t *testing.T) {
testManifestAPIManifestList(t, env2, schema2Args)
}

// storageManifestErrDriverFactory implements the factory.StorageDriverFactory interface.
type storageManifestErrDriverFactory struct{}

const (
repositoryWithManifestNotFound = "manifesttagnotfound"
repositoryWithManifestInvalidPath = "manifestinvalidpath"
repositoryWithManifestBadLink = "manifestbadlink"
repositoryWithGenericStorageError = "genericstorageerr"
)

func (factory *storageManifestErrDriverFactory) Create(parameters map[string]interface{}) (storagedriver.StorageDriver, error) {
// Initialize the mock driver
var errGenericStorage = errors.New("generic storage error")
return &mockErrorDriver{
returnErrs: []mockErrorMapping{
{
pathMatch: fmt.Sprintf("%s/_manifests/tags", repositoryWithManifestNotFound),
content: nil,
err: storagedriver.PathNotFoundError{},
},
{
pathMatch: fmt.Sprintf("%s/_manifests/tags", repositoryWithManifestInvalidPath),
content: nil,
err: storagedriver.InvalidPathError{},
},
{
pathMatch: fmt.Sprintf("%s/_manifests/tags", repositoryWithManifestBadLink),
content: []byte("this is a bad sha"),
err: nil,
},
{
pathMatch: fmt.Sprintf("%s/_manifests/tags", repositoryWithGenericStorageError),
content: nil,
err: errGenericStorage,
},
},
}, nil
}

type mockErrorMapping struct {
pathMatch string
content []byte
err error
}

// mockErrorDriver implements StorageDriver to force storage error on manifest request
type mockErrorDriver struct {
storagedriver.StorageDriver
returnErrs []mockErrorMapping
}

func (dr *mockErrorDriver) GetContent(ctx context.Context, path string) ([]byte, error) {
for _, returns := range dr.returnErrs {
if strings.Contains(path, returns.pathMatch) {
return returns.content, returns.err
}
}
return nil, errors.New("Unknown storage error")
}

func TestGetManifestWithStorageError(t *testing.T) {
factory.Register("storagemanifesterror", &storageManifestErrDriverFactory{})
config := configuration.Configuration{
Storage: configuration.Storage{
"storagemanifesterror": configuration.Parameters{},
"maintenance": configuration.Parameters{"uploadpurging": map[interface{}]interface{}{
"enabled": false,
}},
},
}
config.HTTP.Headers = headerConfig
env1 := newTestEnvWithConfig(t, &config)
defer env1.Shutdown()

repo, _ := reference.ParseNamed(repositoryWithManifestNotFound)
testManifestWithStorageError(t, env1, repo, http.StatusNotFound, v2.ErrorCodeManifestUnknown)

repo, _ = reference.ParseNamed(repositoryWithGenericStorageError)
testManifestWithStorageError(t, env1, repo, http.StatusInternalServerError, errcode.ErrorCodeUnknown)

repo, _ = reference.ParseNamed(repositoryWithManifestInvalidPath)
testManifestWithStorageError(t, env1, repo, http.StatusInternalServerError, errcode.ErrorCodeUnknown)

repo, _ = reference.ParseNamed(repositoryWithManifestBadLink)
testManifestWithStorageError(t, env1, repo, http.StatusInternalServerError, errcode.ErrorCodeUnknown)
}

func TestManifestDelete(t *testing.T) {
schema1Repo, _ := reference.ParseNamed("foo/schema1")
schema2Repo, _ := reference.ParseNamed("foo/schema2")
Expand Down Expand Up @@ -852,6 +942,26 @@ func testManifestDeleteDisabled(t *testing.T, env *testEnv, imageName reference.
checkResponse(t, "status of disabled delete of manifest", resp, http.StatusMethodNotAllowed)
}

func testManifestWithStorageError(t *testing.T, env *testEnv, imageName reference.Named, expectedStatusCode int, expectedErrorCode errcode.ErrorCode) {
tag := "latest"
tagRef, _ := reference.WithTag(imageName, tag)
manifestURL, err := env.builder.BuildManifestURL(tagRef)
if err != nil {
t.Fatalf("unexpected error getting manifest url: %v", err)
}

// -----------------------------
// Attempt to fetch the manifest
resp, err := http.Get(manifestURL)
if err != nil {
t.Fatalf("unexpected error getting manifest: %v", err)
}
defer resp.Body.Close()
checkResponse(t, "getting non-existent manifest", resp, expectedStatusCode)
checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, expectedErrorCode)
return
}

func testManifestAPISchema1(t *testing.T, env *testEnv, imageName reference.Named) manifestArgs {
tag := "thetag"
args := manifestArgs{imageName: imageName}
Expand Down
24 changes: 20 additions & 4 deletions registry/handlers/manifests.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
tags := imh.Repository.Tags(imh)
desc, err := tags.Get(imh, imh.Tag)
if err != nil {
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
if _, ok := err.(distribution.ErrTagUnknown); ok {
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
} else {
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
}
return
}
imh.Digest = desc.Digest
Expand All @@ -94,7 +98,11 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
}
manifest, err = manifests.Get(imh, imh.Digest, options...)
if err != nil {
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
if _, ok := err.(distribution.ErrManifestUnknownRevision); ok {
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
} else {
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
}
return
}

Expand Down Expand Up @@ -161,7 +169,11 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)

manifest, err = manifests.Get(imh, manifestDigest)
if err != nil {
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
if _, ok := err.(distribution.ErrManifestUnknownRevision); ok {
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
} else {
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
}
return
}

Expand Down Expand Up @@ -191,7 +203,11 @@ func (imh *manifestHandler) convertSchema2Manifest(schema2Manifest *schema2.Dese
blobs := imh.Repository.Blobs(imh)
configJSON, err := blobs.Get(imh, targetDescriptor.Digest)
if err != nil {
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
if err == distribution.ErrBlobUnknown {
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
} else {
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
}
return nil, err
}

Expand Down

0 comments on commit 0111f1e

Please sign in to comment.