package importer
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
gocontext "golang.org/x/net/context"
"github.com/docker/distribution"
"github.com/docker/distribution/context"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/api/errcode"
kapi "k8s.io/kubernetes/pkg/api"
"github.com/openshift/origin/pkg/dockerregistry"
"github.com/openshift/origin/pkg/image/api"
)
type mockRetriever struct {
repo distribution.Repository
insecure bool
err error
}
func (r *mockRetriever) Repository(ctx gocontext.Context, registry *url.URL, repoName string, insecure bool) (distribution.Repository, error) {
r.insecure = insecure
return r.repo, r.err
}
type mockRepository struct {
repoErr, getErr, getByTagErr, getTagErr, tagErr, untagErr, allTagErr, err error
blobs *mockBlobStore
manifest distribution.Manifest
tags map[string]string
}
func (r *mockRepository) Name() string { return "test" }
func (r *mockRepository) Named() reference.Named {
named, _ := reference.WithName("test")
return named
}
func (r *mockRepository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
return r, r.repoErr
}
func (r *mockRepository) Blobs(ctx context.Context) distribution.BlobStore { return r.blobs }
func (r *mockRepository) Exists(ctx context.Context, dgst digest.Digest) (bool, error) {
return false, r.getErr
}
func (r *mockRepository) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
for _, option := range options {
if _, ok := option.(distribution.WithTagOption); ok {
return r.manifest, r.getByTagErr
}
}
return r.manifest, r.getErr
}
func (r *mockRepository) Delete(ctx context.Context, dgst digest.Digest) error {
return fmt.Errorf("not implemented")
}
func (r *mockRepository) Put(ctx context.Context, manifest distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) {
return "", fmt.Errorf("not implemented")
}
func (r *mockRepository) Tags(ctx context.Context) distribution.TagService {
return &mockTagService{repo: r}
}
type mockBlobStore struct {
distribution.BlobStore
blobs map[digest.Digest][]byte
statErr, serveErr, openErr error
}
func (r *mockBlobStore) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
return distribution.Descriptor{}, r.statErr
}
func (r *mockBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, req *http.Request, dgst digest.Digest) error {
return r.serveErr
}
func (r *mockBlobStore) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) {
return nil, r.openErr
}
func (r *mockBlobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
b, exists := r.blobs[dgst]
if !exists {
return nil, distribution.ErrBlobUnknown
}
return b, nil
}
type mockTagService struct {
distribution.TagService
repo *mockRepository
}
func (r *mockTagService) Get(ctx context.Context, tag string) (distribution.Descriptor, error) {
v, ok := r.repo.tags[tag]
if !ok {
return distribution.Descriptor{}, r.repo.getTagErr
}
dgst, err := digest.ParseDigest(v)
if err != nil {
panic(err)
}
return distribution.Descriptor{Digest: dgst}, r.repo.getTagErr
}
func (r *mockTagService) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error {
r.repo.tags[tag] = desc.Digest.String()
return r.repo.tagErr
}
func (r *mockTagService) Untag(ctx context.Context, tag string) error {
if _, ok := r.repo.tags[tag]; ok {
delete(r.repo.tags, tag)
}
return r.repo.untagErr
}
func (r *mockTagService) All(ctx context.Context) (res []string, err error) {
err = r.repo.allTagErr
for tag := range r.repo.tags {
res = append(res, tag)
}
return
}
func (r *mockTagService) Lookup(ctx context.Context, digest distribution.Descriptor) ([]string, error) {
return nil, fmt.Errorf("not implemented")
}
func TestSchema1ToImage(t *testing.T) {
m := &schema1.SignedManifest{}
if err := json.Unmarshal([]byte(etcdManifest), m); err != nil {
t.Fatal(err)
}
image, err := schema1ToImage(m, digest.Digest("sha256:test"))
if err != nil {
t.Fatal(err)
}
if image.DockerImageMetadata.ID != "sha256:test" {
t.Errorf("unexpected image: %#v", image.DockerImageMetadata.ID)
}
}
func TestDockerV1Fallback(t *testing.T) {
var uri *url.URL
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Docker-Endpoints", uri.Host)
// get all tags
if strings.HasSuffix(r.URL.Path, "/tags") {
fmt.Fprintln(w, `{"tag1":"image1", "test":"image2"}`)
w.WriteHeader(http.StatusOK)
return
}
if strings.HasSuffix(r.URL.Path, "/images") {
fmt.Fprintln(w, `{"tag1":"image1", "test":"image2"}`)
w.WriteHeader(http.StatusOK)
return
}
if strings.HasSuffix(r.URL.Path, "/json") {
fmt.Fprintln(w, `{"ID":"image2"}`)
w.WriteHeader(http.StatusOK)
return
}
t.Logf("tried to access %s", r.URL.Path)
w.WriteHeader(http.StatusNotFound)
}))
client := dockerregistry.NewClient(10*time.Second, false)
ctx := gocontext.WithValue(gocontext.Background(), ContextKeyV1RegistryClient, client)
uri, _ = url.Parse(server.URL)
isi := &api.ImageStreamImport{
Spec: api.ImageStreamImportSpec{
Repository: &api.RepositoryImportSpec{
From: kapi.ObjectReference{Kind: "DockerImage", Name: uri.Host + "/test:test"},
ImportPolicy: api.TagImportPolicy{Insecure: true},
},
},
}
retriever := &mockRetriever{err: fmt.Errorf("does not support v2 API")}
im := NewImageStreamImporter(retriever, 5, nil, nil)
if err := im.Import(ctx, isi); err != nil {
t.Fatal(err)
}
if images := isi.Status.Repository.Images; len(images) != 2 || images[0].Tag != "tag1" || images[1].Tag != "test" {
t.Errorf("unexpected images: %#v", images)
}
}
func TestPing(t *testing.T) {
retriever := NewContext(http.DefaultTransport, http.DefaultTransport).WithCredentials(NoCredentials).(*repositoryRetriever)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
uri, _ := url.Parse(server.URL)
_, err := retriever.ping(*uri, true, retriever.context.InsecureTransport)
if !strings.Contains(err.Error(), "does not support v2 API") {
t.Errorf("Expected ErrNotV2Registry, got %v", err)
}
uri.Scheme = "https"
_, err = retriever.ping(*uri, true, retriever.context.InsecureTransport)
if !strings.Contains(err.Error(), "does not support v2 API") {
t.Errorf("Expected ErrNotV2Registry, got %v", err)
}
}
func TestShouldRetry(t *testing.T) {
r := NewRetryRepository(nil, 1, 0).(*retryRepository)
// nil error doesn't consume retries
if r.shouldRetry(nil) {
t.Fatal(r)
}
if r.retries != 1 || r.initial != nil {
t.Fatal(r)
}
// normal error doesn't consume retries
if r.shouldRetry(fmt.Errorf("error")) {
t.Fatal(r)
}
if r.retries != 1 || r.initial != nil {
t.Fatal(r)
}
// docker error doesn't consume retries
if r.shouldRetry(errcode.ErrorCodeDenied) {
t.Fatal(r)
}
if r.retries != 1 || r.initial != nil {
t.Fatal(r)
}
now := time.Unix(1, 0)
nowFn = func() time.Time {
return now
}
// should retry unauthorized
r = NewRetryRepository(nil, 1, 0).(*retryRepository)
if !r.shouldRetry(errcode.ErrorCodeUnauthorized) {
t.Fatal(r)
}
if r.retries != 0 || r.initial == nil || !r.initial.Equal(now) {
t.Fatal(r)
}
if r.shouldRetry(errcode.ErrorCodeUnauthorized) {
t.Fatal(r)
}
// should not retry unauthorized after one second
r = NewRetryRepository(nil, 2, time.Second).(*retryRepository)
if !r.shouldRetry(errcode.ErrorCodeUnauthorized) {
t.Fatal(r)
}
if r.retries != 1 || r.initial == nil || !r.initial.Equal(time.Unix(1, 0)) || r.wait != (time.Second) {
t.Fatal(r)
}
now = time.Unix(3, 0)
if !r.shouldRetry(errcode.ErrorCodeUnauthorized) {
t.Fatal(r)
}
if r.retries != 0 || r.initial == nil || !r.initial.Equal(time.Unix(1, 0)) || r.wait != (time.Second) {
t.Fatal(r)
}
if r.shouldRetry(errcode.ErrorCodeUnauthorized) {
t.Fatal(r)
}
// should retry unauthorized within one second and preserve initial time
now = time.Unix(0, 0)
r = NewRetryRepository(nil, 2, time.Millisecond).(*retryRepository)
if !r.shouldRetry(errcode.ErrorCodeUnauthorized) {
t.Fatal(r)
}
if r.retries != 1 || r.initial == nil || !r.initial.Equal(time.Unix(0, 0)) {
t.Fatal(r)
}
now = time.Unix(0, time.Millisecond.Nanoseconds()/2)
if !r.shouldRetry(errcode.ErrorCodeUnauthorized) {
t.Fatal(r)
}
if r.retries != 0 || r.initial == nil || !r.initial.Equal(time.Unix(0, 0)) {
t.Fatal(r)
}
}
func TestRetryFailure(t *testing.T) {
if !isDockerError(errcode.ErrorCodeUnauthorized, errcode.ErrorCodeUnauthorized) {
t.Fatal("not an error")
}
// do not retry on Manifests()
repo := &mockRepository{repoErr: fmt.Errorf("does not support v2 API")}
r := NewRetryRepository(repo, 1, 0).(*retryRepository)
if m, err := r.Manifests(nil); m != nil || err != repo.repoErr || r.retries != 1 {
t.Fatalf("unexpected: %v %v %#v", m, err, r)
}
// do not retry on Manifests()
repo = &mockRepository{repoErr: errcode.ErrorCodeUnauthorized}
r = NewRetryRepository(repo, 4, 0).(*retryRepository)
if m, err := r.Manifests(nil); m != nil || err != repo.repoErr || r.retries != 4 {
t.Fatalf("unexpected: %v %v %#v", m, err, r)
}
// do not retry on non standard errors
repo = &mockRepository{getErr: fmt.Errorf("does not support v2 API")}
r = NewRetryRepository(repo, 4, 0).(*retryRepository)
m, err := r.Manifests(nil)
if err != nil {
t.Fatal(err)
}
if _, err := m.Get(nil, digest.Digest("foo")); err != repo.getErr || r.retries != 4 {
t.Fatalf("unexpected: %v %v %#v", m, err, r)
}
// retry four times
repo = &mockRepository{
getErr: errcode.ErrorCodeUnauthorized,
blobs: &mockBlobStore{
serveErr: errcode.ErrorCodeUnauthorized,
statErr: errcode.ErrorCodeUnauthorized,
openErr: errcode.ErrorCodeUnauthorized,
},
}
r = NewRetryRepository(repo, 4, 0).(*retryRepository)
if m, err = r.Manifests(nil); err != nil {
t.Fatal(err)
}
r.retries = 2
if _, err := m.Get(nil, digest.Digest("foo")); err != repo.getErr || r.retries != 0 {
t.Fatalf("unexpected: %v %#v", err, r)
}
r.retries = 2
if m, err := m.Exists(nil, "foo"); m || err != repo.getErr || r.retries != 0 {
t.Fatalf("unexpected: %v %v %#v", m, err, r)
}
r.retries = 2
b := r.Blobs(nil)
if err != nil {
t.Fatal(err)
}
if _, err := b.Stat(nil, digest.Digest("x")); err != repo.blobs.statErr || r.retries != 0 {
t.Fatalf("unexpected: %v %#v", err, r)
}
r.retries = 2
if err := b.ServeBlob(nil, nil, nil, digest.Digest("foo")); err != repo.blobs.serveErr || r.retries != 0 {
t.Fatalf("unexpected: %v %#v", err, r)
}
r.retries = 2
if _, err := b.Open(nil, digest.Digest("foo")); err != repo.blobs.openErr || r.retries != 0 {
t.Fatalf("unexpected: %v %#v", err, r)
}
}