From 0ef1757537003c419df3909b925d9f1d76f064a9 Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Tue, 27 Feb 2024 10:55:58 +0100 Subject: [PATCH 1/3] bus: filter by alert severity --- alerts/alerts.go | 55 +++++++++++++++++++++----------- bus/bus.go | 9 +++++- bus/client/alerts.go | 3 ++ internal/testing/cluster_test.go | 38 ++++++++++++++++++++++ 4 files changed, 86 insertions(+), 19 deletions(-) diff --git a/alerts/alerts.go b/alerts/alerts.go index f11004dbe..a14d460b6 100644 --- a/alerts/alerts.go +++ b/alerts/alerts.go @@ -66,14 +66,19 @@ type ( } AlertsOpts struct { - Offset int - Limit int + Offset int + Limit int + Severity Severity } AlertsResponse struct { - Alerts []Alert `json:"alerts"` - HasMore bool `json:"hasMore"` - Total int `json:"total"` + Alerts []Alert `json:"alerts"` + HasMore bool `json:"hasMore"` + Total int `json:"total"` + TotalInfo int `json:"totalInfo"` + TotalWarning int `json:"totalWarning"` + TotalError int `json:"totalError"` + TotalCritical int `json:"totalCritical"` } ) @@ -93,15 +98,8 @@ func (s Severity) String() string { } } -// MarshalJSON implements the json.Marshaler interface. -func (s Severity) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`%q`, s.String())), nil -} - -// UnmarshalJSON implements the json.Unmarshaler interface. -func (s *Severity) UnmarshalJSON(b []byte) error { - status := strings.Trim(string(b), `"`) - switch status { +func (s *Severity) LoadString(str string) error { + switch str { case severityInfoStr: *s = SeverityInfo case severityWarningStr: @@ -111,11 +109,21 @@ func (s *Severity) UnmarshalJSON(b []byte) error { case severityCriticalStr: *s = SeverityCritical default: - return fmt.Errorf("unrecognized severity: %v", status) + return fmt.Errorf("unrecognized severity: %v", str) } return nil } +// MarshalJSON implements the json.Marshaler interface. +func (s Severity) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`%q`, s.String())), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (s *Severity) UnmarshalJSON(b []byte) error { + return s.LoadString(strings.Trim(string(b), `"`)) +} + // RegisterAlert implements the Alerter interface. func (m *Manager) RegisterAlert(ctx context.Context, alert Alert) error { if alert.ID == (types.Hash256{}) { @@ -176,9 +184,7 @@ func (m *Manager) Alerts(_ context.Context, opts AlertsOpts) (AlertsResponse, er defer m.mu.Unlock() offset, limit := opts.Offset, opts.Limit - resp := AlertsResponse{ - Total: len(m.alerts), - } + resp := AlertsResponse{} if offset >= len(m.alerts) { return resp, nil @@ -188,6 +194,19 @@ func (m *Manager) Alerts(_ context.Context, opts AlertsOpts) (AlertsResponse, er alerts := make([]Alert, 0, len(m.alerts)) for _, a := range m.alerts { + resp.Total++ + if a.Severity == SeverityInfo { + resp.TotalInfo++ + } else if a.Severity == SeverityWarning { + resp.TotalWarning++ + } else if a.Severity == SeverityError { + resp.TotalError++ + } else if a.Severity == SeverityCritical { + resp.TotalCritical++ + } + if opts.Severity != 0 && a.Severity != opts.Severity { + continue // filter by severity + } alerts = append(alerts, a) } sort.Slice(alerts, func(i, j int) bool { diff --git a/bus/bus.go b/bus/bus.go index e7e6ddaac..4106bc231 100644 --- a/bus/bus.go +++ b/bus/bus.go @@ -1739,6 +1739,7 @@ func (b *bus) handleGETAlerts(jc jape.Context) { return } offset, limit := 0, -1 + var severity alerts.Severity if jc.DecodeForm("offset", &offset) != nil { return } else if jc.DecodeForm("limit", &limit) != nil { @@ -1746,8 +1747,14 @@ func (b *bus) handleGETAlerts(jc jape.Context) { } else if offset < 0 { jc.Error(errors.New("offset must be non-negative"), http.StatusBadRequest) return + } else if jc.DecodeForm("severity", &severity) != nil { + return } - ar, err := b.alertMgr.Alerts(jc.Request.Context(), alerts.AlertsOpts{Offset: offset, Limit: limit}) + ar, err := b.alertMgr.Alerts(jc.Request.Context(), alerts.AlertsOpts{ + Offset: offset, + Limit: limit, + Severity: severity, + }) if jc.Check("failed to fetch alerts", err) != nil { return } diff --git a/bus/client/alerts.go b/bus/client/alerts.go index 7eceaeaed..28c3b9a84 100644 --- a/bus/client/alerts.go +++ b/bus/client/alerts.go @@ -16,6 +16,9 @@ func (c *Client) Alerts(ctx context.Context, opts alerts.AlertsOpts) (resp alert if opts.Limit != 0 { values.Set("limit", fmt.Sprint(opts.Limit)) } + if opts.Severity != 0 { + values.Set("severity", opts.Severity.String()) + } err = c.c.WithContext(ctx).GET("/alerts?"+values.Encode(), &resp) return } diff --git a/internal/testing/cluster_test.go b/internal/testing/cluster_test.go index f30a0906a..69b318f66 100644 --- a/internal/testing/cluster_test.go +++ b/internal/testing/cluster_test.go @@ -1974,6 +1974,44 @@ func TestAlerts(t *testing.T) { if len(foundAlerts) != 1 || foundAlerts[0].ID != alert2.ID { t.Fatal("wrong alert") } + + // register more alerts + for severity := alerts.SeverityInfo; severity <= alerts.SeverityCritical; severity++ { + for j := 0; j < 3*int(severity); j++ { + tt.OK(b.RegisterAlert(context.Background(), alerts.Alert{ + ID: frand.Entropy256(), + Severity: severity, + Message: "test", + Data: map[string]interface{}{ + "origin": "test", + }, + Timestamp: time.Now(), + })) + } + } + for severity := alerts.SeverityInfo; severity <= alerts.SeverityCritical; severity++ { + ar, err = b.Alerts(context.Background(), alerts.AlertsOpts{Severity: severity}) + tt.OK(err) + if ar.Total != 32 { + t.Fatal("expected 32 alerts", ar.Total) + } else if ar.TotalInfo != 3 { + t.Fatal("expected 3 info alerts", ar.TotalInfo) + } else if ar.TotalWarning != 6 { + t.Fatal("expected 6 warning alerts", ar.TotalWarning) + } else if ar.TotalError != 9 { + t.Fatal("expected 9 error alerts", ar.TotalError) + } else if ar.TotalCritical != 14 { + t.Fatal("expected 14 critical alerts", ar.TotalCritical) + } else if severity == alerts.SeverityInfo && len(ar.Alerts) != ar.TotalInfo { + t.Fatalf("expected %v info alerts, got %v", ar.TotalInfo, len(ar.Alerts)) + } else if severity == alerts.SeverityWarning && len(ar.Alerts) != ar.TotalWarning { + t.Fatalf("expected %v warning alerts, got %v", ar.TotalWarning, len(ar.Alerts)) + } else if severity == alerts.SeverityError && len(ar.Alerts) != ar.TotalError { + t.Fatalf("expected %v error alerts, got %v", ar.TotalError, len(ar.Alerts)) + } else if severity == alerts.SeverityCritical && len(ar.Alerts) != ar.TotalCritical { + t.Fatalf("expected %v critical alerts, got %v", ar.TotalCritical, len(ar.Alerts)) + } + } } func TestMultipartUploads(t *testing.T) { From 7c5b3c91efab2acf8f4b90588dace1240d00c0f7 Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Tue, 27 Feb 2024 13:56:29 +0100 Subject: [PATCH 2/3] bus: breakdown totals --- alerts/alerts.go | 28 ++++++++++++++----------- internal/testing/cluster_test.go | 36 ++++++++++++++++---------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/alerts/alerts.go b/alerts/alerts.go index a14d460b6..1ebebbc80 100644 --- a/alerts/alerts.go +++ b/alerts/alerts.go @@ -72,16 +72,21 @@ type ( } AlertsResponse struct { - Alerts []Alert `json:"alerts"` - HasMore bool `json:"hasMore"` - Total int `json:"total"` - TotalInfo int `json:"totalInfo"` - TotalWarning int `json:"totalWarning"` - TotalError int `json:"totalError"` - TotalCritical int `json:"totalCritical"` + Alerts []Alert `json:"alerts"` + HasMore bool `json:"hasMore"` + Totals struct { + Info int `json:"info"` + Warning int `json:"warning"` + Error int `json:"error"` + Critical int `json:"critical"` + } `json:"total"` } ) +func (ar AlertsResponse) Total() int { + return ar.Totals.Info + ar.Totals.Warning + ar.Totals.Error + ar.Totals.Critical +} + // String implements the fmt.Stringer interface. func (s Severity) String() string { switch s { @@ -194,15 +199,14 @@ func (m *Manager) Alerts(_ context.Context, opts AlertsOpts) (AlertsResponse, er alerts := make([]Alert, 0, len(m.alerts)) for _, a := range m.alerts { - resp.Total++ if a.Severity == SeverityInfo { - resp.TotalInfo++ + resp.Totals.Info++ } else if a.Severity == SeverityWarning { - resp.TotalWarning++ + resp.Totals.Warning++ } else if a.Severity == SeverityError { - resp.TotalError++ + resp.Totals.Error++ } else if a.Severity == SeverityCritical { - resp.TotalCritical++ + resp.Totals.Critical++ } if opts.Severity != 0 && a.Severity != opts.Severity { continue // filter by severity diff --git a/internal/testing/cluster_test.go b/internal/testing/cluster_test.go index 69b318f66..6b5f88769 100644 --- a/internal/testing/cluster_test.go +++ b/internal/testing/cluster_test.go @@ -1992,24 +1992,24 @@ func TestAlerts(t *testing.T) { for severity := alerts.SeverityInfo; severity <= alerts.SeverityCritical; severity++ { ar, err = b.Alerts(context.Background(), alerts.AlertsOpts{Severity: severity}) tt.OK(err) - if ar.Total != 32 { - t.Fatal("expected 32 alerts", ar.Total) - } else if ar.TotalInfo != 3 { - t.Fatal("expected 3 info alerts", ar.TotalInfo) - } else if ar.TotalWarning != 6 { - t.Fatal("expected 6 warning alerts", ar.TotalWarning) - } else if ar.TotalError != 9 { - t.Fatal("expected 9 error alerts", ar.TotalError) - } else if ar.TotalCritical != 14 { - t.Fatal("expected 14 critical alerts", ar.TotalCritical) - } else if severity == alerts.SeverityInfo && len(ar.Alerts) != ar.TotalInfo { - t.Fatalf("expected %v info alerts, got %v", ar.TotalInfo, len(ar.Alerts)) - } else if severity == alerts.SeverityWarning && len(ar.Alerts) != ar.TotalWarning { - t.Fatalf("expected %v warning alerts, got %v", ar.TotalWarning, len(ar.Alerts)) - } else if severity == alerts.SeverityError && len(ar.Alerts) != ar.TotalError { - t.Fatalf("expected %v error alerts, got %v", ar.TotalError, len(ar.Alerts)) - } else if severity == alerts.SeverityCritical && len(ar.Alerts) != ar.TotalCritical { - t.Fatalf("expected %v critical alerts, got %v", ar.TotalCritical, len(ar.Alerts)) + if ar.Total() != 32 { + t.Fatal("expected 32 alerts", ar.Total()) + } else if ar.Totals.Info != 3 { + t.Fatal("expected 3 info alerts", ar.Totals.Info) + } else if ar.Totals.Warning != 6 { + t.Fatal("expected 6 warning alerts", ar.Totals.Warning) + } else if ar.Totals.Error != 9 { + t.Fatal("expected 9 error alerts", ar.Totals.Error) + } else if ar.Totals.Critical != 14 { + t.Fatal("expected 14 critical alerts", ar.Totals.Critical) + } else if severity == alerts.SeverityInfo && len(ar.Alerts) != ar.Totals.Info { + t.Fatalf("expected %v info alerts, got %v", ar.Totals.Info, len(ar.Alerts)) + } else if severity == alerts.SeverityWarning && len(ar.Alerts) != ar.Totals.Warning { + t.Fatalf("expected %v warning alerts, got %v", ar.Totals.Warning, len(ar.Alerts)) + } else if severity == alerts.SeverityError && len(ar.Alerts) != ar.Totals.Error { + t.Fatalf("expected %v error alerts, got %v", ar.Totals.Error, len(ar.Alerts)) + } else if severity == alerts.SeverityCritical && len(ar.Alerts) != ar.Totals.Critical { + t.Fatalf("expected %v critical alerts, got %v", ar.Totals.Critical, len(ar.Alerts)) } } } From 6db1e38cf0f415f330c6ba54a24682af69d1dc16 Mon Sep 17 00:00:00 2001 From: Christopher Schinnerl Date: Tue, 27 Feb 2024 13:58:47 +0100 Subject: [PATCH 3/3] Update alerts/alerts.go Co-authored-by: Peter-Jan Brone --- alerts/alerts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alerts/alerts.go b/alerts/alerts.go index 1ebebbc80..6b009360d 100644 --- a/alerts/alerts.go +++ b/alerts/alerts.go @@ -79,7 +79,7 @@ type ( Warning int `json:"warning"` Error int `json:"error"` Critical int `json:"critical"` - } `json:"total"` + } `json:"totals"` } )