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
|