heat/heat/tests/engine/service/test_stack_create.py

386 lines
17 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 oslo_messaging.rpc import dispatcher
from oslo_service import threadgroup
import six
from heat.common import environment_util as env_util
from heat.common import exception
from heat.engine.clients.os import glance
from heat.engine.clients.os import nova
from heat.engine import environment
from heat.engine import properties
from heat.engine.resources.aws.ec2 import instance as instances
from heat.engine import service
from heat.engine import stack
from heat.engine import template as templatem
from heat.objects import stack as stack_object
from heat.tests import common
from heat.tests.engine import tools
from heat.tests.openstack.nova import fakes as fakes_nova
from heat.tests import utils
class StackCreateTest(common.HeatTestCase):
def setUp(self):
super(StackCreateTest, self).setUp()
self.ctx = utils.dummy_context()
self.man = service.EngineService('a-host', 'a-topic')
self.man.thread_group_mgr = service.ThreadGroupManager()
@mock.patch.object(threadgroup, 'ThreadGroup')
@mock.patch.object(stack.Stack, 'validate')
def _test_stack_create(self, stack_name, mock_validate, mock_tg,
environment_files=None):
mock_tg.return_value = tools.DummyThreadGroup()
params = {'foo': 'bar'}
template = '{ "Template": "data" }'
stk = tools.get_stack(stack_name, self.ctx)
mock_tmpl = self.patchobject(templatem, 'Template', return_value=stk.t)
mock_env = self.patchobject(environment, 'Environment',
return_value=stk.env)
mock_stack = self.patchobject(stack, 'Stack', return_value=stk)
mock_merge = self.patchobject(env_util, 'merge_environments')
result = self.man.create_stack(self.ctx, stack_name,
template, params, None, {},
environment_files=environment_files)
self.assertEqual(stk.identifier(), result)
self.assertIsInstance(result, dict)
self.assertTrue(result['stack_id'])
mock_tmpl.assert_called_once_with(template, files=None)
mock_env.assert_called_once_with(params)
mock_stack.assert_called_once_with(
self.ctx, stack_name, stk.t, owner_id=None, nested_depth=0,
user_creds_id=None, stack_user_project_id=None,
convergence=cfg.CONF.convergence_engine, parent_resource=None)
if environment_files:
mock_merge.assert_called_once_with(environment_files, None,
params, mock.ANY)
mock_validate.assert_called_once_with()
def test_stack_create(self):
stack_name = 'service_create_test_stack'
self._test_stack_create(stack_name)
def test_stack_create_with_environment_files(self):
stack_name = 'env_files_test_stack'
environment_files = ['env_1', 'env_2']
self._test_stack_create(stack_name,
environment_files=environment_files)
def test_stack_create_equals_max_per_tenant(self):
cfg.CONF.set_override('max_stacks_per_tenant', 1)
stack_name = 'service_create_test_stack_equals_max'
self._test_stack_create(stack_name)
def test_stack_create_exceeds_max_per_tenant(self):
cfg.CONF.set_override('max_stacks_per_tenant', 0)
stack_name = 'service_create_test_stack_exceeds_max'
ex = self.assertRaises(dispatcher.ExpectedException,
self._test_stack_create, stack_name)
self.assertEqual(exception.RequestLimitExceeded, ex.exc_info[0])
self.assertIn("You have reached the maximum stacks per tenant",
six.text_type(ex.exc_info[1]))
@mock.patch.object(stack.Stack, 'validate')
def test_stack_create_verify_err(self, mock_validate):
mock_validate.side_effect = exception.StackValidationFailed(message='')
stack_name = 'service_create_verify_err_test_stack'
params = {'foo': 'bar'}
template = '{ "Template": "data" }'
stk = tools.get_stack(stack_name, self.ctx)
mock_tmpl = self.patchobject(templatem, 'Template', return_value=stk.t)
mock_env = self.patchobject(environment, 'Environment',
return_value=stk.env)
mock_stack = self.patchobject(stack, 'Stack', return_value=stk)
ex = self.assertRaises(dispatcher.ExpectedException,
self.man.create_stack,
self.ctx, stack_name, template, params,
None, {})
self.assertEqual(exception.StackValidationFailed, ex.exc_info[0])
mock_tmpl.assert_called_once_with(template, files=None)
mock_env.assert_called_once_with(params)
mock_stack.assert_called_once_with(
self.ctx, stack_name, stk.t, owner_id=None, nested_depth=0,
user_creds_id=None, stack_user_project_id=None,
convergence=cfg.CONF.convergence_engine, parent_resource=None)
def test_stack_create_invalid_stack_name(self):
stack_name = 'service_create_test_stack_invalid_name'
stack = tools.get_stack('test_stack', self.ctx)
self.assertRaises(dispatcher.ExpectedException,
self.man.create_stack,
self.ctx, stack_name, stack.t.t, {}, None, {})
def test_stack_create_invalid_resource_name(self):
stack_name = 'stack_create_invalid_resource_name'
stk = tools.get_stack(stack_name, self.ctx)
tmpl = dict(stk.t)
tmpl['resources']['Web/Server'] = tmpl['resources']['WebServer']
del tmpl['resources']['WebServer']
ex = self.assertRaises(dispatcher.ExpectedException,
self.man.create_stack,
self.ctx, stack_name,
stk.t.t, {}, None, {})
self.assertEqual(exception.StackValidationFailed, ex.exc_info[0])
@mock.patch.object(stack.Stack, 'create_stack_user_project_id')
def test_stack_create_authorization_failure(self, mock_create):
stack_name = 'stack_create_authorization_failure'
stk = tools.get_stack(stack_name, self.ctx)
mock_create.side_effect = exception.AuthorizationFailure
ex = self.assertRaises(dispatcher.ExpectedException,
self.man.create_stack,
self.ctx, stack_name,
stk.t.t, {}, None, {})
self.assertEqual(exception.StackValidationFailed, ex.exc_info[0])
def test_stack_create_no_credentials(self):
cfg.CONF.set_default('deferred_auth_method', 'password')
stack_name = 'test_stack_create_no_credentials'
params = {'foo': 'bar'}
template = '{ "Template": "data" }'
stk = tools.get_stack(stack_name, self.ctx)
# force check for credentials on create
stk['WebServer'].requires_deferred_auth = True
mock_tmpl = self.patchobject(templatem, 'Template', return_value=stk.t)
mock_env = self.patchobject(environment, 'Environment',
return_value=stk.env)
mock_stack = self.patchobject(stack, 'Stack', return_value=stk)
# test stack create using context without password
ctx_no_pwd = utils.dummy_context(password=None)
ex = self.assertRaises(dispatcher.ExpectedException,
self.man.create_stack,
ctx_no_pwd, stack_name,
template, params, None, {}, None)
self.assertEqual(exception.MissingCredentialError, ex.exc_info[0])
self.assertEqual('Missing required credential: X-Auth-Key',
six.text_type(ex.exc_info[1]))
mock_tmpl.assert_called_once_with(template, files=None)
mock_env.assert_called_once_with(params)
mock_stack.assert_called_once_with(
ctx_no_pwd, stack_name, stk.t, owner_id=None, nested_depth=0,
user_creds_id=None, stack_user_project_id=None,
convergence=cfg.CONF.convergence_engine, parent_resource=None)
mock_tmpl.reset_mock()
mock_env.reset_mock()
mock_stack.reset_mock()
# test stack create using context without user
ctx_no_pwd = utils.dummy_context(password=None)
ctx_no_user = utils.dummy_context(user=None)
ex = self.assertRaises(dispatcher.ExpectedException,
self.man.create_stack,
ctx_no_user, stack_name,
template, params, None, {})
self.assertEqual(exception.MissingCredentialError, ex.exc_info[0])
self.assertEqual('Missing required credential: X-Auth-User',
six.text_type(ex.exc_info[1]))
mock_tmpl.assert_called_once_with(template, files=None)
mock_env.assert_called_once_with(params)
mock_stack.assert_called_once_with(
ctx_no_user, stack_name, stk.t, owner_id=None, nested_depth=0,
user_creds_id=None, stack_user_project_id=None,
convergence=cfg.CONF.convergence_engine, parent_resource=None)
@mock.patch.object(stack_object.Stack, 'count_total_resources')
def test_stack_create_total_resources_equals_max(self, ctr):
stack_name = 'stack_create_total_resources_equals_max'
params = {}
tpl = {
'heat_template_version': '2014-10-16',
'resources': {
'A': {'type': 'GenericResourceType'},
'B': {'type': 'GenericResourceType'},
'C': {'type': 'GenericResourceType'}
}
}
template = templatem.Template(tpl)
stk = stack.Stack(self.ctx, stack_name, template)
ctr.return_value = 3
mock_tmpl = self.patchobject(templatem, 'Template', return_value=stk.t)
mock_env = self.patchobject(environment, 'Environment',
return_value=stk.env)
mock_stack = self.patchobject(stack, 'Stack', return_value=stk)
cfg.CONF.set_override('max_resources_per_stack', 3)
result = self.man.create_stack(self.ctx, stack_name, template, params,
None, {})
mock_tmpl.assert_called_once_with(template, files=None)
mock_env.assert_called_once_with(params)
mock_stack.assert_called_once_with(
self.ctx, stack_name, stk.t, owner_id=None, nested_depth=0,
user_creds_id=None, stack_user_project_id=None,
convergence=cfg.CONF.convergence_engine, parent_resource=None)
self.assertEqual(stk.identifier(), result)
root_stack_id = stk.root_stack_id()
self.assertEqual(3, stk.total_resources(root_stack_id))
self.man.thread_group_mgr.groups[stk.id].wait()
stk.delete()
def test_stack_create_total_resources_exceeds_max(self):
stack_name = 'stack_create_total_resources_exceeds_max'
params = {}
tpl = {
'heat_template_version': '2014-10-16',
'resources': {
'A': {'type': 'GenericResourceType'},
'B': {'type': 'GenericResourceType'},
'C': {'type': 'GenericResourceType'}
}
}
cfg.CONF.set_override('max_resources_per_stack', 2)
ex = self.assertRaises(dispatcher.ExpectedException,
self.man.create_stack, self.ctx, stack_name,
tpl, params, None, {})
self.assertEqual(exception.RequestLimitExceeded, ex.exc_info[0])
self.assertIn(exception.StackResourceLimitExceeded.msg_fmt,
six.text_type(ex.exc_info[1]))
@mock.patch.object(threadgroup, 'ThreadGroup')
@mock.patch.object(stack.Stack, 'validate')
def test_stack_create_nested(self, mock_validate, mock_tg):
convergence_engine = cfg.CONF.convergence_engine
stack_name = 'service_create_nested_test_stack'
parent_stack = tools.get_stack(stack_name + '_parent', self.ctx)
owner_id = parent_stack.store()
mock_tg.return_value = tools.DummyThreadGroup()
stk = tools.get_stack(stack_name, self.ctx, with_params=True,
owner_id=owner_id, nested_depth=1)
tmpl_id = stk.t.store(self.ctx)
mock_load = self.patchobject(templatem.Template, 'load',
return_value=stk.t)
mock_stack = self.patchobject(stack, 'Stack', return_value=stk)
result = self.man.create_stack(self.ctx, stack_name, None,
None, None, {},
owner_id=owner_id, nested_depth=1,
template_id=tmpl_id)
self.assertEqual(stk.identifier(), result)
self.assertIsInstance(result, dict)
self.assertTrue(result['stack_id'])
mock_load.assert_called_once_with(self.ctx, tmpl_id)
mock_stack.assert_called_once_with(self.ctx, stack_name, stk.t,
owner_id=owner_id, nested_depth=1,
user_creds_id=None,
stack_user_project_id=None,
convergence=convergence_engine,
parent_resource=None)
mock_validate.assert_called_once_with()
def test_stack_validate(self):
stack_name = 'stack_create_test_validate'
stk = tools.get_stack(stack_name, self.ctx)
fc = fakes_nova.FakeClient()
self.patchobject(nova.NovaClientPlugin, '_create', return_value=fc)
self.patchobject(glance.GlanceClientPlugin, 'find_image_by_name_or_id',
return_value=744)
resource = stk['WebServer']
resource.properties = properties.Properties(
resource.properties_schema,
{
'ImageId': 'CentOS 5.2',
'KeyName': 'test',
'InstanceType': 'm1.large'
},
context=self.ctx)
stk.validate()
resource.properties = properties.Properties(
resource.properties_schema,
{
'KeyName': 'test',
'InstanceType': 'm1.large'
},
context=self.ctx)
self.assertRaises(exception.StackValidationFailed, stk.validate)
def test_validate_deferred_auth_context_trusts(self):
stk = tools.get_stack('test_deferred_auth', self.ctx)
stk['WebServer'].requires_deferred_auth = True
ctx = utils.dummy_context(user=None, password=None)
cfg.CONF.set_default('deferred_auth_method', 'trusts')
# using trusts, no username or password required
self.man._validate_deferred_auth_context(ctx, stk)
def test_validate_deferred_auth_context_not_required(self):
stk = tools.get_stack('test_deferred_auth', self.ctx)
stk['WebServer'].requires_deferred_auth = False
ctx = utils.dummy_context(user=None, password=None)
cfg.CONF.set_default('deferred_auth_method', 'password')
# stack performs no deferred operations, so no username or
# password required
self.man._validate_deferred_auth_context(ctx, stk)
def test_validate_deferred_auth_context_missing_credentials(self):
stk = tools.get_stack('test_deferred_auth', self.ctx)
stk['WebServer'].requires_deferred_auth = True
cfg.CONF.set_default('deferred_auth_method', 'password')
# missing username
ctx = utils.dummy_context(user=None)
ex = self.assertRaises(exception.MissingCredentialError,
self.man._validate_deferred_auth_context,
ctx, stk)
self.assertEqual('Missing required credential: X-Auth-User',
six.text_type(ex))
# missing password
ctx = utils.dummy_context(password=None)
ex = self.assertRaises(exception.MissingCredentialError,
self.man._validate_deferred_auth_context,
ctx, stk)
self.assertEqual('Missing required credential: X-Auth-Key',
six.text_type(ex))
@mock.patch.object(instances.Instance, 'validate')
@mock.patch.object(stack.Stack, 'total_resources')
def test_stack_create_max_unlimited(self, total_res_mock, validate_mock):
total_res_mock.return_value = 9999
validate_mock.return_value = None
cfg.CONF.set_override('max_resources_per_stack', -1)
stack_name = 'service_create_test_max_unlimited'
self._test_stack_create(stack_name)