docker/services/rightsizer_parent_service.py (147 lines of code) (raw):

from typing import List, Union from modular_sdk.commons.constants import RIGHTSIZER_PARENT_TYPE, \ RIGHTSIZER_LICENSES_PARENT_TYPE, \ TENANT_PARENT_MAP_RIGHTSIZER_LICENSES_TYPE, ParentScope, ParentType from modular_sdk.models.parent import Parent from modular_sdk.models.tenant import Tenant from modular_sdk.services.customer_service import CustomerService from modular_sdk.services.parent_service import ParentService from modular_sdk.services.tenant_service import TenantService from pynamodb.attributes import MapAttribute from commons.constants import TENANT_PARENT_MAP_RIGHTSIZER_TYPE, \ PARENT_SCOPE_ALL_TENANTS, PARENT_SCOPE_SPECIFIC_TENANT from commons.log_helper import get_logger from models.parent_attributes import ParentMeta, ShapeRule, LicensesParentMeta from services.environment_service import EnvironmentService _LOG = get_logger('r8s-parent-service') class RightSizerParentService(ParentService): def __init__(self, tenant_service: TenantService, customer_service: CustomerService, environment_service: EnvironmentService): self.environment_service = environment_service self.parent_type_meta_mapping = { RIGHTSIZER_PARENT_TYPE: ParentMeta, RIGHTSIZER_LICENSES_PARENT_TYPE: LicensesParentMeta } self.parent_type_tenant_pid_mapping = { RIGHTSIZER_PARENT_TYPE: TENANT_PARENT_MAP_RIGHTSIZER_TYPE, RIGHTSIZER_LICENSES_PARENT_TYPE: TENANT_PARENT_MAP_RIGHTSIZER_LICENSES_TYPE } self._excess_attributes_cache = {} super(RightSizerParentService, self).__init__( customer_service=customer_service, tenant_service=tenant_service ) def resolve(self, licensed_parent: Parent, scan_tenants: list = None) -> Parent: """ Resolves RIGHTSIZER parent from RIGHTSIZER_LICENSES. Depends on the RIGHTSIZER_LICENSES scope, RIGHTSIZER parent will be resolved in different ways: ALL_TENANTS: first RIGHTSIZER parent linked to same application with ALL_TENANTS scope and matching cloud will be taken SPECIFIC_TENANTS: RIGHSIZER parent will be taken from scan_tenants, from Tenant.pid map :param licensed_parent: Parent of RIGHTSIZER_LICENSES type :param scan_tenants: list of tenants to scan - required for SPECIFIC_TENANTS scope :return: Parent with RIGHTSIZER type """ _LOG.debug(f'Searching for RIGHTSIZER parent for licensed parent ' f'\'{licensed_parent.parent_id}\'') licensed_parent_meta = self.get_parent_meta(parent=licensed_parent) if licensed_parent.scope == ParentScope.ALL.value: _LOG.debug(f'Licensed scope: {ParentScope.ALL.value}. ' f'Going to search for Parent directly') parents = self.list_application_parents( application_id=licensed_parent.application_id, only_active=True, type_=ParentType.RIGHTSIZER_PARENT.value ) for parent in parents: if parent.scope == ParentScope.ALL.value and \ licensed_parent.cloud == parent.cloud: return parent elif licensed_parent.scope == ParentScope.SPECIFIC.value \ and scan_tenants: _LOG.debug(f'Licensed scope: {ParentScope.SPECIFIC.value}. ' f'Validating tenant {licensed_parent.tenant_name}') tenant = self.tenant_service.get( tenant_name=licensed_parent.tenant_name) parent_map = tenant.parent_map.as_dict() linked_parent_id = parent_map.get( TENANT_PARENT_MAP_RIGHTSIZER_TYPE) linked_licensed_parent_id = parent_map.get( TENANT_PARENT_MAP_RIGHTSIZER_LICENSES_TYPE ) if linked_licensed_parent_id != licensed_parent.parent_id: return if not linked_parent_id: _LOG.warning(f'Tenant \'{licensed_parent.tenant_name}\' ' f'don\'t have RIGHTSIZER type linkage.') return return self.get_parent_by_id(parent_id=linked_parent_id) @staticmethod def list_application_parents(application_id, type_: str, only_active=True): if only_active: return list(Parent.scan( filter_condition= (Parent.type == type_) & (Parent.application_id == application_id) & (Parent.is_deleted == False))) return list(Parent.scan( filter_condition=(Parent.application_id == application_id) & (Parent.type == type_))) def get_parent_meta(self, parent: Parent) -> \ Union[ParentMeta, LicensesParentMeta]: meta: MapAttribute = parent.meta meta_model = self.parent_type_meta_mapping.get(parent.type, RIGHTSIZER_PARENT_TYPE) if meta: meta_dict = meta.as_dict() allowed_keys = list(meta_model._attributes.keys()) excess_attributes = {} meta_dict_filtered = {} for key, value in meta_dict.items(): if key not in allowed_keys: excess_attributes[key] = value else: meta_dict_filtered[key] = value if excess_attributes: self._excess_attributes_cache[parent.parent_id] = \ excess_attributes application_meta_obj = meta_model(**meta_dict_filtered) else: application_meta_obj = meta_model() return application_meta_obj @staticmethod def list_shape_rules(parent_meta: ParentMeta) -> \ List[ShapeRule]: if not parent_meta.shape_rules: return [] return [ShapeRule(**rule) for rule in parent_meta.shape_rules] def get_shape_rule(self, parent_meta: ParentMeta, rule_id: str) -> Union[ShapeRule, None]: rules = self.list_shape_rules(parent_meta=parent_meta) if not rules: return for rule in rules: if rule.rule_id == rule_id: return rule @staticmethod def get_shape_rule_dto(shape_rule: ShapeRule): return shape_rule.as_dict() def list_activated_tenants(self, parent: Parent, cloud: str, rate_limit: int = None) -> List[Tenant]: tenants = self.tenant_service.i_get_tenant_by_customer( customer_id=parent.customer_id, active=True, attributes_to_get=[Tenant.name, Tenant.parent_map], cloud=cloud, rate_limit=rate_limit ) linked_tenants = [] target_pid_key = self.parent_type_tenant_pid_mapping.get( parent.type, RIGHTSIZER_PARENT_TYPE) for tenant in tenants: _LOG.debug(f'Processing tenant \'{tenant.name}\'') parent_map = tenant.parent_map.as_dict() if target_pid_key not in parent_map: _LOG.debug(f'Tenant \'{tenant.name}\' does not have linked ' f'RIGHTSIZER parent, skipping.') continue linked_parent_id = parent_map.get(target_pid_key) if parent.parent_id == linked_parent_id: _LOG.debug(f'Tenant {tenant.name} is linked to ' f'parent \'{parent.parent_id}\'') linked_tenants.append(tenant) return linked_tenants