Tzu-Mainn Chen a6ea6bb4bf Update ironic properties to match REST API doc
The referenced doc is here:

http://docs.openstack.org/developer/ironic/webapi/v1.html

This fix should make it far more explicit what units are needed
for various node properties.  Note that Ironic and Nova BareMetal
now use the exact same property names.

Closes-bug: #1342156
Change-Id: I024c29d0ab648ed5db000ddb6d0ffaa24e8805b8
2014-07-18 13:15:24 +02:00

518 lines
17 KiB
Python

# 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 logging
from django.utils.translation import ugettext_lazy as _
from horizon.utils import memoized
from novaclient.v1_1.contrib import baremetal
from openstack_dashboard.api import base
from openstack_dashboard.api import glance
from openstack_dashboard.api import nova
from openstack_dashboard.test.test_data import utils as test_utils
from tuskar_ui.cached_property import cached_property # noqa
from tuskar_ui.handle_errors import handle_errors # noqa
from tuskar_ui.test.test_data import heat_data
from tuskar_ui.test.test_data import node_data
from tuskar_ui import utils
TEST_DATA = test_utils.TestDataContainer()
node_data.data(TEST_DATA)
heat_data.data(TEST_DATA)
LOG = logging.getLogger(__name__)
def baremetalclient(request):
nc = nova.novaclient(request)
return baremetal.BareMetalNodeManager(nc)
# FIXME(lsmola) This should be done in Horizon, they don't have caching
@memoized.memoized
def image_get(request, image_id):
"""Returns an Image object with metadata
Returns an Image object populated with metadata for image
with supplied identifier.
:param image_id: list of objects to be put into a dict
:type object_list: list
:return: object
:rtype: glanceclient.v1.images.Image
"""
image = glance.image_get(request, image_id)
return image
class IronicNode(base.APIResourceWrapper):
_attrs = ('id', 'uuid', 'instance_uuid', 'driver', 'driver_info',
'properties', 'power_state', 'maintenance')
@classmethod
def create(cls, request, ipmi_address, architecture, cpus, memory_mb,
local_gb, mac_addresses, ipmi_username=None, ipmi_password=None,
driver=None):
"""Create a Node in Ironic
"""
node = TEST_DATA.ironicclient_nodes.first()
return cls(node)
@classmethod
def get(cls, request, uuid):
"""Return the IronicNode that matches the ID
:param request: request object
:type request: django.http.HttpRequest
:param uuid: ID of IronicNode to be retrieved
:type uuid: str
:return: matching IronicNode, or None if no IronicNode matches the ID
:rtype: tuskar_ui.api.node.IronicNode
"""
nodes = IronicNode.list(request) + IronicNode.list_discovered(request)
for node in nodes:
if node.uuid == uuid:
return node
@classmethod
def get_by_instance_uuid(cls, request, instance_uuid):
"""Return the IronicNode associated with the instance ID
:param request: request object
:type request: django.http.HttpRequest
:param instance_uuid: ID of Instance that is deployed on the IronicNode
to be retrieved
:type instance_uuid: str
:return: matching IronicNode
:rtype: tuskar_ui.api.node.IronicNode
:raises: ironicclient.exc.HTTPNotFound if there is no IronicNode with
the matching instance UUID
"""
for node in IronicNode.list(request):
if node.instance_uuid == instance_uuid:
return node
@classmethod
@handle_errors(_("Unable to retrieve nodes"), [])
def list(cls, request, associated=None):
"""Return a list of IronicNodes
:param request: request object
:type request: django.http.HttpRequest
:param associated: should we also retrieve all IronicNodes, only those
associated with an Instance, or only those not
associated with an Instance?
:type associated: bool
:return: list of IronicNodes, or an empty list if there are none
:rtype: list of tuskar_ui.api.node.IronicNode
"""
nodes = [node for node in TEST_DATA.ironicclient_nodes.list()
if not node.newly_discovered]
if associated is not None:
if associated:
nodes = [node for node in nodes
if node.instance_uuid is not None]
else:
nodes = [node for node in nodes
if node.instance_uuid is None]
return [cls(node) for node in nodes]
@classmethod
@handle_errors(_("Unable to retrieve newly discovered nodes"), [])
def list_discovered(cls, request):
"""Return a list of IronicNodes which have been newly discovered
"""
nodes = [node for node in TEST_DATA.ironicclient_nodes.list()
if node.newly_discovered]
return [cls(node) for node in nodes]
@classmethod
def delete(cls, request, uuid):
"""Remove the IronicNode matching the ID if it
exists; otherwise, does nothing.
:param request: request object
:type request: django.http.HttpRequest
:param uuid: ID of IronicNode to be removed
:type uuid: str
"""
return
@cached_property
def addresses(self):
"""Return a list of port addresses associated with this IronicNode
:return: list of port addresses associated with this IronicNode, or
an empty list if no addresses are associated with
this IronicNode
:rtype: list of str
"""
# we don't use an association in the node test data, because that
# association is unclear (to me); the REST API uses item links that
# are difficult to simulate. for mock purposes, no harm in just
# returning all ports
ports = TEST_DATA.ironicclient_ports.list()
return [port.address for port in ports]
@cached_property
def cpus(self):
return self.properties['cpus']
@cached_property
def memory_mb(self):
return self.properties['memory_mb']
@cached_property
def local_gb(self):
return self.properties['local_gb']
class BareMetalNode(base.APIResourceWrapper):
_attrs = ('id', 'uuid', 'instance_uuid', 'memory_mb', 'cpus', 'local_gb',
'task_state', 'pm_user', 'pm_address', 'interfaces')
@classmethod
def create(cls, request, ipmi_address, architecture, cpus, memory_mb,
local_gb, mac_addresses, ipmi_username=None, ipmi_password=None,
driver=None):
"""Create a Nova BareMetalNode
"""
node = TEST_DATA.baremetalclient_nodes.first()
return cls(node)
@classmethod
def get(cls, request, uuid):
"""Return the BareMetalNode that matches the ID
:param request: request object
:type request: django.http.HttpRequest
:param uuid: ID of BareMetalNode to be retrieved
:type uuid: str
:return: matching BareMetalNode, or None if no BareMetalNode matches
the ID
:rtype: tuskar_ui.api.node.BareMetalNode
"""
for node in BareMetalNode.list(request):
if node.uuid == uuid:
return node
@classmethod
def get_by_instance_uuid(cls, request, instance_uuid):
"""Return the BareMetalNode associated with the instance ID
:param request: request object
:type request: django.http.HttpRequest
:param instance_uuid: ID of Instance that is deployed on the
BareMetalNode to be retrieved
:type instance_uuid: str
:return: matching BareMetalNode
:rtype: tuskar_ui.api.node.BareMetalNode
:raises: ironicclient.exc.HTTPNotFound if there is no BareMetalNode
with the matching instance UUID
"""
for node in BareMetalNode.list(request):
if node.instance_uuid == instance_uuid:
return node
@classmethod
def list(cls, request, associated=None):
"""Return a list of BareMetalNodes
:param request: request object
:type request: django.http.HttpRequest
:param associated: should we also retrieve all BareMetalNodes, only
those associated with an Instance, or only those not
associated with an Instance?
:type associated: bool
:return: list of BareMetalNodes, or an empty list if there are none
:rtype: list of tuskar_ui.api.node.BareMetalNode
"""
nodes = TEST_DATA.baremetalclient_nodes.list()
if associated is not None:
if associated:
nodes = [node for node in nodes
if node.instance_uuid is not None]
else:
nodes = [node for node in nodes
if node.instance_uuid is None]
return [cls(node) for node in nodes]
@classmethod
def delete(cls, request, uuid):
"""Remove the BareMetalNode if it exists; otherwise, do nothing.
:param request: request object
:type request: django.http.HttpRequest
:param uuid: ID of BareMetalNode to be removed
:type uuid: str
"""
return
@cached_property
def power_state(self):
"""Return a power state of this BareMetalNode
:return: power state of this node
:rtype: str
"""
task_state_dict = {
'initializing': 'initializing',
'active': 'on',
'reboot': 'rebooting',
'building': 'building',
'deploying': 'deploying',
'prepared': 'prepared',
'deleting': 'deleting',
'deploy failed': 'deploy failed',
'deploy complete': 'deploy complete',
'deleted': 'deleted',
'error': 'error',
}
return task_state_dict.get(self.task_state, 'off')
@cached_property
def driver(self):
"""Return driver for this BareMetalNode
"""
return "IPMI + PXE"
@cached_property
def driver_info(self):
"""Return driver_info for this BareMetalNode
:return: return pm_address property of this BareMetalNode
:rtype: dict of str
"""
try:
ip_address = (self.instance._apiresource.addresses['ctlplane'][0]
['addr'])
except Exception:
LOG.error("Couldn't obtain IP address")
ip_address = None
return {
'ipmi_username': self.pm_user,
'ipmi_address': self.pm_address,
'ip_address': ip_address
}
@cached_property
def addresses(self):
"""Return a list of port addresses associated with this BareMetalNode
:return: list of port addresses associated with this BareMetalNode, or
an empty list if no addresses are associated with
this BareMetalNode
:rtype: list of str
"""
return [interface["address"] for interface in
self.interfaces]
class NodeClient(object):
def __init__(self, request):
if self.ironic_enabled(request):
self.node_class = IronicNode
else:
self.node_class = BareMetalNode
@classmethod
def ironic_enabled(cls, request):
return base.is_service_enabled(request, 'baremetal')
class Node(base.APIResourceWrapper):
_attrs = ('id', 'uuid', 'instance_uuid', 'driver', 'driver_info',
'power_state', 'addresses', 'maintenance', 'cpus',
'memory_mb', 'local_gb')
def __init__(self, apiresource, request=None, **kwargs):
"""Initialize a Node
:param apiresource: apiresource we want to wrap
:type apiresource: novaclient.v1_1.contrib.baremetal.BareMetalNode
:param request: request
:type request: django.core.handlers.wsgi.WSGIRequest
:param instance: instance relation we want to cache
:type instance: openstack_dashboard.api.nova.Server
:return: Node object
:rtype: tusar_ui.api.node.Node
"""
super(Node, self).__init__(apiresource)
self._request = request
if 'instance' in kwargs:
self._instance = kwargs['instance']
@classmethod
def create(cls, request, ipmi_address, architecture, cpus, memory_mb,
local_gb, mac_addresses, ipmi_username=None, ipmi_password=None,
driver=None):
return cls(NodeClient(request).node_class.create(
request, ipmi_address, architecture, cpus, memory_mb, local_gb,
mac_addresses, ipmi_username=ipmi_username,
ipmi_password=ipmi_password, driver=driver))
@classmethod
@handle_errors(_("Unable to retrieve node"))
def get(cls, request, uuid):
node = NodeClient(request).node_class.get(request, uuid)
if node.instance_uuid is not None:
for server in TEST_DATA.novaclient_servers.list():
if server.id == node.instance_uuid:
break
else:
server = None
return cls(node, instance=server, request=request)
return cls(node)
@classmethod
@handle_errors(_("Unable to retrieve node"))
def get_by_instance_uuid(cls, request, instance_uuid):
node = NodeClient(request).node_class.get_by_instance_uuid(
request, instance_uuid)
for server in TEST_DATA.novaclient_servers.list():
if server.id == node.instance_uuid:
break
else:
server = None
return cls(node, instance=server, request=request)
@classmethod
@handle_errors(_("Unable to retrieve nodes"), [])
def list(cls, request, associated=None):
nodes = NodeClient(request).node_class.list(
request, associated=associated)
if associated is None or associated:
servers = TEST_DATA.novaclient_servers.list()
servers_dict = utils.list_to_dict(servers)
nodes_with_instance = []
for n in nodes:
server = servers_dict.get(n.instance_uuid, None)
nodes_with_instance.append(cls(n, instance=server,
request=request))
return nodes_with_instance
else:
return [cls(node, request=request) for node in nodes]
@classmethod
@handle_errors(_("Unable to retrieve discovered nodes"), [])
def list_discovered(cls, request):
nodes = NodeClient(request).node_class.list_discovered(request)
return [cls(node, request=request) for node in nodes]
@classmethod
def delete(cls, request, uuid):
NodeClient(request).node_class.delete(request, uuid)
@cached_property
def instance(self):
"""Return the Nova Instance associated with this Node
:return: Nova Instance associated with this Node; or
None if there is no Instance associated with this
Node, or no matching Instance is found
:rtype: Instance
"""
if hasattr(self, '_instance'):
return self._instance
if self.instance_uuid:
for server in TEST_DATA.novaclient_servers.list():
if server.id == self.instance_uuid:
return server
@cached_property
def image_name(self):
"""Return image name of associated instance
Returns image name of instance associated with node
:return: Image name of instance
:rtype: string
"""
if self.instance is None:
return
for image in TEST_DATA.glanceclient_images.list():
if image.id == self.instance.image['id']:
return image.name
@cached_property
def instance_status(self):
return getattr(getattr(self, 'instance', None),
'status', None)
@cached_property
def provisioning_status(self):
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
"""
error_states = ('deploy failed', 'error',)
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 == 'on']
else:
nodes = [node for node in nodes if node.power_state != 'on']
return nodes