package notifier import ( "fmt" "io/ioutil" "math/rand" "net/http" "net/http/httptest" "os" "sync" "testing" ) func TestConfigWatcherReload(t *testing.T) { f, err := ioutil.TempFile("", "") if err != nil { t.Fatal(err) } defer func() { _ = os.Remove(f.Name()) }() writeToFile(t, f.Name(), ` static_configs: - targets: - localhost:9093 - localhost:9094 `) cw, err := newWatcher(f.Name(), nil) if err != nil { t.Fatalf("failed to start config watcher: %s", err) } defer cw.mustStop() ns := cw.notifiers() if len(ns) != 2 { t.Fatalf("expected to have 2 notifiers; got %d %#v", len(ns), ns) } f2, err := ioutil.TempFile("", "") if err != nil { t.Fatal(err) } defer func() { _ = os.Remove(f2.Name()) }() writeToFile(t, f2.Name(), ` static_configs: - targets: - 127.0.0.1:9093 `) checkErr(t, cw.reload(f2.Name())) ns = cw.notifiers() if len(ns) != 1 { t.Fatalf("expected to have 1 notifier; got %d", len(ns)) } expAddr := "http://127.0.0.1:9093/api/v2/alerts" if ns[0].Addr() != expAddr { t.Fatalf("expected to get %q; got %q instead", expAddr, ns[0].Addr()) } } func TestConfigWatcherStart(t *testing.T) { consulSDServer := newFakeConsulServer() defer consulSDServer.Close() consulSDFile, err := ioutil.TempFile("", "") if err != nil { t.Fatal(err) } defer func() { _ = os.Remove(consulSDFile.Name()) }() writeToFile(t, consulSDFile.Name(), fmt.Sprintf(` scheme: https path_prefix: proxy consul_sd_configs: - server: %s services: - alertmanager `, consulSDServer.URL)) cw, err := newWatcher(consulSDFile.Name(), nil) if err != nil { t.Fatalf("failed to start config watcher: %s", err) } defer cw.mustStop() if len(cw.notifiers()) != 2 { t.Fatalf("expected to get 2 notifiers; got %d", len(cw.notifiers())) } expAddr1 := fmt.Sprintf("https://%s/proxy/api/v2/alerts", fakeConsulService1) expAddr2 := fmt.Sprintf("https://%s/proxy/api/v2/alerts", fakeConsulService2) n1, n2 := cw.notifiers()[0], cw.notifiers()[1] if n1.Addr() != expAddr1 { t.Fatalf("exp address %q; got %q", expAddr1, n1.Addr()) } if n2.Addr() != expAddr2 { t.Fatalf("exp address %q; got %q", expAddr2, n2.Addr()) } } // TestConfigWatcherReloadConcurrent supposed to test concurrent // execution of configuration update. // Should be executed with -race flag func TestConfigWatcherReloadConcurrent(t *testing.T) { consulSDServer1 := newFakeConsulServer() defer consulSDServer1.Close() consulSDServer2 := newFakeConsulServer() defer consulSDServer2.Close() consulSDFile, err := ioutil.TempFile("", "") if err != nil { t.Fatal(err) } defer func() { _ = os.Remove(consulSDFile.Name()) }() writeToFile(t, consulSDFile.Name(), fmt.Sprintf(` consul_sd_configs: - server: %s services: - alertmanager - server: %s services: - consul `, consulSDServer1.URL, consulSDServer2.URL)) staticAndConsulSDFile, err := ioutil.TempFile("", "") if err != nil { t.Fatal(err) } defer func() { _ = os.Remove(staticAndConsulSDFile.Name()) }() writeToFile(t, staticAndConsulSDFile.Name(), fmt.Sprintf(` static_configs: - targets: - localhost:9093 - localhost:9095 consul_sd_configs: - server: %s services: - alertmanager - server: %s services: - consul `, consulSDServer1.URL, consulSDServer2.URL)) paths := []string{ staticAndConsulSDFile.Name(), consulSDFile.Name(), "testdata/static.good.yaml", "unknownFields.bad.yaml", } cw, err := newWatcher(paths[0], nil) if err != nil { t.Fatalf("failed to start config watcher: %s", err) } defer cw.mustStop() const workers = 500 const iterations = 10 wg := sync.WaitGroup{} wg.Add(workers) for i := 0; i < workers; i++ { go func() { defer wg.Done() for i := 0; i < iterations; i++ { rnd := rand.Intn(len(paths)) _ = cw.reload(paths[rnd]) // update can fail and this is expected _ = cw.notifiers() } }() } wg.Wait() } func writeToFile(t *testing.T, file, b string) { t.Helper() checkErr(t, os.WriteFile(file, []byte(b), 0644)) } func checkErr(t *testing.T, err error) { t.Helper() if err != nil { t.Fatalf("unexpected err: %s", err) } } const ( fakeConsulService1 = "127.0.0.1:9093" fakeConsulService2 = "127.0.0.1:9095" ) func newFakeConsulServer() *httptest.Server { mux := http.NewServeMux() mux.HandleFunc("/v1/agent/self", func(rw http.ResponseWriter, _ *http.Request) { rw.Write([]byte(`{"Config": {"Datacenter": "dc1"}}`)) }) mux.HandleFunc("/v1/catalog/services", func(rw http.ResponseWriter, _ *http.Request) { rw.Header().Set("X-Consul-Index", "1") rw.Write([]byte(`{ "alertmanager": [ "alertmanager", "__scheme__=http" ] }`)) }) mux.HandleFunc("/v1/health/service/alertmanager", func(rw http.ResponseWriter, _ *http.Request) { rw.Header().Set("X-Consul-Index", "1") rw.Write([]byte(` [ { "Node": { "ID": "e8e3629a-3f50-9d6e-aaf8-f173b5b05c72", "Node": "machine", "Address": "127.0.0.1", "Datacenter": "dc1", "TaggedAddresses": { "lan": "127.0.0.1", "lan_ipv4": "127.0.0.1", "wan": "127.0.0.1", "wan_ipv4": "127.0.0.1" }, "Meta": { "consul-network-segment": "" }, "CreateIndex": 13, "ModifyIndex": 14 }, "Service": { "ID": "am1", "Service": "alertmanager", "Tags": [ "alertmanager", "__scheme__=http" ], "Address": "", "Meta": null, "Port": 9093, "Weights": { "Passing": 1, "Warning": 1 }, "EnableTagOverride": false, "Proxy": { "Mode": "", "MeshGateway": {}, "Expose": {} }, "Connect": {}, "CreateIndex": 16, "ModifyIndex": 16 } }, { "Node": { "ID": "e8e3629a-3f50-9d6e-aaf8-f173b5b05c72", "Node": "machine", "Address": "127.0.0.1", "Datacenter": "dc1", "TaggedAddresses": { "lan": "127.0.0.1", "lan_ipv4": "127.0.0.1", "wan": "127.0.0.1", "wan_ipv4": "127.0.0.1" }, "Meta": { "consul-network-segment": "" }, "CreateIndex": 13, "ModifyIndex": 14 }, "Service": { "ID": "am2", "Service": "alertmanager", "Tags": [ "alertmanager", "bad-node" ], "Address": "", "Meta": null, "Port": 9095, "Weights": { "Passing": 1, "Warning": 1 }, "EnableTagOverride": false, "Proxy": { "Mode": "", "MeshGateway": {}, "Expose": {} }, "Connect": {}, "CreateIndex": 15, "ModifyIndex": 15 } } ]`)) }) return httptest.NewServer(mux) }