diff --git a/orm/orm_client/ormcli/cmscli.py b/orm/orm_client/ormcli/cmscli.py index 0999b87d..c9958464 100644 --- a/orm/orm_client/ormcli/cmscli.py +++ b/orm/orm_client/ormcli/cmscli.py @@ -270,6 +270,73 @@ def add_to_parser(service_sub): parser_list_customer.add_argument('--metadata', action='append', nargs="+", type=str, help='') + # group + parser_create_group = subparsers.add_parser('creste_group', + help='[<"X-RANGER-Client" ' + 'header>] ') + parser_create_group.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_create_group.add_argument('datafile', + type=argparse.FileType('r'), + help='') + parser_delete_group = subparsers.add_parser('delete_group', + help='[<"X-RANGER-Client" ' + 'header>] ') + parser_delete_group.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_delete_group.add_argument('groupid', type=str, help='') + + # groups region + parser_add_groups_region = subparsers.add_parser( + 'add_groups_region', + help='[<"X-RANGER-Client" ' + 'header>] ' + '') + parser_add_groups_region.add_argument( + 'client', **cli_common.ORM_CLIENT_KWARGS) + parser_add_groups_region.add_argument( + 'groupid', type=str, help='') + parser_add_groups_region.add_argument( + 'datafile', type=argparse.FileType('r'), + help='') + + parser_delete_groups_region = subparsers.add_parser( + 'delete_groups_region', + help='[<"X-RANGER-Client" header>] [--force_delete] ' + ' ') + parser_delete_groups_region.add_argument('client', + **cli_common.ORM_CLIENT_KWARGS) + parser_delete_groups_region.add_argument('groupid', + type=str, + help='') + parser_delete_groups_region.add_argument('regionid', + type=str, + help='') + parser_delete_groups_region.add_argument('--force_delete', + help='force delete groups region', + action="store_true") + + # get group + h1, h2 = '[<"X-RANGER-Client" header>]', '' + parser_get_group = subparsers.add_parser('get_group', + help='%s %s' % (h1, h2)) + parser_get_group.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_get_group.add_argument('groupid', type=str, help=h2) + + # list groups + h1 = '[<"X-RANGER-Client" header>]' + h2 = '[--region ] [--starts_with ] [--contains ]' + parser_list_groups = subparsers.add_parser('list_groups', + help='%s %s' % (h1, h2)) + parser_list_groups.add_argument('client', **cli_common.ORM_CLIENT_KWARGS) + parser_list_groups.add_argument('--region', type=str, help='region name') + parser_list_groups.add_argument('--starts_with', type=str, + help='group name') + parser_list_groups.add_argument('--contains', type=str, + help='* contains in group name') + return parser @@ -279,42 +346,47 @@ def preparm(p): def cmd_details(args): if args.subcmd == 'create_customer': - return requests.post, '' + return requests.post, 'customers/' elif args.subcmd == 'delete_customer': - return requests.delete, '/%s' % args.custid + return requests.delete, 'customers/%s' % args.custid elif args.subcmd == 'update_customer': - return requests.put, '/%s' % args.custid + return requests.put, 'customers/%s' % args.custid elif args.subcmd == 'add_region': - return requests.post, '/%s/regions' % args.custid + return requests.post, 'customers/%s/regions' % args.custid elif args.subcmd == 'replace_region': - return requests.put, '/%s/regions' % args.custid + return requests.put, 'customers/%s/regions' % args.custid elif args.subcmd == 'delete_region': - return requests.delete, '/%s/regions/%s/%s' % (args.custid, - args.regionid, - args.force_delete) + return requests.delete, 'customers/%s/regions/%s/%s' % ( + args.custid, + args.regionid, + args.force_delete) elif args.subcmd == 'add_user': - return requests.post, '/%s/regions/%s/users' % ( + return requests.post, 'customers/%s/regions/%s/users' % ( args.custid, args.regionid) elif args.subcmd == 'replace_user': - return requests.put, '/%s/regions/%s/users' % ( + return requests.put, 'customers/%s/regions/%s/users' % ( args.custid, args.regionid) elif args.subcmd == 'delete_user': - return requests.delete, '/%s/regions/%s/users/%s' % ( - args.custid, args.regionid, args.userid) + return requests.delete, 'customers/%s/regions/%s/users/%s' % ( + args.custid, + args.regionid, + args.userid) elif args.subcmd == 'add_default_user': - return requests.post, '/%s/users' % args.custid + return requests.post, 'customers/%s/users' % args.custid elif args.subcmd == 'replace_default_user': - return requests.put, '/%s/users' % args.custid + return requests.put, 'customers/%s/users' % args.custid elif args.subcmd == 'delete_default_user': - return requests.delete, '/%s/users/%s' % (args.custid, args.userid) + return requests.delete, 'customers/%s/users/%s' % ( + args.custid, + args.userid) elif args.subcmd == 'add_metadata': - return requests.post, '/%s/metadata' % args.custid + return requests.post, 'customers/%s/metadata' % args.custid elif args.subcmd == 'replace_metadata': - return requests.put, '/%s/metadata' % args.custid + return requests.put, 'customers/%s/metadata' % args.custid elif args.subcmd == 'get_customer': - return requests.get, '/%s' % args.custid + return requests.get, 'customers/%s' % args.custid elif args.subcmd == 'enabled': - return requests.put, '/%s/enabled' % args.custid + return requests.put, 'customers/%s/enabled' % args.custid elif args.subcmd == 'list_customers': param = '' if args.region: @@ -328,7 +400,29 @@ def cmd_details(args): if args.metadata: for meta in args.metadata: param += '%smetadata=%s' % (preparm(param), meta[0]) - return requests.get, '/%s' % param + return requests.get, 'customers/%s' % param + elif args.subcmd == 'create_group': + return requests.post, 'groups/' + elif args.subcmd == 'delete_group': + return requests.delete, 'groups/%s' % args.groupid + elif args.subcmd == 'add_groups_region': + return requests.post, 'groups/%s/regions' % args.groupid + elif args.subcmd == 'delete_groups_region': + return requests.delete, 'groups/%s/regions/%s/%s' % ( + args.groupid, + args.regionid, + args.force_delete) + elif args.subcmd == 'get_group': + return requests.get, 'groups/%s' % args.groupid + elif args.subcmd == 'list_groups': + param = '' + if args.region: + param += '%sregion=%s' % (preparm(param), args.region) + if args.starts_with: + param += '%sstarts_with=%s' % (preparm(param), args.starts_with) + if args.contains: + param += '%scontains=%s' % (preparm(param), args.contains) + return requests.get, 'groups/%s' % param def get_token(timeout, args, host): @@ -384,7 +478,11 @@ def get_token(timeout, args, host): 'Failed in get_token, host: {}, region: {}'.format(host, auth_region)) url = url % (keystone_ep,) - data = data % (base_config.user_domain_name, username, password, tenant_name, base_config.project_domain_name,) + data = data % (base_config.user_domain_name, + username, + password, + tenant_name, + base_config.project_domain_name,) if args.verbose: print( @@ -413,14 +511,16 @@ def get_environment_variable(argument): def run(args): - rms_url = args.rms_base_url if args.rms_base_url else base_config.rms['base_url'] - host = args.cms_base_url if args.cms_base_url else base_config.cms['base_url'] + rms_url = args.rms_base_url if args.rms_base_url else \ + base_config.rms['base_url'] + host = args.cms_base_url if args.cms_base_url else \ + base_config.cms['base_url'] port = args.port if args.port else base_config.cms['port'] data = args.datafile.read() if 'datafile' in args else '{}' timeout = args.timeout if args.timeout else 10 rest_cmd, cmd_url = cmd_details(args) - url = '%s/v1/orm/customers' % (host) + cmd_url + url = '%s/v1/orm/' % (host) + cmd_url if args.faceless: auth_token = auth_region = requester = client = '' else: diff --git a/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/regions.py b/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/regions.py new file mode 100644 index 00000000..2249112b --- /dev/null +++ b/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/regions.py @@ -0,0 +1,112 @@ +from oslo_db.exception import DBDuplicateEntry +from pecan import request, rest +from wsmeext.pecan import wsexpose + +from orm.common.orm_common.utils import api_error_utils as err_utils +from orm.common.orm_common.utils import utils +from orm.services.customer_manager.cms_rest.logger import get_logger +from orm.services.customer_manager.cms_rest.logic.error_base import ErrorStatus +from orm.services.customer_manager.cms_rest.logic.group_logic import GroupLogic +from orm.services.customer_manager.cms_rest.model.GroupModels import \ + Region, RegionResultWrapper +from orm.services.customer_manager.cms_rest.utils import authentication + +LOG = get_logger(__name__) + + +class RegionController(rest.RestController): + + @wsexpose([str], str, str, rest_content_types='json') + def get(self, group_id, region_id): + return ["This is groups region controller ", "group id: " + group_id] + + @wsexpose(RegionResultWrapper, str, body=[Region], + rest_content_types='json', status_code=200) + def post(self, group_id, regions): + LOG.info("RegionController - Add Regions group id {0} " + "regions: {1}".format(group_id, str(regions))) + authentication.authorize(request, 'groups:add_region') + try: + group_logic = GroupLogic() + result = group_logic.add_regions(group_id, + regions, + request.transaction_id) + LOG.info("RegionController - Add Regions finished: " + str(result)) + + event_details = 'Group {} regions: {} added'.format( + group_id, [r.name for r in regions]) + utils.audit_trail('add group regions', + request.transaction_id, + request.headers, + group_id, + event_details=event_details) + + except DBDuplicateEntry as exception: + LOG.log_exception( + "RegionController - Group Region already exists", exception) + raise err_utils.get_error(request.transaction_id, + status_code=409, + message='Region already exists', + error_details=exception.message) + + except ErrorStatus as exception: + LOG.log_exception( + "RegionController - Failed to add regions", exception) + raise err_utils.get_error(request.transaction_id, + message=exception.message, + status_code=exception.status_code) + except Exception as exception: + LOG.log_exception( + "RegionController - Failed in add regions", exception) + raise err_utils.get_error(request.transaction_id, + status_code=500, + error_details=str(exception)) + + return result + + @wsexpose(None, str, str, str, str, status_code=204) + def delete(self, group_id, region_id, force_delete='False'): + + if force_delete == 'True': + force_delete = True + else: + force_delete = False + + requester = request.headers.get('X-RANGER-Requester') + is_rds_client_request = requester == 'rds_resource_service_proxy' + LOG.info("Delete Region group id {0} region_id: {1} by RDS Proxy: " + " {2} ".format(group_id, region_id, is_rds_client_request)) + authentication.authorize(request, 'groups:delete_region') + try: + group_logic = GroupLogic() + group_logic.delete_region(group_id, + region_id, + request.transaction_id, + is_rds_client_request, + force_delete) + LOG.info("RegionController - Delete Region finished") + + event_details = 'Group {} region: {} deleted'.format(group_id, + region_id) + utils.audit_trail('delete group region', + request.transaction_id, + request.headers, + group_id, + event_details=event_details) + + except ValueError as exception: + raise err_utils.get_error(request.transaction_id, + message=exception.message, + status_code=404) + except ErrorStatus as exception: + LOG.log_exception("RegionController - Failed to delete region", + exception) + raise err_utils.get_error(request.transaction_id, + message=exception.message, + status_code=exception.status_code) + except Exception as exception: + LOG.log_exception("RegionController - Failed in delete Region", + exception) + raise err_utils.get_error(request.transaction_id, + status_code=500, + error_details=str(exception)) diff --git a/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/root.py b/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/root.py index fd4301a1..300cf2eb 100755 --- a/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/root.py +++ b/orm/services/customer_manager/cms_rest/controllers/v1/orm/group/root.py @@ -4,12 +4,15 @@ from wsmeext.pecan import wsexpose from orm.common.orm_common.utils import api_error_utils as err_utils from orm.common.orm_common.utils import utils -from orm.services.customer_manager.cms_rest.controllers.v1.orm.customer.regions import RegionController -from orm.services.customer_manager.cms_rest.controllers.v1.orm.customer.users import DefaultUserController +from orm.services.customer_manager.cms_rest.controllers.v1.orm.customer.users \ + import DefaultUserController +from orm.services.customer_manager.cms_rest.controllers.v1.orm.group.regions \ + import RegionController from orm.services.customer_manager.cms_rest.logger import get_logger from orm.services.customer_manager.cms_rest.logic.error_base import ErrorStatus from orm.services.customer_manager.cms_rest.logic.group_logic import GroupLogic -from orm.services.customer_manager.cms_rest.model.GroupModels import Group, GroupResultWrapper, GroupSummaryResponse +from orm.services.customer_manager.cms_rest.model.GroupModels \ + import Group, GroupResultWrapper, GroupSummaryResponse from orm.services.customer_manager.cms_rest.utils import authentication LOG = get_logger(__name__) @@ -26,22 +29,26 @@ class GroupController(rest.RestController): try: group_logic = GroupLogic() result = group_logic.get_group(group_uuid) - LOG.info("GroupController - GetGroupDetails finished well: " + str(result)) + LOG.info( + "GroupController - GetGroupDetails finished: " + str(result)) except ErrorStatus as exception: - LOG.log_exception("GroupController - Failed to GetGroupDetails", exception) + LOG.log_exception("GroupController - Failed to GetGroupDetails", + exception) raise err_utils.get_error(request.transaction_id, message=exception.message, status_code=exception.status_code) except Exception as exception: - LOG.log_exception("GroupController - Failed to GetGroupDetails", exception) + LOG.log_exception("GroupController - Failed to GetGroupDetails", + exception) raise err_utils.get_error(request.transaction_id, status_code=500, error_details=exception.message) return result - @wsexpose(GroupResultWrapper, body=Group, rest_content_types='json', status_code=201) + @wsexpose(GroupResultWrapper, body=Group, rest_content_types='json', + status_code=201) def post(self, group): LOG.info("GroupController - CreateGroup: " + str(group)) authentication.authorize(request, 'groups:create') @@ -54,12 +61,17 @@ class GroupController(rest.RestController): try: uuid = utils.create_or_validate_uuid(group.uuid, 'groupId') except TypeError: - raise ErrorStatus(409.1, 'Unable to create Group ID {0}'.format(group.uuid)) - + raise ErrorStatus( + 409.1, 'Unable to create Group ID {0}'.format( + group.uuid)) try: - result = group_logic.create_group(group, uuid, request.transaction_id) + result = group_logic.create_group(group, + uuid, + request.transaction_id) except oslo_db.exception.DBDuplicateEntry as exception: - raise ErrorStatus(409.2, 'Group field {0} already exists'.format(exception.columns)) + raise ErrorStatus( + 409.2, 'Group field {0} already exists'.format( + exception.columns)) LOG.info("GroupController - Group Created: " + str(result)) utils.audit_trail('create group', request.transaction_id, @@ -68,20 +80,25 @@ class GroupController(rest.RestController): return result except ErrorStatus as exception: - LOG.log_exception("GroupController - Failed to CreateGroup", exception) + LOG.log_exception("GroupController - Failed to CreateGroup", + exception) raise err_utils.get_error(request.transaction_id, message=exception.message, status_code=exception.status_code) - @wsexpose(GroupResultWrapper, str, body=Group, rest_content_types='json', status_code=200) + @wsexpose(GroupResultWrapper, str, body=Group, rest_content_types='json', + status_code=200) def put(self, group_id, group): LOG.info("GroupController - UpdateGroup: " + str(group)) authentication.authorize(request, 'groups:update') try: group_logic = GroupLogic() - result = group_logic.update_group(group, group_id, request.transaction_id) + result = group_logic.update_group(group, + group_id, + request.transaction_id) response.status = 200 - LOG.info("GroupController - UpdateGroup finished well: " + str(group)) + LOG.info( + "GroupController - UpdateGroup finished well: " + str(group)) utils.audit_trail('update group', request.transaction_id, request.headers, group_id, @@ -94,7 +111,8 @@ class GroupController(rest.RestController): status_code=exception.status_code) except Exception as exception: - LOG.log_exception("GroupController - Failed to UpdateGroup", exception) + LOG.log_exception("GroupController - Failed to UpdateGroup", + exception) raise err_utils.get_error(request.transaction_id, status_code=500, error_details=exception.message) @@ -108,7 +126,6 @@ class GroupController(rest.RestController): LOG.info("GroupController - GetGrouplist") authentication.authorize(request, 'groups:get_all') - # This shouldn't be necessary, but apparently is on mtn29 start = 0 if start is None else start limit = 0 if limit is None else limit @@ -121,12 +138,14 @@ class GroupController(rest.RestController): limit) return result except ErrorStatus as exception: - LOG.log_exception("GroupController - Failed to GetGrouplist", exception) + LOG.log_exception("GroupController - Failed to GetGrouplist", + exception) raise err_utils.get_error(request.transaction_id, status_code=exception.status_code) except Exception as exception: - LOG.log_exception("GroupController - Failed to GetGrouplist", exception) + LOG.log_exception("GroupController - Failed to GetGrouplist", + exception) raise err_utils.get_error(request.transaction_id, status_code=500, error_details=exception.message) diff --git a/orm/services/customer_manager/cms_rest/data/data_manager.py b/orm/services/customer_manager/cms_rest/data/data_manager.py index ccec1f22..9ef0eba8 100755 --- a/orm/services/customer_manager/cms_rest/data/data_manager.py +++ b/orm/services/customer_manager/cms_rest/data/data_manager.py @@ -1,14 +1,21 @@ import logging -from orm.services.customer_manager.cms_rest.data.sql_alchemy.customer_record import CustomerRecord -from orm.services.customer_manager.cms_rest.data.sql_alchemy.customer_region_record import CustomerRegionRecord -from orm.services.customer_manager.cms_rest.data.sql_alchemy.group_record import GroupRecord -from orm.services.customer_manager.cms_rest.data.sql_alchemy.models import (CmsRole, CmsUser, Customer, - Groups, - CustomerRegion, Quota, - QuotaFieldDetail, Region, - UserRole) -from orm.services.customer_manager.cms_rest.data.sql_alchemy.user_role_record import UserRoleRecord +from orm.services.customer_manager.cms_rest.data.sql_alchemy.customer_record \ + import CustomerRecord +from orm.services.customer_manager.cms_rest.data.sql_alchemy.\ + customer_region_record import CustomerRegionRecord +from orm.services.customer_manager.cms_rest.data.sql_alchemy.\ + group_record import GroupRecord +from orm.services.customer_manager.cms_rest.data.sql_alchemy.\ + groups_region_record import GroupsRegionRecord +from orm.services.customer_manager.cms_rest.data.sql_alchemy.models \ + import (CmsRole, CmsUser, Customer, + Groups, GroupRegion, + CustomerRegion, Quota, + QuotaFieldDetail, Region, + UserRole) +from orm.services.customer_manager.cms_rest.data.sql_alchemy.user_role_record \ + import UserRoleRecord from orm.services.customer_manager.cms_rest.logic.error_base import ErrorStatus import oslo_db from oslo_db.sqlalchemy import session as db_session @@ -38,7 +45,8 @@ class DataManager(object): if not connection_string: connection_string = conf.database.connection_string - self._engine_facade = db_session.EngineFacade(connection_string, autocommit=False) + self._engine_facade = db_session.EngineFacade(connection_string, + autocommit=False) self._session = None listen(self.session, 'before_flush', on_before_flush) self.image_record = None @@ -63,7 +71,9 @@ class DataManager(object): try: self.session.flush() except oslo_db.exception.DBDuplicateEntry as exception: - raise ErrorStatus(409.2, 'Duplicate Entry {0} already exist'.format(exception.columns)) + raise ErrorStatus( + 409.2, 'Duplicate Entry {0} already exist'.format( + exception.columns)) except Exception: raise @@ -136,6 +146,12 @@ class DataManager(object): self.session) return self.customer_region_record + if record_name == "GroupRegion" or record_name == "group_region": + if not hasattr(self, "groups_region_record"): + self.groups_region_record = GroupsRegionRecord( + self.session) + return self.groups_region_record + if record_name == "UserRole" or record_name == "user_role": if not hasattr(self, "user_role_record"): self.user_role_record = UserRoleRecord(self.session) @@ -211,7 +227,8 @@ class DataManager(object): sql_group = Groups( uuid=uuid, name=group.name, - domain_id=1, + domain_name='default', + enabled=group.enabled, description=group.description ) @@ -268,6 +285,27 @@ class DataManager(object): return db_region + def add_group_region(self, group_id, region_id): + group_region = GroupRegion( + group_id=group_id, + region_id=region_id + ) + + self.session.add(group_region) + self.flush() + + def add_region(self, region): + db_region = self.session.query(Region).filter( + Region.name == region.name).first() + if not (db_region is None): + return db_region + + db_region = Region(name=region.name, type=region.type) + self.session.add(db_region) + self.flush() + + return db_region + def get_region_id_by_name(self, name): region_id = self.session.query(Region.id).filter( Region.name == name).scalar() diff --git a/orm/services/customer_manager/cms_rest/data/sql_alchemy/group_record.py b/orm/services/customer_manager/cms_rest/data/sql_alchemy/group_record.py index adae4224..a2ede37f 100755 --- a/orm/services/customer_manager/cms_rest/data/sql_alchemy/group_record.py +++ b/orm/services/customer_manager/cms_rest/data/sql_alchemy/group_record.py @@ -1,6 +1,9 @@ from __builtin__ import int -from orm.services.customer_manager.cms_rest.data.sql_alchemy.models import (Groups) +from orm.services.customer_manager.cms_rest.data.sql_alchemy.models import ( + Groups, + Region, + GroupRegion) from orm.services.customer_manager.cms_rest.logger import get_logger LOG = get_logger(__name__) @@ -9,7 +12,8 @@ LOG = get_logger(__name__) class GroupRecord: def __init__(self, session): - # this model is uses only for the parameters of access mothods, not an instance of model in the database + # this model is uses only for the parameters of access mothods, + # not an instance of model in the database self.__groups = Groups() self.__TableName = "groups" @@ -31,11 +35,13 @@ class GroupRecord: try: self.session.add(groups) except Exception as exception: - LOG.log_exception("Failed to insert Group" + str(groups), exception) + LOG.log_exception("Failed to insert Group" + str(groups), + exception) raise def delete_by_primary_key(self, group_id): - result = self.session.connection().execute("delete from groups where id = {}".format(group_id)) # nosec + cmd = 'DELETE FROM groups WHERE id = %s' + result = self.session.connection().execute(cmd, (group_id)) return result def read_by_primary_key(self): @@ -44,7 +50,7 @@ class GroupRecord: def read_groups(self, group_id): try: groups = self.session.query(Groups).filter(Groups.id == group_id) - return group.first() + return groups.first() except Exception as exception: message = "Failed to read_groups:group_id: %d " % (group_id) @@ -53,7 +59,8 @@ class GroupRecord: def read_group_by_uuid(self, group_uuid): try: - groups = self.session.query(Groups).filter(Groups.uuid == group_uuid) + groups = self.session.query(Groups).filter( + Groups.uuid == group_uuid) return groups.first() except Exception as exception: @@ -61,14 +68,35 @@ class GroupRecord: LOG.log_exception(message, exception) raise - def get_group_id_from_uuid(self, uuid): - result = self.session.connection().scalar("SELECT id from groups WHERE uuid = \"{}\"".format(uuid)) # nosec + def get_group_id_from_uuid(self, group_uuid): + cmd = "SELECT id from groups WHERE uuid = %s" + result = self.session.connection().scalar(cmd, (group_uuid)) if result: return int(result) else: return None + def get_groups_status_by_uuids(self, uuid_str): + cmd = "SELECT id, resource_id, region, status FROM " \ + "rds_resource_status_view WHERE resource_id IN (%s)" + results = self.session.connection().execute(cmd, (uuid_str)) + + group_region = {} + if results: + resource_status = dict( + (id, (resource_id, region, status)) + for id, resource_id, region, status in results) + # using resource_status, create group_region with resource_id + # as key and (region, status) as value + for v in resource_status.values(): + if v[0] in group_region: + group_region[v[0]].append(v[1:]) + else: + group_region[v[0]] = [v[1:]] + results.close() + return group_region + def delete_group_by_uuid(self, uuid): try: result = self.session.query(Groups).filter( @@ -85,9 +113,8 @@ class GroupRecord: try: LOG.info("get_groups_by_criteria: criteria: {0}".format(criteria)) region = criteria['region'] if 'region' in criteria else None - user = criteria['user'] if 'user' in criteria else None - rgroup = criteria['rgroup'] if 'rgroup' in criteria else None - starts_with = criteria['starts_with'] if 'starts_with' in criteria else None + starts_with = criteria['starts_with'] if 'starts_with' in \ + criteria else None contains = criteria['contains'] if 'contains' in criteria else None query = self.session.query(Groups) @@ -100,11 +127,20 @@ class GroupRecord: query = query.filter( Groups.name.ilike("%{}%".format(contains))) + if region: + query = query.join(GroupRegion).filter( + GroupRegion.group_id == Groups.uuid) + query = query.join(Region).filter( + Region.id == GroupRegion.region_id, + Region.type == 'single', + Region.name == region) + query = self.customise_query(query, criteria) return query.all() except Exception as exception: - message = "Failed to get_groups_by_criteria: criteria: {0}".format(criteria) + message = "Failed to get_groups_by_criteria: " \ + "criteria: {0}".format(criteria) LOG.log_exception(message, exception) raise diff --git a/orm/services/customer_manager/cms_rest/data/sql_alchemy/groups_region_record.py b/orm/services/customer_manager/cms_rest/data/sql_alchemy/groups_region_record.py new file mode 100755 index 00000000..4cfed89a --- /dev/null +++ b/orm/services/customer_manager/cms_rest/data/sql_alchemy/groups_region_record.py @@ -0,0 +1,97 @@ +from orm.services.customer_manager.cms_rest.data.sql_alchemy.group_record \ + import GroupRecord +from orm.services.customer_manager.cms_rest.data.sql_alchemy.models \ + import GroupRegion +from orm.services.customer_manager.cms_rest.data.sql_alchemy.region_record \ + import RegionRecord +from orm.services.customer_manager.cms_rest.logger import get_logger + +LOG = get_logger(__name__) + + +class GroupsRegionRecord: + def __init__(self, session): + + # thie model uses for the parameters for any acceess methods - not + # as instance of record in the table + self.__groups_region = GroupRegion() + self.__TableName = "groups_region" + + if (session): + self.session = session + + def setDBSession(self, session): + self.session = session + + @property + def groups_region(self): + return self.__groups_region + + @groups_region.setter + def groups_region(self): + self.__groups_region = GroupRegion() + + def insert(self, group_region): + try: + self.session.add(group_region) + except Exception as exception: + LOG.log_exception( + "Failed to insert group_region" + str(group_region), exception) + raise + + def get_regions_for_group(self, group_uuid): + group_regions = [] + + try: + group_record = GroupRecord(self.session) + group_id = group_record.get_group_id_from_uuid(group_uuid) + query = self.session.query(GroupRegion).filter( + GroupRegion.group_id == group_id) + + for group_region in query.all(): + group_regions.append(group_region) + return group_regions + + except Exception as exception: + message = "Failed to get_region_names_for_group: %d" % (group_id) + LOG.log_exception(message, exception) + raise + + def delete_region_for_group(self, group_uuid, region_name): + # get region id by name + region_record = RegionRecord(self.session) + region_id = region_record.get_region_id_from_name(region_name) + if region_id is None: + raise ValueError( + 'region with the region name {0} not found'.format( + region_name)) + cmd = 'DELETE FROM groups_region WHERE group_id = %s and \ + region_id = %s' + result = self.session.connection().execute(cmd, + (group_uuid, region_id)) + + self.session.flush() + + if result.rowcount == 0: + LOG.warn('region with the region name {0} not found'.format( + region_name)) + raise ValueError( + 'region with the region name {0} not found'.format( + region_name)) + + LOG.debug("num records deleted: " + str(result.rowcount)) + return result + + def delete_all_regions_for_group(self, group_id): + # group_id can be a uuid (type of string) or id (type of int). + # If group_id is uuid, then get id from uuid and use the id in the + # next sql command + if isinstance(group_id, basestring): + group_record = GroupRecord(self.session) + group_id = group_record.get_group_id_from_uuid(group_id) + + # not including default region which is -1 + cmd = 'DELETE FROM groups_region WHERE group_id = %s and \ + region_id <> -1' + result = self.session.connection().execute(cmd, (group_id)) + return result diff --git a/orm/services/customer_manager/cms_rest/data/sql_alchemy/models.py b/orm/services/customer_manager/cms_rest/data/sql_alchemy/models.py index cfc214d7..25f51483 100755 --- a/orm/services/customer_manager/cms_rest/data/sql_alchemy/models.py +++ b/orm/services/customer_manager/cms_rest/data/sql_alchemy/models.py @@ -33,8 +33,8 @@ class CmsDomain(Base, CMSBaseModel): ''' -' Groups is a DataObject and contains all the fields defined in Groups table record. -' defined as SqlAlchemy model map to a table +' Groups is a DataObject and contains all the fields defined in Groups +' table record, defined as SqlAlchemy model map to a table ''' @@ -43,16 +43,21 @@ class Groups(Base, CMSBaseModel): id = Column(Integer, primary_key=True) uuid = Column(String(64), nullable=False, unique=True) - domain_id = Column(Integer, ForeignKey('cms_domain.id'), primary_key=True, nullable=False) + domain_name = Column(String(64), ForeignKey('cms_domain.name'), nullable=False) name = Column(String(64), nullable=False, unique=True) description = Column(String(255), nullable=True) + enabled = Column(SmallInteger, nullable=False) + group_regions = relationship("GroupRegion", cascade="all, delete, delete-orphan") def __json__(self): return dict( uuid=self.uuid, name=self.name, description=self.description, - domain_id=self.domain_id + domain_name=self.domain_name, + enabled=self.enabled, + group_regions=[group_region.__json__() for group_region in + self.group_regions] ) def get_dict(self): @@ -62,23 +67,72 @@ class Groups(Base, CMSBaseModel): proxy_dict = { "uuid": self.uuid, "name": self.name, - "domain_id": self.domain_id, - "description": self.description + "domain_name": self.domain_name, + "description": self.description, + "enabled": 1 if self.enabled else 0 + } + group_regions = self.get_group_regions() + proxy_dict["regions"] = [group_region.get_proxy_dict() for group_region in group_regions] + + return proxy_dict + + def get_group_regions(self): + group_regions = [] + for group_region in self.group_regions: + if group_region.region_id != -1: + group_regions.append(group_region) + return group_regions + + def to_wsme(self): + uuid = self.uuid + name = self.name + domain_name = self.domain_name + description = self.description + enabled = True if self.enabled else False + regions = [group_region.to_wsme() for group_region in self.group_regions if + group_region.region_id != -1] + result = GroupWsmeModels.Group(description=description, + name=name, + uuid=uuid, + regions=regions, + enabled=enabled, + domain_name=domain_name) + return result + +''' +' GroupRegion is a DataObject and contains all the fields defined in GroupRegion table record. +' defined as SqlAlchemy model map to a table +''' + + +class GroupRegion(Base, CMSBaseModel): + __tablename__ = "groups_region" + + group_id = Column(String(64), ForeignKey('groups.uuid'), primary_key=True, nullable=False, index=True) + region_id = Column(Integer, ForeignKey('cms_region.id'), primary_key=True, nullable=False, index=True) + + region = relationship("Region", viewonly=True) + + def __json__(self): + return dict( + group_id=self.group_id, + region_id=self.region_id + ) + + def get_proxy_dict(self): + proxy_dict = { + "name": self.region.name, + "action": "modify" } return proxy_dict def to_wsme(self): - uuid = self.uuid - name = self.name - domainId = self.domain_id - description = self.description - - result = GroupWsmeModels.Group(description=description, - name=name, - uuid=uuid, - domainId=domainId) - return result + name = self.region.name + type = self.region.type + region = GroupWsmeModels.Region(name=name, + type=type) + return region ''' ' CmsUser is a DataObject and contains all the fields defined in CmsUser table record. diff --git a/orm/services/customer_manager/cms_rest/etc/policy.json b/orm/services/customer_manager/cms_rest/etc/policy.json index d5df3f26..6405b865 100755 --- a/orm/services/customer_manager/cms_rest/etc/policy.json +++ b/orm/services/customer_manager/cms_rest/etc/policy.json @@ -41,6 +41,7 @@ "groups:get_all": "rule:admin_or_support_or_viewer_or_creator", "groups:create": "rule:admin_or_support_or_creator", "groups:update": "rule:admin_or_creator", - "groups:delete": "rule:admin" - + "groups:delete": "rule:admin", + "groups:add_region": "rule:admin_or_support_or_creator", + "groups:delete_region": "rule:admin_or_creator" } diff --git a/orm/services/customer_manager/cms_rest/logic/group_logic.py b/orm/services/customer_manager/cms_rest/logic/group_logic.py index b7c31076..10ac6c1b 100755 --- a/orm/services/customer_manager/cms_rest/logic/group_logic.py +++ b/orm/services/customer_manager/cms_rest/logic/group_logic.py @@ -1,12 +1,20 @@ from pecan import request +from pecan import conf, request +import requests from orm.common.orm_common.utils import utils -from orm.services.customer_manager.cms_rest.data.data_manager import DataManager -from orm.services.customer_manager.cms_rest.logger import get_logger -from orm.services.customer_manager.cms_rest.logic.error_base import (ErrorStatus) -from orm.services.customer_manager.cms_rest.model.GroupModels import (GroupResultWrapper, GroupSummary, - GroupSummaryResponse) +from orm.common.orm_common.utils.cross_api_utils import (get_regions_of_group, + set_utils_conf) +from orm.services.customer_manager.cms_rest.data.data_manager import \ + DataManager +from orm.services.customer_manager.cms_rest.logger import get_logger +from orm.services.customer_manager.cms_rest.logic.error_base import ( + DuplicateEntryError, ErrorStatus) +from orm.services.customer_manager.cms_rest.model.GroupModels import ( + GroupResultWrapper, GroupSummary, GroupSummaryResponse) + +from orm.services.customer_manager.cms_rest.rds_proxy import RdsProxy LOG = get_logger(__name__) @@ -21,14 +29,47 @@ class GroupLogic(object): sql_group = datamanager.add_group(group, uuid) + sql_group_id = sql_group.uuid + datamanager.add_group_region(sql_group_id, -1) + + self.add_regions_to_db(group.regions, sql_group_id, datamanager) + return sql_group + def add_regions_to_db(self, regions, sql_group_id, + datamanager, default_users=[]): + for region in regions: + + sql_region = datamanager.add_region(region) + try: + datamanager.add_group_region(sql_group_id, sql_region.id) + except Exception as ex: + if hasattr(ex, 'orig') and ex.orig[0] == 1062: + raise DuplicateEntryError( + 'Error, duplicate entry, region ' + + region.name + + ' already associated with group') + raise ex + def create_group(self, group, uuid, transaction_id): datamanager = DataManager() try: group.handle_region_group() sql_group = self.build_full_group(group, uuid, datamanager) - group_result_wrapper = build_response(uuid, transaction_id, 'create') + group_result_wrapper = build_response(uuid, transaction_id, + 'create') + + if sql_group.group_regions and len(sql_group.group_regions) > 1: + group_dict = sql_group.get_proxy_dict() + for region in group_dict["regions"]: + region["action"] = "create" + + datamanager.flush() + RdsProxy.send_group_dict(group_dict, transaction_id, "POST") + else: + LOG.debug( + "Group with no regions - wasn't send to RDS Proxy " + + str(group)) datamanager.commit() @@ -49,17 +90,19 @@ class GroupLogic(object): sql_group = group_record.read_group_by_uuid(group_uuid) if not sql_group: - raise ErrorStatus(404, 'group {0} was not found'.format(group_uuid)) - old_group_dict = sql_group.get_proxy_dict() + raise ErrorStatus( + 404, 'group {0} was not found'.format(group_uuid)) + # old_group_dict = sql_group.get_proxy_dict() group_record.delete_by_primary_key(group_id) datamanager.flush() sql_group = self.build_full_group(group, group_uuid, datamanager) - new_group_dict = sql_group.get_proxy_dict() + # new_group_dict = sql_group.get_proxy_dict() - group_result_wrapper = build_response(group_uuid, transaction_id, 'update') - datamanager.flush() # i want to get any exception created by this insert + group_result_wrapper = build_response(group_uuid, transaction_id, + 'update') + datamanager.flush() datamanager.commit() return group_result_wrapper @@ -69,17 +112,75 @@ class GroupLogic(object): datamanager.rollback() raise - def get_group(self, group): - + def delete_region(self, group_id, region_id, transaction_id, + on_success_by_rds, force_delete): datamanager = DataManager() + try: + group_region = datamanager.get_record('group_region') + sql_group = datamanager.get_group_by_uuid_or_name(group_id) + if on_success_by_rds and sql_group is None: + return + if sql_group is None: + raise ErrorStatus( + 404, + "group with id {} does not exist".format(group_id)) + group_dict = sql_group.get_proxy_dict() + group_region.delete_region_for_group(group_id, region_id) + datamanager.flush() + + if on_success_by_rds: + datamanager.commit() + LOG.debug("Region {0} in group {1} deleted".format(region_id, + group_id)) + else: + region = next((r.region for r in sql_group.group_regions + if r.region.name == region_id), None) + if region: + if region.type == 'group': + set_utils_conf(conf) + regions = get_regions_of_group(region.name) + else: + regions = [region_id] + for region in group_dict['regions']: + if region['name'] in regions: + region['action'] = 'delete' + + RdsProxy.send_group_dict(group_dict, transaction_id, "PUT") + if force_delete: + datamanager.commit() + else: + datamanager.rollback() + + except Exception as exp: + datamanager.rollback() + raise + + finally: + datamanager.close() + + def get_group(self, group): + datamanager = DataManager() sql_group = datamanager.get_group_by_uuid_or_name(group) if not sql_group: raise ErrorStatus(404, 'group: {0} not found'.format(group)) - ret_group = sql_group.to_wsme() - ret_group.status = 'no regions' + + if sql_group.get_group_regions(): + resp = requests.get(conf.api.rds_server.base + + conf.api.rds_server.status + + sql_group.uuid, verify=conf.verify).json() + + for item in ret_group.regions: + for status in resp['regions']: + if status['region'] == item.name: + item.status = status['status'] + if status['error_msg']: + item.error_message = status['error_msg'] + ret_group.status = resp['status'] + else: + ret_group.status = 'no regions' return ret_group @@ -87,16 +188,48 @@ class GroupLogic(object): start=0, limit=0): datamanager = DataManager() group_record = datamanager.get_record('group') - sql_groups = group_record.get_groups_by_criteria(region=region, - user=user, - starts_with=starts_with, - contains=contains, - start=start, - limit=limit) + sql_groups = group_record.get_groups_by_criteria( + region=region, + user=user, + starts_with=starts_with, + contains=contains, + start=start, + limit=limit) response = GroupSummaryResponse() if sql_groups: + uuids = ','.join(str(sql_group.uuid) + for sql_group in sql_groups + if sql_group and sql_group.uuid) + resource_status = group_record.get_groups_status_by_uuids(uuids) + for sql_group in sql_groups: groups = GroupSummary.from_db_model(sql_group) + + if sql_group.uuid: + # rds_region list contains tuples - each containing the + # region associated with the customer along with the + # region status + rds_region = resource_status.get(sql_group.uuid) + if rds_region and groups.regions: + # set customer.status to 'error' if any of the regions + # has an 'Error' status' else, if any region status + # shows 'Submitted' then set customer status to + # 'Pending'; otherwise customer status is 'Success' + error_status = [item for item in rds_region + if item[1] == 'Error'] + submitted_status = [item for item in rds_region + if item[1] == 'Submitted'] + success_status = [item for item in rds_region + if item[1] == 'Success'] + + if len(error_status) > 0: + groups.status = 'Error' + elif len(submitted_status) > 0: + groups.status = 'Pending' + elif len(success_status) > 0: + groups.status = 'Success' + else: + groups.status = 'no regions' response.groups.append(groups) return response @@ -109,12 +242,59 @@ class GroupLogic(object): sql_group = group_record.read_group_by_uuid(group_id) if sql_group is None: - raise ErrorStatus(404, "Group '{0}' not found".format(group_id)) + raise ErrorStatus( + 404, "Group '{0}' not found".format(group_id)) + + regions = sql_group.get_group_regions() + if len(regions) > 0: + # Do not delete a group that still has region(s) + raise ErrorStatus(405, + "Cannot delete a group that has region(s). " + "Please delete the region(s) first and then " + "delete the group.") + else: + expected_status = 'Success' + invalid_status = 'N/A' + # Get status from RDS + resp = RdsProxy.get_status(sql_group.uuid) + if resp.status_code == 200: + status_resp = resp.json() + if 'status' in status_resp.keys(): + LOG.debug('RDS returned status: {}'.format( + status_resp['status'])) + status = status_resp['status'] + else: + # Invalid response from RDS + LOG.error('Response from RDS did not contain status') + status = invalid_status + elif resp.status_code == 404: + # Group not found in RDS, that means it never has any + # region(s). So it is OK to delete it. + LOG.debug( + 'Resource not found in RDS, so it is OK to delete') + status = expected_status + else: + # Invalid status code from RDS + log_message = 'Invalid response code from RDS: {}'.format( + resp.status_code) + log_message = log_message.replace('\n', '_').replace('\r', + '_') + LOG.warning(log_message) + status = invalid_status + + if status == invalid_status: + raise ErrorStatus(500, "Could not get group status") + elif status != expected_status: + raise ErrorStatus( + 409, + "The group has not been deleted " + "successfully from all of its regions " + "(either the deletion failed on one of the " + "regions or it is still in progress)") # OK to delete group_record.delete_group_by_uuid(group_id) - - datamanager.flush() # i want to get any exception created by this delete + datamanager.flush() datamanager.commit() except Exception as exp: LOG.log_exception("GroupLogic - Failed to delete group", exp) diff --git a/orm/services/customer_manager/cms_rest/model/GroupModels.py b/orm/services/customer_manager/cms_rest/model/GroupModels.py index 3467a4df..e62015c8 100755 --- a/orm/services/customer_manager/cms_rest/model/GroupModels.py +++ b/orm/services/customer_manager/cms_rest/model/GroupModels.py @@ -1,14 +1,38 @@ from orm.services.customer_manager.cms_rest.logic.error_base import ErrorStatus from orm.services.customer_manager.cms_rest.model.Model import Model +from orm.common.orm_common.utils.cross_api_utils import (get_regions_of_group, + set_utils_conf) +from pecan import conf import wsme from wsme import types as wtypes class Region(Model): - """network model the group + """network model the region """ - def __init__(self, id): - self.id = id + name = wsme.wsattr(wsme.types.text, mandatory=True) + type = wsme.wsattr(wsme.types.text, default="single", mandatory=False) + status = wsme.wsattr(wsme.types.text, mandatory=False) + error_message = wsme.wsattr(wsme.types.text, mandatory=False) + + def __init__(self, name="", type="single", users=[], status="", + error_message=""): + """Create a new region. + + :param name: region name + :param type: region type + :param quotas: quotas ( array of Quota) + :param users: array of users of specific region + :param status: status of creation + :param error_message: error message if status is error + """ + + self.name = name + self.type = type + self.users = users + self.status = status + if error_message: + self.error_message = error_message class Group(Model): @@ -17,12 +41,13 @@ class Group(Model): description = wsme.wsattr(wsme.types.text, mandatory=True) name = wsme.wsattr(wsme.types.text, mandatory=True) status = wsme.wsattr(wsme.types.text, mandatory=False) - domainId = wsme.wsattr(int, mandatory=True) + domain_name = wsme.wsattr(wsme.types.text, mandatory=True) uuid = wsme.wsattr(wsme.types.text, mandatory=False) + enabled = wsme.wsattr(bool, mandatory=True) regions = wsme.wsattr([Region], mandatory=False) - def __init__(self, description="", name="", - regions=[], status="", domainId=1, uuid=None): + def __init__(self, description="", name="", enabled=False, + regions=[], status="", domain_name='default', uuid=None): """Create a new Group. :param description: Server name @@ -31,7 +56,8 @@ class Group(Model): self.description = description self.name = name self.status = status - self.domainId = domainId + self.domain_name = domain_name + self.enabled = enabled self.regions = regions if uuid is not None: self.uuid = uuid @@ -44,18 +70,27 @@ class Group(Model): if context == "update": for region in self.regions: if region.type == "group": - raise ErrorStatus(400, "region type is invalid for update, \'group\' can be only in create") + raise ErrorStatus(400, + "region type is invalid for update, " + " \'group\' can be only in create") def handle_region_group(self): regions_to_add = [] - for region in self.regions[:]: # get copy of it to be able to delete from the origin + # get copy of it to be able to delete from the origin + for region in self.regions[:]: if region.type == "group": group_regions = self.get_regions_for_group(region.name) if not group_regions: - raise ErrorStatus(404, 'Group {} Not found'.format(region.name)) + raise ErrorStatus( + 404, 'Group {} Not found'.format(region.name)) self.regions.remove(region) + # remove duplicates if exist + self.regions.extend(set(regions_to_add)) - self.regions.extend(set(regions_to_add)) # remove duplicates if exist + def get_regions_for_group(self, group_name): + set_utils_conf(conf) + regions = get_regions_of_group(group_name) + return regions class GroupResult(Model): @@ -83,33 +118,44 @@ class GroupResultWrapper(Model): self.group = group_result -""" GroupSummary is a DataObject and contains all the fields defined in GroupSummary structure. """ +""" GroupSummary is a DataObject and contains all the fields + defined in GroupSummary structure. +""" class GroupSummary(Model): name = wsme.wsattr(wsme.types.text) id = wsme.wsattr(wsme.types.text) description = wsme.wsattr(wsme.types.text) - domain_id = wsme.wsattr(int, mandatory=True) + domain_name = wsme.wsattr(wsme.types.text) + enabled = wsme.wsattr(bool, mandatory=True) status = wsme.wsattr(wtypes.text, mandatory=True) + regions = wsme.wsattr([str], mandatory=True) def __init__(self, name='', id='', description='', - status="", domain_id=0): + status="", enabled=True, domain_name='default', regions=[]): Model.__init__(self) self.name = name self.id = id self.description = description + self.enabled = enabled self.status = status - self.domain_id = domain_id + self.domain_name = domain_name + self.regions = regions @staticmethod def from_db_model(sql_group): + regions = [region.region.name for region in + sql_group.group_regions if + region.region_id != -1] group = GroupSummary() group.id = sql_group.uuid group.name = sql_group.name group.description = sql_group.description - group.domain_id = sql_group.domain_id + group.enabled = bool(sql_group.enabled) + group.domain_name = sql_group.domain_name + group.regions = regions return group @@ -122,4 +168,31 @@ class GroupSummaryResponse(Model): self.groups = [] +""" Region Result Handler """ + + +class RegionResult(Model): + id = wsme.wsattr(wsme.types.text, mandatory=True) + added = wsme.wsattr(wsme.types.text, mandatory=False) + links = wsme.wsattr({str: str}, mandatory=True) + + def __init__(self, id, added=None, links={}): + self.id = id + self.added = added + self.links = links + + +class RegionResultWrapper(Model): + transaction_id = wsme.wsattr(wsme.types.text, mandatory=True) + regions = wsme.wsattr([RegionResult], mandatory=True) + + def __init__(self, transaction_id, regions): + regions_result = [RegionResult(region['id'], + region['added'], + region['links']) for region in regions] + + self.transaction_id = transaction_id + self.regions = regions_result + + """ ****************************************************************** """ diff --git a/orm/services/customer_manager/cms_rest/rds_proxy.py b/orm/services/customer_manager/cms_rest/rds_proxy.py index de6f0e9b..25e7f2cb 100755 --- a/orm/services/customer_manager/cms_rest/rds_proxy.py +++ b/orm/services/customer_manager/cms_rest/rds_proxy.py @@ -17,28 +17,34 @@ class RdsProxy(object): def get_status(resource_id): try: LOG.debug( - "Sending to RDS Server to get status: " + conf.api.rds_server.base + conf.api.rds_server.status + resource_id) + "Sending to RDS Server to get status: " + + conf.api.rds_server.base + conf.api.rds_server.status + + resource_id) resp = requests.get( - conf.api.rds_server.base + conf.api.rds_server.status + resource_id, - verify=conf.verify) + conf.api.rds_server.base + conf.api.rds_server.status + + resource_id, verify=conf.verify) LOG.debug( - "Sending to RDS Server to get status: " + conf.api.rds_server.base + conf.api.rds_server.status + resource_id) + "Sending to RDS Server to get status: " + + conf.api.rds_server.base + + conf.api.rds_server.status + resource_id) pp = pprint.PrettyPrinter(width=30) pretty_text = pp.pformat(resp.json()) LOG.debug("Response from RDS Server:\n" + pretty_text) return resp except Exception as exp: - LOG.log_exception( - "CustomerLogic - Failed to Get status for customer : " + resource_id, - exp) + LOG.log_exception("CustomerLogic - Failed to Get status for " + "customer : " + resource_id, exp) raise @staticmethod - def send_customer(customer, transaction_id, method): # method is "POST" or "PUT" - return RdsProxy.send_customer_dict(customer.get_proxy_dict(), transaction_id, method) + def send_customer(customer, transaction_id, method): + # method is "POST" or "PUT" + return RdsProxy.send_customer_dict(customer.get_proxy_dict(), + transaction_id, + method) @staticmethod - def send_customer_dict(customer_dict, transaction_id, method): # method is "POST" or "PUT" + def send_customer_dict(customer_dict, transaction_id, method): data = { "service_template": { @@ -75,26 +81,105 @@ class RdsProxy(object): 'X-RANGER-Client'] if 'X-RANGER-Client' in request.headers else \ 'NA' headers['X-RANGER-Requester'] = request.headers[ - 'X-RANGER-Requester'] if 'X-RANGER-Requester' in request.headers else \ - '' + 'X-RANGER-Requester'] if 'X-RANGER-Requester' in \ + request.headers else '' - LOG.debug("Wrapper JSON before sending action: {0} to Rds Proxy\n{1}".format(method, pretty_text)) - LOG.info("Sending to RDS Server: " + conf.api.rds_server.base + conf.api.rds_server.resources) + LOG.debug("Wrapper JSON before sending action: {0} to Rds " + "Proxy\n{1}".format(method, pretty_text)) + LOG.info("Sending to RDS Server: " + + conf.api.rds_server.base + conf.api.rds_server.resources) wrapper_json = json.dumps(data) if method == "POST": - resp = requests.post(conf.api.rds_server.base + conf.api.rds_server.resources, - data=wrapper_json, - headers=headers, - verify=conf.verify) + resp = requests.post( + conf.api.rds_server.base + conf.api.rds_server.resources, + data=wrapper_json, + headers=headers, + verify=conf.verify) else: - resp = requests.put(conf.api.rds_server.base + conf.api.rds_server.resources, - data=wrapper_json, - headers=headers, - verify=conf.verify) + resp = requests.put( + conf.api.rds_server.base + conf.api.rds_server.resources, + data=wrapper_json, + headers=headers, + verify=conf.verify) if resp.content: - LOG.debug("Response Content from rds server: {0}".format(resp.content)) + LOG.debug( + "Response Content from rds server: {0}".format(resp.content)) + + content = resp.content + if resp.content: + content = resp.json() + + if resp.content and 200 <= resp.status_code < 300: + content = resp.json() + return content + + raise ErrorStatus(resp.status_code, content) + + @staticmethod + def send_group_dict(group_dict, transaction_id, method): + data = { + "service_template": + { + "resource": { + "resource_type": "group" + }, + "model": str(json.dumps(group_dict)), + "tracking": { + "external_id": "", + "tracking_id": transaction_id + } + } + } + + data_to_display = { + "service_template": + { + "resource": { + "resource_type": "group" + }, + "model": group_dict, + "tracking": { + "external_id": "", + "tracking_id": transaction_id + } + } + } + + pp = pprint.PrettyPrinter(width=30) + pretty_text = pp.pformat(data_to_display) + wrapper_json = json.dumps(data) + + headers['X-RANGER-Client'] = request.headers[ + 'X-RANGER-Client'] if 'X-RANGER-Client' in request.headers else \ + 'NA' + headers['X-RANGER-Requester'] = request.headers[ + 'X-RANGER-Requester'] if 'X-RANGER-Requester' in \ + request.headers else '' + + LOG.debug("Wrapper JSON before sending action: {0} to Rds " + "Proxy\n{1}".format(method, pretty_text)) + LOG.info("Sending to RDS Server: " + conf.api.rds_server.base + + conf.api.rds_server.resources) + + wrapper_json = json.dumps(data) + + if method == "POST": + resp = requests.post( + conf.api.rds_server.base + conf.api.rds_server.resources, + data=wrapper_json, + headers=headers, + verify=conf.verify) + else: + resp = requests.put( + conf.api.rds_server.base + conf.api.rds_server.resources, + data=wrapper_json, + headers=headers, + verify=conf.verify) + if resp.content: + LOG.debug( + "Response Content from rds server: {0}".format(resp.content)) content = resp.content if resp.content: diff --git a/orm/services/customer_manager/scripts/db_scripts/ranger_cms_create_db.sql b/orm/services/customer_manager/scripts/db_scripts/ranger_cms_create_db.sql index 7a9f1406..4845700a 100755 --- a/orm/services/customer_manager/scripts/db_scripts/ranger_cms_create_db.sql +++ b/orm/services/customer_manager/scripts/db_scripts/ranger_cms_create_db.sql @@ -106,52 +106,54 @@ create table if not exists groups ( id integer auto_increment not null, uuid varchar(64) not null, - domain_id integer not null, + domain_name varchar(64) not null, name varchar(64) not null, description varchar(255) not null, + enabled tinyint not null, primary key (id), - foreign key (`domain_id`) references `cms_domain` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, + foreign key (`domain_name`) references `cms_domain` (`name`) ON DELETE CASCADE ON UPDATE NO ACTION, + unique name_idx(name), unique uuid_idx (uuid)); create table if not exists groups_region ( region_id integer not null, - group_id integer not null, + group_id varchar(64) not null, primary key (region_id, group_id), foreign key (`region_id`) references `cms_region` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, - foreign key (`group_id`) references `groups` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, + foreign key (`group_id`) references `groups` (`uuid`) ON DELETE CASCADE ON UPDATE NO ACTION, index region_id (region_id), index group_id_idx (group_id)); -create table if not exists group_role +create table if not exists groups_role ( role_id integer not null, - group_id integer not null, - region_id integer not null, + group_id varchar(64) not null, + region_id integer not null, primary key (role_id, region_id, group_id), - foreign key (role_id) references cms_role(id), - foreign key (`group_id`) references `groups` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, + foreign key (role_id) references cms_role(id), + foreign key (`group_id`) references `groups` (`uuid`) ON DELETE CASCADE ON UPDATE NO ACTION, index region_id (region_id), index group_id_idx (group_id)); -create table if not exists group_user +create table if not exists groups_user ( - group_id integer not null, + group_id varchar(64) not null, user_id integer not null, primary key (group_id, user_id), foreign key (`user_id`) references `cms_user` (`id`) ON DELETE CASCADE, - foreign key (`group_id`) references `groups` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, + foreign key (`group_id`) references `groups` (`uuid`) ON DELETE CASCADE ON UPDATE NO ACTION, index user_id (user_id), index group_id (group_id)); -create table if not exists group_customer +create table if not exists groups_customer ( - group_id integer not null, + group_id varchar(64) not null, customer_id integer not null, region_id integer not null, primary key (group_id, customer_id, region_id), - foreign key (`group_id`) references `groups` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, + foreign key (`group_id`) references `groups` (`uuid`) ON DELETE CASCADE ON UPDATE NO ACTION, foreign key (`customer_id`) references `customer` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, foreign key (`region_id`) references `cms_region` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, index customer_id_idx (customer_id), diff --git a/orm/services/customer_manager/scripts/db_scripts/ranger_cms_update_db.sql b/orm/services/customer_manager/scripts/db_scripts/ranger_cms_update_db.sql index a7696922..d8b9b139 100644 --- a/orm/services/customer_manager/scripts/db_scripts/ranger_cms_update_db.sql +++ b/orm/services/customer_manager/scripts/db_scripts/ranger_cms_update_db.sql @@ -3,6 +3,7 @@ SET sql_notes=0; USE orm; insert ignore into cms_region(id,name,type) values(-1, "DEFAULT", "single"); +insert ignore into cms_domain(id,name) values(1, "default"); DROP PROCEDURE IF EXISTS MoveKeyToQuota; DELIMITER ;; diff --git a/orm/services/resource_distributor/config.py b/orm/services/resource_distributor/config.py index 78191eaa..8c806c81 100755 --- a/orm/services/resource_distributor/config.py +++ b/orm/services/resource_distributor/config.py @@ -41,7 +41,8 @@ audit = { cms = { 'base_url': config.cms['base_url'], - 'delete_region': 'v1/orm/customers/{0}/regions/{1}' + 'delete_region': 'v1/orm/customers/{0}/regions/{1}', + 'delete_groups_region': 'v1/orm/groups/{0}/regions/{1}' } fms = { @@ -115,6 +116,9 @@ yaml_configs = { }, 'image_yaml': { 'yaml_version': '2014-10-16' + }, + 'group_yaml': { + 'yaml_version': '2014-10-16' } } @@ -148,18 +152,22 @@ region_resource_id_status = { { 'customer', 'image', - 'flavor' + 'flavor', + 'group' }, 'allowed_ranger_agent_resource_version': { 'customer': '1.0', 'image': '1.0', - 'flavor': '1.0' + 'flavor': '1.0', + 'group': '1.0' } } app_module = app['modules'][0] -logging = config.get_log_config(config.rds['log'], app['service_name'], app_module) +logging = config.get_log_config(config.rds['log'], + app['service_name'], + app_module) verify = config.ssl_verify diff --git a/orm/services/resource_distributor/rds/controllers/v1/resources/root.py b/orm/services/resource_distributor/rds/controllers/v1/resources/root.py index 4be19580..12a88e30 100755 --- a/orm/services/resource_distributor/rds/controllers/v1/resources/root.py +++ b/orm/services/resource_distributor/rds/controllers/v1/resources/root.py @@ -73,10 +73,11 @@ class Result(wtypes.DynamicBase): """class method, json header.""" customer = wsme.wsattr(CreatedResource, mandatory=False) + group = wsme.wsattr(CreatedResource, mandatory=False) flavor = wsme.wsattr(CreatedResource, mandatory=False) image = wsme.wsattr(CreatedResource, mandatory=False) - def __init__(self, customer=None, + def __init__(self, customer=None, group=None, flavor=None, image=None): """init function. @@ -85,6 +86,8 @@ class Result(wtypes.DynamicBase): """ if customer is not None: self.customer = customer + if group is not None: + self.group = group if flavor is not None: self.flavor = flavor if image is not None: diff --git a/orm/services/resource_distributor/rds/ordupdate/ord_notifier.py b/orm/services/resource_distributor/rds/ordupdate/ord_notifier.py index c4fa4551..1eac24ce 100755 --- a/orm/services/resource_distributor/rds/ordupdate/ord_notifier.py +++ b/orm/services/resource_distributor/rds/ordupdate/ord_notifier.py @@ -212,7 +212,7 @@ def notify_ord(transaction_id, :param transaction_id: The transaction id under which the resource was updated :param tracking_id: The tracking ID of the whole operation - :param resource_type: The resource type ("customer" | "image" | "flavor") + :param resource_type: The resource type ("customer" | "group" | "image" | "flavor") :param resource_template_version: The version id of the change in git :param resource_name: The updated resource name :param resource_id: The updated resource ID diff --git a/orm/services/resource_distributor/rds/proxies/rds_resource_service_proxy.py b/orm/services/resource_distributor/rds/proxies/rds_resource_service_proxy.py index c4f44f94..4ccc6123 100755 --- a/orm/services/resource_distributor/rds/proxies/rds_resource_service_proxy.py +++ b/orm/services/resource_distributor/rds/proxies/rds_resource_service_proxy.py @@ -4,7 +4,8 @@ from pecan import conf import requests from orm.services.resource_distributor.rds.services.base import ErrorMessage -from orm.services.resource_distributor.rds.utils import authentication as AuthService +from orm.services.resource_distributor.rds.utils import \ + authentication as AuthService logger = logging.getLogger(__name__) @@ -25,26 +26,39 @@ def _set_headers(region): def invoke_resources_region_delete(resource_type, region, resource_id): - logger.debug( - "REGION STATUS PROXY - send delete status to {} service for region {}".format(resource_type, region)) + logger.debug("REGION STATUS PROXY - send delete status to {} service for " + "region {}".format(resource_type, region)) _set_headers(region) try: if resource_type == "customer": - logger.debug("sending the data to {} server delete method ".format(resource_type)) + logger.debug("sending the data to {} server delete " + "method ".format(resource_type)) response = requests.delete( - conf.cms.base_url + (conf.cms.delete_region).format(resource_id, region), + conf.cms.base_url + (conf.cms.delete_region).format( + resource_id, region), + headers=headers, verify=conf.verify) + elif resource_type == "group": + logger.debug("sending the data to {} server delete " + "method ".format(resource_type)) + response = requests.delete( + conf.cms.base_url + (conf.cms.delete_groups_region).format( + resource_id, region), headers=headers, verify=conf.verify) elif resource_type == "flavor": - logger.debug("sending the data to {} server delete method ".format(resource_type)) + logger.debug("sending the data to {} server delete " + "method ".format(resource_type)) response = requests.delete( - conf.fms.base_url + (conf.fms.delete_region).format(resource_id, region), + conf.fms.base_url + (conf.fms.delete_region).format( + resource_id, region), headers=headers, verify=conf.verify) elif resource_type == "image": - logger.debug("sending the data to {} server delete method ".format(resource_type)) + logger.debug("sending the data to {} server delete " + "method ".format(resource_type)) response = requests.delete( - conf.ims.base_url + (conf.ims.delete_region).format(resource_id, region), + conf.ims.base_url + (conf.ims.delete_region).format( + resource_id, region), headers=headers, verify=conf.verify) else: response = None @@ -78,20 +92,22 @@ def send_image_metadata(meta_data, region, resource_id, action='post'): _set_headers(region) data_to_send_as_json = json.dumps(data_to_send) logger.debug("sending the data to ims server post method ") - logger.debug("ims server {0} path = {1}".format(conf.ims.base_url, - conf.ims.metadata_path).format( - resource_id, region)) + logger.debug("ims server {0} path = {1}".format( + conf.ims.base_url, + conf.ims.metadata_path).format(resource_id, region)) if action == 'post': try: response = requests.post( - conf.ims.base_url + (conf.ims.metadata_path).format(resource_id, region), + conf.ims.base_url + (conf.ims.metadata_path).format( + resource_id, region), data=data_to_send_as_json, headers=headers, verify=conf.verify) logger.debug("got response from ims {}".format(response)) except requests.ConnectionError as exp: logger.error(exp) logger.exception(exp) - raise ErrorMessage("fail to connect to server {}".format(exp.message)) + raise ErrorMessage( + "fail to connect to server {}".format(exp.message)) if response.status_code != 200: raise ErrorMessage( diff --git a/orm/services/resource_distributor/rds/services/resource.py b/orm/services/resource_distributor/rds/services/resource.py index c5d88a49..0b656633 100755 --- a/orm/services/resource_distributor/rds/services/resource.py +++ b/orm/services/resource_distributor/rds/services/resource.py @@ -4,7 +4,7 @@ import time 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_bulder, - yaml_image_builder) + yaml_group_builder, yaml_image_builder) from orm.services.resource_distributor.rds.services.base import ConflictValue, ErrorMessage from orm.services.resource_distributor.rds.services.model.resource_input import ResourceData as InputData from orm.services.resource_distributor.rds.sot import sot_factory @@ -20,7 +20,7 @@ def _get_inputs_from_resource_type(jsondata, resource_type, external_transaction_id, operation="create"): - if resource_type == "customer": + if resource_type == "customer" or resource_type == "group": input_data = InputData(resource_id=jsondata['uuid'], resource_type=resource_type, operation=operation, @@ -99,6 +99,8 @@ def _create_data_to_sot(input_data): yamldata = "delete" elif input_data.resource_type == "customer": yamldata = yaml_customer_builder.yamlbuilder(jsondata, target) + elif input_data.resource_type == "group": + yamldata = yaml_group_builder.yamlbuilder(jsondata, target) elif input_data.resource_type == "flavor": yamldata = yaml_flavor_bulder.yamlbuilder(jsondata, target) elif input_data.resource_type == "image": diff --git a/orm/services/resource_distributor/rds/services/yaml_group_builder.py b/orm/services/resource_distributor/rds/services/yaml_group_builder.py new file mode 100755 index 00000000..d4dcb23f --- /dev/null +++ b/orm/services/resource_distributor/rds/services/yaml_group_builder.py @@ -0,0 +1,69 @@ +"""yaml build build yaml from json input.""" +import logging +import re +import yaml + +from pecan import conf + +logger = logging.getLogger(__name__) + + +def create_final_yaml(title, description, resources, outputs): + """put all yaml strings together. + + :param title: ther version of yaml + :param description: file description + :param resources: body of the yaml file + :param outputs: the output of the yaml + :return: the full string of yaml file + """ + title_yaml = re.sub("'", "", yaml.dump(title, default_flow_style=False)) + description_yaml = yaml.dump(description, default_flow_style=False) + resourcesyaml = re.sub("''", '', yaml.dump(resources, + default_flow_style=False)) + resources_yaml = re.sub("'", '', resourcesyaml) + yamldata = title_yaml + yamldata = yamldata + "\n" + description_yaml + yamldata = yamldata + "\n" + resources_yaml + yamldata = yamldata + "\n" + yaml.dump(outputs) + return yamldata + + +def yamlbuilder(alldata, region): + logger.info("building group yaml") + logger.debug("start building group yaml for region %s" % region['name']) + """build group yaml. + + build yaml file from json + :param alldata: full json data + :param region: data per region + :return: the full string of yaml file + """ + outputs = {} + resources = {} + yaml_version = conf.yaml_configs.group_yaml.yaml_version + title = {'heat_template_version': yaml_version} + description = {'description': 'yaml file for region - %s' % region['name']} + jsondata = alldata + group_name = jsondata['name'] + group_description = '"%s"' % (jsondata['description']) + status = {"0": False, "1": True}[str(jsondata['enabled'])] + outputs['outputs'] = {} + resources['resources'] = {} + + resources['resources'][group_name] =\ + {'type': 'OS::Keystone::Group\n', + 'properties': {'name': "%s" % group_name, + 'description': group_description, + 'domain': alldata['domain_name'], + 'roles': []}} + + # create the output + outputs['outputs'][group_name] =\ + {"value": {"get_resource": "%s" % group_name}} + + # putting all parts together for full yaml + yamldata = create_final_yaml(title, description, resources, outputs) + logger.debug( + "done building group yaml for region %s " % region['name']) + return yamldata diff --git a/orm/tests/unit/cms/test_group_logic.py b/orm/tests/unit/cms/test_group_logic.py new file mode 100644 index 00000000..a524a95f --- /dev/null +++ b/orm/tests/unit/cms/test_group_logic.py @@ -0,0 +1,224 @@ +from orm.services.customer_manager.cms_rest.data.sql_alchemy\ + import models as sql_models +from orm.services.customer_manager.cms_rest.logic.error_base import ErrorStatus +from orm.services.customer_manager.cms_rest.logic import group_logic +import orm.services.customer_manager.cms_rest.model.GroupModels as models +from orm.tests.unit.cms import FunctionalTest + +import mock + +group = None +data_manager_mock = None +record_mock = None +mock_returns_error = False +flow_type = 0 + + +STATUS_JSON = { + "regions": [ + { + "status": "Success", + "region": "GRP1", + "error_code": "", + "error_msg": "" + } + ], + "status": "Success" +} + + +class RdsStatus(object): + + def __init__(self, status_code=200, status="Success", oy=False): + self.status_code = status_code + self.status = status + self.oy = oy + + def json(self): + if self.oy: + return {} + else: + return {"status": self.status} + + +class TestGroupLogic(FunctionalTest): + def setUp(self): + global group + + FunctionalTest.setUp(self) + group_logic.DataManager = get_mock_datamanager + group_logic.pecan = mock.MagicMock() + group_logic.utils = mock.MagicMock() + group_logic.utils.make_transid.return_value = 'some_trans_id' + group_logic.utils.audit_trail.return_value = None + group_logic.utils.make_uuid.return_value = 'some_uuid' + group_logic.utils.get_time_human.return_value = '111' + + group_logic.RdsProxy = mock.MagicMock() + group_logic.RdsProxy.send_group.return_value = None + group_logic.RdsProxy.get_status.return_value = RdsStatus() + + group_logic.build_response = mock.MagicMock() + + group = models.Group() + + global flow_type + flow_type = 0 + + def tearDown(self): + global mock_returns_error + FunctionalTest.tearDown(self) + mock_returns_error = False + + def test_create_group_success_with_regions(self): + group.regions = [models.Region(name="a")] + group.name = 'Group Name' + logic = group_logic.GroupLogic() + logic.create_group(group, 'some_uuid', 'some_trans_id') + + assert data_manager_mock.commit.called + assert not data_manager_mock.rollback.called + + def test_delete_region_success(self): + logic = group_logic.GroupLogic() + logic.delete_region('group_id', 'region_id', 'transaction_is', True, + False) + + assert record_mock.delete_region_for_group.called + assert data_manager_mock.commit.called + + def test_delete_region_success_force_delete(self): + logic = group_logic.GroupLogic() + logic.delete_region('group_id', 'region_id', 'transaction_is', True, + True) + + assert record_mock.delete_region_for_group.called + assert data_manager_mock.commit.called + + def test_delete_region_error(self): + global mock_returns_error + mock_returns_error = True + + logic = group_logic.GroupLogic() + + self.assertRaises(SystemError, logic.delete_region, 'group_id', + 'region_id', 'transaction_is', True, False) + assert data_manager_mock.rollback.called + + def test_get_group_list_by_criteria(self): + logic = group_logic.GroupLogic() + result = logic.get_group_list_by_criteria(None, None, None, None) + self.assertTrue(data_manager_mock.get_record.called) + self.assertTrue(record_mock.get_groups_by_criteria.called) + + def test_get_group_success(self): + logic = group_logic.GroupLogic() + get_mock = mock.MagicMock() + get_mock.json.return_value = STATUS_JSON + group_logic.requests.get = mock.MagicMock(return_value=get_mock) + logic.get_group('group_id') + self.assertTrue(data_manager_mock.get_group_by_uuid_or_name.called) + + def test_get_group_not_found(self): + global flow_type + flow_type = 1 + logic = group_logic.GroupLogic() + self.assertRaises(ErrorStatus, logic.get_group, 'group_id') + self.assertTrue(data_manager_mock.get_group_by_uuid_or_name.called) + + def test_delete_group_by_uuid_success(self): + logic = group_logic.GroupLogic() + logic.delete_group_by_uuid('group_id') + + # Customer found in CMS DB but not found in RDS + group_logic.RdsProxy.get_status.return_value = RdsStatus( + status_code=404) + logic.delete_group_by_uuid('group_id') + + def test_delete_group_by_uuid_not_found(self): + global flow_type + # Change the flow to "customer not found in CMS DB" + flow_type = 1 + logic = group_logic.GroupLogic() + + # test that ErrorStatus exception is raised when no customer found + with self.assertRaises(group_logic.ErrorStatus): + logic.delete_group_by_uuid('group_id') + + def test_delete_group_by_uuid_errors(self): + global mock_returns_error + mock_returns_error = True + logic = group_logic.GroupLogic() + self.assertRaises(SystemError, logic.delete_group_by_uuid, 'group_id') + + # RDS returned an empty json + mock_returns_error = False + group_logic.RdsProxy.get_status.return_value = RdsStatus(oy=True) + self.assertRaises(group_logic.ErrorStatus, + logic.delete_group_by_uuid, + 'group_id') + + # RDS returned 500 + group_logic.RdsProxy.get_status.return_value = RdsStatus( + status_code=500) + self.assertRaises(group_logic.ErrorStatus, + logic.delete_group_by_uuid, + 'group_id') + + # RDS returned Error status + group_logic.RdsProxy.get_status.return_value = RdsStatus( + status='Error') + self.assertRaises(group_logic.ErrorStatus, + logic.delete_group_by_uuid, + 'group_id') + + def test_delete_group_by_uuid_conflict(self): + global flow_type + flow_type = 2 + logic = group_logic.GroupLogic() + self.assertRaises(group_logic.ErrorStatus, logic.delete_group_by_uuid, + 'group_id') + + +def get_mock_datamanager(): + global data_manager_mock + global record_mock + + sql_group = sql_models.Groups(name='a') + sql_group.group_regions = [] + + data_manager_mock = mock.MagicMock() + record_mock = mock.MagicMock() + record_mock.get_groups_by_criteria.return_value = [sql_group] + + def _get_group(): + def mock_to_wsme(): + return models.Group(regions=[models.Region()]) + + sql_group = sql_models.Groups() + sql_group.to_wsme = mock_to_wsme + sql_group.uuid = '1234' + sql_group.status = 'Success' + sql_group.name = 'GRP1' + + return sql_group + + if not mock_returns_error: + data_manager_mock.get_group_by_uuid_or_name.return_value = _get_group() + record_mock.delete_region_for_group.return_value = None + record_mock.delete_group_by_uuid.return_value = None + + if flow_type == 1: + record_mock.read_group_by_uuid.return_value = None + data_manager_mock.get_group_by_uuid_or_name.return_value = None + elif flow_type == 2: + q = mock.MagicMock() + q.get_group_regions.return_value = [mock.MagicMock()] + record_mock.read_group_by_uuid.return_value = q + record_mock.delete_group_by_uuid.side_effect = SystemError() + else: + record_mock.read_group_by_uuid.side_effect = SystemError() + record_mock.delete_region_for_group.side_effect = SystemError() + + data_manager_mock.get_record.return_value = record_mock + return data_manager_mock diff --git a/orm/tests/unit/cms/test_groups.py b/orm/tests/unit/cms/test_groups.py new file mode 100644 index 00000000..b9e323eb --- /dev/null +++ b/orm/tests/unit/cms/test_groups.py @@ -0,0 +1,217 @@ +import mock +import requests + +from orm.services.customer_manager.cms_rest.controllers.v1.orm.group\ + import root +from orm.services.customer_manager.cms_rest.logic.error_base import ErrorStatus +from orm.services.customer_manager.cms_rest.model import GroupModels +from orm.tests.unit.cms import FunctionalTest, test_utils +from wsme.exc import ClientSideError + +group_logic_mock = None + + +class TestGroupController(FunctionalTest): + def setUp(self): + FunctionalTest.setUp(self) + + root.authentication = mock.MagicMock() + + root.GroupLogic = get_mock_group_logic + root.GroupLogic.return_error = 0 + + root.utils = mock.MagicMock() + root.utils.make_transid.return_value = 'some_trans_id' + root.utils.audit_trail.return_value = None + root.utils.make_uuid.return_value = 'some_uuid' + + root.err_utils = mock.MagicMock() + + def tearDown(self): + FunctionalTest.tearDown(self) + + def test_create_group(self): + # given + requests.post = mock.MagicMock(return_value=ResponseMock(201)) + + # when + response = self.app.post_json('/v1/orm/groups', GROUP_JSON) + + # assert + assert response.status_int == 201 + assert root.utils.audit_trail.called + assert root.utils.create_or_validate_uuid.called + assert group_logic_mock.create_group_called + + def test_create_group_fail(self): + # given + requests.post = mock.MagicMock() + + root.GroupLogic.return_error = 1 + + root.err_utils.get_error = mock.MagicMock( + return_value=ClientSideError("blabla", 500)) + # when + response = self.app.post_json('/v1/orm/groups', + GROUP_JSON, expect_errors=True) + # assert + self.assertEqual(response.status_int, 500) + + def test_get_group(self): + # given + requests.get = mock.MagicMock(return_value=ResponseMock(200)) + + # when + response = self.app.get('/v1/orm/groups/some_id') + + # assert + assert response.status_int == 200 + assert group_logic_mock.get_group.called + + def test_get_group_fail_bad_request(self): + # given + requests.put = mock.MagicMock() + root.GroupLogic.return_error = 1 + root.err_utils.get_error = mock.MagicMock( + return_value=ClientSideError("blabla", 500)) + + # when + response = self.app.get('/v1/orm/groups/some_id', expect_errors=True) + + # assert + self.assertEqual(response.status_int, 500) + assert group_logic_mock.get_group.called + + def test_get_group_fail(self): + # given + requests.put = mock.MagicMock() + root.GroupLogic.return_error = 2 + root.err_utils.get_error = mock.MagicMock( + return_value=ClientSideError("blabla", 404)) + + # when + response = self.app.get('/v1/orm/groups/some_id', expect_errors=True) + + # assert + self.assertEqual(response.status_int, 404) + assert group_logic_mock.get_group.called + + def test_get_list_group(self): + # given + requests.get = mock.MagicMock(return_value=ResponseMock(200)) + + # when + response = self.app.get('/v1/orm/groups?region=region') + + # assert + assert group_logic_mock.get_group_list_by_criteria.called + + def test_get_list_group_fail(self): + # given + requests.get = mock.MagicMock() + root.GroupLogic.return_error = 1 + root.err_utils.get_error = mock.MagicMock( + return_value=ClientSideError("blabla", 500)) + + # when + response = self.app.get('/v1/orm/groups?region=region', + expect_errors=True) + + # assert + self.assertEqual(response.status_int, 500) + + def test_get_list_group_bad_request(self): + # given + requests.get = mock.MagicMock() + root.GroupLogic.return_error = 2 + root.err_utils.get_error = mock.MagicMock( + return_value=ClientSideError("blabla", 500)) + + # when + response = self.app.get('/v1/orm/groups?region=region', + expect_errors=True) + + # assert + self.assertEqual(response.status_int, 500) + + @mock.patch.object(root, 'authentication') + def test_delete_group_success(self, mock_auth): + response = self.app.delete('/v1/orm/groups/test') + self.assertEqual(response.status_int, 204) + + @mock.patch.object(root, 'authentication') + def test_delete_group_conflict(self, mock_auth): + root.GroupLogic.return_error = 2 + root.err_utils.get_error = test_utils.get_error + response = self.app.delete('/v1/orm/groups/test', expect_errors=True) + + self.assertEqual(response.status_int, 409) + + @mock.patch.object(root, 'authentication') + def test_delete_group_error(self, mock_auth): + root.GroupLogic.return_error = 1 + root.err_utils.get_error = test_utils.get_error + response = self.app.delete('/v1/orm/groups/test', expect_errors=True) + + self.assertEqual(response.status_int, 500) + + +def get_mock_group_logic(): + global group_logic_mock + group_logic_mock = mock.MagicMock() + + if root.GroupLogic.return_error == 0: + res = GroupModels.GroupResultWrapper(transaction_id='1', + id='1', + links={}, + updated=None, + created='1') + list_res = GroupModels.GroupSummaryResponse() + list_res.groups.append( + GroupModels.GroupSummary(name='1', id='1', description='1')) + group_logic_mock.get_group.return_value = GroupModels.Group( + **RET_GROUP_JSON) + group_logic_mock.get_group_list_by_criteria.return_value = list_res + group_logic_mock.create_group.return_value = res + + elif root.GroupLogic.return_error == 1: + group_logic_mock.create_group.side_effect = SystemError() + group_logic_mock.get_group.side_effect = SystemError() + group_logic_mock.get_group_list_by_criteria.side_effect = SystemError() + group_logic_mock.delete_group_by_uuid.side_effect = SystemError() + + else: + group_logic_mock.get_group.side_effect = ErrorStatus(status_code=404) + group_logic_mock.get_group_list_by_criteria.side_effect = ErrorStatus( + status_code=404) + group_logic_mock.delete_group_by_uuid.side_effect = ErrorStatus( + status_code=409) + + return group_logic_mock + + +class ResponseMock: + def __init__(self, status_code=200): + self.status_code = status_code + + +GROUP_JSON = { + "description": "Group description", + "enabled": True, + "name": "myGroup", + "domain_name": "default", + "regions": [ + { + "name": "SAN1", + "type": "single" + } + ] +} + +RET_GROUP_JSON = { + "description": "Group description", + "name": "myName", + "domain_name": "default", + "enabled": True, + "regions": [GroupModels.Region()] +} diff --git a/orm/tests/unit/ormcli/test_cmscli.py b/orm/tests/unit/ormcli/test_cmscli.py index f12dd734..5c2ec224 100755 --- a/orm/tests/unit/ormcli/test_cmscli.py +++ b/orm/tests/unit/ormcli/test_cmscli.py @@ -33,6 +33,7 @@ class CmsTests(TestCase): # Set up the args parameter args = mock.MagicMock() args.custid = 'test_custid' + args.groupid = 'test_groupid' args.regionid = 'test_region' args.userid = 'test_userid' args.region = 'test_region' @@ -42,35 +43,57 @@ class CmsTests(TestCase): args.force_delete is False subcmd_to_result = { - 'create_customer': (requests.post, '',), - 'delete_customer': (requests.delete, '/%s' % args.custid,), - 'update_customer': (requests.put, '/%s' % args.custid,), - 'add_region': (requests.post, '/%s/regions' % args.custid,), - 'replace_region': (requests.put, '/%s/regions' % args.custid,), + 'create_customer': (requests.post, 'customers/',), + 'delete_customer': ( + requests.delete, 'customers/%s' % args.custid,), + 'update_customer': (requests.put, 'customers/%s' % args.custid,), + 'add_region': ( + requests.post, 'customers/%s/regions' % args.custid,), + 'replace_region': ( + requests.put, 'customers/%s/regions' % args.custid,), 'delete_region': ( requests.delete, - '/%s/regions/%s/%s' % (args.custid, args.regionid, - args.force_delete),), + 'customers/%s/regions/%s/%s' % (args.custid, args.regionid, + args.force_delete),), 'add_user': ( - requests.post, - '/%s/regions/%s/users' % (args.custid, args.regionid),), + requests.post, 'customers/%s/regions/%s/users' % ( + args.custid, args.regionid),), 'replace_user': ( requests.put, - '/%s/regions/%s/users' % (args.custid, args.regionid),), - 'delete_user': (requests.delete, '/%s/regions/%s/users/%s' % ( - args.custid, args.regionid, args.userid),), - 'add_default_user': (requests.post, '/%s/users' % args.custid,), - 'replace_default_user': (requests.put, '/%s/users' % args.custid,), + 'customers/%s/regions/%s/users' % ( + args.custid, args.regionid),), + 'delete_user': ( + requests.delete, 'customers/%s/regions/%s/users/%s' % ( + args.custid, args.regionid, args.userid),), + 'add_default_user': ( + requests.post, 'customers/%s/users' % args.custid,), + 'replace_default_user': ( + requests.put, 'customers/%s/users' % args.custid,), 'delete_default_user': ( - requests.delete, '/%s/users/%s' % (args.custid, args.userid),), - 'add_metadata': (requests.post, '/%s/metadata' % args.custid,), - 'replace_metadata': (requests.put, '/%s/metadata' % args.custid,), - 'get_customer': (requests.get, '/%s' % args.custid,), + requests.delete, 'customers/%s/users/%s' % ( + args.custid, args.userid),), + 'add_metadata': ( + requests.post, 'customers/%s/metadata' % args.custid,), + 'replace_metadata': ( + requests.put, 'customers/%s/metadata' % args.custid,), + 'get_customer': (requests.get, 'customers/%s' % args.custid,), 'list_customers': (requests.get, - '/?region=%s&user=%s&starts_with=%s' + 'customers/?region=%s&user=%s&starts_with=%s' '&contains=%s' % (args.region, args.user, args.starts_with, - args.contains)) + args.contains)), + 'delete_group': ( + requests.delete, 'groups/%s' % args.groupid,), + 'delete_groups_region': ( + requests.delete, + 'groups/%s/regions/%s/%s' % (args.groupid, args.regionid, + args.force_delete),), + 'get_group': (requests.get, 'groups/%s' % args.groupid,), + 'list_groups': (requests.get, + 'groups/?region=%s&starts_with=%s' + '&contains=%s' % (args.region, + args.starts_with, + args.contains)) } # Assert that each subcommand returns the expected details @@ -105,7 +128,10 @@ class CmsTests(TestCase): @mock.patch.object(cmscli.cli_common, 'get_keystone_ep') @mock.patch.object(cmscli.requests, 'post') @mock.patch.object(cmscli.requests, 'get') - def test_list_customers(self, mock_get, mock_post, mock_get_keystone_ep): + @mock.patch.object(cmscli, 'get_token') + @mock.patch.object(cmscli, 'globals') + def test_list_customers(self, mock_globals, mock_get_token, + mock_get, mock_post, mock_get_keystone_ep): mock_post.return_value = self.respond(TJ, 200) mock_get.return_value = self.mock_response args = ormcli.main('orm cms list_customers t'.split()) @@ -222,7 +248,7 @@ class CmsTests(TestCase): @mock.patch('requests.get') @mock.patch('requests.post') - def test_list_customers(self, mock_post, mock_get): + def test_list_customers_with_filters(self, mock_post, mock_get): cli = ormcli.Cli() cli.create_parser() cli.parse( @@ -230,3 +256,70 @@ class CmsTests(TestCase): resp = self.respond('{"Hi, mom"}', 200, {'X-Subject-Token': 989}) mock_post.return_value = self.respond( {"access": {"token": {"id": 989}}}, 200) + + @mock.patch.object(cmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(cmscli.requests, 'post') + @mock.patch.object(cmscli.requests, 'get') + @mock.patch.object(cmscli, 'get_token') + @mock.patch.object(cmscli, 'globals') + def test_list_groups(self, mock_globals, mock_get_token, + mock_get, mock_post, mock_get_keystone_ep): + mock_post.return_value = self.respond(TJ, 200) + mock_get.return_value = self.mock_response + args = ormcli.main('orm cms list_groups t'.split()) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn(json.dumps(TJ), output) + + @mock.patch.object(cmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(cmscli.requests, 'post') + @mock.patch.object(cmscli.requests, 'get') + @mock.patch.object(cmscli, 'get_token') + @mock.patch.object(cmscli, 'globals') + def test_list_groups_a(self, mock_globals, mock_get_token, + mock_get, mock_post, mock_get_keystone_ep): + mock_post.return_value = self.respond(TJ, 200) + mock_get.return_value = self.mock_response + mock_get.__name__ = 'a' + args = ormcli.main('orm cms --verbose list_groups t'.split()) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn(json.dumps(TJ), output) + + @mock.patch.object(cmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(cmscli.requests, 'post') + @mock.patch.object(cmscli.requests, 'get') + def test_list_groups_e(self, mock_get, mock_post, mock_get_keystone_ep): + mock_post.return_value = self.respond(TJ, 200) + mock_get.side_effect = Exception('e') + with self.assertRaises(SystemExit) as cm: + args = ormcli.main('orm cms list_groups t'.split()) + self.assertEqual(cm.exception.code, 1) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn('e', output) + + @mock.patch.object(cmscli.cli_common, 'get_keystone_ep') + @mock.patch.object(cmscli.requests, 'post') + @mock.patch.object(cmscli.requests, 'get') + @mock.patch.object(cmscli, 'get_token') + @mock.patch.object(cmscli, 'globals') + def test_list_groups_errors(self, mock_globals, mock_get_token, + mock_get, mock_post, + mock_get_keystone_ep): + mock_post.return_value = self.respond(TJ, 200) + mock_get.return_value = self.respond(TJ, 204, oy=True) + with self.assertRaises(SystemExit) as cm: + args = ormcli.main('orm cms list_groups t'.split()) + self.assertEqual(cm.exception.code, 0) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertEqual('', output) + + mock_get.return_value = self.respond(TJ, 404, oy=True) + with self.assertRaises(SystemExit) as cm: + args = ormcli.main('orm cms --faceless list_groups t'.split()) + self.assertEqual(cm.exception.code, 1) + sys.stdout.seek(0) + output = sys.stdout.read() + self.assertIn('API error:', output) diff --git a/orm/tests/unit/rds/services/test_group_yaml.py b/orm/tests/unit/rds/services/test_group_yaml.py new file mode 100644 index 00000000..3b4a22dd --- /dev/null +++ b/orm/tests/unit/rds/services/test_group_yaml.py @@ -0,0 +1,42 @@ +"""unittests create group yaml module.""" +from mock import patch +import unittest +import yaml + +from orm.services.resource_distributor.rds.services import\ + yaml_group_builder as GroupBuild + +alldata = { + 'domain_name': 'nc', + 'description': 'this is a description', 'enabled': 1, + 'regions': [{'name': 'regionname'}], + 'name': 'test_group'} + +yaml_group = \ + 'heat_template_version: 2015-1-1\n\ndescription: yaml file for region - ' \ + 'regionname\n\nresources:\n'\ + ' test_group:\n properties:\n'\ + ' description: "this is a description"\n'\ + ' domain: nc\n'\ + ' name: test_group\n roles: []\n'\ + ' type: OS::Keystone::Group\n\n\n'\ + 'outputs:\n'\ + ' test_group:\n'\ + ' value: {get_resource: test_group}\n' + +region = {'name': 'regionname', + 'rangerAgentVersion': 1.0} + + +class CreateResource(unittest.TestCase): + """class metohd.""" + maxDiff = None + + @patch.object(GroupBuild, 'conf') + def test_create_group_yaml_nousers(self, mock_conf): + """test valid dict to yaml output as expected without users.""" + ver = mock_conf.yaml_configs.group_yaml.yaml_version = '2015-1-1' + yamlfile = GroupBuild.yamlbuilder(alldata, region) + yamlfile_as_json = yaml.safe_load(yamlfile) + self.assertEqual(yamlfile_as_json['heat_template_version'], ver) + self.assertEqual(yaml.safe_load(yamlfile), yaml.safe_load(yaml_group))