
Ilo drivers sets capabilities:boot_mode in node properties if there is none. For a node capable of booting in both bios and uefi, setting of this property by driver prevents it from getting selected for future deploys requests for other boot mode. It would be selected only for deploy requests where flavor extra_specs specifies capabilities:boot_mode with a value matching with one set by driver in the node properties. Closes-Bug: 1418327 Change-Id: I90571487840c6a74699a8fbfbac8035c3efb5672
278 lines
9.2 KiB
Python
278 lines
9.2 KiB
Python
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# 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 ironic.common import exception
|
|
from ironic.common.i18n import _
|
|
from ironic.common.i18n import _LW
|
|
from ironic.drivers import base
|
|
from ironic.openstack.common import log as logging
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class MixinVendorInterface(base.VendorInterface):
|
|
"""Wrapper around multiple VendorInterfaces."""
|
|
|
|
def __init__(self, mapping, driver_passthru_mapping=None):
|
|
"""Wrapper around multiple VendorInterfaces.
|
|
|
|
:param mapping: dict of {'method': interface} specifying how to combine
|
|
multiple vendor interfaces into one vendor driver.
|
|
:param driver_passthru_mapping: dict of {'method': interface}
|
|
specifying how to map
|
|
driver_vendor_passthru calls to
|
|
interfaces.
|
|
|
|
"""
|
|
self.mapping = mapping
|
|
self.driver_level_mapping = driver_passthru_mapping or {}
|
|
self.vendor_routes = self._build_routes(self.mapping)
|
|
self.driver_routes = self._build_routes(self.driver_level_mapping,
|
|
driver_passthru=True)
|
|
|
|
def _build_routes(self, map_dict, driver_passthru=False):
|
|
"""Build the mapping for the vendor calls.
|
|
|
|
Build the mapping between the given methods and the corresponding
|
|
method metadata.
|
|
|
|
:param map_dict: dict of {'method': interface} specifying how
|
|
to map multiple vendor calls to interfaces.
|
|
:param driver_passthru: Boolean value. Whether build the mapping
|
|
to the node vendor passthru or driver
|
|
vendor passthru.
|
|
"""
|
|
d = {}
|
|
for method_name in map_dict:
|
|
iface = map_dict[method_name]
|
|
if driver_passthru:
|
|
driver_methods = iface.driver_routes
|
|
else:
|
|
driver_methods = iface.vendor_routes
|
|
|
|
try:
|
|
d.update({method_name: driver_methods[method_name]})
|
|
except KeyError:
|
|
pass
|
|
return d
|
|
|
|
def _get_route(self, method):
|
|
"""Return the driver interface which contains the given method.
|
|
|
|
:param method: The name of the vendor method.
|
|
"""
|
|
if not method:
|
|
raise exception.MissingParameterValue(
|
|
_("Method not specified when calling vendor extension."))
|
|
|
|
try:
|
|
route = self.mapping[method]
|
|
except KeyError:
|
|
raise exception.InvalidParameterValue(
|
|
_('No handler for method %s') % method)
|
|
|
|
return route
|
|
|
|
def get_properties(self):
|
|
"""Return the properties from all the VendorInterfaces.
|
|
|
|
:returns: a dictionary of <property_name>:<property_description>
|
|
entries.
|
|
"""
|
|
properties = {}
|
|
interfaces = set(self.mapping.values())
|
|
for interface in interfaces:
|
|
properties.update(interface.get_properties())
|
|
return properties
|
|
|
|
def validate(self, task, method, **kwargs):
|
|
"""Call validate on the appropriate interface only.
|
|
|
|
:raises: UnsupportedDriverExtension if 'method' can not be mapped to
|
|
the supported interfaces.
|
|
:raises: InvalidParameterValue if 'method' is invalid.
|
|
:raisee: MissingParameterValue if missing 'method' or parameters
|
|
in kwargs.
|
|
|
|
"""
|
|
route = self._get_route(method)
|
|
route.validate(task, method=method, **kwargs)
|
|
|
|
|
|
def get_node_mac_addresses(task):
|
|
"""Get all MAC addresses for the ports belonging to this task's node.
|
|
|
|
:param task: a TaskManager instance containing the node to act on.
|
|
:returns: A list of MAC addresses in the format xx:xx:xx:xx:xx:xx.
|
|
"""
|
|
return [p.address for p in task.ports]
|
|
|
|
|
|
def get_node_capability(node, capability):
|
|
"""Returns 'capability' value from node's 'capabilities' property.
|
|
|
|
:param node: Node object.
|
|
:param capability: Capability key.
|
|
:return: Capability value.
|
|
If capability is not present, then return "None"
|
|
|
|
"""
|
|
capabilities = node.properties.get('capabilities')
|
|
|
|
if not capabilities:
|
|
return
|
|
|
|
for node_capability in capabilities.split(','):
|
|
parts = node_capability.split(':')
|
|
if len(parts) == 2 and parts[0] and parts[1]:
|
|
if parts[0] == capability:
|
|
return parts[1]
|
|
else:
|
|
LOG.warn(_LW("Ignoring malformed capability '%s'. "
|
|
"Format should be 'key:val'."), node_capability)
|
|
|
|
|
|
def rm_node_capability(task, capability):
|
|
"""Remove 'capability' from node's 'capabilities' property.
|
|
|
|
:param task: Task object.
|
|
:param capability: Capability key.
|
|
|
|
"""
|
|
node = task.node
|
|
properties = node.properties
|
|
capabilities = properties.get('capabilities')
|
|
|
|
if not capabilities:
|
|
return
|
|
|
|
caps = []
|
|
for cap in capabilities.split(','):
|
|
parts = cap.split(':')
|
|
if len(parts) == 2 and parts[0] and parts[1]:
|
|
if parts[0] == capability:
|
|
continue
|
|
caps.append(cap)
|
|
new_cap_str = ",".join(caps)
|
|
properties['capabilities'] = new_cap_str if new_cap_str else None
|
|
node.properties = properties
|
|
node.save()
|
|
|
|
|
|
def add_node_capability(task, capability, value):
|
|
"""Add 'capability' to node's 'capabilities' property.
|
|
|
|
If 'capability' is already present, then a duplicate entry
|
|
will be added.
|
|
|
|
:param task: Task object.
|
|
:param capability: Capability key.
|
|
:param value: Capability value.
|
|
|
|
"""
|
|
node = task.node
|
|
properties = node.properties
|
|
capabilities = properties.get('capabilities')
|
|
|
|
new_cap = ':'.join([capability, value])
|
|
|
|
if capabilities:
|
|
capabilities = ','.join([capabilities, new_cap])
|
|
else:
|
|
capabilities = new_cap
|
|
|
|
properties['capabilities'] = capabilities
|
|
node.properties = properties
|
|
node.save()
|
|
|
|
|
|
def validate_capability(node, capability_name, valid_values):
|
|
"""Validate a capabability set in node property
|
|
|
|
:param node: an ironic node object.
|
|
:param capability_name: the name of the capability.
|
|
:parameter valid_values: an iterable with valid values expected for
|
|
that capability.
|
|
:raises: InvalidParameterValue, if the capability is not set to the
|
|
expected values.
|
|
"""
|
|
value = get_node_capability(node, capability_name)
|
|
|
|
if value and value not in valid_values:
|
|
valid_value_str = ', '.join(valid_values)
|
|
raise exception.InvalidParameterValue(
|
|
_("Invalid %(capability)s parameter '%(value)s'. "
|
|
"Acceptable values are: %(valid_values)s.") %
|
|
{'capability': capability_name, 'value': value,
|
|
'valid_values': valid_value_str})
|
|
|
|
|
|
def validate_boot_mode_capability(node):
|
|
"""Validate the boot_mode capability set in node properties.
|
|
|
|
:param node: an ironic node object.
|
|
:raises: InvalidParameterValue, if 'boot_mode' capability is set
|
|
other than 'bios' or 'uefi' or None.
|
|
|
|
"""
|
|
validate_capability(node, 'boot_mode', ('bios', 'uefi'))
|
|
|
|
|
|
def validate_boot_option_capability(node):
|
|
"""Validate the boot_option capability set in node properties.
|
|
|
|
:param node: an ironic node object.
|
|
:raises: InvalidParameterValue, if 'boot_option' capability is set
|
|
other than 'local' or 'netboot' or None.
|
|
|
|
"""
|
|
validate_capability(node, 'boot_option', ('local', 'netboot'))
|
|
|
|
|
|
def validate_secure_boot_capability(node):
|
|
"""Validate the secure_boot capability set in node property.
|
|
|
|
:param node: an ironic node object.
|
|
:raises: InvalidParameterValue, if 'secure_boot' capability is set
|
|
other than 'true' or 'false' or None.
|
|
|
|
"""
|
|
validate_capability(node, 'secure_boot', ('true', 'false'))
|
|
|
|
|
|
def get_boot_mode_for_deploy(node):
|
|
"""Returns the boot mode that would be used for deploy.
|
|
|
|
This method returns deploy_boot_mode available in node field
|
|
boot_mode from 'capabilities' of node 'properties'.
|
|
Otherwise returns boot mode specified in node's 'instance_info'.
|
|
|
|
:param node: an ironic node object.
|
|
:returns: Value of boot mode that would be used for deploy.
|
|
Possible values are 'bios', 'uefi'.
|
|
It would return None if boot mode is present neither
|
|
in 'capabilities' of node 'properties' nor in node's
|
|
'instance_info'.
|
|
|
|
"""
|
|
boot_mode = get_node_capability(node, 'boot_mode')
|
|
if boot_mode is None:
|
|
instance_info = node.instance_info
|
|
boot_mode = instance_info.get('deploy_boot_mode', None)
|
|
|
|
LOG.debug('Deploy boot mode is %(boot_mode)s for %(node)s.',
|
|
{'boot_mode': boot_mode, 'node': node.uuid})
|
|
return boot_mode
|