"Fossies" - the Fresh Open Source Software Archive

Member "ironic-16.0.3/ironic/drivers/base.py" (18 Jan 2021, 70619 Bytes) of package /linux/misc/openstack/ironic-16.0.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "base.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 16.0.2_vs_16.0.3.

    1 # -*- encoding: utf-8 -*-
    2 #
    3 # Copyright 2013 Hewlett-Packard Development Company, L.P.
    4 #
    5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
    6 # not use this file except in compliance with the License. You may obtain
    7 # a copy of the License at
    8 #
    9 #      http://www.apache.org/licenses/LICENSE-2.0
   10 #
   11 # Unless required by applicable law or agreed to in writing, software
   12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   14 # License for the specific language governing permissions and limitations
   15 # under the License.
   16 """
   17 Abstract base classes for drivers.
   18 """
   19 
   20 import abc
   21 import collections
   22 import copy
   23 import functools
   24 import inspect
   25 import json
   26 import os
   27 
   28 from oslo_log import log as logging
   29 from oslo_utils import excutils
   30 
   31 from ironic.common import exception
   32 from ironic.common.i18n import _
   33 from ironic.common import raid
   34 from ironic.common import states
   35 
   36 LOG = logging.getLogger(__name__)
   37 
   38 RAID_CONFIG_SCHEMA = os.path.join(os.path.dirname(__file__),
   39                                   'raid_config_schema.json')
   40 
   41 RAID_APPLY_CONFIGURATION_ARGSINFO = {
   42     "raid_config": {
   43         "description": "The RAID configuration to apply.",
   44         "required": True,
   45     },
   46     "create_root_volume": {
   47         "description": (
   48             "Setting this to 'False' indicates not to create root "
   49             "volume that is specified in 'raid_config'. Default "
   50             "value is 'True'."
   51         ),
   52         "required": False,
   53     },
   54     "create_nonroot_volumes": {
   55         "description": (
   56             "Setting this to 'False' indicates not to create "
   57             "non-root volumes (all except the root volume) in "
   58             "'raid_config'. Default value is 'True'."
   59         ),
   60         "required": False,
   61     },
   62     "delete_existing": {
   63         "description": (
   64             "Setting this to 'True' indicates to delete existing RAID "
   65             "configuration prior to creating the new configuration. "
   66             "Default value is 'True'."
   67         ),
   68         "required": False,
   69     }
   70 }
   71 """
   72 This may be used as the deploy_step argsinfo argument for RAID interfaces
   73 implementing an apply_configuration deploy step.
   74 """
   75 
   76 
   77 class BareDriver(object):
   78     """A bare driver object which will have interfaces attached later.
   79 
   80     Any composable interfaces should be added as class attributes of this
   81     class, as well as appended to core_interfaces or standard_interfaces here.
   82     """
   83 
   84     bios = None
   85     """`Standard` attribute for BIOS related features.
   86 
   87     A reference to an instance of :class:BIOSInterface.
   88     """
   89 
   90     boot = None
   91     """`Standard` attribute for boot related features.
   92 
   93     A reference to an instance of :class:BootInterface.
   94     """
   95 
   96     console = None
   97     """`Standard` attribute for managing console access.
   98 
   99     A reference to an instance of :class:ConsoleInterface.
  100     """
  101 
  102     deploy = None
  103     """`Core` attribute for managing deployments.
  104 
  105     A reference to an instance of :class:DeployInterface.
  106     """
  107 
  108     inspect = None
  109     """`Standard` attribute for inspection related features.
  110 
  111     A reference to an instance of :class:InspectInterface.
  112     """
  113 
  114     management = None
  115     """`Standard` attribute for management related features.
  116 
  117     A reference to an instance of :class:ManagementInterface.
  118     """
  119 
  120     network = None
  121     """`Core` attribute for network connectivity.
  122 
  123     A reference to an instance of :class:NetworkInterface.
  124     """
  125 
  126     power = None
  127     """`Core` attribute for managing power state.
  128 
  129     A reference to an instance of :class:PowerInterface.
  130     """
  131 
  132     raid = None
  133     """`Standard` attribute for RAID related features.
  134 
  135     A reference to an instance of :class:RaidInterface.
  136     """
  137 
  138     rescue = None
  139     """`Standard` attribute for accessing rescue features.
  140 
  141     A reference to an instance of :class:RescueInterface.
  142     """
  143 
  144     storage = None
  145     """`Standard` attribute for (remote) storage interface.
  146 
  147     A reference to an instance of :class:StorageInterface.
  148     """
  149 
  150     vendor = None
  151     """Attribute for accessing any vendor-specific extensions.
  152 
  153     A reference to an instance of :class:VendorInterface.
  154     """
  155 
  156     @property
  157     def core_interfaces(self):
  158         """Interfaces that are required to be implemented."""
  159         return ['boot', 'deploy', 'management', 'network', 'power']
  160 
  161     @property
  162     def optional_interfaces(self):
  163         """Interfaces that can be no-op."""
  164         return ['bios', 'console', 'inspect', 'raid', 'rescue', 'storage']
  165 
  166     @property
  167     def all_interfaces(self):
  168         return self.non_vendor_interfaces + ['vendor']
  169 
  170     @property
  171     def non_vendor_interfaces(self):
  172         return list(self.core_interfaces + self.optional_interfaces)
  173 
  174     def get_properties(self):
  175         """Get the properties of the driver.
  176 
  177         :returns: dictionary of <property name>:<property description> entries.
  178         """
  179 
  180         properties = {}
  181         for iface_name in self.all_interfaces:
  182             iface = getattr(self, iface_name, None)
  183             if iface:
  184                 properties.update(iface.get_properties())
  185         return properties
  186 
  187 
  188 ALL_INTERFACES = set(BareDriver().all_interfaces)
  189 """Constant holding all known interfaces."""
  190 
  191 
  192 class BaseInterface(object, metaclass=abc.ABCMeta):
  193     """A base interface implementing common functions for Driver Interfaces."""
  194 
  195     supported = True
  196     """Indicates if an interface is supported.
  197 
  198     This will be set to False for interfaces which are untested in first- or
  199     third-party CI, or in the process of being deprecated.
  200     """
  201 
  202     interface_type = 'base'
  203     """Interface type, used for clean steps and logging."""
  204 
  205     @abc.abstractmethod
  206     def get_properties(self):
  207         """Return the properties of the interface.
  208 
  209         :returns: dictionary of <property name>:<property description> entries.
  210         """
  211 
  212     @abc.abstractmethod
  213     def validate(self, task):
  214         """Validate the driver-specific Node deployment info.
  215 
  216         This method validates whether the 'driver_info' and/or 'instance_info'
  217         properties of the task's node contains the required information for
  218         this interface to function.
  219 
  220         This method is often executed synchronously in API requests, so it
  221         should not conduct long-running checks.
  222 
  223         :param task: A TaskManager instance containing the node to act on.
  224         :raises: InvalidParameterValue on malformed parameter(s)
  225         :raises: MissingParameterValue on missing parameter(s)
  226         """
  227 
  228     def __new__(cls, *args, **kwargs):
  229         # Get the list of clean steps and deploy steps, when the interface is
  230         # initialized by the conductor. We use __new__ instead of __init___
  231         # to avoid breaking backwards compatibility with all the drivers.
  232         # We want to return all steps, regardless of priority.
  233 
  234         super_new = super(BaseInterface, cls).__new__
  235         if super_new is object.__new__:
  236             instance = super_new(cls)
  237         else:
  238             instance = super_new(cls, *args, **kwargs)
  239         instance.clean_steps = []
  240         instance.deploy_steps = []
  241         for n, method in inspect.getmembers(instance, inspect.ismethod):
  242             if getattr(method, '_is_clean_step', False):
  243                 # Create a CleanStep to represent this method
  244                 step = {'step': method.__name__,
  245                         'priority': method._clean_step_priority,
  246                         'abortable': method._clean_step_abortable,
  247                         'argsinfo': method._clean_step_argsinfo,
  248                         'interface': instance.interface_type}
  249                 instance.clean_steps.append(step)
  250             if getattr(method, '_is_deploy_step', False):
  251                 # Create a DeployStep to represent this method
  252                 step = {'step': method.__name__,
  253                         'priority': method._deploy_step_priority,
  254                         'argsinfo': method._deploy_step_argsinfo,
  255                         'interface': instance.interface_type}
  256                 instance.deploy_steps.append(step)
  257         if instance.clean_steps:
  258             LOG.debug('Found clean steps %(steps)s for interface '
  259                       '%(interface)s',
  260                       {'steps': instance.clean_steps,
  261                        'interface': instance.interface_type})
  262         if instance.deploy_steps:
  263             LOG.debug('Found deploy steps %(steps)s for interface '
  264                       '%(interface)s',
  265                       {'steps': instance.deploy_steps,
  266                        'interface': instance.interface_type})
  267         return instance
  268 
  269     def _execute_step(self, task, step):
  270         """Execute the step on task.node.
  271 
  272         A step must take a single positional argument: a TaskManager
  273         object. It may take one or more keyword variable arguments.
  274 
  275         :param task: A TaskManager object
  276         :param step: The step dictionary representing the step to execute
  277         """
  278         args = step.get('args')
  279         if args is not None:
  280             return getattr(self, step['step'])(task, **args)
  281         else:
  282             return getattr(self, step['step'])(task)
  283 
  284     def get_clean_steps(self, task):
  285         """Get a list of (enabled and disabled) clean steps for the interface.
  286 
  287         This function will return all clean steps (both enabled and disabled)
  288         for the interface, in an unordered list.
  289 
  290         :param task: A TaskManager object, useful for interfaces overriding
  291             this function
  292         :raises NodeCleaningFailure: if there is a problem getting the steps
  293             from the driver. For example, when a node (using an agent driver)
  294             has just been enrolled and the agent isn't alive yet to be queried
  295             for the available clean steps.
  296         :returns: A list of clean step dictionaries
  297         """
  298         return self.clean_steps
  299 
  300     def execute_clean_step(self, task, step):
  301         """Execute the clean step on task.node.
  302 
  303         A clean step must take a single positional argument: a TaskManager
  304         object. It may take one or more keyword variable arguments (for
  305         use with manual cleaning only.)
  306 
  307         A step can be executed synchronously or asynchronously. A step should
  308         return None if the method has completed synchronously or
  309         states.CLEANWAIT if the step will continue to execute asynchronously.
  310         If the step executes asynchronously, it should issue a call to the
  311         'continue_node_clean' RPC, so the conductor can begin the next
  312         clean step.
  313 
  314         :param task: A TaskManager object
  315         :param step: The clean step dictionary representing the step to execute
  316         :returns: None if this method has completed synchronously, or
  317             states.CLEANWAIT if the step will continue to execute
  318             asynchronously.
  319         """
  320         return self._execute_step(task, step)
  321 
  322     def get_deploy_steps(self, task):
  323         """Get a list of (enabled and disabled) deploy steps for the interface.
  324 
  325         This function will return all deploy steps (both enabled and disabled)
  326         for the interface, in an unordered list.
  327 
  328         :param task: A TaskManager object, useful for interfaces overriding
  329             this function
  330         :raises InstanceDeployFailure: if there is a problem getting the steps
  331             from the driver. For example, when a node (using an agent driver)
  332             has just been enrolled and the agent isn't alive yet to be queried
  333             for the available deploy steps.
  334         :returns: A list of deploy step dictionaries
  335         """
  336         return self.deploy_steps
  337 
  338     def execute_deploy_step(self, task, step):
  339         """Execute the deploy step on task.node.
  340 
  341         A deploy step must take a single positional argument: a TaskManager
  342         object. It may take one or more keyword variable arguments (for
  343         use in the future, when deploy steps can be specified via the API).
  344 
  345         A step can be executed synchronously or asynchronously. A step should
  346         return None if the method has completed synchronously or
  347         states.DEPLOYWAIT if the step will continue to execute asynchronously.
  348         If the step executes asynchronously, it should issue a call to the
  349         'continue_node_deploy' RPC, so the conductor can begin the next
  350         deploy step.
  351 
  352         :param task: A TaskManager object
  353         :param step: The deploy step dictionary representing the step to
  354             execute
  355         :returns: None if this method has completed synchronously, or
  356             states.DEPLOYWAIT if the step will continue to execute
  357             asynchronously.
  358         """
  359         return self._execute_step(task, step)
  360 
  361 
  362 class DeployInterface(BaseInterface):
  363     """Interface for deploy-related actions."""
  364     interface_type = 'deploy'
  365 
  366     @abc.abstractmethod
  367     def deploy(self, task):
  368         """Perform a deployment to the task's node.
  369 
  370         Perform the necessary work to deploy an image onto the specified node.
  371         This method will be called after prepare(), which may have already
  372         performed any preparatory steps, such as pre-caching some data for the
  373         node.
  374 
  375         :param task: A TaskManager instance containing the node to act on.
  376         :returns: status of the deploy. One of ironic.common.states.
  377         """
  378 
  379     @abc.abstractmethod
  380     def tear_down(self, task):
  381         """Tear down a previous deployment on the task's node.
  382 
  383         Given a node that has been previously deployed to,
  384         do all cleanup and tear down necessary to "un-deploy" that node.
  385 
  386         :param task: A TaskManager instance containing the node to act on.
  387         :returns: status of the deploy. One of ironic.common.states.
  388         """
  389 
  390     @abc.abstractmethod
  391     def prepare(self, task):
  392         """Prepare the deployment environment for the task's node.
  393 
  394         If preparation of the deployment environment ahead of time is possible,
  395         this method should be implemented by the driver.
  396 
  397         If implemented, this method must be idempotent. It may be called
  398         multiple times for the same node on the same conductor.
  399 
  400         This method is called before `deploy`.
  401 
  402         :param task: A TaskManager instance containing the node to act on.
  403         """
  404 
  405     @abc.abstractmethod
  406     def clean_up(self, task):
  407         """Clean up the deployment environment for the task's node.
  408 
  409         If preparation of the deployment environment ahead of time is possible,
  410         this method should be implemented by the driver. It should erase
  411         anything cached by the `prepare` method.
  412 
  413         If implemented, this method must be idempotent. It may be called
  414         multiple times for the same node on the same conductor, and it may be
  415         called by multiple conductors in parallel. Therefore, it must not
  416         require an exclusive lock.
  417 
  418         This method is called before `tear_down`.
  419 
  420         :param task: A TaskManager instance containing the node to act on.
  421         """
  422 
  423     @abc.abstractmethod
  424     def take_over(self, task):
  425         """Take over management of this task's node from a dead conductor.
  426 
  427         If conductors' hosts maintain a static relationship to nodes, this
  428         method should be implemented by the driver to allow conductors to
  429         perform the necessary work during the remapping of nodes to conductors
  430         when a conductor joins or leaves the cluster.
  431 
  432         For example, the PXE driver has an external dependency:
  433             Neutron must forward DHCP BOOT requests to a conductor which has
  434             prepared the tftpboot environment for the given node. When a
  435             conductor goes offline, another conductor must change this setting
  436             in Neutron as part of remapping that node's control to itself.
  437             This is performed within the `takeover` method.
  438 
  439         :param task: A TaskManager instance containing the node to act on.
  440         """
  441 
  442     def prepare_cleaning(self, task):
  443         """Prepare the node for cleaning tasks.
  444 
  445         For example, nodes that use the Ironic Python Agent will need to
  446         boot the ramdisk in order to do in-band cleaning tasks.
  447 
  448         If the function is asynchronous, the driver will need to handle
  449         settings node.driver_internal_info['clean_steps'] and node.clean_step,
  450         as they would be set in ironic.conductor.manager._do_node_clean,
  451         but cannot be set when this is asynchronous. After, the interface
  452         should make an RPC call to continue_node_cleaning to start cleaning.
  453 
  454         NOTE(JoshNang) this should be moved to BootInterface when it gets
  455         implemented.
  456 
  457         :param task: A TaskManager instance containing the node to act on.
  458         :returns: If this function is going to be asynchronous, should return
  459             `states.CLEANWAIT`. Otherwise, should return `None`. The interface
  460             will need to call _get_cleaning_steps and then RPC to
  461             continue_node_cleaning
  462         """
  463         pass
  464 
  465     def tear_down_cleaning(self, task):
  466         """Tear down after cleaning is completed.
  467 
  468         Given that cleaning is complete, do all cleanup and tear
  469         down necessary to allow the node to be deployed to again.
  470 
  471         NOTE(JoshNang) this should be moved to BootInterface when it gets
  472         implemented.
  473 
  474         :param task: A TaskManager instance containing the node to act on.
  475         """
  476         pass
  477 
  478     def heartbeat(self, task, callback_url, agent_version,
  479                   agent_verify_ca=None):
  480         """Record a heartbeat for the node.
  481 
  482         :param task: A TaskManager instance containing the node to act on.
  483         :param callback_url: a URL to use to call to the ramdisk.
  484         :param agent_version: The version of the agent that is heartbeating
  485         :param agent_verify_ca: TLS certificate for the agent.
  486         :return: None
  487         """
  488         LOG.warning('Got heartbeat message from node %(node)s, but '
  489                     'the driver %(driver)s does not support heartbeating',
  490                     {'node': task.node.uuid, 'driver': task.node.driver})
  491 
  492 
  493 class BootInterface(BaseInterface):
  494     """Interface for boot-related actions."""
  495     interface_type = 'boot'
  496     capabilities = []
  497 
  498     @abc.abstractmethod
  499     def prepare_ramdisk(self, task, ramdisk_params):
  500         """Prepares the boot of Ironic ramdisk.
  501 
  502         This method prepares the boot of the deploy or rescue ramdisk after
  503         reading relevant information from the node's database.
  504 
  505         :param task: A task from TaskManager.
  506         :param ramdisk_params: The options to be passed to the ironic ramdisk.
  507             Different implementations might want to boot the ramdisk in
  508             different ways by passing parameters to them.  For example,
  509 
  510             When Agent ramdisk is booted to deploy a node, it takes the
  511             parameters ipa-api-url, etc.
  512 
  513             Other implementations can make use of ramdisk_params to pass such
  514             information.  Different implementations of boot interface will
  515             have different ways of passing parameters to the ramdisk.
  516         :returns: None
  517         """
  518 
  519     @abc.abstractmethod
  520     def clean_up_ramdisk(self, task):
  521         """Cleans up the boot of ironic ramdisk.
  522 
  523         This method cleans up the environment that was setup for booting the
  524         deploy or rescue ramdisk.
  525 
  526         :param task: A task from TaskManager.
  527         :returns: None
  528         """
  529 
  530     @abc.abstractmethod
  531     def prepare_instance(self, task):
  532         """Prepares the boot of instance.
  533 
  534         This method prepares the boot of the instance after reading
  535         relevant information from the node's database.
  536 
  537         :param task: A task from TaskManager.
  538         :returns: None
  539         """
  540 
  541     @abc.abstractmethod
  542     def clean_up_instance(self, task):
  543         """Cleans up the boot of instance.
  544 
  545         This method cleans up the environment that was setup for booting
  546         the instance.
  547 
  548         :param task: A task from TaskManager.
  549         :returns: None
  550         """
  551 
  552     def validate_rescue(self, task):
  553         """Validate that the node has required properties for rescue.
  554 
  555         :param task: A TaskManager instance with the node being checked
  556         :raises: MissingParameterValue if node is missing one or more required
  557             parameters
  558         :raises: UnsupportedDriverExtension
  559         """
  560         raise exception.UnsupportedDriverExtension(
  561             driver=task.node.driver, extension='validate_rescue')
  562 
  563     def validate_inspection(self, task):
  564         """Validate that the node has required properties for inspection.
  565 
  566         :param task: A TaskManager instance with the node being checked
  567         :raises: MissingParameterValue if node is missing one or more required
  568             parameters
  569         :raises: UnsupportedDriverExtension
  570         """
  571         raise exception.UnsupportedDriverExtension(
  572             driver=task.node.driver, extension='validate_inspection')
  573 
  574 
  575 class PowerInterface(BaseInterface):
  576     """Interface for power-related actions."""
  577     interface_type = 'power'
  578 
  579     @abc.abstractmethod
  580     def get_power_state(self, task):
  581         """Return the power state of the task's node.
  582 
  583         :param task: A TaskManager instance containing the node to act on.
  584         :raises: MissingParameterValue if a required parameter is missing.
  585         :returns: A power state. One of :mod:`ironic.common.states`.
  586         """
  587 
  588     @abc.abstractmethod
  589     def set_power_state(self, task, power_state, timeout=None):
  590         """Set the power state of the task's node.
  591 
  592         :param task: A TaskManager instance containing the node to act on.
  593         :param power_state: Any power state from :mod:`ironic.common.states`.
  594         :param timeout: timeout (in seconds) positive integer (> 0) for any
  595           power state. ``None`` indicates to use default timeout.
  596         :raises: MissingParameterValue if a required parameter is missing.
  597         """
  598 
  599     @abc.abstractmethod
  600     def reboot(self, task, timeout=None):
  601         """Perform a hard reboot of the task's node.
  602 
  603         Drivers are expected to properly handle case when node is powered off
  604         by powering it on.
  605 
  606         :param task: A TaskManager instance containing the node to act on.
  607         :param timeout: timeout (in seconds) positive integer (> 0) for any
  608           power state. ``None`` indicates to use default timeout.
  609         :raises: MissingParameterValue if a required parameter is missing.
  610         """
  611 
  612     def get_supported_power_states(self, task):
  613         """Get a list of the supported power states.
  614 
  615         :param task: A TaskManager instance containing the node to act on.
  616         :returns: A list with the supported power states defined
  617                   in :mod:`ironic.common.states`.
  618         """
  619         return [states.POWER_ON, states.POWER_OFF, states.REBOOT]
  620 
  621     def supports_power_sync(self, task):
  622         """Check if power sync is supported for the given node.
  623 
  624         If ``False``, the conductor will simply store whatever
  625         ``get_power_state`` returns in the database instead of trying
  626         to force the expected power state.
  627 
  628         :param task: A TaskManager instance containing the node to act on.
  629         :returns: boolean, whether power sync is supported.
  630         """
  631         return True
  632 
  633 
  634 class ConsoleInterface(BaseInterface):
  635     """Interface for console-related actions."""
  636     interface_type = "console"
  637 
  638     @abc.abstractmethod
  639     def start_console(self, task):
  640         """Start a remote console for the task's node.
  641 
  642         This method should not raise an exception if console already started.
  643 
  644         :param task: A TaskManager instance containing the node to act on.
  645         """
  646 
  647     @abc.abstractmethod
  648     def stop_console(self, task):
  649         """Stop the remote console session for the task's node.
  650 
  651         :param task: A TaskManager instance containing the node to act on.
  652         """
  653 
  654     @abc.abstractmethod
  655     def get_console(self, task):
  656         """Get connection information about the console.
  657 
  658         This method should return the necessary information for the
  659         client to access the console.
  660 
  661         :param task: A TaskManager instance containing the node to act on.
  662         :returns: the console connection information.
  663         """
  664 
  665 
  666 class RescueInterface(BaseInterface):
  667     """Interface for rescue-related actions."""
  668     interface_type = "rescue"
  669 
  670     @abc.abstractmethod
  671     def rescue(self, task):
  672         """Boot the task's node into a rescue environment.
  673 
  674         :param task: A TaskManager instance containing the node to act on.
  675         :raises: InstanceRescueFailure if node validation or rescue operation
  676                  fails.
  677         :returns: states.RESCUEWAIT if rescue is in progress asynchronously
  678                   or states.RESCUE if it is complete.
  679         """
  680 
  681     @abc.abstractmethod
  682     def unrescue(self, task):
  683         """Tear down the rescue environment, and return to normal.
  684 
  685         :param task: A TaskManager instance containing the node to act on.
  686         :raises: InstanceUnrescueFailure if node validation or unrescue
  687                  operation fails.
  688         :returns: states.ACTIVE if it is successful.
  689         """
  690 
  691     def clean_up(self, task):
  692         """Clean up the rescue environment for the task's node.
  693 
  694         This is particularly useful for nodes where rescuing is asynchronous
  695         and a timeout occurs.
  696 
  697         :param task: A TaskManager instance containing the node to act on.
  698         :returns: None
  699         """
  700         pass
  701 
  702 
  703 # Representation of a single vendor method metadata
  704 VendorMetadata = collections.namedtuple('VendorMetadata', ['method',
  705                                                            'metadata'])
  706 
  707 
  708 def _passthru(http_methods, method=None, async_call=True,
  709               driver_passthru=False, description=None,
  710               attach=False, require_exclusive_lock=True):
  711     """A decorator for registering a function as a passthru function.
  712 
  713     Decorator ensures function is ready to catch any ironic exceptions
  714     and reraise them after logging the issue. It also catches non-ironic
  715     exceptions reraising them as a VendorPassthruException after writing
  716     a log.
  717 
  718     Logs need to be added because even though the exception is being
  719     reraised, it won't be handled if it is an async. call.
  720 
  721     :param http_methods: A list of supported HTTP methods by the vendor
  722                          function.
  723     :param method: an arbitrary string describing the action to be taken.
  724     :param async_call: Boolean value. If True invoke the passthru function
  725                   asynchronously; if False, synchronously. If a passthru
  726                   function touches the BMC we strongly recommend it to
  727                   run asynchronously. Defaults to True.
  728     :param driver_passthru: Boolean value. True if this is a driver vendor
  729                             passthru method, and False if it is a node
  730                             vendor passthru method.
  731     :param attach: Boolean value. True if the return value should be
  732                    attached to the response object, and False if the return
  733                    value should be returned in the response body.
  734                    Defaults to False.
  735     :param description: a string shortly describing what the method does.
  736     :param require_exclusive_lock: Boolean value. Only valid for node passthru
  737                                    methods. If True, lock the node before
  738                                    validate() and invoking the vendor method.
  739                                    The node remains locked during execution
  740                                    for a synchronous passthru method. If False,
  741                                    don't lock the node. Defaults to True.
  742     """
  743 
  744     def handle_passthru(func):
  745         api_method = method
  746         if api_method is None:
  747             api_method = func.__name__
  748 
  749         supported_ = [i.upper() for i in http_methods]
  750         description_ = description or ''
  751         metadata = VendorMetadata(api_method, {'http_methods': supported_,
  752                                                'async': async_call,
  753                                                'description': description_,
  754                                                'attach': attach})
  755         if driver_passthru:
  756             func._driver_metadata = metadata
  757         else:
  758             metadata[1]['require_exclusive_lock'] = require_exclusive_lock
  759             func._vendor_metadata = metadata
  760 
  761         passthru_logmessage = 'vendor_passthru failed with method %s'
  762 
  763         @functools.wraps(func)
  764         def passthru_handler(*args, **kwargs):
  765             try:
  766                 return func(*args, **kwargs)
  767             except exception.IronicException:
  768                 with excutils.save_and_reraise_exception():
  769                     LOG.exception(passthru_logmessage, api_method)
  770             except Exception as e:
  771                 # catch-all in case something bubbles up here
  772                 LOG.exception(passthru_logmessage, api_method)
  773                 raise exception.VendorPassthruException(message=e)
  774         return passthru_handler
  775     return handle_passthru
  776 
  777 
  778 def passthru(http_methods, method=None, async_call=True, description=None,
  779              attach=False, require_exclusive_lock=True):
  780     return _passthru(http_methods, method, async_call,
  781                      driver_passthru=False,
  782                      description=description, attach=attach,
  783                      require_exclusive_lock=require_exclusive_lock)
  784 
  785 
  786 def driver_passthru(http_methods, method=None, async_call=True,
  787                     description=None, attach=False):
  788     return _passthru(http_methods, method, async_call,
  789                      driver_passthru=True, description=description,
  790                      attach=attach)
  791 
  792 
  793 class VendorInterface(BaseInterface):
  794     """Interface for all vendor passthru functionality.
  795 
  796     Additional vendor- or driver-specific capabilities should be
  797     implemented as a method in the class inheriting from this class and
  798     use the @passthru or @driver_passthru decorators.
  799 
  800     Methods decorated with @driver_passthru should be short-lived because
  801     it is a blocking call.
  802     """
  803     interface_type = "vendor"
  804 
  805     def __new__(cls, *args, **kwargs):
  806         super_new = super(VendorInterface, cls).__new__
  807         if super_new is object.__new__:
  808             inst = super_new(cls)
  809         else:
  810             inst = super_new(cls, *args, **kwargs)
  811 
  812         inst.vendor_routes = {}
  813         inst.driver_routes = {}
  814 
  815         for name, ref in inspect.getmembers(inst, predicate=inspect.ismethod):
  816             vmeta = getattr(ref, '_vendor_metadata', None)
  817             dmeta = getattr(ref, '_driver_metadata', None)
  818 
  819             if vmeta is not None:
  820                 metadata = copy.deepcopy(vmeta.metadata)
  821                 metadata['func'] = ref
  822                 inst.vendor_routes.update({vmeta.method: metadata})
  823 
  824             if dmeta is not None:
  825                 metadata = copy.deepcopy(dmeta.metadata)
  826                 metadata['func'] = ref
  827                 inst.driver_routes.update({dmeta.method: metadata})
  828 
  829         return inst
  830 
  831     @abc.abstractmethod
  832     def validate(self, task, method=None, **kwargs):
  833         """Validate vendor-specific actions.
  834 
  835         If invalid, raises an exception; otherwise returns None.
  836 
  837         :param task: A task from TaskManager.
  838         :param method: Method to be validated
  839         :param kwargs: Info for action.
  840         :raises: UnsupportedDriverExtension if 'method' can not be mapped to
  841                  the supported interfaces.
  842         :raises: InvalidParameterValue if kwargs does not contain 'method'.
  843         :raises: MissingParameterValue
  844         """
  845 
  846     def driver_validate(self, method, **kwargs):
  847         """Validate driver-vendor-passthru actions.
  848 
  849         If invalid, raises an exception; otherwise returns None.
  850 
  851         :param method: method to be validated
  852         :param kwargs: info for action.
  853         :raises: MissingParameterValue if kwargs does not contain
  854                  certain parameter.
  855         :raises: InvalidParameterValue if parameter does not match.
  856         """
  857         pass
  858 
  859 
  860 class ManagementInterface(BaseInterface):
  861     """Interface for management related actions."""
  862     interface_type = 'management'
  863 
  864     @abc.abstractmethod
  865     def get_supported_boot_devices(self, task):
  866         """Get a list of the supported boot devices.
  867 
  868         :param task: A task from TaskManager.
  869         :returns: A list with the supported boot devices defined
  870                   in :mod:`ironic.common.boot_devices`.
  871         """
  872 
  873     @abc.abstractmethod
  874     def set_boot_device(self, task, device, persistent=False):
  875         """Set the boot device for a node.
  876 
  877         Set the boot device to use on next reboot of the node.
  878 
  879         :param task: A task from TaskManager.
  880         :param device: The boot device, one of
  881                        :mod:`ironic.common.boot_devices`.
  882         :param persistent: Boolean value. True if the boot device will
  883                            persist to all future boots, False if not.
  884                            Default: False.
  885         :raises: InvalidParameterValue if an invalid boot device is
  886                  specified.
  887         :raises: MissingParameterValue if a required parameter is missing
  888         """
  889 
  890     @abc.abstractmethod
  891     def get_boot_device(self, task):
  892         """Get the current boot device for a node.
  893 
  894         Provides the current boot device of the node. Be aware that not
  895         all drivers support this.
  896 
  897         :param task: A task from TaskManager.
  898         :raises: MissingParameterValue if a required parameter is missing
  899         :returns: A dictionary containing:
  900 
  901             :boot_device:
  902                 Ahe boot device, one of :mod:`ironic.common.boot_devices` or
  903                 None if it is unknown.
  904             :persistent:
  905                 Whether the boot device will persist to all future boots or
  906                 not, None if it is unknown.
  907 
  908         """
  909 
  910     def get_supported_boot_modes(self, task):
  911         """Get a list of the supported boot modes.
  912 
  913         NOTE: Not all drivers support this method. Older hardware
  914               may not implement that.
  915 
  916         :param task: A task from TaskManager.
  917         :raises: UnsupportedDriverExtension if requested operation is
  918                  not supported by the driver
  919         :raises: DriverOperationError or its derivative in case
  920                  of driver runtime error.
  921         :raises: MissingParameterValue if a required parameter is missing
  922         :returns: A list with the supported boot modes defined
  923                   in :mod:`ironic.common.boot_modes`. If boot
  924                   mode support can't be determined, empty list
  925                   is returned.
  926         """
  927         raise exception.UnsupportedDriverExtension(
  928             driver=task.node.driver, extension='get_supported_boot_modes')
  929 
  930     def set_boot_mode(self, task, mode):
  931         """Set the boot mode for a node.
  932 
  933         Set the boot mode to use on next reboot of the node.
  934 
  935         Drivers implementing this method are required to implement
  936         the `get_supported_boot_modes` method as well.
  937 
  938         NOTE: Not all drivers support this method. Hardware supporting only
  939             one boot mode may not implement that.
  940 
  941         :param task: A task from TaskManager.
  942         :param mode: The boot mode, one of
  943                      :mod:`ironic.common.boot_modes`.
  944         :raises: InvalidParameterValue if an invalid boot mode is
  945                  specified.
  946         :raises: MissingParameterValue if a required parameter is missing
  947         :raises: UnsupportedDriverExtension if requested operation is
  948                  not supported by the driver
  949         :raises: DriverOperationError or its derivative in case
  950                  of driver runtime error.
  951         """
  952         raise exception.UnsupportedDriverExtension(
  953             driver=task.node.driver, extension='set_boot_mode')
  954 
  955     def get_boot_mode(self, task):
  956         """Get the current boot mode for a node.
  957 
  958         Provides the current boot mode of the node.
  959 
  960         NOTE: Not all drivers support this method. Older hardware
  961               may not implement that.
  962 
  963         :param task: A task from TaskManager.
  964         :raises: MissingParameterValue if a required parameter is missing
  965         :raises: DriverOperationError or its  derivative in case
  966                  of driver runtime error.
  967         :raises: UnsupportedDriverExtension if requested operation is
  968                  not supported by the driver
  969         :returns: The boot mode, one of :mod:`ironic.common.boot_mode` or
  970                   None if it is unknown.
  971         """
  972         raise exception.UnsupportedDriverExtension(
  973             driver=task.node.driver, extension='get_boot_mode')
  974 
  975     @abc.abstractmethod
  976     def get_sensors_data(self, task):
  977         """Get sensors data method.
  978 
  979         :param task: A TaskManager instance.
  980         :raises: FailedToGetSensorData when getting the sensor data fails.
  981         :raises: FailedToParseSensorData when parsing sensor data fails.
  982         :returns: Returns a consistent format dict of sensor data grouped by
  983                   sensor type, which can be processed by Ceilometer.
  984                   eg,
  985 
  986                   ::
  987 
  988                       {
  989                         'Sensor Type 1': {
  990                           'Sensor ID 1': {
  991                             'Sensor Reading': 'current value',
  992                             'key1': 'value1',
  993                             'key2': 'value2'
  994                           },
  995                           'Sensor ID 2': {
  996                             'Sensor Reading': 'current value',
  997                             'key1': 'value1',
  998                             'key2': 'value2'
  999                           }
 1000                         },
 1001                         'Sensor Type 2': {
 1002                           'Sensor ID 3': {
 1003                             'Sensor Reading': 'current value',
 1004                             'key1': 'value1',
 1005                             'key2': 'value2'
 1006                           },
 1007                           'Sensor ID 4': {
 1008                             'Sensor Reading': 'current value',
 1009                             'key1': 'value1',
 1010                             'key2': 'value2'
 1011                           }
 1012                         }
 1013                       }
 1014         """
 1015 
 1016     def inject_nmi(self, task):
 1017         """Inject NMI, Non Maskable Interrupt.
 1018 
 1019         Inject NMI (Non Maskable Interrupt) for a node immediately.
 1020 
 1021         :param task: A TaskManager instance containing the node to act on.
 1022         :raises: UnsupportedDriverExtension
 1023         """
 1024         raise exception.UnsupportedDriverExtension(
 1025             driver=task.node.driver, extension='inject_nmi')
 1026 
 1027     def get_supported_indicators(self, task, component=None):
 1028         """Get a map of the supported indicators (e.g. LEDs).
 1029 
 1030         :param task: A task from TaskManager.
 1031         :param component: If not `None`, return indicator information
 1032             for just this component, otherwise return indicators for
 1033             all existing components.
 1034         :returns: A dictionary of hardware components
 1035             (:mod:`ironic.common.components`) as keys with values
 1036             being dictionaries having indicator IDs as keys and indicator
 1037             properties as values.
 1038 
 1039             ::
 1040 
 1041                 {
 1042                     'chassis': {
 1043                         'enclosure-0': {
 1044                             "readonly": true,
 1045                             "states": [
 1046                                 "off",
 1047                                 "on"
 1048                             ]
 1049                         }
 1050                     },
 1051                     'system':
 1052                         'blade-A': {
 1053                             "readonly": true,
 1054                             "states": [
 1055                                 "pff",
 1056                                 "on"
 1057                             ]
 1058                         }
 1059                     },
 1060                     'drive':
 1061                         'ssd0': {
 1062                             "readonly": true,
 1063                             "states": [
 1064                                 "off",
 1065                                 "on"
 1066                             ]
 1067                         }
 1068                     }
 1069                 }
 1070 
 1071         """
 1072         raise exception.UnsupportedDriverExtension(
 1073             driver=task.node.driver, extension='get_supported_indicators')
 1074 
 1075     def set_indicator_state(self, task, component, indicator, state):
 1076         """Set indicator on the hardware component to the desired state.
 1077 
 1078         :param task: A task from TaskManager.
 1079         :param component: The hardware component, one of
 1080             :mod:`ironic.common.components`.
 1081         :param indicator: Indicator ID (as reported by
 1082             `get_supported_indicators`).
 1083         :state: Desired state of the indicator, one of
 1084             :mod:`ironic.common.indicator_states`.
 1085         :raises: InvalidParameterValue if an invalid component, indicator
 1086             or state is specified.
 1087         :raises: MissingParameterValue if a required parameter is missing
 1088         """
 1089         raise exception.UnsupportedDriverExtension(
 1090             driver=task.node.driver, extension='set_indicator_state')
 1091 
 1092     def get_indicator_state(self, task, component, indicator):
 1093         """Get current state of the indicator of the hardware component.
 1094 
 1095         :param task: A task from TaskManager.
 1096         :param component: The hardware component, one of
 1097             :mod:`ironic.common.components`.
 1098         :param indicator: Indicator ID (as reported by
 1099             `get_supported_indicators`).
 1100         :raises: InvalidParameterValue if an invalid component or indicator
 1101             is specified.
 1102         :raises: MissingParameterValue if a required parameter is missing
 1103         :returns: Current state of the indicator, one of
 1104             :mod:`ironic.common.indicator_states`.
 1105 
 1106         """
 1107         raise exception.UnsupportedDriverExtension(
 1108             driver=task.node.driver, extension='get_indicator_state')
 1109 
 1110     def detect_vendor(self, task):
 1111         """Detects, stores, and returns the hardware vendor.
 1112 
 1113         If the Node object ``properties`` field does not already contain
 1114         a ``vendor`` field, then this method is intended to query
 1115         Detects the BMC hardware vendor and stores the returned value
 1116         with-in the Node object ``properties`` field if detected.
 1117 
 1118         :param task: A task from TaskManager.
 1119         :raises: InvalidParameterValue if an invalid component, indicator
 1120             or state is specified.
 1121         :raises: MissingParameterValue if a required parameter is missing
 1122         :returns: String representing the BMC reported Vendor or
 1123                   Manufacturer, otherwise returns None.
 1124         """
 1125         raise exception.UnsupportedDriverExtension(
 1126             driver=task.node.driver, extension='detect_vendor')
 1127 
 1128 
 1129 class InspectInterface(BaseInterface):
 1130     """Interface for inspection-related actions."""
 1131     interface_type = 'inspect'
 1132 
 1133     ESSENTIAL_PROPERTIES = {'memory_mb', 'local_gb', 'cpus', 'cpu_arch'}
 1134     """The properties required by scheduler/deploy."""
 1135 
 1136     @abc.abstractmethod
 1137     def inspect_hardware(self, task):
 1138         """Inspect hardware.
 1139 
 1140         Inspect hardware to obtain the essential & additional hardware
 1141         properties.
 1142 
 1143         :param task: A task from TaskManager.
 1144         :raises: HardwareInspectionFailure, if unable to get essential
 1145                  hardware properties.
 1146         :returns: Resulting state of the inspection i.e. states.MANAGEABLE
 1147                   or None.
 1148         """
 1149 
 1150     def abort(self, task):
 1151         """Abort asynchronized hardware inspection.
 1152 
 1153         Abort an ongoing hardware introspection, this is only used for
 1154         asynchronize based inspect interface.
 1155 
 1156         NOTE: This interface is called with node exclusive lock held, the
 1157         interface implementation is expected to be a quick processing.
 1158 
 1159         :param task: a task from TaskManager.
 1160         :raises: UnsupportedDriverExtension, if the method is not implemented
 1161                  by specific inspect interface.
 1162         """
 1163         raise exception.UnsupportedDriverExtension(
 1164             driver=task.node.driver, extension='abort')
 1165 
 1166 
 1167 def cache_bios_settings(func):
 1168     """A decorator to cache bios settings after running the function.
 1169 
 1170     :param func: Function or method to wrap.
 1171     """
 1172     @functools.wraps(func)
 1173     def wrapped(self, task, *args, **kwargs):
 1174         result = func(self, task, *args, **kwargs)
 1175         self.cache_bios_settings(task)
 1176         return result
 1177     return wrapped
 1178 
 1179 
 1180 class BIOSInterface(BaseInterface):
 1181     interface_type = 'bios'
 1182 
 1183     @abc.abstractmethod
 1184     def apply_configuration(self, task, settings):
 1185         """Validate & apply BIOS settings on the given node.
 1186 
 1187         This method takes the BIOS settings from the settings param and
 1188         applies BIOS settings on the given node. It may also validate the
 1189         given bios settings before applying any settings and manage
 1190         failures when setting an invalid BIOS config. In the case of
 1191         needing password to update the BIOS config, it will be taken from
 1192         the driver_info properties. After the BIOS configuration is done,
 1193         cache_bios_settings will be called to update the node's BIOS setting
 1194         table with the BIOS configuration applied on the node.
 1195 
 1196         :param task: a TaskManager instance.
 1197         :param settings: Dictonary containing the BIOS configuration.
 1198         :raises: UnsupportedDriverExtension, if the node's driver doesn't
 1199             support BIOS configuration.
 1200         :raises: InvalidParameterValue, if validation of settings fails.
 1201         :raises: MissingParameterValue, if some required parameters are
 1202             missing.
 1203         :returns: states.CLEANWAIT if BIOS configuration is in progress
 1204             asynchronously or None if it is complete.
 1205         """
 1206 
 1207     @abc.abstractmethod
 1208     def factory_reset(self, task):
 1209         """Reset BIOS configuration to factory default on the given node.
 1210 
 1211         This method resets BIOS configuration to factory default on the
 1212         given node. After the BIOS reset action is done, cache_bios_settings
 1213         will be called to update the node's BIOS settings table with default
 1214         bios settings.
 1215 
 1216         :param task: a TaskManager instance.
 1217         :raises: UnsupportedDriverExtension, if the node's driver doesn't
 1218             support BIOS reset.
 1219         :returns: states.CLEANWAIT if BIOS configuration is in progress
 1220             asynchronously or None if it is complete.
 1221         """
 1222 
 1223     @abc.abstractmethod
 1224     def cache_bios_settings(self, task):
 1225         """Store or update BIOS properties on the given node.
 1226 
 1227         This method stores BIOS properties to the bios_settings table during
 1228         'cleaning' operation and updates bios_settings table when
 1229         apply_configuration() and factory_reset() are called to set new BIOS
 1230         configurations. It will also update the timestamp of each bios setting.
 1231 
 1232         :param task: a TaskManager instance.
 1233         :raises: UnsupportedDriverExtension, if the node's driver doesn't
 1234             support getting BIOS properties from bare metal.
 1235         :returns: None.
 1236         """
 1237 
 1238 
 1239 class RAIDInterface(BaseInterface):
 1240     interface_type = 'raid'
 1241 
 1242     def __init__(self):
 1243         """Constructor for RAIDInterface class."""
 1244         with open(RAID_CONFIG_SCHEMA, 'r') as raid_schema_fobj:
 1245             self.raid_schema = json.load(raid_schema_fobj)
 1246 
 1247     def get_properties(self):
 1248         """Return the properties of the interface.
 1249 
 1250         :returns: dictionary of <property name>:<property description> entries.
 1251         """
 1252         return {}
 1253 
 1254     def validate(self, task):
 1255         """Validates the RAID Interface.
 1256 
 1257         This method validates the properties defined by Ironic for RAID
 1258         configuration. Driver implementations of this interface can override
 1259         this method for doing more validations (such as BMC's credentials).
 1260 
 1261         :param task: A TaskManager instance.
 1262         :raises: InvalidParameterValue, if the RAID configuration is invalid.
 1263         :raises: MissingParameterValue, if some parameters are missing.
 1264         """
 1265         target_raid_config = task.node.target_raid_config
 1266         if not target_raid_config:
 1267             return
 1268         self.validate_raid_config(task, target_raid_config)
 1269 
 1270     def validate_raid_config(self, task, raid_config):
 1271         """Validates the given RAID configuration.
 1272 
 1273         This method validates the given RAID configuration.  Driver
 1274         implementations of this interface can override this method to support
 1275         custom parameters for RAID configuration.
 1276 
 1277         :param task: A TaskManager instance.
 1278         :param raid_config: The RAID configuration to validate.
 1279         :raises: InvalidParameterValue, if the RAID configuration is invalid.
 1280         """
 1281         raid.validate_configuration(raid_config, self.raid_schema)
 1282 
 1283     # NOTE(mgoddard): This is not marked as a deploy step, because it requires
 1284     # the create_configuration method to support use during deployment, which
 1285     # might not be true for all implementations. Subclasses wishing to expose
 1286     # an apply_configuration deploy step should implement this method with a
 1287     # deploy_step decorator. The RAID_APPLY_CONFIGURATION_ARGSINFO variable may
 1288     # be used for the deploy_step argsinfo argument. The create_configuration
 1289     # method must also accept a delete_existing argument.
 1290     def apply_configuration(self, task, raid_config, create_root_volume=True,
 1291                             create_nonroot_volumes=True,
 1292                             delete_existing=True):
 1293         """Applies RAID configuration on the given node.
 1294 
 1295         :param task: A TaskManager instance.
 1296         :param raid_config: The RAID configuration to apply.
 1297         :param create_root_volume: Setting this to False indicates
 1298             not to create root volume that is specified in raid_config.
 1299             Default value is True.
 1300         :param create_nonroot_volumes: Setting this to False indicates
 1301             not to create non-root volumes (all except the root volume) in
 1302             raid_config.  Default value is True.
 1303         :param delete_existing: Setting this to True indicates to delete RAID
 1304             configuration prior to creating the new configuration.
 1305         :raises: InvalidParameterValue, if the RAID configuration is invalid.
 1306         :returns: states.DEPLOYWAIT if RAID configuration is in progress
 1307             asynchronously or None if it is complete.
 1308         """
 1309         self.validate_raid_config(task, raid_config)
 1310         node = task.node
 1311         node.target_raid_config = raid_config
 1312         node.save()
 1313         return self.create_configuration(
 1314             task,
 1315             create_root_volume=create_root_volume,
 1316             create_nonroot_volumes=create_nonroot_volumes,
 1317             delete_existing=delete_existing)
 1318 
 1319     @abc.abstractmethod
 1320     def create_configuration(self, task,
 1321                              create_root_volume=True,
 1322                              create_nonroot_volumes=True,
 1323                              delete_existing=True):
 1324         """Creates RAID configuration on the given node.
 1325 
 1326         This method creates a RAID configuration on the given node.
 1327         It assumes that the target RAID configuration is already
 1328         available in node.target_raid_config.
 1329         Implementations of this interface are supposed to read the
 1330         RAID configuration from node.target_raid_config. After the
 1331         RAID configuration is done (either in this method OR in a call-back
 1332         method), ironic.common.raid.update_raid_info()
 1333         may be called to sync the node's RAID-related information with the
 1334         RAID configuration applied on the node.
 1335 
 1336         :param task: A TaskManager instance.
 1337         :param create_root_volume: Setting this to False indicates
 1338             not to create root volume that is specified in the node's
 1339             target_raid_config. Default value is True.
 1340         :param create_nonroot_volumes: Setting this to False indicates
 1341             not to create non-root volumes (all except the root volume) in the
 1342             node's target_raid_config.  Default value is True.
 1343         :param delete_existing: Setting this to True indicates to delete RAID
 1344             configuration prior to creating the new configuration.
 1345         :returns: states.CLEANWAIT (cleaning) or states.DEPLOYWAIT (deployment)
 1346             if RAID configuration is in progress asynchronously, or None if it
 1347             is complete.
 1348         """
 1349 
 1350     @abc.abstractmethod
 1351     def delete_configuration(self, task):
 1352         """Deletes RAID configuration on the given node.
 1353 
 1354         This method deletes the RAID configuration on the give node.
 1355         After RAID configuration is deleted, node.raid_config should be
 1356         cleared by the implementation.
 1357 
 1358         :param task: A TaskManager instance.
 1359         :returns: states.CLEANWAIT (cleaning) or states.DEPLOYWAIT (deployment)
 1360             if deletion is in progress asynchronously, or None if it is
 1361             complete.
 1362         """
 1363 
 1364     def get_logical_disk_properties(self):
 1365         """Get the properties that can be specified for logical disks.
 1366 
 1367         This method returns a dictionary containing the properties that can
 1368         be specified for logical disks and a textual description for them.
 1369 
 1370         :returns: A dictionary containing properties that can be mentioned for
 1371             logical disks and a textual description for them.
 1372         """
 1373         return raid.get_logical_disk_properties(self.raid_schema)
 1374 
 1375 
 1376 class NetworkInterface(BaseInterface):
 1377     """Base class for network interfaces."""
 1378 
 1379     interface_type = 'network'
 1380 
 1381     def get_properties(self):
 1382         """Return the properties of the interface.
 1383 
 1384         :returns: dictionary of <property name>:<property description> entries.
 1385         """
 1386         return {}
 1387 
 1388     def validate(self, task):
 1389         """Validates the network interface.
 1390 
 1391         :param task: A TaskManager instance.
 1392         :raises: InvalidParameterValue, if the network interface configuration
 1393             is invalid.
 1394         :raises: MissingParameterValue, if some parameters are missing.
 1395         """
 1396 
 1397     @abc.abstractmethod
 1398     def port_changed(self, task, port_obj):
 1399         """Handle any actions required when a port changes
 1400 
 1401         :param task: A TaskManager instance.
 1402         :param port_obj: a changed Port object.
 1403         :raises: Conflict, FailedToUpdateDHCPOptOnPort
 1404         """
 1405 
 1406     @abc.abstractmethod
 1407     def portgroup_changed(self, task, portgroup_obj):
 1408         """Handle any actions required when a port changes
 1409 
 1410         :param task: A TaskManager instance.
 1411         :param portgroup_obj: a changed Port object.
 1412         :raises: Conflict, FailedToUpdateDHCPOptOnPort
 1413         """
 1414 
 1415     @abc.abstractmethod
 1416     def vif_attach(self, task, vif_info):
 1417         """Attach a virtual network interface to a node
 1418 
 1419         :param task: A TaskManager instance.
 1420         :param vif_info: a dictionary of information about a VIF.
 1421             It must have an 'id' key, whose value is a unique identifier
 1422             for that VIF.
 1423         :raises: NetworkError, VifAlreadyAttached, NoFreePhysicalPorts
 1424         """
 1425 
 1426     @abc.abstractmethod
 1427     def vif_detach(self, task, vif_id):
 1428         """Detach a virtual network interface from a node
 1429 
 1430         :param task: A TaskManager instance.
 1431         :param vif_id: A VIF ID to detach
 1432         :raises: NetworkError, VifNotAttached
 1433         """
 1434 
 1435     @abc.abstractmethod
 1436     def vif_list(self, task):
 1437         """List attached VIF IDs for a node
 1438 
 1439         :param task: A TaskManager instance.
 1440         :returns: List of VIF dictionaries, each dictionary will have an 'id'
 1441             entry with the ID of the VIF.
 1442         """
 1443 
 1444     @abc.abstractmethod
 1445     def get_current_vif(self, task, p_obj):
 1446         """Returns the currently used VIF associated with port or portgroup
 1447 
 1448         We are booting the node only in one network at a time, and presence of
 1449         cleaning_vif_port_id means we're doing cleaning,
 1450         of provisioning_vif_port_id - provisioning,
 1451         of rescuing_vif_port_id - rescuing.
 1452         Otherwise it's a tenant network.
 1453 
 1454         :param task: A TaskManager instance.
 1455         :param p_obj: Ironic port or portgroup object.
 1456         :returns: VIF ID associated with p_obj or None.
 1457         """
 1458 
 1459     @abc.abstractmethod
 1460     def add_provisioning_network(self, task):
 1461         """Add the provisioning network to a node.
 1462 
 1463         :param task: A TaskManager instance.
 1464         :raises: NetworkError
 1465         """
 1466 
 1467     @abc.abstractmethod
 1468     def remove_provisioning_network(self, task):
 1469         """Remove the provisioning network from a node.
 1470 
 1471         :param task: A TaskManager instance.
 1472         """
 1473 
 1474     @abc.abstractmethod
 1475     def configure_tenant_networks(self, task):
 1476         """Configure tenant networks for a node.
 1477 
 1478         :param task: A TaskManager instance.
 1479         :raises: NetworkError
 1480         """
 1481 
 1482     @abc.abstractmethod
 1483     def unconfigure_tenant_networks(self, task):
 1484         """Unconfigure tenant networks for a node.
 1485 
 1486         :param task: A TaskManager instance.
 1487         """
 1488 
 1489     @abc.abstractmethod
 1490     def add_cleaning_network(self, task):
 1491         """Add the cleaning network to a node.
 1492 
 1493         :param task: A TaskManager instance.
 1494         :returns: a dictionary in the form {port.uuid: neutron_port['id']}
 1495         :raises: NetworkError
 1496         """
 1497 
 1498     @abc.abstractmethod
 1499     def remove_cleaning_network(self, task):
 1500         """Remove the cleaning network from a node.
 1501 
 1502         :param task: A TaskManager instance.
 1503         :raises: NetworkError
 1504         """
 1505 
 1506     def validate_rescue(self, task):
 1507         """Validates the network interface for rescue operation.
 1508 
 1509         :param task: A TaskManager instance.
 1510         :raises: InvalidParameterValue, if the network interface configuration
 1511             is invalid.
 1512         :raises: MissingParameterValue, if some parameters are missing.
 1513         """
 1514         pass
 1515 
 1516     def add_rescuing_network(self, task):
 1517         """Add the rescuing network to the node.
 1518 
 1519         :param task: A TaskManager instance.
 1520         :returns: a dictionary in the form {port.uuid: neutron_port['id']}
 1521         :raises: NetworkError
 1522         :raises: InvalidParameterValue, if the network interface configuration
 1523             is invalid.
 1524         """
 1525         return {}
 1526 
 1527     def remove_rescuing_network(self, task):
 1528         """Removes the rescuing network from a node.
 1529 
 1530         :param task: A TaskManager instance.
 1531         :raises: NetworkError
 1532         :raises: InvalidParameterValue, if the network interface configuration
 1533             is invalid.
 1534         :raises: MissingParameterValue, if some parameters are missing.
 1535         """
 1536         pass
 1537 
 1538     def validate_inspection(self, task):
 1539         """Validate that the node has required properties for inspection.
 1540 
 1541         :param task: A TaskManager instance with the node being checked
 1542         :raises: MissingParameterValue if node is missing one or more required
 1543             parameters
 1544         :raises: UnsupportedDriverExtension
 1545         """
 1546         raise exception.UnsupportedDriverExtension(
 1547             driver=task.node.driver, extension='validate_inspection')
 1548 
 1549     def add_inspection_network(self, task):
 1550         """Add the inspection network to the node.
 1551 
 1552         :param task: A TaskManager instance.
 1553         :returns: a dictionary in the form {port.uuid: neutron_port['id']}
 1554         :raises: NetworkError
 1555         :raises: InvalidParameterValue, if the network interface configuration
 1556             is invalid.
 1557         """
 1558         return {}
 1559 
 1560     def remove_inspection_network(self, task):
 1561         """Removes the inspection network from a node.
 1562 
 1563         :param task: A TaskManager instance.
 1564         :raises: NetworkError
 1565         :raises: InvalidParameterValue, if the network interface configuration
 1566             is invalid.
 1567         :raises: MissingParameterValue, if some parameters are missing.
 1568         """
 1569 
 1570     def need_power_on(self, task):
 1571         """Check if ironic node must be powered on before applying network changes
 1572 
 1573         :param task: A TaskManager instance.
 1574         :returns: Boolean.
 1575         """
 1576         return False
 1577 
 1578     def get_node_network_data(self, task):
 1579         """Return network configuration for node NICs.
 1580 
 1581         Gather L2 and L3 network settings from ironic port/portgroups
 1582         objects and underlying network provider, then put together
 1583         collected data in form of Nova network metadata (`network_data.json`)
 1584         dict.
 1585 
 1586         Ironic would eventually pass network configuration to the node
 1587         being managed out-of-band.
 1588 
 1589         :param task: A TaskManager instance.
 1590         :raises: InvalidParameterValue, if the network interface configuration
 1591             is invalid.
 1592         :raises: MissingParameterValue, if some parameters are missing.
 1593         :returns: a dict holding network configuration information adhearing
 1594             Nova network metadata layout (`network_data.json`).
 1595         """
 1596         return task.node.network_data or {}
 1597 
 1598 
 1599 class StorageInterface(BaseInterface, metaclass=abc.ABCMeta):
 1600     """Base class for storage interfaces."""
 1601 
 1602     interface_type = 'storage'
 1603 
 1604     @abc.abstractmethod
 1605     def attach_volumes(self, task):
 1606         """Informs the storage subsystem to attach all volumes for the node.
 1607 
 1608         :param task: A TaskManager instance.
 1609         :raises: UnsupportedDriverExtension
 1610         """
 1611 
 1612     @abc.abstractmethod
 1613     def detach_volumes(self, task):
 1614         """Informs the storage subsystem to detach all volumes for the node.
 1615 
 1616         :param task: A TaskManager instance.
 1617         :raises: UnsupportedDriverExtension
 1618         """
 1619 
 1620     @abc.abstractmethod
 1621     def should_write_image(self, task):
 1622         """Determines if deploy should perform the image write-out.
 1623 
 1624         :param task: A TaskManager instance.
 1625         :returns: Boolean value to indicate if the interface expects
 1626                   the image to be written by Ironic.
 1627         :raises: UnsupportedDriverExtension
 1628         """
 1629 
 1630 
 1631 def _validate_argsinfo(argsinfo):
 1632     """Validate args info.
 1633 
 1634     This method validates args info, so that the values are the expected
 1635     data types and required values are specified.
 1636 
 1637     :param argsinfo: a dictionary of keyword arguments where key is the name of
 1638         the argument and value is a dictionary as follows::
 1639 
 1640             'description': <description>. Required. This should include
 1641                            possible values.
 1642             'required': Boolean. Optional; default is False. True if this
 1643                         argument is required.  If so, it must be specified in
 1644                         the clean request; false if it is optional.
 1645     :raises InvalidParameterValue: if any of the arguments are invalid
 1646     """
 1647     if not argsinfo:
 1648         return
 1649 
 1650     if not isinstance(argsinfo, dict):
 1651         raise exception.InvalidParameterValue(
 1652             _('"argsinfo" must be a dictionary instead of "%s"') %
 1653             argsinfo)
 1654     for (arg, info) in argsinfo.items():
 1655         if not isinstance(info, dict):
 1656             raise exception.InvalidParameterValue(
 1657                 _('Argument "%(arg)s" must be a dictionary instead of '
 1658                   '"%(val)s".') % {'arg': arg, 'val': info})
 1659         has_description = False
 1660         for (key, value) in info.items():
 1661             if key == 'description':
 1662                 if not isinstance(value, str):
 1663                     raise exception.InvalidParameterValue(
 1664                         _('For argument "%(arg)s", "description" must be a '
 1665                           'string value instead of "%(value)s".') %
 1666                         {'arg': arg, 'value': value})
 1667                 has_description = True
 1668             elif key == 'required':
 1669                 if not isinstance(value, bool):
 1670                     raise exception.InvalidParameterValue(
 1671                         _('For argument "%(arg)s", "required" must be a '
 1672                           'Boolean value instead of "%(value)s".') %
 1673                         {'arg': arg, 'value': value})
 1674             else:
 1675                 raise exception.InvalidParameterValue(
 1676                     _('Argument "%(arg)s" has an invalid key named "%(key)s". '
 1677                       'It must be "description" or "required".')
 1678                     % {'key': key, 'arg': arg})
 1679         if not has_description:
 1680             raise exception.InvalidParameterValue(
 1681                 _('Argument "%(arg)s" is missing a "description".') %
 1682                 {'arg': arg})
 1683 
 1684 
 1685 def clean_step(priority, abortable=False, argsinfo=None):
 1686     """Decorator for cleaning steps.
 1687 
 1688     Cleaning steps may be used in manual or automated cleaning.
 1689 
 1690     For automated cleaning, only steps with priorities greater than 0 are
 1691     used. These steps are ordered by priority from highest value to lowest
 1692     value. For steps with the same priority, they are ordered by driver
 1693     interface priority (see conductor.steps.CLEANING_INTERFACE_PRIORITY).
 1694     execute_clean_step() will be called on each step.
 1695 
 1696     For manual cleaning, the clean steps will be executed in a similar fashion
 1697     to automated cleaning, but the steps and order of execution must be
 1698     explicitly specified by the user when invoking the cleaning API.
 1699 
 1700     Decorated clean steps must take as the only positional argument, a
 1701     TaskManager object. Clean steps used in manual cleaning may also take
 1702     keyword variable arguments (as described in argsinfo).
 1703 
 1704     Clean steps can be either synchronous or asynchronous.  If the step is
 1705     synchronous, it should return `None` when finished, and the conductor
 1706     will continue on to the next step. While the clean step is executing, the
 1707     node will be in `states.CLEANING` provision state. If the step is
 1708     asynchronous, the step should return `states.CLEANWAIT` to the
 1709     conductor before it starts the asynchronous work.  When the step is
 1710     complete, the step should make an RPC call to `continue_node_clean` to
 1711     move to the next step in cleaning. The node will be in `states.CLEANWAIT`
 1712     provision state during the asynchronous work.
 1713 
 1714     Examples::
 1715 
 1716         class MyInterface(base.BaseInterface):
 1717             # CONF.example_cleaning_priority should be an int CONF option
 1718             @base.clean_step(priority=CONF.example_cleaning_priority)
 1719             def example_cleaning(self, task):
 1720                 # do some cleaning
 1721 
 1722             @base.clean_step(priority=0, abortable=True, argsinfo=
 1723                              {'size': {'description': 'size of widget (MB)',
 1724                                        'required': True}})
 1725             def advanced_clean(self, task, **kwargs):
 1726                 # do some advanced cleaning
 1727 
 1728     :param priority: an integer priority, should be a CONF option
 1729     :param abortable: Boolean value. Whether the clean step is abortable
 1730         or not; defaults to False.
 1731     :param argsinfo: a dictionary of keyword arguments where key is the name of
 1732         the argument and value is a dictionary as follows::
 1733 
 1734             'description': <description>. Required. This should include
 1735                            possible values.
 1736             'required': Boolean. Optional; default is False. True if this
 1737                         argument is required.  If so, it must be specified in
 1738                         the clean request; false if it is optional.
 1739     :raises InvalidParameterValue: if any of the arguments are invalid
 1740     """
 1741     def decorator(func):
 1742         func._is_clean_step = True
 1743         if isinstance(priority, int):
 1744             func._clean_step_priority = priority
 1745         else:
 1746             raise exception.InvalidParameterValue(
 1747                 _('"priority" must be an integer value instead of "%s"')
 1748                 % priority)
 1749 
 1750         if isinstance(abortable, bool):
 1751             func._clean_step_abortable = abortable
 1752         else:
 1753             raise exception.InvalidParameterValue(
 1754                 _('"abortable" must be a Boolean value instead of "%s"')
 1755                 % abortable)
 1756 
 1757         _validate_argsinfo(argsinfo)
 1758         func._clean_step_argsinfo = argsinfo
 1759         return func
 1760     return decorator
 1761 
 1762 
 1763 def deploy_step(priority, argsinfo=None):
 1764     """Decorator for deployment steps.
 1765 
 1766     Only steps with priorities greater than 0 are used.
 1767     These steps are ordered by priority from highest value to lowest
 1768     value. For steps with the same priority, they are ordered by driver
 1769     interface priority (see conductor.steps.DEPLOYING_INTERFACE_PRIORITY).
 1770     execute_deploy_step() will be called on each step.
 1771 
 1772     Decorated deploy steps must take as the only positional argument, a
 1773     TaskManager object.
 1774 
 1775     Deploy steps can be either synchronous or asynchronous.  If the step is
 1776     synchronous, it should return `None` when finished, and the conductor
 1777     will continue on to the next step. While the deploy step is executing, the
 1778     node will be in `states.DEPLOYING` provision state. If the step is
 1779     asynchronous, the step should return `states.DEPLOYWAIT` to the
 1780     conductor before it starts the asynchronous work.  When the step is
 1781     complete, the step should make an RPC call to `continue_node_deploy` to
 1782     move to the next step in deployment. The node will be in
 1783     `states.DEPLOYWAIT` provision state during the asynchronous work.
 1784 
 1785     Examples::
 1786 
 1787         class MyInterface(base.BaseInterface):
 1788             @base.deploy_step(priority=100)
 1789             def example_deploying(self, task):
 1790                 # do some deploying
 1791 
 1792     :param priority: an integer (>=0) priority; used for determining the order
 1793         in which the step is run in the deployment process.
 1794     :param argsinfo: a dictionary of keyword arguments where key is the name of
 1795         the argument and value is a dictionary as follows::
 1796 
 1797             'description': <description>. Required. This should include
 1798                            possible values.
 1799             'required': Boolean. Optional; default is False. True if this
 1800                         argument is required.  If so, it must be specified in
 1801                         the deployment request; false if it is optional.
 1802     :raises InvalidParameterValue: if any of the arguments are invalid
 1803     """
 1804     def decorator(func):
 1805         func._is_deploy_step = True
 1806         if isinstance(priority, int) and priority >= 0:
 1807             func._deploy_step_priority = priority
 1808         else:
 1809             raise exception.InvalidParameterValue(
 1810                 _('"priority" must be an integer value >= 0, instead of "%s"')
 1811                 % priority)
 1812 
 1813         _validate_argsinfo(argsinfo)
 1814         func._deploy_step_argsinfo = argsinfo
 1815         return func
 1816     return decorator