Browse Source

Move signals to SignalResponder class

This change relocates the code that creates and deletes heat, swift and
zaqar signals out of SoftwareDeployment and into SignalResponder, as a
first step in making these signals available to all resources. As part of
this change the structure of the Swift URLs was changed to match that of
the SwiftSignal resource.

Change-Id: Id5701b3696b0ab41433a3f158fabce1f36aabe16
Implements: blueprint uniform-resource-signals
changes/83/198183/11
Miguel Grinberg 7 years ago
parent
commit
ca8effbbae
  1. 101
      heat/engine/resources/openstack/heat/software_deployment.py
  2. 149
      heat/engine/resources/signal_responder.py
  3. 20
      heat/engine/resources/stack_user.py
  4. 1
      heat/tests/fakes.py
  5. 25
      heat/tests/generic_resource.py
  6. 202
      heat/tests/test_signal.py
  7. 59
      heat/tests/test_software_deployment.py

101
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:

149
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')

20
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(

1
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

25
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):

202
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',

59
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))

Loading…
Cancel
Save