From 5b9348c553d183bc62f6e7cc8f934766fac162bd Mon Sep 17 00:00:00 2001 From: allencloud Date: Wed, 21 Dec 2016 18:31:05 +0800 Subject: [PATCH] support registry mirror config reload Signed-off-by: allencloud --- daemon/daemon.go | 29 +++++- daemon/daemon_test.go | 94 +++++++++++++++++++ docs/reference/commandline/dockerd.md | 1 + .../docker_cli_events_unix_test.go | 2 +- registry/config.go | 50 ++++++++-- registry/config_test.go | 5 +- registry/registry_test.go | 2 +- registry/service.go | 9 ++ 8 files changed, 174 insertions(+), 18 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index e402d6f2e3..eaa85d482d 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -995,14 +995,15 @@ func (daemon *Daemon) initDiscovery(config *Config) error { // Reload reads configuration changes and modifies the // daemon according to those changes. // These are the settings that Reload changes: -// - Daemon labels. -// - Daemon debug log level. -// - Daemon insecure registries. +// - Daemon labels +// - Daemon debug log level +// - Insecure registries +// - Registry mirrors // - Daemon max concurrent downloads // - Daemon max concurrent uploads -// - Cluster discovery (reconfigure and restart). +// - Cluster discovery (reconfigure and restart) // - Daemon live restore -// - Daemon shutdown timeout (in seconds). +// - Daemon shutdown timeout (in seconds) func (daemon *Daemon) Reload(config *Config) (err error) { daemon.configStore.reloadLock.Lock() @@ -1035,6 +1036,14 @@ func (daemon *Daemon) Reload(config *Config) (err error) { return err } } + + if config.IsValueSet("registry-mirrors") { + daemon.configStore.Mirrors = config.Mirrors + if err := daemon.RegistryService.LoadMirrors(config.Mirrors); err != nil { + return err + } + } + if config.IsValueSet("live-restore") { daemon.configStore.LiveRestoreEnabled = config.LiveRestoreEnabled if err := daemon.containerdRemote.UpdateOptions(libcontainerd.WithLiveRestore(config.LiveRestoreEnabled)); err != nil { @@ -1087,6 +1096,16 @@ func (daemon *Daemon) Reload(config *Config) (err error) { attributes["insecure-registries"] = "[]" } + if daemon.configStore.Mirrors != nil { + mirrors, err := json.Marshal(daemon.configStore.Mirrors) + if err != nil { + return err + } + attributes["registry-mirrors"] = string(mirrors) + } else { + attributes["registry-mirrors"] = "[]" + } + attributes["cluster-store"] = daemon.configStore.ClusterStore if daemon.configStore.ClusterOpts != nil { opts, err := json.Marshal(daemon.configStore.ClusterOpts) diff --git a/daemon/daemon_test.go b/daemon/daemon_test.go index 00817bd1b6..87550d455b 100644 --- a/daemon/daemon_test.go +++ b/daemon/daemon_test.go @@ -341,6 +341,100 @@ func TestDaemonReloadLabels(t *testing.T) { } } +func TestDaemonReloadMirrors(t *testing.T) { + daemon := &Daemon{} + + daemon.RegistryService = registry.NewService(registry.ServiceOptions{ + InsecureRegistries: []string{}, + Mirrors: []string{ + "https://mirror.test1.com", + "https://mirror.test2.com", // this will be removed when reloading + "https://mirror.test3.com", // this will be removed when reloading + }, + }) + + daemon.configStore = &Config{} + + type pair struct { + valid bool + mirrors []string + after []string + } + + loadMirrors := []pair{ + { + valid: false, + mirrors: []string{"10.10.1.11:5000"}, // this mirror is invalid + after: []string{}, + }, + { + valid: false, + mirrors: []string{"mirror.test1.com"}, // this mirror is invalid + after: []string{}, + }, + { + valid: false, + mirrors: []string{"10.10.1.11:5000", "mirror.test1.com"}, // mirrors are invalid + after: []string{}, + }, + { + valid: true, + mirrors: []string{"https://mirror.test1.com", "https://mirror.test4.com"}, + after: []string{"https://mirror.test1.com/", "https://mirror.test4.com/"}, + }, + } + + for _, value := range loadMirrors { + valuesSets := make(map[string]interface{}) + valuesSets["registry-mirrors"] = value.mirrors + + newConfig := &Config{ + CommonConfig: CommonConfig{ + ServiceOptions: registry.ServiceOptions{ + Mirrors: value.mirrors, + }, + valuesSet: valuesSets, + }, + } + + err := daemon.Reload(newConfig) + if !value.valid && err == nil { + // mirrors should be invalid, should be a non-nil error + t.Fatalf("Expected daemon reload error with invalid mirrors: %s, while get nil", value.mirrors) + } + + if value.valid { + if err != nil { + // mirrors should be valid, should be no error + t.Fatal(err) + } + registryService := daemon.RegistryService.ServiceConfig() + + if len(registryService.Mirrors) != len(value.after) { + t.Fatalf("Expected %d daemon mirrors %s while get %d with %s", + len(value.after), + value.after, + len(registryService.Mirrors), + registryService.Mirrors) + } + + dataMap := map[string]struct{}{} + + for _, mirror := range registryService.Mirrors { + if _, exist := dataMap[mirror]; !exist { + dataMap[mirror] = struct{}{} + } + } + + for _, address := range value.after { + if _, exist := dataMap[address]; !exist { + t.Fatalf("Expected %s in daemon mirrors, while get none", address) + } + } + } + } +} + func TestDaemonReloadInsecureRegistries(t *testing.T) { daemon := &Daemon{} // initialize daemon with existing insecure registries: "127.0.0.0/8", "10.10.1.11:5000", "10.10.1.22:5000" diff --git a/docs/reference/commandline/dockerd.md b/docs/reference/commandline/dockerd.md index 8d4423e3a4..58edac5ad2 100644 --- a/docs/reference/commandline/dockerd.md +++ b/docs/reference/commandline/dockerd.md @@ -1289,6 +1289,7 @@ The list of currently supported options that can be reconfigured is this: be used to run containers - `authorization-plugin`: specifies the authorization plugins to use. - `insecure-registries`: it replaces the daemon insecure registries with a new set of insecure registries. If some existing insecure registries in daemon's configuration are not in newly reloaded insecure resgitries, these existing ones will be removed from daemon's config. +- `registry-mirrors`: it replaces the daemon registry mirrors with a new set of registry mirrors. If some existing registry mirrors in daemon's configuration are not in newly reloaded registry mirrors, these existing ones will be removed from daemon's config. Updating and reloading the cluster configurations such as `--cluster-store`, `--cluster-advertise` and `--cluster-store-opts` will take effect only if diff --git a/integration-cli/docker_cli_events_unix_test.go b/integration-cli/docker_cli_events_unix_test.go index 87bc80a4e6..41bd35e3ff 100644 --- a/integration-cli/docker_cli_events_unix_test.go +++ b/integration-cli/docker_cli_events_unix_test.go @@ -429,7 +429,7 @@ func (s *DockerDaemonSuite) TestDaemonEvents(c *check.C) { out, err = s.d.Cmd("events", "--since=0", "--until", daemonUnixTime(c)) c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, fmt.Sprintf("daemon reload %s (cluster-advertise=, cluster-store=, cluster-store-opts={}, debug=true, default-runtime=runc, insecure-registries=[], labels=[\"bar=foo\"], live-restore=false, max-concurrent-downloads=1, max-concurrent-uploads=5, name=%s, runtimes=runc:{docker-runc []}, shutdown-timeout=10)", daemonID, daemonName)) + c.Assert(out, checker.Contains, fmt.Sprintf("daemon reload %s (cluster-advertise=, cluster-store=, cluster-store-opts={}, debug=true, default-runtime=runc, insecure-registries=[], labels=[\"bar=foo\"], live-restore=false, max-concurrent-downloads=1, max-concurrent-uploads=5, name=%s, registry-mirrors=[], runtimes=runc:{docker-runc []}, shutdown-timeout=10)", daemonID, daemonName)) } func (s *DockerDaemonSuite) TestDaemonEventsWithFilters(c *check.C) { diff --git a/registry/config.go b/registry/config.go index 9a4f6a9251..c839d87cf6 100644 --- a/registry/config.go +++ b/registry/config.go @@ -84,16 +84,46 @@ func newServiceConfig(options ServiceOptions) *serviceConfig { IndexConfigs: make(map[string]*registrytypes.IndexInfo, 0), // Hack: Bypass setting the mirrors to IndexConfigs since they are going away // and Mirrors are only for the official registry anyways. - Mirrors: options.Mirrors, }, V2Only: options.V2Only, } + config.LoadMirrors(options.Mirrors) config.LoadInsecureRegistries(options.InsecureRegistries) return config } +// LoadMirrors loads mirrors to config, after removing duplicates. +// Returns an error if mirrors contains an invalid mirror. +func (config *serviceConfig) LoadMirrors(mirrors []string) error { + mMap := map[string]struct{}{} + unique := []string{} + + for _, mirror := range mirrors { + m, err := ValidateMirror(mirror) + if err != nil { + return err + } + if _, exist := mMap[m]; !exist { + mMap[m] = struct{}{} + unique = append(unique, m) + } + } + + config.Mirrors = unique + + // Configure public registry since mirrors may have changed. + config.IndexConfigs[IndexName] = ®istrytypes.IndexInfo{ + Name: IndexName, + Mirrors: config.Mirrors, + Secure: true, + Official: true, + } + + return nil +} + // LoadInsecureRegistries loads insecure registries to config func (config *serviceConfig) LoadInsecureRegistries(registries []string) error { // Localhost is by default considered as an insecure registry @@ -208,18 +238,20 @@ func isSecureIndex(config *serviceConfig, indexName string) bool { func ValidateMirror(val string) (string, error) { uri, err := url.Parse(val) if err != nil { - return "", fmt.Errorf("%s is not a valid URI", val) + return "", fmt.Errorf("invalid mirror: %q is not a valid URI", val) } - if uri.Scheme != "http" && uri.Scheme != "https" { - return "", fmt.Errorf("Unsupported scheme %s", uri.Scheme) + return "", fmt.Errorf("invalid mirror: unsupported scheme %q in %q", uri.Scheme, uri) } - - if uri.Path != "" || uri.RawQuery != "" || uri.Fragment != "" { - return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI") + if (uri.Path != "" && uri.Path != "/") || uri.RawQuery != "" || uri.Fragment != "" { + return "", fmt.Errorf("invalid mirror: path, query, or fragment at end of the URI %q", uri) } - - return fmt.Sprintf("%s://%s/", uri.Scheme, uri.Host), nil + if uri.User != nil { + // strip password from output + uri.User = url.UserPassword(uri.User.Username(), "xxxxx") + return "", fmt.Errorf("invalid mirror: username/password not allowed in URI %q", uri) + } + return strings.TrimSuffix(val, "/") + "/", nil } // ValidateIndexName validates an index name. diff --git a/registry/config_test.go b/registry/config_test.go index 25578a7f2b..0382530776 100644 --- a/registry/config_test.go +++ b/registry/config_test.go @@ -7,7 +7,9 @@ import ( func TestValidateMirror(t *testing.T) { valid := []string{ "http://mirror-1.com", + "http://mirror-1.com/", "https://mirror-1.com", + "https://mirror-1.com/", "http://localhost", "https://localhost", "http://localhost:5000", @@ -21,15 +23,14 @@ func TestValidateMirror(t *testing.T) { invalid := []string{ "!invalid!://%as%", "ftp://mirror-1.com", - "http://mirror-1.com/", "http://mirror-1.com/?q=foo", "http://mirror-1.com/v1/", "http://mirror-1.com/v1/?q=foo", "http://mirror-1.com/v1/?q=foo#frag", "http://mirror-1.com?q=foo", "https://mirror-1.com#frag", - "https://mirror-1.com/", "https://mirror-1.com/#frag", + "http://foo:bar@mirror-1.com/", "https://mirror-1.com/v1/", "https://mirror-1.com/v1/#", "https://mirror-1.com?q", diff --git a/registry/registry_test.go b/registry/registry_test.go index 786dfbed40..a4e5312142 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -663,7 +663,7 @@ func TestMirrorEndpointLookup(t *testing.T) { } return false } - s := DefaultService{config: makeServiceConfig([]string{"my.mirror"}, nil)} + s := DefaultService{config: makeServiceConfig([]string{"https://my.mirror"}, nil)} imageName, err := reference.WithName(IndexName + "/test/image") if err != nil { diff --git a/registry/service.go b/registry/service.go index 596a9c7e5f..af74cbad26 100644 --- a/registry/service.go +++ b/registry/service.go @@ -31,6 +31,7 @@ type Service interface { Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) ServiceConfig() *registrytypes.ServiceConfig TLSConfig(hostname string) (*tls.Config, error) + LoadMirrors([]string) error LoadInsecureRegistries([]string) error } @@ -73,6 +74,14 @@ func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig { return &servConfig } +// LoadMirrors loads registry mirrors for Service +func (s *DefaultService) LoadMirrors(mirrors []string) error { + s.mu.Lock() + defer s.mu.Unlock() + + return s.config.LoadMirrors(mirrors) +} + // LoadInsecureRegistries loads insecure registries for Service func (s *DefaultService) LoadInsecureRegistries(registries []string) error { s.mu.Lock()