diff --git a/discovery/manager.go b/discovery/manager.go index 6f5ad2d03d..00a6258282 100644 --- a/discovery/manager.go +++ b/discovery/manager.go @@ -314,11 +314,13 @@ func (m *Manager) allGroups() map[string][]*targetgroup.Group { } func (m *Manager) registerProviders(cfg sd_config.ServiceDiscoveryConfig, setName string) { + var added bool add := func(cfg interface{}, newDiscoverer func() (Discoverer, error)) { t := reflect.TypeOf(cfg).String() for _, p := range m.providers { if reflect.DeepEqual(cfg, p.config) { p.subs = append(p.subs, setName) + added = true return } } @@ -337,6 +339,7 @@ func (m *Manager) registerProviders(cfg sd_config.ServiceDiscoveryConfig, setNam subs: []string{setName}, } m.providers = append(m.providers, &provider) + added = true } for _, c := range cfg.DNSSDConfigs { @@ -401,7 +404,17 @@ func (m *Manager) registerProviders(cfg sd_config.ServiceDiscoveryConfig, setNam } if len(cfg.StaticConfigs) > 0 { add(setName, func() (Discoverer, error) { - return &StaticProvider{cfg.StaticConfigs}, nil + return &StaticProvider{TargetGroups: cfg.StaticConfigs}, nil + }) + } + if !added { + // Add an empty target group to force the refresh of the corresponding + // scrape pool and to notify the receiver that this target set has no + // current targets. + // It can happen because the combined set of SD configurations is empty + // or because we fail to instantiate all the SD configurations. + add(setName, func() (Discoverer, error) { + return &StaticProvider{TargetGroups: []*targetgroup.Group{&targetgroup.Group{}}}, nil }) } } diff --git a/discovery/manager_test.go b/discovery/manager_test.go index d58f1643fc..220c99b8ae 100644 --- a/discovery/manager_test.go +++ b/discovery/manager_test.go @@ -719,6 +719,7 @@ func assertEqualGroups(t *testing.T, got, expected []*targetgroup.Group, msg fun } func verifyPresence(t *testing.T, tSets map[poolKey]map[string]*targetgroup.Group, poolKey poolKey, label string, present bool) { + t.Helper() if _, ok := tSets[poolKey]; !ok { t.Fatalf("'%s' should be present in Pool keys: %v", poolKey, tSets) return @@ -741,7 +742,7 @@ func verifyPresence(t *testing.T, tSets map[poolKey]map[string]*targetgroup.Grou if !present { msg = "not" } - t.Fatalf("'%s' should %s be present in Targets labels: %v", label, msg, mergedTargets) + t.Fatalf("%q should %s be present in Targets labels: %q", label, msg, mergedTargets) } } @@ -781,7 +782,7 @@ scrape_configs: - targets: ["foo:9090"] ` if err := yaml.UnmarshalStrict([]byte(sTwo), cfg); err != nil { - t.Fatalf("Unable to load YAML config sOne: %s", err) + t.Fatalf("Unable to load YAML config sTwo: %s", err) } c = make(map[string]sd_config.ServiceDiscoveryConfig) for _, v := range cfg.ScrapeConfigs { @@ -794,6 +795,67 @@ scrape_configs: verifyPresence(t, discoveryManager.targets, poolKey{setName: "prometheus", provider: "string/0"}, "{__address__=\"bar:9090\"}", false) } +// TestTargetSetRecreatesEmptyStaticConfigs ensures that reloading a config file after +// removing all targets from the static_configs sends an update with empty targetGroups. +// This is required to signal the receiver that this target set has no current targets. +func TestTargetSetRecreatesEmptyStaticConfigs(t *testing.T) { + cfg := &config.Config{} + + sOne := ` +scrape_configs: + - job_name: 'prometheus' + static_configs: + - targets: ["foo:9090"] +` + if err := yaml.UnmarshalStrict([]byte(sOne), cfg); err != nil { + t.Fatalf("Unable to load YAML config sOne: %s", err) + } + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + discoveryManager := NewManager(ctx, log.NewNopLogger()) + discoveryManager.updatert = 100 * time.Millisecond + go discoveryManager.Run() + + c := make(map[string]sd_config.ServiceDiscoveryConfig) + for _, v := range cfg.ScrapeConfigs { + c[v.JobName] = v.ServiceDiscoveryConfig + } + discoveryManager.ApplyConfig(c) + + <-discoveryManager.SyncCh() + verifyPresence(t, discoveryManager.targets, poolKey{setName: "prometheus", provider: "string/0"}, "{__address__=\"foo:9090\"}", true) + + sTwo := ` +scrape_configs: + - job_name: 'prometheus' + static_configs: +` + if err := yaml.UnmarshalStrict([]byte(sTwo), cfg); err != nil { + t.Fatalf("Unable to load YAML config sTwo: %s", err) + } + c = make(map[string]sd_config.ServiceDiscoveryConfig) + for _, v := range cfg.ScrapeConfigs { + c[v.JobName] = v.ServiceDiscoveryConfig + } + discoveryManager.ApplyConfig(c) + + <-discoveryManager.SyncCh() + + pkey := poolKey{setName: "prometheus", provider: "string/0"} + targetGroups, ok := discoveryManager.targets[pkey] + if !ok { + t.Fatalf("'%v' should be present in target groups", pkey) + } + group, ok := targetGroups[""] + if !ok { + t.Fatalf("missing '' key in target groups %v", targetGroups) + } + + if len(group.Targets) != 0 { + t.Fatalf("Invalid number of targets: expected 0, got %d", len(group.Targets)) + } +} + func TestIdenticalConfigurationsAreCoalesced(t *testing.T) { tmpFile, err := ioutil.TempFile("", "sd") if err != nil {