From 2ccdfdf103322c2312e6bcced333976a6a166fbe Mon Sep 17 00:00:00 2001 From: stewie925 Date: Fri, 22 Nov 2019 15:49:10 -0800 Subject: [PATCH] Port Private Flavor Tenant python2 logic changes Change-Id: Icb7aab24f15c7ec970330d84baf03a9c07f00897 --- .../controllers/v1/orm/flavors/flavors.py | 12 +-- .../controllers/v1/orm/flavors/tenants.py | 18 ++-- .../fms_rest/data/sql_alchemy/data_manager.py | 27 +++++- .../fms_rest/data/sql_alchemy/db_models.py | 2 - .../fms_rest/logic/flavor_logic.py | 83 +++++++++++++++++-- .../rds/services/resource.py | 45 ++++++++++ .../rds/services/yaml_flavor_builder.py | 3 +- orm/tests/unit/fms/test_flavor_logic.py | 28 ++++--- 8 files changed, 181 insertions(+), 37 deletions(-) diff --git a/orm/services/flavor_manager/fms_rest/controllers/v1/orm/flavors/flavors.py b/orm/services/flavor_manager/fms_rest/controllers/v1/orm/flavors/flavors.py index ee191c36..950eb708 100755 --- a/orm/services/flavor_manager/fms_rest/controllers/v1/orm/flavors/flavors.py +++ b/orm/services/flavor_manager/fms_rest/controllers/v1/orm/flavors/flavors.py @@ -59,20 +59,20 @@ class FlavorController(rest.RestController): except ErrorStatus as exception: LOG.log_exception("FlavorController - Failed to CreateFlavor", exception) raise err_utils.get_error(request.transaction_id, - message=exception.message, + message=str(exception), status_code=exception.status_code) except ValueError as exception: LOG.log_exception("FlavorController - Failed to CreateFlavor", exception) raise err_utils.get_error(request.transaction_id, status_code=400, - error_details=exception.message) + error_details=str(exception)) except Exception as exception: LOG.log_exception("FlavorController - Failed to CreateFlavor", exception) raise err_utils.get_error(request.transaction_id, status_code=500, - error_details=exception.message) + error_details=str(exception)) @wsexpose(FlavorWrapper, str, body=FlavorWrapper, rest_content_types='json') def put(self, flavor_id, flavors): @@ -94,7 +94,7 @@ class FlavorController(rest.RestController): except ErrorStatus as exception: LOG.log_exception("FlavorController - Failed to GetFlavorDetails", exception) raise err_utils.get_error(request.transaction_id, - message=exception.message, + message=str(exception), status_code=exception.status_code) except Exception as exception: @@ -121,7 +121,7 @@ class FlavorController(rest.RestController): except ErrorStatus as exception: LOG.log_exception("FlavorController - Failed to GetFlavorlist", exception) raise err_utils.get_error(request.transaction_id, - message=exception.message, + message=str(exception), status_code=exception.status_code) except Exception as exception: @@ -148,7 +148,7 @@ class FlavorController(rest.RestController): except ErrorStatus as exception: LOG.log_exception("FlavorController - Failed to delete flavor", exception) raise err_utils.get_error(request.transaction_id, - message=exception.message, + message=str(exception), status_code=exception.status_code) except Exception as exception: diff --git a/orm/services/flavor_manager/fms_rest/controllers/v1/orm/flavors/tenants.py b/orm/services/flavor_manager/fms_rest/controllers/v1/orm/flavors/tenants.py index 7fb1e22b..df2c4d12 100755 --- a/orm/services/flavor_manager/fms_rest/controllers/v1/orm/flavors/tenants.py +++ b/orm/services/flavor_manager/fms_rest/controllers/v1/orm/flavors/tenants.py @@ -22,7 +22,6 @@ class TenantController(rest.RestController): flavor_logic, utils = di.resolver.unpack(TenantController) LOG.info("TenantController - add tenants: " + str(tenant_wrapper)) authentication.authorize(request, 'flavor:add_flavor_tenants') - try: result = flavor_logic.add_tenants(flavor_id, tenant_wrapper, request.transaction_id) @@ -34,21 +33,20 @@ class TenantController(rest.RestController): request.headers, flavor_id, event_details=event_details) return result - except ValueError as exception: - LOG.log_exception("TenantController - Failed to add tenants", exception) + LOG.log_exception("TenantController - Failed to add tenants", str(exception)) raise err_utils.get_error(request.transaction_id, - message=exception.message, + message=str(exception), status_code=400) except ErrorStatus as exception: - LOG.log_exception("TenantController - Failed to add tenants", exception) + LOG.log_exception("TenantController - Failed to add tenants", str(exception)) raise err_utils.get_error(request.transaction_id, - message=exception.message, + message=str(exception), status_code=exception.status_code) except Exception as exception: - LOG.log_exception("TenantController - Failed to add tenants", exception) + LOG.log_exception("TenantController - Failed to add tenants", str(exception)) raise err_utils.get_error(request.transaction_id, status_code=500, error_details=str(exception)) @@ -72,13 +70,13 @@ class TenantController(rest.RestController): event_details=event_details) except ErrorStatus as exception: - LOG.log_exception("TenantController - Failed to delete tenant", exception) + LOG.log_exception("TenantController - Failed to delete tenant", str(exception)) raise err_utils.get_error(request.transaction_id, - message=exception.message, + message=str(exception), status_code=exception.status_code) except Exception as exception: - LOG.log_exception("TenantController - Failed to delete tenant", exception) + LOG.log_exception("TenantController - Failed to delete tenant", str(exception)) raise err_utils.get_error(request.transaction_id, status_code=500, error_details=str(exception)) diff --git a/orm/services/flavor_manager/fms_rest/data/sql_alchemy/data_manager.py b/orm/services/flavor_manager/fms_rest/data/sql_alchemy/data_manager.py index eb9f7892..4016c8d9 100755 --- a/orm/services/flavor_manager/fms_rest/data/sql_alchemy/data_manager.py +++ b/orm/services/flavor_manager/fms_rest/data/sql_alchemy/data_manager.py @@ -1,7 +1,8 @@ import logging # from orm.services.flavor_manager.fms_rest.logic.error_base import DuplicateEntityError -from orm.services.flavor_manager.fms_rest.data.sql_alchemy.flavor.flavor_record import FlavorRecord +from orm.services.flavor_manager.fms_rest.data.sql_alchemy.flavor.\ + flavor_record import FlavorRecord from oslo_db.sqlalchemy.enginefacade import LegacyEngineFacade from pecan import conf @@ -86,3 +87,27 @@ class DataManager(object): self.flavor_record = FlavorRecord(self.session) return self.flavor_record return None + + def get_valid_tenant_region_list(self, requested_tenants, + requested_regions): + + datamanager = DataManager() + # convert tenant and region lists to sql-friendly list format + tenants_list = str(tuple(requested_tenants)).rstrip(',)') + ')' + regions_list = str(tuple(requested_regions)).rstrip(',)') + ')' + + # --- use sql query for processing time considerations + # get valid tenants list for the region(s) by checking + # tenant/region status in resource_status table if + # status shows 'Success' and operation not 'delete' + sql_query = '''select a.uuid, b.name from customer a, + cms_region b, customer_region c, resource_status d + where c.customer_id = a.id and c.region_id = b.id + and d.resource_id = a.uuid and d.region = b.name + and ((d.status = 'Success' and d.operation != 'delete') + or (d.status = 'Error' and d.operation = 'modify')) + and a.uuid in %s and b.name in %s''' + + results = datamanager.session.connection().execute(sql_query % (tenants_list, regions_list)).fetchall() + + return results diff --git a/orm/services/flavor_manager/fms_rest/data/sql_alchemy/db_models.py b/orm/services/flavor_manager/fms_rest/data/sql_alchemy/db_models.py index 23349c42..cfa663fa 100755 --- a/orm/services/flavor_manager/fms_rest/data/sql_alchemy/db_models.py +++ b/orm/services/flavor_manager/fms_rest/data/sql_alchemy/db_models.py @@ -334,8 +334,6 @@ class Flavor(Base, FMSBaseModel): if self.visibility == "public" and len(self.flavor_tenants) > 0: raise ValueError( "tenants should not be specified for a public flavor") - elif self.visibility == "private" and len(self.flavor_tenants) == 0: - raise ValueError("Tenants must be specified for a private flavor") elif self.visibility not in ["private", "public"]: raise ValueError( "Flavor visibility can be 'public' or 'private'," diff --git a/orm/services/flavor_manager/fms_rest/logic/flavor_logic.py b/orm/services/flavor_manager/fms_rest/logic/flavor_logic.py index 999264e4..5852a759 100755 --- a/orm/services/flavor_manager/fms_rest/logic/flavor_logic.py +++ b/orm/services/flavor_manager/fms_rest/logic/flavor_logic.py @@ -18,6 +18,35 @@ LOG = get_logger(__name__) di = injector.get_di() +def validate_tenants_regions_list(requested_tenants, requested_regions, + action, datamanager): + regions_list, tenants_list = [], [] + results = datamanager.get_valid_tenant_region_list(requested_tenants, + requested_regions) + + valid_tenants_list, valid_regions_list = [], [] + + # the first element in the results tuple is the tenant + # prep result_tenant_list from result_rows and remove NoneTypes from list + result_tenant_list = [x[0] for x in results] + result_tenant_list = filter(None, result_tenant_list) + # lastly clean up valid_tenants_list list by removing duplicate items + valid_tenants_list = list(dict.fromkeys(result_tenant_list)) + + # second element in the results tuple is the region - compile the region + # data into valid_regions_list and eliminate duplicates from the list + valid_regions_list = [x[1] for x in results] + valid_regions_list = list(dict.fromkeys(valid_regions_list)) + + # update the tenants list with validated tenants from query results + # and will be reflected in the create/get flavor as well as + # add/ tenant responses + requested_tenants = valid_tenants_list + requested_regions = valid_regions_list + + return requested_tenants, requested_regions + + @di.dependsOn('data_manager') def create_flavor(flavor, flavor_uuid, transaction_id): DataManager = di.resolver.unpack(create_flavor) @@ -31,6 +60,22 @@ def create_flavor(flavor, flavor_uuid, transaction_id): flavor.flavor.name = calculate_name(flavor) LOG.debug("Flavor name is [{}] ".format(flavor.flavor.name)) + flavor_regions = [] + for region in flavor.flavor.regions: + flavor_regions.append(region.name) + + # validate which tenants from the original tenants list to + # be assigned to the private flavor; if no valid tenants + # found, the valid_tenants_list will return empty list + if flavor.flavor.visibility == 'private': + valid_tenants_list, valid_regions_list = \ + validate_tenants_regions_list(flavor.flavor.tenants, + # flavor.flavor.regions, + flavor_regions, + 'create', datamanager) + # replace the original tenant list in the private flavor + # with the valid_tenants_list + flavor.flavor.tenants = valid_tenants_list sql_flavor = flavor.to_db_model() @@ -217,6 +262,7 @@ def add_regions(flavor_uuid, regions, transaction_id): existing_region_names = sql_flavor.get_existing_region_names() + flvr_tenant_list, flvr_region_list = [], [] for region in regions.regions: if region.name == '' or region.name.isspace(): raise ErrorStatus(400, 'Cannot add region with an empty name') @@ -227,10 +273,25 @@ def add_regions(flavor_uuid, regions, transaction_id): db_region = FlavorRegion(region_name=region.name, region_type='single') sql_flavor.add_region(db_region) + flvr_region_list.append(region.name) # get any exception created by previous actions against the database datamanager.flush() + # private flavor new logic + # validate tenants assigned to the flavor + for tenant in sql_flavor.flavor_tenants: + flvr_tenant_list.append(tenant.tenant_id) + + valid_tenants_list, valid_regions_list = \ + validate_tenants_regions_list(flvr_tenant_list, + flvr_region_list, + 'create', datamanager) + # update tenant list + for tenant in flvr_tenant_list: + if tenant not in valid_tenants_list: + sql_flavor.remove_tenant(tenant) + send_to_rds_if_needed( sql_flavor, existing_region_names, "put", transaction_id) @@ -298,6 +359,8 @@ def add_tenants(flavor_uuid, tenants, transaction_id): DataManager = di.resolver.unpack(add_tenants) datamanager = DataManager() + valid_tenants_list, valid_regions_list = [], [] + try: flavor_rec = datamanager.get_record('flavor') sql_flavor = flavor_rec.get_flavor_by_id(flavor_uuid) @@ -309,6 +372,21 @@ def add_tenants(flavor_uuid, tenants, transaction_id): raise ErrorStatus(405, 'Cannot add tenant to a public flavor') existing_region_names = sql_flavor.get_existing_region_names() + existing_region_list = [] + + for x in existing_region_names: + existing_region_list.append(x) + if tenants.tenants: + valid_tenants_list, valid_regions_list = \ + validate_tenants_regions_list(tenants.tenants, + existing_region_list, + 'create', datamanager) + # replace tenants.tenants with only the valid tenants + tenants.tenants = valid_tenants_list + + # issue error message if tenant list is empty + if not tenants.tenants: + raise ValueError("At least one valid tenant must be provided") for tenant in tenants.tenants: if not isinstance(tenant, str): @@ -357,11 +435,6 @@ def delete_tenant(flavor_uuid, tenant_id, transaction_id): raise ValueError("{} is a public flavor, delete tenant" " action is not relevant".format(flavor_uuid)) - if len(sql_flavor.flavor_tenants) == 1 \ - and sql_flavor.flavor_tenants[0].tenant_id == tenant_id: - raise ValueError( - 'Private flavor must have at least one tenant') - existing_region_names = sql_flavor.get_existing_region_names() sql_flavor.remove_tenant(tenant_id) # get any exception created by previous actions against the database diff --git a/orm/services/resource_distributor/rds/services/resource.py b/orm/services/resource_distributor/rds/services/resource.py index 7db1eb5f..83075596 100755 --- a/orm/services/resource_distributor/rds/services/resource.py +++ b/orm/services/resource_distributor/rds/services/resource.py @@ -2,6 +2,8 @@ import logging import time +from orm.services.flavor_manager.fms_rest.data.sql_alchemy.data_manager \ + import DataManager from orm.services.resource_distributor.rds.services import region_resource_id_status as regionResourceIdStatus from orm.services.resource_distributor.rds.services import (yaml_customer_builder, yaml_flavor_builder, yaml_group_builder, yaml_image_builder) @@ -79,6 +81,27 @@ def _set_all_statuses_to_error(input_data, message=None): status="Error") +def get_valid_tenants(tenants, region): + # identify valid tenants for a particular region and save in + # valid_tenants_list; if no tenants validated return empty list + requested_tenants, valid_tenants_list = [], [] + for tenant in tenants: + requested_tenants.append(tenant['tenant_id']) + requested_region = [region] + + datamanager = DataManager() + customer_region = datamanager.get_record('customer_region') + results = datamanager.get_valid_tenant_region_list( + requested_tenants, requested_region) + + for x in results: + valid_tenants_list.append(x[0]) + # prep result_tenant_list from result_rows and remove NoneTypes from list + valid_tenants_list = filter(None, valid_tenants_list) + + return valid_tenants_list + + def _create_data_to_sot(input_data): """create data. @@ -102,7 +125,29 @@ def _create_data_to_sot(input_data): elif input_data.resource_type == "group": yamldata = yaml_group_builder.yamlbuilder(jsondata, target) elif input_data.resource_type == "flavor": + if input_data.model['visibility'] == 'private': + ok_tenants_list = [] + ok_tenants = {} + + # skip tenant validation if tenants list is empty + if input_data.model['tenants']: + valid_tenants_list = get_valid_tenants( + input_data.model['tenants'], target['name']) + + for tenant in valid_tenants_list: + ok_tenants['tenant_id'] = tenant + ok_tenants_list.append(ok_tenants.copy()) + + # Note: If ok_tenant_list is empty, just create heat template + # for private flavor with empty tenant list + if not ok_tenants_list: + ok_tenants['tenant_id'] = None + ok_tenants_list.append(ok_tenants.copy()) + jsondata['tenants'] = ok_tenants_list + + # now issue yamldata for flavor either public or private yamldata = yaml_flavor_builder.yamlbuilder(jsondata, target) + elif input_data.resource_type == "image": yamldata = yaml_image_builder.yamlbuilder(jsondata, target) targetslist.append({"region_id": target['name'], diff --git a/orm/services/resource_distributor/rds/services/yaml_flavor_builder.py b/orm/services/resource_distributor/rds/services/yaml_flavor_builder.py index af3d5537..0eecbd7a 100755 --- a/orm/services/resource_distributor/rds/services/yaml_flavor_builder.py +++ b/orm/services/resource_distributor/rds/services/yaml_flavor_builder.py @@ -52,7 +52,8 @@ def yamlbuilder(alldata, region): extra_specs[key] = value # Handle tenants for tenant in alldata['tenants']: - tenants.append(tenant['tenant_id']) + if tenant['tenant_id']: + tenants.append(tenant['tenant_id']) # Generate the output resources['resources'] = {} diff --git a/orm/tests/unit/fms/test_flavor_logic.py b/orm/tests/unit/fms/test_flavor_logic.py index 75dba489..5b688d10 100755 --- a/orm/tests/unit/fms/test_flavor_logic.py +++ b/orm/tests/unit/fms/test_flavor_logic.py @@ -52,29 +52,43 @@ class TestFlavorLogic(FunctionalTest): self.assertRaises(flavor_logic.ErrorStatus, flavor_logic.create_flavor, flavor, 'uuid', 'transaction') + @patch.object(flavor_logic, 'validate_tenants_regions_list') @patch.object(flavor_logic, 'FlavorWrapper') - def test_create_flavor_success(self, mock_flavorwrapper): + def test_create_flavor_success(self, mock_flavorwrapper, mock_validate): mock_flavorwrapper.from_db_model.return_value = get_flavor_mock() + + # Flavor is public - test success + flavor = get_flavor_mock() + flavor.flavor.visibility = 'public' global error error = 31 injector.override_injected_dependency( ('rds_proxy', get_rds_proxy_mock())) - res_flavor = flavor_logic.create_flavor(get_flavor_mock(), 'uuid', + res_flavor = flavor_logic.create_flavor(flavor, 'uuid', 'transaction') + mock_validate.assert_not_called() self.assertEqual(res_flavor.flavor.profile, 'N1') self.assertEqual(res_flavor.flavor.ram, '1024') self.assertEqual(res_flavor.flavor.vcpus, '1') self.assertEqual(res_flavor.flavor.series, cfg.CONF.fms.flavor_series[0]) self.assertEqual(res_flavor.flavor.id, 'g') + # Test that flavor validate model works by passing bad swap value flavor = get_flavor_mock() flavor.flavor.swap = '1024000' self.assertRaises(flavor_logic.ErrorStatus, flavor_logic.create_flavor, flavor, 'uuid', 'transaction') + # Flavor is private - test success flavor.flavor.validate_model = MagicMock() + flavor.flavor.visibility = 'private' + flavor.flavor.tenants = ['1234'] + flavor.flavor.regions = [Region(name='test_region')] + mock_validate.return_value = [['1234'], ['rgn1']] + res_flavor = flavor_logic.create_flavor(flavor, 'uuid', 'transaction') + mock_validate.assert_called() self.assertEqual(res_flavor.flavor.profile, 'N1') self.assertEqual(res_flavor.flavor.ram, '1024') self.assertEqual(res_flavor.flavor.vcpus, '1') @@ -582,16 +596,6 @@ class TestFlavorLogic(FunctionalTest): 'tenant_id', 'transaction') - global FLAVOR_MOCK - tenant = MagicMock() - tenant.tenant_id = 'tenant_id' - FLAVOR_MOCK.flavor_tenants = [tenant] - error = 6 - self.assertRaises(ValueError, - flavor_logic.delete_tenant, 'uuid', - 'tenant_id', - 'transaction') - @patch.object(flavor_logic, 'send_to_rds_if_needed') @patch.object(flavor_logic, 'get_flavor_by_uuid') @patch.object(models, 'request')