From a0f9ee338fa8f73c94f8428fcf7566aefacc7f8a Mon Sep 17 00:00:00 2001 From: Ladislav Smola Date: Fri, 24 Oct 2014 15:29:43 +0200 Subject: [PATCH] Adding provision state of Ironic Adding provision state and extra to API of Nodes. Showing provision state stats in overview page. Change-Id: Ic49f5e34c01ead0f7fc43cf1c073e1463105125c --- tuskar_ui/api/node.py | 21 +++++- tuskar_ui/infrastructure/nodes/tabs.py | 64 ++++++++++++++----- .../nodes/templates/nodes/_overview.html | 50 ++++++++++++++- tuskar_ui/infrastructure/nodes/tests.py | 8 +-- tuskar_ui/test/test_data/node_data.py | 9 +++ 5 files changed, 129 insertions(+), 23 deletions(-) diff --git a/tuskar_ui/api/node.py b/tuskar_ui/api/node.py index d5b04fc44..4dc377a75 100644 --- a/tuskar_ui/api/node.py +++ b/tuskar_ui/api/node.py @@ -32,6 +32,7 @@ from tuskar_ui.utils import utils ERROR_STATES = set(['deploy failed', 'error']) POWER_ON_STATES = set(['on', 'power on']) +# TODO(tzumainn) this states doesn't seems to be correct, please fix this # overall state of the node; not power states DISCOVERING_STATE = 'discovering' DISCOVERED_STATE = 'discovered' @@ -39,6 +40,13 @@ PROVISIONED_STATE = 'provisioned' PROVISIONING_FAILED_STATE = 'provisioning failed' PROVISIONING_STATE = 'provisioning' FREE_STATE = 'free' +# TODO(tzumain) the below should be correct +PROVISION_STATE_FREE = ['deleted', None] +PROVISION_STATE_PROVISIONED = ['active'] +PROVISION_STATE_PROVISIONING = [ + 'deploying', 'wait call-back', 'rebuild', 'deploy complete'] +PROVISION_STATE_DELETING = ['deleting'] +PROVISION_STATE_ERROR = ['error', 'deploy failed'] LOG = logging.getLogger(__name__) @@ -490,6 +498,16 @@ class BareMetalNode(base.APIResourceWrapper): return [interface["address"] for interface in self.interfaces] + @cached_property + def extra(self): + """Ironic compatibility parameter.""" + return {} + + @cached_property + def provision_state(self): + """Ironic compatibility parameter.""" + return None + class NodeClient(object): @@ -507,7 +525,8 @@ class NodeClient(object): class Node(base.APIResourceWrapper): _attrs = ('id', 'uuid', 'instance_uuid', 'driver', 'driver_info', 'state', 'power_state', 'target_power_state', 'addresses', 'maintenance', - 'cpus', 'memory_mb', 'local_gb', 'cpu_arch') + 'cpus', 'memory_mb', 'local_gb', 'cpu_arch', 'extra', + 'provision_state') def __init__(self, apiresource, request=None, **kwargs): """Initialize a Node diff --git a/tuskar_ui/infrastructure/nodes/tabs.py b/tuskar_ui/infrastructure/nodes/tabs.py index f490949b9..6cad987e2 100644 --- a/tuskar_ui/infrastructure/nodes/tabs.py +++ b/tuskar_ui/infrastructure/nodes/tabs.py @@ -40,23 +40,31 @@ class OverviewTab(tabs.Tab): node.memory_mb) local_gb = sum(int(node.local_gb) for node in nodes if node.local_gb) - nodes_provisioned = api.node.Node.list(request, associated=True) - nodes_free = api.node.Node.list(request, associated=False) - nodes_all_count = (utils.length(nodes_provisioned) + - utils.length(nodes_free)) + nodes_provisioned = set(utils.filter_items( + nodes, provision_state__in=api.node.PROVISION_STATE_PROVISIONED)) + nodes_free = set(utils.filter_items( + nodes, provision_state__in=api.node.PROVISION_STATE_FREE)) + nodes_deleting = set(utils.filter_items( + nodes, provision_state__in=api.node.PROVISION_STATE_DELETING)) + nodes_error = set(utils.filter_items( + nodes, provision_state__in=api.node.PROVISION_STATE_ERROR)) - nodes_provisioned_maintenance = list(utils.filter_items( + nodes_provisioned_maintenance = set(utils.filter_items( nodes_provisioned, maintenance=True)) - nodes_provisioned_not_maintenance = list( - set(nodes_provisioned) - set(nodes_provisioned_maintenance)) + nodes_provisioned_not_maintenance = ( + nodes_provisioned - nodes_provisioned_maintenance) - nodes_free_maintenance = list(utils.filter_items( + nodes_provisioning = set(utils.filter_items( + nodes, + provision_state__in=api.node.PROVISION_STATE_PROVISIONING)) + + nodes_free_maintenance = set(utils.filter_items( nodes_free, maintenance=True)) - nodes_free_not_maintenance = list( - set(nodes_free) - set(nodes_free_maintenance)) + nodes_free_not_maintenance = ( + nodes_free - nodes_free_maintenance) nodes_maintenance = ( - nodes_provisioned_maintenance + nodes_free_maintenance) + nodes_provisioned_maintenance | nodes_free_maintenance) nodes_provisioned_down = utils.filter_items( nodes_provisioned, power_state__not_in=api.node.POWER_ON_STATES) @@ -67,18 +75,42 @@ class OverviewTab(tabs.Tab): nodes_up = utils.filter_items( nodes, power_state__in=api.node.POWER_ON_STATES) + nodes_free_count = len(nodes_free_not_maintenance) + nodes_provisioned_count = len( + nodes_provisioned_not_maintenance) + nodes_provisioning_count = len(nodes_provisioning) + nodes_maintenance_count = len(nodes_maintenance) + nodes_deleting_count = len(nodes_deleting) + nodes_error_count = len(nodes_error) + context = { 'cpus': cpus, 'memory_gb': memory_mb / 1024.0, 'local_gb': local_gb, 'nodes_up_count': utils.length(nodes_up), 'nodes_down_count': utils.length(nodes_down), - 'nodes_provisioned_count': utils.length( - nodes_provisioned_not_maintenance), - 'nodes_free_count': utils.length(nodes_free_not_maintenance), - 'nodes_maintenance_count': utils.length(nodes_maintenance), - 'nodes_all_count': nodes_all_count + 'nodes_provisioned_count': nodes_provisioned_count, + 'nodes_provisioning_count': nodes_provisioning_count, + 'nodes_free_count': nodes_free_count, + 'nodes_deleting_count': nodes_deleting_count, + 'nodes_error_count': nodes_error_count, + 'nodes_maintenance_count': nodes_maintenance_count, + 'nodes_all_count': len(nodes), + 'nodes_status_data': + 'Provisioned={0}|Free={1}|Maintenance={2}'.format( + nodes_provisioned_count, nodes_free_count, + nodes_maintenance_count) } + # additional node status pie chart data, showing only if it appears + if nodes_provisioning_count: + context['nodes_status_data'] += '|Provisioning={0}'.format( + nodes_provisioning_count) + if nodes_deleting_count: + context['nodes_status_data'] += '|Deleting={0}'.format( + nodes_deleting_count) + if nodes_error_count: + context['nodes_status_data'] += '|Error={0}'.format( + nodes_error_count) if api_base.is_service_enabled(self.request, 'metering'): context['meter_conf'] = ( diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/_overview.html b/tuskar_ui/infrastructure/nodes/templates/nodes/_overview.html index 3a4c13fc9..447537a9d 100644 --- a/tuskar_ui/infrastructure/nodes/templates/nodes/_overview.html +++ b/tuskar_ui/infrastructure/nodes/templates/nodes/_overview.html @@ -17,7 +17,7 @@

{% trans 'Nodes Status' %}

-
+

{% trans 'Power Status' %}

@@ -33,6 +33,54 @@ {{ nodes_provisioned_count }} {% trans 'provisioned nodes' %}
+ {% if nodes_provisioning_count %} +
+ + {% blocktrans count nodes_provisioning_count as counter %} + {{ counter }} node + {% plural %} + {{ counter }} nodes + {% endblocktrans %} + + {% blocktrans count nodes_provisioning_count as counter %} + is being provisioned + {% plural %} + are being provisioned + {% endblocktrans %} +
+ {% endif %} + {% if nodes_deleting_count %} +
+ + {% blocktrans count nodes_deleting_count as counter %} + {{ counter }} node + {% plural %} + {{ counter }} nodes + {% endblocktrans %} + + {% blocktrans count nodes_deleting_count as counter %} + is being deleted + {% plural %} + are being deleted + {% endblocktrans %} +
+ {% endif %} + {% if nodes_error_count %} +
+ + {% blocktrans count nodes_error_count as counter %} + {{ counter }} node + {% plural %} + {{ counter }} nodes + {% endblocktrans %} + + {% blocktrans count nodes_error_count as counter %} + {{ counter }} failed + {% plural %} + {{ counter }} failed + {% endblocktrans %} +
+ {% endif %} {% url 'horizon:infrastructure:nodes:nodes_performance' as node_perf_url %} diff --git a/tuskar_ui/infrastructure/nodes/tests.py b/tuskar_ui/infrastructure/nodes/tests.py index 9c61de5c0..0ee977dc4 100644 --- a/tuskar_ui/infrastructure/nodes/tests.py +++ b/tuskar_ui/infrastructure/nodes/tests.py @@ -54,8 +54,7 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase): 'list.return_value': [], }) as mock: res = self.client.get(INDEX_URL) - # FIXME(lsmola) optimize, this should call 1 time, what the hell - self.assertEqual(mock.list.call_count, 7) + self.assertEqual(mock.list.call_count, 5) self.assertTemplateUsed( res, 'infrastructure/nodes/index.html') @@ -76,8 +75,7 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase): 'list.return_value': nodes, }) as Node: res = self.client.get(INDEX_URL + '?tab=nodes__' + tab_name) - # FIXME(lsmola) horrible count, optimize - self.assertEqual(Node.list.call_count, 7) + self.assertEqual(Node.list.call_count, 5) self.assertTemplateUsed( res, 'infrastructure/nodes/index.html') @@ -104,7 +102,7 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase): 'list.side_effect': self._raise_tuskar_exception, }) as mock: res = self.client.get(INDEX_URL + '?tab=nodes__' + tab_name) - self.assertEqual(mock.list.call_count, 4) + self.assertEqual(mock.list.call_count, 2) self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/tuskar_ui/test/test_data/node_data.py b/tuskar_ui/test/test_data/node_data.py index a73519e12..e23d1ae92 100644 --- a/tuskar_ui/test/test_data/node_data.py +++ b/tuskar_ui/test/test_data/node_data.py @@ -137,6 +137,7 @@ def data(TEST): 'provision_state': 'active', 'maintenance': None, 'newly_discovered': None, + 'provision_state': 'active', 'extra': {} }) node_2 = node.Node( @@ -162,6 +163,7 @@ def data(TEST): 'provision_state': 'active', 'maintenance': None, 'newly_discovered': None, + 'provision_state': 'active', 'extra': {} }) node_3 = node.Node( @@ -187,6 +189,7 @@ def data(TEST): 'provision_state': 'active', 'maintenance': None, 'newly_discovered': None, + 'provision_state': 'deploying', 'extra': {} }) node_4 = node.Node( @@ -212,6 +215,7 @@ def data(TEST): 'provision_state': 'active', 'maintenance': None, 'newly_discovered': None, + 'provision_state': 'deploying', 'extra': {} }) node_5 = node.Node( @@ -237,6 +241,7 @@ def data(TEST): 'provision_state': 'error', 'maintenance': None, 'newly_discovered': None, + 'provision_state': 'deploying', 'extra': {} }) node_6 = node.Node( @@ -262,6 +267,7 @@ def data(TEST): 'provision_state': 'active', 'maintenance': None, 'newly_discovered': None, + 'provision_state': 'active', 'extra': {} }) node_7 = node.Node( @@ -286,6 +292,7 @@ def data(TEST): 'target_power_state': 'on', 'maintenance': True, 'newly_discovered': None, + 'provision_state': 'deploying', 'extra': {} }) node_8 = node.Node( @@ -310,6 +317,7 @@ def data(TEST): 'target_power_state': 'on', 'maintenance': True, 'newly_discovered': True, + 'provision_state': 'active', 'extra': {} }) node_9 = node.Node( @@ -334,6 +342,7 @@ def data(TEST): 'target_power_state': 'on', 'maintenance': True, 'newly_discovered': True, + 'provision_state': 'active', 'extra': {} }) TEST.ironicclient_nodes.add(node_1, node_2, node_3, node_4, node_5, node_6,