summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRadomir Dopieralski <openstack@sheep.art.pl>2013-10-25 11:19:31 +0200
committerRadomir Dopieralski <openstack@sheep.art.pl>2013-10-29 17:16:39 +0100
commit7b2282331a48afedd2506e0adc110028ff81f769 (patch)
treee8042b91c8f029cfb647ff31ef94ea80591f0115
parentd2171beaf8d06929a37b78d9d8c451790ee546a9 (diff)
downloadtuskar-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.py346
-rw-r--r--tuskar_ui/cached_property.py63
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