From 0d31548886d82f6cba674836cdf34deb1d9e3eab Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Thu, 27 Feb 2014 06:13:26 -0500 Subject: [PATCH] Using flavors from Roles -flavors defined in roles are now written into parameters that are fed to heat -association for image name and flavor param are stored in template tools by hard, for Icehouse -exceptions for processing stack erros set to 400 -fixed docs of the helper functions Change-Id: I0e3e8dcd78a36fc1e008eedfa6ec793015e3ac1e --- tuskar/api/controllers/v1/overcloud.py | 72 +++++++++++++++---- tuskar/common/exception.py | 25 +++++-- tuskar/heat/template_tools.py | 10 ++- .../api/controllers/v1/test_overcloud.py | 34 +++++++-- 4 files changed, 114 insertions(+), 27 deletions(-) diff --git a/tuskar/api/controllers/v1/overcloud.py b/tuskar/api/controllers/v1/overcloud.py index 45c3b527..f88f392a 100644 --- a/tuskar/api/controllers/v1/overcloud.py +++ b/tuskar/api/controllers/v1/overcloud.py @@ -30,11 +30,12 @@ LOG = logging.getLogger(__name__) POC_PARAMS = {'controller': 1, 'compute': 2} -def parse_counts(counts, overcloud_roles): +def parse_counts_and_flavors(counts, overcloud_roles): """Helper for parsing the OvercloudRoleCount object - Given a list of OvercloudRoleCount objects return a dict of - (image_name, count) in a format used for building a template. + Given a list of OvercloudRoleCount and dict of OverlcoudRole objects + return a dict of (image_name, count) and (image_name, flavor_id) in a + format used for building a template. :param counts: List of tuskar.api.controllers.v1.models.OvercloudRoleCount :type counts: list @@ -43,16 +44,19 @@ def parse_counts(counts, overcloud_roles): we can access image_name and flavor_id of roles :type overcloud_roles: dict - :return: Dict of (image_name, count) - :rtype: dict + :return: Tuple of dicts {(image_name, count)}, {(image_name, flavor_id)} + :rtype: two dict objects """ parsed_counts = {} + parsed_flavors = {} for count_obj in counts: image_name = overcloud_roles[count_obj.overcloud_role_id].image_name + flavor_id = overcloud_roles[count_obj.overcloud_role_id].flavor_id count = count_obj.num_nodes parsed_counts[image_name] = count + parsed_flavors[image_name] = flavor_id - return parsed_counts + return parsed_counts, parsed_flavors def filter_template_attributes(allowed_data, attributes): @@ -86,6 +90,30 @@ def get_overcloud_roles_dict(): pecan.request.dbapi.get_overcloud_roles()) +def get_flavor_attributes(parsed_flavors): + """Helper for building dict of flavor attributes + + Given a dict of parsed flavors, it will put a flavor_ids stored in + role into attributes that will be fed to heat stack create/update. + + Mapping of image name to flavor_param is stored in template_tools.ROLES. + + :param parsed_flavors: Dict of (image_name, flavor_id) + :type parsed_flavors: dict + + :return: Dict of (flavor_param, flavor_id) for Heat Template params + :rtype: dict + """ + flavor_attributes = {} + for image_name, flavor_id in parsed_flavors.items(): + role = template_tools.ROLES.get(image_name, None) + if role: + flavor_param = role['flavor_param'] + flavor_attributes[flavor_param] = flavor_id + + return flavor_attributes + + def process_stack(attributes, counts, overcloud_roles, create=False): """Helper function for processing the stack. @@ -106,35 +134,51 @@ def process_stack(attributes, counts, overcloud_roles, create=False): :param create: A flag to designate if we are creating or updating the stack :type create: bool """ + heat_client = HeatClient() + try: - overcloud = template_tools.merge_templates( - parse_counts(counts, overcloud_roles)) + # Get how many of each role we want and what flavor each role uses. + parsed_counts, parsed_flavors = parse_counts_and_flavors( + counts, overcloud_roles) + except Exception as e: + raise exception.ParseCountsAndFlavorsFailed(six.text_type(e)) + + try: + # Build the template + overcloud = template_tools.merge_templates(parsed_counts) except Exception as e: raise exception.HeatTemplateCreateFailed(six.text_type(e)) - heat_client = HeatClient() - - stack_exists = heat_client.exists_stack() try: + # Get the parameters that the template accepts and validate allowed_data = heat_client.validate_template(overcloud) except Exception as e: raise exception.HeatTemplateValidateFailed(six.text_type(e)) + stack_exists = heat_client.exists_stack() if stack_exists and create: raise exception.StackAlreadyCreated() elif not stack_exists and not create: raise exception.StackNotFound() + try: + # Put flavors from OverloudRoles into attributes + attributes.update(get_flavor_attributes(parsed_flavors)) + + # Filter the attributes to allowed only + filtered_attributes = filter_template_attributes(allowed_data, + attributes) + except Exception as e: + raise exception.HeatStackProcessingAttributesFailed(six.text_type(e)) + if create: operation = heat_client.create_stack else: operation = heat_client.update_stack try: - result = operation( - overcloud, - filter_template_attributes(allowed_data, attributes)) + result = operation(overcloud, filtered_attributes) except Exception as e: if create: raise exception.HeatStackCreateFailed(six.text_type(e)) diff --git a/tuskar/common/exception.py b/tuskar/common/exception.py index fac7ffd7..02a79f2c 100644 --- a/tuskar/common/exception.py +++ b/tuskar/common/exception.py @@ -114,6 +114,11 @@ class TuskarException(Exception): return six.text_type(self) +class Invalid(TuskarException): + message = _("Invalid.") + code = 400 + + class NotAuthorized(TuskarException): message = _("Not authorized.") code = 403 @@ -153,7 +158,7 @@ class OvercloudRoleExists(DuplicateEntry): message = _("Overcloud role with name %(name)s already exists.") -class OvercloudRoleInUse(TuskarException): +class OvercloudRoleInUse(Invalid): message = _('Role %(name)s is in use by an overcloud.') @@ -182,21 +187,29 @@ class StackAlreadyCreated(DuplicateEntry): message = _("The Stack for this Overcloud already exists.") -class HeatTemplateCreateFailed(TuskarException): +class ParseCountsAndFlavorsFailed(DuplicateEntry): + message = _("Parsing of counts and flavors from roles failed.") + + +class HeatTemplateCreateFailed(Invalid): message = _("The Heat template failed to create.") -class HeatTemplateValidateFailed(TuskarException): +class HeatTemplateValidateFailed(Invalid): message = _("Validation of the Heat template failed.") -class HeatStackUpdateFailed(TuskarException): +class HeatStackProcessingAttributesFailed(Invalid): + message = _("Processing of Heat stack attributes failed") + + +class HeatStackUpdateFailed(Invalid): message = _("The Heat stack failed to update.") -class HeatStackCreateFailed(TuskarException): +class HeatStackCreateFailed(Invalid): message = _("The Heat stack failed to update.") -class HeatStackDeleteFailed(TuskarException): +class HeatStackDeleteFailed(Invalid): message = _("The Heat stack failed to delete.") diff --git a/tuskar/heat/template_tools.py b/tuskar/heat/template_tools.py index d002384b..7cd77f51 100644 --- a/tuskar/heat/template_tools.py +++ b/tuskar/heat/template_tools.py @@ -22,13 +22,21 @@ from oslo.config import cfg from tripleo_heat_merge import merge -# The name of the compute Overcloud role - defined for special case handling +# TODO(lsmola) For now static definition of roles for Icehouse +# we will need to load these associations from somewhere. +OVERCLOUD_CONTROL_ROLE = 'overcloud-control' OVERCLOUD_COMPUTE_ROLE = 'overcloud-compute' OVERCLOUD_VOLUME_ROLE = 'overcloud-cinder-volume' + ROLES = {} +ROLES[OVERCLOUD_CONTROL_ROLE] = {'template_param': 'Control', + 'flavor_param': 'OvercloudControlFlavor', + 'file_name': None} ROLES[OVERCLOUD_COMPUTE_ROLE] = {'template_param': 'NovaCompute', + 'flavor_param': 'OvercloudComputeFlavor', 'file_name': 'overcloud-source.yaml'} ROLES[OVERCLOUD_VOLUME_ROLE] = {'template_param': 'BlockStorage', + 'flavor_param': 'OvercloudBlockStorageFlavor', 'file_name': 'block-storage.yaml'} diff --git a/tuskar/tests/api/controllers/v1/test_overcloud.py b/tuskar/tests/api/controllers/v1/test_overcloud.py index 6ceebe5f..ef414cd4 100644 --- a/tuskar/tests/api/controllers/v1/test_overcloud.py +++ b/tuskar/tests/api/controllers/v1/test_overcloud.py @@ -74,10 +74,10 @@ class OvercloudTests(base.TestCase): def test_parse_counts_overcloud_roles_explicit(self): # Setup overcloud_role_1 = db_models.OvercloudRole( - image_name='overcloud-compute') + image_name='overcloud-compute', flavor_id='1') overcloud_role_2 = db_models.OvercloudRole( - image_name='overcloud-block-storage') + image_name='overcloud-cinder-volume', flavor_id='1') mock_overcloud_roles = {1: overcloud_role_1, 2: overcloud_role_2} @@ -90,12 +90,34 @@ class OvercloudTests(base.TestCase): mock_counts = [overcloud_role_count_1, overcloud_role_count_2] # Test - result = overcloud.parse_counts(mock_counts, - overcloud_roles=mock_overcloud_roles) + result = overcloud.parse_counts_and_flavors( + mock_counts, + overcloud_roles=mock_overcloud_roles) # Verify - self.assertEqual(result, {'overcloud-compute': 5, - 'overcloud-block-storage': 9}) + self.assertEqual(result, + ({'overcloud-compute': 5, + 'overcloud-cinder-volume': 9}, + {'overcloud-compute': '1', + 'overcloud-cinder-volume': '1'})) + + def test_get_flavor_attributes(self): + # Setup + parsed_flavors = {'fake': '5', + 'overcloud-control': '1', + 'overcloud-compute': '2', + 'overcloud-cinder-volume': '3'} + + # Test + result = overcloud.get_flavor_attributes(parsed_flavors) + + # Verify + # The flavors names are stored in template_tools, so this also tests + # it hasn't been changed. This flavor names must match what is int + # the template. + self.assertEqual(result, {'OvercloudControlFlavor': '1', + 'OvercloudComputeFlavor': '2', + 'OvercloudBlockStorageFlavor': '3'}) def test_filter_template_attributes(self): # Setup