323 lines
14 KiB
Python
323 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 mock
|
|
from oslo.config import cfg
|
|
|
|
|
|
from heat.common import exception
|
|
from heat.common import template_format
|
|
from heat.engine import environment
|
|
from heat.engine import parser
|
|
from heat.engine import resource
|
|
# imports for mocking
|
|
from heat.engine.resources import autoscaling
|
|
from heat.engine.resources import image
|
|
from heat.engine.resources import instance
|
|
from heat.engine.resources import loadbalancer
|
|
from heat.engine.resources import nova_keypair
|
|
from heat.engine.resources import user
|
|
from heat.engine.resources import wait_condition as waitc
|
|
from heat.engine import signal_responder as signal
|
|
from heat.engine import stack_resource
|
|
from heat.openstack.common import timeutils
|
|
from heat.tests import common
|
|
from heat.tests import generic_resource
|
|
# reuse the same template than autoscaling tests
|
|
from heat.tests.test_autoscaling import as_template
|
|
from heat.tests import utils
|
|
|
|
|
|
class NotificationTest(common.HeatTestCase):
|
|
|
|
def setUp(self):
|
|
super(NotificationTest, self).setUp()
|
|
utils.setup_dummy_db()
|
|
|
|
cfg.CONF.import_opt('notification_driver',
|
|
'heat.openstack.common.notifier.api')
|
|
|
|
cfg.CONF.set_default('notification_driver',
|
|
['heat.openstack.common.notifier.test_notifier'])
|
|
cfg.CONF.set_default('host', 'test_host')
|
|
resource._register_class('GenericResource',
|
|
generic_resource.ResourceWithProps)
|
|
|
|
def create_test_stack(self):
|
|
test_template = {'Parameters': {'Foo': {'Type': 'String'},
|
|
'Pass': {'Type': 'String',
|
|
'NoEcho': True}},
|
|
'Resources':
|
|
{'TestResource': {'Type': 'GenericResource',
|
|
'Properties': {'Foo': 'abc'}}},
|
|
'Outputs': {'food':
|
|
{'Value':
|
|
{'Fn::GetAtt': ['TestResource',
|
|
'foo']}}}}
|
|
template = parser.Template(test_template)
|
|
self.ctx = utils.dummy_context()
|
|
self.ctx.tenant_id = 'test_tenant'
|
|
|
|
env = environment.Environment()
|
|
env.load({u'parameters':
|
|
{u'Foo': 'user_data', u'Pass': 'secret'}})
|
|
self.stack_name = utils.random_name()
|
|
stack = parser.Stack(self.ctx, self.stack_name, template,
|
|
env=env, disable_rollback=True)
|
|
self.stack = stack
|
|
stack.store()
|
|
self.created_time = stack.created_time
|
|
self.create_at = timeutils.isotime(self.created_time)
|
|
stack.create()
|
|
|
|
self.expected = {}
|
|
for action in ('create', 'suspend', 'delete'):
|
|
self.make_mocks(action)
|
|
|
|
def make_mocks(self, action):
|
|
stack_arn = self.stack.identifier().arn()
|
|
self.expected[action] = [
|
|
mock.call(self.ctx,
|
|
'orchestration.test_host',
|
|
'orchestration.stack.%s.start' % action,
|
|
'INFO',
|
|
{'state_reason': 'Stack %s started' % action.upper(),
|
|
'user_id': 'test_username',
|
|
'stack_identity': stack_arn,
|
|
'tenant_id': 'test_tenant',
|
|
'create_at': self.create_at,
|
|
'stack_name': self.stack_name,
|
|
'state': '%s_IN_PROGRESS' % action.upper()}),
|
|
mock.call(self.ctx, 'orchestration.test_host',
|
|
'orchestration.stack.%s.end' % action,
|
|
'INFO',
|
|
{'state_reason':
|
|
'Stack %s completed successfully' % action.upper(),
|
|
'user_id': 'test_username',
|
|
'stack_identity': stack_arn,
|
|
'tenant_id': 'test_tenant',
|
|
'create_at': self.create_at,
|
|
'stack_name': self.stack_name,
|
|
'state': '%s_COMPLETE' % action.upper()})]
|
|
|
|
@utils.stack_delete_after
|
|
def test_create_stack(self):
|
|
with mock.patch('heat.openstack.common.notifier.api.notify') \
|
|
as mock_notify:
|
|
self.create_test_stack()
|
|
self.assertEqual((self.stack.CREATE, self.stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
self.assertEqual(self.expected['create'],
|
|
mock_notify.call_args_list)
|
|
|
|
@utils.stack_delete_after
|
|
def test_create_and_suspend_stack(self):
|
|
with mock.patch('heat.openstack.common.notifier.api.notify') \
|
|
as mock_notify:
|
|
self.create_test_stack()
|
|
self.assertEqual((self.stack.CREATE, self.stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
self.assertEqual(self.expected['create'],
|
|
mock_notify.call_args_list)
|
|
self.stack.suspend()
|
|
self.assertEqual((self.stack.SUSPEND, self.stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
expected = self.expected['create'] + self.expected['suspend']
|
|
self.assertEqual(expected, mock_notify.call_args_list)
|
|
|
|
@utils.stack_delete_after
|
|
def test_create_and_delete_stack(self):
|
|
with mock.patch('heat.openstack.common.notifier.api.notify') \
|
|
as mock_notify:
|
|
self.create_test_stack()
|
|
self.assertEqual((self.stack.CREATE, self.stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
self.assertEqual(self.expected['create'],
|
|
mock_notify.call_args_list)
|
|
self.stack.delete()
|
|
self.assertEqual((self.stack.DELETE, self.stack.COMPLETE),
|
|
self.stack.state)
|
|
expected = self.expected['create'] + self.expected['delete']
|
|
|
|
self.assertEqual(expected, mock_notify.call_args_list)
|
|
|
|
|
|
class ScaleNotificationTest(common.HeatTestCase):
|
|
|
|
def setUp(self):
|
|
super(ScaleNotificationTest, self).setUp()
|
|
utils.setup_dummy_db()
|
|
|
|
cfg.CONF.import_opt('notification_driver',
|
|
'heat.openstack.common.notifier.api')
|
|
|
|
cfg.CONF.set_default('notification_driver',
|
|
['heat.openstack.common.notifier.test_notifier'])
|
|
cfg.CONF.set_default('host', 'test_host')
|
|
self.ctx = utils.dummy_context()
|
|
self.ctx.tenant_id = 'test_tenant'
|
|
|
|
def create_autoscaling_stack_and_get_group(self):
|
|
|
|
env = environment.Environment()
|
|
env.load({u'parameters':
|
|
{u'KeyName': 'foo', 'ImageId': 'cloudimage'}})
|
|
t = template_format.parse(as_template)
|
|
template = parser.Template(t)
|
|
self.stack_name = utils.random_name()
|
|
stack = parser.Stack(self.ctx, self.stack_name, template,
|
|
env=env, disable_rollback=True)
|
|
stack.store()
|
|
self.created_time = stack.created_time
|
|
self.create_at = timeutils.isotime(self.created_time)
|
|
stack.create()
|
|
self.stack = stack
|
|
group = stack['WebServerGroup']
|
|
self.assertEqual((group.CREATE, group.COMPLETE), group.state)
|
|
return group
|
|
|
|
def mock_stack_except_for_group(self):
|
|
self.m_validate = self.patchobject(parser.Stack, 'validate')
|
|
self.patchobject(nova_keypair.KeypairConstraint, 'validate')
|
|
self.patchobject(image.ImageConstraint, 'validate')
|
|
self.patchobject(instance.Instance, 'handle_create')\
|
|
.return_value = True
|
|
self.patchobject(instance.Instance, 'check_create_complete')\
|
|
.return_value = True
|
|
self.patchobject(stack_resource.StackResource,
|
|
'check_update_complete').return_value = True
|
|
|
|
self.patchobject(loadbalancer.LoadBalancer, 'handle_update')
|
|
self.patchobject(user.User, 'handle_create')
|
|
self.patchobject(user.AccessKey, 'handle_create')
|
|
self.patchobject(waitc.WaitCondition, 'handle_create')
|
|
self.patchobject(signal.SignalResponder, 'handle_create')
|
|
|
|
def expected_notifs_calls(self, group, adjust,
|
|
start_capacity, end_capacity=None,
|
|
with_error=None):
|
|
|
|
stack_arn = self.stack.identifier().arn()
|
|
expected = [mock.call(self.ctx,
|
|
'orchestration.test_host',
|
|
'orchestration.autoscaling.start',
|
|
'INFO',
|
|
{'state_reason':
|
|
'Stack CREATE completed successfully',
|
|
'user_id': 'test_username',
|
|
'stack_identity': stack_arn,
|
|
'tenant_id': 'test_tenant',
|
|
'create_at': self.create_at,
|
|
'adjustment_type': 'ChangeInCapacity',
|
|
'groupname': group.FnGetRefId(),
|
|
'capacity': start_capacity,
|
|
'adjustment': adjust,
|
|
'stack_name': self.stack_name,
|
|
'message': 'Start resizing the group %s' %
|
|
group.FnGetRefId(),
|
|
'state': 'CREATE_COMPLETE'})
|
|
]
|
|
if with_error:
|
|
expected += [mock.call(self.ctx,
|
|
'orchestration.test_host',
|
|
'orchestration.autoscaling.error',
|
|
'ERROR',
|
|
{'state_reason':
|
|
'Stack CREATE completed successfully',
|
|
'user_id': 'test_username',
|
|
'stack_identity': stack_arn,
|
|
'tenant_id': 'test_tenant',
|
|
'create_at': self.create_at,
|
|
'adjustment_type': 'ChangeInCapacity',
|
|
'groupname': group.FnGetRefId(),
|
|
'capacity': start_capacity,
|
|
'adjustment': adjust,
|
|
'stack_name': self.stack_name,
|
|
'message': with_error,
|
|
'state': 'CREATE_COMPLETE'})
|
|
]
|
|
else:
|
|
expected += [mock.call(self.ctx,
|
|
'orchestration.test_host',
|
|
'orchestration.autoscaling.end',
|
|
'INFO',
|
|
{'state_reason':
|
|
'Stack CREATE completed successfully',
|
|
'user_id': 'test_username',
|
|
'stack_identity': stack_arn,
|
|
'tenant_id': 'test_tenant',
|
|
'create_at': self.create_at,
|
|
'adjustment_type': 'ChangeInCapacity',
|
|
'groupname': group.FnGetRefId(),
|
|
'capacity': end_capacity,
|
|
'adjustment': adjust,
|
|
'stack_name': self.stack_name,
|
|
'message': 'End resizing the group %s' %
|
|
group.FnGetRefId(),
|
|
'state': 'CREATE_COMPLETE'})
|
|
]
|
|
|
|
return expected
|
|
|
|
@utils.stack_delete_after
|
|
def test_scale_success(self):
|
|
with mock.patch('heat.engine.notification.stack.send'):
|
|
with mock.patch('heat.openstack.common.notifier.api.notify') \
|
|
as mock_notify:
|
|
|
|
self.mock_stack_except_for_group()
|
|
group = self.create_autoscaling_stack_and_get_group()
|
|
expected = self.expected_notifs_calls(group,
|
|
adjust=1,
|
|
start_capacity=1,
|
|
end_capacity=2,
|
|
)
|
|
group.adjust(1)
|
|
self.assertEqual(2, len(group.get_instance_names()))
|
|
mock_notify.assert_has_calls(expected)
|
|
|
|
expected = self.expected_notifs_calls(group,
|
|
adjust=-1,
|
|
start_capacity=2,
|
|
end_capacity=1,
|
|
)
|
|
group.adjust(-1)
|
|
self.assertEqual(1, len(group.get_instance_names()))
|
|
mock_notify.assert_has_calls(expected)
|
|
|
|
@utils.stack_delete_after
|
|
def test_scaleup_failure(self):
|
|
with mock.patch('heat.engine.notification.stack.send'):
|
|
with mock.patch('heat.openstack.common.notifier.api.notify') \
|
|
as mock_notify:
|
|
|
|
self.mock_stack_except_for_group()
|
|
group = self.create_autoscaling_stack_and_get_group()
|
|
|
|
err_message = 'Boooom'
|
|
m_as = self.patchobject(autoscaling.AutoScalingGroup, 'resize')
|
|
m_as.side_effect = exception.Error(err_message)
|
|
|
|
expected = self.expected_notifs_calls(group,
|
|
adjust=2,
|
|
start_capacity=1,
|
|
with_error=err_message,
|
|
)
|
|
self.assertRaises(exception.Error, group.adjust, 2)
|
|
self.assertEqual(1, len(group.get_instance_names()))
|
|
mock_notify.assert_has_calls(expected)
|