package daemon // import "github.com/docker/docker/daemon" import ( "os" "reflect" "sort" "testing" "time" "github.com/docker/docker/daemon/config" "github.com/docker/docker/daemon/images" "github.com/docker/docker/pkg/discovery" _ "github.com/docker/docker/pkg/discovery/memory" "github.com/docker/docker/registry" "github.com/docker/libnetwork" "github.com/sirupsen/logrus" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) // muteLogs suppresses logs that are generated during the test func muteLogs() { logrus.SetLevel(logrus.ErrorLevel) } func TestDaemonReloadLabels(t *testing.T) { daemon := &Daemon{ configStore: &config.Config{ CommonConfig: config.CommonConfig{ Labels: []string{"foo:bar"}, }, }, imageService: images.NewImageService(images.ImageServiceConfig{}), } muteLogs() 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 TestDaemonReloadAllowNondistributableArtifacts(t *testing.T) { daemon := &Daemon{ configStore: &config.Config{}, imageService: images.NewImageService(images.ImageServiceConfig{}), } muteLogs() var err error // Initialize daemon with some registries. daemon.RegistryService, err = registry.NewService(registry.ServiceOptions{ AllowNondistributableArtifacts: []string{ "127.0.0.0/8", "10.10.1.11:5000", "10.10.1.22:5000", // This will be removed during reload. "docker1.com", "docker2.com", // This will be removed during reload. }, }) if err != nil { t.Fatal(err) } registries := []string{ "127.0.0.0/8", "10.10.1.11:5000", "10.10.1.33:5000", // This will be added during reload. "docker1.com", "docker3.com", // This will be added during reload. } newConfig := &config.Config{ CommonConfig: config.CommonConfig{ ServiceOptions: registry.ServiceOptions{ AllowNondistributableArtifacts: registries, }, ValuesSet: map[string]interface{}{ "allow-nondistributable-artifacts": registries, }, }, } if err := daemon.Reload(newConfig); err != nil { t.Fatal(err) } var actual []string serviceConfig := daemon.RegistryService.ServiceConfig() for _, value := range serviceConfig.AllowNondistributableArtifactsCIDRs { actual = append(actual, value.String()) } actual = append(actual, serviceConfig.AllowNondistributableArtifactsHostnames...) sort.Strings(registries) sort.Strings(actual) assert.Check(t, is.DeepEqual(registries, actual)) } func TestDaemonReloadMirrors(t *testing.T) { daemon := &Daemon{ imageService: images.NewImageService(images.ImageServiceConfig{}), } muteLogs() var err error daemon.RegistryService, err = registry.NewService(registry.ServiceOptions{ InsecureRegistries: []string{}, Mirrors: []string{ "https://mirror.test1.example.com", "https://mirror.test2.example.com", // this will be removed when reloading "https://mirror.test3.example.com", // this will be removed when reloading }, }) if err != nil { t.Fatal(err) } 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.example.com"}, // mirrors are invalid after: []string{}, }, { valid: true, mirrors: []string{"https://mirror.test1.example.com", "https://mirror.test4.example.com"}, after: []string{"https://mirror.test1.example.com/", "https://mirror.test4.example.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{ imageService: images.NewImageService(images.ImageServiceConfig{}), } muteLogs() var err error // initialize daemon with existing insecure registries: "127.0.0.0/8", "10.10.1.11:5000", "10.10.1.22:5000" daemon.RegistryService, err = 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.example.com", "docker2.example.com", // this will be removed when reloading }, }) if err != nil { t.Fatal(err) } 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.example.com", // this will be kept "docker3.example.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.example.com"]; ok { t.Fatalf("Expected no insecure registry of docker2.com, got %d", value) } } func TestDaemonReloadNotAffectOthers(t *testing.T) { daemon := &Daemon{ imageService: images.NewImageService(images.ImageServiceConfig{}), } muteLogs() 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.Fatal("Expected debug 'enabled', got 'disabled'") } } func TestDaemonDiscoveryReload(t *testing.T) { daemon := &Daemon{ imageService: images.NewImageService(images.ImageServiceConfig{}), } muteLogs() 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{ imageService: images.NewImageService(images.ImageServiceConfig{}), } daemon.configStore = &config.Config{} muteLogs() 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{ imageService: images.NewImageService(images.ImageServiceConfig{}), } 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) } } func TestDaemonReloadNetworkDiagnosticPort(t *testing.T) { if os.Getuid() != 0 { t.Skip("root required") } daemon := &Daemon{ imageService: images.NewImageService(images.ImageServiceConfig{}), } daemon.configStore = &config.Config{} valuesSet := make(map[string]interface{}) valuesSet["network-diagnostic-port"] = 2000 enableConfig := &config.Config{ CommonConfig: config.CommonConfig{ NetworkDiagnosticPort: 2000, ValuesSet: valuesSet, }, } disableConfig := &config.Config{ CommonConfig: config.CommonConfig{}, } netOptions, err := daemon.networkOptions(enableConfig, nil, nil) if err != nil { t.Fatal(err) } controller, err := libnetwork.New(netOptions...) if err != nil { t.Fatal(err) } daemon.netController = controller // Enable/Disable the server for some iterations for i := 0; i < 10; i++ { enableConfig.CommonConfig.NetworkDiagnosticPort++ if err := daemon.Reload(enableConfig); err != nil { t.Fatal(err) } // Check that the diagnostic is enabled if !daemon.netController.IsDiagnosticEnabled() { t.Fatalf("diagnostic should be enable") } // Reload if err := daemon.Reload(disableConfig); err != nil { t.Fatal(err) } // Check that the diagnostic is disabled if daemon.netController.IsDiagnosticEnabled() { t.Fatalf("diagnostic should be disable") } } enableConfig.CommonConfig.NetworkDiagnosticPort++ // 2 times the enable should not create problems if err := daemon.Reload(enableConfig); err != nil { t.Fatal(err) } // Check that the diagnostic is enabled if !daemon.netController.IsDiagnosticEnabled() { t.Fatalf("diagnostic should be enable") } // Check that another reload does not cause issues if err := daemon.Reload(enableConfig); err != nil { t.Fatal(err) } // Check that the diagnostic is enable if !daemon.netController.IsDiagnosticEnabled() { t.Fatalf("diagnostic should be enable") } }