diff --git a/monitor_alert_definitions.go b/monitor_alert_definitions.go index 93ef0157d..a15188ef6 100644 --- a/monitor_alert_definitions.go +++ b/monitor_alert_definitions.go @@ -67,6 +67,7 @@ type AlertDefinition struct { Scope AlertDefinitionScope `json:"scope"` Regions []string `json:"regions"` Entities AlertDefinitionEntities `json:"entities"` + GroupBy []string `json:"group_by"` } // TriggerConditions represents the trigger conditions for an alert. @@ -167,6 +168,7 @@ type AlertDefinitionCreateOptions struct { Description *string `json:"description,omitzero"` Scope AlertDefinitionScope `json:"scope,omitzero"` Regions []string `json:"regions,omitzero"` + GroupBy []string `json:"group_by,omitzero"` } // AlertDefinitionUpdateOptions are the options used to update an alert definition. @@ -180,6 +182,21 @@ type AlertDefinitionUpdateOptions struct { Description *string `json:"description,omitzero"` Status *AlertDefinitionStatus `json:"status,omitzero"` Regions []string `json:"regions,omitzero"` + GroupBy []string `json:"group_by,omitzero"` +} + +// AlertDefinitionCloneOptions are the options used to clone an existing alert definition. +type AlertDefinitionCloneOptions struct { + Label string `json:"label"` + Severity *int `json:"severity,omitzero"` + ChannelIDs []int `json:"channel_ids,omitzero"` + RuleCriteria *RuleCriteriaOptions `json:"rule_criteria,omitzero"` + TriggerConditions *TriggerConditions `json:"trigger_conditions,omitzero"` + EntityIDs []string `json:"entity_ids,omitzero"` + Description *string `json:"description,omitzero"` + Scope AlertDefinitionScope `json:"scope,omitzero"` + Regions []string `json:"regions,omitzero"` + GroupBy []string `json:"group_by,omitzero"` } // UnmarshalJSON implements the json.Unmarshaler interface @@ -307,3 +324,14 @@ func (c *Client) ListMonitorAlertDefinitionEntities( e := formatAPIPath("monitor/services/%s/alert-definitions/%d/entities", serviceType, alertID) return getPaginatedResults[AlertDefinitionEntity](ctx, c, e, opts) } + +// CloneMonitorAlertDefinition clones an ACLP Monitor Alert Definition. +func (c *Client) CloneMonitorAlertDefinition( + ctx context.Context, + serviceType string, + alertID int, + opts AlertDefinitionCloneOptions, +) (*AlertDefinition, error) { + e := formatAPIPath("monitor/services/%s/alert-definitions/%d/clone", serviceType, alertID) + return doPOSTRequest[AlertDefinition](ctx, c, e, opts) +} diff --git a/test/integration/fixtures/TestMonitorAlertDefinition.yaml b/test/integration/fixtures/TestMonitorAlertDefinition.yaml index 8ae0739a2..f93859117 100644 --- a/test/integration/fixtures/TestMonitorAlertDefinition.yaml +++ b/test/integration/fixtures/TestMonitorAlertDefinition.yaml @@ -14,54 +14,31 @@ interactions: url: https://api.linode.com/v4beta/monitor/alert-definitions?page=1 method: GET response: - body: '{"pages": 1, "page": 1, "results": 4, "data": [{"id": 10000, "label": + body: '{"pages": 1, "page": 1, "results": 2, "data": [{"id": 10000, "label": "High Memory Usage Plan Dedicated", "description": "Alert triggers when dedicated - plan nodes consistently reach critical memory usage, risking application performance - degradation.", "service_type": "dbaas", "type": "system", "scope": "entity", - "class": "dedicated", "regions": [], "status": "enabled", "severity": - 2, "rule_criteria": {"rules": [{"label": "Memory Usage", "metric": "memory_usage", - "unit": "percent", "aggregate_function": "avg", "operator": "gt", "threshold": - 95, "dimension_filters": []}]}, "alert_channels": [{"id": 10000, "label": "Read-Write - Channel", "url": "/monitor/alert-channels/10000", "type": "alert-channels"}], - "trigger_conditions": {"criteria_condition": "ALL", "polling_interval_seconds": - 300, "evaluation_period_seconds": 300, "trigger_occurrences": 3}, "created": - "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": "system", - "updated_by": "system", "entities": {"url": "/monitor/services/dbaas/alert-definitions/10000/entities", - "has_more_resources": false, "count": 5}}, {"id": 10001, "label": "High Memory - Usage Plan Shared", "description": "Alert triggers when shared plan nodes consistently - reach critical memory usage, risking application performance degradation.", - "service_type": "dbaas", "type": "system", "scope": "entity", "class": "shared", - "regions": [], "status": "enabled", "severity": 2, "rule_criteria": {"rules": [{"label": "Memory Usage", + plan nodes consistently reach critical memory usage.", "service_type": "dbaas", + "type": "system", "scope": "entity", "class": "dedicated", "regions": [], "status": + "enabled", "severity": 2, "rule_criteria": {"rules": [{"label": "Memory Usage", "metric": "memory_usage", "unit": "percent", "aggregate_function": "avg", "operator": - "gt", "threshold": 90, "dimension_filters": []}]}, "alert_channels": [{"id": + "gt", "threshold": 95, "dimension_filters": []}]}, "alert_channels": [{"id": 10000, "label": "Read-Write Channel", "url": "/monitor/alert-channels/10000", "type": "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", "polling_interval_seconds": 300, "evaluation_period_seconds": 300, "trigger_occurrences": 3}, "created": "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": - "system", "updated_by": "system", "entities": {"url": "/monitor/services/dbaas/alert-definitions/10001/entities", - "has_more_resources": false, "count": 2}}, {"id": 11001, "label": "test_alert_logs_prod", - "description": "some desc1", "service_type": "logs", "type": "user", "scope": - "entity", "class": null, "regions": [], "status": "enabled", "severity": 2, "rule_criteria": {"rules": [{"label": - "Successful Upload Count", "metric": "success_upload_count", "unit": "count", - "aggregate_function": "sum", "operator": "lt", "threshold": 100000, "dimension_filters": - []}]}, "alert_channels": [{"id": 10004, "label": "Alert Channel", "url": + "system", "updated_by": "system", "group_by": ["entity_id"], "entities": {"url": + "/monitor/services/dbaas/alert-definitions/10000/entities", "has_more_resources": + false, "count": 5}}, {"id": 11184, "label": "Test alert", "description": + "Test alert", "service_type": "dbaas", "type": "user", "scope": + "entity", "class": null, "regions": [], "status": "disabled", "severity": 3, + "rule_criteria": {"rules": [{"label": "CPU Usage", "metric": "cpu_usage", "unit": + "percent", "aggregate_function": "avg", "operator": "gt", "threshold": 0, "dimension_filters": + []}]}, "alert_channels": [{"id": 10004, "label": "Team Channel", "url": "/monitor/alert-channels/10004", "type": "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", "polling_interval_seconds": 300, "evaluation_period_seconds": 300, "trigger_occurrences": 1}, "created": "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": "tester", "updated_by": - "tester", "entities": {"url": "/monitor/services/logs/alert-definitions/11001/entities", - "has_more_resources": false, "count": 1}}, {"id": 11021, "label": "Test Alert", "description": "Test Alert", "service_type": - "dbaas", "type": "user", "scope": "entity", "class": null, "regions": [], "status": - "enabled", "severity": 1, "rule_criteria": {"rules": [{"label": "Memory Usage", "metric": - "memory_usage", "unit": "percent", "aggregate_function": "sum", "operator": - "gt", "threshold": 100, "dimension_filters": []}]}, "alert_channels": [{"id": - 10004, "label": "Alert Channel", "url": "/monitor/alert-channels/10004", - "type": "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", - "polling_interval_seconds": 1800, "evaluation_period_seconds": 900, "trigger_occurrences": - 10}, "created": "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": - "tester", "updated_by": "tester", "entities": - {"url": "/monitor/services/dbaas/alert-definitions/11021/entities", "has_more_resources": - false, "count": 2}}]}' + "tester", "group_by": ["entity_id"], "entities": {"url": "/monitor/services/dbaas/alert-definitions/11184/entities", + "has_more_resources": false, "count": 1}}]}' headers: Access-Control-Allow-Credentials: - "true" @@ -84,7 +61,7 @@ interactions: Content-Type: - application/json Expires: - - Mon, 27 Apr 2026 12:32:17 GMT + - Thu, 04 Jun 2026 10:41:16 GMT Pragma: - no-cache Strict-Transport-Security: @@ -114,7 +91,7 @@ interactions: duration: "" - request: body: '{"label":"go-test-alert-definition-create","severity":2,"channel_ids":[10000],"rule_criteria":{"rules":[{"aggregate_function":"avg","dimension_filters":[{"dimension_label":"node_type","operator":"eq","value":"primary"}],"metric":"memory_usage","operator":"gt","threshold":90}]},"trigger_conditions":{"criteria_condition":"ALL","evaluation_period_seconds":300,"polling_interval_seconds":300,"trigger_occurrences":1},"description":"Test - alert definition creation"}' + alert definition creation","group_by":["entity_id"]}' form: {} headers: Accept: @@ -126,18 +103,19 @@ interactions: url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions method: POST response: - body: '{"id": 11072, "label": "go-test-alert-definition-create", "description": + body: '{"id": 11230, "label": "go-test-alert-definition-create", "description": "Test alert definition creation", "service_type": "dbaas", "type": "user", "scope": - "entity", "class": null, "regions": [], "status": "provisioning", "severity": 2, "rule_criteria": {"rules": [{"label": - "Memory Usage", "metric": "memory_usage", "unit": "percent", "aggregate_function": - "avg", "operator": "gt", "threshold": 90, "dimension_filters": [{"label": "Node - Type", "dimension_label": "node_type", "operator": "eq", "value": "primary"}]}]}, - "alert_channels": [{"id": 10000, "label": "Read-Write Channel", "url": "/monitor/alert-channels/10000", - "type": "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", - "polling_interval_seconds": 300, "evaluation_period_seconds": 300, "trigger_occurrences": - 1}, "created": "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": - "tester", "updated_by": "tester", "entities": - {"url": "/monitor/services/dbaas/alert-definitions/11072/entities", "has_more_resources": + "entity", "class": null, "regions": [], "status": "provisioning", "severity": + 2, "rule_criteria": {"rules": [{"label": "Memory Usage", "metric": "memory_usage", + "unit": "percent", "aggregate_function": "avg", "operator": "gt", "threshold": + 90, "dimension_filters": [{"label": "Node Type", "dimension_label": "node_type", + "operator": "eq", "value": "primary"}]}]}, "alert_channels": [{"id": 10000, + "label": "Read-Write Channel", "url": "/monitor/alert-channels/10000", "type": + "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", "polling_interval_seconds": + 300, "evaluation_period_seconds": 300, "trigger_occurrences": 1}, "created": + "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": "tester", + "updated_by": "tester", "group_by": ["entity_id"], "entities": + {"url": "/monitor/services/dbaas/alert-definitions/11230/entities", "has_more_resources": false, "count": 0}}' headers: Access-Control-Allow-Credentials: @@ -161,7 +139,7 @@ interactions: Content-Type: - application/json Expires: - - Mon, 27 Apr 2026 12:32:18 GMT + - Thu, 04 Jun 2026 10:41:17 GMT Pragma: - no-cache Strict-Transport-Security: @@ -198,21 +176,22 @@ interactions: - application/json User-Agent: - linodego/dev https://github.com/linode/linodego - url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions/11072 + url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions/11230 method: GET response: - body: '{"id": 11072, "label": "go-test-alert-definition-create", "description": + body: '{"id": 11230, "label": "go-test-alert-definition-create", "description": "Test alert definition creation", "service_type": "dbaas", "type": "user", "scope": - "entity", "class": null, "regions": [], "status": "enabled", "severity": 2, "rule_criteria": {"rules": [{"label": - "Memory Usage", "metric": "memory_usage", "unit": "percent", "aggregate_function": - "avg", "operator": "gt", "threshold": 90, "dimension_filters": [{"label": "Node - Type", "dimension_label": "node_type", "operator": "eq", "value": "primary"}]}]}, - "alert_channels": [{"id": 10000, "label": "Read-Write Channel", "url": "/monitor/alert-channels/10000", - "type": "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", - "polling_interval_seconds": 300, "evaluation_period_seconds": 300, "trigger_occurrences": - 1}, "created": "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": - "tester", "updated_by": "tester", "entities": - {"url": "/monitor/services/dbaas/alert-definitions/11072/entities", "has_more_resources": + "entity", "class": null, "regions": [], "status": "enabled", "severity": 2, + "rule_criteria": {"rules": [{"label": "Memory Usage", "metric": "memory_usage", + "unit": "percent", "aggregate_function": "avg", "operator": "gt", "threshold": + 90, "dimension_filters": [{"label": "Node Type", "dimension_label": "node_type", + "operator": "eq", "value": "primary"}]}]}, "alert_channels": [{"id": 10000, + "label": "Read-Write Channel", "url": "/monitor/alert-channels/10000", "type": + "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", "polling_interval_seconds": + 300, "evaluation_period_seconds": 300, "trigger_occurrences": 1}, "created": + "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": "tester", + "updated_by": "tester", "group_by": ["entity_id"], "entities": + {"url": "/monitor/services/dbaas/alert-definitions/11230/entities", "has_more_resources": false, "count": 0}}' headers: Access-Control-Allow-Credentials: @@ -236,7 +215,7 @@ interactions: Content-Type: - application/json Expires: - - Mon, 27 Apr 2026 12:32:35 GMT + - Thu, 04 Jun 2026 10:41:33 GMT Pragma: - no-cache Strict-Transport-Security: @@ -275,21 +254,22 @@ interactions: - application/json User-Agent: - linodego/dev https://github.com/linode/linodego - url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions/11072 + url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions/11230 method: PUT response: - body: '{"id": 11072, "label": "go-test-alert-definition-create-updated", "description": + body: '{"id": 11230, "label": "go-test-alert-definition-create-updated", "description": "Test alert definition creation", "service_type": "dbaas", "type": "user", "scope": - "entity", "class": null, "regions": [], "status": "enabled", "severity": 2, "rule_criteria": {"rules": [{"label": - "Memory Usage", "metric": "memory_usage", "unit": "percent", "aggregate_function": - "avg", "operator": "gt", "threshold": 90, "dimension_filters": [{"label": "Node - Type", "dimension_label": "node_type", "operator": "eq", "value": "primary"}]}]}, - "alert_channels": [{"id": 10000, "label": "Read-Write Channel", "url": "/monitor/alert-channels/10000", - "type": "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", - "polling_interval_seconds": 300, "evaluation_period_seconds": 300, "trigger_occurrences": - 1}, "created": "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": - "tester", "updated_by": "tester", "entities": - {"url": "/monitor/services/dbaas/alert-definitions/11072/entities", "has_more_resources": + "entity", "class": null, "regions": [], "status": "enabled", "severity": 2, + "rule_criteria": {"rules": [{"label": "Memory Usage", "metric": "memory_usage", + "unit": "percent", "aggregate_function": "avg", "operator": "gt", "threshold": + 90, "dimension_filters": [{"label": "Node Type", "dimension_label": "node_type", + "operator": "eq", "value": "primary"}]}]}, "alert_channels": [{"id": 10000, + "label": "Read-Write Channel", "url": "/monitor/alert-channels/10000", "type": + "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", "polling_interval_seconds": + 300, "evaluation_period_seconds": 300, "trigger_occurrences": 1}, "created": + "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": "tester", + "updated_by": "tester", "group_by": ["entity_id"], "entities": + {"url": "/monitor/services/dbaas/alert-definitions/11230/entities", "has_more_resources": false, "count": 0}}' headers: Access-Control-Allow-Credentials: @@ -313,7 +293,7 @@ interactions: Content-Type: - application/json Expires: - - Mon, 27 Apr 2026 12:32:36 GMT + - Thu, 04 Jun 2026 10:41:35 GMT Pragma: - no-cache Strict-Transport-Security: @@ -350,7 +330,7 @@ interactions: - application/json User-Agent: - linodego/dev https://github.com/linode/linodego - url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions/11072 + url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions/11230 method: DELETE response: body: '{}' @@ -378,7 +358,7 @@ interactions: Content-Type: - application/json Expires: - - Mon, 27 Apr 2026 12:32:37 GMT + - Thu, 04 Jun 2026 10:41:36 GMT Pragma: - no-cache Strict-Transport-Security: diff --git a/test/integration/fixtures/TestMonitorAlertDefinition_Clone.yaml b/test/integration/fixtures/TestMonitorAlertDefinition_Clone.yaml new file mode 100644 index 000000000..61bed1a88 --- /dev/null +++ b/test/integration/fixtures/TestMonitorAlertDefinition_Clone.yaml @@ -0,0 +1,436 @@ +--- +version: 1 +interactions: +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/monitor/alert-channels?page=1 + method: GET + response: + body: '{"pages": 1, "page": 1, "results": 2, "data": [{"id": 10000, "label": "Read-Write + Channel", "channel_type": "email", "type": "system", "details": {"email": {"usernames": + [], "recipient_type": "read_write_users"}}, "alerts": {"url": "/monitor/alert-channels/10000/alerts", + "type": "alerts-definitions", "alert_count": 9}, "created": "2018-01-02T03:04:05", + "updated": "2018-01-02T03:04:05", "created_by": "system", "updated_by": "system"}, + {"id": 10118, "label": "notification channel", "channel_type": "email", "type": "user", + "details": {"email": {"usernames": ["tester"], "recipient_type": + "user"}}, "alerts": {"url": "/monitor/alert-channels/10118/alerts", "type": + "alerts-definitions", "alert_count": 0}, "created": "2018-01-02T03:04:05", "updated": + "2018-01-02T03:04:05", "created_by": "tester", "updated_by": + "tester"}]}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 04 Jun 2026 10:36:31 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + - Accept-Encoding + X-Accepted-Oauth-Scopes: + - monitor:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - account:read_only databases:read_only domains:read_only events:read_only firewall:read_only + images:read_only ips:read_only linodes:read_only lke:read_only longview:read_only + monitor:read_write nodebalancers:read_only object_storage:read_only stackscripts:read_only + volumes:read_only + X-Ratelimit-Limit: + - "1840" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"label":"go-test-alert-definition-clone-source","severity":2,"channel_ids":[10000],"rule_criteria":{"rules":[{"aggregate_function":"avg","dimension_filters":[{"dimension_label":"node_type","operator":"eq","value":"primary"}],"metric":"memory_usage","operator":"gt","threshold":90}]},"trigger_conditions":{"criteria_condition":"ALL","evaluation_period_seconds":300,"polling_interval_seconds":300,"trigger_occurrences":1},"description":"Source + alert definition for clone test","group_by":["entity_id"]}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions + method: POST + response: + body: '{"id": 11228, "label": "go-test-alert-definition-clone-source", "description": + "Source alert definition for clone test", "service_type": "dbaas", "type": "user", + "scope": "entity", "class": null, "regions": [], "status": "provisioning", "severity": + 2, "rule_criteria": {"rules": [{"label": "Memory Usage", "metric": "memory_usage", + "unit": "percent", "aggregate_function": "avg", "operator": "gt", "threshold": + 90, "dimension_filters": [{"label": "Node Type", "dimension_label": "node_type", + "operator": "eq", "value": "primary"}]}]}, "alert_channels": [{"id": 10000, + "label": "Read-Write Channel", "url": "/monitor/alert-channels/10000", "type": + "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", "polling_interval_seconds": + 300, "evaluation_period_seconds": 300, "trigger_occurrences": 1}, "created": + "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": "tester", + "updated_by": "tester", "group_by": ["entity_id"], "entities": + {"url": "/monitor/services/dbaas/alert-definitions/11228/entities", "has_more_resources": + false, "count": 0}}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 04 Jun 2026 10:36:32 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Accept-Encoding + X-Accepted-Oauth-Scopes: + - databases:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - account:read_only databases:read_only domains:read_only events:read_only firewall:read_only + images:read_only ips:read_only linodes:read_only lke:read_only longview:read_only + monitor:read_write nodebalancers:read_only object_storage:read_only stackscripts:read_only + volumes:read_only + X-Ratelimit-Limit: + - "1840" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions/11228 + method: GET + response: + body: '{"id": 11228, "label": "go-test-alert-definition-clone-source", "description": + "Source alert definition for clone test", "service_type": "dbaas", "type": "user", + "scope": "entity", "class": null, "regions": [], "status": "enabled", "severity": + 2, "rule_criteria": {"rules": [{"label": "Memory Usage", "metric": "memory_usage", + "unit": "percent", "aggregate_function": "avg", "operator": "gt", "threshold": + 90, "dimension_filters": [{"label": "Node Type", "dimension_label": "node_type", + "operator": "eq", "value": "primary"}]}]}, "alert_channels": [{"id": 10000, + "label": "Read-Write Channel", "url": "/monitor/alert-channels/10000", "type": + "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", "polling_interval_seconds": + 300, "evaluation_period_seconds": 300, "trigger_occurrences": 1}, "created": + "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": "tester", + "updated_by": "tester", "group_by": ["entity_id"], "entities": + {"url": "/monitor/services/dbaas/alert-definitions/11228/entities", "has_more_resources": + false, "count": 0}}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 04 Jun 2026 10:36:48 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + - Accept-Encoding + X-Accepted-Oauth-Scopes: + - monitor:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - account:read_only databases:read_only domains:read_only events:read_only firewall:read_only + images:read_only ips:read_only linodes:read_only lke:read_only longview:read_only + monitor:read_write nodebalancers:read_only object_storage:read_only stackscripts:read_only + volumes:read_only + X-Ratelimit-Limit: + - "1840" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"label":"go-test-alert-definition-clone-source-clone","severity":1,"channel_ids":[10000],"trigger_conditions":{"criteria_condition":"ALL","evaluation_period_seconds":900,"polling_interval_seconds":300,"trigger_occurrences":3},"description":"Cloned + alert definition"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions/11228/clone + method: POST + response: + body: '{"id": 11229, "label": "go-test-alert-definition-clone-source-clone", "description": + "Cloned alert definition", "service_type": "dbaas", "type": "user", "scope": + "entity", "class": null, "regions": [], "status": "provisioning", "severity": + 1, "rule_criteria": {"rules": [{"label": "Memory Usage", "metric": "memory_usage", + "unit": "percent", "aggregate_function": "avg", "operator": "gt", "threshold": + 90, "dimension_filters": [{"label": "Node Type", "dimension_label": "node_type", + "operator": "eq", "value": "primary"}]}]}, "alert_channels": [{"id": 10000, + "label": "Read-Write Channel", "url": "/monitor/alert-channels/10000", "type": + "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", "polling_interval_seconds": + 300, "evaluation_period_seconds": 900, "trigger_occurrences": 3}, "created": + "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": "tester", + "updated_by": "tester", "group_by": ["entity_id"], "entities": + {"url": "/monitor/services/dbaas/alert-definitions/11229/entities", "has_more_resources": + false, "count": 0}}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 04 Jun 2026 10:36:50 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Accept-Encoding + X-Accepted-Oauth-Scopes: + - databases:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - account:read_only databases:read_only domains:read_only events:read_only firewall:read_only + images:read_only ips:read_only linodes:read_only lke:read_only longview:read_only + monitor:read_write nodebalancers:read_only object_storage:read_only stackscripts:read_only + volumes:read_only + X-Ratelimit-Limit: + - "1840" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions/11228 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 04 Jun 2026 10:36:51 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - databases:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - account:read_only databases:read_only domains:read_only events:read_only firewall:read_only + images:read_only ips:read_only linodes:read_only lke:read_only longview:read_only + monitor:read_write nodebalancers:read_only object_storage:read_only stackscripts:read_only + volumes:read_only + X-Ratelimit-Limit: + - "1840" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions/11229 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 04 Jun 2026 10:36:52 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - databases:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - account:read_only databases:read_only domains:read_only events:read_only firewall:read_only + images:read_only ips:read_only linodes:read_only lke:read_only longview:read_only + monitor:read_write nodebalancers:read_only object_storage:read_only stackscripts:read_only + volumes:read_only + X-Ratelimit-Limit: + - "1840" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" diff --git a/test/integration/fixtures/TestMonitorAlertDefinition_CreateWithIdempotency.yaml b/test/integration/fixtures/TestMonitorAlertDefinition_CreateWithIdempotency.yaml index 3a2630f41..82983afec 100644 --- a/test/integration/fixtures/TestMonitorAlertDefinition_CreateWithIdempotency.yaml +++ b/test/integration/fixtures/TestMonitorAlertDefinition_CreateWithIdempotency.yaml @@ -47,7 +47,7 @@ interactions: Content-Type: - application/json Expires: - - Mon, 27 Apr 2026 12:32:45 GMT + - Thu, 04 Jun 2026 17:17:32 GMT Pragma: - no-cache Strict-Transport-Security: @@ -76,7 +76,7 @@ interactions: code: 200 duration: "" - request: - body: '{"label":"go-test-alert-definition-idempotency-1777293165535535919","severity":2,"channel_ids":[10000],"rule_criteria":{"rules":[{"aggregate_function":"avg","dimension_filters":[{"dimension_label":"node_type","operator":"eq","value":"primary"}],"metric":"memory_usage","operator":"gt","threshold":90}]},"trigger_conditions":{"criteria_condition":"ALL","evaluation_period_seconds":300,"polling_interval_seconds":300,"trigger_occurrences":1},"description":"Test + body: '{"label":"go-test-alert-definition-idempotency-1780593452875420575","severity":2,"channel_ids":[10000],"rule_criteria":{"rules":[{"aggregate_function":"avg","dimension_filters":[{"dimension_label":"node_type","operator":"eq","value":"primary"}],"metric":"memory_usage","operator":"gt","threshold":90}]},"trigger_conditions":{"criteria_condition":"ALL","evaluation_period_seconds":300,"polling_interval_seconds":300,"trigger_occurrences":1},"description":"Test alert definition creation with idempotency"}' form: {} headers: @@ -89,18 +89,19 @@ interactions: url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions method: POST response: - body: '{"id": 11073, "label": "go-test-alert-definition-idempotency-1777293165535535919", + body: '{"id": 11234, "label": "go-test-alert-definition-idempotency-1780593452875420575", "description": "Test alert definition creation with idempotency", "service_type": "dbaas", "type": "user", "scope": "entity", "class": null, "regions": [], "status": - "provisioning", "severity": 2, "rule_criteria": {"rules": [{"label": "Memory Usage", "metric": "memory_usage", - "unit": "percent", "aggregate_function": "avg", "operator": "gt", "threshold": - 90, "dimension_filters": [{"label": "Node Type", "dimension_label": "node_type", - "operator": "eq", "value": "primary"}]}]}, "alert_channels": [{"id": 10000, - "label": "Read-Write Channel", "url": "/monitor/alert-channels/10000", "type": - "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", "polling_interval_seconds": - 300, "evaluation_period_seconds": 300, "trigger_occurrences": 1}, "created": - "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": "tester", - "updated_by": "tester", "entities": {"url": "/monitor/services/dbaas/alert-definitions/11073/entities", + "provisioning", "severity": 2, "rule_criteria": {"rules": [{"label": "Memory + Usage", "metric": "memory_usage", "unit": "percent", "aggregate_function": "avg", + "operator": "gt", "threshold": 90, "dimension_filters": [{"label": "Node Type", + "dimension_label": "node_type", "operator": "eq", "value": "primary"}]}]}, "alert_channels": + [{"id": 10000, "label": "Read-Write Channel", "url": "/monitor/alert-channels/10000", + "type": "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", + "polling_interval_seconds": 300, "evaluation_period_seconds": 300, "trigger_occurrences": + 1}, "created": "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": + "tester", "updated_by": "tester", "group_by": + ["entity_id"], "entities": {"url": "/monitor/services/dbaas/alert-definitions/11234/entities", "has_more_resources": false, "count": 0}}' headers: Access-Control-Allow-Credentials: @@ -124,7 +125,7 @@ interactions: Content-Type: - application/json Expires: - - Mon, 27 Apr 2026 12:32:46 GMT + - Thu, 04 Jun 2026 17:17:34 GMT Pragma: - no-cache Strict-Transport-Security: @@ -152,7 +153,7 @@ interactions: code: 200 duration: "" - request: - body: '{"label":"go-test-alert-definition-idempotency-1777293165535535919","severity":2,"channel_ids":[10000],"rule_criteria":{"rules":[{"aggregate_function":"avg","dimension_filters":[{"dimension_label":"node_type","operator":"eq","value":"primary"}],"metric":"memory_usage","operator":"gt","threshold":90}]},"trigger_conditions":{"criteria_condition":"ALL","evaluation_period_seconds":300,"polling_interval_seconds":300,"trigger_occurrences":1},"description":"Test + body: '{"label":"go-test-alert-definition-idempotency-1780593452875420575","severity":2,"channel_ids":[10000],"rule_criteria":{"rules":[{"aggregate_function":"avg","dimension_filters":[{"dimension_label":"node_type","operator":"eq","value":"primary"}],"metric":"memory_usage","operator":"gt","threshold":90}]},"trigger_conditions":{"criteria_condition":"ALL","evaluation_period_seconds":300,"polling_interval_seconds":300,"trigger_occurrences":1},"description":"Test alert definition creation with idempotency"}' form: {} headers: @@ -183,7 +184,7 @@ interactions: Content-Type: - application/json Expires: - - Mon, 27 Apr 2026 12:32:47 GMT + - Thu, 04 Jun 2026 17:17:34 GMT Pragma: - no-cache Strict-Transport-Security: @@ -212,7 +213,7 @@ interactions: - application/json User-Agent: - linodego/dev https://github.com/linode/linodego - url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions/11073 + url: https://api.linode.com/v4beta/monitor/services/dbaas/alert-definitions/11234 method: DELETE response: body: '{}' @@ -240,7 +241,7 @@ interactions: Content-Type: - application/json Expires: - - Mon, 27 Apr 2026 12:32:48 GMT + - Thu, 04 Jun 2026 17:17:35 GMT Pragma: - no-cache Strict-Transport-Security: diff --git a/test/integration/fixtures/TestMonitorAlertDefinitions_List.yaml b/test/integration/fixtures/TestMonitorAlertDefinitions_List.yaml index 7fa1c7992..9f116a3dd 100644 --- a/test/integration/fixtures/TestMonitorAlertDefinitions_List.yaml +++ b/test/integration/fixtures/TestMonitorAlertDefinitions_List.yaml @@ -14,54 +14,31 @@ interactions: url: https://api.linode.com/v4beta/monitor/alert-definitions?page=1 method: GET response: - body: '{"pages": 1, "page": 1, "results": 4, "data": [{"id": 10000, "label": + body: '{"pages": 1, "page": 1, "results": 2, "data": [{"id": 10000, "label": "High Memory Usage Plan Dedicated", "description": "Alert triggers when dedicated - plan nodes consistently reach critical memory usage, risking application performance - degradation.", "service_type": "dbaas", "type": "system", "scope": "entity", - "class": "dedicated", "regions": [], "status": "enabled", "severity": - 2, "rule_criteria": {"rules": [{"label": "Memory Usage", "metric": "memory_usage", - "unit": "percent", "aggregate_function": "avg", "operator": "gt", "threshold": - 95, "dimension_filters": []}]}, "alert_channels": [{"id": 10000, "label": "Read-Write - Channel", "url": "/monitor/alert-channels/10000", "type": "alert-channels"}], - "trigger_conditions": {"criteria_condition": "ALL", "polling_interval_seconds": - 300, "evaluation_period_seconds": 300, "trigger_occurrences": 3}, "created": - "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": "system", - "updated_by": "system", "entities": {"url": "/monitor/services/dbaas/alert-definitions/10000/entities", - "has_more_resources": false, "count": 5}}, {"id": 10001, "label": "High Memory - Usage Plan Shared", "description": "Alert triggers when shared plan nodes consistently - reach critical memory usage, risking application performance degradation.", - "service_type": "dbaas", "type": "system", "scope": "entity", "class": "shared", - "regions": [], "status": "enabled", "rule_criteria": {"rules": [{"label": "Memory Usage", + plan nodes consistently reach critical memory usage.", "service_type": "dbaas", + "type": "system", "scope": "entity", "class": "dedicated", "regions": [], "status": + "enabled", "severity": 2, "rule_criteria": {"rules": [{"label": "Memory Usage", "metric": "memory_usage", "unit": "percent", "aggregate_function": "avg", "operator": - "gt", "threshold": 90, "dimension_filters": []}]}, "alert_channels": [{"id": + "gt", "threshold": 95, "dimension_filters": []}]}, "alert_channels": [{"id": 10000, "label": "Read-Write Channel", "url": "/monitor/alert-channels/10000", "type": "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", "polling_interval_seconds": 300, "evaluation_period_seconds": 300, "trigger_occurrences": 3}, "created": "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": - "system", "updated_by": "system", "entities": {"url": "/monitor/services/dbaas/alert-definitions/10001/entities", - "has_more_resources": false, "count": 2}}, {"id": 11001, "label": "test_alert_logs_prod", - "description": "some desc1", "service_type": "logs", "type": "user", "scope": - "entity", "class": null, "regions": [], "status": "enabled", "severity": 2, "rule_criteria": {"rules": [{"label": - "Successful Upload Count", "metric": "success_upload_count", "unit": "count", - "aggregate_function": "sum", "operator": "lt", "threshold": 100000, "dimension_filters": - []}]}, "alert_channels": [{"id": 10004, "label": "Alert Channel", "url": + "system", "updated_by": "system", "group_by": ["entity_id"], "entities": {"url": + "/monitor/services/dbaas/alert-definitions/10000/entities", "has_more_resources": + false, "count": 5}}, {"id": 11184, "label": "Test alert", "description": + "Test alert", "service_type": "dbaas", "type": "user", "scope": + "entity", "class": null, "regions": [], "status": "disabled", "severity": 3, + "rule_criteria": {"rules": [{"label": "CPU Usage", "metric": "cpu_usage", "unit": + "percent", "aggregate_function": "avg", "operator": "gt", "threshold": 0, "dimension_filters": + []}]}, "alert_channels": [{"id": 10004, "label": "Team Channel", "url": "/monitor/alert-channels/10004", "type": "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", "polling_interval_seconds": 300, "evaluation_period_seconds": 300, "trigger_occurrences": 1}, "created": "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": "tester", "updated_by": - "tester", "entities": {"url": "/monitor/services/logs/alert-definitions/11001/entities", - "has_more_resources": false, "count": 1}}, {"id": 11021, "label": "Test Alert", "description": "Test Alert", "service_type": - "dbaas", "type": "user", "scope": "entity", "class": null, "regions": [], "status": - "enabled", "severity": 1, "rule_criteria": {"rules": [{"label": "Memory Usage", "metric": - "memory_usage", "unit": "percent", "aggregate_function": "sum", "operator": - "gt", "threshold": 100, "dimension_filters": []}]}, "alert_channels": [{"id": - 10004, "label": "Alert Channel", "url": "/monitor/alert-channels/10004", - "type": "alert-channels"}], "trigger_conditions": {"criteria_condition": "ALL", - "polling_interval_seconds": 1800, "evaluation_period_seconds": 900, "trigger_occurrences": - 10}, "created": "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "created_by": - "tester", "updated_by": "tester", "entities": - {"url": "/monitor/services/dbaas/alert-definitions/11021/entities", "has_more_resources": - false, "count": 2}}]}' + "tester", "group_by": ["entity_id"], "entities": {"url": "/monitor/services/dbaas/alert-definitions/11184/entities", + "has_more_resources": false, "count": 1}}]}' headers: Access-Control-Allow-Credentials: - "true" @@ -84,7 +61,7 @@ interactions: Content-Type: - application/json Expires: - - Mon, 27 Apr 2026 12:32:41 GMT + - Thu, 04 Jun 2026 17:01:13 GMT Pragma: - no-cache Strict-Transport-Security: diff --git a/test/integration/monitor_alert_definitions_test.go b/test/integration/monitor_alert_definitions_test.go index 42050d3d9..340529b20 100644 --- a/test/integration/monitor_alert_definitions_test.go +++ b/test/integration/monitor_alert_definitions_test.go @@ -35,6 +35,7 @@ func TestMonitorAlertDefinition_smoke(t *testing.T) { // Check few mandatory fields on each listed alert assert.NotZero(t, alert.ID, "alert.ID should not be zero") assert.NotEmpty(t, alert.Label, "alert.Label should not be empty") + assert.NotNil(t, alert.GroupBy, "alert.GroupBy should be present") // If alert has a rule, validate basic rule structure if len(alert.RuleCriteria.Rules) > 0 { @@ -78,6 +79,7 @@ func TestMonitorAlertDefinition_smoke(t *testing.T) { Description: linodego.Pointer("Test alert definition creation"), ChannelIDs: []int{channelID}, EntityIDs: nil, + GroupBy: []string{"entity_id"}, TriggerConditions: &linodego.TriggerConditions{ CriteriaCondition: "ALL", EvaluationPeriodSeconds: 300, @@ -115,6 +117,7 @@ func TestMonitorAlertDefinition_smoke(t *testing.T) { assert.Equal(t, createOpts.Label, createdAlert.Label) assert.Equal(t, createOpts.Severity, createdAlert.Severity) assert.Equal(t, *createOpts.Description, createdAlert.Description) + assert.NotNil(t, createdAlert.GroupBy) // More thorough assertions on the created alert's nested fields // TriggerConditions is a struct, so it is never nil @@ -172,6 +175,7 @@ func TestMonitorAlertDefinition_smoke(t *testing.T) { assert.NotNil(t, updatedAlert) assert.Equal(t, createdAlert.ID, updatedAlert.ID, "updated alert should keep same ID") assert.Equal(t, newLabel, updatedAlert.Label, "updated alert should have the new label") + assert.NotNil(t, updatedAlert.GroupBy) } // Clean up created alert definition @@ -211,6 +215,7 @@ func TestMonitorAlertDefinitions_List(t *testing.T) { assert.NotZero(t, alert.ID) assert.NotEmpty(t, alert.Label) assert.NotEmpty(t, alert.ServiceType) + assert.NotNil(t, alert.GroupBy) } } @@ -331,3 +336,123 @@ func TestMonitorAlertDefinitionEntities_List(t *testing.T) { assert.NotEmpty(t, entity.URL) } } + +func TestMonitorAlertDefinition_Clone(t *testing.T) { + ctx := waitContext(t, 300*time.Second) + + client, teardown := createTestClient(t, "fixtures/TestMonitorAlertDefinition_Clone") + defer teardown() + + // Get a channel ID to use + channels, err := client.ListAlertChannels(context.Background(), nil) + if err != nil || len(channels) == 0 { + t.Fatalf("failed to determine a monitor channel to use: %s", err) + } + testChannelID := channels[0].ID + + // Create the source alert definition + createOpts := linodego.AlertDefinitionCreateOptions{ + Label: "go-test-alert-definition-clone-source", + Severity: int(linodego.SeverityLow), + Description: linodego.Pointer("Source alert definition for clone test"), + ChannelIDs: []int{testChannelID}, + EntityIDs: nil, + GroupBy: []string{"entity_id"}, + TriggerConditions: &linodego.TriggerConditions{ + CriteriaCondition: "ALL", + EvaluationPeriodSeconds: 300, + PollingIntervalSeconds: 300, + TriggerOccurrences: 1, + }, + RuleCriteria: &linodego.RuleCriteriaOptions{ + Rules: []linodego.RuleOptions{ + { + AggregateFunction: "avg", + Metric: "memory_usage", + Operator: "gt", + Threshold: 90.0, + DimensionFilters: []linodego.DimensionFilterOptions{ + { + DimensionLabel: "node_type", + Operator: "eq", + Value: "primary", + }, + }, + }, + }, + }, + } + + sourceAlert, err := client.CreateMonitorAlertDefinition(context.Background(), testMonitorAlertDefinitionServiceType, createOpts) + if err != nil { + t.Fatalf("CreateMonitorAlertDefinition failed: %s", err) + } + assert.NotNil(t, sourceAlert) + assert.Equal(t, createOpts.Label, sourceAlert.Label) + assert.NotNil(t, sourceAlert.GroupBy) + + // Wait for the source alert to be enabled before cloning + _, err = client.WaitForAlertDefinitionStatus( + ctx, + linodego.AlertDefinitionStatusEnabled, + testMonitorAlertDefinitionServiceType, + sourceAlert.ID, + ) + if err != nil { + t.Fatalf("failed to wait for source alert definition to be enabled: %s", err) + } + + // Clone the source alert definition with overridden fields + cloneLabel := sourceAlert.Label + "-clone" + overrideSeverity := int(linodego.SeverityMedium) + cloneOpts := linodego.AlertDefinitionCloneOptions{ + Label: cloneLabel, + Description: linodego.Pointer("Cloned alert definition"), + Severity: &overrideSeverity, + ChannelIDs: []int{testChannelID}, + TriggerConditions: &linodego.TriggerConditions{ + CriteriaCondition: "ALL", + EvaluationPeriodSeconds: 900, + PollingIntervalSeconds: 300, + TriggerOccurrences: 3, + }, + } + + clonedAlert, err := client.CloneMonitorAlertDefinition(context.Background(), testMonitorAlertDefinitionServiceType, sourceAlert.ID, cloneOpts) + if err != nil { + // Cleanup source before failing + _ = client.DeleteMonitorAlertDefinition(context.Background(), testMonitorAlertDefinitionServiceType, sourceAlert.ID) + t.Fatalf("CloneMonitorAlertDefinition failed: %s", err) + } + assert.NotNil(t, clonedAlert) + assert.NotEqual(t, sourceAlert.ID, clonedAlert.ID, "cloned alert should have a different ID") + assert.Equal(t, cloneLabel, clonedAlert.Label, "cloned alert should have the specified label") + assert.Equal(t, *cloneOpts.Description, clonedAlert.Description, "cloned alert should have the overridden description") + assert.Equal(t, overrideSeverity, clonedAlert.Severity, "cloned alert should have the overridden severity") + assert.Equal(t, sourceAlert.Scope, clonedAlert.Scope, "cloned alert scope should be inherited from source") + assert.NotNil(t, clonedAlert.GroupBy) + assert.Equal(t, cloneOpts.TriggerConditions.EvaluationPeriodSeconds, clonedAlert.TriggerConditions.EvaluationPeriodSeconds) + assert.Equal(t, cloneOpts.TriggerConditions.PollingIntervalSeconds, clonedAlert.TriggerConditions.PollingIntervalSeconds) + assert.Equal(t, cloneOpts.TriggerConditions.TriggerOccurrences, clonedAlert.TriggerConditions.TriggerOccurrences) + + // Cleanup both source and cloned alert definitions + for _, alertID := range []int{sourceAlert.ID, clonedAlert.ID} { + maxWait := 2 * time.Minute + baseDelay := 2 * time.Second + var lastErr error + start := time.Now() + for attempt := 0; time.Since(start) < maxWait; attempt++ { + err = client.DeleteMonitorAlertDefinition(context.Background(), testMonitorAlertDefinitionServiceType, alertID) + if err == nil { + break + } + lastErr = err + sleep := baseDelay * (1 << attempt) + if sleep > 30*time.Second { + sleep = 30 * time.Second + } + time.Sleep(sleep) + } + assert.NoError(t, err, "DeleteMonitorAlertDefinition failed after retries for alert ID %d: %v", alertID, lastErr) + } +} diff --git a/test/unit/monitor_alert_definitions_test.go b/test/unit/monitor_alert_definitions_test.go index 237d005b0..31ccdbd08 100644 --- a/test/unit/monitor_alert_definitions_test.go +++ b/test/unit/monitor_alert_definitions_test.go @@ -21,6 +21,7 @@ const ( "service_type": "dbaas", "description": "A test alert for dbaas service", "scope": "entity", + "group_by": ["entity_id"], "regions": [], "alert_channels": [ { @@ -79,6 +80,7 @@ const ( "description": "A test alert for dbaas service", "status": "enabled", "scope": "entity", + "group_by": ["entity_id"], "regions": [], "alert_channels": [ { @@ -139,6 +141,7 @@ const ( "status": "disabled", "scope": "entity", "description": "A test alert for dbaas service", + "group_by": ["entity_id"], "regions": [], "alert_channels": [ { @@ -210,6 +213,118 @@ const ( "pages": 1, "results": 3 }` + + monitorAlertDefinitionCloneResponse = `{ + "id": 456, + "label": "test-alert-definition-clone", + "severity": 1, + "type": "user", + "service_type": "dbaas", + "description": "A test alert for dbaas service", + "scope": "entity", + "group_by": ["entity_id"], + "regions": [], + "alert_channels": [ + { + "id": 10000, + "label": "Read-Write Channel", + "type": "email", + "url": "/monitor/alert-channels/10000" + } + ], + "rule_criteria": { + "rules": [ + { + "aggregate_function": "avg", + "dimension_filters": [ + { + "dimension_label": "node_type", + "label": "Node Type", + "operator": "eq", + "value": "primary" + } + ], + "label": "High CPU Usage", + "metric": "cpu_usage", + "operator": "gt", + "threshold": 90, + "unit": "percent" + } + ] + }, + "trigger_conditions": { + "criteria_condition": "ALL", + "evaluation_period_seconds": 300, + "polling_interval_seconds": 60, + "trigger_occurrences": 3 + }, + "class": "", + "status": "enabled", + "entities": { + "url": "/monitor/services/dbaas/alert-definitions/456/entities", + "count": 0, + "has_more_resources": false + }, + "created": "2024-01-01T00:00:00", + "updated": "2024-01-01T00:00:00", + "updated_by": "tester" + }` + + monitorAlertDefinitionCloneWithOverridesResponse = `{ + "id": 789, + "label": "test-alert-definition-clone-overrides", + "severity": 1, + "type": "user", + "service_type": "dbaas", + "description": "cloned alert definition", + "scope": "entity", + "group_by": ["entity_id"], + "regions": [], + "alert_channels": [ + { + "id": 10000, + "label": "Read-Write Channel", + "type": "email", + "url": "/monitor/alert-channels/10000" + } + ], + "rule_criteria": { + "rules": [ + { + "aggregate_function": "avg", + "dimension_filters": [ + { + "dimension_label": "node_type", + "label": "Node Type", + "operator": "eq", + "value": "primary" + } + ], + "label": "High CPU Usage", + "metric": "cpu_usage", + "operator": "gt", + "threshold": 90, + "unit": "percent" + } + ] + }, + "trigger_conditions": { + "criteria_condition": "ALL", + "evaluation_period_seconds": 300, + "polling_interval_seconds": 900, + "trigger_occurrences": 3 + }, + "class": "", + "status": "enabled", + "entities": { + "url": "/monitor/services/dbaas/alert-definitions/789/entities", + "count": 0, + "has_more_resources": false + }, + "created": "2024-01-01T00:00:00", + "updated": "2024-01-01T00:00:00", + "updated_by": "tester" + }` ) func TestCreateMonitorAlertDefinition(t *testing.T) { @@ -226,6 +341,7 @@ func TestCreateMonitorAlertDefinition(t *testing.T) { Regions: []string{}, ChannelIDs: []int{1}, EntityIDs: []string{"12345"}, + GroupBy: []string{"entity_id"}, } alert, err := base.Client.CreateMonitorAlertDefinition(context.Background(), testMonitorAlertDefinitionServiceType, createOpts) @@ -234,6 +350,7 @@ func TestCreateMonitorAlertDefinition(t *testing.T) { assert.Equal(t, "test-alert-definition", alert.Label) assert.Equal(t, testMonitorAlertDefinitionID, alert.ID) assert.Equal(t, linodego.AlertDefinitionScopeEntity, alert.Scope) + assert.Equal(t, []string{"entity_id"}, alert.GroupBy) assert.Empty(t, alert.Regions) assert.Equal(t, "/monitor/services/dbaas/alert-definitions/123/entities", alert.Entities.URL) assert.Equal(t, 0, alert.Entities.Count) @@ -258,6 +375,7 @@ func TestCreateMonitorAlertDefinitionWithIdempotency(t *testing.T) { Regions: []string{}, ChannelIDs: []int{1}, EntityIDs: []string{"12345"}, + GroupBy: []string{"entity_id"}, } alert, err := base.Client.CreateMonitorAlertDefinitionWithIdempotency( @@ -275,6 +393,7 @@ func TestCreateMonitorAlertDefinitionWithIdempotency(t *testing.T) { assert.Equal(t, "/monitor/services/dbaas/alert-definitions/123/entities", alert.Entities.URL) assert.Equal(t, 0, alert.Entities.Count) assert.False(t, alert.Entities.HasMoreResources) + assert.Equal(t, []string{"entity_id"}, alert.GroupBy) } func TestGetMonitorAlertDefinition(t *testing.T) { @@ -298,6 +417,7 @@ func TestGetMonitorAlertDefinition(t *testing.T) { assert.NotNil(t, alert.RuleCriteria) assert.NotNil(t, alert.RuleCriteria.Rules) assert.NotNil(t, alert.TriggerConditions) + assert.Equal(t, []string{"entity_id"}, alert.GroupBy) } func TestListMonitorAlertDefinitions(t *testing.T) { @@ -321,6 +441,7 @@ func TestListMonitorAlertDefinitions(t *testing.T) { assert.NotNil(t, alerts[0].RuleCriteria) assert.NotNil(t, alerts[0].RuleCriteria.Rules) assert.NotNil(t, alerts[0].TriggerConditions) + assert.Equal(t, []string{"entity_id"}, alerts[0].GroupBy) } func TestUpdateMonitorAlertDefinition(t *testing.T) { @@ -334,6 +455,7 @@ func TestUpdateMonitorAlertDefinition(t *testing.T) { Label: "test-alert-definition-renamed", Severity: int(linodego.SeverityLow), ChannelIDs: []int{1, 2}, + GroupBy: []string{"entity_id"}, } alert, err := base.Client.UpdateMonitorAlertDefinition( @@ -352,6 +474,7 @@ func TestUpdateMonitorAlertDefinition(t *testing.T) { assert.Equal(t, "/monitor/services/dbaas/alert-definitions/123/entities", alert.Entities.URL) assert.Equal(t, 2, alert.Entities.Count) assert.True(t, alert.Entities.HasMoreResources) + assert.Equal(t, []string{"entity_id"}, alert.GroupBy) } func TestDeleteMonitorAlertDefinition(t *testing.T) { @@ -398,3 +521,73 @@ func TestListMonitorAlertDefinitionEntities(t *testing.T) { assert.Equal(t, "/v4/databases/mysql/instances/3", entities[2].URL) assert.Equal(t, "dbaas", entities[2].Type) } + +func TestCloneMonitorAlertDefinition(t *testing.T) { + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockPost("monitor/services/dbaas/alert-definitions/123/clone", json.RawMessage(monitorAlertDefinitionCloneResponse)) + + cloneOpts := linodego.AlertDefinitionCloneOptions{ + Label: "test-alert-definition-clone", + } + + alert, err := base.Client.CloneMonitorAlertDefinition( + context.Background(), + testMonitorAlertDefinitionServiceType, + testMonitorAlertDefinitionID, + cloneOpts, + ) + assert.NoError(t, err) + assert.NotNil(t, alert) + assert.Equal(t, "test-alert-definition-clone", alert.Label) + assert.Equal(t, 456, alert.ID) + assert.Equal(t, linodego.AlertDefinitionScopeEntity, alert.Scope) + assert.NotNil(t, alert.RuleCriteria) + assert.NotNil(t, alert.RuleCriteria.Rules) + assert.NotNil(t, alert.TriggerConditions) + assert.Equal(t, []string{"entity_id"}, alert.GroupBy) +} + +func TestCloneMonitorAlertDefinitionWithOverrides(t *testing.T) { + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockPost("monitor/services/dbaas/alert-definitions/123/clone", json.RawMessage(monitorAlertDefinitionCloneWithOverridesResponse)) + + severity := int(linodego.SeverityMedium) + cloneOpts := linodego.AlertDefinitionCloneOptions{ + Label: "test-alert-definition-clone-overrides", + Severity: &severity, + Description: linodego.Pointer("cloned alert definition"), + Scope: linodego.AlertDefinitionScopeEntity, + ChannelIDs: []int{1}, + EntityIDs: []string{"12345"}, + GroupBy: []string{"entity_id"}, + TriggerConditions: &linodego.TriggerConditions{ + CriteriaCondition: "ALL", + EvaluationPeriodSeconds: 300, + PollingIntervalSeconds: 900, + TriggerOccurrences: 3, + }, + } + + alert, err := base.Client.CloneMonitorAlertDefinition( + context.Background(), + testMonitorAlertDefinitionServiceType, + testMonitorAlertDefinitionID, + cloneOpts, + ) + assert.NoError(t, err) + assert.NotNil(t, alert) + assert.Equal(t, "test-alert-definition-clone-overrides", alert.Label) + assert.Equal(t, 789, alert.ID) + assert.Equal(t, linodego.AlertDefinitionScopeEntity, alert.Scope) + assert.Equal(t, "cloned alert definition", alert.Description) + assert.Equal(t, int(linodego.SeverityMedium), alert.Severity) + assert.NotNil(t, alert.TriggerConditions) + assert.Equal(t, 900, alert.TriggerConditions.PollingIntervalSeconds) + assert.Equal(t, []string{"entity_id"}, alert.GroupBy) +}