You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
547 lines
19 KiB
547 lines
19 KiB
# |
|
# 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()
|
|
|