diff --git a/heat/engine/resources/openstack/heat/software_deployment.py b/heat/engine/resources/openstack/heat/software_deployment.py index 0b87aea57a..b782e87580 100644 --- a/heat/engine/resources/openstack/heat/software_deployment.py +++ b/heat/engine/resources/openstack/heat/software_deployment.py @@ -13,7 +13,6 @@ import copy import six -import uuid from oslo_config import cfg from oslo_log import log as logging @@ -315,63 +314,6 @@ class SoftwareDeployment(signal_responder.SignalResponder): def _build_derived_options(self, action, source): return source.get(sc.SoftwareConfig.OPTIONS) - def _get_temp_url(self): - put_url = self.data().get('signal_temp_url') - if put_url: - return put_url - - container = self.physical_resource_name() - object_name = str(uuid.uuid4()) - - self.client('swift').put_container(container) - - put_url = self.client_plugin('swift').get_temp_url( - container, object_name) - self.data_set('signal_temp_url', put_url) - self.data_set('signal_object_name', object_name) - - self.client('swift').put_object( - container, object_name, '') - return put_url - - def _get_queue_id(self): - queue_id = self.data().get('signal_queue_id') - if queue_id: - return queue_id - - queue_id = self.physical_resource_name() - zaqar = self.client('zaqar') - zaqar.queue(queue_id).ensure_exists() - self.data_set('signal_queue_id', queue_id) - return queue_id - - def _delete_temp_url(self): - object_name = self.data().get('signal_object_name') - if not object_name: - return - try: - container = self.physical_resource_name() - swift = self.client('swift') - swift.delete_object(container, object_name) - headers = swift.head_container(container) - if int(headers['x-container-object-count']) == 0: - swift.delete_container(container) - except Exception as ex: - self.client_plugin('swift').ignore_not_found(ex) - self.data_delete('signal_object_name') - self.data_delete('signal_temp_url') - - def _delete_queue(self): - queue_id = self.data().get('signal_queue_id') - if not queue_id: - return - zaqar = self.client('zaqar') - try: - zaqar.queue(queue_id).delete() - except Exception as ex: - self.client_plugin('zaqar').ignore_not_found(ex) - self.data_delete('signal_queue_id') - def _build_derived_inputs(self, action, source): scl = sc.SoftwareConfig inputs = copy.deepcopy(source.get(scl.INPUTS)) or [] @@ -440,7 +382,7 @@ class SoftwareDeployment(signal_responder.SignalResponder): scl.DESCRIPTION: _('ID of signal to use for signaling ' 'output values'), scl.TYPE: 'String', - 'value': self._get_temp_url() + 'value': self._get_swift_signal_url() }) inputs.append({ scl.NAME: self.DEPLOY_SIGNAL_VERB, @@ -450,31 +392,32 @@ class SoftwareDeployment(signal_responder.SignalResponder): 'value': 'PUT' }) elif self._signal_transport_heat() or self._signal_transport_zaqar(): + creds = self._get_heat_signal_credentials() inputs.extend([{ scl.NAME: self.DEPLOY_AUTH_URL, scl.DESCRIPTION: _('URL for API authentication'), scl.TYPE: 'String', - 'value': self.keystone().v3_endpoint + 'value': creds['auth_url'] }, { scl.NAME: self.DEPLOY_USERNAME, scl.DESCRIPTION: _('Username for API authentication'), scl.TYPE: 'String', - 'value': self.physical_resource_name(), + 'value': creds['username'] }, { scl.NAME: self.DEPLOY_USER_ID, scl.DESCRIPTION: _('User ID for API authentication'), scl.TYPE: 'String', - 'value': self._get_user_id(), + 'value': creds['user_id'] }, { scl.NAME: self.DEPLOY_PASSWORD, scl.DESCRIPTION: _('Password for API authentication'), scl.TYPE: 'String', - 'value': self.password + 'value': creds['password'] }, { scl.NAME: self.DEPLOY_PROJECT_ID, scl.DESCRIPTION: _('ID of project for API authentication'), scl.TYPE: 'String', - 'value': self.stack.stack_user_project_id + 'value': creds['project_id'] }]) if self._signal_transport_zaqar(): inputs.append({ @@ -482,31 +425,14 @@ class SoftwareDeployment(signal_responder.SignalResponder): scl.DESCRIPTION: _('ID of queue to use for signaling ' 'output values'), scl.TYPE: 'String', - 'value': self._get_queue_id() + 'value': self._get_zaqar_signal_queue_id() }) return inputs def handle_create(self): - if self._signal_transport_cfn(): - self._create_user() - self._create_keypair() - if self._signal_transport_heat() or self._signal_transport_zaqar(): - self.password = uuid.uuid4().hex - self._create_user() return self._handle_action(self.CREATE) - @property - def password(self): - return self.data().get('password') - - @password.setter - def password(self, password): - if password is None: - self.data_delete('password') - else: - self.data_set('password', password, True) - def check_create_complete(self, sd): if not sd: return True @@ -536,15 +462,8 @@ class SoftwareDeployment(signal_responder.SignalResponder): return True def _delete_resource(self): - if self._signal_transport_cfn(): - self._delete_ec2_signed_url() - self._delete_user() - elif self._signal_transport_heat(): - self._delete_user() - elif self._signal_transport_temp_url(): - self._delete_temp_url() - elif self._signal_transport_zaqar(): - self._delete_queue() + self._delete_signals() + self._delete_user() derived_config_id = None if self.resource_id is not None: diff --git a/heat/engine/resources/signal_responder.py b/heat/engine/resources/signal_responder.py index 5693bf5968..2579247fd0 100644 --- a/heat/engine/resources/signal_responder.py +++ b/heat/engine/resources/signal_responder.py @@ -11,6 +11,8 @@ # License for the specific language governing permissions and limitations # under the License. +import uuid + from keystoneclient.contrib.ec2 import utils as ec2_utils from oslo_config import cfg from oslo_log import log as logging @@ -36,17 +38,42 @@ class SignalResponder(stack_user.StackUser): # API operations as a consequence of handling a signal requires_deferred_auth = True - def handle_create(self): - super(SignalResponder, self).handle_create() - self._create_keypair() - def handle_delete(self): + self._delete_signals() super(SignalResponder, self).handle_delete() + + def _delete_signals(self): self._delete_ec2_signed_url() self._delete_heat_signal_url() + self._delete_swift_signal_url() + self._delete_zaqar_signal_queue() - def _delete_ec2_signed_url(self): - self.data_delete('ec2_signed_url') + @property + def password(self): + return self.data().get('password') + + @password.setter + def password(self, password): + if password is None: + self.data_delete('password') + else: + self.data_set('password', password, True) + + def _get_heat_signal_credentials(self): + """Return OpenStack credentials that can be used to send a signal. + + These credentials are for the user associated with this resource in + the heat stack user domain. + """ + if self._get_user_id() is None: + if self.password is None: + self.password = uuid.uuid4().hex + self._create_user() + return {'auth_url': self.keystone().v3_endpoint, + 'username': self.physical_resource_name(), + 'user_id': self._get_user_id(), + 'password': self.password, + 'project_id': self.stack.stack_user_project_id} def _get_ec2_signed_url(self, signal_type=SIGNAL): """Create properly formatted and pre-signed URL. @@ -65,9 +92,19 @@ class SignalResponder(stack_user.StackUser): secret_key = self.data().get('secret_key') if not access_key or not secret_key: - LOG.warn(_LW('Cannot generate signed url, ' - 'no stored access/secret key')) - return + if self.id is None or self.action == self.DELETE: + # it is either too early or too late to do this + return + if self._get_user_id() is None: + self._create_user() + self._create_keypair() + access_key = self.data().get('access_key') + secret_key = self.data().get('secret_key') + + if not access_key or not secret_key: + LOG.warn(_LW('Cannot generate signed url, ' + 'unable to create keypair')) + return config_url = cfg.CONF.heat_waitcondition_server_url if config_url: @@ -106,14 +143,23 @@ class SignalResponder(stack_user.StackUser): self.data_set('ec2_signed_url', url) return url - def _delete_heat_signal_url(self): - self.data_delete('heat_signal_url') + def _delete_ec2_signed_url(self): + self.data_delete('ec2_signed_url') + self._delete_keypair() def _get_heat_signal_url(self): + """Return a heat-api signal URL for this resource. + + This URL is not pre-signed, valid user credentials are required. + """ stored = self.data().get('heat_signal_url') if stored is not None: return stored + if self.id is None or self.action == self.DELETE: + # it is either too early or too late to do this + return + url = self.client_plugin('heat').get_heat_url() host_url = urlparse.urlparse(url) path = self.identifier().url_path() @@ -123,3 +169,84 @@ class SignalResponder(stack_user.StackUser): self.data_set('heat_signal_url', url) return url + + def _delete_heat_signal_url(self): + self.data_delete('heat_signal_url') + + def _get_swift_signal_url(self): + """Create properly formatted and pre-signed Swift signal URL. + + This uses a Swift pre-signed temp_url. + """ + put_url = self.data().get('swift_signal_url') + if put_url: + return put_url + + if self.id is None or self.action == self.DELETE: + # it is either too early or too late to do this + return + + container = self.stack.id + object_name = self.physical_resource_name() + + self.client('swift').put_container(container) + + put_url = self.client_plugin('swift').get_temp_url( + container, object_name) + self.data_set('swift_signal_url', put_url) + self.data_set('swift_signal_object_name', object_name) + + self.client('swift').put_object( + container, object_name, '') + return put_url + + def _delete_swift_signal_url(self): + object_name = self.data().get('swift_signal_object_name') + if not object_name: + return + try: + container = self.physical_resource_name() + swift = self.client('swift') + swift.delete_object(container, object_name) + headers = swift.head_container(container) + if int(headers['x-container-object-count']) == 0: + swift.delete_container(container) + except Exception as ex: + self.client_plugin('swift').ignore_not_found(ex) + self.data_delete('swift_signal_object_name') + self.data_delete('swift_signal_url') + + def _get_zaqar_signal_queue_id(self): + """Return a zaqar queue_id for signaling this resource. + + This uses the created user for the credentials. + """ + queue_id = self.data().get('zaqar_signal_queue_id') + if queue_id: + return queue_id + + if self.id is None or self.action == self.DELETE: + # it is either too early or too late to do this + return + + if self._get_user_id() is None: + if self.password is None: + self.password = uuid.uuid4().hex + self._create_user() + + queue_id = self.physical_resource_name() + zaqar = self.client('zaqar') + zaqar.queue(queue_id).ensure_exists() + self.data_set('zaqar_signal_queue_id', queue_id) + return queue_id + + def _delete_zaqar_signal_queue(self): + queue_id = self.data().get('zaqar_signal_queue_id') + if not queue_id: + return + zaqar = self.client('zaqar') + try: + zaqar.queue(queue_id).delete() + except Exception as ex: + self.client_plugin('zaqar').ignore_not_found(ex) + self.data_delete('zaqar_signal_queue_id') diff --git a/heat/engine/resources/stack_user.py b/heat/engine/resources/stack_user.py index b4c034f7a2..d6aa26e6c1 100644 --- a/heat/engine/resources/stack_user.py +++ b/heat/engine/resources/stack_user.py @@ -25,7 +25,7 @@ LOG = logging.getLogger(__name__) class StackUser(resource.Resource): # Subclasses create a user, and optionally keypair associated with a - # resource in a stack. Users are created in the heat stack user domain + # resource in a stack. Users are created in the heat stack user domain # (in a project specific to the stack) def __init__(self, name, json_snippet, stack): super(StackUser, self).__init__(name, json_snippet, stack) @@ -34,6 +34,10 @@ class StackUser(resource.Resource): self._create_user() def _create_user(self): + if self.data().get('user_id'): + # a user has been created already + return + # Check for stack user project, create if not yet set if not self.stack.stack_user_project_id: project_id = self.keystone().create_stack_domain_project( @@ -85,6 +89,10 @@ class StackUser(resource.Resource): user_id = self._get_user_id() if user_id is None: return + + # the user is going away, so we want the keypair gone as well + self._delete_keypair() + try: self.keystone().delete_stack_domain_user( user_id=user_id, project_id=self.stack.stack_user_project_id) @@ -100,8 +108,7 @@ class StackUser(resource.Resource): self.keystone().delete_stack_user(user_id) except kc_exception.NotFound: pass - for data_key in ('credential_id', 'access_key', 'secret_key'): - self.data_delete(data_key) + self.data_delete('user_id') def handle_suspend(self): user_id = self._get_user_id() @@ -125,6 +132,9 @@ class StackUser(resource.Resource): # Subclasses may optionally call this in handle_create to create # an ec2 keypair associated with the user, the resulting keys are # stored in resource_data + if self.data().get('credential_id'): + return # a keypair was created already + user_id = self._get_user_id() kp = self.keystone().create_stack_domain_user_keypair( user_id=user_id, project_id=self.stack.stack_user_project_id) @@ -146,10 +156,12 @@ class StackUser(resource.Resource): def _delete_keypair(self): # Subclasses may optionally call this to delete a keypair created # via _create_keypair - user_id = self._get_user_id() credential_id = self.data().get('credential_id') if not credential_id: return + user_id = self._get_user_id() + if user_id is None: + return try: self.keystone().delete_stack_domain_user_keypair( diff --git a/heat/tests/fakes.py b/heat/tests/fakes.py index 7d86ffd1e4..02d5d9eda7 100644 --- a/heat/tests/fakes.py +++ b/heat/tests/fakes.py @@ -109,6 +109,7 @@ class FakeKeystoneClient(object): self.credential_id = credential_id self.token = auth_token self.context = context + self.v3_endpoint = 'http://localhost:5000/v3' class FakeCred(object): id = self.credential_id diff --git a/heat/tests/generic_resource.py b/heat/tests/generic_resource.py index 5466b109c2..5ec9437d25 100644 --- a/heat/tests/generic_resource.py +++ b/heat/tests/generic_resource.py @@ -132,8 +132,11 @@ class ResourceWithRequiredProps(GenericResource): class SignalResource(signal_responder.SignalResponder): - properties_schema = {} - attributes_schema = {'AlarmUrl': attributes.Schema('Get a signed webhook')} + properties_schema = { + 'signal_transport': properties.Schema(properties.Schema.STRING, + default='CFN_SIGNAL')} + attributes_schema = {'AlarmUrl': attributes.Schema('Get a signed webhook'), + 'signal': attributes.Schema('Get a signal')} def handle_create(self): super(SignalResource, self).handle_create() @@ -144,8 +147,22 @@ class SignalResource(signal_responder.SignalResponder): {'type': self.type(), 'details': details}) def _resolve_attribute(self, name): - if name == 'AlarmUrl' and self.resource_id is not None: - return six.text_type(self._get_ec2_signed_url()) + if self.resource_id is not None: + if self.properties['signal_transport'] == 'CFN_SIGNAL': + d = {'alarm_url': six.text_type(self._get_ec2_signed_url())} + elif self.properties['signal_transport'] == 'HEAT_SIGNAL': + d = self._get_heat_signal_credentials() + d['alarm_url'] = six.text_type(self._get_heat_signal_url()) + elif self.properties['signal_transport'] == 'TEMP_URL_SIGNAL': + d = {'alarm_url': six.text_type(self._get_swift_signal_url())} + elif self.properties['signal_transport'] == 'ZAQAR_SIGNAL': + d = self._get_heat_signal_credentials() + d['queue_id'] = six.text_type( + self._get_zaqar_signal_queue_id()) + if name == 'AlarmUrl': + return d['alarm_url'] + elif name == 'signal': + return d class StackUserResource(stack_user.StackUser): diff --git a/heat/tests/test_signal.py b/heat/tests/test_signal.py index fed3f6ff98..4082899cd8 100644 --- a/heat/tests/test_signal.py +++ b/heat/tests/test_signal.py @@ -12,14 +12,15 @@ # under the License. import datetime -import six +import uuid from keystoneclient import exceptions as kc_exceptions +import mox +import six from heat.common import exception from heat.common import template_format from heat.engine import resource -from heat.engine.resources import stack_user from heat.engine import scheduler from heat.engine import stack as parser from heat.engine import template @@ -30,13 +31,53 @@ from heat.tests import generic_resource from heat.tests import utils -test_template_signal = ''' +test_cfn_template_signal = ''' { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Just a test.", "Parameters" : {}, "Resources" : { - "signal_handler" : {"Type" : "SignalResourceType"}, + "signal_handler" : {"Type" : "SignalResourceType", + "Properties": {"signal_transport": "CFN_SIGNAL"}}, + "resource_X" : {"Type" : "GenericResourceType"} + } +} +''' + +test_heat_template_signal = ''' +{ + "AWSTemplateFormatVersion" : "2010-09-09", + "Description" : "Just a test.", + "Parameters" : {}, + "Resources" : { + "signal_handler" : {"Type" : "SignalResourceType", + "Properties": {"signal_transport": "HEAT_SIGNAL"}}, + "resource_X" : {"Type" : "GenericResourceType"} + } +} +''' + +test_swift_template_signal = ''' +{ + "AWSTemplateFormatVersion" : "2010-09-09", + "Description" : "Just a test.", + "Parameters" : {}, + "Resources" : { + "signal_handler" : {"Type" : "SignalResourceType", + "Properties": {"signal_transport": "TEMP_URL_SIGNAL"}}, + "resource_X" : {"Type" : "GenericResourceType"} + } +} +''' + +test_zaqar_template_signal = ''' +{ + "AWSTemplateFormatVersion" : "2010-09-09", + "Description" : "Just a test.", + "Parameters" : {}, + "Resources" : { + "signal_handler" : {"Type" : "SignalResourceType", + "Properties": {"signal_transport": "ZAQAR_SIGNAL"}}, "resource_X" : {"Type" : "GenericResourceType"} } } @@ -52,11 +93,12 @@ class SignalTest(common.HeatTestCase): def tearDown(self): super(SignalTest, self).tearDown() - def create_stack(self, stack_name='test_stack', stub=True): - templ = template.Template(template_format.parse(test_template_signal)) + def create_stack(self, templ=test_cfn_template_signal, + stack_name='test_stack', stub=True): + tpl = template.Template(template_format.parse(templ)) ctx = utils.dummy_context() ctx.tenant_id = 'test_tenant' - stack = parser.Stack(ctx, stack_name, templ, + stack = parser.Stack(ctx, stack_name, tpl, disable_rollback=True) # Stub out the stack ID so we have a known value @@ -67,23 +109,6 @@ class SignalTest(common.HeatTestCase): return stack - def test_handle_create_fail_keypair_raise(self): - self.stack = self.create_stack(stack_name='create_fail_keypair') - - self.m.StubOutWithMock(stack_user.StackUser, '_create_keypair') - stack_user.StackUser._create_keypair().AndRaise(Exception('Failed')) - self.m.ReplayAll() - - self.stack.create() - - rsrc = self.stack['signal_handler'] - rs_data = resource_data_object.ResourceData.get_all(rsrc) - self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state) - self.assertIn('Failed', rsrc.status_reason) - self.assertEqual('1234', rs_data.get('user_id')) - self.assertIsNone(rsrc.resource_id) - self.m.VerifyAll() - def test_resource_data(self): self.stub_keystoneclient( access='anaccesskey', @@ -97,6 +122,7 @@ class SignalTest(common.HeatTestCase): rsrc = self.stack['signal_handler'] self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) + rsrc._create_keypair() # Ensure the resource data has been stored correctly rs_data = resource_data_object.ResourceData.get_all(rsrc) @@ -174,8 +200,131 @@ class SignalTest(common.HeatTestCase): rsrc = self.stack['signal_handler'] self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) - first_url = rsrc.FnGetAtt('AlarmUrl') - second_url = rsrc.FnGetAtt('AlarmUrl') + first_url = rsrc.FnGetAtt('signal') + second_url = rsrc.FnGetAtt('signal') + self.assertEqual(first_url, second_url) + self.m.VerifyAll() + + def test_FnGetAtt_Heat_Signal(self): + self.stack = self.create_stack(test_heat_template_signal) + self.m.StubOutWithMock(self.stack.clients.client_plugin('heat'), + 'get_heat_url') + + self.stack.clients.client_plugin('heat').get_heat_url().AndReturn( + 'http://server.test:8004/v1') + + self.m.ReplayAll() + self.stack.create() + + rsrc = self.stack['signal_handler'] + self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) + + signal = rsrc.FnGetAtt('signal') + self.assertEqual('http://localhost:5000/v3', signal['auth_url']) + self.assertEqual('aprojectid', signal['project_id']) + self.assertEqual('1234', signal['user_id']) + self.assertIn('username', signal) + self.assertIn('password', signal) + self.m.VerifyAll() + + def test_FnGetAtt_Heat_Signal_is_cached(self): + self.stack = self.create_stack(test_heat_template_signal) + + self.m.ReplayAll() + self.stack.create() + + rsrc = self.stack['signal_handler'] + self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) + + first_url = rsrc.FnGetAtt('signal') + second_url = rsrc.FnGetAtt('signal') + self.assertEqual(first_url, second_url) + self.m.VerifyAll() + + def test_FnGetAtt_Zaqar_Signal(self): + self.stack = self.create_stack(test_zaqar_template_signal) + + self.m.ReplayAll() + self.stack.create() + + rsrc = self.stack['signal_handler'] + self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) + + signal = rsrc.FnGetAtt('signal') + self.assertEqual('http://localhost:5000/v3', signal['auth_url']) + self.assertEqual('aprojectid', signal['project_id']) + self.assertEqual('1234', signal['user_id']) + self.assertIn('username', signal) + self.assertIn('password', signal) + self.assertIn('queue_id', signal) + self.m.VerifyAll() + + def test_FnGetAtt_Zaqar_Signal_is_cached(self): + self.stack = self.create_stack(test_zaqar_template_signal) + + self.m.ReplayAll() + self.stack.create() + + rsrc = self.stack['signal_handler'] + self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) + + first_url = rsrc.FnGetAtt('signal') + second_url = rsrc.FnGetAtt('signal') + self.assertEqual(first_url, second_url) + self.m.VerifyAll() + + def test_FnGetAtt_Swift_Signal(self): + self.stack = self.create_stack(test_swift_template_signal) + self.m.StubOutWithMock(self.stack.clients.client('swift'), + 'put_container') + self.m.StubOutWithMock(self.stack.clients.client('swift'), + 'put_object') + self.m.StubOutWithMock(self.stack.clients.client_plugin('swift'), + 'get_temp_url') + + self.stack.clients.client('swift').put_container( + mox.IgnoreArg()).AndReturn(None) + self.stack.clients.client_plugin('swift').get_temp_url( + mox.IgnoreArg(), mox.IgnoreArg()).AndReturn( + 'http://192.0.2.1/v1/AUTH_aprojectid/foo/bar') + self.stack.clients.client('swift').put_object( + mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(None) + + self.m.ReplayAll() + self.stack.create() + + rsrc = self.stack['signal_handler'] + self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) + + self.assertEqual('http://192.0.2.1/v1/AUTH_aprojectid/foo/bar', + rsrc.FnGetAtt('AlarmUrl')) + self.m.VerifyAll() + + def test_FnGetAtt_Swift_Signal_is_cached(self): + self.stack = self.create_stack(test_swift_template_signal) + self.m.StubOutWithMock(self.stack.clients.client('swift'), + 'put_container') + self.m.StubOutWithMock(self.stack.clients.client('swift'), + 'put_object') + self.m.StubOutWithMock(self.stack.clients.client_plugin('swift'), + 'get_temp_url') + + self.stack.clients.client('swift').put_container( + mox.IgnoreArg()).AndReturn(None) + self.stack.clients.client_plugin('swift').get_temp_url( + mox.IgnoreArg(), mox.IgnoreArg()).AndReturn( + 'http://192.0.2.1/v1/AUTH_aprojectid/foo/' + uuid.uuid4().hex) + self.stack.clients.client('swift').put_object( + mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(None) + + self.m.ReplayAll() + self.stack.create() + + rsrc = self.stack['signal_handler'] + self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) + + first_url = rsrc.FnGetAtt('signal') + second_url = rsrc.FnGetAtt('signal') self.assertEqual(first_url, second_url) self.m.VerifyAll() @@ -190,6 +339,7 @@ class SignalTest(common.HeatTestCase): self.stack.create() rsrc = self.stack['signal_handler'] + rsrc.resource_id_set('signal') self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) self.assertIn('http://server.test:8000/v1/signal', diff --git a/heat/tests/test_software_deployment.py b/heat/tests/test_software_deployment.py index 85167e2c80..413b137ec9 100644 --- a/heat/tests/test_software_deployment.py +++ b/heat/tests/test_software_deployment.py @@ -979,22 +979,22 @@ class SoftwareDeploymentTest(common.HeatTestCase): self.deployment.id = 23 self.deployment.uuid = str(uuid.uuid4()) self.deployment.action = self.deployment.CREATE - container = self.deployment.physical_resource_name() + object_name = self.deployment.physical_resource_name() - temp_url = self.deployment._get_temp_url() + temp_url = self.deployment._get_swift_signal_url() temp_url_pattern = re.compile( '^http://192.0.2.1/v1/AUTH_test_tenant_id/' - '(software_deployment_test_stack-deployment_mysql-.*)/(.*)' + '(.*)/(software_deployment_test_stack-deployment_mysql-.*)' '\\?temp_url_sig=.*&temp_url_expires=\\d*$') self.assertRegex(temp_url, temp_url_pattern) m = temp_url_pattern.search(temp_url) - object_name = m.group(2) - self.assertEqual(container, m.group(1)) - self.assertEqual(dep_data['signal_object_name'], object_name) + container = m.group(1) + self.assertEqual(object_name, m.group(2)) + self.assertEqual(dep_data['swift_signal_object_name'], object_name) - self.assertEqual(dep_data['signal_temp_url'], temp_url) + self.assertEqual(dep_data['swift_signal_url'], temp_url) - self.assertEqual(temp_url, self.deployment._get_temp_url()) + self.assertEqual(temp_url, self.deployment._get_swift_signal_url()) sc.put_container.assert_called_once_with(container) sc.put_object.assert_called_once_with(container, object_name, '') @@ -1002,7 +1002,7 @@ class SoftwareDeploymentTest(common.HeatTestCase): def test_delete_temp_url(self): object_name = str(uuid.uuid4()) dep_data = { - 'signal_object_name': object_name + 'swift_signal_object_name': object_name } self._create_stack(self.template_temp_url_signal) @@ -1021,31 +1021,34 @@ class SoftwareDeploymentTest(common.HeatTestCase): self.deployment.id = 23 self.deployment.uuid = str(uuid.uuid4()) container = self.deployment.physical_resource_name() - self.deployment._delete_temp_url() + self.deployment._delete_swift_signal_url() sc.delete_object.assert_called_once_with(container, object_name) self.assertEqual( - [mock.call('signal_object_name'), mock.call('signal_temp_url')], + [mock.call('swift_signal_object_name'), + mock.call('swift_signal_url')], self.deployment.data_delete.mock_calls) swift_exc = swift.SwiftClientPlugin.exceptions_module sc.delete_object.side_effect = swift_exc.ClientException( 'Not found', http_status=404) - self.deployment._delete_temp_url() + self.deployment._delete_swift_signal_url() self.assertEqual( - [mock.call('signal_object_name'), mock.call('signal_temp_url'), - mock.call('signal_object_name'), mock.call('signal_temp_url')], + [mock.call('swift_signal_object_name'), + mock.call('swift_signal_url'), + mock.call('swift_signal_object_name'), + mock.call('swift_signal_url')], self.deployment.data_delete.mock_calls) - del(dep_data['signal_object_name']) + del(dep_data['swift_signal_object_name']) self.deployment.physical_resource_name = mock.Mock() - self.deployment._delete_temp_url() + self.deployment._delete_swift_signal_url() self.assertFalse(self.deployment.physical_resource_name.called) def test_handle_action_temp_url(self): self._create_stack(self.template_temp_url_signal) dep_data = { - 'signal_temp_url': ( + 'swift_signal_url': ( 'http://192.0.2.1/v1/AUTH_a/b/c' '?temp_url_sig=ctemp_url_expires=1234') } @@ -1079,17 +1082,18 @@ class SoftwareDeploymentTest(common.HeatTestCase): self.deployment.uuid = str(uuid.uuid4()) self.deployment.action = self.deployment.CREATE - queue_id = self.deployment._get_queue_id() + queue_id = self.deployment._get_zaqar_signal_queue_id() self.assertEqual(2, len(zc.queue.mock_calls)) self.assertEqual(queue_id, zc.queue.mock_calls[0][1][0]) - self.assertEqual(queue_id, dep_data['signal_queue_id']) + self.assertEqual(queue_id, dep_data['zaqar_signal_queue_id']) - self.assertEqual(queue_id, self.deployment._get_queue_id()) + self.assertEqual(queue_id, + self.deployment._get_zaqar_signal_queue_id()) def test_delete_zaqar_queue(self): queue_id = str(uuid.uuid4()) dep_data = { - 'signal_queue_id': queue_id + 'zaqar_signal_queue_id': queue_id } self._create_stack(self.template_zaqar_signal) @@ -1103,23 +1107,24 @@ class SoftwareDeploymentTest(common.HeatTestCase): self.deployment.id = 23 self.deployment.uuid = str(uuid.uuid4()) - self.deployment._delete_queue() + self.deployment._delete_zaqar_signal_queue() zc.queue.assert_called_once_with(queue_id) zc.queue.delete.assert_called_once() self.assertEqual( - [mock.call('signal_queue_id')], + [mock.call('zaqar_signal_queue_id')], self.deployment.data_delete.mock_calls) zaqar_exc = zaqar.ZaqarClientPlugin.exceptions_module zc.queue.delete.side_effect = zaqar_exc.ResourceNotFound() - self.deployment._delete_queue() + self.deployment._delete_zaqar_signal_queue() self.assertEqual( - [mock.call('signal_queue_id'), mock.call('signal_queue_id')], + [mock.call('zaqar_signal_queue_id'), + mock.call('zaqar_signal_queue_id')], self.deployment.data_delete.mock_calls) - dep_data.pop('signal_queue_id') + dep_data.pop('zaqar_signal_queue_id') self.deployment.physical_resource_name = mock.Mock() - self.deployment._delete_queue() + self.deployment._delete_zaqar_signal_queue() self.assertEqual(2, len(self.deployment.data_delete.mock_calls))