Add a generic function for filtering API objects

Replace the filter_nodes() function with a more generic solution.
We can now filter a list of generic API object, by any attribute.

Change-Id: I76f8226df5123a8038220658cb340b67461024b4
This commit is contained in:
Ana Krivokapic 2014-10-06 12:59:10 +02:00
parent 493d6c06d9
commit fc0a6f77bc
6 changed files with 98 additions and 80 deletions

View File

@ -589,38 +589,3 @@ class Node(base.APIResourceWrapper):
if self.instance_uuid:
return _("Provisioned")
return _("Free")
def filter_nodes(nodes, healthy=None, power_state=None):
"""Filters the list of Nodes and returns the filtered list.
:param nodes: list of tuskar_ui.api.node.Node objects to filter
:type nodes: list
:param healthy: retrieve all Nodes (healthy=None),
only the healthly ones (healthy=True),
or only those in an error state (healthy=False)
:type healthy: None or bool
:param power_state: retrieve all Nodes (power_state=None),
only those that are running (power_state=True),
or only those that are stopped (power_state=False)
:type power_state: None or bool
:return: list of filtered tuskar_ui.api.node.Node objects
:rtype: list
"""
if healthy is not None:
if healthy:
nodes = [node for node in nodes
if node.power_state not in ERROR_STATES]
else:
nodes = [node for node in nodes
if node.power_state in ERROR_STATES]
if power_state is not None:
if power_state:
nodes = [node for node in nodes
if node.power_state in POWER_ON_STATES]
else:
nodes = [node for node in nodes
if node.power_state not in POWER_ON_STATES]
return nodes

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import itertools
from django.core import urlresolvers
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
@ -21,6 +23,7 @@ from openstack_dashboard.api import base as api_base
from tuskar_ui import api
from tuskar_ui.infrastructure.nodes import tables
from tuskar_ui.utils import metering as metering_utils
from tuskar_ui.utils import utils
class OverviewTab(tabs.Tab):
@ -34,34 +37,26 @@ class OverviewTab(tabs.Tab):
memory_mb = sum(int(node.memory_mb) for node in nodes if
node.memory_mb)
local_gb = sum(int(node.local_gb) for node in nodes if node.local_gb)
deployed_nodes = api.node.Node.list(request, associated=True)
free_nodes = api.node.Node.list(request, associated=False)
deployed_nodes_error = api.node.filter_nodes(
deployed_nodes, healthy=False)
deployed_nodes_down = api.node.filter_nodes(
deployed_nodes, power_state=False)
free_nodes_error = api.node.filter_nodes(free_nodes, healthy=False)
free_nodes_down = api.node.filter_nodes(free_nodes, power_state=False)
total_nodes = deployed_nodes + free_nodes
total_nodes_error = deployed_nodes_error + free_nodes_error
total_nodes_down = deployed_nodes_down + free_nodes_down
total_nodes_healthy = api.node.filter_nodes(total_nodes, healthy=True)
total_nodes_up = api.node.filter_nodes(total_nodes, power_state=True)
nodes_provisioned = api.node.Node.list(request, associated=True)
nodes_free = api.node.Node.list(request, associated=False)
nodes_provisioned_down = utils.filter_items(
nodes_provisioned, power_state__not_in=api.node.POWER_ON_STATES)
nodes_free_down = utils.filter_items(
nodes_free, power_state__not_in=api.node.POWER_ON_STATES)
nodes_down = itertools.chain(nodes_provisioned_down, nodes_free_down)
nodes_up = utils.filter_items(
nodes, power_state__in=api.node.POWER_ON_STATES)
context = {
'cpus': cpus,
'memory_gb': memory_mb / 1024.0,
'local_gb': local_gb,
'total_nodes_healthy': total_nodes_healthy,
'total_nodes_up': total_nodes_up,
'total_nodes_error': total_nodes_error,
'total_nodes_down': total_nodes_down,
'deployed_nodes': deployed_nodes,
'deployed_nodes_error': deployed_nodes_error,
'deployed_nodes_down': deployed_nodes_down,
'free_nodes': free_nodes,
'free_nodes_error': free_nodes_error,
'free_nodes_down': free_nodes_down,
'nodes_up_count': utils.length(nodes_up),
'nodes_down_count': utils.length(nodes_down),
'nodes_provisioned_count': utils.length(nodes_provisioned),
'nodes_free_count': utils.length(nodes_free),
}
if api_base.is_service_enabled(self.request, 'metering'):
@ -99,9 +94,6 @@ class RegisteredTab(tabs.TableTab):
nodes = api.node.Node.list(self.request, maintenance=False,
_error_redirect=redirect)
if 'errors' in self.request.GET:
return api.node.filter_nodes(nodes, healthy=False)
if nodes:
all_resources = api.heat.Resource.list_all_resources(self.request)
for node in nodes:

View File

@ -24,16 +24,16 @@
<div class="col-xs-4">
<div class="widget">
<h3>{% trans 'Free Nodes' %}</h3>
<div class="d3_pie_chart_distribution" data-used="Deployed={{ deployed_nodes|length }}|Free={{ free_nodes|length }}"></div>
<div class="d3_pie_chart_distribution" data-used="Provisioned={{ nodes_provisioned_count }}|Free={{ nodes_free_count }}"></div>
<div class="widget-info">
<a href="{% url 'horizon:infrastructure:nodes:index' %}?tab=nodes__registered">
<span class="info">{{ free_nodes|length|default:0 }}</span>
<span class="info">{{ nodes_free_count }}</span>
{% trans 'Free Nodes' %}
</a>
</div>
<div class="widget-info">
<a href="{% url 'horizon:infrastructure:nodes:index' %}?tab=nodes__registered">
<span class="info">{{ deployed_nodes|length|default:0 }}</span>
<span class="info">{{ nodes_provisioned_count }}</span>
{% trans 'Provisioned Nodes' %}
</a>
</div>
@ -42,7 +42,7 @@
<div class="col-xs-4">
<div class="widget">
<h2>{% trans 'Power Status' %}</h2>
<div class="d3_pie_chart_distribution" data-used="Running={{ total_nodes_up|length }}|Stopped={{ total_nodes_down|length }}"></div>
<div class="d3_pie_chart_distribution" data-used="Running={{ nodes_up_count }}|Stopped={{ nodes_down_count }}"></div>
</div>
</div>
</div>
@ -129,7 +129,7 @@
<div class="col-lg-1">
<div class="widget-info">
<a href="{% url 'horizon:infrastructure:nodes:index' %}?tab=nodes__registered">
<span class="info">{{ deployed_nodes|length|default:0 }}</span><br>
<span class="info">{{ nodes_provisioned_count }}</span><br>
{% trans 'Provisioned' %}<br>
{% trans 'Nodes' %}<br>
</a>

View File

@ -141,17 +141,3 @@ class NodeAPITests(test.APITestCase):
node = self.baremetalclient_nodes.first()
ret_val = api.node.BareMetalNode(node).addresses
self.assertEqual(2, len(ret_val))
def test_filter_nodes(self):
nodes = self.baremetalclient_nodes.list()
nodes = [api.node.BareMetalNode(node) for node in nodes]
num_nodes = len(nodes)
with patch('novaclient.v1_1.contrib.baremetal.'
'BareMetalNodeManager.list', return_value=nodes):
all_nodes = api.node.filter_nodes(nodes)
healthy_nodes = api.node.filter_nodes(nodes, healthy=True)
defective_nodes = api.node.filter_nodes(nodes, healthy=False)
self.assertEqual(len(all_nodes), num_nodes)
self.assertEqual(len(healthy_nodes), num_nodes - 1)
self.assertEqual(len(defective_nodes), 1)

View File

@ -0,0 +1,34 @@
# -*- coding: utf8 -*-
#
# 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 tuskar_ui.test import helpers as test
from tuskar_ui.utils import utils
class TestItem(object):
def __init__(self, index):
self.index = index
class UtilsTests(test.TestCase):
def test_filter_items(self):
items = [TestItem(i) for i in range(7)]
first = utils.filter_items(items, index=0)
even = utils.filter_items(items, index__in=(0, 2, 4, 6))
last_two = utils.filter_items(items, index__not_in=range(5))
self.assertEqual(utils.length(first), 1)
self.assertEqual(utils.length(even), 4)
self.assertEqual(utils.length(last_two), 2)

View File

@ -34,3 +34,44 @@ def list_to_dict(object_list, key_attribute='id'):
:rtype: dict
"""
return dict((getattr(o, key_attribute), o) for o in object_list)
def length(iterator):
"""A length function for iterators
Returns the number of items in the specified iterator. Note that this
function consumes the iterator in the process.
"""
return sum(1 for _item in iterator)
def filter_items(items, **kwargs):
"""Filters the list of items and returns the filtered list.
Example usage:
>>> class Item(object):
... def __init__(self, index):
... self.index = index
... def __repr__(self):
... return '<Item index=%d>' % self.index
>>> items = [Item(i) for i in range(7)]
>>> list(filter_items(items, index=1))
[<Item index=1>]
>>> list(filter_items(items, index__in=(1, 2, 3)))
[<Item index=1>, <Item index=2>, <Item index=3>]
>>> list(filter_items(items, index__not_in=(1, 2, 3)))
[<Item index=0>, <Item index=4>, <Item index=5>, <Item index=6>]
"""
for item in items:
for name, value in kwargs.items():
if name.endswith('__in'):
if getattr(item, name[:-len('__in')]) not in value:
break
elif name.endswith('__not_in'):
if getattr(item, name[:-len('__not_in')]) in value:
break
else:
if getattr(item, name) != value:
break
else:
yield item