Merge "Move signals to SignalResponder class"

This commit is contained in:
Jenkins 2015-07-15 05:23:28 +00:00 committed by Gerrit Code Review
commit 8fe749e8a0
7 changed files with 394 additions and 163 deletions

View File

@ -13,7 +13,6 @@
import copy import copy
import six import six
import uuid
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
@ -315,63 +314,6 @@ class SoftwareDeployment(signal_responder.SignalResponder):
def _build_derived_options(self, action, source): def _build_derived_options(self, action, source):
return source.get(sc.SoftwareConfig.OPTIONS) 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): def _build_derived_inputs(self, action, source):
scl = sc.SoftwareConfig scl = sc.SoftwareConfig
inputs = copy.deepcopy(source.get(scl.INPUTS)) or [] 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 ' scl.DESCRIPTION: _('ID of signal to use for signaling '
'output values'), 'output values'),
scl.TYPE: 'String', scl.TYPE: 'String',
'value': self._get_temp_url() 'value': self._get_swift_signal_url()
}) })
inputs.append({ inputs.append({
scl.NAME: self.DEPLOY_SIGNAL_VERB, scl.NAME: self.DEPLOY_SIGNAL_VERB,
@ -450,31 +392,32 @@ class SoftwareDeployment(signal_responder.SignalResponder):
'value': 'PUT' 'value': 'PUT'
}) })
elif self._signal_transport_heat() or self._signal_transport_zaqar(): elif self._signal_transport_heat() or self._signal_transport_zaqar():
creds = self._get_heat_signal_credentials()
inputs.extend([{ inputs.extend([{
scl.NAME: self.DEPLOY_AUTH_URL, scl.NAME: self.DEPLOY_AUTH_URL,
scl.DESCRIPTION: _('URL for API authentication'), scl.DESCRIPTION: _('URL for API authentication'),
scl.TYPE: 'String', scl.TYPE: 'String',
'value': self.keystone().v3_endpoint 'value': creds['auth_url']
}, { }, {
scl.NAME: self.DEPLOY_USERNAME, scl.NAME: self.DEPLOY_USERNAME,
scl.DESCRIPTION: _('Username for API authentication'), scl.DESCRIPTION: _('Username for API authentication'),
scl.TYPE: 'String', scl.TYPE: 'String',
'value': self.physical_resource_name(), 'value': creds['username']
}, { }, {
scl.NAME: self.DEPLOY_USER_ID, scl.NAME: self.DEPLOY_USER_ID,
scl.DESCRIPTION: _('User ID for API authentication'), scl.DESCRIPTION: _('User ID for API authentication'),
scl.TYPE: 'String', scl.TYPE: 'String',
'value': self._get_user_id(), 'value': creds['user_id']
}, { }, {
scl.NAME: self.DEPLOY_PASSWORD, scl.NAME: self.DEPLOY_PASSWORD,
scl.DESCRIPTION: _('Password for API authentication'), scl.DESCRIPTION: _('Password for API authentication'),
scl.TYPE: 'String', scl.TYPE: 'String',
'value': self.password 'value': creds['password']
}, { }, {
scl.NAME: self.DEPLOY_PROJECT_ID, scl.NAME: self.DEPLOY_PROJECT_ID,
scl.DESCRIPTION: _('ID of project for API authentication'), scl.DESCRIPTION: _('ID of project for API authentication'),
scl.TYPE: 'String', scl.TYPE: 'String',
'value': self.stack.stack_user_project_id 'value': creds['project_id']
}]) }])
if self._signal_transport_zaqar(): if self._signal_transport_zaqar():
inputs.append({ inputs.append({
@ -482,31 +425,14 @@ class SoftwareDeployment(signal_responder.SignalResponder):
scl.DESCRIPTION: _('ID of queue to use for signaling ' scl.DESCRIPTION: _('ID of queue to use for signaling '
'output values'), 'output values'),
scl.TYPE: 'String', scl.TYPE: 'String',
'value': self._get_queue_id() 'value': self._get_zaqar_signal_queue_id()
}) })
return inputs return inputs
def handle_create(self): 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) 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): def check_create_complete(self, sd):
if not sd: if not sd:
return True return True
@ -536,15 +462,8 @@ class SoftwareDeployment(signal_responder.SignalResponder):
return True return True
def _delete_resource(self): def _delete_resource(self):
if self._signal_transport_cfn(): self._delete_signals()
self._delete_ec2_signed_url() self._delete_user()
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()
derived_config_id = None derived_config_id = None
if self.resource_id is not None: if self.resource_id is not None:

View File

@ -11,6 +11,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import uuid
from keystoneclient.contrib.ec2 import utils as ec2_utils from keystoneclient.contrib.ec2 import utils as ec2_utils
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging 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 # API operations as a consequence of handling a signal
requires_deferred_auth = True requires_deferred_auth = True
def handle_create(self):
super(SignalResponder, self).handle_create()
self._create_keypair()
def handle_delete(self): def handle_delete(self):
self._delete_signals()
super(SignalResponder, self).handle_delete() super(SignalResponder, self).handle_delete()
def _delete_signals(self):
self._delete_ec2_signed_url() self._delete_ec2_signed_url()
self._delete_heat_signal_url() self._delete_heat_signal_url()
self._delete_swift_signal_url()
self._delete_zaqar_signal_queue()
def _delete_ec2_signed_url(self): @property
self.data_delete('ec2_signed_url') 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): def _get_ec2_signed_url(self, signal_type=SIGNAL):
"""Create properly formatted and pre-signed URL. """Create properly formatted and pre-signed URL.
@ -65,9 +92,19 @@ class SignalResponder(stack_user.StackUser):
secret_key = self.data().get('secret_key') secret_key = self.data().get('secret_key')
if not access_key or not secret_key: if not access_key or not secret_key:
LOG.warn(_LW('Cannot generate signed url, ' if self.id is None or self.action == self.DELETE:
'no stored access/secret key')) # it is either too early or too late to do this
return 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 config_url = cfg.CONF.heat_waitcondition_server_url
if config_url: if config_url:
@ -106,14 +143,23 @@ class SignalResponder(stack_user.StackUser):
self.data_set('ec2_signed_url', url) self.data_set('ec2_signed_url', url)
return url return url
def _delete_heat_signal_url(self): def _delete_ec2_signed_url(self):
self.data_delete('heat_signal_url') self.data_delete('ec2_signed_url')
self._delete_keypair()
def _get_heat_signal_url(self): 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') stored = self.data().get('heat_signal_url')
if stored is not None: if stored is not None:
return stored 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() url = self.client_plugin('heat').get_heat_url()
host_url = urlparse.urlparse(url) host_url = urlparse.urlparse(url)
path = self.identifier().url_path() path = self.identifier().url_path()
@ -123,3 +169,84 @@ class SignalResponder(stack_user.StackUser):
self.data_set('heat_signal_url', url) self.data_set('heat_signal_url', url)
return 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')

View File

@ -25,7 +25,7 @@ LOG = logging.getLogger(__name__)
class StackUser(resource.Resource): class StackUser(resource.Resource):
# Subclasses create a user, and optionally keypair associated with a # 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) # (in a project specific to the stack)
def __init__(self, name, json_snippet, stack): def __init__(self, name, json_snippet, stack):
super(StackUser, self).__init__(name, json_snippet, stack) super(StackUser, self).__init__(name, json_snippet, stack)
@ -34,6 +34,10 @@ class StackUser(resource.Resource):
self._create_user() self._create_user()
def _create_user(self): 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 # Check for stack user project, create if not yet set
if not self.stack.stack_user_project_id: if not self.stack.stack_user_project_id:
project_id = self.keystone().create_stack_domain_project( project_id = self.keystone().create_stack_domain_project(
@ -85,6 +89,10 @@ class StackUser(resource.Resource):
user_id = self._get_user_id() user_id = self._get_user_id()
if user_id is None: if user_id is None:
return return
# the user is going away, so we want the keypair gone as well
self._delete_keypair()
try: try:
self.keystone().delete_stack_domain_user( self.keystone().delete_stack_domain_user(
user_id=user_id, project_id=self.stack.stack_user_project_id) 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) self.keystone().delete_stack_user(user_id)
except kc_exception.NotFound: except kc_exception.NotFound:
pass pass
for data_key in ('credential_id', 'access_key', 'secret_key'): self.data_delete('user_id')
self.data_delete(data_key)
def handle_suspend(self): def handle_suspend(self):
user_id = self._get_user_id() user_id = self._get_user_id()
@ -125,6 +132,9 @@ class StackUser(resource.Resource):
# Subclasses may optionally call this in handle_create to create # Subclasses may optionally call this in handle_create to create
# an ec2 keypair associated with the user, the resulting keys are # an ec2 keypair associated with the user, the resulting keys are
# stored in resource_data # stored in resource_data
if self.data().get('credential_id'):
return # a keypair was created already
user_id = self._get_user_id() user_id = self._get_user_id()
kp = self.keystone().create_stack_domain_user_keypair( kp = self.keystone().create_stack_domain_user_keypair(
user_id=user_id, project_id=self.stack.stack_user_project_id) user_id=user_id, project_id=self.stack.stack_user_project_id)
@ -146,10 +156,12 @@ class StackUser(resource.Resource):
def _delete_keypair(self): def _delete_keypair(self):
# Subclasses may optionally call this to delete a keypair created # Subclasses may optionally call this to delete a keypair created
# via _create_keypair # via _create_keypair
user_id = self._get_user_id()
credential_id = self.data().get('credential_id') credential_id = self.data().get('credential_id')
if not credential_id: if not credential_id:
return return
user_id = self._get_user_id()
if user_id is None:
return
try: try:
self.keystone().delete_stack_domain_user_keypair( self.keystone().delete_stack_domain_user_keypair(

View File

@ -109,6 +109,7 @@ class FakeKeystoneClient(object):
self.credential_id = credential_id self.credential_id = credential_id
self.token = auth_token self.token = auth_token
self.context = context self.context = context
self.v3_endpoint = 'http://localhost:5000/v3'
class FakeCred(object): class FakeCred(object):
id = self.credential_id id = self.credential_id

View File

@ -136,8 +136,11 @@ class ResourceWithRequiredProps(GenericResource):
class SignalResource(signal_responder.SignalResponder): class SignalResource(signal_responder.SignalResponder):
properties_schema = {} properties_schema = {
attributes_schema = {'AlarmUrl': attributes.Schema('Get a signed webhook')} '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): def handle_create(self):
super(SignalResource, self).handle_create() super(SignalResource, self).handle_create()
@ -148,8 +151,22 @@ class SignalResource(signal_responder.SignalResponder):
{'type': self.type(), 'details': details}) {'type': self.type(), 'details': details})
def _resolve_attribute(self, name): def _resolve_attribute(self, name):
if name == 'AlarmUrl' and self.resource_id is not None: if self.resource_id is not None:
return six.text_type(self._get_ec2_signed_url()) 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): class StackUserResource(stack_user.StackUser):

View File

@ -12,14 +12,15 @@
# under the License. # under the License.
import datetime import datetime
import six import uuid
from keystoneclient import exceptions as kc_exceptions from keystoneclient import exceptions as kc_exceptions
import mox
import six
from heat.common import exception from heat.common import exception
from heat.common import template_format from heat.common import template_format
from heat.engine import resource from heat.engine import resource
from heat.engine.resources import stack_user
from heat.engine import scheduler from heat.engine import scheduler
from heat.engine import stack as parser from heat.engine import stack as parser
from heat.engine import template from heat.engine import template
@ -30,13 +31,53 @@ from heat.tests import generic_resource
from heat.tests import utils from heat.tests import utils
test_template_signal = ''' test_cfn_template_signal = '''
{ {
"AWSTemplateFormatVersion" : "2010-09-09", "AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Just a test.", "Description" : "Just a test.",
"Parameters" : {}, "Parameters" : {},
"Resources" : { "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"} "resource_X" : {"Type" : "GenericResourceType"}
} }
} }
@ -52,11 +93,12 @@ class SignalTest(common.HeatTestCase):
def tearDown(self): def tearDown(self):
super(SignalTest, self).tearDown() super(SignalTest, self).tearDown()
def create_stack(self, stack_name='test_stack', stub=True): def create_stack(self, templ=test_cfn_template_signal,
templ = template.Template(template_format.parse(test_template_signal)) stack_name='test_stack', stub=True):
tpl = template.Template(template_format.parse(templ))
ctx = utils.dummy_context() ctx = utils.dummy_context()
ctx.tenant_id = 'test_tenant' ctx.tenant_id = 'test_tenant'
stack = parser.Stack(ctx, stack_name, templ, stack = parser.Stack(ctx, stack_name, tpl,
disable_rollback=True) disable_rollback=True)
# Stub out the stack ID so we have a known value # Stub out the stack ID so we have a known value
@ -67,23 +109,6 @@ class SignalTest(common.HeatTestCase):
return stack 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): def test_resource_data(self):
self.stub_keystoneclient( self.stub_keystoneclient(
access='anaccesskey', access='anaccesskey',
@ -97,6 +122,7 @@ class SignalTest(common.HeatTestCase):
rsrc = self.stack['signal_handler'] rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
rsrc._create_keypair()
# Ensure the resource data has been stored correctly # Ensure the resource data has been stored correctly
rs_data = resource_data_object.ResourceData.get_all(rsrc) rs_data = resource_data_object.ResourceData.get_all(rsrc)
@ -174,8 +200,131 @@ class SignalTest(common.HeatTestCase):
rsrc = self.stack['signal_handler'] rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
first_url = rsrc.FnGetAtt('AlarmUrl') first_url = rsrc.FnGetAtt('signal')
second_url = rsrc.FnGetAtt('AlarmUrl') 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.assertEqual(first_url, second_url)
self.m.VerifyAll() self.m.VerifyAll()
@ -190,6 +339,7 @@ class SignalTest(common.HeatTestCase):
self.stack.create() self.stack.create()
rsrc = self.stack['signal_handler'] rsrc = self.stack['signal_handler']
rsrc.resource_id_set('signal')
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertIn('http://server.test:8000/v1/signal', self.assertIn('http://server.test:8000/v1/signal',

View File

@ -979,22 +979,22 @@ class SoftwareDeploymentTest(common.HeatTestCase):
self.deployment.id = 23 self.deployment.id = 23
self.deployment.uuid = str(uuid.uuid4()) self.deployment.uuid = str(uuid.uuid4())
self.deployment.action = self.deployment.CREATE 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( temp_url_pattern = re.compile(
'^http://192.0.2.1/v1/AUTH_test_tenant_id/' '^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*$') '\\?temp_url_sig=.*&temp_url_expires=\\d*$')
self.assertRegex(temp_url, temp_url_pattern) self.assertRegex(temp_url, temp_url_pattern)
m = temp_url_pattern.search(temp_url) m = temp_url_pattern.search(temp_url)
object_name = m.group(2) container = m.group(1)
self.assertEqual(container, m.group(1)) self.assertEqual(object_name, m.group(2))
self.assertEqual(dep_data['signal_object_name'], object_name) 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_container.assert_called_once_with(container)
sc.put_object.assert_called_once_with(container, object_name, '') sc.put_object.assert_called_once_with(container, object_name, '')
@ -1002,7 +1002,7 @@ class SoftwareDeploymentTest(common.HeatTestCase):
def test_delete_temp_url(self): def test_delete_temp_url(self):
object_name = str(uuid.uuid4()) object_name = str(uuid.uuid4())
dep_data = { dep_data = {
'signal_object_name': object_name 'swift_signal_object_name': object_name
} }
self._create_stack(self.template_temp_url_signal) self._create_stack(self.template_temp_url_signal)
@ -1021,31 +1021,34 @@ class SoftwareDeploymentTest(common.HeatTestCase):
self.deployment.id = 23 self.deployment.id = 23
self.deployment.uuid = str(uuid.uuid4()) self.deployment.uuid = str(uuid.uuid4())
container = self.deployment.physical_resource_name() 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) sc.delete_object.assert_called_once_with(container, object_name)
self.assertEqual( 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) self.deployment.data_delete.mock_calls)
swift_exc = swift.SwiftClientPlugin.exceptions_module swift_exc = swift.SwiftClientPlugin.exceptions_module
sc.delete_object.side_effect = swift_exc.ClientException( sc.delete_object.side_effect = swift_exc.ClientException(
'Not found', http_status=404) 'Not found', http_status=404)
self.deployment._delete_temp_url() self.deployment._delete_swift_signal_url()
self.assertEqual( self.assertEqual(
[mock.call('signal_object_name'), mock.call('signal_temp_url'), [mock.call('swift_signal_object_name'),
mock.call('signal_object_name'), mock.call('signal_temp_url')], mock.call('swift_signal_url'),
mock.call('swift_signal_object_name'),
mock.call('swift_signal_url')],
self.deployment.data_delete.mock_calls) 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.physical_resource_name = mock.Mock()
self.deployment._delete_temp_url() self.deployment._delete_swift_signal_url()
self.assertFalse(self.deployment.physical_resource_name.called) self.assertFalse(self.deployment.physical_resource_name.called)
def test_handle_action_temp_url(self): def test_handle_action_temp_url(self):
self._create_stack(self.template_temp_url_signal) self._create_stack(self.template_temp_url_signal)
dep_data = { dep_data = {
'signal_temp_url': ( 'swift_signal_url': (
'http://192.0.2.1/v1/AUTH_a/b/c' 'http://192.0.2.1/v1/AUTH_a/b/c'
'?temp_url_sig=ctemp_url_expires=1234') '?temp_url_sig=ctemp_url_expires=1234')
} }
@ -1079,17 +1082,18 @@ class SoftwareDeploymentTest(common.HeatTestCase):
self.deployment.uuid = str(uuid.uuid4()) self.deployment.uuid = str(uuid.uuid4())
self.deployment.action = self.deployment.CREATE 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(2, len(zc.queue.mock_calls))
self.assertEqual(queue_id, zc.queue.mock_calls[0][1][0]) 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): def test_delete_zaqar_queue(self):
queue_id = str(uuid.uuid4()) queue_id = str(uuid.uuid4())
dep_data = { dep_data = {
'signal_queue_id': queue_id 'zaqar_signal_queue_id': queue_id
} }
self._create_stack(self.template_zaqar_signal) self._create_stack(self.template_zaqar_signal)
@ -1103,23 +1107,24 @@ class SoftwareDeploymentTest(common.HeatTestCase):
self.deployment.id = 23 self.deployment.id = 23
self.deployment.uuid = str(uuid.uuid4()) 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.assert_called_once_with(queue_id)
self.assertTrue(zc.queue(self.deployment.uuid).delete.called) self.assertTrue(zc.queue(self.deployment.uuid).delete.called)
self.assertEqual( self.assertEqual(
[mock.call('signal_queue_id')], [mock.call('zaqar_signal_queue_id')],
self.deployment.data_delete.mock_calls) self.deployment.data_delete.mock_calls)
zaqar_exc = zaqar.ZaqarClientPlugin.exceptions_module zaqar_exc = zaqar.ZaqarClientPlugin.exceptions_module
zc.queue.delete.side_effect = zaqar_exc.ResourceNotFound() zc.queue.delete.side_effect = zaqar_exc.ResourceNotFound()
self.deployment._delete_queue() self.deployment._delete_zaqar_signal_queue()
self.assertEqual( 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) 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.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)) self.assertEqual(2, len(self.deployment.data_delete.mock_calls))