Files
karma/internal/alertmanager/upstream_test.go
2026-03-09 19:44:23 +00:00

309 lines
8.9 KiB
Go

package alertmanager
import (
"net/http"
"testing"
"time"
"github.com/prymitive/karma/internal/config"
)
type testCase struct {
config config.AlertmanagerConfig
shouldFail bool
}
var testCases = []testCase{
{
config: config.AlertmanagerConfig{
Cluster: "cluster",
Name: "name",
URI: "http://localhost:9093",
ExternalURI: "http://localhost:9093",
Timeout: time.Second * 30,
Proxy: false,
ReadOnly: false,
Headers: map[string]string{},
},
},
{
config: config.AlertmanagerConfig{
Cluster: "cluster",
Name: "proxy",
URI: "http://localhost:9094",
ExternalURI: "http://localhost:9094",
Timeout: time.Second * 30,
Proxy: true,
ReadOnly: false,
Headers: map[string]string{},
},
},
{
config: config.AlertmanagerConfig{
Cluster: "cluster",
Name: "name",
URI: "http://localhost:9095",
ExternalURI: "http://localhost:9095",
Timeout: time.Second * 30,
Proxy: false,
ReadOnly: true,
Headers: map[string]string{},
},
},
{
config: config.AlertmanagerConfig{
Cluster: "cluster",
Name: "name",
URI: "%gh&%ij",
ExternalURI: "http://localhost:9095",
Timeout: time.Second * 30,
Proxy: false,
ReadOnly: true,
Headers: map[string]string{},
},
shouldFail: true,
},
{
config: config.AlertmanagerConfig{
Cluster: "cluster",
Name: "name",
URI: "http://localhost:9095",
ExternalURI: "%gh&%ij",
Timeout: time.Second * 30,
Proxy: false,
ReadOnly: true,
Headers: map[string]string{},
},
shouldFail: true,
},
{
config: config.AlertmanagerConfig{
Cluster: "cluster",
Name: "name",
URI: "http://localhost:9093",
ExternalURI: "http://localhost:9093",
Timeout: time.Second * 30,
Proxy: false,
ReadOnly: false,
Headers: map[string]string{},
Healthcheck: config.AlertmanagerHealthcheck{
Filters: map[string][]string{
"prom1": {"@age>a"},
},
},
},
shouldFail: true,
},
{
config: config.AlertmanagerConfig{
Cluster: "cluster",
Name: "name",
URI: "http://localhost:9093",
ExternalURI: "http://localhost:9093",
Timeout: time.Second * 30,
Proxy: false,
ReadOnly: false,
Headers: map[string]string{},
Healthcheck: config.AlertmanagerHealthcheck{
Filters: map[string][]string{
"prom1": {" "},
},
},
},
shouldFail: true,
},
}
func saveUpstreams() map[string]*Alertmanager {
saved := make(map[string]*Alertmanager, len(upstreams))
for k, v := range upstreams {
saved[k] = v
}
return saved
}
func restoreUpstreams(saved map[string]*Alertmanager) {
upstreams = saved
}
func TestUnregisterAll(t *testing.T) {
// verifies that UnregisterAll removes all registered instances
saved := saveUpstreams()
defer restoreUpstreams(saved)
am, err := NewAlertmanager("cluster", "unreg-test", "http://localhost")
if err != nil {
t.Fatalf("NewAlertmanager failed: %s", err)
}
_ = RegisterAlertmanager(am)
UnregisterAll()
ams := GetAlertmanagers()
if len(ams) != 0 {
t.Errorf("Expected 0 alertmanagers after UnregisterAll, got %d", len(ams))
}
}
func TestRegisterAlertmanagerDuplicate(t *testing.T) {
// verifies that registering the same name twice returns an error
saved := saveUpstreams()
defer restoreUpstreams(saved)
UnregisterAll()
am, err := NewAlertmanager("cluster", "dup-test", "http://localhost")
if err != nil {
t.Fatalf("NewAlertmanager failed: %s", err)
}
if regErr := RegisterAlertmanager(am); regErr != nil {
t.Fatalf("First RegisterAlertmanager failed: %s", regErr)
}
am2, err := NewAlertmanager("cluster", "dup-test", "http://localhost:9094")
if err != nil {
t.Fatalf("NewAlertmanager failed: %s", err)
}
if err := RegisterAlertmanager(am2); err == nil {
t.Error("Second RegisterAlertmanager with same name should have returned an error")
}
}
func TestGetAlertmanagerByName(t *testing.T) {
saved := saveUpstreams()
defer restoreUpstreams(saved)
UnregisterAll()
am, err := NewAlertmanager("cluster", "lookup-test", "http://localhost")
if err != nil {
t.Fatalf("NewAlertmanager failed: %s", err)
}
_ = RegisterAlertmanager(am)
// verifies that a registered instance is found by name
found := GetAlertmanagerByName("lookup-test")
if found == nil {
t.Error("GetAlertmanagerByName returned nil for registered instance")
}
if found != nil && found.Name != "lookup-test" {
t.Errorf("GetAlertmanagerByName returned wrong instance: %s", found.Name)
}
// verifies that a non-existent name returns nil
notFound := GetAlertmanagerByName("does-not-exist")
if notFound != nil {
t.Errorf("GetAlertmanagerByName returned non-nil for unregistered name: %s", notFound.Name)
}
}
func TestWithHealthchecksVisible(t *testing.T) {
// verifies that WithHealthchecksVisible sets the healthchecksVisible field
am, err := NewAlertmanager("cluster", "hcv-test", "http://localhost",
WithHealthchecksVisible(true),
)
if err != nil {
t.Fatalf("NewAlertmanager failed: %s", err)
}
if !am.healthchecksVisible {
t.Error("Expected healthchecksVisible to be true")
}
}
func TestIsHealthy(t *testing.T) {
// verifies that a fresh alertmanager with no errors is healthy
am, err := NewAlertmanager("cluster", "healthy-test", "http://localhost")
if err != nil {
t.Fatalf("NewAlertmanager failed: %s", err)
}
if !am.IsHealthy() {
t.Error("Expected fresh alertmanager to be healthy")
}
// verifies that setting lastError makes it unhealthy
am.lock.Lock()
am.lastError = "something went wrong"
am.lock.Unlock()
if am.IsHealthy() {
t.Error("Expected alertmanager with lastError to be unhealthy")
}
}
func TestClusterMemberNames(t *testing.T) {
saved := saveUpstreams()
defer restoreUpstreams(saved)
UnregisterAll()
// register three alertmanagers: two in same cluster, one different
am1, _ := NewAlertmanager("prod", "am-1", "http://localhost:9091")
am2, _ := NewAlertmanager("prod", "am-2", "http://localhost:9092")
am3, _ := NewAlertmanager("staging", "am-3", "http://localhost:9093")
_ = RegisterAlertmanager(am1)
_ = RegisterAlertmanager(am2)
_ = RegisterAlertmanager(am3)
// verifies that am1 sees itself and am2 as cluster members, but not am3
members := am1.ClusterMemberNames()
if len(members) != 2 {
t.Errorf("Expected 2 cluster members, got %d: %v", len(members), members)
}
if members[0] != "am-1" || members[1] != "am-2" {
t.Errorf("Expected [am-1 am-2], got %v", members)
}
// verifies that am3 only sees itself
members3 := am3.ClusterMemberNames()
if len(members3) != 1 {
t.Errorf("Expected 1 cluster member for staging, got %d: %v", len(members3), members3)
}
if members3[0] != "am-3" {
t.Errorf("Expected [am-3], got %v", members3)
}
}
func TestOptions(t *testing.T) {
for _, tc := range testCases {
var httpTransport http.RoundTripper
var err error
if tc.config.TLS.CA != "" || tc.config.TLS.Cert != "" || tc.config.TLS.InsecureSkipVerify {
httpTransport, err = NewHTTPTransport(tc.config.TLS.CA, tc.config.TLS.Cert, tc.config.TLS.Key, tc.config.TLS.InsecureSkipVerify)
if err != nil {
t.Errorf("Failed to create HTTP transport for Alertmanager '%s' with URI '%s': %s", tc.config.Name, tc.config.URI, err)
}
}
am, err := NewAlertmanager(
tc.config.Cluster,
tc.config.Name,
tc.config.URI,
WithExternalURI(tc.config.ExternalURI),
WithRequestTimeout(tc.config.Timeout),
WithProxy(tc.config.Proxy),
WithReadOnly(tc.config.ReadOnly),
WithHTTPTransport(httpTransport), // we will pass a nil unless TLS.CA or TLS.Cert is set
WithHTTPHeaders(tc.config.Headers),
WithCORSCredentials(tc.config.CORS.Credentials),
WithHealthchecks(tc.config.Healthcheck.Filters),
)
didFail := err != nil
if didFail != tc.shouldFail {
t.Errorf("Alertmanager '%s' error mismatch, shouldFail=%v, err=%v", tc.config.Name, tc.shouldFail, err)
}
if err != nil {
t.Logf("Alertmanager '%s' returned an error: %v", tc.config.Name, err)
continue
}
if am.Name != tc.config.Name {
t.Errorf("AlertmanagerConfig with name '%s' returned Alertmanager with name '%s'", tc.config.Name, am.Name)
}
if am.URI != tc.config.URI {
t.Errorf("AlertmanagerConfig with name '%s' and URI '%s' returned Alertmanager with URI '%s'", tc.config.Name, tc.config.URI, am.URI)
}
if am.ProxyRequests != tc.config.Proxy {
t.Errorf("AlertmanagerConfig with name '%s' and proxy '%v' returned Alertmanager with proxy '%v'", tc.config.Name, tc.config.Proxy, am.ProxyRequests)
}
if am.ReadOnly != tc.config.ReadOnly {
t.Errorf("AlertmanagerConfig with name '%s' and readonly '%v' returned Alertmanager with readonly '%v'", tc.config.Name, tc.config.ReadOnly, am.ReadOnly)
}
if am.CORSCredentials != tc.config.CORS.Credentials {
t.Errorf("AlertmanagerConfig with name '%s' and cors:credentials '%v' returned Alertmanager with cors:credentials '%v'", tc.config.Name, tc.config.CORS.Credentials, am.CORSCredentials)
}
}
}