Пример #1
0
    def _get_timeframe(self):
        """Obtain timeframe start and end dates.

        Returns:
            (DateTime): start datetime for query filter
            (DateTime): end datetime for query filter

        """
        dh = DateHelper()
        self.end_datetime = dh.today
        if self.time_scope_units == 'month':
            self.start_datetime = dh.n_months_ago(dh.next_month_start,
                                                  abs(self.time_scope_value))
        else:
            self.start_datetime = dh.n_days_ago(dh.today,
                                                abs(self.time_scope_value))

        self._create_time_interval()
        return (self.start_datetime, self.end_datetime, self.time_interval)
Пример #2
0
class OCPReportDataGenerator:
    """Populate the database with OCP report data."""
    def __init__(self, tenant, current_month_only=False, dated_tags=None):
        """Set up the class."""
        self.tenant = tenant
        self.fake = Faker()
        self.dh = DateHelper()
        self.dated_tags = True if dated_tags is None or dated_tags is True else False

        self.today = self.dh.today
        self.one_month_ago = self.dh.n_months_ago(self.dh.today, 1)

        self.last_month = self.dh.last_month_start

        if current_month_only:
            report_days = 10
            first_of_month = self.today.replace(microsecond=0,
                                                second=0,
                                                minute=0,
                                                day=1)
            diff_from_first = self.today - first_of_month
            if diff_from_first.days < 10:
                report_days = 1 + diff_from_first.days
            self.period_ranges = [
                (self.dh.this_month_start, self.dh.this_month_end),
            ]
            self.report_ranges = [
                (self.today - relativedelta(days=i)
                 for i in range(report_days)),
            ]

        else:
            self.period_ranges = [
                (self.dh.last_month_start, self.dh.last_month_end),
                (self.dh.this_month_start, self.dh.this_month_end),
            ]

            self.report_ranges = [
                (self.one_month_ago - relativedelta(days=i)
                 for i in range(11)),
                (self.today - relativedelta(days=i) for i in range(11)),
            ]

    def create_manifest_entry(self, billing_period_start, provider_id):
        """Populate a report manifest entry."""
        manifest_creation_datetime = billing_period_start + relativedelta(
            days=random.randint(1, 27))
        manifest_updated_datetime = manifest_creation_datetime + relativedelta(
            days=random.randint(1, 2))
        data = {
            'assembly_id': uuid4(),
            'manifest_creation_datetime': manifest_creation_datetime,
            'manifest_updated_datetime': manifest_updated_datetime,
            'billing_period_start_datetime': billing_period_start,
            'num_processed_files': 1,
            'num_total_files': 1,
            'provider_id': provider_id
        }
        manifest_entry = CostUsageReportManifest(**data)
        manifest_entry.save()
        return manifest_entry

    def create_report_status_entry(self, billing_period_start, manifest_id):
        """Populate a report status entry."""
        etag_hasher = hashlib.new('ripemd160')
        etag_hasher.update(bytes(str(billing_period_start), 'utf-8'))
        ocp_etag = etag_hasher.hexdigest()

        last_started_datetime = billing_period_start + relativedelta(
            days=random.randint(1, 3))
        last_completed_datetime = last_started_datetime + relativedelta(days=1)
        data = {
            'report_name': uuid4(),
            'last_completed_datetime': last_completed_datetime,
            'last_started_datetime': last_started_datetime,
            'etag': ocp_etag,
            'manifest_id': manifest_id,
        }
        status_entry = CostUsageReportStatus(**data)
        status_entry.save()
        return status_entry

    def add_data_to_tenant(self, provider_id=None):
        """Populate tenant with data."""
        self.cluster_id = self.fake.word()
        self.cluster_alias = self.fake.word()
        self.namespaces = [self.fake.word() for _ in range(2)]
        self.nodes = [self.fake.word() for _ in range(2)]
        self.line_items = [{
            'namespace': random.choice(self.namespaces),
            'node': node,
            'pod': self.fake.word(),
            'persistentvolumeclaim': self.fake.word(),
            'persistentvolume': self.fake.word(),
            'storageclass': self.fake.word()
        } for node in self.nodes]
        with tenant_context(self.tenant):
            for i, period in enumerate(self.period_ranges):
                report_period = self.create_ocp_report_period(
                    period, provider_id)
                if provider_id:
                    manifest_entry = self.create_manifest_entry(
                        report_period.report_period_start, provider_id)
                    self.create_report_status_entry(
                        manifest_entry.billing_period_start_datetime,
                        manifest_entry.id)
                for report_date in self.report_ranges[i]:
                    report = self.create_ocp_report(report_period, report_date)
                    self.create_line_items(report_period, report)
                    self.create_storage_line_items(report_period, report)

            self._populate_daily_table()
            self._populate_daily_summary_table()
            self._populate_storage_daily_table()
            self._populate_storage_daily_summary_table()
            self._populate_charge_info()
            self._populate_pod_label_summary_table()
            self._populate_volume_claim_label_summary_table()
            self._populate_volume_label_summary_table()

    @transaction.atomic
    def remove_data_from_tenant(self):
        """Remove the added data."""
        with tenant_context(self.tenant):
            for table in (OCPUsageLineItem, OCPUsageLineItemDaily,
                          OCPUsageLineItemDailySummary, OCPStorageLineItem,
                          OCPStorageLineItemDaily,
                          OCPStorageLineItemDailySummary, OCPUsageReport,
                          OCPUsageReportPeriod):
                table.objects.all().delete()

    @transaction.atomic
    def remove_data_from_reporting_common(self):
        """Remove the public report statistics."""
        for table in (CostUsageReportManifest, CostUsageReportStatus):
            table.objects.all().delete()

    def create_ocp_report_period(self, period, provider_id=1):
        """Create the OCP report period DB rows."""
        data = {
            'cluster_id': self.cluster_id,
            'report_period_start': period[0],
            'report_period_end': period[1],
            'summary_data_creation_datetime': self.dh._now,
            'summary_data_updated_datetime': self.dh._now,
            'provider_id': provider_id
        }
        report_period = OCPUsageReportPeriod(**data)
        report_period.save()
        return report_period

    def create_ocp_report(self, period, interval_start):
        """Create the OCP report DB rows."""
        data = {
            'interval_start': interval_start,
            'interval_end': interval_start,
            'report_period': period
        }

        report = OCPUsageReport(**data)
        report.save()
        return report

    def _gen_pod_labels(self, report):
        """Create pod labels for output data."""
        apps = [
            self.fake.word(),
            self.fake.word(),
            self.fake.word(),  # pylint: disable=no-member
            self.fake.word(),
            self.fake.word(),
            self.fake.word()
        ]  # pylint: disable=no-member
        organizations = [
            self.fake.word(),
            self.fake.word(),  # pylint: disable=no-member
            self.fake.word(),
            self.fake.word()
        ]  # pylint: disable=no-member
        markets = [
            self.fake.word(),
            self.fake.word(),
            self.fake.word(),  # pylint: disable=no-member
            self.fake.word(),
            self.fake.word(),
            self.fake.word()
        ]  # pylint: disable=no-member
        versions = [
            self.fake.word(),
            self.fake.word(),
            self.fake.word(),  # pylint: disable=no-member
            self.fake.word(),
            self.fake.word(),
            self.fake.word()
        ]  # pylint: disable=no-member

        seeded_labels = {
            'environment': ['dev', 'ci', 'qa', 'stage', 'prod'],
            'app': apps,
            'organization': organizations,
            'market': markets,
            'version': versions
        }
        gen_label_keys = [
            self.fake.word(),
            self.fake.word(),
            self.fake.word(),  # pylint: disable=no-member
            self.fake.word(),
            self.fake.word(),
            self.fake.word()
        ]  # pylint: disable=no-member
        all_label_keys = list(seeded_labels.keys()) + gen_label_keys
        num_labels = random.randint(2, len(all_label_keys))
        chosen_label_keys = random.choices(all_label_keys, k=num_labels)

        labels = {}
        for label_key in chosen_label_keys:
            label_value = self.fake.word()  # pylint: disable=no-member
            if label_key in seeded_labels:
                label_value = random.choice(seeded_labels[label_key])
            if self.dated_tags:
                labels['{}-{}-{}*{}_label'.format(report.interval_start.month,
                                                  report.interval_start.day,
                                                  report.interval_start.year,
                                                  label_key)] = label_value
            else:
                labels['{}_label'.format(label_key)] = label_value

        return labels

    def create_line_items(self, report_period, report):
        """Create OCP hourly usage line items."""
        node_cpu_cores = random.randint(1, 8)
        node_memory_gb = random.randint(4, 32)
        for row in self.line_items:
            data = {
                'report_period':
                report_period,
                'report':
                report,
                'namespace':
                row.get('namespace'),
                'pod':
                row.get('pod'),
                'node':
                row.get('node'),
                'pod_usage_cpu_core_seconds':
                Decimal(random.uniform(0, 3600)),
                'pod_request_cpu_core_seconds':
                Decimal(random.uniform(0, 3600)),
                'pod_limit_cpu_core_seconds':
                Decimal(random.uniform(0, 3600)),
                'pod_usage_memory_byte_seconds':
                Decimal(random.uniform(0, 3600) * 1e9),
                'pod_request_memory_byte_seconds':
                Decimal(random.uniform(0, 3600) * 1e9),
                'pod_limit_memory_byte_seconds':
                Decimal(random.uniform(0, 3600) * 1e9),
                'node_capacity_cpu_cores':
                Decimal(node_cpu_cores),
                'node_capacity_cpu_core_seconds':
                Decimal(node_cpu_cores * 3600),
                'node_capacity_memory_bytes':
                Decimal(node_memory_gb * 1e9),
                'node_capacity_memory_byte_seconds':
                Decimal(node_memory_gb * 1e9 * 3600),
                'pod_labels':
                self._gen_pod_labels(report)
            }
            line_item = OCPUsageLineItem(**data)
            try:
                line_item.save()
            except IntegrityError as exc:
                LOG.critical('Error in mock data generation: %s', exc)

    def _populate_daily_table(self):
        """Populate the daily table."""
        included_fields = [
            'namespace',
            'pod',
            'node',
            'pod_labels',
        ]
        annotations = {
            'usage_start':
            F('report__interval_start'),
            'usage_end':
            F('report__interval_start'),
            'pod_usage_cpu_core_seconds':
            Sum('pod_usage_cpu_core_seconds'),
            'pod_request_cpu_core_seconds':
            Sum('pod_request_cpu_core_seconds'),
            'pod_limit_cpu_core_seconds':
            Sum('pod_limit_cpu_core_seconds'),
            'pod_usage_memory_byte_seconds':
            Sum('pod_usage_memory_byte_seconds'),
            'pod_request_memory_byte_seconds':
            Sum('pod_request_memory_byte_seconds'),
            'pod_limit_memory_byte_seconds':
            Sum('pod_limit_memory_byte_seconds'),
            'cluster_id':
            F('report_period__cluster_id'),
            'cluster_alias':
            Value(self.cluster_alias, output_field=CharField()),
            'node_capacity_cpu_cores':
            Max('node_capacity_cpu_cores'),
            'node_capacity_cpu_core_seconds':
            Max('node_capacity_cpu_core_seconds'),
            'node_capacity_memory_bytes':
            Max('node_capacity_memory_bytes'),
            'node_capacity_memory_byte_seconds':
            Max('node_capacity_memory_byte_seconds')
        }
        entries = OCPUsageLineItem.objects\
            .values(*included_fields)\
            .annotate(**annotations)

        cluster_capacity_cpu_core_seconds = Decimal(0)
        cluster_capacity_memory_byte_seconds = Decimal(0)

        cluster_cap = OCPUsageLineItem.objects\
            .values(*['node'])\
            .annotate(
                **{
                    'node_capacity_cpu_core_seconds': Max('node_capacity_cpu_core_seconds'),
                    'node_capacity_memory_byte_seconds': Sum('node_capacity_memory_byte_seconds')
                }
            )

        for node in cluster_cap:
            cluster_capacity_cpu_core_seconds += node.get(
                'node_capacity_cpu_core_seconds')
            cluster_capacity_memory_byte_seconds += node.get(
                'node_capacity_memory_byte_seconds')

        for entry in entries:
            entry['total_seconds'] = 3600
            entry[
                'cluster_capacity_cpu_core_seconds'] = cluster_capacity_cpu_core_seconds
            entry[
                'cluster_capacity_memory_byte_seconds'] = cluster_capacity_memory_byte_seconds
            entry[
                'total_capacity_cpu_core_seconds'] = cluster_capacity_cpu_core_seconds
            entry[
                'total_capacity_memory_byte_seconds'] = cluster_capacity_memory_byte_seconds
            daily = OCPUsageLineItemDaily(**entry)
            daily.save()

    def _populate_daily_summary_table(self):
        """Populate the daily summary table."""
        included_fields = [
            'usage_start',
            'usage_end',
            'namespace',
            'pod',
            'node',
            'cluster_id',
            'cluster_alias',
            'node_capacity_cpu_cores',
            'pod_labels',
        ]
        annotations = {
            'pod_usage_cpu_core_hours':
            F('pod_usage_cpu_core_seconds') / 3600,
            'pod_request_cpu_core_hours':
            Sum(
                ExpressionWrapper(F('pod_request_cpu_core_seconds') / 3600,
                                  output_field=DecimalField())),
            'pod_limit_cpu_core_hours':
            Sum(
                ExpressionWrapper(F('pod_limit_cpu_core_seconds') / 3600,
                                  output_field=DecimalField())),
            'pod_usage_memory_gigabyte_hours':
            Sum(
                ExpressionWrapper(F('pod_usage_memory_byte_seconds') / 3600,
                                  output_field=DecimalField())) *
            math.pow(2, -30),
            'pod_request_memory_gigabyte_hours':
            Sum(
                ExpressionWrapper(F('pod_request_memory_byte_seconds') / 3600,
                                  output_field=DecimalField())) *
            math.pow(2, -30),
            'pod_limit_memory_gigabyte_hours':
            ExpressionWrapper(F('pod_limit_memory_byte_seconds') / 3600,
                              output_field=DecimalField()) * math.pow(2, -30),
            'node_capacity_cpu_core_hours':
            F('node_capacity_cpu_core_seconds') / 3600,
            'node_capacity_memory_gigabytes':
            F('node_capacity_memory_bytes') * math.pow(2, -30),
            'node_capacity_memory_gigabyte_hours':
            ExpressionWrapper(F('node_capacity_memory_byte_seconds') / 3600,
                              output_field=DecimalField()) * math.pow(2, -30),
            'cluster_capacity_cpu_core_hours':
            F('cluster_capacity_cpu_core_seconds') / 3600,
            'cluster_capacity_memory_gigabyte_hours':
            ExpressionWrapper(F('cluster_capacity_memory_byte_seconds') / 3600,
                              output_field=DecimalField()) * math.pow(2, -30),
            'total_capacity_cpu_core_hours':
            F('cluster_capacity_cpu_core_seconds') / 3600 * 2,
            'total_capacity_memory_gigabyte_hours':
            ExpressionWrapper(
                F('cluster_capacity_memory_byte_seconds') / 3600 * 2,
                output_field=DecimalField()) * math.pow(2, -30),
        }

        entries = OCPUsageLineItemDaily.objects.values(
            *included_fields).annotate(**annotations)

        for entry in entries:
            summary = OCPUsageLineItemDailySummary(**entry)
            summary.save()

    def _populate_charge_info(self):
        """Populate the charge information in summary table."""
        entries = OCPUsageLineItemDailySummary.objects.all()
        for entry in entries:
            mem_usage = entry.pod_usage_memory_gigabyte_hours
            mem_request = entry.pod_request_memory_gigabyte_hours
            mem_charge = max(float(mem_usage), float(mem_request)) * 0.25

            entry.pod_charge_memory_gigabyte_hours = mem_charge

            cpu_usage = entry.pod_usage_cpu_core_hours
            cpu_request = entry.pod_request_cpu_core_hours
            cpu_charge = max(float(cpu_usage), float(cpu_request)) * 0.50

            entry.pod_charge_cpu_core_hours = cpu_charge

            entry.save()

    def create_storage_line_items(self, report_period, report):
        """Create OCP hourly usage line items."""
        vol_gb = random.randint(4, 32)
        for row in self.line_items:
            data = {
                'report_period':
                report_period,
                'report':
                report,
                'namespace':
                row.get('namespace'),
                'pod':
                row.get('pod'),
                'persistentvolumeclaim':
                row.get('persistentvolumeclaim'),
                'persistentvolume':
                row.get('persistentvolume'),
                'storageclass':
                row.get('storageclass'),
                'volume_request_storage_byte_seconds':
                Decimal(random.uniform(0, 3600) * 1e9),
                'persistentvolumeclaim_usage_byte_seconds':
                Decimal(random.uniform(0, 3600) * 1e9),
                'persistentvolumeclaim_capacity_bytes':
                Decimal(vol_gb * 1e9),
                'persistentvolumeclaim_capacity_byte_seconds':
                Decimal(vol_gb * 1e9 * 3600),
                'persistentvolume_labels':
                self._gen_pod_labels(report),
                'persistentvolumeclaim_labels':
                self._gen_pod_labels(report)
            }
            line_item = OCPStorageLineItem(**data)
            try:
                line_item.save()
            except IntegrityError as exc:
                LOG.critical('Error in mock data generation: %s', exc)

    def _populate_storage_daily_table(self):
        """Populate the daily table."""
        included_fields = [
            'namespace', 'pod', 'persistentvolumeclaim', 'persistentvolume',
            'storageclass', 'persistentvolume_labels',
            'persistentvolumeclaim_labels'
        ]
        annotations = {
            'node':
            Value(random.choice(self.line_items).get('node'),
                  output_field=CharField()),
            'usage_start':
            F('report__interval_start'),
            'usage_end':
            F('report__interval_start'),
            'persistentvolumeclaim_capacity_bytes':
            Max('persistentvolumeclaim_capacity_bytes'),
            'persistentvolumeclaim_capacity_byte_seconds':
            Sum('persistentvolumeclaim_capacity_byte_seconds'),
            'volume_request_storage_byte_seconds':
            Sum('volume_request_storage_byte_seconds'),
            'persistentvolumeclaim_usage_byte_seconds':
            Sum('persistentvolumeclaim_usage_byte_seconds'),
            'cluster_id':
            F('report_period__cluster_id'),
            'cluster_alias':
            Value(self.cluster_alias, output_field=CharField()),
        }
        entries = OCPStorageLineItem.objects\
            .values(*included_fields)\
            .annotate(**annotations)

        for entry in entries:
            entry['total_seconds'] = 3600
            daily = OCPStorageLineItemDaily(**entry)
            daily.save()

    def _populate_storage_daily_summary_table(self):
        """Populate the daily summary table."""
        included_fields = [
            'usage_start', 'usage_end', 'namespace', 'pod', 'node',
            'persistentvolumeclaim', 'persistentvolume', 'storageclass',
            'cluster_id', 'cluster_alias'
        ]
        annotations = {
            'volume_labels':
            Coalesce(F('persistentvolume_labels'),
                     F('persistentvolumeclaim_labels')),
            'persistentvolumeclaim_capacity_gigabyte':
            ExpressionWrapper(F('persistentvolumeclaim_capacity_bytes') *
                              math.pow(2, -30),
                              output_field=DecimalField()),
            'persistentvolumeclaim_capacity_gigabyte_months':
            ExpressionWrapper(
                F('persistentvolumeclaim_capacity_byte_seconds') / 86400 * 30 *
                math.pow(2, -30),
                output_field=DecimalField()),
            'volume_request_storage_gigabyte_months':
            ExpressionWrapper(F('volume_request_storage_byte_seconds') /
                              86400 * 30 * math.pow(2, -30),
                              output_field=DecimalField()),
            'persistentvolumeclaim_usage_gigabyte_months':
            ExpressionWrapper(F('persistentvolumeclaim_usage_byte_seconds') /
                              86400 * 30 * math.pow(2, -30),
                              output_field=DecimalField())
        }

        entries = OCPStorageLineItemDaily.objects.values(
            *included_fields).annotate(**annotations)

        for entry in entries:
            summary = OCPStorageLineItemDailySummary(**entry)
            summary.save()

    def _populate_pod_label_summary_table(self):
        """Populate pod label key and values."""
        raw_sql = """
            INSERT INTO reporting_ocpusagepodlabel_summary
            SELECT l.key,
                array_agg(DISTINCT l.value) as values
            FROM (
                SELECT key,
                    value
                FROM reporting_ocpusagelineitem_daily AS li,
                    jsonb_each_text(li.pod_labels) labels
            ) l
            GROUP BY l.key
            ON CONFLICT (key) DO UPDATE
            SET values = EXCLUDED.values
        """

        with connection.cursor() as cursor:
            cursor.execute(raw_sql)

    def _populate_volume_claim_label_summary_table(self):
        """Populate pod label key and values."""
        raw_sql = """
            INSERT INTO reporting_ocpstoragevolumeclaimlabel_summary
            SELECT l.key,
                array_agg(DISTINCT l.value) as values
            FROM (
                SELECT key,
                    value
                FROM reporting_ocpusagelineitem_daily AS li,
                    jsonb_each_text(li.pod_labels) labels
            ) l
            GROUP BY l.key
            ON CONFLICT (key) DO UPDATE
            SET values = EXCLUDED.values
        """

        with connection.cursor() as cursor:
            cursor.execute(raw_sql)

    def _populate_volume_label_summary_table(self):
        """Populate pod label key and values."""
        raw_sql = """
            INSERT INTO reporting_ocpstoragevolumelabel_summary
            SELECT l.key,
                array_agg(DISTINCT l.value) as values
            FROM (
                SELECT key,
                    value
                FROM reporting_ocpusagelineitem_daily AS li,
                    jsonb_each_text(li.pod_labels) labels
            ) l
            GROUP BY l.key
            ON CONFLICT (key) DO UPDATE
            SET values = EXCLUDED.values
        """

        with connection.cursor() as cursor:
            cursor.execute(raw_sql)