Tzu-Mainn Chen 544cef78a6 Fixed rack tests
This patch fixes the tests.  There's one failure
left; fixing it requires stubbing out the baremetal
client, which I'm holding off on doing for now.

Change-Id: I30e5ffd187a3309906909e7f0a75583d957c42fc
2013-08-01 16:18:58 +02:00

1002 lines
34 KiB
Python

# 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.
import copy
import logging
import re
from collections import namedtuple
import itertools
from datetime import timedelta
from random import randint
from django.conf import settings
from django.db.models import Sum, Max
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from tuskarclient.v1 import client as tuskar_client
from openstack_dashboard.api import base, nova
import openstack_dashboard.dashboards.infrastructure.models as dummymodels
LOG = logging.getLogger(__name__)
TUSKAR_ENDPOINT_URL = getattr(settings, 'TUSKAR_ENDPOINT_URL')
NOVA_BAREMETAL_CREDS = getattr(settings, 'NOVA_BAREMETAL_CREDS')
OVERCLOUD_AUTH_URL = getattr(settings, 'OVERCLOUD_AUTH_URL')
OVERCLOUD_USERNAME = getattr(settings, 'OVERCLOUD_USERNAME')
OVERCLOUD_PASSWORD = getattr(settings, 'OVERCLOUD_PASSWORD')
# FIXME: request isn't used right in the tuskar client right now, but looking
# at other clients, it seems like it will be in the future
def tuskarclient(request):
c = tuskar_client.Client(TUSKAR_ENDPOINT_URL)
return c
class StringIdAPIResourceWrapper(base.APIResourceWrapper):
# horizon DataTable class expects ids to be string,
# if it's not string, then comparison in
# horizon/tables/base.py:get_object_by_id fails.
# Because of this, ids returned from dummy api are converted to string
# (luckily django autoconverts strings to integers when passing string to
# django model id)
def __init__(self, apiresource, request=None):
self.request = request
self._apiresource = apiresource
# FIXME
# this is redefined from base.APIResourceWrapper,
# remove this when tuskarclient returns object instead of dict
def __getattr__(self, attr):
if attr in self._attrs:
if issubclass(self._apiresource.__class__, dict):
return self._apiresource.get(attr)
else:
return self._apiresource.__getattribute__(attr)
else:
msg = ('Attempted to access unknown attribute "%s" on '
'APIResource object of type "%s" wrapping resource of '
'type "%s".') % (attr, self.__class__,
self._apiresource.__class__)
LOG.debug(exceptions.error_color(msg))
raise AttributeError(attr)
@property
def id(self):
return str(self._apiresource.id)
# FIXME: self.request is required when calling some instance
# methods (e.g. list_flavors), once we really start using this request
# param (if ever), a proper request value should be set
@property
def request(self):
return getattr(self, '_request', None)
@request.setter
def request(self, value):
setattr(self, '_request', value)
class Alert(StringIdAPIResourceWrapper):
"""Wrapper for the Alert object returned by the
dummy model.
"""
_attrs = ['message', 'time']
class Capacity(StringIdAPIResourceWrapper):
"""Wrapper for the Capacity object returned by the
dummy model.
"""
_attrs = ['name', 'value', 'unit']
@classmethod
def create(cls, request, content_object, name, value, unit):
c = dummymodels.Capacity(
content_object=content_object,
name=name,
value=value,
unit=unit)
c.save()
return Capacity(c)
@classmethod
def update(cls, request, capacity_id, content_object, name, value, unit):
c = dummymodels.Capacity.objects.get(id=capacity_id)
c.content_object = content_object
c.name = name
c.value = value
c.unit = unit
c.save()
return cls(c)
# defines a random usage of capacity - API should probably be able to
# determine usage of capacity based on capacity value and obejct_id
@property
def usage(self):
if not hasattr(self, '_usage'):
self._usage = randint(0, int(self.value))
return self._usage
# defines a random average of capacity - API should probably be able to
# determine average of capacity based on capacity value and obejct_id
@property
def average(self):
if not hasattr(self, '_average'):
self._average = randint(0, int(self.value))
return self._average
class Node(StringIdAPIResourceWrapper):
"""Wrapper for the Node object returned by the
dummy model.
"""
_attrs = ['id', 'pm_address', 'cpus', 'memory_mb', 'service_host',
'local_gb', 'pm_user']
@classmethod
def manager(cls):
nc = nova.nova_client.Client(
NOVA_BAREMETAL_CREDS['user'],
NOVA_BAREMETAL_CREDS['password'],
NOVA_BAREMETAL_CREDS['tenant'],
auth_url=NOVA_BAREMETAL_CREDS['auth_url'],
bypass_url=NOVA_BAREMETAL_CREDS['bypass_url'])
return nova.baremetal.BareMetalNodeManager(nc)
@classmethod
def get(cls, request, node_id):
node = cls(cls.manager().get(node_id))
node.request = request
# FIXME ugly, fix after demo, make abstraction of instance details
# this is realy not optimal, but i dont hve time do fix it now
instances, more = nova.server_list(
request,
search_opts={'paginate': True},
all_tenants=True)
instance_details = {}
for instance in instances:
id = (instance.
_apiresource._info['OS-EXT-SRV-ATTR:hypervisor_hostname'])
instance_details[id] = instance
detail = instance_details.get(node_id)
if detail:
addresses = detail._apiresource.addresses.get('ctlplane')
if addresses:
node.ip_address_other = (", "
.join([addr['addr'] for addr in addresses]))
node.status = detail._apiresource._info['OS-EXT-STS:vm_state']
node.power_mamanegemt = ""
if node.pm_user:
node.power_mamanegemt = node.pm_user + "/********"
else:
node.status = 'unprovisioned'
return node
@classmethod
def list(cls, request):
return cls.manager().list()
@classmethod
def list_unracked(cls, request):
return [cls(h) for h in dummymodels.Node.objects.all() if (
h.rack is None)]
@classmethod
def create(cls, request, name, mac_address, ip_address, status,
usage, rack):
node = dummymodels.Node(name=name, mac_address=mac_address,
ip_address=ip_address, status=status,
usage=usage, rack=rack)
node.save()
@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
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)
return self._flavors
@property
def capacities(self):
if not hasattr(self, '_capacities'):
self._capacities = [Capacity(c) for c in
self._apiresource.capacities.all()]
return self._capacities
@property
def rack(self):
try:
if not hasattr(self, '_rack'):
# FIXME the node.rack association should be stored somewhere
self._rack = None
for rack in Rack.list(self.request):
for node_obj in rack.list_nodes:
if node_obj.id == self.id:
self._rack = rack
return self._rack
except:
msg = "Could not obtain Nodes's rack"
LOG.debug(exceptions.error_color(msg))
return None
@property
def cpu(self):
if not hasattr(self, '_cpu'):
try:
cpu = dummymodels.Capacity.objects\
.filter(node=self._apiresource)\
.filter(name='cpu')[0]
except:
cpu = dummymodels.Capacity(
name='cpu',
value=_('Unable to retrieve '
'(Is the node configured properly?)'),
unit='')
self._cpu = Capacity(cpu)
return self._cpu
@property
def ram(self):
if not hasattr(self, '_ram'):
try:
ram = dummymodels.Capacity.objects\
.filter(node=self._apiresource)\
.filter(name='ram')[0]
except:
ram = dummymodels.Capacity(
name='ram',
value=_('Unable to retrieve '
'(Is the node configured properly?)'),
unit='')
self._ram = Capacity(ram)
return self._ram
@property
def storage(self):
if not hasattr(self, '_storage'):
try:
storage = dummymodels.Capacity.objects\
.filter(node=self._apiresource)\
.filter(name='storage')[0]
except:
storage = dummymodels.Capacity(
name='storage',
value=_('Unable to retrieve '
'(Is the node configured properly?)'),
unit='')
self._storage = Capacity(storage)
return self._storage
@property
def network(self):
if not hasattr(self, '_network'):
try:
network = dummymodels.Capacity.objects\
.filter(node=self._apiresource)\
.filter(name='network')[0]
except:
network = dummymodels.Capacity(
name='network',
value=_('Unable to retrieve '
'(Is the node configured properly?)'),
unit='')
self._network = Capacity(network)
return self._network
@property
def vm_capacity(self):
if not hasattr(self, '_vm_capacity'):
try:
value = dummymodels.ResourceClassFlavor.objects\
.filter(
resource_class__rack__node=self._apiresource)\
.aggregate(Max("max_vms"))['max_vms__max']
except:
value = _("Unable to retrieve vm capacity")
vm_capacity = dummymodels.Capacity(name=_("Max VMs"),
value=value,
unit=_("VMs"))
self._vm_capacity = Capacity(vm_capacity)
return self._vm_capacity
@property
# FIXME: just mock implementation, add proper one
def running_instances(self):
return 4
@property
# FIXME: just mock implementation, add proper one
def remaining_capacity(self):
return 100 - self.running_instances
@property
# FIXME: just mock implementation, add proper one
def is_provisioned(self):
return self.status != "unprovisioned" and self.rack
@property
def alerts(self):
if not hasattr(self, '_alerts'):
self._alerts = [Alert(a) for a in
dummymodels.Alert.objects
.filter(object_type='node')
.filter(object_id=str(self.id))]
return self._alerts
@property
def mac_address(self):
try:
return self._apiresource.interfaces[0]['address']
except:
return None
@property
def running_virtual_machines(self):
if not hasattr(self, '_running_virtual_machines'):
search_opts = {}
search_opts['all_tenants'] = True
nova_client = nova.nova_client.Client(OVERCLOUD_USERNAME,
OVERCLOUD_PASSWORD,
'admin',
auth_url=OVERCLOUD_AUTH_URL)
self._running_virtual_machines = [s for s in
nova_client.servers.list(True, search_opts)
if s.hostId == self.id]
return self._running_virtual_machines
class Rack(StringIdAPIResourceWrapper):
"""Wrapper for the Rack object returned by the
dummy model.
"""
_attrs = ['id', 'name', 'location', 'subnet', 'nodes', 'state']
@classmethod
def create(cls, request, name, resource_class_id, location, subnet,
nodes=[]):
## FIXME: set nodes here
rack = tuskarclient(request).racks.create(
name=name,
location=location,
subnet=subnet,
nodes=nodes,
resource_class={'id': resource_class_id},
slots=0)
return cls(rack)
@classmethod
def update(cls, request, rack_id, kwargs):
## FIXME: set nodes here
correct_kwargs = copy.copy(kwargs)
# remove rack_id from kwargs (othervise it is duplicated)
correct_kwargs.pop('rack_id', None)
# correct data mapping for resource_class
if 'resource_class_id' in correct_kwargs:
correct_kwargs['resource_class'] = {
'id': correct_kwargs.pop('resource_class_id', None)}
rack = tuskarclient(request).racks.update(rack_id, **correct_kwargs)
return cls(rack)
@classmethod
def list(cls, request, only_free_racks=False):
if only_free_racks:
return [Rack(r, request) for r in
tuskarclient(request).racks.list() if (
r.resource_class is None)]
else:
return [Rack(r, request) for r in
tuskarclient(request).racks.list()]
@classmethod
def get(cls, request, rack_id):
rack = cls(tuskarclient(request).racks.get(rack_id))
rack.request = request
return rack
@property
def resource_class_id(self):
rclass = getattr(self._apiresource, 'resource_class', None)
return rclass['id'] if rclass else None
@classmethod
def delete(cls, request, rack_id):
tuskarclient(request).racks.delete(rack_id)
@property
def node_ids(self):
""" List of unicode ids of nodes added to rack"""
return [
unicode(node['id']) for node in (
self._apiresource.nodes)]
## FIXME: this will have to be rewritten to ultimately
## fetch nodes from nova baremetal
@property
def list_nodes(self):
if not hasattr(self, '_nodes'):
self._nodes = [Node.get(self.request, node['id']) for node in (
self._apiresource.nodes)]
return self._nodes
def nodes_count(self):
return len(self._apiresource.nodes)
# The idea here is to take a list of MAC addresses and assign them to
# our rack. I'm attaching this here so that we can take one list, versus
# potentially making a long series of API calls.
# The present implementation makes no attempt at optimization since this
# is likely short-lived until a real API is implemented.
@classmethod
def register_nodes(cls, rack_id, nodes_list):
for mac in nodes_list:
# search for MAC
try:
node = dummymodels.Node.objects.get(mac_address=mac)
if node is not None:
node.rack_id = rack_id
node.save()
except:
# FIXME: It is unclear what we're supposed to do in this case.
# I create a new Node, but it's possible we should not
# allow new entries here.
# FIXME: If this stays, we should probably add Capabilities
# here so that graphs work as expected.
Node.create(None, mac, mac, None, None, None, rack_id)
@property
def resource_class(self):
if not hasattr(self, '_resource_class'):
rclass = getattr(self._apiresource, 'resource_class', None)
if rclass:
self._resource_class = ResourceClass.get(self.request,
rclass['id'])
else:
self._resource_class = None
return self._resource_class
@property
def capacities(self):
if not hasattr(self, '_capacities'):
self._capacities = [Capacity(c) for c in
self._apiresource.capacities]
return self._capacities
@property
def vm_capacity(self):
""" Rack VM Capacity is maximum value from It's 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:
value = None
self._vm_capacity = Capacity({'name': "VM Capacity",
'value': value,
'unit': 'VMs'})
return self._vm_capacity
@property
def alerts(self):
if not hasattr(self, '_alerts'):
self._alerts = [Alert(a) for a in
dummymodels.Alert.objects
.filter(object_type='rack')
.filter(object_id=int(self.id))]
return self._alerts
@property
def aggregated_alerts(self):
# FIXME: for now return only list of nodes (particular alerts are not
# used)
return [node for node in self.list_nodes if node.alerts]
@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.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)
return self._flavors
@property
def all_used_instances(self):
return [flavor.used_instances for flavor in self.list_flavors]
@property
def total_instances(self):
# FIXME just mock implementation, add proper one
return sum(self.all_used_instances)
@property
def remaining_capacity(self):
# FIXME just mock implementation, add proper one
return 100 - self.total_instances
@property
def is_provisioned(self):
return (self.state == 'active') or (self.state == 'error')
@property
def is_provisioning(self):
return (self.state == 'provisioning')
@classmethod
def provision(cls, request, rack_id):
tuskarclient(request).data_centers.provision_all()
class ResourceClass(StringIdAPIResourceWrapper):
"""Wrapper for the ResourceClass object returned by the
dummy model.
"""
_attrs = ['id', 'name', 'service_type', 'racks']
@classmethod
def get(cls, request, resource_class_id):
rc = cls(tuskarclient(request).resource_classes.get(resource_class_id))
return rc
@classmethod
def create(self, request, name, service_type, flavors):
return ResourceClass(
tuskarclient(request).resource_classes.create(
name=name,
service_type=service_type,
flavors=flavors))
@classmethod
def list(cls, request):
return [cls(rc) for rc in (
tuskarclient(request).resource_classes.list())]
@classmethod
def update(cls, request, resource_class_id, **kwargs):
resource_class = cls(tuskarclient(request).resource_classes.update(
resource_class_id, **kwargs))
## FIXME: flavors have to be updated separately, seems less than ideal
for flavor_id in resource_class.flavors_ids:
Flavor.delete(request, resource_class.id, flavor_id)
for flavor in kwargs['flavors']:
Flavor.create(request, resource_class.id, **flavor)
return resource_class
@property
def deletable(self):
return (len(self.list_racks) <= 0)
@classmethod
def delete(cls, request, resource_class_id):
tuskarclient(request).resource_classes.delete(resource_class_id)
@property
def racks_ids(self):
""" List of unicode ids of racks added to resource class """
return [
unicode(rack['id']) for rack in (
self._apiresource.racks)]
@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
@property
def capacities(self):
"""Aggregates Rack capacities values
"""
if not hasattr(self, '_capacities'):
capacities = [rack.capacities for rack in self.list_racks]
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
@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
@property
def flavors_ids(self):
""" List of unicode ids of flavors added to resource class """
return [unicode(flavor.id) for flavor in self.list_flavors]
# FIXME: for now, we display list of flavor templates when
# editing a resource class - we have to set id of flavor template, not
# flavor
@property
def flavortemplates_ids(self):
""" List of unicode ids of flavor templates added to resource class """
return [unicode(ft.flavor_template.id) for ft in self.list_flavors]
@property
def list_flavors(self):
if not hasattr(self, '_flavors'):
# 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)
#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
@property
def all_used_instances(self):
return [flavor.used_instances for flavor in self.list_flavors]
@property
def total_instances(self):
# FIXME just mock implementation, add proper one
return sum(self.all_used_instances)
@property
def remaining_capacity(self):
# FIXME just mock implementation, add proper one
return 100 - self.total_instances
@property
def all_flavors(self):
""" Joined relation table resourceclassflavor with all global flavors
"""
if not hasattr(self, '_all_flavors'):
my_flavors = self.list_flavors
self._all_flavors = []
for flavor in FlavorTemplate.list(self.request):
fname = "%s.%s" % (self.name, flavor.name)
f = next((f for f in my_flavors if f.name == fname), None)
if f:
flavor.max_vms = f.max_vms
self._all_flavors.append(flavor)
return self._all_flavors
@property
def nodes(self):
if not hasattr(self, '_nodes'):
self._nodes = [rack.list_nodes for rack in self.list_racks]
return self._nodes
@property
def nodes_count(self):
return len(self.nodes)
@property
def racks_count(self):
return len(self.racks)
@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:
value = _("Unable to retrieve vm capacity")
self._vm_capacity = Capacity({'name': _("VM Capacity"),
'value': value,
'unit': _('VMs')})
return self._vm_capacity
def set_racks(self, request, racks_ids):
# FIXME: there is a bug now in tuskar, we have to remove all racks at
# first and then add new ones:
# https://github.com/tuskar/tuskar/issues/37
tuskarclient(request).resource_classes.update(self.id, racks=[])
racks = [{'id': rid} for rid in racks_ids]
tuskarclient(request).resource_classes.update(self.id, racks=racks)
@property
def aggregated_alerts(self):
# FIXME: for now return only list of racks (particular alerts are not
# used)
return [rack for rack in self.list_racks if (rack.alerts +
rack.aggregated_alerts)]
@property
def has_provisioned_rack(self):
return any([rack.is_provisioned for rack in self.list_racks])
class FlavorTemplate(StringIdAPIResourceWrapper):
"""Wrapper for the Flavor object returned by the
dummy model.
"""
_attrs = ['name']
@property
def max_vms(self):
return getattr(self, '_max_vms', '0')
@max_vms.setter
def max_vms(self, value='0'):
self._max_vms = value
@property
def used_instances(self):
return getattr(self, '_used_instances', 0)
@used_instances.setter
def used_instances(self, value=0):
self._used_instances = value
@classmethod
def list(cls, request, only_free_racks=False):
return [cls(f) for f in dummymodels.FlavorTemplate.objects.all()]
@classmethod
def get(cls, request, flavor_id):
return cls(dummymodels.FlavorTemplate.objects.get(id=flavor_id))
@classmethod
def create(cls, request,
name, cpu, memory, storage, ephemeral_disk, swap_disk):
template = dummymodels.FlavorTemplate(name=name)
template.save()
Capacity.create(request, template, 'cpu', cpu, '')
Capacity.create(request, template, 'memory', memory, 'MB')
Capacity.create(request, template, 'storage', storage, 'GB')
Capacity.create(request,
template, 'ephemeral_disk', ephemeral_disk, 'GB')
Capacity.create(request, template, 'swap_disk', swap_disk, 'MB')
@property
def capacities(self):
if not hasattr(self, '_capacities'):
self._capacities = [Capacity(c) for c in
self._apiresource.capacities.all()]
return self._capacities
def capacity(self, capacity_name):
key = "_%s" % capacity_name
if not hasattr(self, key):
try:
capacity = [c for c in self.capacities if (
c.name == capacity_name)][0]
except:
capacity = dummymodels.Capacity(
name=capacity_name,
value=_('Unable to retrieve '
'(Is the flavor configured properly?)'),
unit='')
setattr(self, key, capacity)
return getattr(self, key)
@property
def cpu(self):
return self.capacity('cpu')
@property
def memory(self):
return self.capacity('memory')
@property
def storage(self):
return self.capacity('storage')
@property
def ephemeral_disk(self):
return self.capacity('ephemeral_disk')
@property
def swap_disk(self):
return self.capacity('swap_disk')
@property
def running_virtual_machines(self):
# FIXME: arbitrary number
return randint(0, int(self.cpu.value))
# defines a random average of capacity - API should probably be able to
# determine average of capacity based on capacity value and obejct_id
def vms_over_time(self, start_time, end_time):
values = []
current_time = start_time
while current_time <= end_time:
values.append(
{'date': current_time,
'active_vms': randint(0, self.running_virtual_machines)})
current_time += timedelta(hours=1)
return values
@classmethod
def update(cls, request, template_id, name, cpu, memory, storage,
ephemeral_disk, swap_disk):
t = dummymodels.FlavorTemplate.objects.get(id=template_id)
t.name = name
t.save()
template = cls(t)
Capacity.update(request, template.cpu.id, template._apiresource,
'cpu', cpu, '')
Capacity.update(request, template.memory.id, template._apiresource,
'memory', memory, 'MB')
Capacity.update(request, template.storage.id, template._apiresource,
'storage', storage, 'GB')
Capacity.update(request, template.ephemeral_disk.id,
template._apiresource, 'ephemeral_disk',
ephemeral_disk, 'GB')
Capacity.update(request, template.swap_disk.id, template._apiresource,
'swap_disk', swap_disk, 'MB')
return template
@classmethod
def delete(cls, request, template_id):
dummymodels.FlavorTemplate.objects.get(id=template_id).delete()
class Flavor(StringIdAPIResourceWrapper):
"""Wrapper for the Flavor object returned by Tuskar.
"""
_attrs = ['name', 'max_vms']
@classmethod
def create(cls, request, resource_class_id, name, max_vms, capacities):
return cls(tuskarclient(request).flavors.create(
resource_class_id,
name=name,
max_vms=max_vms,
capacities=capacities))
@classmethod
def delete(cls, request, resource_class_id, flavor_id):
tuskarclient(request).flavors.delete(resource_class_id, flavor_id)
# FIXME: returns flavor template for this flavor
@property
def flavor_template(self):
# strip resource class prefix from flavor name before comparing:
fname = re.sub(r'^.*\.', '', self.name)
return next(f for f in FlavorTemplate.list(None) if (
f.name == fname))
@property
def capacities(self):
if not hasattr(self, '_capacities'):
## FIXME: should we distinguish between tuskar
## capacities and our internal capacities?
CapacityStruct = 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
def capacity(self, capacity_name):
key = "_%s" % capacity_name
if not hasattr(self, key):
try:
capacity = [c for c in self.capacities if (
c.name == capacity_name)][0]
except:
# FIXME: test this
capacity = Capacity(
name=capacity_name,
value=_('Unable to retrieve '
'(Is the flavor configured properly?)'),
unit='')
setattr(self, key, capacity)
return getattr(self, key)
@property
def cpu(self):
return self.capacity('cpu')
@property
def memory(self):
return self.capacity('memory')
@property
def storage(self):
return self.capacity('storage')
@property
def ephemeral_disk(self):
return self.capacity('ephemeral_disk')
@property
def swap_disk(self):
return self.capacity('swap_disk')
@property
def running_virtual_machines(self):
# FIXME: arbitrary number
return randint(0, int(self.cpu.value))
# defines a random average of capacity - API should probably be able to
# determine average of capacity based on capacity value and obejct_id
def vms_over_time(self, start_time, end_time):
values = []
current_time = start_time
while current_time <= end_time:
values.append({'date': current_time,
'value': randint(0, self.running_virtual_machines)})
current_time += timedelta(hours=1)
return values