mirror of
https://github.com/openshift/image-registry.git
synced 2026-02-05 09:45:55 +01:00
Add integration test TestImageLayers
This commit is contained in:
175
pkg/clientcmd/clientcmd.go
Normal file
175
pkg/clientcmd/clientcmd.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package clientcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
kclientcmd "k8s.io/client-go/tools/clientcmd"
|
||||
kclientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
|
||||
"github.com/openshift/origin/pkg/cmd/flagtypes"
|
||||
"github.com/openshift/origin/pkg/cmd/util"
|
||||
"github.com/openshift/origin/pkg/oc/cli/config"
|
||||
)
|
||||
|
||||
// Config contains all the necessary bits for client configuration
|
||||
type Config struct {
|
||||
// MasterAddr is the address the master can be reached on (host, host:port, or URL).
|
||||
MasterAddr flagtypes.Addr
|
||||
// KubernetesAddr is the address of the Kubernetes server (host, host:port, or URL).
|
||||
// If omitted defaults to the master.
|
||||
KubernetesAddr flagtypes.Addr
|
||||
// CommonConfig is the shared base config for both the OpenShift config and Kubernetes config
|
||||
CommonConfig restclient.Config
|
||||
// Namespace is the namespace to act in
|
||||
Namespace string
|
||||
|
||||
// If true, no environment is loaded (for testing, primarily)
|
||||
SkipEnv bool
|
||||
|
||||
clientConfig clientcmd.ClientConfig
|
||||
}
|
||||
|
||||
// NewConfig returns a new configuration
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
MasterAddr: flagtypes.Addr{Value: "localhost:8080", DefaultScheme: "http", DefaultPort: 8080, AllowPrefix: true}.Default(),
|
||||
KubernetesAddr: flagtypes.Addr{Value: "localhost:8080", DefaultScheme: "http", DefaultPort: 8080}.Default(),
|
||||
CommonConfig: restclient.Config{},
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *Config) BindToFile(configPath string) *Config {
|
||||
defaultOverrides := &kclientcmd.ConfigOverrides{
|
||||
ClusterDefaults: kclientcmdapi.Cluster{
|
||||
Server: os.Getenv("KUBERNETES_MASTER"),
|
||||
},
|
||||
}
|
||||
|
||||
chain := []string{}
|
||||
if envVarFile := os.Getenv(config.OpenShiftConfigPathEnvVar); len(envVarFile) != 0 {
|
||||
chain = append(chain, filepath.SplitList(envVarFile)...)
|
||||
} else if len(configPath) != 0 {
|
||||
chain = append(chain, configPath)
|
||||
} else {
|
||||
chain = append(chain, config.RecommendedHomeFile)
|
||||
}
|
||||
|
||||
defaultClientConfig := kclientcmd.NewDefaultClientConfig(kclientcmdapi.Config{}, defaultOverrides)
|
||||
|
||||
loadingRules := &kclientcmd.ClientConfigLoadingRules{
|
||||
Precedence: chain,
|
||||
DefaultClientConfig: defaultClientConfig,
|
||||
}
|
||||
|
||||
overrides := &kclientcmd.ConfigOverrides{
|
||||
ClusterDefaults: defaultOverrides.ClusterDefaults,
|
||||
}
|
||||
|
||||
cfg.clientConfig = kclientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (cfg *Config) bindEnv() error {
|
||||
// bypass loading from env
|
||||
if cfg.SkipEnv {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
|
||||
// callers may not use the config file if they have specified a master directly, for backwards
|
||||
// compatibility with components that used to use env, switch to service account token, and have
|
||||
// config defined in env.
|
||||
_, masterSet := util.GetEnv("OPENSHIFT_MASTER")
|
||||
specifiedMaster := masterSet || cfg.MasterAddr.Provided
|
||||
|
||||
if cfg.clientConfig != nil && !specifiedMaster {
|
||||
clientConfig, err := cfg.clientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.CommonConfig = *clientConfig
|
||||
cfg.Namespace, _, err = cfg.clientConfig.Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cfg.MasterAddr.Provided {
|
||||
cfg.MasterAddr.Set(cfg.CommonConfig.Host)
|
||||
}
|
||||
if !cfg.KubernetesAddr.Provided {
|
||||
cfg.KubernetesAddr.Set(cfg.CommonConfig.Host)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Legacy path - preserve env vars set on pods that previously were honored.
|
||||
if value, ok := util.GetEnv("KUBERNETES_MASTER"); ok && !cfg.KubernetesAddr.Provided {
|
||||
cfg.KubernetesAddr.Set(value)
|
||||
}
|
||||
if value, ok := util.GetEnv("OPENSHIFT_MASTER"); ok && !cfg.MasterAddr.Provided {
|
||||
cfg.MasterAddr.Set(value)
|
||||
}
|
||||
if value, ok := util.GetEnv("BEARER_TOKEN"); ok && len(cfg.CommonConfig.BearerToken) == 0 {
|
||||
cfg.CommonConfig.BearerToken = value
|
||||
}
|
||||
if value, ok := util.GetEnv("BEARER_TOKEN_FILE"); ok && len(cfg.CommonConfig.BearerToken) == 0 {
|
||||
if tokenData, tokenErr := ioutil.ReadFile(value); tokenErr == nil {
|
||||
cfg.CommonConfig.BearerToken = strings.TrimSpace(string(tokenData))
|
||||
if len(cfg.CommonConfig.BearerToken) == 0 {
|
||||
err = fmt.Errorf("BEARER_TOKEN_FILE %q was empty", value)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Error reading BEARER_TOKEN_FILE %q: %v", value, tokenErr)
|
||||
}
|
||||
}
|
||||
|
||||
if value, ok := util.GetEnv("OPENSHIFT_CA_FILE"); ok && len(cfg.CommonConfig.CAFile) == 0 {
|
||||
cfg.CommonConfig.CAFile = value
|
||||
} else if value, ok := util.GetEnv("OPENSHIFT_CA_DATA"); ok && len(cfg.CommonConfig.CAData) == 0 {
|
||||
cfg.CommonConfig.CAData = []byte(value)
|
||||
}
|
||||
|
||||
if value, ok := util.GetEnv("OPENSHIFT_CERT_FILE"); ok && len(cfg.CommonConfig.CertFile) == 0 {
|
||||
cfg.CommonConfig.CertFile = value
|
||||
} else if value, ok := util.GetEnv("OPENSHIFT_CERT_DATA"); ok && len(cfg.CommonConfig.CertData) == 0 {
|
||||
cfg.CommonConfig.CertData = []byte(value)
|
||||
}
|
||||
|
||||
if value, ok := util.GetEnv("OPENSHIFT_KEY_FILE"); ok && len(cfg.CommonConfig.KeyFile) == 0 {
|
||||
cfg.CommonConfig.KeyFile = value
|
||||
} else if value, ok := util.GetEnv("OPENSHIFT_KEY_DATA"); ok && len(cfg.CommonConfig.KeyData) == 0 {
|
||||
cfg.CommonConfig.KeyData = []byte(value)
|
||||
}
|
||||
|
||||
if value, ok := util.GetEnv("OPENSHIFT_INSECURE"); ok && len(value) != 0 {
|
||||
cfg.CommonConfig.Insecure = value == "true"
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// KubeConfig returns the Kubernetes configuration
|
||||
func (cfg *Config) KubeConfig() *restclient.Config {
|
||||
err := cfg.bindEnv()
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
|
||||
kaddr := cfg.KubernetesAddr
|
||||
if !kaddr.Provided {
|
||||
kaddr = cfg.MasterAddr
|
||||
}
|
||||
|
||||
kConfig := cfg.CommonConfig
|
||||
kConfig.Host = kaddr.URL.String()
|
||||
|
||||
return &kConfig
|
||||
}
|
||||
@@ -40,21 +40,22 @@ import (
|
||||
|
||||
kubeversion "k8s.io/kubernetes/pkg/version"
|
||||
|
||||
"github.com/openshift/origin/pkg/cmd/server/crypto"
|
||||
"github.com/openshift/origin/pkg/version"
|
||||
|
||||
"github.com/openshift/image-registry/pkg/clientcmd"
|
||||
"github.com/openshift/image-registry/pkg/dockerregistry/server"
|
||||
"github.com/openshift/image-registry/pkg/dockerregistry/server/audit"
|
||||
"github.com/openshift/image-registry/pkg/dockerregistry/server/client"
|
||||
registryconfig "github.com/openshift/image-registry/pkg/dockerregistry/server/configuration"
|
||||
"github.com/openshift/image-registry/pkg/dockerregistry/server/maxconnections"
|
||||
"github.com/openshift/image-registry/pkg/dockerregistry/server/prune"
|
||||
"github.com/openshift/origin/pkg/cmd/server/crypto"
|
||||
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
|
||||
"github.com/openshift/origin/pkg/version"
|
||||
)
|
||||
|
||||
var pruneMode = flag.String("prune", "", "prune blobs from the storage and exit (check, delete)")
|
||||
|
||||
func versionFields() log.Fields {
|
||||
return log.Fields{
|
||||
func versionFields() map[interface{}]interface{} {
|
||||
return map[interface{}]interface{}{
|
||||
"distribution_version": distversion.Version,
|
||||
"kubernetes_version": kubeversion.Get(),
|
||||
"openshift_version": version.Get(),
|
||||
@@ -63,7 +64,7 @@ func versionFields() log.Fields {
|
||||
|
||||
// ExecutePruner runs the pruner.
|
||||
func ExecutePruner(configFile io.Reader, dryRun bool) {
|
||||
config, _, err := registryconfig.Parse(configFile)
|
||||
config, extraConfig, err := registryconfig.Parse(configFile)
|
||||
if err != nil {
|
||||
log.Fatalf("error parsing configuration file: %s", err)
|
||||
}
|
||||
@@ -90,9 +91,9 @@ func ExecutePruner(configFile io.Reader, dryRun bool) {
|
||||
} else {
|
||||
registryOptions = append(registryOptions, storage.EnableDelete)
|
||||
}
|
||||
log.WithFields(versionFields()).Info(startPrune)
|
||||
context.GetLoggerWithFields(ctx, versionFields()).Info("start registry")
|
||||
|
||||
registryClient := client.NewRegistryClient(clientcmd.NewConfig().BindToFile())
|
||||
registryClient := client.NewRegistryClient(clientcmd.NewConfig().BindToFile(extraConfig.KubeConfig))
|
||||
|
||||
storageDriver, err := factory.Create(config.Storage.Type(), config.Storage.Parameters())
|
||||
if err != nil {
|
||||
@@ -150,29 +151,29 @@ func Execute(configFile io.Reader) {
|
||||
log.Fatalf("error parsing configuration file: %s", err)
|
||||
}
|
||||
|
||||
err = Start(dockerConfig, extraConfig)
|
||||
ctx := context.Background()
|
||||
ctx, err = configureLogging(ctx, dockerConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("error configuring logger: %v", err)
|
||||
}
|
||||
|
||||
err = Start(ctx, dockerConfig, extraConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Start runs the Docker registry. Start always returns a non-nil error.
|
||||
func Start(dockerConfig *configuration.Configuration, extraConfig *registryconfig.Configuration) error {
|
||||
func Start(ctx context.Context, dockerConfig *configuration.Configuration, extraConfig *registryconfig.Configuration) error {
|
||||
setDefaultMiddleware(dockerConfig)
|
||||
setDefaultLogParameters(dockerConfig)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, err := configureLogging(ctx, dockerConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error configuring logger: %v", err)
|
||||
}
|
||||
|
||||
log.WithFields(versionFields()).Info("start registry")
|
||||
context.GetLoggerWithFields(ctx, versionFields()).Info("start registry")
|
||||
// inject a logger into the uuid library. warns us if there is a problem
|
||||
// with uuid generation under low entropy.
|
||||
uuid.Loggerf = context.GetLogger(ctx).Warnf
|
||||
|
||||
registryClient := client.NewRegistryClient(clientcmd.NewConfig().BindToFile())
|
||||
registryClient := client.NewRegistryClient(clientcmd.NewConfig().BindToFile(extraConfig.KubeConfig))
|
||||
|
||||
readLimiter := newLimiter(extraConfig.Requests.Read)
|
||||
writeLimiter := newLimiter(extraConfig.Requests.Write)
|
||||
@@ -194,6 +195,7 @@ func Start(dockerConfig *configuration.Configuration, extraConfig *registryconfi
|
||||
var (
|
||||
minVersion uint16
|
||||
cipherSuites []uint16
|
||||
err error
|
||||
)
|
||||
if s := os.Getenv("REGISTRY_HTTP_TLS_MINVERSION"); len(s) > 0 {
|
||||
minVersion, err = crypto.TLSVersion(s)
|
||||
|
||||
@@ -19,10 +19,10 @@ import (
|
||||
restclient "k8s.io/client-go/rest"
|
||||
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
|
||||
userapi "github.com/openshift/origin/pkg/user/apis/user"
|
||||
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization/v1"
|
||||
|
||||
"github.com/openshift/image-registry/pkg/clientcmd"
|
||||
"github.com/openshift/image-registry/pkg/dockerregistry/server/client"
|
||||
"github.com/openshift/image-registry/pkg/dockerregistry/server/configuration"
|
||||
"github.com/openshift/image-registry/pkg/dockerregistry/testutil"
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
authclientv1 "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/authorization/v1"
|
||||
kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
||||
|
||||
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
|
||||
"github.com/openshift/image-registry/pkg/clientcmd"
|
||||
imageclientv1 "github.com/openshift/origin/pkg/image/generated/clientset/typed/image/v1"
|
||||
userclientv1 "github.com/openshift/origin/pkg/user/generated/clientset/typed/user/v1"
|
||||
)
|
||||
@@ -104,8 +104,6 @@ type registryClient struct {
|
||||
}
|
||||
|
||||
// NewRegistryClient provides a new registry client.
|
||||
// TODO: Remove clientcmd dependency and move the parsing of required
|
||||
// environemtn variable to registry.
|
||||
func NewRegistryClient(config *clientcmd.Config) RegistryClient {
|
||||
return ®istryClient{
|
||||
kubeConfig: config.KubeConfig(),
|
||||
|
||||
@@ -27,9 +27,10 @@ type openshiftConfig struct {
|
||||
}
|
||||
|
||||
type Configuration struct {
|
||||
Version configuration.Version `yaml:"version"`
|
||||
Metrics Metrics `yaml:"metrics"`
|
||||
Requests Requests `yaml:"requests"`
|
||||
Version configuration.Version `yaml:"version"`
|
||||
Metrics Metrics `yaml:"metrics"`
|
||||
Requests Requests `yaml:"requests"`
|
||||
KubeConfig string `yaml:"kubeconfig"`
|
||||
}
|
||||
|
||||
type Metrics struct {
|
||||
|
||||
@@ -23,6 +23,9 @@ import (
|
||||
imageapi "github.com/openshift/origin/pkg/image/apis/image"
|
||||
imageapiv1 "github.com/openshift/origin/pkg/image/apis/image/v1"
|
||||
"github.com/openshift/origin/pkg/image/util"
|
||||
|
||||
// install image API for k8s.io/kubernetes/pkg/api.Scheme.Converter
|
||||
_ "github.com/openshift/origin/pkg/image/apis/image/install"
|
||||
)
|
||||
|
||||
type ManifestSchemaVersion int
|
||||
|
||||
457
test/integration/imagelayers/imagelayers_test.go
Normal file
457
test/integration/imagelayers/imagelayers_test.go
Normal file
@@ -0,0 +1,457 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/configuration"
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/docker/distribution/digest"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
kapi "k8s.io/kubernetes/pkg/api/v1"
|
||||
|
||||
authorizationapiv1 "github.com/openshift/origin/pkg/authorization/apis/authorization/v1"
|
||||
authorizationv1 "github.com/openshift/origin/pkg/authorization/generated/clientset/typed/authorization/v1"
|
||||
"github.com/openshift/origin/pkg/cmd/util/tokencmd"
|
||||
imageapi "github.com/openshift/origin/pkg/image/apis/image"
|
||||
imagev1 "github.com/openshift/origin/pkg/image/generated/clientset/typed/image/v1"
|
||||
projectapiv1 "github.com/openshift/origin/pkg/project/apis/project/v1"
|
||||
projectv1 "github.com/openshift/origin/pkg/project/generated/clientset/typed/project/v1"
|
||||
|
||||
"github.com/openshift/image-registry/pkg/cmd/dockerregistry"
|
||||
registryconfig "github.com/openshift/image-registry/pkg/dockerregistry/server/configuration"
|
||||
registrytest "github.com/openshift/image-registry/pkg/dockerregistry/testutil"
|
||||
)
|
||||
|
||||
// FindFreeLocalPort returns the number of an available port number on
|
||||
// the loopback interface. Useful for determining the port to launch
|
||||
// a server on. Error handling required - there is a non-zero chance
|
||||
// that the returned port number will be bound by another process
|
||||
// after this function returns.
|
||||
//
|
||||
// k8s.io/kubernetes/test/integration/framework.FindFreeLocalPort
|
||||
func FindFreeLocalPort() (int, error) {
|
||||
l, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer l.Close()
|
||||
_, portStr, err := net.SplitHostPort(l.Addr().String())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return port, nil
|
||||
}
|
||||
|
||||
func waitTCP(addr string) error {
|
||||
var lastErr error
|
||||
err := wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (done bool, err error) {
|
||||
conn, err := net.DialTimeout("tcp", addr, 200*time.Millisecond)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
return false, nil
|
||||
}
|
||||
_ = conn.Close()
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("wait for %s: %v", addr, lastErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitHTTP(rt http.RoundTripper, url string) error {
|
||||
var lastErr error
|
||||
httpClient := &http.Client{
|
||||
Transport: rt,
|
||||
}
|
||||
err := wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (done bool, err error) {
|
||||
resp, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
return false, nil
|
||||
}
|
||||
_ = resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
lastErr = fmt.Errorf("%s", resp.Status)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("wait for %s: %v", url, lastErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartTestRegistry(t *testing.T, kubeConfigPath string) (string, error) {
|
||||
port, err := FindFreeLocalPort()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to find a free local port for the registry: %v", err)
|
||||
}
|
||||
|
||||
// FIXME(dmage): the port can be claimed by someone else before the registry is started.
|
||||
registryAddr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||
|
||||
dockerConfig := &configuration.Configuration{
|
||||
Version: "0.1",
|
||||
Storage: configuration.Storage{
|
||||
"inmemory": configuration.Parameters{},
|
||||
},
|
||||
Auth: configuration.Auth{
|
||||
"openshift": configuration.Parameters{},
|
||||
},
|
||||
Middleware: map[string][]configuration.Middleware{
|
||||
"registry": {{
|
||||
Name: "openshift",
|
||||
}},
|
||||
"repository": {{
|
||||
Name: "openshift",
|
||||
Options: configuration.Parameters{
|
||||
"dockerregistryurl": registryAddr,
|
||||
"acceptschema2": true,
|
||||
"pullthrough": true,
|
||||
"enforcequota": false,
|
||||
"projectcachettl": "1m",
|
||||
"blobrepositorycachettl": "10m",
|
||||
},
|
||||
}},
|
||||
"storage": {{
|
||||
Name: "openshift",
|
||||
}},
|
||||
},
|
||||
}
|
||||
dockerConfig.Log.Level = "debug"
|
||||
dockerConfig.HTTP.Addr = registryAddr
|
||||
|
||||
extraConfig := ®istryconfig.Configuration{
|
||||
KubeConfig: kubeConfigPath,
|
||||
}
|
||||
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
ctx = registrytest.WithTestLogger(ctx, t)
|
||||
err := dockerregistry.Start(ctx, dockerConfig, extraConfig)
|
||||
// We cannot call t.Fatal here, because it's a different goroutine.
|
||||
panic(fmt.Errorf("failed to start the image registry: %v", err))
|
||||
}()
|
||||
|
||||
return registryAddr, waitTCP(registryAddr)
|
||||
}
|
||||
|
||||
// uploadImageWithSchema2Manifest creates a random image with a schema 2
|
||||
// manifest and uploads it to the repository.
|
||||
func uploadImageWithSchema2Manifest(ctx context.Context, repo distribution.Repository, tag string) error {
|
||||
layers := make([]distribution.Descriptor, 3)
|
||||
for i := range layers {
|
||||
content, desc, err := registrytest.MakeRandomLayer()
|
||||
if err != nil {
|
||||
return fmt.Errorf("make random layer: %v", err)
|
||||
}
|
||||
|
||||
if err := registrytest.UploadBlob(ctx, repo, desc, content); err != nil {
|
||||
return fmt.Errorf("upload random blob: %v", err)
|
||||
}
|
||||
|
||||
layers[i] = desc
|
||||
}
|
||||
|
||||
cfg := map[string]interface{}{
|
||||
"rootfs": map[string]interface{}{
|
||||
"diff_ids": make([]string, len(layers)),
|
||||
},
|
||||
"history": make([]struct{}, len(layers)),
|
||||
}
|
||||
|
||||
configContent, err := json.Marshal(&cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal image config: %v", err)
|
||||
}
|
||||
|
||||
config := distribution.Descriptor{
|
||||
Digest: digest.FromBytes(configContent),
|
||||
Size: int64(len(configContent)),
|
||||
}
|
||||
|
||||
if err := registrytest.UploadBlob(ctx, repo, config, configContent); err != nil {
|
||||
return fmt.Errorf("upload image config: %v", err)
|
||||
}
|
||||
|
||||
manifest, err := registrytest.MakeSchema2Manifest(config, layers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("make schema 2 manifest: %v", err)
|
||||
}
|
||||
|
||||
if err := registrytest.UploadManifest(ctx, repo, tag, manifest); err != nil {
|
||||
return fmt.Errorf("upload schema 2 manifest: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getSchema1Manifest simulates a client which supports only schema 1
|
||||
// manifests, fetches a manifest from a registry and returns it.
|
||||
func getSchema1Manifest(transport http.RoundTripper, baseURL, repoName, tag string) (distribution.Manifest, error) {
|
||||
c := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
resp, err := c.Get(baseURL + "/v2/" + repoName + "/manifests/" + tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read manifest %s:%s: %v", repoName, tag, err)
|
||||
}
|
||||
|
||||
m, _, err := distribution.UnmarshalManifest(resp.Header.Get("Content-Type"), body)
|
||||
return m, err
|
||||
}
|
||||
|
||||
type InspectResult struct {
|
||||
State struct {
|
||||
Status string
|
||||
}
|
||||
NetworkSettings struct {
|
||||
IPAddress string
|
||||
}
|
||||
}
|
||||
|
||||
func Inspect(containerID string) (*InspectResult, error) {
|
||||
cmd := exec.Command("docker", "inspect", containerID)
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var v []*InspectResult
|
||||
if err := json.NewDecoder(stdout).Decode(&v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v[0], cmd.Wait()
|
||||
}
|
||||
|
||||
// TestImageLayers tests that the integrated registry handles schema 1
|
||||
// manifests and schema 2 manifests consistently and it produces similar Image
|
||||
// resources for them.
|
||||
//
|
||||
// The test relies on ability of the registry to downconvert manifests.
|
||||
func TestImageLayers(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("", "image-registry-test-integration-")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temporary directory: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
configDir := path.Join(tmpDir, "config")
|
||||
caBundlePath := path.Join(configDir, "master", "ca-bundle.crt")
|
||||
adminKubeConfigPath := path.Join(configDir, "master", "admin.kubeconfig")
|
||||
|
||||
masterContainerIDRaw, err := exec.Command(
|
||||
"docker", "run", "-d",
|
||||
"docker.io/openshift/origin", "start", "master",
|
||||
).Output()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to run the origin container: %v", err)
|
||||
}
|
||||
masterContainerID := strings.TrimSpace(string(masterContainerIDRaw))
|
||||
defer func() {
|
||||
if err := exec.Command("docker", "rm", "-f", "-v", masterContainerID).Run(); err != nil {
|
||||
t.Logf("failed to remove the master container %s: %v", masterContainerID, err)
|
||||
}
|
||||
}()
|
||||
|
||||
masterContainer, err := Inspect(masterContainerID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := waitTCP(masterContainer.NetworkSettings.IPAddress + ":8443"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := exec.Command("docker", "cp", fmt.Sprintf("%s:%s", masterContainerID, "/var/lib/origin/openshift.local.config"), configDir).Run(); err != nil {
|
||||
t.Fatalf("failed to get configs from the master container %s: %v", masterContainerID, err)
|
||||
}
|
||||
|
||||
caBundle, err := ioutil.ReadFile(caBundlePath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read CA bundle: %v", err)
|
||||
}
|
||||
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AppendCertsFromPEM(caBundle)
|
||||
tlsConfig := &tls.Config{
|
||||
RootCAs: rootCAs,
|
||||
}
|
||||
rt := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
|
||||
if err := waitHTTP(rt, fmt.Sprintf("https://%s:8443/healthz", masterContainer.NetworkSettings.IPAddress)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
adminConfig, err := clientcmd.LoadFromFile(adminKubeConfigPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
adminClientConfig, err := clientcmd.NewDefaultClientConfig(*adminConfig, &clientcmd.ConfigOverrides{}).ClientConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
imageClient := imagev1.NewForConfigOrDie(adminClientConfig)
|
||||
|
||||
namespace := "image-registry-test-integration"
|
||||
imageStreamName := "test-imagelayers"
|
||||
user := "testuser"
|
||||
password := "testp@ssw0rd"
|
||||
|
||||
projectClient := projectv1.NewForConfigOrDie(adminClientConfig)
|
||||
_, err = projectClient.ProjectRequests().Create(&projectapiv1.ProjectRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
authorizationClient := authorizationv1.NewForConfigOrDie(adminClientConfig)
|
||||
_, err = authorizationClient.RoleBindings(namespace).Update(&authorizationapiv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "admin",
|
||||
},
|
||||
UserNames: []string{user},
|
||||
RoleRef: kapi.ObjectReference{
|
||||
Name: "admin",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
token, err := tokencmd.RequestToken(adminClientConfig, nil, user, password)
|
||||
if err != nil {
|
||||
t.Fatalf("error requesting token: %v", err)
|
||||
}
|
||||
|
||||
registryAddr, err := StartTestRegistry(t, adminKubeConfigPath)
|
||||
if err != nil {
|
||||
t.Fatalf("start registry: %v", err)
|
||||
}
|
||||
|
||||
creds := registrytest.NewBasicCredentialStore(user, token)
|
||||
|
||||
baseURL := "http://" + registryAddr
|
||||
repoName := fmt.Sprintf("%s/%s", namespace, imageStreamName)
|
||||
|
||||
schema1Tag := "schema1"
|
||||
schema2Tag := "schema2"
|
||||
|
||||
transport, err := registrytest.NewTransport(baseURL, repoName, creds)
|
||||
if err != nil {
|
||||
t.Fatalf("get transport: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
repo, err := registrytest.NewRepository(ctx, repoName, baseURL, transport)
|
||||
if err != nil {
|
||||
t.Fatalf("get repository: %v", err)
|
||||
}
|
||||
|
||||
if err := uploadImageWithSchema2Manifest(ctx, repo, schema2Tag); err != nil {
|
||||
t.Fatalf("upload image with schema 2 manifest: %v", err)
|
||||
}
|
||||
|
||||
// get the schema2 image's manifest downconverted to a schema 1 manifest
|
||||
schema1Manifest, err := getSchema1Manifest(transport, baseURL, repoName, schema2Tag)
|
||||
if err != nil {
|
||||
t.Fatalf("get schema 1 manifest for image schema2: %v", err)
|
||||
}
|
||||
|
||||
if err := registrytest.UploadManifest(ctx, repo, schema1Tag, schema1Manifest); err != nil {
|
||||
t.Fatalf("upload schema 1 manifest: %v", err)
|
||||
}
|
||||
|
||||
schema1ISTag, err := imageClient.ImageStreamTags(namespace).Get(imageStreamName+":"+schema1Tag, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("get image stream tag %s:%s: %v", imageStreamName, schema1Tag, err)
|
||||
}
|
||||
|
||||
schema2ISTag, err := imageClient.ImageStreamTags(namespace).Get(imageStreamName+":"+schema2Tag, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("get image stream tag %s:%s: %v", imageStreamName, schema1Tag, err)
|
||||
}
|
||||
|
||||
if schema1ISTag.Image.DockerImageManifestMediaType == schema2ISTag.Image.DockerImageManifestMediaType {
|
||||
t.Errorf("expected different media types, but got %q", schema1ISTag.Image.DockerImageManifestMediaType)
|
||||
}
|
||||
|
||||
image1LayerOrder := schema1ISTag.Image.Annotations[imageapi.DockerImageLayersOrderAnnotation]
|
||||
image2LayerOrder := schema2ISTag.Image.Annotations[imageapi.DockerImageLayersOrderAnnotation]
|
||||
if image1LayerOrder != image2LayerOrder {
|
||||
t.Errorf("the layer order annotations are different: schema1=%q, schema2=%q", image1LayerOrder, image2LayerOrder)
|
||||
} else if image1LayerOrder == "" {
|
||||
t.Errorf("the layer order annotation is empty or not present")
|
||||
}
|
||||
|
||||
image1Layers := schema1ISTag.Image.DockerImageLayers
|
||||
image2Layers := schema2ISTag.Image.DockerImageLayers
|
||||
if len(image1Layers) != len(image2Layers) {
|
||||
t.Errorf("layers are different: schema1=%#+v, schema2=%#+v", image1Layers, image2Layers)
|
||||
} else {
|
||||
for i := range image1Layers {
|
||||
if image1Layers[i].Name != image2Layers[i].Name {
|
||||
t.Errorf("different names for the layer #%d: schema1=%#+v, schema2=%#+v", i, image1Layers[i], image2Layers[i])
|
||||
}
|
||||
if image1Layers[i].LayerSize != image2Layers[i].LayerSize {
|
||||
t.Errorf("different sizes for the layer #%d: schema1=%#+v, schema2=%#+v", i, image1Layers[i], image2Layers[i])
|
||||
} else if image1Layers[i].LayerSize <= 0 {
|
||||
t.Errorf("unexpected size for the layer #%d: %d", i, image1Layers[i].LayerSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user