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``
|
||||
- Install python-dracclient package
|
||||
|
||||
AMT driver
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
../drivers/amt
|
||||
|
||||
SNMP driver
|
||||
-----------
|
||||
@ -89,24 +82,6 @@ Cisco UCS driver
|
||||
../drivers/ucs
|
||||
|
||||
|
||||
Wake-On-Lan driver
|
||||
------------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
../drivers/wol
|
||||
|
||||
|
||||
iBoot driver
|
||||
------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
../drivers/iboot
|
||||
|
||||
|
||||
CIMC driver
|
||||
-----------
|
||||
|
||||
@ -132,3 +107,16 @@ XenServer ssh driver
|
||||
:maxdepth: 1
|
||||
|
||||
../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``
|
||||
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
|
||||
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.
|
||||
|
||||
|
||||
|
@ -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
|
||||
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.
|
||||
# Refer documentation on how to install and configure this:
|
||||
# http://docs.openstack.org/developer/ironic/drivers/vbox.html
|
||||
|
@ -566,34 +566,6 @@
|
||||
#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]
|
||||
|
||||
#
|
||||
@ -1358,25 +1330,6 @@
|
||||
#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]
|
||||
|
||||
#
|
||||
|
@ -430,15 +430,6 @@ class IPMIFailure(IronicException):
|
||||
_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):
|
||||
_msg_fmt = _("MSFT OCS call failed.")
|
||||
|
||||
@ -659,10 +650,6 @@ class UcsConnectionError(IronicException):
|
||||
"%(node)s. Reason: %(error)s")
|
||||
|
||||
|
||||
class WolOperationError(IronicException):
|
||||
pass
|
||||
|
||||
|
||||
class ImageUploadFailed(IronicException):
|
||||
_msg_fmt = _("Failed to upload %(image_name)s image to web server "
|
||||
"%(web_server)s, reason: %(reason)s")
|
||||
|
@ -16,7 +16,6 @@
|
||||
from oslo_config import cfg
|
||||
|
||||
from ironic.conf import agent
|
||||
from ironic.conf import amt
|
||||
from ironic.conf import api
|
||||
from ironic.conf import audit
|
||||
from ironic.conf import cisco
|
||||
@ -28,7 +27,6 @@ from ironic.conf import deploy
|
||||
from ironic.conf import dhcp
|
||||
from ironic.conf import drac
|
||||
from ironic.conf import glance
|
||||
from ironic.conf import iboot
|
||||
from ironic.conf import ilo
|
||||
from ironic.conf import inspector
|
||||
from ironic.conf import ipmi
|
||||
@ -50,7 +48,6 @@ from ironic.conf import virtualbox
|
||||
CONF = cfg.CONF
|
||||
|
||||
agent.register_opts(CONF)
|
||||
amt.register_opts(CONF)
|
||||
api.register_opts(CONF)
|
||||
audit.register_opts(CONF)
|
||||
cisco.register_opts(CONF)
|
||||
@ -62,7 +59,6 @@ deploy.register_opts(CONF)
|
||||
drac.register_opts(CONF)
|
||||
dhcp.register_opts(CONF)
|
||||
glance.register_opts(CONF)
|
||||
iboot.register_opts(CONF)
|
||||
ilo.register_opts(CONF)
|
||||
inspector.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 = [
|
||||
('DEFAULT', itertools.chain(*_default_opt_lists)),
|
||||
('agent', ironic.conf.agent.opts),
|
||||
('amt', ironic.conf.amt.opts),
|
||||
('api', ironic.conf.api.opts),
|
||||
('audit', ironic.conf.audit.opts),
|
||||
('cimc', ironic.conf.cisco.cimc_opts),
|
||||
@ -43,7 +42,6 @@ _opts = [
|
||||
('dhcp', ironic.conf.dhcp.opts),
|
||||
('drac', ironic.conf.drac.opts),
|
||||
('glance', ironic.conf.glance.list_opts()),
|
||||
('iboot', ironic.conf.iboot.opts),
|
||||
('ilo', ironic.conf.ilo.opts),
|
||||
('inspector', ironic.conf.inspector.list_opts()),
|
||||
('ipmi', ironic.conf.ipmi.opts),
|
||||
|
@ -18,11 +18,8 @@ from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.drivers import base
|
||||
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 power as cimc_power
|
||||
from ironic.drivers.modules import iboot
|
||||
from ironic.drivers.modules import inspector
|
||||
from ironic.drivers.modules import ipminative
|
||||
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 power as ucs_power
|
||||
from ironic.drivers.modules import virtualbox
|
||||
from ironic.drivers.modules import wol
|
||||
|
||||
|
||||
class AgentAndIPMIToolDriver(base.BaseDriver):
|
||||
@ -155,30 +151,6 @@ class AgentAndVirtualBoxDriver(base.BaseDriver):
|
||||
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):
|
||||
"""Agent + Cisco UCSM driver.
|
||||
|
||||
@ -225,46 +197,3 @@ class AgentAndCIMCDriver(base.BaseDriver):
|
||||
self.management = cimc_mgmt.CIMCManagement()
|
||||
self.inspect = inspector.Inspector.create_if_enabled(
|
||||
'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.drivers import base
|
||||
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 power as cimc_power
|
||||
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 vendor_passthru as drac_vendor
|
||||
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 management as ilo_management
|
||||
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 power as ucs_power
|
||||
from ironic.drivers.modules import virtualbox
|
||||
from ironic.drivers.modules import wol
|
||||
from ironic.drivers import utils
|
||||
|
||||
|
||||
@ -171,20 +167,6 @@ class FakeAgentDriver(base.BaseDriver):
|
||||
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):
|
||||
"""Fake iLO driver, used in testing."""
|
||||
|
||||
@ -272,21 +254,6 @@ class FakeIPMIToolInspectorDriver(base.BaseDriver):
|
||||
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):
|
||||
"""Fake MSFT OCS driver."""
|
||||
|
||||
@ -324,16 +291,6 @@ class FakeCIMCDriver(base.BaseDriver):
|
||||
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):
|
||||
"""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.drivers import base
|
||||
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 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 deploy as ilo_deploy
|
||||
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 power as ucs_power
|
||||
from ironic.drivers.modules import virtualbox
|
||||
from ironic.drivers.modules import wol
|
||||
|
||||
|
||||
class PXEAndIPMIToolDriver(base.BaseDriver):
|
||||
@ -176,30 +171,6 @@ class PXEAndSeaMicroDriver(base.BaseDriver):
|
||||
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):
|
||||
"""PXE + Ilo Driver using IloClient interface.
|
||||
|
||||
@ -297,30 +268,6 @@ class PXEAndVirtualBoxDriver(base.BaseDriver):
|
||||
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):
|
||||
"""PXE + MSFT OCS driver.
|
||||
|
||||
@ -384,22 +331,3 @@ class PXEAndCIMCDriver(base.BaseDriver):
|
||||
self.management = cimc_mgmt.CIMCManagement()
|
||||
self.inspect = inspector.Inspector.create_if_enabled(
|
||||
'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():
|
||||
return {
|
||||
"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):
|
||||
result = {
|
||||
"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
|
||||
"""
|
||||
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.drivers import agent
|
||||
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 pxe
|
||||
from ironic.drivers.modules import wol
|
||||
from ironic.tests import base
|
||||
|
||||
|
||||
@ -57,47 +49,3 @@ class AgentAndIPMIToolAndSocatDriverTestCase(base.TestCase):
|
||||
self.assertIsInstance(driver.management, ipmitool.IPMIManagement)
|
||||
self.assertIsInstance(driver.vendor, ipmitool.VendorPassthru)
|
||||
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.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 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 inspect as ilo_inspect
|
||||
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 power as ucs_power
|
||||
from ironic.drivers.modules import virtualbox
|
||||
from ironic.drivers.modules import wol
|
||||
from ironic.drivers import pxe
|
||||
|
||||
|
||||
@ -134,26 +129,6 @@ class PXEDriversTestCase(testtools.TestCase):
|
||||
self.assertRaises(exception.DriverLoadError,
|
||||
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,
|
||||
autospec=True)
|
||||
def test_pxe_ilo_driver(self, try_import_mock):
|
||||
@ -244,28 +219,6 @@ class PXEDriversTestCase(testtools.TestCase):
|
||||
self.assertRaises(exception.DriverLoadError,
|
||||
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,
|
||||
autospec=True)
|
||||
def test_pxe_msftocs_driver(self, try_import_mock):
|
||||
@ -320,15 +273,3 @@ class PXEDriversTestCase(testtools.TestCase):
|
||||
|
||||
self.assertRaises(exception.DriverLoadError,
|
||||
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'
|
||||
)
|
||||
|
||||
# iboot
|
||||
IBOOT_SPEC = (
|
||||
'iBootInterface',
|
||||
)
|
||||
|
||||
# ironic_inspector
|
||||
IRONIC_INSPECTOR_CLIENT_SPEC = (
|
||||
'ClientV1',
|
||||
|
@ -134,17 +134,6 @@ if not oneview_client:
|
||||
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
|
||||
# by the optional drivers.modules.drac module
|
||||
dracclient = importutils.try_import('dracclient')
|
||||
@ -168,20 +157,6 @@ if not dracclient:
|
||||
if 'ironic.drivers.modules.drac' in sys.modules:
|
||||
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
|
||||
# the optional drivers.modules.snmp module
|
||||
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
|
||||
|
||||
ironic.drivers =
|
||||
agent_amt = ironic.drivers.agent:AgentAndAMTDriver
|
||||
agent_iboot = ironic.drivers.agent:AgentAndIBootDriver
|
||||
agent_ilo = ironic.drivers.ilo:IloVirtualMediaAgentDriver
|
||||
agent_ipmitool = ironic.drivers.agent:AgentAndIPMIToolDriver
|
||||
agent_ipmitool_socat = ironic.drivers.agent:AgentAndIPMIToolAndSocatDriver
|
||||
@ -53,7 +51,6 @@ ironic.drivers =
|
||||
agent_ssh = ironic.drivers.agent:AgentAndSSHDriver
|
||||
agent_vbox = ironic.drivers.agent:AgentAndVirtualBoxDriver
|
||||
agent_ucs = ironic.drivers.agent:AgentAndUcsDriver
|
||||
agent_wol = ironic.drivers.agent:AgentAndWakeOnLanDriver
|
||||
fake = ironic.drivers.fake:FakeDriver
|
||||
fake_agent = ironic.drivers.fake:FakeAgentDriver
|
||||
fake_inspector = ironic.drivers.fake:FakeIPMIToolInspectorDriver
|
||||
@ -63,17 +60,14 @@ ironic.drivers =
|
||||
fake_ssh = ironic.drivers.fake:FakeSSHDriver
|
||||
fake_pxe = ironic.drivers.fake:FakePXEDriver
|
||||
fake_seamicro = ironic.drivers.fake:FakeSeaMicroDriver
|
||||
fake_iboot = ironic.drivers.fake:FakeIBootDriver
|
||||
fake_ilo = ironic.drivers.fake:FakeIloDriver
|
||||
fake_drac = ironic.drivers.fake:FakeDracDriver
|
||||
fake_snmp = ironic.drivers.fake:FakeSNMPDriver
|
||||
fake_irmc = ironic.drivers.fake:FakeIRMCDriver
|
||||
fake_vbox = ironic.drivers.fake:FakeVirtualBoxDriver
|
||||
fake_amt = ironic.drivers.fake:FakeAMTDriver
|
||||
fake_msftocs = ironic.drivers.fake:FakeMSFTOCSDriver
|
||||
fake_ucs = ironic.drivers.fake:FakeUcsDriver
|
||||
fake_cimc = ironic.drivers.fake:FakeCIMCDriver
|
||||
fake_wol = ironic.drivers.fake:FakeWakeOnLanDriver
|
||||
fake_oneview = ironic.drivers.fake:FakeOneViewDriver
|
||||
iscsi_ilo = ironic.drivers.ilo:IloVirtualMediaIscsiDriver
|
||||
iscsi_irmc = ironic.drivers.irmc:IRMCVirtualMediaIscsiDriver
|
||||
@ -84,16 +78,13 @@ ironic.drivers =
|
||||
pxe_ssh = ironic.drivers.pxe:PXEAndSSHDriver
|
||||
pxe_vbox = ironic.drivers.pxe:PXEAndVirtualBoxDriver
|
||||
pxe_seamicro = ironic.drivers.pxe:PXEAndSeaMicroDriver
|
||||
pxe_iboot = ironic.drivers.pxe:PXEAndIBootDriver
|
||||
pxe_ilo = ironic.drivers.pxe:PXEAndIloDriver
|
||||
pxe_drac = ironic.drivers.drac:PXEDracDriver
|
||||
pxe_drac_inspector = ironic.drivers.drac:PXEDracInspectorDriver
|
||||
pxe_snmp = ironic.drivers.pxe:PXEAndSNMPDriver
|
||||
pxe_irmc = ironic.drivers.pxe:PXEAndIRMCDriver
|
||||
pxe_amt = ironic.drivers.pxe:PXEAndAMTDriver
|
||||
pxe_msftocs = ironic.drivers.pxe:PXEAndMSFTOCSDriver
|
||||
pxe_ucs = ironic.drivers.pxe:PXEAndUcsDriver
|
||||
pxe_wol = ironic.drivers.pxe:PXEAndWakeOnLanDriver
|
||||
pxe_iscsi_cimc = ironic.drivers.pxe:PXEAndCIMCDriver
|
||||
pxe_agent_cimc = ironic.drivers.agent:AgentAndCIMCDriver
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user