Explorar el Código

support registry mirror config reload

Signed-off-by: allencloud <allen.sun@daocloud.io>
allencloud hace 8 años
padre
commit
5b9348c553

+ 24 - 5
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)

+ 94 - 0
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"

+ 1 - 0
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

+ 1 - 1
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) {

+ 41 - 9
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] = &registrytypes.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.

+ 3 - 2
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",

+ 1 - 1
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 {

+ 9 - 0
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()