Remove iBoot, WoL and AMT drivers
This patch removes the mentioned drivers from ironic code tree. Per our third-party drivers policy they are unsupported, and there is no existing plan to have third-party CI for them. Nevertheless, if anybody still needs them, they are available in ironic-staging-drivers repo. Change-Id: I1219892bbc1b814d80b62c8ec89b90819071870f Related-Bug: #1640533
This commit is contained in:
parent
cf81c491f3
commit
87c2db4dc6
@ -31,13 +31,6 @@ DRAC with PXE deploy
|
|||||||
``/etc/ironic/ironic.conf``
|
``/etc/ironic/ironic.conf``
|
||||||
- Install python-dracclient package
|
- Install python-dracclient package
|
||||||
|
|
||||||
AMT driver
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
../drivers/amt
|
|
||||||
|
|
||||||
SNMP driver
|
SNMP driver
|
||||||
-----------
|
-----------
|
||||||
@ -89,24 +82,6 @@ Cisco UCS driver
|
|||||||
../drivers/ucs
|
../drivers/ucs
|
||||||
|
|
||||||
|
|
||||||
Wake-On-Lan driver
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
../drivers/wol
|
|
||||||
|
|
||||||
|
|
||||||
iBoot driver
|
|
||||||
------------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
../drivers/iboot
|
|
||||||
|
|
||||||
|
|
||||||
CIMC driver
|
CIMC driver
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@ -132,3 +107,16 @@ XenServer ssh driver
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
../drivers/xenserver
|
../drivers/xenserver
|
||||||
|
|
||||||
|
|
||||||
|
Unsupported drivers
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
The following drivers were declared as unsupported in ironic Newton release
|
||||||
|
and as of Ocata release they are removed form ironic:
|
||||||
|
|
||||||
|
- AMT driver - available as part of ironic-staging-drivers_
|
||||||
|
- iBoot driver - available as part of ironic-staging-drivers_
|
||||||
|
- Wake-On-Lan driver - available as part of ironic-staging-drivers_
|
||||||
|
|
||||||
|
.. _ironic-staging-drivers: http://ironic-staging-drivers.readthedocs.io
|
||||||
|
@ -211,7 +211,7 @@ Agent driver attributes:
|
|||||||
These are only some fields in use. Other vendor drivers might expose more ``driver_internal_info``
|
These are only some fields in use. Other vendor drivers might expose more ``driver_internal_info``
|
||||||
properties, please check their development documentation and/or module docstring for details.
|
properties, please check their development documentation and/or module docstring for details.
|
||||||
It is important for developers to make sure these properties follow the precedent of prefixing their
|
It is important for developers to make sure these properties follow the precedent of prefixing their
|
||||||
variable names with a specific interface name (e.g., iboot_bar, amt_xyz), so as to minimize or avoid
|
variable names with a specific interface name (e.g., ilo_bar, drac_xyz), so as to minimize or avoid
|
||||||
any conflicts between interfaces.
|
any conflicts between interfaces.
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
.. _amt:
|
|
||||||
|
|
||||||
===========
|
|
||||||
AMT drivers
|
|
||||||
===========
|
|
||||||
|
|
||||||
Overview
|
|
||||||
========
|
|
||||||
AMT (Active Management Technology) drivers extend Ironic's range to the
|
|
||||||
desktop. AMT/vPro is widely used in desktops to remotely control their power,
|
|
||||||
similar to IPMI in servers.
|
|
||||||
|
|
||||||
AMT drivers use WS-MAN protocol to interact with AMT clients.
|
|
||||||
They work on AMT 7.0/8.0/9.0. AMT 7.0 was released in 2010, so AMT drivers
|
|
||||||
should work on most PCs with vPro.
|
|
||||||
|
|
||||||
There are two AMT drivers:
|
|
||||||
|
|
||||||
* ``pxe_amt`` uses AMT for power management and deploys the user image over
|
|
||||||
iSCSI from the conductor
|
|
||||||
|
|
||||||
* ``agent_amt`` uses AMT for power management and deploys the user image
|
|
||||||
directly to the node via HTTP.
|
|
||||||
|
|
||||||
Set up your environment
|
|
||||||
=======================
|
|
||||||
A detailed reference is available here, and a short guide follows below:
|
|
||||||
|
|
||||||
https://software.intel.com/en-us/articles/intel-active-management-technology-start-here-guide-intel-amt-9#4.2
|
|
||||||
|
|
||||||
* Set up AMT Client
|
|
||||||
|
|
||||||
* Choose a system which supports Intel AMT / vPro. Desktop and laptop systems
|
|
||||||
that support this can often be identified by looking at the "Intel" tag for
|
|
||||||
the word ``vPro``.
|
|
||||||
|
|
||||||
* During boot, press Ctrl+P to enter Intel MEBx management.
|
|
||||||
|
|
||||||
* Reset password -- default is ``admin``. The new password must contain at
|
|
||||||
least one upper case letter, one lower case letter, one digit and one
|
|
||||||
special character, and be at least eight characters.
|
|
||||||
|
|
||||||
* Go to Intel AMT Configuration:
|
|
||||||
|
|
||||||
* Enable all features under SOL/IDER/KVM section
|
|
||||||
|
|
||||||
* Select User Consent and choose None (No password is needed)
|
|
||||||
|
|
||||||
* Select Network Setup section and set IP
|
|
||||||
|
|
||||||
* Activate Network Access
|
|
||||||
|
|
||||||
* MEBx Exit
|
|
||||||
|
|
||||||
* Restart and enable PXE boot in bios
|
|
||||||
|
|
||||||
* Install ``openwsman`` on servers where ``ironic-conductor`` is running:
|
|
||||||
|
|
||||||
* Fedora/RHEL: ``openwsman-python``.
|
|
||||||
|
|
||||||
* Ubuntu: ``python-openwsman``'s most recent version is 2.4.3 which
|
|
||||||
is enough.
|
|
||||||
|
|
||||||
* Or build it yourself from: https://github.com/Openwsman/openwsman
|
|
||||||
|
|
||||||
* Enable the ``pxe_amt`` or ``agent_amt`` driver by adding it to the
|
|
||||||
configuration option ``enabled_drivers`` (typically located at
|
|
||||||
``/etc/ironic/ironic.conf``) and restart the ``ironic-conductor``
|
|
||||||
process::
|
|
||||||
|
|
||||||
service ironic-conductor restart
|
|
||||||
|
|
||||||
* Enroll an AMT node
|
|
||||||
|
|
||||||
* Specify these driver_info properties for the node: ``amt_password``,
|
|
||||||
``amt_address``, and ``amt_username``
|
|
||||||
|
|
||||||
* Boot an instance
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
It is recommended that nodes using the pxe_amt driver be deployed with the
|
|
||||||
`local boot`_ option. This is because the AMT firmware currently has no
|
|
||||||
support for setting a persistent boot device. Nodes deployed without the
|
|
||||||
`local boot`_ option could fail to boot if they are restarted outside of
|
|
||||||
Ironic's control (I.E. rebooted by a local user) because the node will
|
|
||||||
not attempt to PXE / network boot the kernel, using `local boot`_ solves this
|
|
||||||
known issue.
|
|
||||||
|
|
||||||
.. _`local boot`: http://docs.openstack.org/project-install-guide/baremetal/draft/advanced.html#local-boot-with-partition-images
|
|
@ -1,76 +0,0 @@
|
|||||||
.. _IBOOT:
|
|
||||||
|
|
||||||
============
|
|
||||||
iBoot driver
|
|
||||||
============
|
|
||||||
|
|
||||||
Overview
|
|
||||||
========
|
|
||||||
The iBoot power driver enables you to take advantage of power cycle
|
|
||||||
management of nodes using Dataprobe iBoot devices over the DxP protocol.
|
|
||||||
|
|
||||||
Drivers
|
|
||||||
=======
|
|
||||||
|
|
||||||
There are two iboot drivers:
|
|
||||||
|
|
||||||
* The ``pxe_iboot`` driver uses iBoot to control the power state of the
|
|
||||||
node, PXE/iPXE technology for booting and the iSCSI methodology for
|
|
||||||
deploying the node.
|
|
||||||
|
|
||||||
* The ``agent_iboot`` driver uses iBoot to control the power state of the
|
|
||||||
node, PXE/iPXE technology for booting and the Ironic Python Agent for
|
|
||||||
deploying an image to the node.
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
* ``python-iboot`` library should be installed - https://github.com/darkip/python-iboot
|
|
||||||
|
|
||||||
Tested platforms
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
* iBoot-G2
|
|
||||||
|
|
||||||
Configuring and enabling the driver
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
1. Add ``pxe_iboot`` and/or ``agent_iboot`` to the list of ``enabled_drivers``
|
|
||||||
in */etc/ironic/ironic.conf*. For example::
|
|
||||||
|
|
||||||
[DEFAULT]
|
|
||||||
...
|
|
||||||
enabled_drivers = pxe_iboot,agent_iboot
|
|
||||||
|
|
||||||
2. Restart the Ironic conductor service::
|
|
||||||
|
|
||||||
service ironic-conductor restart
|
|
||||||
|
|
||||||
Registering a node with the iBoot driver
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Nodes configured for the iBoot driver should have the ``driver`` property
|
|
||||||
set to ``pxe_iboot`` or ``agent_iboot``.
|
|
||||||
|
|
||||||
The following configuration values are also required in ``driver_info``:
|
|
||||||
|
|
||||||
- ``iboot_address``: The IP address of the iBoot PDU.
|
|
||||||
- ``iboot_username``: User name used for authentication.
|
|
||||||
- ``iboot_password``: Password used for authentication.
|
|
||||||
|
|
||||||
In addition, there are optional properties in ``driver_info``:
|
|
||||||
|
|
||||||
- ``iboot_port``: iBoot PDU port. Defaults to 9100.
|
|
||||||
- ``iboot_relay_id``: iBoot PDU relay ID. This option is useful in order
|
|
||||||
to support multiple nodes attached to a single PDU. Defaults to 1.
|
|
||||||
|
|
||||||
The following sequence of commands can be used to enroll a node with
|
|
||||||
the iBoot driver.
|
|
||||||
|
|
||||||
1. Create node::
|
|
||||||
|
|
||||||
ironic node-create -d pxe_iboot -i iboot_username=<username> -i iboot_password=<password> -i iboot_address=<address>
|
|
||||||
|
|
||||||
References
|
|
||||||
==========
|
|
||||||
.. [1] iBoot-G2 official documentation - http://dataprobe.com/support_iboot-g2.html
|
|
@ -1,128 +0,0 @@
|
|||||||
.. _WOL:
|
|
||||||
|
|
||||||
==================
|
|
||||||
Wake-On-Lan driver
|
|
||||||
==================
|
|
||||||
|
|
||||||
Overview
|
|
||||||
========
|
|
||||||
|
|
||||||
Wake-On-Lan is a standard that allows a computer to be powered on by a
|
|
||||||
network message. This is widely available and doesn't require any fancy
|
|
||||||
hardware to work with [1]_.
|
|
||||||
|
|
||||||
The Wake-On-Lan driver is a **testing** driver not meant for
|
|
||||||
production. And useful for users that wants to try Ironic with real
|
|
||||||
bare metal instead of virtual machines.
|
|
||||||
|
|
||||||
It's important to note that Wake-On-Lan is only capable of powering on
|
|
||||||
the machine. When power off is called the driver won't take any action
|
|
||||||
and will just log a message, the power off require manual intervention
|
|
||||||
to be performed.
|
|
||||||
|
|
||||||
Also, since Wake-On-Lan does not offer any means to determine the current
|
|
||||||
power state of the machine, the driver relies on the power state set in
|
|
||||||
the Ironic database. Any calls to the API to get the power state of the
|
|
||||||
node will return the value from the Ironic's database.
|
|
||||||
|
|
||||||
|
|
||||||
Drivers
|
|
||||||
=======
|
|
||||||
|
|
||||||
pxe_wol
|
|
||||||
^^^^^^^
|
|
||||||
|
|
||||||
Overview
|
|
||||||
~~~~~~~~
|
|
||||||
|
|
||||||
The ``pxe_wol`` driver uses the Wake-On-Lan technology to control the
|
|
||||||
power state, PXE/iPXE technology for booting and the iSCSI methodology
|
|
||||||
for deploying the node.
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
* Wake-On-Lan should be enabled in the BIOS
|
|
||||||
|
|
||||||
Configuring and Enabling the driver
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
1. Add ``pxe_wol`` to the list of ``enabled_drivers`` in
|
|
||||||
*/etc/ironic/ironic.conf*. For example::
|
|
||||||
|
|
||||||
[DEFAULT]
|
|
||||||
...
|
|
||||||
enabled_drivers = pxe_ipmitool,pxe_wol
|
|
||||||
|
|
||||||
2. Restart the Ironic conductor service::
|
|
||||||
|
|
||||||
service ironic-conductor restart
|
|
||||||
|
|
||||||
Registering a node with the Wake-On-Lan driver
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Nodes configured for Wake-On-Lan driver should have the ``driver``
|
|
||||||
property set to ``pxe_wol``.
|
|
||||||
|
|
||||||
The node should have at least one port registered with it because the
|
|
||||||
Wake-On-Lan driver will use the MAC address of the ports to create the
|
|
||||||
magic packet [2]_.
|
|
||||||
|
|
||||||
The following configuration values are optional and can be added to the
|
|
||||||
node's ``driver_info`` as needed to match the network configuration:
|
|
||||||
|
|
||||||
- ``wol_host``: The broadcast IP address; defaults to
|
|
||||||
**255.255.255.255**.
|
|
||||||
- ``wol_port``: The destination port; defaults to **9**.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Say the ``ironic-conductor`` is connected to more than one network and
|
|
||||||
the node you are trying to wake up is in the ``192.0.2.0/24`` range. The
|
|
||||||
``wol_host`` configuration should be set to **192.0.2.255** (the
|
|
||||||
broadcast IP) so the packets will get routed correctly.
|
|
||||||
|
|
||||||
The following sequence of commands can be used to enroll a node with
|
|
||||||
the Wake-On-Lan driver.
|
|
||||||
|
|
||||||
1. Create node::
|
|
||||||
|
|
||||||
ironic node-create -d pxe_wol [-i wol_host=<broadcast ip> [ -i
|
|
||||||
wol_port=<destination port>]]
|
|
||||||
|
|
||||||
The above command ``ironic node-create`` will return UUID of the node,
|
|
||||||
which is the value of *$NODE* in the following command.
|
|
||||||
|
|
||||||
2. Associate port with the node created::
|
|
||||||
|
|
||||||
ironic port-create -n $NODE -a <MAC address>
|
|
||||||
|
|
||||||
|
|
||||||
agent_wol
|
|
||||||
^^^^^^^^^
|
|
||||||
|
|
||||||
Overview
|
|
||||||
~~~~~~~~
|
|
||||||
|
|
||||||
The ``agent_wol`` driver uses the Wake-On-Lan technology to control the
|
|
||||||
power state, PXE/iPXE technology for booting and the Ironic Python Agent
|
|
||||||
for deploying the node.
|
|
||||||
|
|
||||||
Additional requirements
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
* Boot device order should be set to "PXE, DISK" in the BIOS setup
|
|
||||||
|
|
||||||
* BIOS must try next boot device if PXE boot failed
|
|
||||||
|
|
||||||
* Automated cleaning should be disabled, see :ref:`automated_cleaning`
|
|
||||||
|
|
||||||
* Node should be powered off before start of deploy
|
|
||||||
|
|
||||||
Configuration steps are the same as for ``pxe_wol`` driver, replace "pxe_wol"
|
|
||||||
with "agent_wol".
|
|
||||||
|
|
||||||
|
|
||||||
References
|
|
||||||
==========
|
|
||||||
.. [1] Wake-On-Lan - https://en.wikipedia.org/wiki/Wake-on-LAN
|
|
||||||
.. [2] Magic packet - https://en.wikipedia.org/wiki/Wake-on-LAN#Sending_the_magic_packet
|
|
@ -14,16 +14,6 @@ python-seamicroclient>=0.4.0
|
|||||||
UcsSdk==0.8.2.2
|
UcsSdk==0.8.2.2
|
||||||
python-dracclient>=0.1.0
|
python-dracclient>=0.1.0
|
||||||
|
|
||||||
# The amt driver imports a python module called "pywsman", but this does not
|
|
||||||
# exist on pypi.
|
|
||||||
# It is installed by the openwsman-python (on RH) or python-openwsman (on deb)
|
|
||||||
# package, from https://github.com/Openwsman/openwsman/blob/master/bindings/python/Makefile.am#L29
|
|
||||||
# There is *also* a "wsman" module on pypi ... but I think that's the wrong one.
|
|
||||||
|
|
||||||
# The iboot driver does not seem to have any available packages or pip modules,
|
|
||||||
# but the source is available here:
|
|
||||||
# https://github.com/darkip/python-iboot
|
|
||||||
|
|
||||||
# 'pxe_vbox' and 'agent_vbox' drivers require pyremotevbox library.
|
# 'pxe_vbox' and 'agent_vbox' drivers require pyremotevbox library.
|
||||||
# Refer documentation on how to install and configure this:
|
# Refer documentation on how to install and configure this:
|
||||||
# http://docs.openstack.org/developer/ironic/drivers/vbox.html
|
# http://docs.openstack.org/developer/ironic/drivers/vbox.html
|
||||||
|
@ -566,34 +566,6 @@
|
|||||||
#deploy_logs_swift_days_to_expire = 30
|
#deploy_logs_swift_days_to_expire = 30
|
||||||
|
|
||||||
|
|
||||||
[amt]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From ironic
|
|
||||||
#
|
|
||||||
|
|
||||||
# Protocol used for AMT endpoint (string value)
|
|
||||||
# Allowed values: http, https
|
|
||||||
#protocol = http
|
|
||||||
|
|
||||||
# Time interval (in seconds) for successive awake call to AMT
|
|
||||||
# interface, this depends on the IdleTimeout setting on AMT
|
|
||||||
# interface. AMT Interface will go to sleep after 60 seconds
|
|
||||||
# of inactivity by default. IdleTimeout=0 means AMT will not
|
|
||||||
# go to sleep at all. Setting awake_interval=0 will disable
|
|
||||||
# awake call. (integer value)
|
|
||||||
# Minimum value: 0
|
|
||||||
#awake_interval = 60
|
|
||||||
|
|
||||||
# Maximum number of times to attempt an AMT operation, before
|
|
||||||
# failing (integer value)
|
|
||||||
#max_attempts = 3
|
|
||||||
|
|
||||||
# Amount of time (in seconds) to wait, before retrying an AMT
|
|
||||||
# operation (integer value)
|
|
||||||
#action_wait = 10
|
|
||||||
|
|
||||||
|
|
||||||
[api]
|
[api]
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -1358,25 +1330,6 @@
|
|||||||
#username = <None>
|
#username = <None>
|
||||||
|
|
||||||
|
|
||||||
[iboot]
|
|
||||||
|
|
||||||
#
|
|
||||||
# From ironic
|
|
||||||
#
|
|
||||||
|
|
||||||
# Maximum retries for iBoot operations (integer value)
|
|
||||||
#max_retry = 3
|
|
||||||
|
|
||||||
# Time (in seconds) between retry attempts for iBoot
|
|
||||||
# operations (integer value)
|
|
||||||
#retry_interval = 1
|
|
||||||
|
|
||||||
# Time (in seconds) to sleep between when rebooting (powering
|
|
||||||
# off and on again). (integer value)
|
|
||||||
# Minimum value: 0
|
|
||||||
#reboot_delay = 5
|
|
||||||
|
|
||||||
|
|
||||||
[ilo]
|
[ilo]
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -430,15 +430,6 @@ class IPMIFailure(IronicException):
|
|||||||
_msg_fmt = _("IPMI call failed: %(cmd)s.")
|
_msg_fmt = _("IPMI call failed: %(cmd)s.")
|
||||||
|
|
||||||
|
|
||||||
class AMTConnectFailure(IronicException):
|
|
||||||
_msg_fmt = _("Failed to connect to AMT service. This could be caused "
|
|
||||||
"by the wrong amt_address or bad network environment.")
|
|
||||||
|
|
||||||
|
|
||||||
class AMTFailure(IronicException):
|
|
||||||
_msg_fmt = _("AMT call failed: %(cmd)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class MSFTOCSClientApiException(IronicException):
|
class MSFTOCSClientApiException(IronicException):
|
||||||
_msg_fmt = _("MSFT OCS call failed.")
|
_msg_fmt = _("MSFT OCS call failed.")
|
||||||
|
|
||||||
@ -659,10 +650,6 @@ class UcsConnectionError(IronicException):
|
|||||||
"%(node)s. Reason: %(error)s")
|
"%(node)s. Reason: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
class WolOperationError(IronicException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ImageUploadFailed(IronicException):
|
class ImageUploadFailed(IronicException):
|
||||||
_msg_fmt = _("Failed to upload %(image_name)s image to web server "
|
_msg_fmt = _("Failed to upload %(image_name)s image to web server "
|
||||||
"%(web_server)s, reason: %(reason)s")
|
"%(web_server)s, reason: %(reason)s")
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from ironic.conf import agent
|
from ironic.conf import agent
|
||||||
from ironic.conf import amt
|
|
||||||
from ironic.conf import api
|
from ironic.conf import api
|
||||||
from ironic.conf import audit
|
from ironic.conf import audit
|
||||||
from ironic.conf import cisco
|
from ironic.conf import cisco
|
||||||
@ -28,7 +27,6 @@ from ironic.conf import deploy
|
|||||||
from ironic.conf import dhcp
|
from ironic.conf import dhcp
|
||||||
from ironic.conf import drac
|
from ironic.conf import drac
|
||||||
from ironic.conf import glance
|
from ironic.conf import glance
|
||||||
from ironic.conf import iboot
|
|
||||||
from ironic.conf import ilo
|
from ironic.conf import ilo
|
||||||
from ironic.conf import inspector
|
from ironic.conf import inspector
|
||||||
from ironic.conf import ipmi
|
from ironic.conf import ipmi
|
||||||
@ -50,7 +48,6 @@ from ironic.conf import virtualbox
|
|||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
agent.register_opts(CONF)
|
agent.register_opts(CONF)
|
||||||
amt.register_opts(CONF)
|
|
||||||
api.register_opts(CONF)
|
api.register_opts(CONF)
|
||||||
audit.register_opts(CONF)
|
audit.register_opts(CONF)
|
||||||
cisco.register_opts(CONF)
|
cisco.register_opts(CONF)
|
||||||
@ -62,7 +59,6 @@ deploy.register_opts(CONF)
|
|||||||
drac.register_opts(CONF)
|
drac.register_opts(CONF)
|
||||||
dhcp.register_opts(CONF)
|
dhcp.register_opts(CONF)
|
||||||
glance.register_opts(CONF)
|
glance.register_opts(CONF)
|
||||||
iboot.register_opts(CONF)
|
|
||||||
ilo.register_opts(CONF)
|
ilo.register_opts(CONF)
|
||||||
inspector.register_opts(CONF)
|
inspector.register_opts(CONF)
|
||||||
ipmi.register_opts(CONF)
|
ipmi.register_opts(CONF)
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
# Copyright 2016 Intel Corporation
|
|
||||||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# 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 oslo_config import cfg
|
|
||||||
|
|
||||||
from ironic.common.i18n import _
|
|
||||||
|
|
||||||
|
|
||||||
opts = [
|
|
||||||
cfg.StrOpt('protocol',
|
|
||||||
default='http',
|
|
||||||
choices=['http', 'https'],
|
|
||||||
help=_('Protocol used for AMT endpoint')),
|
|
||||||
cfg.IntOpt('awake_interval',
|
|
||||||
default=60,
|
|
||||||
min=0,
|
|
||||||
help=_('Time interval (in seconds) for successive awake call '
|
|
||||||
'to AMT interface, this depends on the IdleTimeout '
|
|
||||||
'setting on AMT interface. AMT Interface will go to '
|
|
||||||
'sleep after 60 seconds of inactivity by default. '
|
|
||||||
'IdleTimeout=0 means AMT will not go to sleep at all. '
|
|
||||||
'Setting awake_interval=0 will disable awake call.')),
|
|
||||||
cfg.IntOpt('max_attempts',
|
|
||||||
default=3,
|
|
||||||
help=_('Maximum number of times to attempt an AMT operation, '
|
|
||||||
'before failing')),
|
|
||||||
cfg.IntOpt('action_wait',
|
|
||||||
default=10,
|
|
||||||
help=_('Amount of time (in seconds) to wait, before retrying '
|
|
||||||
'an AMT operation'))
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
|
||||||
conf.register_opts(opts, group='amt')
|
|
@ -1,42 +0,0 @@
|
|||||||
# Copyright 2016 Intel Corporation
|
|
||||||
# Copyright 2014 Red Hat, Inc.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# 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 oslo_config import cfg
|
|
||||||
|
|
||||||
from ironic.common.i18n import _
|
|
||||||
|
|
||||||
opts = [
|
|
||||||
cfg.IntOpt('max_retry',
|
|
||||||
default=3,
|
|
||||||
help=_('Maximum retries for iBoot operations')),
|
|
||||||
cfg.IntOpt('retry_interval',
|
|
||||||
default=1,
|
|
||||||
help=_('Time (in seconds) between retry attempts for iBoot '
|
|
||||||
'operations')),
|
|
||||||
cfg.IntOpt('reboot_delay',
|
|
||||||
default=5,
|
|
||||||
min=0,
|
|
||||||
help=_('Time (in seconds) to sleep between when rebooting '
|
|
||||||
'(powering off and on again).'))
|
|
||||||
]
|
|
||||||
|
|
||||||
opt_group = cfg.OptGroup(name='iboot',
|
|
||||||
title='Options for the iBoot power driver')
|
|
||||||
|
|
||||||
|
|
||||||
def register_opts(conf):
|
|
||||||
conf.register_group(opt_group)
|
|
||||||
conf.register_opts(opts, group=opt_group)
|
|
@ -31,7 +31,6 @@ _default_opt_lists = [
|
|||||||
_opts = [
|
_opts = [
|
||||||
('DEFAULT', itertools.chain(*_default_opt_lists)),
|
('DEFAULT', itertools.chain(*_default_opt_lists)),
|
||||||
('agent', ironic.conf.agent.opts),
|
('agent', ironic.conf.agent.opts),
|
||||||
('amt', ironic.conf.amt.opts),
|
|
||||||
('api', ironic.conf.api.opts),
|
('api', ironic.conf.api.opts),
|
||||||
('audit', ironic.conf.audit.opts),
|
('audit', ironic.conf.audit.opts),
|
||||||
('cimc', ironic.conf.cisco.cimc_opts),
|
('cimc', ironic.conf.cisco.cimc_opts),
|
||||||
@ -43,7 +42,6 @@ _opts = [
|
|||||||
('dhcp', ironic.conf.dhcp.opts),
|
('dhcp', ironic.conf.dhcp.opts),
|
||||||
('drac', ironic.conf.drac.opts),
|
('drac', ironic.conf.drac.opts),
|
||||||
('glance', ironic.conf.glance.list_opts()),
|
('glance', ironic.conf.glance.list_opts()),
|
||||||
('iboot', ironic.conf.iboot.opts),
|
|
||||||
('ilo', ironic.conf.ilo.opts),
|
('ilo', ironic.conf.ilo.opts),
|
||||||
('inspector', ironic.conf.inspector.list_opts()),
|
('inspector', ironic.conf.inspector.list_opts()),
|
||||||
('ipmi', ironic.conf.ipmi.opts),
|
('ipmi', ironic.conf.ipmi.opts),
|
||||||
|
@ -18,11 +18,8 @@ from ironic.common import exception
|
|||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
from ironic.drivers import base
|
from ironic.drivers import base
|
||||||
from ironic.drivers.modules import agent
|
from ironic.drivers.modules import agent
|
||||||
from ironic.drivers.modules.amt import management as amt_management
|
|
||||||
from ironic.drivers.modules.amt import power as amt_power
|
|
||||||
from ironic.drivers.modules.cimc import management as cimc_mgmt
|
from ironic.drivers.modules.cimc import management as cimc_mgmt
|
||||||
from ironic.drivers.modules.cimc import power as cimc_power
|
from ironic.drivers.modules.cimc import power as cimc_power
|
||||||
from ironic.drivers.modules import iboot
|
|
||||||
from ironic.drivers.modules import inspector
|
from ironic.drivers.modules import inspector
|
||||||
from ironic.drivers.modules import ipminative
|
from ironic.drivers.modules import ipminative
|
||||||
from ironic.drivers.modules import ipmitool
|
from ironic.drivers.modules import ipmitool
|
||||||
@ -31,7 +28,6 @@ from ironic.drivers.modules import ssh
|
|||||||
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
||||||
from ironic.drivers.modules.ucs import power as ucs_power
|
from ironic.drivers.modules.ucs import power as ucs_power
|
||||||
from ironic.drivers.modules import virtualbox
|
from ironic.drivers.modules import virtualbox
|
||||||
from ironic.drivers.modules import wol
|
|
||||||
|
|
||||||
|
|
||||||
class AgentAndIPMIToolDriver(base.BaseDriver):
|
class AgentAndIPMIToolDriver(base.BaseDriver):
|
||||||
@ -155,30 +151,6 @@ class AgentAndVirtualBoxDriver(base.BaseDriver):
|
|||||||
self.raid = agent.AgentRAID()
|
self.raid = agent.AgentRAID()
|
||||||
|
|
||||||
|
|
||||||
class AgentAndAMTDriver(base.BaseDriver):
|
|
||||||
"""Agent + AMT driver.
|
|
||||||
|
|
||||||
This driver implements the `core` functionality, combining
|
|
||||||
:class:`ironic.drivers.amt.AMTPower` for power on/off and reboot with
|
|
||||||
:class:`ironic.drivers.modules.agent_deploy.AgentDeploy` for image
|
|
||||||
deployment. Implementations are in those respective classes; this
|
|
||||||
class is merely the glue between them.
|
|
||||||
"""
|
|
||||||
|
|
||||||
supported = False
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if not importutils.try_import('pywsman'):
|
|
||||||
raise exception.DriverLoadError(
|
|
||||||
driver=self.__class__.__name__,
|
|
||||||
reason=_("Unable to import pywsman library"))
|
|
||||||
self.power = amt_power.AMTPower()
|
|
||||||
self.boot = pxe.PXEBoot()
|
|
||||||
self.deploy = agent.AgentDeploy()
|
|
||||||
self.management = amt_management.AMTManagement()
|
|
||||||
self.vendor = agent.AgentVendorInterface()
|
|
||||||
|
|
||||||
|
|
||||||
class AgentAndUcsDriver(base.BaseDriver):
|
class AgentAndUcsDriver(base.BaseDriver):
|
||||||
"""Agent + Cisco UCSM driver.
|
"""Agent + Cisco UCSM driver.
|
||||||
|
|
||||||
@ -225,46 +197,3 @@ class AgentAndCIMCDriver(base.BaseDriver):
|
|||||||
self.management = cimc_mgmt.CIMCManagement()
|
self.management = cimc_mgmt.CIMCManagement()
|
||||||
self.inspect = inspector.Inspector.create_if_enabled(
|
self.inspect = inspector.Inspector.create_if_enabled(
|
||||||
'AgentAndCIMCDriver')
|
'AgentAndCIMCDriver')
|
||||||
|
|
||||||
|
|
||||||
class AgentAndWakeOnLanDriver(base.BaseDriver):
|
|
||||||
"""Agent + WakeOnLan driver.
|
|
||||||
|
|
||||||
This driver implements the `core` functionality, combining
|
|
||||||
:class:`ironic.drivers.modules.wol.WakeOnLanPower` for power on with
|
|
||||||
:class:'ironic.driver.modules.agent.AgentDeploy' (for image deployment.)
|
|
||||||
Implementations are in those respective classes;
|
|
||||||
this class is merely the glue between them.
|
|
||||||
"""
|
|
||||||
|
|
||||||
supported = False
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.power = wol.WakeOnLanPower()
|
|
||||||
self.boot = pxe.PXEBoot()
|
|
||||||
self.deploy = agent.AgentDeploy()
|
|
||||||
self.vendor = agent.AgentVendorInterface()
|
|
||||||
|
|
||||||
|
|
||||||
class AgentAndIBootDriver(base.BaseDriver):
|
|
||||||
"""Agent + IBoot PDU driver.
|
|
||||||
|
|
||||||
This driver implements the `core` functionality, combining
|
|
||||||
:class:`ironic.drivers.modules.iboot.IBootPower` for power
|
|
||||||
on/off and reboot with
|
|
||||||
:class:'ironic.driver.modules.agent.AgentDeploy' (for image deployment.)
|
|
||||||
Implementations are in those respective classes;
|
|
||||||
this class is merely the glue between them.
|
|
||||||
"""
|
|
||||||
|
|
||||||
supported = False
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if not importutils.try_import('iboot'):
|
|
||||||
raise exception.DriverLoadError(
|
|
||||||
driver=self.__class__.__name__,
|
|
||||||
reason=_("Unable to import iboot library"))
|
|
||||||
self.power = iboot.IBootPower()
|
|
||||||
self.boot = pxe.PXEBoot()
|
|
||||||
self.deploy = agent.AgentDeploy()
|
|
||||||
self.vendor = agent.AgentVendorInterface()
|
|
||||||
|
@ -23,8 +23,6 @@ from ironic.common import exception
|
|||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
from ironic.drivers import base
|
from ironic.drivers import base
|
||||||
from ironic.drivers.modules import agent
|
from ironic.drivers.modules import agent
|
||||||
from ironic.drivers.modules.amt import management as amt_mgmt
|
|
||||||
from ironic.drivers.modules.amt import power as amt_power
|
|
||||||
from ironic.drivers.modules.cimc import management as cimc_mgmt
|
from ironic.drivers.modules.cimc import management as cimc_mgmt
|
||||||
from ironic.drivers.modules.cimc import power as cimc_power
|
from ironic.drivers.modules.cimc import power as cimc_power
|
||||||
from ironic.drivers.modules.drac import deploy as drac_deploy
|
from ironic.drivers.modules.drac import deploy as drac_deploy
|
||||||
@ -34,7 +32,6 @@ from ironic.drivers.modules.drac import power as drac_power
|
|||||||
from ironic.drivers.modules.drac import raid as drac_raid
|
from ironic.drivers.modules.drac import raid as drac_raid
|
||||||
from ironic.drivers.modules.drac import vendor_passthru as drac_vendor
|
from ironic.drivers.modules.drac import vendor_passthru as drac_vendor
|
||||||
from ironic.drivers.modules import fake
|
from ironic.drivers.modules import fake
|
||||||
from ironic.drivers.modules import iboot
|
|
||||||
from ironic.drivers.modules.ilo import inspect as ilo_inspect
|
from ironic.drivers.modules.ilo import inspect as ilo_inspect
|
||||||
from ironic.drivers.modules.ilo import management as ilo_management
|
from ironic.drivers.modules.ilo import management as ilo_management
|
||||||
from ironic.drivers.modules.ilo import power as ilo_power
|
from ironic.drivers.modules.ilo import power as ilo_power
|
||||||
@ -57,7 +54,6 @@ from ironic.drivers.modules import ssh
|
|||||||
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
||||||
from ironic.drivers.modules.ucs import power as ucs_power
|
from ironic.drivers.modules.ucs import power as ucs_power
|
||||||
from ironic.drivers.modules import virtualbox
|
from ironic.drivers.modules import virtualbox
|
||||||
from ironic.drivers.modules import wol
|
|
||||||
from ironic.drivers import utils
|
from ironic.drivers import utils
|
||||||
|
|
||||||
|
|
||||||
@ -171,20 +167,6 @@ class FakeAgentDriver(base.BaseDriver):
|
|||||||
self.raid = agent.AgentRAID()
|
self.raid = agent.AgentRAID()
|
||||||
|
|
||||||
|
|
||||||
class FakeIBootDriver(base.BaseDriver):
|
|
||||||
"""Fake iBoot driver."""
|
|
||||||
|
|
||||||
supported = False
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if not importutils.try_import('iboot'):
|
|
||||||
raise exception.DriverLoadError(
|
|
||||||
driver=self.__class__.__name__,
|
|
||||||
reason=_("Unable to import iboot library"))
|
|
||||||
self.power = iboot.IBootPower()
|
|
||||||
self.deploy = fake.FakeDeploy()
|
|
||||||
|
|
||||||
|
|
||||||
class FakeIloDriver(base.BaseDriver):
|
class FakeIloDriver(base.BaseDriver):
|
||||||
"""Fake iLO driver, used in testing."""
|
"""Fake iLO driver, used in testing."""
|
||||||
|
|
||||||
@ -272,21 +254,6 @@ class FakeIPMIToolInspectorDriver(base.BaseDriver):
|
|||||||
self.inspect = inspector.Inspector()
|
self.inspect = inspector.Inspector()
|
||||||
|
|
||||||
|
|
||||||
class FakeAMTDriver(base.BaseDriver):
|
|
||||||
"""Fake AMT driver."""
|
|
||||||
|
|
||||||
supported = False
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if not importutils.try_import('pywsman'):
|
|
||||||
raise exception.DriverLoadError(
|
|
||||||
driver=self.__class__.__name__,
|
|
||||||
reason=_("Unable to import pywsman library"))
|
|
||||||
self.power = amt_power.AMTPower()
|
|
||||||
self.deploy = fake.FakeDeploy()
|
|
||||||
self.management = amt_mgmt.AMTManagement()
|
|
||||||
|
|
||||||
|
|
||||||
class FakeMSFTOCSDriver(base.BaseDriver):
|
class FakeMSFTOCSDriver(base.BaseDriver):
|
||||||
"""Fake MSFT OCS driver."""
|
"""Fake MSFT OCS driver."""
|
||||||
|
|
||||||
@ -324,16 +291,6 @@ class FakeCIMCDriver(base.BaseDriver):
|
|||||||
self.management = cimc_mgmt.CIMCManagement()
|
self.management = cimc_mgmt.CIMCManagement()
|
||||||
|
|
||||||
|
|
||||||
class FakeWakeOnLanDriver(base.BaseDriver):
|
|
||||||
"""Fake Wake-On-Lan driver."""
|
|
||||||
|
|
||||||
supported = False
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.power = wol.WakeOnLanPower()
|
|
||||||
self.deploy = fake.FakeDeploy()
|
|
||||||
|
|
||||||
|
|
||||||
class FakeOneViewDriver(base.BaseDriver):
|
class FakeOneViewDriver(base.BaseDriver):
|
||||||
"""Fake OneView driver. For testing purposes. """
|
"""Fake OneView driver. For testing purposes. """
|
||||||
|
|
||||||
|
@ -1,232 +0,0 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
"""
|
|
||||||
Common functionalities for AMT Driver
|
|
||||||
"""
|
|
||||||
import time
|
|
||||||
from xml import etree
|
|
||||||
|
|
||||||
from oslo_concurrency import processutils
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_utils import importutils
|
|
||||||
import six
|
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.common.i18n import _, _LE
|
|
||||||
from ironic.common import utils
|
|
||||||
from ironic.conf import CONF
|
|
||||||
|
|
||||||
|
|
||||||
pywsman = importutils.try_import('pywsman')
|
|
||||||
|
|
||||||
_SOAP_ENVELOPE = 'http://www.w3.org/2003/05/soap-envelope'
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
REQUIRED_PROPERTIES = {
|
|
||||||
'amt_address': _('IP address or host name of the node. Required.'),
|
|
||||||
'amt_password': _('Password. Required.'),
|
|
||||||
'amt_username': _('Username to log into AMT system. Required.'),
|
|
||||||
}
|
|
||||||
OPTIONAL_PROPERTIES = {
|
|
||||||
'amt_protocol': _('Protocol used for AMT endpoint. one of http, https; '
|
|
||||||
'default is "http". Optional.'),
|
|
||||||
}
|
|
||||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
|
||||||
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
|
||||||
|
|
||||||
# TODO(lintan): More boot devices are supported by AMT, but not useful
|
|
||||||
# currently. Add them in the future.
|
|
||||||
BOOT_DEVICES_MAPPING = {
|
|
||||||
boot_devices.PXE: 'Intel(r) AMT: Force PXE Boot',
|
|
||||||
boot_devices.DISK: 'Intel(r) AMT: Force Hard-drive Boot',
|
|
||||||
boot_devices.CDROM: 'Intel(r) AMT: Force CD/DVD Boot',
|
|
||||||
}
|
|
||||||
DEFAULT_BOOT_DEVICE = boot_devices.DISK
|
|
||||||
|
|
||||||
AMT_PROTOCOL_PORT_MAP = {
|
|
||||||
'http': 16992,
|
|
||||||
'https': 16993,
|
|
||||||
}
|
|
||||||
|
|
||||||
# ReturnValue constants
|
|
||||||
RET_SUCCESS = '0'
|
|
||||||
|
|
||||||
# A dict cache last awake call to AMT Interface
|
|
||||||
AMT_AWAKE_CACHE = {}
|
|
||||||
|
|
||||||
|
|
||||||
class Client(object):
|
|
||||||
"""AMT client.
|
|
||||||
|
|
||||||
Create a pywsman client to connect to the target server
|
|
||||||
"""
|
|
||||||
def __init__(self, address, protocol, username, password):
|
|
||||||
port = AMT_PROTOCOL_PORT_MAP[protocol]
|
|
||||||
path = '/wsman'
|
|
||||||
self.client = pywsman.Client(address, port, path, protocol,
|
|
||||||
username, password)
|
|
||||||
|
|
||||||
def wsman_get(self, resource_uri, options=None):
|
|
||||||
"""Get target server info
|
|
||||||
|
|
||||||
:param options: client options
|
|
||||||
:param resource_uri: a URI to an XML schema
|
|
||||||
:returns: XmlDoc object
|
|
||||||
:raises: AMTFailure if get unexpected response.
|
|
||||||
:raises: AMTConnectFailure if unable to connect to the server.
|
|
||||||
"""
|
|
||||||
if options is None:
|
|
||||||
options = pywsman.ClientOptions()
|
|
||||||
doc = self.client.get(options, resource_uri)
|
|
||||||
item = 'Fault'
|
|
||||||
fault = xml_find(doc, _SOAP_ENVELOPE, item)
|
|
||||||
if fault is not None:
|
|
||||||
LOG.exception(_LE('Call to AMT with URI %(uri)s failed: '
|
|
||||||
'got Fault %(fault)s'),
|
|
||||||
{'uri': resource_uri, 'fault': fault.text})
|
|
||||||
raise exception.AMTFailure(cmd='wsman_get')
|
|
||||||
return doc
|
|
||||||
|
|
||||||
def wsman_invoke(self, options, resource_uri, method, data=None):
|
|
||||||
"""Invoke method on target server
|
|
||||||
|
|
||||||
:param options: client options
|
|
||||||
:param resource_uri: a URI to an XML schema
|
|
||||||
:param method: invoke method
|
|
||||||
:param data: a XmlDoc as invoke input
|
|
||||||
:returns: XmlDoc object
|
|
||||||
:raises: AMTFailure if get unexpected response.
|
|
||||||
:raises: AMTConnectFailure if unable to connect to the server.
|
|
||||||
"""
|
|
||||||
if data is None:
|
|
||||||
doc = self.client.invoke(options, resource_uri, method)
|
|
||||||
else:
|
|
||||||
doc = self.client.invoke(options, resource_uri, method, data)
|
|
||||||
item = "ReturnValue"
|
|
||||||
return_value = xml_find(doc, resource_uri, item).text
|
|
||||||
if return_value != RET_SUCCESS:
|
|
||||||
LOG.exception(_LE("Call to AMT with URI %(uri)s and "
|
|
||||||
"method %(method)s failed: return value "
|
|
||||||
"was %(value)s"),
|
|
||||||
{'uri': resource_uri, 'method': method,
|
|
||||||
'value': return_value})
|
|
||||||
raise exception.AMTFailure(cmd='wsman_invoke')
|
|
||||||
return doc
|
|
||||||
|
|
||||||
|
|
||||||
def parse_driver_info(node):
|
|
||||||
"""Parses and creates AMT driver info
|
|
||||||
|
|
||||||
:param node: an Ironic node object.
|
|
||||||
:returns: AMT driver info.
|
|
||||||
:raises: MissingParameterValue if any required parameters are missing.
|
|
||||||
:raises: InvalidParameterValue if any parameters have invalid values.
|
|
||||||
"""
|
|
||||||
|
|
||||||
info = node.driver_info or {}
|
|
||||||
d_info = {}
|
|
||||||
missing_info = []
|
|
||||||
|
|
||||||
for param in REQUIRED_PROPERTIES:
|
|
||||||
value = info.get(param)
|
|
||||||
if value:
|
|
||||||
if not isinstance(value, six.binary_type):
|
|
||||||
value = value.encode()
|
|
||||||
d_info[param[4:]] = value
|
|
||||||
else:
|
|
||||||
missing_info.append(param)
|
|
||||||
|
|
||||||
if missing_info:
|
|
||||||
raise exception.MissingParameterValue(_(
|
|
||||||
"AMT driver requires the following to be set in "
|
|
||||||
"node's driver_info: %s.") % missing_info)
|
|
||||||
|
|
||||||
d_info['uuid'] = node.uuid
|
|
||||||
param = 'amt_protocol'
|
|
||||||
protocol = info.get(param, CONF.amt.get(param[4:]))
|
|
||||||
if protocol not in AMT_PROTOCOL_PORT_MAP:
|
|
||||||
raise exception.InvalidParameterValue(
|
|
||||||
_("Invalid protocol %s.") % protocol)
|
|
||||||
if not isinstance(value, six.binary_type):
|
|
||||||
protocol = protocol.encode()
|
|
||||||
d_info[param[4:]] = protocol
|
|
||||||
|
|
||||||
return d_info
|
|
||||||
|
|
||||||
|
|
||||||
def get_wsman_client(node):
|
|
||||||
"""Return a AMT Client object
|
|
||||||
|
|
||||||
:param node: an Ironic node object.
|
|
||||||
:returns: a Client object
|
|
||||||
:raises: MissingParameterValue if any required parameters are missing.
|
|
||||||
:raises: InvalidParameterValue if any parameters have invalid values.
|
|
||||||
"""
|
|
||||||
driver_info = parse_driver_info(node)
|
|
||||||
client = Client(address=driver_info['address'],
|
|
||||||
protocol=driver_info['protocol'],
|
|
||||||
username=driver_info['username'],
|
|
||||||
password=driver_info['password'])
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
|
||||||
def xml_find(doc, namespace, item):
|
|
||||||
"""Find the first element with namespace and item, in the XML doc
|
|
||||||
|
|
||||||
:param doc: a doc object.
|
|
||||||
:param namespace: the namespace of the element.
|
|
||||||
:param item: the element name.
|
|
||||||
:returns: the element object or None
|
|
||||||
:raises: AMTConnectFailure if unable to connect to the server.
|
|
||||||
"""
|
|
||||||
if doc is None:
|
|
||||||
raise exception.AMTConnectFailure()
|
|
||||||
tree = etree.ElementTree.fromstring(doc.root().string())
|
|
||||||
query = ('.//{%(namespace)s}%(item)s' % {'namespace': namespace,
|
|
||||||
'item': item})
|
|
||||||
return tree.find(query)
|
|
||||||
|
|
||||||
|
|
||||||
def awake_amt_interface(node):
|
|
||||||
"""Wake up AMT interface.
|
|
||||||
|
|
||||||
AMT interface goes to sleep after a period of time if the host is off.
|
|
||||||
This method will ping AMT interface to wake it up. Because there is
|
|
||||||
no guarantee that the AMT address in driver_info is correct, only
|
|
||||||
ping the IP five times which is enough to wake it up.
|
|
||||||
|
|
||||||
:param node: an Ironic node object.
|
|
||||||
:raises: AMTConnectFailure if unable to connect to the server.
|
|
||||||
"""
|
|
||||||
awake_interval = CONF.amt.awake_interval
|
|
||||||
if awake_interval == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
now = time.time()
|
|
||||||
last_awake = AMT_AWAKE_CACHE.get(node.uuid, 0)
|
|
||||||
if now - last_awake > awake_interval:
|
|
||||||
cmd_args = ['ping', '-i', 0.2, '-c', 5,
|
|
||||||
node.driver_info['amt_address']]
|
|
||||||
try:
|
|
||||||
utils.execute(*cmd_args)
|
|
||||||
except processutils.ProcessExecutionError as err:
|
|
||||||
LOG.error(_LE('Unable to awake AMT interface on node '
|
|
||||||
'%(node_id)s. Error: %(error)s'),
|
|
||||||
{'node_id': node.uuid, 'error': err})
|
|
||||||
raise exception.AMTConnectFailure()
|
|
||||||
else:
|
|
||||||
LOG.debug(('Successfully awakened AMT interface on node '
|
|
||||||
'%(node_id)s.'), {'node_id': node.uuid})
|
|
||||||
AMT_AWAKE_CACHE[node.uuid] = now
|
|
@ -1,247 +0,0 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
AMT Management Driver
|
|
||||||
"""
|
|
||||||
import copy
|
|
||||||
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_utils import excutils
|
|
||||||
from oslo_utils import importutils
|
|
||||||
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.common.i18n import _, _LE, _LI
|
|
||||||
from ironic.conductor import task_manager
|
|
||||||
from ironic.drivers import base
|
|
||||||
from ironic.drivers.modules.amt import common as amt_common
|
|
||||||
from ironic.drivers.modules.amt import resource_uris
|
|
||||||
|
|
||||||
pywsman = importutils.try_import('pywsman')
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
_ADDRESS = 'http://schemas.xmlsoap.org/ws/2004/08/addressing'
|
|
||||||
_ANONYMOUS = 'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous'
|
|
||||||
_WSMAN = 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd'
|
|
||||||
|
|
||||||
|
|
||||||
def _generate_change_boot_order_input(device):
|
|
||||||
"""Generate Xmldoc as change_boot_order input.
|
|
||||||
|
|
||||||
This generates a Xmldoc used as input for change_boot_order.
|
|
||||||
|
|
||||||
:param device: the boot device.
|
|
||||||
:returns: Xmldoc.
|
|
||||||
"""
|
|
||||||
method_input = "ChangeBootOrder_INPUT"
|
|
||||||
namespace = resource_uris.CIM_BootConfigSetting
|
|
||||||
doc = pywsman.XmlDoc(method_input)
|
|
||||||
root = doc.root()
|
|
||||||
root.set_ns(namespace)
|
|
||||||
|
|
||||||
child = root.add(namespace, 'Source', None)
|
|
||||||
child.add(_ADDRESS, 'Address', _ANONYMOUS)
|
|
||||||
|
|
||||||
grand_child = child.add(_ADDRESS, 'ReferenceParameters', None)
|
|
||||||
grand_child.add(_WSMAN, 'ResourceURI', resource_uris.CIM_BootSourceSetting)
|
|
||||||
g_grand_child = grand_child.add(_WSMAN, 'SelectorSet', None)
|
|
||||||
g_g_grand_child = g_grand_child.add(_WSMAN, 'Selector', device)
|
|
||||||
g_g_grand_child.attr_add(_WSMAN, 'Name', 'InstanceID')
|
|
||||||
return doc
|
|
||||||
|
|
||||||
|
|
||||||
def _set_boot_device_order(node, boot_device):
|
|
||||||
"""Set boot device order configuration of AMT Client.
|
|
||||||
|
|
||||||
:param node: a node object
|
|
||||||
:param boot_device: the boot device
|
|
||||||
:raises: AMTFailure
|
|
||||||
:raises: AMTConnectFailure
|
|
||||||
"""
|
|
||||||
amt_common.awake_amt_interface(node)
|
|
||||||
client = amt_common.get_wsman_client(node)
|
|
||||||
device = amt_common.BOOT_DEVICES_MAPPING[boot_device]
|
|
||||||
doc = _generate_change_boot_order_input(device)
|
|
||||||
|
|
||||||
method = 'ChangeBootOrder'
|
|
||||||
|
|
||||||
options = pywsman.ClientOptions()
|
|
||||||
options.add_selector('InstanceID', 'Intel(r) AMT: Boot Configuration 0')
|
|
||||||
|
|
||||||
try:
|
|
||||||
client.wsman_invoke(options, resource_uris.CIM_BootConfigSetting,
|
|
||||||
method, doc)
|
|
||||||
except (exception.AMTFailure, exception.AMTConnectFailure) as e:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
LOG.exception(_LE("Failed to set boot device %(boot_device)s for "
|
|
||||||
"node %(node_id)s with error: %(error)s."),
|
|
||||||
{'boot_device': boot_device, 'node_id': node.uuid,
|
|
||||||
'error': e})
|
|
||||||
else:
|
|
||||||
LOG.info(_LI("Successfully set boot device %(boot_device)s for "
|
|
||||||
"node %(node_id)s"),
|
|
||||||
{'boot_device': boot_device, 'node_id': node.uuid})
|
|
||||||
|
|
||||||
|
|
||||||
def _generate_enable_boot_config_input():
|
|
||||||
"""Generate Xmldoc as enable_boot_config input.
|
|
||||||
|
|
||||||
This generates a Xmldoc used as input for enable_boot_config.
|
|
||||||
|
|
||||||
:returns: Xmldoc.
|
|
||||||
"""
|
|
||||||
method_input = "SetBootConfigRole_INPUT"
|
|
||||||
namespace = resource_uris.CIM_BootService
|
|
||||||
doc = pywsman.XmlDoc(method_input)
|
|
||||||
root = doc.root()
|
|
||||||
root.set_ns(namespace)
|
|
||||||
|
|
||||||
child = root.add(namespace, 'BootConfigSetting', None)
|
|
||||||
child.add(_ADDRESS, 'Address', _ANONYMOUS)
|
|
||||||
|
|
||||||
grand_child = child.add(_ADDRESS, 'ReferenceParameters', None)
|
|
||||||
grand_child.add(_WSMAN, 'ResourceURI', resource_uris.CIM_BootConfigSetting)
|
|
||||||
g_grand_child = grand_child.add(_WSMAN, 'SelectorSet', None)
|
|
||||||
g_g_grand_child = g_grand_child.add(_WSMAN, 'Selector',
|
|
||||||
'Intel(r) AMT: Boot Configuration 0')
|
|
||||||
g_g_grand_child.attr_add(_WSMAN, 'Name', 'InstanceID')
|
|
||||||
root.add(namespace, 'Role', '1')
|
|
||||||
return doc
|
|
||||||
|
|
||||||
|
|
||||||
def _enable_boot_config(node):
|
|
||||||
"""Enable boot configuration of AMT Client.
|
|
||||||
|
|
||||||
:param node: a node object
|
|
||||||
:raises: AMTFailure
|
|
||||||
:raises: AMTConnectFailure
|
|
||||||
"""
|
|
||||||
amt_common.awake_amt_interface(node)
|
|
||||||
client = amt_common.get_wsman_client(node)
|
|
||||||
method = 'SetBootConfigRole'
|
|
||||||
doc = _generate_enable_boot_config_input()
|
|
||||||
options = pywsman.ClientOptions()
|
|
||||||
options.add_selector('Name', 'Intel(r) AMT Boot Service')
|
|
||||||
try:
|
|
||||||
client.wsman_invoke(options, resource_uris.CIM_BootService,
|
|
||||||
method, doc)
|
|
||||||
except (exception.AMTFailure, exception.AMTConnectFailure) as e:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
LOG.exception(_LE("Failed to enable boot config for node "
|
|
||||||
"%(node_id)s with error: %(error)s."),
|
|
||||||
{'node_id': node.uuid, 'error': e})
|
|
||||||
else:
|
|
||||||
LOG.info(_LI("Successfully enabled boot config for node %(node_id)s."),
|
|
||||||
{'node_id': node.uuid})
|
|
||||||
|
|
||||||
|
|
||||||
class AMTManagement(base.ManagementInterface):
|
|
||||||
|
|
||||||
def get_properties(self):
|
|
||||||
return copy.deepcopy(amt_common.COMMON_PROPERTIES)
|
|
||||||
|
|
||||||
def validate(self, task):
|
|
||||||
"""Validate the driver_info in the node
|
|
||||||
|
|
||||||
Check if the driver_info contains correct required fields
|
|
||||||
|
|
||||||
:param task: a TaskManager instance contains the target node
|
|
||||||
:raises: MissingParameterValue if any required parameters are missing.
|
|
||||||
:raises: InvalidParameterValue if any parameters have invalid values.
|
|
||||||
"""
|
|
||||||
# FIXME(lintan): validate hangs if unable to reach AMT, so dont
|
|
||||||
# connect to the node until bug 1314961 is resolved.
|
|
||||||
amt_common.parse_driver_info(task.node)
|
|
||||||
|
|
||||||
def get_supported_boot_devices(self, task):
|
|
||||||
"""Get a list of the supported boot devices.
|
|
||||||
|
|
||||||
:param task: a task from TaskManager.
|
|
||||||
:returns: A list with the supported boot devices.
|
|
||||||
"""
|
|
||||||
return list(amt_common.BOOT_DEVICES_MAPPING)
|
|
||||||
|
|
||||||
@task_manager.require_exclusive_lock
|
|
||||||
def set_boot_device(self, task, device, persistent=False):
|
|
||||||
"""Set the boot device for the task's node.
|
|
||||||
|
|
||||||
Set the boot device to use on next boot of the node.
|
|
||||||
|
|
||||||
:param task: a task from TaskManager.
|
|
||||||
:param device: the boot device
|
|
||||||
:param persistent: Boolean value. True if the boot device will
|
|
||||||
persist to all future boots, False if not.
|
|
||||||
Default: False.
|
|
||||||
:raises: InvalidParameterValue if an invalid boot device is specified.
|
|
||||||
"""
|
|
||||||
node = task.node
|
|
||||||
|
|
||||||
if device not in amt_common.BOOT_DEVICES_MAPPING:
|
|
||||||
raise exception.InvalidParameterValue(
|
|
||||||
_("set_boot_device called with invalid device "
|
|
||||||
"%(device)s for node %(node_id)s."
|
|
||||||
) % {'device': device, 'node_id': node.uuid})
|
|
||||||
|
|
||||||
# AMT/vPro doesn't support set boot_device persistent, so we have to
|
|
||||||
# save amt_boot_device/amt_boot_persistent in driver_internal_info.
|
|
||||||
driver_internal_info = node.driver_internal_info
|
|
||||||
driver_internal_info['amt_boot_device'] = device
|
|
||||||
driver_internal_info['amt_boot_persistent'] = persistent
|
|
||||||
node.driver_internal_info = driver_internal_info
|
|
||||||
node.save()
|
|
||||||
|
|
||||||
def get_boot_device(self, task):
|
|
||||||
"""Get the current boot device for the task's node.
|
|
||||||
|
|
||||||
Returns the current boot device of the node.
|
|
||||||
|
|
||||||
:param task: a task from TaskManager.
|
|
||||||
:returns: a dictionary containing:
|
|
||||||
|
|
||||||
:boot_device: the boot device
|
|
||||||
:persistent: Whether the boot device will persist to all
|
|
||||||
future boots or not, None if it is unknown.
|
|
||||||
|
|
||||||
"""
|
|
||||||
driver_internal_info = task.node.driver_internal_info
|
|
||||||
device = driver_internal_info.get('amt_boot_device')
|
|
||||||
persistent = driver_internal_info.get('amt_boot_persistent')
|
|
||||||
if not device:
|
|
||||||
device = amt_common.DEFAULT_BOOT_DEVICE
|
|
||||||
persistent = True
|
|
||||||
return {'boot_device': device,
|
|
||||||
'persistent': persistent}
|
|
||||||
|
|
||||||
def ensure_next_boot_device(self, node, boot_device):
|
|
||||||
"""Set next boot device (one time only) of AMT Client.
|
|
||||||
|
|
||||||
:param node: a node object
|
|
||||||
:param boot_device: the boot device
|
|
||||||
:raises: AMTFailure
|
|
||||||
:raises: AMTConnectFailure
|
|
||||||
"""
|
|
||||||
driver_internal_info = node.driver_internal_info
|
|
||||||
if not driver_internal_info.get('amt_boot_persistent'):
|
|
||||||
driver_internal_info['amt_boot_device'] = (
|
|
||||||
amt_common.DEFAULT_BOOT_DEVICE)
|
|
||||||
driver_internal_info['amt_boot_persistent'] = True
|
|
||||||
node.driver_internal_info = driver_internal_info
|
|
||||||
node.save()
|
|
||||||
|
|
||||||
_set_boot_device_order(node, boot_device)
|
|
||||||
_enable_boot_config(node)
|
|
||||||
|
|
||||||
def get_sensors_data(self, task):
|
|
||||||
raise NotImplementedError()
|
|
@ -1,250 +0,0 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
AMT Power Driver
|
|
||||||
"""
|
|
||||||
import copy
|
|
||||||
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_service import loopingcall
|
|
||||||
from oslo_utils import excutils
|
|
||||||
from oslo_utils import importutils
|
|
||||||
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.common.i18n import _, _LE, _LI, _LW
|
|
||||||
from ironic.common import states
|
|
||||||
from ironic.conductor import task_manager
|
|
||||||
from ironic.conf import CONF
|
|
||||||
from ironic.drivers import base
|
|
||||||
from ironic.drivers.modules.amt import common as amt_common
|
|
||||||
from ironic.drivers.modules.amt import resource_uris
|
|
||||||
|
|
||||||
pywsman = importutils.try_import('pywsman')
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
AMT_POWER_MAP = {
|
|
||||||
states.POWER_ON: '2',
|
|
||||||
states.POWER_OFF: '8',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _generate_power_action_input(action):
|
|
||||||
"""Generate Xmldoc as set_power_state input.
|
|
||||||
|
|
||||||
This generates a Xmldoc used as input for set_power_state.
|
|
||||||
|
|
||||||
:param action: the power action.
|
|
||||||
:returns: Xmldoc.
|
|
||||||
"""
|
|
||||||
method_input = "RequestPowerStateChange_INPUT"
|
|
||||||
address = 'http://schemas.xmlsoap.org/ws/2004/08/addressing'
|
|
||||||
anonymous = ('http://schemas.xmlsoap.org/ws/2004/08/addressing/'
|
|
||||||
'role/anonymous')
|
|
||||||
wsman = 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd'
|
|
||||||
namespace = resource_uris.CIM_PowerManagementService
|
|
||||||
|
|
||||||
doc = pywsman.XmlDoc(method_input)
|
|
||||||
root = doc.root()
|
|
||||||
root.set_ns(namespace)
|
|
||||||
root.add(namespace, 'PowerState', action)
|
|
||||||
|
|
||||||
child = root.add(namespace, 'ManagedElement', None)
|
|
||||||
child.add(address, 'Address', anonymous)
|
|
||||||
|
|
||||||
grand_child = child.add(address, 'ReferenceParameters', None)
|
|
||||||
grand_child.add(wsman, 'ResourceURI', resource_uris.CIM_ComputerSystem)
|
|
||||||
|
|
||||||
g_grand_child = grand_child.add(wsman, 'SelectorSet', None)
|
|
||||||
g_g_grand_child = g_grand_child.add(wsman, 'Selector', 'ManagedSystem')
|
|
||||||
g_g_grand_child.attr_add(wsman, 'Name', 'Name')
|
|
||||||
return doc
|
|
||||||
|
|
||||||
|
|
||||||
def _set_power_state(node, target_state):
|
|
||||||
"""Set power state of the AMT Client.
|
|
||||||
|
|
||||||
:param node: a node object.
|
|
||||||
:param target_state: desired power state.
|
|
||||||
:raises: AMTFailure
|
|
||||||
:raises: AMTConnectFailure
|
|
||||||
"""
|
|
||||||
amt_common.awake_amt_interface(node)
|
|
||||||
client = amt_common.get_wsman_client(node)
|
|
||||||
|
|
||||||
method = 'RequestPowerStateChange'
|
|
||||||
options = pywsman.ClientOptions()
|
|
||||||
options.add_selector('Name', 'Intel(r) AMT Power Management Service')
|
|
||||||
|
|
||||||
doc = _generate_power_action_input(AMT_POWER_MAP[target_state])
|
|
||||||
try:
|
|
||||||
client.wsman_invoke(options, resource_uris.CIM_PowerManagementService,
|
|
||||||
method, doc)
|
|
||||||
except (exception.AMTFailure, exception.AMTConnectFailure) as e:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
LOG.exception(_LE("Failed to set power state %(state)s for "
|
|
||||||
"node %(node_id)s with error: %(error)s."),
|
|
||||||
{'state': target_state, 'node_id': node.uuid,
|
|
||||||
'error': e})
|
|
||||||
else:
|
|
||||||
LOG.info(_LI("Power state set to %(state)s for node %(node_id)s"),
|
|
||||||
{'state': target_state, 'node_id': node.uuid})
|
|
||||||
|
|
||||||
|
|
||||||
def _power_status(node):
|
|
||||||
"""Get the power status for a node.
|
|
||||||
|
|
||||||
:param node: a node object.
|
|
||||||
:returns: one of ironic.common.states POWER_OFF, POWER_ON or ERROR.
|
|
||||||
:raises: AMTFailure.
|
|
||||||
:raises: AMTConnectFailure.
|
|
||||||
"""
|
|
||||||
amt_common.awake_amt_interface(node)
|
|
||||||
client = amt_common.get_wsman_client(node)
|
|
||||||
namespace = resource_uris.CIM_AssociatedPowerManagementService
|
|
||||||
try:
|
|
||||||
doc = client.wsman_get(namespace)
|
|
||||||
except (exception.AMTFailure, exception.AMTConnectFailure) as e:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
LOG.exception(_LE("Failed to get power state for node %(node_id)s "
|
|
||||||
"with error: %(error)s."),
|
|
||||||
{'node_id': node.uuid, 'error': e})
|
|
||||||
|
|
||||||
item = "PowerState"
|
|
||||||
power_state = amt_common.xml_find(doc, namespace, item).text
|
|
||||||
for state in AMT_POWER_MAP:
|
|
||||||
if power_state == AMT_POWER_MAP[state]:
|
|
||||||
return state
|
|
||||||
return states.ERROR
|
|
||||||
|
|
||||||
|
|
||||||
def _set_and_wait(task, target_state):
|
|
||||||
"""Helper function for DynamicLoopingCall.
|
|
||||||
|
|
||||||
This method changes the power state and polls AMT until the desired
|
|
||||||
power state is reached.
|
|
||||||
|
|
||||||
:param task: a TaskManager instance contains the target node.
|
|
||||||
:param target_state: desired power state.
|
|
||||||
:returns: one of ironic.common.states.
|
|
||||||
:raises: PowerStateFailure if cannot set the node to target_state.
|
|
||||||
:raises: AMTFailure.
|
|
||||||
:raises: AMTConnectFailure
|
|
||||||
:raises: InvalidParameterValue
|
|
||||||
"""
|
|
||||||
node = task.node
|
|
||||||
driver = task.driver
|
|
||||||
if target_state not in (states.POWER_ON, states.POWER_OFF):
|
|
||||||
raise exception.InvalidParameterValue(_('Unsupported target_state: %s')
|
|
||||||
% target_state)
|
|
||||||
elif target_state == states.POWER_ON:
|
|
||||||
boot_device = node.driver_internal_info.get('amt_boot_device')
|
|
||||||
if boot_device and boot_device != amt_common.DEFAULT_BOOT_DEVICE:
|
|
||||||
driver.management.ensure_next_boot_device(node, boot_device)
|
|
||||||
|
|
||||||
def _wait(status):
|
|
||||||
status['power'] = _power_status(node)
|
|
||||||
if status['power'] == target_state:
|
|
||||||
raise loopingcall.LoopingCallDone()
|
|
||||||
|
|
||||||
if status['iter'] >= CONF.amt.max_attempts:
|
|
||||||
status['power'] = states.ERROR
|
|
||||||
LOG.warning(_LW("AMT failed to set power state %(state)s after "
|
|
||||||
"%(tries)s retries on node %(node_id)s."),
|
|
||||||
{'state': target_state, 'tries': status['iter'],
|
|
||||||
'node_id': node.uuid})
|
|
||||||
raise loopingcall.LoopingCallDone()
|
|
||||||
|
|
||||||
try:
|
|
||||||
_set_power_state(node, target_state)
|
|
||||||
except Exception:
|
|
||||||
# Log failures but keep trying
|
|
||||||
LOG.warning(_LW("AMT set power state %(state)s for node %(node)s "
|
|
||||||
"- Attempt %(attempt)s times of %(max_attempt)s "
|
|
||||||
"failed."),
|
|
||||||
{'state': target_state, 'node': node.uuid,
|
|
||||||
'attempt': status['iter'] + 1,
|
|
||||||
'max_attempt': CONF.amt.max_attempts})
|
|
||||||
status['iter'] += 1
|
|
||||||
|
|
||||||
status = {'power': None, 'iter': 0}
|
|
||||||
|
|
||||||
timer = loopingcall.FixedIntervalLoopingCall(_wait, status)
|
|
||||||
timer.start(interval=CONF.amt.action_wait).wait()
|
|
||||||
|
|
||||||
if status['power'] != target_state:
|
|
||||||
raise exception.PowerStateFailure(pstate=target_state)
|
|
||||||
|
|
||||||
return status['power']
|
|
||||||
|
|
||||||
|
|
||||||
class AMTPower(base.PowerInterface):
|
|
||||||
"""AMT Power interface.
|
|
||||||
|
|
||||||
This Power interface control the power of node by providing power on/off
|
|
||||||
and reset functions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_properties(self):
|
|
||||||
return copy.deepcopy(amt_common.COMMON_PROPERTIES)
|
|
||||||
|
|
||||||
def validate(self, task):
|
|
||||||
"""Validate the driver_info in the node.
|
|
||||||
|
|
||||||
Check if the driver_info contains correct required fields
|
|
||||||
|
|
||||||
:param task: a TaskManager instance contains the target node.
|
|
||||||
:raises: MissingParameterValue if any required parameters are missing.
|
|
||||||
:raises: InvalidParameterValue if any parameters have invalid values.
|
|
||||||
"""
|
|
||||||
# FIXME(lintan): validate hangs if unable to reach AMT, so dont
|
|
||||||
# connect to the node until bug 1314961 is resolved.
|
|
||||||
amt_common.parse_driver_info(task.node)
|
|
||||||
|
|
||||||
def get_power_state(self, task):
|
|
||||||
"""Get the power state from the node.
|
|
||||||
|
|
||||||
:param task: a TaskManager instance contains the target node.
|
|
||||||
:raises: AMTFailure.
|
|
||||||
:raises: AMTConnectFailure.
|
|
||||||
"""
|
|
||||||
return _power_status(task.node)
|
|
||||||
|
|
||||||
@task_manager.require_exclusive_lock
|
|
||||||
def set_power_state(self, task, pstate):
|
|
||||||
"""Set the power state of the node.
|
|
||||||
|
|
||||||
Turn the node power on or off.
|
|
||||||
|
|
||||||
:param task: a TaskManager instance contains the target node.
|
|
||||||
:param pstate: The desired power state of the node.
|
|
||||||
:raises: PowerStateFailure if the power cannot set to pstate.
|
|
||||||
:raises: AMTFailure.
|
|
||||||
:raises: AMTConnectFailure.
|
|
||||||
:raises: InvalidParameterValue
|
|
||||||
"""
|
|
||||||
_set_and_wait(task, pstate)
|
|
||||||
|
|
||||||
@task_manager.require_exclusive_lock
|
|
||||||
def reboot(self, task):
|
|
||||||
"""Cycle the power of the node
|
|
||||||
|
|
||||||
:param task: a TaskManager instance contains the target node.
|
|
||||||
:raises: PowerStateFailure if failed to reboot.
|
|
||||||
:raises: AMTFailure.
|
|
||||||
:raises: AMTConnectFailure.
|
|
||||||
:raises: InvalidParameterValue
|
|
||||||
"""
|
|
||||||
_set_and_wait(task, states.POWER_OFF)
|
|
||||||
_set_and_wait(task, states.POWER_ON)
|
|
@ -1,35 +0,0 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
XML Schemas to define the requests sent to AMT
|
|
||||||
"""
|
|
||||||
|
|
||||||
CIM_AssociatedPowerManagementService = ('http://schemas.dmtf.org/wbem/wscim/'
|
|
||||||
'1/cim-schema/2/'
|
|
||||||
'CIM_AssociatedPowerManagementService')
|
|
||||||
|
|
||||||
CIM_PowerManagementService = ('http://schemas.dmtf.org/wbem/wscim/1/'
|
|
||||||
'cim-schema/2/CIM_PowerManagementService')
|
|
||||||
|
|
||||||
CIM_ComputerSystem = ('http://schemas.dmtf.org/wbem/wscim/'
|
|
||||||
'1/cim-schema/2/CIM_ComputerSystem')
|
|
||||||
|
|
||||||
CIM_BootConfigSetting = ('http://schemas.dmtf.org/wbem/wscim/'
|
|
||||||
'1/cim-schema/2/CIM_BootConfigSetting')
|
|
||||||
|
|
||||||
CIM_BootSourceSetting = ('http://schemas.dmtf.org/wbem/wscim/'
|
|
||||||
'1/cim-schema/2/CIM_BootSourceSetting')
|
|
||||||
|
|
||||||
CIM_BootService = ('http://schemas.dmtf.org/wbem/wscim/'
|
|
||||||
'1/cim-schema/2/CIM_BootService')
|
|
@ -1,30 +0,0 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
"""
|
|
||||||
AMT Vendor Methods
|
|
||||||
"""
|
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
|
||||||
from ironic.conductor import task_manager
|
|
||||||
from ironic.drivers.modules import deploy_utils
|
|
||||||
from ironic.drivers.modules import iscsi_deploy
|
|
||||||
|
|
||||||
|
|
||||||
class AMTPXEVendorPassthru(iscsi_deploy.VendorPassthru):
|
|
||||||
|
|
||||||
@task_manager.require_exclusive_lock
|
|
||||||
def continue_deploy(self, task):
|
|
||||||
if deploy_utils.get_boot_option(task.node) == "netboot":
|
|
||||||
task.driver.management.ensure_next_boot_device(task.node,
|
|
||||||
boot_devices.PXE)
|
|
||||||
super(AMTPXEVendorPassthru, self).continue_deploy(task)
|
|
@ -1,257 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Red Hat, Inc.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Ironic iBoot PDU power manager.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_service import loopingcall
|
|
||||||
from oslo_utils import importutils
|
|
||||||
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.common.i18n import _, _LW
|
|
||||||
from ironic.common import states
|
|
||||||
from ironic.common import utils
|
|
||||||
from ironic.conductor import task_manager
|
|
||||||
from ironic.conf import CONF
|
|
||||||
from ironic.drivers import base
|
|
||||||
|
|
||||||
iboot = importutils.try_import('iboot')
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
REQUIRED_PROPERTIES = {
|
|
||||||
'iboot_address': _("IP address of the node. Required."),
|
|
||||||
'iboot_username': _("username. Required."),
|
|
||||||
'iboot_password': _("password. Required."),
|
|
||||||
}
|
|
||||||
OPTIONAL_PROPERTIES = {
|
|
||||||
'iboot_relay_id': _("iBoot PDU relay id; default is 1. Optional."),
|
|
||||||
'iboot_port': _("iBoot PDU port; default is 9100. Optional."),
|
|
||||||
}
|
|
||||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
|
||||||
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_driver_info(node):
|
|
||||||
info = node.driver_info or {}
|
|
||||||
missing_info = [key for key in REQUIRED_PROPERTIES if not info.get(key)]
|
|
||||||
if missing_info:
|
|
||||||
raise exception.MissingParameterValue(
|
|
||||||
_("Missing the following iBoot credentials in node's"
|
|
||||||
" driver_info: %s.") % missing_info)
|
|
||||||
|
|
||||||
address = info.get('iboot_address', None)
|
|
||||||
username = info.get('iboot_username', None)
|
|
||||||
password = info.get('iboot_password', None)
|
|
||||||
|
|
||||||
relay_id = info.get('iboot_relay_id', 1)
|
|
||||||
try:
|
|
||||||
relay_id = int(relay_id)
|
|
||||||
except ValueError:
|
|
||||||
raise exception.InvalidParameterValue(
|
|
||||||
_("iBoot PDU relay id must be an integer."))
|
|
||||||
|
|
||||||
port = info.get('iboot_port', 9100)
|
|
||||||
port = utils.validate_network_port(port, 'iboot_port')
|
|
||||||
|
|
||||||
return {
|
|
||||||
'address': address,
|
|
||||||
'username': username,
|
|
||||||
'password': password,
|
|
||||||
'port': port,
|
|
||||||
'relay_id': relay_id,
|
|
||||||
'uuid': node.uuid,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _get_connection(driver_info):
|
|
||||||
# NOTE: python-iboot wants username and password as strings (not unicode)
|
|
||||||
return iboot.iBootInterface(driver_info['address'],
|
|
||||||
str(driver_info['username']),
|
|
||||||
str(driver_info['password']),
|
|
||||||
port=driver_info['port'],
|
|
||||||
num_relays=driver_info['relay_id'])
|
|
||||||
|
|
||||||
|
|
||||||
def _switch(driver_info, enabled):
|
|
||||||
conn = _get_connection(driver_info)
|
|
||||||
relay_id = driver_info['relay_id']
|
|
||||||
|
|
||||||
def _wait_for_switch(mutable):
|
|
||||||
if mutable['retries'] > CONF.iboot.max_retry:
|
|
||||||
LOG.warning(_LW(
|
|
||||||
'Reached maximum number of attempts (%(attempts)d) to set '
|
|
||||||
'power state for node %(node)s to "%(op)s"'),
|
|
||||||
{'attempts': mutable['retries'], 'node': driver_info['uuid'],
|
|
||||||
'op': states.POWER_ON if enabled else states.POWER_OFF})
|
|
||||||
raise loopingcall.LoopingCallDone()
|
|
||||||
|
|
||||||
try:
|
|
||||||
mutable['retries'] += 1
|
|
||||||
mutable['response'] = conn.switch(relay_id, enabled)
|
|
||||||
if mutable['response']:
|
|
||||||
raise loopingcall.LoopingCallDone()
|
|
||||||
except (TypeError, IndexError):
|
|
||||||
LOG.warning(_LW("Cannot call set power state for node '%(node)s' "
|
|
||||||
"at relay '%(relay)s'. iBoot switch() failed."),
|
|
||||||
{'node': driver_info['uuid'], 'relay': relay_id})
|
|
||||||
|
|
||||||
mutable = {'response': False, 'retries': 0}
|
|
||||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_switch,
|
|
||||||
mutable)
|
|
||||||
timer.start(interval=CONF.iboot.retry_interval).wait()
|
|
||||||
return mutable['response']
|
|
||||||
|
|
||||||
|
|
||||||
def _sleep_switch(seconds):
|
|
||||||
"""Function broken out for testing purpose."""
|
|
||||||
time.sleep(seconds)
|
|
||||||
|
|
||||||
|
|
||||||
def _check_power_state(driver_info, pstate):
|
|
||||||
"""Function to check power state is correct. Up to max retries."""
|
|
||||||
# always try once + number of retries
|
|
||||||
for num in range(0, 1 + CONF.iboot.max_retry):
|
|
||||||
state = _power_status(driver_info)
|
|
||||||
if state == pstate:
|
|
||||||
return
|
|
||||||
if num < CONF.iboot.max_retry:
|
|
||||||
time.sleep(CONF.iboot.retry_interval)
|
|
||||||
raise exception.PowerStateFailure(pstate=pstate)
|
|
||||||
|
|
||||||
|
|
||||||
def _power_status(driver_info):
|
|
||||||
conn = _get_connection(driver_info)
|
|
||||||
relay_id = driver_info['relay_id']
|
|
||||||
|
|
||||||
def _wait_for_power_status(mutable):
|
|
||||||
|
|
||||||
if mutable['retries'] > CONF.iboot.max_retry:
|
|
||||||
LOG.warning(_LW(
|
|
||||||
'Reached maximum number of attempts (%(attempts)d) to get '
|
|
||||||
'power state for node %(node)s'),
|
|
||||||
{'attempts': mutable['retries'], 'node': driver_info['uuid']})
|
|
||||||
raise loopingcall.LoopingCallDone()
|
|
||||||
|
|
||||||
try:
|
|
||||||
mutable['retries'] += 1
|
|
||||||
response = conn.get_relays()
|
|
||||||
status = response[relay_id - 1]
|
|
||||||
if status:
|
|
||||||
mutable['state'] = states.POWER_ON
|
|
||||||
else:
|
|
||||||
mutable['state'] = states.POWER_OFF
|
|
||||||
raise loopingcall.LoopingCallDone()
|
|
||||||
except (TypeError, IndexError):
|
|
||||||
LOG.warning(_LW("Cannot get power state for node '%(node)s' at "
|
|
||||||
"relay '%(relay)s'. iBoot get_relays() failed."),
|
|
||||||
{'node': driver_info['uuid'], 'relay': relay_id})
|
|
||||||
|
|
||||||
mutable = {'state': states.ERROR, 'retries': 0}
|
|
||||||
|
|
||||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_power_status,
|
|
||||||
mutable)
|
|
||||||
timer.start(interval=CONF.iboot.retry_interval).wait()
|
|
||||||
return mutable['state']
|
|
||||||
|
|
||||||
|
|
||||||
class IBootPower(base.PowerInterface):
|
|
||||||
"""iBoot PDU Power Driver for Ironic
|
|
||||||
|
|
||||||
This PowerManager class provides a mechanism for controlling power state
|
|
||||||
via an iBoot capable device.
|
|
||||||
|
|
||||||
Requires installation of python-iboot:
|
|
||||||
|
|
||||||
https://github.com/darkip/python-iboot
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_properties(self):
|
|
||||||
return COMMON_PROPERTIES
|
|
||||||
|
|
||||||
def validate(self, task):
|
|
||||||
"""Validate driver_info for iboot driver.
|
|
||||||
|
|
||||||
:param task: a TaskManager instance containing the node to act on.
|
|
||||||
:raises: InvalidParameterValue if iboot parameters are invalid.
|
|
||||||
:raises: MissingParameterValue if required iboot parameters are
|
|
||||||
missing.
|
|
||||||
|
|
||||||
"""
|
|
||||||
_parse_driver_info(task.node)
|
|
||||||
|
|
||||||
def get_power_state(self, task):
|
|
||||||
"""Get the current power state of the task's node.
|
|
||||||
|
|
||||||
:param task: a TaskManager instance containing the node to act on.
|
|
||||||
:returns: one of ironic.common.states POWER_OFF, POWER_ON or ERROR.
|
|
||||||
:raises: InvalidParameterValue if iboot parameters are invalid.
|
|
||||||
:raises: MissingParameterValue if required iboot parameters are
|
|
||||||
missing.
|
|
||||||
|
|
||||||
"""
|
|
||||||
driver_info = _parse_driver_info(task.node)
|
|
||||||
return _power_status(driver_info)
|
|
||||||
|
|
||||||
@task_manager.require_exclusive_lock
|
|
||||||
def set_power_state(self, task, pstate):
|
|
||||||
"""Turn the power on or off.
|
|
||||||
|
|
||||||
:param task: a TaskManager instance containing the node to act on.
|
|
||||||
:param pstate: The desired power state, one of ironic.common.states
|
|
||||||
POWER_ON, POWER_OFF.
|
|
||||||
:raises: InvalidParameterValue if iboot parameters are invalid or if
|
|
||||||
an invalid power state was specified.
|
|
||||||
:raises: MissingParameterValue if required iboot parameters are
|
|
||||||
missing.
|
|
||||||
:raises: PowerStateFailure if the power couldn't be set to pstate.
|
|
||||||
|
|
||||||
"""
|
|
||||||
driver_info = _parse_driver_info(task.node)
|
|
||||||
if pstate == states.POWER_ON:
|
|
||||||
_switch(driver_info, True)
|
|
||||||
elif pstate == states.POWER_OFF:
|
|
||||||
_switch(driver_info, False)
|
|
||||||
else:
|
|
||||||
raise exception.InvalidParameterValue(
|
|
||||||
_("set_power_state called with invalid "
|
|
||||||
"power state %s.") % pstate)
|
|
||||||
|
|
||||||
_check_power_state(driver_info, pstate)
|
|
||||||
|
|
||||||
@task_manager.require_exclusive_lock
|
|
||||||
def reboot(self, task):
|
|
||||||
"""Cycles the power to the task's node.
|
|
||||||
|
|
||||||
:param task: a TaskManager instance containing the node to act on.
|
|
||||||
:raises: InvalidParameterValue if iboot parameters are invalid.
|
|
||||||
:raises: MissingParameterValue if required iboot parameters are
|
|
||||||
missing.
|
|
||||||
:raises: PowerStateFailure if the final state of the node is not
|
|
||||||
POWER_ON.
|
|
||||||
|
|
||||||
"""
|
|
||||||
driver_info = _parse_driver_info(task.node)
|
|
||||||
_switch(driver_info, False)
|
|
||||||
_sleep_switch(CONF.iboot.reboot_delay)
|
|
||||||
_switch(driver_info, True)
|
|
||||||
_check_power_state(driver_info, states.POWER_ON)
|
|
@ -1,180 +0,0 @@
|
|||||||
# Copyright 2015 Red Hat, Inc.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Ironic Wake-On-Lan power manager.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import socket
|
|
||||||
import time
|
|
||||||
|
|
||||||
from oslo_log import log
|
|
||||||
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.common.i18n import _, _LI
|
|
||||||
from ironic.common import states
|
|
||||||
from ironic.common import utils
|
|
||||||
from ironic.conductor import task_manager
|
|
||||||
from ironic.drivers import base
|
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
REQUIRED_PROPERTIES = {}
|
|
||||||
OPTIONAL_PROPERTIES = {
|
|
||||||
'wol_host': _('Broadcast IP address; defaults to '
|
|
||||||
'255.255.255.255. Optional.'),
|
|
||||||
'wol_port': _("Destination port; defaults to 9. Optional."),
|
|
||||||
}
|
|
||||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
|
||||||
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
|
||||||
|
|
||||||
|
|
||||||
def _send_magic_packets(task, dest_host, dest_port):
|
|
||||||
"""Create and send magic packets.
|
|
||||||
|
|
||||||
Creates and sends a magic packet for each MAC address registered in
|
|
||||||
the Node.
|
|
||||||
|
|
||||||
:param task: a TaskManager instance containing the node to act on.
|
|
||||||
:param dest_host: The broadcast to this IP address.
|
|
||||||
:param dest_port: The destination port.
|
|
||||||
:raises: WolOperationError if an error occur when connecting to the
|
|
||||||
host or sending the magic packets
|
|
||||||
|
|
||||||
"""
|
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
||||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
|
||||||
with contextlib.closing(s) as sock:
|
|
||||||
for port in task.ports:
|
|
||||||
address = port.address.replace(':', '')
|
|
||||||
|
|
||||||
# TODO(lucasagomes): Implement sending the magic packets with
|
|
||||||
# SecureON password feature. If your NIC is capable of, you can
|
|
||||||
# set the password of your SecureON using the ethtool utility.
|
|
||||||
data = 'FFFFFFFFFFFF' + (address * 16)
|
|
||||||
packet = bytearray.fromhex(data)
|
|
||||||
|
|
||||||
try:
|
|
||||||
sock.sendto(packet, (dest_host, dest_port))
|
|
||||||
except socket.error as e:
|
|
||||||
msg = (_("Failed to send Wake-On-Lan magic packets to "
|
|
||||||
"node %(node)s port %(port)s. Error: %(error)s") %
|
|
||||||
{'node': task.node.uuid, 'port': port.address,
|
|
||||||
'error': e})
|
|
||||||
LOG.exception(msg)
|
|
||||||
raise exception.WolOperationError(msg)
|
|
||||||
|
|
||||||
# let's not flood the network with broadcast packets
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_parameters(task):
|
|
||||||
driver_info = task.node.driver_info
|
|
||||||
host = driver_info.get('wol_host', '255.255.255.255')
|
|
||||||
port = driver_info.get('wol_port', 9)
|
|
||||||
port = utils.validate_network_port(port, 'wol_port')
|
|
||||||
|
|
||||||
if len(task.ports) < 1:
|
|
||||||
raise exception.MissingParameterValue(_(
|
|
||||||
'Wake-On-Lan needs at least one port resource to be '
|
|
||||||
'registered in the node'))
|
|
||||||
|
|
||||||
return {'host': host, 'port': port}
|
|
||||||
|
|
||||||
|
|
||||||
class WakeOnLanPower(base.PowerInterface):
|
|
||||||
"""Wake-On-Lan Driver for Ironic
|
|
||||||
|
|
||||||
This PowerManager class provides a mechanism for controlling power
|
|
||||||
state via Wake-On-Lan.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_properties(self):
|
|
||||||
return COMMON_PROPERTIES
|
|
||||||
|
|
||||||
def validate(self, task):
|
|
||||||
"""Validate driver.
|
|
||||||
|
|
||||||
:param task: a TaskManager instance containing the node to act on.
|
|
||||||
:raises: InvalidParameterValue if parameters are invalid.
|
|
||||||
:raises: MissingParameterValue if required parameters are missing.
|
|
||||||
|
|
||||||
"""
|
|
||||||
_parse_parameters(task)
|
|
||||||
|
|
||||||
def get_power_state(self, task):
|
|
||||||
"""Not supported. Get the current power state of the task's node.
|
|
||||||
|
|
||||||
This operation is not supported by the Wake-On-Lan driver. So
|
|
||||||
value returned will be from the database and may not reflect
|
|
||||||
the actual state of the system.
|
|
||||||
|
|
||||||
:returns: POWER_OFF if power state is not set otherwise return
|
|
||||||
the node's power_state value from the database.
|
|
||||||
|
|
||||||
"""
|
|
||||||
pstate = task.node.power_state
|
|
||||||
return states.POWER_OFF if pstate is states.NOSTATE else pstate
|
|
||||||
|
|
||||||
@task_manager.require_exclusive_lock
|
|
||||||
def set_power_state(self, task, pstate):
|
|
||||||
"""Wakes the task's node on power on. Powering off is not supported.
|
|
||||||
|
|
||||||
Wakes the task's node on. Wake-On-Lan does not support powering
|
|
||||||
the task's node off so, just log it.
|
|
||||||
|
|
||||||
:param task: a TaskManager instance containing the node to act on.
|
|
||||||
:param pstate: The desired power state, one of ironic.common.states
|
|
||||||
POWER_ON, POWER_OFF.
|
|
||||||
:raises: InvalidParameterValue if parameters are invalid.
|
|
||||||
:raises: MissingParameterValue if required parameters are missing.
|
|
||||||
:raises: WolOperationError if an error occur when sending the
|
|
||||||
magic packets
|
|
||||||
|
|
||||||
"""
|
|
||||||
node = task.node
|
|
||||||
params = _parse_parameters(task)
|
|
||||||
if pstate == states.POWER_ON:
|
|
||||||
_send_magic_packets(task, params['host'], params['port'])
|
|
||||||
elif pstate == states.POWER_OFF:
|
|
||||||
LOG.info(_LI('Power off called for node %s. Wake-On-Lan does not '
|
|
||||||
'support this operation. Manual intervention '
|
|
||||||
'required to perform this action.'), node.uuid)
|
|
||||||
else:
|
|
||||||
raise exception.InvalidParameterValue(_(
|
|
||||||
"set_power_state called for Node %(node)s with invalid "
|
|
||||||
"power state %(pstate)s.") % {'node': node.uuid,
|
|
||||||
'pstate': pstate})
|
|
||||||
|
|
||||||
@task_manager.require_exclusive_lock
|
|
||||||
def reboot(self, task):
|
|
||||||
"""Not supported. Cycles the power to the task's node.
|
|
||||||
|
|
||||||
This operation is not fully supported by the Wake-On-Lan
|
|
||||||
driver. So this method will just try to power the task's node on.
|
|
||||||
|
|
||||||
:param task: a TaskManager instance containing the node to act on.
|
|
||||||
:raises: InvalidParameterValue if parameters are invalid.
|
|
||||||
:raises: MissingParameterValue if required parameters are missing.
|
|
||||||
:raises: WolOperationError if an error occur when sending the
|
|
||||||
magic packets
|
|
||||||
|
|
||||||
"""
|
|
||||||
LOG.info(_LI('Reboot called for node %s. Wake-On-Lan does '
|
|
||||||
'not fully support this operation. Trying to '
|
|
||||||
'power on the node.'), task.node.uuid)
|
|
||||||
self.set_power_state(task, states.POWER_ON)
|
|
@ -23,12 +23,8 @@ from ironic.common import exception
|
|||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
from ironic.drivers import base
|
from ironic.drivers import base
|
||||||
from ironic.drivers.modules import agent
|
from ironic.drivers.modules import agent
|
||||||
from ironic.drivers.modules.amt import management as amt_management
|
|
||||||
from ironic.drivers.modules.amt import power as amt_power
|
|
||||||
from ironic.drivers.modules.amt import vendor as amt_vendor
|
|
||||||
from ironic.drivers.modules.cimc import management as cimc_mgmt
|
from ironic.drivers.modules.cimc import management as cimc_mgmt
|
||||||
from ironic.drivers.modules.cimc import power as cimc_power
|
from ironic.drivers.modules.cimc import power as cimc_power
|
||||||
from ironic.drivers.modules import iboot
|
|
||||||
from ironic.drivers.modules.ilo import console as ilo_console
|
from ironic.drivers.modules.ilo import console as ilo_console
|
||||||
from ironic.drivers.modules.ilo import deploy as ilo_deploy
|
from ironic.drivers.modules.ilo import deploy as ilo_deploy
|
||||||
from ironic.drivers.modules.ilo import inspect as ilo_inspect
|
from ironic.drivers.modules.ilo import inspect as ilo_inspect
|
||||||
@ -51,7 +47,6 @@ from ironic.drivers.modules import ssh
|
|||||||
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
||||||
from ironic.drivers.modules.ucs import power as ucs_power
|
from ironic.drivers.modules.ucs import power as ucs_power
|
||||||
from ironic.drivers.modules import virtualbox
|
from ironic.drivers.modules import virtualbox
|
||||||
from ironic.drivers.modules import wol
|
|
||||||
|
|
||||||
|
|
||||||
class PXEAndIPMIToolDriver(base.BaseDriver):
|
class PXEAndIPMIToolDriver(base.BaseDriver):
|
||||||
@ -176,30 +171,6 @@ class PXEAndSeaMicroDriver(base.BaseDriver):
|
|||||||
self.console = seamicro.ShellinaboxConsole()
|
self.console = seamicro.ShellinaboxConsole()
|
||||||
|
|
||||||
|
|
||||||
class PXEAndIBootDriver(base.BaseDriver):
|
|
||||||
"""PXE + IBoot PDU driver.
|
|
||||||
|
|
||||||
This driver implements the `core` functionality, combining
|
|
||||||
:class:`ironic.drivers.modules.iboot.IBootPower` for power
|
|
||||||
on/off and reboot with
|
|
||||||
:class:`ironic.drivers.modules.iscsi_deploy.ISCSIDeploy` for
|
|
||||||
image deployment. Implementations are in those respective classes;
|
|
||||||
this class is merely the glue between them.
|
|
||||||
"""
|
|
||||||
|
|
||||||
supported = False
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if not importutils.try_import('iboot'):
|
|
||||||
raise exception.DriverLoadError(
|
|
||||||
driver=self.__class__.__name__,
|
|
||||||
reason=_("Unable to import iboot library"))
|
|
||||||
self.power = iboot.IBootPower()
|
|
||||||
self.boot = pxe.PXEBoot()
|
|
||||||
self.deploy = iscsi_deploy.ISCSIDeploy()
|
|
||||||
self.vendor = iscsi_deploy.VendorPassthru()
|
|
||||||
|
|
||||||
|
|
||||||
class PXEAndIloDriver(base.BaseDriver):
|
class PXEAndIloDriver(base.BaseDriver):
|
||||||
"""PXE + Ilo Driver using IloClient interface.
|
"""PXE + Ilo Driver using IloClient interface.
|
||||||
|
|
||||||
@ -297,30 +268,6 @@ class PXEAndVirtualBoxDriver(base.BaseDriver):
|
|||||||
self.raid = agent.AgentRAID()
|
self.raid = agent.AgentRAID()
|
||||||
|
|
||||||
|
|
||||||
class PXEAndAMTDriver(base.BaseDriver):
|
|
||||||
"""PXE + AMT driver.
|
|
||||||
|
|
||||||
This driver implements the `core` functionality, combining
|
|
||||||
:class:`ironic.drivers.amt.AMTPower` for power on/off and reboot with
|
|
||||||
:class:`ironic.drivers.modules.iscsi_deploy.ISCSIDeploy` for image
|
|
||||||
deployment. Implementations are in those respective classes; this
|
|
||||||
class is merely the glue between them.
|
|
||||||
"""
|
|
||||||
|
|
||||||
supported = False
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if not importutils.try_import('pywsman'):
|
|
||||||
raise exception.DriverLoadError(
|
|
||||||
driver=self.__class__.__name__,
|
|
||||||
reason=_("Unable to import pywsman library"))
|
|
||||||
self.power = amt_power.AMTPower()
|
|
||||||
self.boot = pxe.PXEBoot()
|
|
||||||
self.deploy = iscsi_deploy.ISCSIDeploy()
|
|
||||||
self.management = amt_management.AMTManagement()
|
|
||||||
self.vendor = amt_vendor.AMTPXEVendorPassthru()
|
|
||||||
|
|
||||||
|
|
||||||
class PXEAndMSFTOCSDriver(base.BaseDriver):
|
class PXEAndMSFTOCSDriver(base.BaseDriver):
|
||||||
"""PXE + MSFT OCS driver.
|
"""PXE + MSFT OCS driver.
|
||||||
|
|
||||||
@ -384,22 +331,3 @@ class PXEAndCIMCDriver(base.BaseDriver):
|
|||||||
self.management = cimc_mgmt.CIMCManagement()
|
self.management = cimc_mgmt.CIMCManagement()
|
||||||
self.inspect = inspector.Inspector.create_if_enabled(
|
self.inspect = inspector.Inspector.create_if_enabled(
|
||||||
'PXEAndCIMCDriver')
|
'PXEAndCIMCDriver')
|
||||||
|
|
||||||
|
|
||||||
class PXEAndWakeOnLanDriver(base.BaseDriver):
|
|
||||||
"""PXE + WakeOnLan driver.
|
|
||||||
|
|
||||||
This driver implements the `core` functionality, combining
|
|
||||||
:class:`ironic.drivers.modules.wol.WakeOnLanPower` for power on
|
|
||||||
:class:`ironic.drivers.modules.iscsi_deploy.ISCSIDeploy` for image
|
|
||||||
deployment. Implementations are in those respective classes;
|
|
||||||
this class is merely the glue between them.
|
|
||||||
"""
|
|
||||||
|
|
||||||
supported = False
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.power = wol.WakeOnLanPower()
|
|
||||||
self.boot = pxe.PXEBoot()
|
|
||||||
self.deploy = iscsi_deploy.ISCSIDeploy()
|
|
||||||
self.vendor = iscsi_deploy.VendorPassthru()
|
|
||||||
|
@ -127,15 +127,6 @@ def get_test_irmc_info():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_test_amt_info():
|
|
||||||
return {
|
|
||||||
"amt_address": "1.2.3.4",
|
|
||||||
"amt_protocol": "http",
|
|
||||||
"amt_username": "admin",
|
|
||||||
"amt_password": "fake",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_test_msftocs_info():
|
def get_test_msftocs_info():
|
||||||
return {
|
return {
|
||||||
"msftocs_base_url": "http://fakehost:8000",
|
"msftocs_base_url": "http://fakehost:8000",
|
||||||
@ -170,14 +161,6 @@ def get_test_agent_driver_internal_info():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_test_iboot_info():
|
|
||||||
return {
|
|
||||||
"iboot_address": "1.2.3.4",
|
|
||||||
"iboot_username": "admin",
|
|
||||||
"iboot_password": "fake",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_test_snmp_info(**kw):
|
def get_test_snmp_info(**kw):
|
||||||
result = {
|
result = {
|
||||||
"snmp_driver": kw.get("snmp_driver", "teltronix"),
|
"snmp_driver": kw.get("snmp_driver", "teltronix"),
|
||||||
|
@ -1,217 +0,0 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Test class for AMT Common
|
|
||||||
"""
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from oslo_concurrency import processutils
|
|
||||||
from oslo_config import cfg
|
|
||||||
import time
|
|
||||||
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.common import utils
|
|
||||||
from ironic.drivers.modules.amt import common as amt_common
|
|
||||||
from ironic.drivers.modules.amt import resource_uris
|
|
||||||
from ironic.tests import base
|
|
||||||
from ironic.tests.unit.db import base as db_base
|
|
||||||
from ironic.tests.unit.db import utils as db_utils
|
|
||||||
from ironic.tests.unit.drivers.modules.amt import utils as test_utils
|
|
||||||
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
|
||||||
as mock_specs
|
|
||||||
from ironic.tests.unit.objects import utils as obj_utils
|
|
||||||
|
|
||||||
INFO_DICT = db_utils.get_test_amt_info()
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class AMTCommonMethodsTestCase(db_base.DbTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AMTCommonMethodsTestCase, self).setUp()
|
|
||||||
self.node = obj_utils.create_test_node(self.context,
|
|
||||||
driver='fake_amt',
|
|
||||||
driver_info=INFO_DICT)
|
|
||||||
|
|
||||||
def test_parse_driver_info(self):
|
|
||||||
info = amt_common.parse_driver_info(self.node)
|
|
||||||
|
|
||||||
self.assertEqual(b'1.2.3.4', info['address'])
|
|
||||||
self.assertEqual(b'admin', info['username'])
|
|
||||||
self.assertEqual(b'fake', info['password'])
|
|
||||||
self.assertEqual(INFO_DICT['amt_protocol'], info['protocol'])
|
|
||||||
self.assertEqual('1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
|
|
||||||
info['uuid'])
|
|
||||||
|
|
||||||
def test_parse_driver_info_missing_address(self):
|
|
||||||
del self.node.driver_info['amt_address']
|
|
||||||
|
|
||||||
self.assertRaises(exception.MissingParameterValue,
|
|
||||||
amt_common.parse_driver_info, self.node)
|
|
||||||
|
|
||||||
def test_parse_driver_info_missing_username(self):
|
|
||||||
del self.node.driver_info['amt_username']
|
|
||||||
|
|
||||||
self.assertRaises(exception.MissingParameterValue,
|
|
||||||
amt_common.parse_driver_info, self.node)
|
|
||||||
|
|
||||||
def test_parse_driver_info_missing_password(self):
|
|
||||||
del self.node.driver_info['amt_password']
|
|
||||||
self.assertRaises(exception.MissingParameterValue,
|
|
||||||
amt_common.parse_driver_info, self.node)
|
|
||||||
|
|
||||||
def test_parse_driver_info_missing_protocol(self):
|
|
||||||
del self.node.driver_info['amt_protocol']
|
|
||||||
info = amt_common.parse_driver_info(self.node)
|
|
||||||
self.assertEqual('http', info['protocol'])
|
|
||||||
|
|
||||||
def test_parse_driver_info_wrong_protocol(self):
|
|
||||||
self.node.driver_info['amt_protocol'] = 'fake-protocol'
|
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
|
||||||
amt_common.parse_driver_info, self.node)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'Client', spec_set=True, autospec=True)
|
|
||||||
def test_get_wsman_client(self, mock_client):
|
|
||||||
info = amt_common.parse_driver_info(self.node)
|
|
||||||
amt_common.get_wsman_client(self.node)
|
|
||||||
options = {'address': info['address'],
|
|
||||||
'protocol': info['protocol'],
|
|
||||||
'username': info['username'],
|
|
||||||
'password': info['password']}
|
|
||||||
|
|
||||||
mock_client.assert_called_once_with(**options)
|
|
||||||
|
|
||||||
def test_xml_find(self):
|
|
||||||
namespace = 'http://fake'
|
|
||||||
value = 'fake_value'
|
|
||||||
test_xml = test_utils.build_soap_xml([{'test_element': value}],
|
|
||||||
namespace)
|
|
||||||
mock_doc = test_utils.mock_wsman_root(test_xml)
|
|
||||||
|
|
||||||
result = amt_common.xml_find(mock_doc, namespace, 'test_element')
|
|
||||||
self.assertEqual(value, result.text)
|
|
||||||
|
|
||||||
def test_xml_find_fail(self):
|
|
||||||
mock_doc = None
|
|
||||||
self.assertRaises(exception.AMTConnectFailure,
|
|
||||||
amt_common.xml_find,
|
|
||||||
mock_doc, 'namespace', 'test_element')
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC)
|
|
||||||
class AMTCommonClientTestCase(base.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(AMTCommonClientTestCase, self).setUp()
|
|
||||||
self.info = {key[4:]: INFO_DICT[key] for key in INFO_DICT}
|
|
||||||
|
|
||||||
def test_wsman_get(self, mock_client_pywsman):
|
|
||||||
namespace = resource_uris.CIM_AssociatedPowerManagementService
|
|
||||||
result_xml = test_utils.build_soap_xml([{'PowerState':
|
|
||||||
'2'}],
|
|
||||||
namespace)
|
|
||||||
mock_doc = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.get.return_value = mock_doc
|
|
||||||
client = amt_common.Client(**self.info)
|
|
||||||
|
|
||||||
client.wsman_get(namespace)
|
|
||||||
mock_pywsman.get.assert_called_once_with(mock.ANY, namespace)
|
|
||||||
|
|
||||||
def test_wsman_get_fail(self, mock_client_pywsman):
|
|
||||||
namespace = amt_common._SOAP_ENVELOPE
|
|
||||||
result_xml = test_utils.build_soap_xml([{'Fault': 'fault'}],
|
|
||||||
namespace)
|
|
||||||
mock_doc = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.get.return_value = mock_doc
|
|
||||||
client = amt_common.Client(**self.info)
|
|
||||||
|
|
||||||
self.assertRaises(exception.AMTFailure, client.wsman_get, namespace)
|
|
||||||
mock_pywsman.get.assert_called_once_with(mock.ANY, namespace)
|
|
||||||
|
|
||||||
def test_wsman_invoke(self, mock_client_pywsman):
|
|
||||||
namespace = resource_uris.CIM_BootSourceSetting
|
|
||||||
result_xml = test_utils.build_soap_xml([{'ReturnValue':
|
|
||||||
'0'}],
|
|
||||||
namespace)
|
|
||||||
mock_doc = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.invoke.return_value = mock_doc
|
|
||||||
method = 'ChangeBootOrder'
|
|
||||||
options = mock.Mock(spec_set=[])
|
|
||||||
client = amt_common.Client(**self.info)
|
|
||||||
doc = None
|
|
||||||
client.wsman_invoke(options, namespace, method, doc)
|
|
||||||
mock_pywsman.invoke.assert_called_once_with(options, namespace, method)
|
|
||||||
doc = 'fake-input'
|
|
||||||
client.wsman_invoke(options, namespace, method, doc)
|
|
||||||
mock_pywsman.invoke.assert_called_with(options, namespace, method, doc)
|
|
||||||
|
|
||||||
def test_wsman_invoke_fail(self, mock_client_pywsman):
|
|
||||||
namespace = resource_uris.CIM_BootSourceSetting
|
|
||||||
result_xml = test_utils.build_soap_xml([{'ReturnValue':
|
|
||||||
'2'}],
|
|
||||||
namespace)
|
|
||||||
mock_doc = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.invoke.return_value = mock_doc
|
|
||||||
method = 'fake-method'
|
|
||||||
options = mock.Mock(spec_set=[])
|
|
||||||
|
|
||||||
client = amt_common.Client(**self.info)
|
|
||||||
|
|
||||||
self.assertRaises(exception.AMTFailure,
|
|
||||||
client.wsman_invoke,
|
|
||||||
options, namespace, method)
|
|
||||||
mock_pywsman.invoke.assert_called_once_with(options, namespace, method)
|
|
||||||
|
|
||||||
|
|
||||||
class AwakeAMTInterfaceTestCase(db_base.DbTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(AwakeAMTInterfaceTestCase, self).setUp()
|
|
||||||
amt_common.AMT_AWAKE_CACHE = {}
|
|
||||||
self.info = INFO_DICT
|
|
||||||
self.node = obj_utils.create_test_node(self.context,
|
|
||||||
driver='fake_amt',
|
|
||||||
driver_info=self.info)
|
|
||||||
|
|
||||||
@mock.patch.object(utils, 'execute', spec_set=True, autospec=True)
|
|
||||||
def test_awake_amt_interface(self, mock_ex):
|
|
||||||
amt_common.awake_amt_interface(self.node)
|
|
||||||
expected_args = ['ping', '-i', 0.2, '-c', 5, '1.2.3.4']
|
|
||||||
mock_ex.assert_called_once_with(*expected_args)
|
|
||||||
|
|
||||||
@mock.patch.object(utils, 'execute', spec_set=True, autospec=True)
|
|
||||||
def test_awake_amt_interface_fail(self, mock_ex):
|
|
||||||
mock_ex.side_effect = processutils.ProcessExecutionError('x')
|
|
||||||
self.assertRaises(exception.AMTConnectFailure,
|
|
||||||
amt_common.awake_amt_interface,
|
|
||||||
self.node)
|
|
||||||
|
|
||||||
@mock.patch.object(utils, 'execute', spec_set=True, autospec=True)
|
|
||||||
def test_awake_amt_interface_in_cache_time(self, mock_ex):
|
|
||||||
amt_common.AMT_AWAKE_CACHE[self.node.uuid] = time.time()
|
|
||||||
amt_common.awake_amt_interface(self.node)
|
|
||||||
self.assertFalse(mock_ex.called)
|
|
||||||
|
|
||||||
@mock.patch.object(utils, 'execute', spec_set=True, autospec=True)
|
|
||||||
def test_awake_amt_interface_disable(self, mock_ex):
|
|
||||||
CONF.set_override('awake_interval', 0, 'amt')
|
|
||||||
amt_common.awake_amt_interface(self.node)
|
|
||||||
self.assertFalse(mock_ex.called)
|
|
||||||
|
|
||||||
def test_out_range_protocol(self):
|
|
||||||
self.assertRaises(ValueError, cfg.CONF.set_override,
|
|
||||||
'protocol', 'fake', 'amt',
|
|
||||||
enforce_type=True)
|
|
@ -1,242 +0,0 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Test class for AMT ManagementInterface
|
|
||||||
"""
|
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.conductor import task_manager
|
|
||||||
from ironic.drivers.modules.amt import common as amt_common
|
|
||||||
from ironic.drivers.modules.amt import management as amt_mgmt
|
|
||||||
from ironic.drivers.modules.amt import resource_uris
|
|
||||||
from ironic.tests.unit.conductor import mgr_utils
|
|
||||||
from ironic.tests.unit.db import base as db_base
|
|
||||||
from ironic.tests.unit.db import utils as db_utils
|
|
||||||
from ironic.tests.unit.drivers.modules.amt import utils as test_utils
|
|
||||||
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
|
||||||
as mock_specs
|
|
||||||
from ironic.tests.unit.objects import utils as obj_utils
|
|
||||||
|
|
||||||
INFO_DICT = db_utils.get_test_amt_info()
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC)
|
|
||||||
class AMTManagementInteralMethodsTestCase(db_base.DbTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AMTManagementInteralMethodsTestCase, self).setUp()
|
|
||||||
mgr_utils.mock_the_extension_manager(driver='fake_amt')
|
|
||||||
self.node = obj_utils.create_test_node(self.context,
|
|
||||||
driver='fake_amt',
|
|
||||||
driver_info=INFO_DICT)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test__set_boot_device_order(self, mock_aw, mock_client_pywsman):
|
|
||||||
namespace = resource_uris.CIM_BootConfigSetting
|
|
||||||
device = boot_devices.PXE
|
|
||||||
result_xml = test_utils.build_soap_xml([{'ReturnValue': '0'}],
|
|
||||||
namespace)
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.invoke.return_value = mock_xml
|
|
||||||
|
|
||||||
amt_mgmt._set_boot_device_order(self.node, device)
|
|
||||||
|
|
||||||
mock_pywsman.invoke.assert_called_once_with(
|
|
||||||
mock.ANY, namespace, 'ChangeBootOrder', mock.ANY)
|
|
||||||
self.assertTrue(mock_aw.called)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test__set_boot_device_order_fail(self, mock_aw, mock_client_pywsman):
|
|
||||||
namespace = resource_uris.CIM_BootConfigSetting
|
|
||||||
device = boot_devices.PXE
|
|
||||||
result_xml = test_utils.build_soap_xml([{'ReturnValue': '2'}],
|
|
||||||
namespace)
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.invoke.return_value = mock_xml
|
|
||||||
|
|
||||||
self.assertRaises(exception.AMTFailure,
|
|
||||||
amt_mgmt._set_boot_device_order, self.node, device)
|
|
||||||
mock_pywsman.invoke.assert_called_once_with(
|
|
||||||
mock.ANY, namespace, 'ChangeBootOrder', mock.ANY)
|
|
||||||
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.invoke.return_value = None
|
|
||||||
|
|
||||||
self.assertRaises(exception.AMTConnectFailure,
|
|
||||||
amt_mgmt._set_boot_device_order, self.node, device)
|
|
||||||
self.assertTrue(mock_aw.called)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test__enable_boot_config(self, mock_aw, mock_client_pywsman):
|
|
||||||
namespace = resource_uris.CIM_BootService
|
|
||||||
result_xml = test_utils.build_soap_xml([{'ReturnValue': '0'}],
|
|
||||||
namespace)
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.invoke.return_value = mock_xml
|
|
||||||
|
|
||||||
amt_mgmt._enable_boot_config(self.node)
|
|
||||||
|
|
||||||
mock_pywsman.invoke.assert_called_once_with(
|
|
||||||
mock.ANY, namespace, 'SetBootConfigRole', mock.ANY)
|
|
||||||
self.assertTrue(mock_aw.called)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test__enable_boot_config_fail(self, mock_aw, mock_client_pywsman):
|
|
||||||
namespace = resource_uris.CIM_BootService
|
|
||||||
result_xml = test_utils.build_soap_xml([{'ReturnValue': '2'}],
|
|
||||||
namespace)
|
|
||||||
mock_xml = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.invoke.return_value = mock_xml
|
|
||||||
|
|
||||||
self.assertRaises(exception.AMTFailure,
|
|
||||||
amt_mgmt._enable_boot_config, self.node)
|
|
||||||
mock_pywsman.invoke.assert_called_once_with(
|
|
||||||
mock.ANY, namespace, 'SetBootConfigRole', mock.ANY)
|
|
||||||
|
|
||||||
mock_pywsman = mock_client_pywsman.Client.return_value
|
|
||||||
mock_pywsman.invoke.return_value = None
|
|
||||||
|
|
||||||
self.assertRaises(exception.AMTConnectFailure,
|
|
||||||
amt_mgmt._enable_boot_config, self.node)
|
|
||||||
self.assertTrue(mock_aw.called)
|
|
||||||
|
|
||||||
|
|
||||||
class AMTManagementTestCase(db_base.DbTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AMTManagementTestCase, self).setUp()
|
|
||||||
mgr_utils.mock_the_extension_manager(driver='fake_amt')
|
|
||||||
self.info = INFO_DICT
|
|
||||||
self.node = obj_utils.create_test_node(self.context,
|
|
||||||
driver='fake_amt',
|
|
||||||
driver_info=self.info)
|
|
||||||
|
|
||||||
def test_get_properties(self):
|
|
||||||
expected = amt_common.COMMON_PROPERTIES
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
self.assertEqual(expected, task.driver.get_properties())
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'parse_driver_info', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_validate(self, mock_drvinfo):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
task.driver.management.validate(task)
|
|
||||||
mock_drvinfo.assert_called_once_with(task.node)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'parse_driver_info', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_validate_fail(self, mock_drvinfo):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
mock_drvinfo.side_effect = exception.InvalidParameterValue('x')
|
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
|
||||||
task.driver.management.validate,
|
|
||||||
task)
|
|
||||||
|
|
||||||
def test_get_supported_boot_devices(self):
|
|
||||||
expected = [boot_devices.PXE, boot_devices.DISK, boot_devices.CDROM]
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
self.assertEqual(
|
|
||||||
sorted(expected),
|
|
||||||
sorted(task.driver.management.
|
|
||||||
get_supported_boot_devices(task)))
|
|
||||||
|
|
||||||
def test_set_boot_device_one_time(self):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=False) as task:
|
|
||||||
task.driver.management.set_boot_device(task, 'pxe')
|
|
||||||
self.assertEqual('pxe',
|
|
||||||
task.node.driver_internal_info["amt_boot_device"])
|
|
||||||
self.assertFalse(
|
|
||||||
task.node.driver_internal_info["amt_boot_persistent"])
|
|
||||||
|
|
||||||
def test_set_boot_device_persistent(self):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=False) as task:
|
|
||||||
task.driver.management.set_boot_device(task, 'pxe',
|
|
||||||
persistent=True)
|
|
||||||
self.assertEqual('pxe',
|
|
||||||
task.node.driver_internal_info["amt_boot_device"])
|
|
||||||
self.assertTrue(
|
|
||||||
task.node.driver_internal_info["amt_boot_persistent"])
|
|
||||||
|
|
||||||
def test_set_boot_device_fail(self):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=False) as task:
|
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
|
||||||
task.driver.management.set_boot_device,
|
|
||||||
task, 'fake-device')
|
|
||||||
|
|
||||||
@mock.patch.object(amt_mgmt, '_enable_boot_config', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(amt_mgmt, '_set_boot_device_order', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_ensure_next_boot_device_one_time(self, mock_sbdo, mock_ebc):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
device = boot_devices.PXE
|
|
||||||
task.node.driver_internal_info['amt_boot_device'] = 'pxe'
|
|
||||||
task.driver.management.ensure_next_boot_device(task.node, device)
|
|
||||||
self.assertEqual('disk',
|
|
||||||
task.node.driver_internal_info["amt_boot_device"])
|
|
||||||
self.assertTrue(
|
|
||||||
task.node.driver_internal_info["amt_boot_persistent"])
|
|
||||||
mock_sbdo.assert_called_once_with(task.node, device)
|
|
||||||
mock_ebc.assert_called_once_with(task.node)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_mgmt, '_enable_boot_config', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(amt_mgmt, '_set_boot_device_order', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_ensure_next_boot_device_persistent(self, mock_sbdo, mock_ebc):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
device = boot_devices.PXE
|
|
||||||
task.node.driver_internal_info['amt_boot_device'] = 'pxe'
|
|
||||||
task.node.driver_internal_info['amt_boot_persistent'] = True
|
|
||||||
task.driver.management.ensure_next_boot_device(task.node, device)
|
|
||||||
self.assertEqual('pxe',
|
|
||||||
task.node.driver_internal_info["amt_boot_device"])
|
|
||||||
self.assertTrue(
|
|
||||||
task.node.driver_internal_info["amt_boot_persistent"])
|
|
||||||
mock_sbdo.assert_called_once_with(task.node, device)
|
|
||||||
mock_ebc.assert_called_once_with(task.node)
|
|
||||||
|
|
||||||
def test_get_boot_device(self):
|
|
||||||
expected = {'boot_device': boot_devices.DISK, 'persistent': True}
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
self.assertEqual(expected,
|
|
||||||
task.driver.management.get_boot_device(task))
|
|
||||||
|
|
||||||
def test_get_sensor_data(self):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
self.assertRaises(NotImplementedError,
|
|
||||||
task.driver.management.get_sensors_data,
|
|
||||||
task)
|
|
@ -1,293 +0,0 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Test class for AMT ManagementInterface
|
|
||||||
"""
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.common import states
|
|
||||||
from ironic.conductor import task_manager
|
|
||||||
from ironic.drivers.modules.amt import common as amt_common
|
|
||||||
from ironic.drivers.modules.amt import management as amt_mgmt
|
|
||||||
from ironic.drivers.modules.amt import power as amt_power
|
|
||||||
from ironic.drivers.modules.amt import resource_uris
|
|
||||||
from ironic.tests.unit.conductor import mgr_utils
|
|
||||||
from ironic.tests.unit.db import base as db_base
|
|
||||||
from ironic.tests.unit.db import utils as db_utils
|
|
||||||
from ironic.tests.unit.drivers.modules.amt import utils as test_utils
|
|
||||||
from ironic.tests.unit.objects import utils as obj_utils
|
|
||||||
|
|
||||||
INFO_DICT = db_utils.get_test_amt_info()
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class AMTPowerInteralMethodsTestCase(db_base.DbTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AMTPowerInteralMethodsTestCase, self).setUp()
|
|
||||||
mgr_utils.mock_the_extension_manager(driver='fake_amt')
|
|
||||||
self.info = INFO_DICT
|
|
||||||
self.node = obj_utils.create_test_node(self.context,
|
|
||||||
driver='fake_amt',
|
|
||||||
driver_info=self.info)
|
|
||||||
CONF.set_override('max_attempts', 2, 'amt')
|
|
||||||
CONF.set_override('action_wait', 0, 'amt')
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(amt_common, 'get_wsman_client', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test__set_power_state(self, mock_client_pywsman, mock_aw):
|
|
||||||
namespace = resource_uris.CIM_PowerManagementService
|
|
||||||
mock_client = mock_client_pywsman.return_value
|
|
||||||
amt_power._set_power_state(self.node, states.POWER_ON)
|
|
||||||
mock_client.wsman_invoke.assert_called_once_with(
|
|
||||||
mock.ANY, namespace, 'RequestPowerStateChange', mock.ANY)
|
|
||||||
self.assertTrue(mock_aw.called)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(amt_common, 'get_wsman_client', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test__set_power_state_fail(self, mock_client_pywsman, mock_aw):
|
|
||||||
mock_client = mock_client_pywsman.return_value
|
|
||||||
mock_client.wsman_invoke.side_effect = exception.AMTFailure('x')
|
|
||||||
self.assertRaises(exception.AMTFailure,
|
|
||||||
amt_power._set_power_state,
|
|
||||||
self.node, states.POWER_ON)
|
|
||||||
self.assertTrue(mock_aw.called)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(amt_common, 'get_wsman_client', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test__power_status(self, mock_gwc, mock_aw):
|
|
||||||
namespace = resource_uris.CIM_AssociatedPowerManagementService
|
|
||||||
result_xml = test_utils.build_soap_xml([{'PowerState':
|
|
||||||
'2'}],
|
|
||||||
namespace)
|
|
||||||
mock_doc = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_client = mock_gwc.return_value
|
|
||||||
mock_client.wsman_get.return_value = mock_doc
|
|
||||||
self.assertEqual(
|
|
||||||
states.POWER_ON, amt_power._power_status(self.node))
|
|
||||||
|
|
||||||
result_xml = test_utils.build_soap_xml([{'PowerState':
|
|
||||||
'8'}],
|
|
||||||
namespace)
|
|
||||||
mock_doc = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_client = mock_gwc.return_value
|
|
||||||
mock_client.wsman_get.return_value = mock_doc
|
|
||||||
self.assertEqual(
|
|
||||||
states.POWER_OFF, amt_power._power_status(self.node))
|
|
||||||
|
|
||||||
result_xml = test_utils.build_soap_xml([{'PowerState':
|
|
||||||
'4'}],
|
|
||||||
namespace)
|
|
||||||
mock_doc = test_utils.mock_wsman_root(result_xml)
|
|
||||||
mock_client = mock_gwc.return_value
|
|
||||||
mock_client.wsman_get.return_value = mock_doc
|
|
||||||
self.assertEqual(
|
|
||||||
states.ERROR, amt_power._power_status(self.node))
|
|
||||||
self.assertTrue(mock_aw.called)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'awake_amt_interface', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(amt_common, 'get_wsman_client', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test__power_status_fail(self, mock_gwc, mock_aw):
|
|
||||||
mock_client = mock_gwc.return_value
|
|
||||||
mock_client.wsman_get.side_effect = exception.AMTFailure('x')
|
|
||||||
self.assertRaises(exception.AMTFailure,
|
|
||||||
amt_power._power_status,
|
|
||||||
self.node)
|
|
||||||
self.assertTrue(mock_aw.called)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_mgmt.AMTManagement, 'ensure_next_boot_device',
|
|
||||||
spec_set=True, autospec=True)
|
|
||||||
@mock.patch.object(amt_power, '_power_status', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(amt_power, '_set_power_state', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test__set_and_wait_power_on_with_boot_device(self, mock_sps,
|
|
||||||
mock_ps, mock_enbd):
|
|
||||||
target_state = states.POWER_ON
|
|
||||||
boot_device = boot_devices.PXE
|
|
||||||
mock_ps.side_effect = [states.POWER_OFF, states.POWER_ON]
|
|
||||||
mock_enbd.return_value = None
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
task.node.driver_internal_info['amt_boot_device'] = boot_device
|
|
||||||
result = amt_power._set_and_wait(task, target_state)
|
|
||||||
self.assertEqual(states.POWER_ON, result)
|
|
||||||
mock_enbd.assert_called_with(task.driver.management, task.node,
|
|
||||||
boot_devices.PXE)
|
|
||||||
mock_sps.assert_called_once_with(task.node, states.POWER_ON)
|
|
||||||
mock_ps.assert_called_with(task.node)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_power, '_power_status', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(amt_power, '_set_power_state', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test__set_and_wait_power_on_without_boot_device(self, mock_sps,
|
|
||||||
mock_ps):
|
|
||||||
target_state = states.POWER_ON
|
|
||||||
mock_ps.side_effect = [states.POWER_OFF, states.POWER_ON]
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
self.assertEqual(states.POWER_ON,
|
|
||||||
amt_power._set_and_wait(task, target_state))
|
|
||||||
mock_sps.assert_called_once_with(task.node, states.POWER_ON)
|
|
||||||
mock_ps.assert_called_with(task.node)
|
|
||||||
|
|
||||||
boot_device = boot_devices.DISK
|
|
||||||
self.node.driver_internal_info['amt_boot_device'] = boot_device
|
|
||||||
mock_ps.side_effect = [states.POWER_OFF, states.POWER_ON]
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
self.assertEqual(states.POWER_ON,
|
|
||||||
amt_power._set_and_wait(task, target_state))
|
|
||||||
mock_sps.assert_called_with(task.node, states.POWER_ON)
|
|
||||||
mock_ps.assert_called_with(task.node)
|
|
||||||
|
|
||||||
def test__set_and_wait_wrong_target_state(self):
|
|
||||||
target_state = 'fake-state'
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
|
||||||
amt_power._set_and_wait, task, target_state)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_power, '_power_status', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(amt_power, '_set_power_state', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test__set_and_wait_exceed_iterations(self, mock_sps,
|
|
||||||
mock_ps):
|
|
||||||
target_state = states.POWER_ON
|
|
||||||
mock_ps.side_effect = [states.POWER_OFF, states.POWER_OFF,
|
|
||||||
states.POWER_OFF]
|
|
||||||
mock_sps.return_value = exception.AMTFailure('x')
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
self.assertRaises(exception.PowerStateFailure,
|
|
||||||
amt_power._set_and_wait, task, target_state)
|
|
||||||
mock_sps.assert_called_with(task.node, states.POWER_ON)
|
|
||||||
mock_ps.assert_called_with(task.node)
|
|
||||||
self.assertEqual(3, mock_ps.call_count)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_power, '_power_status', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test__set_and_wait_already_target_state(self, mock_ps):
|
|
||||||
target_state = states.POWER_ON
|
|
||||||
mock_ps.side_effect = [states.POWER_ON]
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
self.assertEqual(states.POWER_ON,
|
|
||||||
amt_power._set_and_wait(task, target_state))
|
|
||||||
mock_ps.assert_called_with(task.node)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_power, '_power_status', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
@mock.patch.object(amt_power, '_set_power_state', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test__set_and_wait_power_off(self, mock_sps, mock_ps):
|
|
||||||
target_state = states.POWER_OFF
|
|
||||||
mock_ps.side_effect = [states.POWER_ON, states.POWER_OFF]
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
self.assertEqual(states.POWER_OFF,
|
|
||||||
amt_power._set_and_wait(task, target_state))
|
|
||||||
mock_sps.assert_called_once_with(task.node, states.POWER_OFF)
|
|
||||||
mock_ps.assert_called_with(task.node)
|
|
||||||
|
|
||||||
|
|
||||||
class AMTPowerTestCase(db_base.DbTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AMTPowerTestCase, self).setUp()
|
|
||||||
mgr_utils.mock_the_extension_manager(driver='fake_amt')
|
|
||||||
self.info = INFO_DICT
|
|
||||||
self.node = obj_utils.create_test_node(self.context,
|
|
||||||
driver='fake_amt',
|
|
||||||
driver_info=self.info)
|
|
||||||
|
|
||||||
def test_get_properties(self):
|
|
||||||
expected = amt_common.COMMON_PROPERTIES
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
self.assertEqual(expected, task.driver.get_properties())
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'parse_driver_info', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_validate(self, mock_drvinfo):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
task.driver.power.validate(task)
|
|
||||||
mock_drvinfo.assert_called_once_with(task.node)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_common, 'parse_driver_info', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_validate_fail(self, mock_drvinfo):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
mock_drvinfo.side_effect = exception.InvalidParameterValue('x')
|
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
|
||||||
task.driver.power.validate,
|
|
||||||
task)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_power, '_power_status', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_get_power_state(self, mock_ps):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
mock_ps.return_value = states.POWER_ON
|
|
||||||
self.assertEqual(states.POWER_ON,
|
|
||||||
task.driver.power.get_power_state(task))
|
|
||||||
mock_ps.assert_called_once_with(task.node)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_power, '_set_and_wait', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_set_power_state(self, mock_saw):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=False) as task:
|
|
||||||
pstate = states.POWER_ON
|
|
||||||
mock_saw.return_value = states.POWER_ON
|
|
||||||
task.driver.power.set_power_state(task, pstate)
|
|
||||||
mock_saw.assert_called_once_with(task, pstate)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_power, '_set_and_wait', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_set_power_state_fail(self, mock_saw):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=False) as task:
|
|
||||||
pstate = states.POWER_ON
|
|
||||||
mock_saw.side_effect = exception.PowerStateFailure('x')
|
|
||||||
self.assertRaises(exception.PowerStateFailure,
|
|
||||||
task.driver.power.set_power_state,
|
|
||||||
task, pstate)
|
|
||||||
mock_saw.assert_called_once_with(task, pstate)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_power, '_set_and_wait', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_reboot(self, mock_saw):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=False) as task:
|
|
||||||
task.driver.power.reboot(task)
|
|
||||||
calls = [mock.call(task, states.POWER_OFF),
|
|
||||||
mock.call(task, states.POWER_ON)]
|
|
||||||
mock_saw.assert_has_calls(calls)
|
|
@ -1,90 +0,0 @@
|
|||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Test class for AMT Vendor methods."""
|
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
|
||||||
from ironic.common import states
|
|
||||||
from ironic.conductor import task_manager
|
|
||||||
from ironic.drivers.modules.amt import management as amt_mgmt
|
|
||||||
from ironic.drivers.modules import iscsi_deploy
|
|
||||||
from ironic.tests.unit.conductor import mgr_utils
|
|
||||||
from ironic.tests.unit.db import base as db_base
|
|
||||||
from ironic.tests.unit.db import utils as db_utils
|
|
||||||
from ironic.tests.unit.objects import utils as obj_utils
|
|
||||||
|
|
||||||
INFO_DICT = db_utils.get_test_amt_info()
|
|
||||||
|
|
||||||
|
|
||||||
class AMTPXEVendorPassthruTestCase(db_base.DbTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AMTPXEVendorPassthruTestCase, self).setUp()
|
|
||||||
mgr_utils.mock_the_extension_manager(driver="pxe_amt")
|
|
||||||
self.node = obj_utils.create_test_node(
|
|
||||||
self.context, driver='pxe_amt', driver_info=INFO_DICT)
|
|
||||||
|
|
||||||
def test_vendor_routes(self):
|
|
||||||
expected = ['heartbeat']
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
vendor_routes = task.driver.vendor.vendor_routes
|
|
||||||
self.assertIsInstance(vendor_routes, dict)
|
|
||||||
self.assertEqual(sorted(expected), sorted(list(vendor_routes)))
|
|
||||||
|
|
||||||
def test_driver_routes(self):
|
|
||||||
expected = ['lookup']
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
driver_routes = task.driver.vendor.driver_routes
|
|
||||||
self.assertIsInstance(driver_routes, dict)
|
|
||||||
self.assertEqual(sorted(expected), sorted(list(driver_routes)))
|
|
||||||
|
|
||||||
@mock.patch.object(amt_mgmt.AMTManagement, 'ensure_next_boot_device',
|
|
||||||
spec_set=True, autospec=True)
|
|
||||||
@mock.patch.object(iscsi_deploy.VendorPassthru, 'continue_deploy',
|
|
||||||
spec_set=True, autospec=True)
|
|
||||||
def test_vendorpassthru_continue_deploy_netboot(self,
|
|
||||||
mock_pxe_vendorpassthru,
|
|
||||||
mock_ensure):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=False) as task:
|
|
||||||
task.node.provision_state = states.DEPLOYWAIT
|
|
||||||
task.node.target_provision_state = states.ACTIVE
|
|
||||||
task.node.instance_info['capabilities'] = {
|
|
||||||
"boot_option": "netboot"
|
|
||||||
}
|
|
||||||
task.driver.vendor.continue_deploy(task)
|
|
||||||
mock_ensure.assert_called_with(
|
|
||||||
task.driver.management, task.node, boot_devices.PXE)
|
|
||||||
mock_pxe_vendorpassthru.assert_called_once_with(
|
|
||||||
task.driver.vendor, task)
|
|
||||||
|
|
||||||
@mock.patch.object(amt_mgmt.AMTManagement, 'ensure_next_boot_device',
|
|
||||||
spec_set=True, autospec=True)
|
|
||||||
@mock.patch.object(iscsi_deploy.VendorPassthru, 'continue_deploy',
|
|
||||||
spec_set=True, autospec=True)
|
|
||||||
def test_vendorpassthru_continue_deploy_localboot(self,
|
|
||||||
mock_pxe_vendorpassthru,
|
|
||||||
mock_ensure):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=False) as task:
|
|
||||||
task.node.provision_state = states.DEPLOYWAIT
|
|
||||||
task.node.target_provision_state = states.ACTIVE
|
|
||||||
task.node.instance_info['capabilities'] = {"boot_option": "local"}
|
|
||||||
task.driver.vendor.continue_deploy(task)
|
|
||||||
self.assertFalse(mock_ensure.called)
|
|
||||||
mock_pxe_vendorpassthru.assert_called_once_with(
|
|
||||||
task.driver.vendor, task)
|
|
@ -1,73 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Red Hat, Inc.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# 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 xml import etree
|
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
|
|
||||||
def build_soap_xml(items, namespace=None):
|
|
||||||
"""Build a SOAP XML.
|
|
||||||
|
|
||||||
:param items: a list of dictionaries where key is the element name
|
|
||||||
and the value is the element text.
|
|
||||||
:param namespace: the namespace for the elements, None for no
|
|
||||||
namespace. Defaults to None
|
|
||||||
:returns: a XML string.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _create_element(name, value=None):
|
|
||||||
xml_string = name
|
|
||||||
if namespace:
|
|
||||||
xml_string = "{%(namespace)s}%(item)s" % {'namespace': namespace,
|
|
||||||
'item': xml_string}
|
|
||||||
|
|
||||||
element = etree.ElementTree.Element(xml_string)
|
|
||||||
element.text = value
|
|
||||||
return element
|
|
||||||
|
|
||||||
soap_namespace = "http://www.w3.org/2003/05/soap-envelope"
|
|
||||||
envelope_element = etree.ElementTree.Element(
|
|
||||||
"{%s}Envelope" % soap_namespace)
|
|
||||||
body_element = etree.ElementTree.Element("{%s}Body" % soap_namespace)
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
for i in item:
|
|
||||||
insertion_point = _create_element(i)
|
|
||||||
if isinstance(item[i], dict):
|
|
||||||
for j, value in item[i].items():
|
|
||||||
insertion_point.append(_create_element(j, value))
|
|
||||||
else:
|
|
||||||
insertion_point.text = item[i]
|
|
||||||
|
|
||||||
body_element.append(insertion_point)
|
|
||||||
|
|
||||||
envelope_element.append(body_element)
|
|
||||||
return etree.ElementTree.tostring(envelope_element)
|
|
||||||
|
|
||||||
|
|
||||||
def mock_wsman_root(return_value):
|
|
||||||
"""Helper function to mock the root() from wsman client."""
|
|
||||||
mock_xml_root = mock.Mock(spec_set=['string'])
|
|
||||||
mock_xml_root.string.return_value = return_value
|
|
||||||
|
|
||||||
mock_xml = mock.Mock(spec_set=['context', 'root'])
|
|
||||||
mock_xml.context.return_value = None
|
|
||||||
mock_xml.root.return_value = mock_xml_root
|
|
||||||
|
|
||||||
return mock_xml
|
|
@ -1,411 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2014 Red Hat, Inc.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Test class for iBoot PDU driver module."""
|
|
||||||
|
|
||||||
import types
|
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from ironic.common import driver_factory
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.common import states
|
|
||||||
from ironic.conductor import task_manager
|
|
||||||
from ironic.drivers.modules import iboot
|
|
||||||
from ironic.tests.unit.conductor import mgr_utils
|
|
||||||
from ironic.tests.unit.db import base as db_base
|
|
||||||
from ironic.tests.unit.db import utils as db_utils
|
|
||||||
from ironic.tests.unit.objects import utils as obj_utils
|
|
||||||
|
|
||||||
|
|
||||||
INFO_DICT = db_utils.get_test_iboot_info()
|
|
||||||
|
|
||||||
|
|
||||||
class IBootPrivateMethodTestCase(db_base.DbTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(IBootPrivateMethodTestCase, self).setUp()
|
|
||||||
self.config(max_retry=0, group='iboot')
|
|
||||||
self.config(retry_interval=0, group='iboot')
|
|
||||||
|
|
||||||
def test__parse_driver_info_good(self):
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=INFO_DICT)
|
|
||||||
info = iboot._parse_driver_info(node)
|
|
||||||
self.assertEqual(INFO_DICT['iboot_address'], info['address'])
|
|
||||||
self.assertEqual(INFO_DICT['iboot_username'], info['username'])
|
|
||||||
self.assertEqual(INFO_DICT['iboot_password'], info['password'])
|
|
||||||
self.assertEqual(9100, info['port'])
|
|
||||||
self.assertEqual(1, info['relay_id'])
|
|
||||||
|
|
||||||
def test__parse_driver_info_good_with_explicit_port(self):
|
|
||||||
info = dict(INFO_DICT)
|
|
||||||
info['iboot_port'] = '1234'
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=info)
|
|
||||||
info = iboot._parse_driver_info(node)
|
|
||||||
self.assertEqual(1234, info['port'])
|
|
||||||
|
|
||||||
def test__parse_driver_info_good_with_explicit_relay_id(self):
|
|
||||||
info = dict(INFO_DICT)
|
|
||||||
info['iboot_relay_id'] = '2'
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=info)
|
|
||||||
info = iboot._parse_driver_info(node)
|
|
||||||
self.assertEqual(2, info['relay_id'])
|
|
||||||
|
|
||||||
def test__parse_driver_info_missing_address(self):
|
|
||||||
info = dict(INFO_DICT)
|
|
||||||
del info['iboot_address']
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=info)
|
|
||||||
self.assertRaises(exception.MissingParameterValue,
|
|
||||||
iboot._parse_driver_info,
|
|
||||||
node)
|
|
||||||
|
|
||||||
def test__parse_driver_info_missing_username(self):
|
|
||||||
info = dict(INFO_DICT)
|
|
||||||
del info['iboot_username']
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=info)
|
|
||||||
self.assertRaises(exception.MissingParameterValue,
|
|
||||||
iboot._parse_driver_info,
|
|
||||||
node)
|
|
||||||
|
|
||||||
def test__parse_driver_info_missing_password(self):
|
|
||||||
info = dict(INFO_DICT)
|
|
||||||
del info['iboot_password']
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=info)
|
|
||||||
self.assertRaises(exception.MissingParameterValue,
|
|
||||||
iboot._parse_driver_info,
|
|
||||||
node)
|
|
||||||
|
|
||||||
def test__parse_driver_info_bad_port(self):
|
|
||||||
info = dict(INFO_DICT)
|
|
||||||
info['iboot_port'] = 'not-integer'
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=info)
|
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
|
||||||
iboot._parse_driver_info,
|
|
||||||
node)
|
|
||||||
|
|
||||||
def test__parse_driver_info_bad_relay_id(self):
|
|
||||||
info = dict(INFO_DICT)
|
|
||||||
info['iboot_relay_id'] = 'not-integer'
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=info)
|
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
|
||||||
iboot._parse_driver_info,
|
|
||||||
node)
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_get_connection', autospec=True)
|
|
||||||
def test__power_status_on(self, mock_get_conn):
|
|
||||||
mock_connection = mock.MagicMock(spec_set=['get_relays'])
|
|
||||||
mock_connection.get_relays.return_value = [True]
|
|
||||||
mock_get_conn.return_value = mock_connection
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=INFO_DICT)
|
|
||||||
info = iboot._parse_driver_info(node)
|
|
||||||
|
|
||||||
status = iboot._power_status(info)
|
|
||||||
|
|
||||||
self.assertEqual(states.POWER_ON, status)
|
|
||||||
mock_get_conn.assert_called_once_with(info)
|
|
||||||
mock_connection.get_relays.assert_called_once_with()
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_get_connection', autospec=True)
|
|
||||||
def test__power_status_off(self, mock_get_conn):
|
|
||||||
mock_connection = mock.MagicMock(spec_set=['get_relays'])
|
|
||||||
mock_connection.get_relays.return_value = [False]
|
|
||||||
mock_get_conn.return_value = mock_connection
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=INFO_DICT)
|
|
||||||
info = iboot._parse_driver_info(node)
|
|
||||||
|
|
||||||
status = iboot._power_status(info)
|
|
||||||
|
|
||||||
self.assertEqual(states.POWER_OFF, status)
|
|
||||||
mock_get_conn.assert_called_once_with(info)
|
|
||||||
mock_connection.get_relays.assert_called_once_with()
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_get_connection', autospec=True)
|
|
||||||
def test__power_status_exception(self, mock_get_conn):
|
|
||||||
mock_connection = mock.MagicMock(spec_set=['get_relays'])
|
|
||||||
mock_connection.get_relays.return_value = None
|
|
||||||
mock_get_conn.return_value = mock_connection
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=INFO_DICT)
|
|
||||||
info = iboot._parse_driver_info(node)
|
|
||||||
|
|
||||||
status = iboot._power_status(info)
|
|
||||||
self.assertEqual(states.ERROR, status)
|
|
||||||
mock_get_conn.assert_called_once_with(info)
|
|
||||||
mock_connection.get_relays.assert_called_once_with()
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_get_connection', autospec=True)
|
|
||||||
def test__power_status_exception_type_error(self, mock_get_conn):
|
|
||||||
mock_connection = mock.MagicMock(spec_set=['get_relays'])
|
|
||||||
side_effect = TypeError("Surprise!")
|
|
||||||
mock_connection.get_relays.side_effect = side_effect
|
|
||||||
|
|
||||||
mock_get_conn.return_value = mock_connection
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=INFO_DICT)
|
|
||||||
info = iboot._parse_driver_info(node)
|
|
||||||
|
|
||||||
status = iboot._power_status(info)
|
|
||||||
self.assertEqual(states.ERROR, status)
|
|
||||||
mock_get_conn.assert_called_once_with(info)
|
|
||||||
mock_connection.get_relays.assert_called_once_with()
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_get_connection', autospec=True)
|
|
||||||
def test__power_status_exception_index_error(self, mock_get_conn):
|
|
||||||
mock_connection = mock.MagicMock(spec_set=['get_relays'])
|
|
||||||
side_effect = IndexError("Gotcha!")
|
|
||||||
mock_connection.get_relays.side_effect = side_effect
|
|
||||||
|
|
||||||
mock_get_conn.return_value = mock_connection
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=INFO_DICT)
|
|
||||||
info = iboot._parse_driver_info(node)
|
|
||||||
status = iboot._power_status(info)
|
|
||||||
self.assertEqual(states.ERROR, status)
|
|
||||||
|
|
||||||
mock_get_conn.assert_called_once_with(info)
|
|
||||||
mock_connection.get_relays.assert_called_once_with()
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_get_connection', autospec=True)
|
|
||||||
def test__power_status_error(self, mock_get_conn):
|
|
||||||
mock_connection = mock.MagicMock(spec_set=['get_relays'])
|
|
||||||
mock_connection.get_relays.return_value = list()
|
|
||||||
mock_get_conn.return_value = mock_connection
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=INFO_DICT)
|
|
||||||
info = iboot._parse_driver_info(node)
|
|
||||||
|
|
||||||
status = iboot._power_status(info)
|
|
||||||
|
|
||||||
self.assertEqual(states.ERROR, status)
|
|
||||||
mock_get_conn.assert_called_once_with(info)
|
|
||||||
mock_connection.get_relays.assert_called_once_with()
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_get_connection', autospec=True)
|
|
||||||
def test__power_status_retries(self, mock_get_conn):
|
|
||||||
self.config(max_retry=1, group='iboot')
|
|
||||||
|
|
||||||
mock_connection = mock.MagicMock(spec_set=['get_relays'])
|
|
||||||
side_effect = TypeError("Surprise!")
|
|
||||||
mock_connection.get_relays.side_effect = side_effect
|
|
||||||
|
|
||||||
mock_get_conn.return_value = mock_connection
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=INFO_DICT)
|
|
||||||
info = iboot._parse_driver_info(node)
|
|
||||||
|
|
||||||
status = iboot._power_status(info)
|
|
||||||
self.assertEqual(states.ERROR, status)
|
|
||||||
mock_get_conn.assert_called_once_with(info)
|
|
||||||
self.assertEqual(2, mock_connection.get_relays.call_count)
|
|
||||||
|
|
||||||
|
|
||||||
class IBootDriverTestCase(db_base.DbTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(IBootDriverTestCase, self).setUp()
|
|
||||||
self.config(max_retry=0, group='iboot')
|
|
||||||
self.config(retry_interval=0, group='iboot')
|
|
||||||
self.config(reboot_delay=0, group='iboot')
|
|
||||||
mgr_utils.mock_the_extension_manager(driver='fake_iboot')
|
|
||||||
self.driver = driver_factory.get_driver('fake_iboot')
|
|
||||||
self.node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
driver='fake_iboot',
|
|
||||||
driver_info=INFO_DICT)
|
|
||||||
self.info = iboot._parse_driver_info(self.node)
|
|
||||||
|
|
||||||
def test_get_properties(self):
|
|
||||||
expected = iboot.COMMON_PROPERTIES
|
|
||||||
with task_manager.acquire(
|
|
||||||
self.context, self.node.uuid, shared=True) as task:
|
|
||||||
self.assertEqual(expected, task.driver.get_properties())
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_power_status', autospec=True)
|
|
||||||
@mock.patch.object(iboot, '_switch', autospec=True)
|
|
||||||
def test_set_power_state_good(self, mock_switch, mock_power_status):
|
|
||||||
mock_power_status.return_value = states.POWER_ON
|
|
||||||
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
|
||||||
task.driver.power.set_power_state(task, states.POWER_ON)
|
|
||||||
|
|
||||||
# ensure functions were called with the valid parameters
|
|
||||||
mock_switch.assert_called_once_with(self.info, True)
|
|
||||||
mock_power_status.assert_called_once_with(self.info)
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_power_status', autospec=True)
|
|
||||||
@mock.patch.object(iboot, '_switch', autospec=True)
|
|
||||||
def test_set_power_state_bad(self, mock_switch, mock_power_status):
|
|
||||||
mock_power_status.return_value = states.POWER_OFF
|
|
||||||
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
|
||||||
self.assertRaises(exception.PowerStateFailure,
|
|
||||||
task.driver.power.set_power_state,
|
|
||||||
task, states.POWER_ON)
|
|
||||||
|
|
||||||
# ensure functions were called with the valid parameters
|
|
||||||
mock_switch.assert_called_once_with(self.info, True)
|
|
||||||
mock_power_status.assert_called_once_with(self.info)
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_power_status', autospec=True)
|
|
||||||
@mock.patch.object(iboot, '_switch', autospec=True)
|
|
||||||
def test_set_power_state_retry(self, mock_switch, mock_power_status):
|
|
||||||
self.config(max_retry=2, group='iboot')
|
|
||||||
mock_power_status.return_value = states.POWER_OFF
|
|
||||||
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
|
||||||
self.assertRaises(exception.PowerStateFailure,
|
|
||||||
task.driver.power.set_power_state,
|
|
||||||
task, states.POWER_ON)
|
|
||||||
|
|
||||||
# ensure functions were called with the valid parameters
|
|
||||||
mock_switch.assert_called_once_with(self.info, True)
|
|
||||||
# 1 + 2 retries
|
|
||||||
self.assertEqual(3, mock_power_status.call_count)
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_power_status', autospec=True)
|
|
||||||
@mock.patch.object(iboot, '_switch', autospec=True)
|
|
||||||
def test_set_power_state_invalid_parameter(self, mock_switch,
|
|
||||||
mock_power_status):
|
|
||||||
mock_power_status.return_value = states.POWER_ON
|
|
||||||
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
|
||||||
task.driver.power.set_power_state,
|
|
||||||
task, states.NOSTATE)
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_sleep_switch', spec_set=types.FunctionType)
|
|
||||||
@mock.patch.object(iboot, '_power_status', autospec=True)
|
|
||||||
@mock.patch.object(iboot, '_switch', spec_set=types.FunctionType)
|
|
||||||
def test_reboot_good(self, mock_switch, mock_power_status,
|
|
||||||
mock_sleep_switch):
|
|
||||||
self.config(reboot_delay=3, group='iboot')
|
|
||||||
manager = mock.MagicMock(spec_set=['switch', 'sleep'])
|
|
||||||
mock_power_status.return_value = states.POWER_ON
|
|
||||||
|
|
||||||
manager.attach_mock(mock_switch, 'switch')
|
|
||||||
manager.attach_mock(mock_sleep_switch, 'sleep')
|
|
||||||
expected = [mock.call.switch(self.info, False),
|
|
||||||
mock.call.sleep(3),
|
|
||||||
mock.call.switch(self.info, True)]
|
|
||||||
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
|
||||||
task.driver.power.reboot(task)
|
|
||||||
|
|
||||||
self.assertEqual(manager.mock_calls, expected)
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_sleep_switch', spec_set=types.FunctionType)
|
|
||||||
@mock.patch.object(iboot, '_power_status', autospec=True)
|
|
||||||
@mock.patch.object(iboot, '_switch', spec_set=types.FunctionType)
|
|
||||||
def test_reboot_bad(self, mock_switch, mock_power_status,
|
|
||||||
mock_sleep_switch):
|
|
||||||
self.config(reboot_delay=3, group='iboot')
|
|
||||||
manager = mock.MagicMock(spec_set=['switch', 'sleep'])
|
|
||||||
mock_power_status.return_value = states.POWER_OFF
|
|
||||||
|
|
||||||
manager.attach_mock(mock_switch, 'switch')
|
|
||||||
manager.attach_mock(mock_sleep_switch, 'sleep')
|
|
||||||
expected = [mock.call.switch(self.info, False),
|
|
||||||
mock.call.sleep(3),
|
|
||||||
mock.call.switch(self.info, True)]
|
|
||||||
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
|
||||||
self.assertRaises(exception.PowerStateFailure,
|
|
||||||
task.driver.power.reboot, task)
|
|
||||||
|
|
||||||
self.assertEqual(manager.mock_calls, expected)
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_power_status', autospec=True)
|
|
||||||
@mock.patch.object(iboot, '_get_connection', autospec=True)
|
|
||||||
def test__switch_retries(self, mock_get_conn, mock_power_status):
|
|
||||||
self.config(max_retry=1, group='iboot')
|
|
||||||
mock_power_status.return_value = states.POWER_ON
|
|
||||||
|
|
||||||
mock_connection = mock.MagicMock(spec_set=['switch'])
|
|
||||||
side_effect = TypeError("Surprise!")
|
|
||||||
mock_connection.switch.side_effect = side_effect
|
|
||||||
mock_get_conn.return_value = mock_connection
|
|
||||||
|
|
||||||
iboot._switch(self.info, False)
|
|
||||||
self.assertEqual(2, mock_connection.switch.call_count)
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_power_status', autospec=True)
|
|
||||||
def test_get_power_state(self, mock_power_status):
|
|
||||||
mock_power_status.return_value = states.POWER_ON
|
|
||||||
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
|
||||||
state = task.driver.power.get_power_state(task)
|
|
||||||
self.assertEqual(state, states.POWER_ON)
|
|
||||||
|
|
||||||
# ensure functions were called with the valid parameters
|
|
||||||
mock_power_status.assert_called_once_with(self.info)
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_parse_driver_info', autospec=True)
|
|
||||||
def test_validate_good(self, parse_drv_info_mock):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
task.driver.power.validate(task)
|
|
||||||
self.assertEqual(1, parse_drv_info_mock.call_count)
|
|
||||||
|
|
||||||
@mock.patch.object(iboot, '_parse_driver_info', autospec=True)
|
|
||||||
def test_validate_fails(self, parse_drv_info_mock):
|
|
||||||
side_effect = exception.InvalidParameterValue("Bad input")
|
|
||||||
parse_drv_info_mock.side_effect = side_effect
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=True) as task:
|
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
|
||||||
task.driver.power.validate, task)
|
|
||||||
self.assertEqual(1, parse_drv_info_mock.call_count)
|
|
@ -1,194 +0,0 @@
|
|||||||
# Copyright 2015 Red Hat, Inc.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Test class for Wake-On-Lan driver module."""
|
|
||||||
|
|
||||||
import socket
|
|
||||||
import time
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from oslo_utils import uuidutils
|
|
||||||
|
|
||||||
from ironic.common import driver_factory
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.common import states
|
|
||||||
from ironic.conductor import task_manager
|
|
||||||
from ironic.drivers.modules import wol
|
|
||||||
from ironic.tests.unit.conductor import mgr_utils
|
|
||||||
from ironic.tests.unit.db import base as db_base
|
|
||||||
from ironic.tests.unit.objects import utils as obj_utils
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(time, 'sleep', lambda *_: None)
|
|
||||||
class WakeOnLanPrivateMethodTestCase(db_base.DbTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(WakeOnLanPrivateMethodTestCase, self).setUp()
|
|
||||||
mgr_utils.mock_the_extension_manager(driver='fake_wol')
|
|
||||||
self.driver = driver_factory.get_driver('fake_wol')
|
|
||||||
self.node = obj_utils.create_test_node(self.context,
|
|
||||||
driver='fake_wol')
|
|
||||||
self.port = obj_utils.create_test_port(self.context,
|
|
||||||
node_id=self.node.id)
|
|
||||||
|
|
||||||
def test__parse_parameters(self):
|
|
||||||
with task_manager.acquire(
|
|
||||||
self.context, self.node.uuid, shared=True) as task:
|
|
||||||
params = wol._parse_parameters(task)
|
|
||||||
self.assertEqual('255.255.255.255', params['host'])
|
|
||||||
self.assertEqual(9, params['port'])
|
|
||||||
|
|
||||||
def test__parse_parameters_non_default_params(self):
|
|
||||||
with task_manager.acquire(
|
|
||||||
self.context, self.node.uuid, shared=True) as task:
|
|
||||||
task.node.driver_info = {'wol_host': '1.2.3.4',
|
|
||||||
'wol_port': 7}
|
|
||||||
params = wol._parse_parameters(task)
|
|
||||||
self.assertEqual('1.2.3.4', params['host'])
|
|
||||||
self.assertEqual(7, params['port'])
|
|
||||||
|
|
||||||
def test__parse_parameters_no_ports_fail(self):
|
|
||||||
node = obj_utils.create_test_node(
|
|
||||||
self.context,
|
|
||||||
uuid=uuidutils.generate_uuid(),
|
|
||||||
driver='fake_wol')
|
|
||||||
with task_manager.acquire(
|
|
||||||
self.context, node.uuid, shared=True) as task:
|
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
|
||||||
wol._parse_parameters, task)
|
|
||||||
|
|
||||||
@mock.patch.object(socket, 'socket', autospec=True, spec_set=True)
|
|
||||||
def test_send_magic_packets(self, mock_socket):
|
|
||||||
fake_socket = mock.Mock(spec=socket, spec_set=True)
|
|
||||||
mock_socket.return_value = fake_socket()
|
|
||||||
obj_utils.create_test_port(self.context,
|
|
||||||
uuid=uuidutils.generate_uuid(),
|
|
||||||
address='aa:bb:cc:dd:ee:ff',
|
|
||||||
node_id=self.node.id)
|
|
||||||
with task_manager.acquire(
|
|
||||||
self.context, self.node.uuid, shared=True) as task:
|
|
||||||
wol._send_magic_packets(task, '255.255.255.255', 9)
|
|
||||||
|
|
||||||
expected_calls = [
|
|
||||||
mock.call(),
|
|
||||||
mock.call().setsockopt(socket.SOL_SOCKET,
|
|
||||||
socket.SO_BROADCAST, 1),
|
|
||||||
mock.call().sendto(mock.ANY, ('255.255.255.255', 9)),
|
|
||||||
mock.call().sendto(mock.ANY, ('255.255.255.255', 9)),
|
|
||||||
mock.call().close()]
|
|
||||||
|
|
||||||
fake_socket.assert_has_calls(expected_calls)
|
|
||||||
self.assertEqual(1, mock_socket.call_count)
|
|
||||||
|
|
||||||
@mock.patch.object(socket, 'socket', autospec=True, spec_set=True)
|
|
||||||
def test_send_magic_packets_network_sendto_error(self, mock_socket):
|
|
||||||
fake_socket = mock.Mock(spec=socket, spec_set=True)
|
|
||||||
fake_socket.return_value.sendto.side_effect = socket.error('boom')
|
|
||||||
mock_socket.return_value = fake_socket()
|
|
||||||
with task_manager.acquire(
|
|
||||||
self.context, self.node.uuid, shared=True) as task:
|
|
||||||
self.assertRaises(exception.WolOperationError,
|
|
||||||
wol._send_magic_packets,
|
|
||||||
task, '255.255.255.255', 9)
|
|
||||||
self.assertEqual(1, mock_socket.call_count)
|
|
||||||
# assert sendt0() was invoked
|
|
||||||
fake_socket.return_value.sendto.assert_called_once_with(
|
|
||||||
mock.ANY, ('255.255.255.255', 9))
|
|
||||||
|
|
||||||
@mock.patch.object(socket, 'socket', autospec=True, spec_set=True)
|
|
||||||
def test_magic_packet_format(self, mock_socket):
|
|
||||||
fake_socket = mock.Mock(spec=socket, spec_set=True)
|
|
||||||
mock_socket.return_value = fake_socket()
|
|
||||||
with task_manager.acquire(
|
|
||||||
self.context, self.node.uuid, shared=True) as task:
|
|
||||||
wol._send_magic_packets(task, '255.255.255.255', 9)
|
|
||||||
|
|
||||||
expct_packet = (b'\xff\xff\xff\xff\xff\xffRT\x00\xcf-1RT\x00'
|
|
||||||
b'\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT'
|
|
||||||
b'\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT\x00'
|
|
||||||
b'\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT'
|
|
||||||
b'\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1RT\x00\xcf-1')
|
|
||||||
mock_socket.return_value.sendto.assert_called_once_with(
|
|
||||||
expct_packet, ('255.255.255.255', 9))
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(time, 'sleep', lambda *_: None)
|
|
||||||
class WakeOnLanDriverTestCase(db_base.DbTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(WakeOnLanDriverTestCase, self).setUp()
|
|
||||||
mgr_utils.mock_the_extension_manager(driver='fake_wol')
|
|
||||||
self.driver = driver_factory.get_driver('fake_wol')
|
|
||||||
self.node = obj_utils.create_test_node(self.context,
|
|
||||||
driver='fake_wol')
|
|
||||||
self.port = obj_utils.create_test_port(self.context,
|
|
||||||
node_id=self.node.id)
|
|
||||||
|
|
||||||
def test_get_properties(self):
|
|
||||||
expected = wol.COMMON_PROPERTIES
|
|
||||||
with task_manager.acquire(
|
|
||||||
self.context, self.node.uuid, shared=True) as task:
|
|
||||||
self.assertEqual(expected, task.driver.get_properties())
|
|
||||||
|
|
||||||
def test_get_power_state(self):
|
|
||||||
with task_manager.acquire(
|
|
||||||
self.context, self.node.uuid, shared=True) as task:
|
|
||||||
task.node.power_state = states.POWER_ON
|
|
||||||
pstate = task.driver.power.get_power_state(task)
|
|
||||||
self.assertEqual(states.POWER_ON, pstate)
|
|
||||||
|
|
||||||
def test_get_power_state_nostate(self):
|
|
||||||
with task_manager.acquire(
|
|
||||||
self.context, self.node.uuid, shared=True) as task:
|
|
||||||
task.node.power_state = states.NOSTATE
|
|
||||||
pstate = task.driver.power.get_power_state(task)
|
|
||||||
self.assertEqual(states.POWER_OFF, pstate)
|
|
||||||
|
|
||||||
@mock.patch.object(wol, '_send_magic_packets', autospec=True,
|
|
||||||
spec_set=True)
|
|
||||||
def test_set_power_state_power_on(self, mock_magic):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
|
||||||
task.driver.power.set_power_state(task, states.POWER_ON)
|
|
||||||
mock_magic.assert_called_once_with(task, '255.255.255.255', 9)
|
|
||||||
|
|
||||||
@mock.patch.object(wol.LOG, 'info', autospec=True, spec_set=True)
|
|
||||||
@mock.patch.object(wol, '_send_magic_packets', autospec=True,
|
|
||||||
spec_set=True)
|
|
||||||
def test_set_power_state_power_off(self, mock_magic, mock_log):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
|
||||||
task.driver.power.set_power_state(task, states.POWER_OFF)
|
|
||||||
mock_log.assert_called_once_with(mock.ANY, self.node.uuid)
|
|
||||||
# assert magic packets weren't sent
|
|
||||||
self.assertFalse(mock_magic.called)
|
|
||||||
|
|
||||||
@mock.patch.object(wol, '_send_magic_packets', autospec=True,
|
|
||||||
spec_set=True)
|
|
||||||
def test_set_power_state_power_fail(self, mock_magic):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
|
||||||
task.driver.power.set_power_state,
|
|
||||||
task, 'wrong-state')
|
|
||||||
# assert magic packets weren't sent
|
|
||||||
self.assertFalse(mock_magic.called)
|
|
||||||
|
|
||||||
@mock.patch.object(wol.LOG, 'info', autospec=True, spec_set=True)
|
|
||||||
@mock.patch.object(wol.WakeOnLanPower, 'set_power_state', autospec=True,
|
|
||||||
spec_set=True)
|
|
||||||
def test_reboot(self, mock_power, mock_log):
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
|
||||||
task.driver.power.reboot(task)
|
|
||||||
mock_log.assert_called_once_with(mock.ANY, self.node.uuid)
|
|
||||||
mock_power.assert_called_once_with(task.driver.power, task,
|
|
||||||
states.POWER_ON)
|
|
@ -16,18 +16,10 @@
|
|||||||
Test class for Agent Deploy Driver
|
Test class for Agent Deploy Driver
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import mock
|
|
||||||
import testtools
|
|
||||||
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.drivers import agent
|
from ironic.drivers import agent
|
||||||
from ironic.drivers.modules import agent as agent_module
|
from ironic.drivers.modules import agent as agent_module
|
||||||
from ironic.drivers.modules.amt import management as amt_management
|
|
||||||
from ironic.drivers.modules.amt import power as amt_power
|
|
||||||
from ironic.drivers.modules import iboot
|
|
||||||
from ironic.drivers.modules import ipmitool
|
from ironic.drivers.modules import ipmitool
|
||||||
from ironic.drivers.modules import pxe
|
from ironic.drivers.modules import pxe
|
||||||
from ironic.drivers.modules import wol
|
|
||||||
from ironic.tests import base
|
from ironic.tests import base
|
||||||
|
|
||||||
|
|
||||||
@ -57,47 +49,3 @@ class AgentAndIPMIToolAndSocatDriverTestCase(base.TestCase):
|
|||||||
self.assertIsInstance(driver.management, ipmitool.IPMIManagement)
|
self.assertIsInstance(driver.management, ipmitool.IPMIManagement)
|
||||||
self.assertIsInstance(driver.vendor, ipmitool.VendorPassthru)
|
self.assertIsInstance(driver.vendor, ipmitool.VendorPassthru)
|
||||||
self.assertIsInstance(driver.raid, agent_module.AgentRAID)
|
self.assertIsInstance(driver.raid, agent_module.AgentRAID)
|
||||||
|
|
||||||
|
|
||||||
class AgentAndAMTDriverTestCase(testtools.TestCase):
|
|
||||||
|
|
||||||
@mock.patch.object(agent.importutils, 'try_import', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test___init__(self, mock_try_import):
|
|
||||||
mock_try_import.return_value = True
|
|
||||||
driver = agent.AgentAndAMTDriver()
|
|
||||||
|
|
||||||
self.assertIsInstance(driver.power, amt_power.AMTPower)
|
|
||||||
self.assertIsInstance(driver.boot, pxe.PXEBoot)
|
|
||||||
self.assertIsInstance(driver.deploy, agent_module.AgentDeploy)
|
|
||||||
self.assertIsInstance(driver.management, amt_management.AMTManagement)
|
|
||||||
self.assertIsInstance(driver.vendor, agent_module.AgentVendorInterface)
|
|
||||||
|
|
||||||
@mock.patch.object(agent.importutils, 'try_import')
|
|
||||||
def test___init___try_import_exception(self, mock_try_import):
|
|
||||||
mock_try_import.return_value = False
|
|
||||||
|
|
||||||
self.assertRaises(exception.DriverLoadError,
|
|
||||||
agent.AgentAndAMTDriver)
|
|
||||||
|
|
||||||
|
|
||||||
class AgentAndWakeOnLanDriverTestCase(testtools.TestCase):
|
|
||||||
|
|
||||||
def test___init__(self):
|
|
||||||
driver = agent.AgentAndWakeOnLanDriver()
|
|
||||||
|
|
||||||
self.assertIsInstance(driver.power, wol.WakeOnLanPower)
|
|
||||||
self.assertIsInstance(driver.boot, pxe.PXEBoot)
|
|
||||||
self.assertIsInstance(driver.deploy, agent_module.AgentDeploy)
|
|
||||||
self.assertIsInstance(driver.vendor, agent_module.AgentVendorInterface)
|
|
||||||
|
|
||||||
|
|
||||||
class AgentAndIBootDriverTestCase(testtools.TestCase):
|
|
||||||
|
|
||||||
def test___init__(self):
|
|
||||||
driver = agent.AgentAndIBootDriver()
|
|
||||||
|
|
||||||
self.assertIsInstance(driver.power, iboot.IBootPower)
|
|
||||||
self.assertIsInstance(driver.boot, pxe.PXEBoot)
|
|
||||||
self.assertIsInstance(driver.deploy, agent_module.AgentDeploy)
|
|
||||||
self.assertIsInstance(driver.vendor, agent_module.AgentVendorInterface)
|
|
||||||
|
@ -21,12 +21,8 @@ import testtools
|
|||||||
|
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.drivers.modules import agent
|
from ironic.drivers.modules import agent
|
||||||
from ironic.drivers.modules.amt import management as amt_management
|
|
||||||
from ironic.drivers.modules.amt import power as amt_power
|
|
||||||
from ironic.drivers.modules.amt import vendor as amt_vendor
|
|
||||||
from ironic.drivers.modules.cimc import management as cimc_management
|
from ironic.drivers.modules.cimc import management as cimc_management
|
||||||
from ironic.drivers.modules.cimc import power as cimc_power
|
from ironic.drivers.modules.cimc import power as cimc_power
|
||||||
from ironic.drivers.modules import iboot
|
|
||||||
from ironic.drivers.modules.ilo import console as ilo_console
|
from ironic.drivers.modules.ilo import console as ilo_console
|
||||||
from ironic.drivers.modules.ilo import inspect as ilo_inspect
|
from ironic.drivers.modules.ilo import inspect as ilo_inspect
|
||||||
from ironic.drivers.modules.ilo import management as ilo_management
|
from ironic.drivers.modules.ilo import management as ilo_management
|
||||||
@ -46,7 +42,6 @@ from ironic.drivers.modules import ssh
|
|||||||
from ironic.drivers.modules.ucs import management as ucs_management
|
from ironic.drivers.modules.ucs import management as ucs_management
|
||||||
from ironic.drivers.modules.ucs import power as ucs_power
|
from ironic.drivers.modules.ucs import power as ucs_power
|
||||||
from ironic.drivers.modules import virtualbox
|
from ironic.drivers.modules import virtualbox
|
||||||
from ironic.drivers.modules import wol
|
|
||||||
from ironic.drivers import pxe
|
from ironic.drivers import pxe
|
||||||
|
|
||||||
|
|
||||||
@ -134,26 +129,6 @@ class PXEDriversTestCase(testtools.TestCase):
|
|||||||
self.assertRaises(exception.DriverLoadError,
|
self.assertRaises(exception.DriverLoadError,
|
||||||
pxe.PXEAndSeaMicroDriver)
|
pxe.PXEAndSeaMicroDriver)
|
||||||
|
|
||||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_pxe_iboot_driver(self, try_import_mock):
|
|
||||||
try_import_mock.return_value = True
|
|
||||||
|
|
||||||
driver = pxe.PXEAndIBootDriver()
|
|
||||||
|
|
||||||
self.assertIsInstance(driver.power, iboot.IBootPower)
|
|
||||||
self.assertIsInstance(driver.boot, pxe_module.PXEBoot)
|
|
||||||
self.assertIsInstance(driver.deploy, iscsi_deploy.ISCSIDeploy)
|
|
||||||
self.assertIsInstance(driver.vendor, iscsi_deploy.VendorPassthru)
|
|
||||||
|
|
||||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_pxe_iboot_driver_import_error(self, try_import_mock):
|
|
||||||
try_import_mock.return_value = False
|
|
||||||
|
|
||||||
self.assertRaises(exception.DriverLoadError,
|
|
||||||
pxe.PXEAndIBootDriver)
|
|
||||||
|
|
||||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
||||||
autospec=True)
|
autospec=True)
|
||||||
def test_pxe_ilo_driver(self, try_import_mock):
|
def test_pxe_ilo_driver(self, try_import_mock):
|
||||||
@ -244,28 +219,6 @@ class PXEDriversTestCase(testtools.TestCase):
|
|||||||
self.assertRaises(exception.DriverLoadError,
|
self.assertRaises(exception.DriverLoadError,
|
||||||
pxe.PXEAndVirtualBoxDriver)
|
pxe.PXEAndVirtualBoxDriver)
|
||||||
|
|
||||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_pxe_amt_driver(self, try_import_mock):
|
|
||||||
try_import_mock.return_value = True
|
|
||||||
|
|
||||||
driver = pxe.PXEAndAMTDriver()
|
|
||||||
|
|
||||||
self.assertIsInstance(driver.power, amt_power.AMTPower)
|
|
||||||
self.assertIsInstance(driver.boot, pxe_module.PXEBoot)
|
|
||||||
self.assertIsInstance(driver.deploy, iscsi_deploy.ISCSIDeploy)
|
|
||||||
self.assertIsInstance(driver.management,
|
|
||||||
amt_management.AMTManagement)
|
|
||||||
self.assertIsInstance(driver.vendor, amt_vendor.AMTPXEVendorPassthru)
|
|
||||||
|
|
||||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_pxe_amt_driver_import_error(self, try_import_mock):
|
|
||||||
try_import_mock.return_value = False
|
|
||||||
|
|
||||||
self.assertRaises(exception.DriverLoadError,
|
|
||||||
pxe.PXEAndAMTDriver)
|
|
||||||
|
|
||||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
||||||
autospec=True)
|
autospec=True)
|
||||||
def test_pxe_msftocs_driver(self, try_import_mock):
|
def test_pxe_msftocs_driver(self, try_import_mock):
|
||||||
@ -320,15 +273,3 @@ class PXEDriversTestCase(testtools.TestCase):
|
|||||||
|
|
||||||
self.assertRaises(exception.DriverLoadError,
|
self.assertRaises(exception.DriverLoadError,
|
||||||
pxe.PXEAndCIMCDriver)
|
pxe.PXEAndCIMCDriver)
|
||||||
|
|
||||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
|
||||||
autospec=True)
|
|
||||||
def test_pxe_wakeonlan_driver(self, try_import_mock):
|
|
||||||
try_import_mock.return_value = True
|
|
||||||
|
|
||||||
driver = pxe.PXEAndWakeOnLanDriver()
|
|
||||||
|
|
||||||
self.assertIsInstance(driver.power, wol.WakeOnLanPower)
|
|
||||||
self.assertIsInstance(driver.boot, pxe_module.PXEBoot)
|
|
||||||
self.assertIsInstance(driver.deploy, iscsi_deploy.ISCSIDeploy)
|
|
||||||
self.assertIsInstance(driver.vendor, iscsi_deploy.VendorPassthru)
|
|
||||||
|
@ -33,11 +33,6 @@ DRACCLIENT_CONSTANTS_MOD_SPEC = (
|
|||||||
'REBOOT'
|
'REBOOT'
|
||||||
)
|
)
|
||||||
|
|
||||||
# iboot
|
|
||||||
IBOOT_SPEC = (
|
|
||||||
'iBootInterface',
|
|
||||||
)
|
|
||||||
|
|
||||||
# ironic_inspector
|
# ironic_inspector
|
||||||
IRONIC_INSPECTOR_CLIENT_SPEC = (
|
IRONIC_INSPECTOR_CLIENT_SPEC = (
|
||||||
'ClientV1',
|
'ClientV1',
|
||||||
|
@ -134,17 +134,6 @@ if not oneview_client:
|
|||||||
six.moves.reload_module(sys.modules['ironic.drivers.modules.oneview'])
|
six.moves.reload_module(sys.modules['ironic.drivers.modules.oneview'])
|
||||||
|
|
||||||
|
|
||||||
# attempt to load the external 'pywsman' library, which is required by
|
|
||||||
# the optional drivers.modules.amt module
|
|
||||||
pywsman = importutils.try_import('pywsman')
|
|
||||||
if not pywsman:
|
|
||||||
pywsman = mock.MagicMock(spec_set=mock_specs.PYWSMAN_SPEC)
|
|
||||||
sys.modules['pywsman'] = pywsman
|
|
||||||
# Now that the external library has been mocked, if anything had already
|
|
||||||
# loaded any of the drivers, reload them.
|
|
||||||
if 'ironic.drivers.modules.amt' in sys.modules:
|
|
||||||
six.moves.reload_module(sys.modules['ironic.drivers.modules.amt'])
|
|
||||||
|
|
||||||
# attempt to load the external 'python-dracclient' library, which is required
|
# attempt to load the external 'python-dracclient' library, which is required
|
||||||
# by the optional drivers.modules.drac module
|
# by the optional drivers.modules.drac module
|
||||||
dracclient = importutils.try_import('dracclient')
|
dracclient = importutils.try_import('dracclient')
|
||||||
@ -168,20 +157,6 @@ if not dracclient:
|
|||||||
if 'ironic.drivers.modules.drac' in sys.modules:
|
if 'ironic.drivers.modules.drac' in sys.modules:
|
||||||
six.moves.reload_module(sys.modules['ironic.drivers.modules.drac'])
|
six.moves.reload_module(sys.modules['ironic.drivers.modules.drac'])
|
||||||
|
|
||||||
# attempt to load the external 'iboot' library, which is required by
|
|
||||||
# the optional drivers.modules.iboot module
|
|
||||||
iboot = importutils.try_import("iboot")
|
|
||||||
if not iboot:
|
|
||||||
ib = mock.MagicMock(spec_set=mock_specs.IBOOT_SPEC)
|
|
||||||
ib.iBootInterface = mock.MagicMock(spec_set=[])
|
|
||||||
sys.modules['iboot'] = ib
|
|
||||||
|
|
||||||
# if anything has loaded the iboot driver yet, reload it now that the
|
|
||||||
# external library has been mocked
|
|
||||||
if 'ironic.drivers.modules.iboot' in sys.modules:
|
|
||||||
six.moves.reload_module(sys.modules['ironic.drivers.modules.iboot'])
|
|
||||||
|
|
||||||
|
|
||||||
# attempt to load the external 'pysnmp' library, which is required by
|
# attempt to load the external 'pysnmp' library, which is required by
|
||||||
# the optional drivers.modules.snmp module
|
# the optional drivers.modules.snmp module
|
||||||
pysnmp = importutils.try_import("pysnmp")
|
pysnmp = importutils.try_import("pysnmp")
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
upgrades:
|
||||||
|
- |
|
||||||
|
iBoot, Wake-On-LAN and AMT drivers are removed from ironic as they
|
||||||
|
neither have nor are planning to have a third-party CI.
|
||||||
|
They are still available from unsupported ironic driver collection
|
||||||
|
in ``ironic-staging-drivers`` repository.
|
||||||
|
If the ironic installation was using any driver based on those,
|
||||||
|
the operator has to install ``ironic-staging-drivers``
|
||||||
|
and change the driver on affected nodes
|
||||||
|
according to following correspondence list
|
||||||
|
|
||||||
|
* agent_amt -> pxe_amt_agent
|
||||||
|
* pxe_amt -> pxe_amt_iscsi
|
||||||
|
* agent_wol -> pxe_wol_agent
|
||||||
|
* pxe_wol -> pxe_wol_iscsi
|
||||||
|
* agent_iboot -> pxe_iboot_agent
|
||||||
|
* pxe_iboot -> pxe_iboot_iscsi
|
@ -42,8 +42,6 @@ ironic.dhcp =
|
|||||||
none = ironic.dhcp.none:NoneDHCPApi
|
none = ironic.dhcp.none:NoneDHCPApi
|
||||||
|
|
||||||
ironic.drivers =
|
ironic.drivers =
|
||||||
agent_amt = ironic.drivers.agent:AgentAndAMTDriver
|
|
||||||
agent_iboot = ironic.drivers.agent:AgentAndIBootDriver
|
|
||||||
agent_ilo = ironic.drivers.ilo:IloVirtualMediaAgentDriver
|
agent_ilo = ironic.drivers.ilo:IloVirtualMediaAgentDriver
|
||||||
agent_ipmitool = ironic.drivers.agent:AgentAndIPMIToolDriver
|
agent_ipmitool = ironic.drivers.agent:AgentAndIPMIToolDriver
|
||||||
agent_ipmitool_socat = ironic.drivers.agent:AgentAndIPMIToolAndSocatDriver
|
agent_ipmitool_socat = ironic.drivers.agent:AgentAndIPMIToolAndSocatDriver
|
||||||
@ -53,7 +51,6 @@ ironic.drivers =
|
|||||||
agent_ssh = ironic.drivers.agent:AgentAndSSHDriver
|
agent_ssh = ironic.drivers.agent:AgentAndSSHDriver
|
||||||
agent_vbox = ironic.drivers.agent:AgentAndVirtualBoxDriver
|
agent_vbox = ironic.drivers.agent:AgentAndVirtualBoxDriver
|
||||||
agent_ucs = ironic.drivers.agent:AgentAndUcsDriver
|
agent_ucs = ironic.drivers.agent:AgentAndUcsDriver
|
||||||
agent_wol = ironic.drivers.agent:AgentAndWakeOnLanDriver
|
|
||||||
fake = ironic.drivers.fake:FakeDriver
|
fake = ironic.drivers.fake:FakeDriver
|
||||||
fake_agent = ironic.drivers.fake:FakeAgentDriver
|
fake_agent = ironic.drivers.fake:FakeAgentDriver
|
||||||
fake_inspector = ironic.drivers.fake:FakeIPMIToolInspectorDriver
|
fake_inspector = ironic.drivers.fake:FakeIPMIToolInspectorDriver
|
||||||
@ -63,17 +60,14 @@ ironic.drivers =
|
|||||||
fake_ssh = ironic.drivers.fake:FakeSSHDriver
|
fake_ssh = ironic.drivers.fake:FakeSSHDriver
|
||||||
fake_pxe = ironic.drivers.fake:FakePXEDriver
|
fake_pxe = ironic.drivers.fake:FakePXEDriver
|
||||||
fake_seamicro = ironic.drivers.fake:FakeSeaMicroDriver
|
fake_seamicro = ironic.drivers.fake:FakeSeaMicroDriver
|
||||||
fake_iboot = ironic.drivers.fake:FakeIBootDriver
|
|
||||||
fake_ilo = ironic.drivers.fake:FakeIloDriver
|
fake_ilo = ironic.drivers.fake:FakeIloDriver
|
||||||
fake_drac = ironic.drivers.fake:FakeDracDriver
|
fake_drac = ironic.drivers.fake:FakeDracDriver
|
||||||
fake_snmp = ironic.drivers.fake:FakeSNMPDriver
|
fake_snmp = ironic.drivers.fake:FakeSNMPDriver
|
||||||
fake_irmc = ironic.drivers.fake:FakeIRMCDriver
|
fake_irmc = ironic.drivers.fake:FakeIRMCDriver
|
||||||
fake_vbox = ironic.drivers.fake:FakeVirtualBoxDriver
|
fake_vbox = ironic.drivers.fake:FakeVirtualBoxDriver
|
||||||
fake_amt = ironic.drivers.fake:FakeAMTDriver
|
|
||||||
fake_msftocs = ironic.drivers.fake:FakeMSFTOCSDriver
|
fake_msftocs = ironic.drivers.fake:FakeMSFTOCSDriver
|
||||||
fake_ucs = ironic.drivers.fake:FakeUcsDriver
|
fake_ucs = ironic.drivers.fake:FakeUcsDriver
|
||||||
fake_cimc = ironic.drivers.fake:FakeCIMCDriver
|
fake_cimc = ironic.drivers.fake:FakeCIMCDriver
|
||||||
fake_wol = ironic.drivers.fake:FakeWakeOnLanDriver
|
|
||||||
fake_oneview = ironic.drivers.fake:FakeOneViewDriver
|
fake_oneview = ironic.drivers.fake:FakeOneViewDriver
|
||||||
iscsi_ilo = ironic.drivers.ilo:IloVirtualMediaIscsiDriver
|
iscsi_ilo = ironic.drivers.ilo:IloVirtualMediaIscsiDriver
|
||||||
iscsi_irmc = ironic.drivers.irmc:IRMCVirtualMediaIscsiDriver
|
iscsi_irmc = ironic.drivers.irmc:IRMCVirtualMediaIscsiDriver
|
||||||
@ -84,16 +78,13 @@ ironic.drivers =
|
|||||||
pxe_ssh = ironic.drivers.pxe:PXEAndSSHDriver
|
pxe_ssh = ironic.drivers.pxe:PXEAndSSHDriver
|
||||||
pxe_vbox = ironic.drivers.pxe:PXEAndVirtualBoxDriver
|
pxe_vbox = ironic.drivers.pxe:PXEAndVirtualBoxDriver
|
||||||
pxe_seamicro = ironic.drivers.pxe:PXEAndSeaMicroDriver
|
pxe_seamicro = ironic.drivers.pxe:PXEAndSeaMicroDriver
|
||||||
pxe_iboot = ironic.drivers.pxe:PXEAndIBootDriver
|
|
||||||
pxe_ilo = ironic.drivers.pxe:PXEAndIloDriver
|
pxe_ilo = ironic.drivers.pxe:PXEAndIloDriver
|
||||||
pxe_drac = ironic.drivers.drac:PXEDracDriver
|
pxe_drac = ironic.drivers.drac:PXEDracDriver
|
||||||
pxe_drac_inspector = ironic.drivers.drac:PXEDracInspectorDriver
|
pxe_drac_inspector = ironic.drivers.drac:PXEDracInspectorDriver
|
||||||
pxe_snmp = ironic.drivers.pxe:PXEAndSNMPDriver
|
pxe_snmp = ironic.drivers.pxe:PXEAndSNMPDriver
|
||||||
pxe_irmc = ironic.drivers.pxe:PXEAndIRMCDriver
|
pxe_irmc = ironic.drivers.pxe:PXEAndIRMCDriver
|
||||||
pxe_amt = ironic.drivers.pxe:PXEAndAMTDriver
|
|
||||||
pxe_msftocs = ironic.drivers.pxe:PXEAndMSFTOCSDriver
|
pxe_msftocs = ironic.drivers.pxe:PXEAndMSFTOCSDriver
|
||||||
pxe_ucs = ironic.drivers.pxe:PXEAndUcsDriver
|
pxe_ucs = ironic.drivers.pxe:PXEAndUcsDriver
|
||||||
pxe_wol = ironic.drivers.pxe:PXEAndWakeOnLanDriver
|
|
||||||
pxe_iscsi_cimc = ironic.drivers.pxe:PXEAndCIMCDriver
|
pxe_iscsi_cimc = ironic.drivers.pxe:PXEAndCIMCDriver
|
||||||
pxe_agent_cimc = ironic.drivers.agent:AgentAndCIMCDriver
|
pxe_agent_cimc = ironic.drivers.agent:AgentAndCIMCDriver
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user