Skip to content

Commit 090e4e6

Browse files
authored
CLOUDP-136887: Add alert configuration feature (#717)
* Add alert configuration feature
1 parent 8117a87 commit 090e4e6

File tree

14 files changed

+1774
-21
lines changed

14 files changed

+1774
-21
lines changed

.github/workflows/test-e2e.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ jobs:
7373
k8s: [ "v1.21.1-kind" ] # <K8sGitVersion>-<Platform>
7474
test:
7575
[
76+
"alert-config",
7677
"auditing",
7778
"bundle-test",
7879
"cloud-access-role",

config/crd/bases/atlas.mongodb.com_atlasprojects.yaml

Lines changed: 466 additions & 0 deletions
Large diffs are not rendered by default.

pkg/api/v1/alert_configurations.go

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
package v1
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
7+
"go.mongodb.org/atlas/mongodbatlas"
8+
"go.uber.org/zap"
9+
10+
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/util"
11+
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/compat"
12+
)
13+
14+
type AlertConfiguration struct {
15+
// If omitted, the configuration is disabled.
16+
Enabled bool `json:"enabled,omitempty"`
17+
// The type of event that will trigger an alert.
18+
EventTypeName string `json:"eventTypeName,omitempty"`
19+
// You can filter using the matchers array only when the EventTypeName specifies an event for a host, replica set, or sharded cluster.
20+
Matchers []Matcher `json:"matchers,omitempty"`
21+
// Threshold causes an alert to be triggered.
22+
Threshold *Threshold `json:"threshold,omitempty"`
23+
// Notifications are sending when an alert condition is detected.
24+
Notifications []Notification `json:"notifications,omitempty"`
25+
// MetricThreshold causes an alert to be triggered.
26+
MetricThreshold *MetricThreshold `json:"metricThreshold,omitempty"`
27+
}
28+
29+
func (in *AlertConfiguration) ToAtlas() (*mongodbatlas.AlertConfiguration, error) {
30+
if in == nil {
31+
return nil, nil
32+
}
33+
// Some field can be converted directly
34+
result := &mongodbatlas.AlertConfiguration{
35+
Enabled: &in.Enabled,
36+
EventTypeName: in.EventTypeName,
37+
}
38+
39+
for _, m := range in.Matchers {
40+
matcher := mongodbatlas.Matcher{
41+
FieldName: m.FieldName,
42+
Operator: m.Operator,
43+
Value: m.Value,
44+
}
45+
result.Matchers = append(result.Matchers, matcher)
46+
}
47+
48+
for _, n := range in.Notifications {
49+
notification := &mongodbatlas.Notification{}
50+
err := compat.JSONCopy(&notification, n)
51+
if err != nil {
52+
return nil, err
53+
}
54+
result.Notifications = append(result.Notifications, *notification)
55+
}
56+
57+
// Some fields require special conversion
58+
tr, err := in.Threshold.ToAtlas()
59+
if err != nil {
60+
return nil, err
61+
}
62+
result.Threshold = tr
63+
metricThreshold, err := in.MetricThreshold.ToAtlas()
64+
if err != nil {
65+
return nil, err
66+
}
67+
result.MetricThreshold = metricThreshold
68+
return result, err
69+
}
70+
71+
type Matcher struct {
72+
// Name of the field in the target object to match on.
73+
FieldName string `json:"fieldName,omitempty"`
74+
// The operator to test the field’s value.
75+
Operator string `json:"operator,omitempty"`
76+
// Value to test with the specified operator.
77+
Value string `json:"value,omitempty"`
78+
}
79+
80+
func (in *Matcher) IsEqual(matcher mongodbatlas.Matcher) bool {
81+
if in == nil {
82+
return false
83+
}
84+
return in.FieldName == matcher.FieldName &&
85+
in.Operator == matcher.Operator &&
86+
in.Value == matcher.Value
87+
}
88+
89+
type Threshold struct {
90+
// Operator to apply when checking the current metric value against the threshold value. it accepts the following values: GREATER_THAN, LESS_THAN
91+
Operator string `json:"operator,omitempty"`
92+
// The units for the threshold value
93+
Units string `json:"units,omitempty"`
94+
// Threshold value outside which an alert will be triggered.
95+
Threshold string `json:"threshold,omitempty"`
96+
}
97+
98+
func (in *Threshold) IsEqual(threshold *mongodbatlas.Threshold) bool {
99+
logger := zap.NewExample().Sugar()
100+
if in == nil {
101+
return threshold == nil
102+
}
103+
if threshold == nil {
104+
return false
105+
}
106+
logger.Debugf("threshold: %v", threshold)
107+
if in.Operator != threshold.Operator {
108+
logger.Debugf("operator: %s != %s", in.Operator, threshold.Operator)
109+
return false
110+
}
111+
if in.Units != threshold.Units {
112+
logger.Debugf("units: %s != %s", in.Units, threshold.Units)
113+
return false
114+
}
115+
if in.Threshold != strconv.FormatFloat(threshold.Threshold, 'f', -1, 64) {
116+
logger.Debugf("threshold: %s != %s", in.Threshold, strconv.FormatFloat(threshold.Threshold, 'f', -1, 64))
117+
return false
118+
}
119+
return true
120+
}
121+
122+
func (in *Threshold) ToAtlas() (*mongodbatlas.Threshold, error) {
123+
if in == nil {
124+
return nil, nil
125+
}
126+
tr, err := strconv.ParseFloat(in.Threshold, 64)
127+
if err != nil {
128+
return nil, fmt.Errorf("failed to parse threshold value: %w. should be float", err)
129+
}
130+
result := &mongodbatlas.Threshold{
131+
Operator: in.Operator,
132+
Units: in.Units,
133+
Threshold: tr,
134+
}
135+
return result, nil
136+
}
137+
138+
type Notification struct {
139+
// Slack API token or Bot token. Populated for the SLACK notifications type. If the token later becomes invalid, Atlas sends an email to the project owner and eventually removes the token.
140+
APIToken string `json:"apiToken,omitempty"`
141+
// Slack channel name. Populated for the SLACK notifications type.
142+
ChannelName string `json:"channelName,omitempty"`
143+
// Datadog API Key. Found in the Datadog dashboard. Populated for the DATADOG notifications type.
144+
DatadogAPIKey string `json:"datadogApiKey,omitempty"`
145+
// Region that indicates which API URL to use
146+
DatadogRegion string `json:"datadogRegion,omitempty"`
147+
// Number of minutes to wait after an alert condition is detected before sending out the first notification.
148+
DelayMin *int `json:"delayMin,omitempty"`
149+
// Email address to which alert notifications are sent. Populated for the EMAIL notifications type.
150+
EmailAddress string `json:"emailAddress,omitempty"`
151+
// Flag indicating if email notifications should be sent. Populated for ORG, GROUP, and USER notifications types.
152+
EmailEnabled *bool `json:"emailEnabled,omitempty"`
153+
// The Flowdock personal API token. Populated for the FLOWDOCK notifications type. If the token later becomes invalid, Atlas sends an email to the project owner and eventually removes the token.
154+
FlowdockAPIToken string `json:"flowdockApiToken,omitempty"`
155+
// Flowdock flow namse in lower-case letters.
156+
FlowName string `json:"flowName,omitempty"`
157+
// Number of minutes to wait between successive notifications for unacknowledged alerts that are not resolved.
158+
IntervalMin int `json:"intervalMin,omitempty"`
159+
// Mobile number to which alert notifications are sent. Populated for the SMS notifications type.
160+
MobileNumber string `json:"mobileNumber,omitempty"`
161+
// Opsgenie API Key. Populated for the OPS_GENIE notifications type. If the key later becomes invalid, Atlas sends an email to the project owner and eventually removes the token.
162+
OpsGenieAPIKey string `json:"opsGenieApiKey,omitempty"`
163+
// Region that indicates which API URL to use.
164+
OpsGenieRegion string `json:"opsGenieRegion,omitempty"`
165+
// Flowdock organization name in lower-case letters. This is the name that appears after www.flowdock.com/app/ in the URL string. Populated for the FLOWDOCK notifications type.
166+
OrgName string `json:"orgName,omitempty"`
167+
// PagerDuty service key. Populated for the PAGER_DUTY notifications type. If the key later becomes invalid, Atlas sends an email to the project owner and eventually removes the key.
168+
ServiceKey string `json:"serviceKey,omitempty"`
169+
// Flag indicating if text message notifications should be sent. Populated for ORG, GROUP, and USER notifications types.
170+
SMSEnabled *bool `json:"smsEnabled,omitempty"`
171+
// Unique identifier of a team.
172+
TeamID string `json:"teamId,omitempty"`
173+
// Label for the team that receives this notification.
174+
TeamName string `json:"teamName,omitempty"`
175+
// Type of alert notification.
176+
TypeName string `json:"typeName,omitempty"`
177+
// Name of the Atlas user to which to send notifications. Only a user in the project that owns the alert configuration is allowed here. Populated for the USER notifications type.
178+
Username string `json:"username,omitempty"`
179+
// VictorOps API key. Populated for the VICTOR_OPS notifications type. If the key later becomes invalid, Atlas sends an email to the project owner and eventually removes the key.
180+
VictorOpsAPIKey string `json:"victorOpsApiKey,omitempty"`
181+
// VictorOps routing key. Populated for the VICTOR_OPS notifications type. If the key later becomes invalid, Atlas sends an email to the project owner and eventually removes the key.
182+
VictorOpsRoutingKey string `json:"victorOpsRoutingKey,omitempty"`
183+
// The following roles grant privileges within a project.
184+
Roles []string `json:"roles,omitempty"`
185+
}
186+
187+
func (in *Notification) IsEqual(notification mongodbatlas.Notification) bool {
188+
if in == nil {
189+
return false
190+
}
191+
if in.APIToken != notification.APIToken ||
192+
in.ChannelName != notification.ChannelName ||
193+
in.DatadogAPIKey != notification.DatadogAPIKey ||
194+
in.DatadogRegion != notification.DatadogRegion ||
195+
!util.PtrValuesEqual(in.DelayMin, notification.DelayMin) ||
196+
in.EmailAddress != notification.EmailAddress ||
197+
!util.PtrValuesEqual(in.EmailEnabled, notification.EmailEnabled) ||
198+
in.FlowdockAPIToken != notification.FlowdockAPIToken ||
199+
in.FlowName != notification.FlowName ||
200+
in.IntervalMin != notification.IntervalMin ||
201+
in.MobileNumber != notification.MobileNumber ||
202+
in.OpsGenieAPIKey != notification.OpsGenieAPIKey ||
203+
in.OpsGenieRegion != notification.OpsGenieRegion ||
204+
in.OrgName != notification.OrgName ||
205+
in.ServiceKey != notification.ServiceKey ||
206+
!util.PtrValuesEqual(in.SMSEnabled, notification.SMSEnabled) ||
207+
in.TeamID != notification.TeamID ||
208+
in.TeamName != notification.TeamName ||
209+
in.TypeName != notification.TypeName ||
210+
in.Username != notification.Username ||
211+
in.VictorOpsAPIKey != notification.VictorOpsAPIKey ||
212+
in.VictorOpsRoutingKey != notification.VictorOpsRoutingKey {
213+
return false
214+
}
215+
216+
if !util.IsEqualWithoutOrder(in.Roles, notification.Roles) {
217+
return false
218+
}
219+
220+
return true
221+
}
222+
223+
// MetricThreshold causes an alert to be triggered. Required if "eventTypeName" : "OUTSIDE_METRIC_THRESHOLD".
224+
type MetricThreshold struct {
225+
// Name of the metric to check.
226+
MetricName string `json:"metricName,omitempty"`
227+
// Operator to apply when checking the current metric value against the threshold value.
228+
Operator string `json:"operator,omitempty"`
229+
// Threshold value outside which an alert will be triggered.
230+
Threshold string `json:"threshold"`
231+
// The units for the threshold value.
232+
Units string `json:"units,omitempty"`
233+
// This must be set to AVERAGE. Atlas computes the current metric value as an average.
234+
Mode string `json:"mode,omitempty"`
235+
}
236+
237+
func (in *MetricThreshold) IsEqual(threshold *mongodbatlas.MetricThreshold) bool {
238+
if in == nil {
239+
return threshold == nil
240+
}
241+
if threshold == nil {
242+
return false
243+
}
244+
return in.MetricName == threshold.MetricName &&
245+
in.Operator == threshold.Operator &&
246+
in.Threshold == strconv.FormatFloat(threshold.Threshold, 'f', -1, 64) &&
247+
in.Units == threshold.Units &&
248+
in.Mode == threshold.Mode
249+
}
250+
251+
func (in *MetricThreshold) ToAtlas() (*mongodbatlas.MetricThreshold, error) {
252+
if in == nil {
253+
return nil, nil
254+
}
255+
tr, err := strconv.ParseFloat(in.Threshold, 64)
256+
if err != nil {
257+
return nil, fmt.Errorf("failed to parse threshold value: %w. should be float", err)
258+
}
259+
result := &mongodbatlas.MetricThreshold{
260+
MetricName: in.MetricName,
261+
Operator: in.Operator,
262+
Threshold: tr,
263+
Units: in.Units,
264+
Mode: in.Mode,
265+
}
266+
return result, nil
267+
}

pkg/api/v1/atlasproject_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ type AtlasProjectSpec struct {
6565
// CloudProviderAccessRoles is a list of Cloud Provider Access Roles configured for the current Project.
6666
CloudProviderAccessRoles []CloudProviderAccessRole `json:"cloudProviderAccessRoles,omitempty"`
6767

68+
// AlertConfiguration is a list of Alert Configurations configured for the current Project.
69+
AlertConfigurations []AlertConfiguration `json:"alertConfigurations,omitempty"`
70+
71+
// AlertConfigurationSyncEnabled is a flag that enables/disables Alert Configurations sync for the current Project.
72+
// If true - project alert configurations will be synced according to AlertConfigurations.
73+
// If not - alert configurations will not be modified by the operator. They can be managed through API, cli, UI.
74+
//kubebuilder:default:=false
75+
// +optional
76+
AlertConfigurationSyncEnabled bool `json:"alertConfigurationSyncEnabled,omitempty"`
77+
6878
// NetworkPeers is a list of Network Peers configured for the current Project.
6979
NetworkPeers []NetworkPeer `json:"networkPeers,omitempty"`
7080

0 commit comments

Comments
 (0)