c67c54240c
This fixes the imports plus some timeouts waiting for nova baremetal. Change-Id: I4dc7aad55a05068081ab0b5a12da7c57d3a032f4 Signed-off-by: Tomas Sedovic <tsedovic@redhat.com>
917 lines
31 KiB
Python
917 lines
31 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.
|
|
|
|
from collections import namedtuple
|
|
import copy
|
|
from datetime import timedelta
|
|
import logging
|
|
from random import randint
|
|
import re
|
|
|
|
from django.conf import settings
|
|
from django.db.models import Max
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from horizon import exceptions
|
|
from requests import ConnectionError
|
|
|
|
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 tuskar_ui.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
|
|
|
|
|
|
def baremetalclient(request):
|
|
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 baremetal.BareMetalNodeManager(nc)
|
|
|
|
|
|
def overcloudclient(request):
|
|
c = nova.nova_client.Client(OVERCLOUD_USERNAME,
|
|
OVERCLOUD_PASSWORD,
|
|
'admin',
|
|
auth_url=OVERCLOUD_AUTH_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 get(cls, request, node_id):
|
|
node = cls(baremetalclient(request).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_management = ""
|
|
if node.pm_user:
|
|
node.power_management = node.pm_user + "/********"
|
|
else:
|
|
node.status = 'unprovisioned'
|
|
|
|
return node
|
|
|
|
@classmethod
|
|
def list(cls, request):
|
|
return [Node(n, request) for n in
|
|
baremetalclient(request).list()]
|
|
|
|
@classmethod
|
|
def list_unracked(cls, request):
|
|
try:
|
|
return [n for n in Node.list(request) if (n.rack is None)]
|
|
except ConnectionError:
|
|
return []
|
|
|
|
@classmethod
|
|
def create(cls, request, name, cpus, memory_mb, local_gb, prov_mac_address,
|
|
pm_address, pm_user, pm_password, terminal_port):
|
|
node = baremetalclient(request).create(name, cpus, memory_mb,
|
|
local_gb, prov_mac_address,
|
|
pm_address, pm_user,
|
|
pm_password, terminal_port)
|
|
return cls(node)
|
|
|
|
@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.get_resource_class:
|
|
return []
|
|
resource_class = self.rack.get_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 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 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
|
|
self._running_virtual_machines = [s for s in
|
|
overcloudclient(self.request).
|
|
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',
|
|
'capacities', 'resource_class']
|
|
|
|
@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
|
|
|
|
@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.nodes)]
|
|
|
|
@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
|
|
|
|
@property
|
|
def nodes_count(self):
|
|
return len(self.nodes)
|
|
|
|
@property
|
|
def resource_class_id(self):
|
|
rclass = self.resource_class
|
|
return rclass['id'] if rclass else None
|
|
|
|
@property
|
|
def get_resource_class(self):
|
|
if not hasattr(self, '_resource_class'):
|
|
rclass = self.resource_class
|
|
if rclass:
|
|
self._resource_class = ResourceClass.get(self.request,
|
|
rclass['id'])
|
|
else:
|
|
self._resource_class = None
|
|
return self._resource_class
|
|
|
|
@property
|
|
def list_capacities(self):
|
|
if not hasattr(self, '_capacities'):
|
|
self._capacities = [Capacity(c) for c in self.capacities]
|
|
return self._capacities
|
|
|
|
@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.get_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.get_resource_class:
|
|
return []
|
|
added_flavors = tuskarclient(self.request).flavors\
|
|
.list(self.get_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))
|
|
rc.request = request
|
|
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, request) 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.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.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
|
|
|
|
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 racks_count(self):
|
|
return len(self.racks)
|
|
|
|
@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 nodes(self):
|
|
if not hasattr(self, '_nodes'):
|
|
nodes_lists = [rack.list_nodes for rack in self.list_racks]
|
|
self._nodes = [node for nodes in nodes_lists for node in nodes]
|
|
return self._nodes
|
|
|
|
@property
|
|
def nodes_count(self):
|
|
return len(self.nodes)
|
|
|
|
@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]
|
|
|
|
@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_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
|
|
|
|
# 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 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 capacities(self):
|
|
"""Aggregates Rack capacities values
|
|
"""
|
|
if not hasattr(self, '_capacities'):
|
|
capacities = [rack.list_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 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
|
|
|
|
@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 = ['id', '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
|