diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/details.html b/tuskar_ui/infrastructure/nodes/templates/nodes/details.html
index 99d5a124e..f807f46db 100644
--- a/tuskar_ui/infrastructure/nodes/templates/nodes/details.html
+++ b/tuskar_ui/infrastructure/nodes/templates/nodes/details.html
@@ -9,41 +9,136 @@
{% block main %}
-
{% trans "Info" %}
-
- - {% trans "MAC Addresses" %}
- - {{ node.addresses|join:", "|default:"—" }}
- - {% trans "UUID" %}
- - {{ node.uuid|default:"—" }}
- - {% trans "Instance UUID" %}
- - {{ node.instance_uuid|default:"—" }}
- - {% trans "Driver" %}
- - {{ node.driver|default:"—" }}
- - {% trans "Power state" %}
- - {{ node.power_state|default:"—" }}
-
+
{% trans "Info" %}
+
+ - {% trans "MAC Addresses" %}
+ - {{ node.addresses|join:", "|default:"—" }}
+ - {% trans "UUID" %}
+ - {{ node.uuid|default:"—" }}
+ - {% trans "Instance UUID" %}
+ - {{ node.instance_uuid|default:"—" }}
+ - {% trans "Driver" %}
+ - {{ node.driver|default:"—" }}
+ - {% trans "Power state" %}
+ - {{ node.power_state|default:"—" }}
+
-
{% trans "Driver Info" %}
-
- - {% trans "IPMI address" %}
- - {{ node.driver_info.ipmi_address|default:"—" }}
- - {% trans "IPMI username" %}
- - {{ node.driver_info.ipmi_username|default:"—" }}
-
+
{% trans "Driver Info" %}
+
+ - {% trans "IPMI address" %}
+ - {{ node.driver_info.ipmi_address|default:"—" }}
+ - {% trans "IPMI username" %}
+ - {{ node.driver_info.ipmi_username|default:"—" }}
+
-
{% trans "Properties" %}
-
- - {% trans "Local disk" %}
- - {{ node.properties.local_disk|filesizeformat|default:"—" }}
- - {% trans "RAM" %}
- - {{ node.properties.ram|filesizeformat|default:"—" }}
- - {% trans "CPU" %}
- - {{ node.properties.cpu|default:"—" }}
-
+
{% trans "Properties" %}
+
+ - {% trans "Local disk" %}
+ - {{ node.properties.local_disk|filesizeformat|default:"—" }}
+ - {% trans "RAM" %}
+ - {{ node.properties.ram|filesizeformat|default:"—" }}
+ - {% trans "CPU" %}
+ - {{ node.properties.cpu|default:"—" }}
+
+
+{% trans "Performance and Capacity" %}
+
+{% if meters %}
+
+{% url 'horizon:infrastructure:nodes:performance' node.id as node_perf_url %}
+
+
+
+
+
+
+
+
+ {% for meter_name, meter_label in meters %}
+ {% if forloop.counter0|divisibleby:"4" %}
+
+ {% endif %}
+
+ {% include "infrastructure/_performance_chart.html" with label=meter_label url=node_perf_url|add:"?meter="|add:meter_name only %}
+ |
+ {% if forloop.counter|divisibleby:"4" %}
+
+ {% endif %}
+ {% endfor %}
+
+
+{% else %}
+ Metering service is not enabled.
+{% endif %}
{% endblock %}
diff --git a/tuskar_ui/infrastructure/nodes/tests.py b/tuskar_ui/infrastructure/nodes/tests.py
index ef298c2d0..838f9d96c 100644
--- a/tuskar_ui/infrastructure/nodes/tests.py
+++ b/tuskar_ui/infrastructure/nodes/tests.py
@@ -13,11 +13,13 @@
# under the License.
import contextlib
+import json
from django.core import urlresolvers
from mock import patch, call # noqa
+from openstack_dashboard.test import helpers
from openstack_dashboard.test.test_data import utils
from tuskar_ui import api as api
from tuskar_ui.handle_errors import handle_errors # noqa
@@ -28,11 +30,12 @@ from tuskar_ui.test.test_data import tuskar_data
INDEX_URL = urlresolvers.reverse('horizon:infrastructure:nodes:index')
REGISTER_URL = urlresolvers.reverse('horizon:infrastructure:nodes:register')
DETAIL_VIEW = 'horizon:infrastructure:nodes:detail'
+PERFORMANCE_VIEW = 'horizon:infrastructure:nodes:performance'
TEST_DATA = utils.TestDataContainer()
tuskar_data.data(TEST_DATA)
-class NodesTests(test.BaseAdminViewTests):
+class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
@handle_errors("Error!", [])
def _raise_tuskar_exception(self, request, *args, **kwargs):
raise self.exceptions.tuskar
@@ -244,3 +247,30 @@ class NodesTests(test.BaseAdminViewTests):
self.assertEqual(mock.get.call_count, 1)
self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ def test_performance(self):
+ node = api.Node(self.ironicclient_nodes.list()[0])
+ meters = self.meters.list()
+ resources = self.resources.list()
+
+ ceilometerclient = self.stub_ceilometerclient()
+ ceilometerclient.resources = self.mox.CreateMockAnything()
+ ceilometerclient.resources.list(q=[]).AndReturn(resources)
+ ceilometerclient.meters = self.mox.CreateMockAnything()
+ ceilometerclient.meters.list(None).AndReturn(meters)
+
+ self.mox.ReplayAll()
+
+ with patch('tuskar_ui.api.Node', **{
+ 'spec_set': ['get'],
+ 'get.return_value': node,
+ }):
+ url = urlresolvers.reverse(PERFORMANCE_VIEW, args=(node.uuid,))
+ url += '?meter=cpu&date_options=7'
+ res = self.client.get(url)
+
+ json_content = json.loads(res.content)
+ self.assertEqual(res.status_code, 200)
+ self.assertIn('series', json_content)
+ self.assertIn('settings', json_content)
+ self.assertIn('stats', json_content)
diff --git a/tuskar_ui/infrastructure/nodes/urls.py b/tuskar_ui/infrastructure/nodes/urls.py
index ee16a89e7..3b9b890d5 100644
--- a/tuskar_ui/infrastructure/nodes/urls.py
+++ b/tuskar_ui/infrastructure/nodes/urls.py
@@ -24,4 +24,6 @@ urlpatterns = urls.patterns(
name='register'),
urls.url(r'^(?P[^/]+)/$', views.DetailView.as_view(),
name='detail'),
+ urls.url(r'^(?P[^/]+)/performance/$',
+ views.PerformanceView.as_view(), name='performance'),
)
diff --git a/tuskar_ui/infrastructure/nodes/views.py b/tuskar_ui/infrastructure/nodes/views.py
index cb99f647f..ddeca2a8e 100644
--- a/tuskar_ui/infrastructure/nodes/views.py
+++ b/tuskar_ui/infrastructure/nodes/views.py
@@ -11,13 +11,20 @@
# 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 json
from django.core.urlresolvers import reverse_lazy
+from django import http
+from django.utils.translation import ugettext_lazy as _
+from django.views.generic import base
from horizon import forms as horizon_forms
from horizon import tabs as horizon_tabs
from horizon import views as horizon_views
+from openstack_dashboard.api import base as api_base
+from openstack_dashboard.dashboards.admin.metering import views as metering
+
from tuskar_ui import api
from tuskar_ui.infrastructure.nodes import forms
from tuskar_ui.infrastructure.nodes import tabs
@@ -71,4 +78,100 @@ class DetailView(horizon_views.APIView):
redirect = reverse_lazy('horizon:infrastructure:nodes:index')
node = api.Node.get(request, node_uuid, _error_redirect=redirect)
context['node'] = node
+
+ if api_base.is_service_enabled(request, 'metering'):
+ context['meters'] = (
+ ('cpu', _('CPU')),
+ ('disk', _('Disk')),
+ ('network', _('Network Bandwidth (In)')),
+ ('energy', _('Energy')),
+ ('memory', _('Memory')),
+ ('swap', _('Swap')),
+ ('network-out', _('Network Bandwidth (Out)')),
+ ('power', _('Power')),
+ )
+
return context
+
+
+class PerformanceView(base.TemplateView):
+ def get(self, request, *args, **kwargs):
+ meter = request.GET.get('meter')
+ date_options = request.GET.get('date_options')
+ date_from = request.GET.get('date_from')
+ date_to = request.GET.get('date_to')
+ stats_attr = request.GET.get('stats_attr', 'avg')
+ group_by = request.GET.get('group_by')
+
+ meter_name = meter.replace(".", "_")
+ resource_name = 'id' if group_by == "project" else 'resource_id'
+ node_uuid = kwargs.get('node_uuid')
+
+ additional_query = [{'field': 'resource_id',
+ 'op': 'eq',
+ 'value': node_uuid}]
+
+ resources, unit = metering.query_data(
+ request=request,
+ date_from=date_from,
+ date_to=date_to,
+ date_options=date_options,
+ group_by=group_by,
+ meter=meter,
+ additional_query=additional_query)
+ series = metering.SamplesView._series_for_meter(resources,
+ resource_name,
+ meter_name,
+ stats_attr,
+ unit)
+
+ average = used = 0
+ tooltip_average = ''
+
+ if series:
+ values = [point['y'] for point in series[0]['data']]
+ average = sum(values) / len(values)
+ used = values[-1]
+ first_date = series[0]['data'][0]['x']
+ last_date = series[0]['data'][-1]['x']
+ tooltip_average = _(
+ 'Average %(average)s %(unit)s
From: %(first_date)s, to: '
+ '%(last_date)s'
+ ) % (dict(average=average, unit=unit, first_date=first_date,
+ last_date=last_date)
+ )
+
+ ret = {
+ 'series': series,
+ 'settings': {
+ 'renderer': 'StaticAxes',
+ 'yMin': 0,
+ 'yMax': 100,
+ 'higlight_last_point': True,
+ 'auto_size': False,
+ 'auto_resize': False,
+ 'axes_x': False,
+ 'axes_y': False,
+ 'bar_chart_settings': {
+ 'orientation': 'vertical',
+ 'used_label_placement': 'left',
+ 'width': 30,
+ 'color_scale_domain': [0, 80, 80, 100],
+ 'color_scale_range': [
+ '#0000FF',
+ '#0000FF',
+ '#FF0000',
+ '#FF0000'
+ ],
+ 'average_color_scale_domain': [0, 100],
+ 'average_color_scale_range': ['#0000FF', '#0000FF']
+ }
+ },
+ 'stats': {
+ 'average': average,
+ 'used': used,
+ 'tooltip_average': tooltip_average,
+ }
+ }
+
+ return http.HttpResponse(json.dumps(ret), mimetype='application/json')
diff --git a/tuskar_ui/infrastructure/templates/infrastructure/_performance_chart.html b/tuskar_ui/infrastructure/templates/infrastructure/_performance_chart.html
new file mode 100644
index 000000000..8bf04b42a
--- /dev/null
+++ b/tuskar_ui/infrastructure/templates/infrastructure/_performance_chart.html
@@ -0,0 +1,16 @@
+{{ label }}
+