Merge pull request #30303 from allencloud/rewrite-reload

refactor config reload in daemon
This commit is contained in:
Victor Vieux 2017-02-15 09:40:55 -08:00 committed by GitHub
commit ee560bd748
7 changed files with 714 additions and 632 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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

View File

@ -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
daemon/reload.go Normal file
View File

@ -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
daemon/reload_test.go Normal file
View File

@ -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)
}
}