From 31ad6f311a9582ef2101c6c29fb2e8568e162e9d Mon Sep 17 00:00:00 2001 From: Luke Couzens Date: Tue, 13 Aug 2024 13:59:28 +0100 Subject: [PATCH] Add AWS Savings Plan Negation (#524) * Add AWS Savings Plan Negation options --- nise/__init__.py | 2 +- .../generators/aws/data_transfer_generator.py | 17 +++++++-- nise/generators/aws/ec2_generator.py | 38 +++++++++++++++++-- tests/test_aws_generator.py | 29 ++++++++++++++ 4 files changed, 78 insertions(+), 8 deletions(-) diff --git a/nise/__init__.py b/nise/__init__.py index 0eddf277..384e889b 100644 --- a/nise/__init__.py +++ b/nise/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.6.5" +__version__ = "4.6.6" VERSION = __version__.split(".") diff --git a/nise/generators/aws/data_transfer_generator.py b/nise/generators/aws/data_transfer_generator.py index 3753e454..713fde75 100644 --- a/nise/generators/aws/data_transfer_generator.py +++ b/nise/generators/aws/data_transfer_generator.py @@ -45,6 +45,7 @@ def __init__(self, start_date, end_date, currency, payer_account, usage_accounts self._rate = float(self.attributes.get("rate", 0)) or None self._resource_id = f"i-{self.attributes.get('resource_id', self.fake.ean8())}" self._saving = float(self.attributes.get("saving", 0)) or None + self._negation = self.attributes.get("negation") or False self._tags = self.attributes.get("tags", self._tags) @property @@ -81,7 +82,8 @@ def _update_data(self, row, start, end, **kwargs): resource_id = self._resource_id if self._resource_id else self.fake.ean8() rate = self._rate if self._rate else round(uniform(0.12, 0.19), 3) - saving = self._saving if self._saving else round(uniform(0.12, 0.19), 3) + saving = self._saving + negation = self._negation amount = self._amount if self._amount else uniform(0.000002, 0.09) cost = amount * rate trans_desc, operation, description, location1, location2, trans_type, aws_region = self._get_data_transfer( @@ -118,8 +120,17 @@ def _update_data(self, row, start, end, **kwargs): # Overwrite lineItem/LineItemType for items with applied Savings plan if saving is not None: row["lineItem/LineItemType"] = "SavingsPlanCoveredUsage" - self._add_tag_data(row) - self._add_category_data(row) + + if negation: + row["lineItem/LineItemType"] = "SavingsPlanNegation" + row["lineItem/UnblendedCost"] = -abs(cost) + row["lineItem/LineItemDescription"] = f"SavingsPlanNegation used by AccountId : {self.payer_account}" + row["lineItem/ResourceId"] = None + row["lineItem/BlendedCost"] = -abs(cost) + + if not negation: + self._add_tag_data(row) + self._add_category_data(row) return row def generate_data(self, report_type=None): diff --git a/nise/generators/aws/ec2_generator.py b/nise/generators/aws/ec2_generator.py index dab94f0b..e22423f0 100644 --- a/nise/generators/aws/ec2_generator.py +++ b/nise/generators/aws/ec2_generator.py @@ -36,6 +36,8 @@ class EC2Generator(AWSGenerator): "0.096", "0.096", "0.045", + 1, + False, "${cost} per On Demand Linux {inst_type} Instance Hour", ), ( @@ -48,6 +50,8 @@ class EC2Generator(AWSGenerator): "0.34", "0.34", "0.17", + 1, + False, "${cost} per On Demand Linux {inst_type} Instance Hour", ), ( @@ -60,6 +64,8 @@ class EC2Generator(AWSGenerator): "0.199", "0.199", "0.099", + 1, + False, "${cost} per On Demand Linux {inst_type} Instance Hour", ), ( @@ -72,6 +78,8 @@ class EC2Generator(AWSGenerator): "0.133", "0.133", "0.067", + 1, + False, "${cost} per On Demand Linux {inst_type} Instance Hour", ), ) @@ -115,12 +123,27 @@ def __init__(self, start_date, end_date, currency, payer_account, usage_accounts instance_type.get("cost"), instance_type.get("rate"), instance_type.get("saving"), + instance_type.get("amount", "1"), + instance_type.get("negation", False), "${cost} per On Demand Linux {inst_type} Instance Hour", ) def _update_data(self, row, start, end, **kwargs): """Update data with generator specific data.""" - inst_type, physical_cores, vcpu, memory, storage, family, cost, rate, saving, description = self._instance_type + ( + inst_type, + physical_cores, + vcpu, + memory, + storage, + family, + cost, + rate, + saving, + amount, + negation, + description, + ) = self._instance_type inst_description = description.format(cost=cost, inst_type=inst_type) product_name = "Amazon Elastic Compute Cloud" @@ -137,7 +160,7 @@ def _update_data(self, row, start, end, **kwargs): row["lineItem/Operation"] = "RunInstances" row["lineItem/AvailabilityZone"] = avail_zone row["lineItem/ResourceId"] = self._resource_id - row["lineItem/UsageAmount"] = "1" + row["lineItem/UsageAmount"] = amount row["lineItem/UnblendedRate"] = rate row["lineItem/UnblendedCost"] = cost row["lineItem/BlendedRate"] = rate @@ -179,9 +202,16 @@ def _update_data(self, row, start, end, **kwargs): # Overwrite lineItem/LineItemType for items with applied Savings plan if saving is not None: row["lineItem/LineItemType"] = "SavingsPlanCoveredUsage" + if negation: + row["lineItem/LineItemType"] = "SavingsPlanNegation" + row["lineItem/UnblendedCost"] = -abs(cost) + row["lineItem/LineItemDescription"] = f"SavingsPlanNegation used by AccountId : {self.payer_account}" + row["lineItem/ResourceId"] = None + row["lineItem/BlendedCost"] = -abs(cost) - self._add_tag_data(row) - self._add_category_data(row) + if not negation: + self._add_tag_data(row) + self._add_category_data(row) return row def generate_data(self, report_type=None): diff --git a/tests/test_aws_generator.py b/tests/test_aws_generator.py index 48d0b259..5c8dbf16 100644 --- a/tests/test_aws_generator.py +++ b/tests/test_aws_generator.py @@ -327,6 +327,21 @@ def test_update_data(self): self.assertEqual(row["product/productFamily"], "Data Transfer") self.assertEqual(row[self.cost_category_key], self.cost_category_value) + def test_update_data_transfer_negation(self): + """Test DataTransfer specific update data with negation costs.""" + self.attributes = { + "rate": 20, + "amount": 1, + "negation": True, + } + generator = DataTransferGenerator( + self.two_hours_ago, self.now, self.currency, self.payer_account, self.usage_accounts, self.attributes + ) + start_row = {} + row = generator._update_data(start_row, self.two_hours_ago, self.now) + + self.assertEqual(row["lineItem/LineItemType"], "SavingsPlanNegation") + class TestEBSGenerator(AWSGeneratorTestCase): """Tests for the EBS Generator type.""" @@ -377,6 +392,8 @@ def test_init_with_attributes(self): "cost": "1", "rate": "1", "saving": "1", + "amount": 1, + "negation": False, } self.attributes["instance_type"] = self.instance_type @@ -388,6 +405,18 @@ def test_init_with_attributes(self): self.assertEqual(generator._resource_id, "i-" + self.resource_id) self.assertEqual(generator._instance_type[:-1], tuple(self.instance_type.values())) + def test_update_data_ec2_negation(self): + """Test EC2 specific update data with negation costs.""" + self.instance_type = {"negation": True, "cost": 10} + self.attributes["instance_type"] = self.instance_type + generator = EC2Generator( + self.two_hours_ago, self.now, self.currency, self.payer_account, self.usage_accounts, self.attributes + ) + start_row = {} + row = generator._update_data(start_row, self.two_hours_ago, self.now) + + self.assertEqual(row["lineItem/LineItemType"], "SavingsPlanNegation") + def test_update_data(self): """Test EBS specific update data method.""" generator = EC2Generator(