diff --git a/etc/monasca.conf b/etc/monasca.conf index 8798852d6..5d7e9dfe0 100644 --- a/etc/monasca.conf +++ b/etc/monasca.conf @@ -39,6 +39,9 @@ events_driver = none # The driver to use for the transforms repository transforms_driver = mysql_transforms_repo +# The driver to use for the notifications repository +notifications_driver = mysql_notifications_repo + [dispatcher] driver = v2_reference diff --git a/monasca/api/monasca_api_v2.py b/monasca/api/monasca_api_v2.py index f3acc5904..9fb16f171 100644 --- a/monasca/api/monasca_api_v2.py +++ b/monasca/api/monasca_api_v2.py @@ -46,33 +46,17 @@ class V2API(object): def do_get_statistics(self, req, res): res.status = '501 Not Implemented' - @resource_api.Restify('/v2.0/notification-methods', method='post') - def do_post_notification_methods(self, req, res): - res.status = '501 Not Implemented' - - @resource_api.Restify('/v2.0/notification-methods/{id}', method='put') - def do_put_notification_methods(self, req, res, id): - res.status = '501 Not Implemented' - - @resource_api.Restify('/v2.0/notification-methods/{id}', method='delete') - def do_delete_notification_methods(self, req, res, id): - res.status = '501 Not Implemented' - - @resource_api.Restify('/v2.0/notification-methods/{id}', method='get') - def do_get_notification_methods(self, req, res, id): - res.status = '501 Not Implemented' - @resource_api.Restify('/v2.0/alarm-definitions', method='post') def do_post_alarm_definitions(self, req, res): res.status = '501 Not Implemented' @resource_api.Restify('/v2.0/alarm-definitions/{id}', method='get') def do_get_alarm_definition(self, req, res, id): - res.status = '501 Not Implemented' + res.status = '501 Not Implemented' @resource_api.Restify('/v2.0/alarm-definitions/{id}', method='put') def do_put_alarm_definitions(self, req, res, id): - res.status = '501 Not Implemented' + res.status = '501 Not Implemented' @resource_api.Restify('/v2.0/alarm-definitions', method='get') def do_get_alarm_definitions(self, req, res): @@ -112,4 +96,4 @@ class V2API(object): @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' \ No newline at end of file + res.status = '501 Not Implemented' diff --git a/monasca/api/monasca_notifications_api_v2.py b/monasca/api/monasca_notifications_api_v2.py new file mode 100644 index 000000000..bfd0e6c8f --- /dev/null +++ b/monasca/api/monasca_notifications_api_v2.py @@ -0,0 +1,46 @@ +# 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. + +from monasca.common import resource_api +from monasca.openstack.common import log + + +LOG = log.getLogger(__name__) + + +class NotificationsV2API(object): + + def __init__(self, global_conf): + LOG.debug('initializing V2API!') + self.global_conf = global_conf + + @resource_api.Restify('/v2.0/notification-methods', method='post') + def do_post_notification_methods(self, req, res): + res.status = '501 Not Implemented' + + @resource_api.Restify('/v2.0/notification-methods/{id}', method='delete') + def do_delete_notification_methods(self, req, res, id): + res.status = '501 Not Implemented' + + @resource_api.Restify('/v2.0/notification-methods', method='get') + def do_get_notification_methods(self, req, res): + res.status = '501 Not Implemented' + + @resource_api.Restify('/v2.0/notification-methods/{id}', method='get') + def do_get_notification_method(self, req, res, id): + res.status = '501 Not Implemented' + + @resource_api.Restify('/v2.0/notification-methods/{id}', method='put') + def do_put_notification_methods(self, req, res, id): + res.status = '501 Not Implemented' diff --git a/monasca/api/server.py b/monasca/api/server.py index 1c6ed317c..9b5481ace 100644 --- a/monasca/api/server.py +++ b/monasca/api/server.py @@ -26,6 +26,7 @@ from wsgiref import simple_server METRICS_DISPATCHER_NAMESPACE = 'monasca.metrics_dispatcher' EVENTS_DISPATCHER_NAMESPACE = 'monasca.events_dispatcher' TRANSFORMS_DISPATCHER_NAMESPACE = 'monasca.transforms_dispatcher' +NOTIFICATIONS_DISPATCHER_NAMESPACE = 'monasca.notifications_dispatcher' LOG = log.getLogger(__name__) @@ -63,7 +64,8 @@ cfg.CONF.register_opts(messaging_opts, messaging_group) repositories_opts = [ cfg.StrOpt('metrics_driver', default='influxdb_metrics_repo', help='The repository driver to use for metrics'), cfg.StrOpt('events_driver', default='fake_events_repo', help='The repository driver to use for events'), - cfg.StrOpt('transforms_driver', default='mysql_transforms_repo', help='The repository driver to use for transforms') + cfg.StrOpt('transforms_driver', default='mysql_transforms_repo', help='The repository driver to use for transforms'), + cfg.StrOpt('notifications_driver', default='mysql_notifications_repo', help='The repository driver to use for notifications') ] repositories_group = cfg.OptGroup(name='repositories', title='repositories') @@ -161,45 +163,21 @@ def api_app(conf): # Create the application app = resource_api.ResourceAPI() - # load the metrics driver specified by dispatcher in the monasca.ini file - metrics_manager = driver.DriverManager(namespace=METRICS_DISPATCHER_NAMESPACE, - name=cfg.CONF.dispatcher.driver, - invoke_on_load=True, - invoke_args=[conf]) + # add the metrics resource + app.add_resource('metrics', METRICS_DISPATCHER_NAMESPACE, + cfg.CONF.dispatcher.driver, [conf]) - LOG.debug('Metrics dispatcher driver %s is loaded.' % cfg.CONF.dispatcher.driver) + # add the events resource + app.add_resource('events', EVENTS_DISPATCHER_NAMESPACE, + cfg.CONF.dispatcher.driver, [conf]) - # add the driver to the application - app.add_route(None, metrics_manager.driver) + # add the transforms resource + app.add_resource('transforms', TRANSFORMS_DISPATCHER_NAMESPACE, + cfg.CONF.dispatcher.driver, [conf]) - LOG.debug('Metrics dispatcher driver has been added to the routes!') - - - # load the events driver specified by dispatcher in the monasca.ini file - events_manager = driver.DriverManager(namespace=EVENTS_DISPATCHER_NAMESPACE, - name=cfg.CONF.dispatcher.driver, - invoke_on_load=True, - invoke_args=[conf]) - - LOG.debug('Events dispatcher driver %s is loaded.' % cfg.CONF.dispatcher.driver) - - # add the driver to the application - app.add_route(None, events_manager.driver) - - LOG.debug('Events dispatcher driver has been added to the routes!') - - # load the events driver specified by dispatcher in the monasca.ini file - transforms_manager = driver.DriverManager(namespace=TRANSFORMS_DISPATCHER_NAMESPACE, - name=cfg.CONF.dispatcher.driver, - invoke_on_load=True, - invoke_args=[conf]) - - LOG.debug('Transforms dispatcher driver %s is loaded.' % cfg.CONF.dispatcher.driver) - - # add the driver to the application - app.add_route(None, transforms_manager.driver) - - LOG.debug('Transforms dispatcher driver has been added to the routes!') + # add the notifications resource + app.add_resource('notifications', NOTIFICATIONS_DISPATCHER_NAMESPACE, + cfg.CONF.dispatcher.driver, [conf]) return app @@ -207,4 +185,4 @@ def api_app(conf): if __name__ == '__main__': wsgi_app = loadapp('config:etc/monasca.ini', relative_to=os.getcwd()) httpd = simple_server.make_server('127.0.0.1', 9000, wsgi_app) - httpd.serve_forever() \ No newline at end of file + httpd.serve_forever() diff --git a/monasca/common/repositories/mysql/notifications_repository.py b/monasca/common/repositories/mysql/notifications_repository.py new file mode 100644 index 000000000..533bdb4ab --- /dev/null +++ b/monasca/common/repositories/mysql/notifications_repository.py @@ -0,0 +1,133 @@ +# 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 datetime +from monasca.common.repositories import notifications_repository +from monasca.common.repositories import exceptions +from monasca.openstack.common import log +import peewee +import model + +LOG = log.getLogger(__name__) + + +class Notification_Method(model.Model): + id = peewee.TextField(36) + tenant_id = peewee.TextField(36) + name = peewee.TextField() + type = peewee.TextField() + address = peewee.TextField() + created_at = peewee.DateTimeField() + updated_at = peewee.DateTimeField() + + +class NotificationsRepository( + notifications_repository.NotificationsRepository): + + def notification_from_result(self, result): + notification = dict(id=result.id, + name=result.name, + type=result.type, + address=result.address) + return notification + + def exists(self, tenant_id, name): + try: + return (Notification_Method.select().where( + (Notification_Method.tenant_id == tenant_id) & ( + Notification_Method.name == name)).count() > 0) + except Exception as ex: + LOG.exception(ex) + raise exceptions.RepositoryException(ex) + + def create_notification( + self, id, tenant_id, name, notification_type, address): + try: + now = datetime.datetime.utcnow() + q = Notification_Method.create( + id=id, + tenant_id=tenant_id, + name=name, + notification_type=notification_type, + address=address, + created_at=now, + updated_at=now) + except Exception as ex: + LOG.exception(ex) + raise exceptions.RepositoryException(ex) + + def list_notifications(self, tenant_id): + try: + q = Notification_Method.select().where( + Notification_Method.tenant_id == tenant_id) + results = q.execute() + + notifications = [] + notifications = [ + self.notification_from_result(result) for result in results] + return notifications + except Exception as ex: + LOG.exception(ex) + raise exceptions.RepositoryException(ex) + + def delete_notification(self, tenant_id, notification_id): + num_rows_deleted = 0 + + try: + q = Notification_Method.delete().where( + (Notification_Method.tenant_id == tenant_id) & ( + Notification_Method.id == notification_id)) + num_rows_deleted = q.execute() + except Exception as ex: + LOG.exception(ex) + raise exceptions.RepositoryException(ex) + + if num_rows_deleted < 1: + raise exceptions.DoesNotExistException() + + return + + def list_notification(self, tenant_id, notification_id): + try: + result = Notification_Method.get( + (Notification_Method.tenant_id == tenant_id) & ( + Notification_Method.id == notification_id)) + return (self.notification_from_result(result)) + except Notification_Method.DoesNotExist as e: + raise exceptions.DoesNotExistException(str(e)) + except Exception as ex: + LOG.exception(ex) + raise exceptions.RepositoryException(ex) + + 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, + type=notification_type, + address=address, + created_at=now, + updated_at=now).where( + (Notification_Method.tenant_id == tenant_id) & ( + Notification_Method.id == id)) + # Execute the query, updating the database. + num_rows_updated = q.execute() + except Exception as ex: + LOG.exception(ex) + raise exceptions.RepositoryException(ex) + else: + if num_rows_updated == 0: + raise exceptions.DoesNotExistException('Not Found') diff --git a/monasca/common/repositories/notifications_repository.py b/monasca/common/repositories/notifications_repository.py new file mode 100644 index 000000000..e97bc0712 --- /dev/null +++ b/monasca/common/repositories/notifications_repository.py @@ -0,0 +1,42 @@ +# 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 abc +import six + + +@six.add_metaclass(abc.ABCMeta) +class NotificationsRepository(object): + + @abc.abstractmethod + def create_notification(self, id, tenant_id, name, notification_type, + address): + return + + @abc.abstractmethod + def list_notifications(self, tenant_id): + return + + @abc.abstractmethod + def delete_notification(self, tenant_id, notification_id): + return + + @abc.abstractmethod + def list_notification(self, tenant_id, notification_id): + return + + @abc.abstractmethod + def update_notification(self, id, tenant_id, name, notification_type, + address): + return diff --git a/monasca/common/resource_api.py b/monasca/common/resource_api.py index 589de31dc..eeed252f2 100644 --- a/monasca/common/resource_api.py +++ b/monasca/common/resource_api.py @@ -18,13 +18,32 @@ import falcon from falcon import api_helpers from monasca.openstack.common import log - +from stevedore import driver RESOURCE_METHOD_FLAG = 'fab05a04-b861-4651-bd0c-9cb3eb9a6088' LOG = log.getLogger(__name__) +def init_driver(namespace, driver_name, drv_invoke_args=None): + """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) + """ + invoke_args_tuple = () + if drv_invoke_args: + invoke_args_tuple = drv_invoke_args + mgr = driver.DriverManager( + namespace = namespace, + name = driver_name, + invoke_on_load = True, + invoke_args = invoke_args_tuple + ) + return mgr.driver + + class Restify(object): def __init__(self, path='', method='GET'): if not path: @@ -41,7 +60,6 @@ class Restify(object): class ResourceAPI(falcon.API): - def add_route(self, uri_template, resource): """Associates uri patterns with resource methods. @@ -114,4 +132,21 @@ class ResourceAPI(falcon.API): path_maps[item][1])) except Exception: LOG.exception('Error occurred while adding the resource') - LOG.debug(self._routes) \ No newline at end of file + LOG.debug(self._routes) + + def add_resource(self, resource_name, namespace, driver_name, + invoke_args=None, 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) + :param invoke_args: args to pass to the driver (a tuple) + :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.' % + (resource_name, driver_name)) + self.add_route(uri, resource_driver) + LOG.debug('%s dispatcher driver has been added to the routes!' % + (resource_name)) \ No newline at end of file diff --git a/monasca/v2/common/schemas/notifications_request_body_schema.py b/monasca/v2/common/schemas/notifications_request_body_schema.py new file mode 100644 index 000000000..6ec08f564 --- /dev/null +++ b/monasca/v2/common/schemas/notifications_request_body_schema.py @@ -0,0 +1,36 @@ +# 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. + +from voluptuous import Schema +from voluptuous import Optional, Required, Any, All, Length +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))) +} + +request_body_schema = Schema(Any(notification_schema)) + + +def validate(msg): + try: + request_body_schema(msg) + except Exception as ex: + LOG.debug(ex) + raise exceptions.ValidationException(str(ex)) diff --git a/monasca/v2/reference/helpers.py b/monasca/v2/reference/helpers.py index ced8d6c7b..75fba2b47 100644 --- a/monasca/v2/reference/helpers.py +++ b/monasca/v2/reference/helpers.py @@ -20,7 +20,7 @@ from monasca.openstack.common import log 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__) @@ -32,16 +32,16 @@ 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 + """Determines if one or more of the X-ROLES is 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. :return: Returns True if in the list of authorized roles, otherwise False. - ''' + """ str_roles = req.get_header('X-ROLES') - if str_roles == None: + if str_roles is None: return False roles = str_roles.lower().split(',') for role in roles: @@ -51,15 +51,15 @@ 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. + """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. - :raises falcon.HTTPUnauthorized: - ''' + :raises falcon.HTTPUnauthorized + """ str_roles = req.get_header('X-ROLES') - if str_roles == None: + if str_roles is None: raise falcon.HTTPUnauthorized('Forbidden', 'Tenant does not have any roles', '') roles = str_roles.lower().split(',') @@ -72,21 +72,21 @@ def validate_authorization(req, authorized_roles): def get_tenant_id(req): - ''' - Returns the tenant ID in the HTTP request header. + """Returns the tenant ID in the HTTP request header. + :param req: HTTP request object. - ''' + """ return req.get_header('X-TENANT-ID') def get_cross_tenant_or_tenant_id(req, delegate_authorized_roles): - ''' - Evaluates whether the tenant ID or cross tenant ID should be returned. + """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) if 'tenant_id' in params: @@ -96,10 +96,10 @@ def get_cross_tenant_or_tenant_id(req, delegate_authorized_roles): def get_query_name(req): - ''' - Returns the query param "name" if supplied. + """Returns the query param "name" if supplied. + :param req: HTTP request object. - ''' + """ params = parse_query_string(req.query_string) name = '' if 'name' in params: @@ -108,12 +108,12 @@ def get_query_name(req): def get_query_dimensions(req): - ''' - Gets and parses the query param dimensions. + """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) dimensions = {} @@ -193,11 +193,11 @@ def get_query_period(req): def validate_query_name(name): - ''' - Validates the query param name. + """Validates the query param name. + :param name: Query param name. :raises falcon.HTTPBadRequest: If name is not valid. - ''' + """ try: metric_name_schema.validate(name) except schemas_exceptions.ValidationException as ex: @@ -206,13 +206,77 @@ def validate_query_name(name): def validate_query_dimensions(dimensions): - ''' - Validates the query param dimensions. + """Validates the query param dimensions. + :param dimensions: Query param dimensions. :raises falcon.HTTPBadRequest: If dimensions are not valid. - ''' + """ try: dimensions_schema.validate(dimensions) except schemas_exceptions.ValidationException as ex: LOG.debug(ex) - raise falcon.HTTPBadRequest('Bad request', ex.message) \ No newline at end of file + raise falcon.HTTPBadRequest('Bad request', ex.message) + + +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 + """ + href = uri + '/' + resource_id + link_dict = dict(href=href, rel=rel) + return link_dict + + +def add_links_to_resource(resource, uri): + """Adds links to the given resource dictionary. + + :param resource: the resource dictionary you wish to add links. + :param uri: the http request.uri. + """ + resource['links'] = [get_link(uri, resource['id'])] + return resource + + +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. + """ + for resource in resourcelist: + add_links_to_resource(resource, uri) + return resourcelist + + +def read_http_resource(req): + """Read from http request and return json. + + :param req: the http request. + """ + try: + msg = req.stream.read() + json_msg = simplejson.loads(msg) + return json_msg + except ValueError as ex: + LOG.debug(ex) + raise falcon.HTTPBadRequest( + 'Bad request', + 'Request body is not valid JSON') + + +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 + """ + msg = 'No %s method exists for tenant_id = %s id = %s' % ( + resource_name, tenant_id, resource_id) + raise falcon.HTTPError( + status='404 Not Found', + title='Not Found', + description=msg, + code=404) diff --git a/monasca/v2/reference/notifications.py b/monasca/v2/reference/notifications.py new file mode 100644 index 000000000..dfbfcaa4b --- /dev/null +++ b/monasca/v2/reference/notifications.py @@ -0,0 +1,223 @@ +# 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. + +# 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.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.reference import helpers + +LOG = log.getLogger(__name__) + + +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._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 + """ + try: + schemas_notifications.validate(notification) + except schemas_exceptions.ValidationException as ex: + LOG.debug(ex) + raise falcon.HTTPBadRequest('Bad request', ex.message) + + def _create_notification(self, id, tenant_id, notification): + """Store the notification using the repository. + + :param notification: A notification object. + :raises: falcon.HTTPServiceUnavailable,falcon.HTTPConflict + """ + try: + name = notification['name'] + notification_type = notification['type'].upper() + 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) + self._notifications_repo.create_notification( + id, + tenant_id, + name, + notification_type, + address) + except repository_exceptions.RepositoryException as ex: + LOG.error(ex) + raise falcon.HTTPInternalServerError( + 'Service unavailable', + ex.message) + + 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) + """ + try: + name = notification['name'] + notification_type = notification['type'].upper() + address = notification['address'] + self._notifications_repo.update_notification( + id, + tenant_id, + name, + notification_type, + address) + except repository_exceptions.DoesNotExistException: + helpers.raise_not_found_exception('notification', id, tenant_id) + except repository_exceptions.RepositoryException as ex: + LOG.error(ex) + raise falcon.HTTPInternalServerError( + 'Service unavailable', + ex.message) + + def _create_notification_response(self, id, notification, uri): + name = notification['name'] + notification_type = notification['type'].upper() + address = notification['address'] + response = { + 'id': id, + 'name': name, + 'type': notification_type, + 'address': address + } + return json.dumps(helpers.add_links_to_resource(response, uri)) + + def _list_notifications(self, tenant_id, uri): + """Lists all notifications for this tenant id. + + :param tenant_id: The tenant id. + :raises: falcon.HTTPServiceUnavailable + """ + try: + notifications = self._notifications_repo.list_notifications( + tenant_id) + return json.dumps( + helpers.add_links_to_resource_list(notifications, uri)) + except repository_exceptions.RepositoryException as ex: + LOG.error(ex) + raise falcon.HTTPInternalServerError( + 'Service unavailable', + ex.message) + + 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): + """ + try: + notifications = self._notifications_repo.list_notification( + tenant_id, + notification_id) + return json.dumps( + helpers.add_links_to_resource(notifications, uri)) + except repository_exceptions.DoesNotExistException: + helpers.raise_not_found_exception('notification', notification_id, tenant_id) + except repository_exceptions.RepositoryException as ex: + LOG.error(ex) + raise falcon.HTTPInternalServerError( + 'Service unavailable', + ex.message) + + 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) + """ + try: + self._notifications_repo.delete_notification( + tenant_id, + notification_id) + except repository_exceptions.DoesNotExistException: + helpers.raise_not_found_exception('notification', notification_id, tenant_id) + except repository_exceptions.RepositoryException as ex: + LOG.error(ex) + raise falcon.HTTPInternalServerError( + 'Service unavailable', + ex.message) + + @resource_api.Restify('/v2.0/notification-methods', method='post') + def do_post_notification_methods(self, req, res): + helpers.validate_json_content_type(req) + helpers.validate_authorization(req, self._default_authorized_roles) + notification = helpers.read_http_resource(req) + self._validate_notification(notification) + id = uuidutils.generate_uuid() + tenant_id = helpers.get_tenant_id(req) + self._create_notification(id, tenant_id, notification) + res.body = self._create_notification_response( + id, + notification, + req.uri) + res.status = falcon.HTTP_200 + + @resource_api.Restify('/v2.0/notification-methods', method='get') + def do_get_notification_methods(self, req, res): + helpers.validate_authorization(req, self._default_authorized_roles) + tenant_id = helpers.get_tenant_id(req) + res.body = self._list_notifications(tenant_id, req.uri) + res.status = falcon.HTTP_200 + + @resource_api.Restify('/v2.0/notification-methods/{id}', method='delete') + def do_delete_notification_methods(self, req, res, id): + helpers.validate_authorization(req, self._default_authorized_roles) + tenant_id = helpers.get_tenant_id(req) + self._delete_notification(tenant_id, id) + res.status = falcon.HTTP_204 + + @resource_api.Restify('/v2.0/notification-methods/{id}', method='get') + def do_get_notification_method(self, req, res, id): + helpers.validate_authorization(req, self._default_authorized_roles) + tenant_id = helpers.get_tenant_id(req) + res.body = self._list_notification(tenant_id, id, req.uri) + res.status = falcon.HTTP_200 + + @resource_api.Restify('/v2.0/notification-methods/{id}', method='put') + def do_put_notification_methods(self, req, res, id): + helpers.validate_json_content_type(req) + helpers.validate_authorization(req, self._default_authorized_roles) + notification = helpers.read_http_resource(req) + self._validate_notification(notification) + tenant_id = helpers.get_tenant_id(req) + self._update_notification(id, tenant_id, notification) + res.body = self._create_notification_response( + id, + notification, + req.uri) + res.status = falcon.HTTP_200 diff --git a/setup.cfg b/setup.cfg index 179d3a2b9..fbec8ee63 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,6 +43,9 @@ monasca.events_dispatcher = monasca.transforms_dispatcher = v2_reference = monasca.v2.reference.transforms:Transforms +monasca.notifications_dispatcher = + v2_reference = monasca.v2.reference.notifications:Notifications + paste.filter_factory = login = monasca.middleware.login:filter_factory inspector = monasca.middleware.inspector:filter_factory @@ -59,6 +62,7 @@ monasca.repositories = influxdb_metrics_repo = monasca.common.repositories.influxdb.metrics_repository:MetricsRepository fake_events_repo = monasca.common.repositories.fake.events_repository:EventsRepository mysql_transforms_repo = monasca.common.repositories.mysql.transforms_repository:TransformsRepository + mysql_notifications_repo = monasca.common.repositories.mysql.notifications_repository:NotificationsRepository [pbr] warnerrors = True