package integration import ( "reflect" "strings" "testing" "time" "github.com/openshift/origin/pkg/dockerregistry" ) const ( pulpRegistryName = "registry.access.redhat.com" dockerHubV2RegistryName = "index.docker.io" dockerHubV1RegistryName = "registry.hub.docker.com" quayRegistryName = "quay.io" maxRetryCount = 4 retryAfter = time.Millisecond * 500 ) var ( // Below are lists of error patterns for use with `retryOnErrors` utility. // unreachableErrorPatterns will match following error examples: // Get https://registry.com/v2/: dial tcp registry.com:443: i/o timeout // Get https://registry.com/v2/: dial tcp: lookup registry.com: no such host // Get https://registry.com/v2/: dial tcp registry.com:443: getsockopt: connection refused // Get https://registry.com/v2/: read tcp 127.0.0.1:39849->registry.com:443: read: connection reset by peer // Get https://registry.com/v2/: net/http: request cancelled while waiting for connection // Get https://registry.com/v2/: net/http: TLS handshake timeout // the registry "https://registry.com/v2/" could not be reached unreachableErrorPatterns = []string{ "dial tcp", "read tcp", "net/http", "could not be reached", } // imageNotFoundErrorPatterns will match following error examples: // the image "..." in repository "..." was not found and may have been deleted // tag "..." has not been set on repository "..." // use only with non-internal registry imageNotFoundErrorPatterns = []string{ "was not found and may have been deleted", "has not been set on repository", } ) // retryOnErrors invokes given function several times until it succeeds, // returns unexpected error or a maximum number of attempts is reached. It // should be used to wrap calls to remote registry to prevent test failures // because of short-term outages or image updates. func retryOnErrors(t *testing.T, errorPatterns []string, f func() error) error { timeout := retryAfter attempt := 0 for err := f(); err != nil; err = f() { match := false for _, pattern := range errorPatterns { if strings.Contains(err.Error(), pattern) { match = true break } } if !match || attempt >= maxRetryCount { return err } t.Logf("caught error \"%v\", retrying in %s", err, timeout.String()) time.Sleep(timeout) timeout = timeout * 2 attempt += 1 } return nil } // retryWhenUnreachable is a convenient wrapper for retryOnErrors that makes it // retry when the registry is not reachable. Additional error patterns may // follow. func retryWhenUnreachable(t *testing.T, f func() error, errorPatterns ...string) error { return retryOnErrors(t, append(errorPatterns, unreachableErrorPatterns...), f) } func TestRegistryClientConnect(t *testing.T) { c := dockerregistry.NewClient(10*time.Second, true) conn, err := c.Connect("docker.io", false) if err != nil { t.Fatal(err) } for _, s := range []string{"index.docker.io", "https://docker.io", "https://index.docker.io"} { otherConn, err := c.Connect(s, false) if err != nil { t.Errorf("%s: can't connect: %v", s, err) continue } if !reflect.DeepEqual(otherConn, conn) { t.Errorf("%s: did not reuse connection: %#v %#v", s, conn, otherConn) } } otherConn, err := c.Connect("index.docker.io:443", false) if err != nil || reflect.DeepEqual(otherConn, conn) { t.Errorf("should not have reused index.docker.io:443: %v", err) } if _, err := c.Connect("http://ba%3/", false); err == nil { t.Error("Unexpected non-error") } } func TestRegistryClientConnectPulpRegistry(t *testing.T) { c := dockerregistry.NewClient(10*time.Second, true) conn, err := c.Connect(pulpRegistryName, false) if err != nil { t.Fatal(err) } var image *dockerregistry.Image err = retryWhenUnreachable(t, func() error { image, err = conn.ImageByTag("library", "rhel", "latest") return err }, imageNotFoundErrorPatterns...) if err != nil { if strings.Contains(err.Error(), "x509: certificate has expired or is not yet valid") { t.Skip("SKIPPING: due to expired certificate of %s: %v", pulpRegistryName, err) } t.Skip("pulp is failing") //t.Fatal(err) } if len(image.ID) == 0 { t.Fatalf("image had no ID: %#v", image) } } func TestRegistryClientDockerHubV2(t *testing.T) { c := dockerregistry.NewClient(10*time.Second, true) conn, err := c.Connect(dockerHubV2RegistryName, false) if err != nil { t.Fatal(err) } var image *dockerregistry.Image err = retryWhenUnreachable(t, func() error { image, err = conn.ImageByTag("kubernetes", "guestbook", "latest") return err }) if err != nil { t.Fatal(err) } if len(image.ID) == 0 { t.Fatalf("image had no ID: %#v", image) } } func TestRegistryClientDockerHubV1(t *testing.T) { c := dockerregistry.NewClient(10*time.Second, true) // a v1 only path conn, err := c.Connect(dockerHubV1RegistryName, false) if err != nil { t.Fatal(err) } var image *dockerregistry.Image err = retryWhenUnreachable(t, func() error { image, err = conn.ImageByTag("kubernetes", "guestbook", "latest") return err }) if err != nil { t.Fatal(err) } if len(image.ID) == 0 { t.Fatalf("image had no ID: %#v", image) } } func TestRegistryClientRegistryNotFound(t *testing.T) { conn, err := dockerregistry.NewClient(10*time.Second, true).Connect("localhost:65000", false) if err != nil { t.Fatal(err) } if _, err := conn.ImageByID("foo", "bar", "baz"); !dockerregistry.IsRegistryNotFound(err) { t.Error(err) } } func doTestRegistryClientImage(t *testing.T, registry, reponame, version string) { conn, err := dockerregistry.NewClient(10*time.Second, version == "v2").Connect(registry, false) if err != nil { t.Fatal(err) } err = retryWhenUnreachable(t, func() error { _, err := conn.ImageByTag("openshift", "origin-not-found", "latest") return err }) if err == nil || (!dockerregistry.IsRepositoryNotFound(err) && !dockerregistry.IsTagNotFound(err)) { t.Errorf("%s: unexpected error: %v", version, err) } var image *dockerregistry.Image err = retryWhenUnreachable(t, func() error { image, err = conn.ImageByTag("openshift", reponame, "latest") return err }) if err != nil { t.Fatal(err) } if image.Comment != "Imported from -" { t.Errorf("%s: unexpected image comment", version) } if image.Architecture != "amd64" { t.Errorf("%s: unexpected image architecture", version) } if version == "v2" && !image.PullByID { t.Errorf("%s: should be able to pull by ID %s", version, image.ID) } var other *dockerregistry.Image err = retryWhenUnreachable(t, func() error { other, err = conn.ImageByID("openshift", reponame, image.ID) return err }) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(other.ContainerConfig.Entrypoint, image.ContainerConfig.Entrypoint) { t.Errorf("%s: unexpected image: %#v", version, other) } } func TestRegistryClientAPIv2ManifestV2Schema2(t *testing.T) { t.Log("openshift/schema-v2-test-repo was pushed by Docker 1.11.1") doTestRegistryClientImage(t, dockerHubV2RegistryName, "schema-v2-test-repo", "v2") } func TestRegistryClientAPIv2ManifestV2Schema1(t *testing.T) { t.Log("openshift/schema-v1-test-repo was pushed by Docker 1.8.2") doTestRegistryClientImage(t, dockerHubV2RegistryName, "schema-v1-test-repo", "v2") } func TestRegistryClientAPIv1(t *testing.T) { t.Log("openshift/schema-v1-test-repo was pushed by Docker 1.8.2") doTestRegistryClientImage(t, dockerHubV1RegistryName, "schema-v1-test-repo", "v1") } func TestRegistryClientQuayIOImage(t *testing.T) { conn, err := dockerregistry.NewClient(10*time.Second, true).Connect("quay.io", false) if err != nil { t.Fatal(err) } err = retryWhenUnreachable(t, func() error { _, err := conn.ImageByTag("coreos", "etcd", "latest") return err }, imageNotFoundErrorPatterns...) if err != nil { t.Skip("SKIPPING: unexpected error from quay.io: %v", err) } }