Browse Source

Integrate with oslo.conf and oslo.log

Change upgrades the monasca-notification to leverage
the capabilities of both oslo.log and oslo.conf:

- configuration of logging separated from application settings
- ability to enforce data types for application settings
- ability to use oslo.config-generator capabilities
- automatic configuration parsing done by oslo.cfg

That change will bring it closer to the rest of monasca
components where such transition has happened already.
However, in the rest of monasca, oslo.cfg was partially
or fully implemented whereas monasca-notification has
been relying on YAML based configuration file.

Therefore backward compatybility for such format will
be kept for now.

Story: 2000959
Task: 4093
Task: 4092

Change-Id: Ia75c3b60d0fada854178f21ca5ccb9e6a880f37f
tags/1.12.0
Tomasz Trębski 2 years ago
parent
commit
e1a9b9a96a
53 changed files with 2007 additions and 608 deletions
  1. 2
    0
      .gitignore
  2. 5
    0
      config-generator/notification.conf
  3. 46
    0
      etc/monasca/notification-logging.conf
  4. 3
    15
      monasca_notification/common/repositories/mysql/mysql_repo.py
  5. 18
    14
      monasca_notification/common/repositories/orm/orm_repo.py
  6. 2
    2
      monasca_notification/common/repositories/postgres/pgsql_repo.py
  7. 18
    21
      monasca_notification/common/utils.py
  8. 162
    0
      monasca_notification/conf/__init__.py
  9. 40
    0
      monasca_notification/conf/cli.py
  10. 129
    0
      monasca_notification/conf/database.py
  11. 77
    0
      monasca_notification/conf/kafka.py
  12. 53
    0
      monasca_notification/conf/notifiers.py
  13. 55
    0
      monasca_notification/conf/processors.py
  14. 45
    0
      monasca_notification/conf/queues.py
  15. 41
    0
      monasca_notification/conf/retry.py
  16. 41
    0
      monasca_notification/conf/statsd.py
  17. 102
    0
      monasca_notification/conf/types.py
  18. 61
    0
      monasca_notification/conf/zookeeper.py
  19. 64
    0
      monasca_notification/config.py
  20. 34
    45
      monasca_notification/main.py
  21. 21
    24
      monasca_notification/notification_engine.py
  22. 15
    11
      monasca_notification/periodic_engine.py
  23. 1
    4
      monasca_notification/plugins/abstract_notifier.py
  24. 42
    16
      monasca_notification/plugins/email_notifier.py
  25. 37
    13
      monasca_notification/plugins/hipchat_notifier.py
  26. 47
    22
      monasca_notification/plugins/jira_notifier.py
  27. 30
    10
      monasca_notification/plugins/pagerduty_notifier.py
  28. 35
    17
      monasca_notification/plugins/slack_notifier.py
  29. 27
    7
      monasca_notification/plugins/webhook_notifier.py
  30. 8
    6
      monasca_notification/processors/alarm_processor.py
  31. 8
    6
      monasca_notification/processors/notification_processor.py
  32. 21
    24
      monasca_notification/retry_engine.py
  33. 24
    26
      monasca_notification/types/notifiers.py
  34. 23
    0
      monasca_notification/version.py
  35. 5
    2
      requirements.txt
  36. 3
    1
      setup.cfg
  37. 8
    6
      test-requirements.txt
  38. 83
    0
      tests/base.py
  39. 125
    0
      tests/resources/notification.yaml
  40. 12
    12
      tests/test_alarm_processor.py
  41. 226
    0
      tests/test_config.py
  42. 16
    22
      tests/test_email_notification.py
  43. 7
    4
      tests/test_hipchat_notification.py
  44. 30
    36
      tests/test_jira_notification.py
  45. 10
    9
      tests/test_mysql_repo.py
  46. 22
    30
      tests/test_notification_processor.py
  47. 39
    134
      tests/test_notifiers.py
  48. 6
    5
      tests/test_orm_repo.py
  49. 10
    7
      tests/test_pagerduty_notification.py
  50. 38
    40
      tests/test_slack_notification.py
  51. 15
    11
      tests/test_utils.py
  52. 7
    6
      tests/test_webhook_notification.py
  53. 8
    0
      tox.ini

+ 2
- 0
.gitignore View File

@@ -16,3 +16,5 @@ dist
16 16
 .stestr/
17 17
 .coverage.*
18 18
 cover/
19
+
20
+*.sample

+ 5
- 0
config-generator/notification.conf View File

@@ -0,0 +1,5 @@
1
+[DEFAULT]
2
+output_file = etc/monasca/notification.conf.sample
3
+wrap_width = 80
4
+namespace = monasca_notification
5
+namespace = oslo.log

+ 46
- 0
etc/monasca/notification-logging.conf View File

@@ -0,0 +1,46 @@
1
+[loggers]
2
+keys = root, kafka, zookeeper, statsd
3
+
4
+[handlers]
5
+keys = console, file
6
+
7
+[formatters]
8
+keys = context
9
+
10
+[logger_root]
11
+level = DEBUG
12
+handlers = console, file
13
+
14
+[logger_kafka]
15
+qualname = kafka
16
+level = DEBUG
17
+handlers = console, file
18
+propagate = 0
19
+
20
+[logger_zookeeper]
21
+qualname = zookeeper
22
+level = DEBUG
23
+handlers = console, file
24
+propagate = 0
25
+
26
+[logger_statsd]
27
+qualname = statsd
28
+level = DEBUG
29
+handlers = console, file
30
+propagate = 0
31
+
32
+[handler_console]
33
+class = logging.StreamHandler
34
+args = (sys.stderr,)
35
+level = DEBUG
36
+formatter = context
37
+
38
+[handler_file]
39
+class = logging.handlers.RotatingFileHandler
40
+level = DEBUG
41
+formatter = context
42
+# store up to 5*100MB of logs
43
+args = ('monasca-notification.log', 'a', 104857600, 5)
44
+
45
+[formatter_context]
46
+class = oslo_log.formatters.ContextFormatter

+ 3
- 15
monasca_notification/common/repositories/mysql/mysql_repo.py View File

@@ -11,7 +11,7 @@
11 11
 # or implied. See the License for the specific language governing permissions and limitations under
12 12
 # the License.
13 13
 
14
-import logging
14
+from oslo_log import log as logging
15 15
 import pymysql
16 16
 
17 17
 from monasca_notification.common.repositories.base import base_repo
@@ -24,21 +24,9 @@ log = logging.getLogger(__name__)
24 24
 class MysqlRepo(base_repo.BaseRepo):
25 25
     def __init__(self, config):
26 26
         super(MysqlRepo, self).__init__(config)
27
-        if 'ssl' in config['mysql']:
28
-            self._mysql_ssl = config['mysql']['ssl']
29
-        else:
30
-            self._mysql_ssl = None
31
-
32
-        if 'port' in config['mysql']:
33
-            self._mysql_port = config['mysql']['port']
34
-        else:
35
-            #
36
-            # If port isn't specified in the config file,
37
-            # set it to the default value.
38
-            #
39
-            self._mysql_port = 3306
40
-
27
+        self._mysql_ssl = config['mysql']['ssl'] or None
41 28
         self._mysql_host = config['mysql']['host']
29
+        self._mysql_port = config['mysql']['port']
42 30
         self._mysql_user = config['mysql']['user']
43 31
         self._mysql_passwd = config['mysql']['passwd']
44 32
         self._mysql_dbname = config['mysql']['db']

+ 18
- 14
monasca_notification/common/repositories/orm/orm_repo.py View File

@@ -1,4 +1,4 @@
1
-# Copyright 2015 FUJITSU LIMITED
1
+# Copyright 2015-2017 FUJITSU LIMITED
2 2
 # (C) Copyright 2015,2016 Hewlett Packard Enterprise Development LP
3 3
 #
4 4
 # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
@@ -11,7 +11,8 @@
11 11
 # or implied. See the License for the specific language governing permissions and limitations under
12 12
 # the License.
13 13
 
14
-import logging
14
+from oslo_config import cfg
15
+from oslo_log import log as logging
15 16
 from sqlalchemy import engine_from_config, MetaData
16 17
 from sqlalchemy.sql import select, bindparam, and_, insert
17 18
 from sqlalchemy.exc import DatabaseError
@@ -19,12 +20,15 @@ from sqlalchemy.exc import DatabaseError
19 20
 from monasca_notification.common.repositories import exceptions as exc
20 21
 from monasca_notification.common.repositories.orm import models
21 22
 
22
-log = logging.getLogger(__name__)
23
+LOG = logging.getLogger(__name__)
24
+CONF = cfg.CONF
23 25
 
24 26
 
25 27
 class OrmRepo(object):
26
-    def __init__(self, config):
27
-        self._orm_engine = engine_from_config(config['database']['orm'], prefix='')
28
+    def __init__(self):
29
+        self._orm_engine = engine_from_config({
30
+            'url': CONF.orm.url
31
+        }, prefix='')
28 32
 
29 33
         metadata = MetaData()
30 34
 
@@ -54,38 +58,38 @@ class OrmRepo(object):
54 58
     def fetch_notifications(self, alarm):
55 59
         try:
56 60
             with self._orm_engine.connect() as conn:
57
-                log.debug('Orm query {%s}', str(self._orm_query))
61
+                LOG.debug('Orm query {%s}', str(self._orm_query))
58 62
                 notifications = conn.execute(self._orm_query,
59 63
                                              alarm_definition_id=alarm['alarmDefinitionId'],
60 64
                                              alarm_state=alarm['newState'])
61 65
 
62 66
                 return [(row[0], row[1].lower(), row[2], row[3], row[4]) for row in notifications]
63 67
         except DatabaseError as e:
64
-            log.exception("Couldn't fetch alarms actions %s", e)
68
+            LOG.exception("Couldn't fetch alarms actions %s", e)
65 69
             raise exc.DatabaseException(e)
66 70
 
67 71
     def get_alarm_current_state(self, alarm_id):
68 72
         try:
69 73
             with self._orm_engine.connect() as conn:
70
-                log.debug('Orm query {%s}', str(self._orm_get_alarm_state))
74
+                LOG.debug('Orm query {%s}', str(self._orm_get_alarm_state))
71 75
                 result = conn.execute(self._orm_get_alarm_state,
72 76
                                       alarm_id=alarm_id)
73 77
                 row = result.fetchone()
74 78
                 state = row[0] if row is not None else None
75 79
                 return state
76 80
         except DatabaseError as e:
77
-            log.exception("Couldn't fetch the current alarm state %s", e)
81
+            LOG.exception("Couldn't fetch the current alarm state %s", e)
78 82
             raise exc.DatabaseException(e)
79 83
 
80 84
     def fetch_notification_method_types(self):
81 85
         try:
82 86
             with self._orm_engine.connect() as conn:
83
-                log.debug('Orm query {%s}', str(self._orm_nmt_query))
87
+                LOG.debug('Orm query {%s}', str(self._orm_nmt_query))
84 88
                 notification_method_types = conn.execute(self._orm_nmt_query).fetchall()
85 89
 
86 90
                 return [row[0] for row in notification_method_types]
87 91
         except DatabaseError as e:
88
-            log.exception("Couldn't fetch notification method types %s", e)
92
+            LOG.exception("Couldn't fetch notification method types %s", e)
89 93
             raise exc.DatabaseException(e)
90 94
 
91 95
     def insert_notification_method_types(self, notification_types):
@@ -98,13 +102,13 @@ class OrmRepo(object):
98 102
                     conn.execute(self._orm_add_notification_type, b_name=notification_type)
99 103
 
100 104
         except DatabaseError as e:
101
-            log.debug("Failed to insert notification types %s", notification_types)
105
+            LOG.debug("Failed to insert notification types %s", notification_types)
102 106
             raise exc.DatabaseException(e)
103 107
 
104 108
     def get_notification(self, notification_id):
105 109
         try:
106 110
             with self._orm_engine.connect() as conn:
107
-                log.debug('Orm query {%s}', str(self._orm_get_notification))
111
+                LOG.debug('Orm query {%s}', str(self._orm_get_notification))
108 112
                 result = conn.execute(self._orm_get_notification,
109 113
                                       notification_id=notification_id)
110 114
                 notification = result.fetchone()
@@ -113,5 +117,5 @@ class OrmRepo(object):
113 117
                 else:
114 118
                     return [notification[0], notification[1].lower(), notification[2], notification[3]]
115 119
         except DatabaseError as e:
116
-            log.exception("Couldn't fetch the notification method %s", e)
120
+            LOG.exception("Couldn't fetch the notification method %s", e)
117 121
             raise exc.DatabaseException(e)

+ 2
- 2
monasca_notification/common/repositories/postgres/pgsql_repo.py View File

@@ -1,4 +1,4 @@
1
-# Copyright 2015 FUJITSU LIMITED
1
+# Copyright 2015-2017 FUJITSU LIMITED
2 2
 # (C) Copyright 2016 Hewlett Packard Enterprise Development LP
3 3
 #
4 4
 # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
@@ -11,7 +11,7 @@
11 11
 # or implied. See the License for the specific language governing permissions and limitations under
12 12
 # the License.
13 13
 
14
-import logging
14
+from oslo_log import log as logging
15 15
 import psycopg2
16 16
 
17 17
 from monasca_notification.common.repositories.base import base_repo

+ 18
- 21
monasca_notification/common/utils.py View File

@@ -13,25 +13,25 @@
13 13
 # implied.
14 14
 # See the License for the specific language governing permissions and
15 15
 # limitations under the License.
16
-import logging
17 16
 import monascastatsd
18 17
 
19
-from monasca_common.simport import simport
18
+from oslo_config import cfg
19
+from oslo_log import log
20 20
 
21 21
 from monasca_notification.common.repositories import exceptions
22 22
 from monasca_notification.notification import Notification
23 23
 
24
-log = logging.getLogger(__name__)
24
+LOG = log.getLogger(__name__)
25
+CONF = cfg.CONF
25 26
 
26 27
 NOTIFICATION_DIMENSIONS = {'service': 'monitoring',
27 28
                            'component': 'monasca-notification'}
28 29
 
29 30
 
30
-def get_db_repo(config):
31
-    if 'database' in config and 'repo_driver' in config['database']:
32
-        return simport.load(config['database']['repo_driver'])(config)
33
-    else:
34
-        return simport.load('monasca_notification.common.repositories.mysql.mysql_repo:MysqlRepo')(config)
31
+def get_db_repo():
32
+    repo_driver = CONF.database.repo_driver
33
+    LOG.debug('Enabling the %s RDB repository', repo_driver)
34
+    return repo_driver(CONF)
35 35
 
36 36
 
37 37
 def construct_notification_object(db_repo, notification_json):
@@ -47,7 +47,7 @@ def construct_notification_object(db_repo, notification_json):
47 47
         stored_notification = grab_stored_notification_method(db_repo, notification.id)
48 48
         # Notification method was deleted
49 49
         if stored_notification is None:
50
-            log.debug("Notification method {0} was deleted from database. "
50
+            LOG.debug("Notification method {0} was deleted from database. "
51 51
                       "Will stop sending.".format(notification.id))
52 52
             return None
53 53
         # Update notification method with most up to date values
@@ -58,11 +58,11 @@ def construct_notification_object(db_repo, notification_json):
58 58
             notification.period = stored_notification[3]
59 59
             return notification
60 60
     except exceptions.DatabaseException:
61
-        log.warn("Error querying mysql for notification method. "
61
+        LOG.warn("Error querying mysql for notification method. "
62 62
                  "Using currently cached method.")
63 63
         return notification
64 64
     except Exception as e:
65
-        log.warn("Error when attempting to construct notification {0}".format(e))
65
+        LOG.warn("Error when attempting to construct notification {0}".format(e))
66 66
         return None
67 67
 
68 68
 
@@ -70,21 +70,18 @@ def grab_stored_notification_method(db_repo, notification_id):
70 70
         try:
71 71
             stored_notification = db_repo.get_notification(notification_id)
72 72
         except exceptions.DatabaseException:
73
-            log.debug('Database Error.  Attempting reconnect')
73
+            LOG.debug('Database Error.  Attempting reconnect')
74 74
             stored_notification = db_repo.get_notification(notification_id)
75 75
 
76 76
         return stored_notification
77 77
 
78 78
 
79
-def get_statsd_client(config, dimensions=None):
79
+def get_statsd_client(dimensions=None):
80 80
     local_dims = dimensions.copy() if dimensions else {}
81 81
     local_dims.update(NOTIFICATION_DIMENSIONS)
82
-    if 'statsd' in config:
83
-        client = monascastatsd.Client(name='monasca',
84
-                                      host=config['statsd'].get('host', 'localhost'),
85
-                                      port=config['statsd'].get('port', 8125),
86
-                                      dimensions=local_dims)
87
-    else:
88
-        client = monascastatsd.Client(name='monasca',
89
-                                      dimensions=local_dims)
82
+    client = monascastatsd.Client(name='monasca',
83
+                                  host=CONF.statsd.host,
84
+                                  port=CONF.statsd.port,
85
+                                  dimensions=local_dims)
86
+
90 87
     return client

+ 162
- 0
monasca_notification/conf/__init__.py View File

@@ -0,0 +1,162 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+import collections
16
+import copy
17
+
18
+from oslo_config import cfg
19
+from oslo_log import log
20
+from oslo_utils import importutils
21
+
22
+from monasca_notification.conf import cli
23
+from monasca_notification.conf import database
24
+from monasca_notification.conf import kafka
25
+from monasca_notification.conf import notifiers
26
+from monasca_notification.conf import processors
27
+from monasca_notification.conf import queues
28
+from monasca_notification.conf import retry
29
+from monasca_notification.conf import statsd
30
+from monasca_notification.conf import zookeeper
31
+
32
+LOG = log.getLogger(__name__)
33
+CONF = cfg.CONF
34
+
35
+CONF_OPTS = [
36
+    cli,
37
+    database,
38
+    kafka,
39
+    notifiers,
40
+    processors,
41
+    queues,
42
+    retry,
43
+    statsd,
44
+    zookeeper
45
+]
46
+
47
+
48
+def register_opts(conf=None):
49
+    if conf is None:
50
+        conf = CONF
51
+    for m in CONF_OPTS:
52
+        m.register_opts(conf)
53
+
54
+
55
+def list_opts():
56
+    opts = collections.defaultdict(list)
57
+    for m in CONF_OPTS:
58
+        configs = copy.deepcopy(m.list_opts())
59
+        for key, val in configs.items():
60
+            opts[key].extend(val)
61
+    return _tupleize(opts)
62
+
63
+
64
+def load_from_yaml(yaml_config, conf=None):
65
+    # build named BACKWARD_MAP to modules set_defaults
66
+
67
+    if conf is None:
68
+        conf = CONF
69
+
70
+    def _noop(*arg, **kwargs):
71
+        pass
72
+
73
+    def _plain_override(g=None, **opts):
74
+        for k, v in opts.items():
75
+            conf.set_override(group=g, name=k, override=v)
76
+
77
+    def _load_plugin_settings(**notifiers_cfg):
78
+        notifiers_cfg = {t.lower(): v for t, v in notifiers_cfg.items()}
79
+        enabled_plugins = notifiers_cfg.pop('plugins', [])
80
+
81
+        # We still can have these 3 (email, pagerduty, webhook)
82
+        #  that are considered as builtin, hence should be always enabled
83
+        conf_to_plugin = {
84
+            'email': 'monasca_notification.plugins.'
85
+                     'email_notifier:EmailNotifier',
86
+            'pagerduty': 'monasca_notification.plugins.'
87
+                         'pagerduty_notifier:PagerdutyNotifier',
88
+            'webhook': 'monasca_notification.plugins.'
89
+                       'webhook_notifier:WebhookNotifier'
90
+        }
91
+        for ctp_key, ctp_clazz in conf_to_plugin.items():
92
+            if ctp_key in notifiers_cfg and ctp_key not in enabled_plugins:
93
+                LOG.debug('%s enabled as builtin plugin', ctp_key)
94
+                enabled_plugins.append(ctp_clazz)
95
+
96
+        _plain_override(g='notification_types', enabled=enabled_plugins)
97
+        if not enabled_plugins:
98
+            return
99
+
100
+        for ep in enabled_plugins:
101
+            ep_module = importutils.import_module(ep.split(':')[0])
102
+            ep_clazz = importutils.import_class(ep.replace(':', '.'))
103
+
104
+            if not hasattr(ep_module, 'register_opts'):
105
+                LOG.debug('%s does not have \'register_opts\' method')
106
+                continue
107
+            if not hasattr(ep_clazz, 'type'):
108
+                LOG.debug('%s does not have \'type\' class variable')
109
+                continue
110
+
111
+            ep_r_opt = getattr(ep_module, 'register_opts')
112
+            ep_type = getattr(ep_clazz, 'type')
113
+
114
+            ep_r_opt(conf)  # register options
115
+            _plain_override(g='%s_notifier' % ep_type,
116
+                            **notifiers_cfg.get(ep_type))
117
+
118
+            LOG.debug('Registered options and values of the %s notifier',
119
+                      ep_type)
120
+
121
+    def _configure_and_warn_the_logging(logging_config):
122
+        LOG.warning('Configuration of the logging system from '
123
+                    '\'notification.yml\' has been deprecated and '
124
+                    'Please check how to configure logging with '
125
+                    'oslo.log library.')
126
+        import logging.config
127
+        logging.config.dictConfig(logging_config)
128
+
129
+    mappper = {
130
+        'statsd': [lambda d: _plain_override(g='statsd', **d)],
131
+        'retry': [lambda d: _plain_override(g='retry_engine', **d)],
132
+        'database': [
133
+            lambda d: _plain_override(g='database', repo_driver=d['repo_driver']),
134
+            lambda d: _plain_override(g='orm', url=d['orm']['url'])
135
+        ],
136
+        'postgresql': [lambda d: _plain_override(g='postgresql', **d)],
137
+        'mysql': [lambda d: _plain_override(g='mysql', **d)],
138
+        'processors': [
139
+            lambda d: _plain_override(g='alarm_processor',
140
+                                      number=d['alarm']['number'],
141
+                                      ttl=d['alarm']['ttl']),
142
+            lambda d: _plain_override(g='notification_processor',
143
+                                      number=d['notification']['number'])
144
+        ],
145
+        'queues': [lambda d: _plain_override(g='queues', **d)],
146
+        'kafka': [lambda d: _plain_override(g='kafka', **d)],
147
+        'zookeeper': [lambda d: _plain_override(g='zookeeper', **d)],
148
+        'notification_types': [lambda d: _load_plugin_settings(**d)],
149
+        'logging': [_configure_and_warn_the_logging]
150
+    }
151
+
152
+    for key, opts in yaml_config.items():
153
+        LOG.debug('Loading group %s from deprecated yaml configuration', key)
154
+        handlers = mappper.get(key, [_noop])
155
+        if len(handlers) == 1 and handlers[0] == _noop:
156
+            LOG.warning('Unmapped configuration group %s from YAML file', key)
157
+        [handler(opts) for handler in handlers]
158
+
159
+
160
+def _tupleize(d):
161
+    """Convert a dict of options to the 2-tuple format."""
162
+    return [(key, value) for key, value in d.items()]

+ 40
- 0
monasca_notification/conf/cli.py View File

@@ -0,0 +1,40 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+from oslo_config import cfg
16
+
17
+cli_opts = [
18
+    cfg.StrOpt(name='yaml_config', default=None,
19
+               positional=True,
20
+               help='Backward compatible option that allows to pass path '
21
+                    'to YAML file containing configuration '
22
+                    'of monasca-notitifcation.',
23
+               deprecated_for_removal=True,
24
+               deprecated_since='1.9.0',
25
+               deprecated_reason='monasca-notification has moved to '
26
+                                 'oslo.conf henceusing YAML based '
27
+                                 'configuration will be removed '
28
+                                 'after PIKE release.')
29
+]
30
+
31
+
32
+def register_opts(conf):
33
+    for opt in cli_opts:
34
+        conf.register_cli_opt(opt=opt)
35
+
36
+
37
+def list_opts():
38
+    return {
39
+        'default': cli_opts
40
+    }

+ 129
- 0
monasca_notification/conf/database.py View File

@@ -0,0 +1,129 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+from oslo_config import cfg
16
+
17
+from monasca_notification.conf import types
18
+
19
+_POSTGRESQL = 'postgresql'
20
+_MYSQL = 'mysql'
21
+_ORM = 'orm'
22
+_REPO_DRIVERS_MAP = {
23
+    _POSTGRESQL: 'monasca_notification.common.repositories.'
24
+                 'postgres.pgsql_repo:PostgresqlRepo',
25
+    _MYSQL: 'monasca_notification.common.repositories.'
26
+            'mysql.mysql_repo:MysqlRepo',
27
+    _ORM: 'monasca_notification.common.repositories.'
28
+          'orm.orm_repo:OrmRepo'
29
+}
30
+_ACCEPTABLE_DRIVER_KEYS = set(list(_REPO_DRIVERS_MAP.keys()) +
31
+                              list(_REPO_DRIVERS_MAP.values()))
32
+
33
+_DEFAULT_DB_HOST = '127.0.0.1'
34
+_DEFAULT_DB_USER = 'notification'
35
+_DEFAULT_DB_PASSWORD = 'password'
36
+_DEFAULT_DB_NAME = 'mon'
37
+_DEFAULT_POSTGRESQL_PORT = 5432
38
+_DEFAULT_MYSQL_PORT = 3306
39
+
40
+db_group = cfg.OptGroup('database',
41
+                        title='Database Options',
42
+                        help='Driver configuration for database connectivity.')
43
+
44
+db_opts = [
45
+    types.PluginOpt(name='repo_driver', choices=_ACCEPTABLE_DRIVER_KEYS,
46
+                    default=_MYSQL, plugin_map=_REPO_DRIVERS_MAP,
47
+                    required=True, advanced=True,
48
+                    help='Driver name (or full class path) that should be '
49
+                         'used to handle RDB connections. Accepts either '
50
+                         'short labels {0} or full class names {1}. '
51
+                         'Configuring either of them will require presence of '
52
+                         'one of following sections {0} inside configuration '
53
+                         'file.'.format(_REPO_DRIVERS_MAP.keys(),
54
+                                        _REPO_DRIVERS_MAP.values())
55
+                    )
56
+]
57
+
58
+orm_group = cfg.OptGroup('orm',
59
+                         title='ORM Options',
60
+                         help='Configuration options to configure '
61
+                              'ORM RBD driver.')
62
+orm_opts = [
63
+    cfg.StrOpt(name='url', default=None,
64
+               help='Connection string for sqlalchemy.')
65
+]
66
+
67
+mysql_group = cfg.OptGroup('mysql',
68
+                           title='MySQL Options',
69
+                           help='Configuration options to configure '
70
+                                'plain MySQL RBD driver.')
71
+mysql_opts = [
72
+    cfg.HostAddressOpt(name='host', default=_DEFAULT_DB_HOST,
73
+                       help='IP address of MySQL instance.'),
74
+    cfg.PortOpt(name='port', default=_DEFAULT_MYSQL_PORT,
75
+                help='Port number of MySQL instance.'),
76
+    cfg.StrOpt(name='user', default=_DEFAULT_DB_USER,
77
+               help='Username to connect to MySQL '
78
+                    'instance and given database.'),
79
+    cfg.StrOpt(name='passwd', default=_DEFAULT_DB_PASSWORD,
80
+               ignore_case=True, secret=True,
81
+               help='Password to connect to MySQL instance '
82
+                    'and given database.'),
83
+    cfg.DictOpt(name='ssl', default={},
84
+                help='A dict of arguments similar '
85
+                     'to mysql_ssl_set parameters.'),
86
+    cfg.StrOpt(name='db', default=_DEFAULT_DB_NAME,
87
+               help='Database name available in given MySQL instance.')
88
+]
89
+
90
+postgresql_group = cfg.OptGroup('postgresql',
91
+                                title='PostgreSQL Options',
92
+                                help='Configuration options to configure '
93
+                                     'plain PostgreSQL RBD driver.')
94
+postgresql_opts = [
95
+    cfg.HostAddressOpt(name='host', default=_DEFAULT_DB_HOST,
96
+                       help='IP address of PostgreSQL instance.'),
97
+    cfg.PortOpt(name='port', default=_DEFAULT_POSTGRESQL_PORT,
98
+                help='Port number of PostgreSQL instance.'),
99
+    cfg.StrOpt(name='user', default=_DEFAULT_DB_USER,
100
+               help='Username to connect to PostgreSQL '
101
+                    'instance and given database.'),
102
+    cfg.StrOpt(name='password', default=_DEFAULT_DB_PASSWORD,
103
+               secret=True, help='Password to connect to PostgreSQL '
104
+                                 'instance and given database'),
105
+    cfg.StrOpt(name='database', default=_DEFAULT_DB_NAME,
106
+               help='Database name available in '
107
+                    'given PostgreSQL instance.')
108
+]
109
+
110
+
111
+def register_opts(conf):
112
+    conf.register_group(db_group)
113
+    conf.register_group(orm_group)
114
+    conf.register_group(mysql_group)
115
+    conf.register_group(postgresql_group)
116
+
117
+    conf.register_opts(db_opts, group=db_group)
118
+    conf.register_opts(orm_opts, group=orm_group)
119
+    conf.register_opts(mysql_opts, group=mysql_group)
120
+    conf.register_opts(postgresql_opts, group=postgresql_group)
121
+
122
+
123
+def list_opts():
124
+    return {
125
+        db_group: db_opts,
126
+        orm_group: orm_opts,
127
+        mysql_group: mysql_opts,
128
+        postgresql_group: postgresql_opts,
129
+    }

+ 77
- 0
monasca_notification/conf/kafka.py View File

@@ -0,0 +1,77 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+from oslo_config import cfg
16
+
17
+from monasca_notification.conf import types
18
+
19
+_DEFAULT_URL = '127.0.0.1:9092'
20
+_DEFAULT_GROUP = 'monasca-notification'
21
+_DEFAULT_ALARM_TOPIC = 'alarm-state-transitions'
22
+_DEFAULT_NOTIFICATION_TOPIC = 'alarm-notifications'
23
+_DEFAULT_RETRY_TOPIC = 'retry-notifications'
24
+_DEFAULT_PERIODIC_TOPICS = {
25
+    60: '60-seconds-notifications'
26
+}
27
+_DEFAULT_MAX_OFFSET_LAG = 600
28
+
29
+kafka_group = cfg.OptGroup('kafka',
30
+                           title='Kafka Options',
31
+                           help='Options under this group allow to configure '
32
+                                'valid connection or Kafka queue.')
33
+
34
+kafka_opts = [
35
+    cfg.ListOpt(name='url', item_type=types.HostAddressPortType(),
36
+                bounds=False,
37
+                default=_DEFAULT_URL, required=True,
38
+                help='List of addresses (with ports) pointing '
39
+                     'at zookeeper cluster.'),
40
+    cfg.StrOpt(name='group', default=_DEFAULT_GROUP,
41
+               required=True, advanced=True,
42
+               help='Consumer\'s group for monasca-notification client.'),
43
+    cfg.StrOpt(name='alarm_topic', default=_DEFAULT_ALARM_TOPIC,
44
+               required=True, advanced=True,
45
+               help='Topic name in kafka where alarm '
46
+                    'transitions are stored.'),
47
+    cfg.StrOpt(name='notification_topic', default=_DEFAULT_NOTIFICATION_TOPIC,
48
+               required=True, advanced=True,
49
+               help='Topic name in kafka where alarm '
50
+                    'notifications are stored.'),
51
+    cfg.StrOpt(name='notification_retry_topic', default=_DEFAULT_RETRY_TOPIC,
52
+               required=True, advanced=True,
53
+               help='Topic name in kafka where notifications, that have '
54
+                    'failed to be sent and are waiting for retry operations, '
55
+                    'are stored.'),
56
+    cfg.DictOpt(name='periodic', default=_DEFAULT_PERIODIC_TOPICS,
57
+                required=True, advanced=True,
58
+                help='Dict of periodic topics. Keys are the period and '
59
+                     'values the actual topic names in kafka where '
60
+                     'notifications are stored.'),
61
+    cfg.IntOpt(name='max_offset_lag', default=_DEFAULT_MAX_OFFSET_LAG,
62
+               required=True, advanced=True,
63
+               help='Maximum lag for topic that is acceptable by '
64
+                    'the monasca-notification. Notifications that are older '
65
+                    'than this offset are skipped.')
66
+]
67
+
68
+
69
+def register_opts(conf):
70
+    conf.register_group(kafka_group)
71
+    conf.register_opts(kafka_opts, group=kafka_group)
72
+
73
+
74
+def list_opts():
75
+    return {
76
+        kafka_group: kafka_opts
77
+    }

+ 53
- 0
monasca_notification/conf/notifiers.py View File

@@ -0,0 +1,53 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+from oslo_config import cfg
16
+
17
+# NOTE(kornicameister) Until https://review.openstack.org/#/c/435136/
18
+# is merged we only treat these below as plugins.
19
+# WEBHOOK, EMAIL, PAGERDUTY are now treated as built-in & hardcoded
20
+# user has no possibility of enabling/disabling them
21
+
22
+_KEY_MAP = {
23
+    'hipchat': 'monasca_notification.plugins.hipchat_notifier.HipChatNotifier',
24
+    'slack': 'monasca_notification.plugins.slack_notifier.SlackNotifier',
25
+    'jira': 'monasca_notification.plugins.jira_notifier.JiraNotifier'
26
+}
27
+
28
+notifier_group = cfg.OptGroup('notification_types',
29
+                              title='Notification types',
30
+                              help='Group allows to configure available '
31
+                                   'notifiers inside notification engine.')
32
+
33
+notifier_opts = [
34
+    cfg.ListOpt(name='enabled', default=[],
35
+                item_type=lambda x: _KEY_MAP.get(x, x), bounds=False,
36
+                advanced=True, sample_default=','.join(_KEY_MAP.keys()),
37
+                help='List of enabled notification types. You may specify '
38
+                     'full class name {} '
39
+                     'or shorter label {}.'.format(_KEY_MAP.get('hipchat'),
40
+                                                   'hipchat')
41
+                )
42
+]
43
+
44
+
45
+def register_opts(conf):
46
+    conf.register_group(notifier_group)
47
+    conf.register_opts(notifier_opts, group=notifier_group)
48
+
49
+
50
+def list_opts():
51
+    return {
52
+        notifier_group: notifier_opts,
53
+    }

+ 55
- 0
monasca_notification/conf/processors.py View File

@@ -0,0 +1,55 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+from oslo_config import cfg
16
+
17
+ap_group = cfg.OptGroup('alarm_processor',
18
+                        title='Alarm processor group',
19
+                        help='Options to configure alarm processor.')
20
+
21
+ap_opts = [
22
+    cfg.IntOpt(name='number', min=1, default=2,
23
+               help='Number of alarm processors to spawn',
24
+               deprecated_for_removal=True,
25
+               deprecated_since='1.8.0',
26
+               deprecated_reason='Options is not used in the current code '
27
+                                 'and will be removed in future releases.'),
28
+    cfg.IntOpt(name='ttl', default=14400,
29
+               advanced=True,
30
+               help='Alarms older than TTL are not processed '
31
+                    'by notification engine.')
32
+]
33
+
34
+np_group = cfg.OptGroup('notification_processor',
35
+                        title='Notification processor group',
36
+                        help='Options to configure notification processor.')
37
+np_opts = [
38
+    cfg.IntOpt(name='number', min=1,
39
+               default=4, help='Number of notification processors to spawn.')
40
+]
41
+
42
+
43
+def register_opts(conf):
44
+    conf.register_group(ap_group)
45
+    conf.register_group(np_group)
46
+
47
+    conf.register_opts(ap_opts, group=ap_group)
48
+    conf.register_opts(np_opts, group=np_group)
49
+
50
+
51
+def list_opts():
52
+    return {
53
+        ap_group: ap_opts,
54
+        np_group: np_opts
55
+    }

+ 45
- 0
monasca_notification/conf/queues.py View File

@@ -0,0 +1,45 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+from oslo_config import cfg
16
+
17
+queues_group = cfg.OptGroup('queues',
18
+                            title='Queues Options',
19
+                            help=('Options under this group allow to '
20
+                                  'configure valid connection sizes of '
21
+                                  'all queues.'))
22
+
23
+queues_opts = [
24
+    cfg.IntOpt(name='alarms_size', min=1, default=256,
25
+               help='Size of the alarms queue.'),
26
+    cfg.IntOpt(name='finished_size', min=1, default=256,
27
+               help='Size of the finished alarms queue.'),
28
+    cfg.IntOpt(name='notifications_size', min=1, default=256,
29
+               help='Size of notifications queue.'),
30
+    cfg.IntOpt(name='sent_notifications_size', min=1, default=50,
31
+               help='Size of sent notifications queue. '
32
+                    'Limiting this size reduces potential or '
33
+                    're-sent notifications after a failure.')
34
+]
35
+
36
+
37
+def register_opts(conf):
38
+    conf.register_group(queues_group)
39
+    conf.register_opts(queues_opts, group=queues_group)
40
+
41
+
42
+def list_opts():
43
+    return {
44
+        queues_group: queues_opts
45
+    }

+ 41
- 0
monasca_notification/conf/retry.py View File

@@ -0,0 +1,41 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+from oslo_config import cfg
16
+
17
+retry_engine_group = cfg.OptGroup('retry_engine',
18
+                                  title='Retry Engine Options',
19
+                                  help='Options under this group allow to '
20
+                                       'configure valid connection '
21
+                                       'for retry engine.')
22
+
23
+retry_opts = [
24
+    cfg.IntOpt(name='interval', min=30, default=30,
25
+               advanced=True,
26
+               help='How often should retry happen.'),
27
+    cfg.IntOpt(name='max_attempts', default=5,
28
+               advanced=True,
29
+               help='How many times should retrying be tried.')
30
+]
31
+
32
+
33
+def register_opts(conf):
34
+    conf.register_group(retry_engine_group)
35
+    conf.register_opts(retry_opts, group=retry_engine_group)
36
+
37
+
38
+def list_opts():
39
+    return {
40
+        retry_engine_group: retry_opts
41
+    }

+ 41
- 0
monasca_notification/conf/statsd.py View File

@@ -0,0 +1,41 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+from oslo_config import cfg
16
+
17
+_DEFAULT_HOST = '127.0.0.1'
18
+_DEFAULT_PORT = 8125
19
+
20
+statsd_group = cfg.OptGroup('statsd',
21
+                            title='statsd Options',
22
+                            help='Options under this group allow '
23
+                            'to configure valid connection '
24
+                            'to statsd server launched by monasca-agent.')
25
+
26
+statsd_opts = [
27
+    cfg.HostAddressOpt('host', default=_DEFAULT_HOST,
28
+                       help='IP address of statsd server.'),
29
+    cfg.PortOpt('port', default=_DEFAULT_PORT, help='Port of statsd server.'),
30
+]
31
+
32
+
33
+def register_opts(conf):
34
+    conf.register_group(statsd_group)
35
+    conf.register_opts(statsd_opts, group=statsd_group)
36
+
37
+
38
+def list_opts():
39
+    return {
40
+        statsd_group: statsd_opts
41
+    }

+ 102
- 0
monasca_notification/conf/types.py View File

@@ -0,0 +1,102 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+from oslo_config import cfg
16
+from oslo_config import types
17
+from oslo_log import log
18
+from oslo_utils import importutils
19
+
20
+LOG = log.getLogger(__name__)
21
+
22
+
23
+class Plugin(types.String):
24
+    def __init__(self, ignore_missing=False, choices=None, plugin_map=None):
25
+
26
+        if not plugin_map:
27
+            # since simport is used, we cannot tell where plugin is located
28
+            # thus plugin map wouldn't contain it
29
+            plugin_map = {}
30
+
31
+        super(Plugin, self).__init__(choices, quotes=False, ignore_case=True,
32
+                                     type_name='plugin value')
33
+        self._plugin_map = plugin_map
34
+        self._ingore_mission = ignore_missing
35
+
36
+    def __call__(self, value):
37
+        value = super(Plugin, self).__call__(value)
38
+        value = self._get_actual_target(value)
39
+        cls = None
40
+
41
+        try:
42
+            value = value.replace(':', '.')
43
+            cls = importutils.import_class(value)
44
+        except ImportError:
45
+            if not self._ingore_mission:
46
+                raise ValueError('%s cannot be imported' % value)
47
+            else:
48
+                LOG.exception('%s cannot be imported', value)
49
+
50
+        return cls
51
+
52
+    def _get_actual_target(self, value):
53
+
54
+        # NOTE(trebskit) missing values will be handled
55
+        # by choices from StringType
56
+
57
+        if value in self._plugin_map.keys():
58
+            return self._plugin_map[value]
59
+
60
+        return value
61
+
62
+
63
+class PluginOpt(cfg.Opt):
64
+    def __init__(self, name, choices=None, plugin_map=None, **kwargs):
65
+        plugin = Plugin(choices=choices, plugin_map=plugin_map)
66
+        super(PluginOpt, self).__init__(name,
67
+                                        type=plugin,
68
+                                        **kwargs)
69
+
70
+
71
+class HostAddressPortType(types.HostAddress):
72
+    """HostAddress with additional port"""
73
+
74
+    def __init__(self, version=None):
75
+        type_name = 'host address port value'
76
+        super(HostAddressPortType, self).__init__(version,
77
+                                                  type_name=type_name)
78
+
79
+    def __call__(self, value):
80
+        addr, port = value.split(':')
81
+
82
+        addr = self._validate_addr(addr)
83
+        port = self._validate_port(port)
84
+
85
+        if addr and port:
86
+            return '%s:%d' % (addr, port)
87
+        raise ValueError('%s is not valid host address with optional port')
88
+
89
+    @staticmethod
90
+    def _validate_port(port=80):
91
+        port = types.Port()(port)
92
+        return port
93
+
94
+    def _validate_addr(self, addr):
95
+        try:
96
+            addr = self.ip_address(addr)
97
+        except ValueError:
98
+            try:
99
+                addr = self.hostname(addr)
100
+            except ValueError:
101
+                raise ValueError("%s is not a valid host address", addr)
102
+        return addr

+ 61
- 0
monasca_notification/conf/zookeeper.py View File

@@ -0,0 +1,61 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+from oslo_config import cfg
16
+
17
+from monasca_notification.conf import types
18
+
19
+_DEFAULT_URL = '127.0.0.1:2181'
20
+_DEFAULT_NOTIFICATION_PATH = '/notification/alarms'
21
+_DEFAULT_RETRY_PATH = '/notification/retry'
22
+_DEFAULT_PERIODIC_PATH = {
23
+    60: '/notification/60_seconds'
24
+}
25
+
26
+zookeeper_group = cfg.OptGroup('zookeeper',
27
+                               title='Zookeeper Options',
28
+                               help='Options under this group allow to '
29
+                                    'configure settings for zookeeper '
30
+                                    'handling.')
31
+
32
+zookeeper_opts = [
33
+    cfg.ListOpt(name='url', item_type=types.HostAddressPortType(),
34
+                default=_DEFAULT_URL, required=True,
35
+                help='List of addresses (with ports) pointing '
36
+                     'at zookeeper cluster.'),
37
+    cfg.StrOpt(name='notification_path', default=_DEFAULT_NOTIFICATION_PATH,
38
+               required=True, advanced=True,
39
+               help='Path in zookeeper tree to track notification offsets.'),
40
+    cfg.StrOpt(name='notification_retry_path', default=_DEFAULT_RETRY_PATH,
41
+               required=True, advanced=True,
42
+               help='Path in zookeeper tree to track notification '
43
+                    'retries offsets.'),
44
+    cfg.DictOpt(name='periodic_path', default=_DEFAULT_PERIODIC_PATH,
45
+                required=True, advanced=True,
46
+                help='Paths in zookeeper tree to track periodic offsets. '
47
+                     'Keys must be integers describing the interval '
48
+                     'of periodic notification. Values are actual '
49
+                     'paths inside zookeeper tree.')
50
+]
51
+
52
+
53
+def register_opts(conf):
54
+    conf.register_group(zookeeper_group)
55
+    conf.register_opts(zookeeper_opts, group=zookeeper_group)
56
+
57
+
58
+def list_opts():
59
+    return {
60
+        zookeeper_group: zookeeper_opts
61
+    }

+ 64
- 0
monasca_notification/config.py View File

@@ -0,0 +1,64 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+from oslo_log import log
16
+import yaml
17
+
18
+from monasca_notification import conf
19
+from monasca_notification import version
20
+
21
+LOG = log.getLogger(__name__)
22
+CONF = conf.CONF
23
+_CONF_LOADED = False
24
+
25
+
26
+def parse_args(argv, no_yaml=False):
27
+    """Sets up configuration of monasca-notification."""
28
+
29
+    global _CONF_LOADED
30
+    if _CONF_LOADED:
31
+        LOG.debug('Configuration has been already loaded')
32
+        return
33
+
34
+    conf.register_opts(CONF)
35
+    log.register_options(CONF)
36
+
37
+    default_log_levels = (log.get_default_log_levels())
38
+    log.set_defaults(default_log_levels=default_log_levels)
39
+
40
+    CONF(args=argv,
41
+         project='monasca',
42
+         prog='notification',
43
+         version=version.version_string,
44
+         description='''
45
+         monasca-notification is an engine responsible for
46
+         transforming alarm transitions into proper notifications
47
+         ''')
48
+    log.setup(CONF,
49
+              product_name='monasca-notification',
50
+              version=version.version_string)
51
+
52
+    if not no_yaml:
53
+        # note(trebskit) used only in test cases as the notification.yml
54
+        # will be dropped eventually
55
+        set_from_yaml()
56
+
57
+    _CONF_LOADED = True
58
+
59
+
60
+def set_from_yaml():
61
+    if CONF.yaml_config:
62
+        LOG.info('Detected usage of deprecated YAML configuration')
63
+        yaml_cfg = yaml.safe_load(open(CONF.yaml_config, 'rb'))
64
+        conf.load_from_yaml(yaml_cfg)

+ 34
- 45
monasca_notification/main.py View File

@@ -18,20 +18,23 @@
18 18
     This engine reads alarms from Kafka and then notifies the customer using their configured notification method.
19 19
 """
20 20
 
21
-import logging
22
-import logging.config
23 21
 import multiprocessing
24 22
 import os
25 23
 import signal
26 24
 import sys
27 25
 import time
28
-import yaml
26
+import warnings
29 27
 
30
-from monasca_notification.notification_engine import NotificationEngine
31
-from monasca_notification.periodic_engine import PeriodicEngine
32
-from monasca_notification.retry_engine import RetryEngine
28
+from oslo_log import log
29
+
30
+from monasca_notification import config
31
+from monasca_notification import notification_engine
32
+from monasca_notification import periodic_engine
33
+from monasca_notification import retry_engine
34
+
35
+LOG = log.getLogger(__name__)
36
+CONF = config.CONF
33 37
 
34
-log = logging.getLogger(__name__)
35 38
 processors = []  # global list to facilitate clean signal handling
36 39
 exiting = False
37 40
 
@@ -45,10 +48,10 @@ def clean_exit(signum, frame=None):
45 48
         # Since this is set up as a handler for SIGCHLD when this kills one
46 49
         # child it gets another signal, the global exiting avoids this running
47 50
         # multiple times.
48
-        log.debug('Exit in progress clean_exit received additional signal %s' % signum)
51
+        LOG.debug('Exit in progress clean_exit received additional signal %s' % signum)
49 52
         return
50 53
 
51
-    log.info('Received signal %s, beginning graceful shutdown.' % signum)
54
+    LOG.info('Received signal %s, beginning graceful shutdown.' % signum)
52 55
     exiting = True
53 56
     wait_for_exit = False
54 57
 
@@ -68,7 +71,7 @@ def clean_exit(signum, frame=None):
68 71
 
69 72
     # Kill everything, that didn't already die
70 73
     for child in multiprocessing.active_children():
71
-        log.debug('Killing pid %s' % child.pid)
74
+        LOG.debug('Killing pid %s' % child.pid)
72 75
         try:
73 76
             os.kill(child.pid, signal.SIGKILL)
74 77
         except Exception:  # nosec
@@ -82,50 +85,35 @@ def clean_exit(signum, frame=None):
82 85
     sys.exit(signum)
83 86
 
84 87
 
85
-def start_process(process_type, config, *args):
86
-    log.info("start process: {}".format(process_type))
87
-    p = process_type(config, *args)
88
+def start_process(process_type, *args):
89
+    LOG.info("start process: {}".format(process_type))
90
+    p = process_type(*args)
88 91
     p.run()
89 92
 
90 93
 
91 94
 def main(argv=None):
92
-    if argv is None:
93
-        argv = sys.argv
94
-    if len(argv) == 2:
95
-        config_file = argv[1]
96
-    elif len(argv) > 2:
97
-        print("Usage: " + argv[0] + " <config_file>")
98
-        print("Config file defaults to /etc/monasca/notification.yaml")
99
-        return 1
100
-    else:
101
-        config_file = '/etc/monasca/notification.yaml'
102
-
103
-    config = yaml.safe_load(open(config_file, 'rb'))
104
-
105
-    # Setup logging
106
-    try:
107
-        if config['logging']['raise_exceptions'] is True:
108
-            logging.raiseExceptions = True
109
-        else:
110
-            logging.raiseExceptions = False
111
-    except KeyError:
112
-        logging.raiseExceptions = False
113
-        pass
114
-    logging.config.dictConfig(config['logging'])
115
-
116
-    for proc in range(0, config['processors']['notification']['number']):
95
+    warnings.simplefilter('always')
96
+    config.parse_args(argv=argv)
97
+
98
+    for proc in range(0, CONF.notification_processor.number):
117 99
         processors.append(multiprocessing.Process(
118
-            target=start_process, args=(NotificationEngine, config)))
100
+            target=start_process,
101
+            args=(notification_engine.NotificationEngine,))
102
+        )
119 103
 
120 104
     processors.append(multiprocessing.Process(
121
-        target=start_process, args=(RetryEngine, config)))
105
+        target=start_process,
106
+        args=(retry_engine.RetryEngine,))
107
+    )
122 108
 
123
-    if 60 in config['kafka']['periodic']:
109
+    if 60 in CONF.kafka.periodic:
124 110
         processors.append(multiprocessing.Process(
125
-            target=start_process, args=(PeriodicEngine, config, 60)))
111
+            target=start_process,
112
+            args=(periodic_engine.PeriodicEngine, 60))
113
+        )
126 114
 
127 115
     try:
128
-        log.info('Starting processes')
116
+        LOG.info('Starting processes')
129 117
         for process in processors:
130 118
             process.start()
131 119
 
@@ -139,8 +127,9 @@ def main(argv=None):
139 127
             time.sleep(10)
140 128
 
141 129
     except Exception:
142
-        log.exception('Error! Exiting.')
130
+        LOG.exception('Error! Exiting.')
143 131
         clean_exit(signal.SIGKILL)
144 132
 
133
+
145 134
 if __name__ == "__main__":
146
-    sys.exit(main())
135
+    sys.exit(main(sys.argv[1:]))

+ 21
- 24
monasca_notification/notification_engine.py View File

@@ -1,4 +1,5 @@
1 1
 # (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
2
+# Copyright 2017 Fujitsu LIMITED
2 3
 #
3 4
 # Licensed under the Apache License, Version 2.0 (the "License");
4 5
 # you may not use this file except in compliance with the License.
@@ -13,44 +14,40 @@
13 14
 # See the License for the specific language governing permissions and
14 15
 # limitations under the License.
15 16
 
16
-import logging
17 17
 import time
18 18
 
19
+from oslo_config import cfg
20
+from oslo_log import log as logging
21
+
19 22
 from monasca_common.kafka import consumer
20 23
 from monasca_common.kafka import producer
21 24
 from monasca_notification.common.utils import get_statsd_client
22
-from monasca_notification.processors.alarm_processor import AlarmProcessor
23
-from monasca_notification.processors.notification_processor import NotificationProcessor
25
+from monasca_notification.processors import alarm_processor as ap
26
+from monasca_notification.processors import notification_processor as np
24 27
 
25 28
 log = logging.getLogger(__name__)
29
+CONF = cfg.CONF
26 30
 
27 31
 
28 32
 class NotificationEngine(object):
29
-    def __init__(self, config):
30
-        self._topics = {}
31
-        self._topics['notification_topic'] = config['kafka']['notification_topic']
32
-        self._topics['retry_topic'] = config['kafka']['notification_retry_topic']
33
-
34
-        self._statsd = get_statsd_client(config)
33
+    def __init__(self):
34
+        self._statsd = get_statsd_client()
35 35
         self._consumer = consumer.KafkaConsumer(
36
-            config['kafka']['url'],
37
-            config['zookeeper']['url'],
38
-            config['zookeeper']['notification_path'],
39
-            config['kafka']['group'],
40
-            config['kafka']['alarm_topic'])
41
-        self._producer = producer.KafkaProducer(config['kafka']['url'])
42
-        self._alarm_ttl = config['processors']['alarm']['ttl']
43
-        self._alarms = AlarmProcessor(self._alarm_ttl, config)
44
-        self._notifier = NotificationProcessor(config)
45
-
46
-        self._config = config
36
+            CONF.kafka.url,
37
+            ','.join(CONF.zookeeper.url),
38
+            CONF.zookeeper.notification_path,
39
+            CONF.kafka.group,
40
+            CONF.kafka.alarm_topic)
41
+        self._producer = producer.KafkaProducer(CONF.kafka.url)
42
+        self._alarms = ap.AlarmProcessor()
43
+        self._notifier = np.NotificationProcessor()
47 44
 
48 45
     def _add_periodic_notifications(self, notifications):
49 46
         for notification in notifications:
50 47
             topic = notification.periodic_topic
51
-            if topic in self._config['kafka']['periodic'] and notification.type == "webhook":
48
+            if topic in CONF.kafka.periodic and notification.type == "webhook":
52 49
                 notification.notification_timestamp = time.time()
53
-                self._producer.publish(self._config['kafka']['periodic'][topic],
50
+                self._producer.publish(CONF.kafka.periodic[topic],
54 51
                                        [notification.to_json()])
55 52
 
56 53
     def run(self):
@@ -62,9 +59,9 @@ class NotificationEngine(object):
62 59
                 self._add_periodic_notifications(notifications)
63 60
 
64 61
                 sent, failed = self._notifier.send(notifications)
65
-                self._producer.publish(self._topics['notification_topic'],
62
+                self._producer.publish(CONF.kafka.notification_topic,
66 63
                                        [i.to_json() for i in sent])
67
-                self._producer.publish(self._topics['retry_topic'],
64
+                self._producer.publish(CONF.kafka.notification_retry_topic,
68 65
                                        [i.to_json() for i in failed])
69 66
 
70 67
             self._consumer.commit()

+ 15
- 11
monasca_notification/periodic_engine.py View File

@@ -1,4 +1,5 @@
1 1
 # (C) Copyright 2016 Hewlett Packard Enterprise Development LP
2
+# Copyright 2017 Fujitsu LIMITED
2 3
 #
3 4
 # Licensed under the Apache License, Version 2.0 (the "License");
4 5
 # you may not use this file except in compliance with the License.
@@ -14,9 +15,11 @@
14 15
 # limitations under the License.
15 16
 
16 17
 import json
17
-import logging
18 18
 import time
19 19
 
20
+from oslo_config import cfg
21
+from oslo_log import log as logging
22
+
20 23
 from monasca_common.kafka import consumer
21 24
 from monasca_common.kafka import producer
22 25
 from monasca_notification.common.repositories import exceptions
@@ -26,25 +29,26 @@ from monasca_notification.common.utils import get_statsd_client
26 29
 from monasca_notification.processors import notification_processor
27 30
 
28 31
 log = logging.getLogger(__name__)
32
+CONF = cfg.CONF
29 33
 
30 34
 
31 35
 class PeriodicEngine(object):
32
-    def __init__(self, config, period):
33
-        self._topic_name = config['kafka']['periodic'][period]
36
+    def __init__(self, period):
37
+        self._topic_name = CONF.kafka.periodic[period]
34 38
 
35
-        self._statsd = get_statsd_client(config)
39
+        self._statsd = get_statsd_client()
36 40
 
37
-        zookeeper_path = config['zookeeper']['periodic_path'][period]
38
-        self._consumer = consumer.KafkaConsumer(config['kafka']['url'],
39
-                                                config['zookeeper']['url'],
41
+        zookeeper_path = CONF.zookeeper.periodic_path[period]
42
+        self._consumer = consumer.KafkaConsumer(CONF.kafka.url,
43
+                                                ','.join(CONF.zookeeper.url),
40 44
                                                 zookeeper_path,
41
-                                                config['kafka']['group'],
45
+                                                CONF.kafka.group,
42 46
                                                 self._topic_name)
43 47
 
44
-        self._producer = producer.KafkaProducer(config['kafka']['url'])
48
+        self._producer = producer.KafkaProducer(CONF.kafka.url)
45 49
 
46
-        self._notifier = notification_processor.NotificationProcessor(config)
47
-        self._db_repo = get_db_repo(config)
50
+        self._notifier = notification_processor.NotificationProcessor()
51
+        self._db_repo = get_db_repo()
48 52
         self._period = period
49 53
 
50 54
     def _keep_sending(self, alarm_id, original_state, type, period):

+ 1
- 4
monasca_notification/plugins/abstract_notifier.py View File

@@ -1,4 +1,5 @@
1 1
 # (C) Copyright 2015 Hewlett Packard Enterprise Development LP
2
+# Copyright 2017 Fujitsu LIMITED
2 3
 #
3 4
 # Licensed under the Apache License, Version 2.0 (the "License");
4 5
 # you may not use this file except in compliance with the License.
@@ -23,10 +24,6 @@ class AbstractNotifier(object):
23 24
     def __init__(self):
24 25
         pass
25 26
 
26
-    @abc.abstractproperty
27
-    def type(self):
28
-        pass
29
-
30 27
     @abc.abstractproperty
31 28
     def statsd_name(self):
32 29
         pass

+ 42
- 16
monasca_notification/plugins/email_notifier.py View File

@@ -1,4 +1,5 @@
1 1
 # (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
2
+# Copyright 2017 Fujitsu LIMITED
2 3
 #
3 4
 # Licensed under the Apache License, Version 2.0 (the "License");
4 5
 # you may not use this file except in compliance with the License.
@@ -20,9 +21,13 @@ import six
20 21
 import smtplib
21 22
 import time
22 23
 
24
+from debtcollector import removals
25
+from oslo_config import cfg
23 26
 
24 27
 from monasca_notification.plugins import abstract_notifier
25 28
 
29
+CONF = cfg.CONF
30
+
26 31
 EMAIL_SINGLE_HOST_BASE = u'''On host "{hostname}" for target "{target_host}" {message}
27 32
 
28 33
 Alarm "{alarm_name}" transitioned to the {state} state at {timestamp} UTC
@@ -60,22 +65,39 @@ With dimensions
60 65
 {metric_dimensions}'''
61 66
 
62 67
 
68
+def register_opts(conf):
69
+    gr = cfg.OptGroup(name='%s_notifier' % EmailNotifier.type)
70
+    opts = [
71
+        cfg.StrOpt(name='from_addr'),
72
+        cfg.HostAddressOpt(name='server'),
73
+        cfg.PortOpt(name='port', default=25),
74
+        cfg.IntOpt(name='timeout', default=5, min=1),
75
+        cfg.StrOpt(name='user', default=None),
76
+        cfg.StrOpt(name='password', default=None, secret=True),
77
+        cfg.StrOpt(name='grafana_url', default=None)
78
+    ]
79
+
80
+    conf.register_group(gr)
81
+    conf.register_opts(opts, group=gr)
82
+
83
+
63 84
 class EmailNotifier(abstract_notifier.AbstractNotifier):
64 85
 
86
+    type = 'email'
87
+
65 88
     def __init__(self, log):
66 89
         super(EmailNotifier, self).__init__()
67 90
         self._log = log
68 91
         self._smtp = None
69
-        self._config = None
70 92
 
71
-    def config(self, config):
72
-        self._config = config
93
+    @removals.remove(
94
+        message='Configuration of notifier is available through oslo.cfg',
95
+        version='1.9.0',
96
+        removal_version='3.0.0'
97
+    )
98
+    def config(self, config=None):
73 99
         self._smtp_connect()
74 100
 
75
-    @property
76
-    def type(self):
77
-        return "email"
78
-
79 101
     @property
80 102
     def statsd_name(self):
81 103
         return "sent_smtp_count"
@@ -122,7 +144,7 @@ class EmailNotifier(abstract_notifier.AbstractNotifier):
122 144
             return False
123 145
 
124 146
     def _sendmail(self, notification, msg):
125
-        self._smtp.sendmail(self._config['from_addr'],
147
+        self._smtp.sendmail(CONF.email_notifier.from_addr,
126 148
                             notification.address,
127 149
                             msg.as_string())
128 150
         self._log.debug("Sent email to {}, notification {}".format(notification.address,
@@ -135,15 +157,19 @@ class EmailNotifier(abstract_notifier.AbstractNotifier):
135 157
     def _smtp_connect(self):
136 158
         """Connect to the smtp server
137 159
         """
138
-        self._log.info("Connecting to Email Server {}".format(self._config['server']))
160
+        self._log.info("Connecting to Email Server {}".format(
161
+            CONF.email_notifier.server))
139 162
 
140 163
         try:
141
-            smtp = smtplib.SMTP(self._config['server'],
142
-                                self._config['port'],
143
-                                timeout=self._config['timeout'])
164
+            smtp = smtplib.SMTP(CONF.email_notifier.server,
165
+                                CONF.email_notifier.port,
166
+                                timeout=CONF.email_notifier.timeout)
144 167
 
145
-            if ('user', 'password') in self._config.keys():
146
-                smtp.login(self._config['user'], self._config['password'])
168
+            email_notifier_user = CONF.email_notifier.user
169
+            email_notifier_password = CONF.email_notifier.password
170
+            if email_notifier_user and email_notifier_password:
171
+                smtp.login(email_notifier_user,
172
+                           email_notifier_password)
147 173
 
148 174
             self._smtp = smtp
149 175
             return True
@@ -222,7 +248,7 @@ class EmailNotifier(abstract_notifier.AbstractNotifier):
222 248
 
223 249
         msg = email.mime.text.MIMEText(text, 'plain', 'utf-8')
224 250
         msg['Subject'] = email.header.Header(subject, 'utf-8')
225
-        msg['From'] = self._config['from_addr']
251
+        msg['From'] = CONF.email_notifier.from_addr
226 252
         msg['To'] = notification.address
227 253
         msg['Date'] = email.utils.formatdate(localtime=True, usegmt=True)
228 254
 
@@ -237,7 +263,7 @@ class EmailNotifier(abstract_notifier.AbstractNotifier):
237 263
         has been defined.
238 264
         """
239 265
 
240
-        grafana_url = self._config.get('grafana_url', None)
266
+        grafana_url = CONF.email_notifier.grafana_url
241 267
         if grafana_url is None:
242 268
             return None
243 269
 

+ 37
- 13
monasca_notification/plugins/hipchat_notifier.py View File

@@ -1,4 +1,5 @@
1 1
 # (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
2
+# Copyright 2017 Fujitsu LIMITED
2 3
 #
3 4
 # Licensed under the Apache License, Version 2.0 (the "License");
4 5
 # you may not use this file except in compliance with the License.
@@ -16,10 +17,13 @@
16 17
 import requests
17 18
 import ujson as json
18 19
 
20
+from debtcollector import removals
21
+from oslo_config import cfg
19 22
 from six.moves import urllib
20 23
 
21 24
 from monasca_notification.plugins import abstract_notifier
22 25
 
26
+CONF = cfg.CONF
23 27
 
24 28
 """
25 29
    notification.address = https://hipchat.hpcloud.net/v2/room/<room_id>/notification?auth_token=432432
@@ -44,17 +48,34 @@ SEVERITY_COLORS = {"low": 'green',
44 48
                    'critical': 'red'}
45 49
 
46 50
 
51
+def register_opts(conf):
52
+    gr = cfg.OptGroup(name='%s_notifier' % HipChatNotifier.type)
53
+    opts = [
54
+        cfg.IntOpt(name='timeout', default=5, min=1),
55
+        cfg.BoolOpt(name='insecure', default=True),
56
+        cfg.StrOpt(name='ca_certs', default=None),
57
+        cfg.StrOpt(name='proxy', default=None)
58
+    ]
59
+
60
+    conf.register_group(gr)
61
+    conf.register_opts(opts, group=gr)
62
+
63
+
47 64
 class HipChatNotifier(abstract_notifier.AbstractNotifier):
65
+
66
+    type = 'hipchat'
67
+
48 68
     def __init__(self, log):
69
+        super(HipChatNotifier, self).__init__()
49 70
         self._log = log
50 71
 
51
-    def config(self, config_dict):
52
-        self._config = {'timeout': 5}
53
-        self._config.update(config_dict)
54
-
55
-    @property
56
-    def type(self):
57
-        return "hipchat"
72
+    @removals.remove(
73
+        message='Configuration of notifier is available through oslo.cfg',
74
+        version='1.9.0',
75
+        removal_version='3.0.0'
76
+    )
77
+    def config(self, config_dict=None):
78
+        pass
58 79
 
59 80
     @property
60 81
     def statsd_name(self):
@@ -97,14 +118,17 @@ class HipChatNotifier(abstract_notifier.AbstractNotifier):
97 118
         url = urllib.parse.urljoin(notification.address, urllib.parse.urlparse(notification.address).path)
98 119
 
99 120
         # Default option is to do cert verification
100
-        verify = not self._config.get('insecure', True)
121
+        verify = not CONF.hipchat_notifier.insecure
122
+        ca_certs = CONF.hipchat_notifier.ca_certs
123
+        proxy = CONF.hipchat_notifier.proxy
124
+
101 125
         # If ca_certs is specified, do cert validation and ignore insecure flag
102
-        if (self._config.get("ca_certs")):
103
-            verify = self._config.get("ca_certs")
126
+        if ca_certs is not None:
127
+            verify = ca_certs
104 128
 
105 129
         proxyDict = None
106
-        if (self._config.get("proxy")):
107
-            proxyDict = {"https": self._config.get("proxy")}
130
+        if proxy is not None:
131
+            proxyDict = {'https': proxy}
108 132
 
109 133
         try:
110 134
             # Posting on the given URL
@@ -113,7 +137,7 @@ class HipChatNotifier(abstract_notifier.AbstractNotifier):
113 137
                                    verify=verify,
114 138
                                    params=query_params,
115 139
                                    proxies=proxyDict,
116
-                                   timeout=self._config['timeout'])
140
+                                   timeout=CONF.hipchat_notifier.timeout)
117 141
 
118 142
             if result.status_code in range(200, 300):
119 143
                 self._log.info("Notification successfully posted.")

+ 47
- 22
monasca_notification/plugins/jira_notifier.py View File

@@ -1,4 +1,5 @@
1 1
 # (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
2
+# Copyright 2017 Fujitsu LIMITED
2 3
 #
3 4
 # Licensed under the Apache License, Version 2.0 (the "License");
4 5
 # you may not use this file except in compliance with the License.
@@ -19,6 +20,9 @@ from six.moves import urllib
19 20
 import ujson as json
20 21
 import yaml
21 22
 
23
+from debtcollector import removals
24
+from oslo_config import cfg
25
+
22 26
 from monasca_notification.plugins.abstract_notifier import AbstractNotifier
23 27
 
24 28
 """
@@ -51,27 +55,40 @@ from monasca_notification.plugins.abstract_notifier import AbstractNotifier
51 55
 """
52 56
 
53 57
 
58
+CONF = cfg.CONF
59
+
60
+
61
+def register_opts(conf):
62
+    gr = cfg.OptGroup(name='%s_notifier' % JiraNotifier.type)
63
+    opts = [
64
+        cfg.IntOpt(name='timeout', default=5, min=1),
65
+        cfg.StrOpt(name='user', required=False),
66
+        cfg.StrOpt(name='password', required=False, secret=True),
67
+        cfg.StrOpt(name='custom_formatter', default=None),
68
+        cfg.StrOpt(name='proxy', default=None)
69
+    ]
70
+
71
+    conf.register_group(gr)
72
+    conf.register_opts(opts, group=gr)
73
+
74
+
54 75
 class JiraNotifier(AbstractNotifier):
55 76
 
77
+    type = 'jira'
56 78
     _search_query = search_query = "project={} and reporter='{}' and summary ~ '{}'"
57 79
 
58 80
     def __init__(self, log):
81
+        super(JiraNotifier, self).__init__()
59 82
         self._log = log
60 83
         self.jira_fields_format = None
61 84
 
85
+    @removals.remove(
86
+        message='Configuration of notifier is available through oslo.cfg',
87
+        version='1.9.0',
88
+        removal_version='3.0.0'
89
+    )
62 90
     def config(self, config_dict):
63
-        self._config = {'timeout': 5}
64
-        if not config_dict.get("user") and not config_dict.get("password"):
65
-            message = "Missing user and password settings in JIRA plugin configuration"
66
-            self._log.exception(message)
67
-            raise Exception(message)
68
-
69
-        self._config.update(config_dict)
70
-        self.jira_fields_format = self._get_jira_custom_format_fields()
71
-
72
-    @property
73
-    def type(self):
74
-        return "jira"
91
+        pass
75 92
 
76 93
     @property
77 94
     def statsd_name(self):
@@ -80,9 +97,10 @@ class JiraNotifier(AbstractNotifier):
80 97
     def _get_jira_custom_format_fields(self):
81 98
         jira_fields_format = None
82 99
 
83
-        if (not self.jira_fields_format and self._config.get("custom_formatter")):
100
+        formatter = CONF.jira_notifier.custom_formatter
101
+        if not self.jira_fields_format and formatter:
84 102
             try:
85
-                with open(self._config.get("custom_formatter")) as f:
103
+                with open(formatter, 'r') as f:
86 104
                     jira_fields_format = yaml.safe_load(f)
87 105
             except Exception:
88 106
                 self._log.exception("Unable to read custom_formatter file. Check file location")
@@ -139,8 +157,10 @@ class JiraNotifier(AbstractNotifier):
139 157
         return jira_fields
140 158
 
141 159
     def _build_jira_message(self, notification):
142
-        if self._config.get("custom_formatter"):
143
-            return self._build_custom_jira_message(notification, self.jira_fields_format)
160
+        formatter = CONF.jira_notifier.custom_formatter
161
+        if formatter:
162
+            return self._build_custom_jira_message(notification,
163
+                                                   self._get_jira_custom_format_fields())
144 164
 
145 165
         return self._build_default_jira_message(notification)
146 166
 
@@ -159,13 +179,17 @@ class JiraNotifier(AbstractNotifier):
159 179
         if query_params.get("component"):
160 180
             jira_fields["component"] = query_params["component"][0]
161 181
 
162
-        auth = (self._config["user"], self._config["password"])
163
-        proxyDict = None
164
-        if (self._config.get("proxy")):
165
-            proxyDict = {"https": self._config.get("proxy")}
182
+        auth = (
183
+            CONF.jira_notifier.user,
184
+            CONF.jira_notifier.password
185
+        )
186
+        proxy = CONF.jira_notifier.proxy
187
+        proxy_dict = None
188
+        if proxy is not None:
189
+            proxy_dict = {"https": proxy}
166 190
 
167 191
         try:
168
-            jira_obj = jira.JIRA(url, basic_auth=auth, proxies=proxyDict)
192
+            jira_obj = jira.JIRA(url, basic_auth=auth, proxies=proxy_dict)
169 193
 
170 194
             self.jira_workflow(jira_fields, jira_obj, notification)
171 195
         except Exception:
@@ -192,7 +216,8 @@ class JiraNotifier(AbstractNotifier):
192 216
             issue_dict["components"] = [{"name": jira_fields.get("component")}]
193 217
 
194 218
         search_term = self._search_query.format(issue_dict["project"]["key"],
195
-                                                self._config["user"], notification.alarm_id)
219
+                                                CONF.jira_notifier.user,
220
+                                                notification.alarm_id)
196 221
         issue_list = jira_obj.search_issues(search_term)
197 222
         if not issue_list:
198 223
             self._log.debug("Creating an issue with the data {}".format(issue_dict))

+ 30
- 10
monasca_notification/plugins/pagerduty_notifier.py View File

@@ -1,4 +1,5 @@
1 1
 # (C) Copyright 2015,2016 Hewlett Packard Enterprise Development LP
2
+# Copyright 2017 Fujitsu LIMITED
2 3
 #
3 4
 # Licensed under the Apache License, Version 2.0 (the "License");
4 5
 # you may not use this file except in compliance with the License.
@@ -16,25 +17,44 @@
16 17
 import requests
17 18
 import ujson as json
18 19
 
20
+from debtcollector import removals
21
+from oslo_config import cfg
22
+
19 23
 from monasca_notification.plugins import abstract_notifier
20 24
 
21 25
 
26
+CONF = cfg.CONF
22 27
 VALID_HTTP_CODES = [200, 201, 204]
23 28
 
24 29
 
30
+def register_opts(conf):
31
+    gr = cfg.OptGroup(name='%s_notifier' % PagerdutyNotifier.type)
32
+    opts = [
33
+        cfg.IntOpt(name='timeout', default=5, min=1),
34
+        cfg.StrOpt(name='url',
35
+                   default='https://events.pagerduty.com/'
36
+                           'generic/2010-04-15/create_event.json')
37
+    ]
38
+
39
+    conf.register_group(gr)
40
+    conf.register_opts(opts, group=gr)
41
+
42
+
25 43
 class PagerdutyNotifier(abstract_notifier.AbstractNotifier):
44
+
45
+    type = 'pagerduty'
46
+
26 47
     def __init__(self, log):
48
+        super(PagerdutyNotifier, self).__init__()
27 49
         self._log = log
28 50
 
51
+    @removals.remove(
52
+        message='Configuration of notifier is available through oslo.cfg',
53
+        version='1.9.0',
54
+        removal_version='3.0.0'
55
+    )
29 56
     def config(self, config):
30
-        self._config = {
31
-            'timeout': 5,
32
-            'url': 'https://events.pagerduty.com/generic/2010-04-15/create_event.json'}
33
-        self._config.update(config)
34
-
35
-    @property
36
-    def type(self):
37
-        return "pagerduty"
57
+        pass
38 58
 
39 59
     @property
40 60
     def statsd_name(self):
@@ -44,7 +64,7 @@ class PagerdutyNotifier(abstract_notifier.AbstractNotifier):
44 64
         """Send pagerduty notification
45 65
         """
46 66
 
47
-        url = self._config['url']
67
+        url = CONF.pagerduty_notifier.url
48 68
         headers = {"content-type": "application/json"}
49 69
         body = {"service_key": notification.address,
50 70
                 "event_type": "trigger",
@@ -60,7 +80,7 @@ class PagerdutyNotifier(abstract_notifier.AbstractNotifier):
60 80
             result = requests.post(url=url,
61 81
                                    data=json.dumps(body),
62 82
                                    headers=headers,
63
-                                   timeout=self._config['timeout'])
83
+                                   timeout=CONF.pagerduty_notifier.timeout)
64 84
 
65 85
             if result.status_code in VALID_HTTP_CODES:
66 86
                 return True

+ 35
- 17
monasca_notification/plugins/slack_notifier.py View File

@@ -1,4 +1,5 @@
1 1
 # (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
2
+# Copyright 2017 Fujitsu LIMITED
2 3
 #
3 4
 # Licensed under the Apache License, Version 2.0 (the "License");
4 5
 # you may not use this file except in compliance with the License.
@@ -17,8 +18,26 @@ import requests
17 18
 from six.moves import urllib
18 19
 import ujson as json
19 20
 
21
+from debtcollector import removals
22
+from oslo_config import cfg
23
+
20 24
 from monasca_notification.plugins import abstract_notifier
21 25
 
26
+CONF = cfg.CONF
27
+
28
+
29
+def register_opts(conf):
30
+    gr = cfg.OptGroup(name='%s_notifier' % SlackNotifier.type)
31
+    opts = [
32
+        cfg.IntOpt(name='timeout', default=5, min=1),
33
+        cfg.BoolOpt(name='insecure', default=True),
34
+        cfg.StrOpt(name='ca_certs', default=None),
35
+        cfg.StrOpt(name='proxy', default=None)
36
+    ]
37
+
38
+    conf.register_group(gr)
39
+    conf.register_opts(opts, group=gr)
40
+
22 41
 
23 42
 class SlackNotifier(abstract_notifier.AbstractNotifier):
24 43
     """This module is a notification plugin to integrate with Slack.
@@ -42,25 +61,24 @@ class SlackNotifier(abstract_notifier.AbstractNotifier):
42 61
             https://api.slack.com/incoming-webhooks
43 62
     """
44 63
 
45
-    CONFIG_CA_CERTS = 'ca_certs'
46
-    CONFIG_INSECURE = 'insecure'
47
-    CONFIG_PROXY = 'proxy'
48
-    CONFIG_TIMEOUT = 'timeout'
64
+    type = 'slack'
65
+
49 66
     MAX_CACHE_SIZE = 100
50 67
     RESPONSE_OK = 'ok'
51 68
 
52 69
     _raw_data_url_caches = []
53 70
 
54 71
     def __init__(self, log):
72
+        super(SlackNotifier, self).__init__()
55 73
         self._log = log
56 74
 
75
+    @removals.remove(
76
+        message='Configuration of notifier is available through oslo.cfg',
77
+        version='1.9.0',
78
+        removal_version='3.0.0'
79
+    )
57 80
     def config(self, config_dict):
58
-        self._config = {'timeout': 5}
59
-        self._config.update(config_dict)
60
-
61
-    @property
62
-    def type(self):
63
-        return "slack"
81
+        pass
64 82
 
65 83
     @property
66 84
     def statsd_name(self):
@@ -141,12 +159,12 @@ class SlackNotifier(abstract_notifier.AbstractNotifier):
141 159
 
142 160
         # Default option is to do cert verification
143 161
         # If ca_certs is specified, do cert validation and ignore insecure flag
144
-        verify = self._config.get(self.CONFIG_CA_CERTS,
145
-                                  (not self._config.get(self.CONFIG_INSECURE, True)))
162
+        verify = CONF.slack_notifier.ca_certs or not CONF.slack_notifier.insecure
146 163
 
147
-        proxyDict = None
148
-        if (self.CONFIG_PROXY in self._config):
149
-            proxyDict = {'https': self._config.get(self.CONFIG_PROXY)}
164
+        proxy = CONF.slack_notifier.proxy
165
+        proxy_dict = None
166
+        if proxy is not None:
167
+            proxy_dict = {'https': proxy}
150 168
 
151 169
         data_format_list = ['json', 'data']
152 170
         if url in SlackNotifier._raw_data_url_caches:
@@ -159,8 +177,8 @@ class SlackNotifier(abstract_notifier.AbstractNotifier):
159 177
                 'url': url,
160 178
                 'verify': verify,
161 179
                 'params': query_params,
162
-                'proxies': proxyDict,
163
-                'timeout': self._config[self.CONFIG_TIMEOUT],
180
+                'proxies': proxy_dict,
181
+                'timeout': CONF.slack_notifier.timeout,
164 182
                 data_format: slack_message
165 183
             }
166 184
             if self._send_message(request_options):

+ 27
- 7
monasca_notification/plugins/webhook_notifier.py View File

@@ -1,4 +1,5 @@
1 1
 # (C) Copyright 2015,2016 Hewlett Packard Enterprise Development LP
2
+# Copyright 2017 Fujitsu LIMITED
2 3
 #
3 4
 # Licensed under the Apache License, Version 2.0 (the "License");
4 5
 # you may not use this file except in compliance with the License.
@@ -16,20 +17,39 @@
16 17
 import requests
17 18
 import ujson as json
18 19
 
20
+from debtcollector import removals
21
+from oslo_config import cfg
22
+
19 23
 from monasca_notification.plugins import abstract_notifier
20 24
 
25
+CONF = cfg.CONF
26
+
27
+
28
+def register_opts(conf):
29
+    gr = cfg.OptGroup(name='%s_notifier' % WebhookNotifier.type)
30
+    opts = [
31
+        cfg.IntOpt(name='timeout', default=5, min=1)
32
+    ]
33
+
34
+    conf.register_group(gr)
35
+    conf.register_opts(opts, group=gr)
36
+
21 37
 
22 38
 class WebhookNotifier(abstract_notifier.AbstractNotifier):
39
+
40
+    type = 'webhook'
41
+
23 42
     def __init__(self, log):
43
+        super(WebhookNotifier, self).__init__()
24 44
         self._log = log
25 45
 
46
+    @removals.remove(
47
+        message='Configuration of notifier is available through oslo.cfg',
48
+        version='1.9.0',
49
+        removal_version='3.0.0'
50
+    )
26 51
     def config(self, config_dict):
27
-        self._config = {'timeout': 5}
28
-        self._config.update(config_dict)
29
-
30
-    @property
31
-    def type(self):
32
-        return "webhook"
52
+        pass
33 53
 
34 54
     @property
35 55
     def statsd_name(self):
@@ -60,7 +80,7 @@ class WebhookNotifier(abstract_notifier.AbstractNotifier):
60 80
             result = requests.post(url=url,
61 81
                                    data=json.dumps(body),
62 82
                                    headers=headers,
63
-                                   timeout=self._config['timeout'])
83
+                                   timeout=CONF.webhook_notifier.timeout)
64 84
 
65 85
             if result.status_code in range(200, 300):
66 86
                 self._log.info("Notification successfully posted.")

+ 8
- 6
monasca_notification/processors/alarm_processor.py View File

@@ -13,10 +13,11 @@
13 13
 # implied.
14 14
 # See the License for the specific language governing permissions and
15 15
 # limitations under the License.
16
+import time
16 17
 
17
-import logging
18
+from oslo_config import cfg
19
+from oslo_log import log as logging
18 20
 import six
19
-import time
20 21
 import ujson as json
21 22
 
22 23
 from monasca_notification.common.repositories import exceptions as exc
@@ -27,13 +28,14 @@ from monasca_notification import notification_exceptions
27 28
 
28 29
 
29 30
 log = logging.getLogger(__name__)
31
+CONF = cfg.CONF
30 32
 
31 33
 
32 34
 class AlarmProcessor(object):
33
-    def __init__(self, alarm_ttl, config):
34
-        self._alarm_ttl = alarm_ttl
35
-        self._statsd = get_statsd_client(config)
36
-        self._db_repo = get_db_repo(config)
35
+    def __init__(self):
36
+        self._alarm_ttl = CONF.alarm_processor.ttl
37
+        self._statsd = get_statsd_client()
38
+        self._db_repo = get_db_repo()
37 39
 
38 40
     @staticmethod
39 41
     def _parse_alarm(alarm_data):

+ 8
- 6
monasca_notification/processors/notification_processor.py View File

@@ -13,7 +13,7 @@
13 13
 # See the License for the specific language governing permissions and
14 14
 # limitations under the License.
15 15
 
16
-import logging
16
+from oslo_log import log as logging
17 17
 
18 18
 from monasca_notification.common.repositories import exceptions as exc
19 19
 from monasca_notification.common.utils import get_db_repo
@@ -25,12 +25,14 @@ log = logging.getLogger(__name__)
25 25
 
26 26
 class NotificationProcessor(object):
27 27
 
28
-    def __init__(self, config):
29
-        self.statsd = get_statsd_client(config)
28
+    def __init__(self):
29
+        self.statsd = get_statsd_client()
30 30
         notifiers.init(self.statsd)
31
-        notifiers.load_plugins(config['notification_types'])
32
-        notifiers.config(config['notification_types'])
33
-        self._db_repo = get_db_repo(config)
31
+
32
+        notifiers.load_plugins()
33
+        notifiers.config()
34
+
35
+        self._db_repo = get_db_repo()
34 36
         self.insert_configured_plugins()
35 37
 
36 38
     def _remaining_plugin_types(self):

+ 21
- 24
monasca_notification/retry_engine.py View File

@@ -1,4 +1,5 @@
1 1
 # (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
2
+# Copyright 2017 Fujitsu LIMITED
2 3
 #
3 4
 # Licensed under the Apache License, Version 2.0 (the "License");
4 5
 # you may not use this file except in compliance with the License.
@@ -14,9 +15,11 @@
14 15
 # limitations under the License.
15 16
 
16 17
 import json
17
-import logging
18 18
 import time
19 19
 
20
+from oslo_config import cfg
21
+from oslo_log import log as logging
22
+
20 23
 from monasca_common.kafka import consumer
21 24
 from monasca_common.kafka import producer
22 25
 from monasca_notification.common.utils import construct_notification_object
@@ -25,30 +28,24 @@ from monasca_notification.common.utils import get_statsd_client
25 28
 from monasca_notification.processors import notification_processor
26 29
 
27 30
 log = logging.getLogger(__name__)
31
+CONF = cfg.CONF
28 32
 
29 33
 
30 34
 class RetryEngine(object):
31
-    def __init__(self, config):
32
-        self._retry_interval = config['retry']['interval']
33
-        self._retry_max = config['retry']['max_attempts']
34
-
35
-        self._topics = {}
36
-        self._topics['notification_topic'] = config['kafka']['notification_topic']
37
-        self._topics['retry_topic'] = config['kafka']['notification_retry_topic']
38
-
39
-        self._statsd = get_statsd_client(config)
35
+    def __init__(self):
36
+        self._statsd = get_statsd_client()
40 37
 
41 38
         self._consumer = consumer.KafkaConsumer(
42
-            config['kafka']['url'],
43
-            config['zookeeper']['url'],
44
-            config['zookeeper']['notification_retry_path'],
45
-            config['kafka']['group'],
46
-            config['kafka']['notification_retry_topic'])
47
-
48
-        self._producer = producer.KafkaProducer(config['kafka']['url'])
39
+            CONF.kafka.url,
40
+            ','.join(CONF.zookeeper.url),
41
+            CONF.zookeeper.notification_retry_path,
42
+            CONF.kafka.group,
43
+            CONF.kafka.notification_retry_topic
44
+        )
45
+        self._producer = producer.KafkaProducer(CONF.kafka.url)
49 46
 
50
-        self._notifier = notification_processor.NotificationProcessor(config)
51
-        self._db_repo = get_db_repo(config)
47
+        self._notifier = notification_processor.NotificationProcessor()
48
+        self._db_repo = get_db_repo()
52 49
 
53 50
     def run(self):
54 51
         for raw_notification in self._consumer:
@@ -62,7 +59,7 @@ class RetryEngine(object):
62 59
                 self._consumer.commit()
63 60
                 continue
64 61
 
65
-            wait_duration = self._retry_interval - (
62
+            wait_duration = CONF.retry_engine.interval - (
66 63
                 time.time() - notification_data['notification_timestamp'])
67 64
 
68 65
             if wait_duration > 0:
@@ -71,19 +68,19 @@ class RetryEngine(object):
71 68
             sent, failed = self._notifier.send([notification])
72 69
 
73 70
             if sent:
74
-                self._producer.publish(self._topics['notification_topic'],
71
+                self._producer.publish(CONF.kafka.notification_topic,
75 72
                                        [notification.to_json()])
76 73
 
77 74
             if failed:
78 75
                 notification.retry_count += 1
79 76
                 notification.notification_timestamp = time.time()
80
-                if notification.retry_count < self._retry_max:
77
+                if notification.retry_count < CONF.retry_engine.max_attempts:
81 78
                     log.error(u"retry failed for {} with name {} "
82 79
                               u"at {}.  "
83 80
                               u"Saving for later retry.".format(notification.type,
84 81
                                                                 notification.name,
85 82
                                                                 notification.address))
86
-                    self._producer.publish(self._topics['retry_topic'],
83
+                    self._producer.publish(CONF.kafka.notification_retry_topic,
87 84
                                            [notification.to_json()])
88 85
                 else:
89 86
                     log.error(u"retry failed for {} with name {} "
@@ -92,6 +89,6 @@ class RetryEngine(object):
92 89
                               .format(notification.type,
93 90
                                       notification.name,
94 91
                                       notification.address,
95
-                                      self._retry_max))
92
+                                      CONF.retry_engine.max_attempts))
96 93
 
97 94
             self._consumer.commit()

+ 24
- 26
monasca_notification/types/notifiers.py View File

@@ -1,4 +1,5 @@
1 1
 # (C) Copyright 2015,2016 Hewlett Packard Enterprise Development LP
2
+# Copyright 2017 Fujitsu LIMITED
2 3
 #
3 4
 # Licensed under the Apache License, Version 2.0 (the "License");
4 5
 # you may not use this file except in compliance with the License.
@@ -13,15 +14,19 @@
13 14
 # See the License for the specific language governing permissions and
14 15
 # limitations under the License.
15 16
 
16
-import logging
17 17
 import time
18 18
 
19
-from monasca_common.simport import simport
19
+from debtcollector import removals
20
+from oslo_config import cfg
21
+from oslo_log import log as logging
22
+from oslo_utils import importutils
23
+
20 24
 from monasca_notification.plugins import email_notifier
21 25
 from monasca_notification.plugins import pagerduty_notifier
22 26
 from monasca_notification.plugins import webhook_notifier
23 27
 
24 28
 log = logging.getLogger(__name__)
29
+CONF = cfg.CONF
25 30
 
26 31
 possible_notifiers = None
27 32
 configured_notifiers = None
@@ -49,14 +54,16 @@ def init(statsd_obj):
49 54
     ]
50 55
 
51 56
 
52
-def load_plugins(config):
57
+def load_plugins():
53 58
     global possible_notifiers
54
-
55
-    for plugin_class in config.get("plugins", []):
59
+    for plugin_class in CONF.notification_types.enabled:
56 60
         try:
57
-            possible_notifiers.append(simport.load(plugin_class)(log))
61
+            plugin_class = plugin_class.replace(':', '.')
62
+            clz = importutils.import_class(plugin_class)
63
+            possible_notifiers.append(clz(logging.getLogger(plugin_class)))
58 64
         except Exception:
59
-            log.exception("unable to load the class {0} , ignoring it".format(plugin_class))
65
+            log.exception("unable to load the class %s , ignoring it" %
66
+                          plugin_class)
60 67
 
61 68
 
62 69
 def enabled_notifications():
@@ -68,29 +75,20 @@ def enabled_notifications():
68 75
     return results
69 76
 
70 77
 
71
-def config(cfg):
78
+@removals.remove(
79
+    message='Loading the plugin configuration has been moved to oslo, '
80
+            'This method will be fully deleted in future releases',
81
+    version='1.9.0',
82
+    removal_version='3.0.0'
83
+)
84
+def config():
72 85
     global possible_notifiers, configured_notifiers, statsd_counter
73 86
 
74
-    formatted_config = {t.lower(): v for t, v in cfg.items()}
75 87
     for notifier in possible_notifiers:
76 88
         ntype = notifier.type.lower()
77
-        if ntype in formatted_config:
78
-            try:
79
-                notifier.config(formatted_config[ntype])
80
-                configured_notifiers[ntype] = notifier
81
-                statsd_counter[ntype] = statsd.get_counter(notifier.statsd_name)
82
-                log.info("{} notification ready".format(ntype))
83
-            except Exception:
84
-                log.exception("config exception for {}".format(ntype))
85
-        else:
86
-            log.warn("No config data for type: {}".format(ntype))
87
-    config_with_no_notifiers = set(formatted_config.keys()) - set(configured_notifiers.keys())
88
-    # Plugins section contains only additional plugins and should not be
89
-    # considered as a separate plugin
90
-    if 'plugins' in config_with_no_notifiers:
91
-        config_with_no_notifiers.remove('plugins')
92
-    if config_with_no_notifiers:
93
-        log.warn("No notifiers found for {0}". format(", ".join(config_with_no_notifiers)))
89
+        configured_notifiers[ntype] = notifier
90
+        statsd_counter[ntype] = statsd.get_counter(notifier.statsd_name)
91
+        log.info("{} notification ready".format(ntype))
94 92
 
95 93
 
96 94
 def send_notifications(notifications):

+ 23
- 0
monasca_notification/version.py View File

@@ -0,0 +1,23 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+from pbr import version
16
+
17
+__all__ = [
18
+    'version_info',
19
+    'version_string'
20
+]
21
+
22
+version_info = version.VersionInfo('monasca-notification')
23
+version_string = version_info.version_string()

+ 5
- 2
requirements.txt View File

@@ -1,9 +1,12 @@
1 1
 # The order of packages is significant, because pip processes them in the order
2 2
 # of appearance. Changing the order has an impact on the overall integration
3 3
 # process, which may cause wedges in the gate later.
4
-pbr!=2.1.0,>=2.0.0 # Apache-2.0
4
+pbr>=2.0.0,!=2.1.0  # Apache-2.0
5
+debtcollector>=1.2.0  # Apache-2.0
5 6
 monasca-statsd>=1.1.0 # Apache-2.0
6 7
 requests>=2.14.2 # Apache-2.0
7
-PyYAML>=3.10.0 # MIT
8
+PyYAML>=3.10  # MIT
8 9
 six>=1.9.0 # MIT
9 10
 monasca-common>=1.4.0 # Apache-2.0
11
+oslo.config>=4.6.0  # Apache-2.0
12
+oslo.log>=3.30.0  # Apache-2.0

+ 3
- 1
setup.cfg View File

@@ -13,8 +13,10 @@ home-page = https://github.com/stackforge/monasca-notification
13 13
 license = Apache
14 14
 
15 15
 [entry_points]
16
-console_scripts = 
16
+console_scripts =
17 17
     monasca-notification = monasca_notification.main:main
18
+oslo.config.opts =
19
+    monasca_notification = monasca_notification.conf:list_opts
18 20
 
19 21
 [files]
20 22
 packages = monasca_notification

+ 8
- 6
test-requirements.txt View File

@@ -2,14 +2,16 @@
2 2
 # of appearance. Changing the order has an impact on the overall integration
3 3
 # process, which may cause wedges in the gate later.
4 4
 # Hacking already pins down pep8, pyflakes and flake8
5
+
5 6
 bandit>=1.1.0 # Apache-2.0
7
+Babel>=2.3.4,!=2.4.0  # BSD
6 8
 hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
7
-coverage!=4.4,>=4.0 # Apache-2.0
8
-mock>=2.0 # BSD
9
-funcsigs>=0.4;python_version=='2.7' or python_version=='2.6' # Apache-2.0
10
-os-testr>=0.8.0 # Apache-2.0
9
+coverage>=4.0,!=4.4  # Apache-2.0
10
+mock>=2.0.0  # BSD
11
+funcsigs>=1.0.0;python_version=='2.7' or python_version=='2.6'  # Apache-2.0
12
+os-testr>=1.0.0 # Apache-2.0
11 13
 oslotest>=1.10.0 # Apache-2.0
12 14
 testrepository>=0.0.18 # Apache-2.0/BSD
13
-SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT
15
+SQLAlchemy>=1.0.10,!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8  # MIT
14 16
 PyMySQL>=0.7.6 # MIT License
15
-psycopg2>=2.5 # LGPL/ZPL
17
+psycopg2>=2.6.2 # LGPL/ZPL

+ 83
- 0
tests/base.py View File

@@ -0,0 +1,83 @@
1
+# Copyright 2017 FUJITSU LIMITED
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+#      http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+import fixtures
16
+import mock
17
+
18
+from oslo_config import cfg
19
+from oslo_config import fixture as oo_cfg
20
+from oslotest import base as oslotest_base
21
+
22
+from monasca_notification import conf
23
+from monasca_notification import config
24
+
25
+
26
+class DisableStatsdFixture(fixtures.Fixture):
27
+
28
+    def setUp(self):
29
+        super(DisableStatsdFixture, self).setUp()
30
+        statsd_patch = mock.patch('monascastatsd.Connection')
31
+        statsd_patch.start()
32
+        self.addCleanup(statsd_patch.stop)
33
+
34
+
35
+class ConfigFixture(oo_cfg.Config):
36
+    """Mocks configuration"""
37
+
38
+    def __init__(self):
39
+        super(ConfigFixture, self).__init__(config.CONF)
40
+
41
+    def setUp(self):
42
+        super(ConfigFixture, self).setUp()
43
+
44
+        self.addCleanup(self._clean_config_loaded_flag)
45
+
46
+        conf.register_opts()
47
+        # prevent test from trying to load the yaml file
48
+        config.parse_args(argv=[], no_yaml=True)
49
+
50
+    @staticmethod
51