From 4f58ff2e5865f66fc5bad6eb3927085d3a1be115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Thu, 11 Jun 2020 19:32:22 +0100 Subject: [PATCH] feat(backend): use cluster name from config --- cmd/karma/alerts.go | 45 +- cmd/karma/auth_headers_test.go | 1 + cmd/karma/main.go | 1 + cmd/karma/proxy_test.go | 5 + .../tests/testscript/log_full_config_env.txt | 3 +- .../tests/testscript/log_full_config_file.txt | 18 +- cmd/karma/views_test.go | 557 +++++++++++++++++- internal/alertmanager/dedup_test.go | 4 +- internal/alertmanager/model_test.go | 2 +- internal/alertmanager/models.go | 1 + internal/alertmanager/upstream.go | 3 +- internal/alertmanager/upstream_test.go | 4 + internal/slices/slices.go | 35 ++ internal/slices/slices_test.go | 48 ++ 14 files changed, 703 insertions(+), 24 deletions(-) diff --git a/cmd/karma/alerts.go b/cmd/karma/alerts.go index f6c8ea52c..870fdce14 100644 --- a/cmd/karma/alerts.go +++ b/cmd/karma/alerts.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "sort" + "strings" "github.com/gin-gonic/gin" "vbom.ml/util/sortorder" @@ -14,8 +15,6 @@ import ( "github.com/prymitive/karma/internal/models" "github.com/prymitive/karma/internal/slices" "github.com/prymitive/karma/internal/uri" - - log "github.com/sirupsen/logrus" ) func getFiltersFromQuery(filterStrings []string) ([]filters.FilterT, bool) { @@ -89,6 +88,23 @@ func countersToLabelStats(counters map[string]map[string]int) models.LabelNameSt return data } +func clusterMembersFromConfig(am *alertmanager.Alertmanager) []string { + members := []string{} + + upstreams := alertmanager.GetAlertmanagers() + for _, upstream := range upstreams { + if upstream.Cluster == am.Cluster { + members = append(members, upstream.Name) + } + } + + return members +} + +func clusterMembersFromAPI(am *alertmanager.Alertmanager) []string { + return am.ClusterMemberNames() +} + func getUpstreams() models.AlertmanagerAPISummary { summary := models.AlertmanagerAPISummary{} @@ -96,13 +112,24 @@ func getUpstreams() models.AlertmanagerAPISummary { upstreams := alertmanager.GetAlertmanagers() for _, upstream := range upstreams { members := upstream.ClusterMemberNames() - key, err := slices.StringSliceToSHA1(members) - if err != nil { - log.Errorf("slices.StringSliceToSHA1 error: %s", err) - continue + + var clusterName string + if upstream.Cluster != "" { + configPeers := clusterMembersFromConfig(upstream) + apiPeers := clusterMembersFromAPI(upstream) + missing, extra := slices.StringSliceDiff(configPeers, apiPeers) + + if len(missing) == 0 && len(extra) == 0 { + clusterName = upstream.Cluster + } else { + clusterName = fmt.Sprintf("%s @ %s", strings.Join(members, " | "), upstream.Cluster) + } + } else { + clusterName = strings.Join(members, " | ") } - if _, found := clusters[key]; !found { - clusters[key] = members + + if _, found := clusters[clusterName]; !found { + clusters[clusterName] = members } u := models.AlertmanagerAPIStatus{ @@ -114,7 +141,7 @@ func getUpstreams() models.AlertmanagerAPISummary { CORSCredentials: upstream.CORSCredentials, Error: upstream.Error(), Version: upstream.Version(), - Cluster: upstream.ClusterID(), + Cluster: clusterName, ClusterMembers: members, } if !upstream.ProxyRequests { diff --git a/cmd/karma/auth_headers_test.go b/cmd/karma/auth_headers_test.go index 468f69147..bae8a3efb 100644 --- a/cmd/karma/auth_headers_test.go +++ b/cmd/karma/auth_headers_test.go @@ -68,6 +68,7 @@ func TestAuthHeader(t *testing.T) { apiCache = cache.New(cache.NoExpiration, 10*time.Second) am, err := alertmanager.NewAlertmanager( + "cluster", fmt.Sprintf("testAuthHeader/%s", version), testCase.alertmanagerURI, alertmanager.WithRequestTimeout(time.Second*5), diff --git a/cmd/karma/main.go b/cmd/karma/main.go index e717c19cc..d7e23c980 100644 --- a/cmd/karma/main.go +++ b/cmd/karma/main.go @@ -178,6 +178,7 @@ func setupUpstreams() error { } am, err := alertmanager.NewAlertmanager( + s.Cluster, s.Name, s.URI, alertmanager.WithExternalURI(s.ExternalURI), diff --git a/cmd/karma/proxy_test.go b/cmd/karma/proxy_test.go index 9310c5b43..55602051c 100644 --- a/cmd/karma/proxy_test.go +++ b/cmd/karma/proxy_test.go @@ -105,6 +105,7 @@ func TestProxy(t *testing.T) { r := ginTestEngine() am, err := alertmanager.NewAlertmanager( + "cluster", "dummy", "http://localhost:9093", alertmanager.WithRequestTimeout(time.Second*5), @@ -207,6 +208,7 @@ func TestProxyHeaders(t *testing.T) { testCase := testCase //scopelint pin r := ginTestEngine() am, err := alertmanager.NewAlertmanager( + "cluster", "dummy", testCase.alertmanagerURI, alertmanager.WithRequestTimeout(time.Second*5), @@ -310,6 +312,7 @@ func TestProxyToSubURIAlertmanager(t *testing.T) { r := ginTestEngine() am, err := alertmanager.NewAlertmanager( + "cluster", "suburi", testCase.alertmanagerURI, alertmanager.WithRequestTimeout(time.Second*5), @@ -505,6 +508,7 @@ func TestProxyUserRewrite(t *testing.T) { r := ginTestEngine() am, err := alertmanager.NewAlertmanager( + "cluster", "proxyAuth", "http://localhost", alertmanager.WithRequestTimeout(time.Second*5), @@ -997,6 +1001,7 @@ func TestProxySilenceACL(t *testing.T) { r := ginTestEngine() am, err := alertmanager.NewAlertmanager( + "cluster", "proxyACL", "http://localhost", alertmanager.WithRequestTimeout(time.Second*5), diff --git a/cmd/karma/tests/testscript/log_full_config_env.txt b/cmd/karma/tests/testscript/log_full_config_env.txt index 9876016f3..27ef795b7 100644 --- a/cmd/karma/tests/testscript/log_full_config_env.txt +++ b/cmd/karma/tests/testscript/log_full_config_env.txt @@ -85,7 +85,8 @@ level=info msg=" silences: \"\"" level=info msg="alertmanager:" level=info msg=" interval: 10s" level=info msg=" servers:" -level=info msg=" - name: ro" +level=info msg=" - cluster: \"\"" +level=info msg=" name: ro" level=info msg=" uri: http://localhost:9093" level=info msg=" external_uri: http://localhost:9093" level=info msg=" timeout: 10s" diff --git a/cmd/karma/tests/testscript/log_full_config_file.txt b/cmd/karma/tests/testscript/log_full_config_file.txt index e93ac89a0..08e342b43 100644 --- a/cmd/karma/tests/testscript/log_full_config_file.txt +++ b/cmd/karma/tests/testscript/log_full_config_file.txt @@ -20,11 +20,13 @@ authorization: alertmanager: interval: 10s servers: - - name: ha1 + - cluster: HA + name: ha1 uri: "http://localhost:9093" timeout: 10s proxy: true - - name: ha2 + - cluster: HA + name: ha2 uri: "http://localhost:9094" timeout: 10s readonly: true @@ -266,7 +268,8 @@ level=info msg=" silences: \"\"" level=info msg="alertmanager:" level=info msg=" interval: 10s" level=info msg=" servers:" -level=info msg=" - name: ha1" +level=info msg=" - cluster: HA" +level=info msg=" name: ha1" level=info msg=" uri: http://localhost:9093" level=info msg=" external_uri: \"\"" level=info msg=" timeout: 10s" @@ -280,7 +283,8 @@ level=info msg=" insecureSkipVerify: false" level=info msg=" headers: {}" level=info msg=" cors:" level=info msg=" credentials: include" -level=info msg=" - name: ha2" +level=info msg=" - cluster: HA" +level=info msg=" name: ha2" level=info msg=" uri: http://localhost:9094" level=info msg=" external_uri: \"\"" level=info msg=" timeout: 10s" @@ -294,7 +298,8 @@ level=info msg=" insecureSkipVerify: false" level=info msg=" headers: {}" level=info msg=" cors:" level=info msg=" credentials: omit" -level=info msg=" - name: local" +level=info msg=" - cluster: \"\"" +level=info msg=" name: local" level=info msg=" uri: http://localhost:9095" level=info msg=" external_uri: \"\"" level=info msg=" timeout: 40s" @@ -309,7 +314,8 @@ level=info msg=" headers:" level=info msg=" X-Auth-Test: some-token-or-other-string" level=info msg=" cors:" level=info msg=" credentials: same-origin" -level=info msg=" - name: client-auth" +level=info msg=" - cluster: \"\"" +level=info msg=" name: client-auth" level=info msg=" uri: https://localhost:9096" level=info msg=" external_uri: \"\"" level=info msg=" timeout: 10s" diff --git a/cmd/karma/views_test.go b/cmd/karma/views_test.go index 9c6f12dd0..8ea64ce3b 100644 --- a/cmd/karma/views_test.go +++ b/cmd/karma/views_test.go @@ -1025,12 +1025,558 @@ func TestUpstreamStatus(t *testing.T) { CORSCredentials: "include", Error: `^unknown error \(status 404\): .+`, Version: "", - Cluster: "843c4a11660fe38ea61e6960a29d4f4796da6488", + Cluster: "default", ClusterMembers: []string{"default"}, }, }, Clusters: map[string][]string{ - "843c4a11660fe38ea61e6960a29d4f4796da6488": {"default"}, + "default": {"default"}, + }, + }, + }, + { + Name: "HA Cluster", + mocks: []mockT{ + { + uri: "http://ha1.example.com/metrics", + code: 200, + body: `alertmanager_build_info{version="0.20.0"} 1`, + }, + { + uri: "http://ha2.example.com/metrics", + code: 200, + body: `alertmanager_build_info{version="0.19.0"} 1`, + }, + { + uri: "http://ha1.example.com/api/v2/status", + code: 200, + body: `{ + "cluster": { + "name": "AAAAAAAAAAAAAAAAAAAAAAAAAA", + "peers": [ + { + "address": "10.16.0.1:9094", + "name": "AAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + { + "address": "10.16.0.2:9094", + "name": "BBBBBBBBBBBBBBBBBBBBBBBBBB" + } + ], + "status": "ready" + }, + "versionInfo": { + "version":"0.20.0" + } +}`, + }, + { + uri: "http://ha2.example.com/api/v2/status", + code: 200, + body: `{ + "cluster": { + "name": "BBBBBBBBBBBBBBBBBBBBBBBBBB", + "peers": [ + { + "address": "10.16.0.1:9094", + "name": "AAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + { + "address": "10.16.0.2:9094", + "name": "BBBBBBBBBBBBBBBBBBBBBBBBBB" + } + ], + "status": "ready" + }, + "versionInfo": { + "version":"0.19.0" + } +}`, + }, + { + uri: "http://ha1.example.com/api/v2/alerts/groups", + code: 200, + body: "[]", + }, + { + uri: "http://ha1.example.com/api/v2/silences", + code: 200, + body: "[]", + }, + { + uri: "http://ha2.example.com/api/v2/alerts/groups", + code: 200, + body: "[]", + }, + { + uri: "http://ha2.example.com/api/v2/silences", + code: 200, + body: "[]", + }, + }, + upstreams: []config.AlertmanagerConfig{ + { + Cluster: "HA", + Name: "ha1", + URI: "http://ha1.example.com", + Proxy: false, + ReadOnly: false, + Headers: map[string]string{}, + CORS: config.AlertmanagerCORS{ + Credentials: "omit", + }, + }, + { + Cluster: "HA", + Name: "ha2", + URI: "http://ha2.example.com", + Proxy: false, + ReadOnly: true, + Headers: map[string]string{}, + CORS: config.AlertmanagerCORS{ + Credentials: "omit", + }, + }, + }, + status: models.AlertmanagerAPISummary{ + Counters: models.AlertmanagerAPICounters{ + Total: 2, + Healthy: 2, + Failed: 0, + }, + Instances: []models.AlertmanagerAPIStatus{ + { + Name: "ha1", + URI: "http://ha1.example.com", + PublicURI: "http://ha1.example.com", + ReadOnly: false, + Headers: map[string]string{}, + CORSCredentials: "omit", + Error: "", + Version: "0.20.0", + Cluster: "HA", + ClusterMembers: []string{"ha1", "ha2"}, + }, + { + Name: "ha2", + URI: "http://ha2.example.com", + PublicURI: "http://ha2.example.com", + ReadOnly: true, + Headers: map[string]string{}, + CORSCredentials: "omit", + Error: "", + Version: "0.19.0", + Cluster: "HA", + ClusterMembers: []string{"ha1", "ha2"}, + }, + }, + Clusters: map[string][]string{ + "HA": {"ha1", "ha2"}, + }, + }, + }, + { + Name: "HA Cluster Without Name", + mocks: []mockT{ + { + uri: "http://ha1.example.com/metrics", + code: 200, + body: `alertmanager_build_info{version="0.20.0"} 1`, + }, + { + uri: "http://ha2.example.com/metrics", + code: 200, + body: `alertmanager_build_info{version="0.19.0"} 1`, + }, + { + uri: "http://ha1.example.com/api/v2/status", + code: 200, + body: `{ + "cluster": { + "name": "AAAAAAAAAAAAAAAAAAAAAAAAAA", + "peers": [ + { + "address": "10.16.0.1:9094", + "name": "AAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + { + "address": "10.16.0.2:9094", + "name": "BBBBBBBBBBBBBBBBBBBBBBBBBB" + } + ], + "status": "ready" + }, + "versionInfo": { + "version":"0.20.0" + } +}`, + }, + { + uri: "http://ha2.example.com/api/v2/status", + code: 200, + body: `{ + "cluster": { + "name": "BBBBBBBBBBBBBBBBBBBBBBBBBB", + "peers": [ + { + "address": "10.16.0.1:9094", + "name": "AAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + { + "address": "10.16.0.2:9094", + "name": "BBBBBBBBBBBBBBBBBBBBBBBBBB" + } + ], + "status": "ready" + }, + "versionInfo": { + "version":"0.19.0" + } +}`, + }, + { + uri: "http://ha1.example.com/api/v2/alerts/groups", + code: 200, + body: "[]", + }, + { + uri: "http://ha1.example.com/api/v2/silences", + code: 200, + body: "[]", + }, + { + uri: "http://ha2.example.com/api/v2/alerts/groups", + code: 200, + body: "[]", + }, + { + uri: "http://ha2.example.com/api/v2/silences", + code: 200, + body: "[]", + }, + }, + upstreams: []config.AlertmanagerConfig{ + { + Name: "ha1", + URI: "http://ha1.example.com", + Proxy: false, + ReadOnly: false, + Headers: map[string]string{}, + CORS: config.AlertmanagerCORS{ + Credentials: "same-site", + }, + }, + { + Name: "ha2", + URI: "http://ha2.example.com", + Proxy: false, + ReadOnly: true, + Headers: map[string]string{}, + CORS: config.AlertmanagerCORS{ + Credentials: "same-site", + }, + }, + }, + status: models.AlertmanagerAPISummary{ + Counters: models.AlertmanagerAPICounters{ + Total: 2, + Healthy: 2, + Failed: 0, + }, + Instances: []models.AlertmanagerAPIStatus{ + { + Name: "ha1", + URI: "http://ha1.example.com", + PublicURI: "http://ha1.example.com", + ReadOnly: false, + Headers: map[string]string{}, + CORSCredentials: "same-site", + Error: "", + Version: "0.20.0", + Cluster: "ha1 | ha2", + ClusterMembers: []string{"ha1", "ha2"}, + }, + { + Name: "ha2", + URI: "http://ha2.example.com", + PublicURI: "http://ha2.example.com", + ReadOnly: true, + Headers: map[string]string{}, + CORSCredentials: "same-site", + Error: "", + Version: "0.19.0", + Cluster: "ha1 | ha2", + ClusterMembers: []string{"ha1", "ha2"}, + }, + }, + Clusters: map[string][]string{ + "ha1 | ha2": {"ha1", "ha2"}, + }, + }, + }, + { + Name: "Broken Cluster", + mocks: []mockT{ + { + uri: "http://ha1.example.com/metrics", + code: 200, + body: `alertmanager_build_info{version="0.20.0"} 1`, + }, + { + uri: "http://ha2.example.com/metrics", + code: 200, + body: `alertmanager_build_info{version="0.19.0"} 1`, + }, + { + uri: "http://ha1.example.com/api/v2/status", + code: 200, + body: `{ + "cluster": { + "name": "AAAAAAAAAAAAAAAAAAAAAAAAAA", + "peers": [ + { + "address": "10.16.0.1:9094", + "name": "AAAAAAAAAAAAAAAAAAAAAAAAAA" + } + ], + "status": "ready" + }, + "versionInfo": { + "version":"0.20.0" + } +}`, + }, + { + uri: "http://ha2.example.com/api/v2/status", + code: 200, + body: `{ + "cluster": { + "name": "BBBBBBBBBBBBBBBBBBBBBBBBBB", + "peers": [ + { + "address": "10.16.0.2:9094", + "name": "BBBBBBBBBBBBBBBBBBBBBBBBBB" + } + ], + "status": "ready" + }, + "versionInfo": { + "version":"0.19.0" + } +}`, + }, + { + uri: "http://ha1.example.com/api/v2/alerts/groups", + code: 200, + body: "[]", + }, + { + uri: "http://ha1.example.com/api/v2/silences", + code: 200, + body: "[]", + }, + { + uri: "http://ha2.example.com/api/v2/alerts/groups", + code: 200, + body: "[]", + }, + { + uri: "http://ha2.example.com/api/v2/silences", + code: 200, + body: "[]", + }, + }, + upstreams: []config.AlertmanagerConfig{ + { + Cluster: "Broken HA", + Name: "ha1", + URI: "http://ha1.example.com", + Proxy: false, + ReadOnly: false, + Headers: map[string]string{}, + CORS: config.AlertmanagerCORS{ + Credentials: "omit", + }, + }, + { + Cluster: "Broken HA", + Name: "ha2", + URI: "http://ha2.example.com", + Proxy: false, + ReadOnly: true, + Headers: map[string]string{}, + CORS: config.AlertmanagerCORS{ + Credentials: "omit", + }, + }, + }, + status: models.AlertmanagerAPISummary{ + Counters: models.AlertmanagerAPICounters{ + Total: 2, + Healthy: 2, + Failed: 0, + }, + Instances: []models.AlertmanagerAPIStatus{ + { + Name: "ha1", + URI: "http://ha1.example.com", + PublicURI: "http://ha1.example.com", + ReadOnly: false, + Headers: map[string]string{}, + CORSCredentials: "omit", + Error: "", + Version: "0.20.0", + Cluster: "ha1 @ Broken HA", + ClusterMembers: []string{"ha1"}, + }, + { + Name: "ha2", + URI: "http://ha2.example.com", + PublicURI: "http://ha2.example.com", + ReadOnly: true, + Headers: map[string]string{}, + CORSCredentials: "omit", + Error: "", + Version: "0.19.0", + Cluster: "ha2 @ Broken HA", + ClusterMembers: []string{"ha2"}, + }, + }, + Clusters: map[string][]string{ + "ha1 @ Broken HA": {"ha1"}, + "ha2 @ Broken HA": {"ha2"}, + }, + }, + }, + { + Name: "Broken Cluster Without Name", + mocks: []mockT{ + { + uri: "http://ha1.example.com/metrics", + code: 200, + body: `alertmanager_build_info{version="0.20.0"} 1`, + }, + { + uri: "http://ha2.example.com/metrics", + code: 200, + body: `alertmanager_build_info{version="0.19.0"} 1`, + }, + { + uri: "http://ha1.example.com/api/v2/status", + code: 200, + body: `{ + "cluster": { + "name": "AAAAAAAAAAAAAAAAAAAAAAAAAA", + "peers": [ + { + "address": "10.16.0.1:9094", + "name": "AAAAAAAAAAAAAAAAAAAAAAAAAA" + } + ], + "status": "ready" + }, + "versionInfo": { + "version":"0.20.0" + } +}`, + }, + { + uri: "http://ha2.example.com/api/v2/status", + code: 200, + body: `{ + "cluster": { + "name": "BBBBBBBBBBBBBBBBBBBBBBBBBB", + "peers": [ + { + "address": "10.16.0.2:9094", + "name": "BBBBBBBBBBBBBBBBBBBBBBBBBB" + } + ], + "status": "ready" + }, + "versionInfo": { + "version":"0.19.0" + } +}`, + }, + { + uri: "http://ha1.example.com/api/v2/alerts/groups", + code: 200, + body: "[]", + }, + { + uri: "http://ha1.example.com/api/v2/silences", + code: 200, + body: "[]", + }, + { + uri: "http://ha2.example.com/api/v2/alerts/groups", + code: 200, + body: "[]", + }, + { + uri: "http://ha2.example.com/api/v2/silences", + code: 200, + body: "[]", + }, + }, + upstreams: []config.AlertmanagerConfig{ + { + Name: "ha1", + URI: "http://ha1.example.com", + Proxy: false, + ReadOnly: false, + Headers: map[string]string{}, + CORS: config.AlertmanagerCORS{ + Credentials: "omit", + }, + }, + { + Name: "ha2", + URI: "http://ha2.example.com", + Proxy: false, + ReadOnly: true, + Headers: map[string]string{}, + CORS: config.AlertmanagerCORS{ + Credentials: "omit", + }, + }, + }, + status: models.AlertmanagerAPISummary{ + Counters: models.AlertmanagerAPICounters{ + Total: 2, + Healthy: 2, + Failed: 0, + }, + Instances: []models.AlertmanagerAPIStatus{ + { + Name: "ha1", + URI: "http://ha1.example.com", + PublicURI: "http://ha1.example.com", + ReadOnly: false, + Headers: map[string]string{}, + CORSCredentials: "omit", + Error: "", + Version: "0.20.0", + Cluster: "ha1", + ClusterMembers: []string{"ha1"}, + }, + { + Name: "ha2", + URI: "http://ha2.example.com", + PublicURI: "http://ha2.example.com", + ReadOnly: true, + Headers: map[string]string{}, + CORSCredentials: "omit", + Error: "", + Version: "0.19.0", + Cluster: "ha2", + ClusterMembers: []string{"ha2"}, + }, + }, + Clusters: map[string][]string{ + "ha1": {"ha1"}, + "ha2": {"ha2"}, }, }, }, @@ -1047,7 +1593,10 @@ func TestUpstreamStatus(t *testing.T) { alertmanager.UnregisterAll() mockConfig() config.Config.Alertmanager.Servers = testCase.upstreams - _ = setupUpstreams() + err := setupUpstreams() + if err != nil { + t.Error(err) + } log.SetLevel(log.FatalLevel) pullFromAlertmanager() r := ginTestEngine() @@ -1060,7 +1609,7 @@ func TestUpstreamStatus(t *testing.T) { } ur := models.AlertsResponse{} - err := json.Unmarshal(resp.Body.Bytes(), &ur) + err = json.Unmarshal(resp.Body.Bytes(), &ur) if err != nil { t.Errorf("Failed to unmarshal response: %s", err) } diff --git a/internal/alertmanager/dedup_test.go b/internal/alertmanager/dedup_test.go index 8d63dade1..7372407d1 100644 --- a/internal/alertmanager/dedup_test.go +++ b/internal/alertmanager/dedup_test.go @@ -22,7 +22,7 @@ func init() { for _, version := range mock.ListAllMocks() { name := fmt.Sprintf("dedup-mock-%s", version) uri := fmt.Sprintf("http://%s.localhost", version) - am, err := alertmanager.NewAlertmanager(name, uri, alertmanager.WithRequestTimeout(time.Second)) + am, err := alertmanager.NewAlertmanager("cluster", name, uri, alertmanager.WithRequestTimeout(time.Second)) if err != nil { log.Fatal(err) } @@ -185,7 +185,7 @@ func TestClearData(t *testing.T) { for _, version := range mock.ListAllMocks() { name := fmt.Sprintf("clear-data-mock-%s", version) uri := fmt.Sprintf("http://localhost/clear/%s", version) - am, _ := alertmanager.NewAlertmanager(name, uri, alertmanager.WithRequestTimeout(time.Second)) + am, _ := alertmanager.NewAlertmanager("cluster", name, uri, alertmanager.WithRequestTimeout(time.Second)) mock.RegisterURL(fmt.Sprintf("%s/metrics", uri), version, "metrics") _ = am.Pull() diff --git a/internal/alertmanager/model_test.go b/internal/alertmanager/model_test.go index c0872402d..5ccff879f 100644 --- a/internal/alertmanager/model_test.go +++ b/internal/alertmanager/model_test.go @@ -87,7 +87,7 @@ var uriTests = []uriTest{ func TestAlertmanagerURI(t *testing.T) { for i, test := range uriTests { - am, err := NewAlertmanager("test", test.rawURI, WithExternalURI(test.extURI), WithProxy(test.proxy)) + am, err := NewAlertmanager("cluster", "test", test.rawURI, WithExternalURI(test.extURI), WithProxy(test.proxy)) if err != nil { t.Error(err) } diff --git a/internal/alertmanager/models.go b/internal/alertmanager/models.go index 95371a831..7faebac07 100644 --- a/internal/alertmanager/models.go +++ b/internal/alertmanager/models.go @@ -36,6 +36,7 @@ type Alertmanager struct { URI string `json:"uri"` ExternalURI string `json:"-"` RequestTimeout time.Duration `json:"timeout"` + Cluster string `json:"cluster"` Name string `json:"name"` // whenever this instance should be proxied ProxyRequests bool `json:"proxyRequests"` diff --git a/internal/alertmanager/upstream.go b/internal/alertmanager/upstream.go index 141daff6f..576dc3a47 100644 --- a/internal/alertmanager/upstream.go +++ b/internal/alertmanager/upstream.go @@ -22,11 +22,12 @@ var ( ) // NewAlertmanager creates a new Alertmanager instance -func NewAlertmanager(name, upstreamURI string, opts ...Option) (*Alertmanager, error) { +func NewAlertmanager(cluster, name, upstreamURI string, opts ...Option) (*Alertmanager, error) { am := &Alertmanager{ URI: upstreamURI, ExternalURI: "", RequestTimeout: time.Second * 10, + Cluster: cluster, Name: name, lock: sync.RWMutex{}, alertGroups: []models.AlertGroup{}, diff --git a/internal/alertmanager/upstream_test.go b/internal/alertmanager/upstream_test.go index 0fea9359c..f37bfcd0f 100644 --- a/internal/alertmanager/upstream_test.go +++ b/internal/alertmanager/upstream_test.go @@ -15,6 +15,7 @@ type testCase struct { var testCases = []testCase{ { config: config.AlertmanagerConfig{ + Cluster: "cluster", Name: "name", URI: "http://localhost:9093", ExternalURI: "http://localhost:9093", @@ -26,6 +27,7 @@ var testCases = []testCase{ }, { config: config.AlertmanagerConfig{ + Cluster: "cluster", Name: "proxy", URI: "http://localhost:9094", ExternalURI: "http://localhost:9094", @@ -37,6 +39,7 @@ var testCases = []testCase{ }, { config: config.AlertmanagerConfig{ + Cluster: "cluster", Name: "name", URI: "http://localhost:9095", ExternalURI: "http://localhost:9095", @@ -60,6 +63,7 @@ func TestOptions(t *testing.T) { } am, err := NewAlertmanager( + tc.config.Cluster, tc.config.Name, tc.config.URI, WithExternalURI(tc.config.ExternalURI), diff --git a/internal/slices/slices.go b/internal/slices/slices.go index f526be008..da293869f 100644 --- a/internal/slices/slices.go +++ b/internal/slices/slices.go @@ -34,3 +34,38 @@ func StringSliceToSHA1(stringArray []string) (string, error) { } return fmt.Sprintf("%x", h.Sum(nil)), nil } + +func StringSliceDiff(slice1 []string, slice2 []string) ([]string, []string) { + missing := []string{} + extra := []string{} + + var found bool + + for _, s1 := range slice1 { + found = false + for _, s2 := range slice2 { + if s1 == s2 { + found = true + break + } + } + if !found { + missing = append(missing, s1) + } + } + + for _, s2 := range slice2 { + found = false + for _, s1 := range slice1 { + if s2 == s1 { + found = true + break + } + } + if !found { + extra = append(extra, s2) + } + } + + return missing, extra +} diff --git a/internal/slices/slices_test.go b/internal/slices/slices_test.go index c5db88c5b..901386794 100644 --- a/internal/slices/slices_test.go +++ b/internal/slices/slices_test.go @@ -4,6 +4,8 @@ import ( "testing" "github.com/prymitive/karma/internal/slices" + + "github.com/google/go-cmp/cmp" ) type stringSliceTest struct { @@ -121,3 +123,49 @@ func TestStringSliceToSHA1(t *testing.T) { t.Errorf("StringSliceToSHA1() returned empty string") } } + +func TestStringSliceDiff(t *testing.T) { + type testCaseT struct { + a []string + b []string + missing []string + extra []string + } + + testCases := []testCaseT{ + { + a: []string{"a"}, + b: []string{"a"}, + missing: []string{}, + extra: []string{}, + }, + { + a: []string{}, + b: []string{"a"}, + missing: []string{}, + extra: []string{"a"}, + }, + { + a: []string{"a", "b"}, + b: []string{"a"}, + missing: []string{"b"}, + extra: []string{}, + }, + { + a: []string{"a", "b"}, + b: []string{"c"}, + missing: []string{"a", "b"}, + extra: []string{"c"}, + }, + } + + for _, testCase := range testCases { + missing, extra := slices.StringSliceDiff(testCase.a, testCase.b) + if diff := cmp.Diff(testCase.missing, missing); diff != "" { + t.Errorf("Incorrect slice diff missing (-want +got):\n%s", diff) + } + if diff := cmp.Diff(testCase.extra, extra); diff != "" { + t.Errorf("Incorrect slice diff extra (-want +got):\n%s", diff) + } + } +}