Commit 6aac4d23 authored by James Lopez's avatar James Lopez

Merge branch '214556-user-defined-alert-identification' into 'master'

User defined alert fingerprinting

Closes #217050

See merge request gitlab-org/gitlab!32613
parents 2cfd33d4 41d69023
......@@ -135,6 +135,10 @@ module AlertManagement
monitoring_tool == Gitlab::AlertManagement::AlertParams::MONITORING_TOOLS[:prometheus]
end
def register_new_event!
increment!(:events, 1)
end
private
def hosts_length
......
......@@ -10,7 +10,7 @@ module Projects
return forbidden unless alerts_service_activated?
return unauthorized unless valid_token?(token)
alert = create_alert
alert = process_alert
return bad_request unless alert.persisted?
process_incident_issues(alert) if process_issues?
......@@ -26,13 +26,29 @@ module Projects
delegate :alerts_service, :alerts_service_activated?, to: :project
def am_alert_params
strong_memoize(:am_alert_params) do
Gitlab::AlertManagement::AlertParams.from_generic_alert(project: project, payload: params.to_h)
end
end
def process_alert
if alert = find_alert_by_fingerprint(am_alert_params[:fingerprint])
alert.register_new_event!
else
create_alert
end
end
def create_alert
AlertManagement::Alert.create(am_alert_params)
end
def find_alert_by_fingerprint(fingerprint)
return unless fingerprint
AlertManagement::Alert.for_fingerprint(project, fingerprint).first
end
def send_email?
incident_management_setting.send_email?
end
......
---
title: Set fingerprints and increment events count for Alert Management alerts
merge_request: 32613
author:
type: added
......@@ -28,7 +28,7 @@ To set up the generic alerts integration:
## Customizing the payload
You can customize the payload by sending the following parameters. All fields are optional:
You can customize the payload by sending the following parameters. All fields other than `title` are optional:
| Property | Type | Description |
| -------- | ---- | ----------- |
......@@ -39,6 +39,7 @@ You can customize the payload by sending the following parameters. All fields ar
| `monitoring_tool` | String | The name of the associated monitoring tool. |
| `hosts` | String or Array | One or more hosts, as to where this incident occurred. |
| `severity` | String | The severity of the alert. Must be one of `critical`, `high`, `medium`, `low`, `info`, `unknown`. Default is `critical`. |
| `fingerprint` | String or Array | The unique identifier of the alert. This can be used to group occurrences of the same alert. |
TIP: **Payload size:**
Ensure your requests are smaller than the [payload application limits](../../../administration/instance_limits.md#generic-alert-json-payloads).
......@@ -65,5 +66,7 @@ Example payload:
"service": "service affected",
"monitoring_tool": "value",
"hosts": "value",
"severity": "high",
"fingerprint": "d19381d4e8ebca87b55cda6e8eee7385"
}
```
......@@ -20,7 +20,8 @@ module Gitlab
hosts: Array(annotations[:hosts]),
payload: payload,
started_at: parsed_payload['startsAt'],
severity: annotations[:severity]
severity: annotations[:severity],
fingerprint: annotations[:fingerprint]
}
end
......
# frozen_string_literal: true
module Gitlab
module AlertManagement
class Fingerprint
def self.generate(data)
new.generate(data)
end
def generate(data)
return unless data.present?
if data.is_a?(Array)
data = flatten_array(data)
end
Digest::SHA1.hexdigest(data.to_s)
end
private
def flatten_array(array)
array.flatten.map!(&:to_s).join
end
end
end
end
......@@ -106,7 +106,7 @@ module Gitlab
end
def gitlab_fingerprint
Digest::SHA1.hexdigest(plain_gitlab_fingerprint)
Gitlab::AlertManagement::Fingerprint.generate(plain_gitlab_fingerprint)
end
def valid?
......
......@@ -35,6 +35,10 @@ module Gitlab
payload[:severity].presence || DEFAULT_SEVERITY
end
def fingerprint
Gitlab::AlertManagement::Fingerprint.generate(payload[:fingerprint])
end
def annotations
primary_params
.reverse_merge(flatten_secondary_params)
......@@ -49,7 +53,8 @@ module Gitlab
'monitoring_tool' => payload[:monitoring_tool],
'service' => payload[:service],
'hosts' => hosts.presence,
'severity' => severity
'severity' => severity,
'fingerprint' => fingerprint
}
end
......
......@@ -32,7 +32,8 @@ describe Gitlab::AlertManagement::AlertParams do
severity: 'critical',
hosts: ['gitlab.com'],
payload: payload,
started_at: started_at
started_at: started_at,
fingerprint: nil
)
end
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::AlertManagement::Fingerprint do
using RSpec::Parameterized::TableSyntax
let_it_be(:alert) { create(:alert_management_alert) }
describe '.generate' do
subject { described_class.generate(data) }
context 'when data is an array' do
let(:data) { [1, 'fingerprint', 'given'] }
it 'flattens the array' do
expect_next_instance_of(described_class) do |obj|
expect(obj).to receive(:flatten_array)
end
subject
end
it 'returns the hashed fingerprint' do
expected_fingerprint = Digest::SHA1.hexdigest(data.flatten.map!(&:to_s).join)
expect(subject).to eq(expected_fingerprint)
end
end
context 'when data is a non-array type' do
where(:data) do
[
111,
'fingerprint',
:fingerprint,
true,
{ test: true }
]
end
with_them do
it 'performs like a hashed fingerprint' do
expect(subject).to eq(Digest::SHA1.hexdigest(data.to_s))
end
end
end
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
require 'spec_helper'
describe Gitlab::Alerting::NotificationPayloadParser do
describe '.call' do
......@@ -89,6 +89,39 @@ describe Gitlab::Alerting::NotificationPayloadParser do
end
end
context 'with fingerprint' do
before do
payload[:fingerprint] = data
end
shared_examples 'fingerprint generation' do
it 'generates the fingerprint correctly' do
expect(result).to eq(Gitlab::AlertManagement::Fingerprint.generate(data))
end
end
context 'with blank fingerprint' do
it_behaves_like 'fingerprint generation' do
let(:data) { ' ' }
let(:result) { subject.dig('annotations', 'fingerprint') }
end
end
context 'with fingerprint given' do
it_behaves_like 'fingerprint generation' do
let(:data) { 'fingerprint' }
let(:result) { subject.dig('annotations', 'fingerprint') }
end
end
context 'with array fingerprint given' do
it_behaves_like 'fingerprint generation' do
let(:data) { [1, 'fingerprint', 'given'] }
let(:result) { subject.dig('annotations', 'fingerprint') }
end
end
end
context 'when payload attributes have blank lines' do
let(:payload) do
{
......
......@@ -317,4 +317,14 @@ describe AlertManagement::Alert do
expect { subject }.to change { alert.reload.ended_at }.to nil
end
end
describe '#register_new_event!' do
subject { alert.register_new_event! }
let(:alert) { create(:alert_management_alert) }
it 'increments the events count by 1' do
expect { subject }.to change { alert.events}.by(1)
end
end
end
......@@ -73,6 +73,7 @@ describe Projects::Alerting::NotifyService do
describe '#execute' do
let(:token) { 'invalid-token' }
let(:starts_at) { Time.current.change(usec: 0) }
let(:fingerprint) { 'testing' }
let(:service) { described_class.new(project, nil, payload) }
let(:payload_raw) do
{
......@@ -82,7 +83,8 @@ describe Projects::Alerting::NotifyService do
monitoring_tool: 'GitLab RSpec',
service: 'GitLab Test Suite',
description: 'Very detailed description',
hosts: ['1.1.1.1', '2.2.2.2']
hosts: ['1.1.1.1', '2.2.2.2'],
fingerprint: fingerprint
}.with_indifferent_access
end
let(:payload) { ActionController::Parameters.new(payload_raw).permit! }
......@@ -131,11 +133,23 @@ describe Projects::Alerting::NotifyService do
description: payload_raw.fetch(:description),
monitoring_tool: payload_raw.fetch(:monitoring_tool),
service: payload_raw.fetch(:service),
fingerprint: nil,
fingerprint: Digest::SHA1.hexdigest(fingerprint),
ended_at: nil
)
end
context 'existing alert with same fingerprint' do
let!(:existing_alert) { create(:alert_management_alert, project: project, fingerprint: Digest::SHA1.hexdigest(fingerprint)) }
it 'does not create AlertManagement::Alert' do
expect { subject }.not_to change(AlertManagement::Alert, :count)
end
it 'increments the existing alert count' do
expect { subject }.to change { existing_alert.reload.events }.from(1).to(2)
end
end
context 'with a minimal payload' do
let(:payload_raw) do
{
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment