deb-heat/heat/tests/test_signal.py
Miguel Grinberg ca8effbbae 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
2015-07-14 10:41:30 -07:00

548 lines
19 KiB
Python

#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
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 import scheduler
from heat.engine import stack as parser
from heat.engine import template
from heat.objects import resource_data as resource_data_object
from heat.tests import common
from heat.tests import fakes
from heat.tests import generic_resource
from heat.tests import utils
test_cfn_template_signal = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Just a test.",
"Parameters" : {},
"Resources" : {
"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"}
}
}
'''
class SignalTest(common.HeatTestCase):
def setUp(self):
super(SignalTest, self).setUp()
self.stack_id = 'STACKABCD1234'
def tearDown(self):
super(SignalTest, self).tearDown()
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, tpl,
disable_rollback=True)
# Stub out the stack ID so we have a known value
with utils.UUIDStub(self.stack_id):
stack.store()
if stub:
self.stub_keystoneclient()
return stack
def test_resource_data(self):
self.stub_keystoneclient(
access='anaccesskey',
secret='verysecret',
credential_id='mycredential')
self.stack = self.create_stack(stack_name='resource_data_test',
stub=False)
self.m.ReplayAll()
self.stack.create()
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)
self.assertEqual('mycredential', rs_data.get('credential_id'))
self.assertEqual('anaccesskey', rs_data.get('access_key'))
self.assertEqual('verysecret', rs_data.get('secret_key'))
self.assertEqual('1234', rs_data.get('user_id'))
self.assertEqual(rsrc.resource_id, rs_data.get('user_id'))
self.assertEqual(4, len(list(six.iterkeys(rs_data))))
self.m.VerifyAll()
def test_get_user_id(self):
self.stack = self.create_stack(stack_name='resource_data_test',
stub=False)
self.stub_keystoneclient(access='anaccesskey', secret='verysecret')
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
# Ensure the resource data has been stored correctly
rs_data = resource_data_object.ResourceData.get_all(rsrc)
self.assertEqual('1234', rs_data.get('user_id'))
self.assertEqual('1234', rsrc.resource_id)
self.assertEqual('1234', rsrc._get_user_id())
# Check user id can still be fetched from resource_id
# if the resource data is not there.
resource_data_object.ResourceData.delete(rsrc, 'user_id')
self.assertRaises(
exception.NotFound, resource_data_object.ResourceData.get_val,
rsrc, 'user_id')
self.assertEqual('1234', rsrc._get_user_id())
self.m.VerifyAll()
def test_FnGetAtt_Alarm_Url(self):
self.stack = self.create_stack()
self.m.StubOutWithMock(self.stack.clients.client_plugin('heat'),
'get_heat_cfn_url')
self.stack.clients.client_plugin('heat').get_heat_cfn_url().AndReturn(
'http://server.test:8000/v1')
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['signal_handler']
created_time = datetime.datetime(2012, 11, 29, 13, 49, 37)
rsrc.created_time = created_time
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
expected_url = "".join([
'http://server.test:8000/v1/signal/',
'arn%3Aopenstack%3Aheat%3A%3Atest_tenant%3Astacks%2F',
'test_stack%2FSTACKABCD1234%2Fresources%2F',
'signal_handler?',
'Timestamp=2012-11-29T13%3A49%3A37Z&',
'SignatureMethod=HmacSHA256&',
'AWSAccessKeyId=4567&',
'SignatureVersion=2&',
'Signature=',
'VW4NyvRO4WhQdsQ4rxl5JMUr0AlefHN6OLsRz9oZyls%3D'])
self.assertEqual(expected_url, rsrc.FnGetAtt('AlarmUrl'))
self.m.VerifyAll()
def test_FnGetAtt_Alarm_Url_is_cached(self):
self.stack = self.create_stack()
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_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()
def test_FnGetAtt_delete(self):
self.stack = self.create_stack()
self.m.StubOutWithMock(self.stack.clients.client_plugin('heat'),
'get_heat_cfn_url')
self.stack.clients.client_plugin('heat').get_heat_cfn_url().AndReturn(
'http://server.test:8000/v1')
self.m.ReplayAll()
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',
rsrc.FnGetAtt('AlarmUrl'))
scheduler.TaskRunner(rsrc.delete)()
self.assertEqual('None', rsrc.FnGetAtt('AlarmUrl'))
self.m.VerifyAll()
def test_delete_not_found(self):
self.stack = self.create_stack(stack_name='test_delete_not_found',
stub=False)
class FakeKeystoneClientFail(fakes.FakeKeystoneClient):
def delete_stack_user(self, name):
raise kc_exceptions.NotFound()
self.stub_keystoneclient(fake_client=FakeKeystoneClientFail())
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
scheduler.TaskRunner(rsrc.delete)()
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
self.m.VerifyAll()
def test_signal(self):
test_d = {'Data': 'foo', 'Reason': 'bar',
'Status': 'SUCCESS', 'UniqueId': '123'}
self.stack = self.create_stack()
# to confirm we get a call to handle_signal
self.m.StubOutWithMock(generic_resource.SignalResource,
'handle_signal')
generic_resource.SignalResource.handle_signal(test_d).AndReturn(None)
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertTrue(rsrc.requires_deferred_auth)
rsrc.signal(details=test_d)
self.m.VerifyAll()
def test_signal_no_action(self):
test_d = {'Data': 'foo', 'Reason': 'bar',
'Status': 'SUCCESS', 'UniqueId': '123'}
self.stack = self.create_stack()
self.stack.create()
# mock a NoActionRequired from handle_signal()
self.m.StubOutWithMock(generic_resource.SignalResource,
'handle_signal')
generic_resource.SignalResource.handle_signal(test_d).AndRaise(
resource.NoActionRequired())
# _add_event should not be called.
self.m.StubOutWithMock(generic_resource.SignalResource,
'_add_event')
self.m.ReplayAll()
rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertTrue(rsrc.requires_deferred_auth)
rsrc.signal(details=test_d)
self.m.VerifyAll()
def test_signal_different_reason_types(self):
self.stack = self.create_stack()
self.stack.create()
rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertTrue(rsrc.requires_deferred_auth)
ceilo_details = {'current': 'foo', 'reason': 'apples',
'previous': 'SUCCESS'}
ceilo_expected = 'alarm state changed from SUCCESS to foo (apples)'
watch_details = {'state': 'go_for_it'}
watch_expected = 'alarm state changed to go_for_it'
str_details = 'a string details'
str_expected = str_details
none_details = None
none_expected = 'No signal details provided'
# to confirm we get a string reason
self.m.StubOutWithMock(generic_resource.SignalResource,
'_add_event')
generic_resource.SignalResource._add_event(
'SIGNAL', 'COMPLETE', ceilo_expected).AndReturn(None)
generic_resource.SignalResource._add_event(
'SIGNAL', 'COMPLETE', watch_expected).AndReturn(None)
generic_resource.SignalResource._add_event(
'SIGNAL', 'COMPLETE', str_expected).AndReturn(None)
generic_resource.SignalResource._add_event(
'SIGNAL', 'COMPLETE', none_expected).AndReturn(None)
self.m.ReplayAll()
for test_d in (ceilo_details, watch_details, str_details,
none_details):
rsrc.signal(details=test_d)
self.m.VerifyAll()
def test_signal_plugin_reason(self):
# Ensure if handle_signal returns data, we use it as the reason
self.stack = self.create_stack()
self.stack.create()
rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.m.StubOutWithMock(generic_resource.SignalResource,
'handle_signal')
signal_details = {'status': 'COMPLETE'}
ret_expected = "Received COMPLETE signal"
generic_resource.SignalResource.handle_signal(
signal_details).AndReturn(ret_expected)
self.m.StubOutWithMock(generic_resource.SignalResource,
'_add_event')
generic_resource.SignalResource._add_event(
'SIGNAL', 'COMPLETE', 'Signal: %s' % ret_expected).AndReturn(None)
self.m.ReplayAll()
rsrc.signal(details=signal_details)
self.m.VerifyAll()
def test_signal_wrong_resource(self):
# assert that we get the correct exception when calling a
# resource.signal() that does not have a handle_signal()
self.stack = self.create_stack()
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['resource_X']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
err_metadata = {'Data': 'foo', 'Status': 'SUCCESS', 'UniqueId': '123'}
self.assertRaises(exception.ResourceActionNotSupported, rsrc.signal,
details=err_metadata)
self.m.VerifyAll()
def test_signal_reception_wrong_state(self):
# assert that we get the correct exception when calling a
# resource.signal() that is in having a destructive action.
self.stack = self.create_stack()
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
# manually override the action to DELETE
rsrc.action = rsrc.DELETE
err_metadata = {'Data': 'foo', 'Status': 'SUCCESS', 'UniqueId': '123'}
self.assertRaises(exception.ResourceFailure, rsrc.signal,
details=err_metadata)
self.m.VerifyAll()
def test_signal_reception_failed_call(self):
# assert that we get the correct exception from resource.signal()
# when resource.handle_signal() raises an exception.
self.stack = self.create_stack()
test_d = {'Data': 'foo', 'Reason': 'bar',
'Status': 'SUCCESS', 'UniqueId': '123'}
# to confirm we get a call to handle_signal
self.m.StubOutWithMock(generic_resource.SignalResource,
'handle_signal')
generic_resource.SignalResource.handle_signal(test_d).AndRaise(
ValueError)
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertRaises(exception.ResourceFailure,
rsrc.signal, details=test_d)
self.m.VerifyAll()