diff options
-rw-r--r-- | heat/engine/resources/openstack/monasca/__init__.py | 0 | ||||
-rw-r--r-- | heat/engine/resources/openstack/monasca/notification.py | 115 | ||||
-rw-r--r-- | heat/tests/openstack/monasca/__init__.py | 0 | ||||
-rw-r--r-- | heat/tests/openstack/monasca/test_notification.py | 177 |
4 files changed, 292 insertions, 0 deletions
diff --git a/heat/engine/resources/openstack/monasca/__init__.py b/heat/engine/resources/openstack/monasca/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/heat/engine/resources/openstack/monasca/__init__.py diff --git a/heat/engine/resources/openstack/monasca/notification.py b/heat/engine/resources/openstack/monasca/notification.py new file mode 100644 index 000000000..be4df0071 --- /dev/null +++ b/heat/engine/resources/openstack/monasca/notification.py @@ -0,0 +1,115 @@ +# +# 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 heat.common.i18n import _ +from heat.engine import clients +from heat.engine import constraints +from heat.engine import properties +from heat.engine import resource +from heat.engine import support + + +class MonascaNotification(resource.Resource): + """Heat Template Resource for Monasca Notification.""" + + support_status = support.SupportStatus( + version='5.0.0', + status=support.UNSUPPORTED) + + default_client_name = 'monasca' + + NOTIFICATION_TYPES = ( + EMAIL, WEBHOOK, PAGERDUTY + ) = ( + 'email', 'webhook', 'pagerduty' + ) + + PROPERTIES = ( + NAME, TYPE, ADDRESS + ) = ( + 'name', 'type', 'address' + ) + + properties_schema = { + NAME: properties.Schema( + properties.Schema.STRING, + _('Name of the notification. By default, physical resource name ' + 'is used.'), + update_allowed=True + ), + TYPE: properties.Schema( + properties.Schema.STRING, + _('Type of the notification.'), + update_allowed=True, + required=True, + constraints=[constraints.AllowedValues( + NOTIFICATION_TYPES + )] + ), + ADDRESS: properties.Schema( + properties.Schema.STRING, + _('Address of the notification. It could be a valid email ' + 'address, url or service key based on notification type.'), + update_allowed=True, + required=True, + ) + } + + def handle_create(self): + args = dict( + name=(self.properties[self.NAME] or + self.physical_resource_name()), + type=self.properties[self.TYPE], + address=self.properties[self.ADDRESS] + ) + + notification = self.client().notifications.create(**args) + self.resource_id_set(notification['id']) + + def handle_update(self, + prop_diff, + json_snippet=None, + tmpl_diff=None): + args = dict(notification_id=self.resource_id) + + args['name'] = (prop_diff.get(self.NAME) or + self.properties[self.NAME]) + + args['type'] = (prop_diff.get(self.TYPE) or + self.properties[self.TYPE]) + + args['address'] = (prop_diff.get(self.ADDRESS) or + self.properties[self.ADDRESS]) + + self.client().notifications.update(**args) + + def handle_delete(self): + if self.resource_id is not None: + try: + self.client().notifications.delete( + notification_id=self.resource_id) + except Exception as ex: + self.client_plugin().ignore_not_found(ex) + + +def resource_mapping(): + return { + 'OS::Monasca::Notification': MonascaNotification + } + + +def available_resource_mapping(): + if not clients.has_client(MonascaNotification.default_client_name): + return {} + + return resource_mapping() diff --git a/heat/tests/openstack/monasca/__init__.py b/heat/tests/openstack/monasca/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/heat/tests/openstack/monasca/__init__.py diff --git a/heat/tests/openstack/monasca/test_notification.py b/heat/tests/openstack/monasca/test_notification.py new file mode 100644 index 000000000..10a503452 --- /dev/null +++ b/heat/tests/openstack/monasca/test_notification.py @@ -0,0 +1,177 @@ +# +# 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 mock + +from heat.engine.clients.os import monasca as client_plugin +from heat.engine import resource +from heat.engine.resources.openstack.monasca import notification +from heat.engine import stack +from heat.engine import template +from heat.tests import common +from heat.tests import utils + + +sample_template = { + 'heat_template_version': '2015-10-15', + 'resources': { + 'test_resource': { + 'type': 'OS::Monasca::Notification', + 'properties': { + 'name': 'test-notification', + 'type': 'webhook', + 'address': 'http://localhost:80/' + } + } + } +} + +RESOURCE_TYPE = 'OS::Monasca::Notification' + + +class MonascaNotification(notification.MonascaNotification): + ''' + Monasca service is not available by default. So, this class overrides + the is_service_available to return True + ''' + @classmethod + def is_service_available(cls, context): + return True + + +class MonascaNotificationTest(common.HeatTestCase): + + def setUp(self): + super(MonascaNotificationTest, self).setUp() + + self.ctx = utils.dummy_context() + # As monascaclient is not part of requirements.txt, RESOURCE_TYPE is + # not registered by default. For testing, its registered here + resource._register_class(RESOURCE_TYPE, + MonascaNotification) + self.stack = stack.Stack( + self.ctx, 'test_stack', + template.Template(sample_template) + ) + + self.test_resource = self.stack['test_resource'] + + # Mock client plugin + self.test_client_plugin = mock.MagicMock() + self.test_resource.client_plugin = mock.MagicMock( + return_value=self.test_client_plugin) + + # Mock client + self.test_client = mock.MagicMock() + self.test_resource.client = mock.MagicMock( + return_value=self.test_client) + + def _get_mock_resource(self): + value = dict(id='477e8273-60a7-4c41-b683-fdb0bc7cd152') + + return value + + def test_resource_handle_create(self): + mock_notification_create = self.test_client.notifications.create + mock_resource = self._get_mock_resource() + mock_notification_create.return_value = mock_resource + + # validate the properties + self.assertEqual( + 'test-notification', + self.test_resource.properties.get( + notification.MonascaNotification.NAME)) + self.assertEqual( + 'webhook', + self.test_resource.properties.get( + notification.MonascaNotification.TYPE)) + self.assertEqual( + 'http://localhost:80/', + self.test_resource.properties.get( + notification.MonascaNotification.ADDRESS)) + + self.test_resource.data_set = mock.Mock() + self.test_resource.handle_create() + + args = dict( + name='test-notification', + type='webhook', + address='http://localhost:80/' + ) + + mock_notification_create.assert_called_once_with(**args) + + # validate physical resource id + self.assertEqual(mock_resource['id'], self.test_resource.resource_id) + + def test_resource_handle_update(self): + mock_notification_update = self.test_client.notifications.update + self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151' + + prop_diff = {notification.MonascaNotification.ADDRESS: + 'http://localhost:1234/', + notification.MonascaNotification.NAME: 'name-updated', + notification.MonascaNotification.TYPE: 'webhook'} + + self.test_resource.handle_update(json_snippet=None, + tmpl_diff=None, + prop_diff=prop_diff) + + args = dict( + notification_id=self.test_resource.resource_id, + name='name-updated', + type='webhook', + address='http://localhost:1234/' + ) + mock_notification_update.assert_called_once_with(**args) + + def test_resource_handle_delete(self): + mock_notification_delete = self.test_client.notifications.delete + self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151' + mock_notification_delete.return_value = None + + self.assertIsNone(self.test_resource.handle_delete()) + mock_notification_delete.assert_called_once_with( + notification_id=self.test_resource.resource_id + ) + + def test_resource_handle_delete_resource_id_is_none(self): + self.test_resource.resource_id = None + self.assertIsNone(self.test_resource.handle_delete()) + + def test_resource_handle_delete_not_found(self): + # TODO(skraynev): remove it when monasca client will be + # merged in global requirements + class NotFound(Exception): + pass + + client_plugin.monasca_exc = mock.Mock() + client_plugin.monasca_exc.NotFound = NotFound + + self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151' + mock_notification_delete = self.test_client.notifications.delete + mock_notification_delete.side_effect = ( + client_plugin.monasca_exc.NotFound) + self.assertIsNone(self.test_resource.handle_delete()) + self.assertEqual(1, + self.test_client_plugin.ignore_not_found.call_count) + e_type = type(self.test_client_plugin.ignore_not_found.call_args[0][0]) + self.assertEqual(type(client_plugin.monasca_exc.NotFound()), e_type) + + def test_resource_mapping(self): + mapping = notification.resource_mapping() + self.assertEqual(1, len(mapping)) + self.assertEqual(notification.MonascaNotification, + mapping[RESOURCE_TYPE]) + self.assertIsInstance(self.test_resource, + notification.MonascaNotification) |