Add HipChat and Slack Notification types

This patch
   1. Supports for loading new notification types as plugins.
   2) Adds new plugins for HipChat and Slack
   3) Insert Notification types during startup

Partially-implements: blueprint notification-engine-plugin
Change-Id: I246ced3fe22a9797a3c8384f7bda166797cfac3a
This commit is contained in:
haali1 2016-06-27 15:09:31 -07:00 committed by Haneef Ali
parent c74ea85c71
commit be6fb21e19
23 changed files with 435 additions and 45 deletions

View File

@ -1,5 +1,5 @@
# Copyright 2015 FUJITSU LIMITED # Copyright 2015 FUJITSU LIMITED
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License at # in compliance with the License. You may obtain a copy of the License at
@ -21,3 +21,5 @@ class BaseRepo(object):
self._find_alarm_state_sql = """SELECT state self._find_alarm_state_sql = """SELECT state
FROM alarm FROM alarm
WHERE alarm.id = %s""" WHERE alarm.id = %s"""
self._insert_notification_types_sql = """INSERT INTO notification_method_type (name) VALUES ( %s)"""
self._find_all_notification_types_sql = """SELECT name from notification_method_type """

View File

@ -1,5 +1,5 @@
# Copyright 2015 FUJITSU LIMITED # Copyright 2015 FUJITSU LIMITED
# (C) Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2015,2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License at # in compliance with the License. You may obtain a copy of the License at
@ -86,3 +86,29 @@ class MysqlRepo(BaseRepo):
self._mysql = None self._mysql = None
log.exception("Couldn't fetch the current alarm state %s", e) log.exception("Couldn't fetch the current alarm state %s", e)
raise exc.DatabaseException(e) raise exc.DatabaseException(e)
def fetch_notification_method_types(self):
try:
if self._mysql is None:
self._connect_to_mysql()
cur = self._mysql.cursor()
cur.execute(self._find_all_notification_types_sql)
for row in cur:
yield (row[0])
except pymysql.Error as e:
self._mysql = None
log.exception("Couldn't fetch notification types %s", e)
raise exc.DatabaseException(e)
def insert_notification_method_types(self, notification_types):
try:
if self._mysql is None:
self._connect_to_mysql()
cur = self._mysql.cursor()
cur.executemany(self._insert_notification_types_sql, notification_types)
except pymysql.Error as e:
self._mysql = None
log.exception("Couldn't insert notification types %s", e)
raise exc.DatabaseException(e)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2015 Fujitsu Technology Solutions # Copyright 2015 Fujitsu Technology Solutions
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -44,3 +44,8 @@ def create_notification_method_model(metadata=None):
Column('period', int), Column('period', int),
Column('created_at', DateTime, default=lambda: datetime.utcnow()), Column('created_at', DateTime, default=lambda: datetime.utcnow()),
Column('updated_at', DateTime, onupdate=lambda: datetime.utcnow())) Column('updated_at', DateTime, onupdate=lambda: datetime.utcnow()))
def create_notification_method_type_model(metadata=None):
return Table('notification_method_type', metadata,
Column('name', String(20), primary_key=True))

View File

@ -1,5 +1,5 @@
# Copyright 2015 FUJITSU LIMITED # Copyright 2015 FUJITSU LIMITED
# (C) Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2015,2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License at # in compliance with the License. You may obtain a copy of the License at
@ -30,6 +30,7 @@ class OrmRepo(object):
aa = models.create_alarm_action_model(metadata).alias('aa') aa = models.create_alarm_action_model(metadata).alias('aa')
nm = models.create_notification_method_model(metadata).alias('nm') nm = models.create_notification_method_model(metadata).alias('nm')
nmt = models.create_notification_method_type_model(metadata).alias('nmt')
self._orm_query = select([nm.c.name, nm.c.type, nm.c.address, nm.c.periodic_interval])\ self._orm_query = select([nm.c.name, nm.c.type, nm.c.address, nm.c.periodic_interval])\
.select_from(aa.join(nm, aa.c.action_id == nm.c.id))\ .select_from(aa.join(nm, aa.c.action_id == nm.c.id))\
@ -37,6 +38,8 @@ class OrmRepo(object):
and_(aa.c.alarm_definition_id == bindparam('alarm_definition_id'), and_(aa.c.alarm_definition_id == bindparam('alarm_definition_id'),
aa.c.alarm_state == bindparam('alarm_state'))) aa.c.alarm_state == bindparam('alarm_state')))
self._orm_nmt_query = select([nmt.c.name])
self._orm = None self._orm = None
def fetch_notification(self, alarm): def fetch_notification(self, alarm):
@ -51,3 +54,25 @@ class OrmRepo(object):
except DatabaseError as e: except DatabaseError as e:
log.exception("Couldn't fetch alarms actions %s", e) log.exception("Couldn't fetch alarms actions %s", e)
raise exc.DatabaseException(e) raise exc.DatabaseException(e)
def fetch_notification_method_types(self):
try:
with self._orm_engine.connect() as conn:
log.debug('Orm query {%s}', str(self._orm_nmt_query))
notification_method_types = conn.execute(self._orm_nmt_query).fetchall()
return [row[0] for row in notification_method_types]
except DatabaseError as e:
log.exception("Couldn't fetch notification method types %s", e)
raise exc.DatabaseException(e)
def insert_notification_method_types(self, notification_types):
try:
with self._orm_engine.connect() as conn:
for notification_type in notification_types:
conn.execute(self.nmt.insert(), notification_type)
except DatabaseError as e:
self._mysql = None
log.exception("Couldn't insert notification types %s", e)
raise exc.DatabaseException(e)

View File

@ -1,5 +1,5 @@
# Copyright 2015 FUJITSU LIMITED # Copyright 2015 FUJITSU LIMITED
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License at # in compliance with the License. You may obtain a copy of the License at
@ -59,3 +59,29 @@ class PostgresqlRepo(BaseRepo):
except psycopg2.Error as e: except psycopg2.Error as e:
log.exception("Couldn't fetch current alarm state %s", e) log.exception("Couldn't fetch current alarm state %s", e)
raise exc.DatabaseException(e) raise exc.DatabaseException(e)
def fetch_notification_method_types(self):
try:
if self._pgsql is None:
self._connect_to_pgsql()
cur = self._pgsql.cursor()
cur.execute(self._find_all_notification_types_sql)
for row in cur:
yield (row[0])
except psycopg2.Error as e:
self._mysql = None
log.exception("Couldn't fetch notification types %s", e)
raise exc.DatabaseException(e)
def insert_notification_method_types(self, notification_types):
try:
if self._pgsql is None:
self._connect_to_pgsql()
cur = self._pgsql.cursor()
cur.executemany(self._insert_notification_types_sql, notification_types)
except psycopg2.Error as e:
self._mysql = None
log.exception("Couldn't insert notification types %s", e)
raise exc.DatabaseException(e)

View File

@ -1,4 +1,4 @@
# (C) Copyright 2015-2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -41,7 +41,7 @@ class NotificationEngine(object):
self._producer = KafkaProducer(config['kafka']['url']) self._producer = KafkaProducer(config['kafka']['url'])
self._alarm_ttl = config['processors']['alarm']['ttl'] self._alarm_ttl = config['processors']['alarm']['ttl']
self._alarms = AlarmProcessor(self._alarm_ttl, config) self._alarms = AlarmProcessor(self._alarm_ttl, config)
self._notifier = NotificationProcessor(config['notification_types']) self._notifier = NotificationProcessor(config)
self._config = config self._config = config

View File

@ -1,4 +1,4 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -45,7 +45,7 @@ class PeriodicEngine(object):
self._producer = KafkaProducer(config['kafka']['url']) self._producer = KafkaProducer(config['kafka']['url'])
self._notifier = NotificationProcessor(config['notification_types']) self._notifier = NotificationProcessor(config)
self._db_repo = get_db_repo(config) self._db_repo = get_db_repo(config)
def _keep_sending(self, alarm_id, original_state): def _keep_sending(self, alarm_id, original_state):

View File

View File

@ -1,4 +1,4 @@
# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2015 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.

View File

@ -1,4 +1,4 @@
# (C) Copyright 2015-2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -17,7 +17,7 @@ import email.mime.text
import smtplib import smtplib
import time import time
from abstract_notifier import AbstractNotifier from monasca_notification.plugins.abstract_notifier import AbstractNotifier
EMAIL_SINGLE_HOST_BASE = u'''On host "{hostname}" for target "{target_host}" {message} EMAIL_SINGLE_HOST_BASE = u'''On host "{hostname}" for target "{target_host}" {message}

View File

@ -0,0 +1,112 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development LP
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import requests
import urlparse
from monasca_notification.plugins.abstract_notifier import AbstractNotifier
"""
notification.address = https://hipchat.hpcloud.net/v2/room/<room_id>/notification?auth_token=432432
How to get access token?
1) Login to Hipchat with the user account which is used for notification
2) Go to this page. https://hipchat.hpcloud.net/account/api (Replace your hipchat server name)
3) You can see option to "Create token". Use the capability "SendNotification"
How to get the Room ID?
1) Login to Hipchat with the user account which is used for notification
2) Go to this page. https://hipchat.hpcloud.net/account/api (Replace your hipchat server name)
3) Click on the Rooms tab
4) Click on any Room of your choice.
5) Room ID is the API ID field
"""
class HipChatNotifier(AbstractNotifier):
def __init__(self, log):
self._log = log
def config(self, config_dict):
self._config = {'timeout': 5}
self._config.update(config_dict)
@property
def type(self):
return "hipchat"
@property
def statsd_name(self):
return 'sent_hipchat_count'
def _build_hipchat_message(self, notification):
"""Builds hipchat message body
"""
body = {'alarm_id': notification.alarm_id,
'alarm_definition_id': notification.raw_alarm['alarmDefinitionId'],
'alarm_name': notification.alarm_name,
'alarm_description': notification.raw_alarm['alarmDescription'],
'alarm_timestamp': notification.alarm_timestamp,
'state': notification.state,
'old_state': notification.raw_alarm['oldState'],
'message': notification.message,
'tenant_id': notification.tenant_id,
'metrics': notification.metrics}
hipchat_request = {}
hipchat_request['color'] = 'green'
hipchat_request['message_format'] = 'text'
hipchat_request['message'] = json.dumps(body, indent=3)
return hipchat_request
def send_notification(self, notification):
"""Send the notification via hipchat
Posts on the given url
"""
hipchat_message = self._build_hipchat_message(notification)
parsed_url = urlparse.urlsplit(notification.address)
query_params = urlparse.parse_qs(parsed_url.query)
# URL without query params
url = urlparse.urljoin(notification.address, urlparse.urlparse(notification.address).path)
# Default option is to do cert verification
verify = self._config.get('insecure', False)
# If ca_certs is specified, do cert validation and ignore insecure flag
if (self._config.get("ca_certs")):
verify = self._config.get("ca_certs")
try:
# Posting on the given URL
result = requests.post(url=url,
data=hipchat_message,
verify=verify,
params=query_params,
timeout=self._config['timeout'])
if result.status_code in range(200, 300):
self._log.info("Notification successfully posted.")
return True
else:
msg = "Received an HTTP code {} when trying to send to hipchat on URL {} with response {} ."
self._log.error(msg.format(result.status_code, url, result.text))
return False
except Exception:
self._log.exception("Error trying to send to hipchat on URL {}".format(url))
return False

View File

@ -1,4 +1,4 @@
# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2015,2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -16,7 +16,8 @@
import json import json
import requests import requests
from abstract_notifier import AbstractNotifier from monasca_notification.plugins.abstract_notifier import AbstractNotifier
VALID_HTTP_CODES = [200, 201, 204] VALID_HTTP_CODES = [200, 201, 204]

View File

@ -0,0 +1,116 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development LP
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import requests
import urlparse
from monasca_notification.plugins.abstract_notifier import AbstractNotifier
"""
notification.address = https://slack.com/api/chat.postMessage?token=token&channel=#channel"
Slack documentation about tokens:
1. Login to your slack account via browser and check the following pages
a. https://api.slack.com/docs/oauth-test-tokens
b. https://api.slack.com/tokens
"""
class SlackNotifier(AbstractNotifier):
def __init__(self, log):
self._log = log
def config(self, config_dict):
self._config = {'timeout': 5}
self._config.update(config_dict)
@property
def type(self):
return "slack"
@property
def statsd_name(self):
return 'sent_slack_count'
def _build_slack_message(self, notification):
"""Builds slack message body
"""
body = {'alarm_id': notification.alarm_id,
'alarm_definition_id': notification.raw_alarm['alarmDefinitionId'],
'alarm_name': notification.alarm_name,
'alarm_description': notification.raw_alarm['alarmDescription'],
'alarm_timestamp': notification.alarm_timestamp,
'state': notification.state,
'old_state': notification.raw_alarm['oldState'],
'message': notification.message,
'tenant_id': notification.tenant_id,
'metrics': notification.metrics}
slack_request = {}
slack_request['text'] = json.dumps(body, indent=3)
return slack_request
def send_notification(self, notification):
"""Send the notification via slack
Posts on the given url
"""
slack_message = self._build_slack_message(notification)
address = notification.address
# "#" is reserved character and replace it with ascii equivalent
# Slack room has "#" as first character
address = address.replace("#", "%23")
parsed_url = urlparse.urlsplit(address)
query_params = urlparse.parse_qs(parsed_url.query)
# URL without query params
url = urlparse.urljoin(address, urlparse.urlparse(address).path)
# Default option is to do cert verification
verify = self._config.get('insecure', False)
# If ca_certs is specified, do cert validation and ignore insecure flag
if (self._config.get("ca_certs")):
verify = self._config.get("ca_certs")
try:
# Posting on the given URL
self._log.debug("Sending to the url {0} , with query_params {1}".format(url, query_params))
result = requests.post(url=url,
data=slack_message,
verify=verify,
params=query_params,
timeout=self._config['timeout'])
if result.status_code not in range(200, 300):
self._log.error("Received an HTTP code {} when trying to post on URL {}."
.format(result.status_code, url))
return False
# Slack returns 200 ok even if the token is invalid. Response has valid error message
response = json.loads(result.text)
if response.get('ok'):
self._log.info("Notification successfully posted.")
return True
else:
self._log.error("Received an error message {} when trying to send to slack on URL {}."
.format(response.get("error"), url))
return False
except Exception:
self._log.exception("Error trying to send to slack on URL {}".format(url))
return False

View File

@ -1,4 +1,4 @@
# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2015,2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -16,7 +16,7 @@
import json import json
import requests import requests
from abstract_notifier import AbstractNotifier from monasca_notification.plugins.abstract_notifier import AbstractNotifier
class WebhookNotifier(AbstractNotifier): class WebhookNotifier(AbstractNotifier):

View File

@ -1,4 +1,4 @@
# (C) Copyright 2014-2015 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -16,6 +16,7 @@
import logging import logging
import monascastatsd import monascastatsd
from monasca_notification.common.utils import get_db_repo
from monasca_notification.processors.base import BaseProcessor from monasca_notification.processors.base import BaseProcessor
from monasca_notification.types import notifiers from monasca_notification.types import notifiers
@ -27,7 +28,24 @@ class NotificationProcessor(BaseProcessor):
def __init__(self, config): def __init__(self, config):
self.statsd = monascastatsd.Client(name='monasca', dimensions=BaseProcessor.dimensions) self.statsd = monascastatsd.Client(name='monasca', dimensions=BaseProcessor.dimensions)
notifiers.init(self.statsd) notifiers.init(self.statsd)
notifiers.config(config) notifiers.load_plugins(config['notification_types'])
notifiers.config(config['notification_types'])
self._db_repo = get_db_repo(config)
self.insert_configured_plugins()
def insert_configured_plugins(self):
"""Persists configured plugin types in DB
For each notification type configured add it in db, if it is not there
"""
configured_plugin_types = notifiers.enabled_notifications()
persisted_plugin_types = self._db_repo.fetch_notification_method_types()
remaining_plugin_types = set(configured_plugin_types) - set(persisted_plugin_types)
if remaining_plugin_types:
log.info("New plugins detected: Adding new notification types {} to database"
.format(remaining_plugin_types))
self._db_repo.insert_notification_method_types(remaining_plugin_types)
def send(self, notifications): def send(self, notifications):
"""Send the notifications """Send the notifications

View File

@ -1,4 +1,4 @@
# (C) Copyright 2015-2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -47,7 +47,7 @@ class RetryEngine(object):
self._producer = KafkaProducer(config['kafka']['url']) self._producer = KafkaProducer(config['kafka']['url'])
self._notifier = NotificationProcessor(config['notification_types']) self._notifier = NotificationProcessor(config)
def run(self): def run(self):
for raw_notification in self._consumer: for raw_notification in self._consumer:

View File

@ -1,4 +1,4 @@
# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2015,2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -16,9 +16,10 @@
import logging import logging
import time import time
from monasca_notification.types import email_notifier from monasca_common.simport import simport
from monasca_notification.types import pagerduty_notifier from monasca_notification.plugins import email_notifier
from monasca_notification.types import webhook_notifier from monasca_notification.plugins import pagerduty_notifier
from monasca_notification.plugins import webhook_notifier
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -40,10 +41,18 @@ def init(statsd_obj):
possible_notifiers.append(pagerduty_notifier.PagerdutyNotifier(log)) possible_notifiers.append(pagerduty_notifier.PagerdutyNotifier(log))
def load_plugins(config):
for plugin_class in config.get("plugins", []):
try:
possible_notifiers.append(simport.load(plugin_class)(log))
except Exception:
log.exception("unable to load the class {0} , ignoring it".format(plugin_class))
def enabled_notifications(): def enabled_notifications():
results = [] results = []
for key in configured_notifiers: for key in configured_notifiers:
results.append(key) results.append(key.upper())
return results return results

View File

@ -33,6 +33,10 @@ postgresql:
host: 127.0.0.1 host: 127.0.0.1
notification_types: notification_types:
plugins:
- monasca_notification.plugins.hipchat_notifier:HipChatNotifier
- monasca_notification.plugins.slack_notifier:SlackNotifier
email: email:
server: 192.168.10.4 server: 192.168.10.4
port: 25 port: 25
@ -48,6 +52,16 @@ notification_types:
timeout: 5 timeout: 5
url: "https://events.pagerduty.com/generic/2010-04-15/create_event.json" url: "https://events.pagerduty.com/generic/2010-04-15/create_event.json"
hipchat:
timeout: 5
ca_certs: "/etc/ssl/certs/ca-certificates.crt"
insecure: False
slack:
timeout: 5
ca_certs: "/etc/ssl/certs/ca-certificates.crt"
insecure: False
processors: processors:
alarm: alarm:
number: 2 number: 2

View File

@ -1,4 +1,4 @@
# (C) Copyright 2014-2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -20,7 +20,7 @@ import time
import unittest import unittest
from monasca_notification.notification import Notification from monasca_notification.notification import Notification
from monasca_notification.types import email_notifier from monasca_notification.plugins import email_notifier
UNICODE_CHAR = unichr(2344) UNICODE_CHAR = unichr(2344)
UNICODE_CHAR_ENCODED = UNICODE_CHAR.encode("utf-8") UNICODE_CHAR_ENCODED = UNICODE_CHAR.encode("utf-8")
@ -90,7 +90,7 @@ class TestEmail(unittest.TestCase):
def _smtbStubException(self, *arg, **kwargs): def _smtbStubException(self, *arg, **kwargs):
return smtpStubException(self.trap) return smtpStubException(self.trap)
@mock.patch('monasca_notification.types.email_notifier.smtplib') @mock.patch('monasca_notification.plugins.email_notifier.smtplib')
def notify(self, smtp_stub, metric, mock_smtp): def notify(self, smtp_stub, metric, mock_smtp):
mock_smtp.SMTP = smtp_stub mock_smtp.SMTP = smtp_stub
@ -185,7 +185,7 @@ class TestEmail(unittest.TestCase):
return_value = self.trap.pop(0) return_value = self.trap.pop(0)
self.assertTrue(return_value) self.assertTrue(return_value)
@mock.patch('monasca_notification.types.email_notifier.smtplib') @mock.patch('monasca_notification.plugins.email_notifier.smtplib')
def test_smtp_sendmail_failed_connection_twice(self, mock_smtp): def test_smtp_sendmail_failed_connection_twice(self, mock_smtp):
"""Email that fails on smtp_connect twice """Email that fails on smtp_connect twice
""" """
@ -229,7 +229,7 @@ class TestEmail(unittest.TestCase):
self.assertIn("SMTP server disconnected. Will reconnect and retry message.", self.trap) self.assertIn("SMTP server disconnected. Will reconnect and retry message.", self.trap)
self.assertIn("Unable to connect to email server.", self.trap) self.assertIn("Unable to connect to email server.", self.trap)
@mock.patch('monasca_notification.types.email_notifier.smtplib') @mock.patch('monasca_notification.plugins.email_notifier.smtplib')
def test_smtp_sendmail_smtp_None(self, mock_smtp): def test_smtp_sendmail_smtp_None(self, mock_smtp):
"""Email that fails on smtp_connect twice """Email that fails on smtp_connect twice
""" """
@ -277,7 +277,7 @@ class TestEmail(unittest.TestCase):
.format(self.email_config['server']), .format(self.email_config['server']),
self.trap) self.trap)
@mock.patch('monasca_notification.types.email_notifier.smtplib') @mock.patch('monasca_notification.plugins.email_notifier.smtplib')
def test_smtp_sendmail_failed_connection_once_then_email(self, mock_smtp): def test_smtp_sendmail_failed_connection_once_then_email(self, mock_smtp):
"""Email that fails on smtp_connect once then email """Email that fails on smtp_connect once then email
""" """
@ -319,7 +319,7 @@ class TestEmail(unittest.TestCase):
self.assertIn("Error sending Email Notification", self.trap) self.assertIn("Error sending Email Notification", self.trap)
self.assertNotIn("Unable to connect to email server.", self.trap) self.assertNotIn("Unable to connect to email server.", self.trap)
@mock.patch('monasca_notification.types.email_notifier.smtplib') @mock.patch('monasca_notification.plugins.email_notifier.smtplib')
def test_smtp_sendmail_failed_connection_once(self, mock_smtp): def test_smtp_sendmail_failed_connection_once(self, mock_smtp):
"""Email that fails on smtp_connect once """Email that fails on smtp_connect once
""" """
@ -359,7 +359,7 @@ class TestEmail(unittest.TestCase):
self.assertIn("Sent email to %s, notification %s" self.assertIn("Sent email to %s, notification %s"
% (notification.address, notification.to_json()), self.trap) % (notification.address, notification.to_json()), self.trap)
@mock.patch('monasca_notification.types.email_notifier.smtplib') @mock.patch('monasca_notification.plugins.email_notifier.smtplib')
def test_smtp_sendmail_failed_exception(self, mock_smtp): def test_smtp_sendmail_failed_exception(self, mock_smtp):
"""Email that fails on exception """Email that fails on exception
""" """

View File

@ -1,4 +1,4 @@
# (C) Copyright 2014-2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -47,17 +47,24 @@ class TestNotificationProcessor(unittest.TestCase):
'timeout': 60, 'timeout': 60,
'from_addr': 'hpcs.mon@hp.com'} 'from_addr': 'hpcs.mon@hp.com'}
self.mysql_config = {'ssl': None,
'host': 'mysql_host',
'port': 'mysql_port',
'user': 'mysql_user',
'db': 'dbname',
'passwd': 'mysql_passwd'}
def tearDown(self): def tearDown(self):
pass pass
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
# Test helper functions # Test helper functions
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
@mock.patch('pymysql.connect')
@mock.patch('monasca_notification.processors.notification_processor.monascastatsd') @mock.patch('monasca_notification.processors.notification_processor.monascastatsd')
@mock.patch('monasca_notification.types.notifiers.email_notifier.smtplib') @mock.patch('monasca_notification.types.notifiers.email_notifier.smtplib')
@mock.patch('monasca_notification.processors.notification_processor.notifiers.log') @mock.patch('monasca_notification.processors.notification_processor.notifiers.log')
def _start_processor(self, notifications, mock_log, mock_smtp, mock_statsd): def _start_processor(self, notifications, mock_log, mock_smtp, mock_statsd, mock_pymsql):
"""Start the processor with the proper mocks """Start the processor with the proper mocks
""" """
# Since the log runs in another thread I can mock it directly, instead change the methods to put to a queue # Since the log runs in another thread I can mock it directly, instead change the methods to put to a queue
@ -68,6 +75,8 @@ class TestNotificationProcessor(unittest.TestCase):
config = {} config = {}
config["email"] = self.email_config config["email"] = self.email_config
config["mysql"] = self.mysql_config
config["notification_types"] = {}
processor = (notification_processor.NotificationProcessor(config)) processor = (notification_processor.NotificationProcessor(config))
processor.send(notifications) processor.send(notifications)

View File

@ -1,4 +1,4 @@
# (C) Copyright 2015-2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -153,7 +153,7 @@ class TestInterface(unittest.TestCase):
self.assertEqual(len(notifications), 3) self.assertEqual(len(notifications), 3)
self.assertEqual(sorted(notifications), self.assertEqual(sorted(notifications),
["email", "pagerduty", "webhook"]) ["EMAIL", "PAGERDUTY", "WEBHOOK"])
@mock.patch('monasca_notification.types.notifiers.email_notifier.smtplib') @mock.patch('monasca_notification.types.notifiers.email_notifier.smtplib')
@mock.patch('monasca_notification.types.notifiers.log') @mock.patch('monasca_notification.types.notifiers.log')
@ -364,3 +364,30 @@ class TestInterface(unittest.TestCase):
self.assertEqual(self.statsd.timer.timer_calls['email_time_start'], 3) self.assertEqual(self.statsd.timer.timer_calls['email_time_start'], 3)
self.assertEqual(self.statsd.timer.timer_calls['email_time_stop'], 3) self.assertEqual(self.statsd.timer.timer_calls['email_time_stop'], 3)
self.assertEqual(self.statsd.counter.counter, 3) self.assertEqual(self.statsd.counter.counter, 3)
def test_plugin_load(self):
config_dict = {"plugins": ["monasca_notification.plugins.hipchat_notifier:HipChatNotifier",
"monasca_notification.plugins.slack_notifier:SlackNotifier"]}
notifiers.init(self.statsd)
notifiers.load_plugins(config_dict)
self.assertEqual(len(notifiers.possible_notifiers), 5)
configured_plugins = ["email", "webhook", "pagerduty", "hipchat", "slack"]
for plugin in notifiers.configured_notifiers:
self.asssertIn(plugin.type in configured_plugins)
@mock.patch('monasca_notification.types.notifiers.log')
def test_invalid_plugin_load_exception_ignored(self, mock_log):
mock_log.exception = self.trap.append
config_dict = {"plugins": ["monasca_notification.plugins.hipchat_notifier:UnknownPlugin",
"monasca_notification.plugins.slack_notifier:SlackNotifier"]}
notifiers.init(self.statsd)
notifiers.load_plugins(config_dict)
self.assertEqual(len(notifiers.possible_notifiers), 4)
self.assertEqual(len(self.trap), 1)
configured_plugins = ["email", "webhook", "pagerduty", "slack"]
for plugin in notifiers.configured_notifiers:
self.asssertIn(plugin.type in configured_plugins)

View File

@ -1,4 +1,4 @@
# (C) Copyright 2014-2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -21,7 +21,7 @@ import time
import unittest import unittest
from monasca_notification.notification import Notification from monasca_notification.notification import Notification
from monasca_notification.types import pagerduty_notifier from monasca_notification.plugins import pagerduty_notifier
def alarm(metrics): def alarm(metrics):
@ -125,7 +125,7 @@ class TestWebhook(unittest.TestCase):
self.assertRegexpMatches(log_msg, "key=<ABCDEF>") self.assertRegexpMatches(log_msg, "key=<ABCDEF>")
self.assertRegexpMatches(log_msg, "response=%s" % http_response) self.assertRegexpMatches(log_msg, "response=%s" % http_response)
@mock.patch('monasca_notification.types.pagerduty_notifier.requests') @mock.patch('monasca_notification.plugins.pagerduty_notifier.requests')
def notify(self, http_func, mock_requests): def notify(self, http_func, mock_requests):
mock_log = mock.MagicMock() mock_log = mock.MagicMock()
mock_log.warn = self.trap.put mock_log.warn = self.trap.put

View File

@ -1,4 +1,4 @@
# (C) Copyright 2014-2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -20,7 +20,7 @@ import requests
import unittest import unittest
from monasca_notification.notification import Notification from monasca_notification.notification import Notification
from monasca_notification.types import webhook_notifier from monasca_notification.plugins import webhook_notifier
def alarm(metrics): def alarm(metrics):
@ -67,7 +67,7 @@ class TestWebhook(unittest.TestCase):
self.trap.put("timeout %s" % kwargs["timeout"]) self.trap.put("timeout %s" % kwargs["timeout"])
raise requests.exceptions.Timeout raise requests.exceptions.Timeout
@mock.patch('monasca_notification.types.webhook_notifier.requests') @mock.patch('monasca_notification.plugins.webhook_notifier.requests')
def notify(self, http_func, mock_requests): def notify(self, http_func, mock_requests):
mock_log = mock.MagicMock() mock_log = mock.MagicMock()
mock_log.warn = self.trap.put mock_log.warn = self.trap.put