From bb02466fe8250290a4ccbbb4941ed32b5a9cffce Mon Sep 17 00:00:00 2001 From: Ethan Lynn Date: Mon, 15 Feb 2016 17:24:31 +0800 Subject: [PATCH] Add OS::Senlin::Receiver Resource Add OS::Senlin::Receiver resource to heat, following is an example: recv: type: OS::Senlin::Receiver properties: cluster: c1 action: CLUSTER_SCALE_OUT type: webhook Change-Id: I255ffe215876df5c2410962a3e38016f8e7f690e blueprint: senlin-resources --- .../resources/openstack/senlin/receiver.py | 131 +++++++++++++++++ heat/tests/openstack/senlin/test_receiver.py | 135 ++++++++++++++++++ 2 files changed, 266 insertions(+) create mode 100644 heat/engine/resources/openstack/senlin/receiver.py create mode 100644 heat/tests/openstack/senlin/test_receiver.py diff --git a/heat/engine/resources/openstack/senlin/receiver.py b/heat/engine/resources/openstack/senlin/receiver.py new file mode 100644 index 0000000000..b383d14fac --- /dev/null +++ b/heat/engine/resources/openstack/senlin/receiver.py @@ -0,0 +1,131 @@ +# +# 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 attributes +from heat.engine import constraints +from heat.engine import properties +from heat.engine import resource +from heat.engine import support + + +class Receiver(resource.Resource): + """A resource that creates Senlin Receiver. + + Receiver is an abstract resource created at the senlin engine + that can be used to hook the engine to some external event/alarm sources. + """ + + support_status = support.SupportStatus(version='6.0.0') + + default_client_name = 'senlin' + + PROPERTIES = ( + CLUSTER, ACTION, NAME, TYPE, PARAMS, + ) = ( + 'cluster', 'action', 'name', 'type', 'params', + ) + + ATTRIBUTES = ( + ATTR_CHANNEL, + ) = ( + 'channel', + ) + + _ACTIONS = ( + CLUSTER_SCALE_OUT, CLUSTER_SCALE_IN, + ) = ( + 'CLUSTER_SCALE_OUT', 'CLUSTER_SCALE_IN', + ) + + _TYPES = ( + WEBHOOK, + ) = ( + 'webhook', + ) + + properties_schema = { + CLUSTER: properties.Schema( + properties.Schema.STRING, + _('Name or ID of target cluster.'), + required=True, + constraints=[ + constraints.CustomConstraint('senlin.cluster') + ] + ), + ACTION: properties.Schema( + properties.Schema.STRING, + _('The action to be executed when the receiver is signaled.'), + required=True, + constraints=[ + constraints.AllowedValues(_ACTIONS) + ] + ), + NAME: properties.Schema( + properties.Schema.STRING, + _('Name of the senlin receiver. By default, ' + 'physical resource name is used.'), + ), + TYPE: properties.Schema( + properties.Schema.STRING, + _('Type of receiver.'), + default=WEBHOOK, + constraints=[ + constraints.AllowedValues(_TYPES) + ] + ), + PARAMS: properties.Schema( + properties.Schema.MAP, + _('The parameters passed to action when the receiver ' + 'is signaled.'), + ), + } + + attributes_schema = { + ATTR_CHANNEL: attributes.Schema( + _("The channel for receiving signals."), + type=attributes.Schema.MAP + ), + } + + def handle_create(self): + params = { + 'name': (self.properties[self.NAME] or + self.physical_resource_name()), + 'cluster_id': self.properties[self.CLUSTER], + 'type': self.properties[self.TYPE], + 'action': self.properties[self.ACTION], + 'params': self.properties[self.PARAMS], + } + + recv = self.client().create_receiver(**params) + self.resource_id_set(recv.id) + + def handle_delete(self): + if self.resource_id is not None: + with self.client_plugin().ignore_not_found: + self.client().delete_receiver(self.resource_id) + + def _show_resource(self): + recv = self.client().get_receiver(self.resource_id) + return recv.to_dict() + + def _resolve_attribute(self, name): + recv = self.client().get_receiver(self.resource_id) + return getattr(recv, name, None) + + +def resource_mapping(): + return { + 'OS::Senlin::Receiver': Receiver + } diff --git a/heat/tests/openstack/senlin/test_receiver.py b/heat/tests/openstack/senlin/test_receiver.py new file mode 100644 index 0000000000..baa0549171 --- /dev/null +++ b/heat/tests/openstack/senlin/test_receiver.py @@ -0,0 +1,135 @@ +# +# 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 senlinclient.common import exc + +from heat.common import template_format +from heat.engine.clients.os import senlin +from heat.engine.resources.openstack.senlin import receiver as sr +from heat.engine import scheduler +from heat.tests import common +from heat.tests import utils + + +receiver_stack_template = """ +heat_template_version: 2016-04-08 +description: Senlin Receiver Template +resources: + senlin-receiver: + type: OS::Senlin::Receiver + properties: + name: SenlinReceiver + cluster: fake_cluster + action: CLUSTER_SCALE_OUT + type: webhook + params: + foo: bar +""" + + +class FakeReceiver(object): + def __init__(self, id='some_id'): + self.id = id + self.name = "SenlinReceiver" + self.cluster_id = "fake_cluster" + self.action = "CLUSTER_SCALE_OUT" + self.channel = {'alarm_url': "http://foo.bar/webhooks/fake_url"} + + def to_dict(self): + return { + 'id': self.id, + 'name': self.name, + 'cluster_id': self.cluster_id, + 'action': self.action, + 'channel': self.channel, + 'actor': {'trust_id': ['fake_trust_id']} + } + + +class SenlinReceiverTest(common.HeatTestCase): + def setUp(self): + super(SenlinReceiverTest, self).setUp() + self.senlin_mock = mock.MagicMock() + self.patchobject(sr.Receiver, 'client', + return_value=self.senlin_mock) + self.patchobject(senlin.ClusterConstraint, 'validate', + return_value=True) + self.fake_r = FakeReceiver() + self.t = template_format.parse(receiver_stack_template) + + def _init_recv(self, template): + self.stack = utils.parse_stack(template) + recv = self.stack['senlin-receiver'] + return recv + + def _create_recv(self, template): + recv = self._init_recv(template) + self.senlin_mock.create_receiver.return_value = self.fake_r + self.senlin_mock.get_receiver.return_value = self.fake_r + scheduler.TaskRunner(recv.create)() + self.assertEqual((recv.CREATE, recv.COMPLETE), + recv.state) + self.assertEqual(self.fake_r.id, recv.resource_id) + return recv + + def test_recv_create_success(self): + self._create_recv(self.t) + expect_kwargs = { + 'name': 'SenlinReceiver', + 'cluster_id': 'fake_cluster', + 'action': 'CLUSTER_SCALE_OUT', + 'type': 'webhook', + 'params': {'foo': 'bar'}, + } + self.senlin_mock.create_receiver.assert_called_once_with( + **expect_kwargs) + + def test_recv_delete_success(self): + self.senlin_mock.delete_receiver.return_value = None + recv = self._create_recv(self.t) + scheduler.TaskRunner(recv.delete)() + self.senlin_mock.delete_receiver.assert_called_once_with( + recv.resource_id) + + def test_recv_delete_not_found(self): + self.senlin_mock.delete_receiver.side_effect = [ + exc.sdkexc.ResourceNotFound(http_status=404) + ] + recv = self._create_recv(self.t) + scheduler.TaskRunner(recv.delete)() + self.senlin_mock.delete_receiver.assert_called_once_with( + recv.resource_id) + + def test_resource_mapping(self): + mapping = sr.resource_mapping() + self.assertEqual(1, len(mapping)) + self.assertEqual(sr.Receiver, + mapping['OS::Senlin::Receiver']) + + def test_cluster_resolve_attribute(self): + excepted_show = { + 'id': 'some_id', + 'name': 'SenlinReceiver', + 'cluster_id': 'fake_cluster', + 'action': 'CLUSTER_SCALE_OUT', + 'channel': {'alarm_url': "http://foo.bar/webhooks/fake_url"}, + 'actor': {'trust_id': ['fake_trust_id']} + } + recv = self._create_recv(self.t) + self.assertEqual(self.fake_r.channel, + recv._resolve_attribute('channel')) + self.assertEqual(excepted_show, + recv._show_resource())