Remove most unsupported drivers
In accordance with ironic CI policies, this patch removesi some drivers that are not tested on upstream or third-party CI and for which maintainers have not communicated any plans to have such testing. This includes: - virtualbox drivers - seamicro drivers - msftocs drivers Change-Id: Ia3a7d798c877f4628946ee6c56d850b9847e6c3e Closes-Bug: #1663018
This commit is contained in:
parent
d601a11e91
commit
02ce7246ec
@ -125,9 +125,8 @@ The web console can be configured in Bare Metal service in the following way:
|
||||
ironic driver-properties <driver>
|
||||
|
||||
For ``*_ipmitool`` and ``*_ipminative`` drivers, this option is ``ipmi_terminal_port``.
|
||||
For ``seamicro`` driver, this option is ``seamicro_terminal_port``. Give a customized port
|
||||
number to ``<customized_port>``, for example ``8023``, this customized port is used in
|
||||
web console url.
|
||||
Give a customized port number to ``<customized_port>``,
|
||||
for example ``8023``, this customized port is used in web console url.
|
||||
|
||||
Get web console information for a node as follows::
|
||||
|
||||
|
@ -48,14 +48,6 @@ iLO driver
|
||||
|
||||
../drivers/ilo
|
||||
|
||||
SeaMicro driver
|
||||
---------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
../drivers/seamicro
|
||||
|
||||
iRMC driver
|
||||
-----------
|
||||
|
||||
@ -64,15 +56,6 @@ iRMC driver
|
||||
|
||||
../drivers/irmc
|
||||
|
||||
VirtualBox driver
|
||||
-----------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
../drivers/vbox
|
||||
|
||||
|
||||
Cisco UCS driver
|
||||
----------------
|
||||
|
||||
@ -118,5 +101,8 @@ 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_
|
||||
- Virtualbox drivers
|
||||
- SeaMicro drivers
|
||||
- MSFT OCS drivers
|
||||
|
||||
.. _ironic-staging-drivers: http://ironic-staging-drivers.readthedocs.io
|
||||
|
@ -1,117 +0,0 @@
|
||||
.. _SeaMicro:
|
||||
|
||||
===============
|
||||
SeaMicro driver
|
||||
===============
|
||||
|
||||
Overview
|
||||
========
|
||||
The SeaMicro power driver enables you to take advantage of power cycle
|
||||
management of servers (nodes) within the SeaMicro chassis. The SeaMicro
|
||||
driver is targeted for SeaMicro Fabric Compute systems.
|
||||
|
||||
Prerequisites
|
||||
=============
|
||||
|
||||
* ``python-seamicroclient`` is a python package which contains a set of modules
|
||||
for managing SeaMicro Fabric Compute systems.
|
||||
|
||||
Install ``python-seamicroclient`` [1]_ module on the Ironic conductor node.
|
||||
Minimum version required is 0.4.0::
|
||||
|
||||
$ pip install "python-seamicroclient>=0.4.0"
|
||||
|
||||
Drivers
|
||||
=======
|
||||
|
||||
pxe_seamicro driver
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Overview
|
||||
~~~~~~~~
|
||||
``pxe_seamicro`` driver uses PXE/iSCSI (just like ``pxe_ipmitool`` driver) to
|
||||
deploy the image and uses SeaMicro to do all management operations on the
|
||||
baremetal node (instead of using IPMI).
|
||||
|
||||
Target Users
|
||||
~~~~~~~~~~~~
|
||||
* Users who want to use PXE/iSCSI for deployment in their environment.
|
||||
* Users who want to use SeaMicro Fabric Compute systems.
|
||||
|
||||
Tested Platforms
|
||||
~~~~~~~~~~~~~~~~
|
||||
This driver works on SeaMicro Fabric Compute system.
|
||||
It has been tested with the following servers:
|
||||
|
||||
* SeaMicro SM15000-XN
|
||||
* SeaMicro SM15000-OP
|
||||
|
||||
Requirements
|
||||
~~~~~~~~~~~~
|
||||
None.
|
||||
|
||||
Configuring and Enabling the driver
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
1. Build or download a deploy image, see `Building or downloading a deploy ramdisk image`_.
|
||||
|
||||
2. Upload these images to Glance::
|
||||
|
||||
glance image-create --name deploy-ramdisk.kernel --disk-format aki --container-format aki < deploy-ramdisk.kernel
|
||||
glance image-create --name deploy-ramdisk.initramfs --disk-format ari --container-format ari < deploy-ramdisk.initramfs
|
||||
|
||||
3. Add ``pxe_seamicro`` to the list of ``enabled_drivers`` in
|
||||
``/etc/ironic/ironic.conf``. For example::
|
||||
|
||||
enabled_drivers = pxe_ipmitool,pxe_seamicro
|
||||
|
||||
4. Restart the Ironic conductor service::
|
||||
|
||||
service ironic-conductor restart
|
||||
|
||||
Registering SeaMicro node in Ironic
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Nodes configured for SeaMicro driver should have the ``driver`` property set to
|
||||
``pxe_seamicro``. The following configuration values are also required in
|
||||
``driver_info``:
|
||||
|
||||
- ``seamicro_api_endpoint``: IP address or hostname of the SeaMicro with valid
|
||||
URL as http://<IP_address/hostname>/v2.0
|
||||
- ``seamicro_server_id``: SeaMicro Server ID. Expected format is <int>/<int>
|
||||
- ``seamicro_username``: SeaMicro Username with administrator privileges.
|
||||
- ``seamicro_password``: Password for the above SeaMicro user.
|
||||
- ``deploy_kernel``: The Glance UUID of the deployment kernel.
|
||||
- ``deploy_ramdisk``: The Glance UUID of the deployment ramdisk.
|
||||
- ``seamicro_api_version``: (optional) SeaMicro API Version defaults to "2".
|
||||
- ``seamicro_terminal_port``: (optional) Node's UDP port for console access.
|
||||
Any unused port on the Ironic conductor node may be used.
|
||||
|
||||
The following sequence of commands can be used to enroll a SeaMicro node and
|
||||
boot an instance on it:
|
||||
|
||||
Create nova baremetal flavor corresponding to SeaMicro server's config::
|
||||
|
||||
nova flavor-create baremetal auto <memory_size_in_MB> <disk_size_in_GB> <number_of_cpus>
|
||||
|
||||
Create Node::
|
||||
|
||||
ironic node-create -d pxe_seamicro -i seamicro_api_endpoint=https://<seamicro_ip_address>/ -i seamicro_server_id=<seamicro_server_id> -i seamicro_username=<seamicro_username> -i seamicro_password=<seamicro_password> -i seamicro_api_version=<seamicro_api_version> -i seamicro_terminal_port=<seamicro_terminal_port> -i deploy_kernel=<glance_uuid_of_deploy_kernel> -i deploy_ramdisk=<glance_uuid_of_deploy_ramdisk> -p cpus=<number_of_cpus> -p memory_mb=<memory_size_in_MB> -p local_gb=<local_disk_size_in_GB> -p cpu_arch=<cpu_arch>
|
||||
|
||||
Associate port with the node created::
|
||||
|
||||
ironic port-create -n $NODE -a <MAC_address_of_SeaMicro_server's_NIC>
|
||||
|
||||
Associate properties with the flavor::
|
||||
|
||||
nova flavor-key baremetal set "cpu_arch"=<cpu_arch>
|
||||
|
||||
Boot the Instance::
|
||||
|
||||
nova boot --flavor baremetal --image test-image instance-1
|
||||
|
||||
References
|
||||
==========
|
||||
.. [1] Python-seamicroclient - https://pypi.python.org/pypi/python-seamicroclient
|
||||
.. [2] DiskImage-Builder - http://docs.openstack.org/developer/diskimage-builder/
|
||||
|
||||
.. _`Building or downloading a deploy ramdisk image`: http://docs.openstack.org/project-install-guide/baremetal/draft/deploy-ramdisk.html
|
@ -1,121 +0,0 @@
|
||||
.. _vbox:
|
||||
|
||||
==================
|
||||
VirtualBox drivers
|
||||
==================
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
VirtualBox drivers can be used to test Ironic by using VirtualBox VMs to
|
||||
simulate bare metal nodes.
|
||||
|
||||
Ironic provides support via the ``pxe_ssh`` and ``agent_ssh`` drivers for using
|
||||
a VirtualBox VM as a bare metal target and do provisioning on it. It works by
|
||||
connecting via SSH into the VirtualBox host and running commands using
|
||||
VBoxManage. This works well if you have VirtualBox installed on a Linux box.
|
||||
But when VirtualBox is installed on a Windows box, configuring and getting SSH
|
||||
to work with VBoxManage is difficult (if not impossible) due to the following
|
||||
reasons:
|
||||
|
||||
* Windows doesn't come with native SSH support and one needs to use some
|
||||
third-party software to enable SSH support on Windows.
|
||||
* Even after configuring SSH, VBoxManage doesn't work remotely due to how
|
||||
Windows manages user accounts -- the native Windows user account is different
|
||||
from the corresponding SSH user account, and VBoxManage doesn't work
|
||||
properly when done with the SSH user account.
|
||||
* Even after tweaking the policies of the VirtualBox application, the remote
|
||||
VBoxManage and VBoxSvc don't sync each other properly and often results in
|
||||
a crash.
|
||||
|
||||
VirtualBox drivers use SOAP to talk to the VirtualBox web service running on
|
||||
the VirtualBox host. These drivers are primarily intended for Ironic developers
|
||||
running Windows on their laptops/desktops, although they can be used on other
|
||||
operating systems as well. Using these drivers, a developer could configure a
|
||||
cloud controller on one VirtualBox VM and use other VMs in the same VirtualBox
|
||||
as bare metals for that cloud controller.
|
||||
|
||||
These VirtualBox drivers are available :
|
||||
|
||||
* ``pxe_vbox``: uses iSCSI-based deployment mechanism.
|
||||
* ``agent_vbox``: uses agent-based deployment mechanism.
|
||||
* ``fake_vbox``: uses VirtualBox for power and management, but uses fake
|
||||
deploy.
|
||||
|
||||
|
||||
Setting up development environment
|
||||
==================================
|
||||
|
||||
* Install VirtualBox on your desktop or laptop.
|
||||
|
||||
* Create a VM for the cloud controller. Do not power on the VM now.
|
||||
For example, ``cloud-controller``.
|
||||
|
||||
* In VirtualBox Manager, Select ``cloud-controller`` VM -> Click Settings ->
|
||||
Network -> Adapter 2 -> Select 'Enable Network Adapter' ->
|
||||
Select Attached to: Internal Network -> Select Name: intnet
|
||||
|
||||
* Create a VM in VirtualBox to act as bare metal. A VM with 1 CPU,
|
||||
1 GB memory should be sufficient. Let's name this VM as ``baremetal``.
|
||||
|
||||
* In VirtualBox Manager, Select ``baremetal`` VM -> Click Settings ->
|
||||
Network -> Adapter 1 -> Select 'Enable Network Adapter' ->
|
||||
Select Attached to: Internal Network -> Select Name: intnet
|
||||
|
||||
* Configure the VirtualBox web service to disable authentication. (This is
|
||||
only a suggestion. If you want, enable authentication with the appropriate
|
||||
web service authentication library.)
|
||||
|
||||
::
|
||||
|
||||
VBoxManage setproperty websrvauthlibrary null
|
||||
|
||||
* Run VirtualBox web service::
|
||||
|
||||
C:\Program Files\Oracle\VirtualBox\VBoxWebSrv.exe
|
||||
|
||||
* Power on the ``cloud-controller`` VM.
|
||||
|
||||
* All the following instructions are to be done in the ``cloud-controller`` VM.
|
||||
|
||||
* Install the GNU/Linux distribution of your choice.
|
||||
|
||||
* Set up devstack.
|
||||
|
||||
* Install pyremotevbox::
|
||||
|
||||
sudo pip install "pyremotevbox>=0.5.0"
|
||||
|
||||
* Enable one (or more) of the VirtualBox drivers (``pxe_vbox``, ``agent_vbox``,
|
||||
or ``fake_vbox``) via the ``enabled_drivers`` configuration option in
|
||||
``/etc/ironic/ironic.conf``, and restart Ironic conductor.
|
||||
|
||||
* Set up flat networking on ``eth1``. For details on how to do this, see
|
||||
`Configure Networking to communicate with the bare metal server`_.
|
||||
|
||||
* Enroll a VirtualBox node. The following examples use the ``pxe_vbox``
|
||||
driver.
|
||||
|
||||
::
|
||||
|
||||
ironic node-create -d pxe_vbox -i virtualbox_host='10.0.2.2' -i virtualbox_vmname='baremetal'
|
||||
|
||||
If you are using authentication with VirtualBox web service, your username
|
||||
and password need to be provided. The ironic node-create command will look
|
||||
like::
|
||||
|
||||
ironic node-create -d pxe_vbox -i virtualbox_host='10.0.2.2' -i virtualbox_vmname='baremetal' -i virtualbox_username=<username> -i virtualbox_password=<password>
|
||||
|
||||
If VirtualBox web service is listening on a different port than the default
|
||||
18083, then that port may be specified using the driver_info
|
||||
parameter ``virtualbox_port``.
|
||||
|
||||
* Add other Node properties and trigger provisioning on the bare metal node.
|
||||
|
||||
.. note::
|
||||
When a newly created bare metal VM is powered on for the first time by
|
||||
Ironic (during provisioning), VirtualBox will automatically pop up a
|
||||
dialog box asking to 'Select start-up disk'. Just press 'Cancel' to
|
||||
continue booting the VM.
|
||||
|
||||
.. _`Configure Networking to communicate with the bare metal server`: http://docs.openstack.org/project-install-guide/baremetal/draft/configure-integration.html#configure-networking-to-communicate-with-the-bare-metal-server
|
@ -10,14 +10,8 @@ pysnmp
|
||||
python-ironic-inspector-client>=1.5.0
|
||||
python-oneviewclient<3.0.0,>=2.5.2
|
||||
python-scciclient>=0.4.0
|
||||
python-seamicroclient>=0.4.0
|
||||
UcsSdk==0.8.2.2
|
||||
python-dracclient>=0.1.0
|
||||
|
||||
# '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
|
||||
pyremotevbox>=0.5.0
|
||||
|
||||
# The CIMC drivers use the Cisco IMC SDK version 0.7.2 or greater
|
||||
ImcSdk>=0.7.2
|
||||
|
@ -3291,20 +3291,6 @@
|
||||
#ipxe_use_swift = false
|
||||
|
||||
|
||||
[seamicro]
|
||||
|
||||
#
|
||||
# From ironic
|
||||
#
|
||||
|
||||
# Maximum retries for SeaMicro operations (integer value)
|
||||
#max_retry = 3
|
||||
|
||||
# Seconds to wait for power action to be completed (integer
|
||||
# value)
|
||||
#action_timeout = 10
|
||||
|
||||
|
||||
[service_catalog]
|
||||
|
||||
#
|
||||
@ -3543,16 +3529,3 @@
|
||||
# Username (string value)
|
||||
# Deprecated group/name - [swift]/user-name
|
||||
#username = <None>
|
||||
|
||||
|
||||
[virtualbox]
|
||||
|
||||
#
|
||||
# From ironic
|
||||
#
|
||||
|
||||
# Port on which VirtualBox web service is listening. (port
|
||||
# value)
|
||||
# Minimum value: 0
|
||||
# Maximum value: 65535
|
||||
#port = 18083
|
||||
|
@ -43,8 +43,8 @@ service via hrefs.
|
||||
There are however some limitations for different drivers:
|
||||
|
||||
* If you're using one of the drivers that use agent deploy method (namely,
|
||||
``agent_ilo``, ``agent_ipmitool``, ``agent_pyghmi``, ``agent_ssh`` or
|
||||
``agent_vbox``) you have to know MD5 checksum for your instance image. To
|
||||
``agent_ilo``, ``agent_ipmitool``, ``agent_pyghmi`` or ``agent_ssh``)
|
||||
you have to know MD5 checksum for your instance image. To
|
||||
compute it, you can use the following command::
|
||||
|
||||
md5sum image.qcow2
|
||||
|
@ -476,10 +476,6 @@ class IPMIFailure(IronicException):
|
||||
_msg_fmt = _("IPMI call failed: %(cmd)s.")
|
||||
|
||||
|
||||
class MSFTOCSClientApiException(IronicException):
|
||||
_msg_fmt = _("MSFT OCS call failed.")
|
||||
|
||||
|
||||
class SSHConnectFailed(IronicException):
|
||||
_msg_fmt = _("Failed to establish SSH connection to host %(host)s.")
|
||||
|
||||
@ -666,11 +662,6 @@ class IRMCSharedFileSystemNotMounted(IronicException):
|
||||
_msg_fmt = _("iRMC shared file system '%(share)s' is not mounted.")
|
||||
|
||||
|
||||
class VirtualBoxOperationFailed(IronicException):
|
||||
_msg_fmt = _("VirtualBox operation '%(operation)s' failed. "
|
||||
"Error: %(error)s")
|
||||
|
||||
|
||||
class HardwareInspectionFailure(IronicException):
|
||||
_msg_fmt = _("Failed to inspect hardware. Reason: %(error)s")
|
||||
|
||||
|
@ -38,12 +38,10 @@ from ironic.conf import metrics_statsd
|
||||
from ironic.conf import neutron
|
||||
from ironic.conf import oneview
|
||||
from ironic.conf import pxe
|
||||
from ironic.conf import seamicro
|
||||
from ironic.conf import service_catalog
|
||||
from ironic.conf import snmp
|
||||
from ironic.conf import ssh
|
||||
from ironic.conf import swift
|
||||
from ironic.conf import virtualbox
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@ -70,9 +68,7 @@ metrics_statsd.register_opts(CONF)
|
||||
neutron.register_opts(CONF)
|
||||
oneview.register_opts(CONF)
|
||||
pxe.register_opts(CONF)
|
||||
seamicro.register_opts(CONF)
|
||||
service_catalog.register_opts(CONF)
|
||||
snmp.register_opts(CONF)
|
||||
ssh.register_opts(CONF)
|
||||
swift.register_opts(CONF)
|
||||
virtualbox.register_opts(CONF)
|
||||
|
@ -54,12 +54,10 @@ _opts = [
|
||||
('neutron', ironic.conf.neutron.list_opts()),
|
||||
('oneview', ironic.conf.oneview.opts),
|
||||
('pxe', ironic.conf.pxe.opts),
|
||||
('seamicro', ironic.conf.seamicro.opts),
|
||||
('service_catalog', ironic.conf.service_catalog.list_opts()),
|
||||
('snmp', ironic.conf.snmp.opts),
|
||||
('ssh', ironic.conf.ssh.opts),
|
||||
('swift', ironic.conf.swift.list_opts()),
|
||||
('virtualbox', ironic.conf.virtualbox.opts),
|
||||
]
|
||||
|
||||
|
||||
|
@ -1,34 +0,0 @@
|
||||
# Copyright 2016 Intel Corporation
|
||||
#
|
||||
# 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 SeaMicro operations')),
|
||||
cfg.IntOpt('action_timeout',
|
||||
default=10,
|
||||
help=_('Seconds to wait for power action to be completed'))
|
||||
]
|
||||
|
||||
opt_group = cfg.OptGroup(name='seamicro',
|
||||
title='Options for the seamicro power driver')
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(opt_group)
|
||||
conf.register_opts(opts, group=opt_group)
|
@ -1,27 +0,0 @@
|
||||
# Copyright 2016 Intel Corporation
|
||||
#
|
||||
# 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.PortOpt('port',
|
||||
default=18083,
|
||||
help=_('Port on which VirtualBox web service is listening.')),
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_opts(opts, group='virtualbox')
|
@ -27,7 +27,6 @@ from ironic.drivers.modules import pxe
|
||||
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
|
||||
|
||||
|
||||
# For backward compatibility
|
||||
@ -87,33 +86,6 @@ class AgentAndSSHDriver(base.BaseDriver):
|
||||
self.console = ssh.ShellinaboxConsole()
|
||||
|
||||
|
||||
class AgentAndVirtualBoxDriver(base.BaseDriver):
|
||||
"""Agent + VirtualBox driver.
|
||||
|
||||
NOTE: This driver is meant only for testing environments.
|
||||
|
||||
This driver implements the `core` functionality, combining
|
||||
:class:`ironic.drivers.modules.virtualbox.VirtualBoxPower` (for power
|
||||
on/off and reboot of VirtualBox virtual machines), with
|
||||
:class:`ironic.drivers.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('pyremotevbox'):
|
||||
raise exception.DriverLoadError(
|
||||
driver=self.__class__.__name__,
|
||||
reason=_("Unable to import pyremotevbox library"))
|
||||
self.power = virtualbox.VirtualBoxPower()
|
||||
self.boot = pxe.PXEBoot()
|
||||
self.deploy = agent.AgentDeploy()
|
||||
self.management = virtualbox.VirtualBoxManagement()
|
||||
self.raid = agent.AgentRAID()
|
||||
|
||||
|
||||
class AgentAndUcsDriver(base.BaseDriver):
|
||||
"""Agent + Cisco UCSM driver.
|
||||
|
||||
|
@ -42,18 +42,14 @@ from ironic.drivers.modules.irmc import inspect as irmc_inspect
|
||||
from ironic.drivers.modules.irmc import management as irmc_management
|
||||
from ironic.drivers.modules.irmc import power as irmc_power
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
from ironic.drivers.modules.msftocs import management as msftocs_management
|
||||
from ironic.drivers.modules.msftocs import power as msftocs_power
|
||||
from ironic.drivers.modules.oneview import common as oneview_common
|
||||
from ironic.drivers.modules.oneview import management as oneview_management
|
||||
from ironic.drivers.modules.oneview import power as oneview_power
|
||||
from ironic.drivers.modules import pxe
|
||||
from ironic.drivers.modules import seamicro
|
||||
from ironic.drivers.modules import snmp
|
||||
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 import utils
|
||||
|
||||
|
||||
@ -146,23 +142,6 @@ class FakeIPMINativeDriver(base.BaseDriver):
|
||||
self.management = ipminative.NativeIPMIManagement()
|
||||
|
||||
|
||||
class FakeSeaMicroDriver(base.BaseDriver):
|
||||
"""Fake SeaMicro driver."""
|
||||
|
||||
supported = False
|
||||
|
||||
def __init__(self):
|
||||
if not importutils.try_import('seamicroclient'):
|
||||
raise exception.DriverLoadError(
|
||||
driver=self.__class__.__name__,
|
||||
reason=_("Unable to import seamicroclient library"))
|
||||
self.power = seamicro.Power()
|
||||
self.deploy = fake.FakeDeploy()
|
||||
self.management = seamicro.Management()
|
||||
self.vendor = seamicro.VendorPassthru()
|
||||
self.console = seamicro.ShellinaboxConsole()
|
||||
|
||||
|
||||
class FakeAgentDriver(base.BaseDriver):
|
||||
"""Example implementation of an AgentDriver."""
|
||||
|
||||
@ -230,21 +209,6 @@ class FakeIRMCDriver(base.BaseDriver):
|
||||
self.inspect = irmc_inspect.IRMCInspect()
|
||||
|
||||
|
||||
class FakeVirtualBoxDriver(base.BaseDriver):
|
||||
"""Fake VirtualBox driver."""
|
||||
|
||||
supported = False
|
||||
|
||||
def __init__(self):
|
||||
if not importutils.try_import('pyremotevbox'):
|
||||
raise exception.DriverLoadError(
|
||||
driver=self.__class__.__name__,
|
||||
reason=_("Unable to import pyremotevbox library"))
|
||||
self.power = virtualbox.VirtualBoxPower()
|
||||
self.deploy = fake.FakeDeploy()
|
||||
self.management = virtualbox.VirtualBoxManagement()
|
||||
|
||||
|
||||
class FakeIPMIToolInspectorDriver(base.BaseDriver):
|
||||
"""Fake Inspector driver."""
|
||||
|
||||
@ -260,17 +224,6 @@ class FakeIPMIToolInspectorDriver(base.BaseDriver):
|
||||
self.inspect = inspector.Inspector()
|
||||
|
||||
|
||||
class FakeMSFTOCSDriver(base.BaseDriver):
|
||||
"""Fake MSFT OCS driver."""
|
||||
|
||||
supported = False
|
||||
|
||||
def __init__(self):
|
||||
self.power = msftocs_power.MSFTOCSPower()
|
||||
self.deploy = fake.FakeDeploy()
|
||||
self.management = msftocs_management.MSFTOCSManagement()
|
||||
|
||||
|
||||
class FakeUcsDriver(base.BaseDriver):
|
||||
"""Fake UCS driver."""
|
||||
|
||||
|
@ -1,110 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
import re
|
||||
|
||||
import six
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.drivers.modules.msftocs import msftocsclient
|
||||
|
||||
REQUIRED_PROPERTIES = {
|
||||
'msftocs_base_url': _('Base url of the OCS chassis manager REST API, '
|
||||
'e.g.: http://10.0.0.1:8000. Required.'),
|
||||
'msftocs_blade_id': _('Blade id, must be a number between 1 and the '
|
||||
'maximum number of blades available in the chassis. '
|
||||
'Required.'),
|
||||
'msftocs_username': _('Username to access the chassis manager REST API. '
|
||||
'Required.'),
|
||||
'msftocs_password': _('Password to access the chassis manager REST API. '
|
||||
'Required.'),
|
||||
}
|
||||
|
||||
|
||||
def get_client_info(driver_info):
|
||||
"""Returns an instance of the REST API client and the blade id.
|
||||
|
||||
:param driver_info: the node's driver_info dict.
|
||||
"""
|
||||
client = msftocsclient.MSFTOCSClientApi(driver_info['msftocs_base_url'],
|
||||
driver_info['msftocs_username'],
|
||||
driver_info['msftocs_password'])
|
||||
return client, driver_info['msftocs_blade_id']
|
||||
|
||||
|
||||
def get_properties():
|
||||
"""Returns the driver's properties."""
|
||||
return copy.deepcopy(REQUIRED_PROPERTIES)
|
||||
|
||||
|
||||
def _is_valid_url(url):
|
||||
"""Checks whether a URL is valid.
|
||||
|
||||
:param url: a url string.
|
||||
:returns: True if the url is valid or None, False otherwise.
|
||||
"""
|
||||
r = re.compile(
|
||||
r'^https?://'
|
||||
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)*[A-Z]{2,6}\.?|'
|
||||
r'localhost|'
|
||||
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
|
||||
r'(?::\d+)?'
|
||||
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
||||
|
||||
return bool(isinstance(url, six.string_types) and r.search(url))
|
||||
|
||||
|
||||
def _check_required_properties(driver_info):
|
||||
"""Checks if all required properties are present.
|
||||
|
||||
:param driver_info: the node's driver_info dict.
|
||||
:raises: MissingParameterValue if one or more required properties are
|
||||
missing.
|
||||
"""
|
||||
missing_properties = set(REQUIRED_PROPERTIES) - set(driver_info)
|
||||
if missing_properties:
|
||||
raise exception.MissingParameterValue(
|
||||
_('The following parameters were missing: %s') %
|
||||
' '.join(missing_properties))
|
||||
|
||||
|
||||
def parse_driver_info(node):
|
||||
"""Checks for the required properties and values validity.
|
||||
|
||||
:param node: the target node.
|
||||
:raises: MissingParameterValue if one or more required properties are
|
||||
missing.
|
||||
:raises: InvalidParameterValue if a parameter value is invalid.
|
||||
"""
|
||||
driver_info = node.driver_info
|
||||
_check_required_properties(driver_info)
|
||||
|
||||
base_url = driver_info.get('msftocs_base_url')
|
||||
if not _is_valid_url(base_url):
|
||||
raise exception.InvalidParameterValue(
|
||||
_('"%s" is not a valid "msftocs_base_url"') % base_url)
|
||||
|
||||
blade_id = driver_info.get('msftocs_blade_id')
|
||||
try:
|
||||
blade_id = int(blade_id)
|
||||
except ValueError:
|
||||
raise exception.InvalidParameterValue(
|
||||
_('"%s" is not a valid "msftocs_blade_id"') % blade_id)
|
||||
if blade_id < 1:
|
||||
raise exception.InvalidParameterValue(
|
||||
_('"msftocs_blade_id" must be greater than 0. The provided value '
|
||||
'is: %s') % blade_id)
|
@ -1,121 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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 ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules.msftocs import common as msftocs_common
|
||||
from ironic.drivers.modules.msftocs import msftocsclient
|
||||
from ironic.drivers import utils as drivers_utils
|
||||
|
||||
BOOT_TYPE_TO_DEVICE_MAP = {
|
||||
msftocsclient.BOOT_TYPE_FORCE_PXE: boot_devices.PXE,
|
||||
msftocsclient.BOOT_TYPE_FORCE_DEFAULT_HDD: boot_devices.DISK,
|
||||
msftocsclient.BOOT_TYPE_FORCE_INTO_BIOS_SETUP: boot_devices.BIOS,
|
||||
}
|
||||
DEVICE_TO_BOOT_TYPE_MAP = {v: k for k, v in BOOT_TYPE_TO_DEVICE_MAP.items()}
|
||||
|
||||
DEFAULT_BOOT_DEVICE = boot_devices.DISK
|
||||
|
||||
|
||||
class MSFTOCSManagement(base.ManagementInterface):
|
||||
def get_properties(self):
|
||||
"""Returns the driver's properties."""
|
||||
return msftocs_common.get_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 containing the target node.
|
||||
:raises: MissingParameterValue if any required parameters are missing.
|
||||
:raises: InvalidParameterValue if any parameters have invalid values.
|
||||
"""
|
||||
msftocs_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(BOOT_TYPE_TO_DEVICE_MAP.values())
|
||||
|
||||
def _check_valid_device(self, device, node):
|
||||
"""Checks if the desired boot device is valid for this driver.
|
||||
|
||||
:param device: a boot device.
|
||||
:param node: the target node.
|
||||
:raises: InvalidParameterValue if the boot device is not valid.
|
||||
"""
|
||||
if device not in DEVICE_TO_BOOT_TYPE_MAP:
|
||||
raise exception.InvalidParameterValue(
|
||||
_("set_boot_device called with invalid device %(device)s for "
|
||||
"node %(node_id)s.") %
|
||||
{'device': device, 'node_id': node.uuid})
|
||||
|
||||
@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.
|
||||
"""
|
||||
self._check_valid_device(device, task.node)
|
||||
client, blade_id = msftocs_common.get_client_info(
|
||||
task.node.driver_info)
|
||||
|
||||
boot_mode = drivers_utils.get_node_capability(task.node, 'boot_mode')
|
||||
uefi = (boot_mode == 'uefi')
|
||||
|
||||
boot_type = DEVICE_TO_BOOT_TYPE_MAP[device]
|
||||
client.set_next_boot(blade_id, boot_type, persistent, uefi)
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
client, blade_id = msftocs_common.get_client_info(
|
||||
task.node.driver_info)
|
||||
device = BOOT_TYPE_TO_DEVICE_MAP.get(
|
||||
client.get_next_boot(blade_id), DEFAULT_BOOT_DEVICE)
|
||||
|
||||
# Note(alexpilotti): Although the ChasssisManager REST API allows to
|
||||
# specify the persistent boot status in SetNextBoot, currently it does
|
||||
# not provide a way to retrieve the value with GetNextBoot.
|
||||
# This is being addressed in the ChassisManager API.
|
||||
return {'boot_device': device,
|
||||
'persistent': None}
|
||||
|
||||
def get_sensors_data(self, task):
|
||||
raise NotImplementedError()
|
@ -1,176 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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.
|
||||
|
||||
"""
|
||||
MSFT OCS ChassisManager v2.0 REST API client
|
||||
https://github.com/MSOpenTech/ChassisManager
|
||||
"""
|
||||
|
||||
import posixpath
|
||||
from xml import etree
|
||||
|
||||
from oslo_log import log
|
||||
import requests
|
||||
from requests import auth
|
||||
from requests import exceptions as requests_exceptions
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _, _LE
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
WCSNS = 'http://schemas.datacontract.org/2004/07/Microsoft.GFS.WCS.Contracts'
|
||||
COMPLETION_CODE_SUCCESS = "Success"
|
||||
|
||||
BOOT_TYPE_UNKNOWN = 0
|
||||
BOOT_TYPE_NO_OVERRIDE = 1
|
||||
BOOT_TYPE_FORCE_PXE = 2
|
||||
BOOT_TYPE_FORCE_DEFAULT_HDD = 3
|
||||
BOOT_TYPE_FORCE_INTO_BIOS_SETUP = 4
|
||||
BOOT_TYPE_FORCE_FLOPPY_OR_REMOVABLE = 5
|
||||
|
||||
BOOT_TYPE_MAP = {
|
||||
'Unknown': BOOT_TYPE_UNKNOWN,
|
||||
'NoOverride': BOOT_TYPE_NO_OVERRIDE,
|
||||
'ForcePxe': BOOT_TYPE_FORCE_PXE,
|
||||
'ForceDefaultHdd': BOOT_TYPE_FORCE_DEFAULT_HDD,
|
||||
'ForceIntoBiosSetup': BOOT_TYPE_FORCE_INTO_BIOS_SETUP,
|
||||
'ForceFloppyOrRemovable': BOOT_TYPE_FORCE_FLOPPY_OR_REMOVABLE,
|
||||
}
|
||||
|
||||
POWER_STATUS_ON = "ON"
|
||||
POWER_STATUS_OFF = "OFF"
|
||||
|
||||
|
||||
class MSFTOCSClientApi(object):
|
||||
def __init__(self, base_url, username, password):
|
||||
self._base_url = base_url
|
||||
self._username = username
|
||||
self._password = password
|
||||
|
||||
def _exec_cmd(self, rel_url):
|
||||
"""Executes a command by calling the chassis manager API."""
|
||||
url = posixpath.join(self._base_url, rel_url)
|
||||
try:
|
||||
response = requests.get(
|
||||
url, auth=auth.HTTPBasicAuth(self._username, self._password))
|
||||
response.raise_for_status()
|
||||
except requests_exceptions.RequestException as ex:
|
||||
msg = _("HTTP call failed: %s") % ex
|
||||
LOG.exception(msg)
|
||||
raise exception.MSFTOCSClientApiException(msg)
|
||||
|
||||
xml_response = response.text
|
||||
LOG.debug("Call to %(url)s got response: %(xml_response)s",
|
||||
{"url": url, "xml_response": xml_response})
|
||||
return xml_response
|
||||
|
||||
def _check_completion_code(self, xml_response):
|
||||
try:
|
||||
et = etree.ElementTree.fromstring(xml_response)
|
||||
except etree.ElementTree.ParseError as ex:
|
||||
LOG.exception(_LE("XML parsing failed: %s"), ex)
|
||||
raise exception.MSFTOCSClientApiException(
|
||||
_("Invalid XML: %s") % xml_response)
|
||||
item = et.find("./n:completionCode", namespaces={'n': WCSNS})
|
||||
if item is None or item.text != COMPLETION_CODE_SUCCESS:
|
||||
raise exception.MSFTOCSClientApiException(
|
||||
_("Operation failed: %s") % xml_response)
|
||||
return et
|
||||
|
||||
def get_blade_state(self, blade_id):
|
||||
"""Returns whether a blade's chipset is receiving power (soft-power).
|
||||
|
||||
:param blade_id: the blade id
|
||||
:returns: one of:
|
||||
POWER_STATUS_ON,
|
||||
POWER_STATUS_OFF
|
||||
:raises: MSFTOCSClientApiException
|
||||
"""
|
||||
et = self._check_completion_code(
|
||||
self._exec_cmd("GetBladeState?bladeId=%d" % blade_id))
|
||||
return et.find('./n:bladeState', namespaces={'n': WCSNS}).text
|
||||
|
||||
def set_blade_on(self, blade_id):
|
||||
"""Supplies power to a blade chipset (soft-power state).
|
||||
|
||||
:param blade_id: the blade id
|
||||
:raises: MSFTOCSClientApiException
|
||||
"""
|
||||
self._check_completion_code(
|
||||
self._exec_cmd("SetBladeOn?bladeId=%d" % blade_id))
|
||||
|
||||
def set_blade_off(self, blade_id):
|
||||
"""Shuts down a given blade (soft-power state).
|
||||
|
||||
:param blade_id: the blade id
|
||||
:raises: MSFTOCSClientApiException
|
||||
"""
|
||||
self._check_completion_code(
|
||||
self._exec_cmd("SetBladeOff?bladeId=%d" % blade_id))
|
||||
|
||||
def set_blade_power_cycle(self, blade_id, off_time=0):
|
||||
"""Performs a soft reboot of a given blade.
|
||||
|
||||
:param blade_id: the blade id
|
||||
:param off_time: seconds to wait between shutdown and boot
|
||||
:raises: MSFTOCSClientApiException
|
||||
"""
|
||||
self._check_completion_code(
|
||||
self._exec_cmd("SetBladeActivePowerCycle?bladeId=%(blade_id)d&"
|
||||
"offTime=%(off_time)d" %
|
||||
{"blade_id": blade_id, "off_time": off_time}))
|
||||
|
||||
def get_next_boot(self, blade_id):
|
||||
"""Returns the next boot device configured for a given blade.
|
||||
|
||||
:param blade_id: the blade id
|
||||
:returns: one of:
|
||||
BOOT_TYPE_UNKNOWN,
|
||||
BOOT_TYPE_NO_OVERRIDE,
|
||||
BOOT_TYPE_FORCE_PXE, BOOT_TYPE_FORCE_DEFAULT_HDD,
|
||||
BOOT_TYPE_FORCE_INTO_BIOS_SETUP,
|
||||
BOOT_TYPE_FORCE_FLOPPY_OR_REMOVABLE
|
||||
:raises: MSFTOCSClientApiException
|
||||
"""
|
||||
et = self._check_completion_code(
|
||||
self._exec_cmd("GetNextBoot?bladeId=%d" % blade_id))
|
||||
return BOOT_TYPE_MAP[
|
||||
et.find('./n:nextBoot', namespaces={'n': WCSNS}).text]
|
||||
|
||||
def set_next_boot(self, blade_id, boot_type, persistent=True, uefi=True):
|
||||
"""Sets the next boot device for a given blade.
|
||||
|
||||
:param blade_id: the blade id
|
||||
:param boot_type: possible values:
|
||||
BOOT_TYPE_UNKNOWN,
|
||||
BOOT_TYPE_NO_OVERRIDE,
|
||||
BOOT_TYPE_FORCE_PXE,
|
||||
BOOT_TYPE_FORCE_DEFAULT_HDD,
|
||||
BOOT_TYPE_FORCE_INTO_BIOS_SETUP,
|
||||
BOOT_TYPE_FORCE_FLOPPY_OR_REMOVABLE
|
||||
:param persistent: whether this setting affects the next boot only or
|
||||
every subsequent boot
|
||||
:param uefi: True if UEFI, False otherwise
|
||||
:raises: MSFTOCSClientApiException
|
||||
"""
|
||||
self._check_completion_code(
|
||||
self._exec_cmd(
|
||||
"SetNextBoot?bladeId=%(blade_id)d&bootType=%(boot_type)d&"
|
||||
"uefi=%(uefi)s&persistent=%(persistent)s" %
|
||||
{"blade_id": blade_id,
|
||||
"boot_type": boot_type,
|
||||
"uefi": str(uefi).lower(),
|
||||
"persistent": str(persistent).lower()}))
|
@ -1,105 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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.
|
||||
|
||||
"""
|
||||
MSFT OCS Power Driver
|
||||
"""
|
||||
from oslo_log import log
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _, _LE
|
||||
from ironic.common import states
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules.msftocs import common as msftocs_common
|
||||
from ironic.drivers.modules.msftocs import msftocsclient
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
POWER_STATES_MAP = {
|
||||
msftocsclient.POWER_STATUS_ON: states.POWER_ON,
|
||||
msftocsclient.POWER_STATUS_OFF: states.POWER_OFF,
|
||||
}
|
||||
|
||||
|
||||
class MSFTOCSPower(base.PowerInterface):
|
||||
def get_properties(self):
|
||||
"""Returns the driver's properties."""
|
||||
return msftocs_common.get_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 containing the target node.
|
||||
:raises: MissingParameterValue if any required parameters are missing.
|
||||
:raises: InvalidParameterValue if any parameters have invalid values.
|
||||
"""
|
||||
msftocs_common.parse_driver_info(task.node)
|
||||
|
||||
def get_power_state(self, task):
|
||||
"""Get the power state from the node.
|
||||
|
||||
:param task: a TaskManager instance containing the target node.
|
||||
:raises: MSFTOCSClientApiException.
|
||||
"""
|
||||
client, blade_id = msftocs_common.get_client_info(
|
||||
task.node.driver_info)
|
||||
return POWER_STATES_MAP[client.get_blade_state(blade_id)]
|
||||
|
||||
@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: InvalidParameterValue
|
||||
"""
|
||||
client, blade_id = msftocs_common.get_client_info(
|
||||
task.node.driver_info)
|
||||
|
||||
try:
|
||||
if pstate == states.POWER_ON:
|
||||
client.set_blade_on(blade_id)
|
||||
elif pstate == states.POWER_OFF:
|
||||
client.set_blade_off(blade_id)
|
||||
else:
|
||||
raise exception.InvalidParameterValue(
|
||||
_('Unsupported target_state: %s') % pstate)
|
||||
except exception.MSFTOCSClientApiException as ex:
|
||||
LOG.exception(_LE("Changing the power state to %(pstate)s failed. "
|
||||
"Error: %(err_msg)s"),
|
||||
{"pstate": pstate, "err_msg": ex})
|
||||
raise exception.PowerStateFailure(pstate=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.
|
||||
"""
|
||||
client, blade_id = msftocs_common.get_client_info(
|
||||
task.node.driver_info)
|
||||
try:
|
||||
client.set_blade_power_cycle(blade_id)
|
||||
except exception.MSFTOCSClientApiException as ex:
|
||||
LOG.exception(_LE("Reboot failed. Error: %(err_msg)s"),
|
||||
{"err_msg": ex})
|
||||
raise exception.PowerStateFailure(pstate=states.REBOOT)
|
@ -1,672 +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.
|
||||
|
||||
"""
|
||||
Ironic SeaMicro interfaces.
|
||||
|
||||
Provides basic power control of servers in SeaMicro chassis via
|
||||
python-seamicroclient.
|
||||
|
||||
Provides vendor passthru methods for SeaMicro specific functionality.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import loopingcall
|
||||
from oslo_utils import importutils
|
||||
from six.moves.urllib import parse as urlparse
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _, _LE, _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
|
||||
from ironic.drivers.modules import console_utils
|
||||
|
||||
seamicroclient = importutils.try_import('seamicroclient')
|
||||
if seamicroclient:
|
||||
from seamicroclient import client as seamicro_client
|
||||
from seamicroclient import exceptions as seamicro_client_exception
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
_BOOT_DEVICES_MAP = {
|
||||
boot_devices.DISK: 'hd0',
|
||||
boot_devices.PXE: 'pxe',
|
||||
}
|
||||
|
||||
REQUIRED_PROPERTIES = {
|
||||
'seamicro_api_endpoint': _("API endpoint. Required."),
|
||||
'seamicro_password': _("password. Required."),
|
||||
'seamicro_server_id': _("server ID. Required."),
|
||||
'seamicro_username': _("username. Required."),
|
||||
}
|
||||
OPTIONAL_PROPERTIES = {
|
||||
'seamicro_api_version': _("version of SeaMicro API client; default is 2. "
|
||||
"Optional.")
|
||||
}
|
||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
||||
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
||||
CONSOLE_PROPERTIES = {
|
||||
'seamicro_terminal_port': _("node's UDP port to connect to. "
|
||||
"Only required for console access.")
|
||||
}
|
||||
PORT_BASE = 2000
|
||||
|
||||
|
||||
def _get_client(*args, **kwargs):
|
||||
"""Creates the python-seamicro_client
|
||||
|
||||
:param kwargs: A dict of keyword arguments to be passed to the method,
|
||||
which should contain: 'username', 'password',
|
||||
'auth_url', 'api_version' parameters.
|
||||
:returns: SeaMicro API client.
|
||||
"""
|
||||
|
||||
cl_kwargs = {'username': kwargs['username'],
|
||||
'password': kwargs['password'],
|
||||
'auth_url': kwargs['api_endpoint']}
|
||||
try:
|
||||
return seamicro_client.Client(kwargs['api_version'], **cl_kwargs)
|
||||
except seamicro_client_exception.UnsupportedVersion as e:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Invalid 'seamicro_api_version' parameter. Reason: %s.") % e)
|
||||
|
||||
|
||||
def _parse_driver_info(node):
|
||||
"""Parses and creates seamicro driver info
|
||||
|
||||
:param node: An Ironic node object.
|
||||
:returns: SeaMicro driver info.
|
||||
:raises: MissingParameterValue if any required parameters are missing.
|
||||
:raises: InvalidParameterValue if required parameter are invalid.
|
||||
"""
|
||||
|
||||
info = node.driver_info or {}
|
||||
missing_info = [key for key in REQUIRED_PROPERTIES if not info.get(key)]
|
||||
if missing_info:
|
||||
raise exception.MissingParameterValue(_(
|
||||
"SeaMicro driver requires the following parameters to be set in"
|
||||
" node's driver_info: %s.") % missing_info)
|
||||
|
||||
api_endpoint = info.get('seamicro_api_endpoint')
|
||||
username = info.get('seamicro_username')
|
||||
password = info.get('seamicro_password')
|
||||
server_id = info.get('seamicro_server_id')
|
||||
api_version = info.get('seamicro_api_version', "2")
|
||||
port = info.get('seamicro_terminal_port')
|
||||
|
||||
if port is not None:
|
||||
port = utils.validate_network_port(port, 'seamicro_terminal_port')
|
||||
|
||||
r = re.compile(r"(^[0-9]+)/([0-9]+$)")
|
||||
if not r.match(server_id):
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Invalid 'seamicro_server_id' parameter in node's "
|
||||
"driver_info. Expected format of 'seamicro_server_id' "
|
||||
"is <int>/<int>"))
|
||||
|
||||
url = urlparse.urlparse(api_endpoint)
|
||||
if (not (url.scheme == "http") or not url.netloc):
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Invalid 'seamicro_api_endpoint' parameter in node's "
|
||||
"driver_info."))
|
||||
|
||||
res = {'username': username,
|
||||
'password': password,
|
||||
'api_endpoint': api_endpoint,
|
||||
'server_id': server_id,
|
||||
'api_version': api_version,
|
||||
'uuid': node.uuid,
|
||||
'port': port}
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _get_server(driver_info):
|
||||
"""Get server from server_id."""
|
||||
|
||||
s_client = _get_client(**driver_info)
|
||||
return s_client.servers.get(driver_info['server_id'])
|
||||
|
||||
|
||||
def _get_volume(driver_info, volume_id):
|
||||
"""Get volume from volume_id."""
|
||||
|
||||
s_client = _get_client(**driver_info)
|
||||
return s_client.volumes.get(volume_id)
|
||||
|
||||
|
||||
def _get_power_status(node):
|
||||
"""Get current power state of this node
|
||||
|
||||
:param node: Ironic node one of :class:`ironic.db.models.Node`
|
||||
:raises: InvalidParameterValue if a seamicro parameter is invalid.
|
||||
:raises: MissingParameterValue if required seamicro parameters are
|
||||
missing.
|
||||
:raises: ServiceUnavailable on an error from SeaMicro Client.
|
||||
:returns: Power state of the given node
|
||||
"""
|
||||
|
||||
seamicro_info = _parse_driver_info(node)
|
||||
try:
|
||||
server = _get_server(seamicro_info)
|
||||
if not hasattr(server, 'active') or server.active is None:
|
||||
return states.ERROR
|
||||
if not server.active:
|
||||
return states.POWER_OFF
|
||||
elif server.active:
|
||||
return states.POWER_ON
|
||||
|
||||
except seamicro_client_exception.NotFound:
|
||||
raise exception.NodeNotFound(node=node.uuid)
|
||||
except seamicro_client_exception.ClientException as ex:
|
||||
LOG.error(_LE("SeaMicro client exception %(msg)s for node %(uuid)s"),
|
||||
{'msg': ex.message, 'uuid': node.uuid})
|
||||
raise exception.ServiceUnavailable(message=ex.message)
|
||||
|
||||
|
||||
def _power_on(node, timeout=None):
|
||||
"""Power ON this node
|
||||
|
||||
:param node: An Ironic node object.
|
||||
:param timeout: Time in seconds to wait till power on is complete.
|
||||
:raises: InvalidParameterValue if a seamicro parameter is invalid.
|
||||
:raises: MissingParameterValue if required seamicro parameters are
|
||||
missing.
|
||||
:returns: Power state of the given node.
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = CONF.seamicro.action_timeout
|
||||
state = [None]
|
||||
retries = [0]
|
||||
seamicro_info = _parse_driver_info(node)
|
||||
server = _get_server(seamicro_info)
|
||||
|
||||
def _wait_for_power_on(state, retries):
|
||||
"""Called at an interval until the node is powered on."""
|
||||
|
||||
state[0] = _get_power_status(node)
|
||||
if state[0] == states.POWER_ON:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
if retries[0] > CONF.seamicro.max_retry:
|
||||
state[0] = states.ERROR
|
||||
raise loopingcall.LoopingCallDone()
|
||||
try:
|
||||
retries[0] += 1
|
||||
server.power_on()
|
||||
except seamicro_client_exception.ClientException:
|
||||
LOG.warning(_LW("Power-on failed for node %s."),
|
||||
node.uuid)
|
||||
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_power_on,
|
||||
state, retries)
|
||||
timer.start(interval=timeout).wait()
|
||||
return state[0]
|
||||
|
||||
|
||||
def _power_off(node, timeout=None):
|
||||
"""Power OFF this node
|
||||
|
||||
:param node: Ironic node one of :class:`ironic.db.models.Node`
|
||||
:param timeout: Time in seconds to wait till power off is compelete
|
||||
:raises: InvalidParameterValue if a seamicro parameter is invalid.
|
||||
:raises: MissingParameterValue if required seamicro parameters are
|
||||
missing.
|
||||
:returns: Power state of the given node
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = CONF.seamicro.action_timeout
|
||||
state = [None]
|
||||
retries = [0]
|
||||
seamicro_info = _parse_driver_info(node)
|
||||
server = _get_server(seamicro_info)
|
||||
|
||||
def _wait_for_power_off(state, retries):
|
||||
"""Called at an interval until the node is powered off."""
|
||||
|
||||
state[0] = _get_power_status(node)
|
||||
if state[0] == states.POWER_OFF:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
if retries[0] > CONF.seamicro.max_retry:
|
||||
state[0] = states.ERROR
|
||||
raise loopingcall.LoopingCallDone()
|
||||
try:
|
||||
retries[0] += 1
|
||||
server.power_off()
|
||||
except seamicro_client_exception.ClientException:
|
||||
LOG.warning(_LW("Power-off failed for node %s."),
|
||||
node.uuid)
|
||||
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_power_off,
|
||||
state, retries)
|
||||
timer.start(interval=timeout).wait()
|
||||
return state[0]
|
||||
|
||||
|
||||
def _reboot(node, timeout=None):
|
||||
"""Reboot this node.
|
||||
|
||||
:param node: Ironic node one of :class:`ironic.db.models.Node`
|
||||
:param timeout: Time in seconds to wait till reboot is compelete
|
||||
:raises: InvalidParameterValue if a seamicro parameter is invalid.
|
||||
:raises: MissingParameterValue if required seamicro parameters are
|
||||
missing.
|
||||
:returns: Power state of the given node
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = CONF.seamicro.action_timeout
|
||||
state = [None]
|
||||
retries = [0]
|
||||
seamicro_info = _parse_driver_info(node)
|
||||
server = _get_server(seamicro_info)
|
||||
|
||||
def _wait_for_reboot(state, retries):
|
||||
"""Called at an interval until the node is rebooted successfully."""
|
||||
|
||||
state[0] = _get_power_status(node)
|
||||
if state[0] == states.POWER_ON:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
if retries[0] > CONF.seamicro.max_retry:
|
||||
state[0] = states.ERROR
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
try:
|
||||
retries[0] += 1
|
||||
server.reset()
|
||||
except seamicro_client_exception.ClientException:
|
||||
LOG.warning(_LW("Reboot failed for node %s."),
|
||||
node.uuid)
|
||||
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_reboot,
|
||||
state, retries)
|
||||
server.reset()
|
||||
timer.start(interval=timeout).wait()
|
||||
return state[0]
|
||||
|
||||
|
||||
def _validate_volume(driver_info, volume_id):
|
||||
"""Validates if volume is in Storage pools designated for ironic."""
|
||||
|
||||
volume = _get_volume(driver_info, volume_id)
|
||||
|
||||
# Check if the ironic <scard>/ironic-<pool_id>/<volume_id> naming scheme
|
||||
# is present in volume id
|
||||
try:
|
||||
pool_id = volume.id.split('/')[1].lower()
|
||||
except IndexError:
|
||||
pool_id = ""
|
||||
|
||||
if "ironic-" in pool_id:
|
||||
return True
|
||||
else:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Invalid volume id specified"))
|
||||
|
||||
|
||||
def _get_pools(driver_info, filters=None):
|
||||
"""Get SeaMicro storage pools matching given filters."""
|
||||
|
||||
s_client = _get_client(**driver_info)
|
||||
return s_client.pools.list(filters=filters)
|
||||
|
||||
|
||||
def _create_volume(driver_info, volume_size):
|
||||
"""Create volume in the SeaMicro storage pools designated for ironic."""
|
||||
|
||||
ironic_pools = _get_pools(driver_info, filters={'id': 'ironic-'})
|
||||
if ironic_pools is None:
|
||||
raise exception.VendorPassthruException(_(
|
||||
"No storage pools found for ironic"))
|
||||
|
||||
least_used_pool = sorted(ironic_pools,
|
||||
key=lambda x: x.freeSize)[0]
|
||||
return _get_client(**driver_info).volumes.create(volume_size,
|
||||
least_used_pool)
|
||||
|
||||
|
||||
def get_telnet_port(driver_info):
|
||||
"""Get SeaMicro telnet port to listen."""
|
||||
server_id = int(driver_info['server_id'].split("/")[0])
|
||||
return PORT_BASE + (10 * server_id)
|
||||
|
||||
|
||||
class Power(base.PowerInterface):
|
||||
"""SeaMicro Power Interface.
|
||||
|
||||
This PowerInterface class provides a mechanism for controlling the power
|
||||
state of servers in a seamicro chassis.
|
||||
"""
|
||||
|
||||
def get_properties(self):
|
||||
return COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task):
|
||||
"""Check that node 'driver_info' is valid.
|
||||
|
||||
Check that node 'driver_info' contains the required fields.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: MissingParameterValue if required seamicro parameters are
|
||||
missing.
|
||||
"""
|
||||
_parse_driver_info(task.node)
|
||||
|
||||
def get_power_state(self, task):
|
||||
"""Get the current power state of the task's node.
|
||||
|
||||
Poll the host for the current power state of the node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: ServiceUnavailable on an error from SeaMicro Client.
|
||||
:raises: InvalidParameterValue if a seamicro parameter is invalid.
|
||||
:raises: MissingParameterValue when a required parameter is missing
|
||||
:returns: power state. One of :class:`ironic.common.states`.
|
||||
|
||||
"""
|
||||
return _get_power_status(task.node)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_power_state(self, task, pstate):
|
||||
"""Turn the power on or off.
|
||||
|
||||
Set the power state of a node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param pstate: Either POWER_ON or POWER_OFF from :class:
|
||||
`ironic.common.states`.
|
||||
:raises: InvalidParameterValue if an invalid power state was specified
|
||||
or a seamicro parameter is invalid.
|
||||
:raises: MissingParameterValue when a required parameter is missing
|
||||
:raises: PowerStateFailure if the desired power state couldn't be set.
|
||||
"""
|
||||
|
||||
if pstate == states.POWER_ON:
|
||||
state = _power_on(task.node)
|
||||
elif pstate == states.POWER_OFF:
|
||||
state = _power_off(task.node)
|
||||
else:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"set_power_state called with invalid power state."))
|
||||
|
||||
if state != pstate:
|
||||
raise exception.PowerStateFailure(pstate=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 a seamicro parameter is invalid.
|
||||
:raises: MissingParameterValue if required seamicro parameters are
|
||||
missing.
|
||||
:raises: PowerStateFailure if the final state of the node is not
|
||||
POWER_ON.
|
||||
"""
|
||||
state = _reboot(task.node)
|
||||
|
||||
if state != states.POWER_ON:
|
||||
raise exception.PowerStateFailure(pstate=states.POWER_ON)
|
||||
|
||||
|
||||
class VendorPassthru(base.VendorInterface):
|
||||
"""SeaMicro vendor-specific methods."""
|
||||
|
||||
def get_properties(self):
|
||||
return COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task, method, **kwargs):
|
||||
_parse_driver_info(task.node)
|
||||
|
||||
@base.passthru(['POST'],
|
||||
description=_("Set an untagged VLAN ID for NIC 0 of node. "
|
||||
"Required argument: 'vlan_id' - ID of "
|
||||
"untagged VLAN."))
|
||||
def set_node_vlan_id(self, task, **kwargs):
|
||||
"""Sets an untagged vlan id for NIC 0 of node.
|
||||
|
||||
@kwargs vlan_id: id of untagged vlan for NIC 0 of node
|
||||
"""
|
||||
node = task.node
|
||||
vlan_id = kwargs.get('vlan_id')
|
||||
if not vlan_id:
|
||||
raise exception.MissingParameterValue(_("No vlan id provided"))
|
||||
|
||||
seamicro_info = _parse_driver_info(node)
|
||||
try:
|
||||
server = _get_server(seamicro_info)
|
||||
|
||||
# remove current vlan for server
|
||||
if len(server.nic['0']['untaggedVlan']) > 0:
|
||||
server.unset_untagged_vlan(server.nic['0']['untaggedVlan'])
|
||||
server = server.refresh(5)
|
||||
server.set_untagged_vlan(vlan_id)
|
||||
except seamicro_client_exception.ClientException as ex:
|
||||
LOG.error(_LE("SeaMicro client exception: %s"), ex.message)
|
||||
raise exception.VendorPassthruException(message=ex.message)
|
||||
|
||||
properties = node.properties
|
||||
properties['seamicro_vlan_id'] = vlan_id
|
||||
node.properties = properties
|
||||
node.save()
|
||||
|
||||
@base.passthru(['POST'],
|
||||
description=_("Attach volume to node. Arguments: "
|
||||
"1. 'volume_id' - ID of pre-provisioned "
|
||||
"volume. This is optional. If not specified, "
|
||||
"a volume is created in SeaMicro storage "
|
||||
"pool. 2. 'volume_size' - size of new volume "
|
||||
"(if volume_id is not specified)."))
|
||||
def attach_volume(self, task, **kwargs):
|
||||
"""Attach a volume to a node.
|
||||
|
||||
Attach volume from SeaMicro storage pools for ironic to node.
|
||||
If kwargs['volume_id'] not given, Create volume in SeaMicro
|
||||
storage pool and attach to node.
|
||||
|
||||
@kwargs volume_id: id of pre-provisioned volume that is to be attached
|
||||
as root volume of node
|
||||
@kwargs volume_size: size of new volume to be created and attached
|
||||
as root volume of node
|
||||
"""
|
||||
node = task.node
|
||||
seamicro_info = _parse_driver_info(node)
|
||||
volume_id = kwargs.get('volume_id')
|
||||
|
||||
if volume_id is None:
|
||||
volume_size = kwargs.get('volume_size')
|
||||
if volume_size is None:
|
||||
raise exception.MissingParameterValue(
|
||||
_("No volume size provided for creating volume"))
|
||||
volume_id = _create_volume(seamicro_info, volume_size)
|
||||
|
||||
if _validate_volume(seamicro_info, volume_id):
|
||||
try:
|
||||
server = _get_server(seamicro_info)
|
||||
server.detach_volume()
|
||||
server = server.refresh(5)
|
||||
server.attach_volume(volume_id)
|
||||
except seamicro_client_exception.ClientException as ex:
|
||||
LOG.error(_LE("SeaMicro client exception: %s"), ex.message)
|
||||
raise exception.VendorPassthruException(message=ex.message)
|
||||
|
||||
properties = node.properties
|
||||
properties['seamicro_volume_id'] = volume_id
|
||||
node.properties = properties
|
||||
node.save()
|
||||
|
||||
|
||||
class Management(base.ManagementInterface):
|
||||
|
||||
def get_properties(self):
|
||||
return COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task):
|
||||
"""Check that 'driver_info' contains SeaMicro credentials.
|
||||
|
||||
Validates whether the 'driver_info' property of the supplied
|
||||
task's node contains the required credentials information.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:raises: MissingParameterValue when a required parameter is missing
|
||||
|
||||
"""
|
||||
_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 defined
|
||||
in :mod:`ironic.common.boot_devices`.
|
||||
|
||||
"""
|
||||
return list(_BOOT_DEVICES_MAP.keys())
|
||||
|
||||
@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 reboot of the node.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:param device: the boot device, one of
|
||||
:mod:`ironic.common.boot_devices`.
|
||||
:param persistent: Boolean value. True if the boot device will
|
||||
persist to all future boots, False if not.
|
||||
Default: False. Ignored by this driver.
|
||||
:raises: InvalidParameterValue if an invalid boot device is
|
||||
specified or if a seamicro parameter is invalid.
|
||||
:raises: IronicException on an error from seamicro-client.
|
||||
:raises: MissingParameterValue when a required parameter is missing
|
||||
|
||||
"""
|
||||
if device not in self.get_supported_boot_devices(task):
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Invalid boot device %s specified.") % device)
|
||||
|
||||
seamicro_info = _parse_driver_info(task.node)
|
||||
try:
|
||||
server = _get_server(seamicro_info)
|
||||
boot_device = _BOOT_DEVICES_MAP[device]
|
||||
server.set_boot_order(boot_device)
|
||||
except seamicro_client_exception.ClientException as ex:
|
||||
LOG.error(_LE("Seamicro set boot device failed for node "
|
||||
"%(node)s with the following error: %(error)s"),
|
||||
{'node': task.node.uuid, 'error': ex})
|
||||
raise exception.IronicException(ex)
|
||||
|
||||
def get_boot_device(self, task):
|
||||
"""Get the current boot device for the task's node.
|
||||
|
||||
Returns the current boot device of the node. Be aware that not
|
||||
all drivers support this.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:returns: a dictionary containing:
|
||||
|
||||
:boot_device: the boot device, one of
|
||||
:mod:`ironic.common.boot_devices` or None if it is unknown.
|
||||
:persistent: Whether the boot device will persist to all
|
||||
future boots or not, None if it is unknown.
|
||||
|
||||
"""
|
||||
# TODO(lucasagomes): The python-seamicroclient library currently
|
||||
# doesn't expose a method to get the boot device, update it once
|
||||
# it's implemented.
|
||||
return {'boot_device': None, 'persistent': None}
|
||||
|
||||
def get_sensors_data(self, task):
|
||||
"""Get sensors data method.
|
||||
|
||||
Not implemented by this driver.
|
||||
:param task: a TaskManager instance.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class ShellinaboxConsole(base.ConsoleInterface):
|
||||
"""A ConsoleInterface that uses telnet and shellinabox."""
|
||||
|
||||
def get_properties(self):
|
||||
d = COMMON_PROPERTIES.copy()
|
||||
d.update(CONSOLE_PROPERTIES)
|
||||
return d
|
||||
|
||||
def validate(self, task):
|
||||
"""Validate the Node console info.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:raises: MissingParameterValue if required seamicro parameters are
|
||||
missing
|
||||
:raises: InvalidParameterValue if required parameter are invalid.
|
||||
"""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
if not driver_info['port']:
|
||||
raise exception.MissingParameterValue(_(
|
||||
"Missing 'seamicro_terminal_port' parameter in node's "
|
||||
"driver_info"))
|
||||
|
||||
def start_console(self, task):
|
||||
"""Start a remote console for the node.
|
||||
|
||||
:param task: a task from TaskManager
|
||||
:raises: MissingParameterValue if required seamicro parameters are
|
||||
missing
|
||||
:raises: ConsoleError if the directory for the PID file cannot be
|
||||
created
|
||||
:raises: ConsoleSubprocessFailed when invoking the subprocess failed
|
||||
:raises: InvalidParameterValue if required parameter are invalid.
|
||||
"""
|
||||
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
telnet_port = get_telnet_port(driver_info)
|
||||
chassis_ip = urlparse.urlparse(driver_info['api_endpoint']).netloc
|
||||
|
||||
seamicro_cmd = ("/:%(uid)s:%(gid)s:HOME:telnet %(chassis)s %(port)s"
|
||||
% {'uid': os.getuid(),
|
||||
'gid': os.getgid(),
|
||||
'chassis': chassis_ip,
|
||||
'port': telnet_port})
|
||||
|
||||
console_utils.start_shellinabox_console(driver_info['uuid'],
|
||||
driver_info['port'],
|
||||
seamicro_cmd)
|
||||
|
||||
def stop_console(self, task):
|
||||
"""Stop the remote console session for the node.
|
||||
|
||||
:param task: a task from TaskManager
|
||||
:raises: ConsoleError if unable to stop the console
|
||||
"""
|
||||
|
||||
console_utils.stop_shellinabox_console(task.node.uuid)
|
||||
|
||||
def get_console(self, task):
|
||||
"""Get the type and connection information about the console.
|
||||
|
||||
:raises: MissingParameterValue if required seamicro parameters are
|
||||
missing
|
||||
:raises: InvalidParameterValue if required parameter are invalid.
|
||||
"""
|
||||
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
url = console_utils.get_shellinabox_console_url(driver_info['port'])
|
||||
return {'type': 'shellinabox', 'url': url}
|
@ -1,393 +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.
|
||||
|
||||
"""
|
||||
VirtualBox Driver Modules
|
||||
"""
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _, _LE, _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
|
||||
|
||||
pyremotevbox = importutils.try_import('pyremotevbox')
|
||||
if pyremotevbox:
|
||||
from pyremotevbox import exception as virtualbox_exc
|
||||
from pyremotevbox import vbox as virtualbox
|
||||
|
||||
IRONIC_TO_VIRTUALBOX_DEVICE_MAPPING = {
|
||||
boot_devices.PXE: 'Network',
|
||||
boot_devices.DISK: 'HardDisk',
|
||||
boot_devices.CDROM: 'DVD',
|
||||
}
|
||||
VIRTUALBOX_TO_IRONIC_DEVICE_MAPPING = {
|
||||
v: k for k, v in IRONIC_TO_VIRTUALBOX_DEVICE_MAPPING.items()}
|
||||
|
||||
VIRTUALBOX_TO_IRONIC_POWER_MAPPING = {
|
||||
'PoweredOff': states.POWER_OFF,
|
||||
'Running': states.POWER_ON,
|
||||
'Error': states.ERROR
|
||||
}
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
REQUIRED_PROPERTIES = {
|
||||
'virtualbox_vmname': _("Name of the VM in VirtualBox. Required."),
|
||||
'virtualbox_host': _("IP address or hostname of the VirtualBox host. "
|
||||
"Required.")
|
||||
}
|
||||
|
||||
OPTIONAL_PROPERTIES = {
|
||||
'virtualbox_username': _("Username for the VirtualBox host. "
|
||||
"Default value is ''. Optional."),
|
||||
'virtualbox_password': _("Password for 'virtualbox_username'. "
|
||||
"Default value is ''. Optional."),
|
||||
'virtualbox_port': _("Port on which VirtualBox web service is listening. "
|
||||
"Optional."),
|
||||
}
|
||||
|
||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
||||
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
||||
|
||||
|
||||
def _strip_virtualbox_from_param_name(param_name):
|
||||
if param_name.startswith('virtualbox_'):
|
||||
return param_name[11:]
|
||||
else:
|
||||
return param_name
|
||||
|
||||
|
||||
def _parse_driver_info(node):
|
||||
"""Gets the driver specific node driver info.
|
||||
|
||||
This method validates whether the 'driver_info' property of the
|
||||
supplied node contains the required information for this driver.
|
||||
|
||||
:param node: an Ironic Node object.
|
||||
:returns: a dict containing information from driver_info (or where
|
||||
applicable, config values).
|
||||
:raises: MissingParameterValue, if some required parameter(s) are missing
|
||||
in the node's driver_info.
|
||||
:raises: InvalidParameterValue, if some parameter(s) have invalid value(s)
|
||||
in the node's driver_info.
|
||||
"""
|
||||
info = node.driver_info
|
||||
d_info = {}
|
||||
|
||||
missing_params = []
|
||||
for param in REQUIRED_PROPERTIES:
|
||||
try:
|
||||
d_info_param_name = _strip_virtualbox_from_param_name(param)
|
||||
d_info[d_info_param_name] = info[param]
|
||||
except KeyError:
|
||||
missing_params.append(param)
|
||||
|
||||
if missing_params:
|
||||
msg = (_("The following parameters are missing in driver_info: %s") %
|
||||
', '.join(missing_params))
|
||||
raise exception.MissingParameterValue(msg)
|
||||
|
||||
for param in OPTIONAL_PROPERTIES:
|
||||
if param in info:
|
||||
d_info_param_name = _strip_virtualbox_from_param_name(param)
|
||||
d_info[d_info_param_name] = info[param]
|
||||
|
||||
port = d_info.get('port', CONF.virtualbox.port)
|
||||
d_info['port'] = utils.validate_network_port(port, 'virtualbox_port')
|
||||
|
||||
return d_info
|
||||
|
||||
|
||||
def _run_virtualbox_method(node, ironic_method, vm_object_method,
|
||||
*call_args, **call_kwargs):
|
||||
"""Runs a method of pyremotevbox.vbox.VirtualMachine
|
||||
|
||||
This runs a method from pyremotevbox.vbox.VirtualMachine.
|
||||
The VirtualMachine method to be invoked and the argument(s) to be
|
||||
passed to it are to be provided.
|
||||
|
||||
:param node: an Ironic Node object.
|
||||
:param ironic_method: the Ironic method which called
|
||||
'_run_virtualbox_method'. This is used for logging only.
|
||||
:param vm_object_method: The method on the VirtualMachine object
|
||||
to be called.
|
||||
:param call_args: The args to be passed to 'vm_object_method'.
|
||||
:param call_kwargs: The kwargs to be passed to the 'vm_object_method'.
|
||||
:returns: The value returned by 'vm_object_method'
|
||||
:raises: VirtualBoxOperationFailed, if execution of 'vm_object_method'
|
||||
failed.
|
||||
:raises: InvalidParameterValue,
|
||||
- if 'vm_object_method' is not a valid 'VirtualMachine' method.
|
||||
- if some parameter(s) have invalid value(s) in the node's driver_info.
|
||||
:raises: MissingParameterValue, if some required parameter(s) are missing
|
||||
in the node's driver_info.
|
||||
:raises: pyremotevbox.exception.VmInWrongPowerState, if operation cannot
|
||||
be performed when vm is in the current power state.
|
||||
"""
|
||||
driver_info = _parse_driver_info(node)
|
||||
try:
|
||||
host = virtualbox.VirtualBoxHost(**driver_info)
|
||||
vm_object = host.find_vm(driver_info['vmname'])
|
||||
except virtualbox_exc.PyRemoteVBoxException as exc:
|
||||
LOG.error(_LE("Failed while creating a VirtualMachine object for "
|
||||
"node %(node_id)s. Error: %(error)s."),
|
||||
{'node_id': node.uuid, 'error': exc})
|
||||
raise exception.VirtualBoxOperationFailed(operation=vm_object_method,
|
||||
error=exc)
|
||||
|
||||
try:
|
||||
func = getattr(vm_object, vm_object_method)
|
||||
except AttributeError:
|
||||
error_msg = _("Invalid VirtualMachine method '%s' passed "
|
||||
"to '_run_virtualbox_method'.")
|
||||
raise exception.InvalidParameterValue(error_msg % vm_object_method)
|
||||
|
||||
try:
|
||||
return func(*call_args, **call_kwargs)
|
||||
except virtualbox_exc.PyRemoteVBoxException as exc:
|
||||
error_msg = _LE("'%(ironic_method)s' failed for node %(node_id)s with "
|
||||
"error: %(error)s.")
|
||||
LOG.error(error_msg, {'ironic_method': ironic_method,
|
||||
'node_id': node.uuid,
|
||||
'error': exc})
|
||||
raise exception.VirtualBoxOperationFailed(operation=vm_object_method,
|
||||
error=exc)
|
||||
|
||||
|
||||
class VirtualBoxPower(base.PowerInterface):
|
||||
def get_properties(self):
|
||||
return COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task):
|
||||
"""Check if node.driver_info contains the required credentials.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:raises: MissingParameterValue, if some required parameter(s) are
|
||||
missing in the node's driver_info.
|
||||
:raises: InvalidParameterValue, if some parameter(s) have invalid
|
||||
value(s) in the node's driver_info.
|
||||
"""
|
||||
_parse_driver_info(task.node)
|
||||
|
||||
def _apply_boot_device(self, task):
|
||||
"""Get the target boot device and apply on the baremetal machine .
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
"""
|
||||
driver_internal_info = task.node.driver_internal_info
|
||||
device = driver_internal_info.pop('vbox_target_boot_device', None)
|
||||
if device is not None:
|
||||
task.driver.management.set_boot_device(task, device)
|
||||
task.node.driver_internal_info = driver_internal_info
|
||||
task.node.save()
|
||||
|
||||
def get_power_state(self, task):
|
||||
"""Gets the current power state.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:returns: one of :mod:`ironic.common.states`
|
||||
:raises: MissingParameterValue, if some required parameter(s) are
|
||||
missing in the node's driver_info.
|
||||
:raises: InvalidParameterValue, if some parameter(s) have invalid
|
||||
value(s) in the node's driver_info.
|
||||
:raises: VirtualBoxOperationFailed, if error encountered from
|
||||
VirtualBox operation.
|
||||
"""
|
||||
power_status = _run_virtualbox_method(task.node, 'get_power_state',
|
||||
'get_power_status')
|
||||
try:
|
||||
return VIRTUALBOX_TO_IRONIC_POWER_MAPPING[power_status]
|
||||
except KeyError:
|
||||
msg = _LE("VirtualBox returned unknown state '%(state)s' for "
|
||||
"node %(node)s")
|
||||
LOG.error(msg, {'state': power_status, 'node': task.node.uuid})
|
||||
return states.ERROR
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_power_state(self, task, target_state):
|
||||
"""Turn the current power state on or off.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param target_state: The desired power state POWER_ON,POWER_OFF or
|
||||
REBOOT from :mod:`ironic.common.states`.
|
||||
:raises: MissingParameterValue, if some required parameter(s) are
|
||||
missing in the node's driver_info.
|
||||
:raises: InvalidParameterValue, if some parameter(s) have invalid
|
||||
value(s) in the node's driver_info OR if an invalid power state
|
||||
was specified.
|
||||
:raises: VirtualBoxOperationFailed, if error encountered from
|
||||
VirtualBox operation.
|
||||
"""
|
||||
|
||||
# We set boot device before power on to avoid the case that user
|
||||
# shuts down the machine without calling power off method here. For
|
||||
# instance, soft power off the machine from OS.
|
||||
if target_state == states.POWER_OFF:
|
||||
_run_virtualbox_method(task.node, 'set_power_state', 'stop')
|
||||
self._apply_boot_device(task)
|
||||
elif target_state == states.POWER_ON:
|
||||
self._apply_boot_device(task)
|
||||
_run_virtualbox_method(task.node, 'set_power_state', 'start')
|
||||
elif target_state == states.REBOOT:
|
||||
self.reboot(task)
|
||||
else:
|
||||
msg = _("'set_power_state' called with invalid power "
|
||||
"state '%s'") % target_state
|
||||
raise exception.InvalidParameterValue(msg)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def reboot(self, task):
|
||||
"""Reboot the node.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:raises: MissingParameterValue, if some required parameter(s) are
|
||||
missing in the node's driver_info.
|
||||
:raises: InvalidParameterValue, if some parameter(s) have invalid
|
||||
value(s) in the node's driver_info.
|
||||
:raises: VirtualBoxOperationFailed, if error encountered from
|
||||
VirtualBox operation.
|
||||
"""
|
||||
_run_virtualbox_method(task.node, 'reboot', 'stop')
|
||||
self._apply_boot_device(task)
|
||||
_run_virtualbox_method(task.node, 'reboot', 'start')
|
||||
|
||||
|
||||
class VirtualBoxManagement(base.ManagementInterface):
|
||||
def get_properties(self):
|
||||
return COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task):
|
||||
"""Check that 'driver_info' contains required credentials.
|
||||
|
||||
Validates whether the 'driver_info' property of the supplied
|
||||
task's node contains the required credentials information.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:raises: MissingParameterValue, if some required parameter(s) are
|
||||
missing in the node's driver_info.
|
||||
:raises: InvalidParameterValue, if some parameter(s) have invalid
|
||||
value(s) in the node's driver_info.
|
||||
"""
|
||||
_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 defined
|
||||
in :mod:`ironic.common.boot_devices`.
|
||||
"""
|
||||
return list(IRONIC_TO_VIRTUALBOX_DEVICE_MAPPING.keys())
|
||||
|
||||
def _get_boot_device_from_hardware(self, task):
|
||||
boot_dev = _run_virtualbox_method(task.node,
|
||||
'get_boot_device', 'get_boot_device')
|
||||
ironic_boot_dev = VIRTUALBOX_TO_IRONIC_DEVICE_MAPPING.get(boot_dev)
|
||||
persistent = True
|
||||
if not ironic_boot_dev:
|
||||
persistent = None
|
||||
msg = _LW("VirtualBox returned unknown boot "
|
||||
"device '%(device)s' for node %(node)s")
|
||||
LOG.warning(msg, {'device': boot_dev, 'node': task.node.uuid})
|
||||
return (ironic_boot_dev, persistent)
|
||||
|
||||
def get_boot_device(self, task):
|
||||
"""Get the current boot device for a node.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:returns: a dictionary containing:
|
||||
'boot_device': one of the ironic.common.boot_devices or None
|
||||
'persistent': True if boot device is persistent, False otherwise
|
||||
:raises: MissingParameterValue, if some required parameter(s) are
|
||||
missing in the node's driver_info.
|
||||
:raises: InvalidParameterValue, if some parameter(s) have invalid
|
||||
value(s) in the node's driver_info.
|
||||
:raises: VirtualBoxOperationFailed, if error encountered from
|
||||
VirtualBox operation.
|
||||
"""
|
||||
if task.driver.power.get_power_state(task) == states.POWER_OFF:
|
||||
ironic_boot_dev, persistent = \
|
||||
self._get_boot_device_from_hardware(task)
|
||||
else:
|
||||
ironic_boot_dev = task.node. \
|
||||
driver_internal_info.get('vbox_target_boot_device')
|
||||
if ironic_boot_dev is not None:
|
||||
msg = _LW("As ironic node %(node)s is"
|
||||
" powered on, we will set to boot"
|
||||
" from %(device)s before next boot.")
|
||||
LOG.warning(msg, {'node': task.node.uuid,
|
||||
'device': ironic_boot_dev})
|
||||
persistent = True
|
||||
else:
|
||||
# Maybe the vbox_target_boot_device is cleaned
|
||||
ironic_boot_dev, persistent = \
|
||||
self._get_boot_device_from_hardware(task)
|
||||
return {'boot_device': ironic_boot_dev, 'persistent': persistent}
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_boot_device(self, task, device, persistent=False):
|
||||
"""Set the boot device for a node.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:param device: ironic.common.boot_devices
|
||||
:param persistent: This argument is ignored as VirtualBox support only
|
||||
persistent boot devices.
|
||||
:raises: MissingParameterValue, if some required parameter(s) are
|
||||
missing in the node's driver_info.
|
||||
:raises: InvalidParameterValue, if some parameter(s) have invalid
|
||||
value(s) in the node's driver_info.
|
||||
:raises: VirtualBoxOperationFailed, if error encountered from
|
||||
VirtualBox operation.
|
||||
"""
|
||||
# NOTE(rameshg87): VirtualBox has only persistent boot devices.
|
||||
try:
|
||||
boot_dev = IRONIC_TO_VIRTUALBOX_DEVICE_MAPPING[device]
|
||||
except KeyError:
|
||||
raise exception.InvalidParameterValue(
|
||||
_("Invalid boot device %s specified.") % device)
|
||||
|
||||
if task.driver.power.get_power_state(task) == states.POWER_OFF:
|
||||
|
||||
_run_virtualbox_method(task.node, 'set_boot_device',
|
||||
'set_boot_device', boot_dev)
|
||||
else:
|
||||
LOG.warning(_LW('Node %(node_uuid)s: As VirtualBox do not support '
|
||||
'setting boot device when VM is powered on, we '
|
||||
'will set booting from %(device)s when reboot '
|
||||
'next time.'),
|
||||
{'node_uuid': task.node.uuid, 'device': device})
|
||||
# We should store target boot device in case the
|
||||
# end user shutoff the baremetal machine from NOVA API.
|
||||
boot_device_now = self.get_boot_device(task)['boot_device']
|
||||
if device != boot_device_now:
|
||||
driver_internal_info = task.node.driver_internal_info
|
||||
driver_internal_info['vbox_target_boot_device'] = device
|
||||
task.node.driver_internal_info = driver_internal_info
|
||||
task.node.save()
|
||||
|
||||
def get_sensors_data(self, task):
|
||||
"""Get sensors data.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:raises: FailedToGetSensorData when getting the sensor data fails.
|
||||
:raises: FailedToParseSensorData when parsing sensor data fails.
|
||||
:returns: returns a consistent format dict of sensor data grouped by
|
||||
sensor type, which can be processed by Ceilometer.
|
||||
"""
|
||||
raise NotImplementedError()
|
@ -39,15 +39,11 @@ from ironic.drivers.modules.irmc import inspect as irmc_inspect
|
||||
from ironic.drivers.modules.irmc import management as irmc_management
|
||||
from ironic.drivers.modules.irmc import power as irmc_power
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
from ironic.drivers.modules.msftocs import management as msftocs_management
|
||||
from ironic.drivers.modules.msftocs import power as msftocs_power
|
||||
from ironic.drivers.modules import pxe
|
||||
from ironic.drivers.modules import seamicro
|
||||
from ironic.drivers.modules import snmp
|
||||
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
|
||||
|
||||
|
||||
# For backward compatibility
|
||||
@ -110,32 +106,6 @@ class PXEAndIPMINativeDriver(base.BaseDriver):
|
||||
self.raid = agent.AgentRAID()
|
||||
|
||||
|
||||
class PXEAndSeaMicroDriver(base.BaseDriver):
|
||||
"""PXE + SeaMicro driver.
|
||||
|
||||
This driver implements the `core` functionality, combining
|
||||
:class:`ironic.drivers.modules.seamicro.Power` 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('seamicroclient'):
|
||||
raise exception.DriverLoadError(
|
||||
driver=self.__class__.__name__,
|
||||
reason=_("Unable to import seamicroclient library"))
|
||||
self.power = seamicro.Power()
|
||||
self.boot = pxe.PXEBoot()
|
||||
self.deploy = iscsi_deploy.ISCSIDeploy()
|
||||
self.management = seamicro.Management()
|
||||
self.vendor = seamicro.VendorPassthru()
|
||||
self.console = seamicro.ShellinaboxConsole()
|
||||
|
||||
|
||||
class PXEAndIloDriver(base.BaseDriver):
|
||||
"""PXE + Ilo Driver using IloClient interface.
|
||||
|
||||
@ -206,52 +176,6 @@ class PXEAndIRMCDriver(base.BaseDriver):
|
||||
self.inspect = irmc_inspect.IRMCInspect()
|
||||
|
||||
|
||||
class PXEAndVirtualBoxDriver(base.BaseDriver):
|
||||
"""PXE + VirtualBox driver.
|
||||
|
||||
NOTE: This driver is meant only for testing environments.
|
||||
|
||||
This driver implements the `core` functionality, combining
|
||||
:class:`ironic.drivers.virtualbox.VirtualBoxPower` for power on/off and
|
||||
reboot of VirtualBox virtual machines, 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('pyremotevbox'):
|
||||
raise exception.DriverLoadError(
|
||||
driver=self.__class__.__name__,
|
||||
reason=_("Unable to import pyremotevbox library"))
|
||||
self.power = virtualbox.VirtualBoxPower()
|
||||
self.boot = pxe.PXEBoot()
|
||||
self.deploy = iscsi_deploy.ISCSIDeploy()
|
||||
self.management = virtualbox.VirtualBoxManagement()
|
||||
self.raid = agent.AgentRAID()
|
||||
|
||||
|
||||
class PXEAndMSFTOCSDriver(base.BaseDriver):
|
||||
"""PXE + MSFT OCS driver.
|
||||
|
||||
This driver implements the `core` functionality, combining
|
||||
:class:`ironic.drivers.modules.msftocs.power.MSFTOCSPower` 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):
|
||||
self.power = msftocs_power.MSFTOCSPower()
|
||||
self.boot = pxe.PXEBoot()
|
||||
self.deploy = iscsi_deploy.ISCSIDeploy()
|
||||
self.management = msftocs_management.MSFTOCSManagement()
|
||||
|
||||
|
||||
class PXEAndUcsDriver(base.BaseDriver):
|
||||
"""PXE + Cisco UCSM driver.
|
||||
|
||||
|
@ -4776,12 +4776,6 @@ class ManagerTestProperties(mgr_utils.ServiceSetUpMixin,
|
||||
'deploy_forces_oob_reboot']
|
||||
self._check_driver_properties("fake_pxe", expected)
|
||||
|
||||
def test_driver_properties_fake_seamicro(self):
|
||||
expected = ['seamicro_api_endpoint', 'seamicro_password',
|
||||
'seamicro_server_id', 'seamicro_username',
|
||||
'seamicro_api_version', 'seamicro_terminal_port']
|
||||
self._check_driver_properties("fake_seamicro", expected)
|
||||
|
||||
def test_driver_properties_fake_snmp(self):
|
||||
expected = ['snmp_driver', 'snmp_address', 'snmp_port', 'snmp_version',
|
||||
'snmp_community', 'snmp_security', 'snmp_outlet']
|
||||
@ -4813,14 +4807,6 @@ class ManagerTestProperties(mgr_utils.ServiceSetUpMixin,
|
||||
'deploy_forces_oob_reboot']
|
||||
self._check_driver_properties("pxe_ssh", expected)
|
||||
|
||||
def test_driver_properties_pxe_seamicro(self):
|
||||
expected = ['deploy_kernel', 'deploy_ramdisk',
|
||||
'seamicro_api_endpoint', 'seamicro_password',
|
||||
'seamicro_server_id', 'seamicro_username',
|
||||
'seamicro_api_version', 'seamicro_terminal_port',
|
||||
'deploy_forces_oob_reboot']
|
||||
self._check_driver_properties("pxe_seamicro", expected)
|
||||
|
||||
def test_driver_properties_pxe_snmp(self):
|
||||
expected = ['deploy_kernel', 'deploy_ramdisk',
|
||||
'snmp_driver', 'snmp_address', 'snmp_port', 'snmp_version',
|
||||
|
@ -89,15 +89,6 @@ def get_test_pxe_instance_info():
|
||||
}
|
||||
|
||||
|
||||
def get_test_seamicro_info():
|
||||
return {
|
||||
"seamicro_api_endpoint": "http://1.2.3.4",
|
||||
"seamicro_username": "admin",
|
||||
"seamicro_password": "fake",
|
||||
"seamicro_server_id": "0/0",
|
||||
}
|
||||
|
||||
|
||||
def get_test_ilo_info():
|
||||
return {
|
||||
"ilo_address": "1.2.3.4",
|
||||
@ -127,15 +118,6 @@ def get_test_irmc_info():
|
||||
}
|
||||
|
||||
|
||||
def get_test_msftocs_info():
|
||||
return {
|
||||
"msftocs_base_url": "http://fakehost:8000",
|
||||
"msftocs_username": "admin",
|
||||
"msftocs_password": "fake",
|
||||
"msftocs_blade_id": 1,
|
||||
}
|
||||
|
||||
|
||||
def get_test_agent_instance_info():
|
||||
return {
|
||||
'image_source': 'fake-image',
|
||||
|
@ -1,110 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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 MSFT OCS common functions
|
||||
"""
|
||||
|
||||
import mock
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers.modules.msftocs import common as msftocs_common
|
||||
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_msftocs_info()
|
||||
|
||||
|
||||
class MSFTOCSCommonTestCase(db_base.DbTestCase):
|
||||
def setUp(self):
|
||||
super(MSFTOCSCommonTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver='fake_msftocs')
|
||||
self.info = INFO_DICT
|
||||
self.node = obj_utils.create_test_node(self.context,
|
||||
driver='fake_msftocs',
|
||||
driver_info=self.info)
|
||||
|
||||
def test_get_client_info(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
driver_info = task.node.driver_info
|
||||
(client, blade_id) = msftocs_common.get_client_info(driver_info)
|
||||
|
||||
self.assertEqual(driver_info['msftocs_base_url'], client._base_url)
|
||||
self.assertEqual(driver_info['msftocs_username'], client._username)
|
||||
self.assertEqual(driver_info['msftocs_password'], client._password)
|
||||
self.assertEqual(driver_info['msftocs_blade_id'], blade_id)
|
||||
|
||||
@mock.patch.object(msftocs_common, '_is_valid_url', autospec=True)
|
||||
def test_parse_driver_info(self, mock_is_valid_url):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
msftocs_common.parse_driver_info(task.node)
|
||||
mock_is_valid_url.assert_called_once_with(
|
||||
task.node.driver_info['msftocs_base_url'])
|
||||
|
||||
def test_parse_driver_info_fail_missing_param(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
del task.node.driver_info['msftocs_base_url']
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
msftocs_common.parse_driver_info,
|
||||
task.node)
|
||||
|
||||
def test_parse_driver_info_fail_bad_url(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.driver_info['msftocs_base_url'] = "bad-url"
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
msftocs_common.parse_driver_info,
|
||||
task.node)
|
||||
|
||||
def test_parse_driver_info_fail_bad_blade_id_type(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.driver_info['msftocs_blade_id'] = "bad-blade-id"
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
msftocs_common.parse_driver_info,
|
||||
task.node)
|
||||
|
||||
def test_parse_driver_info_fail_bad_blade_id_value(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.driver_info['msftocs_blade_id'] = 0
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
msftocs_common.parse_driver_info,
|
||||
task.node)
|
||||
|
||||
def test__is_valid_url(self):
|
||||
self.assertIs(True, msftocs_common._is_valid_url("http://fake.com"))
|
||||
self.assertIs(
|
||||
True, msftocs_common._is_valid_url("http://www.fake.com"))
|
||||
self.assertIs(True, msftocs_common._is_valid_url("http://FAKE.com"))
|
||||
self.assertIs(True, msftocs_common._is_valid_url("http://fake"))
|
||||
self.assertIs(
|
||||
True, msftocs_common._is_valid_url("http://fake.com/blah"))
|
||||
self.assertIs(True, msftocs_common._is_valid_url("http://localhost"))
|
||||
self.assertIs(True, msftocs_common._is_valid_url("https://fake.com"))
|
||||
self.assertIs(True, msftocs_common._is_valid_url("http://10.0.0.1"))
|
||||
self.assertIs(False, msftocs_common._is_valid_url("bad-url"))
|
||||
self.assertIs(False, msftocs_common._is_valid_url("http://.bad-url"))
|
||||
self.assertIs(False, msftocs_common._is_valid_url("http://bad-url$"))
|
||||
self.assertIs(False, msftocs_common._is_valid_url("http://$bad-url"))
|
||||
self.assertIs(False, msftocs_common._is_valid_url("http://bad$url"))
|
||||
self.assertIs(False, msftocs_common._is_valid_url(None))
|
||||
self.assertIs(False, msftocs_common._is_valid_url(0))
|
@ -1,132 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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 MSFT OCS ManagementInterface
|
||||
"""
|
||||
|
||||
import mock
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers.modules.msftocs import common as msftocs_common
|
||||
from ironic.drivers.modules.msftocs import msftocsclient
|
||||
from ironic.drivers import utils as drivers_utils
|
||||
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_msftocs_info()
|
||||
|
||||
|
||||
class MSFTOCSManagementTestCase(db_base.DbTestCase):
|
||||
def setUp(self):
|
||||
super(MSFTOCSManagementTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver='fake_msftocs')
|
||||
self.info = INFO_DICT
|
||||
self.node = obj_utils.create_test_node(self.context,
|
||||
driver='fake_msftocs',
|
||||
driver_info=self.info)
|
||||
|
||||
def test_get_properties(self):
|
||||
expected = msftocs_common.REQUIRED_PROPERTIES
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
self.assertEqual(expected, task.driver.get_properties())
|
||||
|
||||
@mock.patch.object(msftocs_common, 'parse_driver_info', 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(msftocs_common, 'parse_driver_info', 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)
|
||||
|
||||
def test_get_supported_boot_devices(self):
|
||||
expected = [boot_devices.PXE, boot_devices.DISK, boot_devices.BIOS]
|
||||
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)))
|
||||
|
||||
@mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
|
||||
def _test_set_boot_device_one_time(self, persistent, uefi,
|
||||
mock_gci):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
|
||||
blade_id = task.node.driver_info['msftocs_blade_id']
|
||||
mock_gci.return_value = (mock_c, blade_id)
|
||||
|
||||
if uefi:
|
||||
drivers_utils.add_node_capability(task, 'boot_mode', 'uefi')
|
||||
|
||||
task.driver.management.set_boot_device(
|
||||
task, boot_devices.PXE, persistent)
|
||||
|
||||
mock_gci.assert_called_once_with(task.node.driver_info)
|
||||
mock_c.set_next_boot.assert_called_once_with(
|
||||
blade_id, msftocsclient.BOOT_TYPE_FORCE_PXE, persistent, uefi)
|
||||
|
||||
def test_set_boot_device_one_time(self):
|
||||
self._test_set_boot_device_one_time(False, False)
|
||||
|
||||
def test_set_boot_device_persistent(self):
|
||||
self._test_set_boot_device_one_time(True, False)
|
||||
|
||||
def test_set_boot_device_uefi(self):
|
||||
self._test_set_boot_device_one_time(True, True)
|
||||
|
||||
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(msftocs_common, 'get_client_info', autospec=True)
|
||||
def test_get_boot_device(self, mock_gci):
|
||||
expected = {'boot_device': boot_devices.DISK, 'persistent': None}
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
|
||||
blade_id = task.node.driver_info['msftocs_blade_id']
|
||||
mock_gci.return_value = (mock_c, blade_id)
|
||||
force_hdd = msftocsclient.BOOT_TYPE_FORCE_DEFAULT_HDD
|
||||
mock_c.get_next_boot.return_value = force_hdd
|
||||
|
||||
self.assertEqual(expected,
|
||||
task.driver.management.get_boot_device(task))
|
||||
mock_gci.assert_called_once_with(task.node.driver_info)
|
||||
mock_c.get_next_boot.assert_called_once_with(blade_id)
|
||||
|
||||
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,182 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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 MSFT OCS REST API client
|
||||
"""
|
||||
|
||||
import mock
|
||||
import requests
|
||||
from requests import exceptions as requests_exceptions
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.drivers.modules.msftocs import msftocsclient
|
||||
from ironic.tests import base
|
||||
|
||||
|
||||
FAKE_BOOT_RESPONSE = (
|
||||
'<BootResponse xmlns="%s" '
|
||||
'xmlns:i="http://www.w3.org/2001/XMLSchema-instance">'
|
||||
'<completionCode>Success</completionCode>'
|
||||
'<apiVersion>1</apiVersion>'
|
||||
'<statusDescription>Success</statusDescription>'
|
||||
'<bladeNumber>1</bladeNumber>'
|
||||
'<nextBoot>ForcePxe</nextBoot>'
|
||||
'</BootResponse>') % msftocsclient.WCSNS
|
||||
|
||||
FAKE_BLADE_RESPONSE = (
|
||||
'<BladeResponse xmlns="%s" '
|
||||
'xmlns:i="http://www.w3.org/2001/XMLSchema-instance">'
|
||||
'<completionCode>Success</completionCode>'
|
||||
'<apiVersion>1</apiVersion>'
|
||||
'<statusDescription/>'
|
||||
'<bladeNumber>1</bladeNumber>'
|
||||
'</BladeResponse>') % msftocsclient.WCSNS
|
||||
|
||||
FAKE_POWER_STATE_RESPONSE = (
|
||||
'<PowerStateResponse xmlns="%s" '
|
||||
'xmlns:i="http://www.w3.org/2001/XMLSchema-instance">'
|
||||
'<completionCode>Success</completionCode>'
|
||||
'<apiVersion>1</apiVersion>'
|
||||
'<statusDescription>Blade Power is On, firmware decompressed'
|
||||
'</statusDescription>'
|
||||
'<bladeNumber>1</bladeNumber>'
|
||||
'<Decompression>0</Decompression>'
|
||||
'<powerState>ON</powerState>'
|
||||
'</PowerStateResponse>') % msftocsclient.WCSNS
|
||||
|
||||
FAKE_BLADE_STATE_RESPONSE = (
|
||||
'<BladeStateResponse xmlns="%s" '
|
||||
'xmlns:i="http://www.w3.org/2001/XMLSchema-instance">'
|
||||
'<completionCode>Success</completionCode>'
|
||||
'<apiVersion>1</apiVersion>'
|
||||
'<statusDescription/>'
|
||||
'<bladeNumber>1</bladeNumber>'
|
||||
'<bladeState>ON</bladeState>'
|
||||
'</BladeStateResponse>') % msftocsclient.WCSNS
|
||||
|
||||
|
||||
class MSFTOCSClientApiTestCase(base.TestCase):
|
||||
def setUp(self):
|
||||
super(MSFTOCSClientApiTestCase, self).setUp()
|
||||
self._fake_base_url = "http://fakehost:8000"
|
||||
self._fake_username = "admin"
|
||||
self._fake_password = 'fake'
|
||||
self._fake_blade_id = 1
|
||||
self._client = msftocsclient.MSFTOCSClientApi(
|
||||
self._fake_base_url, self._fake_username, self._fake_password)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test__exec_cmd(self, mock_get):
|
||||
fake_response_text = 'fake_response_text'
|
||||
fake_rel_url = 'fake_rel_url'
|
||||
mock_get.return_value.text = 'fake_response_text'
|
||||
|
||||
self.assertEqual(fake_response_text,
|
||||
self._client._exec_cmd(fake_rel_url))
|
||||
mock_get.assert_called_once_with(
|
||||
self._fake_base_url + "/" + fake_rel_url, auth=mock.ANY)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test__exec_cmd_http_get_fail(self, mock_get):
|
||||
fake_rel_url = 'fake_rel_url'
|
||||
mock_get.side_effect = requests_exceptions.ConnectionError('x')
|
||||
|
||||
self.assertRaises(exception.MSFTOCSClientApiException,
|
||||
self._client._exec_cmd,
|
||||
fake_rel_url)
|
||||
mock_get.assert_called_once_with(
|
||||
self._fake_base_url + "/" + fake_rel_url, auth=mock.ANY)
|
||||
|
||||
def test__check_completion_code(self):
|
||||
et = self._client._check_completion_code(FAKE_BOOT_RESPONSE)
|
||||
self.assertEqual('{%s}BootResponse' % msftocsclient.WCSNS, et.tag)
|
||||
|
||||
def test__check_completion_code_fail(self):
|
||||
self.assertRaises(exception.MSFTOCSClientApiException,
|
||||
self._client._check_completion_code,
|
||||
'<fake xmlns="%s"></fake>' % msftocsclient.WCSNS)
|
||||
|
||||
def test__check_completion_with_bad_completion_code_fail(self):
|
||||
self.assertRaises(exception.MSFTOCSClientApiException,
|
||||
self._client._check_completion_code,
|
||||
'<fake xmlns="%s">'
|
||||
'<completionCode>Fail</completionCode>'
|
||||
'</fake>' % msftocsclient.WCSNS)
|
||||
|
||||
def test__check_completion_code_xml_parsing_fail(self):
|
||||
self.assertRaises(exception.MSFTOCSClientApiException,
|
||||
self._client._check_completion_code,
|
||||
'bad_xml')
|
||||
|
||||
@mock.patch.object(
|
||||
msftocsclient.MSFTOCSClientApi, '_exec_cmd', autospec=True)
|
||||
def test_get_blade_state(self, mock_exec_cmd):
|
||||
mock_exec_cmd.return_value = FAKE_BLADE_STATE_RESPONSE
|
||||
self.assertEqual(
|
||||
msftocsclient.POWER_STATUS_ON,
|
||||
self._client.get_blade_state(self._fake_blade_id))
|
||||
mock_exec_cmd.assert_called_once_with(
|
||||
self._client, "GetBladeState?bladeId=%d" % self._fake_blade_id)
|
||||
|
||||
@mock.patch.object(
|
||||
msftocsclient.MSFTOCSClientApi, '_exec_cmd', autospec=True)
|
||||
def test_set_blade_on(self, mock_exec_cmd):
|
||||
mock_exec_cmd.return_value = FAKE_BLADE_RESPONSE
|
||||
self._client.set_blade_on(self._fake_blade_id)
|
||||
mock_exec_cmd.assert_called_once_with(
|
||||
self._client, "SetBladeOn?bladeId=%d" % self._fake_blade_id)
|
||||
|
||||
@mock.patch.object(
|
||||
msftocsclient.MSFTOCSClientApi, '_exec_cmd', autospec=True)
|
||||
def test_set_blade_off(self, mock_exec_cmd):
|
||||
mock_exec_cmd.return_value = FAKE_BLADE_RESPONSE
|
||||
self._client.set_blade_off(self._fake_blade_id)
|
||||
mock_exec_cmd.assert_called_once_with(
|
||||
self._client, "SetBladeOff?bladeId=%d" % self._fake_blade_id)
|
||||
|
||||
@mock.patch.object(
|
||||
msftocsclient.MSFTOCSClientApi, '_exec_cmd', autospec=True)
|
||||
def test_set_blade_power_cycle(self, mock_exec_cmd):
|
||||
mock_exec_cmd.return_value = FAKE_BLADE_RESPONSE
|
||||
self._client.set_blade_power_cycle(self._fake_blade_id)
|
||||
mock_exec_cmd.assert_called_once_with(
|
||||
self._client,
|
||||
"SetBladeActivePowerCycle?bladeId=%d&offTime=0" %
|
||||
self._fake_blade_id)
|
||||
|
||||
@mock.patch.object(
|
||||
msftocsclient.MSFTOCSClientApi, '_exec_cmd', autospec=True)
|
||||
def test_get_next_boot(self, mock_exec_cmd):
|
||||
mock_exec_cmd.return_value = FAKE_BOOT_RESPONSE
|
||||
self.assertEqual(
|
||||
msftocsclient.BOOT_TYPE_FORCE_PXE,
|
||||
self._client.get_next_boot(self._fake_blade_id))
|
||||
mock_exec_cmd.assert_called_once_with(
|
||||
self._client, "GetNextBoot?bladeId=%d" % self._fake_blade_id)
|
||||
|
||||
@mock.patch.object(
|
||||
msftocsclient.MSFTOCSClientApi, '_exec_cmd', autospec=True)
|
||||
def test_set_next_boot(self, mock_exec_cmd):
|
||||
mock_exec_cmd.return_value = FAKE_BOOT_RESPONSE
|
||||
self._client.set_next_boot(self._fake_blade_id,
|
||||
msftocsclient.BOOT_TYPE_FORCE_PXE)
|
||||
mock_exec_cmd.assert_called_once_with(
|
||||
self._client,
|
||||
"SetNextBoot?bladeId=%(blade_id)d&bootType=%(boot_type)d&"
|
||||
"uefi=%(uefi)s&persistent=%(persistent)s" %
|
||||
{"blade_id": self._fake_blade_id,
|
||||
"boot_type": msftocsclient.BOOT_TYPE_FORCE_PXE,
|
||||
"uefi": "true", "persistent": "true"})
|
@ -1,163 +0,0 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# 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 MSFT OCS PowerInterface
|
||||
"""
|
||||
|
||||
import mock
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import states
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers.modules.msftocs import common as msftocs_common
|
||||
from ironic.drivers.modules.msftocs import msftocsclient
|
||||
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_msftocs_info()
|
||||
|
||||
|
||||
class MSFTOCSPowerTestCase(db_base.DbTestCase):
|
||||
def setUp(self):
|
||||
super(MSFTOCSPowerTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver='fake_msftocs')
|
||||
self.info = INFO_DICT
|
||||
self.node = obj_utils.create_test_node(self.context,
|
||||
driver='fake_msftocs',
|
||||
driver_info=self.info)
|
||||
|
||||
def test_get_properties(self):
|
||||
expected = msftocs_common.REQUIRED_PROPERTIES
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
self.assertEqual(expected, task.driver.get_properties())
|
||||
|
||||
@mock.patch.object(msftocs_common, 'parse_driver_info', 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(msftocs_common, 'parse_driver_info', 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(msftocs_common, 'get_client_info', autospec=True)
|
||||
def test_get_power_state(self, mock_gci):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
|
||||
blade_id = task.node.driver_info['msftocs_blade_id']
|
||||
mock_gci.return_value = (mock_c, blade_id)
|
||||
mock_c.get_blade_state.return_value = msftocsclient.POWER_STATUS_ON
|
||||
|
||||
self.assertEqual(states.POWER_ON,
|
||||
task.driver.power.get_power_state(task))
|
||||
mock_gci.assert_called_once_with(task.node.driver_info)
|
||||
mock_c.get_blade_state.assert_called_once_with(blade_id)
|
||||
|
||||
@mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
|
||||
def test_set_power_state_on(self, mock_gci):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
|
||||
blade_id = task.node.driver_info['msftocs_blade_id']
|
||||
mock_gci.return_value = (mock_c, blade_id)
|
||||
|
||||
task.driver.power.set_power_state(task, states.POWER_ON)
|
||||
mock_gci.assert_called_once_with(task.node.driver_info)
|
||||
mock_c.set_blade_on.assert_called_once_with(blade_id)
|
||||
|
||||
@mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
|
||||
def test_set_power_state_off(self, mock_gci):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
|
||||
blade_id = task.node.driver_info['msftocs_blade_id']
|
||||
mock_gci.return_value = (mock_c, blade_id)
|
||||
|
||||
task.driver.power.set_power_state(task, states.POWER_OFF)
|
||||
mock_gci.assert_called_once_with(task.node.driver_info)
|
||||
mock_c.set_blade_off.assert_called_once_with(blade_id)
|
||||
|
||||
@mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
|
||||
def test_set_power_state_blade_on_fail(self, mock_gci):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
|
||||
blade_id = task.node.driver_info['msftocs_blade_id']
|
||||
mock_gci.return_value = (mock_c, blade_id)
|
||||
|
||||
ex = exception.MSFTOCSClientApiException('x')
|
||||
mock_c.set_blade_on.side_effect = ex
|
||||
|
||||
pstate = states.POWER_ON
|
||||
self.assertRaises(exception.PowerStateFailure,
|
||||
task.driver.power.set_power_state,
|
||||
task, pstate)
|
||||
mock_gci.assert_called_once_with(task.node.driver_info)
|
||||
mock_c.set_blade_on.assert_called_once_with(blade_id)
|
||||
|
||||
@mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
|
||||
def test_set_power_state_invalid_parameter_fail(self, mock_gci):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
|
||||
blade_id = task.node.driver_info['msftocs_blade_id']
|
||||
mock_gci.return_value = (mock_c, blade_id)
|
||||
|
||||
pstate = states.ERROR
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.power.set_power_state,
|
||||
task, pstate)
|
||||
mock_gci.assert_called_once_with(task.node.driver_info)
|
||||
|
||||
@mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
|
||||
def test_reboot(self, mock_gci):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
|
||||
blade_id = task.node.driver_info['msftocs_blade_id']
|
||||
mock_gci.return_value = (mock_c, blade_id)
|
||||
|
||||
task.driver.power.reboot(task)
|
||||
mock_gci.assert_called_once_with(task.node.driver_info)
|
||||
mock_c.set_blade_power_cycle.assert_called_once_with(blade_id)
|
||||
|
||||
@mock.patch.object(msftocs_common, 'get_client_info', autospec=True)
|
||||
def test_reboot_fail(self, mock_gci):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
mock_c = mock.MagicMock(spec=msftocsclient.MSFTOCSClientApi)
|
||||
blade_id = task.node.driver_info['msftocs_blade_id']
|
||||
mock_gci.return_value = (mock_c, blade_id)
|
||||
|
||||
ex = exception.MSFTOCSClientApiException('x')
|
||||
mock_c.set_blade_power_cycle.side_effect = ex
|
||||
|
||||
self.assertRaises(exception.PowerStateFailure,
|
||||
task.driver.power.reboot,
|
||||
task)
|
||||
mock_gci.assert_called_once_with(task.node.driver_info)
|
||||
mock_c.set_blade_power_cycle.assert_called_once_with(blade_id)
|
@ -1,689 +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 Ironic SeaMicro driver."""
|
||||
|
||||
|
||||
import mock
|
||||
from oslo_utils import uuidutils
|
||||
from seamicroclient import client as seamicro_client
|
||||
from seamicroclient import exceptions as seamicro_client_exception
|
||||
from six.moves import http_client
|
||||
|
||||
from ironic.common import boot_devices
|
||||
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 console_utils
|
||||
from ironic.drivers.modules import seamicro
|
||||
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_seamicro_info()
|
||||
|
||||
|
||||
class Fake_Server(object):
|
||||
def __init__(self, active=False, *args, **kwargs):
|
||||
self.active = active
|
||||
self.nic = {'0': {'untaggedVlan': ''}}
|
||||
|
||||
def power_on(self):
|
||||
self.active = True
|
||||
|
||||
def power_off(self, force=False):
|
||||
self.active = False
|
||||
|
||||
def reset(self):
|
||||
self.active = True
|
||||
|
||||
def set_untagged_vlan(self, vlan_id):
|
||||
return
|
||||
|
||||
def attach_volume(self, volume_id):
|
||||
return
|
||||
|
||||
def detach_volume(self):
|
||||
return
|
||||
|
||||
def set_boot_order(self, boot_order):
|
||||
return
|
||||
|
||||
def refresh(self, wait=0):
|
||||
return self
|
||||
|
||||
|
||||
class Fake_Volume(object):
|
||||
def __init__(self, id=None, *args, **kwargs):
|
||||
if id is None:
|
||||
self.id = "%s/%s/%s" % ("0", "ironic-p6-6",
|
||||
uuidutils.generate_uuid())
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
|
||||
class Fake_Pool(object):
|
||||
def __init__(self, freeSize=None, *args, **kwargs):
|
||||
self.freeSize = freeSize
|
||||
|
||||
|
||||
class SeaMicroValidateParametersTestCase(db_base.DbTestCase):
|
||||
|
||||
def test__parse_driver_info_good(self):
|
||||
# make sure we get back the expected things
|
||||
node = obj_utils.get_test_node(
|
||||
self.context,
|
||||
driver='fake_seamicro',
|
||||
driver_info=INFO_DICT)
|
||||
info = seamicro._parse_driver_info(node)
|
||||
self.assertEqual('http://1.2.3.4', info['api_endpoint'])
|
||||
self.assertEqual('admin', info['username'])
|
||||
self.assertEqual('fake', info['password'])
|
||||
self.assertEqual('0/0', info['server_id'])
|
||||
self.assertEqual('1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
|
||||
info['uuid'])
|
||||
|
||||
def test__parse_driver_info_missing_api_endpoint(self):
|
||||
# make sure error is raised when info is missing
|
||||
info = dict(INFO_DICT)
|
||||
del info['seamicro_api_endpoint']
|
||||
node = obj_utils.get_test_node(self.context, driver_info=info)
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
seamicro._parse_driver_info,
|
||||
node)
|
||||
|
||||
def test__parse_driver_info_missing_username(self):
|
||||
# make sure error is raised when info is missing
|
||||
info = dict(INFO_DICT)
|
||||
del info['seamicro_username']
|
||||
node = obj_utils.get_test_node(self.context, driver_info=info)
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
seamicro._parse_driver_info,
|
||||
node)
|
||||
|
||||
def test__parse_driver_info_missing_password(self):
|
||||
# make sure error is raised when info is missing
|
||||
info = dict(INFO_DICT)
|
||||
del info['seamicro_password']
|
||||
node = obj_utils.get_test_node(self.context, driver_info=info)
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
seamicro._parse_driver_info,
|
||||
node)
|
||||
|
||||
def test__parse_driver_info_missing_server_id(self):
|
||||
# make sure error is raised when info is missing
|
||||
info = dict(INFO_DICT)
|
||||
del info['seamicro_server_id']
|
||||
node = obj_utils.get_test_node(self.context, driver_info=info)
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
seamicro._parse_driver_info,
|
||||
node)
|
||||
|
||||
def test__parse_driver_info_empty_terminal_port(self):
|
||||
info = dict(INFO_DICT)
|
||||
info['seamicro_terminal_port'] = ''
|
||||
node = obj_utils.get_test_node(self.context, driver_info=info)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
seamicro._parse_driver_info,
|
||||
node)
|
||||
|
||||
|
||||
@mock.patch('eventlet.greenthread.sleep', lambda n: None)
|
||||
class SeaMicroPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SeaMicroPrivateMethodsTestCase, self).setUp()
|
||||
n = {
|
||||
'driver': 'fake_seamicro',
|
||||
'driver_info': INFO_DICT
|
||||
}
|
||||
self.node = obj_utils.create_test_node(self.context, **n)
|
||||
self.Server = Fake_Server
|
||||
self.Volume = Fake_Volume
|
||||
self.Pool = Fake_Pool
|
||||
self.config(action_timeout=0, group='seamicro')
|
||||
self.config(max_retry=2, group='seamicro')
|
||||
|
||||
self.info = seamicro._parse_driver_info(self.node)
|
||||
|
||||
@mock.patch.object(seamicro_client, "Client", autospec=True)
|
||||
def test__get_client(self, mock_client):
|
||||
args = {'username': self.info['username'],
|
||||
'password': self.info['password'],
|
||||
'auth_url': self.info['api_endpoint']}
|
||||
seamicro._get_client(**self.info)
|
||||
mock_client.assert_called_once_with(self.info['api_version'], **args)
|
||||
|
||||
@mock.patch.object(seamicro_client, "Client", autospec=True)
|
||||
def test__get_client_fail(self, mock_client):
|
||||
args = {'username': self.info['username'],
|
||||
'password': self.info['password'],
|
||||
'auth_url': self.info['api_endpoint']}
|
||||
mock_client.side_effect = seamicro_client_exception.UnsupportedVersion
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
seamicro._get_client,
|
||||
**self.info)
|
||||
mock_client.assert_called_once_with(self.info['api_version'], **args)
|
||||
|
||||
@mock.patch.object(seamicro, "_get_server", autospec=True)
|
||||
def test__get_power_status_on(self, mock_get_server):
|
||||
mock_get_server.return_value = self.Server(active=True)
|
||||
pstate = seamicro._get_power_status(self.node)
|
||||
self.assertEqual(states.POWER_ON, pstate)
|
||||
|
||||
@mock.patch.object(seamicro, "_get_server", autospec=True)
|
||||
def test__get_power_status_off(self, mock_get_server):
|
||||
mock_get_server.return_value = self.Server(active=False)
|
||||
pstate = seamicro._get_power_status(self.node)
|
||||
self.assertEqual(states.POWER_OFF, pstate)
|
||||
|
||||
@mock.patch.object(seamicro, "_get_server", autospec=True)
|
||||
def test__get_power_status_error(self, mock_get_server):
|
||||
mock_get_server.return_value = self.Server(active=None)
|
||||
pstate = seamicro._get_power_status(self.node)
|
||||
self.assertEqual(states.ERROR, pstate)
|
||||
|
||||
@mock.patch.object(seamicro, "_get_server", autospec=True)
|
||||
def test__power_on_good(self, mock_get_server):
|
||||
mock_get_server.return_value = self.Server(active=False)
|
||||
pstate = seamicro._power_on(self.node)
|
||||
self.assertEqual(states.POWER_ON, pstate)
|
||||
|
||||
@mock.patch.object(seamicro, "_get_server", autospec=True)
|
||||
def test__power_on_fail(self, mock_get_server):
|
||||
def fake_power_on():
|
||||
return
|
||||
|
||||
server = self.Server(active=False)
|
||||
server.power_on = fake_power_on
|
||||
mock_get_server.return_value = server
|
||||
pstate = seamicro._power_on(self.node)
|
||||
self.assertEqual(states.ERROR, pstate)
|
||||
|
||||
@mock.patch.object(seamicro, "_get_server", autospec=True)
|
||||
def test__power_off_good(self, mock_get_server):
|
||||
mock_get_server.return_value = self.Server(active=True)
|
||||
pstate = seamicro._power_off(self.node)
|
||||
self.assertEqual(states.POWER_OFF, pstate)
|
||||
|
||||
@mock.patch.object(seamicro, "_get_server", autospec=True)
|
||||
def test__power_off_fail(self, mock_get_server):
|
||||
def fake_power_off():
|
||||
return
|
||||
server = self.Server(active=True)
|
||||
server.power_off = fake_power_off
|
||||
mock_get_server.return_value = server
|
||||
pstate = seamicro._power_off(self.node)
|
||||
self.assertEqual(states.ERROR, pstate)
|
||||
|
||||
@mock.patch.object(seamicro, "_get_server", autospec=True)
|
||||
def test__reboot_good(self, mock_get_server):
|
||||
mock_get_server.return_value = self.Server(active=True)
|
||||
pstate = seamicro._reboot(self.node)
|
||||
self.assertEqual(states.POWER_ON, pstate)
|
||||
|
||||
@mock.patch.object(seamicro, "_get_server", autospec=True)
|
||||
def test__reboot_fail(self, mock_get_server):
|
||||
def fake_reboot():
|
||||
return
|
||||
server = self.Server(active=False)
|
||||
server.reset = fake_reboot
|
||||
mock_get_server.return_value = server
|
||||
pstate = seamicro._reboot(self.node)
|
||||
self.assertEqual(states.ERROR, pstate)
|
||||
|
||||
@mock.patch.object(seamicro, "_get_volume", autospec=True)
|
||||
def test__validate_fail(self, mock_get_volume):
|
||||
volume_id = "0/p6-6/vol1"
|
||||
volume = self.Volume()
|
||||
volume.id = volume_id
|
||||
mock_get_volume.return_value = volume
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
seamicro._validate_volume, self.info, volume_id)
|
||||
|
||||
@mock.patch.object(seamicro, "_get_volume", autospec=True)
|
||||
def test__validate_good(self, mock_get_volume):
|
||||
volume = self.Volume()
|
||||
mock_get_volume.return_value = volume
|
||||
valid = seamicro._validate_volume(self.info, volume.id)
|
||||
self.assertTrue(valid)
|
||||
|
||||
@mock.patch.object(seamicro, "_get_pools", autospec=True)
|
||||
def test__create_volume_fail(self, mock_get_pools):
|
||||
mock_get_pools.return_value = None
|
||||
self.assertRaises(exception.IronicException,
|
||||
seamicro._create_volume,
|
||||
self.info, 2)
|
||||
|
||||
@mock.patch.object(seamicro, "_get_pools", autospec=True)
|
||||
@mock.patch.object(seamicro, "_get_client", autospec=True)
|
||||
def test__create_volume_good(self, mock_get_client, mock_get_pools):
|
||||
pools = [self.Pool(1), self.Pool(6), self.Pool(5)]
|
||||
mock_seamicro_volumes = mock.MagicMock(spec_set=['create'])
|
||||
mock_get_client.return_value = mock.MagicMock(
|
||||
volumes=mock_seamicro_volumes, spec_set=['volumes'])
|
||||
mock_get_pools.return_value = pools
|
||||
seamicro._create_volume(self.info, 2)
|
||||
|
||||
|
||||
class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SeaMicroPowerDriverTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver='fake_seamicro')
|
||||
self.driver = driver_factory.get_driver('fake_seamicro')
|
||||
self.node = obj_utils.create_test_node(self.context,
|
||||
driver='fake_seamicro',
|
||||
driver_info=INFO_DICT)
|
||||
self.get_server_patcher = mock.patch.object(seamicro, '_get_server',
|
||||
autospec=True)
|
||||
|
||||
self.get_server_mock = None
|
||||
self.Server = Fake_Server
|
||||
self.Volume = Fake_Volume
|
||||
self.info = seamicro._parse_driver_info(self.node)
|
||||
|
||||
def test_get_properties(self):
|
||||
expected = seamicro.COMMON_PROPERTIES
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=True) as task:
|
||||
self.assertEqual(expected, task.driver.power.get_properties())
|
||||
|
||||
expected = (list(seamicro.COMMON_PROPERTIES) +
|
||||
list(seamicro.CONSOLE_PROPERTIES))
|
||||
console_properties = task.driver.console.get_properties().keys()
|
||||
self.assertEqual(sorted(expected), sorted(console_properties))
|
||||
self.assertEqual(sorted(expected),
|
||||
sorted(task.driver.get_properties().keys()))
|
||||
|
||||
def test_vendor_routes(self):
|
||||
expected = ['set_node_vlan_id', 'attach_volume']
|
||||
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(vendor_routes))
|
||||
|
||||
def test_driver_routes(self):
|
||||
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({}, driver_routes)
|
||||
|
||||
@mock.patch.object(seamicro, '_parse_driver_info', autospec=True)
|
||||
def test_power_interface_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(seamicro, '_parse_driver_info', autospec=True)
|
||||
def test_power_interface_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)
|
||||
|
||||
@mock.patch.object(seamicro, '_reboot', autospec=True)
|
||||
def test_reboot(self, mock_reboot):
|
||||
mock_reboot.return_value = states.POWER_ON
|
||||
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
task.driver.power.reboot(task)
|
||||
|
||||
mock_reboot.assert_called_once_with(task.node)
|
||||
|
||||
def test_set_power_state_bad_state(self):
|
||||
self.get_server_mock = self.get_server_patcher.start()
|
||||
self.get_server_mock.return_value = self.Server()
|
||||
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.IronicException,
|
||||
task.driver.power.set_power_state,
|
||||
task, "BAD_PSTATE")
|
||||
self.get_server_patcher.stop()
|
||||
|
||||
@mock.patch.object(seamicro, '_power_on', autospec=True)
|
||||
def test_set_power_state_on_good(self, mock_power_on):
|
||||
mock_power_on.return_value = states.POWER_ON
|
||||
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
task.driver.power.set_power_state(task, states.POWER_ON)
|
||||
|
||||
mock_power_on.assert_called_once_with(task.node)
|
||||
|
||||
@mock.patch.object(seamicro, '_power_on', autospec=True)
|
||||
def test_set_power_state_on_fail(self, mock_power_on):
|
||||
mock_power_on.return_value = states.POWER_OFF
|
||||
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.PowerStateFailure,
|
||||
task.driver.power.set_power_state,
|
||||
task, states.POWER_ON)
|
||||
|
||||
mock_power_on.assert_called_once_with(task.node)
|
||||
|
||||
@mock.patch.object(seamicro, '_power_off', autospec=True)
|
||||
def test_set_power_state_off_good(self, mock_power_off):
|
||||
mock_power_off.return_value = states.POWER_OFF
|
||||
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
task.driver.power.set_power_state(task, states.POWER_OFF)
|
||||
|
||||
mock_power_off.assert_called_once_with(task.node)
|
||||
|
||||
@mock.patch.object(seamicro, '_power_off', autospec=True)
|
||||
def test_set_power_state_off_fail(self, mock_power_off):
|
||||
mock_power_off.return_value = states.POWER_ON
|
||||
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.PowerStateFailure,
|
||||
task.driver.power.set_power_state,
|
||||
task, states.POWER_OFF)
|
||||
|
||||
mock_power_off.assert_called_once_with(task.node)
|
||||
|
||||
@mock.patch.object(seamicro, '_parse_driver_info', autospec=True)
|
||||
def test_vendor_passthru_validate_good(self, mock_info):
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=True) as task:
|
||||
for method in task.driver.vendor.vendor_routes:
|
||||
task.driver.vendor.validate(task, **{'method': method})
|
||||
self.assertEqual(len(task.driver.vendor.vendor_routes),
|
||||
mock_info.call_count)
|
||||
|
||||
@mock.patch.object(seamicro, '_parse_driver_info', autospec=True)
|
||||
def test_vendor_passthru_validate_parse_driver_info_fail(self, mock_info):
|
||||
mock_info.side_effect = exception.InvalidParameterValue("bad")
|
||||
with task_manager.acquire(self.context, self.node['uuid'],
|
||||
shared=True) as task:
|
||||
method = list(task.driver.vendor.vendor_routes)[0]
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.vendor.validate,
|
||||
task, **{'method': method})
|
||||
mock_info.assert_called_once_with(task.node)
|
||||
|
||||
@mock.patch.object(seamicro, '_get_server', autospec=True)
|
||||
def test_set_node_vlan_id_good(self, mock_get_server):
|
||||
vlan_id = "12"
|
||||
mock_get_server.return_value = self.Server(active="true")
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
kwargs = {'vlan_id': vlan_id}
|
||||
task.driver.vendor.set_node_vlan_id(task, **kwargs)
|
||||
mock_get_server.assert_called_once_with(self.info)
|
||||
|
||||
def test_set_node_vlan_id_no_input(self):
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.vendor.set_node_vlan_id,
|
||||
task, **{})
|
||||
|
||||
@mock.patch.object(seamicro, '_get_server', autospec=True)
|
||||
def test_set_node_vlan_id_fail(self, mock_get_server):
|
||||
def fake_set_untagged_vlan(self, **kwargs):
|
||||
raise seamicro_client_exception.ClientException(
|
||||
http_client.INTERNAL_SERVER_ERROR)
|
||||
|
||||
vlan_id = "12"
|
||||
server = self.Server(active="true")
|
||||
server.set_untagged_vlan = fake_set_untagged_vlan
|
||||
mock_get_server.return_value = server
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
kwargs = {'vlan_id': vlan_id}
|
||||
self.assertRaises(exception.IronicException,
|
||||
task.driver.vendor.set_node_vlan_id,
|
||||
task, **kwargs)
|
||||
|
||||
mock_get_server.assert_called_once_with(self.info)
|
||||
|
||||
@mock.patch.object(seamicro, '_get_server', autospec=True)
|
||||
@mock.patch.object(seamicro, '_validate_volume', autospec=True)
|
||||
def test_attach_volume_with_volume_id_good(self, mock_validate_volume,
|
||||
mock_get_server):
|
||||
volume_id = '0/ironic-p6-1/vol1'
|
||||
mock_validate_volume.return_value = True
|
||||
mock_get_server.return_value = self.Server(active="true")
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
kwargs = {'volume_id': volume_id}
|
||||
task.driver.vendor.attach_volume(task, **kwargs)
|
||||
mock_get_server.assert_called_once_with(self.info)
|
||||
|
||||
@mock.patch.object(seamicro, '_get_server', autospec=True)
|
||||
@mock.patch.object(seamicro, '_get_volume', autospec=True)
|
||||
def test_attach_volume_with_invalid_volume_id_fail(self,
|
||||
mock_get_volume,
|
||||
mock_get_server):
|
||||
volume_id = '0/p6-1/vol1'
|
||||
mock_get_volume.return_value = self.Volume(volume_id)
|
||||
mock_get_server.return_value = self.Server(active="true")
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
kwargs = {'volume_id': volume_id}
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.vendor.attach_volume,
|
||||
task, **kwargs)
|
||||
|
||||
@mock.patch.object(seamicro, '_get_server', autospec=True)
|
||||
@mock.patch.object(seamicro, '_validate_volume', autospec=True)
|
||||
def test_attach_volume_fail(self, mock_validate_volume,
|
||||
mock_get_server):
|
||||
def fake_attach_volume(self, **kwargs):
|
||||
raise seamicro_client_exception.ClientException(
|
||||
http_client.INTERNAL_SERVER_ERROR)
|
||||
|
||||
volume_id = '0/p6-1/vol1'
|
||||
mock_validate_volume.return_value = True
|
||||
server = self.Server(active="true")
|
||||
server.attach_volume = fake_attach_volume
|
||||
mock_get_server.return_value = server
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
kwargs = {'volume_id': volume_id}
|
||||
self.assertRaises(exception.IronicException,
|
||||
task.driver.vendor.attach_volume,
|
||||
task, **kwargs)
|
||||
|
||||
mock_get_server.assert_called_once_with(self.info)
|
||||
|
||||
@mock.patch.object(seamicro, '_get_server', autospec=True)
|
||||
@mock.patch.object(seamicro, '_validate_volume', autospec=True)
|
||||
@mock.patch.object(seamicro, '_create_volume', autospec=True)
|
||||
def test_attach_volume_with_volume_size_good(self, mock_create_volume,
|
||||
mock_validate_volume,
|
||||
mock_get_server):
|
||||
volume_id = '0/ironic-p6-1/vol1'
|
||||
volume_size = 2
|
||||
mock_create_volume.return_value = volume_id
|
||||
mock_validate_volume.return_value = True
|
||||
mock_get_server.return_value = self.Server(active="true")
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
kwargs = {'volume_size': volume_size}
|
||||
task.driver.vendor.attach_volume(task, **kwargs)
|
||||
mock_get_server.assert_called_once_with(self.info)
|
||||
mock_create_volume.assert_called_once_with(self.info, volume_size)
|
||||
|
||||
def test_attach_volume_with_no_input_fail(self):
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.vendor.attach_volume, task,
|
||||
**{})
|
||||
|
||||
@mock.patch.object(seamicro, '_get_server', autospec=True)
|
||||
def test_set_boot_device_good(self, mock_get_server):
|
||||
boot_device = "disk"
|
||||
mock_get_server.return_value = self.Server(active="true")
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
task.driver.management.set_boot_device(task, boot_device)
|
||||
mock_get_server.assert_called_once_with(self.info)
|
||||
|
||||
@mock.patch.object(seamicro, '_get_server', autospec=True)
|
||||
def test_set_boot_device_invalid_device_fail(self, mock_get_server):
|
||||
boot_device = "invalid_device"
|
||||
mock_get_server.return_value = self.Server(active="true")
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.management.set_boot_device,
|
||||
task, boot_device)
|
||||
|
||||
@mock.patch.object(seamicro, '_get_server', autospec=True)
|
||||
def test_set_boot_device_fail(self, mock_get_server):
|
||||
def fake_set_boot_order(self, **kwargs):
|
||||
raise seamicro_client_exception.ClientException(
|
||||
http_client.INTERNAL_SERVER_ERROR)
|
||||
|
||||
boot_device = "pxe"
|
||||
server = self.Server(active="true")
|
||||
server.set_boot_order = fake_set_boot_order
|
||||
mock_get_server.return_value = server
|
||||
with task_manager.acquire(self.context, self.info['uuid'],
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.IronicException,
|
||||
task.driver.management.set_boot_device,
|
||||
task, boot_device)
|
||||
|
||||
mock_get_server.assert_called_once_with(self.info)
|
||||
|
||||
def test_management_interface_get_supported_boot_devices(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
expected = [boot_devices.PXE, boot_devices.DISK]
|
||||
self.assertEqual(sorted(expected), sorted(task.driver.management.
|
||||
get_supported_boot_devices(task)))
|
||||
|
||||
def test_management_interface_get_boot_device(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
expected = {'boot_device': None, 'persistent': None}
|
||||
self.assertEqual(expected,
|
||||
task.driver.management.get_boot_device(task))
|
||||
|
||||
def test_management_interface_validate_good(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
task.driver.management.validate(task)
|
||||
|
||||
def test_management_interface_validate_fail(self):
|
||||
# Missing SEAMICRO driver_info information
|
||||
node = obj_utils.create_test_node(self.context,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
driver='fake_seamicro')
|
||||
with task_manager.acquire(self.context, node.uuid) as task:
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
task.driver.management.validate, task)
|
||||
|
||||
|
||||
class SeaMicroDriverTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SeaMicroDriverTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver='fake_seamicro')
|
||||
self.driver = driver_factory.get_driver('fake_seamicro')
|
||||
self.node = obj_utils.create_test_node(self.context,
|
||||
driver='fake_seamicro',
|
||||
driver_info=INFO_DICT)
|
||||
self.get_server_patcher = mock.patch.object(seamicro, '_get_server',
|
||||
autospec=True)
|
||||
|
||||
self.get_server_mock = None
|
||||
self.Server = Fake_Server
|
||||
self.Volume = Fake_Volume
|
||||
self.info = seamicro._parse_driver_info(self.node)
|
||||
|
||||
@mock.patch.object(console_utils, 'start_shellinabox_console',
|
||||
autospec=True)
|
||||
def test_start_console(self, mock_exec):
|
||||
mock_exec.return_value = None
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
self.driver.console.start_console(task)
|
||||
|
||||
mock_exec.assert_called_once_with(self.info['uuid'],
|
||||
self.info['port'],
|
||||
mock.ANY)
|
||||
|
||||
@mock.patch.object(console_utils, 'start_shellinabox_console',
|
||||
autospec=True)
|
||||
def test_start_console_fail(self, mock_exec):
|
||||
mock_exec.side_effect = exception.ConsoleSubprocessFailed(
|
||||
error='error')
|
||||
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
self.assertRaises(exception.ConsoleSubprocessFailed,
|
||||
self.driver.console.start_console,
|
||||
task)
|
||||
|
||||
@mock.patch.object(console_utils, 'stop_shellinabox_console',
|
||||
autospec=True)
|
||||
def test_stop_console(self, mock_exec):
|
||||
mock_exec.return_value = None
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
self.driver.console.stop_console(task)
|
||||
|
||||
mock_exec.assert_called_once_with(self.info['uuid'])
|
||||
|
||||
@mock.patch.object(console_utils, 'stop_shellinabox_console',
|
||||
autospec=True)
|
||||
def test_stop_console_fail(self, mock_stop):
|
||||
mock_stop.side_effect = exception.ConsoleError()
|
||||
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
self.assertRaises(exception.ConsoleError,
|
||||
self.driver.console.stop_console,
|
||||
task)
|
||||
|
||||
mock_stop.assert_called_once_with(self.node.uuid)
|
||||
|
||||
@mock.patch.object(console_utils, 'start_shellinabox_console',
|
||||
autospec=True)
|
||||
def test_start_console_fail_nodir(self, mock_exec):
|
||||
mock_exec.side_effect = exception.ConsoleError()
|
||||
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
self.assertRaises(exception.ConsoleError,
|
||||
self.driver.console.start_console,
|
||||
task)
|
||||
mock_exec.assert_called_once_with(self.node.uuid, mock.ANY, mock.ANY)
|
||||
|
||||
@mock.patch.object(console_utils, 'get_shellinabox_console_url',
|
||||
autospec=True)
|
||||
def test_get_console(self, mock_exec):
|
||||
url = 'http://localhost:4201'
|
||||
mock_exec.return_value = url
|
||||
expected = {'type': 'shellinabox', 'url': url}
|
||||
|
||||
with task_manager.acquire(self.context,
|
||||
self.node.uuid) as task:
|
||||
console_info = self.driver.console.get_console(task)
|
||||
|
||||
self.assertEqual(expected, console_info)
|
||||
mock_exec.assert_called_once_with(self.info['port'])
|
@ -1,420 +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 VirtualBox Driver Modules."""
|
||||
|
||||
import mock
|
||||
from pyremotevbox import exception as pyremotevbox_exc
|
||||
from pyremotevbox import vbox as pyremotevbox_vbox
|
||||
|
||||
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 import virtualbox
|
||||
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
|
||||
|
||||
INFO_DICT = {
|
||||
'virtualbox_vmname': 'baremetal1',
|
||||
'virtualbox_host': '10.0.2.2',
|
||||
'virtualbox_username': 'username',
|
||||
'virtualbox_password': 'password',
|
||||
'virtualbox_port': 12345,
|
||||
}
|
||||
|
||||
|
||||
class VirtualBoxMethodsTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VirtualBoxMethodsTestCase, self).setUp()
|
||||
driver_info = INFO_DICT.copy()
|
||||
mgr_utils.mock_the_extension_manager(driver="fake_vbox")
|
||||
self.node = obj_utils.create_test_node(self.context,
|
||||
driver='fake_vbox',
|
||||
driver_info=driver_info)
|
||||
|
||||
def test__parse_driver_info(self):
|
||||
info = virtualbox._parse_driver_info(self.node)
|
||||
self.assertEqual('baremetal1', info['vmname'])
|
||||
self.assertEqual('10.0.2.2', info['host'])
|
||||
self.assertEqual('username', info['username'])
|
||||
self.assertEqual('password', info['password'])
|
||||
self.assertEqual(12345, info['port'])
|
||||
|
||||
def test__parse_driver_info_missing_vmname(self):
|
||||
del self.node.driver_info['virtualbox_vmname']
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
virtualbox._parse_driver_info, self.node)
|
||||
|
||||
def test__parse_driver_info_missing_host(self):
|
||||
del self.node.driver_info['virtualbox_host']
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
virtualbox._parse_driver_info, self.node)
|
||||
|
||||
def test__parse_driver_info_invalid_port(self):
|
||||
self.node.driver_info['virtualbox_port'] = 'invalid-port'
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
virtualbox._parse_driver_info, self.node)
|
||||
|
||||
def test__parse_driver_info_missing_port(self):
|
||||
del self.node.driver_info['virtualbox_port']
|
||||
info = virtualbox._parse_driver_info(self.node)
|
||||
self.assertEqual(18083, info['port'])
|
||||
|
||||
@mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost', autospec=True)
|
||||
def test__run_virtualbox_method(self, host_mock):
|
||||
host_object_mock = mock.MagicMock(spec_set=['find_vm'])
|
||||
func_mock = mock.MagicMock(spec_set=[])
|
||||
vm_object_mock = mock.MagicMock(spec_set=['foo'], foo=func_mock)
|
||||
host_mock.return_value = host_object_mock
|
||||
host_object_mock.find_vm.return_value = vm_object_mock
|
||||
func_mock.return_value = 'return-value'
|
||||
|
||||
return_value = virtualbox._run_virtualbox_method(
|
||||
self.node, 'some-ironic-method', 'foo', 'args', kwarg='kwarg')
|
||||
|
||||
host_mock.assert_called_once_with(vmname='baremetal1',
|
||||
host='10.0.2.2',
|
||||
username='username',
|
||||
password='password',
|
||||
port=12345)
|
||||
host_object_mock.find_vm.assert_called_once_with('baremetal1')
|
||||
func_mock.assert_called_once_with('args', kwarg='kwarg')
|
||||
self.assertEqual('return-value', return_value)
|
||||
|
||||
@mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost', autospec=True)
|
||||
def test__run_virtualbox_method_get_host_fails(self, host_mock):
|
||||
host_mock.side_effect = pyremotevbox_exc.PyRemoteVBoxException
|
||||
|
||||
self.assertRaises(exception.VirtualBoxOperationFailed,
|
||||
virtualbox._run_virtualbox_method,
|
||||
self.node, 'some-ironic-method', 'foo',
|
||||
'args', kwarg='kwarg')
|
||||
|
||||
@mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost', autospec=True)
|
||||
def test__run_virtualbox_method_find_vm_fails(self, host_mock):
|
||||
host_object_mock = mock.MagicMock(spec_set=['find_vm'])
|
||||
host_mock.return_value = host_object_mock
|
||||
exc = pyremotevbox_exc.PyRemoteVBoxException
|
||||
host_object_mock.find_vm.side_effect = exc
|
||||
|
||||
self.assertRaises(exception.VirtualBoxOperationFailed,
|
||||
virtualbox._run_virtualbox_method,
|
||||
self.node, 'some-ironic-method', 'foo', 'args',
|
||||
kwarg='kwarg')
|
||||
host_mock.assert_called_once_with(vmname='baremetal1',
|
||||
host='10.0.2.2',
|
||||
username='username',
|
||||
password='password',
|
||||
port=12345)
|
||||
host_object_mock.find_vm.assert_called_once_with('baremetal1')
|
||||
|
||||
@mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost', autospec=True)
|
||||
def test__run_virtualbox_method_func_fails(self, host_mock):
|
||||
host_object_mock = mock.MagicMock(spec_set=['find_vm'])
|
||||
host_mock.return_value = host_object_mock
|
||||
func_mock = mock.MagicMock()
|
||||
vm_object_mock = mock.MagicMock(spec_set=['foo'], foo=func_mock)
|
||||
host_object_mock.find_vm.return_value = vm_object_mock
|
||||
func_mock.side_effect = pyremotevbox_exc.PyRemoteVBoxException
|
||||
|
||||
self.assertRaises(exception.VirtualBoxOperationFailed,
|
||||
virtualbox._run_virtualbox_method,
|
||||
self.node, 'some-ironic-method', 'foo',
|
||||
'args', kwarg='kwarg')
|
||||
host_mock.assert_called_once_with(vmname='baremetal1',
|
||||
host='10.0.2.2',
|
||||
username='username',
|
||||
password='password',
|
||||
port=12345)
|
||||
host_object_mock.find_vm.assert_called_once_with('baremetal1')
|
||||
func_mock.assert_called_once_with('args', kwarg='kwarg')
|
||||
|
||||
@mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost', autospec=True)
|
||||
def test__run_virtualbox_method_invalid_method(self, host_mock):
|
||||
host_object_mock = mock.MagicMock(spec_set=['find_vm'])
|
||||
host_mock.return_value = host_object_mock
|
||||
vm_object_mock = mock.MagicMock(spec_set=[])
|
||||
host_object_mock.find_vm.return_value = vm_object_mock
|
||||
del vm_object_mock.foo
|
||||
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
virtualbox._run_virtualbox_method,
|
||||
self.node, 'some-ironic-method', 'foo',
|
||||
'args', kwarg='kwarg')
|
||||
host_mock.assert_called_once_with(vmname='baremetal1',
|
||||
host='10.0.2.2',
|
||||
username='username',
|
||||
password='password',
|
||||
port=12345)
|
||||
host_object_mock.find_vm.assert_called_once_with('baremetal1')
|
||||
|
||||
@mock.patch.object(pyremotevbox_vbox, 'VirtualBoxHost', autospec=True)
|
||||
def test__run_virtualbox_method_vm_wrong_power_state(self, host_mock):
|
||||
host_object_mock = mock.MagicMock(spec_set=['find_vm'])
|
||||
host_mock.return_value = host_object_mock
|
||||
func_mock = mock.MagicMock(spec_set=[])
|
||||
vm_object_mock = mock.MagicMock(spec_set=['foo'], foo=func_mock)
|
||||
host_object_mock.find_vm.return_value = vm_object_mock
|
||||
func_mock.side_effect = pyremotevbox_exc.VmInWrongPowerState
|
||||
|
||||
# _run_virtualbox_method() doesn't catch VmInWrongPowerState and
|
||||
# lets caller handle it.
|
||||
self.assertRaises(pyremotevbox_exc.VmInWrongPowerState,
|
||||
virtualbox._run_virtualbox_method,
|
||||
self.node, 'some-ironic-method', 'foo',
|
||||
'args', kwarg='kwarg')
|
||||
host_mock.assert_called_once_with(vmname='baremetal1',
|
||||
host='10.0.2.2',
|
||||
username='username',
|
||||
password='password',
|
||||
port=12345)
|
||||
host_object_mock.find_vm.assert_called_once_with('baremetal1')
|
||||
func_mock.assert_called_once_with('args', kwarg='kwarg')
|
||||
|
||||
|
||||
class VirtualBoxPowerTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VirtualBoxPowerTestCase, self).setUp()
|
||||
driver_info = INFO_DICT.copy()
|
||||
mgr_utils.mock_the_extension_manager(driver="fake_vbox")
|
||||
self.node = obj_utils.create_test_node(self.context,
|
||||
driver='fake_vbox',
|
||||
driver_info=driver_info)
|
||||
|
||||
def test_get_properties(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
properties = task.driver.power.get_properties()
|
||||
|
||||
self.assertIn('virtualbox_vmname', properties)
|
||||
self.assertIn('virtualbox_host', properties)
|
||||
|
||||
@mock.patch.object(virtualbox, '_parse_driver_info', autospec=True)
|
||||
def test_validate(self, parse_info_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.power.validate(task)
|
||||
parse_info_mock.assert_called_once_with(task.node)
|
||||
|
||||
@mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
|
||||
def test_get_power_state(self, run_method_mock):
|
||||
run_method_mock.return_value = 'PoweredOff'
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
power_state = task.driver.power.get_power_state(task)
|
||||
run_method_mock.assert_called_once_with(task.node,
|
||||
'get_power_state',
|
||||
'get_power_status')
|
||||
self.assertEqual(states.POWER_OFF, power_state)
|
||||
|
||||
@mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
|
||||
def test_get_power_state_invalid_state(self, run_method_mock):
|
||||
run_method_mock.return_value = 'invalid-state'
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
power_state = task.driver.power.get_power_state(task)
|
||||
run_method_mock.assert_called_once_with(task.node,
|
||||
'get_power_state',
|
||||
'get_power_status')
|
||||
self.assertEqual(states.ERROR, power_state)
|
||||
|
||||
@mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
|
||||
def test_set_power_state_off(self, run_method_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.power.set_power_state(task, states.POWER_OFF)
|
||||
run_method_mock.assert_called_once_with(task.node,
|
||||
'set_power_state',
|
||||
'stop')
|
||||
|
||||
@mock.patch.object(virtualbox.VirtualBoxManagement, 'set_boot_device')
|
||||
@mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
|
||||
def test_set_power_state_on(self, run_method_mock, set_boot_device_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['vbox_target_boot_device'] = 'pxe'
|
||||
task.driver.power.set_power_state(task, states.POWER_ON)
|
||||
run_method_mock.assert_called_once_with(task.node,
|
||||
'set_power_state',
|
||||
'start')
|
||||
set_boot_device_mock.assert_called_once_with(task, 'pxe')
|
||||
|
||||
@mock.patch.object(virtualbox.VirtualBoxManagement, 'set_boot_device')
|
||||
@mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
|
||||
def test_set_power_state_reboot(self, run_method_mock,
|
||||
mock_set_boot_device):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['vbox_target_boot_device'] = 'pxe'
|
||||
task.driver.power.set_power_state(task, states.REBOOT)
|
||||
run_method_mock.assert_any_call(task.node,
|
||||
'reboot',
|
||||
'stop')
|
||||
mock_set_boot_device.assert_called_once_with(task, 'pxe')
|
||||
run_method_mock.assert_any_call(task.node,
|
||||
'reboot',
|
||||
'start')
|
||||
|
||||
def test_set_power_state_invalid_state(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.power.set_power_state,
|
||||
task, 'invalid-state')
|
||||
|
||||
@mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
|
||||
def test_reboot(self, run_method_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.power.reboot(task)
|
||||
run_method_mock.assert_any_call(task.node,
|
||||
'reboot',
|
||||
'stop')
|
||||
run_method_mock.assert_any_call(task.node,
|
||||
'reboot',
|
||||
'start')
|
||||
|
||||
|
||||
class VirtualBoxManagementTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VirtualBoxManagementTestCase, self).setUp()
|
||||
driver_info = INFO_DICT.copy()
|
||||
mgr_utils.mock_the_extension_manager(driver="fake_vbox")
|
||||
self.node = obj_utils.create_test_node(self.context,
|
||||
driver='fake_vbox',
|
||||
driver_info=driver_info)
|
||||
|
||||
def test_get_properties(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
properties = task.driver.management.get_properties()
|
||||
|
||||
self.assertIn('virtualbox_vmname', properties)
|
||||
self.assertIn('virtualbox_host', properties)
|
||||
|
||||
@mock.patch.object(virtualbox, '_parse_driver_info', autospec=True)
|
||||
def test_validate(self, parse_info_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.management.validate(task)
|
||||
parse_info_mock.assert_called_once_with(task.node)
|
||||
|
||||
def test_get_supported_boot_devices(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
devices = task.driver.management.get_supported_boot_devices(task)
|
||||
self.assertIn(boot_devices.PXE, devices)
|
||||
self.assertIn(boot_devices.DISK, devices)
|
||||
self.assertIn(boot_devices.CDROM, devices)
|
||||
|
||||
@mock.patch.object(virtualbox.VirtualBoxPower,
|
||||
'get_power_state', autospec=True)
|
||||
@mock.patch.object(virtualbox, '_run_virtualbox_method',
|
||||
autospec=True)
|
||||
def test_get_boot_device_VM_Poweroff_ok(self, run_method_mock,
|
||||
get_power_state_mock):
|
||||
run_method_mock.return_value = 'Network'
|
||||
get_power_state_mock.return_value = states.POWER_OFF
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
ret_val = task.driver.management.get_boot_device(task)
|
||||
run_method_mock.assert_called_once_with(task.node,
|
||||
'get_boot_device',
|
||||
'get_boot_device')
|
||||
self.assertEqual(boot_devices.PXE, ret_val['boot_device'])
|
||||
self.assertTrue(ret_val['persistent'])
|
||||
|
||||
@mock.patch.object(virtualbox.VirtualBoxPower,
|
||||
'get_power_state', autospec=True)
|
||||
def test_get_boot_device_VM_Poweron_ok(self, get_power_state_mock):
|
||||
get_power_state_mock.return_value = states.POWER_ON
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['vbox_target_boot_device'] = 'pxe'
|
||||
ret_val = task.driver.management.get_boot_device(task)
|
||||
self.assertEqual(boot_devices.PXE, ret_val['boot_device'])
|
||||
self.assertTrue(ret_val['persistent'])
|
||||
|
||||
@mock.patch.object(virtualbox.VirtualBoxPower,
|
||||
'get_power_state', autospec=True)
|
||||
@mock.patch.object(virtualbox, '_run_virtualbox_method',
|
||||
autospec=True)
|
||||
def test_get_boot_device_target_device_none_ok(self,
|
||||
run_method_mock,
|
||||
get_power_state_mock):
|
||||
run_method_mock.return_value = 'Network'
|
||||
get_power_state_mock.return_value = states.POWER_ON
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.node.driver_internal_info['vbox_target_boot_device'] = None
|
||||
ret_val = task.driver.management.get_boot_device(task)
|
||||
run_method_mock.assert_called_once_with(task.node,
|
||||
'get_boot_device',
|
||||
'get_boot_device')
|
||||
self.assertEqual(boot_devices.PXE, ret_val['boot_device'])
|
||||
self.assertTrue(ret_val['persistent'])
|
||||
|
||||
@mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
|
||||
def test_get_boot_device_invalid(self, run_method_mock):
|
||||
run_method_mock.return_value = 'invalid-boot-device'
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
ret_val = task.driver.management.get_boot_device(task)
|
||||
self.assertIsNone(ret_val['boot_device'])
|
||||
self.assertIsNone(ret_val['persistent'])
|
||||
|
||||
@mock.patch.object(virtualbox.VirtualBoxPower,
|
||||
'get_power_state', autospec=True)
|
||||
@mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
|
||||
def test_set_boot_device_VM_Poweroff_ok(self, run_method_mock,
|
||||
get_power_state_mock):
|
||||
get_power_state_mock.return_value = states.POWER_OFF
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.management.set_boot_device(task, boot_devices.PXE)
|
||||
run_method_mock.assert_called_with(task.node,
|
||||
'set_boot_device',
|
||||
'set_boot_device',
|
||||
'Network')
|
||||
|
||||
@mock.patch.object(virtualbox.VirtualBoxPower,
|
||||
'get_power_state', autospec=True)
|
||||
@mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
|
||||
def test_set_boot_device_VM_Poweron_ok(self, run_method_mock,
|
||||
get_power_state_mock):
|
||||
get_power_state_mock.return_value = states.POWER_ON
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.management.set_boot_device(task, boot_devices.PXE)
|
||||
self.assertEqual('pxe',
|
||||
task.node.driver_internal_info
|
||||
['vbox_target_boot_device'])
|
||||
|
||||
@mock.patch.object(virtualbox, '_run_virtualbox_method', autospec=True)
|
||||
def test_set_boot_device_invalid(self, run_method_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.management.set_boot_device,
|
||||
task, 'invalid-boot-device')
|
||||
|
||||
def test_get_sensors_data(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertRaises(NotImplementedError,
|
||||
task.driver.management.get_sensors_data,
|
||||
task)
|
@ -33,15 +33,11 @@ from ironic.drivers.modules import ipmitool
|
||||
from ironic.drivers.modules.irmc import management as irmc_management
|
||||
from ironic.drivers.modules.irmc import power as irmc_power
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
from ironic.drivers.modules.msftocs import management as msftocs_management
|
||||
from ironic.drivers.modules.msftocs import power as msftocs_power
|
||||
from ironic.drivers.modules import pxe as pxe_module
|
||||
from ironic.drivers.modules import seamicro
|
||||
from ironic.drivers.modules import snmp
|
||||
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 import pxe
|
||||
|
||||
|
||||
@ -83,28 +79,6 @@ class PXEDriversTestCase(testtools.TestCase):
|
||||
self.assertRaises(exception.DriverLoadError,
|
||||
pxe.PXEAndIPMINativeDriver)
|
||||
|
||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
||||
autospec=True)
|
||||
def test_pxe_seamicro_driver(self, try_import_mock):
|
||||
try_import_mock.return_value = True
|
||||
|
||||
driver = pxe.PXEAndSeaMicroDriver()
|
||||
|
||||
self.assertIsInstance(driver.power, seamicro.Power)
|
||||
self.assertIsInstance(driver.boot, pxe_module.PXEBoot)
|
||||
self.assertIsInstance(driver.deploy, iscsi_deploy.ISCSIDeploy)
|
||||
self.assertIsInstance(driver.management, seamicro.Management)
|
||||
self.assertIsInstance(driver.vendor, seamicro.VendorPassthru)
|
||||
self.assertIsInstance(driver.console, seamicro.ShellinaboxConsole)
|
||||
|
||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
||||
autospec=True)
|
||||
def test_pxe_seamicro_driver_import_error(self, try_import_mock):
|
||||
try_import_mock.return_value = False
|
||||
|
||||
self.assertRaises(exception.DriverLoadError,
|
||||
pxe.PXEAndSeaMicroDriver)
|
||||
|
||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
||||
autospec=True)
|
||||
def test_pxe_ilo_driver(self, try_import_mock):
|
||||
@ -173,41 +147,6 @@ class PXEDriversTestCase(testtools.TestCase):
|
||||
self.assertRaises(exception.DriverLoadError,
|
||||
pxe.PXEAndIRMCDriver)
|
||||
|
||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
||||
autospec=True)
|
||||
def test_pxe_vbox_driver(self, try_import_mock):
|
||||
try_import_mock.return_value = True
|
||||
|
||||
driver = pxe.PXEAndVirtualBoxDriver()
|
||||
|
||||
self.assertIsInstance(driver.power, virtualbox.VirtualBoxPower)
|
||||
self.assertIsInstance(driver.boot, pxe_module.PXEBoot)
|
||||
self.assertIsInstance(driver.deploy, iscsi_deploy.ISCSIDeploy)
|
||||
self.assertIsInstance(driver.management,
|
||||
virtualbox.VirtualBoxManagement)
|
||||
self.assertIsInstance(driver.raid, agent.AgentRAID)
|
||||
|
||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
||||
autospec=True)
|
||||
def test_pxe_vbox_driver_import_error(self, try_import_mock):
|
||||
try_import_mock.return_value = False
|
||||
|
||||
self.assertRaises(exception.DriverLoadError,
|
||||
pxe.PXEAndVirtualBoxDriver)
|
||||
|
||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
||||
autospec=True)
|
||||
def test_pxe_msftocs_driver(self, try_import_mock):
|
||||
try_import_mock.return_value = True
|
||||
|
||||
driver = pxe.PXEAndMSFTOCSDriver()
|
||||
|
||||
self.assertIsInstance(driver.power, msftocs_power.MSFTOCSPower)
|
||||
self.assertIsInstance(driver.boot, pxe_module.PXEBoot)
|
||||
self.assertIsInstance(driver.deploy, iscsi_deploy.ISCSIDeploy)
|
||||
self.assertIsInstance(driver.management,
|
||||
msftocs_management.MSFTOCSManagement)
|
||||
|
||||
@mock.patch.object(pxe.importutils, 'try_import', spec_set=True,
|
||||
autospec=True)
|
||||
def test_pxe_ucs_driver(self, try_import_mock):
|
||||
|
@ -73,19 +73,6 @@ PYGHMI_IPMICMD_SPEC = (
|
||||
'Command',
|
||||
)
|
||||
|
||||
# pyremotevbox
|
||||
PYREMOTEVBOX_SPEC = (
|
||||
'exception',
|
||||
'vbox',
|
||||
)
|
||||
PYREMOTEVBOX_EXC_SPEC = (
|
||||
'PyRemoteVBoxException',
|
||||
'VmInWrongPowerState',
|
||||
)
|
||||
PYREMOTEVBOX_VBOX_SPEC = (
|
||||
'VirtualBoxHost',
|
||||
)
|
||||
|
||||
# pywsman
|
||||
PYWSMAN_SPEC = (
|
||||
'Client',
|
||||
@ -150,17 +137,3 @@ ONEVIEWCLIENT_STATES_SPEC = (
|
||||
'ONEVIEW_RESETTING',
|
||||
'ONEVIEW_ERROR',
|
||||
)
|
||||
|
||||
# seamicro
|
||||
SEAMICRO_SPEC = (
|
||||
'client',
|
||||
'exceptions',
|
||||
)
|
||||
# seamicro.client module
|
||||
SEAMICRO_CLIENT_MOD_SPEC = (
|
||||
'Client',
|
||||
)
|
||||
SEAMICRO_EXC_SPEC = (
|
||||
'ClientException',
|
||||
'UnsupportedVersion',
|
||||
)
|
||||
|
@ -22,7 +22,6 @@ respective external libraries' actually being present.
|
||||
Any external library required by a third-party driver should be mocked here.
|
||||
Current list of mocked libraries:
|
||||
|
||||
- seamicroclient
|
||||
- ipminative
|
||||
- proliantutils
|
||||
- pysnmp
|
||||
@ -43,24 +42,6 @@ from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
||||
as mock_specs
|
||||
|
||||
|
||||
# attempt to load the external 'seamicroclient' library, which is
|
||||
# required by the optional drivers.modules.seamicro module
|
||||
seamicroclient = importutils.try_import("seamicroclient")
|
||||
if not seamicroclient:
|
||||
smc = mock.MagicMock(spec_set=mock_specs.SEAMICRO_SPEC)
|
||||
smc.client = mock.MagicMock(spec_set=mock_specs.SEAMICRO_CLIENT_MOD_SPEC)
|
||||
smc.exceptions = mock.MagicMock(spec_set=mock_specs.SEAMICRO_EXC_SPEC)
|
||||
smc.exceptions.ClientException = Exception
|
||||
smc.exceptions.UnsupportedVersion = Exception
|
||||
sys.modules['seamicroclient'] = smc
|
||||
sys.modules['seamicroclient.client'] = smc.client
|
||||
sys.modules['seamicroclient.exceptions'] = smc.exceptions
|
||||
|
||||
# if anything has loaded the seamicro driver yet, reload it now that
|
||||
# the external library has been mocked
|
||||
if 'ironic.drivers.modules.seamicro' in sys.modules:
|
||||
six.moves.reload_module(sys.modules['ironic.drivers.modules.seamicro'])
|
||||
|
||||
# IPMITool driver checks the system for presence of 'ipmitool' binary during
|
||||
# __init__. We bypass that check in order to run the unit tests, which do not
|
||||
# depend on 'ipmitool' being on the system.
|
||||
@ -217,21 +198,6 @@ irmc_boot.check_share_fs_mounted_patcher = mock.patch(
|
||||
irmc_boot.check_share_fs_mounted_patcher.return_value = None
|
||||
|
||||
|
||||
pyremotevbox = importutils.try_import('pyremotevbox')
|
||||
if not pyremotevbox:
|
||||
pyremotevbox = mock.MagicMock(spec_set=mock_specs.PYREMOTEVBOX_SPEC)
|
||||
pyremotevbox.exception = mock.MagicMock(
|
||||
spec_set=mock_specs.PYREMOTEVBOX_EXC_SPEC)
|
||||
pyremotevbox.exception.PyRemoteVBoxException = Exception
|
||||
pyremotevbox.exception.VmInWrongPowerState = Exception
|
||||
pyremotevbox.vbox = mock.MagicMock(
|
||||
spec_set=mock_specs.PYREMOTEVBOX_VBOX_SPEC)
|
||||
sys.modules['pyremotevbox'] = pyremotevbox
|
||||
if 'ironic.drivers.modules.virtualbox' in sys.modules:
|
||||
six.moves.reload_module(
|
||||
sys.modules['ironic.drivers.modules.virtualbox'])
|
||||
|
||||
|
||||
ironic_inspector_client = importutils.try_import('ironic_inspector_client')
|
||||
if not ironic_inspector_client:
|
||||
ironic_inspector_client = mock.MagicMock(
|
||||
|
@ -0,0 +1,32 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
A number of drivers that were declared as unsupported in Newton release
|
||||
have been removed from ironic tree. This includes drivers with
|
||||
power and/or management driver interfaces based on:
|
||||
|
||||
- MSFT OCS
|
||||
- SeaMicro client
|
||||
- Virtualbox over pyremotevbox client
|
||||
|
||||
As a result, the following ironic drivers will no longer be available:
|
||||
|
||||
- agent_vbox
|
||||
- fake_msftocs
|
||||
- fake_seamicro
|
||||
- fake_vbox
|
||||
- pxe_msftocs
|
||||
- pxe_seamicro
|
||||
- pxe_vbox
|
||||
|
||||
After upgrading, if one or more of these drivers are in the
|
||||
'enabled_drivers' configuration option,
|
||||
the ironic-conductor service will fail to start.
|
||||
Any existing ironic nodes with these drivers assigned will become
|
||||
inoperational via ironic after ironic upgrade,
|
||||
as it will be not possible to change any node state/properties
|
||||
except changing the node driver.
|
||||
Operators having one of the drivers listed above enabled are required to
|
||||
either disable those drivers and assign another existing driver
|
||||
to affected nodes as appropriate,
|
||||
or install these drivers from elsewhere separately.
|
@ -49,7 +49,6 @@ ironic.drivers =
|
||||
agent_pxe_oneview = ironic.drivers.oneview:AgentPXEOneViewDriver
|
||||
agent_pyghmi = ironic.drivers.agent:AgentAndIPMINativeDriver
|
||||
agent_ssh = ironic.drivers.agent:AgentAndSSHDriver
|
||||
agent_vbox = ironic.drivers.agent:AgentAndVirtualBoxDriver
|
||||
agent_ucs = ironic.drivers.agent:AgentAndUcsDriver
|
||||
fake = ironic.drivers.fake:FakeDriver
|
||||
fake_soft_power = ironic.drivers.fake:FakeSoftPowerDriver
|
||||
@ -60,13 +59,10 @@ ironic.drivers =
|
||||
fake_ipminative = ironic.drivers.fake:FakeIPMINativeDriver
|
||||
fake_ssh = ironic.drivers.fake:FakeSSHDriver
|
||||
fake_pxe = ironic.drivers.fake:FakePXEDriver
|
||||
fake_seamicro = ironic.drivers.fake:FakeSeaMicroDriver
|
||||
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_msftocs = ironic.drivers.fake:FakeMSFTOCSDriver
|
||||
fake_ucs = ironic.drivers.fake:FakeUcsDriver
|
||||
fake_cimc = ironic.drivers.fake:FakeCIMCDriver
|
||||
fake_oneview = ironic.drivers.fake:FakeOneViewDriver
|
||||
@ -77,14 +73,11 @@ ironic.drivers =
|
||||
pxe_ipmitool_socat = ironic.drivers.ipmi:PXEAndIPMIToolAndSocatDriver
|
||||
pxe_ipminative = ironic.drivers.pxe:PXEAndIPMINativeDriver
|
||||
pxe_ssh = ironic.drivers.pxe:PXEAndSSHDriver
|
||||
pxe_vbox = ironic.drivers.pxe:PXEAndVirtualBoxDriver
|
||||
pxe_seamicro = ironic.drivers.pxe:PXEAndSeaMicroDriver
|
||||
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_msftocs = ironic.drivers.pxe:PXEAndMSFTOCSDriver
|
||||
pxe_ucs = ironic.drivers.pxe:PXEAndUcsDriver
|
||||
pxe_iscsi_cimc = ironic.drivers.pxe:PXEAndCIMCDriver
|
||||
pxe_agent_cimc = ironic.drivers.agent:AgentAndCIMCDriver
|
||||
|
Loading…
Reference in New Issue
Block a user