Add metering panel to admin console
Now it has usage tables for disk usage, network traffic usage, network(neutron) usage, objectstore usage, all agregates of project. A multiseries linechart with choosable meters, group by, value and time span. Reasonable amount of tests implemented over all resource usage tabs. Change-Id: I21342d595ee08da45707e909f3d9802707d912cc Implements: blueprint admin-resource-usage-page
This commit is contained in:
parent
8ac4267a8d
commit
bef4ee0489
@ -22,7 +22,7 @@ import horizon
|
||||
class SystemPanels(horizon.PanelGroup):
|
||||
slug = "admin"
|
||||
name = _("System Panel")
|
||||
panels = ('overview', 'hypervisors', 'instances', 'volumes',
|
||||
panels = ('overview', 'metering', 'hypervisors', 'instances', 'volumes',
|
||||
'flavors', 'images', 'networks', 'routers', 'defaults', 'info')
|
||||
|
||||
|
||||
|
27
openstack_dashboard/dashboards/admin/metering/panel.py
Normal file
27
openstack_dashboard/dashboards/admin/metering/panel.py
Normal file
@ -0,0 +1,27 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# 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 django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
import horizon
|
||||
from openstack_dashboard.dashboards.admin import dashboard
|
||||
|
||||
|
||||
class Metering(horizon.Panel):
|
||||
name = _("Resource Usage")
|
||||
slug = 'metering'
|
||||
permissions = ('openstack.services.metering', 'openstack.roles.admin', )
|
||||
|
||||
|
||||
dashboard.Admin.register(Metering)
|
217
openstack_dashboard/dashboards/admin/metering/tables.py
Normal file
217
openstack_dashboard/dashboards/admin/metering/tables.py
Normal file
@ -0,0 +1,217 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django.template.defaultfilters import filesizeformat # noqa
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
from openstack_dashboard import api
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CommonFilterAction(tables.FilterAction):
|
||||
def filter(self, table, resources, filter_string):
|
||||
q = filter_string.lower()
|
||||
return [resource for resource in resources
|
||||
if q in resource.resource.lower() or
|
||||
q in resource.tenant.lower() or
|
||||
q in resource.user.lower()]
|
||||
|
||||
|
||||
def get_status(fields):
|
||||
# TODO(lsmola) it should periodically renew the tables I guess
|
||||
def transform(datum):
|
||||
if any([getattr(datum, field, None) is 0 or getattr(datum, field, None)
|
||||
for field in fields]):
|
||||
return _("up")
|
||||
else:
|
||||
return _("none")
|
||||
return transform
|
||||
|
||||
|
||||
class GlobalUsageTable(tables.DataTable):
|
||||
tenant = tables.Column("tenant", verbose_name=_("Tenant"), sortable=True,
|
||||
filters=(lambda (t): getattr(t, 'name', ""),))
|
||||
user = tables.Column("user", verbose_name=_("User"), sortable=True,
|
||||
filters=(lambda (u): getattr(u, 'name', ""),))
|
||||
instance = tables.Column("resource",
|
||||
verbose_name=_("Resource"),
|
||||
sortable=True)
|
||||
|
||||
|
||||
class GlobalDiskUsageTable(tables.DataTable):
|
||||
tenant = tables.Column("id", verbose_name=_("Tenant"), sortable=True)
|
||||
disk_read_bytes = tables.Column("disk_read_bytes",
|
||||
filters=(filesizeformat,),
|
||||
verbose_name=_("Disk Read Bytes"),
|
||||
sortable=True)
|
||||
disk_read_requests = tables.Column("disk_read_requests",
|
||||
verbose_name=_("Disk Read Requests"),
|
||||
sortable=True)
|
||||
disk_write_bytes = tables.Column("disk_write_bytes",
|
||||
verbose_name=_("Disk Write Bytes"),
|
||||
filters=(filesizeformat,),
|
||||
sortable=True)
|
||||
disk_write_requests = tables.Column("disk_write_requests",
|
||||
verbose_name=_("Disk Write Requests"),
|
||||
sortable=True)
|
||||
|
||||
class Meta:
|
||||
name = "global_disk_usage"
|
||||
verbose_name = _("Global Disk Usage (average of last 30 days)")
|
||||
table_actions = (CommonFilterAction,)
|
||||
multi_select = False
|
||||
|
||||
|
||||
class GlobalNetworkTrafficUsageTable(tables.DataTable):
|
||||
tenant = tables.Column("id", verbose_name=_("Tenant"), sortable=True)
|
||||
network_incoming_bytes = tables\
|
||||
.Column("network_incoming_bytes",
|
||||
verbose_name=_("Network incoming Bytes"),
|
||||
filters=(filesizeformat,),
|
||||
sortable=True)
|
||||
network_incoming_packets = tables\
|
||||
.Column("network_incoming_packets",
|
||||
verbose_name=_("Network incoming Packets"),
|
||||
sortable=True)
|
||||
network_outgoing_bytes = tables\
|
||||
.Column("network_outgoing_bytes",
|
||||
verbose_name=_("Network Outgoing Bytes"),
|
||||
filters=(filesizeformat,),
|
||||
sortable=True)
|
||||
network_outgoing_packets = tables\
|
||||
.Column("network_outgoing_packets",
|
||||
verbose_name=_("Network Outgoing Packets"),
|
||||
sortable=True)
|
||||
|
||||
class Meta:
|
||||
name = "global_network_traffic_usage"
|
||||
verbose_name = _("Global Network Traffic Usage (average "
|
||||
"of last 30 days)")
|
||||
table_actions = (CommonFilterAction,)
|
||||
multi_select = False
|
||||
|
||||
|
||||
class GlobalNetworkUsageTable(tables.DataTable):
|
||||
tenant = tables.Column("id", verbose_name=_("Tenant"), sortable=True)
|
||||
network_duration = tables.Column("network",
|
||||
verbose_name=_("Network Duration"),
|
||||
sortable=True)
|
||||
network_creation_requests = tables\
|
||||
.Column("network_create",
|
||||
verbose_name=_("Network Creation Requests"),
|
||||
sortable=True)
|
||||
subnet_duration = tables.Column("subnet",
|
||||
verbose_name=_("Subnet Duration"),
|
||||
sortable=True)
|
||||
subnet_creation = tables.Column("subnet_create",
|
||||
verbose_name=_("Subnet Creation Requests"),
|
||||
sortable=True)
|
||||
port_duration = tables.Column("port",
|
||||
verbose_name=_("Port Duration"),
|
||||
sortable=True)
|
||||
port_creation = tables.Column("port_create",
|
||||
verbose_name=_("Port Creation Requests"),
|
||||
sortable=True)
|
||||
router_duration = tables.Column("router",
|
||||
verbose_name=_("Router Duration"),
|
||||
sortable=True)
|
||||
router_creation = tables.Column("router_create",
|
||||
verbose_name=_("Router Creation Requests"),
|
||||
sortable=True)
|
||||
port_duration = tables.Column("port",
|
||||
verbose_name=_("Port Duration"),
|
||||
sortable=True)
|
||||
port_creation = tables.Column("port_create",
|
||||
verbose_name=_("Port Creation Requests"),
|
||||
sortable=True)
|
||||
ip_floating_duration = tables\
|
||||
.Column("ip_floating",
|
||||
verbose_name=_("Floating IP Duration"),
|
||||
sortable=True)
|
||||
ip_floating_creation = tables\
|
||||
.Column("ip_floating_create",
|
||||
verbose_name=_("Floating IP Creation Requests"),
|
||||
sortable=True)
|
||||
|
||||
class Meta:
|
||||
name = "global_network_usage"
|
||||
verbose_name = _("Global Network Usage (average of last 30 days)")
|
||||
table_actions = (CommonFilterAction,)
|
||||
multi_select = False
|
||||
|
||||
|
||||
class GlobalObjectStoreUsageUpdateRow(tables.Row):
|
||||
ajax = True
|
||||
|
||||
def get_data(self, request, object_id):
|
||||
ceilometer_usage = api.ceilometer.CeilometerUsage(request)
|
||||
|
||||
query = ceilometer_usage.query_from_object_id(object_id)
|
||||
try:
|
||||
data = ceilometer_usage.global_object_store_usage(
|
||||
query,
|
||||
with_statistics=True)
|
||||
except Exception:
|
||||
data = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve statistics.'))
|
||||
return None
|
||||
return data[0]
|
||||
|
||||
|
||||
class GlobalObjectStoreUsageTable(tables.DataTable):
|
||||
tenant = tables.Column("tenant", verbose_name=_("Tenant"), sortable=True,
|
||||
filters=(lambda (t): getattr(t, 'name', ""),))
|
||||
status = tables.Column(get_status(["storage_objects",
|
||||
"storage_objects_size",
|
||||
"storage_objects_incoming_bytes",
|
||||
"storage_objects_outgoing_bytes"]),
|
||||
verbose_name=_("Status"),
|
||||
hidden=True)
|
||||
resource = tables.Column("resource",
|
||||
verbose_name=_("Resource"),
|
||||
sortable=True)
|
||||
storage_incoming_bytes = tables.Column(
|
||||
"storage_objects_incoming_bytes",
|
||||
verbose_name=_("Object Storage Incoming Bytes"),
|
||||
filters=(filesizeformat,),
|
||||
sortable=True)
|
||||
storage_outgoing_bytes = tables.Column(
|
||||
"storage_objects_outgoing_bytes",
|
||||
verbose_name=_("Object Storage Outgoing Bytes"),
|
||||
filters=(filesizeformat,),
|
||||
sortable=True)
|
||||
storage_objects = tables.Column(
|
||||
"storage_objects",
|
||||
verbose_name=_("Total Number of Objects"),
|
||||
sortable=True)
|
||||
storage_objects_size = tables.Column(
|
||||
"storage_objects_size",
|
||||
filters=(filesizeformat,),
|
||||
verbose_name=_("Total Size of Objects "),
|
||||
sortable=True)
|
||||
|
||||
class Meta:
|
||||
name = "global_object_store_usage"
|
||||
verbose_name = _("Global Object Store Usage (average of last 30 days)")
|
||||
table_actions = (CommonFilterAction,)
|
||||
row_class = GlobalObjectStoreUsageUpdateRow
|
||||
status_columns = ["status"]
|
||||
multi_select = False
|
223
openstack_dashboard/dashboards/admin/metering/tabs.py
Normal file
223
openstack_dashboard/dashboards/admin/metering/tabs.py
Normal file
@ -0,0 +1,223 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# 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 datetime import datetime # noqa
|
||||
from datetime import timedelta # noqa
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api import ceilometer
|
||||
|
||||
from openstack_dashboard.dashboards.admin.metering import tables
|
||||
|
||||
|
||||
def make_tenant_queries(request, days_before=30):
|
||||
try:
|
||||
tenants, more = api.keystone.tenant_list(
|
||||
request,
|
||||
domain=None,
|
||||
paginate=True,
|
||||
marker="tenant_marker")
|
||||
except Exception:
|
||||
tenants = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve tenant list.'))
|
||||
queries = {}
|
||||
for tenant in tenants:
|
||||
tenant_query = [{
|
||||
"field": "project_id",
|
||||
"op": "eq",
|
||||
"value": tenant.id}]
|
||||
|
||||
queries[tenant.name] = tenant_query
|
||||
|
||||
# TODO(lsmola) Just show last 30 days, should be switchable somewhere
|
||||
# above the table.
|
||||
date_from = datetime.now() - timedelta(days_before)
|
||||
date_to = datetime.now()
|
||||
additional_query = [{'field': 'timestamp',
|
||||
'op': 'ge',
|
||||
'value': date_from},
|
||||
{'field': 'timestamp',
|
||||
'op': 'le',
|
||||
'value': date_to}]
|
||||
|
||||
return queries, additional_query
|
||||
|
||||
|
||||
def list_of_resource_aggregates(request, meters, stats_attr="avg"):
|
||||
queries, additional_query = make_tenant_queries(request)
|
||||
|
||||
ceilometer_usage = ceilometer.CeilometerUsage(request)
|
||||
try:
|
||||
resource_aggregates = ceilometer_usage.\
|
||||
resource_aggregates_with_statistics(
|
||||
queries, meters, stats_attr="avg",
|
||||
additional_query=additional_query)
|
||||
except Exception:
|
||||
resource_aggregates = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve statistics.'))
|
||||
|
||||
return resource_aggregates
|
||||
|
||||
|
||||
class GlobalDiskUsageTab(tabs.TableTab):
|
||||
table_classes = (tables.GlobalDiskUsageTable,)
|
||||
name = _("Global Disk Usage")
|
||||
slug = "global_disk_usage"
|
||||
template_name = ("horizon/common/_detail_table.html")
|
||||
preload = False
|
||||
|
||||
def get_global_disk_usage_data(self):
|
||||
""" Disk usage table data aggregated by project """
|
||||
request = self.tab_group.request
|
||||
return list_of_resource_aggregates(request,
|
||||
ceilometer.GlobalDiskUsage.meters)
|
||||
|
||||
|
||||
class GlobalNetworkTrafficUsageTab(tabs.TableTab):
|
||||
table_classes = (tables.GlobalNetworkTrafficUsageTable,)
|
||||
name = _("Global Network Traffic Usage")
|
||||
slug = "global_network_traffic_usage"
|
||||
template_name = ("horizon/common/_detail_table.html")
|
||||
preload = False
|
||||
|
||||
def get_global_network_traffic_usage_data(self):
|
||||
request = self.tab_group.request
|
||||
return list_of_resource_aggregates(request,
|
||||
ceilometer.GlobalNetworkTrafficUsage.meters)
|
||||
|
||||
|
||||
class GlobalNetworkUsageTab(tabs.TableTab):
|
||||
table_classes = (tables.GlobalNetworkUsageTable,)
|
||||
name = _("Global Network Usage")
|
||||
slug = "global_network_usage"
|
||||
template_name = ("horizon/common/_detail_table.html")
|
||||
preload = False
|
||||
|
||||
def get_global_network_usage_data(self):
|
||||
request = self.tab_group.request
|
||||
return list_of_resource_aggregates(request,
|
||||
ceilometer.GlobalNetworkUsage.meters)
|
||||
|
||||
def allowed(self, request):
|
||||
permissions = ("openstack.services.network",)
|
||||
return request.user.has_perms(permissions)
|
||||
|
||||
|
||||
class GlobalObjectStoreUsageTab(tabs.TableTab):
|
||||
table_classes = (tables.GlobalObjectStoreUsageTable,)
|
||||
name = _("Global Object Store Usage")
|
||||
slug = "global_object_store_usage"
|
||||
template_name = ("horizon/common/_detail_table.html")
|
||||
preload = False
|
||||
|
||||
def get_global_object_store_usage_data(self):
|
||||
request = self.tab_group.request
|
||||
ceilometer_usage = ceilometer.CeilometerUsage(request)
|
||||
|
||||
date_from = datetime.now() - timedelta(30)
|
||||
date_to = datetime.now()
|
||||
additional_query = [{'field': 'timestamp',
|
||||
'op': 'ge',
|
||||
'value': date_from},
|
||||
{'field': 'timestamp',
|
||||
'op': 'le',
|
||||
'value': date_to}]
|
||||
try:
|
||||
result = ceilometer_usage.global_object_store_usage(
|
||||
with_statistics=True, additional_query=additional_query)
|
||||
except Exception:
|
||||
result = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve statistics.'))
|
||||
return result
|
||||
|
||||
def allowed(self, request):
|
||||
permissions = ("openstack.services.object-store",)
|
||||
return request.user.has_perms(permissions)
|
||||
|
||||
|
||||
class GlobalStatsTab(tabs.Tab):
|
||||
name = _("Stats")
|
||||
slug = "stats"
|
||||
template_name = ("admin/metering/stats.html")
|
||||
preload = False
|
||||
|
||||
def get_context_data(self, request):
|
||||
query = [{"field": "metadata.OS-EXT-AZ:availability_zone",
|
||||
"op": "eq",
|
||||
"value": "nova"}]
|
||||
try:
|
||||
resources = ceilometer.resource_list(request, query,
|
||||
ceilometer_usage_object=None)
|
||||
except Exception:
|
||||
resources = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve Nova Ceilometer '
|
||||
'resources.'))
|
||||
try:
|
||||
resource = resources[0]
|
||||
meters = [link['rel'] for link in resource.links
|
||||
if link['rel'] != "self"]
|
||||
except IndexError:
|
||||
resource = None
|
||||
meters = []
|
||||
|
||||
meter_titles = {"instance": _("Duration of instance"),
|
||||
"instance:<type>": _("Duration of instance <type>"
|
||||
" (openstack types)"),
|
||||
"memory": _("Volume of RAM in MB"),
|
||||
"cpu": _("CPU time used"),
|
||||
"cpu_util": _("Average CPU utilisation"),
|
||||
"vcpus": _("Number of VCPUs"),
|
||||
"disk.read.requests": _("Number of read requests"),
|
||||
"disk.write.requests": _("Number of write requests"),
|
||||
"disk.read.bytes": _("Volume of read in B"),
|
||||
"disk.write.bytes": _("Volume of write in B"),
|
||||
"disk.root.size": _("Size of root disk in GB"),
|
||||
"disk.ephemeral.size": _("Size of ephemeral disk "
|
||||
"in GB"),
|
||||
"network.incoming.bytes": _("number of incoming bytes "
|
||||
"on the network for a VM interface"),
|
||||
"network.outgoing.bytes": _("number of outgoing bytes "
|
||||
"on the network for a VM interface"),
|
||||
"network.incoming.packets": _("number of incoming "
|
||||
"packets for a VM interface"),
|
||||
"network.outgoing.packets": _("number of outgoing "
|
||||
"packets for a VM interface")}
|
||||
|
||||
class MetersWrap(object):
|
||||
""" A quick wrapper for meter and associated titles. """
|
||||
def __init__(self, meter, meter_titles):
|
||||
self.name = meter
|
||||
self.title = meter_titles.get(meter, "")
|
||||
|
||||
meters_objs = []
|
||||
for meter in meters:
|
||||
meters_objs.append(MetersWrap(meter, meter_titles))
|
||||
|
||||
context = {'meters': meters_objs}
|
||||
return context
|
||||
|
||||
|
||||
class CeilometerOverviewTabs(tabs.TabGroup):
|
||||
slug = "ceilometer_overview"
|
||||
tabs = (GlobalDiskUsageTab, GlobalNetworkTrafficUsageTab,
|
||||
GlobalObjectStoreUsageTab, GlobalNetworkUsageTab, GlobalStatsTab,)
|
||||
sticky = True
|
@ -0,0 +1,15 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Resources usage Overview" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Resources usage Overview")%}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,180 @@
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
<div id="samples_url" url="{% url "horizon:admin:metering:samples" %}"></div>
|
||||
<div id="ceilometer-stats">
|
||||
<form class="form-horizontal"
|
||||
id="linechart_general_form">
|
||||
|
||||
<div class="control-group">
|
||||
<label for="meter" class="control-label">{% trans "Metric" %}: </label>
|
||||
<div class="controls line_chart_time_picker">
|
||||
<select data-line-chart-command="select_box_change"
|
||||
name="meter" id="meter" class="span2 example">
|
||||
<optgroup label='{% trans "Compute (Nova)" %}'>
|
||||
{% for meter in meters %}
|
||||
<option title="{{ meter.title }}" value="{{ meter.name }}" data-unit="">
|
||||
{{ meter.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
|
||||
<optgroup label='{% trans "Network (Neutron)" %}'>
|
||||
<option title='{% trans "Duration of network" %}' value="network" data-unit="">network</option>
|
||||
<option title='{% trans "Creation requests for this network" %}' value="network.create" data-unit="">network.create</option>
|
||||
<option title='{% trans "Update requests for this network" %}' value="network.update" data-unit="">network.update</option>
|
||||
<option title='{% trans "Duration of subnet" %}' value="subnet" data-unit="">subnet</option>
|
||||
<option title='{% trans "Creation requests for this subnet" %}' value="subnet.create" data-unit="">subnet.create</option>
|
||||
<option title='{% trans "Update requests for this subnet" %}' value="subnet.update" data-unit="">subnet.update</option>
|
||||
<option title='{% trans "Creation requests for this port" %}' value="port.create" data-unit="">port.create</option>
|
||||
<option title='{% trans "Update requests for this port" %}' value="port.update" data-unit="">port.update</option>
|
||||
<option title='{% trans "Duration of router" %}' value="router" data-unit="">router</option>
|
||||
<option title='{% trans "Creation requests for this router" %}' value="router.create" data-unit="">router.create</option>
|
||||
<option title='{% trans "Update requests for this router" %}' value="router.update" data-unit="">router.update</option>
|
||||
<option title='{% trans "Duration of floating ip" %}' value="ip.floating" data-unit="">ip.floating</option>
|
||||
<option title='{% trans "Creation requests for this floating ip" %}' value="ip.floating.create" data-unit="">ip.floating.create</option>
|
||||
<option title='{% trans "Update requests for this floating ip" %}' value="ip.floating.update" data-unit="">ip.floating.update</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label='{% trans "Image (Glance)" %}'>
|
||||
<option title='{% trans "Uploaded image size" %}' value="image.size" data-unit="">image.size</option>
|
||||
<option title='{% trans "Number of update on the image" %}' value="image.update " data-unit="">image.update </option>
|
||||
<option title='{% trans "Number of upload of the image" %}' value="image.upload " data-unit="">image.upload </option>
|
||||
<option title='{% trans "Number of delete on the image" %}' value="image.delete " data-unit="">image.delete </option>
|
||||
<option title='{% trans "Image is downloaded" %}' value="image.download" data-unit="">image.download</option>
|
||||
<option title='{% trans "Image is served out" %}' value="image.serve" data-unit="">image.serve</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label='{% trans "Volume (Cinder)" %}'>
|
||||
<option title='{% trans "Duration of volume" %}' value="volume" data-unit="">volume</option>
|
||||
<option title='{% trans "Size of volume" %}' value="volume.size" data-unit="">volume.size</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label='{% trans "Object Storage (Swift)" %}'>
|
||||
<option title='{% trans "Number of objects" %}' value="storage.objects" data-unit="">storage.objects</option>
|
||||
<option title='{% trans "Total size of stored objects" %}' value="storage.objects.size" data-unit="">storage.objects.size</option>
|
||||
<option title='{% trans "Number of containers" %}' value="" data-unit="storage.objects.containers">storage.objects.containers</option>
|
||||
<option title='{% trans "Number of incoming bytes" %}' value="storage.objects.incoming.bytes" data-unit="">storage.objects.incoming.bytes</option>
|
||||
<option title='{% trans "Number of outgoing bytes" %}' value="storage.objects.outgoing.bytes" data-unit="">storage.objects.outgoing.bytes</option>
|
||||
<option title='{% trans "Number of API requests against swift" %}' value="storage.api.request" data-unit="">storage.api.request</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label='{% trans "Energy (Kwapi)" %}'>
|
||||
<option title='{% trans "Amount of energy" %}' value="energy" data-unit="">energy</option>
|
||||
<option title='{% trans "Power consumption" %}' value="power" data-unit="">power</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="group_by" class="control-label">{% trans "Group by" %}: </label>
|
||||
<div class="controls">
|
||||
<select data-line-chart-command="select_box_change"
|
||||
id="group_by" name="group_by" class="span2">
|
||||
<option value="" selected="selected">{% trans "--" %}</option>
|
||||
<option selected="selected" value="project" selected>{% trans "Project" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label for="stats_attr" class="control-label">{% trans "Value" %}: </label>
|
||||
<div class="controls">
|
||||
<select data-line-chart-command="select_box_change"
|
||||
id="stats_attr" name="stats_attr" class="span2">
|
||||
|
||||
<option selected="selected" value="avg">{% trans "Avg." %}</option>
|
||||
<option value="min">{% trans "Min." %}</option>
|
||||
<option value="max">{% trans "Max." %}</option>
|
||||
<option value="sum">{% trans "Sum." %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label for="date_options" class="control-label">{% trans "Period" %}: </label>
|
||||
<div class="controls">
|
||||
<select data-line-chart-command="select_box_change"
|
||||
id="date_options" name="date_options" class="span2">
|
||||
<option value="1">{% trans "Last day" %}</option>
|
||||
<option value="7" selected="selected">{% trans "Last week" %}</option>
|
||||
<option value="15">{% trans "Last 15 days" %}</option>
|
||||
<option value="30">{% trans "Last 30 days" %}</option>
|
||||
<option value="365">{% trans "Last year" %}</option>
|
||||
<option value="other">{% trans "Other" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" id="date_from">
|
||||
<label for="date_from" class="control-label">{% trans "From" %}: </label>
|
||||
<div class="controls">
|
||||
<input data-line-chart-command="date_picker_change"
|
||||
type="text" id="date_from" name="date_from" class="span2 example"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" id="date_to">
|
||||
<label for="date_to" class="control-label">{% trans "To" %}: </label>
|
||||
<div class="controls">
|
||||
<input data-line-chart-command="date_picker_change"
|
||||
type="text" name="date_to" class="span2 example"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="info row-fluid detail">
|
||||
<div class="span12">
|
||||
<h4>{% trans "Statistics of all resources" %}</h4>
|
||||
<hr class="header_rule" />
|
||||
<div class="info row-fluid detail">
|
||||
<div class="span9 chart_container">
|
||||
<div class="chart"
|
||||
data-chart-type="line_chart"
|
||||
data-url="{% url 'horizon:admin:metering:samples'%}"
|
||||
data-form-selector='#linechart_general_form'
|
||||
data-legend-selector="#legend"
|
||||
data-smoother-selector="#smoother"
|
||||
data-slider-selector="#slider">
|
||||
</div>
|
||||
<div id="slider"></div>
|
||||
</div>
|
||||
<div class="span3 legend_container">
|
||||
<div id="smoother" title="Smoothing"></div>
|
||||
<div id="legend"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
if (typeof horizon.d3_line_chart !== 'undefined') {
|
||||
horizon.d3_line_chart.init("div[data-chart-type='line_chart']");
|
||||
}
|
||||
|
||||
if (typeof $ !== 'undefined') {
|
||||
show_hide_datepickers();
|
||||
} else {
|
||||
addHorizonLoadEvent(function() {
|
||||
show_hide_datepickers();
|
||||
});
|
||||
}
|
||||
|
||||
function show_hide_datepickers() {
|
||||
$("#date_options").change(function(evt) {
|
||||
// Enhancing behaviour of selectbox, on 'other' value selected, I don't
|
||||
// want to refresh, but show hide the date fields
|
||||
if ($(this).find("option:selected").val() == "other"){
|
||||
evt.stopPropagation();
|
||||
$("#date_from, #date_to").show();
|
||||
} else {
|
||||
$("#date_from, #date_to").hide();
|
||||
}
|
||||
});
|
||||
if ($("#date_options").find("option:selected").val() == "other"){
|
||||
$("#date_from, #date_to").show();
|
||||
} else {
|
||||
$("#date_from, #date_to").hide();
|
||||
}
|
||||
}
|
||||
</script>
|
345
openstack_dashboard/dashboards/admin/metering/tests.py
Normal file
345
openstack_dashboard/dashboards/admin/metering/tests.py
Normal file
@ -0,0 +1,345 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# 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 django.core.urlresolvers import reverse # noqa
|
||||
from django import http # noqa
|
||||
from mox import IsA # noqa
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
INDEX_URL = reverse("horizon:admin:metering:index")
|
||||
|
||||
|
||||
class MeteringViewTests(test.APITestCase, test.BaseAdminViewTests):
|
||||
@test.create_stubs({api.keystone: ('tenant_list',)})
|
||||
def test_disk_usage(self):
|
||||
statistics = self.statistics.list()
|
||||
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest),
|
||||
domain=None,
|
||||
marker='tenant_marker',
|
||||
paginate=True) \
|
||||
.AndReturn([self.tenants.list(), False])
|
||||
|
||||
ceilometerclient = self.stub_ceilometerclient()
|
||||
ceilometerclient.statistics = self.mox.CreateMockAnything()
|
||||
# check that list is called twice for one resource and 2 meters
|
||||
ceilometerclient.statistics.list(meter_name=IsA(str),
|
||||
period=None, q=IsA(list)).\
|
||||
MultipleTimes().\
|
||||
AndReturn(statistics)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# getting all resources and with statistics
|
||||
res = self.client.get(reverse('horizon:admin:metering:index'))
|
||||
self.assertTemplateUsed(res, 'admin/metering/index.html')
|
||||
table_stats = res.context['table'].data
|
||||
|
||||
first = table_stats[0]
|
||||
self.assertEqual(first.id, 'test_tenant')
|
||||
self.assertEqual(first.disk_write_requests, 4.55)
|
||||
self.assertEqual(first.disk_read_bytes, 4.55)
|
||||
self.assertEqual(first.disk_write_bytes, 4.55)
|
||||
self.assertEqual(first.disk_read_bytes, 4.55)
|
||||
|
||||
second = table_stats[1]
|
||||
self.assertEqual(second.id, 'disabled_tenant')
|
||||
self.assertEqual(second.disk_write_requests, 4.55)
|
||||
self.assertEqual(second.disk_read_bytes, 4.55)
|
||||
self.assertEqual(second.disk_write_bytes, 4.55)
|
||||
self.assertEqual(second.disk_read_bytes, 4.55)
|
||||
|
||||
# check there is as many rows as tenants
|
||||
self.assertEqual(len(table_stats),
|
||||
len(self.tenants.list()))
|
||||
self.assertIsInstance(first, api.ceilometer.ResourceAggregate)
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',)})
|
||||
def test_global_network_traffic_usage(self):
|
||||
statistics = self.statistics.list()
|
||||
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest),
|
||||
domain=None,
|
||||
marker='tenant_marker',
|
||||
paginate=True) \
|
||||
.AndReturn([self.tenants.list(), False])
|
||||
|
||||
ceilometerclient = self.stub_ceilometerclient()
|
||||
ceilometerclient.statistics = self.mox.CreateMockAnything()
|
||||
# check that list is called twice for one resource and 2 meters
|
||||
ceilometerclient.statistics.list(meter_name=IsA(str),
|
||||
period=None, q=IsA(list)).\
|
||||
MultipleTimes().\
|
||||
AndReturn(statistics)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# getting all resources and with statistics
|
||||
res = self.client.get(reverse('horizon:admin:metering:index') +
|
||||
"?tab=ceilometer_overview__global_network_traffic_usage")
|
||||
self.assertTemplateUsed(res, 'admin/metering/index.html')
|
||||
table_stats = res.context['table'].data
|
||||
|
||||
first = table_stats[0]
|
||||
self.assertEqual(first.id, 'test_tenant')
|
||||
self.assertEqual(first.network_incoming_bytes, 4.55)
|
||||
self.assertEqual(first.network_incoming_packets, 4.55)
|
||||
self.assertEqual(first.network_outgoing_bytes, 4.55)
|
||||
self.assertEqual(first.network_outgoing_packets, 4.55)
|
||||
|
||||
second = table_stats[1]
|
||||
self.assertEqual(second.id, 'disabled_tenant')
|
||||
self.assertEqual(second.network_incoming_bytes, 4.55)
|
||||
self.assertEqual(second.network_incoming_packets, 4.55)
|
||||
self.assertEqual(second.network_outgoing_bytes, 4.55)
|
||||
self.assertEqual(second.network_outgoing_packets, 4.55)
|
||||
|
||||
# check there is as many rows as tenants
|
||||
self.assertEqual(len(table_stats),
|
||||
len(self.tenants.list()))
|
||||
self.assertIsInstance(first, api.ceilometer.ResourceAggregate)
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',)})
|
||||
def test_global_network_usage(self):
|
||||
statistics = self.statistics.list()
|
||||
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest),
|
||||
domain=None,
|
||||
marker='tenant_marker',
|
||||
paginate=True) \
|
||||
.AndReturn([self.tenants.list(), False])
|
||||
|
||||
ceilometerclient = self.stub_ceilometerclient()
|
||||
ceilometerclient.statistics = self.mox.CreateMockAnything()
|
||||
# check that list is called twice for one resource and 2 meters
|
||||
ceilometerclient.statistics.list(meter_name=IsA(str),
|
||||
period=None, q=IsA(list)).\
|
||||
MultipleTimes().\
|
||||
AndReturn(statistics)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# getting all resources and with statistics
|
||||
res = self.client.get(reverse('horizon:admin:metering:index') +
|
||||
"?tab=ceilometer_overview__global_network_usage")
|
||||
self.assertTemplateUsed(res, 'admin/metering/index.html')
|
||||
table_stats = res.context['table'].data
|
||||
|
||||
first = table_stats[0]
|
||||
self.assertEqual(first.id, 'test_tenant')
|
||||
self.assertEqual(first.network, 4.55)
|
||||
self.assertEqual(first.network_create, 4.55)
|
||||
self.assertEqual(first.subnet, 4.55)
|
||||
self.assertEqual(first.subnet_create, 4.55)
|
||||
self.assertEqual(first.port, 4.55)
|
||||
self.assertEqual(first.port_create, 4.55)
|
||||
self.assertEqual(first.router, 4.55)
|
||||
self.assertEqual(first.router_create, 4.55)
|
||||
self.assertEqual(first.ip_floating, 4.55)
|
||||
self.assertEqual(first.ip_floating_create, 4.55)
|
||||
|
||||
second = table_stats[1]
|
||||
self.assertEqual(second.id, 'disabled_tenant')
|
||||
self.assertEqual(second.network, 4.55)
|
||||
self.assertEqual(second.network_create, 4.55)
|
||||
self.assertEqual(second.subnet, 4.55)
|
||||
self.assertEqual(second.subnet_create, 4.55)
|
||||
self.assertEqual(second.port, 4.55)
|
||||
self.assertEqual(second.port_create, 4.55)
|
||||
self.assertEqual(second.router, 4.55)
|
||||
self.assertEqual(second.router_create, 4.55)
|
||||
self.assertEqual(second.ip_floating, 4.55)
|
||||
self.assertEqual(second.ip_floating_create, 4.55)
|
||||
|
||||
# check there is as many rows as tenants
|
||||
self.assertEqual(len(table_stats),
|
||||
len(self.tenants.list()))
|
||||
self.assertIsInstance(first, api.ceilometer.ResourceAggregate)
|
||||
|
||||
@test.create_stubs({api.ceilometer.CeilometerUsage: ("get_user",
|
||||
"get_tenant")})
|
||||
def test_global_object_store_usage(self):
|
||||
resources = self.resources.list()
|
||||
statistics = self.statistics.list()
|
||||
user = self.ceilometer_users.list()[0]
|
||||
tenant = self.ceilometer_tenants.list()[0]
|
||||
|
||||
ceilometerclient = self.stub_ceilometerclient()
|
||||
ceilometerclient.resources = self.mox.CreateMockAnything()
|
||||
ceilometerclient.resources.list(q=None).AndReturn(resources)
|
||||
|
||||
ceilometerclient.statistics = self.mox.CreateMockAnything()
|
||||
ceilometerclient.statistics.list(meter_name=IsA(str),
|
||||
period=None, q=IsA(list)).\
|
||||
MultipleTimes().\
|
||||
AndReturn(statistics)
|
||||
|
||||
api.ceilometer.CeilometerUsage\
|
||||
.get_user(IsA(str)).MultipleTimes().AndReturn(user)
|
||||
api.ceilometer.CeilometerUsage\
|
||||
.get_tenant(IsA(str)).MultipleTimes().AndReturn(tenant)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# getting all resources and with statistics
|
||||
res = self.client.get(reverse('horizon:admin:metering:index') +
|
||||
"?tab=ceilometer_overview__global_object_store_usage")
|
||||
self.assertTemplateUsed(res, 'admin/metering/index.html')
|
||||
table_stats = res.context['table'].data
|
||||
|
||||
first = table_stats[0]
|
||||
self.assertEqual(first.id, 'fake_project_id__fake_user_id__'
|
||||
'fake_resource_id')
|
||||
self.assertEqual(first.user.name, 'user')
|
||||
self.assertEqual(first.tenant.name, 'test_tenant')
|
||||
self.assertEqual(first.resource, 'fake_resource_id')
|
||||
|
||||
self.assertEqual(first.storage_objects, 4.55)
|
||||
self.assertEqual(first.storage_objects_size, 4.55)
|
||||
self.assertEqual(first.storage_objects_incoming_bytes, 4.55)
|
||||
self.assertEqual(first.storage_objects_outgoing_bytes, 4.55)
|
||||
|
||||
self.assertEqual(len(table_stats), len(resources))
|
||||
self.assertIsInstance(first, api.ceilometer.GlobalObjectStoreUsage)
|
||||
|
||||
def test_stats_page(self):
|
||||
resources = self.resources.list()
|
||||
ceilometerclient = self.stub_ceilometerclient()
|
||||
ceilometerclient.resources = self.mox.CreateMockAnything()
|
||||
# I am returning only 1 resource
|
||||
ceilometerclient.resources.list(q=IsA(list)).AndReturn(resources[:1])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# getting all resources and with statistics
|
||||
res = self.client.get(reverse('horizon:admin:metering:index') +
|
||||
"?tab=ceilometer_overview__stats")
|
||||
self.assertTemplateUsed(res, 'admin/metering/index.html')
|
||||
self.assertTemplateUsed(res, 'admin/metering/stats.html')
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',)})
|
||||
def test_stats_for_line_chart(self):
|
||||
statistics = self.statistics.list()
|
||||
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest),
|
||||
domain=None,
|
||||
marker='tenant_marker',
|
||||
paginate=True) \
|
||||
.AndReturn([self.tenants.list(), False])
|
||||
|
||||
ceilometerclient = self.stub_ceilometerclient()
|
||||
ceilometerclient.statistics = self.mox.CreateMockAnything()
|
||||
# check that list is called twice for one resource and 2 meters
|
||||
ceilometerclient.statistics.list(meter_name="memory",
|
||||
period=IsA(int), q=IsA(list)).\
|
||||
MultipleTimes().\
|
||||
AndReturn(statistics)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# get all statistics of project aggregates
|
||||
res = self.client.get(reverse('horizon:admin:metering:samples') +
|
||||
"?meter=memory&group_by=project&stats_attr=avg&date_options=7")
|
||||
|
||||
self.assertEqual(res._headers['content-type'],
|
||||
('Content-Type', 'application/json'))
|
||||
self.assertEqual(res._container,
|
||||
['{"series": [{"data": [{"y": 4, '
|
||||
'"x": "2012-12-21T11:00:55"}], '
|
||||
'"name": "test_tenant", "unit": ""}, '
|
||||
'{"data": [{"y": 4, '
|
||||
'"x": "2012-12-21T11:00:55"}], '
|
||||
'"name": "disabled_tenant", '
|
||||
'"unit": ""}, '
|
||||
'{"data": [{"y": 4, '
|
||||
'"x": "2012-12-21T11:00:55"}], '
|
||||
'"name": "\\u4e91\\u89c4\\u5219", '
|
||||
'"unit": ""}], '
|
||||
'"settings": {}}'])
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',)})
|
||||
def test_stats_for_line_chart_attr_max(self):
|
||||
statistics = self.statistics.list()
|
||||
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest),
|
||||
domain=None,
|
||||
marker='tenant_marker',
|
||||
paginate=True) \
|
||||
.AndReturn([self.tenants.list(), False])
|
||||
|
||||
ceilometerclient = self.stub_ceilometerclient()
|
||||
ceilometerclient.statistics = self.mox.CreateMockAnything()
|
||||
# check that list is called twice for one resource and 2 meters
|
||||
ceilometerclient.statistics.list(meter_name="memory",
|
||||
period=IsA(int), q=IsA(list)).\
|
||||
MultipleTimes().\
|
||||
AndReturn(statistics)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# get all statistics of project aggregates
|
||||
res = self.client.get(reverse('horizon:admin:metering:samples') +
|
||||
"?meter=memory&group_by=project&stats_attr=max&date_options=7")
|
||||
|
||||
self.assertEqual(res._headers['content-type'],
|
||||
('Content-Type', 'application/json'))
|
||||
self.assertEqual(res._container,
|
||||
['{"series": [{"data": [{"y": 9, '
|
||||
'"x": "2012-12-21T11:00:55"}], '
|
||||
'"name": "test_tenant", "unit": ""}, '
|
||||
'{"data": [{"y": 9, '
|
||||
'"x": "2012-12-21T11:00:55"}], '
|
||||
'"name": "disabled_tenant", '
|
||||
'"unit": ""}, '
|
||||
'{"data": [{"y": 9, '
|
||||
'"x": "2012-12-21T11:00:55"}], '
|
||||
'"name": "\\u4e91\\u89c4\\u5219", '
|
||||
'"unit": ""}], '
|
||||
'"settings": {}}'])
|
||||
|
||||
def test_stats_for_line_chart_no_group_by(self):
|
||||
resources = self.resources.list()
|
||||
statistics = self.statistics.list()
|
||||
|
||||
ceilometerclient = self.stub_ceilometerclient()
|
||||
ceilometerclient.resources = self.mox.CreateMockAnything()
|
||||
ceilometerclient.resources.list(q=IsA(list)).AndReturn(resources)
|
||||
|
||||
ceilometerclient.statistics = self.mox.CreateMockAnything()
|
||||
ceilometerclient.statistics.list(meter_name="storage.objects",
|
||||
period=IsA(int), q=IsA(list)).\
|
||||
MultipleTimes().\
|
||||
AndReturn(statistics)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# getting all resources and with statistics, I have only
|
||||
# 'storage.objects' defined in test data
|
||||
res = self.client.get(reverse('horizon:admin:metering:samples') +
|
||||
"?meter=storage.objects&stats_attr=avg&date_options=7")
|
||||
|
||||
self.assertEqual(res._headers['content-type'],
|
||||
('Content-Type', 'application/json'))
|
||||
self.assertEqual(res._container,
|
||||
['{"series": [{"data": [{"y": 4, '
|
||||
'"x": "2012-12-21T11:00:55"}], '
|
||||
'"name": "fake_resource_id", '
|
||||
'"unit": ""}, '
|
||||
'{"data": [{"y": 4, '
|
||||
'"x": "2012-12-21T11:00:55"}], '
|
||||
'"name": "fake_resource_id2", '
|
||||
'"unit": ""}], '
|
||||
'"settings": {}}'])
|
22
openstack_dashboard/dashboards/admin/metering/urls.py
Normal file
22
openstack_dashboard/dashboards/admin/metering/urls.py
Normal file
@ -0,0 +1,22 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# 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 django.conf.urls import patterns # noqa
|
||||
from django.conf.urls import url # noqa
|
||||
|
||||
from openstack_dashboard.dashboards.admin.metering import views
|
||||
|
||||
urlpatterns = patterns('openstack_dashboard.dashboards.admin.metering.views',
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^samples$', views.SamplesView.as_view(), name='samples'))
|
152
openstack_dashboard/dashboards/admin/metering/views.py
Normal file
152
openstack_dashboard/dashboards/admin/metering/views.py
Normal file
@ -0,0 +1,152 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# 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 datetime import datetime # noqa
|
||||
from datetime import timedelta # noqa
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.http import HttpResponse # noqa
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
from django.views.generic import TemplateView # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api import ceilometer
|
||||
|
||||
from openstack_dashboard.dashboards.admin.metering import tabs as \
|
||||
metering_tabs
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(tabs.TabbedTableView):
|
||||
tab_group_class = metering_tabs.CeilometerOverviewTabs
|
||||
template_name = 'admin/metering/index.html'
|
||||
|
||||
|
||||
class SamplesView(TemplateView):
|
||||
template_name = "admin/metering/samples.csv"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
meter = request.GET.get('meter', None)
|
||||
meter_name = meter.replace(".", "_")
|
||||
date_options = request.GET.get('date_options', None)
|
||||
date_from = request.GET.get('date_from', None)
|
||||
date_to = request.GET.get('date_to', None)
|
||||
resource = request.GET.get('resource', None)
|
||||
stats_attr = request.GET.get('stats_attr', 'avg')
|
||||
|
||||
if (date_options == "other"):
|
||||
try:
|
||||
if date_from:
|
||||
date_from = datetime.strptime(date_from,
|
||||
"%Y-%m-%d")
|
||||
if date_to:
|
||||
date_to = datetime.strptime(date_to,
|
||||
"%Y-%m-%d")
|
||||
except ValueError:
|
||||
raise exceptions.NotFound
|
||||
else:
|
||||
date_from = datetime.now() - timedelta(days=int(date_options))
|
||||
date_to = datetime.now()
|
||||
|
||||
query = [{"field": "metadata.OS-EXT-AZ:availability_zone",
|
||||
"op": "eq",
|
||||
"value": "nova"}]
|
||||
|
||||
additional_query = []
|
||||
if date_from:
|
||||
additional_query += [{'field': 'timestamp',
|
||||
'op': 'ge',
|
||||
'value': date_from}]
|
||||
if date_to:
|
||||
additional_query += [{'field': 'timestamp',
|
||||
'op': 'le',
|
||||
'value': date_to}]
|
||||
|
||||
if request.GET.get('group_by', None) == "project":
|
||||
try:
|
||||
tenants, more = api.keystone.tenant_list(
|
||||
request,
|
||||
domain=None,
|
||||
paginate=True,
|
||||
marker="tenant_marker")
|
||||
except Exception:
|
||||
tenants = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve tenant list.'))
|
||||
queries = {}
|
||||
for tenant in tenants:
|
||||
tenant_query = [{
|
||||
"field": "project_id",
|
||||
"op": "eq",
|
||||
"value": tenant.id}]
|
||||
|
||||
queries[tenant.name] = tenant_query
|
||||
|
||||
ceilometer_usage = ceilometer.CeilometerUsage(request)
|
||||
resources = ceilometer_usage.resource_aggregates_with_statistics(
|
||||
queries, [meter], period=1000, stats_attr=None,
|
||||
additional_query=additional_query)
|
||||
|
||||
series = []
|
||||
for resource in resources:
|
||||
name = resource.id
|
||||
if getattr(resource, meter_name):
|
||||
serie = {'unit': getattr(getattr(resource, meter_name)[0],
|
||||
'unit', ""),
|
||||
'name': name,
|
||||
'data': []}
|
||||
|
||||
for statistic in getattr(resource, meter_name):
|
||||
date = statistic.duration_end[:19]
|
||||
value = int(getattr(statistic, stats_attr))
|
||||
serie['data'].append({'x': date, 'y': value})
|
||||
|
||||
series.append(serie)
|
||||
else:
|
||||
ceilometer_usage = ceilometer.CeilometerUsage(request)
|
||||
try:
|
||||
resources = ceilometer_usage.resources_with_statistics(
|
||||
query, [meter], period=1000, stats_attr=None,
|
||||
additional_query=additional_query)
|
||||
except Exception:
|
||||
resources = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve statistics.'))
|
||||
|
||||
series = []
|
||||
for resource in resources:
|
||||
if getattr(resource, meter_name):
|
||||
serie = {'unit': getattr(getattr(resource, meter_name)[0],
|
||||
'unit', ""),
|
||||
'name': resource.resource_id,
|
||||
'data': []}
|
||||
for statistic in getattr(resource, meter_name):
|
||||
date = statistic.duration_end[:19]
|
||||
value = int(getattr(statistic, stats_attr))
|
||||
serie['data'].append({'x': date, 'y': value})
|
||||
|
||||
series.append(serie)
|
||||
|
||||
ret = {}
|
||||
ret['series'] = series
|
||||
ret['settings'] = {}
|
||||
|
||||
return HttpResponse(json.dumps(ret),
|
||||
mimetype='application/json')
|
@ -2031,6 +2031,7 @@ label.log-length {
|
||||
top: -100px;
|
||||
}
|
||||
|
||||
|
||||
/**** Resource Topology CSS ****/
|
||||
.link {stroke: #999;stroke-width: 1.5px;}
|
||||
.node {cursor:pointer;}
|
||||
@ -2049,3 +2050,24 @@ label.log-length {
|
||||
#info_box p {margin:0;font-size:9pt;line-height:14px;}
|
||||
#info_box a {margin:0;font-size:9pt;line-height:14px;}
|
||||
#info_box .error {color:darkred;}
|
||||
|
||||
#ceilometer-stats .form-horizontal {
|
||||
.control-label {
|
||||
width: auto;
|
||||
}
|
||||
.controls {
|
||||
float: left;
|
||||
margin-left: 0;
|
||||
}
|
||||
.control-group {
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.btn {
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -36,6 +36,7 @@ def data(TEST):
|
||||
TEST.global_network_usages = utils.TestDataContainer()
|
||||
TEST.global_network_traffic_usages = utils.TestDataContainer()
|
||||
TEST.global_object_store_usages = utils.TestDataContainer()
|
||||
TEST.statistics_array = utils.TestDataContainer()
|
||||
|
||||
# users
|
||||
ceilometer_user_dict1 = {'id': "1",
|
||||
@ -93,6 +94,7 @@ def data(TEST):
|
||||
user_id="fake_user_id",
|
||||
timestamp='2012-07-02T10:42:00.000000',
|
||||
metadata={'tag': 'self.counter3', 'display_name': 'test-server'},
|
||||
links=[{'url': 'test_url', 'rel': 'storage.objects'}],
|
||||
)
|
||||
resource_dict_2 = dict(
|
||||
resource_id='fake_resource_id2',
|
||||
@ -100,6 +102,7 @@ def data(TEST):
|
||||
user_id="fake_user_id",
|
||||
timestamp='2012-07-02T10:42:00.000000',
|
||||
metadata={'tag': 'self.counter3', 'display_name': 'test-server'},
|
||||
links=[{'url': 'test_url', 'rel': 'storage.objects'}],
|
||||
)
|
||||
resource_1 = resources.Resource(resources.ResourceManager(None),
|
||||
resource_dict_1)
|
||||
|
Loading…
Reference in New Issue
Block a user