Merge "Improvements in csv export for usage data"
This commit is contained in:
commit
818b92dcf0
@ -171,8 +171,11 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
|||||||
@test.create_stubs({api.nova: ('flavor_list', 'server_list',),
|
@test.create_stubs({api.nova: ('flavor_list', 'server_list',),
|
||||||
api.keystone: ('tenant_list',)})
|
api.keystone: ('tenant_list',)})
|
||||||
def test_index_options_after_migrate(self):
|
def test_index_options_after_migrate(self):
|
||||||
server = self.servers.first()
|
servers = self.servers.list()
|
||||||
server.status = "VERIFY_RESIZE"
|
server1 = servers[0]
|
||||||
|
server1.status = "VERIFY_RESIZE"
|
||||||
|
server2 = servers[2]
|
||||||
|
server2.status = "VERIFY_RESIZE"
|
||||||
api.keystone.tenant_list(IsA(http.HttpRequest)) \
|
api.keystone.tenant_list(IsA(http.HttpRequest)) \
|
||||||
.AndReturn(self.tenants.list())
|
.AndReturn(self.tenants.list())
|
||||||
search_opts = {'marker': None, 'paginate': True}
|
search_opts = {'marker': None, 'paginate': True}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
Usage Report For Period:,{{ usage.start|date:"b. d Y" }},{{ usage.end|date:"b. d Y" }}
|
{% load i18n %}{% trans "Usage Report For Period" %}:,{{ usage.start|date:"b. d Y" }},{{ usage.end|date:"b. d Y" }}
|
||||||
Active Instances:,{{ usage.summary.instances }}
|
{% trans "Active Instances" %}:,{{ usage.summary.instances }}
|
||||||
CPU-HRs Used:,{{ usage.summary.vcpu_hours|floatformat:2 }}
|
{% trans "CPU-HRs Used" %}:,{{ usage.summary.vcpu_hours|floatformat:2 }}
|
||||||
Total Active RAM (MB):,{{ usage.summary.memory_mb }}
|
{% trans "Total Active RAM (MB)" %}:,{{ usage.summary.memory_mb }}
|
||||||
Total Disk Size:,{{ usage.summary.local_gb }}
|
{% trans "Total Disk Size" %}:,{{ usage.summary.local_gb }}
|
||||||
Total Disk Usage:,{{ usage.summary.disk_gb_hours|floatformat:2 }}
|
{% trans "Total Disk Usage" %}:,{{ usage.summary.disk_gb_hours|floatformat:2 }}
|
||||||
|
|
||||||
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|floatformat:2}}
|
|
||||||
{% endfor %}
|
|
||||||
|
Can't render this file because it contains an unexpected character in line 1 and column 46.
|
@ -38,6 +38,7 @@ INDEX_URL = reverse('horizon:project:overview:index')
|
|||||||
|
|
||||||
|
|
||||||
class UsageViewTests(test.BaseAdminViewTests):
|
class UsageViewTests(test.BaseAdminViewTests):
|
||||||
|
|
||||||
@test.create_stubs({api.nova: ('usage_list',),
|
@test.create_stubs({api.nova: ('usage_list',),
|
||||||
quotas: ('tenant_quota_usages',),
|
quotas: ('tenant_quota_usages',),
|
||||||
api.keystone: ('tenant_list',)})
|
api.keystone: ('tenant_list',)})
|
||||||
@ -75,24 +76,26 @@ class UsageViewTests(test.BaseAdminViewTests):
|
|||||||
api.keystone: ('tenant_list',)})
|
api.keystone: ('tenant_list',)})
|
||||||
def test_usage_csv(self):
|
def test_usage_csv(self):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
usage_obj = api.nova.NovaUsage(self.usages.first())
|
usage_obj = [api.nova.NovaUsage(u) for u in self.usages.list()]
|
||||||
quota_data = self.quota_usages.first()
|
quota_data = self.quota_usages.first()
|
||||||
api.keystone.tenant_list(IsA(http.HttpRequest)) \
|
api.keystone.tenant_list(IsA(http.HttpRequest)) \
|
||||||
.AndReturn(self.tenants.list())
|
.AndReturn(self.tenants.list())
|
||||||
api.nova.usage_list(IsA(http.HttpRequest),
|
api.nova.usage_list(IsA(http.HttpRequest),
|
||||||
datetime.datetime(now.year, now.month, 1, 0, 0, 0),
|
datetime.datetime(now.year, now.month, 1, 0, 0, 0),
|
||||||
Func(usage.almost_now)) \
|
Func(usage.almost_now)) \
|
||||||
.AndReturn([usage_obj, usage_obj])
|
.AndReturn(usage_obj)
|
||||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
|
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
csv_url = reverse('horizon:admin:overview:index') + "?format=csv"
|
csv_url = reverse('horizon:admin:overview:index') + "?format=csv"
|
||||||
res = self.client.get(csv_url)
|
res = self.client.get(csv_url)
|
||||||
self.assertTemplateUsed(res, 'admin/overview/usage.csv')
|
self.assertTemplateUsed(res, 'admin/overview/usage.csv')
|
||||||
self.assertTrue(isinstance(res.context['usage'], usage.GlobalUsage))
|
self.assertTrue(isinstance(res.context['usage'], usage.GlobalUsage))
|
||||||
hdr = 'Tenant,VCPUs,RamMB,DiskGB,Usage(Hours)'
|
hdr = 'Project Name,VCPUs,Ram (MB),Disk (GB),Usage (Hours)'
|
||||||
row = '%s,%s,%s,%s,%.2f' % (usage_obj.tenant_id,
|
self.assertContains(res, '%s\r\n' % (hdr))
|
||||||
usage_obj.vcpus,
|
for obj in usage_obj:
|
||||||
usage_obj.memory_mb,
|
row = u'{0},{1},{2},{3},{4:.2f}\r\n'.format(obj.project_name,
|
||||||
usage_obj.disk_gb_hours,
|
obj.vcpus,
|
||||||
usage_obj.vcpu_hours)
|
obj.memory_mb,
|
||||||
self.assertContains(res, '%s\n%s\n%s\n' % (hdr, row, row))
|
obj.disk_gb_hours,
|
||||||
|
obj.vcpu_hours)
|
||||||
|
self.assertContains(res, row)
|
||||||
|
@ -18,19 +18,38 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from django import VERSION
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.template.defaultfilters import floatformat
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from horizon import exceptions
|
from horizon import exceptions
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard import usage
|
from openstack_dashboard import usage
|
||||||
|
from openstack_dashboard.usage.base import BaseCsvResponse
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalUsageCsvRenderer(BaseCsvResponse):
|
||||||
|
|
||||||
|
columns = [_("Project Name"), _("VCPUs"), _("Ram (MB)"),
|
||||||
|
_("Disk (GB)"), _("Usage (Hours)")]
|
||||||
|
|
||||||
|
def get_row_data(self):
|
||||||
|
|
||||||
|
for u in self.context['usage'].usage_list:
|
||||||
|
yield (u.project_name or u.tenant_id,
|
||||||
|
u.vcpus,
|
||||||
|
u.memory_mb,
|
||||||
|
u.local_gb,
|
||||||
|
floatformat(u.vcpu_hours, 2))
|
||||||
|
|
||||||
|
|
||||||
class GlobalOverview(usage.UsageView):
|
class GlobalOverview(usage.UsageView):
|
||||||
table_class = usage.GlobalUsageTable
|
table_class = usage.GlobalUsageTable
|
||||||
usage_class = usage.GlobalUsage
|
usage_class = usage.GlobalUsage
|
||||||
template_name = 'admin/overview/usage.html'
|
template_name = 'admin/overview/usage.html'
|
||||||
|
csv_response_class = GlobalUsageCsvRenderer
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(GlobalOverview, self).get_context_data(**kwargs)
|
context = super(GlobalOverview, self).get_context_data(**kwargs)
|
||||||
@ -39,17 +58,17 @@ class GlobalOverview(usage.UsageView):
|
|||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
data = super(GlobalOverview, self).get_data()
|
data = super(GlobalOverview, self).get_data()
|
||||||
# Pre-fill tenant names
|
# Pre-fill project names
|
||||||
try:
|
try:
|
||||||
tenants = api.keystone.tenant_list(self.request)
|
projects = api.keystone.tenant_list(self.request)
|
||||||
except:
|
except:
|
||||||
tenants = []
|
projects = []
|
||||||
exceptions.handle(self.request,
|
exceptions.handle(self.request,
|
||||||
_('Unable to retrieve project list.'))
|
_('Unable to retrieve project list.'))
|
||||||
for instance in data:
|
for instance in data:
|
||||||
tenant = filter(lambda t: t.id == instance.tenant_id, tenants)
|
project = filter(lambda t: t.id == instance.tenant_id, projects)
|
||||||
if tenant:
|
if project:
|
||||||
instance.tenant_name = getattr(tenant[0], "name", None)
|
instance.project_name = getattr(project[0], "name", None)
|
||||||
else:
|
else:
|
||||||
instance.tenant_name = None
|
instance.project_name = None
|
||||||
return data
|
return data
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
from django.conf.urls.defaults import patterns, url
|
from django.conf.urls.defaults import patterns, url
|
||||||
|
|
||||||
from .views import (IndexView, TenantUsageView,
|
from .views import (IndexView, ProjectUsageView,
|
||||||
CreateProjectView, UpdateProjectView,
|
CreateProjectView, UpdateProjectView,
|
||||||
CreateUserView)
|
CreateUserView)
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ urlpatterns = patterns('',
|
|||||||
url(r'^(?P<tenant_id>[^/]+)/update/$',
|
url(r'^(?P<tenant_id>[^/]+)/update/$',
|
||||||
UpdateProjectView.as_view(), name='update'),
|
UpdateProjectView.as_view(), name='update'),
|
||||||
url(r'^(?P<tenant_id>[^/]+)/usage/$',
|
url(r'^(?P<tenant_id>[^/]+)/usage/$',
|
||||||
TenantUsageView.as_view(), name='usage'),
|
ProjectUsageView.as_view(), name='usage'),
|
||||||
url(r'^(?P<tenant_id>[^/]+)/create_user/$',
|
url(r'^(?P<tenant_id>[^/]+)/create_user/$',
|
||||||
CreateUserView.as_view(), name='create_user'),
|
CreateUserView.as_view(), name='create_user'),
|
||||||
)
|
)
|
||||||
|
@ -116,13 +116,13 @@ class UsersView(tables.MultiTableView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class TenantUsageView(usage.UsageView):
|
class ProjectUsageView(usage.UsageView):
|
||||||
table_class = usage.TenantUsageTable
|
table_class = usage.ProjectUsageTable
|
||||||
usage_class = usage.TenantUsage
|
usage_class = usage.ProjectUsage
|
||||||
template_name = 'admin/projects/usage.html'
|
template_name = 'admin/projects/usage.html'
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
super(TenantUsageView, self).get_data()
|
super(ProjectUsageView, self).get_data()
|
||||||
return self.usage.get_instances()
|
return self.usage.get_instances()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
Usage Report For Period:,{{ usage.start|date:"b. d Y" }},{{ usage.end|date:"b. d Y" }}
|
{% load i18n %}{% trans "Usage Report For Period" %}:,{{ usage.start|date:"b. d Y" }},{{ usage.end|date:"b. d Y" }}
|
||||||
Tenant ID:,{{ usage.tenant_id }}
|
{% trans "Project ID" %}:,{{ usage.project_id }}
|
||||||
Total Active VCPUs:,{{ usage.summary.instances }}
|
{% trans "Project Name" %}:,{{ usage.project_name }}
|
||||||
CPU-HRs Used:,{{ usage.summary.vcpu_hours|floatformat:2 }}
|
{% trans "Total Active VCPUs" %}:,{{ usage.summary.instances }}
|
||||||
Total Active Ram (MB):,{{ usage.summary.memory_mb }}
|
{% trans "CPU-HRs Used" %}:,{{ usage.summary.vcpu_hours|floatformat:2 }}
|
||||||
Total Disk Size:,{{ usage.summary.local_gb }}
|
{% trans "Total Active Ram (MB)" %}:,{{ usage.summary.memory_mb }}
|
||||||
Total Disk Usage:,{{ usage.summary.disk_gb_hours|floatformat:2 }}
|
{% trans "Total Disk Size" %}:,{{ usage.summary.local_gb }}
|
||||||
|
{% trans "Total Disk Usage" %}:,{{ usage.summary.disk_gb_hours|floatformat:2 }}
|
||||||
|
|
||||||
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|floatformat:2 }},{{ s.uptime }},{{ s.state|capfirst|addslashes }}
|
|
||||||
{% endfor %}
|
|
||||||
|
Can't render this file because it contains an unexpected character in line 1 and column 46.
|
@ -36,22 +36,27 @@ INDEX_URL = reverse('horizon:project:overview:index')
|
|||||||
|
|
||||||
|
|
||||||
class UsageViewTests(test.TestCase):
|
class UsageViewTests(test.TestCase):
|
||||||
|
|
||||||
|
@test.create_stubs({api.nova: ('usage_get',),
|
||||||
|
quotas: ('tenant_quota_usages',),
|
||||||
|
api.keystone: ('tenant_get',)})
|
||||||
def test_usage(self):
|
def test_usage(self):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
usage_obj = api.nova.NovaUsage(self.usages.first())
|
usage_obj = api.nova.NovaUsage(self.usages.first())
|
||||||
quota_data = self.quota_usages.first()
|
quota_data = self.quota_usages.first()
|
||||||
self.mox.StubOutWithMock(api.nova, 'usage_get')
|
project = self.tenants.first()
|
||||||
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages')
|
|
||||||
api.nova.usage_get(IsA(http.HttpRequest), self.tenant.id,
|
api.nova.usage_get(IsA(http.HttpRequest), self.tenant.id,
|
||||||
datetime.datetime(now.year, now.month, 1, 0, 0, 0),
|
datetime.datetime(now.year, now.month, 1, 0, 0, 0),
|
||||||
Func(usage.almost_now)) \
|
Func(usage.almost_now)) \
|
||||||
.AndReturn(usage_obj)
|
.AndReturn(usage_obj)
|
||||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
|
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
|
||||||
|
api.keystone.tenant_get(IsA(http.HttpRequest),
|
||||||
|
project.id).AndReturn(project)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
res = self.client.get(reverse('horizon:project:overview:index'))
|
res = self.client.get(reverse('horizon:project:overview:index'))
|
||||||
self.assertTemplateUsed(res, 'project/overview/usage.html')
|
self.assertTemplateUsed(res, 'project/overview/usage.html')
|
||||||
self.assertTrue(isinstance(res.context['usage'], usage.TenantUsage))
|
self.assertTrue(isinstance(res.context['usage'], usage.ProjectUsage))
|
||||||
self.assertContains(res, 'form-horizontal')
|
self.assertContains(res, 'form-horizontal')
|
||||||
|
|
||||||
def test_unauthorized(self):
|
def test_unauthorized(self):
|
||||||
@ -73,12 +78,14 @@ class UsageViewTests(test.TestCase):
|
|||||||
self.assertMessageCount(res, error=1)
|
self.assertMessageCount(res, error=1)
|
||||||
self.assertContains(res, 'Unauthorized:')
|
self.assertContains(res, 'Unauthorized:')
|
||||||
|
|
||||||
|
@test.create_stubs({api.nova: ('usage_get',),
|
||||||
|
quotas: ('tenant_quota_usages',),
|
||||||
|
api.keystone: ('tenant_get',)})
|
||||||
def test_usage_csv(self):
|
def test_usage_csv(self):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
usage_obj = api.nova.NovaUsage(self.usages.first())
|
usage_obj = api.nova.NovaUsage(self.usages.first())
|
||||||
quota_data = self.quota_usages.first()
|
quota_data = self.quota_usages.first()
|
||||||
self.mox.StubOutWithMock(api.nova, 'usage_get')
|
project = self.tenants.first()
|
||||||
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages')
|
|
||||||
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
|
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
|
||||||
api.nova.usage_get(IsA(http.HttpRequest),
|
api.nova.usage_get(IsA(http.HttpRequest),
|
||||||
self.tenant.id,
|
self.tenant.id,
|
||||||
@ -86,18 +93,20 @@ class UsageViewTests(test.TestCase):
|
|||||||
Func(usage.almost_now)) \
|
Func(usage.almost_now)) \
|
||||||
.AndReturn(usage_obj)
|
.AndReturn(usage_obj)
|
||||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
|
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
|
||||||
|
api.keystone.tenant_get(IsA(http.HttpRequest),
|
||||||
|
project.id).AndReturn(project)
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
res = self.client.get(reverse('horizon:project:overview:index') +
|
res = self.client.get(reverse('horizon:project:overview:index') +
|
||||||
"?format=csv")
|
"?format=csv")
|
||||||
self.assertTemplateUsed(res, 'project/overview/usage.csv')
|
self.assertTemplateUsed(res, 'project/overview/usage.csv')
|
||||||
self.assertTrue(isinstance(res.context['usage'], usage.TenantUsage))
|
self.assertTrue(isinstance(res.context['usage'], usage.ProjectUsage))
|
||||||
|
|
||||||
|
@test.create_stubs({api.nova: ('usage_get',),
|
||||||
|
quotas: ('tenant_quota_usages',)})
|
||||||
def test_usage_exception_usage(self):
|
def test_usage_exception_usage(self):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
quota_data = self.quota_usages.first()
|
quota_data = self.quota_usages.first()
|
||||||
self.mox.StubOutWithMock(api.nova, 'usage_get')
|
|
||||||
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages')
|
|
||||||
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
|
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
|
||||||
api.nova.usage_get(IsA(http.HttpRequest),
|
api.nova.usage_get(IsA(http.HttpRequest),
|
||||||
self.tenant.id,
|
self.tenant.id,
|
||||||
@ -111,11 +120,13 @@ class UsageViewTests(test.TestCase):
|
|||||||
self.assertTemplateUsed(res, 'project/overview/usage.html')
|
self.assertTemplateUsed(res, 'project/overview/usage.html')
|
||||||
self.assertEqual(res.context['usage'].usage_list, [])
|
self.assertEqual(res.context['usage'].usage_list, [])
|
||||||
|
|
||||||
|
@test.create_stubs({api.nova: ('usage_get',),
|
||||||
|
quotas: ('tenant_quota_usages',),
|
||||||
|
api.keystone: ('tenant_get',)})
|
||||||
def test_usage_exception_quota(self):
|
def test_usage_exception_quota(self):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
usage_obj = api.nova.NovaUsage(self.usages.first())
|
usage_obj = api.nova.NovaUsage(self.usages.first())
|
||||||
self.mox.StubOutWithMock(api.nova, 'usage_get')
|
project = self.tenants.first()
|
||||||
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages')
|
|
||||||
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
|
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
|
||||||
api.nova.usage_get(IsA(http.HttpRequest),
|
api.nova.usage_get(IsA(http.HttpRequest),
|
||||||
self.tenant.id,
|
self.tenant.id,
|
||||||
@ -124,18 +135,22 @@ class UsageViewTests(test.TestCase):
|
|||||||
.AndReturn(usage_obj)
|
.AndReturn(usage_obj)
|
||||||
quotas.tenant_quota_usages(IsA(http.HttpRequest))\
|
quotas.tenant_quota_usages(IsA(http.HttpRequest))\
|
||||||
.AndRaise(self.exceptions.nova)
|
.AndRaise(self.exceptions.nova)
|
||||||
|
api.keystone.tenant_get(IsA(http.HttpRequest),
|
||||||
|
project.id).AndReturn(project)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
res = self.client.get(reverse('horizon:project:overview:index'))
|
res = self.client.get(reverse('horizon:project:overview:index'))
|
||||||
self.assertTemplateUsed(res, 'project/overview/usage.html')
|
self.assertTemplateUsed(res, 'project/overview/usage.html')
|
||||||
self.assertEqual(res.context['usage'].quotas, {})
|
self.assertEqual(res.context['usage'].quotas, {})
|
||||||
|
|
||||||
|
@test.create_stubs({api.nova: ('usage_get',),
|
||||||
|
quotas: ('tenant_quota_usages',),
|
||||||
|
api.keystone: ('tenant_get',)})
|
||||||
def test_usage_default_tenant(self):
|
def test_usage_default_tenant(self):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
usage_obj = api.nova.NovaUsage(self.usages.first())
|
usage_obj = api.nova.NovaUsage(self.usages.first())
|
||||||
quota_data = self.quota_usages.first()
|
quota_data = self.quota_usages.first()
|
||||||
self.mox.StubOutWithMock(api.nova, 'usage_get')
|
project = self.tenants.first()
|
||||||
self.mox.StubOutWithMock(quotas, 'tenant_quota_usages')
|
|
||||||
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
|
timestamp = datetime.datetime(now.year, now.month, 1, 0, 0, 0)
|
||||||
api.nova.usage_get(IsA(http.HttpRequest),
|
api.nova.usage_get(IsA(http.HttpRequest),
|
||||||
self.tenant.id,
|
self.tenant.id,
|
||||||
@ -143,8 +158,10 @@ class UsageViewTests(test.TestCase):
|
|||||||
Func(usage.almost_now)) \
|
Func(usage.almost_now)) \
|
||||||
.AndReturn(usage_obj)
|
.AndReturn(usage_obj)
|
||||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
|
quotas.tenant_quota_usages(IsA(http.HttpRequest)).AndReturn(quota_data)
|
||||||
|
api.keystone.tenant_get(IsA(http.HttpRequest),
|
||||||
|
project.id).AndReturn(project)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
res = self.client.get(reverse('horizon:project:overview:index'))
|
res = self.client.get(reverse('horizon:project:overview:index'))
|
||||||
self.assertTemplateUsed(res, 'project/overview/usage.html')
|
self.assertTemplateUsed(res, 'project/overview/usage.html')
|
||||||
self.assertTrue(isinstance(res.context['usage'], usage.TenantUsage))
|
self.assertTrue(isinstance(res.context['usage'], usage.ProjectUsage))
|
||||||
|
@ -18,15 +18,38 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from django import VERSION
|
||||||
|
from django.template.defaultfilters import floatformat, capfirst
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from openstack_dashboard import usage
|
from openstack_dashboard import usage
|
||||||
|
from openstack_dashboard.usage.base import BaseCsvResponse
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectUsageCsvRenderer(BaseCsvResponse):
|
||||||
|
|
||||||
|
columns = [_("Instance Name"), _("VCPUs"), _("Ram (MB)"),
|
||||||
|
_("Disk (GB)"), _("Usage (Hours)"),
|
||||||
|
_("Uptime(Seconds)"), _("State")]
|
||||||
|
|
||||||
|
def get_row_data(self):
|
||||||
|
|
||||||
|
for inst in self.context['usage'].get_instances():
|
||||||
|
yield (inst['name'],
|
||||||
|
inst['vcpus'],
|
||||||
|
inst['memory_mb'],
|
||||||
|
inst['local_gb'],
|
||||||
|
floatformat(inst['hours'], 2),
|
||||||
|
inst['uptime'],
|
||||||
|
capfirst(inst['state']))
|
||||||
|
|
||||||
|
|
||||||
class ProjectOverview(usage.UsageView):
|
class ProjectOverview(usage.UsageView):
|
||||||
table_class = usage.TenantUsageTable
|
table_class = usage.ProjectUsageTable
|
||||||
usage_class = usage.TenantUsage
|
usage_class = usage.ProjectUsage
|
||||||
template_name = 'project/overview/usage.html'
|
template_name = 'project/overview/usage.html'
|
||||||
|
csv_response_class = ProjectUsageCsvRenderer
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
super(ProjectOverview, self).get_data()
|
super(ProjectOverview, self).get_data()
|
||||||
|
@ -609,7 +609,8 @@ class VolumeViewTests(test.TestCase):
|
|||||||
@test.create_stubs({cinder: ('volume_get',), api.nova: ('server_list',)})
|
@test.create_stubs({cinder: ('volume_get',), api.nova: ('server_list',)})
|
||||||
def test_edit_attachments(self):
|
def test_edit_attachments(self):
|
||||||
volume = self.volumes.first()
|
volume = self.volumes.first()
|
||||||
servers = self.servers.list()
|
servers = [s for s in self.servers.list()
|
||||||
|
if s.tenant_id == self.request.user.tenant_id]
|
||||||
|
|
||||||
cinder.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
|
cinder.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
|
||||||
api.nova.server_list(IsA(http.HttpRequest)).AndReturn([servers, False])
|
api.nova.server_list(IsA(http.HttpRequest)).AndReturn([servers, False])
|
||||||
@ -632,7 +633,8 @@ class VolumeViewTests(test.TestCase):
|
|||||||
settings.OPENSTACK_HYPERVISOR_FEATURES['can_set_mount_point'] = False
|
settings.OPENSTACK_HYPERVISOR_FEATURES['can_set_mount_point'] = False
|
||||||
|
|
||||||
volume = self.volumes.first()
|
volume = self.volumes.first()
|
||||||
servers = self.servers.list()
|
servers = [s for s in self.servers.list()
|
||||||
|
if s.tenant_id == self.request.user.tenant_id]
|
||||||
|
|
||||||
cinder.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
|
cinder.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
|
||||||
api.nova.server_list(IsA(http.HttpRequest)).AndReturn([servers, False])
|
api.nova.server_list(IsA(http.HttpRequest)).AndReturn([servers, False])
|
||||||
@ -649,13 +651,15 @@ class VolumeViewTests(test.TestCase):
|
|||||||
@test.create_stubs({cinder: ('volume_get',),
|
@test.create_stubs({cinder: ('volume_get',),
|
||||||
api.nova: ('server_get', 'server_list',)})
|
api.nova: ('server_get', 'server_list',)})
|
||||||
def test_edit_attachments_attached_volume(self):
|
def test_edit_attachments_attached_volume(self):
|
||||||
server = self.servers.first()
|
servers = [s for s in self.servers.list()
|
||||||
|
if s.tenant_id == self.request.user.tenant_id]
|
||||||
|
server = servers[0]
|
||||||
volume = self.volumes.list()[0]
|
volume = self.volumes.list()[0]
|
||||||
|
|
||||||
cinder.volume_get(IsA(http.HttpRequest), volume.id) \
|
cinder.volume_get(IsA(http.HttpRequest), volume.id) \
|
||||||
.AndReturn(volume)
|
.AndReturn(volume)
|
||||||
api.nova.server_list(IsA(http.HttpRequest)) \
|
api.nova.server_list(IsA(http.HttpRequest)) \
|
||||||
.AndReturn([self.servers.list(), False])
|
.AndReturn([servers, False])
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ from openstack_dashboard.test import helpers as test
|
|||||||
|
|
||||||
|
|
||||||
class ServerWrapperTests(test.TestCase):
|
class ServerWrapperTests(test.TestCase):
|
||||||
|
|
||||||
def test_get_base_attribute(self):
|
def test_get_base_attribute(self):
|
||||||
server = api.nova.Server(self.servers.first(), self.request)
|
server = api.nova.Server(self.servers.first(), self.request)
|
||||||
self.assertEqual(server.id, self.servers.first().id)
|
self.assertEqual(server.id, self.servers.first().id)
|
||||||
@ -41,7 +42,7 @@ class ServerWrapperTests(test.TestCase):
|
|||||||
image = self.images.first()
|
image = self.images.first()
|
||||||
self.mox.StubOutWithMock(api.glance, 'image_get')
|
self.mox.StubOutWithMock(api.glance, 'image_get')
|
||||||
api.glance.image_get(IsA(http.HttpRequest),
|
api.glance.image_get(IsA(http.HttpRequest),
|
||||||
image.id).AndReturn(image)
|
image.id).AndReturn(image)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
server = api.nova.Server(self.servers.first(), self.request)
|
server = api.nova.Server(self.servers.first(), self.request)
|
||||||
@ -49,6 +50,7 @@ class ServerWrapperTests(test.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class ComputeApiTests(test.APITestCase):
|
class ComputeApiTests(test.APITestCase):
|
||||||
|
|
||||||
def test_server_reboot(self):
|
def test_server_reboot(self):
|
||||||
server = self.servers.first()
|
server = self.servers.first()
|
||||||
HARDNESS = servers.REBOOT_HARD
|
HARDNESS = servers.REBOOT_HARD
|
||||||
@ -99,7 +101,7 @@ class ComputeApiTests(test.APITestCase):
|
|||||||
novaclient = self.stub_novaclient()
|
novaclient = self.stub_novaclient()
|
||||||
novaclient.servers = self.mox.CreateMockAnything()
|
novaclient.servers = self.mox.CreateMockAnything()
|
||||||
novaclient.servers.get_spice_console(server.id,
|
novaclient.servers.get_spice_console(server.id,
|
||||||
console_type).AndReturn(console)
|
console_type).AndReturn(console)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
ret_val = api.nova.server_spice_console(self.request,
|
ret_val = api.nova.server_spice_console(self.request,
|
||||||
@ -148,7 +150,8 @@ class ComputeApiTests(test.APITestCase):
|
|||||||
novaclient.servers.list(True,
|
novaclient.servers.list(True,
|
||||||
{'all_tenants': True,
|
{'all_tenants': True,
|
||||||
'marker': None,
|
'marker': None,
|
||||||
'limit': page_size + 1}).AndReturn(servers)
|
'limit': page_size + 1}). \
|
||||||
|
AndReturn(servers[:page_size + 1])
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
ret_val, has_more = api.nova.server_list(self.request,
|
ret_val, has_more = api.nova.server_list(self.request,
|
||||||
|
@ -139,9 +139,14 @@ def data(TEST):
|
|||||||
'name': 'disabled_tenant',
|
'name': 'disabled_tenant',
|
||||||
'description': "a disabled test tenant.",
|
'description': "a disabled test tenant.",
|
||||||
'enabled': False}
|
'enabled': False}
|
||||||
|
tenant_dict_unicode = {'id': "3",
|
||||||
|
'name': u'\u4e91\u89c4\u5219',
|
||||||
|
'description': "an unicode-named tenant.",
|
||||||
|
'enabled': True}
|
||||||
tenant = tenants.Tenant(tenants.TenantManager, tenant_dict)
|
tenant = tenants.Tenant(tenants.TenantManager, tenant_dict)
|
||||||
disabled_tenant = tenants.Tenant(tenants.TenantManager, tenant_dict_2)
|
disabled_tenant = tenants.Tenant(tenants.TenantManager, tenant_dict_2)
|
||||||
TEST.tenants.add(tenant, disabled_tenant)
|
tenant_unicode = tenants.Tenant(tenants.TenantManager, tenant_dict_unicode)
|
||||||
|
TEST.tenants.add(tenant, disabled_tenant, tenant_unicode)
|
||||||
TEST.tenant = tenant # Your "current" tenant
|
TEST.tenant = tenant # Your "current" tenant
|
||||||
|
|
||||||
tomorrow = datetime_safe.datetime.now() + timedelta(days=1)
|
tomorrow = datetime_safe.datetime.now() + timedelta(days=1)
|
||||||
|
@ -361,6 +361,8 @@ def data(TEST):
|
|||||||
TEST.limits = limits
|
TEST.limits = limits
|
||||||
|
|
||||||
# Servers
|
# Servers
|
||||||
|
tenant3 = TEST.tenants.list()[2]
|
||||||
|
|
||||||
vals = {"host": "http://nova.example.com:8774",
|
vals = {"host": "http://nova.example.com:8774",
|
||||||
"name": "server_1",
|
"name": "server_1",
|
||||||
"status": "ACTIVE",
|
"status": "ACTIVE",
|
||||||
@ -377,7 +379,13 @@ def data(TEST):
|
|||||||
"server_id": "2"})
|
"server_id": "2"})
|
||||||
server_2 = servers.Server(servers.ServerManager(None),
|
server_2 = servers.Server(servers.ServerManager(None),
|
||||||
json.loads(SERVER_DATA % vals)['server'])
|
json.loads(SERVER_DATA % vals)['server'])
|
||||||
TEST.servers.add(server_1, server_2)
|
vals.update({"name": u'\u4e91\u89c4\u5219',
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"tenant_id": tenant3.id,
|
||||||
|
"server_id": "3"})
|
||||||
|
server_3 = servers.Server(servers.ServerManager(None),
|
||||||
|
json.loads(SERVER_DATA % vals)['server'])
|
||||||
|
TEST.servers.add(server_1, server_2, server_3)
|
||||||
|
|
||||||
# VNC Console Data
|
# VNC Console Data
|
||||||
console = {u'console': {u'url': u'http://example.com:6080/vnc_auto.html',
|
console = {u'console': {u'url': u'http://example.com:6080/vnc_auto.html',
|
||||||
@ -434,6 +442,17 @@ def data(TEST):
|
|||||||
json.loads(USAGE_DATA % usage_vals))
|
json.loads(USAGE_DATA % usage_vals))
|
||||||
TEST.usages.add(usage_obj)
|
TEST.usages.add(usage_obj)
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
usage_2_vals = {"tenant_id": tenant3.id,
|
||||||
|
"instance_name": server_3.name,
|
||||||
|
"flavor_name": flavor_1.name,
|
||||||
|
"flavor_vcpus": flavor_1.vcpus,
|
||||||
|
"flavor_disk": flavor_1.disk,
|
||||||
|
"flavor_ram": flavor_1.ram}
|
||||||
|
usage_obj_2 = usage.Usage(usage.UsageManager(None),
|
||||||
|
json.loads(USAGE_DATA % usage_2_vals))
|
||||||
|
TEST.usages.add(usage_obj_2)
|
||||||
|
|
||||||
volume_snapshot = vol_snaps.Snapshot(vol_snaps.SnapshotManager(None),
|
volume_snapshot = vol_snaps.Snapshot(vol_snaps.SnapshotManager(None),
|
||||||
{'id': '40f3fabf-3613-4f5e-90e5-6c9a08333fc3',
|
{'id': '40f3fabf-3613-4f5e-90e5-6c9a08333fc3',
|
||||||
'display_name': 'test snapshot',
|
'display_name': 'test snapshot',
|
||||||
|
@ -56,6 +56,8 @@ class QuotaTests(test.APITestCase):
|
|||||||
quotas: ('is_service_enabled',),
|
quotas: ('is_service_enabled',),
|
||||||
cinder: ('volume_list', 'tenant_quota_get',)})
|
cinder: ('volume_list', 'tenant_quota_get',)})
|
||||||
def test_tenant_quota_usages(self):
|
def test_tenant_quota_usages(self):
|
||||||
|
servers = [s for s in self.servers.list()
|
||||||
|
if s.tenant_id == self.request.user.tenant_id]
|
||||||
quotas.is_service_enabled(IsA(http.HttpRequest),
|
quotas.is_service_enabled(IsA(http.HttpRequest),
|
||||||
'volume').AndReturn(True)
|
'volume').AndReturn(True)
|
||||||
api.nova.flavor_list(IsA(http.HttpRequest)) \
|
api.nova.flavor_list(IsA(http.HttpRequest)) \
|
||||||
@ -65,7 +67,7 @@ class QuotaTests(test.APITestCase):
|
|||||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||||
.AndReturn(self.floating_ips.list())
|
.AndReturn(self.floating_ips.list())
|
||||||
api.nova.server_list(IsA(http.HttpRequest)) \
|
api.nova.server_list(IsA(http.HttpRequest)) \
|
||||||
.AndReturn([self.servers.list(), False])
|
.AndReturn([servers, False])
|
||||||
cinder.volume_list(IsA(http.HttpRequest)) \
|
cinder.volume_list(IsA(http.HttpRequest)) \
|
||||||
.AndReturn(self.volumes.list())
|
.AndReturn(self.volumes.list())
|
||||||
cinder.tenant_quota_get(IsA(http.HttpRequest), '1') \
|
cinder.tenant_quota_get(IsA(http.HttpRequest), '1') \
|
||||||
@ -85,6 +87,8 @@ class QuotaTests(test.APITestCase):
|
|||||||
api.network: ('tenant_floating_ip_list',),
|
api.network: ('tenant_floating_ip_list',),
|
||||||
quotas: ('is_service_enabled',)})
|
quotas: ('is_service_enabled',)})
|
||||||
def test_tenant_quota_usages_without_volume(self):
|
def test_tenant_quota_usages_without_volume(self):
|
||||||
|
servers = [s for s in self.servers.list()
|
||||||
|
if s.tenant_id == self.request.user.tenant_id]
|
||||||
quotas.is_service_enabled(IsA(http.HttpRequest),
|
quotas.is_service_enabled(IsA(http.HttpRequest),
|
||||||
'volume').AndReturn(False)
|
'volume').AndReturn(False)
|
||||||
api.nova.flavor_list(IsA(http.HttpRequest)) \
|
api.nova.flavor_list(IsA(http.HttpRequest)) \
|
||||||
@ -94,7 +98,7 @@ class QuotaTests(test.APITestCase):
|
|||||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||||
.AndReturn(self.floating_ips.list())
|
.AndReturn(self.floating_ips.list())
|
||||||
api.nova.server_list(IsA(http.HttpRequest)) \
|
api.nova.server_list(IsA(http.HttpRequest)) \
|
||||||
.AndReturn([self.servers.list(), False])
|
.AndReturn([servers, False])
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
@ -143,6 +147,8 @@ class QuotaTests(test.APITestCase):
|
|||||||
def test_tenant_quota_usages_unlimited_quota(self):
|
def test_tenant_quota_usages_unlimited_quota(self):
|
||||||
inf_quota = self.quotas.first()
|
inf_quota = self.quotas.first()
|
||||||
inf_quota['ram'] = -1
|
inf_quota['ram'] = -1
|
||||||
|
servers = [s for s in self.servers.list()
|
||||||
|
if s.tenant_id == self.request.user.tenant_id]
|
||||||
|
|
||||||
quotas.is_service_enabled(IsA(http.HttpRequest),
|
quotas.is_service_enabled(IsA(http.HttpRequest),
|
||||||
'volume').AndReturn(True)
|
'volume').AndReturn(True)
|
||||||
@ -153,7 +159,7 @@ class QuotaTests(test.APITestCase):
|
|||||||
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||||
.AndReturn(self.floating_ips.list())
|
.AndReturn(self.floating_ips.list())
|
||||||
api.nova.server_list(IsA(http.HttpRequest)) \
|
api.nova.server_list(IsA(http.HttpRequest)) \
|
||||||
.AndReturn([self.servers.list(), False])
|
.AndReturn([servers, False])
|
||||||
cinder.volume_list(IsA(http.HttpRequest)) \
|
cinder.volume_list(IsA(http.HttpRequest)) \
|
||||||
.AndReturn(self.volumes.list())
|
.AndReturn(self.volumes.list())
|
||||||
cinder.tenant_quota_get(IsA(http.HttpRequest), '1') \
|
cinder.tenant_quota_get(IsA(http.HttpRequest), '1') \
|
||||||
|
@ -14,6 +14,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from .base import BaseUsage, TenantUsage, GlobalUsage, almost_now
|
from .base import BaseUsage, ProjectUsage, GlobalUsage, almost_now
|
||||||
from .views import UsageView
|
from .views import UsageView
|
||||||
from .tables import BaseUsageTable, TenantUsageTable, GlobalUsageTable
|
from .tables import BaseUsageTable, ProjectUsageTable, GlobalUsageTable
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
from __future__ import division
|
from __future__ import division
|
||||||
|
|
||||||
from calendar import monthrange
|
from calendar import monthrange
|
||||||
|
from csv import writer, DictWriter
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
from django import template as django_template, VERSION
|
||||||
|
from django.http.response import HttpResponse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
@ -27,8 +31,8 @@ def almost_now(input_time):
|
|||||||
class BaseUsage(object):
|
class BaseUsage(object):
|
||||||
show_terminated = False
|
show_terminated = False
|
||||||
|
|
||||||
def __init__(self, request, tenant_id=None):
|
def __init__(self, request, project_id=None):
|
||||||
self.tenant_id = tenant_id or request.user.tenant_id
|
self.project_id = project_id or request.user.tenant_id
|
||||||
self.request = request
|
self.request = request
|
||||||
self.summary = {}
|
self.summary = {}
|
||||||
self.usage_list = []
|
self.usage_list = []
|
||||||
@ -101,9 +105,9 @@ class BaseUsage(object):
|
|||||||
_("You are viewing data for the future, "
|
_("You are viewing data for the future, "
|
||||||
"which may or may not exist."))
|
"which may or may not exist."))
|
||||||
|
|
||||||
for tenant_usage in self.usage_list:
|
for project_usage in self.usage_list:
|
||||||
tenant_summary = tenant_usage.get_summary()
|
project_summary = project_usage.get_summary()
|
||||||
for key, value in tenant_summary.items():
|
for key, value in project_summary.items():
|
||||||
self.summary.setdefault(key, 0)
|
self.summary.setdefault(key, 0)
|
||||||
self.summary[key] += value
|
self.summary[key] += value
|
||||||
|
|
||||||
@ -130,7 +134,7 @@ class GlobalUsage(BaseUsage):
|
|||||||
return api.nova.usage_list(self.request, start, end)
|
return api.nova.usage_list(self.request, start, end)
|
||||||
|
|
||||||
|
|
||||||
class TenantUsage(BaseUsage):
|
class ProjectUsage(BaseUsage):
|
||||||
attrs = ('memory_mb', 'vcpus', 'uptime',
|
attrs = ('memory_mb', 'vcpus', 'uptime',
|
||||||
'hours', 'local_gb')
|
'hours', 'local_gb')
|
||||||
|
|
||||||
@ -139,7 +143,9 @@ class TenantUsage(BaseUsage):
|
|||||||
self.show_terminated)
|
self.show_terminated)
|
||||||
instances = []
|
instances = []
|
||||||
terminated_instances = []
|
terminated_instances = []
|
||||||
usage = api.nova.usage_get(self.request, self.tenant_id, start, end)
|
usage = api.nova.usage_get(self.request, self.project_id, start, end)
|
||||||
|
project = api.keystone.tenant_get(self.request, self.project_id)
|
||||||
|
self.project_name = project.name
|
||||||
# Attribute may not exist if there are no instances
|
# Attribute may not exist if there are no instances
|
||||||
if hasattr(usage, 'server_usages'):
|
if hasattr(usage, 'server_usages'):
|
||||||
now = self.today
|
now = self.today
|
||||||
@ -155,3 +161,130 @@ class TenantUsage(BaseUsage):
|
|||||||
instances.append(server_usage)
|
instances.append(server_usage)
|
||||||
usage.server_usages = instances
|
usage.server_usages = instances
|
||||||
return (usage,)
|
return (usage,)
|
||||||
|
|
||||||
|
|
||||||
|
class CsvDataMixin(object):
|
||||||
|
|
||||||
|
"""
|
||||||
|
CSV data Mixin - provides handling for CSV data
|
||||||
|
|
||||||
|
.. attribute:: columns
|
||||||
|
|
||||||
|
A list of CSV column definitions. If omitted - no column titles
|
||||||
|
will be shown in the result file. Optional.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.out = StringIO()
|
||||||
|
super(CsvDataMixin, self).__init__()
|
||||||
|
if hasattr(self, "columns"):
|
||||||
|
self.writer = DictWriter(self.out, map(self.encode, self.columns))
|
||||||
|
self.is_dict = True
|
||||||
|
else:
|
||||||
|
self.writer = writer(self.out)
|
||||||
|
self.is_dict = False
|
||||||
|
|
||||||
|
def write_csv_header(self):
|
||||||
|
if self.is_dict:
|
||||||
|
try:
|
||||||
|
self.writer.writeheader()
|
||||||
|
except AttributeError:
|
||||||
|
# For Python<2.7
|
||||||
|
self.writer.writerow(dict(zip(
|
||||||
|
self.writer.fieldnames,
|
||||||
|
self.writer.fieldnames)))
|
||||||
|
|
||||||
|
def write_csv_row(self, args):
|
||||||
|
if self.is_dict:
|
||||||
|
self.writer.writerow(dict(zip(
|
||||||
|
self.writer.fieldnames, map(self.encode, args))))
|
||||||
|
else:
|
||||||
|
self.writer.writerow(map(self.encode, args))
|
||||||
|
|
||||||
|
def encode(self, value):
|
||||||
|
# csv and StringIO cannot work with mixed encodings,
|
||||||
|
# so encode all with utf-8
|
||||||
|
return unicode(value).encode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
class BaseCsvResponse(CsvDataMixin, HttpResponse):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Base CSV response class. Provides handling of CSV data.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, request, template, context, content_type, **kwargs):
|
||||||
|
super(BaseCsvResponse, self).__init__()
|
||||||
|
self['Content-Disposition'] = 'attachment; filename="%s"' % (
|
||||||
|
kwargs.get("filename", "export.csv"),)
|
||||||
|
self['Content-Type'] = content_type
|
||||||
|
self.context = context
|
||||||
|
self.header = None
|
||||||
|
if template:
|
||||||
|
# Display some header info if provided as a template
|
||||||
|
header_template = django_template.loader.get_template(template)
|
||||||
|
context = django_template.RequestContext(request, self.context)
|
||||||
|
self.header = header_template.render(context)
|
||||||
|
|
||||||
|
if self.header:
|
||||||
|
self.out.write(self.encode(self.header))
|
||||||
|
|
||||||
|
self.write_csv_header()
|
||||||
|
|
||||||
|
for row in self.get_row_data():
|
||||||
|
self.write_csv_row(row)
|
||||||
|
|
||||||
|
self.out.flush()
|
||||||
|
self.content = self.out.getvalue()
|
||||||
|
self.out.close()
|
||||||
|
|
||||||
|
def get_row_data(self):
|
||||||
|
raise NotImplementedError("You must define a get_row_data method on %s"
|
||||||
|
% self.__class__.__name__)
|
||||||
|
|
||||||
|
if VERSION >= (1, 5, 0):
|
||||||
|
|
||||||
|
from django.http import StreamingHttpResponse
|
||||||
|
|
||||||
|
class BaseCsvStreamingResponse(CsvDataMixin, StreamingHttpResponse):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Base CSV Streaming class. Provides streaming response for CSV data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, request, template, context, content_type, **kwargs):
|
||||||
|
super(BaseCsvStreamingResponse, self).__init__()
|
||||||
|
self['Content-Disposition'] = 'attachment; filename="%s"' % (
|
||||||
|
kwargs.get("filename", "export.csv"),)
|
||||||
|
self['Content-Type'] = content_type
|
||||||
|
self.context = context
|
||||||
|
self.header = None
|
||||||
|
if template:
|
||||||
|
# Display some header info if provided as a template
|
||||||
|
header_template = django_template.loader.get_template(template)
|
||||||
|
context = django_template.RequestContext(request, self.context)
|
||||||
|
self.header = header_template.render(context)
|
||||||
|
|
||||||
|
self._closable_objects.append(self.out)
|
||||||
|
|
||||||
|
self.streaming_content = self.get_content()
|
||||||
|
|
||||||
|
def buffer(self):
|
||||||
|
buf = self.out.getvalue()
|
||||||
|
self.out.truncate(0)
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def get_content(self):
|
||||||
|
if self.header:
|
||||||
|
self.out.write(self.encode(self.header))
|
||||||
|
|
||||||
|
self.write_csv_header()
|
||||||
|
yield self.buffer()
|
||||||
|
|
||||||
|
for row in self.get_row_data():
|
||||||
|
self.write_csv_row(row)
|
||||||
|
yield self.buffer()
|
||||||
|
|
||||||
|
def get_row_data(self):
|
||||||
|
raise NotImplementedError("You must define a get_row_data method "
|
||||||
|
"on %s" % self.__class__.__name__)
|
||||||
|
@ -27,7 +27,7 @@ class BaseUsageTable(tables.DataTable):
|
|||||||
|
|
||||||
|
|
||||||
class GlobalUsageTable(BaseUsageTable):
|
class GlobalUsageTable(BaseUsageTable):
|
||||||
tenant = tables.Column('tenant_name', verbose_name=_("Project Name"))
|
project = tables.Column('project_name', verbose_name=_("Project Name"))
|
||||||
disk_hours = tables.Column('disk_gb_hours',
|
disk_hours = tables.Column('disk_gb_hours',
|
||||||
verbose_name=_("Disk GB Hours"),
|
verbose_name=_("Disk GB Hours"),
|
||||||
filters=(lambda v: floatformat(v, 2),))
|
filters=(lambda v: floatformat(v, 2),))
|
||||||
@ -38,7 +38,7 @@ class GlobalUsageTable(BaseUsageTable):
|
|||||||
class Meta:
|
class Meta:
|
||||||
name = "global_usage"
|
name = "global_usage"
|
||||||
verbose_name = _("Usage Summary")
|
verbose_name = _("Usage Summary")
|
||||||
columns = ("tenant", "vcpus", "disk", "memory",
|
columns = ("project", "vcpus", "disk", "memory",
|
||||||
"hours", "disk_hours")
|
"hours", "disk_hours")
|
||||||
table_actions = (CSVSummary,)
|
table_actions = (CSVSummary,)
|
||||||
multi_select = False
|
multi_select = False
|
||||||
@ -52,7 +52,7 @@ def get_instance_link(datum):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class TenantUsageTable(BaseUsageTable):
|
class ProjectUsageTable(BaseUsageTable):
|
||||||
instance = tables.Column('name',
|
instance = tables.Column('name',
|
||||||
verbose_name=_("Instance Name"),
|
verbose_name=_("Instance Name"),
|
||||||
link=get_instance_link)
|
link=get_instance_link)
|
||||||
@ -64,7 +64,7 @@ class TenantUsageTable(BaseUsageTable):
|
|||||||
return datum.get('instance_id', id(datum))
|
return datum.get('instance_id', id(datum))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
name = "tenant_usage"
|
name = "project_usage"
|
||||||
verbose_name = _("Usage Summary")
|
verbose_name = _("Usage Summary")
|
||||||
columns = ("instance", "vcpus", "disk", "memory", "uptime")
|
columns = ("instance", "vcpus", "disk", "memory", "uptime")
|
||||||
table_actions = (CSVSummary,)
|
table_actions = (CSVSummary,)
|
||||||
|
@ -28,8 +28,8 @@ class UsageView(tables.DataTableView):
|
|||||||
return "text/html"
|
return "text/html"
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
tenant_id = self.kwargs.get('tenant_id', self.request.user.tenant_id)
|
project_id = self.kwargs.get('project_id', self.request.user.tenant_id)
|
||||||
self.usage = self.usage_class(self.request, tenant_id)
|
self.usage = self.usage_class(self.request, project_id)
|
||||||
self.usage.summarize(*self.usage.get_date_range())
|
self.usage.summarize(*self.usage.get_date_range())
|
||||||
self.usage.get_quotas()
|
self.usage.get_quotas()
|
||||||
self.kwargs['usage'] = self.usage
|
self.kwargs['usage'] = self.usage
|
||||||
@ -43,12 +43,14 @@ class UsageView(tables.DataTableView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def render_to_response(self, context, **response_kwargs):
|
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':
|
if self.request.GET.get('format', 'html') == 'csv':
|
||||||
resp['Content-Disposition'] = 'attachment; filename=usage.csv'
|
render_class = self.csv_response_class
|
||||||
resp['Content-Type'] = 'text/csv'
|
response_kwargs.setdefault("filename", "usage.csv")
|
||||||
|
else:
|
||||||
|
render_class = self.response_class
|
||||||
|
resp = render_class(request=self.request,
|
||||||
|
template=self.get_template_names(),
|
||||||
|
context=context,
|
||||||
|
content_type=self.get_content_type(),
|
||||||
|
**response_kwargs)
|
||||||
return resp
|
return resp
|
||||||
|
Loading…
Reference in New Issue
Block a user