Update node detail page for Juno

Node detail page updated for Juno wireframes.  Data on page uses correct
API calls.  Additional work needed:

* styling
* graphs
* mapping from status to a 'pretty status' (for example, from ACTIVE to
  Provisioned

As part of this work, the node api is corrected to pull the correct Nova
server mock data.

Partial-Implements: blueprint node-details-attribute-update
Change-Id: I38d8bae44a9af0bbc1b598be5c5c58940d6c9b1c
This commit is contained in:
Tzu-Mainn Chen
2014-07-11 06:07:50 +02:00
parent a18f40554c
commit e6f600e9f2
6 changed files with 91 additions and 36 deletions

View File

@@ -127,7 +127,7 @@ class Stack(base.APIResourceWrapper):
r.stack_id == self.id]
if not with_joins:
return [Resource(r, request=self._request)
return [Resource(r, request=self._request, stack=self)
for r in resources]
nodes_dict = utils.list_to_dict(node.Node.list(self._request,
@@ -137,7 +137,7 @@ class Stack(base.APIResourceWrapper):
for r in resources:
joined_resources.append(
Resource(r, node=nodes_dict.get(r.physical_resource_id, None),
request=self._request))
request=self._request, stack=self))
# TODO(lsmola) I want just resources with nova instance
# this could be probably filtered a better way, investigate
return [r for r in joined_resources if r.node is not None]
@@ -329,6 +329,8 @@ class Resource(base.APIResourceWrapper):
self._request = request
if 'node' in kwargs:
self._node = kwargs['node']
if 'stack' in kwargs:
self._stack = kwargs['stack']
@classmethod
def get(cls, request, stack, resource_name):
@@ -349,7 +351,7 @@ class Resource(base.APIResourceWrapper):
"""
for r in TEST_DATA.heatclient_resources.list():
if r.stack_id == stack.id and r.resource_name == resource_name:
return cls(stack, request=request)
return cls(r, request=request, stack=stack)
@classmethod
def get_by_node(cls, request, node):
@@ -416,3 +418,14 @@ class Resource(base.APIResourceWrapper):
return node.Node.get_by_instance_uuid(self._request,
self.physical_resource_id)
return None
@cached_property
def stack(self):
"""Return the Stack associated with this Resource
:return: Stack associated with this Resource, or None if no
Stack is associated
:rtype: tuskar_ui.api.heat.Stack
"""
if hasattr(self, '_stack'):
return self._stack

View File

@@ -339,6 +339,12 @@ class BareMetalNode(base.APIResourceWrapper):
'local_disk': self.local_gb * 1024.0 * 1024.0 * 1024.0
}
@cached_property
def driver(self):
"""Return driver for this BareMetalNode
"""
return "IPMI + PXE"
@cached_property
def driver_info(self):
"""Return driver_info for this BareMetalNode
@@ -419,7 +425,11 @@ class Node(base.APIResourceWrapper):
def get(cls, request, uuid):
node = NodeClient(request).node_class.get(request, uuid)
if node.instance_uuid is not None:
server = TEST_DATA.novaclient_servers.first()
for server in TEST_DATA.novaclient_servers.list():
if server.id == node.instance_uuid:
break
else:
server = None
return cls(node, instance=server, request=request)
return cls(node)
@@ -429,7 +439,11 @@ class Node(base.APIResourceWrapper):
def get_by_instance_uuid(cls, request, instance_uuid):
node = NodeClient(request).node_class.get_by_instance_uuid(
request, instance_uuid)
server = TEST_DATA.novaclient_servers.first()
for server in TEST_DATA.novaclient_servers.list():
if server.id == node.instance_uuid:
break
else:
server = None
return cls(node, instance=server, request=request)
@classmethod
@@ -467,10 +481,9 @@ class Node(base.APIResourceWrapper):
return self._instance
if self.instance_uuid:
server = TEST_DATA.novaclient_servers.first()
return server
return None
for server in TEST_DATA.novaclient_servers.list():
if server.id == self.instance_uuid:
return server
@cached_property
def image_name(self):
@@ -483,13 +496,21 @@ class Node(base.APIResourceWrapper):
"""
if self.instance is None:
return
return image_get(self._request, self.instance.image['id']).name
for image in TEST_DATA.glanceclient_images.list():
if image.id == self.instance.image['id']:
return image.name
@cached_property
def instance_status(self):
return getattr(getattr(self, 'instance', None),
'status', None)
@cached_property
def provisioning_status(self):
if self.instance_uuid:
return _("Provisioned")
return _("Free")
def filter_nodes(nodes, healthy=None):
"""Filters the list of Nodes and returns the filtered list.

View File

@@ -9,16 +9,7 @@
{% block main %}
<div class="row-fluid">
<div class="span12">
<h4>{% trans "Info" %}</h4>
<dl class="clearfix">
<dt>{% trans "MAC Addresses" %}</dt>
<dd>{{ node.addresses|join:", "|default:"&mdash;" }}</dd>
<dt>{% trans "UUID" %}</dt>
<dd>{{ node.uuid|default:"&mdash;" }}</dd>
<dt>{% trans "Instance UUID" %}</dt>
<dd>{{ node.instance_uuid|default:"&mdash;" }}</dd>
<dt>{% trans "Driver" %}</dt>
<dd>{{ node.driver|default:"&mdash;" }}</dd>
<dt>{% trans "Power state" %}</dt>
<dd>{{ node.power_state|default:"&mdash;" }}</dd>
</dl>
@@ -26,23 +17,42 @@
</div>
<div class="row-fluid">
<div class="span6">
<h4>{% trans "Driver Info" %}</h4>
<h4>{% trans "Inventory" %}</h4>
<dl class="clearfix">
<dt>{% trans "IPMI address" %}</dt>
<dd>{{ node.driver_info.ipmi_address|default:"&mdash;" }}</dd>
<dt>{% trans "IPMI username" %}</dt>
<dd>{{ node.driver_info.ipmi_username|default:"&mdash;" }}</dd>
<dt>{% trans "Node UUID" %}</dt>
<dd>{{ node.uuid|default:"&mdash;" }}</dd>
<dt>{% trans "Driver" %}</dt>
<dd>{{ node.driver|default:"&mdash;" }}</dd>
<dt>{% trans "Network Cards" %}</dt>
<dd>{{ node.addresses|length }}</dd>
<dt>{% trans "Registered HW" %}</dt>
<dd>
{{ node.properties.cpu|default:"&mdash;" }} {% trans "CPU" %}<br />
{{ node.properties.ram|filesizeformat|default:"&mdash;" }} {% trans "RAM" %}<br />
{{ node.properties.local_disk|filesizeformat|default:"&mdash;" }} {% trans "HDD" %}
</dd>
</dl>
</div>
<div class="span6">
<h4>{% trans "Properties" %}</h4>
<h4>{% trans "Deployment" %}</h4>
<dl class="clearfix">
<dt>{% trans "Local disk" %}</dt>
<dd>{{ node.properties.local_disk|filesizeformat|default:"&mdash;" }}</dd>
<dt>{% trans "RAM" %}</dt>
<dd>{{ node.properties.ram|filesizeformat|default:"&mdash;" }}</dd>
<dt>{% trans "CPU" %}</dt>
<dd>{{ node.properties.cpu|default:"&mdash;" }}</dd>
<dt>{% trans "Deployment Role" %}</dt>
{% if stack and role %}
<dd><a href="{% url 'horizon:infrastructure:overcloud:role' stack.id role.id %}">{{ role.name }}</a></dd>
{% else %}
<dd>&mdash;</dd>
{% endif %}
<dt>{% trans "Provisioning" %}</dt>
<dd>
{{ node.provisioning_status|default:"&mdash;" }}
{% if node.instance_uuid %}
<br />{{ node.instance.created }}
{% endif %}
</dd>
<dt>{% trans "Image" %}</dt>
<dd>{{ node.image_name|default:"&mdash;" }}</dd>
<dt>{% trans "Instance UUID" %}</dt>
<dd>{{ node.instance_uuid|default:"&mdash;" }}</dd>
</dl>
</div>
</div>

View File

@@ -18,6 +18,7 @@ from django import http
from django.utils.translation import ugettext_lazy as _
from django.views.generic import base
from horizon import exceptions
from horizon import forms as horizon_forms
from horizon import tabs as horizon_tabs
from horizon import views as horizon_views
@@ -79,6 +80,12 @@ class DetailView(horizon_views.APIView):
redirect = reverse_lazy('horizon:infrastructure:nodes:index')
node = api.node.Node.get(request, node_uuid, _error_redirect=redirect)
context['node'] = node
try:
resource = api.heat.Resource.get_by_node(request, node)
context['role'] = resource.role
context['stack'] = resource.stack
except exceptions.NotFound:
pass
if api_base.is_service_enabled(request, 'metering'):
context['meters'] = (
('cpu', _('CPU')),

View File

@@ -113,7 +113,7 @@ class NodeAPITests(test.APITestCase):
with patch('openstack_dashboard.api.glance.image_get',
return_value=image):
ret_val = api.node.Node(node).image_name
self.assertEqual(ret_val, 'overcloud-control')
self.assertEqual(ret_val, 'overcloud-compute')
def test_node_addresses_no_ironic(self):
node = self.baremetalclient_nodes.first()

View File

@@ -157,7 +157,8 @@ def data(TEST):
servers.ServerManager(None),
{'id': 'aa',
'name': 'Compute',
'image': {'id': 1},
'created': '2014-06-26T20:38:06Z',
'image': {'id': '1'},
'flavor': {
'id': '1',
},
@@ -166,7 +167,8 @@ def data(TEST):
servers.ServerManager(None),
{'id': 'bb',
'name': 'Controller',
'image': {'id': 2},
'created': '2014-06-27T20:38:06Z',
'image': {'id': '2'},
'flavor': {
'id': '2',
},
@@ -175,7 +177,8 @@ def data(TEST):
servers.ServerManager(None),
{'id': 'cc',
'name': 'Compute',
'image': {'id': 1},
'created': '2014-06-28T20:38:06Z',
'image': {'id': '1'},
'flavor': {
'id': '1',
},
@@ -184,7 +187,8 @@ def data(TEST):
servers.ServerManager(None),
{'id': 'dd',
'name': 'Compute',
'image': {'id': 1},
'created': '2014-06-29T20:38:06Z',
'image': {'id': '1'},
'flavor': {
'id': '1',
},