Reworked all the usage implementations into one standard set.
Adds a base Usage data object, datatables, class-based views, and more consistent templating for all cases. Bumps environment version to ensure latest novaclient. Fixes bug 922353. Change-Id: Ib2042e9393c8deb0e3ec23403da55a6fb8dd39fb
This commit is contained in:
parent
81d6026750
commit
64b81acc0a
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,7 +2,7 @@
|
||||
*.swp
|
||||
.environment_version
|
||||
.selenium_log
|
||||
.coverage
|
||||
.coverage*
|
||||
.noseids
|
||||
coverage.xml
|
||||
pep8.txt
|
||||
|
@ -130,24 +130,40 @@ class Usage(APIResourceWrapper):
|
||||
'total_local_gb_usage', 'total_memory_mb_usage',
|
||||
'total_vcpus_usage', 'total_hours']
|
||||
|
||||
def get_summary(self):
|
||||
return {'instances': self.total_active_instances,
|
||||
'memory_mb': self.memory_mb,
|
||||
'vcpus': getattr(self, "total_vcpus_usage", 0),
|
||||
'vcpu_hours': self.vcpu_hours,
|
||||
'local_gb': self.local_gb,
|
||||
'disk_gb_hours': self.disk_gb_hours}
|
||||
|
||||
@property
|
||||
def total_active_instances(self):
|
||||
return sum(1 for s in self.server_usages if s['ended_at'] == None)
|
||||
|
||||
@property
|
||||
def total_active_vcpus(self):
|
||||
return sum(s['vcpus']\
|
||||
for s in self.server_usages if s['ended_at'] == None)
|
||||
def vcpus(self):
|
||||
return sum(s['vcpus'] for s in self.server_usages
|
||||
if s['ended_at'] == None)
|
||||
|
||||
@property
|
||||
def total_active_local_gb(self):
|
||||
return sum(s['local_gb']\
|
||||
for s in self.server_usages if s['ended_at'] == None)
|
||||
def vcpu_hours(self):
|
||||
return getattr(self, "total_hours", 0)
|
||||
|
||||
@property
|
||||
def total_active_memory_mb(self):
|
||||
return sum(s['memory_mb']\
|
||||
for s in self.server_usages if s['ended_at'] == None)
|
||||
def local_gb(self):
|
||||
return sum(s['local_gb'] for s in self.server_usages
|
||||
if s['ended_at'] == None)
|
||||
|
||||
@property
|
||||
def memory_mb(self):
|
||||
return sum(s['memory_mb'] for s in self.server_usages
|
||||
if s['ended_at'] == None)
|
||||
|
||||
@property
|
||||
def disk_gb_hours(self):
|
||||
return getattr(self, "total_local_gb_usage", 0)
|
||||
|
||||
|
||||
class SecurityGroup(APIResourceWrapper):
|
||||
|
@ -24,44 +24,74 @@ from django import http
|
||||
from django.core.urlresolvers import reverse
|
||||
from mox import IsA
|
||||
from novaclient import exceptions as nova_exceptions
|
||||
from novaclient.v1_1 import usage as nova_usage
|
||||
|
||||
from horizon import api
|
||||
from horizon import test
|
||||
from horizon import usage
|
||||
|
||||
|
||||
INDEX_URL = reverse('horizon:nova:overview:index')
|
||||
USAGE_DATA = {
|
||||
'total_memory_mb_usage': 64246.89777777778,
|
||||
'total_vcpus_usage': 125.48222222222223,
|
||||
'total_hours': 125.48222222222223,
|
||||
'total_local_gb_usage': 0.0,
|
||||
'tenant_id': u'99e7c0197c3643289d89e9854469a4ae',
|
||||
'stop': u'2012-01-3123: 30: 46',
|
||||
'start': u'2012-01-0100: 00: 00',
|
||||
'server_usages': [
|
||||
{
|
||||
u'memory_mb': 512,
|
||||
u'uptime': 442321,
|
||||
u'started_at': u'2012-01-2620: 38: 21',
|
||||
u'ended_at': None,
|
||||
u'name': u'testing',
|
||||
u'tenant_id': u'99e7c0197c3643289d89e9854469a4ae',
|
||||
u'state': u'active',
|
||||
u'hours': 122.87361111111112,
|
||||
u'vcpus': 1,
|
||||
u'flavor': u'm1.tiny',
|
||||
u'local_gb': 0
|
||||
},
|
||||
{
|
||||
u'memory_mb': 512,
|
||||
u'uptime': 9367,
|
||||
u'started_at': u'2012-01-3120: 54: 15',
|
||||
u'ended_at': None,
|
||||
u'name': u'instance2',
|
||||
u'tenant_id': u'99e7c0197c3643289d89e9854469a4ae',
|
||||
u'state': u'active',
|
||||
u'hours': 2.608611111111111,
|
||||
u'vcpus': 1,
|
||||
u'flavor': u'm1.tiny',
|
||||
u'local_gb': 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class InstanceViewTests(test.BaseViewTests):
|
||||
class UsageViewTests(test.BaseViewTests):
|
||||
def setUp(self):
|
||||
super(InstanceViewTests, self).setUp()
|
||||
self.now = self.override_times()
|
||||
|
||||
server = api.Server(None, self.request)
|
||||
server.id = "1"
|
||||
server.name = 'serverName'
|
||||
server.status = "ACTIVE"
|
||||
|
||||
volume = api.Volume(self.request)
|
||||
volume.id = "1"
|
||||
|
||||
self.servers = (server,)
|
||||
self.volumes = (volume,)
|
||||
super(UsageViewTests, self).setUp()
|
||||
usage_resource = nova_usage.Usage(nova_usage.UsageManager, USAGE_DATA)
|
||||
self.usage = api.nova.Usage(usage_resource)
|
||||
self.usages = (self.usage,)
|
||||
|
||||
def tearDown(self):
|
||||
super(InstanceViewTests, self).tearDown()
|
||||
super(UsageViewTests, self).tearDown()
|
||||
self.reset_times()
|
||||
|
||||
def test_usage(self):
|
||||
TEST_RETURN = 'testReturn'
|
||||
|
||||
now = self.override_times()
|
||||
|
||||
self.mox.StubOutWithMock(api, 'usage_get')
|
||||
api.usage_get(IsA(http.HttpRequest), self.TEST_TENANT,
|
||||
datetime.datetime(now.year, now.month, 1,
|
||||
now.hour, now.minute, now.second),
|
||||
now).AndReturn(TEST_RETURN)
|
||||
datetime.datetime(now.year, now.month, now.day, now.hour,
|
||||
now.minute, now.second)) \
|
||||
.AndReturn(self.usage)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
@ -69,19 +99,21 @@ class InstanceViewTests(test.BaseViewTests):
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/overview/usage.html')
|
||||
|
||||
self.assertEqual(res.context['usage'], TEST_RETURN)
|
||||
self.assertTrue(isinstance(res.context['usage'], usage.TenantUsage))
|
||||
|
||||
def test_usage_csv(self):
|
||||
TEST_RETURN = 'testReturn'
|
||||
now = self.override_times()
|
||||
|
||||
self.mox.StubOutWithMock(api, 'usage_get')
|
||||
timestamp = datetime.datetime(self.now.year, self.now.month, 1,
|
||||
self.now.hour, self.now.minute,
|
||||
self.now.second)
|
||||
timestamp = datetime.datetime(now.year, now.month, 1,
|
||||
now.hour, now.minute,
|
||||
now.second)
|
||||
api.usage_get(IsA(http.HttpRequest),
|
||||
self.TEST_TENANT,
|
||||
timestamp,
|
||||
self.now).AndReturn(TEST_RETURN)
|
||||
datetime.datetime(now.year, now.month, now.day, now.hour,
|
||||
now.minute, now.second)) \
|
||||
.AndReturn(self.usage)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
@ -90,42 +122,46 @@ class InstanceViewTests(test.BaseViewTests):
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/overview/usage.csv')
|
||||
|
||||
self.assertEqual(res.context['usage'], TEST_RETURN)
|
||||
self.assertTrue(isinstance(res.context['usage'], usage.TenantUsage))
|
||||
|
||||
def test_usage_exception(self):
|
||||
self.mox.StubOutWithMock(api, 'usage_get')
|
||||
now = self.override_times()
|
||||
|
||||
timestamp = datetime.datetime(self.now.year, self.now.month, 1,
|
||||
self.now.hour, self.now.minute,
|
||||
self.now.second)
|
||||
self.mox.StubOutWithMock(api, 'usage_get')
|
||||
timestamp = datetime.datetime(now.year, now.month, 1, now.hour,
|
||||
now.minute, now.second)
|
||||
exception = nova_exceptions.ClientException(500)
|
||||
api.usage_get(IsA(http.HttpRequest),
|
||||
self.TEST_TENANT,
|
||||
timestamp,
|
||||
self.now).AndRaise(exception)
|
||||
datetime.datetime(now.year, now.month, now.day, now.hour,
|
||||
now.minute, now.second)) \
|
||||
.AndRaise(exception)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:nova:overview:index'))
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/overview/usage.html')
|
||||
self.assertEqual(res.context['usage']._apiresource, None)
|
||||
self.assertEqual(res.context['usage'].usage_list, [])
|
||||
|
||||
def test_usage_default_tenant(self):
|
||||
TEST_RETURN = 'testReturn'
|
||||
now = self.override_times()
|
||||
|
||||
self.mox.StubOutWithMock(api, 'usage_get')
|
||||
timestamp = datetime.datetime(self.now.year, self.now.month, 1,
|
||||
self.now.hour, self.now.minute,
|
||||
self.now.second)
|
||||
timestamp = datetime.datetime(now.year, now.month, 1,
|
||||
now.hour, now.minute,
|
||||
now.second)
|
||||
api.usage_get(IsA(http.HttpRequest),
|
||||
self.TEST_TENANT,
|
||||
timestamp,
|
||||
self.now).AndReturn(TEST_RETURN)
|
||||
datetime.datetime(now.year, now.month, now.day, now.hour,
|
||||
now.minute, now.second)) \
|
||||
.AndReturn(self.usage)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:nova:overview:index'))
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/overview/usage.html')
|
||||
self.assertEqual(res.context['usage'], TEST_RETURN)
|
||||
self.assertTrue(isinstance(res.context['usage'], usage.TenantUsage))
|
||||
|
@ -21,6 +21,8 @@
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
from .views import ProjectOverview
|
||||
|
||||
urlpatterns = patterns('horizon.dashboards.nova.overview.views',
|
||||
url(r'^$', 'usage', name='index'),
|
||||
url(r'^$', ProjectOverview.as_view(), name='index'),
|
||||
)
|
||||
|
@ -18,75 +18,14 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
import horizon
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import time
|
||||
from horizon import usage
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
class ProjectOverview(usage.UsageView):
|
||||
table_class = usage.TenantUsageTable
|
||||
usage_class = usage.TenantUsage
|
||||
template_name = 'nova/overview/usage.html'
|
||||
|
||||
|
||||
def usage(request, tenant_id=None):
|
||||
tenant_id = tenant_id or request.user.tenant_id
|
||||
today = time.today()
|
||||
date_start = datetime.date(today.year, today.month, 1)
|
||||
datetime_start = datetime.datetime.combine(date_start, time.time())
|
||||
datetime_end = time.utcnow()
|
||||
|
||||
show_terminated = request.GET.get('show_terminated', False)
|
||||
|
||||
try:
|
||||
usage = api.usage_get(request, tenant_id, datetime_start, datetime_end)
|
||||
except:
|
||||
usage = api.nova.Usage(None)
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve usage information.'))
|
||||
|
||||
total_ram = 0
|
||||
ram_unit = "MB"
|
||||
|
||||
instances = []
|
||||
terminated = []
|
||||
if hasattr(usage, 'server_usages'):
|
||||
total_ram = usage.total_active_memory_mb
|
||||
now = datetime.datetime.now()
|
||||
for i in usage.server_usages:
|
||||
i['uptime_at'] = now - datetime.timedelta(seconds=i['uptime'])
|
||||
if i['ended_at'] and not show_terminated:
|
||||
terminated.append(i)
|
||||
else:
|
||||
instances.append(i)
|
||||
|
||||
if total_ram >= 1024:
|
||||
ram_unit = "GB"
|
||||
total_ram /= 1024
|
||||
|
||||
if request.GET.get('format', 'html') == 'csv':
|
||||
template = 'nova/overview/usage.csv'
|
||||
mimetype = "text/csv"
|
||||
else:
|
||||
template = 'nova/overview/usage.html'
|
||||
mimetype = "text/html"
|
||||
|
||||
dash_url = horizon.get_dashboard('nova').get_absolute_url()
|
||||
|
||||
return shortcuts.render(request, template, {
|
||||
'usage': usage,
|
||||
'ram_unit': ram_unit,
|
||||
'total_ram': total_ram,
|
||||
'csv_link': '?format=csv',
|
||||
'show_terminated': show_terminated,
|
||||
'datetime_start': datetime_start,
|
||||
'datetime_end': datetime_end,
|
||||
'instances': instances,
|
||||
'dash_url': dash_url},
|
||||
content_type=mimetype)
|
||||
def get_data(self):
|
||||
super(ProjectOverview, self).get_data()
|
||||
return self.usage.get_instances()
|
||||
|
@ -1,11 +1,11 @@
|
||||
Usage Report For Period:,{{datetime_start|date:"b. d Y H:i"}},/,{{datetime_end|date:"b. d Y H:i"}}
|
||||
Tenant ID:,{{usage.tenant_id}}
|
||||
Total Active VCPUs:,{{usage.total_active_vcpus}}
|
||||
CPU-HRs Used:,{{usage.total_vcpus_usage}}
|
||||
Total Active Ram (MB):,{{usage.total_active_memory_mb}}
|
||||
Total Disk Size:,{{usage.total_active_local_gb}}
|
||||
Total Disk Usage:,{{usage.total_local_gb_usage}}
|
||||
Usage Report For Period:,{{ usage.start|date:"b. d Y" }},/,{{ usage.end|date:"b. d Y" }}
|
||||
Tenant ID:,{{ usage.tenant_id }}
|
||||
Total Active VCPUs:,{{ usage.summary.instances }}
|
||||
CPU-HRs Used:,{{ usage.summary.vcpu_hours }}
|
||||
Total Active Ram (MB):,{{ usage.summary.memory_mb }}
|
||||
Total Disk Size:,{{ usage.summary.local_gb }}
|
||||
Total Disk Usage:,{{ usage.summary.disk_gb_hours }}
|
||||
|
||||
ID,Name,VCPUs,RamMB,DiskGB,Usage(Hours),Uptime(Seconds),State
|
||||
{% for server_usage in usage.server_usages %}{{server_usage.id}},{{server_usage.name|addslashes}},{{server_usage.vcpus|addslashes}},{{server_usage.memory_mb|addslashes}},{{server_usage.local_gb|addslashes}},{{server_usage.hours}},{{server_usage.uptime}},{{server_usage.state|capfirst|addslashes}}
|
||||
Name,VCPUs,RamMB,DiskGB,Usage(Hours),Uptime(Seconds),State
|
||||
{% for s in usage.get_instances %}{{ s.name|addslashes }},{{ s.vcpus|addslashes }},{{ s.memory_mb|addslashes }},{{ s.local_gb|addslashes }},{{ s.hours }},{{ s.uptime }},{{ s.state|capfirst|addslashes }}
|
||||
{% endfor %}
|
||||
|
Can't render this file because it contains an unexpected character in line 1 and column 48.
|
@ -7,72 +7,6 @@
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
|
||||
{% if usage.server_usages %}
|
||||
<div id="usage">
|
||||
<div class="usage_block first">
|
||||
<h3>CPU</h3>
|
||||
<ul>
|
||||
<li><span class="quantity">{{ usage.total_active_vcpus|default:0 }}</span><span class="unit">Cores</span> Active</li>
|
||||
<li><span class="quantity">{{ usage.total_vcpus_usage|floatformat|default:0 }}</span><span class="unit">CPU-HR</span> Used</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="usage_block">
|
||||
<h3>RAM</h3>
|
||||
<ul>
|
||||
<li><span class="quantity">{{ total_ram|default:0 }}</span><span class="unit">{{ ram_unit }}</span> Active</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="usage_block last">
|
||||
<h3>Disk</h3>
|
||||
<ul>
|
||||
<li><span class="quantity">{{ usage.total_active_local_gb|default:0 }}</span><span class="unit">GB</span> Active</li>
|
||||
<li><span class="quantity">{{ usage.total_local_gb|floatformat|default:0 }}</span><span class="unit">GB-HR</span> Used</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='table_title wide'>
|
||||
<a class="csv_download_link pull-right" href="{{ csv_link }}">{% trans "Download CSV" %}</a>
|
||||
|
||||
{% if show_terminated %}
|
||||
<a id="toggle_terminated" href="{{dash_url}}">{% trans "Hide Terminated" %}</a>
|
||||
{% else %}
|
||||
<a id="toggle_terminated" href="{{dash_url}}?show_terminated=True">{% trans "Show Terminated" %}</a>
|
||||
{% endif %}
|
||||
|
||||
<h3>Server Usage Summary</h3>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<tr id='headings'>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Size" %}</th>
|
||||
<th>{% trans "Uptime" %}</th>
|
||||
<th>{% trans "State" %}</th>
|
||||
</tr>
|
||||
<tbody class='main'>
|
||||
{% for instance in instances %}
|
||||
{% if instance.ended_at %}
|
||||
<tr class="terminated">
|
||||
{% else %}
|
||||
<tr class="{% cycle 'odd' 'even' %}">
|
||||
{% endif %}
|
||||
<td>{{ instance.name }}</td>
|
||||
<td>{{ instance.memory_mb|mbformat }} Ram | {{ instance.vcpus }} VCPU | {{ instance.local_gb }}GB Disk</td>
|
||||
<td>{{ instance.uptime_at|timesince }}</td>
|
||||
<td>{{ instance.state|lower|capfirst }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan=9>{% trans "No active instances." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
{% include 'nova/instances_and_volumes/instances/_no_instances.html' %}
|
||||
{% endif %}
|
||||
{% include "horizon/common/_usage_summary.html" %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -21,6 +21,8 @@
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('horizon.dashboards.syspanel.overview.views',
|
||||
url(r'^$', 'usage', name='index'),
|
||||
from .views import GlobalOverview
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', GlobalOverview.as_view(), name='index'),
|
||||
)
|
||||
|
@ -18,108 +18,17 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django import shortcuts
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import forms
|
||||
from horizon import exceptions
|
||||
from horizon import usage
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
class GlobalOverview(usage.UsageView):
|
||||
table_class = usage.GlobalUsageTable
|
||||
usage_class = usage.GlobalUsage
|
||||
template_name = 'syspanel/overview/usage.html'
|
||||
|
||||
|
||||
class GlobalSummary(object):
|
||||
def __init__(self, request):
|
||||
self.summary = {}
|
||||
self.request = request
|
||||
self.usage_list = []
|
||||
|
||||
def usage(self, start, end):
|
||||
try:
|
||||
self.usage_list = api.usage_list(self.request, start, end)
|
||||
except:
|
||||
self.usage_list = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve usage information on date'
|
||||
'range %(start)s to %(end)s' % {"start": start,
|
||||
"end": end}))
|
||||
|
||||
# List of attrs on the Usage object that we would like to summarize
|
||||
attrs = ['total_local_gb_usage', 'total_memory_mb_usage',
|
||||
'total_active_memory_mb', 'total_vcpus_usage',
|
||||
'total_active_instances']
|
||||
|
||||
for attr in attrs:
|
||||
for usage in self.usage_list:
|
||||
self.summary.setdefault(attr, 0)
|
||||
self.summary[attr] += getattr(usage, attr)
|
||||
|
||||
@staticmethod
|
||||
def next_month(date_start):
|
||||
return date_start + relativedelta(months=1)
|
||||
|
||||
@staticmethod
|
||||
def current_month():
|
||||
today = datetime.date.today()
|
||||
return datetime.date(today.year, today.month, 1)
|
||||
|
||||
@staticmethod
|
||||
def get_start_and_end_date(year, month, day=1):
|
||||
date_start = datetime.date(year, month, day)
|
||||
date_end = GlobalSummary.next_month(date_start)
|
||||
datetime_start = datetime.datetime.combine(date_start, datetime.time())
|
||||
datetime_end = datetime.datetime.combine(date_end, datetime.time())
|
||||
|
||||
if date_end > datetime.date.today():
|
||||
datetime_end = datetime.datetime.utcnow()
|
||||
return date_start, date_end, datetime_start, datetime_end
|
||||
|
||||
@staticmethod
|
||||
def csv_link(date_start):
|
||||
return "?date_month=%s&date_year=%s&format=csv" % (date_start.month,
|
||||
date_start.year)
|
||||
|
||||
|
||||
def usage(request):
|
||||
today = datetime.date.today()
|
||||
dateform = forms.DateForm(request.GET, initial={'year': today.year,
|
||||
"month": today.month})
|
||||
if dateform.is_valid():
|
||||
req_year = int(dateform.cleaned_data['year'])
|
||||
req_month = int(dateform.cleaned_data['month'])
|
||||
else:
|
||||
req_year = today.year
|
||||
req_month = today.month
|
||||
date_start, date_end, datetime_start, datetime_end = \
|
||||
GlobalSummary.get_start_and_end_date(req_year, req_month)
|
||||
|
||||
global_summary = GlobalSummary(request)
|
||||
if date_start > GlobalSummary.current_month():
|
||||
messages.error(request, _('No data for the selected period'))
|
||||
datetime_end = datetime_start
|
||||
else:
|
||||
global_summary.usage(datetime_start, datetime_end)
|
||||
|
||||
if request.GET.get('format', 'html') == 'csv':
|
||||
template = 'syspanel/tenants/usage.csv'
|
||||
mimetype = "text/csv"
|
||||
else:
|
||||
template = 'syspanel/tenants/global_usage.html'
|
||||
mimetype = "text/html"
|
||||
|
||||
context = {'dateform': dateform,
|
||||
'datetime_start': datetime_start,
|
||||
'datetime_end': datetime_end,
|
||||
'usage_list': global_summary.usage_list,
|
||||
'csv_link': GlobalSummary.csv_link(date_start),
|
||||
'global_summary': global_summary.summary,
|
||||
'external_links': getattr(settings, 'EXTERNAL_MONITORING', [])}
|
||||
|
||||
return shortcuts.render(request, template, context, content_type=mimetype)
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GlobalOverview, self).get_context_data(**kwargs)
|
||||
context['monitoring'] = getattr(settings, 'EXTERNAL_MONITORING', [])
|
||||
return context
|
||||
|
@ -0,0 +1,9 @@
|
||||
Usage Report For Period:,{{ usage.start|date:"b. d Y" }},/,{{ usage.end|date:"b. d Y" }}
|
||||
Active Instances:,{{ usage.summary.instances }}
|
||||
CPU-HRs Used:,{{ usage.summary.vcpu_hours }}
|
||||
Total Active Memory (MB):,{{ usage.summary.memory_mb }}
|
||||
Total Disk Size:,{{ usage.summary.local_gb }}
|
||||
Total Disk Usage:,{{ usage.summary.disk_gb_hours }}
|
||||
|
||||
Tenant,VCPUs,RamMB,DiskGB,Usage(Hours)
|
||||
{% for u in usage.usage_list %}{{ u.tenant_id|addslashes }},{{ u.vcpus|addslashes }},{{ u.memory_mb|addslashes }},{{ u.local_gb|addslashes }},{{ u.vcpu_hours}}{% endfor %}
|
Can't render this file because it contains an unexpected character in line 1 and column 46.
|
@ -0,0 +1,22 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% load i18n sizeformat %}
|
||||
{% block title %}{% trans "Usage Overview" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title="Overview: "|add:"<small>This page shows overall cloud usage.</small>" %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% if monitoring %}
|
||||
<div id="monitoring">
|
||||
<h3>{% trans "Monitoring" %}: </h3>
|
||||
<ul id="external_links">
|
||||
{% for link in monitoring %}
|
||||
<li><a target="_blank" href="{{ link.1 }}">{{ link.0 }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "horizon/common/_usage_summary.html" %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
@ -1,40 +0,0 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% load i18n sizeformat %}
|
||||
{% block title %}Usage Overview{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{# to make searchable false, just remove it from the include statement #}
|
||||
{% include "horizon/common/_page_header.html" with title=_("System Panel Overview") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% if external_links %}
|
||||
<div id="monitoring">
|
||||
<h3>{% trans "Monitoring" %}: </h3>
|
||||
<ul id="external_links">
|
||||
{% for link in external_links %}
|
||||
<li><a target="_blank" href="{{ link.1 }}">{{ link.0}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form action="" method="get" id="date_form" class="form-horizontal">
|
||||
<h3>{% trans "Select a month to query its usage" %}: </h3>
|
||||
<div class="form-row">
|
||||
{{ dateform.month }}
|
||||
{{ dateform.year }}
|
||||
<input class="btn small" type="submit"/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p id="activity">
|
||||
<span><strong>{% trans "Active Instances" %}:</strong> {{ global_summary.total_active_instances|default:'-' }}</span>
|
||||
<span><strong>{% trans "Active Memory" %}:</strong> {{ global_summary.total_active_memory_mb|mbformat|default:'-' }}</span>
|
||||
<span><strong>{% trans "This month's VCPU-Hours" %}:</strong> {{ global_summary.total_vcpus_usage|floatformat|default:'-' }}</span>
|
||||
<span><strong>{% trans "This month's GB-Hours" %}:</strong> {{ global_summary.total_local_gb_usage|floatformat|default:'-' }}</span>
|
||||
</p>
|
||||
|
||||
{% block activity_list %}{% endblock %}
|
||||
|
||||
{% endblock %}
|
@ -1,43 +0,0 @@
|
||||
{% extends 'syspanel/tenants/base_usage.html' %}
|
||||
{% load i18n sizeformat %}
|
||||
{% block title %}Usage Overview{% endblock %}
|
||||
{% block activity_list %}
|
||||
{% if usage_list %}
|
||||
<div id="usage">
|
||||
<table class="table table-striped table-bordered zebra-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Tenant" %}</th>
|
||||
<th>{% trans "Instances" %}</th>
|
||||
<th>{% trans "VCPUs" %}</th>
|
||||
<th>{% trans "Disk" %}</th>
|
||||
<th>{% trans "RAM" %}</th>
|
||||
<th>{% trans "VCPU CPU-Hours" %}</th>
|
||||
<th>{% trans "Disk GB-Hours" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for usage in usage_list %}
|
||||
<tr>
|
||||
<td><a href="{% url horizon:syspanel:tenants:usage usage.tenant_id %}">{{ usage.tenant_id }}</a></td>
|
||||
<td>{{ usage.total_active_instances }}</td>
|
||||
<td>{{ usage.total_active_vcpus }}</td>
|
||||
<td>{{ usage.total_active_local_gb|diskgbformat }}</td>
|
||||
<td>{{ usage.total_active_memory_mb|mbformat }}</td>
|
||||
<td>{{ usage.total_vcpus_usage|floatformat }}</td>
|
||||
<td>{{ usage.total_local_gb_usage|floatformat }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
<span>Server Usage Summary</span>
|
||||
<a class="csv_download_link pull-right" href="{{ csv_link }}">{% trans "Download CSV" %} »</a>
|
||||
</td>
|
||||
</td>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -1,10 +1,11 @@
|
||||
Usage Report For Period:,{{datetime_start|date:"b. d Y H:i"}},/,{{datetime_end|date:"b. d Y H:i"}}
|
||||
Active Instances:,{{global_summary.total_active_instances|default:'-'}}
|
||||
Active Memory (MB):,{{global_summary.total_active_memory_mb|default:'-'}}
|
||||
This month's VCPU-Hours:,{{global_summary.total_vcpus_usage|floatformat|default:'-'}}
|
||||
This month's GB-Hours:,{{global_summary.total_local_gb_usage|floatformat|default:'-'}}
|
||||
This month's MemoryMB-Hours:,{{global_summary.total_memory_mb_usage|floatformat|default:'-'}}
|
||||
Usage Report For Period:,{{ usage.start|date:"b. d Y" }},/,{{ usage.end|date:"b. d Y" }}
|
||||
Tenant ID:,{{ usage.tenant_id }}
|
||||
Total Active VCPUs:,{{ usage.summary.instances }}
|
||||
CPU-HRs Used:,{{ usage.summary.vcpu_hours }}
|
||||
Total Active Ram (MB):,{{ usage.summary.memory_mb }}
|
||||
Total Disk Size:,{{ usage.summary.local_gb }}
|
||||
Total Disk Usage:,{{ usage.summary.disk_gb_hours }}
|
||||
|
||||
Tenant,Name,VCPUs,RamMB,DiskGB,Usage(Hours),Uptime(Seconds),State
|
||||
{% for usage in usage_list %}{% for server_usage in usage.server_usages %}{{server_usage.tenant_id|addslashes}},{{server_usage.name|addslashes}},{{server_usage.vcpus|addslashes}},{{server_usage.memory_mb|addslashes}},{{server_usage.local_gb|addslashes}},{{server_usage.hours}},{{server_usage.uptime}},{{server_usage.state|capfirst|addslashes}}{% endfor %}
|
||||
Name,VCPUs,RamMB,DiskGB,Usage(Hours),Uptime(Seconds),State
|
||||
{% for s in usage.get_instances %}{{ s.name|addslashes }},{{ s.vcpus|addslashes }},{{ s.memory_mb|addslashes }},{{ s.local_gb|addslashes }},{{ s.hours }},{{ s.uptime }},{{ s.state|capfirst|addslashes }}
|
||||
{% endfor %}
|
||||
|
Can't render this file because it contains an unexpected character in line 1 and column 48.
|
@ -1,39 +1,8 @@
|
||||
{% extends 'syspanel/tenants/base_usage.html' %}
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% load i18n sizeformat %}
|
||||
{% block title %}Usage Overview{% endblock %}
|
||||
{% block activity_list %}
|
||||
{% if instances %}
|
||||
<div id="usage">
|
||||
{% block title %}{% trans "Tenant Usage Overview" %}{% endblock %}
|
||||
|
||||
<table class="table table-striped table-bordered zebra-striped">
|
||||
<thead>
|
||||
<tr id="headings">
|
||||
<th>{% trans "Instances" %}</th>
|
||||
<th>{% trans "VCPUs" %}</th>
|
||||
<th>{% trans "Disk" %}</th>
|
||||
<th>{% trans "RAM" %}</th>
|
||||
<th>{% trans "Hours" %}</th>
|
||||
<th>{% trans "Uptime" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for instance in instances %}
|
||||
<tr>
|
||||
<td>{{ instance.name }}</td>
|
||||
<td>{{ instance.vcpus }}</td>
|
||||
<td>{{ instance.local_gb|diskgbformat }}</td>
|
||||
<td>{{ instance.memory_mb|mbformat }}</td>
|
||||
<td>{{ instance.hours|floatformat}}</td>
|
||||
<td>{{ instance.uptime_at|timesince}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<td colspan="7">
|
||||
<span>Tenant Server Usage Summary.</span>
|
||||
<a class="csv_download_link pull-right" href="{{ csv_link }}">{% trans "Download CSV" %} »</a>
|
||||
</td>
|
||||
</tfoot>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block syspanel_main %}
|
||||
{% include "horizon/common/_usage_summary.html" %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -21,17 +21,18 @@
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
from .views import (IndexView, CreateView, UpdateView, QuotasView, UsersView,
|
||||
AddUserView)
|
||||
AddUserView, TenantUsageView)
|
||||
|
||||
|
||||
urlpatterns = patterns('horizon.dashboards.syspanel.tenants.views',
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', IndexView.as_view(), name='index'),
|
||||
url(r'^create$', CreateView.as_view(), name='create'),
|
||||
url(r'^(?P<tenant_id>[^/]+)/update/$',
|
||||
UpdateView.as_view(), name='update'),
|
||||
url(r'^(?P<tenant_id>[^/]+)/quotas/$',
|
||||
QuotasView.as_view(), name='quotas'),
|
||||
url(r'^(?P<tenant_id>[^/]+)/usage/$', 'usage', name='usage'),
|
||||
url(r'^(?P<tenant_id>[^/]+)/usage/$',
|
||||
TenantUsageView.as_view(), name='usage'),
|
||||
url(r'^(?P<tenant_id>[^/]+)/users/$', UsersView.as_view(), name='users'),
|
||||
url(r'^(?P<tenant_id>[^/]+)/users/(?P<user_id>[^/]+)/add/$',
|
||||
AddUserView.as_view(), name='add_user')
|
||||
|
@ -18,11 +18,9 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import operator
|
||||
|
||||
from django import shortcuts
|
||||
from django import http
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
@ -33,11 +31,10 @@ from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tables
|
||||
from horizon import usage
|
||||
from .forms import AddUser, CreateTenant, UpdateTenant, UpdateQuotas
|
||||
from .tables import TenantsTable, TenantUsersTable, AddUsersTable
|
||||
|
||||
from horizon.dashboards.syspanel.overview.views import GlobalSummary
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -181,60 +178,11 @@ class QuotasView(forms.ModalFormView):
|
||||
'cores': quotas.cores}
|
||||
|
||||
|
||||
def usage(request, tenant_id):
|
||||
today = datetime.date.today()
|
||||
dateform = forms.DateForm(request.GET, initial={'year': today.year,
|
||||
"month": today.month})
|
||||
if dateform.is_valid():
|
||||
req_year = int(dateform.cleaned_data['year'])
|
||||
req_month = int(dateform.cleaned_data['month'])
|
||||
else:
|
||||
req_year = today.year
|
||||
req_month = today.month
|
||||
date_start, date_end, datetime_start, datetime_end = \
|
||||
GlobalSummary.get_start_and_end_date(req_year, req_month)
|
||||
class TenantUsageView(usage.UsageView):
|
||||
table_class = usage.TenantUsageTable
|
||||
usage_class = usage.TenantUsage
|
||||
template_name = 'syspanel/tenants/usage.html'
|
||||
|
||||
if date_start > GlobalSummary.current_month():
|
||||
messages.error(request, _('No data for the selected period'))
|
||||
datetime_end = datetime_start
|
||||
|
||||
usage = {}
|
||||
try:
|
||||
usage = api.usage_get(request, tenant_id, datetime_start, datetime_end)
|
||||
except api_exceptions.ApiException, e:
|
||||
LOG.exception('ApiException getting usage info for tenant "%s"'
|
||||
' on date range "%s to %s"' % (tenant_id,
|
||||
datetime_start,
|
||||
datetime_end))
|
||||
messages.error(request, _('Unable to get usage info: %s') % e.message)
|
||||
|
||||
running_instances = []
|
||||
terminated_instances = []
|
||||
if hasattr(usage, 'server_usages'):
|
||||
now = datetime.datetime.now()
|
||||
for i in usage.server_usages:
|
||||
# this is just a way to phrase uptime in a way that is compatible
|
||||
# with the 'timesince' filter. Use of local time intentional
|
||||
i['uptime_at'] = now - datetime.timedelta(seconds=i['uptime'])
|
||||
if i['ended_at']:
|
||||
terminated_instances.append(i)
|
||||
else:
|
||||
running_instances.append(i)
|
||||
|
||||
if request.GET.get('format', 'html') == 'csv':
|
||||
template = 'syspanel/tenants/usage.csv'
|
||||
mimetype = "text/csv"
|
||||
else:
|
||||
template = 'syspanel/tenants/usage.html'
|
||||
mimetype = "text/html"
|
||||
|
||||
context = {'dateform': dateform,
|
||||
'datetime_start': datetime_start,
|
||||
'datetime_end': datetime_end,
|
||||
'global_summary': usage,
|
||||
'usage_list': [usage],
|
||||
'csv_link': GlobalSummary.csv_link(date_start),
|
||||
'instances': running_instances + terminated_instances,
|
||||
'tenant_id': tenant_id}
|
||||
|
||||
return shortcuts.render(request, template, context, content_type=mimetype)
|
||||
def get_data(self):
|
||||
super(TenantUsageView, self).get_data()
|
||||
return self.usage.get_instances()
|
||||
|
@ -209,7 +209,8 @@ class LinkAction(BaseAction):
|
||||
.. attribute:: url
|
||||
|
||||
A string or a callable which resolves to a url to be used as the link
|
||||
target. (Required)
|
||||
target. You must either define the ``url`` attribute or a override
|
||||
the ``get_link_url`` method on the class.
|
||||
"""
|
||||
method = "GET"
|
||||
bound_url = None
|
||||
@ -224,9 +225,6 @@ class LinkAction(BaseAction):
|
||||
if not self.verbose_name:
|
||||
raise NotImplementedError('A LinkAction object must have a '
|
||||
'verbose_name attribute.')
|
||||
if not self.url:
|
||||
raise NotImplementedError('A LinkAction object must have a '
|
||||
'url attribute.')
|
||||
if attrs:
|
||||
self.attrs.update(attrs)
|
||||
|
||||
@ -240,6 +238,10 @@ class LinkAction(BaseAction):
|
||||
When called for a row action, the current row data object will be
|
||||
passed as the first parameter.
|
||||
"""
|
||||
if not self.url:
|
||||
raise NotImplementedError('A LinkAction class must have a '
|
||||
'url attribute or define its own '
|
||||
'get_link_url method.')
|
||||
if callable(self.url):
|
||||
return self.url(datum, **self.kwargs)
|
||||
try:
|
||||
|
17
horizon/horizon/templates/horizon/common/_usage_summary.html
Normal file
17
horizon/horizon/templates/horizon/common/_usage_summary.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
<form action="" method="get" id="date_form">
|
||||
<h3>{% trans "Select a month to query its usage" %}: </h3>
|
||||
<div class="form-row">
|
||||
{{ form.month }}
|
||||
{{ form.year }}
|
||||
<input class="btn small" type="submit"/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p id="activity">
|
||||
<span><strong>{% trans "Active Instances" %}:</strong> {{ usage.summary.instances|default:'-' }}</span>
|
||||
<span><strong>{% trans "Active Memory" %}:</strong> {{ usage.summary.memory_mb|mbformat|default:'-' }}</span>
|
||||
<span><strong>{% trans "This Month's VCPU-Hours" %}:</strong> {{ usage.summary.vcpu_hours|floatformat|default:'-' }}</span>
|
||||
<span><strong>{% trans "This Month's GB-Hours" %}:</strong> {{ usage.summary.disk_gb_hours|floatformat|default:'-' }}</span>
|
||||
</p>
|
@ -1,11 +1,11 @@
|
||||
import datetime
|
||||
|
||||
|
||||
def time():
|
||||
def time(hour=0, minute=0, second=0, microsecond=0):
|
||||
'''Overrideable version of datetime.datetime.today'''
|
||||
if time.override_time:
|
||||
return time.override_time
|
||||
return datetime.time()
|
||||
return datetime.time(hour, minute, second, microsecond)
|
||||
|
||||
time.override_time = None
|
||||
|
||||
@ -14,7 +14,7 @@ def today():
|
||||
'''Overridable version of datetime.datetime.today'''
|
||||
if today.override_time:
|
||||
return today.override_time
|
||||
return datetime.datetime.today()
|
||||
return datetime.date.today()
|
||||
|
||||
today.override_time = None
|
||||
|
||||
|
19
horizon/horizon/usage/__init__.py
Normal file
19
horizon/horizon/usage/__init__.py
Normal file
@ -0,0 +1,19 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from .base import BaseUsage, TenantUsage, GlobalUsage
|
||||
from .views import UsageView
|
||||
from .tables import BaseUsageTable, TenantUsageTable, GlobalUsageTable
|
138
horizon/horizon/usage/base.py
Normal file
138
horizon/horizon/usage/base.py
Normal file
@ -0,0 +1,138 @@
|
||||
from __future__ import division
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import time
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseUsage(object):
|
||||
show_terminated = False
|
||||
|
||||
def __init__(self, request, tenant_id=None):
|
||||
self.tenant_id = tenant_id or request.user.tenant_id
|
||||
self.request = request
|
||||
self.summary = {}
|
||||
self.usage_list = []
|
||||
|
||||
@property
|
||||
def today(self):
|
||||
return time.today()
|
||||
|
||||
@staticmethod
|
||||
def get_datetime(date, now=False):
|
||||
if now:
|
||||
now = time.utcnow()
|
||||
current_time = time.time(now.hour, now.minute, now.second)
|
||||
else:
|
||||
current_time = time.time()
|
||||
return datetime.datetime.combine(date, current_time)
|
||||
|
||||
@staticmethod
|
||||
def get_start(year, month, day=1):
|
||||
return datetime.date(year, month, day)
|
||||
|
||||
@staticmethod
|
||||
def get_end(year, month, day=1):
|
||||
period = relativedelta(months=1)
|
||||
date_end = BaseUsage.get_start(year, month, day) + period
|
||||
if date_end > time.today():
|
||||
date_end = time.today()
|
||||
return date_end
|
||||
|
||||
def get_instances(self):
|
||||
instance_list = []
|
||||
[instance_list.extend(u.server_usages) for u in self.usage_list]
|
||||
return instance_list
|
||||
|
||||
def get_date_range(self):
|
||||
if not hasattr(self, "start") or not hasattr(self, "end"):
|
||||
args = (self.today.year, self.today.month)
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
args = (int(form.cleaned_data['year']),
|
||||
int(form.cleaned_data['month']))
|
||||
self.start = self.get_start(*args)
|
||||
self.end = self.get_end(*args)
|
||||
return self.start, self.end
|
||||
|
||||
def get_form(self):
|
||||
if not hasattr(self, 'form'):
|
||||
self.form = forms.DateForm(self.request.GET,
|
||||
initial={'year': self.today.year,
|
||||
'month': self.today.month})
|
||||
return self.form
|
||||
|
||||
def get_usage_list(self, start, end):
|
||||
raise NotImplementedError("You must define a get_usage method.")
|
||||
|
||||
def get_summary(self):
|
||||
raise NotImplementedError("You must define a get_summary method.")
|
||||
|
||||
def summarize(self, start, end):
|
||||
if start <= end <= time.today():
|
||||
# Convert to datetime.datetime just for API call.
|
||||
start = BaseUsage.get_datetime(start)
|
||||
end = BaseUsage.get_datetime(end, now=True)
|
||||
try:
|
||||
self.usage_list = self.get_usage_list(start, end)
|
||||
except:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve usage information.'))
|
||||
else:
|
||||
messages.info(self.request,
|
||||
_("You are viewing data for the future, "
|
||||
"which may or may not exist."))
|
||||
|
||||
for tenant_usage in self.usage_list:
|
||||
tenant_summary = tenant_usage.get_summary()
|
||||
for key, value in tenant_summary.items():
|
||||
self.summary.setdefault(key, 0)
|
||||
self.summary[key] += value
|
||||
|
||||
def csv_link(self):
|
||||
return "?date_month=%s&date_year=%s&format=csv" % self.get_date_range()
|
||||
|
||||
|
||||
class GlobalUsage(BaseUsage):
|
||||
show_terminated = True
|
||||
|
||||
def get_usage_list(self, start, end):
|
||||
return api.usage_list(self.request, start, end)
|
||||
|
||||
|
||||
class TenantUsage(BaseUsage):
|
||||
attrs = ('memory_mb', 'vcpus', 'uptime',
|
||||
'hours', 'local_gb')
|
||||
|
||||
def get_usage_list(self, start, end):
|
||||
show_terminated = self.request.GET.get('show_terminated',
|
||||
self.show_terminated)
|
||||
instances = []
|
||||
terminated_instances = []
|
||||
usage = api.usage_get(self.request, self.tenant_id, start, end)
|
||||
# Attribute may not exist if there are no instances
|
||||
if hasattr(usage, 'server_usages'):
|
||||
now = datetime.datetime.now()
|
||||
for server_usage in usage.server_usages:
|
||||
# This is a way to phrase uptime in a way that is compatible
|
||||
# with the 'timesince' filter. (Use of local time intentional.)
|
||||
server_uptime = server_usage['uptime']
|
||||
total_uptime = now - datetime.timedelta(seconds=server_uptime)
|
||||
server_usage['uptime_at'] = total_uptime
|
||||
if server_usage['ended_at'] and not show_terminated:
|
||||
terminated_instances.append(server_usage)
|
||||
else:
|
||||
instances.append(server_usage)
|
||||
usage.server_usages = instances
|
||||
return (usage,)
|
56
horizon/horizon/usage/tables.py
Normal file
56
horizon/horizon/usage/tables.py
Normal file
@ -0,0 +1,56 @@
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.template.defaultfilters import timesince
|
||||
|
||||
from horizon import tables
|
||||
from horizon.templatetags.sizeformat import mbformat
|
||||
|
||||
|
||||
class CSVSummary(tables.LinkAction):
|
||||
name = "csv_summary"
|
||||
verbose_name = _("Download CSV Summary")
|
||||
|
||||
def get_link_url(self, usage=None):
|
||||
return self.table.kwargs['usage'].csv_link()
|
||||
|
||||
|
||||
class BaseUsageTable(tables.DataTable):
|
||||
vcpus = tables.Column('vcpus', verbose_name=_("VCPUs"))
|
||||
disk = tables.Column('local_gb', verbose_name=_("Disk"))
|
||||
memory = tables.Column('memory_mb',
|
||||
verbose_name=_("RAM"),
|
||||
filters=(mbformat,))
|
||||
hours = tables.Column('vcpu_hours', verbose_name=_("VCPU Hours"))
|
||||
|
||||
|
||||
class GlobalUsageTable(BaseUsageTable):
|
||||
tenant = tables.Column('tenant_id', verbose_name=_("Tenant ID"))
|
||||
disk_hours = tables.Column('disk_gb_hours',
|
||||
verbose_name=_("Disk GB Hours"))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return datum.tenant_id
|
||||
|
||||
class Meta:
|
||||
name = "global_usage"
|
||||
verbose_name = _("Usage Summary")
|
||||
columns = ("tenant", "vcpus", "disk", "memory",
|
||||
"hours", "disk_hours")
|
||||
table_actions = (CSVSummary,)
|
||||
multi_select = False
|
||||
|
||||
|
||||
class TenantUsageTable(BaseUsageTable):
|
||||
instance = tables.Column('name')
|
||||
uptime = tables.Column('uptime_at',
|
||||
verbose_name=_("Uptime"),
|
||||
filters=(timesince,))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return datum['name']
|
||||
|
||||
class Meta:
|
||||
name = "tenant_usage"
|
||||
verbose_name = _("Usage Summary")
|
||||
columns = ("instance", "vcpus", "disk", "memory", "uptime")
|
||||
table_actions = (CSVSummary,)
|
||||
multi_select = False
|
52
horizon/horizon/usage/views.py
Normal file
52
horizon/horizon/usage/views.py
Normal file
@ -0,0 +1,52 @@
|
||||
import logging
|
||||
|
||||
from horizon import tables
|
||||
from .base import BaseUsage
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UsageView(tables.DataTableView):
|
||||
usage_class = None
|
||||
show_terminated = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UsageView, self).__init__(*args, **kwargs)
|
||||
if not issubclass(self.usage_class, BaseUsage):
|
||||
raise AttributeError("You must specify a usage_class attribute "
|
||||
"which is a subclass of BaseUsage.")
|
||||
|
||||
def get_template_names(self):
|
||||
if self.request.GET.get('format', 'html') == 'csv':
|
||||
return ".".join((self.template_name.rsplit('.', 1)[0], 'csv'))
|
||||
return self.template_name
|
||||
|
||||
def get_content_type(self):
|
||||
if self.request.GET.get('format', 'html') == 'csv':
|
||||
return "text/csv"
|
||||
return "text/html"
|
||||
|
||||
def get_data(self):
|
||||
tenant_id = self.kwargs.get('tenant_id', self.request.user.tenant_id)
|
||||
self.usage = self.usage_class(self.request, tenant_id)
|
||||
self.usage.summarize(*self.usage.get_date_range())
|
||||
self.kwargs['usage'] = self.usage
|
||||
return self.usage.usage_list
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UsageView, self).get_context_data(**kwargs)
|
||||
context['form'] = self.usage.form
|
||||
context['usage'] = self.usage
|
||||
return context
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
resp = self.response_class(request=self.request,
|
||||
template=self.get_template_names(),
|
||||
context=context,
|
||||
content_type=self.get_content_type(),
|
||||
**response_kwargs)
|
||||
if self.request.GET.get('format', 'html') == 'csv':
|
||||
resp['Content-Disposition'] = 'attachment; filename=usage.csv'
|
||||
resp['Content-Type'] = 'text/csv'
|
||||
return resp
|
@ -551,10 +551,10 @@ table form {
|
||||
min-width: 735px;
|
||||
padding: 5px 0 5px 0;
|
||||
border: 1px solid #e6e6e6;
|
||||
margin-bottom: 25px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#activity.tenant { margin: 0 0 0 0; }
|
||||
#activity span { margin: 0 0 0 10px; }
|
||||
|
||||
#activity strong {
|
||||
@ -574,10 +574,10 @@ table form {
|
||||
}
|
||||
|
||||
#monitoring h3{
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
float: left;
|
||||
margin-top: -8px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
#external_links, #external_links li {
|
||||
|
15
run_tests.sh
15
run_tests.sh
@ -6,7 +6,7 @@ set -o errexit
|
||||
# Increment me any time the environment should be rebuilt.
|
||||
# This includes dependncy changes, directory renames, etc.
|
||||
# Simple integer secuence: 1, 2, 3...
|
||||
environment_version=8
|
||||
environment_version=10
|
||||
#--------------------------------------------------------#
|
||||
|
||||
function usage {
|
||||
@ -205,6 +205,9 @@ function sanity_check {
|
||||
selenium=0
|
||||
fi
|
||||
fi
|
||||
# Remove .pyc files. This is sanity checking because they can linger
|
||||
# after old files are deleted.
|
||||
find . -name "*.pyc" -exec rm -rf {} \;
|
||||
}
|
||||
|
||||
function backup_environment {
|
||||
@ -249,13 +252,9 @@ function install_venv {
|
||||
if [ $quiet -eq 1 ]; then
|
||||
export PIP_NO_INPUT=true
|
||||
fi
|
||||
INSTALL_FAILED=0
|
||||
python tools/install_venv.py || INSTALL_FAILED=1
|
||||
if [ $INSTALL_FAILED -eq 1 ]; then
|
||||
echo "Error updating environment with pip, trying without src packages..."
|
||||
rm -rf $venv/src
|
||||
python tools/install_venv.py
|
||||
fi
|
||||
echo "Fetching new src packages..."
|
||||
rm -rf $venv/src
|
||||
python tools/install_venv.py
|
||||
command_wrapper="$root/${with_venv}"
|
||||
# Make sure it worked and record the environment version
|
||||
sanity_check
|
||||
|
Loading…
x
Reference in New Issue
Block a user