def test_check_for_duplicate_metrics(self): """Check that duplicate rate types for a metric are rejected.""" rate = self.ocp_data["rates"][0] # Add another tiered rate entry for the same metric self.ocp_data["rates"].append(rate) # Make sure we don't allow two tiered rate entries in rates # for the same metric with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.ocp_data) with self.assertRaises(serializers.ValidationError): serializer._check_for_duplicate_metrics(self.ocp_data["rates"])
def test_error_neg_tier_usage_start(self): """Test error when trying to create a negative tiered usage_start.""" self.ocp_data["rates"][0]["tiered_rates"][0]["usage"] = { "usage_start": float(round(Decimal(random.random()), 6) * -1), "usage_end": 20.0, } with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.ocp_data) with self.assertRaises(serializers.ValidationError): if serializer.is_valid(raise_exception=True): serializer.save()
def test_error_on_duplicate_tag_values(self): """Test that specifying both tiered and tag rates fails.""" tag_value = {"tag_value": "key_one", "value": 0.2} self.basic_model["rates"][0]["tag_rates"] = format_tag_rate( tag_values=[tag_value, tag_value]) with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.basic_model) with self.assertRaises(serializers.ValidationError): self.assertFalse(serializer.is_valid(raise_exception=True)) result_err_msg = str(serializer.errors["rates"]) expected_sub_string = "Duplicate tag_value" self.assertIn(expected_sub_string, result_err_msg)
def test_error_on_multiple_tag_values_marked_as_default(self): """Test that multiple default set to true fails.""" tag_values_kwargs = [{"default": True}, {"tag_value": "value_two", "value": 0.3, "default": True}] self.basic_model["rates"][0]["tag_rates"] = format_tag_rate(tag_values=tag_values_kwargs) with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.basic_model) with self.assertRaises(serializers.ValidationError): self.assertFalse(serializer.is_valid(raise_exception=True)) print(serializer.errors) result_err_msg = serializer.errors["rates"][0]["tag_values"][0] expected_err_msg = "Only one tag_value per tag_key can be marked as a default." self.assertEqual(result_err_msg, expected_err_msg)
def test_error_on_usage_bad_start_bound(self): """Test error on a usage_start that does not cover lower bound.""" self.ocp_data['rates'][0]['tiered_rates'][0]['usage'] = { 'usage_start': 5, 'usage_end': None } with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.ocp_data) with self.assertRaises(serializers.ValidationError): if serializer.is_valid(raise_exception=True): serializer.save()
def test_create_storage_tiered_rate(self): """Test creating a storage tiered rate.""" storage_rates = ( metric_constants.OCP_METRIC_STORAGE_GB_REQUEST_MONTH, metric_constants.OCP_METRIC_STORAGE_GB_USAGE_MONTH, ) for storage_rate in storage_rates: ocp_data = { "name": "Test Cost Model", "description": "Test", "source_type": Provider.PROVIDER_OCP, "providers": [{ "uuid": self.provider.uuid, "name": self.provider.name }], "rates": [{ "metric": { "name": storage_rate }, "tiered_rates": [ { "unit": "USD", "value": 0.22, "usage": { "usage_start": None, "usage_end": 10.0 } }, { "unit": "USD", "value": 0.26, "usage": { "usage_start": 10.0, "usage_end": None } }, ], }], "currency": "USD", } with tenant_context(self.tenant): instance = None serializer = CostModelSerializer(data=ocp_data, context=self.request_context) if serializer.is_valid(raise_exception=True): instance = serializer.save() self.assertIsNotNone(instance) self.assertIsNotNone(instance.uuid)
def test_not_OCP_source_type_with_markup(self): """Test that a source type is valid if it has markup.""" self.ocp_data["source_type"] = Provider.PROVIDER_AWS self.ocp_data["rates"] = [] with tenant_context(self.tenant): instance = None serializer = CostModelSerializer(data=self.ocp_data) if serializer.is_valid(raise_exception=True): instance = serializer.save() self.assertIsNotNone(instance) self.assertIsNotNone(instance.markup)
def test_error_on_usage_bad_upper_bound(self): """Test error on a usage_end that does not cover lower bound.""" self.ocp_data["rates"][0]["tiered_rates"][0]["usage"] = { "usage_start": None, "usage_end": 5 } with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.ocp_data) with self.assertRaises(serializers.ValidationError): if serializer.is_valid(raise_exception=True): serializer.save()
def test_error_tier_usage_end_less_than(self): """Test error when trying to create a tiered usage_end less than usage_start.""" self.ocp_data["rates"][0]["tiered_rates"][0]["usage"] = { "usage_start": 10.0, "usage_end": 3.0 } with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.ocp_data) with self.assertRaises(serializers.ValidationError): if serializer.is_valid(raise_exception=True): serializer.save()
def test_error_on_empty_list_for_tag_values(self): """Test that tag_values can not be an empty list.""" self.basic_model["rates"][0]["tag_rates"] = [ format_tag_rate(tag_values=[]) ] with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.basic_model) with self.assertRaises(serializers.ValidationError): self.assertFalse(serializer.is_valid(raise_exception=True)) result_err_msg = serializer.errors["rates"][0]["tag_values"][0] expected_err_msg = "A tag_values can not be an empty list." self.assertEqual(result_err_msg, expected_err_msg)
def test_tiered_rate_with_gaps(self): """Test creating a tiered rate with a gap between the tiers.""" self.ocp_data["rates"][0]["tiered_rates"] = [ {"unit": "USD", "value": 0.22, "usage": {"usage_start": None, "usage_end": 7.0}}, {"unit": "USD", "value": 0.26, "usage_start": 10.0, "usage_end": None}, ] with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.ocp_data) with self.assertRaises(serializers.ValidationError): if serializer.is_valid(raise_exception=True): serializer.save()
def test_rates_error_on_specifying_tiered_and_tag_rates(self): """Test that specifying both tiered and tag rates fails.""" tag_values_kwargs = [{"value": 0.2}] tiered_rate = [{"value": 1.3, "unit": "USD"}] self.basic_model["rates"][0]["tag_rates"] = format_tag_rate(tag_values=tag_values_kwargs) self.basic_model["rates"][0]["tiered_rates"] = tiered_rate with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.basic_model) with self.assertRaises(serializers.ValidationError): self.assertFalse(serializer.is_valid(raise_exception=True)) result_err_msg = serializer.errors["rates"][0]["non_field_errors"][0] expected_err_msg = "Set either 'tiered_rates' or 'tag_rates' but not both" self.assertEqual(result_err_msg, expected_err_msg)
def test_tag_rates_error_on_usage_start_greater_than_usage_end(self): """Test that usage_start greater than a usage end fails""" tag_values_kwargs = [{"usage_start": 10, "usage_end": 2}] self.basic_model["rates"][0]["tag_rates"] = format_tag_rate( tag_values=tag_values_kwargs) with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.basic_model) with self.assertRaises(serializers.ValidationError): self.assertFalse(serializer.is_valid(raise_exception=True)) result_err_msg = serializer.errors["rates"][0]["tag_values"]["usage"][ 0] expected_err_msg = "A tag rate usage_start must be less than usage_end." self.assertEqual(result_err_msg, expected_err_msg)
def test_tag_rates_error_on_negitive_usage_end(self): """Test that a negivite usage_end for tag_rates fails.""" tag_values_kwargs = [{"usage_end": -5}] self.basic_model["rates"][0]["tag_rates"] = format_tag_rate( tag_values=tag_values_kwargs) with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.basic_model) with self.assertRaises(serializers.ValidationError): self.assertFalse(serializer.is_valid(raise_exception=True)) result_err_msg = serializer.errors["rates"][0]["tag_values"]["usage"][ 0] expected_err_msg = "A tag rate usage_end must be positive." self.assertEqual(result_err_msg, expected_err_msg)
def test_rate_cost_type_valid(self): """Test that a valid cost type is accepted.""" self.ocp_data["rates"][0]["tiered_rates"] = [{ "unit": "USD", "value": 0.22, "usage": { "usage_start": None, "usage_end": None }, "cost_type": "Infrastructure", }] with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.ocp_data, context=self.request_context) if serializer.is_valid(raise_exception=True): serializer.save() self.ocp_data["rates"][0]["tiered_rates"] = [{ "unit": "USD", "value": 0.22, "usage": { "usage_start": None, "usage_end": None }, "cost_type": "Supplementary", }] with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.ocp_data, context=self.request_context) if serializer.is_valid(raise_exception=True): serializer.save()
def test_distribution_choices_added_successfully(self): """Test that a source type is valid if it has markup.""" valid_choices = ["cpu", "memory"] for good_input in valid_choices: self.ocp_data["distribution"] = good_input self.assertEqual(self.ocp_data["distribution"], good_input) with tenant_context(self.tenant): instance = None serializer = CostModelSerializer(data=self.ocp_data) if serializer.is_valid(raise_exception=True): instance = serializer.save() self.assertIsNotNone(instance) self.assertIsNotNone(instance.uuid)
def test_tag_rates_error_on_negitive_tag_value(self): """Test that a negivite value in the tag value fails.""" tag_values_kwargs = [{"value": -0.2}] self.basic_model["rates"][0]["tag_rates"] = format_tag_rate( tag_values=tag_values_kwargs) with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.basic_model, context=self.request_context) with self.assertRaises(serializers.ValidationError): self.assertFalse(serializer.is_valid(raise_exception=True)) result_err_msg = serializer.errors["rates"][0]["tag_values"]["value"][ 0] expected_err_msg = "A tag rate value must be nonnegative." self.assertEqual(result_err_msg, expected_err_msg)
def test_create_storage_tiered_rate(self): """Test creating a storage tiered rate.""" storage_rates = ( CostModelMetricsMap.OCP_METRIC_STORAGE_GB_REQUEST_MONTH, CostModelMetricsMap.OCP_METRIC_STORAGE_GB_USAGE_MONTH, ) for storage_rate in storage_rates: ocp_data = { 'name': 'Test Cost Model', 'description': 'Test', 'source_type': Provider.PROVIDER_OCP, 'providers': [{ 'uuid': self.provider.uuid, 'name': self.provider.name }], 'rates': [{ 'metric': { 'name': storage_rate }, 'tiered_rates': [ { 'unit': 'USD', 'value': 0.22, 'usage': { 'usage_start': None, 'usage_end': 10.0 }, }, { 'unit': 'USD', 'value': 0.26, 'usage': { 'usage_start': 10.0, 'usage_end': None }, }, ], }], } with tenant_context(self.tenant): instance = None serializer = CostModelSerializer(data=ocp_data) if serializer.is_valid(raise_exception=True): instance = serializer.save() self.assertIsNotNone(instance) self.assertIsNotNone(instance.uuid)
def test_create_cpu_core_per_hour_tiered_rate(self): """Test creating a cpu_core_per_hour rate.""" self.ocp_data["rates"][0]["tiered_rates"] = [ {"unit": "USD", "value": 0.22, "usage": {"usage_start": None, "usage_end": 10.0}}, {"unit": "USD", "value": 0.26, "usage": {"usage_start": 10.0, "usage_end": None}}, ] with tenant_context(self.tenant): instance = None serializer = CostModelSerializer(data=self.ocp_data) if serializer.is_valid(raise_exception=True): instance = serializer.save() self.assertIsNotNone(instance) self.assertIsNotNone(instance.uuid)
def test_tiered_rate_with_gaps(self): """Test creating a tiered rate with a gap between the tiers.""" self.ocp_data['rates'][0]['tiered_rates'] = [ { 'unit': 'USD', 'value': 0.22, 'usage': {'usage_start': None, 'usage_end': 7.0}, }, {'unit': 'USD', 'value': 0.26, 'usage_start': 10.0, 'usage_end': None}, ] with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.ocp_data) with self.assertRaises(serializers.ValidationError): if serializer.is_valid(raise_exception=True): serializer.save()
def test_validate_rates_allows_duplicate_metric(self): """Check that duplicate rate types for a metric are rejected.""" rate = self.ocp_data["rates"][0] expected_metric_name = rate.get("metric", {}).get("name") expected_metric_count = 2 self.assertIsNotNone(expected_metric_name) # Add another tiered rate entry for the same metric self.ocp_data["rates"].append(rate) result_metric_count = 0 with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.ocp_data) valid_rates = serializer.validate_rates(self.ocp_data["rates"]) for valid_rate in valid_rates: if valid_rate.get("metric", {}).get("name") == expected_metric_name: result_metric_count += 1 self.assertEqual(expected_metric_count, result_metric_count)
def initialize_request(self, context=None): """Initialize model data.""" if context: request_context = context.get("request_context") else: request_context = self.request_context provider_data = { "name": "test_provider", "type": Provider.PROVIDER_OCP.lower(), "authentication": {"credentials": {"cluster_id": self.fake.word()}}, "billing_source": {}, } serializer = ProviderSerializer(data=provider_data, context=request_context) if serializer.is_valid(raise_exception=True): self.provider = serializer.save() self.ocp_metric = metric_constants.OCP_METRIC_CPU_CORE_USAGE_HOUR self.ocp_source_type = Provider.PROVIDER_OCP tiered_rates = [ { "value": round(Decimal(random.random()), 6), "unit": "USD", "usage": {"usage_start": None, "usage_end": None}, } ] self.cost_model_name = "Test Cost Model for test_view.py" # We have one preloaded cost model with bakery so the idx for # the cost model we are creating in the results return is 1 self.results_idx = 1 self.fake_data = { "name": self.cost_model_name, "description": "Test", "source_type": self.ocp_source_type, "source_uuids": [self.provider.uuid], "rates": [ {"metric": {"name": self.ocp_metric}, "cost_type": "Infrastructure", "tiered_rates": tiered_rates} ], "currency": "USD", } with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.fake_data, context=request_context) if serializer.is_valid(raise_exception=True): with patch("cost_models.cost_model_manager.chain"): instance = serializer.save() self.fake_data_cost_model_uuid = instance.uuid
def test_tag_key_can_be_multiple_cost_types(self): """Test that tag keys can be multiple cost types.""" value_kwargs = [{ "value": 0.1, "default": True, "usage_start": 1, "usage_end": 10 }] tag_rates_list = [] cost_types = ["Infrastructure", "Supplementary"] for cost_type in cost_types: rate = { "metric": { "name": metric_constants.OCP_METRIC_CPU_CORE_USAGE_HOUR }, "cost_type": cost_type } rate["tag_rates"] = [format_tag_rate(tag_values=value_kwargs)] tag_rates_list.append(rate) self.basic_model["rates"] = tag_rates_list with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.basic_model) self.assertTrue(serializer.is_valid(raise_exception=True)) serializer.save() data = serializer.data rates = data.get("rates", []) self.assertEqual(len(rates), 2) for rate in rates: tag_rates = rate.get("tag_rates") self.assertIsNotNone(tag_rates) # Check cost types result_cost_type = rate["cost_type"] self.assertIn(result_cost_type, cost_types) cost_types.remove(result_cost_type) for tag_rate in tag_rates: # Check that to_representation is working tag_value = tag_rate["tag_values"][0] decimals = [ tag_value["value"], tag_value["usage"]["usage_start"], tag_value["usage"]["usage_end"] ] for expected_decimal in decimals: self.assertIsInstance(expected_decimal, Decimal)
def test_tag_rates_error_on_duplicate_metric_per_cost_type(self): """Test duplicate key on metric per cost_type.""" tag_values_kwargs = [{"value": 0.2}] cost_model = { "name": "Test Cost Model", "description": "Test", "source_type": Provider.PROVIDER_OCP, "providers": [{ "uuid": self.provider.uuid, "name": self.provider.name }], "markup": { "value": 10, "unit": "percent" }, "rates": [ { "metric": { "name": metric_constants.OCP_METRIC_CPU_CORE_USAGE_HOUR } }, { "metric": { "name": metric_constants.OCP_METRIC_CPU_CORE_USAGE_HOUR } }, ], } cost_model["rates"][0]["tag_rates"] = format_tag_rate( tag_values=tag_values_kwargs) cost_model["rates"][1]["tag_rates"] = format_tag_rate( tag_values=tag_values_kwargs) with tenant_context(self.tenant): serializer = CostModelSerializer(data=cost_model, context=self.request_context) with self.assertRaises(serializers.ValidationError): self.assertFalse(serializer.is_valid(raise_exception=True)) result_err_msg = str(serializer.errors["rates"]) expected_sub_string = "Duplicate tag_key" self.assertIn(expected_sub_string, result_err_msg)
def test_tag_rates_on_duplicate_metric_per_cost_type(self): """Test that specifying both tiered and tag rates fails.""" tag_values_kwargs = [{"value": 0.2}] cost_model = { "name": "Test Cost Model", "description": "Test", "source_type": Provider.PROVIDER_OCP, "providers": [{ "uuid": self.provider.uuid, "name": self.provider.name }], "markup": { "value": 10, "unit": "percent" }, "rates": [ { "metric": { "name": metric_constants.OCP_METRIC_CPU_CORE_USAGE_HOUR } }, { "metric": { "name": metric_constants.OCP_METRIC_CPU_CORE_USAGE_HOUR } }, ], "currency": "USD", } cost_model["rates"][0]["tag_rates"] = format_tag_rate( tag_key="k1", tag_values=tag_values_kwargs) cost_model["rates"][1]["tag_rates"] = format_tag_rate( tag_key="k2", tag_values=tag_values_kwargs) with tenant_context(self.tenant): serializer = CostModelSerializer(data=cost_model, context=self.request_context) self.assertTrue(serializer.is_valid(raise_exception=True)) serializer.save() serializer.data
def test_multiple_tag_values(self): """Test that tag keys can be multiple cost types.""" value_kwargs = [ {"tag_value": "value_one", "value": 0.1, "default": True}, {"tag_value": "value_two", "value": 0.2}, ] self.basic_model["rates"][0]["tag_rates"] = format_tag_rate(tag_values=value_kwargs) with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.basic_model) self.assertTrue(serializer.is_valid(raise_exception=True)) serializer.save() data = serializer.data rates = data["rates"] self.assertEqual(len(rates), 1) for rate in rates: tag_rate = rate.get("tag_rates") self.assertIsNotNone(tag_rate) tag_values = tag_rate["tag_values"] self.assertEqual(len(tag_values), 2)
def initialize_request(self, context=None): """Initialize model data.""" if context: request_context = context.get("request_context") else: request_context = self.request_context provider_data = { "name": "test_provider", "type": Provider.PROVIDER_OCP.lower(), "authentication": {"provider_resource_name": self.fake.word()}, } serializer = ProviderSerializer(data=provider_data, context=request_context) if serializer.is_valid(raise_exception=True): self.provider = serializer.save() self.ocp_metric = metric_constants.OCP_METRIC_CPU_CORE_USAGE_HOUR self.ocp_source_type = Provider.PROVIDER_OCP tiered_rates = [ { "value": round(Decimal(random.random()), 6), "unit": "USD", "usage": {"usage_start": None, "usage_end": None}, } ] self.fake_data = { "name": "Test Cost Model", "description": "Test", "source_type": self.ocp_source_type, "source_uuids": [self.provider.uuid], "rates": [ {"metric": {"name": self.ocp_metric}, "cost_type": "Infrastructure", "tiered_rates": tiered_rates} ], } with tenant_context(self.tenant): serializer = CostModelSerializer(data=self.fake_data, context=request_context) if serializer.is_valid(raise_exception=True): with patch("masu.processor.tasks.update_cost_model_costs.delay"): instance = serializer.save() self.fake_data_cost_model_uuid = instance.uuid
def test_create_storage_no_tiers_rate(self): """Test creating a non tiered storage rate.""" storage_rates = ( CostModelMetricsMap.OCP_METRIC_STORAGE_GB_REQUEST_MONTH, CostModelMetricsMap.OCP_METRIC_STORAGE_GB_USAGE_MONTH, ) for storage_rate in storage_rates: ocp_data = { "name": "Test Cost Model", "description": "Test", "source_type": Provider.PROVIDER_OCP, "providers": [{"uuid": self.provider.uuid, "name": self.provider.name}], "rates": [{"metric": {"name": storage_rate}, "tiered_rates": [{"unit": "USD", "value": 0.22}]}], } with tenant_context(self.tenant): instance = None serializer = CostModelSerializer(data=ocp_data) if serializer.is_valid(raise_exception=True): instance = serializer.save() self.assertIsNotNone(instance) self.assertIsNotNone(instance.uuid)
def test_create_cpu_core_per_hour_tiered_rate(self): """Test creating a cpu_core_per_hour rate.""" self.ocp_data['rates'][0]['tiered_rates'] = [ { 'unit': 'USD', 'value': 0.22, 'usage': {'usage_start': None, 'usage_end': 10.0}, }, { 'unit': 'USD', 'value': 0.26, 'usage': {'usage_start': 10.0, 'usage_end': None}, }, ] with tenant_context(self.tenant): instance = None serializer = CostModelSerializer(data=self.ocp_data) if serializer.is_valid(raise_exception=True): instance = serializer.save() self.assertIsNotNone(instance) self.assertIsNotNone(instance.uuid)
def test_defaulting_currency(self): """Test if currency is none it defaults using get_currency method.""" ocp_metric = metric_constants.OCP_METRIC_CPU_CORE_USAGE_HOUR ocp_source_type = Provider.PROVIDER_OCP tiered_rates = [{"unit": "USD", "value": 0.22}] ocp_data = { "name": "Test Cost Model", "description": "Test", "source_type": ocp_source_type, "providers": [{ "uuid": self.provider.uuid, "name": self.provider.name }], "markup": { "value": 10, "unit": "percent" }, "rates": [{ "metric": { "name": ocp_metric }, "tiered_rates": tiered_rates }], } with tenant_context(self.tenant): instance = None serializer = CostModelSerializer(data=ocp_data, context=self.request_context) if serializer.is_valid(raise_exception=True): instance = serializer.save() self.assertEqual(instance.currency, get_currency(self.request_context["request"]))