From 1e14bba721f19db35dd6ad763710b1c29e2a43eb Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Mon, 17 Feb 2014 14:58:08 +0100 Subject: [PATCH] Getting correct count and attributes from database -passing correct counts to template creation -passing correct parameters to heat stack-create/update Change-Id: If6860b88e7db0656b10edf0841199a17b127a8c9 --- tuskar/api/controllers/v1/overcloud.py | 96 +++++++++++++++---- tuskar/heat/client.py | 7 +- tuskar/heat/template_tools.py | 2 +- .../api/controllers/v1/test_overcloud.py | 82 ++++++++++++---- tuskar/tests/heat/test_template_tools.py | 4 +- 5 files changed, 151 insertions(+), 40 deletions(-) diff --git a/tuskar/api/controllers/v1/overcloud.py b/tuskar/api/controllers/v1/overcloud.py index 8b7317fd..6221a52d 100644 --- a/tuskar/api/controllers/v1/overcloud.py +++ b/tuskar/api/controllers/v1/overcloud.py @@ -25,29 +25,80 @@ import tuskar.heat.template_tools as template_tools LOG = logging.getLogger(__name__) -# FIXME(lsmola) mocked params for POC, remove later by real ones +# FIXME(lsmola) this is for debugging purposes only, remove before I3 POC_PARAMS = {'controller': 1, 'compute': 1} -POC_PARAMS_UPDATE = {'controller': 1, 'compute': 2} -def process_stack(params, create=False): - """Helper function for processing the stack. Given a params dict containing - the Overcloud Roles and initialization parameters create or update the - stack. +def parse_counts(counts): + """Helper for parsing the OvercloudRoleCount object - :param params: Dictionary of initialization params and overcloud roles for - heat template and initialization of stack/ - :type params: dict + Given a list of OvercloudRoleCount objects return a dict of + (image_name, count) in a format used for building a template. + + :param counts: List of tuskar.api.controllers.v1.models.OvercloudRoleCount + :type counts: list + + :return: Dict of (image_name, count) + :rtype: dict + """ + parsed_counts = {} + for count_obj in counts: + image_name = count_obj.overcloud_role.image_name + count = count_obj.num_nodes + parsed_counts[image_name] = count + + return parsed_counts + + +def filter_template_attributes(allowed_data, attributes): + """Helper filtering attributes for template + + Given a list of allowed data and attributes, filter the attributes + only with keys of allowed data and return filtered data. + + :param allowed_data: Dict of allowed attributes for template returned by + validating of template. + :type allowed_data: dict + + :param attributes: Dict of attributes sent from user in deploying stack + operation + :type attributes: Dict + + :return: Dict of filtered attributes + :rtype: dict + """ + allowed_keys = allowed_data.get("Parameters", {}).keys() + + filtered_data = dict([(key, value) for key, value in attributes.items() + if key in allowed_keys]) + + return filtered_data + + +def process_stack(attributes, counts, create=False): + """Helper function for processing the stack. + + Given a params dict containing the Overcloud Roles and initialization + parameters create or update the stack. + + :param attributes: Dictionary of initialization params and overcloud roles + for heat template and initialization of stack + :type attributes: dict + + :param counts: Dictionary of counts of roles to be deployed + :type counts: dict :param create: A flag to designate if we are creating or updating the stack :type create: bool """ - overcloud = template_tools.merge_templates(params) + overcloud = template_tools.merge_templates(parse_counts(counts)) heat_client = HeatClient() stack_exists = heat_client.exists_stack() - if not heat_client.validate_template(overcloud): + valid, allowed_data = heat_client.validate_template(overcloud) + + if not valid: raise exception.InvalidHeatTemplate() if stack_exists and create: @@ -56,7 +107,13 @@ def process_stack(params, create=False): elif not stack_exists and not create: raise exception.StackNotFound() - res = heat_client.create_stack(overcloud, params) + if create: + operation = heat_client.create_stack + else: + operation = heat_client.update_stack + + res = operation(overcloud, + filter_template_attributes(allowed_data, attributes)) if not res: if create: @@ -93,7 +150,6 @@ class OvercloudsController(rest.RestController): :raises: tuskar.common.exception.OvercloudExists: if an overcloud with the given name exists """ - LOG.debug('Creating overcloud: %s' % transfer_overcloud) # Persist to the database @@ -110,7 +166,8 @@ class OvercloudsController(rest.RestController): # step 2- put the right stack_id to the overcloud # step 3- initialize the stack # step 4- set the correct overcloud status - process_stack(POC_PARAMS, create=True) + process_stack(saved_overcloud.attributes, result.counts, + create=True) return saved_overcloud @@ -145,15 +202,16 @@ class OvercloudsController(rest.RestController): # Will raise a not found if there is no overcloud with the ID result = pecan.request.dbapi.update_overcloud(db_delta) - updated = models.Overcloud.from_db_model(result) + updated_overcloud = models.Overcloud.from_db_model(result) # FIXME(lsmola) This is just POC of updating a stack # this probably should also have workflow # step one- build template and stack-update # step 2- set the correct overcloud status - process_stack(POC_PARAMS_UPDATE) + process_stack(updated_overcloud.attributes, updated_overcloud.counts, + create=True) - return updated + return updated_overcloud @wsme_pecan.wsexpose(None, int, status_code=204) def delete(self, overcloud_id): @@ -168,6 +226,10 @@ class OvercloudsController(rest.RestController): # FIXME(lsmola) this should always try to delete both overcloud # and stack. So it requires some exception catch over below. + # FIXME(lsmola) there is also a workflow needed + # step one- delete stack and set status deleting in progress to + # overcloud + # step 2 - once stack is deleted, delete the overcloud LOG.debug('Deleting overcloud with ID: %s' % overcloud_id) pecan.request.dbapi.delete_overcloud_by_id(overcloud_id) diff --git a/tuskar/heat/client.py b/tuskar/heat/client.py index 9a295f96..ccf41965 100644 --- a/tuskar/heat/client.py +++ b/tuskar/heat/client.py @@ -95,11 +95,12 @@ class HeatClient(object): def validate_template(self, template_body): """Validate given Heat template.""" try: - self.connection.stacks.validate(template=template_body) - return True + allowed_data = self.connection.stacks.validate( + template=template_body) + return True, allowed_data except Exception: LOG.exception("Validation of the Heat template failed.") - return False + return False, None def get_stack(self, name=None): """Get overcloud Heat template.""" diff --git a/tuskar/heat/template_tools.py b/tuskar/heat/template_tools.py index 01d8d324..6a1c426f 100644 --- a/tuskar/heat/template_tools.py +++ b/tuskar/heat/template_tools.py @@ -23,7 +23,7 @@ from tripleo_heat_merge import merge # The name of the compute Overcloud role - defined for special case handling -OVERCLOUD_COMPUTE_ROLE = 'compute' +OVERCLOUD_COMPUTE_ROLE = 'overcloud-compute' def generate_scaling_params(overcloud_roles): diff --git a/tuskar/tests/api/controllers/v1/test_overcloud.py b/tuskar/tests/api/controllers/v1/test_overcloud.py index 5fb49474..0241f97a 100644 --- a/tuskar/tests/api/controllers/v1/test_overcloud.py +++ b/tuskar/tests/api/controllers/v1/test_overcloud.py @@ -71,10 +71,55 @@ class OvercloudTests(base.TestCase): mock_db_get.assert_called_once_with(12345) + def test_parse_counts(self): + # Setup + overcloud_role_1 = db_models.OvercloudRole( + image_name='overcloud-compute') + + overcloud_role_2 = db_models.OvercloudRole( + image_name='overcloud-block-storage') + + overcloud_role_count_1 = db_models.OvercloudRoleCount( + overcloud_role_id=2, num_nodes=5, overcloud_role=overcloud_role_1) + + overcloud_role_count_2 = db_models.OvercloudRoleCount( + overcloud_role_id=2, num_nodes=9, overcloud_role=overcloud_role_2) + + mock_counts = [overcloud_role_count_1, overcloud_role_count_2] + + # Test + result = overcloud.parse_counts(mock_counts) + + # Verify + self.assertEqual(result, {'overcloud-compute': 5, + 'overcloud-block-storage': 9}) + + def test_filter_template_attributes(self): + # Setup + allowed_data = {'Parameters': { + 'Allowed_1': 42, + 'Allowed_2': 21, + }} + + attributes = { + 'NotAllowed_1': "Infinity", + 'Allowed_1': 42, + 'NotAllowed_2': "SubZero", + 'Allowed_2': 21, + 'NotAllowed_3': "Goro", + } + + # Test + result = overcloud.filter_template_attributes(allowed_data, attributes) + + # Verify + self.assertEqual(result, {'Allowed_1': 42, + 'Allowed_2': 21}) + @mock.patch('tuskar.heat.template_tools.merge_templates') @mock.patch( 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': True, + 'validate_template.return_value': (True, {}), 'exists_stack.return_value': False, 'create_stack.return_value': True, }) @@ -84,7 +129,7 @@ class OvercloudTests(base.TestCase): mock_heat_merge_templates.return_value = None # Test - response = overcloud.process_stack({}, create=True) + response = overcloud.process_stack({}, {}, create=True) # Verify self.assertEqual(response, None) @@ -92,7 +137,7 @@ class OvercloudTests(base.TestCase): @mock.patch('tuskar.heat.template_tools.merge_templates') @mock.patch( 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': True, + 'validate_template.return_value': (True, {}), 'exists_stack.return_value': False, 'create_stack.return_value': False, }) @@ -105,12 +150,12 @@ class OvercloudTests(base.TestCase): # Test and Verify self.assertRaises( exception.HeatTemplateCreateFailed, - overcloud.process_stack, {}, True) + overcloud.process_stack, {}, {}, True) @mock.patch('tuskar.heat.template_tools.merge_templates') @mock.patch( 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': True, + 'validate_template.return_value': (True, {}), 'exists_stack.return_value': True, 'create_stack.return_value': True, }) @@ -122,12 +167,13 @@ class OvercloudTests(base.TestCase): # Test and Verify self.assertRaises( - exception.StackAlreadyCreated, overcloud.process_stack, {}, True) + exception.StackAlreadyCreated, overcloud.process_stack, {}, {}, + True) @mock.patch('tuskar.heat.template_tools.merge_templates') @mock.patch( 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': False, + 'validate_template.return_value': (False, {}), 'exists_stack.return_value': False, 'create_stack.return_value': True, }) @@ -139,7 +185,8 @@ class OvercloudTests(base.TestCase): # Test and Verify self.assertRaises( - exception.InvalidHeatTemplate, overcloud.process_stack, {}, True) + exception.InvalidHeatTemplate, overcloud.process_stack, {}, {}, + True) @mock.patch('tuskar.api.controllers.v1.overcloud.process_stack') @mock.patch('tuskar.db.sqlalchemy.api.Connection.create_overcloud') @@ -168,7 +215,7 @@ class OvercloudTests(base.TestCase): @mock.patch('tuskar.heat.template_tools.merge_templates') @mock.patch( 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': True, + 'validate_template.return_value': (True, {}), 'exists_stack.return_value': True, 'create_stack.return_value': True, }) @@ -178,7 +225,7 @@ class OvercloudTests(base.TestCase): mock_heat_merge_templates.return_value = None # Test - response = overcloud.process_stack({}) + response = overcloud.process_stack({}, {}) # Verify self.assertEqual(response, None) @@ -186,9 +233,9 @@ class OvercloudTests(base.TestCase): @mock.patch('tuskar.heat.template_tools.merge_templates') @mock.patch( 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': True, + 'validate_template.return_value': (True, {}), 'exists_stack.return_value': True, - 'create_stack.return_value': False, + 'update_stack.return_value': False, }) ) def test_update_stack_heat_exception(self, mock_heat_client, @@ -198,12 +245,13 @@ class OvercloudTests(base.TestCase): # Test and Verify self.assertRaises( - exception.HeatTemplateUpdateFailed, overcloud.process_stack, {}) + exception.HeatTemplateUpdateFailed, overcloud.process_stack, {}, + {}) @mock.patch('tuskar.heat.template_tools.merge_templates') @mock.patch( 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': True, + 'validate_template.return_value': (True, {}), 'exists_stack.return_value': False, 'create_stack.return_value': True, }) @@ -215,12 +263,12 @@ class OvercloudTests(base.TestCase): # Test and Verify self.assertRaises( - exception.StackNotFound, overcloud.process_stack, {}) + exception.StackNotFound, overcloud.process_stack, {}, {}) @mock.patch('tuskar.heat.template_tools.merge_templates') @mock.patch( 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': False, + 'validate_template.return_value': (False, {}), 'exists_stack.return_value': True, 'create_stack.return_value': True, }) @@ -232,7 +280,7 @@ class OvercloudTests(base.TestCase): # Test and Verify self.assertRaises( - exception.InvalidHeatTemplate, overcloud.process_stack, {}) + exception.InvalidHeatTemplate, overcloud.process_stack, {}, {}) @mock.patch('tuskar.api.controllers.v1.overcloud.process_stack') @mock.patch('tuskar.db.sqlalchemy.api.Connection.update_overcloud') diff --git a/tuskar/tests/heat/test_template_tools.py b/tuskar/tests/heat/test_template_tools.py index fcee983f..61e6adaf 100644 --- a/tuskar/tests/heat/test_template_tools.py +++ b/tuskar/tests/heat/test_template_tools.py @@ -23,7 +23,7 @@ class TemplateToolsTests(unittest.TestCase): @mock.patch('tripleo_heat_merge.merge.parse_scaling') def test_generate_scaling_params(self, mock_parse_scaling): # Setup - overcloud_roles = {'controller': 1, 'compute': 12} + overcloud_roles = {'controller': 1, 'overcloud-compute': 12} # Test template_tools.generate_scaling_params(overcloud_roles) @@ -34,7 +34,7 @@ class TemplateToolsTests(unittest.TestCase): @mock.patch('tripleo_heat_merge.merge.merge') def test_merge_templates(self, mock_merge): # Setup - overcloud_roles = {'controller': 1, 'compute': 12} + overcloud_roles = {'controller': 1, 'overcloud-compute': 12} # Test template_tools.merge_templates(overcloud_roles)