Skip to content

Commit

Permalink
Merge pull request #108 from grafana/dev
Browse files Browse the repository at this point in the history
Merge dev to main
  • Loading branch information
mderynck authored Jun 17, 2022
2 parents 3db90db + 856c6d5 commit da98fa3
Show file tree
Hide file tree
Showing 20 changed files with 448 additions and 91 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Change Log

## 1.0.2 (2022-06-17)

- Fix Grafana Alerting integration to handle API changes in Grafana 9
- Improve public api endpoint for for outgoing webhooks (/actions) by adding ability to create, update and delete outgoing webhook instance

## 1.0.0 (2022-06-14)

- First Public Release

## 0.0.71 (2022-06-06)

- Initial Release
- Initial Commit Release
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
services:
engine:
image: grafana/oncall
restart: always
ports:
- 8080:8080
command: >
Expand Down Expand Up @@ -35,6 +36,7 @@ services:
celery:
# TODO: change to the public image once it's public
image: grafana/oncall
restart: always
command: sh -c "./celery_with_exporter.sh"
environment:
BASE_URL: $DOMAIN
Expand Down Expand Up @@ -122,6 +124,7 @@ services:

rabbitmq:
image: "rabbitmq:3.7.15-management"
restart: always
hostname: rabbitmq
mem_limit: 1000m
cpus: 0.5
Expand All @@ -144,6 +147,7 @@ services:

grafana:
image: "grafana/grafana:9.0.0-beta3"
restart: always
mem_limit: 500m
ports:
- 3000:3000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,31 @@ def check_for_connection_errors(cls, organization) -> Optional[str]:
)
return

def alerting_config_with_respect_to_grafana_version(
self, is_grafana_datasource, datasource_id, datasource_uid, client_method, *args
):
"""Quick fix for deprecated grafana alerting api endpoints"""

if is_grafana_datasource:
datasource_attr = GrafanaAlertingSyncManager.GRAFANA_CONTACT_POINT
config, response_info = client_method(datasource_attr, *args)
else:
# Get config by datasource id for Grafana version < 9
datasource_attr = datasource_id
config, response_info = client_method(datasource_attr, *args)

if response_info["status_code"] == status.HTTP_400_BAD_REQUEST:
# Get config by datasource uid for Grafana version >= 9
datasource_attr = datasource_uid
config, response_info = client_method(datasource_attr, *args)
if config is None:
logger.warning(
f"Got config None in alerting_config_with_respect_to_grafana_version with method "
f"{client_method.__name__} for is_grafana_datasource {is_grafana_datasource} for integration "
f"{self.alert_receive_channel.pk}; response: {response_info}"
)
return config, response_info

def create_contact_points(self) -> None:
"""
Get all alertmanager datasources and try to create contact points for them.
Expand Down Expand Up @@ -84,6 +109,10 @@ def create_contact_points(self) -> None:
datasources_to_create.append(datasource)

if datasources_to_create:
logger.warning(
f"Some contact points were not created for integration {self.alert_receive_channel.pk}, "
f"trying to create async"
)
# create other contact points async
schedule_create_contact_points_for_datasource(self.alert_receive_channel.pk, datasources_to_create)
else:
Expand All @@ -98,13 +127,14 @@ def create_contact_point(self, datasource=None) -> Optional["apps.alerts.models.
if datasource is None:
datasource = {}

datasource_id_or_grafana = datasource.get("id") or GrafanaAlertingSyncManager.GRAFANA_CONTACT_POINT
datasource_type = datasource.get("type") or GrafanaAlertingSyncManager.GRAFANA_CONTACT_POINT
is_grafana_datasource = datasource.get("id") is None
logger.info(
f"Create contact point for {datasource_type} datasource, integration {self.alert_receive_channel.pk}"
)
config, response_info = self.client.get_alerting_config(datasource_id_or_grafana)
config, response_info = self.alerting_config_with_respect_to_grafana_version(
is_grafana_datasource, datasource.get("id"), datasource.get("uid"), self.client.get_alerting_config
)

if config is None:
logger.warning(
Expand All @@ -116,7 +146,12 @@ def create_contact_point(self, datasource=None) -> Optional["apps.alerts.models.
updated_config = copy.deepcopy(config)

if config["alertmanager_config"] is None:
default_config, response_info = self.client.get_alertmanager_status_with_config(datasource_id_or_grafana)
default_config, response_info = self.alerting_config_with_respect_to_grafana_version(
is_grafana_datasource,
datasource.get("id"),
datasource.get("uid"),
self.client.get_alertmanager_status_with_config,
)
if default_config is None:
logger.warning(
f"Failed to create contact point (alertmanager_config is None) for integration "
Expand Down Expand Up @@ -144,7 +179,13 @@ def create_contact_point(self, datasource=None) -> Optional["apps.alerts.models.
)
updated_config["alertmanager_config"]["receivers"] = receivers + [new_receiver]

response, response_info = self.client.update_alerting_config(updated_config, datasource_id_or_grafana)
response, response_info = self.alerting_config_with_respect_to_grafana_version(
is_grafana_datasource,
datasource.get("id"),
datasource.get("uid"),
self.client.update_alerting_config,
updated_config,
)
if response is None:
logger.warning(
f"Failed to create contact point for integration {self.alert_receive_channel.pk} (POST): {response_info}"
Expand All @@ -153,7 +194,9 @@ def create_contact_point(self, datasource=None) -> Optional["apps.alerts.models.
logger.warning(f"Config: {config}\nUpdated config: {updated_config}")
return

config, response_info = self.client.get_alerting_config(datasource_id_or_grafana)
config, response_info = self.alerting_config_with_respect_to_grafana_version(
is_grafana_datasource, datasource.get("id"), datasource.get("uid"), self.client.get_alerting_config
)
contact_point = self._create_contact_point_from_payload(config, receiver_name, datasource)
contact_point_created_text = "created" if contact_point else "not created, creation will be retried"
logger.info(
Expand Down Expand Up @@ -232,6 +275,7 @@ def _create_contact_point_from_payload(
uid=receiver_config.get("uid"), # uid is None for non-Grafana datasource
datasource_name=datasource.get("name") or GrafanaAlertingSyncManager.GRAFANA_CONTACT_POINT,
datasource_id=datasource.get("id"), # id is None for Grafana datasource
datasource_uid=datasource.get("uid"), # uid is None for Grafana datasource
)
contact_point.save()
return contact_point
Expand Down Expand Up @@ -268,14 +312,23 @@ def sync_each_contact_point(self) -> None:

def sync_contact_point(self, contact_point) -> None:
"""Update name of contact point and related routes or delete it if integration was deleted"""
datasource_id = contact_point.datasource_id or GrafanaAlertingSyncManager.GRAFANA_CONTACT_POINT
datasource_type = "grafana" if not contact_point.datasource_id else "nongrafana"
datasource_type = (
GrafanaAlertingSyncManager.GRAFANA_CONTACT_POINT
if not (contact_point.datasource_id or contact_point.datasource_uid)
else "nongrafana"
)
is_grafana_datasource = datasource_type == GrafanaAlertingSyncManager.GRAFANA_CONTACT_POINT
logger.info(
f"Sync contact point for {datasource_type} (name: {contact_point.datasource_name}) datasource, integration "
f"{self.alert_receive_channel.pk}"
)

config, response_info = self.client.get_alerting_config(datasource_id)
config, response_info = self.alerting_config_with_respect_to_grafana_version(
is_grafana_datasource,
contact_point.datasource_id,
contact_point.datasource_uid,
self.client.get_alerting_config,
)
if config is None:
logger.warning(
f"Failed to update contact point (GET) for integration {self.alert_receive_channel.pk}: Is unified "
Expand All @@ -286,7 +339,7 @@ def sync_contact_point(self, contact_point) -> None:
receivers = config["alertmanager_config"]["receivers"]
name_in_alerting = self.find_name_of_contact_point(
contact_point.uid,
datasource_id,
is_grafana_datasource,
receivers,
)

Expand All @@ -300,8 +353,8 @@ def sync_contact_point(self, contact_point) -> None:
new_name,
)
contact_point.name = new_name
if datasource_id != GrafanaAlertingSyncManager.GRAFANA_CONTACT_POINT:
datasource_name = self.get_datasource_name(datasource_id)
if not is_grafana_datasource:
datasource_name = self.get_datasource_name(contact_point)
contact_point.datasource_name = datasource_name
contact_point.save(update_fields=["name", "datasource_name"])
# if integration was deleted, delete contact point and related routes
Expand All @@ -310,8 +363,13 @@ def sync_contact_point(self, contact_point) -> None:
updated_config,
name_in_alerting,
)

response, response_info = self.client.update_alerting_config(updated_config, datasource_id)
response, response_info = self.alerting_config_with_respect_to_grafana_version(
is_grafana_datasource,
contact_point.datasource_id,
contact_point.datasource_uid,
self.client.update_alerting_config,
updated_config,
)
if response is None:
logger.warning(
f"Failed to update contact point for integration {self.alert_receive_channel.pk} "
Expand Down Expand Up @@ -379,8 +437,8 @@ def _recursive_remove_routes(cls, alerting_route, name_in_alerting) -> dict:

return alerting_route

def find_name_of_contact_point(self, contact_point_uid, datasource_id, receivers) -> str:
if datasource_id == GrafanaAlertingSyncManager.GRAFANA_CONTACT_POINT:
def find_name_of_contact_point(self, contact_point_uid, is_grafana_datasource, receivers) -> str:
if is_grafana_datasource:
name_in_alerting = self._find_name_of_contact_point_by_uid(contact_point_uid, receivers)
else:
name_in_alerting = self._find_name_of_contact_point_by_integration_url(receivers)
Expand Down Expand Up @@ -415,6 +473,11 @@ def _find_name_of_contact_point_by_integration_url(self, receivers) -> str:
break
return name_in_alerting

def get_datasource_name(self, datasource_id) -> str:
datasource, _ = self.client.get_datasource(datasource_id)
def get_datasource_name(self, contact_point) -> str:
datasource_id = contact_point.datasource_id
datasource_uid = contact_point.datasource_uid
datasource, response_info = self.client.get_datasource(datasource_uid)
if response_info["status_code"] != 200:
# For old Grafana versions (< 9) try to use deprecated endpoint
datasource, _ = self.client.get_datasource_by_id(datasource_id)
return datasource["name"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2022-06-14 15:18

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('alerts', '0002_squashed_initial'),
]

operations = [
migrations.AddField(
model_name='grafanaalertingcontactpoint',
name='datasource_uid',
field=models.CharField(default=None, max_length=100, null=True),
),
]
3 changes: 2 additions & 1 deletion engine/apps/alerts/models/grafana_alerting_contact_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ class GrafanaAlertingContactPoint(models.Model):
default=None,
related_name="contact_points",
)
uid = models.CharField(max_length=100, null=True, default=None) # uid is None for non-Grafana datasource
uid = models.CharField(max_length=100, null=True, default=None) # receiver uid is None for non-Grafana datasource
name = models.CharField(max_length=100)
datasource_name = models.CharField(max_length=100, default="grafana")
datasource_id = models.IntegerField(null=True, default=None) # id is None for Grafana datasource
datasource_uid = models.CharField(max_length=100, null=True, default=None) # uid is None for Grafana datasource
35 changes: 29 additions & 6 deletions engine/apps/alerts/tasks/create_contact_points_for_datasource.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,14 @@ def create_contact_points_for_datasource(alert_receive_channel_id, datasource_li

AlertReceiveChannel = apps.get_model("alerts", "AlertReceiveChannel")

alert_receive_channel = AlertReceiveChannel.objects.get(pk=alert_receive_channel_id)
alert_receive_channel = AlertReceiveChannel.objects.filter(pk=alert_receive_channel_id).first()
if not alert_receive_channel:
logger.debug(
f"Cannot create contact point for integration {alert_receive_channel_id}: integration does not exist"
)
return

grafana_alerting_sync_manager = alert_receive_channel.grafana_alerting_sync_manager

client = GrafanaAPIClient(
api_url=alert_receive_channel.organization.grafana_url,
Expand All @@ -52,21 +59,37 @@ def create_contact_points_for_datasource(alert_receive_channel_id, datasource_li
datasources_to_create = []
for datasource in datasource_list:
contact_point = None
config, response_info = client.get_alerting_config(datasource["id"])
is_grafana_datasource = not (datasource.get("id") or datasource.get("uid"))
config, response_info = grafana_alerting_sync_manager.alerting_config_with_respect_to_grafana_version(
is_grafana_datasource, datasource.get("id"), datasource.get("uid"), client.get_alerting_config
)
if config is None:
logger.debug(
f"Got config None for is_grafana_datasource {is_grafana_datasource} "
f"for integration {alert_receive_channel_id}; response: {response_info}"
)
if response_info.get("status_code") == status.HTTP_404_NOT_FOUND:
client.get_alertmanager_status_with_config(datasource["id"])
contact_point = alert_receive_channel.grafana_alerting_sync_manager.create_contact_point(datasource)
grafana_alerting_sync_manager.alerting_config_with_respect_to_grafana_version(
is_grafana_datasource,
datasource.get("id"),
datasource.get("uid"),
client.get_alertmanager_status_with_config,
)
contact_point = grafana_alerting_sync_manager.create_contact_point(datasource)
elif response_info.get("status_code") == status.HTTP_400_BAD_REQUEST:
logger.warning(
f"Failed to create contact point for integration {alert_receive_channel_id}, "
f"datasource info: {datasource}; response: {response_info}"
)
continue
else:
contact_point = alert_receive_channel.grafana_alerting_sync_manager.create_contact_point(datasource)
contact_point = grafana_alerting_sync_manager.create_contact_point(datasource)
if contact_point is None:
# Failed to create contact point duo to getting wrong alerting config.
logger.warning(
f"Failed to create contact point for integration {alert_receive_channel_id} due to getting wrong "
f"config, datasource info: {datasource}; response: {response_info}. Retrying"
)
# Failed to create contact point due to getting wrong alerting config.
# Add datasource to list and retry to create contact point for it again
datasources_to_create.append(datasource)

Expand Down
2 changes: 0 additions & 2 deletions engine/apps/api/serializers/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import humanize
import pytz
from django.apps import apps
from django.conf import settings
from django.utils import timezone
from rest_framework import fields, serializers

Expand Down Expand Up @@ -121,7 +120,6 @@ def get_env_status(self, obj):
return {
"telegram_configured": telegram_configured,
"twilio_configured": twilio_configured,
"extra_messaging_backends_enabled": settings.FEATURE_EXTRA_MESSAGING_BACKENDS_ENABLED,
}

def get_stats(self, obj):
Expand Down
24 changes: 0 additions & 24 deletions engine/apps/api/tests/test_organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,30 +80,6 @@ def test_current_team_update_permissions(
assert response.status_code == expected_status


@pytest.mark.django_db
@pytest.mark.parametrize("feature_flag_enabled", [False, True])
def test_current_team_messaging_backend_status(
settings,
make_organization,
make_user_for_organization,
make_token_for_organization,
make_user_auth_headers,
feature_flag_enabled,
):
org = make_organization()
tester = make_user_for_organization(org, role=Role.ADMIN)
_, token = make_token_for_organization(org)

client = APIClient()

settings.FEATURE_EXTRA_MESSAGING_BACKENDS_ENABLED = feature_flag_enabled
url = reverse("api-internal:api-current-team")
response = client.get(url, format="json", **make_user_auth_headers(tester, token))

assert response.status_code == status.HTTP_200_OK
assert response.json()["env_status"]["extra_messaging_backends_enabled"] == bool(feature_flag_enabled)


@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
Expand Down
Loading

0 comments on commit da98fa3

Please sign in to comment.