Adds ignore_errors for template_validate in engine

It enhance the validate_template to ignore the given
set of errors while the template is validated.

implements blueprint heat-template-validate-improvements

Change-Id: Id6d85664ae576b6a5d18dbbe84ddf0122fbfaa40
This commit is contained in:
Kanagaraj Manickam 2015-11-18 15:46:43 +05:30
parent 768839d5d5
commit 29e5d96d50
6 changed files with 92 additions and 15 deletions

View File

@ -140,17 +140,25 @@ class Resource(object):
assert issubclass(ResourceClass, Resource)
if not ResourceClass.is_service_available(stack.context):
ex = exception.ResourceTypeUnavailable(
service_name=ResourceClass.default_client_name,
resource_type=definition.resource_type
if not stack.service_check_defer:
ResourceClass._validate_service_availability(
stack.context,
definition.resource_type
)
return super(Resource, cls).__new__(ResourceClass)
@classmethod
def _validate_service_availability(cls, context, resource_type):
if not cls.is_service_available(context):
ex = exception.ResourceTypeUnavailable(
service_name=cls.default_client_name,
resource_type=resource_type
)
LOG.info(six.text_type(ex))
raise ex
return super(Resource, cls).__new__(ResourceClass)
def _init_attributes(self):
"""The method that defines attribute initialization for a resource.
@ -1155,6 +1163,12 @@ class Resource(object):
in an overridden validate() such as accessing properties
may not work.
"""
if self.stack.service_check_defer:
self._validate_service_availability(
self.stack.context,
self.t.resource_type
)
function.validate(self.t)
self.validate_deletion_policy(self.t.deletion_policy())
self.t.update_policy(self.update_policy_schema,

View File

@ -292,7 +292,7 @@ class EngineService(service.Service):
by the RPC caller.
"""
RPC_API_VERSION = '1.23'
RPC_API_VERSION = '1.24'
def __init__(self, host, topic):
super(EngineService, self).__init__()
@ -995,7 +995,8 @@ class EngineService(service.Service):
@context.request_context
def validate_template(self, cnxt, template, params=None, files=None,
environment_files=None, show_nested=False):
environment_files=None, show_nested=False,
ignorable_errors=None):
"""Check the validity of a template.
Checks, so far as we can, that a template is valid, and returns
@ -1010,12 +1011,25 @@ class EngineService(service.Service):
names included in the files dict
:type environment_files: list or None
:param show_nested: if True, any nested templates will be checked
:param ignorable_errors: List of error_code to be ignored as part of
validation
"""
LOG.info(_LI('validate_template'))
if template is None:
msg = _("No Template provided.")
return webob.exc.HTTPBadRequest(explanation=msg)
service_check_defer = False
if ignorable_errors:
invalid_codes = (set(ignorable_errors) -
set(exception.ERROR_CODE_MAP.keys()))
if invalid_codes:
msg = (_("Invalid codes in ignore_errors : %s") %
list(invalid_codes))
return webob.exc.HTTPBadRequest(explanation=msg)
service_check_defer = True
env = environment.Environment(params)
tmpl = templatem.Template(template, files=files, env=env)
try:
@ -1024,10 +1038,12 @@ class EngineService(service.Service):
return {'Error': six.text_type(ex)}
stack_name = 'dummy'
stack = parser.Stack(cnxt, stack_name, tmpl, strict_validate=False)
stack.resource_validate = False
stack = parser.Stack(cnxt, stack_name, tmpl,
strict_validate=False,
resource_validate=False,
service_check_defer=service_check_defer)
try:
stack.validate()
stack.validate(ignorable_errors=ignorable_errors)
except exception.StackValidationFailed as ex:
return {'Error': six.text_type(ex)}

View File

@ -120,7 +120,8 @@ class Stack(collections.Mapping):
use_stored_context=False, username=None,
nested_depth=0, strict_validate=True, convergence=False,
current_traversal=None, tags=None, prev_raw_template_id=None,
current_deps=None, cache_data=None, resource_validate=True):
current_deps=None, cache_data=None, resource_validate=True,
service_check_defer=False):
"""Initialise the Stack.
@ -193,6 +194,12 @@ class Stack(collections.Mapping):
# commonly done in plugin validate() methods
self.resource_validate = resource_validate
# service_check_defer can be used to defer the validation of service
# availability for a given resource, which helps to create the resource
# dependency tree completely when respective service is not available,
# especially during template_validate
self.service_check_defer = service_check_defer
if use_stored_context:
self.context = self.stored_context()
self.context.roles = self.context.clients.client(
@ -671,7 +678,7 @@ class Stack(collections.Mapping):
return handler and handler(resource_name)
@profiler.trace('Stack.validate', hide_args=False)
def validate(self):
def validate(self, ignorable_errors=None):
"""Validates the stack."""
# TODO(sdake) Should return line number of invalid reference
@ -706,7 +713,10 @@ class Stack(collections.Mapping):
result = res.validate_template()
except exception.HeatException as ex:
LOG.debug('%s', ex)
raise
if ignorable_errors and ex.error_code in ignorable_errors:
result = None
else:
raise ex
except AssertionError:
raise
except Exception as ex:

View File

@ -40,7 +40,7 @@ class ServiceEngineTest(common.HeatTestCase):
def test_make_sure_rpc_version(self):
self.assertEqual(
'1.23',
'1.24',
service.EngineService.RPC_API_VERSION,
('RPC version is changed, please update this test to new version '
'and make sure additional test cases are added for RPC APIs '

View File

@ -3128,6 +3128,7 @@ class ResourceAvailabilityTest(common.HeatTestCase):
resource_type='UnavailableResourceType')
mock_stack = mock.MagicMock()
mock_stack.service_check_defer = False
ex = self.assertRaises(
exception.ResourceTypeUnavailable,

View File

@ -14,6 +14,7 @@
import mock
from oslo_messaging.rpc import dispatcher
import six
import webob
from heat.common import exception
from heat.common.i18n import _
@ -1668,3 +1669,38 @@ class ValidateTest(common.HeatTestCase):
t,
{})
self.assertEqual(exception.ResourceTypeUnavailable, ex.exc_info[0])
def test_validate_with_ignorable_errors(self):
t = template_format.parse(
"""
heat_template_version: 2015-10-15
resources:
my_instance:
type: AWS::EC2::Instance
""")
engine = service.EngineService('a', 't')
self.mock_is_service_available.return_value = False
res = dict(engine.validate_template(
self.ctx,
t,
{},
ignorable_errors=[exception.ResourceTypeUnavailable.error_code]))
expected = {'Description': 'No description', 'Parameters': {}}
self.assertEqual(expected, res)
def test_validate_with_ignorable_errors_invalid_error_code(self):
engine = service.EngineService('a', 't')
invalide_error_code = '123456'
invalid_codes = ['99001', invalide_error_code]
res = engine.validate_template(
self.ctx,
mock.MagicMock(),
{},
ignorable_errors=invalid_codes)
msg = _("Invalid codes in ignore_errors : %s") % [invalide_error_code]
ex = webob.exc.HTTPBadRequest(explanation=msg)
self.assertIsInstance(res, webob.exc.HTTPBadRequest)
self.assertEqual(ex.explanation, res.explanation)