# # Copyright 2019 Red Hat, Inc. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. # """Populate test data for OCP on Azure reports.""" import pkgutil import random from uuid import uuid4 from dateutil.relativedelta import relativedelta from django.db import connection from faker import Faker from jinjasql import JinjaSql from model_bakery import baker from tenant_schemas.utils import tenant_context from api.models import Provider from api.models import ProviderAuthentication from api.models import ProviderBillingSource from api.models import Tenant from api.provider.models import ProviderInfrastructureMap from api.report.test.azure.helpers import FakeAzureConfig from api.report.test.ocp.helpers import OCPReportDataGenerator from api.utils import DateHelper from reporting.models import AzureCostEntryBill from reporting.models import OCPAzureCostLineItemDailySummary from reporting.models import OCPAzureCostLineItemProjectDailySummary class OCPAzureReportDataGenerator: """Populate the database with OCP on Azure report data.""" def __init__(self, tenant, provider, current_month_only=False, config=None): """Set up the class.""" # prevent future whammy: assert isinstance(tenant, Tenant), "not a Tenant type" assert isinstance(provider, Provider), "not a Provider type" assert isinstance(current_month_only, bool), "not a bool type" if config: assert isinstance(config, FakeAzureConfig), "not a FakeAzureConfig type" self.tenant = tenant self.provider = provider self.current_month_only = current_month_only self.config = config if config else FakeAzureConfig() self.fake = Faker() self.dh = DateHelper() self.provider_uuid = provider.uuid self.ocp_generator = None # generate a list of dicts with unique keys. self.period_ranges, self.report_ranges = self.report_period_and_range() def report_period_and_range(self): """Return the report period and range.""" period = [] ranges = [] if self.current_month_only: report_days = 10 diff_from_first = self.dh.today - self.dh.this_month_start if diff_from_first.days < 10: report_days = 1 + diff_from_first.days period = [(self.dh.this_month_start, self.dh.this_month_end)] ranges = [list(self.dh.this_month_start + relativedelta(days=i) for i in range(report_days))] else: period = [(self.dh.this_month_start, self.dh.this_month_end)] ranges = [list(self.dh.today - relativedelta(days=i) for i in range(report_days))] else: period = [ (self.dh.last_month_start, self.dh.last_month_end), (self.dh.this_month_start, self.dh.this_month_end), ] one_month_ago = self.dh.today - relativedelta(months=1) diff_from_first = self.dh.today - self.dh.this_month_start if diff_from_first.days < 10: report_days = 1 + diff_from_first.days ranges = [ list(self.dh.last_month_start + relativedelta(days=i) for i in range(report_days)), list(self.dh.this_month_start + relativedelta(days=i) for i in range(report_days)), ] else: ranges = [ list(one_month_ago - relativedelta(days=i) for i in range(10)), list(self.dh.today - relativedelta(days=i) for i in range(10)), ] return (period, ranges) def remove_data_from_tenant(self): """Remove the added data.""" if self.ocp_generator: self.ocp_generator.remove_data_from_tenant() with tenant_context(self.tenant): for table in (OCPAzureCostLineItemDailySummary, OCPAzureCostLineItemProjectDailySummary): table.objects.all().delete() def add_ocp_data_to_tenant(self): """Populate tenant with OCP data.""" assert self.cluster_id, "method must be called after add_data_to_tenant" self.ocp_generator = OCPReportDataGenerator(self.tenant, self.provider, self.current_month_only) ocp_config = { "cluster_id": self.cluster_id, "cluster_alias": self.cluster_alias, "namespaces": self.namespaces, "nodes": self.nodes, } self.ocp_generator.add_data_to_tenant(**ocp_config) def add_data_to_tenant(self, fixed_fields=None, service_name=None): """Populate tenant with data.""" words = list({self.fake.word() for _ in range(10)}) self.cluster_id = random.choice(words) self.cluster_alias = random.choice(words) self.namespaces = random.sample(words, k=2) self.nodes = random.sample(words, k=2) self.ocp_azure_summary_line_items = [ { "namespace": random.choice(self.namespaces), "pod": random.choice(words), "node": node, "resource_id": self.fake.ean8(), } for node in self.nodes ] with tenant_context(self.tenant): for i, period in enumerate(self.period_ranges): for report_date in self.report_ranges[i]: for row in self.ocp_azure_summary_line_items: self._randomize_line_item(retained_fields=fixed_fields) if service_name: self.config.service_name = service_name li = self._populate_ocp_azure_cost_line_item_daily_summary(row, report_date) self._populate_ocp_azure_cost_line_item_project_daily_summary(li, row, report_date) self._populate_azure_tag_summary() def create_ocp_provider(self, cluster_id, cluster_alias, infrastructure_type="Unknown"): """Create OCP test provider.""" auth = baker.make(ProviderAuthentication, provider_resource_name=cluster_id) bill = baker.make(ProviderBillingSource, bucket="") provider_uuid = uuid4() provider_data = { "uuid": provider_uuid, "name": cluster_alias, "authentication": auth, "billing_source": bill, "customer": None, "created_by": None, "type": Provider.PROVIDER_OCP, "setup_complete": False, "infrastructure": None, } provider = Provider(**provider_data) infrastructure = ProviderInfrastructureMap( infrastructure_provider=provider, infrastructure_type=infrastructure_type ) infrastructure.save() provider.infrastructure = infrastructure provider.save() self.cluster_alias = cluster_alias self.provider_uuid = provider_uuid return provider def _randomize_line_item(self, retained_fields=None): """Update our FakeAzureConfig to generate a new line item.""" DEFAULT_FIELDS = ["subscription_guid", "resource_location", "tags"] if not retained_fields: retained_fields = DEFAULT_FIELDS config_dict = {} for field in retained_fields: if field in self.config: config_dict[field] = getattr(self.config, field) self.config = FakeAzureConfig(**config_dict) def _populate_ocp_azure_cost_line_item_daily_summary(self, row, report_date): """Create OCP hourly usage line items.""" if report_date: usage_dt = report_date else: usage_dt = self.fake.date_time_between_dates(self.dh.this_month_start, self.dh.today) usage_qty = random.random() * random.randrange(0, 100) pretax = usage_qty * self.config.meter_rate data = { # OCP Fields: "cluster_id": self.cluster_id, "cluster_alias": self.cluster_alias, "namespace": [row.get("namespace")], "pod": [row.get("pod")], "node": row.get("node"), "resource_id": row.get("resource_id"), "usage_start": usage_dt, "usage_end": usage_dt, # Azure Fields: "cost_entry_bill": baker.make(AzureCostEntryBill), "subscription_guid": self.config.subscription_guid, "instance_type": self.config.instance_type, "service_name": self.config.service_name, "resource_location": self.config.resource_location, "tags": self.select_tags(), "usage_quantity": usage_qty, "pretax_cost": pretax, "markup_cost": pretax * 0.1, "offer_id": random.choice([None, self.fake.pyint()]), "currency": "USD", "unit_of_measure": "some units", "shared_projects": 1, "project_costs": pretax, } line_item = OCPAzureCostLineItemDailySummary(**data) line_item.save() return line_item def _populate_ocp_azure_cost_line_item_project_daily_summary(self, li, row, report_date): """Create OCP hourly usage line items.""" data = { # OCP Fields: "cluster_id": li.cluster_id, "cluster_alias": li.cluster_alias, "namespace": [row.get("namespace")], "pod": [row.get("pod")], "node": row.get("node"), "resource_id": row.get("resource_id"), "usage_start": li.usage_start, "usage_end": li.usage_end, # Azure Fields: "cost_entry_bill": li.cost_entry_bill, "subscription_guid": li.subscription_guid, "instance_type": li.instance_type, "service_name": li.service_name, "resource_location": li.resource_location, "usage_quantity": li.usage_quantity, "unit_of_measure": "some units", "offer_id": li.offer_id, "currency": "USD", "pretax_cost": li.pretax_cost, "project_markup_cost": li.markup_cost, "pod_cost": random.random() * li.pretax_cost, } line_item = OCPAzureCostLineItemProjectDailySummary(**data) line_item.save() def select_tags(self): """Return a random selection of the defined tags.""" return { key: self.config.tags[key] for key in random.choices( list(self.config.tags.keys()), k=random.randrange(2, len(self.config.tags.keys())) ) } def _populate_azure_tag_summary(self): """Populate the Azure tag summary table.""" agg_sql = pkgutil.get_data("masu.database", "sql/reporting_ocpazuretags_summary.sql") agg_sql = agg_sql.decode("utf-8") agg_sql_params = {"schema": connection.schema_name} agg_sql, agg_sql_params = JinjaSql().prepare_query(agg_sql, agg_sql_params) with connection.cursor() as cursor: cursor.execute(agg_sql)