瀏覽代碼

rewrite reload code

Signed-off-by: allencloud <allen.sun@daocloud.io>
allencloud 8 年之前
父節點
當前提交
75f5d63ec8
共有 7 個文件被更改,包括 714 次插入632 次删除
  1. 0 208
      daemon/daemon.go
  2. 3 3
      daemon/daemon_solaris.go
  3. 0 411
      daemon/daemon_test.go
  4. 6 7
      daemon/daemon_unix.go
  5. 3 3
      daemon/daemon_windows.go
  6. 284 0
      daemon/reload.go
  7. 418 0
      daemon/reload_test.go

+ 0 - 208
daemon/daemon.go

@@ -6,7 +6,6 @@
 package daemon
 
 import (
-	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"net"
@@ -996,213 +995,6 @@ func (daemon *Daemon) initDiscovery(conf *config.Config) error {
 	return nil
 }
 
-// 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
-// - Insecure registries
-// - Registry mirrors
-// - Daemon max concurrent downloads
-// - Daemon max concurrent uploads
-// - Cluster discovery (reconfigure and restart)
-// - Daemon live restore
-// - Daemon shutdown timeout (in seconds).
-func (daemon *Daemon) Reload(conf *config.Config) (err error) {
-
-	daemon.configStore.Lock()
-
-	attributes := daemon.platformReload(conf)
-
-	defer func() {
-		// we're unlocking here, because
-		// LogDaemonEventWithAttributes() -> SystemInfo() -> GetAllRuntimes()
-		// holds that lock too.
-		daemon.configStore.Unlock()
-		if err == nil {
-			daemon.LogDaemonEventWithAttributes("reload", attributes)
-		}
-	}()
-
-	if err := daemon.reloadClusterDiscovery(conf); err != nil {
-		return err
-	}
-
-	if conf.IsValueSet("labels") {
-		daemon.configStore.Labels = conf.Labels
-	}
-	if conf.IsValueSet("debug") {
-		daemon.configStore.Debug = conf.Debug
-	}
-	if conf.IsValueSet("insecure-registries") {
-		daemon.configStore.InsecureRegistries = conf.InsecureRegistries
-		if err := daemon.RegistryService.LoadInsecureRegistries(conf.InsecureRegistries); err != nil {
-			return err
-		}
-	}
-
-	if conf.IsValueSet("registry-mirrors") {
-		daemon.configStore.Mirrors = conf.Mirrors
-		if err := daemon.RegistryService.LoadMirrors(conf.Mirrors); err != nil {
-			return err
-		}
-	}
-
-	if conf.IsValueSet("live-restore") {
-		daemon.configStore.LiveRestoreEnabled = conf.LiveRestoreEnabled
-		if err := daemon.containerdRemote.UpdateOptions(libcontainerd.WithLiveRestore(conf.LiveRestoreEnabled)); err != nil {
-			return err
-		}
-	}
-
-	// If no value is set for max-concurrent-downloads we assume it is the default value
-	// We always "reset" as the cost is lightweight and easy to maintain.
-	if conf.IsValueSet("max-concurrent-downloads") && conf.MaxConcurrentDownloads != nil {
-		*daemon.configStore.MaxConcurrentDownloads = *conf.MaxConcurrentDownloads
-	} else {
-		maxConcurrentDownloads := config.DefaultMaxConcurrentDownloads
-		daemon.configStore.MaxConcurrentDownloads = &maxConcurrentDownloads
-	}
-	logrus.Debugf("Reset Max Concurrent Downloads: %d", *daemon.configStore.MaxConcurrentDownloads)
-	if daemon.downloadManager != nil {
-		daemon.downloadManager.SetConcurrency(*daemon.configStore.MaxConcurrentDownloads)
-	}
-
-	// If no value is set for max-concurrent-upload we assume it is the default value
-	// We always "reset" as the cost is lightweight and easy to maintain.
-	if conf.IsValueSet("max-concurrent-uploads") && conf.MaxConcurrentUploads != nil {
-		*daemon.configStore.MaxConcurrentUploads = *conf.MaxConcurrentUploads
-	} else {
-		maxConcurrentUploads := config.DefaultMaxConcurrentUploads
-		daemon.configStore.MaxConcurrentUploads = &maxConcurrentUploads
-	}
-	logrus.Debugf("Reset Max Concurrent Uploads: %d", *daemon.configStore.MaxConcurrentUploads)
-	if daemon.uploadManager != nil {
-		daemon.uploadManager.SetConcurrency(*daemon.configStore.MaxConcurrentUploads)
-	}
-
-	if conf.IsValueSet("shutdown-timeout") {
-		daemon.configStore.ShutdownTimeout = conf.ShutdownTimeout
-		logrus.Debugf("Reset Shutdown Timeout: %d", daemon.configStore.ShutdownTimeout)
-	}
-
-	// We emit daemon reload event here with updatable configurations
-	attributes["debug"] = fmt.Sprintf("%t", daemon.configStore.Debug)
-	attributes["live-restore"] = fmt.Sprintf("%t", daemon.configStore.LiveRestoreEnabled)
-
-	if daemon.configStore.InsecureRegistries != nil {
-		insecureRegistries, err := json.Marshal(daemon.configStore.InsecureRegistries)
-		if err != nil {
-			return err
-		}
-		attributes["insecure-registries"] = string(insecureRegistries)
-	} else {
-		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)
-		if err != nil {
-			return err
-		}
-		attributes["cluster-store-opts"] = string(opts)
-	} else {
-		attributes["cluster-store-opts"] = "{}"
-	}
-	attributes["cluster-advertise"] = daemon.configStore.ClusterAdvertise
-
-	if daemon.configStore.Labels != nil {
-		labels, err := json.Marshal(daemon.configStore.Labels)
-		if err != nil {
-			return err
-		}
-		attributes["labels"] = string(labels)
-	} else {
-		attributes["labels"] = "[]"
-	}
-
-	attributes["max-concurrent-downloads"] = fmt.Sprintf("%d", *daemon.configStore.MaxConcurrentDownloads)
-	attributes["max-concurrent-uploads"] = fmt.Sprintf("%d", *daemon.configStore.MaxConcurrentUploads)
-	attributes["shutdown-timeout"] = fmt.Sprintf("%d", daemon.configStore.ShutdownTimeout)
-
-	return nil
-}
-
-func (daemon *Daemon) reloadClusterDiscovery(conf *config.Config) error {
-	var err error
-	newAdvertise := daemon.configStore.ClusterAdvertise
-	newClusterStore := daemon.configStore.ClusterStore
-	if conf.IsValueSet("cluster-advertise") {
-		if conf.IsValueSet("cluster-store") {
-			newClusterStore = conf.ClusterStore
-		}
-		newAdvertise, err = config.ParseClusterAdvertiseSettings(newClusterStore, conf.ClusterAdvertise)
-		if err != nil && err != discovery.ErrDiscoveryDisabled {
-			return err
-		}
-	}
-
-	if daemon.clusterProvider != nil {
-		if err := conf.IsSwarmCompatible(); err != nil {
-			return err
-		}
-	}
-
-	// check discovery modifications
-	if !config.ModifiedDiscoverySettings(daemon.configStore, newClusterStore, newAdvertise, conf.ClusterOpts) {
-		return nil
-	}
-
-	// enable discovery for the first time if it was not previously enabled
-	if daemon.discoveryWatcher == nil {
-		discoveryWatcher, err := discovery.Init(newClusterStore, newAdvertise, conf.ClusterOpts)
-		if err != nil {
-			return fmt.Errorf("discovery initialization failed (%v)", err)
-		}
-		daemon.discoveryWatcher = discoveryWatcher
-	} else {
-		if err == discovery.ErrDiscoveryDisabled {
-			// disable discovery if it was previously enabled and it's disabled now
-			daemon.discoveryWatcher.Stop()
-		} else {
-			// reload discovery
-			if err = daemon.discoveryWatcher.Reload(conf.ClusterStore, newAdvertise, conf.ClusterOpts); err != nil {
-				return err
-			}
-		}
-	}
-
-	daemon.configStore.ClusterStore = newClusterStore
-	daemon.configStore.ClusterOpts = conf.ClusterOpts
-	daemon.configStore.ClusterAdvertise = newAdvertise
-
-	if daemon.netController == nil {
-		return nil
-	}
-	netOptions, err := daemon.networkOptions(daemon.configStore, daemon.PluginStore, nil)
-	if err != nil {
-		logrus.WithError(err).Warnf("failed to get options with network controller")
-		return nil
-	}
-	err = daemon.netController.ReloadConfiguration(netOptions...)
-	if err != nil {
-		logrus.Warnf("Failed to reload configuration with network controller: %v", err)
-	}
-
-	return nil
-}
-
 func isBridgeNetworkDisabled(conf *config.Config) bool {
 	return conf.BridgeConfig.Iface == config.DisableNetworkBridge
 }

+ 3 - 3
daemon/daemon_solaris.go

@@ -301,9 +301,9 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
 	return warnings, nil
 }
 
-// platformReload updates configuration with platform specific options
-func (daemon *Daemon) platformReload(config *Config) map[string]string {
-	return map[string]string{}
+// reloadPlatform updates configuration with platform specific options
+// and updates the passed attributes
+func (daemon *Daemon) reloadPlatform(config *Config, attributes map[string]string) {
 }
 
 // verifyDaemonSettings performs validation of daemon config struct

+ 0 - 411
daemon/daemon_test.go

@@ -6,18 +6,13 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"reflect"
 	"testing"
-	"time"
 
 	containertypes "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/container"
-	"github.com/docker/docker/daemon/config"
-	"github.com/docker/docker/pkg/discovery"
 	_ "github.com/docker/docker/pkg/discovery/memory"
 	"github.com/docker/docker/pkg/registrar"
 	"github.com/docker/docker/pkg/truncindex"
-	"github.com/docker/docker/registry"
 	"github.com/docker/docker/volume"
 	volumedrivers "github.com/docker/docker/volume/drivers"
 	"github.com/docker/docker/volume/local"
@@ -314,409 +309,3 @@ func TestMerge(t *testing.T) {
 		}
 	}
 }
-
-func TestDaemonReloadLabels(t *testing.T) {
-	daemon := &Daemon{}
-	daemon.configStore = &config.Config{
-		CommonConfig: config.CommonConfig{
-			Labels: []string{"foo:bar"},
-		},
-	}
-
-	valuesSets := make(map[string]interface{})
-	valuesSets["labels"] = "foo:baz"
-	newConfig := &config.Config{
-		CommonConfig: config.CommonConfig{
-			Labels:    []string{"foo:baz"},
-			ValuesSet: valuesSets,
-		},
-	}
-
-	if err := daemon.Reload(newConfig); err != nil {
-		t.Fatal(err)
-	}
-
-	label := daemon.configStore.Labels[0]
-	if label != "foo:baz" {
-		t.Fatalf("Expected daemon label `foo:baz`, got %s", label)
-	}
-}
-
-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.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.Config{
-			CommonConfig: config.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"
-	daemon.RegistryService = registry.NewService(registry.ServiceOptions{
-		InsecureRegistries: []string{
-			"127.0.0.0/8",
-			"10.10.1.11:5000",
-			"10.10.1.22:5000", // this will be removed when reloading
-			"docker1.com",
-			"docker2.com", // this will be removed when reloading
-		},
-	})
-
-	daemon.configStore = &config.Config{}
-
-	insecureRegistries := []string{
-		"127.0.0.0/8",     // this will be kept
-		"10.10.1.11:5000", // this will be kept
-		"10.10.1.33:5000", // this will be newly added
-		"docker1.com",     // this will be kept
-		"docker3.com",     // this will be newly added
-	}
-
-	valuesSets := make(map[string]interface{})
-	valuesSets["insecure-registries"] = insecureRegistries
-
-	newConfig := &config.Config{
-		CommonConfig: config.CommonConfig{
-			ServiceOptions: registry.ServiceOptions{
-				InsecureRegistries: insecureRegistries,
-			},
-			ValuesSet: valuesSets,
-		},
-	}
-
-	if err := daemon.Reload(newConfig); err != nil {
-		t.Fatal(err)
-	}
-
-	// After Reload, daemon.RegistryService will be changed which is useful
-	// for registry communication in daemon.
-	registries := daemon.RegistryService.ServiceConfig()
-
-	// After Reload(), newConfig has come to registries.InsecureRegistryCIDRs and registries.IndexConfigs in daemon.
-	// Then collect registries.InsecureRegistryCIDRs in dataMap.
-	// When collecting, we need to convert CIDRS into string as a key,
-	// while the times of key appears as value.
-	dataMap := map[string]int{}
-	for _, value := range registries.InsecureRegistryCIDRs {
-		if _, ok := dataMap[value.String()]; !ok {
-			dataMap[value.String()] = 1
-		} else {
-			dataMap[value.String()]++
-		}
-	}
-
-	for _, value := range registries.IndexConfigs {
-		if _, ok := dataMap[value.Name]; !ok {
-			dataMap[value.Name] = 1
-		} else {
-			dataMap[value.Name]++
-		}
-	}
-
-	// Finally compare dataMap with the original insecureRegistries.
-	// Each value in insecureRegistries should appear in daemon's insecure registries,
-	// and each can only appear exactly ONCE.
-	for _, r := range insecureRegistries {
-		if value, ok := dataMap[r]; !ok {
-			t.Fatalf("Expected daemon insecure registry %s, got none", r)
-		} else if value != 1 {
-			t.Fatalf("Expected only 1 daemon insecure registry %s, got %d", r, value)
-		}
-	}
-
-	// assert if "10.10.1.22:5000" is removed when reloading
-	if value, ok := dataMap["10.10.1.22:5000"]; ok {
-		t.Fatalf("Expected no insecure registry of 10.10.1.22:5000, got %d", value)
-	}
-
-	// assert if "docker2.com" is removed when reloading
-	if value, ok := dataMap["docker2.com"]; ok {
-		t.Fatalf("Expected no insecure registry of docker2.com, got %d", value)
-	}
-}
-
-func TestDaemonReloadNotAffectOthers(t *testing.T) {
-	daemon := &Daemon{}
-	daemon.configStore = &config.Config{
-		CommonConfig: config.CommonConfig{
-			Labels: []string{"foo:bar"},
-			Debug:  true,
-		},
-	}
-
-	valuesSets := make(map[string]interface{})
-	valuesSets["labels"] = "foo:baz"
-	newConfig := &config.Config{
-		CommonConfig: config.CommonConfig{
-			Labels:    []string{"foo:baz"},
-			ValuesSet: valuesSets,
-		},
-	}
-
-	if err := daemon.Reload(newConfig); err != nil {
-		t.Fatal(err)
-	}
-
-	label := daemon.configStore.Labels[0]
-	if label != "foo:baz" {
-		t.Fatalf("Expected daemon label `foo:baz`, got %s", label)
-	}
-	debug := daemon.configStore.Debug
-	if !debug {
-		t.Fatalf("Expected debug 'enabled', got 'disabled'")
-	}
-}
-
-func TestDaemonDiscoveryReload(t *testing.T) {
-	daemon := &Daemon{}
-	daemon.configStore = &config.Config{
-		CommonConfig: config.CommonConfig{
-			ClusterStore:     "memory://127.0.0.1",
-			ClusterAdvertise: "127.0.0.1:3333",
-		},
-	}
-
-	if err := daemon.initDiscovery(daemon.configStore); err != nil {
-		t.Fatal(err)
-	}
-
-	expected := discovery.Entries{
-		&discovery.Entry{Host: "127.0.0.1", Port: "3333"},
-	}
-
-	select {
-	case <-time.After(10 * time.Second):
-		t.Fatal("timeout waiting for discovery")
-	case <-daemon.discoveryWatcher.ReadyCh():
-	}
-
-	stopCh := make(chan struct{})
-	defer close(stopCh)
-	ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
-
-	select {
-	case <-time.After(1 * time.Second):
-		t.Fatal("failed to get discovery advertisements in time")
-	case e := <-ch:
-		if !reflect.DeepEqual(e, expected) {
-			t.Fatalf("expected %v, got %v\n", expected, e)
-		}
-	case e := <-errCh:
-		t.Fatal(e)
-	}
-
-	valuesSets := make(map[string]interface{})
-	valuesSets["cluster-store"] = "memory://127.0.0.1:2222"
-	valuesSets["cluster-advertise"] = "127.0.0.1:5555"
-	newConfig := &config.Config{
-		CommonConfig: config.CommonConfig{
-			ClusterStore:     "memory://127.0.0.1:2222",
-			ClusterAdvertise: "127.0.0.1:5555",
-			ValuesSet:        valuesSets,
-		},
-	}
-
-	expected = discovery.Entries{
-		&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
-	}
-
-	if err := daemon.Reload(newConfig); err != nil {
-		t.Fatal(err)
-	}
-
-	select {
-	case <-time.After(10 * time.Second):
-		t.Fatal("timeout waiting for discovery")
-	case <-daemon.discoveryWatcher.ReadyCh():
-	}
-
-	ch, errCh = daemon.discoveryWatcher.Watch(stopCh)
-
-	select {
-	case <-time.After(1 * time.Second):
-		t.Fatal("failed to get discovery advertisements in time")
-	case e := <-ch:
-		if !reflect.DeepEqual(e, expected) {
-			t.Fatalf("expected %v, got %v\n", expected, e)
-		}
-	case e := <-errCh:
-		t.Fatal(e)
-	}
-}
-
-func TestDaemonDiscoveryReloadFromEmptyDiscovery(t *testing.T) {
-	daemon := &Daemon{}
-	daemon.configStore = &config.Config{}
-
-	valuesSet := make(map[string]interface{})
-	valuesSet["cluster-store"] = "memory://127.0.0.1:2222"
-	valuesSet["cluster-advertise"] = "127.0.0.1:5555"
-	newConfig := &config.Config{
-		CommonConfig: config.CommonConfig{
-			ClusterStore:     "memory://127.0.0.1:2222",
-			ClusterAdvertise: "127.0.0.1:5555",
-			ValuesSet:        valuesSet,
-		},
-	}
-
-	expected := discovery.Entries{
-		&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
-	}
-
-	if err := daemon.Reload(newConfig); err != nil {
-		t.Fatal(err)
-	}
-
-	select {
-	case <-time.After(10 * time.Second):
-		t.Fatal("timeout waiting for discovery")
-	case <-daemon.discoveryWatcher.ReadyCh():
-	}
-
-	stopCh := make(chan struct{})
-	defer close(stopCh)
-	ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
-
-	select {
-	case <-time.After(1 * time.Second):
-		t.Fatal("failed to get discovery advertisements in time")
-	case e := <-ch:
-		if !reflect.DeepEqual(e, expected) {
-			t.Fatalf("expected %v, got %v\n", expected, e)
-		}
-	case e := <-errCh:
-		t.Fatal(e)
-	}
-}
-
-func TestDaemonDiscoveryReloadOnlyClusterAdvertise(t *testing.T) {
-	daemon := &Daemon{}
-	daemon.configStore = &config.Config{
-		CommonConfig: config.CommonConfig{
-			ClusterStore: "memory://127.0.0.1",
-		},
-	}
-	valuesSets := make(map[string]interface{})
-	valuesSets["cluster-advertise"] = "127.0.0.1:5555"
-	newConfig := &config.Config{
-		CommonConfig: config.CommonConfig{
-			ClusterAdvertise: "127.0.0.1:5555",
-			ValuesSet:        valuesSets,
-		},
-	}
-	expected := discovery.Entries{
-		&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
-	}
-
-	if err := daemon.Reload(newConfig); err != nil {
-		t.Fatal(err)
-	}
-
-	select {
-	case <-daemon.discoveryWatcher.ReadyCh():
-	case <-time.After(10 * time.Second):
-		t.Fatal("Timeout waiting for discovery")
-	}
-	stopCh := make(chan struct{})
-	defer close(stopCh)
-	ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
-
-	select {
-	case <-time.After(1 * time.Second):
-		t.Fatal("failed to get discovery advertisements in time")
-	case e := <-ch:
-		if !reflect.DeepEqual(e, expected) {
-			t.Fatalf("expected %v, got %v\n", expected, e)
-		}
-	case e := <-errCh:
-		t.Fatal(e)
-	}
-
-}

+ 6 - 7
daemon/daemon_unix.go

@@ -568,8 +568,9 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
 	return warnings, nil
 }
 
-// platformReload updates configuration with platform specific options
-func (daemon *Daemon) platformReload(conf *config.Config) map[string]string {
+// reloadPlatform updates configuration with platform specific options
+// and updates the passed attributes
+func (daemon *Daemon) reloadPlatform(conf *config.Config, attributes map[string]string) {
 	if conf.IsValueSet("runtimes") {
 		daemon.configStore.Runtimes = conf.Runtimes
 		// Always set the default one
@@ -593,11 +594,9 @@ func (daemon *Daemon) platformReload(conf *config.Config) map[string]string {
 		runtimeList.WriteString(fmt.Sprintf("%s:%s", name, rt))
 	}
 
-	return map[string]string{
-		"runtimes":         runtimeList.String(),
-		"default-runtime":  daemon.configStore.DefaultRuntime,
-		"default-shm-size": fmt.Sprintf("%d", daemon.configStore.ShmSize),
-	}
+	attributes["runtimes"] = runtimeList.String()
+	attributes["default-runtime"] = daemon.configStore.DefaultRuntime
+	attributes["default-shm-size"] = fmt.Sprintf("%d", daemon.configStore.ShmSize)
 }
 
 // verifyDaemonSettings performs validation of daemon config struct

+ 3 - 3
daemon/daemon_windows.go

@@ -210,9 +210,9 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
 	return warnings, err
 }
 
-// platformReload updates configuration with platform specific options
-func (daemon *Daemon) platformReload(config *config.Config) map[string]string {
-	return map[string]string{}
+// reloadPlatform updates configuration with platform specific options
+// and updates the passed attributes
+func (daemon *Daemon) reloadPlatform(config *config.Config, attributes map[string]string) {
 }
 
 // verifyDaemonSettings performs validation of daemon config struct

+ 284 - 0
daemon/reload.go

@@ -0,0 +1,284 @@
+package daemon
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/daemon/config"
+	"github.com/docker/docker/daemon/discovery"
+	"github.com/docker/docker/libcontainerd"
+)
+
+// Reload reads configuration changes and modifies the
+// daemon according to those changes.
+// These are the settings that Reload changes:
+// - Platform runtime
+// - Daemon debug log level
+// - Daemon max concurrent downloads
+// - Daemon max concurrent uploads
+// - Daemon shutdown timeout (in seconds)
+// - Cluster discovery (reconfigure and restart)
+// - Daemon labels
+// - Insecure registries
+// - Registry mirrors
+// - Daemon live restore
+func (daemon *Daemon) Reload(conf *config.Config) (err error) {
+	daemon.configStore.Lock()
+	attributes := map[string]string{}
+
+	defer func() {
+		// we're unlocking here, because
+		// LogDaemonEventWithAttributes() -> SystemInfo() -> GetAllRuntimes()
+		// holds that lock too.
+		daemon.configStore.Unlock()
+		if err == nil {
+			daemon.LogDaemonEventWithAttributes("reload", attributes)
+		}
+	}()
+
+	daemon.reloadPlatform(conf, attributes)
+	daemon.reloadDebug(conf, attributes)
+	daemon.reloadMaxConcurrentDowloadsAndUploads(conf, attributes)
+	daemon.reloadShutdownTimeout(conf, attributes)
+
+	if err := daemon.reloadClusterDiscovery(conf, attributes); err != nil {
+		return err
+	}
+	if err := daemon.reloadLabels(conf, attributes); err != nil {
+		return err
+	}
+	if err := daemon.reloadInsecureRegistries(conf, attributes); err != nil {
+		return err
+	}
+	if err := daemon.reloadRegistryMirrors(conf, attributes); err != nil {
+		return err
+	}
+	if err := daemon.reloadLiveRestore(conf, attributes); err != nil {
+		return err
+	}
+	return nil
+}
+
+// reloadDebug updates configuration with Debug option
+// and updates the passed attributes
+func (daemon *Daemon) reloadDebug(conf *config.Config, attributes map[string]string) {
+	// update corresponding configuration
+	if conf.IsValueSet("debug") {
+		daemon.configStore.Debug = conf.Debug
+	}
+	// prepare reload event attributes with updatable configurations
+	attributes["debug"] = fmt.Sprintf("%t", daemon.configStore.Debug)
+}
+
+// reloadMaxConcurrentDowloadsAndUploads updates configuration with max concurrent
+// download and upload options and updates the passed attributes
+func (daemon *Daemon) reloadMaxConcurrentDowloadsAndUploads(conf *config.Config, attributes map[string]string) {
+	// If no value is set for max-concurrent-downloads we assume it is the default value
+	// We always "reset" as the cost is lightweight and easy to maintain.
+	if conf.IsValueSet("max-concurrent-downloads") && conf.MaxConcurrentDownloads != nil {
+		*daemon.configStore.MaxConcurrentDownloads = *conf.MaxConcurrentDownloads
+	} else {
+		maxConcurrentDownloads := config.DefaultMaxConcurrentDownloads
+		daemon.configStore.MaxConcurrentDownloads = &maxConcurrentDownloads
+	}
+	logrus.Debugf("Reset Max Concurrent Downloads: %d", *daemon.configStore.MaxConcurrentDownloads)
+	if daemon.downloadManager != nil {
+		daemon.downloadManager.SetConcurrency(*daemon.configStore.MaxConcurrentDownloads)
+	}
+
+	// prepare reload event attributes with updatable configurations
+	attributes["max-concurrent-downloads"] = fmt.Sprintf("%d", *daemon.configStore.MaxConcurrentDownloads)
+
+	// If no value is set for max-concurrent-upload we assume it is the default value
+	// We always "reset" as the cost is lightweight and easy to maintain.
+	if conf.IsValueSet("max-concurrent-uploads") && conf.MaxConcurrentUploads != nil {
+		*daemon.configStore.MaxConcurrentUploads = *conf.MaxConcurrentUploads
+	} else {
+		maxConcurrentUploads := config.DefaultMaxConcurrentUploads
+		daemon.configStore.MaxConcurrentUploads = &maxConcurrentUploads
+	}
+	logrus.Debugf("Reset Max Concurrent Uploads: %d", *daemon.configStore.MaxConcurrentUploads)
+	if daemon.uploadManager != nil {
+		daemon.uploadManager.SetConcurrency(*daemon.configStore.MaxConcurrentUploads)
+	}
+
+	// prepare reload event attributes with updatable configurations
+	attributes["max-concurrent-uploads"] = fmt.Sprintf("%d", *daemon.configStore.MaxConcurrentUploads)
+}
+
+// reloadShutdownTimeout updates configuration with daemon shutdown timeout option
+// and updates the passed attributes
+func (daemon *Daemon) reloadShutdownTimeout(conf *config.Config, attributes map[string]string) {
+	// update corresponding configuration
+	if conf.IsValueSet("shutdown-timeout") {
+		daemon.configStore.ShutdownTimeout = conf.ShutdownTimeout
+		logrus.Debugf("Reset Shutdown Timeout: %d", daemon.configStore.ShutdownTimeout)
+	}
+
+	// prepare reload event attributes with updatable configurations
+	attributes["shutdown-timeout"] = fmt.Sprintf("%d", daemon.configStore.ShutdownTimeout)
+}
+
+// reloadClusterDiscovery updates configuration with cluster discovery options
+// and updates the passed attributes
+func (daemon *Daemon) reloadClusterDiscovery(conf *config.Config, attributes map[string]string) (err error) {
+	defer func() {
+		// prepare reload event attributes with updatable configurations
+		attributes["cluster-store"] = conf.ClusterStore
+		attributes["cluster-advertise"] = conf.ClusterAdvertise
+
+		attributes["cluster-store-opts"] = "{}"
+		if daemon.configStore.ClusterOpts != nil {
+			opts, err2 := json.Marshal(conf.ClusterOpts)
+			if err != nil {
+				err = err2
+			}
+			attributes["cluster-store-opts"] = string(opts)
+		}
+	}()
+
+	newAdvertise := conf.ClusterAdvertise
+	newClusterStore := daemon.configStore.ClusterStore
+	if conf.IsValueSet("cluster-advertise") {
+		if conf.IsValueSet("cluster-store") {
+			newClusterStore = conf.ClusterStore
+		}
+		newAdvertise, err = config.ParseClusterAdvertiseSettings(newClusterStore, conf.ClusterAdvertise)
+		if err != nil && err != discovery.ErrDiscoveryDisabled {
+			return err
+		}
+	}
+
+	if daemon.clusterProvider != nil {
+		if err := conf.IsSwarmCompatible(); err != nil {
+			return err
+		}
+	}
+
+	// check discovery modifications
+	if !config.ModifiedDiscoverySettings(daemon.configStore, newClusterStore, newAdvertise, conf.ClusterOpts) {
+		return nil
+	}
+
+	// enable discovery for the first time if it was not previously enabled
+	if daemon.discoveryWatcher == nil {
+		discoveryWatcher, err := discovery.Init(newClusterStore, newAdvertise, conf.ClusterOpts)
+		if err != nil {
+			return fmt.Errorf("failed to initialize discovery: %v", err)
+		}
+		daemon.discoveryWatcher = discoveryWatcher
+	} else if err == discovery.ErrDiscoveryDisabled {
+		// disable discovery if it was previously enabled and it's disabled now
+		daemon.discoveryWatcher.Stop()
+	} else if err = daemon.discoveryWatcher.Reload(conf.ClusterStore, newAdvertise, conf.ClusterOpts); err != nil {
+		// reload discovery
+		return err
+	}
+
+	daemon.configStore.ClusterStore = newClusterStore
+	daemon.configStore.ClusterOpts = conf.ClusterOpts
+	daemon.configStore.ClusterAdvertise = newAdvertise
+
+	if daemon.netController == nil {
+		return nil
+	}
+	netOptions, err := daemon.networkOptions(daemon.configStore, daemon.PluginStore, nil)
+	if err != nil {
+		logrus.WithError(err).Warnf("failed to get options with network controller")
+		return nil
+	}
+	err = daemon.netController.ReloadConfiguration(netOptions...)
+	if err != nil {
+		logrus.Warnf("Failed to reload configuration with network controller: %v", err)
+	}
+	return nil
+}
+
+// reloadLabels updates configuration with engine labels
+// and updates the passed attributes
+func (daemon *Daemon) reloadLabels(conf *config.Config, attributes map[string]string) error {
+	// update corresponding configuration
+	if conf.IsValueSet("labels") {
+		daemon.configStore.Labels = conf.Labels
+	}
+
+	// prepare reload event attributes with updatable configurations
+	if daemon.configStore.Labels != nil {
+		labels, err := json.Marshal(daemon.configStore.Labels)
+		if err != nil {
+			return err
+		}
+		attributes["labels"] = string(labels)
+	} else {
+		attributes["labels"] = "[]"
+	}
+
+	return nil
+}
+
+// reloadInsecureRegistries updates configuration with insecure registry option
+// and updates the passed attributes
+func (daemon *Daemon) reloadInsecureRegistries(conf *config.Config, attributes map[string]string) error {
+	// update corresponding configuration
+	if conf.IsValueSet("insecure-registries") {
+		daemon.configStore.InsecureRegistries = conf.InsecureRegistries
+		if err := daemon.RegistryService.LoadInsecureRegistries(conf.InsecureRegistries); err != nil {
+			return err
+		}
+	}
+
+	// prepare reload event attributes with updatable configurations
+	if daemon.configStore.InsecureRegistries != nil {
+		insecureRegistries, err := json.Marshal(daemon.configStore.InsecureRegistries)
+		if err != nil {
+			return err
+		}
+		attributes["insecure-registries"] = string(insecureRegistries)
+	} else {
+		attributes["insecure-registries"] = "[]"
+	}
+
+	return nil
+}
+
+// reloadRegistryMirrors updates configuration with registry mirror options
+// and updates the passed attributes
+func (daemon *Daemon) reloadRegistryMirrors(conf *config.Config, attributes map[string]string) error {
+	// update corresponding configuration
+	if conf.IsValueSet("registry-mirrors") {
+		daemon.configStore.Mirrors = conf.Mirrors
+		if err := daemon.RegistryService.LoadMirrors(conf.Mirrors); err != nil {
+			return err
+		}
+	}
+
+	// prepare reload event attributes with updatable configurations
+	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"] = "[]"
+	}
+
+	return nil
+}
+
+// reloadLiveRestore updates configuration with live retore option
+// and updates the passed attributes
+func (daemon *Daemon) reloadLiveRestore(conf *config.Config, attributes map[string]string) error {
+	// update corresponding configuration
+	if conf.IsValueSet("live-restore") {
+		daemon.configStore.LiveRestoreEnabled = conf.LiveRestoreEnabled
+		if err := daemon.containerdRemote.UpdateOptions(libcontainerd.WithLiveRestore(conf.LiveRestoreEnabled)); err != nil {
+			return err
+		}
+	}
+
+	// prepare reload event attributes with updatable configurations
+	attributes["live-restore"] = fmt.Sprintf("%t", daemon.configStore.LiveRestoreEnabled)
+	return nil
+}

+ 418 - 0
daemon/reload_test.go

@@ -0,0 +1,418 @@
+// +build !solaris
+
+package daemon
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/docker/docker/daemon/config"
+	"github.com/docker/docker/pkg/discovery"
+	_ "github.com/docker/docker/pkg/discovery/memory"
+	"github.com/docker/docker/registry"
+)
+
+func TestDaemonReloadLabels(t *testing.T) {
+	daemon := &Daemon{}
+	daemon.configStore = &config.Config{
+		CommonConfig: config.CommonConfig{
+			Labels: []string{"foo:bar"},
+		},
+	}
+
+	valuesSets := make(map[string]interface{})
+	valuesSets["labels"] = "foo:baz"
+	newConfig := &config.Config{
+		CommonConfig: config.CommonConfig{
+			Labels:    []string{"foo:baz"},
+			ValuesSet: valuesSets,
+		},
+	}
+
+	if err := daemon.Reload(newConfig); err != nil {
+		t.Fatal(err)
+	}
+
+	label := daemon.configStore.Labels[0]
+	if label != "foo:baz" {
+		t.Fatalf("Expected daemon label `foo:baz`, got %s", label)
+	}
+}
+
+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.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.Config{
+			CommonConfig: config.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"
+	daemon.RegistryService = registry.NewService(registry.ServiceOptions{
+		InsecureRegistries: []string{
+			"127.0.0.0/8",
+			"10.10.1.11:5000",
+			"10.10.1.22:5000", // this will be removed when reloading
+			"docker1.com",
+			"docker2.com", // this will be removed when reloading
+		},
+	})
+
+	daemon.configStore = &config.Config{}
+
+	insecureRegistries := []string{
+		"127.0.0.0/8",     // this will be kept
+		"10.10.1.11:5000", // this will be kept
+		"10.10.1.33:5000", // this will be newly added
+		"docker1.com",     // this will be kept
+		"docker3.com",     // this will be newly added
+	}
+
+	valuesSets := make(map[string]interface{})
+	valuesSets["insecure-registries"] = insecureRegistries
+
+	newConfig := &config.Config{
+		CommonConfig: config.CommonConfig{
+			ServiceOptions: registry.ServiceOptions{
+				InsecureRegistries: insecureRegistries,
+			},
+			ValuesSet: valuesSets,
+		},
+	}
+
+	if err := daemon.Reload(newConfig); err != nil {
+		t.Fatal(err)
+	}
+
+	// After Reload, daemon.RegistryService will be changed which is useful
+	// for registry communication in daemon.
+	registries := daemon.RegistryService.ServiceConfig()
+
+	// After Reload(), newConfig has come to registries.InsecureRegistryCIDRs and registries.IndexConfigs in daemon.
+	// Then collect registries.InsecureRegistryCIDRs in dataMap.
+	// When collecting, we need to convert CIDRS into string as a key,
+	// while the times of key appears as value.
+	dataMap := map[string]int{}
+	for _, value := range registries.InsecureRegistryCIDRs {
+		if _, ok := dataMap[value.String()]; !ok {
+			dataMap[value.String()] = 1
+		} else {
+			dataMap[value.String()]++
+		}
+	}
+
+	for _, value := range registries.IndexConfigs {
+		if _, ok := dataMap[value.Name]; !ok {
+			dataMap[value.Name] = 1
+		} else {
+			dataMap[value.Name]++
+		}
+	}
+
+	// Finally compare dataMap with the original insecureRegistries.
+	// Each value in insecureRegistries should appear in daemon's insecure registries,
+	// and each can only appear exactly ONCE.
+	for _, r := range insecureRegistries {
+		if value, ok := dataMap[r]; !ok {
+			t.Fatalf("Expected daemon insecure registry %s, got none", r)
+		} else if value != 1 {
+			t.Fatalf("Expected only 1 daemon insecure registry %s, got %d", r, value)
+		}
+	}
+
+	// assert if "10.10.1.22:5000" is removed when reloading
+	if value, ok := dataMap["10.10.1.22:5000"]; ok {
+		t.Fatalf("Expected no insecure registry of 10.10.1.22:5000, got %d", value)
+	}
+
+	// assert if "docker2.com" is removed when reloading
+	if value, ok := dataMap["docker2.com"]; ok {
+		t.Fatalf("Expected no insecure registry of docker2.com, got %d", value)
+	}
+}
+
+func TestDaemonReloadNotAffectOthers(t *testing.T) {
+	daemon := &Daemon{}
+	daemon.configStore = &config.Config{
+		CommonConfig: config.CommonConfig{
+			Labels: []string{"foo:bar"},
+			Debug:  true,
+		},
+	}
+
+	valuesSets := make(map[string]interface{})
+	valuesSets["labels"] = "foo:baz"
+	newConfig := &config.Config{
+		CommonConfig: config.CommonConfig{
+			Labels:    []string{"foo:baz"},
+			ValuesSet: valuesSets,
+		},
+	}
+
+	if err := daemon.Reload(newConfig); err != nil {
+		t.Fatal(err)
+	}
+
+	label := daemon.configStore.Labels[0]
+	if label != "foo:baz" {
+		t.Fatalf("Expected daemon label `foo:baz`, got %s", label)
+	}
+	debug := daemon.configStore.Debug
+	if !debug {
+		t.Fatalf("Expected debug 'enabled', got 'disabled'")
+	}
+}
+
+func TestDaemonDiscoveryReload(t *testing.T) {
+	daemon := &Daemon{}
+	daemon.configStore = &config.Config{
+		CommonConfig: config.CommonConfig{
+			ClusterStore:     "memory://127.0.0.1",
+			ClusterAdvertise: "127.0.0.1:3333",
+		},
+	}
+
+	if err := daemon.initDiscovery(daemon.configStore); err != nil {
+		t.Fatal(err)
+	}
+
+	expected := discovery.Entries{
+		&discovery.Entry{Host: "127.0.0.1", Port: "3333"},
+	}
+
+	select {
+	case <-time.After(10 * time.Second):
+		t.Fatal("timeout waiting for discovery")
+	case <-daemon.discoveryWatcher.ReadyCh():
+	}
+
+	stopCh := make(chan struct{})
+	defer close(stopCh)
+	ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
+
+	select {
+	case <-time.After(1 * time.Second):
+		t.Fatal("failed to get discovery advertisements in time")
+	case e := <-ch:
+		if !reflect.DeepEqual(e, expected) {
+			t.Fatalf("expected %v, got %v\n", expected, e)
+		}
+	case e := <-errCh:
+		t.Fatal(e)
+	}
+
+	valuesSets := make(map[string]interface{})
+	valuesSets["cluster-store"] = "memory://127.0.0.1:2222"
+	valuesSets["cluster-advertise"] = "127.0.0.1:5555"
+	newConfig := &config.Config{
+		CommonConfig: config.CommonConfig{
+			ClusterStore:     "memory://127.0.0.1:2222",
+			ClusterAdvertise: "127.0.0.1:5555",
+			ValuesSet:        valuesSets,
+		},
+	}
+
+	expected = discovery.Entries{
+		&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
+	}
+
+	if err := daemon.Reload(newConfig); err != nil {
+		t.Fatal(err)
+	}
+
+	select {
+	case <-time.After(10 * time.Second):
+		t.Fatal("timeout waiting for discovery")
+	case <-daemon.discoveryWatcher.ReadyCh():
+	}
+
+	ch, errCh = daemon.discoveryWatcher.Watch(stopCh)
+
+	select {
+	case <-time.After(1 * time.Second):
+		t.Fatal("failed to get discovery advertisements in time")
+	case e := <-ch:
+		if !reflect.DeepEqual(e, expected) {
+			t.Fatalf("expected %v, got %v\n", expected, e)
+		}
+	case e := <-errCh:
+		t.Fatal(e)
+	}
+}
+
+func TestDaemonDiscoveryReloadFromEmptyDiscovery(t *testing.T) {
+	daemon := &Daemon{}
+	daemon.configStore = &config.Config{}
+
+	valuesSet := make(map[string]interface{})
+	valuesSet["cluster-store"] = "memory://127.0.0.1:2222"
+	valuesSet["cluster-advertise"] = "127.0.0.1:5555"
+	newConfig := &config.Config{
+		CommonConfig: config.CommonConfig{
+			ClusterStore:     "memory://127.0.0.1:2222",
+			ClusterAdvertise: "127.0.0.1:5555",
+			ValuesSet:        valuesSet,
+		},
+	}
+
+	expected := discovery.Entries{
+		&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
+	}
+
+	if err := daemon.Reload(newConfig); err != nil {
+		t.Fatal(err)
+	}
+
+	select {
+	case <-time.After(10 * time.Second):
+		t.Fatal("timeout waiting for discovery")
+	case <-daemon.discoveryWatcher.ReadyCh():
+	}
+
+	stopCh := make(chan struct{})
+	defer close(stopCh)
+	ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
+
+	select {
+	case <-time.After(1 * time.Second):
+		t.Fatal("failed to get discovery advertisements in time")
+	case e := <-ch:
+		if !reflect.DeepEqual(e, expected) {
+			t.Fatalf("expected %v, got %v\n", expected, e)
+		}
+	case e := <-errCh:
+		t.Fatal(e)
+	}
+}
+
+func TestDaemonDiscoveryReloadOnlyClusterAdvertise(t *testing.T) {
+	daemon := &Daemon{}
+	daemon.configStore = &config.Config{
+		CommonConfig: config.CommonConfig{
+			ClusterStore: "memory://127.0.0.1",
+		},
+	}
+	valuesSets := make(map[string]interface{})
+	valuesSets["cluster-advertise"] = "127.0.0.1:5555"
+	newConfig := &config.Config{
+		CommonConfig: config.CommonConfig{
+			ClusterAdvertise: "127.0.0.1:5555",
+			ValuesSet:        valuesSets,
+		},
+	}
+	expected := discovery.Entries{
+		&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
+	}
+
+	if err := daemon.Reload(newConfig); err != nil {
+		t.Fatal(err)
+	}
+
+	select {
+	case <-daemon.discoveryWatcher.ReadyCh():
+	case <-time.After(10 * time.Second):
+		t.Fatal("Timeout waiting for discovery")
+	}
+	stopCh := make(chan struct{})
+	defer close(stopCh)
+	ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
+
+	select {
+	case <-time.After(1 * time.Second):
+		t.Fatal("failed to get discovery advertisements in time")
+	case e := <-ch:
+		if !reflect.DeepEqual(e, expected) {
+			t.Fatalf("expected %v, got %v\n", expected, e)
+		}
+	case e := <-errCh:
+		t.Fatal(e)
+	}
+}