heat/heat/tests/test_signal.py

410 lines
14 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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
from oslo.config import cfg
from heat.tests import generic_resource
from heat.tests import fakes
from heat.tests.common import HeatTestCase
from heat.tests import utils
from heat.common import exception
from heat.common import template_format
from heat.db import api as db_api
from heat.engine import clients
from heat.engine import parser
from heat.engine import resource
from heat.engine import scheduler
from heat.engine import signal_responder as sr
from keystoneclient import exceptions as kc_exceptions
test_template_signal = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Just a test.",
"Parameters" : {},
"Resources" : {
"signal_handler" : {"Type" : "SignalResourceType"},
"resource_X" : {"Type" : "GenericResourceType"}
},
"Outputs": {
"signed_url": {"Fn::GetAtt": ["signal_handler", "AlarmUrl"]}
}
}
'''
class SignalTest(HeatTestCase):
def setUp(self):
super(SignalTest, self).setUp()
utils.setup_dummy_db()
resource._register_class('SignalResourceType',
generic_resource.SignalResource)
resource._register_class('GenericResourceType',
generic_resource.GenericResource)
cfg.CONF.set_default('heat_waitcondition_server_url',
'http://server.test:8000/v1/waitcondition')
self.stack_id = 'STACKABCD1234'
self.fc = fakes.FakeKeystoneClient()
def tearDown(self):
super(SignalTest, self).tearDown()
utils.reset_dummy_db()
# Note tests creating a stack should be decorated with @stack_delete_after
# to ensure the stack is properly cleaned up
def create_stack(self, stack_name='test_stack', stub=True):
temp = template_format.parse(test_template_signal)
template = parser.Template(temp)
ctx = utils.dummy_context()
ctx.tenant_id = 'test_tenant'
stack = parser.Stack(ctx, stack_name, template,
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.m.StubOutWithMock(sr.SignalResponder, 'keystone')
sr.SignalResponder.keystone().MultipleTimes().AndReturn(
self.fc)
self.m.ReplayAll()
return stack
@utils.stack_delete_after
def test_handle_create_fail_user(self):
self.stack = self.create_stack(stack_name='create_fail_user',
stub=False)
class FakeKeystoneClientFail(fakes.FakeKeystoneClient):
def create_stack_user(self, name):
raise kc_exceptions.Forbidden("Denied!")
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
clients.OpenStackClients.keystone().MultipleTimes().AndReturn(
FakeKeystoneClientFail())
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state)
self.assertIn('Forbidden', rsrc.status_reason)
@utils.stack_delete_after
def test_handle_create_fail_keypair_raise(self):
self.stack = self.create_stack(stack_name='create_fail_keypair',
stub=False)
class FakeKeystoneClientFail(fakes.FakeKeystoneClient):
def create_ec2_keypair(self, name):
raise kc_exceptions.Forbidden("Denied!")
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
clients.OpenStackClients.keystone().MultipleTimes().AndReturn(
FakeKeystoneClientFail(user_id='123xyz'))
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state)
self.assertIn('Forbidden', rsrc.status_reason)
self.assertEqual('123xyz', rsrc.resource_id)
@utils.stack_delete_after
def test_handle_create_fail_keypair_none(self):
self.stack = self.create_stack(stack_name='create_fail_keypair',
stub=False)
class FakeKeystoneClientFail(fakes.FakeKeystoneClient):
def create_ec2_keypair(self, name):
return None
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
clients.OpenStackClients.keystone().MultipleTimes().AndReturn(
FakeKeystoneClientFail(user_id='123xyz'))
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state)
self.assertIn('Error creating ec2 keypair', rsrc.status_reason)
self.assertEqual('123xyz', rsrc.resource_id, '123xyz')
@utils.stack_delete_after
def test_resource_data(self):
self.stack = self.create_stack(stack_name='resource_data_test',
stub=False)
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
clients.OpenStackClients.keystone().MultipleTimes().AndReturn(
fakes.FakeKeystoneClient(
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 = db_api.resource_data_get_all(rsrc)
self.assertEqual('anaccesskey', rs_data.get('access_key'))
self.assertEqual('verysecret', rs_data.get('secret_key'))
self.assertEqual(2, len(rs_data.keys()))
# And that we remove it on delete
scheduler.TaskRunner(rsrc.delete)()
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
rs_data = db_api.resource_data_get_all(rsrc)
self.assertEqual(0, len(rs_data.keys()))
@utils.stack_delete_after
def test_FnGetAtt_Alarm_Url(self):
self.stack = self.create_stack()
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()
@utils.stack_delete_after
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('AlarmUrl')
second_url = rsrc.FnGetAtt('AlarmUrl')
self.assertEqual(first_url, second_url)
self.m.VerifyAll()
@utils.stack_delete_after
def test_FnGetAtt_delete(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)
self.assertIn('http://server.test:8000/v1/signal',
rsrc.FnGetAtt('AlarmUrl'))
scheduler.TaskRunner(rsrc.delete)()
self.assertEqual('None', rsrc.FnGetAtt('AlarmUrl'))
self.m.VerifyAll()
@utils.stack_delete_after
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.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
clients.OpenStackClients.keystone().MultipleTimes().AndReturn(
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()
@utils.stack_delete_after
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()
@utils.stack_delete_after
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()
self.m.UnsetStubs()
# Since we unset the stubs above we must re-stub keystone to keep the
# test isolated from keystoneclient. The unset stubs is done so that we
# do not have to mock out all of the deleting that the
# stack_delete_after decorator will do during cleanup.
self.m.StubOutWithMock(self.stack.clients, 'keystone')
self.stack.clients.keystone().AndReturn(self.fc)
self.m.ReplayAll()
@utils.stack_delete_after
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.ResourceFailure, rsrc.signal,
details=err_metadata)
self.m.VerifyAll()
@utils.stack_delete_after
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()
@utils.stack_delete_after
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()