distil-ui/distil_ui/api/distil.py

317 lines
13 KiB
Python

# Copyright (c) 2014 Catalyst IT Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import collections
import datetime
import logging
import math
import time
from django.conf import settings
import eventlet
from openstack_dashboard.api import base
LOG = logging.getLogger(__name__)
BILLITEM = collections.namedtuple('BillItem',
['id', 'resource', 'count', 'cost'])
EMPTY_BREAKDOWN = [BILLITEM(id=1, resource='N/A', count=0, cost=0)]
RES_NAME_MAPPING = {'Virtual Machine': 'Compute',
'Volume': 'Block Storage'}
KNOWN_RESOURCE_TYPE = ['Compute', 'Block Storage', 'Network', 'Router',
'Image', 'Floating IP', 'Object Storage', 'VPN',
'Inbound International Traffic',
'Outbound International Traffic',
'Inbound National Traffic',
'Outbound National Traffic']
SRV_RES_MAPPING = {'m1.tiny': 'Compute',
'm1.small': 'Compute',
'm1.mini': 'Compute',
'm1.medium': 'Compute',
'c1.small': 'Compute',
'm1.large': 'Compute',
'm1.xlarge': 'Compute',
'c1.large': 'Compute',
'c1.xlarge': 'Compute',
'c1.xxlarge': 'Compute',
'm1.2xlarge': 'Compute',
'c1.c1r1': 'Compute',
'c1.c1r2': 'Compute',
'c1.c1r4': 'Compute',
'c1.c2r1': 'Compute',
'c1.c2r2': 'Compute',
'c1.c2r4': 'Compute',
'c1.c2r8': 'Compute',
'c1.c2r16': 'Compute',
'c1.c4r2': 'Compute',
'c1.c4r4': 'Compute',
'c1.c4r8': 'Compute',
'c1.c4r16': 'Compute',
'c1.c4r32': 'Compute',
'c1.c8r4': 'Compute',
'c1.c8r8': 'Compute',
'c1.c8r16': 'Compute',
'c1.c8r32': 'Compute',
'b1.standard': 'Block Storage',
'o1.standard': 'Object Storage',
'n1.ipv4': 'Floating IP',
'n1.network': 'Network',
'n1.router': 'Router',
'n1.vpn': 'VPN',
'n1.international-in': 'Inbound International Traffic',
'n1.international-out': 'Outbound International Traffic',
'n1.national-in': 'Inbound National Traffic',
'n1.national-out': 'Outbound National Traffic'}
TRAFFIC_MAPPING = {'n1.international-in': 'Inbound International Traffic',
'n1.international-out': 'Outbound International Traffic',
'n1.national-in': 'Inbound National Traffic',
'n1.national-out': 'Outbound National Traffic'}
CACHE = {}
def distilclient(request):
try:
try:
from distilclient import client
except Exception:
from distil.client import client
auth_url = base.url_for(request, service_type='identity')
distil_url = base.url_for(request, service_type='rating')
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
distil = client.Client(distil_url=distil_url,
os_auth_token=request.user.token.id,
os_tenant_id=request.user.tenant_id,
os_auth_url=auth_url,
os_region_name=request.user.services_region,
insecure=insecure,
os_cacert=cacert)
distil.request = request
except Exception as e:
LOG.error(e)
return
return distil
def _get_month_cost(distil_client, tenant_id, start_str, end_str,
history_cost, i):
today = datetime.datetime.today()
start = datetime.datetime.strptime(start_str, '%Y-%m-%dT%H:%M:%S')
cache_key = (distil_client.endpoint + '_' + tenant_id + '_' +
start_str + '_' + end_str)
if cache_key in CACHE:
history_cost[i] = CACHE[cache_key]
return
month_cost = distil_client.get_rated([tenant_id], start_str,
end_str)['usage']
resource_cost = collections.OrderedDict()
prices = {}
cost_details = collections.defaultdict(list)
for res in KNOWN_RESOURCE_TYPE:
cost_details[res] = []
for res_id, details in month_cost['resources'].items():
resource_type = details['type']
for s in details['services']:
if resource_type not in prices:
try:
prices[resource_type] = float(s.get('rate', 0))
except Exception as e:
LOG.error('Failed to get rate for %s since %s' % (s, e))
# Only collect service details for current month, we may support
# the details for history in the future.
if ((start.year == today.year and start.month == today.month) or
s['name'] in TRAFFIC_MAPPING):
try:
s_copy = s.copy()
s_copy['volume'] = round(float(s_copy['volume']), 4)
s_copy['resource_id'] = res_id
cd_key = ('Image' if resource_type == 'Image' else
SRV_RES_MAPPING.get(s['name'], resource_type))
if cd_key in ('Image', 'Block Storage', 'Object Storage'):
s_copy['unit'] = 'gigabyte * hour'
# NOTE(flwang): Get the related resource info
if resource_type == 'Floating IP':
s_copy['resource'] = details['ip address']
if resource_type in ('Image', 'Object Storage Container',
'Network', 'Virtual Machine',
'Router', 'VPN', 'Volume'):
s_copy['resource'] = details['name']
cost_details.get(cd_key).append(s_copy)
except Exception as e:
LOG.error('Failed to save: %s, since %s' % (s, e))
continue
res_type = (resource_type if resource_type not in
RES_NAME_MAPPING else RES_NAME_MAPPING[resource_type])
count, cost = _calculate_count_cost(list(details['services']),
res_type)
if res_type in resource_cost:
tmp_count_cost = resource_cost[res_type]
tmp_count_cost = [tmp_count_cost[0] + count,
tmp_count_cost[1] + cost]
resource_cost[res_type] = tmp_count_cost
else:
resource_cost[res_type] = [count, cost]
# NOTE(flwang): Based on current Distil API design, it's making the
# traffic data associate with floating ip and router. So we need to
# get them out and recalculate the cost of floating ip and router.
if ['admin'] in [r.values() for r in distil_client.request.user.roles]:
_calculate_traffic_cost(cost_details, resource_cost)
breakdown = []
total_cost = 0
for resource, count_cost in resource_cost.items():
rounded_cost = round(count_cost[1], 2)
breakdown.append(BILLITEM(id=len(breakdown) + 1,
resource=resource,
count=count_cost[0],
cost=rounded_cost))
total_cost += rounded_cost
if breakdown:
if start.year == today.year and start.month == today.month:
# Only apply/show the discount for current month
end_str = today.strftime('%Y-%m-%dT%H:00:00')
history_cost[i] = _apply_discount((round(total_cost, 2),
breakdown, cost_details),
start_str,
end_str,
prices)
else:
month_cost = (round(total_cost, 2), breakdown, [])
if month_cost:
CACHE[cache_key] = month_cost
history_cost[i] = month_cost
def _calculate_count_cost(service_details, resource_type):
count = 0
cost = 0
for s in service_details:
if resource_type == 'Image' and s['name'] == 'b1.standard':
count += 1
cost += float(s['cost'])
if SRV_RES_MAPPING.get(s['name'], '') == resource_type:
count += 1
cost += float(s['cost'])
return count, cost
def _calculate_traffic_cost(cost_details, resource_cost):
for resource_type in TRAFFIC_MAPPING.values():
if resource_type in cost_details:
(count, cost) = _calculate_count_cost(cost_details[resource_type],
resource_type)
if cost > 0:
resource_cost[resource_type] = (count, cost)
def _apply_discount(cost, start_str, end_str, prices):
"""Appy discount for the usage costs
For now we only show the discount info for current month cost, because
the discount for history month has shown on customer's invoice.
"""
total_cost, breakdown, cost_details = cost
start = time.mktime(time.strptime(start_str, '%Y-%m-%dT%H:%M:%S'))
end = time.mktime(time.strptime(end_str, '%Y-%m-%dT%H:%M:%S'))
# Get the integer part of the hours
free_hours = math.floor((end - start) / 3600)
free_network_cost = round(prices.get('Network', 0.0164) * free_hours, 2)
free_router_cost = round(prices.get('Router', 0.0170) * free_hours, 2)
for item in breakdown:
if item.resource == 'Network':
free_network_cost = (item.cost if item.cost <= free_network_cost
else free_network_cost)
breakdown[item.id - 1] = item._replace(cost=(item.cost -
free_network_cost))
total_cost -= free_network_cost
if item.resource == 'Router':
free_router_cost = (item.cost if item.cost <= free_router_cost
else free_router_cost)
breakdown[item.id - 1] = item._replace(cost=(item.cost -
free_router_cost))
total_cost -= free_router_cost
return (total_cost, breakdown, cost_details)
def _calculate_start_date(today):
last_year = today.year - 1 if today.month < 12 else today.year
month = ((today.month + 1) % 12 if today.month + 1 > 12
else today.month + 1)
return datetime.datetime(last_year, month, 1)
def _calculate_end_date(start):
year = start.year + 1 if start.month + 1 > 12 else start.year
month = (start.month + 1) % 12 or 12
return datetime.datetime(year, month, 1)
def get_cost(request, distil_client=None, enable_eventlet=True):
"""Get cost for the last 1atest 12 months include current month
This function will return the latest 12 months cost and their breakdown
details, which includes current month.
"""
if enable_eventlet:
eventlet.monkey_patch()
thread_pool = eventlet.GreenPool(size=12)
history_cost = [(0, EMPTY_BREAKDOWN, []) for _ in range(12)]
distil_client = distil_client or distilclient(request)
if not distil_client:
return history_cost
today = datetime.date.today()
start = _calculate_start_date(datetime.date.today())
end = _calculate_end_date(start)
final_end = datetime.datetime(today.year, today.month + 1, 1)
try:
for i in range(12):
start_str = start.strftime("%Y-%m-%dT00:00:00")
end_str = end.strftime("%Y-%m-%dT00:00:00")
thread_pool.spawn_n(_get_month_cost,
distil_client, request.user.tenant_id,
start_str, end_str,
history_cost, i)
start = end
end = _calculate_end_date(start)
if end > final_end:
break
thread_pool.waitall()
except Exception as e:
LOG.exception('Failed to get the history cost data', e)
return history_cost