diff --git a/cloud_governance/common/utils/configs.py b/cloud_governance/common/utils/configs.py index ed408b2b..21e63c38 100644 --- a/cloud_governance/common/utils/configs.py +++ b/cloud_governance/common/utils/configs.py @@ -21,3 +21,35 @@ EC2_NAMESPACE = 'AWS/EC2' CLOUDWATCH_METRICS_AVAILABLE_DAYS = 14 AWS_DEFAULT_GLOBAL_REGION = 'us-east-1' + +# X86 to Graviton +GRAVITON_MAPPINGS = { + 'c6a': 'c6g', + 'c6i': 'c6g', + 'c6in': 'c6gn', + 'c7': 'c7g', + 'c7i': 'c7g', + 'c7a': 'c7g', + 'c7i-flex': 'c7g', + 'g5': 'g5g', + 'hpc7a': 'hpc7g', + 'i4i': 'i4g', + 'm5': 'm6g', + 'm5a': 'm6g', + 'm5n': 'm6g', + 'm5zn': 'm6g', + 'm6i': 'm6g', + 'm6in': 'm6g', + 'm6a': 'm6g', + 'm7i': 'm7g', + 'm7a': 'm7g', + 'm7i-flex': 'm7g', + 'r7i': 'r8g', + 'r7a': 'r7g', + 'r5': 'r7g', + 'r6i': 'r6g', + 't3': 't4g', + 't2': 'a1' +} + +DEFAULT_GRAVITON_INSTANCE = 'm6g' diff --git a/cloud_governance/policy/aws/monitor/cluster_run.py b/cloud_governance/policy/aws/monitor/cluster_run.py index 9f4322a8..175c2b84 100644 --- a/cloud_governance/policy/aws/monitor/cluster_run.py +++ b/cloud_governance/policy/aws/monitor/cluster_run.py @@ -1,7 +1,10 @@ import datetime import re +from math import ceil + from cloud_governance.cloud_resource_orchestration.utils.common_operations import string_equal_ignore_case +from cloud_governance.common.utils.configs import GRAVITON_MAPPINGS, DEFAULT_ROUND_DIGITS, DEFAULT_GRAVITON_INSTANCE from cloud_governance.policy.helpers.aws.aws_policy_operations import AWSPolicyOperations @@ -40,7 +43,8 @@ def run_policy_operations(self): name_tag = self.get_tag_name_from_tags(tags=tags, tag_name='Name') launch_time = instance.get('LaunchTime') running_instances = stopped_instances = 0 - running_days = self.calculate_days(instance.get('LaunchTime')) + running_days = self.calculate_days(launch_time) + running_hours = ceil(self.calculate_hours(launch_time)) stopped_date_time = '' if string_equal_ignore_case(instance_state, 'stopped'): stopped_instances = 1 @@ -54,6 +58,12 @@ def run_policy_operations(self): else: running_instances = 1 creation_date = '' + rosa_cluster = True if self.get_tag_name_from_tags(tags=tags, tag_name='red-hat-managed') else False + instance_type = instance.get('InstanceType') + using_graviton = False + for graviton_instance_family in GRAVITON_MAPPINGS.values(): + if graviton_instance_family in instance_type: + using_graviton = True if 'master' in name_tag.lower(): creation_date = self.__get_creation_date(instance.get('BlockDeviceMappings', [])) instance_data = f"{instance.get('InstanceId')}, {self.get_tag_name_from_tags(tags=tags, tag_name='Name')}, {instance.get('InstanceType')}, {instance_state}, {running_days}, {launch_time}" @@ -63,6 +73,7 @@ def run_policy_operations(self): cluster_data[cluster_tag]['ClusterState'] = instance_state cluster_data[cluster_tag]['StoppedDate'] = stopped_date_time cluster_data[cluster_tag]['Instances'].append(instance_data) + cluster_data[cluster_tag]['InstanceTypes'].append(instance.get('InstanceType')) cluster_data[cluster_tag]['InstanceCount'] = len(cluster_data[cluster_tag]['Instances']) cluster_data[cluster_tag]['Stopped'] = int(cluster_data[cluster_tag]['Stopped']) + stopped_instances cluster_data[cluster_tag]['Running'] = int(cluster_data[cluster_tag]['Running']) + running_instances @@ -72,19 +83,50 @@ def run_policy_operations(self): 'ClusterName2': cluster_tag.split('/')[-1].lower(), 'ResourceId': cluster_tag, 'ClusterTag': cluster_tag, + 'InstanceTypes': [instance.get('InstanceType')], 'User': self.get_tag_name_from_tags(tags=tags, tag_name='User'), 'RunningDays': running_days, + 'RunningHours': running_hours, 'RegionName': self._region, 'PublicCloud': self._cloud_name, 'Instances': [instance_data], + 'LaunchTime': launch_time.date(), 'InstanceCount': 1, 'Stopped': stopped_instances, 'Running': running_instances, + 'Graviton': using_graviton, + 'RosaCluster': rosa_cluster, 'index-id': f'{datetime.datetime.now(datetime.timezone.utc).date()}-{self._cloud_name.lower()}-{self.account.lower()}-{self._region.lower()}-{cluster_tag}', } if creation_date: cluster_data[cluster_tag]['creation_date'] = creation_date cluster_data[cluster_tag]['ClusterState'] = instance_state cluster_data[cluster_tag]['StoppedDate'] = stopped_date_time + for cluster in cluster_data.values(): + instance_types = cluster['InstanceTypes'] + total_cost = 0 + graviton_instance_cost = 0 + cluster['GravitonInstanceTypes'] = [] + running_hours = 1 if cluster['RunningHours'] == 0 else cluster['RunningHours'] + for instance_type in set(instance_types): + instance_types_count = instance_types.count(instance_type) + unit_price = self._resource_pricing.get_ec2_price(region_name=self._region, + instance_type=instance_type) + total_cost += (unit_price * running_hours * instance_types_count) + if not cluster['Graviton']: + instance_family, instance_size = instance_type.split('.') + graviton_instance = f'{DEFAULT_GRAVITON_INSTANCE}.{instance_size}' + if instance_family in GRAVITON_MAPPINGS: + graviton_instance = f'{GRAVITON_MAPPINGS[instance_family]}.{instance_size}' + graviton_unit_price = self._resource_pricing.get_ec2_price(region_name=self._region, + instance_type=graviton_instance) + graviton_instance_cost += (graviton_unit_price * running_hours * instance_types_count) + cluster['GravitonInstanceTypes'].append(f"{graviton_instance}: {instance_types_count}") + + cluster['TotalGravitonInstanceCost'] = round(graviton_instance_cost, DEFAULT_ROUND_DIGITS) + cluster['TotalCost'] = round(total_cost, DEFAULT_ROUND_DIGITS) + cluster['GravitonSavings'] = round(total_cost - graviton_instance_cost, + DEFAULT_ROUND_DIGITS) if graviton_instance_cost != 0 else 0 + cluster['InstanceTypes'] = [f"{x}: {instance_types.count(x)}" for x in set(instance_types)] return list(cluster_data.values()) diff --git a/cloud_governance/policy/helpers/abstract_policy_operations.py b/cloud_governance/policy/helpers/abstract_policy_operations.py index e64a10b0..96ec7f9f 100644 --- a/cloud_governance/policy/helpers/abstract_policy_operations.py +++ b/cloud_governance/policy/helpers/abstract_policy_operations.py @@ -11,7 +11,6 @@ class AbstractPolicyOperations(ABC): - DAYS_TO_NOTIFY_ADMINS = 2 DAYS_TO_TRIGGER_RESOURCE_MAIL = 4 DAILY_HOURS = 24 @@ -46,6 +45,24 @@ def calculate_days(self, create_date: Union[datetime, str], start_date: Union[da days = start_date - create_date.date() return days.days + def calculate_hours(self, create_date: Union[datetime, str], + start_date: Union[datetime, str] = datetime.now(timezone.utc)): + """ + This method returns the hours + :param start_date: + :type start_date: + :param create_date: + :type create_date: + :return: + :rtype: + """ + if isinstance(create_date, str): + create_date = datetime.strptime(create_date, "%Y-%M-%d %H:%M:%S").replace(tzinfo=timezone.utc) + if isinstance(start_date, str): + start_date = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc) + days = start_date - create_date + return days.seconds / 3600 + def get_clean_up_days_count(self, tags: Union[list, dict]): """ This method returns the cleanup days count