From 71c9acb1cc1033461cb9497612d9534318616391 Mon Sep 17 00:00:00 2001 From: Deklan Dieterly Date: Thu, 13 Nov 2014 12:57:00 -0700 Subject: [PATCH] Add alarm history resource Change-Id: Ic19c09cc0808c994cadfb7e3c7680dd58438852d --- monasca/api/alarms_api_v2.py | 8 + monasca/api/monasca_api_v2.py | 10 -- monasca/api/server.py | 16 +- monasca/common/kafka_conn.py | 2 +- monasca/common/messaging/exceptions.py | 1 + monasca/common/messaging/kafka_publisher.py | 6 +- .../messaging/message_formats/cadf/events.py | 1 + .../messaging/message_formats/cadf/metrics.py | 1 + .../events_transform_factory.py | 10 +- .../messaging/message_formats/exceptions.py | 1 + .../message_formats/identity/events.py | 1 - .../message_formats/identity/metrics.py | 1 - .../metrics_transform_factory.py | 10 +- .../message_formats/reference/events.py | 2 +- .../message_formats/reference/metrics.py | 14 +- monasca/common/messaging/publisher.py | 8 +- .../alarm_definitions_repository.py | 14 +- .../common/repositories/alarms_repository.py | 26 ++- .../common/repositories/events_repository.py | 1 + monasca/common/repositories/exceptions.py | 4 +- .../influxdb/metrics_repository.py | 167 +++++++++++++++--- .../common/repositories/metrics_repository.py | 2 +- .../mysql/alarm_definitions_repository.py | 35 ++-- .../repositories/mysql/alarms_repository.py | 38 ++-- monasca/common/repositories/mysql/model.py | 4 +- .../repositories/mysql/mysql_repository.py | 20 +-- .../mysql/notifications_repository.py | 16 +- .../mysql/transforms_repository.py | 37 ++-- .../repositories/notifications_repository.py | 1 + .../repositories/transforms_repository.py | 9 +- monasca/common/resource_api.py | 24 ++- monasca/dispatcher/__init__.py | 1 + .../expression_parser/alarm_expr_parser.py | 127 +++++++------ monasca/middleware/mock_auth_filter.py | 6 +- monasca/tests/__init__.py | 87 --------- monasca/tests/first_test.py | 21 +++ .../alarm_definition_request_body_schema.py | 35 ++-- .../v2/common/schemas/dimensions_schema.py | 12 +- .../schemas/events_request_body_schema.py | 14 +- .../v2/common/schemas/metric_name_schema.py | 9 +- .../schemas/metrics_request_body_schema.py | 21 +-- .../notifications_request_body_schema.py | 20 ++- .../schemas/transforms_request_body_schema.py | 24 +-- monasca/v2/reference/alarm_definitions.py | 69 +++++--- monasca/v2/reference/alarming.py | 28 +-- monasca/v2/reference/alarms.py | 79 +++++++-- monasca/v2/reference/events.py | 39 ++-- monasca/v2/reference/helpers.py | 71 ++++---- monasca/v2/reference/metrics.py | 65 ++++--- monasca/v2/reference/notifications.py | 36 ++-- monasca/v2/reference/resource.py | 19 +- monasca/v2/reference/transforms.py | 26 ++- requirements.txt | 2 +- test-requirements.txt | 1 + tox.ini | 12 +- 55 files changed, 745 insertions(+), 569 deletions(-) create mode 100644 monasca/tests/first_test.py diff --git a/monasca/api/alarms_api_v2.py b/monasca/api/alarms_api_v2.py index d2f865c8f..7a4f61182 100644 --- a/monasca/api/alarms_api_v2.py +++ b/monasca/api/alarms_api_v2.py @@ -45,3 +45,11 @@ class AlarmsV2API(object): @resource_api.Restify('/v2.0/alarms/{id}', method='get') def do_get_alarm_by_id(self, req, res, id): res.status = '501 Not Implemented' + + @resource_api.Restify('/v2.0/alarms/x', method='get') + def do_get_alarms_state_history(self, req, res): + res.status = '501 Not Implemented' + + @resource_api.Restify('/v2.0/alarms/{id}/state-history', method='get') + def do_get_alarm_state_history(self, req, res, id): + res.status = '501 Not Implemented' diff --git a/monasca/api/monasca_api_v2.py b/monasca/api/monasca_api_v2.py index 16a5fb695..1048b6402 100644 --- a/monasca/api/monasca_api_v2.py +++ b/monasca/api/monasca_api_v2.py @@ -45,13 +45,3 @@ class V2API(object): @resource_api.Restify('/v2.0/metrics/statistics', method='get') def do_get_statistics(self, req, res): res.status = '501 Not Implemented' - - - - @resource_api.Restify('/v2.0/alarms/state-history', method='get') - def do_get_alarms_state_history(self, req, res, id): - res.status = '501 Not Implemented' - - @resource_api.Restify('/v2.0/alarms/{id}/state-history', method='get') - def do_get_alarm_state_history(self, req, res, id): - res.status = '501 Not Implemented' diff --git a/monasca/api/server.py b/monasca/api/server.py index 313db0cec..3bc25d3d5 100644 --- a/monasca/api/server.py +++ b/monasca/api/server.py @@ -14,14 +14,16 @@ # License for the specific language governing permissions and limitations # under the License. -from stevedore import driver import os -from monasca.common import resource_api -from monasca.openstack.common import log +from wsgiref import simple_server + from oslo.config import cfg from oslo.config import types -from paste.deploy import loadapp -from wsgiref import simple_server +import paste.deploy + +from monasca.common import resource_api +from monasca.openstack.common import log + METRICS_DISPATCHER_NAMESPACE = 'monasca.metrics_dispatcher' ALARM_DEFINITIONS_DISPATCHER_NAMESPACE = 'monasca.alarm_definitions_dispatcher' @@ -183,6 +185,8 @@ def api_app(conf): if __name__ == '__main__': - wsgi_app = loadapp('config:etc/monasca.ini', relative_to=os.getcwd()) + wsgi_app = ( + paste.deploy.loadapp('config:etc/monasca.ini', + relative_to=os.getcwd())) httpd = simple_server.make_server('127.0.0.1', 9000, wsgi_app) httpd.serve_forever() diff --git a/monasca/common/kafka_conn.py b/monasca/common/kafka_conn.py index f758125ef..9ccc4f971 100644 --- a/monasca/common/kafka_conn.py +++ b/monasca/common/kafka_conn.py @@ -11,13 +11,13 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import time from kafka import client from kafka import common from kafka import consumer from kafka import producer from oslo.config import cfg -import time try: import ujson as json diff --git a/monasca/common/messaging/exceptions.py b/monasca/common/messaging/exceptions.py index a30b61241..0780427cf 100644 --- a/monasca/common/messaging/exceptions.py +++ b/monasca/common/messaging/exceptions.py @@ -12,5 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. + class MessageQueueException(Exception): pass \ No newline at end of file diff --git a/monasca/common/messaging/kafka_publisher.py b/monasca/common/messaging/kafka_publisher.py index 3515586a1..0e638ef3e 100644 --- a/monasca/common/messaging/kafka_publisher.py +++ b/monasca/common/messaging/kafka_publisher.py @@ -11,16 +11,16 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import time from kafka import client from kafka import common from kafka import producer from oslo.config import cfg -import time -from monasca.openstack.common import log -from monasca.common.messaging import publisher from monasca.common.messaging import exceptions +from monasca.common.messaging import publisher +from monasca.openstack.common import log LOG = log.getLogger(__name__) diff --git a/monasca/common/messaging/message_formats/cadf/events.py b/monasca/common/messaging/message_formats/cadf/events.py index 0d6465627..7ef7da149 100644 --- a/monasca/common/messaging/message_formats/cadf/events.py +++ b/monasca/common/messaging/message_formats/cadf/events.py @@ -12,5 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. + def transform(events, tenant_id, region): raise NotImplemented() \ No newline at end of file diff --git a/monasca/common/messaging/message_formats/cadf/metrics.py b/monasca/common/messaging/message_formats/cadf/metrics.py index 8a2cf95f6..47aa9b7e2 100644 --- a/monasca/common/messaging/message_formats/cadf/metrics.py +++ b/monasca/common/messaging/message_formats/cadf/metrics.py @@ -12,5 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. + def transform(metrics, tenant_id, region): raise NotImplemented() \ No newline at end of file diff --git a/monasca/common/messaging/message_formats/events_transform_factory.py b/monasca/common/messaging/message_formats/events_transform_factory.py index ac0ca345f..738753608 100644 --- a/monasca/common/messaging/message_formats/events_transform_factory.py +++ b/monasca/common/messaging/message_formats/events_transform_factory.py @@ -13,15 +13,17 @@ # under the License. from oslo.config import cfg -import monasca.common.messaging.message_formats.reference.events as reference_events + import monasca.common.messaging.message_formats.cadf.events as cadf_events -import monasca.common.messaging.message_formats.identity.events as identity_events +import monasca.common.messaging.message_formats.identity.events as ident_events +import monasca.common.messaging.message_formats.reference.events as ref_events + def create_events_transform(): message_format = cfg.CONF.messaging.events_message_format if message_format == 'reference': - return reference_events.transform + return ref_events.transform elif message_format == 'cadf': return cadf_events.transform else: - return identity_events.transform \ No newline at end of file + return ident_events.transform \ No newline at end of file diff --git a/monasca/common/messaging/message_formats/exceptions.py b/monasca/common/messaging/message_formats/exceptions.py index 96d3835dc..4c0cb71ab 100644 --- a/monasca/common/messaging/message_formats/exceptions.py +++ b/monasca/common/messaging/message_formats/exceptions.py @@ -12,5 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. + class TransformationException(Exception): pass \ No newline at end of file diff --git a/monasca/common/messaging/message_formats/identity/events.py b/monasca/common/messaging/message_formats/identity/events.py index d1cb3031a..445709709 100644 --- a/monasca/common/messaging/message_formats/identity/events.py +++ b/monasca/common/messaging/message_formats/identity/events.py @@ -12,7 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -from datetime import datetime def transform(events, tenant_id, region): return events \ No newline at end of file diff --git a/monasca/common/messaging/message_formats/identity/metrics.py b/monasca/common/messaging/message_formats/identity/metrics.py index 1939e0fcc..3d69b818b 100644 --- a/monasca/common/messaging/message_formats/identity/metrics.py +++ b/monasca/common/messaging/message_formats/identity/metrics.py @@ -12,7 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -from datetime import datetime def transform(metrics, tenant_id, region): return metrics \ No newline at end of file diff --git a/monasca/common/messaging/message_formats/metrics_transform_factory.py b/monasca/common/messaging/message_formats/metrics_transform_factory.py index b02685f44..5129eb60a 100644 --- a/monasca/common/messaging/message_formats/metrics_transform_factory.py +++ b/monasca/common/messaging/message_formats/metrics_transform_factory.py @@ -13,15 +13,17 @@ # under the License. from oslo.config import cfg -import monasca.common.messaging.message_formats.reference.metrics as reference_metrics + import monasca.common.messaging.message_formats.cadf.metrics as cadf_metrics -import monasca.common.messaging.message_formats.identity.metrics as identity_metrics +import monasca.common.messaging.message_formats.identity.metrics as id_metrics +import monasca.common.messaging.message_formats.reference.metrics as r_metrics + def create_metrics_transform(): metrics_message_format = cfg.CONF.messaging.metrics_message_format if metrics_message_format == 'reference': - return reference_metrics.transform + return r_metrics.transform elif metrics_message_format == 'cadf': return cadf_metrics.transform else: - return identity_metrics.transform \ No newline at end of file + return id_metrics.transform \ No newline at end of file diff --git a/monasca/common/messaging/message_formats/reference/events.py b/monasca/common/messaging/message_formats/reference/events.py index d1a13db33..3d9eeea15 100644 --- a/monasca/common/messaging/message_formats/reference/events.py +++ b/monasca/common/messaging/message_formats/reference/events.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from datetime import datetime +import datetime def transform(event, tenant_id, region): diff --git a/monasca/common/messaging/message_formats/reference/metrics.py b/monasca/common/messaging/message_formats/reference/metrics.py index f90b54450..7058fe898 100644 --- a/monasca/common/messaging/message_formats/reference/metrics.py +++ b/monasca/common/messaging/message_formats/reference/metrics.py @@ -12,17 +12,13 @@ # License for the specific language governing permissions and limitations # under the License. -from datetime import datetime +import datetime + def transform(metrics, tenant_id, region): - transformed_metric = { - 'metric': {}, - 'meta': { - 'tenantId': tenant_id, - 'region': region - }, - 'creation_time': datetime.now() - } + transformed_metric = {'metric': {}, + 'meta': {'tenantId': tenant_id, 'region': region}, + 'creation_time': datetime.now()} if isinstance(metrics, list): transformed_metrics = [] diff --git a/monasca/common/messaging/publisher.py b/monasca/common/messaging/publisher.py index 324d07506..55dec9745 100644 --- a/monasca/common/messaging/publisher.py +++ b/monasca/common/messaging/publisher.py @@ -13,16 +13,16 @@ # under the License. import abc + import six @six.add_metaclass(abc.ABCMeta) class Publisher(object): - @abc.abstractmethod def send_message(self, message): - ''' - Sends the message using the message queue. + """Sends the message using the message queue. + :param message: Message to send. - ''' + """ return \ No newline at end of file diff --git a/monasca/common/repositories/alarm_definitions_repository.py b/monasca/common/repositories/alarm_definitions_repository.py index edf00082a..546c9751a 100644 --- a/monasca/common/repositories/alarm_definitions_repository.py +++ b/monasca/common/repositories/alarm_definitions_repository.py @@ -12,20 +12,20 @@ # License for the specific language governing permissions and limitations # under the License. import abc + import six @six.add_metaclass(abc.ABCMeta) class AlarmDefinitionsRepository(object): - def __init__(self): - - super(AlarmDefinitionsRepository, self).__init__() + super(AlarmDefinitionsRepository, self).__init__() @abc.abstractmethod - def create_alarm_definition(self, tenant_id, name, - expression, sub_expr_list, description, severity, match_by, alarm_actions, - undetermined_actions, ok_action): + def create_alarm_definition(self, tenant_id, name, expression, + sub_expr_list, description, severity, match_by, + alarm_actions, undetermined_actions, + ok_action): pass @abc.abstractmethod @@ -50,4 +50,4 @@ class AlarmDefinitionsRepository(object): @abc.abstractmethod def get_alarm_definitions(self, tenant_id, name, dimensions): - pass \ No newline at end of file + pass diff --git a/monasca/common/repositories/alarms_repository.py b/monasca/common/repositories/alarms_repository.py index 116186169..69fcb814e 100644 --- a/monasca/common/repositories/alarms_repository.py +++ b/monasca/common/repositories/alarms_repository.py @@ -12,9 +12,33 @@ # License for the specific language governing permissions and limitations # under the License. import abc + import six @six.add_metaclass(abc.ABCMeta) class AlarmsRepository(object): - pass + + def __init__(self): + + super(AlarmsRepository, self).__init__() + + @abc.abstractmethod + def get_alarm_metrics(self, alarm_id): + pass + + @abc.abstractmethod + def get_sub_alarms(self, tenant_id, alarm_id): + pass + + @abc.abstractmethod + def delete_alarm(self, tenant_id, id): + pass + + @abc.abstractmethod + def get_alarm(self, tenant_id, id): + pass + + @abc.abstractmethod + def get_alarms(self, tenant_id, query_parms): + pass diff --git a/monasca/common/repositories/events_repository.py b/monasca/common/repositories/events_repository.py index 7880c4f94..f2a521f0c 100644 --- a/monasca/common/repositories/events_repository.py +++ b/monasca/common/repositories/events_repository.py @@ -13,6 +13,7 @@ # under the License. import abc + import six diff --git a/monasca/common/repositories/exceptions.py b/monasca/common/repositories/exceptions.py index ccd1aba6a..30f42c552 100644 --- a/monasca/common/repositories/exceptions.py +++ b/monasca/common/repositories/exceptions.py @@ -12,8 +12,10 @@ # License for the specific language governing permissions and limitations # under the License. + class RepositoryException(Exception): pass + class DoesNotExistException(RepositoryException): - pass \ No newline at end of file + pass diff --git a/monasca/common/repositories/influxdb/metrics_repository.py b/monasca/common/repositories/influxdb/metrics_repository.py index 5e413d8b3..0308b4bbc 100644 --- a/monasca/common/repositories/influxdb/metrics_repository.py +++ b/monasca/common/repositories/influxdb/metrics_repository.py @@ -12,13 +12,12 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import json import re +import time import urllib -from time import strftime -from time import gmtime -from influxdb import InfluxDBClient -from influxdb.client import InfluxDBClientError +from influxdb import client from oslo.config import cfg from monasca.common.repositories import exceptions @@ -30,11 +29,12 @@ LOG = log.getLogger(__name__) class MetricsRepository(metrics_repository.MetricsRepository): + def __init__(self): try: self.conf = cfg.CONF - self.influxdb_client = InfluxDBClient( + self.influxdb_client = client.InfluxDBClient( self.conf.influxdb.ip_address, self.conf.influxdb.port, self.conf.influxdb.user, self.conf.influxdb.password, self.conf.influxdb.database_name) @@ -144,8 +144,8 @@ class MetricsRepository(metrics_repository.MetricsRepository): def _decode_influxdb_serie_name_list(self, series_names): - """ - Example series_names from InfluxDB. + """Example series_names from InfluxDB. + [ { "points": [ @@ -178,11 +178,11 @@ class MetricsRepository(metrics_repository.MetricsRepository): return json_metric_list - def _decode_influxdb_serie_name(self, serie_name): - """ - Decodes a serie name from InfluxDB. The raw serie name is + """Decodes a serie name from InfluxDB. + + The raw serie name is formed by url encoding the name, tenant id, region, and dimensions, and concatenating them into a quasi URL query string. @@ -227,11 +227,10 @@ class MetricsRepository(metrics_repository.MetricsRepository): return metric - def measurement_list(self, tenant_id, name, dimensions, start_timestamp, end_timestamp): - """ - Example result from InfluxDB. + """Example result from InfluxDB. + [ { "points": [ @@ -285,9 +284,10 @@ class MetricsRepository(metrics_repository.MetricsRepository): try: result = self.influxdb_client.query(query, 's') - except InfluxDBClientError as ex: - if ex.code == 400 and ex.content == 'Couldn\'t look up ' \ - 'columns': + except client.InfluxDBClientError as ex: + # check for non-existent serie name. + msg = "Couldn't look up columns" + if ex.code == 400 and ex.content == (msg): return json_measurement_list else: raise ex @@ -307,8 +307,9 @@ class MetricsRepository(metrics_repository.MetricsRepository): columns] # format the utc date in the points - fmtd_pts = [[strftime("%Y-%m-%dT%H:%M:%SZ", gmtime(point[0])), - point[1], point[2]] for point in serie['points']] + fmtd_pts = [[time.strftime("%Y-%m-%dT%H:%M:%SZ", + time.gmtime(point[0])), point[1], + point[2]] for point in serie['points']] measurement = {"name": metric['name'], "dimensions": metric['dimensions'], @@ -322,7 +323,6 @@ class MetricsRepository(metrics_repository.MetricsRepository): LOG.exception(ex) raise exceptions.RepositoryException(ex) - def metrics_statistics(self, tenant_id, name, dimensions, start_timestamp, end_timestamp, statistics, period): @@ -336,9 +336,10 @@ class MetricsRepository(metrics_repository.MetricsRepository): try: result = self.influxdb_client.query(query, 's') - except InfluxDBClientError as ex: - if ex.code == 400 and ex.content == 'Couldn\'t look up ' \ - 'columns': + except client.InfluxDBClientError as ex: + # check for non-existent serie name. + msg = "Couldn't look up columns" + if ex.code == 400 and ex.content == (msg): return json_statistics_list else: raise ex @@ -357,9 +358,10 @@ class MetricsRepository(metrics_repository.MetricsRepository): columns = [column.replace('time', 'timestamp') for column in columns] - fmtd_pts_list_list = [[strftime("%Y-%m-%dT%H:%M:%SZ", - gmtime(pts_list[0]))] + pts_list[1:] - for pts_list in serie['points']] + fmtd_pts_list_list = [[time.strftime("%Y-%m-%dT%H:%M:%SZ", + time.gmtime(pts_list[ + 0]))] + pts_list[1:] + for pts_list in serie['points']] measurement = {"name": metric['name'], "dimensions": metric['dimensions'], @@ -370,6 +372,121 @@ class MetricsRepository(metrics_repository.MetricsRepository): return json_statistics_list + except Exception as ex: + LOG.exception(ex) + raise exceptions.RepositoryException(ex) + + def alarm_history(self, tenant_id, alarm_id_list, start_timestamp=None, + end_timestamp=None): + """Example result from Influxdb. + + [ + { + "points": [ + [ + 1415894490, + 272140001, + "6ac10841-d02f-4f7d-a191-ae0a3d9a25f2", + "[{\"name\": \"cpu.system_perc\", \"dimensions\": { + \"hostname\": \"mini-mon\", \"component\": + \"monasca-agent\", \"service\": \"monitoring\"}}, + {\"name\": \"load.avg_1_min\", \"dimensions\": { + \"hostname\": \"mini-mon\", \"component\": + \"monasca-agent\", \"service\": \"monitoring\"}}]", + "ALARM", + "OK", + "Thresholds were exceeded for the sub-alarms: [max( + load.avg_1_min{hostname=mini-mon}) > 0.0, + max(cpu.system_perc) > 0.0]", + "{}" + ], + ], + "name": "alarm_state_history", + "columns": [ + "time", + "sequence_number", + "alarm_id", + "metrics", + "new_state", + "old_state", + "reason", + "reason_data" + ] + } + ] + + :param tenant_id: + :param alarm_id: + :return: + """ + + try: + + json_alarm_history_list = [] + + if not alarm_id_list: + return json_alarm_history_list + + for alarm_id in alarm_id_list: + if '\'' in alarm_id or ';' in alarm_id: + raise Exception( + "Input from user contains single quote ['] or " + "semi-colon [;] characters[ {} ]".format(alarm_id)) + + query = """ + select alarm_id, metrics, old_state, new_state, + reason, reason_data + from alarm_state_history + """ + + where_clause = ( + " where tenant_id = '{}' ".format(tenant_id.encode('utf8'))) + + alarm_id_where_clause_list = ( + [" alarm_id = '{}' ".format(id.encode('utf8')) + for id in alarm_id_list]) + + alarm_id_where_clause = " or ".join(alarm_id_where_clause_list) + + where_clause += ' and (' + alarm_id_where_clause + ')' + + time_clause = '' + if start_timestamp: + # subtract 1 from timestamp to get >= semantics + time_clause += " and time > " + str(start_timestamp - 1) + "s" + if end_timestamp: + # add 1 to timestamp to get <= semantics + time_clause += " and time < " + str(end_timestamp + 1) + "s" + + query += where_clause + time_clause + try: + result = self.influxdb_client.query(query, 's') + except client.InfluxDBClientError as ex: + # check for non-existent serie name. only happens + # if alarm_state_history serie does not exist. + msg = "Couldn't look up columns" + if ex.code == 400 and ex.content == (msg): + return json_alarm_history_list + else: + raise ex + + if not result: + return json_alarm_history_list + + # There's only one serie, alarm_state_history. + for point in result[0]['points']: + alarm_point = {u'alarm_id': point[2], + u'metrics': json.loads(point[3]), + u'old_state': point[4], u'new_state': point[5], + u'reason': point[6], u'reason_data': point[7], + u'timestamp': time.strftime( + "%Y-%m-%dT%H:%M:%SZ", + time.gmtime(point[0]))} + + json_alarm_history_list.append(alarm_point) + + return json_alarm_history_list + except Exception as ex: LOG.exception(ex) raise exceptions.RepositoryException(ex) \ No newline at end of file diff --git a/monasca/common/repositories/metrics_repository.py b/monasca/common/repositories/metrics_repository.py index 8c3766e4d..90438706d 100644 --- a/monasca/common/repositories/metrics_repository.py +++ b/monasca/common/repositories/metrics_repository.py @@ -30,5 +30,5 @@ class MetricsRepository(object): @abc.abstractmethod def metrics_statistics(self, tenant_id, name, dimensions, start_timestamp, - end_timestamp, statistics): + end_timestamp, statistics, period): pass \ No newline at end of file diff --git a/monasca/common/repositories/mysql/alarm_definitions_repository.py b/monasca/common/repositories/mysql/alarm_definitions_repository.py index 51f571894..3aefda8ce 100644 --- a/monasca/common/repositories/mysql/alarm_definitions_repository.py +++ b/monasca/common/repositories/mysql/alarm_definitions_repository.py @@ -13,20 +13,19 @@ # under the License. import datetime -from monasca.common.repositories import alarm_definitions_repository -from monasca.common.repositories.exceptions import DoesNotExistException -from monasca.common.repositories.mysql.mysql_repository import MySQLRepository -from monasca.common.repositories.mysql.mysql_repository import mysql_try_catch_block +from monasca.common.repositories import alarm_definitions_repository as adr +from monasca.common.repositories import exceptions +from monasca.common.repositories.mysql import mysql_repository from monasca.openstack.common import log from monasca.openstack.common import uuidutils -from monasca.common.repositories import exceptions LOG = log.getLogger(__name__) -class AlarmDefinitionsRepository(MySQLRepository, - alarm_definitions_repository.AlarmDefinitionsRepository): +class AlarmDefinitionsRepository(mysql_repository.MySQLRepository, + adr.AlarmDefinitionsRepository): + base_query = """ select ad.id, ad.name, ad.description, ad.expression, ad.match_by, ad.severity, ad.actions_enabled, @@ -56,7 +55,7 @@ class AlarmDefinitionsRepository(MySQLRepository, super(AlarmDefinitionsRepository, self).__init__() - @mysql_try_catch_block + @mysql_repository.mysql_try_catch_block def get_alarm_definition(self, tenant_id, id): parms = [tenant_id, id] @@ -72,9 +71,9 @@ class AlarmDefinitionsRepository(MySQLRepository, if rows: return rows[0] else: - raise DoesNotExistException + raise exceptions.DoesNotExistException - @mysql_try_catch_block + @mysql_repository.mysql_try_catch_block def get_alarm_definitions(self, tenant_id, name, dimensions): parms = [tenant_id] @@ -113,8 +112,7 @@ class AlarmDefinitionsRepository(MySQLRepository, return self._execute_query(query, parms) - - @mysql_try_catch_block + @mysql_repository.mysql_try_catch_block def get_sub_alarms(self, tenant_id, alarm_definition_id): parms = [tenant_id, alarm_definition_id] @@ -131,10 +129,10 @@ class AlarmDefinitionsRepository(MySQLRepository, return self._execute_query(query, parms) - @mysql_try_catch_block + @mysql_repository.mysql_try_catch_block def get_alarm_metrics(self, tenant_id, alarm_definition_id): - parms = [tenant_id, alarm_definition_id] + parms = [tenant_id, alarm_definition_id] query = """select distinct a.id as alarm_id, md.name, mdg.dimensions @@ -156,7 +154,7 @@ class AlarmDefinitionsRepository(MySQLRepository, return self._execute_query(query, parms) - @mysql_try_catch_block + @mysql_repository.mysql_try_catch_block def delete_alarm_definition(self, tenant_id, alarm_definition_id): """Soft delete the alarm definition. @@ -187,7 +185,7 @@ class AlarmDefinitionsRepository(MySQLRepository, return True - @mysql_try_catch_block + @mysql_repository.mysql_try_catch_block def get_sub_alarm_definitions(self, alarm_definition_id): parms = [alarm_definition_id] @@ -206,7 +204,7 @@ class AlarmDefinitionsRepository(MySQLRepository, return self._execute_query(query, parms) - @mysql_try_catch_block + @mysql_repository.mysql_try_catch_block def create_alarm_definition(self, tenant_id, name, expression, sub_expr_list, description, severity, match_by, alarm_actions, undetermined_actions, @@ -275,7 +273,6 @@ class AlarmDefinitionsRepository(MySQLRepository, return alarm_definition_id - def _insert_into_alarm_action(self, cursor, alarm_definition_id, actions, alarm_state): for action in actions: @@ -293,5 +290,3 @@ class AlarmDefinitionsRepository(MySQLRepository, action_id) values(?,?,?)""", alarm_definition_id, alarm_state.encode('utf8'), action.encode('utf8')) - - diff --git a/monasca/common/repositories/mysql/alarms_repository.py b/monasca/common/repositories/mysql/alarms_repository.py index 364e74222..db9b52e75 100644 --- a/monasca/common/repositories/mysql/alarms_repository.py +++ b/monasca/common/repositories/mysql/alarms_repository.py @@ -11,18 +11,18 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from monasca.common.repositories.exceptions import DoesNotExistException from monasca.common.repositories import alarms_repository -from monasca.common.repositories.mysql.mysql_repository import MySQLRepository -from monasca.common.repositories.mysql.mysql_repository import mysql_try_catch_block +from monasca.common.repositories import exceptions +from monasca.common.repositories.mysql import mysql_repository from monasca.openstack.common import log LOG = log.getLogger(__name__) -class AlarmsRepository(MySQLRepository, alarms_repository.AlarmsRepository): +class AlarmsRepository(mysql_repository.MySQLRepository, + alarms_repository.AlarmsRepository): base_query = """ select distinct a.id as alarm_id, a.state, @@ -47,10 +47,10 @@ class AlarmsRepository(MySQLRepository, alarms_repository.AlarmsRepository): super(AlarmsRepository, self).__init__() - @mysql_try_catch_block + @mysql_repository.mysql_try_catch_block def get_alarm_metrics(self, alarm_id): - parms = [alarm_id] + parms = [alarm_id] query = """select distinct a.id as alarm_id, md.name, mdg.dimensions @@ -70,7 +70,7 @@ class AlarmsRepository(MySQLRepository, alarms_repository.AlarmsRepository): return self._execute_query(query, parms) - @mysql_try_catch_block + @mysql_repository.mysql_try_catch_block def get_sub_alarms(self, tenant_id, alarm_id): parms = [tenant_id, alarm_id] @@ -87,20 +87,20 @@ class AlarmsRepository(MySQLRepository, alarms_repository.AlarmsRepository): return self._execute_query(query, parms) - @mysql_try_catch_block + @mysql_repository.mysql_try_catch_block def delete_alarm(self, tenant_id, id): parms = [tenant_id, id] query = """ - delete alarm.* - from alarm - join - (select distinct a.id - from alarm as a - inner join alarm_definition as ad + delete alarm.* + from alarm + join + (select distinct a.id + from alarm as a + inner join alarm_definition as ad on ad.id = a.alarm_definition_id - where ad.tenant_id = ? and a.id = ?) as b + where ad.tenant_id = ? and a.id = ?) as b on b.id = alarm.id """ @@ -109,11 +109,11 @@ class AlarmsRepository(MySQLRepository, alarms_repository.AlarmsRepository): cursor.execute(query, parms) if cursor.rowcount < 1: - raise DoesNotExistException + raise exceptions.DoesNotExistException self._commit_close_cnxn(cnxn) - @mysql_try_catch_block + @mysql_repository.mysql_try_catch_block def get_alarm(self, tenant_id, id): parms = [tenant_id, id] @@ -128,11 +128,11 @@ class AlarmsRepository(MySQLRepository, alarms_repository.AlarmsRepository): rows = self._execute_query(query, parms) if not rows: - raise DoesNotExistException + raise exceptions.DoesNotExistException else: return rows - @mysql_try_catch_block + @mysql_repository.mysql_try_catch_block def get_alarms(self, tenant_id, query_parms): parms = [tenant_id] diff --git a/monasca/common/repositories/mysql/model.py b/monasca/common/repositories/mysql/model.py index 22a15ce50..b6eb61c5c 100644 --- a/monasca/common/repositories/mysql/model.py +++ b/monasca/common/repositories/mysql/model.py @@ -12,10 +12,12 @@ # License for the specific language governing permissions and limitations # under the License. -from monasca.openstack.common import log from oslo.config import cfg import peewee +from monasca.openstack.common import log + + LOG = log.getLogger(__name__) db = peewee.MySQLDatabase(cfg.CONF.mysql.database_name, diff --git a/monasca/common/repositories/mysql/mysql_repository.py b/monasca/common/repositories/mysql/mysql_repository.py index 29286d7c5..f790c1488 100644 --- a/monasca/common/repositories/mysql/mysql_repository.py +++ b/monasca/common/repositories/mysql/mysql_repository.py @@ -11,27 +11,22 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -import datetime -import pyodbc -from monasca.common.repositories.exceptions import DoesNotExistException from oslo.config import cfg +import pyodbc -from monasca.common.repositories import alarms_repository -from monasca.openstack.common import log -from monasca.openstack.common import uuidutils from monasca.common.repositories import exceptions +from monasca.openstack.common import log LOG = log.getLogger(__name__) class MySQLRepository(object): - database_driver = 'MySQL ODBC 5.3 ANSI Driver' - database_cnxn_template = 'DRIVER={' \ - '%s};Server=%s;CHARSET=UTF8;Database=%s;Uid=%s' \ - ';Pwd=%s' + database_cnxn_template = ('DRIVER={' + '%s};Server=%s;CHARSET=UTF8;Database=%s;Uid=%s' + ';Pwd=%s') def __init__(self): @@ -65,7 +60,6 @@ class MySQLRepository(object): cnxn.commit() cnxn.close() - def _execute_query(self, query, parms): cnxn, cursor = self._get_cnxn_cursor_tuple() @@ -87,10 +81,10 @@ def mysql_try_catch_block(fun): return fun(*args, **kwargs) - except DoesNotExistException: + except exceptions.DoesNotExistException: raise except Exception as ex: LOG.exception(ex) raise exceptions.RepositoryException(ex) - return try_it \ No newline at end of file + return try_it diff --git a/monasca/common/repositories/mysql/notifications_repository.py b/monasca/common/repositories/mysql/notifications_repository.py index 533bdb4ab..e83615002 100644 --- a/monasca/common/repositories/mysql/notifications_repository.py +++ b/monasca/common/repositories/mysql/notifications_repository.py @@ -13,11 +13,14 @@ # under the License. import datetime -from monasca.common.repositories import notifications_repository -from monasca.common.repositories import exceptions -from monasca.openstack.common import log -import peewee + import model +import peewee + +from monasca.common.repositories import exceptions +from monasca.common.repositories import notifications_repository +from monasca.openstack.common import log + LOG = log.getLogger(__name__) @@ -55,7 +58,7 @@ class NotificationsRepository( self, id, tenant_id, name, notification_type, address): try: now = datetime.datetime.utcnow() - q = Notification_Method.create( + Notification_Method.create( id=id, tenant_id=tenant_id, name=name, @@ -73,7 +76,6 @@ class NotificationsRepository( Notification_Method.tenant_id == tenant_id) results = q.execute() - notifications = [] notifications = [ self.notification_from_result(result) for result in results] return notifications @@ -82,7 +84,6 @@ class NotificationsRepository( raise exceptions.RepositoryException(ex) def delete_notification(self, tenant_id, notification_id): - num_rows_deleted = 0 try: q = Notification_Method.delete().where( @@ -113,7 +114,6 @@ class NotificationsRepository( def update_notification( self, id, tenant_id, name, notification_type, address): now = datetime.datetime.utcnow() - num_rows_updated = 0 try: q = Notification_Method.update( name=name, diff --git a/monasca/common/repositories/mysql/transforms_repository.py b/monasca/common/repositories/mysql/transforms_repository.py index 40bbbe74f..55ab79a01 100644 --- a/monasca/common/repositories/mysql/transforms_repository.py +++ b/monasca/common/repositories/mysql/transforms_repository.py @@ -4,7 +4,7 @@ # not use this file except in compliance with the License. You may obtain # a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -12,11 +12,13 @@ # License for the specific language governing permissions and limitations # under the License. -from monasca.common.repositories import transforms_repository -from monasca.common.repositories import exceptions -from monasca.openstack.common import log -import peewee import model +import peewee + +from monasca.common.repositories import exceptions +from monasca.common.repositories import transforms_repository +from monasca.openstack.common import log + LOG = log.getLogger(__name__) @@ -32,11 +34,14 @@ class Transform(model.Model): updated_at = peewee.DateTimeField() deleted_at = peewee.DateTimeField() -class TransformsRepository(transforms_repository.TransformsRepository): - def create_transforms(self, id, tenant_id, name, description, specification, enabled): +class TransformsRepository(transforms_repository.TransformsRepository): + def create_transforms(self, id, tenant_id, name, description, + specification, enabled): try: - q = Transform.create(id=id, tenant_id=tenant_id, name=name, description=description, specification=specification, enabled=enabled) + q = Transform.create(id=id, tenant_id=tenant_id, name=name, + description=description, + specification=specification, enabled=enabled) q.save() except Exception as ex: LOG.exception(str(ex)) @@ -49,13 +54,10 @@ class TransformsRepository(transforms_repository.TransformsRepository): transforms = [] for result in results: - transform = { - 'id': result.id, - 'name': result.name, - 'description': result.description, - 'specification': result.specification, - 'enabled': result.enabled - } + transform = {'id': result.id, 'name': result.name, + 'description': result.description, + 'specification': result.specification, + 'enabled': result.enabled} transforms.append(transform) return transforms except Exception as ex: @@ -66,7 +68,8 @@ class TransformsRepository(transforms_repository.TransformsRepository): num_rows_deleted = 0 try: - q = Transform.delete().where((Transform.tenant_id == tenant_id) & (Transform.id == transform_id)) + q = Transform.delete().where((Transform.tenant_id == tenant_id) & ( + Transform.id == transform_id)) num_rows_deleted = q.execute() except Exception as ex: LOG.exception(str(ex)) @@ -75,4 +78,4 @@ class TransformsRepository(transforms_repository.TransformsRepository): if num_rows_deleted < 1: raise exceptions.DoesNotExistException() - return \ No newline at end of file + return diff --git a/monasca/common/repositories/notifications_repository.py b/monasca/common/repositories/notifications_repository.py index e97bc0712..acc923a55 100644 --- a/monasca/common/repositories/notifications_repository.py +++ b/monasca/common/repositories/notifications_repository.py @@ -13,6 +13,7 @@ # under the License. import abc + import six diff --git a/monasca/common/repositories/transforms_repository.py b/monasca/common/repositories/transforms_repository.py index e92135608..c9d42f3dc 100644 --- a/monasca/common/repositories/transforms_repository.py +++ b/monasca/common/repositories/transforms_repository.py @@ -4,7 +4,7 @@ # not use this file except in compliance with the License. You may obtain # a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -13,14 +13,15 @@ # under the License. import abc + import six @six.add_metaclass(abc.ABCMeta) class TransformsRepository(object): - @abc.abstractmethod - def create_transforms(self, id, tenant_id, name, description, specification, enabled): + def create_transforms(self, id, tenant_id, name, description, + specification, enabled): return @abc.abstractmethod @@ -29,4 +30,4 @@ class TransformsRepository(object): @abc.abstractmethod def delete_transform(self, tenant_id, transform_id): - return \ No newline at end of file + return diff --git a/monasca/common/resource_api.py b/monasca/common/resource_api.py index a49c30b6e..b04f668b5 100644 --- a/monasca/common/resource_api.py +++ b/monasca/common/resource_api.py @@ -16,9 +16,9 @@ import falcon from falcon import api_helpers +from stevedore import driver from monasca.openstack.common import log -from stevedore import driver RESOURCE_METHOD_FLAG = 'fab05a04-b861-4651-bd0c-9cb3eb9a6088' @@ -27,21 +27,19 @@ LOG = log.getLogger(__name__) def init_driver(namespace, driver_name, drv_invoke_args=()): """Initialize the resource driver and returns it. - + :param namespace: the resource namespace (in setup.cfg). :param driver_name: the driver name (in monasca.conf) :param invoke_args: args to pass to the driver (a tuple) """ - mgr = driver.DriverManager( - namespace = namespace, - name = driver_name, - invoke_on_load = True, - invoke_args = drv_invoke_args - ) + mgr = driver.DriverManager(namespace=namespace, name=driver_name, + invoke_on_load=True, + invoke_args=drv_invoke_args) return mgr.driver class Restify(object): + def __init__(self, path='', method='GET'): if not path: raise Exception('Path has to be specified.') @@ -130,11 +128,11 @@ class ResourceAPI(falcon.API): except Exception: LOG.exception('Error occurred while adding the resource') LOG.debug(self._routes) - - def add_resource(self, resource_name, namespace, driver_name, + + def add_resource(self, resource_name, namespace, driver_name, invoke_args=(), uri=None): """Loads the resource driver, and adds it to the routes. - + :param resource_name: the name of the resource. :param namespace: the resource namespace (in setup.cfg). :param driver_name: the driver name (in monasca.conf) @@ -142,8 +140,8 @@ class ResourceAPI(falcon.API): :param uri: the uri to associate with the resource """ resource_driver = init_driver(namespace, driver_name, invoke_args) - LOG.debug('%s dispatcher driver %s is loaded.' % + LOG.debug('%s dispatcher driver %s is loaded.' % (resource_name, driver_name)) self.add_route(uri, resource_driver) - LOG.debug('%s dispatcher driver has been added to the routes!' % + LOG.debug('%s dispatcher driver has been added to the routes!' % (resource_name)) \ No newline at end of file diff --git a/monasca/dispatcher/__init__.py b/monasca/dispatcher/__init__.py index 08f995ab7..10610ba93 100644 --- a/monasca/dispatcher/__init__.py +++ b/monasca/dispatcher/__init__.py @@ -15,6 +15,7 @@ # under the License. import abc + import six diff --git a/monasca/expression_parser/alarm_expr_parser.py b/monasca/expression_parser/alarm_expr_parser.py index 3605a4380..a31143384 100644 --- a/monasca/expression_parser/alarm_expr_parser.py +++ b/monasca/expression_parser/alarm_expr_parser.py @@ -16,22 +16,13 @@ import itertools import sys -from pyparsing import CaselessLiteral -from pyparsing import alphanums -from pyparsing import delimitedList -from pyparsing import Forward -from pyparsing import Group -from pyparsing import Literal -from pyparsing import nums -from pyparsing import opAssoc -from pyparsing import operatorPrecedence -from pyparsing import Optional -from pyparsing import stringEnd -from pyparsing import Word +import pyparsing class SubExpr(object): + def __init__(self, tokens): + self._sub_expr = tokens self._func = tokens.func self._metric_name = tokens.metric_name @@ -173,67 +164,70 @@ class BinaryOp(object): class AndSubExpr(BinaryOp): - """ Expand later as needed. - """ + """Expand later as needed.""" pass class OrSubExpr(BinaryOp): - """Expand later as needed. - """ + """Expand later as needed.""" pass -COMMA = Literal(",") -LPAREN = Literal("(") -RPAREN = Literal(")") -EQUAL = Literal("=") -LBRACE = Literal("{") -RBRACE = Literal("}") +COMMA = pyparsing.Literal(",") +LPAREN = pyparsing.Literal("(") +RPAREN = pyparsing.Literal(")") +EQUAL = pyparsing.Literal("=") +LBRACE = pyparsing.Literal("{") +RBRACE = pyparsing.Literal("}") # Initialize non-ascii unicode code points in the Basic Multilingual Plane. unicode_printables = u''.join( unichr(c) for c in xrange(128, 65536) if not unichr(c).isspace()) # Does not like comma. No Literals from above allowed. -valid_identifier_chars = (unicode_printables + alphanums + ".-_#!$%&'*+/:;?@[" - "\\]^`|~") +valid_identifier_chars = ( + (unicode_printables + pyparsing.alphanums + ".-_#!$%&'*+/:;?@[\\]^`|~")) -metric_name = Word(valid_identifier_chars, min=1, max=255)("metric_name") -dimension_name = Word(valid_identifier_chars, min=1, max=255) -dimension_value = Word(valid_identifier_chars, min=1, max=255) +metric_name = ( + pyparsing.Word(valid_identifier_chars, min=1, max=255)("metric_name")) +dimension_name = pyparsing.Word(valid_identifier_chars, min=1, max=255) +dimension_value = pyparsing.Word(valid_identifier_chars, min=1, max=255) -integer_number = Word(nums) -decimal_number = Word(nums + ".") +integer_number = pyparsing.Word(pyparsing.nums) +decimal_number = pyparsing.Word(pyparsing.nums + ".") -max = CaselessLiteral("max") -min = CaselessLiteral("min") -avg = CaselessLiteral("avg") -count = CaselessLiteral("count") -sum = CaselessLiteral("sum") +max = pyparsing.CaselessLiteral("max") +min = pyparsing.CaselessLiteral("min") +avg = pyparsing.CaselessLiteral("avg") +count = pyparsing.CaselessLiteral("count") +sum = pyparsing.CaselessLiteral("sum") func = (max | min | avg | count | sum)("func") -less_than_op = (CaselessLiteral("<") | CaselessLiteral("lt")) -less_than_eq_op = (CaselessLiteral("<=") | CaselessLiteral("lte")) -greater_than_op = (CaselessLiteral(">") | CaselessLiteral("gt")) -greater_than_eq_op = (CaselessLiteral(">=") | CaselessLiteral("gte")) +less_than_op = ( + (pyparsing.CaselessLiteral("<") | pyparsing.CaselessLiteral("lt"))) +less_than_eq_op = ( + (pyparsing.CaselessLiteral("<=") | pyparsing.CaselessLiteral("lte"))) +greater_than_op = ( + (pyparsing.CaselessLiteral(">") | pyparsing.CaselessLiteral("gt"))) +greater_than_eq_op = ( + (pyparsing.CaselessLiteral(">=") | pyparsing.CaselessLiteral("gte"))) # Order is important. Put longer prefix first. relational_op = ( less_than_eq_op | less_than_op | greater_than_eq_op | greater_than_op)( "relational_op") -AND = CaselessLiteral("and") | CaselessLiteral("&&") -OR = CaselessLiteral("or") | CaselessLiteral("||") +AND = pyparsing.CaselessLiteral("and") | pyparsing.CaselessLiteral("&&") +OR = pyparsing.CaselessLiteral("or") | pyparsing.CaselessLiteral("||") logical_op = (AND | OR)("logical_op") -times = CaselessLiteral("times") +times = pyparsing.CaselessLiteral("times") -dimension = Group(dimension_name + EQUAL + dimension_value) +dimension = pyparsing.Group(dimension_name + EQUAL + dimension_value) # Cannot have any whitespace after the comma delimiter. -dimension_list = Group(Optional( - LBRACE + delimitedList(dimension, delim=',', combine=True)( +dimension_list = pyparsing.Group(pyparsing.Optional( + LBRACE + pyparsing.delimitedList(dimension, delim=',', combine=True)( "dimensions_list") + RBRACE)) metric = metric_name + dimension_list("dimensions") @@ -241,17 +235,18 @@ period = integer_number("period") threshold = decimal_number("threshold") periods = integer_number("periods") -expression = Forward() +expression = pyparsing.Forward() -sub_expression = (func + LPAREN + metric + Optional( - COMMA + period) + RPAREN + relational_op + threshold + Optional( +sub_expression = (func + LPAREN + metric + pyparsing.Optional( + COMMA + period) + RPAREN + relational_op + threshold + pyparsing.Optional( times + periods) | LPAREN + expression + RPAREN) sub_expression.setParseAction(SubExpr) -expression = operatorPrecedence(sub_expression, - [(AND, 2, opAssoc.LEFT, AndSubExpr), - (OR, 2, opAssoc.LEFT, OrSubExpr)]) +expression = ( + pyparsing.operatorPrecedence(sub_expression, + [(AND, 2, pyparsing.opAssoc.LEFT, AndSubExpr), + (OR, 2, pyparsing.opAssoc.LEFT, OrSubExpr)])) class AlarmExprParser(object): @@ -262,37 +257,37 @@ class AlarmExprParser(object): def sub_expr_list(self): # Remove all spaces before parsing. Simple, quick fix for whitespace # issue with dimension list not allowing whitespace after comma. - parseResult = (expression + stringEnd).parseString( + parseResult = (expression + pyparsing.stringEnd).parseString( self._expr.replace(' ', '')) sub_expr_list = parseResult[0].operands_list return sub_expr_list def main(): - """ Used for development and testing. """ + """Used for development and testing.""" - expr0 = "max(-_.千幸福的笑脸{घोड़ा=馬, dn2=dv2}, 60) gte 100 times 3 && " \ - "(min(ເຮືອນ{dn3=dv3,家=дом}) < 10 or sum(biz{dn5=dv5}) > 99 and " \ - "count(fizzle) lt 0 or count(baz) > 1)".decode('utf8') + expr0 = ("max(-_.千幸福的笑脸{घोड़ा=馬, dn2=dv2}, 60) gte 100 times 3 && " + "(min(ເຮືອນ{dn3=dv3,家=дом}) < 10 or sum(biz{dn5=dv5}) >9 9and " + "count(fizzle) lt 0 or count(baz) > 1)".decode('utf8')) - expr1 = "max(foo{hostname=mini-mon,千=千}, 120) > 100 and (max(bar)>100 \ - or max(biz)>100)".decode('utf8') + expr1 = ("max(foo{hostname=mini-mon,千=千}, 120) > 100 and (max(bar)>100 " + " or max(biz)>100)".decode('utf8')) expr2 = "max(foo)>=100" for expr in (expr0, expr1, expr2): - print 'orig expr: {}'.format(expr.encode('utf8')) + print ('orig expr: {}'.format(expr.encode('utf8'))) alarmExprParser = AlarmExprParser(expr) sub_expr = alarmExprParser.sub_expr_list for sub_expression in sub_expr: - print 'sub expr: {}'.format( - sub_expression.sub_expr_str.encode('utf8')) - print 'fmtd sub expr: {}'.format( - sub_expression.fmtd_sub_expr_str.encode('utf8')) - print 'sub_expr dimensions: {}'.format( - sub_expression.dimensions_str.encode('utf8')) - print - print + print ('sub expr: {}'.format( + sub_expression.sub_expr_str.encode('utf8'))) + print ('fmtd sub expr: {}'.format( + sub_expression.fmtd_sub_expr_str.encode('utf8'))) + print ('sub_expr dimensions: {}'.format( + sub_expression.dimensions_str.encode('utf8'))) + print () + print () if __name__ == "__main__": diff --git a/monasca/middleware/mock_auth_filter.py b/monasca/middleware/mock_auth_filter.py index 01460bcbf..85eb3e27f 100644 --- a/monasca/middleware/mock_auth_filter.py +++ b/monasca/middleware/mock_auth_filter.py @@ -14,11 +14,13 @@ class MockAuthFilter(object): - ''' + """Authorization filter. + This authorization filter doesn't do any authentication, it just copies the auth token to the tenant ID and supplies the 'admin' role and is meant for testing purposes only. - ''' + """ + def __init__(self, app, conf): self.app = app self.conf = conf diff --git a/monasca/tests/__init__.py b/monasca/tests/__init__.py index 6e1cd6c21..e69de29bb 100644 --- a/monasca/tests/__init__.py +++ b/monasca/tests/__init__.py @@ -1,87 +0,0 @@ -# Copyright 2013 IBM Corp -# -# Author: Tong Li -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging -import os -import tempfile - -import fixtures -import testtools - -_TRUE_VALUES = ('True', 'true', '1', 'yes') -_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s" - - -class BaseTestCase(testtools.TestCase): - - def setUp(self): - super(BaseTestCase, self).setUp() - self._set_timeout() - self._fake_output() - self._fake_logs() - self.useFixture(fixtures.NestedTempfile()) - self.useFixture(fixtures.TempHomeDir()) - self.tempdirs = [] - - def _set_timeout(self): - test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0) - try: - test_timeout = int(test_timeout) - except ValueError: - # If timeout value is invalid do not set a timeout. - test_timeout = 0 - if test_timeout > 0: - self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) - - def _fake_output(self): - if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES: - stdout = self.useFixture(fixtures.StringStream('stdout')).stream - self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) - if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES: - stderr = self.useFixture(fixtures.StringStream('stderr')).stream - self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) - - def _fake_logs(self): - if os.environ.get('OS_DEBUG') in _TRUE_VALUES: - level = logging.DEBUG - else: - level = logging.INFO - capture_logs = os.environ.get('OS_LOG_CAPTURE') in _TRUE_VALUES - if capture_logs: - self.useFixture( - fixtures.FakeLogger( - format=_LOG_FORMAT, - level=level, - nuke_handlers=capture_logs, - ) - ) - else: - logging.basicConfig(format=_LOG_FORMAT, level=level) - - def create_tempfiles(self, files, ext='.conf'): - tempfiles = [] - for (basename, contents) in files: - if not os.path.isabs(basename): - (fd, path) = tempfile.mkstemp(prefix=basename, suffix=ext) - else: - path = basename + ext - fd = os.open(path, os.O_CREAT | os.O_WRONLY) - tempfiles.append(path) - try: - os.write(fd, contents) - finally: - os.close(fd) - return tempfiles diff --git a/monasca/tests/first_test.py b/monasca/tests/first_test.py new file mode 100644 index 000000000..bb3891fc9 --- /dev/null +++ b/monasca/tests/first_test.py @@ -0,0 +1,21 @@ +# Copyright 2014 Hewlett-Packard +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest + + +class Test_first(unittest.TestCase): + + def test_first(self): + assert 1 == 1 diff --git a/monasca/v2/common/schemas/alarm_definition_request_body_schema.py b/monasca/v2/common/schemas/alarm_definition_request_body_schema.py index 1e2117904..b3ee66c52 100644 --- a/monasca/v2/common/schemas/alarm_definition_request_body_schema.py +++ b/monasca/v2/common/schemas/alarm_definition_request_body_schema.py @@ -12,8 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from voluptuous import Schema, Length, Optional -from voluptuous import Required, Any, All +import voluptuous from monasca.openstack.common import log from monasca.v2.common.schemas import exceptions @@ -22,20 +21,26 @@ from monasca.v2.common.schemas import exceptions LOG = log.getLogger(__name__) alarm_definition_schema = { - Required('name'): All(Any(str, unicode), Length(max=250)), - Required('expression'): All(Any(str, unicode), Length(max=4096)), - Optional('description'): All(Any(str, unicode), Length(max=250)), - Optional('severity'): All( - Any('low', 'medium', 'high', 'critical', 'LOW', "MEDIUM", 'HIGH', - 'CRITICAL')), - Optional('match_by'): All(Any([unicode], [str]), Length(max=255)), - Optional('ok_actions'): All(Any([str], [unicode]), Length(max=400)), - Optional('alarm_actions'): All(Any([str], [unicode]), Length(max=400)), - Optional('undetermined_actions'): All(Any([str], [unicode]), - Length(max=400))} + voluptuous.Required('name'): voluptuous.All(voluptuous.Any(str, unicode), + voluptuous.Length(max=250)), + voluptuous.Required('expression'): voluptuous.All( + voluptuous.Any(str, unicode), voluptuous.Length(max=4096)), + voluptuous.Optional('description'): voluptuous.All( + voluptuous.Any(str, unicode), voluptuous.Length(max=250)), + voluptuous.Optional('severity'): voluptuous.All( + voluptuous.Any('low', 'medium', 'high', 'critical', 'LOW', "MEDIUM", + 'HIGH', 'CRITICAL')), + voluptuous.Optional('match_by'): voluptuous.All( + voluptuous.Any([unicode], [str]), voluptuous.Length(max=255)), + voluptuous.Optional('ok_actions'): voluptuous.All( + voluptuous.Any([str], [unicode]), voluptuous.Length(max=400)), + voluptuous.Optional('alarm_actions'): voluptuous.All( + voluptuous.Any([str], [unicode]), voluptuous.Length(max=400)), + voluptuous.Optional('undetermined_actions'): voluptuous.All( + voluptuous.Any([str], [unicode]), voluptuous.Length(max=400))} -request_body_schema = Schema(alarm_definition_schema, required=True, - extra=True) +request_body_schema = voluptuous.Schema(alarm_definition_schema, required=True, + extra=True) def validate(msg): diff --git a/monasca/v2/common/schemas/dimensions_schema.py b/monasca/v2/common/schemas/dimensions_schema.py index 9e120dc8c..648ede2a6 100644 --- a/monasca/v2/common/schemas/dimensions_schema.py +++ b/monasca/v2/common/schemas/dimensions_schema.py @@ -4,7 +4,7 @@ # not use this file except in compliance with the License. You may obtain # a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -12,15 +12,17 @@ # License for the specific language governing permissions and limitations # under the License. -from voluptuous import Schema -from voluptuous import Any, All, Length +import voluptuous + from monasca.openstack.common import log from monasca.v2.common.schemas import exceptions LOG = log.getLogger(__name__) -dimensions_schema = Schema({All(Any(str, unicode), Length(max=255)): - All(Any(str, unicode), Length(max=255))}) +dimensions_schema = voluptuous.Schema({ + voluptuous.All(voluptuous.Any(str, unicode), + voluptuous.Length(max=255)): voluptuous.All( + voluptuous.Any(str, unicode), voluptuous.Length(max=255))}) def validate(dimensions): diff --git a/monasca/v2/common/schemas/events_request_body_schema.py b/monasca/v2/common/schemas/events_request_body_schema.py index 8cd6b39cb..8b11bf80d 100644 --- a/monasca/v2/common/schemas/events_request_body_schema.py +++ b/monasca/v2/common/schemas/events_request_body_schema.py @@ -4,7 +4,7 @@ # not use this file except in compliance with the License. You may obtain # a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -12,17 +12,17 @@ # License for the specific language governing permissions and limitations # under the License. -from voluptuous import Schema -from voluptuous import Any, All, Length +import voluptuous + from monasca.openstack.common import log from monasca.v2.common.schemas import exceptions LOG = log.getLogger(__name__) -# TODO: Add regex to validate key/values don't use any excluded characters. -event_schema_request_body = Schema({All(Any(str, unicode), Length(max=255)): - All(Any(None, str, unicode, bool, int, - float, dict, []))}) +event_schema_request_body = voluptuous.Schema({ + voluptuous.All(voluptuous.Any(str, unicode), + voluptuous.Length(max=255)): voluptuous.All( + voluptuous.Any(None, str, unicode, bool, int, float, dict, []))}) def validate(body): diff --git a/monasca/v2/common/schemas/metric_name_schema.py b/monasca/v2/common/schemas/metric_name_schema.py index 392c5549a..95eec5afa 100644 --- a/monasca/v2/common/schemas/metric_name_schema.py +++ b/monasca/v2/common/schemas/metric_name_schema.py @@ -4,7 +4,7 @@ # not use this file except in compliance with the License. You may obtain # a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -12,14 +12,15 @@ # License for the specific language governing permissions and limitations # under the License. -from voluptuous import Schema -from voluptuous import Any, All, Length +import voluptuous + from monasca.openstack.common import log from monasca.v2.common.schemas import exceptions LOG = log.getLogger(__name__) -metric_name_schema = Schema(All(Any(str, unicode), Length(max=64))) +metric_name_schema = voluptuous.Schema( + voluptuous.All(voluptuous.Any(str, unicode), voluptuous.Length(max=64))) def validate(name): diff --git a/monasca/v2/common/schemas/metrics_request_body_schema.py b/monasca/v2/common/schemas/metrics_request_body_schema.py index eb4b4f997..79b8e1d4a 100644 --- a/monasca/v2/common/schemas/metrics_request_body_schema.py +++ b/monasca/v2/common/schemas/metrics_request_body_schema.py @@ -4,7 +4,7 @@ # not use this file except in compliance with the License. You may obtain # a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -12,23 +12,24 @@ # License for the specific language governing permissions and limitations # under the License. -from voluptuous import Schema -from voluptuous import Required, Any, All, Range, Optional +import voluptuous + from monasca.openstack.common import log -from monasca.v2.common.schemas import metric_name_schema from monasca.v2.common.schemas import dimensions_schema from monasca.v2.common.schemas import exceptions +from monasca.v2.common.schemas import metric_name_schema LOG = log.getLogger(__name__) metric_schema = { - Required('name'): metric_name_schema.metric_name_schema, - Optional('dimensions'): dimensions_schema.dimensions_schema, - Required('timestamp'): All(Any(int, float), Range(min=0)), - Required('value'): Any(int, float) -} + voluptuous.Required('name'): metric_name_schema.metric_name_schema, + voluptuous.Optional('dimensions'): dimensions_schema.dimensions_schema, + voluptuous.Required('timestamp'): voluptuous.All( + voluptuous.Any(int, float), voluptuous.Range(min=0)), + voluptuous.Required('value'): voluptuous.Any(int, float)} -request_body_schema = Schema(Any(metric_schema, [metric_schema])) +request_body_schema = voluptuous.Schema( + voluptuous.Any(metric_schema, [metric_schema])) def validate(msg): diff --git a/monasca/v2/common/schemas/notifications_request_body_schema.py b/monasca/v2/common/schemas/notifications_request_body_schema.py index 6ec08f564..2aea91bb7 100644 --- a/monasca/v2/common/schemas/notifications_request_body_schema.py +++ b/monasca/v2/common/schemas/notifications_request_body_schema.py @@ -4,7 +4,7 @@ # not use this file except in compliance with the License. You may obtain # a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -12,20 +12,24 @@ # License for the specific language governing permissions and limitations # under the License. -from voluptuous import Schema -from voluptuous import Optional, Required, Any, All, Length +import voluptuous + from monasca.openstack.common import log from monasca.v2.common.schemas import exceptions LOG = log.getLogger(__name__) notification_schema = { - Required('name'): Schema(All(Any(str, unicode), Length(max=250))), - Required('type'): Schema(Any("EMAIL", "email")), - Required('address'): Schema(All(Any(str, unicode), Length(max=100))) -} + voluptuous.Required('name'): voluptuous.Schema( + voluptuous.All(voluptuous.Any(str, unicode), + voluptuous.Length(max=250))), + voluptuous.Required('type'): voluptuous.Schema( + voluptuous.Any("EMAIL", "email")), + voluptuous.Required('address'): voluptuous.Schema( + voluptuous.All(voluptuous.Any(str, unicode), + voluptuous.Length(max=100)))} -request_body_schema = Schema(Any(notification_schema)) +request_body_schema = voluptuous.Schema(voluptuous.Any(notification_schema)) def validate(msg): diff --git a/monasca/v2/common/schemas/transforms_request_body_schema.py b/monasca/v2/common/schemas/transforms_request_body_schema.py index 9c8a2f10f..e723ce023 100644 --- a/monasca/v2/common/schemas/transforms_request_body_schema.py +++ b/monasca/v2/common/schemas/transforms_request_body_schema.py @@ -4,7 +4,7 @@ # not use this file except in compliance with the License. You may obtain # a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -12,22 +12,26 @@ # License for the specific language governing permissions and limitations # under the License. -from voluptuous import Schema -from voluptuous import Optional, Required, Any, All, Length +import voluptuous + from monasca.openstack.common import log from monasca.v2.common.schemas import exceptions LOG = log.getLogger(__name__) transform_schema = { - Required('name'): Schema(All(Any(str, unicode), Length(max=64))), - Required('description'): Schema(All(Any(str, unicode), Length(max=250))), - Required('specification'): - Schema(All(Any(str, unicode), Length(max=64536))), - Optional('enabled'): bool -} + voluptuous.Required('name'): voluptuous.Schema( + voluptuous.All(voluptuous.Any(str, unicode), + voluptuous.Length(max=64))), + voluptuous.Required('description'): voluptuous.Schema( + voluptuous.All(voluptuous.Any(str, unicode), + voluptuous.Length(max=250))), + voluptuous.Required('specification'): voluptuous.Schema( + voluptuous.All(voluptuous.Any(str, unicode), + voluptuous.Length(max=64536))), + voluptuous.Optional('enabled'): bool} -request_body_schema = Schema(Any(transform_schema)) +request_body_schema = voluptuous.Schema(voluptuous.Any(transform_schema)) def validate(msg): diff --git a/monasca/v2/reference/alarm_definitions.py b/monasca/v2/reference/alarm_definitions.py index de69efa31..76bd0b899 100644 --- a/monasca/v2/reference/alarm_definitions.py +++ b/monasca/v2/reference/alarm_definitions.py @@ -11,21 +11,24 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + import json import re -from pyparsing import ParseException + import falcon from oslo.config import cfg +import pyparsing +from monasca.api.alarm_definitions_api_v2 import AlarmDefinitionsV2API from monasca.common.repositories import exceptions from monasca.common import resource_api -from monasca.api.alarm_definitions_api_v2 import AlarmDefinitionsV2API -from monasca.expression_parser.alarm_expr_parser import AlarmExprParser +import monasca.expression_parser.alarm_expr_parser from monasca.openstack.common import log -from monasca.v2.reference import helpers -from monasca.v2.common.schemas import alarm_definition_request_body_schema as schema_alarms +from monasca.v2.common.schemas import (alarm_definition_request_body_schema + as schema_alarms) from monasca.v2.common.schemas import exceptions as schemas_exceptions from monasca.v2.reference.alarming import Alarming +from monasca.v2.reference import helpers from monasca.v2.reference.helpers import read_json_msg_body from monasca.v2.reference.resource import resource_try_catch_block @@ -43,13 +46,13 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming): self._region = cfg.CONF.region - self._default_authorized_roles = \ - cfg.CONF.security.default_authorized_roles - self._delegate_authorized_roles = \ - cfg.CONF.security.delegate_authorized_roles - self._post_metrics_authorized_roles = \ - cfg.CONF.security.default_authorized_roles + \ - cfg.CONF.security.agent_authorized_roles + self._default_authorized_roles = ( + cfg.CONF.security.default_authorized_roles) + self._delegate_authorized_roles = ( + cfg.CONF.security.delegate_authorized_roles) + self._post_metrics_authorized_roles = ( + cfg.CONF.security.default_authorized_roles + + cfg.CONF.security.agent_authorized_roles) self._alarm_definitions_repo = resource_api.init_driver( 'monasca.repositories', @@ -134,9 +137,8 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming): @resource_try_catch_block def _alarm_definition_show(self, tenant_id, id): - alarm_definition_row = \ - self._alarm_definitions_repo.get_alarm_definition( - tenant_id, id) + alarm_definition_row = ( + self._alarm_definitions_repo.get_alarm_definition(tenant_id, id)) match_by = get_comma_separated_str_as_list( alarm_definition_row.match_by) @@ -168,9 +170,8 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming): @resource_try_catch_block def _alarm_definition_delete(self, tenant_id, id): - sub_alarm_definition_rows = \ - self._alarm_definitions_repo.get_sub_alarm_definitions( - id) + sub_alarm_definition_rows = ( + self._alarm_definitions_repo.get_sub_alarm_definitions(id)) alarm_metric_rows = self._alarm_definitions_repo.get_alarm_metrics( tenant_id, id) sub_alarm_rows = self._alarm_definitions_repo.get_sub_alarms( @@ -189,9 +190,9 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming): @resource_try_catch_block def _alarm_definition_list(self, tenant_id, name, dimensions, req_uri): - alarm_definition_rows = \ - self._alarm_definitions_repo.get_alarm_definitions( - tenant_id, name, dimensions) + alarm_definition_rows = ( + self._alarm_definitions_repo.get_alarm_definitions(tenant_id, name, + dimensions)) result = [] for alarm_definition_row in alarm_definition_rows: @@ -227,7 +228,6 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming): return result - def _validate_alarm_definition(self, alarm_definition): try: @@ -243,20 +243,29 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming): ok_actions): try: - sub_expr_list = AlarmExprParser(expression).sub_expr_list + sub_expr_list = ( + monasca.expression_parser.alarm_expr_parser. + AlarmExprParser(expression).sub_expr_list) - except ParseException as ex: + except pyparsing.ParseException as ex: LOG.exception(ex) title = "Invalid alarm expression".encode('utf8') msg = "parser failed on expression '{}' at column {}".format( expression.encode('utf8'), str(ex.column).encode('utf')) raise falcon.HTTPBadRequest(title, msg) - alarm_definition_id = \ - self._alarm_definitions_repo.create_alarm_definition( - tenant_id, name, expression, sub_expr_list, description, - severity, match_by, alarm_actions, undetermined_actions, - ok_actions) + alarm_definition_id = ( + self._alarm_definitions_repo. + create_alarm_definition(tenant_id, + name, + expression, + sub_expr_list, + description, + severity, + match_by, + alarm_actions, + undetermined_actions, + ok_actions)) self._send_alarm_definition_created_event(tenant_id, alarm_definition_id, @@ -334,6 +343,7 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming): self._send_event(alarm_definition_created_event_msg) + def get_query_alarm_definition_name(alarm_definition): try: if 'name' in alarm_definition: @@ -407,6 +417,7 @@ def get_query_ok_actions(alarm_definition): else: return [] + def get_comma_separated_str_as_list(comma_separated_str): if not comma_separated_str: diff --git a/monasca/v2/reference/alarming.py b/monasca/v2/reference/alarming.py index 2d702298a..4035686c5 100644 --- a/monasca/v2/reference/alarming.py +++ b/monasca/v2/reference/alarming.py @@ -12,12 +12,14 @@ # License for the specific language governing permissions and limitations # under the License. import json + import falcon from oslo.config import cfg -from monasca.common import resource_api -from monasca.expression_parser.alarm_expr_parser import AlarmExprParser -from monasca.openstack.common import log + from monasca.common.messaging import exceptions as message_queue_exceptions +from monasca.common import resource_api +import monasca.expression_parser.alarm_expr_parser +from monasca.openstack.common import log LOG = log.getLogger(__name__) @@ -33,10 +35,9 @@ class Alarming(object): super(Alarming, self).__init__() - self._message_queue \ - = resource_api.init_driver('monasca.messaging', - cfg.CONF.messaging.driver, - (['events'])) + self._message_queue = ( + resource_api.init_driver('monasca.messaging', + cfg.CONF.messaging.driver, (['events']))) def _send_alarm_deleted_event(self, tenant_id, alarm_definition_id, alarm_metric_rows, sub_alarm_rows): @@ -52,15 +53,17 @@ class Alarming(object): else: sub_alarm_dict[sub_alarm_row.alarm_id] = [sub_alarm_row] + # Forward declaration. + alarm_deleted_event_msg = {} prev_alarm_id = None for alarm_metric_row in alarm_metric_rows: if prev_alarm_id != alarm_metric_row.alarm_id: if prev_alarm_id is not None: - sub_alarms_deleted_event_msg = \ - self._build_sub_alarm_deleted_event_msg( - sub_alarm_dict, prev_alarm_id) + sub_alarms_deleted_event_msg = ( + self._build_sub_alarm_deleted_event_msg(sub_alarm_dict, + prev_alarm_id)) alarm_deleted_event_msg[u'alarm-delete'][ - u'subAlarms': sub_alarms_deleted_event_msg] + u'subAlarms': sub_alarms_deleted_event_msg] self._send_event(alarm_deleted_event_msg) alarm_metrics_event_msg = [] @@ -99,7 +102,8 @@ class Alarming(object): for sub_alarm in sub_alarm_dict[alarm_id]: # There's only one expr in a sub alarm, so just take the first. - sub_expr = AlarmExprParser(sub_alarm.expression).sub_expr_list[0] + sub_expr = (monasca.expression_parser.alarm_expr_parser. + AlarmExprParser(sub_alarm.expression).sub_expr_list[0]) dimensions = {} sub_alarms_deleted_event_msg[sub_alarm.sub_alarm_id] = { u'function': sub_expr.normalized_func, diff --git a/monasca/v2/reference/alarms.py b/monasca/v2/reference/alarms.py index eb0b9449f..a21e01c31 100644 --- a/monasca/v2/reference/alarms.py +++ b/monasca/v2/reference/alarms.py @@ -11,17 +11,19 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + import json -from falcon.util.uri import parse_query_string import re + import falcon from oslo.config import cfg + from monasca.api.alarms_api_v2 import AlarmsV2API from monasca.common.repositories import exceptions from monasca.common import resource_api from monasca.openstack.common import log -from monasca.v2.reference import helpers from monasca.v2.reference.alarming import Alarming +from monasca.v2.reference import helpers from monasca.v2.reference.resource import resource_try_catch_block @@ -38,17 +40,20 @@ class Alarms(AlarmsV2API, Alarming): self._region = cfg.CONF.region - self._default_authorized_roles = \ - cfg.CONF.security.default_authorized_roles - self._delegate_authorized_roles = \ - cfg.CONF.security.delegate_authorized_roles - self._post_metrics_authorized_roles = \ - cfg.CONF.security.default_authorized_roles + \ - cfg.CONF.security.agent_authorized_roles + self._default_authorized_roles = ( + cfg.CONF.security.default_authorized_roles) + self._delegate_authorized_roles = ( + cfg.CONF.security.delegate_authorized_roles) + self._post_metrics_authorized_roles = ( + cfg.CONF.security.default_authorized_roles + + cfg.CONF.security.agent_authorized_roles) self._alarms_repo = resource_api.init_driver( 'monasca.repositories', cfg.CONF.repositories.alarms_driver) + self._metrics_repo = resource_api.init_driver( + 'monasca.repositories', cfg.CONF.repositories.metrics_driver) + except Exception as ex: LOG.exception(ex) raise exceptions.RepositoryException(ex) @@ -91,7 +96,7 @@ class Alarms(AlarmsV2API, Alarming): helpers.validate_authorization(req, self._default_authorized_roles) tenant_id = helpers.get_tenant_id(req) - query_parms = parse_query_string(req.query_string) + query_parms = falcon.uri.parse_query_string(req.query_string) result = self._alarm_list(req.uri, tenant_id, query_parms) @@ -101,6 +106,11 @@ class Alarms(AlarmsV2API, Alarming): @resource_api.Restify('/v2.0/alarms/{id}', method='get') def do_get_alarm_by_id(self, req, res, id): + # Necessary because falcon interprets 'state-history' as an + # alarm id, and this url masks '/v2.0/alarms/state-history'. + if id.lower() == 'state-history': + return self.do_get_alarms_state_history(req, res) + helpers.validate_authorization(req, self._default_authorized_roles) tenant_id = helpers.get_tenant_id(req) @@ -109,6 +119,53 @@ class Alarms(AlarmsV2API, Alarming): res.body = json.dumps(result, ensure_ascii=False).encode('utf8') res.status = falcon.HTTP_200 + @resource_api.Restify('/v2.0/alarms/state-history', method='get') + def do_get_alarms_state_history(self, req, res): + + helpers.validate_authorization(req, self._default_authorized_roles) + tenant_id = helpers.get_tenant_id(req) + start_timestamp = helpers.get_query_starttime_timestamp(req, False) + end_timestamp = helpers.get_query_endtime_timestamp(req, False) + query_parms = falcon.uri.parse_query_string(req.query_string) + + result = self._alarm_history_list(tenant_id, start_timestamp, + end_timestamp, query_parms) + + res.body = json.dumps(result, ensure_ascii=False).encode('utf8') + res.status = falcon.HTTP_200 + + @resource_api.Restify('/v2.0/alarms/{id}/state-history', method='get') + def do_get_alarm_state_history(self, req, res, id): + + helpers.validate_authorization(req, self._default_authorized_roles) + tenant_id = helpers.get_tenant_id(req) + + result = self._alarm_history(tenant_id, [id]) + + res.body = json.dumps(result, ensure_ascii=False).encode('utf8') + res.status = falcon.HTTP_200 + + @resource_try_catch_block + def _alarm_history_list(self, tenant_id, start_timestamp, + end_timestamp, query_parms): + + # get_alarms expects 'metric_dimensions' for dimensions key. + if 'dimensions' in query_parms: + new_query_parms = {'metric_dimensions': query_parms['dimensions']} + else: + new_query_parms = {} + + alarm_rows = self._alarms_repo.get_alarms(tenant_id, new_query_parms) + alarm_id_list = [alarm_row.alarm_id for alarm_row in alarm_rows] + + return self._metrics_repo.alarm_history(tenant_id, alarm_id_list, + start_timestamp, end_timestamp) + + @resource_try_catch_block + def _alarm_history(self, tenant_id, alarm_id): + + return self._metrics_repo.alarm_history(tenant_id, alarm_id) + @resource_try_catch_block def _alarm_delete(self, tenant_id, id): @@ -171,6 +228,8 @@ class Alarms(AlarmsV2API, Alarming): if not alarm_rows: return result + # Forward declaration + alarm = {} prev_alarm_id = None for alarm_row in alarm_rows: if prev_alarm_id != alarm_row.alarm_id: diff --git a/monasca/v2/reference/events.py b/monasca/v2/reference/events.py index c9197d793..e2a7ebeae 100644 --- a/monasca/v2/reference/events.py +++ b/monasca/v2/reference/events.py @@ -17,41 +17,44 @@ import json import falcon from oslo.config import cfg -from monasca.openstack.common import log from monasca.api import monasca_events_api_v2 -from monasca.common import resource_api from monasca.common.messaging import exceptions as message_queue_exceptions from monasca.common.messaging.message_formats import events_transform_factory -from monasca.v2.common import utils +from monasca.common import resource_api +from monasca.openstack.common import log +from monasca.v2.common.schemas import ( + events_request_body_schema as schemas_event) from monasca.v2.common.schemas import exceptions as schemas_exceptions -from monasca.v2.common.schemas import \ - events_request_body_schema as schemas_event +from monasca.v2.common import utils from monasca.v2.reference import helpers LOG = log.getLogger(__name__) class Events(monasca_events_api_v2.EventsV2API): + def __init__(self, global_conf): + super(Events, self).__init__(global_conf) + self._region = cfg.CONF.region - self._default_authorized_roles = \ - cfg.CONF.security.default_authorized_roles - self._delegate_authorized_roles = \ - cfg.CONF.security.delegate_authorized_roles - self._post_events_authorized_roles = \ - cfg.CONF.security.default_authorized_roles + \ - cfg.CONF.security.agent_authorized_roles - self._event_transform = \ - events_transform_factory.create_events_transform() - self._message_queue = \ + self._default_authorized_roles = ( + cfg.CONF.security.default_authorized_roles) + self._delegate_authorized_roles = ( + cfg.CONF.security.delegate_authorized_roles) + self._post_events_authorized_roles = ( + cfg.CONF.security.default_authorized_roles + + cfg.CONF.security.agent_authorized_roles) + self._event_transform = ( + events_transform_factory.create_events_transform()) + self._message_queue = ( resource_api.init_driver('monasca.messaging', cfg.CONF.messaging.driver, - ['raw-events']) + ['raw-events'])) def _validate_event(self, event): """Validates the event - + :param event: An event object. :raises falcon.HTTPBadRequest """ @@ -63,7 +66,7 @@ class Events(monasca_events_api_v2.EventsV2API): def _send_event(self, event): """Send the event using the message queue. - + :param metrics: An event object. :raises: falcon.HTTPServiceUnavailable """ diff --git a/monasca/v2/reference/helpers.py b/monasca/v2/reference/helpers.py index 4dc1de4eb..1f93eda32 100644 --- a/monasca/v2/reference/helpers.py +++ b/monasca/v2/reference/helpers.py @@ -11,25 +11,25 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + import datetime import json -from urlparse import urlparse +import urlparse import falcon -from falcon.util.uri import parse_query_string +import simplejson from monasca.openstack.common import log +from monasca.v2.common.schemas import dimensions_schema from monasca.v2.common.schemas import exceptions as schemas_exceptions from monasca.v2.common.schemas import metric_name_schema -from monasca.v2.common.schemas import dimensions_schema -import simplejson LOG = log.getLogger(__name__) def read_json_msg_body(req): - """ - Read the json_msg from the http request body and return them as JSON. + """Read the json_msg from the http request body and return them as JSON. + :param req: HTTP request object. :return: Returns the metrics as a JSON object. :raises falcon.HTTPBadRequest: @@ -51,9 +51,8 @@ def validate_json_content_type(req): def is_in_role(req, authorized_roles): - """Determines if one or more of the X-ROLES is in the supplied - authorized_roles. - + """Is one or more of the X-ROLES in the supplied authorized_roles. + :param req: HTTP request object. Must contain "X-ROLES" in the HTTP request header. :param authorized_roles: List of authorized roles to check against. @@ -71,7 +70,7 @@ def is_in_role(req, authorized_roles): def validate_authorization(req, authorized_roles): """Validates whether one or more X-ROLES in the HTTP header is authorized. - + :param req: HTTP request object. Must contain "X-ROLES" in the HTTP request header. :param authorized_roles: List of authorized roles to check against. @@ -92,7 +91,7 @@ def validate_authorization(req, authorized_roles): def get_tenant_id(req): """Returns the tenant ID in the HTTP request header. - + :param req: HTTP request object. """ return req.get_header('X-TENANT-ID') @@ -100,14 +99,14 @@ def get_tenant_id(req): def get_x_tenant_or_tenant_id(req, delegate_authorized_roles): """Evaluates whether the tenant ID or cross tenant ID should be returned. - + :param req: HTTP request object. :param delegate_authorized_roles: List of authorized roles that have delegate privileges. :returns: Returns the cross tenant or tenant ID. """ if is_in_role(req, delegate_authorized_roles): - params = parse_query_string(req.query_string) + params = falcon.uri.parse_query_string(req.query_string) if 'tenant_id' in params: tenant_id = params['tenant_id'] return tenant_id @@ -115,12 +114,12 @@ def get_x_tenant_or_tenant_id(req, delegate_authorized_roles): def get_query_name(req, name_required=False): - """ - Returns the query param "name" if supplied. + """Returns the query param "name" if supplied. + :param req: HTTP request object. """ try: - params = parse_query_string(req.query_string) + params = falcon.uri.parse_query_string(req.query_string) if 'name' in params: name = params['name'] return name @@ -136,13 +135,13 @@ def get_query_name(req, name_required=False): def get_query_dimensions(req): """Gets and parses the query param dimensions. - + :param req: HTTP request object. :return: Returns the dimensions as a JSON object :raises falcon.HTTPBadRequest: If dimensions are malformed. """ try: - params = parse_query_string(req.query_string) + params = falcon.uri.parse_query_string(req.query_string) dimensions = {} if 'dimensions' in params: dimensions_str = params['dimensions'] @@ -160,25 +159,31 @@ def get_query_dimensions(req): raise falcon.HTTPBadRequest('Bad request', ex.message) -def get_query_starttime_timestamp(req): +def get_query_starttime_timestamp(req, required=True): try: - params = parse_query_string(req.query_string) + params = falcon.uri.parse_query_string(req.query_string) if 'start_time' in params: return _convert_time_string(params['start_time']) else: - raise Exception("Missing start time") + if required: + raise Exception("Missing start time") + else: + return None except Exception as ex: LOG.debug(ex) raise falcon.HTTPBadRequest('Bad request', ex.message) -def get_query_endtime_timestamp(req): +def get_query_endtime_timestamp(req, required=True): try: - params = parse_query_string(req.query_string) + params = falcon.uri.parse_query_string(req.query_string) if 'end_time' in params: return _convert_time_string(params['end_time']) else: - return None + if required: + raise Exception("Missing end time") + else: + return None except Exception as ex: LOG.debug(ex) raise falcon.HTTPBadRequest('Bad request', ex.message) @@ -192,7 +197,7 @@ def _convert_time_string(date_time_string): def get_query_statistics(req): try: - params = parse_query_string(req.query_string) + params = falcon.uri.parse_query_string(req.query_string) if 'statistics' in params: statistics = params['statistics'].split(',') statistics = [statistic.lower() for statistic in statistics] @@ -209,7 +214,7 @@ def get_query_statistics(req): def get_query_period(req): try: - params = parse_query_string(req.query_string) + params = falcon.uri.parse_query_string(req.query_string) if 'period' in params: return params['period'] else: @@ -221,7 +226,7 @@ def get_query_period(req): def validate_query_name(name): """Validates the query param name. - + :param name: Query param name. :raises falcon.HTTPBadRequest: If name is not valid. """ @@ -234,7 +239,7 @@ def validate_query_name(name): def validate_query_dimensions(dimensions): """Validates the query param dimensions. - + :param dimensions: Query param dimensions. :raises falcon.HTTPBadRequest: If dimensions are not valid. """ @@ -247,7 +252,7 @@ def validate_query_dimensions(dimensions): def get_link(uri, resource_id, rel='self'): """Returns a link dictionary containing href, and rel. - + :param uri: the http request.uri. :param resource_id: the id of the resource """ @@ -265,7 +270,7 @@ def get_link(uri, resource_id, rel='self'): def add_links_to_resource(resource, uri, rel='self'): """Adds links to the given resource dictionary. - + :param resource: the resource dictionary you wish to add links. :param uri: the http request.uri. """ @@ -275,7 +280,7 @@ def add_links_to_resource(resource, uri, rel='self'): def add_links_to_resource_list(resourcelist, uri): """Adds links to the given resource dictionary list. - + :param resourcelist: the list of resources you wish to add links. :param uri: the http request.uri. """ @@ -286,7 +291,7 @@ def add_links_to_resource_list(resourcelist, uri): def read_http_resource(req): """Read from http request and return json. - + :param req: the http request. """ try: @@ -302,7 +307,7 @@ def read_http_resource(req): def raise_not_found_exception(resource_name, resource_id, tenant_id): """Provides exception for not found requests (update, delete, list). - + :param resource_name: the name of the resource. :param resource_id: id of the resource. :param tenant_id: id of the tenant diff --git a/monasca/v2/reference/metrics.py b/monasca/v2/reference/metrics.py index d59bfcfc0..1eee2da79 100644 --- a/monasca/v2/reference/metrics.py +++ b/monasca/v2/reference/metrics.py @@ -16,22 +16,17 @@ import json import falcon from oslo.config import cfg -from stevedore import driver -from monasca.openstack.common import log from monasca.api import monasca_api_v2 -from monasca.common import resource_api from monasca.common.messaging import exceptions as message_queue_exceptions from monasca.common.messaging.message_formats import metrics_transform_factory +from monasca.common import resource_api +from monasca.openstack.common import log +from monasca.v2.common.schemas import (exceptions as schemas_exceptions) +from monasca.v2.common.schemas import ( + metrics_request_body_schema as schemas_metrics) from monasca.v2.common import utils -from monasca.v2.common.schemas import exceptions as schemas_exceptions -from monasca.v2.common.schemas import \ - metrics_request_body_schema as schemas_metrics - -from monasca.common.repositories import exceptions - from monasca.v2.reference import helpers -from monasca.v2.reference.helpers import read_json_msg_body LOG = log.getLogger(__name__) @@ -41,21 +36,23 @@ class Metrics(monasca_api_v2.V2API): def __init__(self, global_conf): try: + super(Metrics, self).__init__(global_conf) + self._region = cfg.CONF.region - self._default_authorized_roles = \ - cfg.CONF.security.default_authorized_roles - self._delegate_authorized_roles = \ - cfg.CONF.security.delegate_authorized_roles - self._post_metrics_authorized_roles = \ - cfg.CONF.security.default_authorized_roles + \ - cfg.CONF.security.agent_authorized_roles - self._metrics_transform = \ - metrics_transform_factory.create_metrics_transform() - self._message_queue = resource_api.init_driver( - 'monasca.messaging', - cfg.CONF.messaging.driver, - ['metrics']) + self._default_authorized_roles = ( + cfg.CONF.security.default_authorized_roles) + self._delegate_authorized_roles = ( + cfg.CONF.security.delegate_authorized_roles) + self._post_metrics_authorized_roles = ( + cfg.CONF.security.default_authorized_roles + + cfg.CONF.security.agent_authorized_roles) + self._metrics_transform = ( + metrics_transform_factory.create_metrics_transform()) + self._message_queue = ( + resource_api.init_driver('monasca.messaging', + cfg.CONF.messaging.driver, + ['metrics'])) self._metrics_repo = resource_api.init_driver( 'monasca.repositories', cfg.CONF.repositories.metrics_driver) @@ -66,7 +63,7 @@ class Metrics(monasca_api_v2.V2API): def _validate_metrics(self, metrics): """Validates the metrics - + :param metrics: A metric object or array of metrics objects. :raises falcon.HTTPBadRequest """ @@ -78,7 +75,7 @@ class Metrics(monasca_api_v2.V2API): def _send_metrics(self, metrics): """Send the metrics using the message queue. - + :param metrics: A metric object or array of metrics objects. :raises: falcon.HTTPServiceUnavailable """ @@ -90,7 +87,7 @@ class Metrics(monasca_api_v2.V2API): except message_queue_exceptions.MessageQueueException as ex: LOG.exception(ex) raise falcon.HTTPServiceUnavailable('Service unavailable', - ex.message) + ex.message, 60) if isinstance(metrics, list): for metric in metrics: @@ -100,7 +97,7 @@ class Metrics(monasca_api_v2.V2API): def _list_metrics(self, tenant_id, name, dimensions): """Query the metric repo for the metrics, format them and return them. - + :param tenant_id: :param name: :param dimensions: @@ -112,7 +109,7 @@ class Metrics(monasca_api_v2.V2API): except Exception as ex: LOG.exception(ex) raise falcon.HTTPServiceUnavailable('Service unavailable', - ex.message) + ex.message, 60) def _measurement_list(self, tenant_id, name, dimensions, start_timestamp, end_timestamp): @@ -124,7 +121,7 @@ class Metrics(monasca_api_v2.V2API): except Exception as ex: LOG.exception(ex) raise falcon.HTTPServiceUnavailable('Service unavailable', - ex.message) + ex.message, 60) def _metric_statistics(self, tenant_id, name, dimensions, start_timestamp, end_timestamp, statistics, period): @@ -137,7 +134,7 @@ class Metrics(monasca_api_v2.V2API): except Exception as ex: LOG.exception(ex) raise falcon.HTTPServiceUnavailable('Service unavailable', - ex.message) + ex.message, 60) @resource_api.Restify('/v2.0/metrics/', method='post') def do_post_metrics(self, req, res): @@ -146,9 +143,9 @@ class Metrics(monasca_api_v2.V2API): self._post_metrics_authorized_roles) metrics = helpers.read_http_resource(req) self._validate_metrics(metrics) - tenant_id = \ + tenant_id = ( helpers.get_x_tenant_or_tenant_id(req, - self._delegate_authorized_roles) + self._delegate_authorized_roles)) transformed_metrics = self._metrics_transform(metrics, tenant_id, self._region) self._send_metrics(transformed_metrics) @@ -175,7 +172,7 @@ class Metrics(monasca_api_v2.V2API): dimensions = helpers.get_query_dimensions(req) helpers.validate_query_dimensions(dimensions) start_timestamp = helpers.get_query_starttime_timestamp(req) - end_timestamp = helpers.get_query_endtime_timestamp(req) + end_timestamp = helpers.get_query_endtime_timestamp(req, False) result = self._measurement_list(tenant_id, name, dimensions, start_timestamp, end_timestamp) res.body = json.dumps(result, ensure_ascii=False).encode('utf8') @@ -190,7 +187,7 @@ class Metrics(monasca_api_v2.V2API): dimensions = helpers.get_query_dimensions(req) helpers.validate_query_dimensions(dimensions) start_timestamp = helpers.get_query_starttime_timestamp(req) - end_timestamp = helpers.get_query_endtime_timestamp(req) + end_timestamp = helpers.get_query_endtime_timestamp(req, False) statistics = helpers.get_query_statistics(req) period = helpers.get_query_period(req) result = self._metric_statistics(tenant_id, name, dimensions, diff --git a/monasca/v2/reference/notifications.py b/monasca/v2/reference/notifications.py index 88e2f65ad..409b97132 100644 --- a/monasca/v2/reference/notifications.py +++ b/monasca/v2/reference/notifications.py @@ -12,22 +12,19 @@ # License for the specific language governing permissions and limitations # under the License. -# TODO: Used simplejson to read the yaml as simplejson transforms to "str" -# not "unicode" - import json import falcon from oslo.config import cfg +from monasca.api import monasca_notifications_api_v2 +from monasca.common.repositories import exceptions as repository_exceptions +from monasca.common import resource_api from monasca.openstack.common import log from monasca.openstack.common import uuidutils -from monasca.api import monasca_notifications_api_v2 -from monasca.common import resource_api -from monasca.common.repositories import exceptions as repository_exceptions -from monasca.v2.common.schemas import exceptions as schemas_exceptions -from monasca.v2.common.schemas import \ - notifications_request_body_schema as schemas_notifications +from monasca.v2.common.schemas import (exceptions as schemas_exceptions) +from monasca.v2.common.schemas import ( + notifications_request_body_schema as schemas_notifications) from monasca.v2.reference import helpers LOG = log.getLogger(__name__) @@ -37,14 +34,14 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API): def __init__(self, global_conf): super(Notifications, self).__init__(global_conf) self._region = cfg.CONF.region - self._default_authorized_roles = \ - cfg.CONF.security.default_authorized_roles + self._default_authorized_roles = ( + cfg.CONF.security.default_authorized_roles) self._notifications_repo = resource_api.init_driver( 'monasca.repositories', cfg.CONF.repositories.notifications_driver) def _validate_notification(self, notification): """Validates the notification - + :param notification: An event object. :raises falcon.HTTPBadRequest """ @@ -56,7 +53,7 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API): def _create_notification(self, id, tenant_id, notification): """Store the notification using the repository. - + :param notification: A notification object. :raises: falcon.HTTPServiceUnavailable,falcon.HTTPConflict """ @@ -66,8 +63,9 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API): address = notification['address'] if self._notifications_repo.exists(tenant_id, name): raise falcon.HTTPConflict('Conflict', ( - 'Notification Method already exists: tenant_id=%s name=%s' % ( - tenant_id, name)), code=409) + 'Notification Method already exists: tenant_id=%s ' + 'name=%s' % ( + tenant_id, name)), code=409) self._notifications_repo.create_notification(id, tenant_id, name, notification_type, address) @@ -78,7 +76,7 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API): def _update_notification(self, id, tenant_id, notification): """Update the notification using the repository. - + :param notification: A notification object. :raises: falcon.HTTPServiceUnavailable,falcon.HTTPError (404) """ @@ -107,7 +105,7 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API): def _list_notifications(self, tenant_id, uri): """Lists all notifications for this tenant id. - + :param tenant_id: The tenant id. :raises: falcon.HTTPServiceUnavailable """ @@ -123,7 +121,7 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API): def _list_notification(self, tenant_id, notification_id, uri): """Lists the notification by id. - + :param tenant_id: The tenant id. :param notification_id: The notification id :raises: falcon.HTTPServiceUnavailable,falcon.HTTPError (404): @@ -143,7 +141,7 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API): def _delete_notification(self, tenant_id, notification_id): """Deletes the notification using the repository. - + :param tenant_id: The tenant id. :param notification_id: The notification id :raises: falcon.HTTPServiceUnavailable,falcon.HTTPError (404) diff --git a/monasca/v2/reference/resource.py b/monasca/v2/reference/resource.py index 458639862..7124af665 100644 --- a/monasca/v2/reference/resource.py +++ b/monasca/v2/reference/resource.py @@ -1,6 +1,19 @@ +# Copyright 2014 Hewlett-Packard +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. import falcon -from monasca.common.messaging import exceptions -from monasca.common.repositories.exceptions import DoesNotExistException + +from monasca.common.repositories import exceptions from monasca.openstack.common import log @@ -17,7 +30,7 @@ def resource_try_catch_block(fun): except falcon.HTTPNotFound: raise - except DoesNotExistException: + except exceptions.DoesNotExistException: raise falcon.HTTPNotFound except falcon.HTTPBadRequest: raise diff --git a/monasca/v2/reference/transforms.py b/monasca/v2/reference/transforms.py index 7cd08140b..fdc7605a0 100644 --- a/monasca/v2/reference/transforms.py +++ b/monasca/v2/reference/transforms.py @@ -12,40 +12,38 @@ # License for the specific language governing permissions and limitations # under the License. -# TODO: Used simplejson to read the yaml as simplejson transforms to "str" -# not "unicode" - import json -import simplejson import falcon from oslo.config import cfg +from monasca.api import monasca_transforms_api_v2 +from monasca.common.repositories import exceptions as repository_exceptions +from monasca.common import resource_api from monasca.openstack.common import log from monasca.openstack.common import uuidutils -from monasca.api import monasca_transforms_api_v2 -from monasca.common import resource_api -from monasca.common.repositories import exceptions as repository_exceptions -from monasca.v2.common.schemas import exceptions as schemas_exceptions -from monasca.v2.common.schemas import \ - transforms_request_body_schema as schemas_transforms +from monasca.v2.common.schemas import (exceptions as schemas_exceptions) +from monasca.v2.common.schemas import ( + transforms_request_body_schema as schemas_transforms) from monasca.v2.reference import helpers + LOG = log.getLogger(__name__) class Transforms(monasca_transforms_api_v2.TransformsV2API): def __init__(self, global_conf): + super(Transforms, self).__init__(global_conf) self._region = cfg.CONF.region - self._default_authorized_roles = \ - cfg.CONF.security.default_authorized_roles + self._default_authorized_roles = ( + cfg.CONF.security.default_authorized_roles) self._transforms_repo = resource_api.init_driver( 'monasca.repositories', cfg.CONF.repositories.transforms_driver) def _validate_transform(self, transform): """Validates the transform - + :param transform: An event object. :raises falcon.HTTPBadRequest """ @@ -57,7 +55,7 @@ class Transforms(monasca_transforms_api_v2.TransformsV2API): def _create_transform(self, id, tenant_id, transform): """Store the transform using the repository. - + :param transform: A transform object. :raises: falcon.HTTPServiceUnavailable """ diff --git a/requirements.txt b/requirements.txt index 40496a133..6b2b5e38b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ gunicorn>=19.1.0 oslo.config>=1.2.1 ujson>=1.33 Pyparsing>=2.0.3 -pyodbc>=3.0.7 +influxdb>=0.1.12 diff --git a/test-requirements.txt b/test-requirements.txt index 9378e9086..0217a5963 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,6 +9,7 @@ pep8<=1.5.6 httplib2>=0.7.5 mock>=1.0 mox>=0.5.3 +nose # Docs Requirements oslosphinx oslotest diff --git a/tox.ini b/tox.ini index 5375cbf55..e627c5784 100644 --- a/tox.ini +++ b/tox.ini @@ -4,14 +4,12 @@ skipsdist = True envlist = py27,py33,pep8 [testenv] +setenv = VIRTUAL_ENV={envdir} +usedevelop = True +install_command = pip install -U {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt -install_command = pip install -U --allow-external pytidylib --allow-insecure pytidylib --allow-external netifaces --allow-insecure netifaces {opts} {packages} -usedevelop = True -setenv = VIRTUAL_ENV={envdir} -commands = python setup.py testr --slowest --testr-args='--concurrency 1 {posargs}' -downloadcache = {toxworkdir}/_download -whitelist_externals = bash +commands = nosetests [testenv:cover] setenv = NOSE_WITH_COVERAGE=1 @@ -32,8 +30,6 @@ commands = python setup.py build_sphinx commands = {posargs} [flake8] -# H305 imports not grouped correctly -ignore = H305 builtins = _ exclude=.venv,.git,.tox,dist,doc,./monasca/openstack/common,*lib/python*,*egg,tools,build show-source = True