summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--heat/engine/resources/openstack/monasca/__init__.py0
-rw-r--r--heat/engine/resources/openstack/monasca/notification.py115
-rw-r--r--heat/tests/openstack/monasca/__init__.py0
-rw-r--r--heat/tests/openstack/monasca/test_notification.py177
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)