deb-heat/heat/tests/test_signal.py
Angus Salkeld 6a48c45bfd Don't create events when signals don't perform an action
If we get repeat signals that cause no concrete actions
this can cause large quantities of senseless events that
suppress useful events.

Change-Id: I79374d27648319f241f36ab041784fab37823ddb
Closes-bug: #1445361
2015-05-06 11:28:44 +10:00

399 lines
14 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 six
from keystoneclient import exceptions as kc_exceptions
from oslo_config import cfg
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
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_template_signal = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Just a test.",
"Parameters" : {},
"Resources" : {
"signal_handler" : {"Type" : "SignalResourceType"},
"resource_X" : {"Type" : "GenericResourceType"}
}
}
'''
class SignalTest(common.HeatTestCase):
def setUp(self):
super(SignalTest, self).setUp()
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'
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))
ctx = utils.dummy_context()
ctx.tenant_id = 'test_tenant'
stack = parser.Stack(ctx, stack_name, templ,
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_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',
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)
# 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.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('AlarmUrl')
second_url = rsrc.FnGetAtt('AlarmUrl')
self.assertEqual(first_url, second_url)
self.m.VerifyAll()
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()
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()