Introduce libvirt power/mgmt driver
Libvirt has its own API. It allows to connect to different hypervisors like xen, vmware, virtualbox, qemu, full list can be found at https://libvirt.org/drivers.html. It supports different type of transports like ssh, tcp, unix sockets. This patch introduces new type of power and management drivers, which use libvirt-python library to connect to hypervisor. Change-Id: I2df214aab95c2f5d2505f5ad4ef9f3a542e44c6a Depends-On: I12211db38a3fdb3b2d733e5769f2c052c32c4a75 Closes-Bug: #1523880
This commit is contained in:
parent
5215fb6bb9
commit
296a45c8af
6
driver-requirements.txt
Normal file
6
driver-requirements.txt
Normal file
@ -0,0 +1,6 @@
|
||||
# This file lists all python libraries which are utilized by drivers,
|
||||
# and may not be listed in global-requirements.
|
||||
|
||||
|
||||
# libvirt driver requires libvirt-python library which is available on pypi
|
||||
libvirt-python>=1.2.5 # LGPLv2+
|
@ -29,3 +29,7 @@ class AMTConnectFailure(exception.IronicException):
|
||||
|
||||
class AMTFailure(exception.IronicException):
|
||||
_msg_fmt = _("AMT call failed: %(cmd)s.")
|
||||
|
||||
|
||||
class LibvirtError(exception.IronicException):
|
||||
message = _("Libvirt call failed: %(err)s.")
|
||||
|
68
ironic_staging_drivers/libvirt/__init__.py
Normal file
68
ironic_staging_drivers/libvirt/__init__.py
Normal file
@ -0,0 +1,68 @@
|
||||
# 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.drivers import base
|
||||
from ironic.drivers.modules import agent
|
||||
from ironic.drivers.modules import fake
|
||||
from ironic.drivers.modules import iscsi_deploy
|
||||
from ironic.drivers.modules import pxe
|
||||
from ironic_staging_drivers.libvirt import power
|
||||
|
||||
|
||||
class FakeLibvirtFakeDriver(base.BaseDriver):
|
||||
"""Example implementation of a Driver."""
|
||||
|
||||
def __init__(self):
|
||||
self.power = power.LibvirtPower()
|
||||
self.deploy = fake.FakeDeploy()
|
||||
self.management = power.LibvirtManagement()
|
||||
|
||||
|
||||
class PXELibvirtAgentDriver(base.BaseDriver):
|
||||
"""PXE + Agent + Libvirt driver.
|
||||
|
||||
NOTE: This driver is meant only for testing environments.
|
||||
|
||||
This driver implements the `core` functionality, combining
|
||||
:class:`ironic.drivers.modules.power.LibvirtPower` (for power on/off and
|
||||
reboot of virtual machines tunneled over Libvirt API), with
|
||||
:class:`ironic.drivers.modules.agent.AgentDeploy` (for image
|
||||
deployment). Implementations are in those respective classes; this class
|
||||
is merely the glue between them.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.power = power.LibvirtPower()
|
||||
self.boot = pxe.PXEBoot()
|
||||
self.deploy = agent.AgentDeploy()
|
||||
self.management = power.LibvirtManagement()
|
||||
self.vendor = agent.AgentVendorInterface()
|
||||
self.raid = agent.AgentRAID()
|
||||
|
||||
|
||||
class PXELibvirtISCSIDriver(base.BaseDriver):
|
||||
"""PXE + Libvirt + iSCSI driver.
|
||||
|
||||
This driver implements the `core` functionality, combining
|
||||
:class:`ironic.drivers.modules.pxe.PXEBoot` for boot and
|
||||
:class:`ironic_staging_drivers.libvirt.LibvirtPower` for power on/off and
|
||||
:class:`ironic.drivers.modules.iscsi_deploy.ISCSIDeploy` for image
|
||||
deployment. Implementations are in those respective classes; this
|
||||
class is merely the glue between them.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.power = power.LibvirtPower()
|
||||
self.boot = pxe.PXEBoot()
|
||||
self.deploy = iscsi_deploy.ISCSIDeploy()
|
||||
self.management = power.LibvirtManagement()
|
||||
self.vendor = iscsi_deploy.VendorPassthru()
|
519
ironic_staging_drivers/libvirt/power.py
Normal file
519
ironic_staging_drivers/libvirt/power.py
Normal file
@ -0,0 +1,519 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Ironic Libvirt power manager and management interface.
|
||||
|
||||
Provides basic power control and management of virtual machines
|
||||
via Libvirt API.
|
||||
|
||||
For use in dev and test environments.
|
||||
|
||||
Currently supported environments are:
|
||||
Virtual Box
|
||||
Virsh
|
||||
VMware WS/ESX/Player
|
||||
XenServer
|
||||
OpenVZ
|
||||
Microsoft Hyper-V
|
||||
Virtuozzo
|
||||
|
||||
Currently supported transports are:
|
||||
unix (open auth)
|
||||
tcp (SASL auth)
|
||||
tls (SASL auth)
|
||||
ssh (SSH Key auth)
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import libvirt
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception as ir_exc
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import states
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers import utils as driver_utils
|
||||
from ironic_staging_drivers.common import exception as isd_exc
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_URI = 'qemu+unix:///system'
|
||||
REQUIRED_PROPERTIES = {}
|
||||
OTHER_PROPERTIES = {
|
||||
'libvirt_uri': _("libvirt URI, default is qemu+unix:///system. Optional."),
|
||||
'sasl_username': _("username to authenticate as. Optional."),
|
||||
'sasl_password': _("password to use for SASL authentication. Optional."),
|
||||
'ssh_key_filename': _("filename of private key "
|
||||
"for authentication. Optional.")
|
||||
}
|
||||
|
||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
||||
COMMON_PROPERTIES.update(OTHER_PROPERTIES)
|
||||
|
||||
|
||||
_BOOT_DEVICES_MAP = {
|
||||
boot_devices.DISK: 'hd',
|
||||
boot_devices.PXE: 'network',
|
||||
boot_devices.CDROM: 'cdrom',
|
||||
}
|
||||
|
||||
|
||||
def _get_libvirt_connection(driver_info):
|
||||
"""Get the libvirt connection.
|
||||
|
||||
:param driver_info: driver info
|
||||
:returns: the active libvirt connection
|
||||
:raises: LibvirtError if failed to connect to the Libvirt uri.
|
||||
"""
|
||||
|
||||
uri = driver_info.get('libvirt_uri') or DEFAULT_URI
|
||||
sasl_username = driver_info.get('sasl_username')
|
||||
sasl_password = driver_info.get('sasl_password')
|
||||
ssh_key_filename = driver_info.get('ssh_key_filename')
|
||||
|
||||
try:
|
||||
if sasl_username and sasl_password:
|
||||
def request_cred(credentials, user_data):
|
||||
for credential in credentials:
|
||||
if credential[0] == libvirt.VIR_CRED_AUTHNAME:
|
||||
credential[4] = sasl_username
|
||||
elif credential[0] == libvirt.VIR_CRED_PASSPHRASE:
|
||||
credential[4] = sasl_password
|
||||
return 0
|
||||
auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_PASSPHRASE],
|
||||
request_cred, None]
|
||||
conn = libvirt.openAuth(uri, auth, 0)
|
||||
elif ssh_key_filename:
|
||||
uri += "?keyfile=%s&no_verify=1" % ssh_key_filename
|
||||
conn = libvirt.open(uri)
|
||||
else:
|
||||
conn = libvirt.open(uri)
|
||||
except libvirt.libvirtError as e:
|
||||
raise isd_exc.LibvirtError(err=e)
|
||||
|
||||
if conn is None:
|
||||
raise isd_exc.LibvirtError(
|
||||
err=_("Failed to open connection to %s") % uri)
|
||||
return conn
|
||||
|
||||
|
||||
def _get_domain_by_macs(task):
|
||||
"""Get the domain the host uses to reference the node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on
|
||||
:returns: the libvirt domain object.
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
:raises: InvalidParameterValue if any connection parameters are
|
||||
incorrect or if failed to connect to the Libvirt uri.
|
||||
:raises: LibvirtError if failed to connect to the Libvirt uri.
|
||||
"""
|
||||
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
conn = _get_libvirt_connection(driver_info)
|
||||
macs = driver_utils.get_node_mac_addresses(task)
|
||||
node_macs = {driver_utils.normalize_mac(mac)
|
||||
for mac in macs}
|
||||
|
||||
full_node_list = conn.listAllDomains()
|
||||
|
||||
for domain in full_node_list:
|
||||
LOG.debug("Checking Domain: %s's Mac address", domain.name())
|
||||
parsed = ET.fromstring(domain.XMLDesc())
|
||||
domain_macs = {driver_utils.normalize_mac(
|
||||
el.attrib['address']) for el in parsed.iter('mac')}
|
||||
|
||||
found_macs = domain_macs & node_macs # this is intersection of sets
|
||||
if found_macs:
|
||||
LOG.debug("Found MAC addresses: %s "
|
||||
"for node: %s", found_macs, driver_info['uuid'])
|
||||
return domain
|
||||
|
||||
raise ir_exc.NodeNotFound(
|
||||
_("Can't find domain with specified MACs: %(macs)s "
|
||||
"for node %(node)s") %
|
||||
{'macs': domain_macs, 'node': driver_info['uuid']})
|
||||
|
||||
|
||||
def _parse_driver_info(node):
|
||||
"""Gets the information needed for accessing the node.
|
||||
|
||||
:param node: the Node of interest.
|
||||
:returns: dictionary of information.
|
||||
:raises: MissingParameterValue if any required parameters are missing.
|
||||
:raises: InvalidParameterValue if any required parameters are incorrect.
|
||||
"""
|
||||
|
||||
info = node.driver_info or {}
|
||||
missing_info = [key for key in REQUIRED_PROPERTIES if not info.get(key)]
|
||||
if missing_info:
|
||||
raise ir_exc.MissingParameterValue(_(
|
||||
"LibvirtPowerDriver requires the following parameters to be set in"
|
||||
"node's driver_info: %s.") % missing_info)
|
||||
|
||||
uri = info.get('libvirt_uri') or DEFAULT_URI
|
||||
sasl_username = info.get('sasl_username')
|
||||
sasl_password = info.get('sasl_password')
|
||||
ssh_key_filename = info.get('ssh_key_filename')
|
||||
|
||||
if sasl_username and sasl_password and ssh_key_filename:
|
||||
raise ir_exc.InvalidParameterValue(_(
|
||||
"LibvirtPower requires one and only one of the authentication, "
|
||||
"(sasl_username, sasl_password) or ssh_key_filename to be set."))
|
||||
|
||||
if ssh_key_filename and not os.path.isfile(ssh_key_filename):
|
||||
raise ir_exc.InvalidParameterValue(_(
|
||||
"SSH key file %s not found.") % ssh_key_filename)
|
||||
|
||||
res = {
|
||||
'libvirt_uri': uri,
|
||||
'uuid': node.uuid,
|
||||
'sasl_username': sasl_username,
|
||||
'sasl_password': sasl_password,
|
||||
'ssh_key_filename': ssh_key_filename,
|
||||
}
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _power_on(domain):
|
||||
"""Power ON this domain.
|
||||
|
||||
:param domain: libvirt domain object.
|
||||
:returns: one of ironic.common.states POWER_ON or ERROR.
|
||||
:raises: LibvirtError if failed to connect to start domain.
|
||||
"""
|
||||
|
||||
current_pstate = _get_power_state(domain)
|
||||
if current_pstate == states.POWER_ON:
|
||||
return current_pstate
|
||||
|
||||
try:
|
||||
domain.create()
|
||||
except libvirt.libvirtError as e:
|
||||
raise isd_exc.LibvirtError(err=e)
|
||||
|
||||
current_pstate = _get_power_state(domain)
|
||||
if current_pstate == states.POWER_ON:
|
||||
return current_pstate
|
||||
else:
|
||||
return states.ERROR
|
||||
|
||||
|
||||
def _power_off(domain):
|
||||
"""Power OFF this domain.
|
||||
|
||||
:param domain: libvirt domain object.
|
||||
:returns: one of ironic.common.states POWER_OFF or ERROR.
|
||||
:raises: LibvirtError if failed to destroy domain.
|
||||
"""
|
||||
|
||||
current_pstate = _get_power_state(domain)
|
||||
if current_pstate == states.POWER_OFF:
|
||||
return current_pstate
|
||||
|
||||
try:
|
||||
domain.destroy()
|
||||
except libvirt.libvirtError as e:
|
||||
raise isd_exc.LibvirtError(err=e)
|
||||
|
||||
current_pstate = _get_power_state(domain)
|
||||
if current_pstate == states.POWER_OFF:
|
||||
return current_pstate
|
||||
else:
|
||||
return states.ERROR
|
||||
|
||||
|
||||
def _power_cycle(domain):
|
||||
"""Power cycles a node.
|
||||
|
||||
:param domain: libvirt domain object.
|
||||
:raises: PowerStateFailure if it failed to set power state to POWER_ON.
|
||||
:raises: LibvirtError if failed to power cycle domain.
|
||||
"""
|
||||
|
||||
try:
|
||||
_power_off(domain)
|
||||
state = _power_on(domain)
|
||||
except libvirt.libvirtError as e:
|
||||
raise isd_exc.LibvirtError(err=e)
|
||||
|
||||
if state != states.POWER_ON:
|
||||
raise ir_exc.PowerStateFailure(pstate=states.POWER_ON)
|
||||
|
||||
|
||||
def _get_power_state(domain):
|
||||
"""Get the current power state of domain.
|
||||
|
||||
:param domain: libvirt domain object.
|
||||
:returns: power state. One of :class:`ironic.common.states`.
|
||||
:raises: LibvirtErr if failed to get doamin status.
|
||||
"""
|
||||
|
||||
try:
|
||||
if domain.isActive():
|
||||
return states.POWER_ON
|
||||
except libvirt.libvirtError as e:
|
||||
raise isd_exc.LibvirtError(err=e)
|
||||
|
||||
return states.POWER_OFF
|
||||
|
||||
|
||||
def _get_boot_device(domain):
|
||||
"""Get the current boot device.
|
||||
|
||||
:param domain: libvirt domain object.
|
||||
:returns: boot device.
|
||||
"""
|
||||
|
||||
boot_element = ET.fromstring(domain.XMLDesc()).find('.//os/boot')
|
||||
boot_dev = None
|
||||
if boot_element is not None:
|
||||
boot_dev = boot_element.attrib.get('dev')
|
||||
|
||||
return boot_dev
|
||||
|
||||
|
||||
def _set_boot_device(conn, domain, device):
|
||||
"""Set the boot device.
|
||||
|
||||
:param conn: active libvirt connection.
|
||||
:param domain: libvirt domain object.
|
||||
:raises: LibvirtError if failed update domain xml.
|
||||
"""
|
||||
|
||||
parsed = ET.fromstring(domain.XMLDesc())
|
||||
os = parsed.find('os')
|
||||
boot_list = os.findall('boot')
|
||||
|
||||
# Clear boot list
|
||||
for boot_el in boot_list:
|
||||
os.remove(boot_el)
|
||||
|
||||
boot_el = ET.SubElement(os, 'boot')
|
||||
boot_el.set('dev', device)
|
||||
|
||||
try:
|
||||
conn.defineXML(ET.tostring(parsed))
|
||||
except libvirt.libvirtError as e:
|
||||
raise isd_exc.LibvirtError(err=e)
|
||||
|
||||
|
||||
class LibvirtPower(base.PowerInterface):
|
||||
"""Libvirt Power Interface.
|
||||
|
||||
This PowerInterface class provides a mechanism for controlling the power
|
||||
state of virtual machines via libvirt.
|
||||
|
||||
"""
|
||||
|
||||
def get_properties(self):
|
||||
return COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task):
|
||||
"""Check that the node's 'driver_info' is valid.
|
||||
|
||||
Check that the node's 'driver_info' contains the requisite fields
|
||||
and that an Libvirt connection to the node can be established.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: InvalidParameterValue if any connection parameters are
|
||||
incorrect or if failed to connect to the libvirt socket.
|
||||
:raises: MissingParameterValue if no ports are enrolled for the given
|
||||
node.
|
||||
"""
|
||||
|
||||
if not driver_utils.get_node_mac_addresses(task):
|
||||
raise ir_exc.MissingParameterValue(
|
||||
_("Node %s does not have any ports associated with it"
|
||||
) % task.node.uuid)
|
||||
|
||||
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 task's node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:returns: power state. One of :class:`ironic.common.states`.
|
||||
:raises: InvalidParameterValue if any connection parameters are
|
||||
incorrect.
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
:raises: LibvirtError if failed to connect to the Libvirt uri.
|
||||
"""
|
||||
|
||||
domain = _get_domain_by_macs(task)
|
||||
return _get_power_state(domain)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_power_state(self, task, pstate):
|
||||
"""Turn the power on or off.
|
||||
|
||||
Set the power state of the task's 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 any connection parameters are
|
||||
incorrect, or if the desired power state is invalid.
|
||||
:raises: MissingParameterValue when a required parameter is missing
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
:raises: PowerStateFailure if it failed to set power state to pstate.
|
||||
:raises: LibvirtError if failed to connect to the Libvirt uri.
|
||||
"""
|
||||
|
||||
domain = _get_domain_by_macs(task)
|
||||
if pstate == states.POWER_ON:
|
||||
state = _power_on(domain)
|
||||
elif pstate == states.POWER_OFF:
|
||||
state = _power_off(domain)
|
||||
else:
|
||||
raise ir_exc.InvalidParameterValue(
|
||||
_("set_power_state called with invalid power state %s."
|
||||
) % pstate)
|
||||
|
||||
if state != pstate:
|
||||
raise ir_exc.PowerStateFailure(pstate=pstate)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def reboot(self, task):
|
||||
"""Cycles the power to the task's node.
|
||||
|
||||
Power cycles a node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: InvalidParameterValue if any connection parameters are
|
||||
incorrect.
|
||||
:raises: MissingParameterValue when a required parameter is missing
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
:raises: PowerStateFailure if it failed to set power state to POWER_ON.
|
||||
:raises: LibvirtError if failed to connect to the Libvirt uri.
|
||||
"""
|
||||
|
||||
domain = _get_domain_by_macs(task)
|
||||
|
||||
_power_cycle(domain)
|
||||
|
||||
state = _get_power_state(domain)
|
||||
|
||||
if state != states.POWER_ON:
|
||||
raise ir_exc.PowerStateFailure(pstate=states.POWER_ON)
|
||||
|
||||
|
||||
class LibvirtManagement(base.ManagementInterface):
|
||||
|
||||
def get_properties(self):
|
||||
return COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task):
|
||||
"""Check that 'driver_info' contains Libvirt URI.
|
||||
|
||||
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 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 any connection parameters are incorrect.
|
||||
:raises: MissingParameterValue if a required parameter is missing
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
:raises: LibvirtError if failed to connect to the Libvirt uri.
|
||||
"""
|
||||
|
||||
domain = _get_domain_by_macs(task)
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
conn = _get_libvirt_connection(driver_info)
|
||||
if device not in self.get_supported_boot_devices(task):
|
||||
raise ir_exc.InvalidParameterValue(_(
|
||||
"Invalid boot device %s specified.") % device)
|
||||
|
||||
boot_device_map = _BOOT_DEVICES_MAP
|
||||
_set_boot_device(conn, domain, boot_device_map[device])
|
||||
|
||||
def get_boot_device(self, task):
|
||||
"""Get the current boot device for the task's node.
|
||||
|
||||
Provides the current boot device of the node. Be aware that not
|
||||
all drivers support this.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:raises: InvalidParameterValue if any connection parameters are
|
||||
incorrect.
|
||||
:raises: MissingParameterValue if a required parameter is missing
|
||||
:raises: NodeNotFound if could not find a VM corresponding to any
|
||||
of the provided MACs.
|
||||
: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.
|
||||
:raises: LibvirtError if failed to connect to the Libvirt uri.
|
||||
"""
|
||||
|
||||
domain = _get_domain_by_macs(task)
|
||||
|
||||
response = {'boot_device': None, 'persistent': None}
|
||||
response['boot_device'] = _get_boot_device(domain)
|
||||
return response
|
||||
|
||||
def get_sensors_data(self, task):
|
||||
"""Get sensors data.
|
||||
|
||||
Not implemented by this driver.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
651
ironic_staging_drivers/tests/unit/libvirt/test_power.py
Normal file
651
ironic_staging_drivers/tests/unit/libvirt/test_power.py
Normal file
@ -0,0 +1,651 @@
|
||||
# Copyright (c) 2016 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Test class for Ironic libvirt driver."""
|
||||
|
||||
|
||||
import tempfile
|
||||
|
||||
import mock
|
||||
|
||||
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 import utils as driver_utils
|
||||
from ironic_staging_drivers.common import exception as isd_exc
|
||||
from ironic_staging_drivers.libvirt import power
|
||||
|
||||
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
|
||||
|
||||
|
||||
def _get_test_libvirt_driver_info(auth_type='ssh_key'):
|
||||
if auth_type == 'ssh_key':
|
||||
return {
|
||||
'libvirt_uri': 'qemu+ssh://test@test/',
|
||||
'ssh_key_filename': '/test/key/file'
|
||||
}
|
||||
elif auth_type == 'sasl':
|
||||
return {
|
||||
'libvirt_uri': 'test+tcp://localhost:5000/test',
|
||||
'sasl_username': 'admin',
|
||||
'sasl_password': 'admin'
|
||||
}
|
||||
elif auth_type == 'no_uri':
|
||||
return {'ssh_key_filename': '/test/key/file'}
|
||||
elif auth_type == 'socket':
|
||||
return {'libvirt_uri': 'qemu+unix:///system?'
|
||||
'socket=/opt/libvirt/run/libvirt-sock'}
|
||||
|
||||
return{
|
||||
'libvirt_uri': 'qemu+ssh://test@test/',
|
||||
'ssh_key_filename': '/test/key/file',
|
||||
'sasl_username': 'admin',
|
||||
'sasl_password': 'admin'
|
||||
}
|
||||
|
||||
|
||||
class FakeLibvirtDomain(object):
|
||||
def __init__(self, uuid=None):
|
||||
self.uuid = uuid
|
||||
|
||||
def name(self):
|
||||
return 'test_libvirt_domain'
|
||||
|
||||
def XMLDesc(self, boot_dev=power._BOOT_DEVICES_MAP[boot_devices.PXE]):
|
||||
return(
|
||||
"""<domain type='qemu' id='4'>
|
||||
<name>test_libvirt_domain</name>
|
||||
<uuid>1be26c0b-03f2-4d2e-ae87-c02d7f33c123</uuid>
|
||||
<bootloader>/usr/bin/pygrub</bootloader>
|
||||
<os>
|
||||
<type arch='x86_64' machine='pc-1.0'>hvm</type>
|
||||
<boot dev='%(boot_dev)s'/>
|
||||
<bios useserial='yes'/>
|
||||
</os>
|
||||
<memory>512000</memory>
|
||||
<vcpu>1</vcpu>
|
||||
<on_poweroff>destroy</on_poweroff>
|
||||
<on_reboot>restart</on_reboot>
|
||||
<on_crash>restart</on_crash>
|
||||
<devices>
|
||||
<interface type='bridge'>
|
||||
<source bridge='br0'/>
|
||||
<mac address='00:16:3e:49:1d:11'/>
|
||||
<script path='vif-bridge'/>
|
||||
</interface>
|
||||
<graphics type='vnc' port='5900'/>
|
||||
<console tty='/dev/pts/4'/>
|
||||
<interface type='network'>
|
||||
<mac address='52:54:00:5c:b7:df'/>
|
||||
<source network='brbm'/>
|
||||
<virtualport type='openvswitch'>
|
||||
<parameters interfaceid='5c20239f'/>
|
||||
</virtualport>
|
||||
<model type='e1000'/>
|
||||
</interface>
|
||||
</devices>
|
||||
</domain>""") % {'boot_dev': boot_dev}
|
||||
|
||||
|
||||
class FakeConnection(object):
|
||||
def listAllDomains(self):
|
||||
return [FakeLibvirtDomain()]
|
||||
|
||||
|
||||
class LibvirtValidateParametersTestCase(db_base.DbTestCase):
|
||||
|
||||
def test__parse_driver_info_good_ssh_key(self):
|
||||
d_info = _get_test_libvirt_driver_info('ssh_key')
|
||||
key_path = tempfile.mkdtemp() + '/test.key'
|
||||
with open(key_path, 'wt'):
|
||||
d_info['ssh_key_filename'] = key_path
|
||||
node = obj_utils.get_test_node(
|
||||
self.context,
|
||||
driver='fake_libvirt_fake',
|
||||
driver_info=d_info)
|
||||
|
||||
info = power._parse_driver_info(node)
|
||||
|
||||
self.assertEqual('qemu+ssh://test@test/', info.get('libvirt_uri'))
|
||||
self.assertEqual(key_path, info.get('ssh_key_filename'))
|
||||
self.assertEqual(node['uuid'], info.get('uuid'))
|
||||
|
||||
def test__parse_driver_info_no_ssh_key(self):
|
||||
node = obj_utils.get_test_node(
|
||||
self.context,
|
||||
driver='fake_libvirt_fake',
|
||||
driver_info=_get_test_libvirt_driver_info('ssh_key'))
|
||||
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
power._parse_driver_info,
|
||||
node)
|
||||
|
||||
def test__parse_driver_info_good_sasl_cred(self):
|
||||
node = obj_utils.get_test_node(
|
||||
self.context,
|
||||
driver='fake_libvirt_fake',
|
||||
driver_info=_get_test_libvirt_driver_info('sasl'))
|
||||
|
||||
info = power._parse_driver_info(node)
|
||||
|
||||
self.assertEqual('test+tcp://localhost:5000/test',
|
||||
info.get('libvirt_uri'))
|
||||
self.assertEqual('admin', info.get('sasl_username'))
|
||||
self.assertEqual('admin', info.get('sasl_password'))
|
||||
self.assertEqual(node['uuid'], info.get('uuid'))
|
||||
|
||||
def test__parse_driver_info_sasl_and_ssh_key(self):
|
||||
node = obj_utils.get_test_node(
|
||||
self.context,
|
||||
driver='fake_libvirt_fake',
|
||||
driver_info=_get_test_libvirt_driver_info('ssh_sasl'))
|
||||
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
power._parse_driver_info,
|
||||
node)
|
||||
|
||||
|
||||
class LibvirtPrivateMethodsTestCase(db_base.DbTestCase):
|
||||
|
||||
@mock.patch.object(power.libvirt, 'openAuth', autospec=True)
|
||||
def test__get_libvirt_connection_sasl_auth(self, libvirt_open_mock):
|
||||
node = obj_utils.get_test_node(
|
||||
self.context,
|
||||
driver='fake_libvirt_fake',
|
||||
driver_info=_get_test_libvirt_driver_info('sasl'))
|
||||
power._get_libvirt_connection(node['driver_info'])
|
||||
|
||||
libvirt_open_mock.assert_called_once_with(
|
||||
'test+tcp://localhost:5000/test',
|
||||
[[power.libvirt.VIR_CRED_AUTHNAME,
|
||||
power.libvirt.VIR_CRED_PASSPHRASE],
|
||||
mock.ANY, # Inline cred function
|
||||
None], 0)
|
||||
|
||||
@mock.patch.object(power.libvirt, 'open', autospec=True)
|
||||
def test__get_libvirt_connection_ssh(self, libvirt_open_mock):
|
||||
node = obj_utils.get_test_node(
|
||||
self.context,
|
||||
driver='fake_libvirt_fake',
|
||||
driver_info=_get_test_libvirt_driver_info('ssh_key'))
|
||||
power._get_libvirt_connection(node['driver_info'])
|
||||
|
||||
libvirt_open_mock.assert_called_once_with(
|
||||
'qemu+ssh://test@test/?keyfile=/test/key/file&no_verify=1')
|
||||
|
||||
@mock.patch.object(power.libvirt, 'open', autospec=True)
|
||||
def test__get_libvirt_connection_socket(self, libvirt_open_mock):
|
||||
node = obj_utils.get_test_node(
|
||||
self.context,
|
||||
driver='fake_libvirt_fake',
|
||||
driver_info=_get_test_libvirt_driver_info('socket'))
|
||||
power._get_libvirt_connection(node['driver_info'])
|
||||
|
||||
libvirt_open_mock.assert_called_once_with(
|
||||
'qemu+unix:///system?socket=/opt/libvirt/run/libvirt-sock')
|
||||
|
||||
@mock.patch.object(power.libvirt, 'open',
|
||||
side_effect=power.libvirt.libvirtError('Error'))
|
||||
def test__get_libvirt_connection_error_conn(self, libvirt_open_mock):
|
||||
node = obj_utils.get_test_node(
|
||||
self.context,
|
||||
driver='fake_libvirt_fake',
|
||||
driver_info=_get_test_libvirt_driver_info('socket'))
|
||||
self.assertRaises(isd_exc.LibvirtError,
|
||||
power._get_libvirt_connection,
|
||||
node['driver_info'])
|
||||
|
||||
@mock.patch.object(power.libvirt, 'open',
|
||||
return_value=None)
|
||||
def test__get_libvirt_connection_error_none_conn(self, libvirt_open_mock):
|
||||
node = obj_utils.get_test_node(
|
||||
self.context,
|
||||
driver='fake_libvirt_fake',
|
||||
driver_info=_get_test_libvirt_driver_info('socket'))
|
||||
self.assertRaises(isd_exc.LibvirtError,
|
||||
power._get_libvirt_connection,
|
||||
node['driver_info'])
|
||||
|
||||
@mock.patch.object(power, '_get_libvirt_connection',
|
||||
return_value=FakeConnection())
|
||||
def test__get_domain_by_macs(self, libvirt_conn_mock):
|
||||
mgr_utils.mock_the_extension_manager(driver="fake_libvirt_fake")
|
||||
driver_factory.get_driver("fake_libvirt_fake")
|
||||
node = obj_utils.create_test_node(
|
||||
self.context,
|
||||
driver='fake_libvirt_fake',
|
||||
driver_info=_get_test_libvirt_driver_info('socket'))
|
||||
obj_utils.create_test_port(self.context,
|
||||
node_id=node.id,
|
||||
address='00:16:3e:49:1d:11')
|
||||
|
||||
with task_manager.acquire(self.context, node.uuid,
|
||||
shared=True) as task:
|
||||
domain = power._get_domain_by_macs(task)
|
||||
|
||||
self.assertEqual('test_libvirt_domain', domain.name())
|
||||
|
||||
@mock.patch.object(power, '_get_libvirt_connection',
|
||||
return_value=FakeConnection())
|
||||
def test__get_domain_by_macs_not_found(self, libvirt_conn_mock):
|
||||
mgr_utils.mock_the_extension_manager(driver="fake_libvirt_fake")
|
||||
driver_factory.get_driver("fake_libvirt_fake")
|
||||
node = obj_utils.create_test_node(
|
||||
self.context,
|
||||
driver='fake_libvirt_fake',
|
||||
driver_info=_get_test_libvirt_driver_info('socket'))
|
||||
obj_utils.create_test_port(self.context,
|
||||
node_id=node.id,
|
||||
address='00:17:3a:50:12:12')
|
||||
|
||||
with task_manager.acquire(self.context, node.uuid,
|
||||
shared=True) as task:
|
||||
|
||||
self.assertRaises(exception.NodeNotFound,
|
||||
power._get_domain_by_macs, task)
|
||||
|
||||
def test__get_power_state_on(self):
|
||||
domain_mock = mock.Mock()
|
||||
domain_mock.isActive = mock.MagicMock(return_value=True)
|
||||
|
||||
state = power._get_power_state(domain_mock)
|
||||
|
||||
domain_mock.isActive.assert_called_once_with()
|
||||
self.assertEqual(states.POWER_ON, state)
|
||||
|
||||
def test__get_power_state_off(self):
|
||||
domain_mock = mock.Mock()
|
||||
domain_mock.isActive = mock.Mock(return_value=False)
|
||||
|
||||
state = power._get_power_state(domain_mock)
|
||||
|
||||
domain_mock.isActive.assert_called_once_with()
|
||||
self.assertEqual(states.POWER_OFF, state)
|
||||
|
||||
def test__get_power_state_error(self):
|
||||
domain_mock = mock.Mock()
|
||||
domain_mock.isActive = mock.MagicMock(
|
||||
side_effect=power.libvirt.libvirtError('Test'))
|
||||
|
||||
self.assertRaises(isd_exc.LibvirtError,
|
||||
power._get_power_state,
|
||||
domain_mock)
|
||||
|
||||
@mock.patch.object(power, '_power_off', autospec=True)
|
||||
@mock.patch.object(power, '_power_on', return_value=states.POWER_ON)
|
||||
def test__power_cycle(self, power_on_mock, power_off_mock):
|
||||
power._power_cycle('fake domain')
|
||||
|
||||
power_on_mock.assert_called_once_with('fake domain')
|
||||
power_off_mock.assert_called_once_with('fake domain')
|
||||
|
||||
@mock.patch.object(power, '_power_off', autospec=True)
|
||||
@mock.patch.object(power, '_power_on', return_value=states.POWER_OFF)
|
||||
def test__power_cycle_failure(self, power_on_mock, power_off_mock):
|
||||
self.assertRaises(exception.PowerStateFailure,
|
||||
power._power_cycle,
|
||||
'fake domain')
|
||||
power_off_mock.assert_called_once_with('fake domain')
|
||||
|
||||
@mock.patch.object(power, '_power_off', autospec=True)
|
||||
@mock.patch.object(power, '_power_on',
|
||||
side_effect=power.libvirt.libvirtError('Test'))
|
||||
def test__power_cycle_error_conn(self, power_on_mock, power_off_mock):
|
||||
self.assertRaises(isd_exc.LibvirtError,
|
||||
power._power_cycle,
|
||||
'fake domain')
|
||||
power_off_mock.assert_called_once_with('fake domain')
|
||||
|
||||
@mock.patch.object(power, '_get_power_state',
|
||||
return_value=states.POWER_ON)
|
||||
def test__power_on_on(self, get_power_mock):
|
||||
state = power._power_on('fake domain')
|
||||
|
||||
get_power_mock.assert_called_once_with('fake domain')
|
||||
self.assertEqual(states.POWER_ON, state)
|
||||
|
||||
@mock.patch.object(power, '_get_power_state',
|
||||
side_effect=[states.POWER_OFF, states.POWER_ON])
|
||||
def test__power_on_off(self, get_power_mock):
|
||||
domain_mock = mock.Mock()
|
||||
domain_mock.create = mock.Mock()
|
||||
|
||||
state = power._power_on(domain_mock)
|
||||
|
||||
get_power_mock.assert_called_with(domain_mock)
|
||||
domain_mock.create.assert_called_once_with()
|
||||
self.assertEqual(states.POWER_ON, state)
|
||||
|
||||
@mock.patch.object(power, '_get_power_state',
|
||||
side_effect=[states.POWER_OFF, states.POWER_OFF])
|
||||
def test__power_on_error_state(self, get_power_mock):
|
||||
domain_mock = mock.Mock()
|
||||
domain_mock.create = mock.Mock()
|
||||
|
||||
state = power._power_on(domain_mock)
|
||||
|
||||
get_power_mock.assert_called_with(domain_mock)
|
||||
domain_mock.create.assert_called_once_with()
|
||||
self.assertEqual(states.ERROR, state)
|
||||
|
||||
@mock.patch.object(power, '_get_power_state',
|
||||
return_value=states.POWER_OFF)
|
||||
def test__power_on_error(self, get_power_mock):
|
||||
domain_mock = mock.Mock()
|
||||
domain_mock.create = mock.Mock(
|
||||
side_effect=power.libvirt.libvirtError('Test'))
|
||||
|
||||
self.assertRaises(isd_exc.LibvirtError,
|
||||
power._power_on,
|
||||
domain_mock)
|
||||
get_power_mock.assert_called_with(domain_mock)
|
||||
|
||||
@mock.patch.object(power, '_get_power_state',
|
||||
return_value=states.POWER_OFF)
|
||||
def test__power_off_off(self, get_power_mock):
|
||||
state = power._power_off('fake domain')
|
||||
|
||||
get_power_mock.assert_called_once_with('fake domain')
|
||||
self.assertEqual(states.POWER_OFF, state)
|
||||
|
||||
@mock.patch.object(power, '_get_power_state',
|
||||
side_effect=[states.POWER_ON, states.POWER_OFF])
|
||||
def test__power_off_on(self, get_power_mock):
|
||||
domain_mock = mock.Mock()
|
||||
domain_mock.destroy = mock.Mock()
|
||||
|
||||
state = power._power_off(domain_mock)
|
||||
|
||||
get_power_mock.assert_called_with(domain_mock)
|
||||
domain_mock.destroy.assert_called_once_with()
|
||||
self.assertEqual(states.POWER_OFF, state)
|
||||
|
||||
@mock.patch.object(power, '_get_power_state',
|
||||
side_effect=[states.POWER_ON, states.POWER_ON])
|
||||
def test__power_off_error_state(self, get_power_mock):
|
||||
domain_mock = mock.Mock()
|
||||
domain_mock.destroy = mock.Mock()
|
||||
|
||||
state = power._power_off(domain_mock)
|
||||
|
||||
get_power_mock.assert_called_with(domain_mock)
|
||||
domain_mock.destroy.assert_called_once_with()
|
||||
self.assertEqual(states.ERROR, state)
|
||||
|
||||
@mock.patch.object(power, '_get_power_state',
|
||||
return_value=states.POWER_ON)
|
||||
def test__power_off_error(self, get_power_mock):
|
||||
domain_mock = mock.Mock()
|
||||
domain_mock.destroy = mock.Mock(
|
||||
side_effect=power.libvirt.libvirtError('Test'))
|
||||
|
||||
self.assertRaises(isd_exc.LibvirtError,
|
||||
power._power_off,
|
||||
domain_mock)
|
||||
get_power_mock.assert_called_with(domain_mock)
|
||||
|
||||
def test__get_boot_device(self):
|
||||
domain = FakeLibvirtDomain()
|
||||
|
||||
boot_dev = power._get_boot_device(domain)
|
||||
|
||||
self.assertEqual(power._BOOT_DEVICES_MAP[boot_devices.PXE],
|
||||
boot_dev)
|
||||
|
||||
def test__set_boot_device(self):
|
||||
conn = mock.Mock(defineXML=mock.Mock())
|
||||
domain = FakeLibvirtDomain()
|
||||
|
||||
power._set_boot_device(
|
||||
conn, domain, power._BOOT_DEVICES_MAP[boot_devices.DISK])
|
||||
|
||||
conn.defineXML.assert_called_once_with(mock.ANY)
|
||||
|
||||
def test__set_boot_device_error(self):
|
||||
conn = mock.Mock(defineXML=mock.Mock(
|
||||
side_effect=power.libvirt.libvirtError('Test')))
|
||||
domain = FakeLibvirtDomain()
|
||||
|
||||
self.assertRaises(isd_exc.LibvirtError,
|
||||
power._set_boot_device,
|
||||
conn, domain,
|
||||
power._BOOT_DEVICES_MAP[boot_devices.DISK])
|
||||
|
||||
|
||||
class LibvirtPowerTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(LibvirtPowerTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver="fake_libvirt_fake")
|
||||
driver_factory.get_driver("fake_libvirt_fake")
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context,
|
||||
driver='fake_libvirt_fake',
|
||||
driver_info=_get_test_libvirt_driver_info('sasl'))
|
||||
obj_utils.create_test_port(self.context,
|
||||
node_id=self.node.id,
|
||||
address='52:54:00:5c:b7:df')
|
||||
|
||||
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('libvirt_uri', properties)
|
||||
self.assertIn('sasl_username', properties)
|
||||
self.assertIn('sasl_password', properties)
|
||||
self.assertIn('ssh_key_filename', properties)
|
||||
|
||||
@mock.patch.object(driver_utils, 'get_node_mac_addresses', autospec=True)
|
||||
def test_validate(self, get_node_macs_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.power.validate(task)
|
||||
|
||||
get_node_macs_mock.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(power.driver_utils,
|
||||
'get_node_mac_addresses', autospec=True)
|
||||
def test_validate_conn_miss_mac(self, get_node_mac_mock):
|
||||
get_node_mac_mock.return_value = None
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
task.driver.power.validate,
|
||||
task)
|
||||
get_node_mac_mock.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(power, '_get_power_state', autospec=True)
|
||||
@mock.patch.object(power, '_get_domain_by_macs', autospec=True)
|
||||
def test_get_power_state(self, get_domain_mock, get_power_state):
|
||||
domain = FakeLibvirtDomain()
|
||||
get_domain_mock.return_value = domain
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.power.get_power_state(task)
|
||||
|
||||
get_domain_mock.assert_called_once_with(task)
|
||||
get_power_state.assert_called_once_with(domain)
|
||||
|
||||
@mock.patch.object(power, '_power_on', autospec=True)
|
||||
@mock.patch.object(power, '_get_domain_by_macs', autospec=True)
|
||||
def test_set_power_state_on(self, get_domain_mock, power_on_mock):
|
||||
domain = FakeLibvirtDomain()
|
||||
get_domain_mock.return_value = domain
|
||||
power_on_mock.return_value = states.POWER_ON
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.power.set_power_state(task, states.POWER_ON)
|
||||
|
||||
get_domain_mock.assert_called_once_with(task)
|
||||
power_on_mock.assert_called_once_with(domain)
|
||||
|
||||
@mock.patch.object(power, '_power_off', autospec=True)
|
||||
@mock.patch.object(power, '_get_domain_by_macs', autospec=True)
|
||||
def test_set_power_state_off(self, get_domain_mock, power_off_mock):
|
||||
domain = FakeLibvirtDomain()
|
||||
get_domain_mock.return_value = domain
|
||||
power_off_mock.return_value = states.POWER_OFF
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.power.set_power_state(task, states.POWER_OFF)
|
||||
|
||||
get_domain_mock.assert_called_once_with(task)
|
||||
power_off_mock.assert_called_once_with(domain)
|
||||
|
||||
@mock.patch.object(power, '_power_on', autospec=True)
|
||||
@mock.patch.object(power, '_get_domain_by_macs', autospec=True)
|
||||
def test_set_power_state_on_failure(self, get_domain_mock,
|
||||
power_on_mock):
|
||||
domain = FakeLibvirtDomain()
|
||||
get_domain_mock.return_value = domain
|
||||
power_on_mock.return_value = states.POWER_OFF
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.PowerStateFailure,
|
||||
task.driver.power.set_power_state,
|
||||
task, states.POWER_ON)
|
||||
|
||||
get_domain_mock.assert_called_once_with(task)
|
||||
power_on_mock.assert_called_once_with(domain)
|
||||
|
||||
@mock.patch.object(power, '_get_domain_by_macs', autospec=True)
|
||||
def test_set_power_state_invalid_state(self, get_domain_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.power.set_power_state,
|
||||
task, 'wrong_state')
|
||||
|
||||
get_domain_mock.assert_called_once_with(task)
|
||||
|
||||
|
||||
class LibvirtManagementTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(LibvirtManagementTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver="fake_libvirt_fake")
|
||||
driver_factory.get_driver("fake_libvirt_fake")
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context,
|
||||
driver='fake_libvirt_fake',
|
||||
driver_info=_get_test_libvirt_driver_info('sasl'))
|
||||
obj_utils.create_test_port(self.context,
|
||||
node_id=self.node.id,
|
||||
address='52:54:00:5c:b7:df')
|
||||
|
||||
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('libvirt_uri', properties)
|
||||
self.assertIn('sasl_username', properties)
|
||||
self.assertIn('sasl_password', properties)
|
||||
self.assertIn('ssh_key_filename', properties)
|
||||
|
||||
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(power, '_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)
|
||||
|
||||
@mock.patch.object(power, '_get_domain_by_macs',
|
||||
return_value=FakeLibvirtDomain())
|
||||
def test_get_boot_device_ok(self, get_domain_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
result = task.driver.management.get_boot_device(task)
|
||||
|
||||
get_domain_mock.assert_called_once_with(task)
|
||||
|
||||
self.assertEqual(power._BOOT_DEVICES_MAP[boot_devices.PXE],
|
||||
result['boot_device'])
|
||||
self.assertIsNone(result['persistent'])
|
||||
|
||||
@mock.patch.object(power, '_get_boot_device', return_value=None)
|
||||
@mock.patch.object(power, '_get_domain_by_macs', autospec=True)
|
||||
def test_get_boot_device_invalid(self, get_domain_mock, get_boot_dev_mock):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
result = task.driver.management.get_boot_device(task)
|
||||
|
||||
self.assertIsNone(result['boot_device'])
|
||||
self.assertIsNone(result['persistent'])
|
||||
|
||||
@mock.patch.object(power, '_set_boot_device', autospec=True)
|
||||
@mock.patch.object(power, '_get_libvirt_connection', autospec=True)
|
||||
@mock.patch.object(power, '_get_domain_by_macs', autospec=True)
|
||||
def test_set_boot_device_ok(self, get_domain_mock, get_conn_mock,
|
||||
set_boot_dev_mock):
|
||||
fake_domain = FakeLibvirtDomain()
|
||||
get_domain_mock.return_value = fake_domain
|
||||
get_conn_mock.return_value = 'fake conn'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.management.set_boot_device(task, boot_devices.PXE)
|
||||
|
||||
get_domain_mock.assert_called_once_with(task)
|
||||
get_conn_mock.assert_called_once_with(
|
||||
{'uuid': self.node.uuid,
|
||||
'libvirt_uri': 'test+tcp://localhost:5000/test',
|
||||
'sasl_password': 'admin',
|
||||
'sasl_username': 'admin',
|
||||
'ssh_key_filename': None})
|
||||
set_boot_dev_mock.assert_called_once_with(
|
||||
'fake conn', fake_domain,
|
||||
power._BOOT_DEVICES_MAP[boot_devices.PXE])
|
||||
|
||||
@mock.patch.object(power, '_get_libvirt_connection', autospec=True)
|
||||
@mock.patch.object(power, '_get_domain_by_macs', autospec=True)
|
||||
def test_set_boot_device_wrong(self, get_domain_mock, get_conn_mock):
|
||||
fake_domain = FakeLibvirtDomain()
|
||||
get_domain_mock.return_value = fake_domain
|
||||
get_conn_mock.return_value = 'fake conn'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.management.set_boot_device,
|
||||
task, boot_devices.BIOS)
|
||||
|
||||
get_domain_mock.assert_called_once_with(task)
|
||||
get_conn_mock.assert_called_once_with(
|
||||
{'uuid': self.node.uuid,
|
||||
'libvirt_uri': 'test+tcp://localhost:5000/test',
|
||||
'sasl_password': 'admin',
|
||||
'sasl_username': 'admin',
|
||||
'ssh_key_filename': None})
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- Addition of the Libvirt power management driver.
|
||||
This driver uses python-libvirt library to connect
|
||||
to hypervisor. It is fast enough and may be used
|
||||
for scale testing.
|
||||
|
@ -32,6 +32,9 @@ ironic.drivers =
|
||||
fake_amt_fake = ironic_staging_drivers.amt.drivers:FakeAMTFakeDriver
|
||||
pxe_amt_iscsi = ironic_staging_drivers.amt.drivers:PXEAndAMTISCSIDriver
|
||||
pxe_amt_agent = ironic_staging_drivers.amt.drivers:PXEAndAMTAgentDriver
|
||||
pxe_libvirt_agent = ironic_staging_drivers.libvirt:PXELibvirtAgentDriver
|
||||
pxe_libvirt_iscsi = ironic_staging_drivers.libvirt:PXELibvirtISCSIDriver
|
||||
fake_libvirt_fake = ironic_staging_drivers.libvirt:FakeLibvirtFakeDriver
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
|
@ -16,3 +16,6 @@ testtools>=1.4.0 # MIT
|
||||
os-testr>=0.4.1 # Apache-2.0
|
||||
reno>=0.1.1 # Apache2
|
||||
mock>=1.2 # BSD
|
||||
|
||||
# libvirt driver requires libvirt-python
|
||||
libvirt-python>=1.2.5 # LGPLv2+
|
||||
|
Loading…
Reference in New Issue
Block a user