diff --git a/daemon/daemon.go b/daemon/daemon.go index 832320b2e5..74c342cd78 100644 --- a/daemon/daemon.go +++ b/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 } diff --git a/daemon/daemon_solaris.go b/daemon/daemon_solaris.go index 0f514e4305..69b4c459d7 100644 --- a/daemon/daemon_solaris.go +++ b/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 diff --git a/daemon/daemon_test.go b/daemon/daemon_test.go index 8d8d5c6db8..eaa3be44d8 100644 --- a/daemon/daemon_test.go +++ b/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) - } - -} diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 1bb543f9e7..06f6f38415 100644 --- a/daemon/daemon_unix.go +++ b/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 diff --git a/daemon/daemon_windows.go b/daemon/daemon_windows.go index 79768eb74b..93154af1b2 100644 --- a/daemon/daemon_windows.go +++ b/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 diff --git a/daemon/reload.go b/daemon/reload.go new file mode 100644 index 0000000000..deb13f54bf --- /dev/null +++ b/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 +} diff --git a/daemon/reload_test.go b/daemon/reload_test.go new file mode 100644 index 0000000000..f338c1c8fc --- /dev/null +++ b/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) + } +}