usage: split out the limit related logic into ProjectUsageView

Previously limit information was always retrieved but it is not
used in the admin overview. This commit moves the limit related logic
into a separate class ProjectUsageView.

testing of usage.views is covered by individual panel tests,
so UTs for panels which uses UsageView rather than the new
ProjectUsageView need to be updated in this commit.

blueprint make-quotas-great-again
Change-Id: Id93b66d11e3b0c98d1fbb0454bb43984c2999d92
This commit is contained in:
Akihiro Motoki 2017-12-09 16:32:58 +09:00
parent 794fec40f9
commit 788388c486
7 changed files with 145 additions and 192 deletions

View File

@ -204,26 +204,8 @@ class AggregatesViewTests(test.BaseAdminViewTests):
@mock.patch('openstack_dashboard.api.nova.extension_supported',
mock.Mock(return_value=False))
@test.create_stubs({api.nova: ('aggregate_details_list',
'availability_zone_list',
'tenant_absolute_limits',),
api.cinder: ('tenant_absolute_limits',),
api.neutron: ('is_extension_supported',
'tenant_floating_ip_list',
'security_group_list'),
api.keystone: ('tenant_list',)})
@test.create_stubs({api.keystone: ('tenant_list',)})
def test_panel_not_available(self):
api.nova.tenant_absolute_limits(IsA(http.HttpRequest)). \
MultipleTimes().AndReturn(self.limits['absolute'])
api.cinder.tenant_absolute_limits(IsA(http.HttpRequest)). \
MultipleTimes().AndReturn(self.cinder_limits['absolute'])
api.neutron.\
is_extension_supported(IsA(http.HttpRequest), 'security-group'). \
MultipleTimes().AndReturn(True)
api.neutron.tenant_floating_ip_list(IsA(http.HttpRequest)) \
.AndReturn(self.floating_ips.list())
api.neutron.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list())
api.keystone.tenant_list(IsA(http.HttpRequest)) \
.AndReturn(self.tenants.list())
self.mox.ReplayAll()

View File

@ -40,14 +40,8 @@ class UsageViewTests(test.BaseAdminViewTests):
def _stub_api_calls(self, nova_stu_enabled):
self.mox.StubOutWithMock(api.nova, 'usage_list')
self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
self.mox.StubOutWithMock(api.nova, 'extension_supported')
self.mox.StubOutWithMock(api.keystone, 'tenant_list')
self.mox.StubOutWithMock(api.neutron, 'is_extension_supported')
self.mox.StubOutWithMock(api.neutron, 'floating_ip_supported')
self.mox.StubOutWithMock(api.neutron, 'tenant_floating_ip_list')
self.mox.StubOutWithMock(api.neutron, 'security_group_list')
self.mox.StubOutWithMock(api.cinder, 'tenant_absolute_limits')
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
@ -99,18 +93,6 @@ class UsageViewTests(test.BaseAdminViewTests):
now.month,
now.day, 23, 59, 59, 0)) \
.AndReturn(usage_list)
api.nova.tenant_absolute_limits(IsA(http.HttpRequest), reserved=True) \
.AndReturn(self.limits['absolute'])
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'security-group').AndReturn(True)
api.neutron.floating_ip_supported(IsA(http.HttpRequest)) \
.AndReturn(True)
api.neutron.tenant_floating_ip_list(IsA(http.HttpRequest)) \
.AndReturn(self.floating_ips.list())
api.neutron.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list())
api.cinder.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndReturn(self.cinder_limits['absolute'])
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:admin:overview:index'))
@ -195,18 +177,6 @@ class UsageViewTests(test.BaseAdminViewTests):
now.month,
now.day, 23, 59, 59, 0)) \
.AndReturn(usage_obj)
api.nova.tenant_absolute_limits(IsA(http.HttpRequest), reserved=True)\
.AndReturn(self.limits['absolute'])
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'security-group').AndReturn(True)
api.neutron.floating_ip_supported(IsA(http.HttpRequest)) \
.AndReturn(True)
api.neutron.tenant_floating_ip_list(IsA(http.HttpRequest)) \
.AndReturn(self.floating_ips.list())
api.neutron.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list())
api.cinder.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndReturn(self.cinder_limits['absolute'])
self.mox.ReplayAll()
csv_url = reverse('horizon:admin:overview:index') + "?format=csv"

View File

@ -1529,31 +1529,12 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
class UsageViewTests(test.BaseAdminViewTests):
def _stub_nova_api_calls(self, nova_stu_enabled=True):
self.mox.StubOutWithMock(api.nova, 'usage_get')
self.mox.StubOutWithMock(api.nova, 'tenant_absolute_limits')
self.mox.StubOutWithMock(api.nova, 'extension_supported')
self.mox.StubOutWithMock(api.cinder, 'tenant_absolute_limits')
api.nova.extension_supported(
'SimpleTenantUsage', IsA(http.HttpRequest)) \
.AndReturn(nova_stu_enabled)
def _stub_neutron_api_calls(self, neutron_sg_enabled=True):
self.mox.StubOutWithMock(api.neutron, 'is_extension_supported')
self.mox.StubOutWithMock(api.neutron, 'floating_ip_supported')
self.mox.StubOutWithMock(api.neutron, 'tenant_floating_ip_list')
if neutron_sg_enabled:
self.mox.StubOutWithMock(api.neutron, 'security_group_list')
api.neutron.is_extension_supported(
IsA(http.HttpRequest),
'security-group').AndReturn(neutron_sg_enabled)
api.neutron.floating_ip_supported(IsA(http.HttpRequest)) \
.AndReturn(True)
api.neutron.tenant_floating_ip_list(IsA(http.HttpRequest)) \
.AndReturn(self.floating_ips.list())
if neutron_sg_enabled:
api.neutron.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list())
def test_usage_csv(self):
self._test_usage_csv(nova_stu_enabled=True)
@ -1583,11 +1564,6 @@ class UsageViewTests(test.BaseAdminViewTests):
api.nova.usage_get(IsA(http.HttpRequest),
self.tenant.id,
start, end).AndReturn(usage_obj)
api.nova.tenant_absolute_limits(IsA(http.HttpRequest), reserved=True)\
.AndReturn(self.limits['absolute'])
api.cinder.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndReturn(self.cinder_limits['absolute'])
self._stub_neutron_api_calls()
self.mox.ReplayAll()
project_id = self.tenants.first().id

View File

@ -53,7 +53,7 @@ class ProjectUsageCsvRenderer(csvbase.BaseCsvResponse):
capfirst(state_label))
class ProjectOverview(usage.UsageView):
class ProjectOverview(usage.ProjectUsageView):
table_class = usage.ProjectUsageTable
usage_class = usage.ProjectUsage
template_name = 'project/overview/usage.html'

View File

@ -18,6 +18,7 @@ from openstack_dashboard.usage.base import ProjectUsage
from openstack_dashboard.usage.tables import BaseUsageTable
from openstack_dashboard.usage.tables import GlobalUsageTable
from openstack_dashboard.usage.tables import ProjectUsageTable
from openstack_dashboard.usage.views import ProjectUsageView
from openstack_dashboard.usage.views import UsageView
@ -28,5 +29,6 @@ __all__ = [
'BaseUsageTable',
'GlobalUsageTable',
'ProjectUsageTable',
'ProjectUsageView',
'UsageView',
]

View File

@ -33,8 +33,6 @@ class BaseUsage(object):
self.request = request
self.summary = {}
self.usage_list = []
self.limits = {}
self.quotas = {}
@property
def today(self):
@ -111,101 +109,6 @@ class BaseUsage(object):
req.session['usage_end'] = end
return self.form
def _get_neutron_usage(self, limits, resource_name):
resource_map = {
'floatingip': {
'api': api.neutron.tenant_floating_ip_list,
'limit_name': 'totalFloatingIpsUsed',
'message': _('Unable to retrieve floating IP addresses.')
},
'security_group': {
'api': api.neutron.security_group_list,
'limit_name': 'totalSecurityGroupsUsed',
'message': _('Unable to retrieve security groups.')
}
}
resource = resource_map[resource_name]
try:
method = resource['api']
current_used = len(method(self.request))
except Exception:
current_used = 0
msg = resource['message']
exceptions.handle(self.request, msg)
limits[resource['limit_name']] = current_used
def _set_neutron_limit(self, limits, neutron_quotas, resource_name):
limit_name_map = {
'floatingip': 'maxTotalFloatingIps',
'security_group': 'maxSecurityGroups',
}
if neutron_quotas is None:
resource_max = float("inf")
else:
resource_max = getattr(neutron_quotas.get(resource_name),
'limit', float("inf"))
if resource_max == -1:
resource_max = float("inf")
limits[limit_name_map[resource_name]] = resource_max
def get_neutron_limits(self):
if not api.base.is_service_enabled(self.request, 'network'):
return
try:
neutron_quotas_supported = (
api.neutron.is_quotas_extension_supported(self.request))
neutron_sg_used = (
api.neutron.is_extension_supported(self.request,
'security-group'))
if api.neutron.floating_ip_supported(self.request):
self._get_neutron_usage(self.limits, 'floatingip')
if neutron_sg_used:
self._get_neutron_usage(self.limits, 'security_group')
# Quotas are an optional extension in Neutron. If it isn't
# enabled, assume the floating IP limit is infinite.
if neutron_quotas_supported:
neutron_quotas = api.neutron.tenant_quota_get(self.request,
self.project_id)
else:
neutron_quotas = None
except Exception:
# Assume neutron security group and quotas are enabled
# because they are enabled in most Neutron plugins.
neutron_sg_used = True
neutron_quotas = None
msg = _('Unable to retrieve network quota information.')
exceptions.handle(self.request, msg)
self._set_neutron_limit(self.limits, neutron_quotas, 'floatingip')
if neutron_sg_used:
self._set_neutron_limit(self.limits, neutron_quotas,
'security_group')
def get_cinder_limits(self):
"""Get volume limits if cinder is enabled."""
if not api.cinder.is_volume_service_enabled(self.request):
return
try:
self.limits.update(api.cinder.tenant_absolute_limits(self.request))
except Exception:
msg = _("Unable to retrieve volume limit information.")
exceptions.handle(self.request, msg)
return
def get_limits(self):
try:
self.limits = api.nova.tenant_absolute_limits(self.request,
reserved=True)
except Exception:
exceptions.handle(self.request,
_("Unable to retrieve limit information."))
self.get_neutron_limits()
self.get_cinder_limits()
def get_usage_list(self, start, end):
return []
@ -260,6 +163,11 @@ class ProjectUsage(BaseUsage):
attrs = ('memory_mb', 'vcpus', 'uptime',
'hours', 'local_gb')
def __init__(self, request, project_id=None):
super(ProjectUsage, self).__init__(request, project_id)
self.limits = {}
self.quotas = {}
def get_usage_list(self, start, end):
show_deleted = self.request.GET.get('show_deleted',
self.show_deleted)
@ -281,3 +189,98 @@ class ProjectUsage(BaseUsage):
instances.append(server_usage)
usage.server_usages = instances
return (usage,)
def get_limits(self):
try:
self.limits = api.nova.tenant_absolute_limits(self.request,
reserved=True)
except Exception:
exceptions.handle(self.request,
_("Unable to retrieve limit information."))
self._get_neutron_limits()
self._get_cinder_limits()
def _get_neutron_usage(self, limits, resource_name):
resource_map = {
'floatingip': {
'api': api.neutron.tenant_floating_ip_list,
'limit_name': 'totalFloatingIpsUsed',
'message': _('Unable to retrieve floating IP addresses.')
},
'security_group': {
'api': api.neutron.security_group_list,
'limit_name': 'totalSecurityGroupsUsed',
'message': _('Unable to retrieve security groups.')
}
}
resource = resource_map[resource_name]
try:
method = resource['api']
current_used = len(method(self.request))
except Exception:
current_used = 0
msg = resource['message']
exceptions.handle(self.request, msg)
limits[resource['limit_name']] = current_used
def _set_neutron_limit(self, limits, neutron_quotas, resource_name):
limit_name_map = {
'floatingip': 'maxTotalFloatingIps',
'security_group': 'maxSecurityGroups',
}
if neutron_quotas is None:
resource_max = float("inf")
else:
resource_max = getattr(neutron_quotas.get(resource_name),
'limit', float("inf"))
if resource_max == -1:
resource_max = float("inf")
limits[limit_name_map[resource_name]] = resource_max
def _get_neutron_limits(self):
if not api.base.is_service_enabled(self.request, 'network'):
return
try:
neutron_quotas_supported = (
api.neutron.is_quotas_extension_supported(self.request))
neutron_sg_used = (
api.neutron.is_extension_supported(self.request,
'security-group'))
if api.neutron.floating_ip_supported(self.request):
self._get_neutron_usage(self.limits, 'floatingip')
if neutron_sg_used:
self._get_neutron_usage(self.limits, 'security_group')
# Quotas are an optional extension in Neutron. If it isn't
# enabled, assume the floating IP limit is infinite.
if neutron_quotas_supported:
neutron_quotas = api.neutron.tenant_quota_get(self.request,
self.project_id)
else:
neutron_quotas = None
except Exception:
# Assume neutron security group and quotas are enabled
# because they are enabled in most Neutron plugins.
neutron_sg_used = True
neutron_quotas = None
msg = _('Unable to retrieve network quota information.')
exceptions.handle(self.request, msg)
self._set_neutron_limit(self.limits, neutron_quotas, 'floatingip')
if neutron_sg_used:
self._set_neutron_limit(self.limits, neutron_quotas,
'security_group')
def _get_cinder_limits(self):
"""Get volume limits if cinder is enabled."""
if not api.cinder.is_volume_service_enabled(self.request):
return
try:
self.limits.update(api.cinder.tenant_absolute_limits(self.request))
except Exception:
msg = _("Unable to retrieve volume limit information.")
exceptions.handle(self.request, msg)
return

View File

@ -48,7 +48,6 @@ class UsageView(tables.DataTableView):
self.request.user.tenant_id)
self.usage = self.usage_class(self.request, project_id)
self.usage.summarize(*self.usage.get_date_range())
self.usage.get_limits()
self.kwargs['usage'] = self.usage
return self.usage.usage_list
except Exception:
@ -61,7 +60,33 @@ class UsageView(tables.DataTableView):
context['table'].kwargs['usage'] = self.usage
context['form'] = self.usage.form
context['usage'] = self.usage
context['charts'] = []
try:
context['simple_tenant_usage_enabled'] = \
api.nova.extension_supported('SimpleTenantUsage', self.request)
except Exception:
context['simple_tenant_usage_enabled'] = True
return context
def render_to_response(self, context, **response_kwargs):
if self.request.GET.get('format', 'html') == 'csv':
render_class = self.csv_response_class
response_kwargs.setdefault("filename", "usage.csv")
else:
render_class = self.response_class
context = self.render_context_with_title(context)
resp = render_class(request=self.request,
template=self.get_template_names(),
context=context,
content_type=self.get_content_type(),
**response_kwargs)
return resp
class ProjectUsageView(UsageView):
def _get_charts_data(self):
charts = []
# (Used key, Max key, Human Readable Name, text to display when
# describing the quota by default it is 'Used')
@ -85,7 +110,7 @@ class UsageView(tables.DataTableView):
text = pgettext_lazy('Label in the limit summary', 'Used')
if len(t) > 3:
text = t[3]
context['charts'].append({
charts.append({
'type': t[0],
'name': t[2],
'used': self.usage.limits[t[0]],
@ -93,23 +118,18 @@ class UsageView(tables.DataTableView):
'text': text
})
try:
context['simple_tenant_usage_enabled'] = \
api.nova.extension_supported('SimpleTenantUsage', self.request)
except Exception:
context['simple_tenant_usage_enabled'] = True
return charts
def get_context_data(self, **kwargs):
context = super(ProjectUsageView, self).get_context_data(**kwargs)
context['charts'] = self._get_charts_data()
return context
def render_to_response(self, context, **response_kwargs):
if self.request.GET.get('format', 'html') == 'csv':
render_class = self.csv_response_class
response_kwargs.setdefault("filename", "usage.csv")
else:
render_class = self.response_class
context = self.render_context_with_title(context)
resp = render_class(request=self.request,
template=self.get_template_names(),
context=context,
content_type=self.get_content_type(),
**response_kwargs)
return resp
def get_data(self):
data = super(ProjectUsageView, self).get_data()
try:
self.usage.get_limits()
except Exception:
exceptions.handle(self.request,
_('Unable to retrieve limits information.'))
return data