Merge "Updates template_validate call to validate parameter_groups."

This commit is contained in:
Jenkins 2014-02-17 06:02:58 +00:00 committed by Gerrit Code Review
commit 7100cfa147
4 changed files with 277 additions and 0 deletions

View File

@ -0,0 +1,68 @@
# 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.
from heat.openstack.common import log as logging
from heat.openstack.common.gettextutils import _
from heat.common.exception import StackValidationFailed
logger = logging.getLogger(__name__)
PARAMETER_GROUPS = 'parameter_groups'
PARAMETERS = 'parameters'
class ParameterGroups(object):
'''
The ParameterGroups specified by the stack's template.
'''
def __init__(self, tmpl):
self.tmpl = tmpl
self.parameters = tmpl.parameters(None, {}, validate_value=False)
logger.debug(self.tmpl)
logger.debug(self.parameters)
self.parameter_names = []
if self.parameters:
self.parameter_names = [param for param in self.parameters]
self.parameter_groups = tmpl.get(PARAMETER_GROUPS)
def validate(self):
'''
Validate that a parameter belongs to only one Parameter Group
and that each parameter name references a valid parameter.
'''
logger.debug(_('Validating Parameter Groups.'))
logger.debug(self.parameter_names)
if self.parameter_groups is not None:
#Loop through groups and validate parameters
grouped_parameters = []
for group in self.parameter_groups:
parameters = group.get(PARAMETERS)
if parameters is None:
raise StackValidationFailed(message=_(
'Parameters must be provided for '
'each Parameter Group.'))
for param in parameters:
#Check if param has been added to a previous group
if param in grouped_parameters:
raise StackValidationFailed(message=_(
'The %s parameter must be assigned to one '
'Parameter Group only.') % param)
else:
grouped_parameters.append(param)
#Check that grouped parameter references a valid Parameter
if param not in self.parameter_names:
raise StackValidationFailed(message=_(
'The Parameter name (%s) does not reference '
'an existing parameter.') % param)

View File

@ -31,6 +31,7 @@ from heat.engine import scheduler
from heat.engine import timestamp
from heat.engine import update
from heat.engine.notification import stack as notification
from heat.engine.parameter_groups import ParameterGroups
from heat.engine.template import Template
from heat.engine.clients import Clients
from heat.db import api as db_api
@ -323,6 +324,10 @@ class Stack(collections.Mapping):
'''
# TODO(sdake) Should return line number of invalid reference
# Validate Parameter Groups
parameter_groups = ParameterGroups(self.t)
parameter_groups.validate()
# Check duplicate names between parameters and resources
dup_names = set(self.parameters.keys()) & set(self.keys())

View File

@ -35,6 +35,7 @@ from heat.engine import environment
from heat.common import exception
from heat.common import identifier
from heat.common import heat_keystoneclient as hkc
from heat.engine import parameter_groups
from heat.engine import parser
from heat.engine import properties
from heat.engine import resource
@ -525,11 +526,16 @@ class EngineService(service.Service):
tmpl_params = tmpl.parameters(None, {}, validate_value=False)
is_real_param = lambda p: p.name not in tmpl_params.PSEUDO_PARAMETERS
params = tmpl_params.map(api.format_validate_parameter, is_real_param)
param_groups = parameter_groups.ParameterGroups(tmpl)
result = {
'Description': tmpl.get('Description', ''),
'Parameters': params,
}
if param_groups.parameter_groups:
result['ParameterGroups'] = param_groups.parameter_groups
return result
@request_context

View File

@ -25,6 +25,7 @@ from heat.engine.resources import instance as instances
from heat.engine import service
from heat.openstack.common.importutils import try_import
from heat.engine import parser
from heat.engine.hot import HOTemplate
from heat.tests.common import HeatTestCase
from heat.tests import utils
@ -537,6 +538,163 @@ test_template_unique_logical_name = '''
}
'''
test_template_duplicate_parameters = '''
# This is a hello world HOT template just defining a single compute instance
heat_template_version: 2013-05-23
description: >
Hello world HOT template that just defines a single compute instance.
Contains just base features to verify base HOT support.
parameter_groups:
- label: Server Group
description: A group of parameters for the server
parameters:
- InstanceType
- KeyName
- ImageId
- label: Database Group
description: A group of parameters for the database
parameters:
- db_password
- db_port
- InstanceType
parameters:
KeyName:
type: string
description: Name of an existing key pair to use for the instance
InstanceType:
type: string
description: Instance type for the instance to be created
default: m1.small
constraints:
- allowed_values: [m1.tiny, m1.small, m1.large]
description: Value must be one of 'm1.tiny', 'm1.small' or 'm1.large'
ImageId:
type: string
description: ID of the image to use for the instance
# parameters below are not used in template, but are for verifying parameter
# validation support in HOT
db_password:
type: string
description: Database password
hidden: true
constraints:
- length: { min: 6, max: 8 }
description: Password length must be between 6 and 8 characters
- allowed_pattern: "[a-zA-Z0-9]+"
description: Password must consist of characters and numbers only
- allowed_pattern: "[A-Z]+[a-zA-Z0-9]*"
description: Password must start with an uppercase character
db_port:
type: number
description: Database port number
default: 50000
constraints:
- range: { min: 40000, max: 60000 }
description: Port number must be between 40000 and 60000
resources:
my_instance:
# Use an AWS resource type since this exists; so why use other name here?
type: AWS::EC2::Instance
properties:
KeyName: { get_param: KeyName }
ImageId: { get_param: ImageId }
InstanceType: { get_param: InstanceType }
outputs:
instance_ip:
description: The IP address of the deployed instance
value: { get_attr: [my_instance, PublicIp] }
'''
test_template_invalid_parameter_name = '''
# This is a hello world HOT template just defining a single compute instance
heat_template_version: 2013-05-23
description: >
Hello world HOT template that just defines a single compute instance.
Contains just base features to verify base HOT support.
parameter_groups:
- label: Server Group
description: A group of parameters for the server
parameters:
- InstanceType
- KeyName
- ImageId
- label: Database Group
description: A group of parameters for the database
parameters:
- db_password
- db_port
- SomethingNotHere
parameters:
KeyName:
type: string
description: Name of an existing key pair to use for the instance
InstanceType:
type: string
description: Instance type for the instance to be created
default: m1.small
constraints:
- allowed_values: [m1.tiny, m1.small, m1.large]
description: Value must be one of 'm1.tiny', 'm1.small' or 'm1.large'
ImageId:
type: string
description: ID of the image to use for the instance
# parameters below are not used in template, but are for verifying parameter
# validation support in HOT
db_password:
type: string
description: Database password
hidden: true
constraints:
- length: { min: 6, max: 8 }
description: Password length must be between 6 and 8 characters
- allowed_pattern: "[a-zA-Z0-9]+"
description: Password must consist of characters and numbers only
- allowed_pattern: "[A-Z]+[a-zA-Z0-9]*"
description: Password must start with an uppercase character
db_port:
type: number
description: Database port number
default: 50000
constraints:
- range: { min: 40000, max: 60000 }
description: Port number must be between 40000 and 60000
resources:
my_instance:
# Use an AWS resource type since this exists; so why use other name here?
type: AWS::EC2::Instance
properties:
KeyName: { get_param: KeyName }
ImageId: { get_param: ImageId }
InstanceType: { get_param: InstanceType }
outputs:
instance_ip:
description: The IP address of the deployed instance
value: { get_attr: [my_instance, PublicIp] }
'''
test_template_no_parameters = '''
heat_template_version: 2013-05-23
description: >
Hello world HOT template that just defines a single compute instance.
Contains just base features to verify base HOT support.
parameter_groups:
- label: Server Group
description: A group of parameters for the server
- label: Database Group
description: A group of parameters for the database
'''
class validateTest(HeatTestCase):
def setUp(self):
@ -852,3 +1010,43 @@ class validateTest(HeatTestCase):
'KeyName': 'test'}))
self.assertRaises(exception.StackValidationFailed, stack.validate)
def test_validate_duplicate_parameters_in_group(self):
t = template_format.parse(test_template_duplicate_parameters)
template = HOTemplate(t)
stack = parser.Stack(self.ctx, 'test_stack', template,
environment.Environment({
'KeyName': 'test',
'ImageId': 'sometestid',
'db_password': 'Pass123'
}))
exc = self.assertRaises(exception.StackValidationFailed,
stack.validate)
self.assertEqual(_('The InstanceType parameter must be assigned to '
'one Parameter Group only.'), str(exc))
def test_validate_invalid_parameter_in_group(self):
t = template_format.parse(test_template_invalid_parameter_name)
template = HOTemplate(t)
stack = parser.Stack(self.ctx, 'test_stack', template,
environment.Environment({
'KeyName': 'test',
'ImageId': 'sometestid',
'db_password': 'Pass123'}))
exc = self.assertRaises(exception.StackValidationFailed,
stack.validate)
self.assertEqual(_('The Parameter name (SomethingNotHere) does not '
'reference an existing parameter.'), str(exc))
def test_validate_no_parameters_in_group(self):
t = template_format.parse(test_template_no_parameters)
template = HOTemplate(t)
stack = parser.Stack(self.ctx, 'test_stack', template)
exc = self.assertRaises(exception.StackValidationFailed,
stack.validate)
self.assertEqual(_('Parameters must be provided for each Parameter '
'Group.'), str(exc))