diff --git a/admin-costs-plugin/garr_costs/content/costs/utils.py b/admin-costs-plugin/garr_costs/content/costs/utils.py index ef039b6763b53c71d2ba465c762aa6d8589508c7..3df3c1bc6590e913c7afd83a7dbe5aecf563cebe 100644 --- a/admin-costs-plugin/garr_costs/content/costs/utils.py +++ b/admin-costs-plugin/garr_costs/content/costs/utils.py @@ -27,7 +27,7 @@ def get_measure_value(measures, metric, resource): return result def get_volume_usage(request, resource, price, usage_start, usage_end, gnocchi_client): - unit = 'GB' + unit = 'GB*hour' try: api.cinder.volume_get(request, resource['id']) except Exception: @@ -51,15 +51,11 @@ def get_volume_usage(request, resource, price, usage_start, usage_end, gnocchi_c def get_instance_usage(resource, price, usage_start, usage_end): unit = 'hour' - start_time = parse_datetime(resource['started_at']).replace(tzinfo=None) - start_query = datetime.strptime(usage_start, "%Y-%m-%d") - diff = start_query - start_time - - hours_interval = get_hours(usage_start, usage_end) - if diff.days < 0: - end_query = datetime.strptime(usage_end, "%Y-%m-%d") - diff = end_query - start_time - hours_interval = diff.days * 24 + + if usage_start == usage_end: + hours_interval = 24 + else: + hours_interval = get_hours(usage_start, usage_end) cost = '{0:.2f}'.format( round(hours_interval * price, 2) diff --git a/admin-costs-plugin/garr_costs/content/costs/views.py b/admin-costs-plugin/garr_costs/content/costs/views.py index 605e61c7b8f48e7dc6fd4c8ca6aa96c2c2a5b6c8..9a3fc1236d75407c291eb176c622ce8a00aeee84 100644 --- a/admin-costs-plugin/garr_costs/content/costs/views.py +++ b/admin-costs-plugin/garr_costs/content/costs/views.py @@ -1,141 +1,164 @@ -import logging - -from oslo_utils import units - -from django.utils.translation import ugettext_lazy as _ -from django.utils import timezone -from horizon import exceptions -from horizon import messages -from horizon import tables -from horizon import fogitrms - -from openstack_dashboard import api -from openstack_dashboard import policy -from sqlalchemy.orm import Session - -from openstack_dashboard.dashboards.project.costs import tables as costs_tables - -from gnocchi import gnocchi_client -from utils import convert_nanoseconds, get_instance_usage, get_volume_usage -from orm import engine, Flavor, Price, Storage - -LOG = logging.getLogger(__name__) - -GARR_DB_KEY = 'GARR_DATABASE' - - -class IndexView(tables.DataTableView): - table_class = costs_tables.CostsTable - page_title = _("Project Costs") - template_name = 'project/costs/index.html' - - def get_context_data(self, **kwargs): - context = super(IndexView, self).get_context_data(**kwargs) - context['form'] = self.get_form(self.request) - context['overall_cost'] = sum(float(row['cost']) for row in context['table'].data) - return context - - @staticmethod - def get_default_interval(): - start_time = timezone.now() - timezone.timedelta(days=1) - end_time = timezone.now() - start = start_time.strftime('%Y-%m-%d') - end = end_time.strftime('%Y-%m-%d') - - return start, end - - def get_form(self, request): - if not hasattr(self, 'form'): - req = request - start = req.GET.get('start', None) - end = req.GET.get('end', None) - if not start and not end: - start, end = self.get_default_interval() - self.form = forms.DateForm(initial={'start': start, 'end': end}) - return self.form - - @staticmethod - def get_interval(request): - usage_start = request.GET.get('start', None) - usage_end = request.GET.get('end', None) - if not usage_start and not usage_end: - usage_start, usage_end = IndexView.get_default_interval() - - return usage_start, usage_end - - def get_data(self): - try: - session = Session(engine) - except Exception as e: - LOG.error('Unable to connect to the database') - LOG.error(e) - return [] - project_id = self.request.GET.get('project', self.request.user.project_id) - query = { - "=": { - "project_id": project_id - } - } - - resource_types = ['instance', 'volume'] - client = gnocchi_client(self.request) - project_resources = [] - for resource_type in resource_types: - try: - resources = client.resource.search(resource_type=resource_type, query=query, details=True) - except Exception: - LOG.error('Error while calling gnocchi api') - messages.error(self.request, _("Error while calling Gnocchi API")) - continue - - project_resources.extend(resources) - - usage_start, usage_end = self.get_interval(self.request) - result = [] - for resource in project_resources: - resource_name = resource.get('name', None) or resource.get('display_name', None) or resource['id'] - try: - price, cost_unit = self.get_price(resource, self.request, session) - except Exception as e: - LOG.error("Unable to fetch price for resource %s of type %s" % (resource_name, resource['type'])) - continue - - if type(cost_unit) is set: - cost_unit = cost_unit.pop() - - try: - if resource['type'] == 'instance': - cost, usage, unit = get_instance_usage(resource, price, usage_start, usage_end) - elif resource['type'] == 'volume': - cost, usage, unit = get_volume_usage(self.request, resource, price, usage_start, usage_end, client) - else: - continue - except Exception as ex: - LOG.error('Unable to get usage for resource %s' % resource) - continue - - result.append( { - 'name': resource_name, - 'unit': unit, - 'value': usage, - 'id': resource['id'], - 'price': '{}/{}'.format(price, cost_unit), - 'resource_type': resource['type'], - 'cost': cost - }) - - return sorted(result, key=lambda resource: resource['name']) - - - def get_price(self, resource, request, db_session): - if resource['type'] == 'instance': - flavor_name = resource.get('flavor_name', None) - if not flavor_name: flavor_name = api.nova.flavor_get(request, resource['flavor_id']).name - flavor = db_session.query(Flavor).filter(Flavor.name == flavor_name).first() - price = db_session.query(Price).filter(Price.resource == flavor.id, Price.type == 'flavor').first() - elif resource['type'] == 'volume': - volume_type = resource['volume_type'] or '' - volume = db_session.query(Storage).filter(Storage.volume == volume_type).first() - price = db_session.query(Price).filter(Price.resource == volume.id, Price.type == 'storage').first() - - return price.price, price.unit +import logging + +from oslo_utils import units + +from django.utils.translation import ugettext_lazy as _ +from django.utils import timezone +from horizon import exceptions +from horizon import messages +from horizon import tables +from horizon import forms + +from openstack_dashboard import api +from openstack_dashboard import policy +from sqlalchemy.orm import Session + +from openstack_dashboard.dashboards.project.costs import tables as costs_tables + +from gnocchi import gnocchi_client +from utils import convert_nanoseconds, get_instance_usage, get_volume_usage +from orm import engine, Flavor, Price, Storage + +LOG = logging.getLogger(__name__) + +GARR_DB_KEY = 'GARR_DATABASE' + + +class IndexView(tables.DataTableView): + table_class = costs_tables.CostsTable + page_title = _("Project Costs") + template_name = 'project/costs/index.html' + + def get_context_data(self, **kwargs): + context = super(IndexView, self).get_context_data(**kwargs) + context['form'] = self.get_form(self.request) + context['overall_cost'] = sum(float(row['cost']) for row in context['table'].data) + return context + + @staticmethod + def get_default_interval(): + start_time = timezone.now() - timezone.timedelta(days=1) + end_time = timezone.now() + start = start_time.strftime('%Y-%m-%d') + end = end_time.strftime('%Y-%m-%d') + + return start, end + + def get_form(self, request): + if not hasattr(self, 'form'): + req = request + start = req.GET.get('start', None) + end = req.GET.get('end', None) + if not start and not end: + start, end = self.get_default_interval() + self.form = forms.DateForm(initial={'start': start, 'end': end}) + return self.form + + @staticmethod + def get_interval(request): + usage_start = request.GET.get('start', None) + usage_end = request.GET.get('end', None) + if (not usage_start) or (not usage_end) or not (usage_end > usage_start): + usage_start, usage_end = IndexView.get_default_interval() + + return usage_start, usage_end + + def get_data(self): + try: + session = Session(engine) + except Exception as e: + LOG.error('Unable to connect to the database') + LOG.error(e) + return [] + + query = { + "=": { + "project_id": self.request.user.project_id + } + } + + resource_types = ['instance', 'volume'] + client = gnocchi_client(self.request) + project_resources = [] + for resource_type in resource_types: + try: + resources = client.resource.search(resource_type=resource_type, query=query, details=True) + except Exception: + LOG.error('Error while calling gnocchi api') + messages.error(self.request, _("Error while calling Gnocchi API")) + continue + + project_resources.extend(resources) + + usage_start, usage_end = self.get_interval(self.request) + result = [] + for resource in project_resources: + resource_not_in_timeframe = False + + resource_name = resource.get('name', None) or resource.get('display_name', None) or resource['id'] + + resource_usage_start = resource['started_at'][:10] + if resource['ended_at']: + resource_usage_end = resource['ended_at'][:10] + else: + resource_usage_end = usage_end + + # make sure that the resource existed during this interval + if (resource_usage_end < usage_start) or (resource_usage_start > usage_end): + resource_not_in_timeframe = True + + # truncate the interval defined by the resource_usage_* variables + if not (resource_usage_start > usage_start): + resource_usage_start = usage_start + if not (resource_usage_end < usage_end): + resource_usage_end = usage_end + + try: + price, cost_unit = self.get_price(resource, self.request, session) + except Exception as e: + LOG.error("Unable to fetch price for resource %s of type %s" % (resource_name, resource['type'])) + continue + + if type(cost_unit) is set: + cost_unit = cost_unit.pop() + + try: + if resource['type'] == 'instance': + cost, usage, unit = get_instance_usage(resource, price, resource_usage_start, resource_usage_end) + elif resource['type'] == 'volume': + cost, usage, unit = get_volume_usage(self.request, resource, price, resource_usage_start, resource_usage_end, client) + else: + continue + except Exception as ex: + LOG.error('Unable to get usage for resource %s' % resource) + continue + + if resource_not_in_timeframe: + cost = '0' + usage = '0' + + result.append( { + 'name': resource_name, + 'unit': unit, + 'value': usage, + 'id': resource['id'], + 'price': '{}/{}'.format(price, cost_unit), + 'resource_type': resource['type'], + 'cost': cost + }) + + return sorted(result, key=lambda resource: resource['name']) + + + def get_price(self, resource, request, db_session): + if resource['type'] == 'instance': + flavor_name = resource.get('flavor_name', None) + if not flavor_name: flavor_name = api.nova.flavor_get(request, resource['flavor_id']).name + flavor = db_session.query(Flavor).filter(Flavor.name == flavor_name).first() + price = db_session.query(Price).filter(Price.resource == flavor.id, Price.type == 'flavor').first() + elif resource['type'] == 'volume': + volume_type = resource['volume_type'] or '' + volume = db_session.query(Storage).filter(Storage.volume == volume_type).first() + price = db_session.query(Price).filter(Price.resource == volume.id, Price.type == 'storage').first() + + return price.price, price.unit diff --git a/costs-plugin/garr_costs/content/costs/utils.py b/costs-plugin/garr_costs/content/costs/utils.py index ef039b6763b53c71d2ba465c762aa6d8589508c7..3df3c1bc6590e913c7afd83a7dbe5aecf563cebe 100644 --- a/costs-plugin/garr_costs/content/costs/utils.py +++ b/costs-plugin/garr_costs/content/costs/utils.py @@ -27,7 +27,7 @@ def get_measure_value(measures, metric, resource): return result def get_volume_usage(request, resource, price, usage_start, usage_end, gnocchi_client): - unit = 'GB' + unit = 'GB*hour' try: api.cinder.volume_get(request, resource['id']) except Exception: @@ -51,15 +51,11 @@ def get_volume_usage(request, resource, price, usage_start, usage_end, gnocchi_c def get_instance_usage(resource, price, usage_start, usage_end): unit = 'hour' - start_time = parse_datetime(resource['started_at']).replace(tzinfo=None) - start_query = datetime.strptime(usage_start, "%Y-%m-%d") - diff = start_query - start_time - - hours_interval = get_hours(usage_start, usage_end) - if diff.days < 0: - end_query = datetime.strptime(usage_end, "%Y-%m-%d") - diff = end_query - start_time - hours_interval = diff.days * 24 + + if usage_start == usage_end: + hours_interval = 24 + else: + hours_interval = get_hours(usage_start, usage_end) cost = '{0:.2f}'.format( round(hours_interval * price, 2) diff --git a/costs-plugin/garr_costs/content/costs/views.py b/costs-plugin/garr_costs/content/costs/views.py index 6c1c5fc96853a9c87cb43a649c1a7370e4ef7f2e..9a3fc1236d75407c291eb176c622ce8a00aeee84 100644 --- a/costs-plugin/garr_costs/content/costs/views.py +++ b/costs-plugin/garr_costs/content/costs/views.py @@ -58,7 +58,7 @@ class IndexView(tables.DataTableView): def get_interval(request): usage_start = request.GET.get('start', None) usage_end = request.GET.get('end', None) - if not usage_start and not usage_end: + if (not usage_start) or (not usage_end) or not (usage_end > usage_start): usage_start, usage_end = IndexView.get_default_interval() return usage_start, usage_end @@ -93,7 +93,26 @@ class IndexView(tables.DataTableView): usage_start, usage_end = self.get_interval(self.request) result = [] for resource in project_resources: + resource_not_in_timeframe = False + resource_name = resource.get('name', None) or resource.get('display_name', None) or resource['id'] + + resource_usage_start = resource['started_at'][:10] + if resource['ended_at']: + resource_usage_end = resource['ended_at'][:10] + else: + resource_usage_end = usage_end + + # make sure that the resource existed during this interval + if (resource_usage_end < usage_start) or (resource_usage_start > usage_end): + resource_not_in_timeframe = True + + # truncate the interval defined by the resource_usage_* variables + if not (resource_usage_start > usage_start): + resource_usage_start = usage_start + if not (resource_usage_end < usage_end): + resource_usage_end = usage_end + try: price, cost_unit = self.get_price(resource, self.request, session) except Exception as e: @@ -105,15 +124,19 @@ class IndexView(tables.DataTableView): try: if resource['type'] == 'instance': - cost, usage, unit = get_instance_usage(resource, price, usage_start, usage_end) + cost, usage, unit = get_instance_usage(resource, price, resource_usage_start, resource_usage_end) elif resource['type'] == 'volume': - cost, usage, unit = get_volume_usage(self.request, resource, price, usage_start, usage_end, client) + cost, usage, unit = get_volume_usage(self.request, resource, price, resource_usage_start, resource_usage_end, client) else: continue except Exception as ex: LOG.error('Unable to get usage for resource %s' % resource) continue + if resource_not_in_timeframe: + cost = '0' + usage = '0' + result.append( { 'name': resource_name, 'unit': unit,