diff options
author | Radomir Dopieralski <openstack@sheep.art.pl> | 2013-10-25 11:19:31 +0200 |
---|---|---|
committer | Radomir Dopieralski <openstack@sheep.art.pl> | 2013-10-29 17:16:39 +0100 |
commit | 7b2282331a48afedd2506e0adc110028ff81f769 (patch) | |
tree | e8042b91c8f029cfb647ff31ef94ea80591f0115 | |
parent | d2171beaf8d06929a37b78d9d8c451790ee546a9 (diff) | |
download | tuskar-ui-7b2282331a48afedd2506e0adc110028ff81f769.tar.gz |
Refactor the cached api calls into @cached_property
We have a lot of repeating code in the tuskar_ui.api that
deals with caching the api calls from properties. I made a
@cached_property decorator that handles that all in one place
instead.
Change-Id: I097a3e9017f64c69762fc5bdcb44396386be948a
-rw-r--r-- | tuskar_ui/api.py | 346 | ||||
-rw-r--r-- | tuskar_ui/cached_property.py | 63 |
2 files changed, 211 insertions, 198 deletions
diff --git a/tuskar_ui/api.py b/tuskar_ui/api.py index c52fb89a..2f1aef91 100644 --- a/tuskar_ui/api.py +++ b/tuskar_ui/api.py @@ -21,13 +21,13 @@ import random import django.conf import django.db.models from django.utils.translation import ugettext_lazy as _ # noqa -import requests - from novaclient.v1_1.contrib import baremetal -from tuskarclient.v1 import client as tuskar_client - from openstack_dashboard.api import base from openstack_dashboard.api import nova +import requests +from tuskarclient.v1 import client as tuskar_client + +from tuskar_ui.cached_property import cached_property # noqa LOG = logging.getLogger(__name__) @@ -149,19 +149,15 @@ class Capacity(StringIdAPIResourceWrapper): # defines a random usage of capacity - API should probably be able to # determine usage of capacity based on capacity value and obejct_id - @property + @cached_property def usage(self): - if not hasattr(self, '_usage'): - self._usage = random.randint(0, int(self.value)) - return self._usage + return random.randint(0, int(self.value)) # defines a random average of capacity - API should probably be able to # determine average of capacity based on capacity value and obejct_id - @property + @cached_property def average(self): - if not hasattr(self, '_average'): - self._average = random.randint(0, int(self.value)) - return self._average + return random.randint(0, int(self.value)) class BaremetalNode(StringIdAPIResourceWrapper): @@ -250,21 +246,19 @@ class BaremetalNode(StringIdAPIResourceWrapper): def remaining_capacity(self): return 100 - self.running_instances - @property + @cached_property def running_virtual_machines(self): - if not hasattr(self, '_running_virtual_machines'): - if OVERCLOUD_CREDS: - search_opts = {} - search_opts['all_tenants'] = True - self._running_virtual_machines = [ - s for s in overcloudclient( - self.request).servers.list(True, search_opts) - if s.hostId == self.id] - else: - LOG.debug('OVERCLOUD_CREDS is not set. ' - 'Can\'t connect to Overcloud') - self._running_virtual_machines = [] - return self._running_virtual_machines + if OVERCLOUD_CREDS: + search_opts = {} + search_opts['all_tenants'] = True + return [ + s for s in overcloudclient( + self.request).servers.list(True, search_opts) + if s.hostId == self.id] + else: + LOG.debug( + "OVERCLOUD_CREDS is not set. Can't connect to Overcloud") + return [] class Node(StringIdAPIResourceWrapper): @@ -284,14 +278,12 @@ class Node(StringIdAPIResourceWrapper): def list(cls, request): return [cls(n, request) for n in (tuskarclient(request).nodes.list())] - @property + @cached_property def rack(self): - if not hasattr(self, '_rack'): - if self.rack_id: - self._rack = Rack.get(self.request, self.rack_id) - else: - self._rack = None - return self._rack + if self.rack_id: + return Rack.get(self.request, self.rack_id) + else: + return None @property def rack_id(self): @@ -300,16 +292,12 @@ class Node(StringIdAPIResourceWrapper): except AttributeError: return None - @property + @cached_property def nova_baremetal_node(self): - if not hasattr(self, '_nova_baremetal_node'): - if self.nova_baremetal_node_id: - self._nova_baremetal_node = BaremetalNode.get( - self.request, - self.nova_baremetal_node_id) - else: - self._nova_baremetal_node = None - return self._nova_baremetal_node + if self.nova_baremetal_node_id: + return BaremetalNode.get(self.request, self.nova_baremetal_node_id) + else: + return None def nova_baremetal_node_attribute(self, attr_name): key = "_%s" % attr_name @@ -345,41 +333,37 @@ class Node(StringIdAPIResourceWrapper): def running_virtual_machines(self): return self.nova_baremetal_node_attribute('running_virtual_machines') - @property + @cached_property def list_flavors(self): - if not hasattr(self, '_flavors'): - # FIXME: just a mock of used instances, add real values - used_instances = 0 - - if not self.rack or not self.rack.resource_class: - return [] - resource_class = self.rack.resource_class + # FIXME: just a mock of used instances, add real values + used_instances = 0 - added_flavors = tuskarclient(self.request).flavors\ - .list(resource_class.id) - self._flavors = [] - if added_flavors: - for f in added_flavors: - flavor_obj = Flavor(f) - #flavor_obj.max_vms = f.max_vms - - # FIXME just a mock of used instances, add real values - used_instances += 5 - flavor_obj.used_instances = used_instances - self._flavors.append(flavor_obj) + if not self.rack or not self.rack.resource_class: + return [] + resource_class = self.rack.resource_class + + added_flavors = tuskarclient( + self.request).flavors.list(resource_class.id) + flavors = [] + if added_flavors: + for flavor in added_flavors: + flavor_obj = Flavor(flavor) + #flavor_obj.max_vms = f.max_vms - return self._flavors + # FIXME just a mock of used instances, add real values + used_instances += 5 + flavor_obj.used_instances = used_instances + flavors.append(flavor_obj) + return flavors @property # FIXME: just mock implementation, add proper one def is_provisioned(self): return (self.status != "unprovisioned" and self.rack) - @property + @cached_property def alerts(self): - if not hasattr(self, '_alerts'): - self._alerts = [] - return self._alerts + return [] class Rack(StringIdAPIResourceWrapper): @@ -438,17 +422,12 @@ class Rack(StringIdAPIResourceWrapper): @property def node_ids(self): - """ List of unicode ids of nodes added to rack""" - return [ - unicode(node['id']) for node in ( - self.nodes)] + """List of unicode ids of nodes added to rack.""" + return [unicode(node['id']) for node in self.nodes] - @property + @cached_property def list_nodes(self): - if not hasattr(self, '_nodes'): - self._nodes = [Node.get(self.request, node['id']) for node in - self.nodes] - return self._nodes + return [Node.get(self.request, node['id']) for node in self.nodes] @property def list_baremetal_nodes(self): @@ -468,44 +447,33 @@ class Rack(StringIdAPIResourceWrapper): except AttributeError: return None - @property + @cached_property def resource_class(self): - if not hasattr(self, '_resource_class'): - if self.resource_class_id: - self._resource_class = ResourceClass.get( - self.request, - self.resource_class_id) - else: - self._resource_class = None - return self._resource_class + if self.resource_class_id: + return ResourceClass.get(self.request, self.resource_class_id) + else: + return None - @property + @cached_property def list_capacities(self): - if not hasattr(self, '_capacities'): - self._capacities = [Capacity(c) for c in self.capacities] - return self._capacities + return [Capacity(c) for c in self.capacities] - @property + @cached_property def vm_capacity(self): - """ Rack VM Capacity is maximum value from its Resource Class's - Flavors max_vms (considering flavor sizes are multiples). """ - if not hasattr(self, '_vm_capacity'): - try: - value = max([flavor.max_vms for flavor in - self.resource_class.list_flavors]) - except Exception: - value = None - self._vm_capacity = Capacity({'name': "VM Capacity", - 'value': value, - 'unit': 'VMs'}) - return self._vm_capacity + Rack VM Capacity is maximum value from its Resource Class's + Flavors max_vms (considering flavor sizes are multiples). + """ + try: + value = max([flavor.max_vms for flavor in + self.resource_class.list_flavors]) + except Exception: + value = None + return Capacity({'name': "VM Capacity", 'value': value, 'unit': 'VMs'}) - @property + @cached_property def alerts(self): - if not hasattr(self, '_alerts'): - self._alerts = [] - return self._alerts + return [] @property def aggregated_alerts(self): @@ -513,29 +481,25 @@ class Rack(StringIdAPIResourceWrapper): # used) return [node for node in self.list_nodes if node.alerts] - @property + @cached_property def list_flavors(self): - if not hasattr(self, '_flavors'): - # FIXME just a mock of used instances, add real values - used_instances = 0 + # FIXME just a mock of used instances, add real values + used_instances = 0 - if not self.resource_class: - return [] - added_flavors = (tuskarclient(self.request) - .flavors - .list(self.resource_class_id)) - self._flavors = [] - if added_flavors: - for f in added_flavors: - flavor_obj = Flavor(f) - #flavor_obj.max_vms = f.max_vms - - # FIXME just a mock of used instances, add real values - used_instances += 2 - flavor_obj.used_instances = used_instances - self._flavors.append(flavor_obj) + if not self.resource_class: + return [] + added_flavors = tuskarclient( + self.request).flavors.list(self.resource_class_id) + flavors = [] + for flavor in added_flavors: + flavor_obj = Flavor(flavor) + #flavor_obj.max_vms = f.max_vms - return self._flavors + # FIXME just a mock of used instances, add real values + used_instances += 2 + flavor_obj.used_instances = used_instances + flavors.append(flavor_obj) + return flavors @property def all_used_instances(self): @@ -621,17 +585,13 @@ class ResourceClass(StringIdAPIResourceWrapper): @property def racks_ids(self): - """ List of unicode ids of racks added to resource class """ - return [ - unicode(rack['id']) for rack in self.racks] + """List of unicode ids of racks added to resource class.""" + return [unicode(rack['id']) for rack in self.racks] - @property + @cached_property def list_racks(self): - """ List of racks added to ResourceClass """ - if not hasattr(self, '_racks'): - self._racks = [Rack.get(self.request, rid) for rid in ( - self.racks_ids)] - return self._racks + """List of racks added to ResourceClass.""" + return [Rack.get(self.request, rid) for rid in self.racks_ids] def set_racks(self, request, racks_ids): # FIXME: there is a bug now in tuskar, we have to remove all racks at @@ -645,24 +605,20 @@ class ResourceClass(StringIdAPIResourceWrapper): def racks_count(self): return len(self.racks) - @property + @cached_property def all_racks(self): - """ List of racks added to ResourceClass + list of free racks, - meaning racks that don't belong to any ResourceClass""" - if not hasattr(self, '_all_racks'): - self._all_racks =\ - [r for r in ( - Rack.list(self.request)) if ( - r.resource_class_id is None or - str(r.resource_class_id) == self.id)] - return self._all_racks + """ + List of racks added to ResourceClass + list of free racks, + meaning racks that don't belong to any ResourceClass. + """ + return [rack for rack in Rack.list(self.request) + if rack.resource_class_id is None or + str(rack.resource_class_id) == self.id] - @property + @cached_property def nodes(self): - if not hasattr(self, '_nodes'): - self._nodes = [n for n in Node.list(self.request) - if n.rack_id in self.racks_ids] - return self._nodes + return [n for n in Node.list(self.request) + if n.rack_id in self.racks_ids] @property def nodes_count(self): @@ -670,26 +626,25 @@ class ResourceClass(StringIdAPIResourceWrapper): @property def flavors_ids(self): - """ List of unicode ids of flavors added to resource class """ + """List of unicode ids of flavors added to resource class.""" return [unicode(flavor.id) for flavor in self.list_flavors] - @property + @cached_property def list_flavors(self): - if not hasattr(self, '_flavors'): - # FIXME just a mock of used instances, add real values - used_instances = 0 + # FIXME just a mock of used instances, add real values + used_instances = 0 - added_flavors = tuskarclient(self.request).flavors.list(self.id) - self._flavors = [] - for f in added_flavors: - flavor_obj = Flavor(f, self.request) - #flavor_obj.max_vms = f.max_vms + added_flavors = tuskarclient(self.request).flavors.list(self.id) + flavors = [] + for flavor in added_flavors: + flavor_obj = Flavor(flavor, self.request) + #flavor_obj.max_vms = f.max_vms - # FIXME just a mock of used instances, add real values - used_instances += 5 - flavor_obj.used_instances = used_instances - self._flavors.append(flavor_obj) - return self._flavors + # FIXME just a mock of used instances, add real values + used_instances += 5 + flavor_obj.used_instances = used_instances + flavors.append(flavor_obj) + return flavors @property def all_used_instances(self): @@ -705,37 +660,36 @@ class ResourceClass(StringIdAPIResourceWrapper): # FIXME just mock implementation, add proper one return 100 - self.total_instances - @property + @cached_property def capacities(self): - """Aggregates Rack capacities values - """ - if not hasattr(self, '_capacities'): - capacities = [rack.list_capacities for rack in self.list_racks] + """Aggregates Rack capacities values.""" - def add_capacities(c1, c2): - return [Capacity({'name': a.name, - 'value': int(a.value) + int(b.value), - 'unit': a.unit}) for a, b in zip(c1, c2)] + def add_capacities(c1, c2): + return [Capacity({ + 'name': a.name, + 'value': int(a.value) + int(b.value), + 'unit': a.unit, + }) for a, b in zip(c1, c2)] - self._capacities = reduce(add_capacities, capacities) - return self._capacities + capacities = [rack.list_capacities for rack in self.list_racks] + return reduce(add_capacities, capacities) - @property + @cached_property def vm_capacity(self): """ Resource Class VM Capacity is maximum value from It's Flavors max_vms (considering flavor sizes are multiples), multipled by number of Racks in Resource Class. """ - if not hasattr(self, '_vm_capacity'): - try: - value = self.racks_count * max([flavor.max_vms for flavor in - self.list_flavors]) - except Exception: - value = _("Unable to retrieve vm capacity") - self._vm_capacity = Capacity({'name': _("VM Capacity"), - 'value': value, - 'unit': _('VMs')}) - return self._vm_capacity + try: + value = self.racks_count * max([flavor.max_vms + for flavor in self.list_flavors]) + except Exception: + value = _("Unable to retrieve vm capacity") + return Capacity({ + 'name': _("VM Capacity"), + 'value': value, + 'unit': _('VMs'), + }) @property def aggregated_alerts(self): @@ -776,18 +730,14 @@ class Flavor(StringIdAPIResourceWrapper): kwargs['resource_class_id'], kwargs['flavor_id']) - @property + @cached_property def capacities(self): - if not hasattr(self, '_capacities'): - ## FIXME: should we distinguish between tuskar - ## capacities and our internal capacities? - CapacityStruct = collections.namedtuple('CapacityStruct', - 'name value unit') - self._capacities = [Capacity(CapacityStruct(name=c['name'], - value=c['value'], - unit=c['unit'])) - for c in self._apiresource.capacities] - return self._capacities + # FIXME: should we distinguish between tuskar capacities and our + # internal capacities? + CapacityStruct = collections.namedtuple( + 'CapacityStruct', 'name value unit') + return [Capacity(CapacityStruct(c['name'], c['value'], c['unit'])) + for c in self._apiresource.capacities] def capacity(self, capacity_name): key = "_%s" % capacity_name diff --git a/tuskar_ui/cached_property.py b/tuskar_ui/cached_property.py new file mode 100644 index 00000000..59582110 --- /dev/null +++ b/tuskar_ui/cached_property.py @@ -0,0 +1,63 @@ +# 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. + +# Copyright (c) Django Software Foundation and individual contributors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of Django nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + +# We would be using django.utils.functional.cached_property, except it +# breaks when used with mox in our tests, because of +# https://code.djangoproject.com/ticket/19872 +# +# So we have a copy of it here, with the bug fixed. +# FIXME: Use django's version when the bug is fixed there. +class cached_property(object): + """ + Decorator that creates converts a method with a single + self argument into a property cached on the instance. + """ + def __init__(self, func): + self.func = func + + def __get__(self, instance, type): + if instance is None: + return self + res = instance.__dict__[self.func.__name__] = self.func(instance) + return res |