diff --git a/README.rst b/README.rst index bb9b11c..56ba208 100644 --- a/README.rst +++ b/README.rst @@ -131,6 +131,94 @@ StatsD server launched by monasca-agent. Default host and port points to - ConfigDBTime - SendNotificationTime +Plugins +------- + +The following notification plugins are available: + +- Email +- HipChat +- Jira +- Pagerduty +- Slack +- Webhook + +The plugins can be configured via the Monasca Notification config file. In +general you will need to follow these steps to enable a plugin: + +- Make sure that the plugin is enabled in the config file +- Make sure that the plugin is configured in the config file +- Restart the Monasca Notification service + +Slack plugin +~~~~~~~~~~~~ + +To use the Slack plugin you must first configure an incoming `webhook`_ +for the Slack channel you wish to post notifications to. The notification can +then be created as follows: + +:: + + monasca notification-create slack_notification slack https://hooks.slack.com/services/MY/SECRET/WEBHOOK/URL + +Note that whilst it is also possible to use a token instead of a webhook, +this approach is now `deprecated`_. + +By default the Slack notification will dump all available information into +the alert. For example, a notification may be posted to Slack which looks +like this: + +:: + + { + "metrics":[ + { + "dimensions":{ + "hostname":"operator" + }, + "id":null, + "name":"cpu.user_perc" + } + ], + "alarm_id":"20a54a65-44b8-4ac9-a398-1f2d888827d2", + "state":"ALARM", + "alarm_timestamp":1556703552, + "tenant_id":"62f7a7a314904aa3ab137d569d6b4fde", + "old_state":"OK", + "alarm_description":"Dummy alarm", + "message":"Thresholds were exceeded for the sub-alarms: count(cpu.user_perc, deterministic) >= 1.0 with the values: [1.0]", + "alarm_definition_id":"78ce7b53-f7e6-4b51-88d0-cb741e7dc906", + "alarm_name":"dummy_alarm" + } + +The format of the above message can be customised with a Jinja template. All fields +from the raw Slack message are available in the template. For example, you may +configure the plugin as follows: + +:: + + [notification_types] + enabled = slack + + [slack_notifier] + message_template = /etc/monasca/slack_template.j2 + timeout = 10 + ca_certs = /etc/ssl/certs/ca-bundle.crt + insecure = False + +With the following contents of `/etc/monasca/slack_template.j2`: + +:: + + {{ alarm_name }} has triggered on {% for item in metrics %}host {{ item.dimensions.hostname }}{% if not loop.last %}, {% endif %}{% endfor %}. + +With this configuration, the raw Slack message above would be transformed +into: + +:: + + dummy_alarm has triggered on host(s): operator. + Future Considerations ===================== @@ -146,3 +234,7 @@ Future Considerations NotificationEngine instance using webhooks to a local http server. Is that fast enough? - Are we putting too much load on Kafka at ~200 commits per second? + +.. _webhook: https://api.slack.com/incoming-webhooks + +.. _deprecated: https://api.slack.com/custom-integrations/legacy-tokens diff --git a/monasca_notification/plugins/slack_notifier.py b/monasca_notification/plugins/slack_notifier.py index f972627..7ff7345 100644 --- a/monasca_notification/plugins/slack_notifier.py +++ b/monasca_notification/plugins/slack_notifier.py @@ -14,11 +14,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os import requests from six.moves import urllib import ujson as json from debtcollector import removals +import jinja2 from oslo_config import cfg from monasca_notification.plugins import abstract_notifier @@ -86,10 +88,19 @@ class SlackNotifier(abstract_notifier.AbstractNotifier): 'metrics': notification.metrics} slack_request = {} - slack_request['text'] = json.dumps(body, indent=3) + if CONF.slack_notifier.message_template: + slack_request['text'] = self._render_message_template(body) + else: + slack_request['text'] = json.dumps(body, indent=3) return slack_request + def _render_message_template(self, params): + path, name = os.path.split(CONF.slack_notifier.message_template) + loader = jinja2.FileSystemLoader(path) + env = jinja2.Environment(loader=loader, autoescape=True) + return env.get_template(name).render(params) + def _check_response(self, result): if 'application/json' in result.headers.get('Content-Type'): response = result.json() @@ -192,7 +203,8 @@ slack_notifier_opts = [ cfg.IntOpt(name='timeout', default=5, min=1), cfg.BoolOpt(name='insecure', default=True), cfg.StrOpt(name='ca_certs', default=None), - cfg.StrOpt(name='proxy', default=None) + cfg.StrOpt(name='proxy', default=None), + cfg.StrOpt(name='message_template', default=None) ] diff --git a/tests/resources/test_slackformat.template b/tests/resources/test_slackformat.template new file mode 100644 index 0000000..3531ce6 --- /dev/null +++ b/tests/resources/test_slackformat.template @@ -0,0 +1 @@ +{{ alarm_name }} has triggered on {% for item in metrics %}host {{ metrics[0].dimensions.hostname }}, service {{ item.dimensions.service }}{% if not loop.last %}, {% endif %}{% endfor %}. diff --git a/tests/test_slack_notification.py b/tests/test_slack_notification.py index 517b317..a5272ed 100644 --- a/tests/test_slack_notification.py +++ b/tests/test_slack_notification.py @@ -110,6 +110,15 @@ class TestSlack(base.PluginTestCase): def _validate_post_args(self, post_args, data_format): self.assertEqual(slack_text(), json.loads(post_args.get(data_format).get('text'))) + self._validate_post_args_base(post_args) + + def _validate_templated_post_args(self, post_args, data_format, + expected_slack_text): + self.assertEqual(expected_slack_text, + post_args.get(data_format).get('text')) + self._validate_post_args_base(post_args) + + def _validate_post_args_base(self, post_args): self.assertEqual({'https': 'http://yourid:password@proxyserver:8080'}, post_args.get('proxies')) self.assertEqual(50, post_args.get('timeout')) @@ -128,6 +137,24 @@ class TestSlack(base.PluginTestCase): self._validate_post_args(mock_method.call_args_list[0][1], 'json') self.assertEqual([], slack_notifier.SlackNotifier._raw_data_url_caches) + def test_templated_slack_webhook_success(self): + """A templated message is successfully sent as json + """ + self.conf_override( + message_template='tests/resources/test_slackformat.template', + group='slack_notifier' + ) + response_list = [RequestsResponse(200, 'ok', + {'Content-Type': 'application/text'})] + mock_method, result = self._notify(response_list) + self.assertTrue(result) + mock_method.assert_called_once() + expected_text = 'test Alarm has triggered on host foo1, service bar1.' + self._validate_templated_post_args(mock_method.call_args_list[0][1], + 'json', + expected_text) + self.assertEqual([], slack_notifier.SlackNotifier._raw_data_url_caches) + def test_slack_webhook_fail(self): """data is sent twice as json and raw data, and slack returns failure for both requests