forked from TrueCloudLab/distribution
Merge pull request #2138 from yuwaMSFT2/master
closes issue#2135 image pull returns 404 on manifest request if there is storage error
This commit is contained in:
commit
0111f1e3cf
2 changed files with 130 additions and 4 deletions
|
@ -3,6 +3,7 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -28,6 +29,8 @@ import (
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
"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/registry/storage/driver/testdriver"
|
||||||
"github.com/docker/distribution/testutil"
|
"github.com/docker/distribution/testutil"
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
|
@ -815,6 +818,93 @@ func TestManifestAPI(t *testing.T) {
|
||||||
testManifestAPIManifestList(t, env2, schema2Args)
|
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) {
|
func TestManifestDelete(t *testing.T) {
|
||||||
schema1Repo, _ := reference.ParseNamed("foo/schema1")
|
schema1Repo, _ := reference.ParseNamed("foo/schema1")
|
||||||
schema2Repo, _ := reference.ParseNamed("foo/schema2")
|
schema2Repo, _ := reference.ParseNamed("foo/schema2")
|
||||||
|
@ -852,6 +942,26 @@ func testManifestDeleteDisabled(t *testing.T, env *testEnv, imageName reference.
|
||||||
checkResponse(t, "status of disabled delete of manifest", resp, http.StatusMethodNotAllowed)
|
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 {
|
func testManifestAPISchema1(t *testing.T, env *testEnv, imageName reference.Named) manifestArgs {
|
||||||
tag := "thetag"
|
tag := "thetag"
|
||||||
args := manifestArgs{imageName: imageName}
|
args := manifestArgs{imageName: imageName}
|
||||||
|
|
|
@ -77,7 +77,11 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
|
||||||
tags := imh.Repository.Tags(imh)
|
tags := imh.Repository.Tags(imh)
|
||||||
desc, err := tags.Get(imh, imh.Tag)
|
desc, err := tags.Get(imh, imh.Tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if _, ok := err.(distribution.ErrTagUnknown); ok {
|
||||||
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
|
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
|
||||||
|
} else {
|
||||||
|
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
imh.Digest = desc.Digest
|
imh.Digest = desc.Digest
|
||||||
|
@ -94,7 +98,11 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
manifest, err = manifests.Get(imh, imh.Digest, options...)
|
manifest, err = manifests.Get(imh, imh.Digest, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if _, ok := err.(distribution.ErrManifestUnknownRevision); ok {
|
||||||
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
|
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
|
||||||
|
} else {
|
||||||
|
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +169,11 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
manifest, err = manifests.Get(imh, manifestDigest)
|
manifest, err = manifests.Get(imh, manifestDigest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if _, ok := err.(distribution.ErrManifestUnknownRevision); ok {
|
||||||
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
|
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
|
||||||
|
} else {
|
||||||
|
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +203,11 @@ func (imh *manifestHandler) convertSchema2Manifest(schema2Manifest *schema2.Dese
|
||||||
blobs := imh.Repository.Blobs(imh)
|
blobs := imh.Repository.Blobs(imh)
|
||||||
configJSON, err := blobs.Get(imh, targetDescriptor.Digest)
|
configJSON, err := blobs.Get(imh, targetDescriptor.Digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err == distribution.ErrBlobUnknown {
|
||||||
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
|
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err))
|
||||||
|
} else {
|
||||||
|
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue