Allow usage of multiple power drivers at once

* Deprecated power_management parameter.
* Added power_managements to configuration.
* Removed power_management variable from NodeCollection
  and Service.
* Added PowerManager class that controls running of
  power drivers.
* Updated docs.

Change-Id: Idabcb1fb907022f0d556667cbf3c36326f526a08
This commit is contained in:
Anton Studenov 2017-01-25 12:58:36 +03:00
parent 8bbbc75bdb
commit 3954065549
21 changed files with 416 additions and 304 deletions

@ -45,12 +45,26 @@ library:
'username': 'root',
}
},
'power_management': {
'driver': 'libvirt',
'args': {
'connection_uri': 'qemu+unix:///system',
'power_managements': [
{
'driver': 'libvirt',
'args': {
'connection_uri': 'qemu+unix:///system',
}
},
{
'driver': 'ipmi',
'args': {
'mac_to_bmc': {
'aa:bb:cc:dd:ee:01': {
'address': '55.55.55.55',
'username': 'foo',
'password': 'bar',
}
}
}
}
}
]
}
Establish a connection to the cloud and verify it:

@ -19,12 +19,14 @@ library:
'username': 'root',
}
},
'power_management': {
'driver': 'libvirt',
'args': {
'connection_uri': 'qemu+unix:///system',
'power_managements': [
{
'driver': 'libvirt',
'args': {
'connection_uri': 'qemu+unix:///system',
}
}
}
]
}
Establish a connection to the cloud and verify it:

@ -25,18 +25,20 @@ def main():
'username': 'root',
}
},
'power_management': {
'driver': 'ipmi',
'args': {
'mac_to_bmc': {
'00:00:00:00:00:00': {
'address': '55.55.55.55',
'username': 'foo',
'password': 'bar',
'power_managements': [
{
'driver': 'ipmi',
'args': {
'mac_to_bmc': {
'00:00:00:00:00:00': {
'address': '55.55.55.55',
'username': 'foo',
'password': 'bar',
}
}
}
}
}
]
}
logging.info('Create connection to the cluster')

@ -25,12 +25,14 @@ def main():
'username': 'root',
}
},
'power_management': {
'driver': 'libvirt',
'args': {
'connection_uri': "qemu+ssh://ubuntu@host.local/system"
'power_managements': [
{
'driver': 'libvirt',
'args': {
'connection_uri': "qemu+ssh://ubuntu@host.local/system"
}
}
}
]
}
logging.info('Create connection to the cluster')

@ -26,12 +26,14 @@ def main():
'private_key_file': '~/.ssh/os_faults',
}
},
'power_management': {
'driver': 'libvirt',
'args': {
'connection_uri': 'qemu+ssh://ubuntu@host.local/system'
'power_managements': [
{
'driver': 'libvirt',
'args': {
'connection_uri': 'qemu+ssh://ubuntu@host.local/system'
}
}
}
]
}
logging.info('# Create connection to the cloud')

@ -80,7 +80,20 @@ CONFIG_SCHEMA = {
},
'required': ['driver', 'args'],
'additionalProperties': False,
}
},
'power_managements': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'driver': {'type': 'string'},
'args': {'type': 'object'},
},
'required': ['driver', 'args'],
'additionalProperties': False,
},
'minItems': 1,
},
},
'required': ['cloud_management'],
}
@ -126,10 +139,22 @@ def connect(cloud_config=None, config_filename=None):
node_discover = _init_driver(node_discover_conf)
cloud_management.set_node_discover(node_discover)
power_managements_conf = cloud_config.get('power_managements')
if power_managements_conf:
for pm_conf in power_managements_conf:
pm = _init_driver(pm_conf)
cloud_management.add_power_management(pm)
power_management_conf = cloud_config.get('power_management')
if power_management_conf:
if power_managements_conf:
raise error.OSFError('Please use only power_managements')
else:
LOG.warning('power_management is deprecated, use '
'power_managements instead.')
power_management = _init_driver(power_management_conf)
cloud_management.set_power_management(power_management)
cloud_management.add_power_management(power_management)
return cloud_management

@ -19,6 +19,7 @@ import six
from os_faults.api import base_driver
from os_faults.api import error
from os_faults.api import node_collection
from os_faults.api import power_management
LOG = logging.getLogger(__name__)
@ -31,11 +32,11 @@ class CloudManagement(base_driver.BaseDriver):
NODE_CLS = node_collection.NodeCollection
def __init__(self):
self.power_management = None
self.power_manager = power_management.PowerManager()
self.node_discover = None
def set_power_management(self, power_management):
self.power_management = power_management
def add_power_management(self, driver):
self.power_manager.add_driver(driver)
def set_node_discover(self, node_discover):
self.node_discover = node_discover
@ -60,9 +61,7 @@ class CloudManagement(base_driver.BaseDriver):
'node_discover is not specified and "{}" '
'driver does not support discovering'.format(self.NAME))
hosts = self.node_discover.discover_hosts()
nodes = self.NODE_CLS(cloud_management=self,
power_management=self.power_management,
hosts=hosts)
nodes = self.NODE_CLS(cloud_management=self, hosts=hosts)
if fqdns:
LOG.debug('Trying to find nodes with FQDNs: %s', fqdns)
@ -78,9 +77,7 @@ class CloudManagement(base_driver.BaseDriver):
"""
if name in self.SERVICE_NAME_TO_CLASS:
klazz = self.SERVICE_NAME_TO_CLASS[name]
return klazz(node_cls=self.NODE_CLS,
cloud_management=self,
power_management=self.power_management)
return klazz(node_cls=self.NODE_CLS, cloud_management=self)
raise error.ServiceError(
'{} driver does not support {!r} service'.format(
self.NAME.title(), name))

@ -26,10 +26,8 @@ Host = collections.namedtuple('Host', ['ip', 'mac', 'fqdn'])
class NodeCollection(object):
def __init__(self, cloud_management=None, power_management=None,
hosts=None):
def __init__(self, cloud_management=None, hosts=None):
self.cloud_management = cloud_management
self.power_management = power_management
self._hosts = set(hosts)
@property
@ -52,11 +50,6 @@ class NodeCollection(object):
'NodeCollections have different cloud_managements: '
'{} and {}'.format(self.cloud_management,
other.cloud_management))
if self.power_management is not other.power_management:
raise error.NodeCollectionError(
'NodeCollections have different power_managements: '
'{} and {}'.format(self.power_management,
other.power_management))
def __add__(self, other):
return self.__or__(other)
@ -86,7 +79,6 @@ class NodeCollection(object):
def _make_instance(self, hosts):
return self.__class__(cloud_management=self.cloud_management,
power_management=self.power_management,
hosts=hosts)
def get_ips(self):
@ -155,7 +147,7 @@ class NodeCollection(object):
"""
LOG.info('Power off nodes: %s', self)
self.power_management.poweroff(self.get_macs())
self.cloud_management.power_manager.poweroff(self.hosts)
@public
def poweron(self):
@ -163,7 +155,7 @@ class NodeCollection(object):
"""
LOG.info('Power on nodes: %s', self)
self.power_management.poweron(self.get_macs())
self.cloud_management.power_manager.poweron(self.hosts)
@public
def reset(self):
@ -171,21 +163,23 @@ class NodeCollection(object):
"""
LOG.info('Reset nodes: %s', self)
self.power_management.reset(self.get_macs())
self.cloud_management.power_manager.reset(self.hosts)
def snapshot(self, snapshot_name, suspend=True):
"""Create snapshot for all nodes
"""
LOG.info('Create snapshot "%s" for nodes: %s', snapshot_name, self)
self.power_management.snapshot(self.get_macs(), snapshot_name, suspend)
self.cloud_management.power_manager.snapshot(
self.hosts, snapshot_name, suspend)
def revert(self, snapshot_name, resume=True):
"""Revert snapshot for all nodes
"""
LOG.info('Revert snapshot "%s" for nodes: %s', snapshot_name, self)
self.power_management.revert(self.get_macs(), snapshot_name, resume)
self.cloud_management.power_manager.revert(
self.hosts, snapshot_name, resume)
@public
def disconnect(self, network_name):

@ -16,25 +16,82 @@ import abc
import six
from os_faults.api import base_driver
from os_faults.api import error
from os_faults import utils
@six.add_metaclass(abc.ABCMeta)
class PowerManagement(base_driver.BaseDriver):
class PowerDriver(base_driver.BaseDriver):
@abc.abstractmethod
def supports(host):
pass
@abc.abstractmethod
def poweroff(self, host):
pass
@abc.abstractmethod
def poweron(self, host):
pass
@abc.abstractmethod
def reset(self, host):
pass
def snapshot(self, host, snapshot_name, suspend=True):
raise NotImplementedError
def revert(self, host, snapshot_name, resume=True):
raise NotImplementedError
class PowerManager(object):
def __init__(self):
self.power_drivers = []
def add_driver(self, driver):
self.power_drivers.append(driver)
def _map_hosts_to_driver(self, hosts):
driver_host_pairs = []
for host in hosts:
for power_driver in self.power_drivers:
if power_driver.supports(host):
driver_host_pairs.append((power_driver, host))
break
else:
raise error.PowerManagementError(
"No supported driver found for host {}".format(host))
return driver_host_pairs
def _run_command(self, cmd, hosts, **kwargs):
driver_host_pairs = self._map_hosts_to_driver(hosts)
tw = utils.ThreadsWrapper()
for driver, host in driver_host_pairs:
kwargs['host'] = host
fn = getattr(driver, cmd)
tw.start_thread(fn, **kwargs)
tw.join_threads()
if tw.errors:
raise error.PowerManagementError(
'There are some errors when working the driver. '
'Please, check logs for more details.')
def poweroff(self, hosts):
pass
self._run_command('poweroff', hosts)
@abc.abstractmethod
def poweron(self, hosts):
pass
self._run_command('poweron', hosts)
@abc.abstractmethod
def reset(self, hosts):
pass
self._run_command('reset', hosts)
def snapshot(self, hosts, snapshot_name, suspend=True):
raise NotImplementedError
self._run_command('snapshot', hosts,
snapshot_name=snapshot_name, suspend=suspend)
def revert(self, hosts, snapshot_name, resume=True):
raise NotImplementedError
self._run_command('revert', hosts,
snapshot_name=snapshot_name, resume=resume)

@ -24,10 +24,9 @@ LOG = logging.getLogger(__name__)
class ServiceAsProcess(service.Service):
def __init__(self, node_cls, cloud_management=None, power_management=None):
def __init__(self, node_cls, cloud_management=None):
self.node_cls = node_cls
self.cloud_management = cloud_management
self.power_management = power_management
def _run_task(self, task, nodes):
ips = nodes.get_ips()
@ -55,7 +54,6 @@ class ServiceAsProcess(service.Service):
if r.status == executor.STATUS_OK]
hosts = [h for h in nodes.hosts if h.ip in success_ips]
return self.node_cls(cloud_management=self.cloud_management,
power_management=self.power_management,
hosts=hosts)
@utils.require_variables('RESTART_CMD', 'SERVICE_NAME')

@ -23,7 +23,7 @@ from os_faults import utils
LOG = logging.getLogger(__name__)
class IPMIDriver(power_management.PowerManagement):
class IPMIDriver(power_management.PowerDriver):
NAME = 'ipmi'
DESCRIPTION = 'IPMI power management driver'
CONFIG_SCHEMA = {
@ -51,6 +51,7 @@ class IPMIDriver(power_management.PowerManagement):
def __init__(self, params):
self.mac_to_bmc = params['mac_to_bmc']
# TODO(astudenov): make macs lowercased
def _find_bmc_by_mac_address(self, mac_address):
if mac_address not in self.mac_to_bmc:
@ -80,36 +81,26 @@ class IPMIDriver(power_management.PowerManagement):
mac_address))
raise error.PowerManagementError(msg)
def _poweroff(self, mac_address):
LOG.debug('Power off Node with MAC address: %s', mac_address)
self._run_set_power_cmd(
mac_address, cmd='off', expected_state='off')
LOG.info('Node powered off: %s', mac_address)
def supports(self, host):
try:
self._find_bmc_by_mac_address(host.mac)
except error.PowerManagementError:
return False
return True
def _poweron(self, mac_address):
LOG.debug('Power on Node with MAC address: %s', mac_address)
self._run_set_power_cmd(
mac_address, cmd='on', expected_state='on')
LOG.info('Node powered on: %s', mac_address)
def poweroff(self, host):
LOG.debug('Power off Node with MAC address: %s', host.mac)
self._run_set_power_cmd(host.mac, cmd='off', expected_state='off')
LOG.info('Node powered off: %s', host.mac)
def _reset(self, mac_address):
LOG.debug('Reset Node with MAC address: %s', mac_address)
def poweron(self, host):
LOG.debug('Power on Node with MAC address: %s', host.mac)
self._run_set_power_cmd(host.mac, cmd='on', expected_state='on')
LOG.info('Node powered on: %s', host.mac)
def reset(self, host):
LOG.debug('Reset Node with MAC address: %s', host.mac)
# boot -- If system is off, then 'on', else 'reset'
self._run_set_power_cmd(mac_address, cmd='boot')
self._run_set_power_cmd(host.mac, cmd='boot')
# NOTE(astudenov): This command does not wait for node to boot
LOG.info('Node reset: %s', mac_address)
def poweroff(self, mac_addresses_list):
kwargs_list = [dict(mac_address=mac_address)
for mac_address in mac_addresses_list]
utils.run(self._poweroff, kwargs_list)
def poweron(self, mac_addresses_list):
kwargs_list = [dict(mac_address=mac_address)
for mac_address in mac_addresses_list]
utils.run(self._poweron, kwargs_list)
def reset(self, mac_addresses_list):
kwargs_list = [dict(mac_address=mac_address)
for mac_address in mac_addresses_list]
utils.run(self._reset, kwargs_list)
LOG.info('Node reset: %s', host.mac)

@ -15,12 +15,11 @@ import logging
from os_faults.api import error
from os_faults.api import power_management
from os_faults import utils
LOG = logging.getLogger(__name__)
class LibvirtDriver(power_management.PowerManagement):
class LibvirtDriver(power_management.PowerDriver):
NAME = 'libvirt'
DESCRIPTION = 'Libvirt power management driver'
CONFIG_SCHEMA = {
@ -60,28 +59,35 @@ class LibvirtDriver(power_management.PowerManagement):
raise error.PowerManagementError(
'Domain with MAC address %s not found!' % mac_address)
def _poweroff(self, mac_address):
LOG.debug('Power off domain with MAC address: %s', mac_address)
domain = self._find_domain_by_mac_address(mac_address)
def supports(self, host):
try:
self._find_domain_by_mac_address(host.mac)
except error.PowerManagementError:
return False
return True
def poweroff(self, host):
LOG.debug('Power off domain with MAC address: %s', host.mac)
domain = self._find_domain_by_mac_address(host.mac)
domain.destroy()
LOG.info('Domain powered off: %s', mac_address)
LOG.info('Domain powered off: %s', host.mac)
def _poweron(self, mac_address):
LOG.debug('Power on domain with MAC address: %s', mac_address)
domain = self._find_domain_by_mac_address(mac_address)
def poweron(self, host):
LOG.debug('Power on domain with MAC address: %s', host.mac)
domain = self._find_domain_by_mac_address(host.mac)
domain.create()
LOG.info('Domain powered on: %s', mac_address)
LOG.info('Domain powered on: %s', host.mac)
def _reset(self, mac_address):
LOG.debug('Reset domain with MAC address: %s', mac_address)
domain = self._find_domain_by_mac_address(mac_address)
def reset(self, host):
LOG.debug('Reset domain with MAC address: %s', host.mac)
domain = self._find_domain_by_mac_address(host.mac)
domain.reset()
LOG.info('Domain reset: %s', mac_address)
LOG.info('Domain reset: %s', host.mac)
def _snapshot(self, mac_address, snapshot_name, suspend):
def snapshot(self, host, snapshot_name, suspend):
LOG.debug('Create snapshot "%s" for domain with MAC address: %s',
snapshot_name, mac_address)
domain = self._find_domain_by_mac_address(mac_address)
snapshot_name, host.mac)
domain = self._find_domain_by_mac_address(host.mac)
if suspend:
domain.suspend()
domain.snapshotCreateXML(
@ -90,12 +96,12 @@ class LibvirtDriver(power_management.PowerManagement):
if suspend:
domain.resume()
LOG.debug('Created snapshot "%s" for domain with MAC address: %s',
snapshot_name, mac_address)
snapshot_name, host.mac)
def _revert(self, mac_address, snapshot_name, resume):
def revert(self, host, snapshot_name, resume):
LOG.debug('Revert snapshot "%s" for domain with MAC address: %s',
snapshot_name, mac_address)
domain = self._find_domain_by_mac_address(mac_address)
snapshot_name, host.mac)
domain = self._find_domain_by_mac_address(host.mac)
snapshot = domain.snapshotLookupByName(snapshot_name)
if domain.isActive():
domain.destroy()
@ -103,33 +109,4 @@ class LibvirtDriver(power_management.PowerManagement):
if resume:
domain.resume()
LOG.debug('Reverted snapshot "%s" for domain with MAC address: %s',
snapshot_name, mac_address)
def poweroff(self, mac_addresses_list):
kwargs_list = [dict(mac_address=mac_address)
for mac_address in mac_addresses_list]
utils.run(self._poweroff, kwargs_list)
def poweron(self, mac_addresses_list):
kwargs_list = [dict(mac_address=mac_address)
for mac_address in mac_addresses_list]
utils.run(self._poweron, kwargs_list)
def reset(self, mac_addresses_list):
kwargs_list = [dict(mac_address=mac_address)
for mac_address in mac_addresses_list]
utils.run(self._reset, kwargs_list)
def snapshot(self, mac_addresses_list, snapshot_name, suspend=True):
kwargs_list = [dict(mac_address=mac_address,
snapshot_name=snapshot_name,
suspend=suspend)
for mac_address in mac_addresses_list]
utils.run(self._snapshot, kwargs_list)
def revert(self, mac_addresses_list, snapshot_name, resume=True):
kwargs_list = [dict(mac_address=mac_address,
snapshot_name=snapshot_name,
resume=resume)
for mac_address in mac_addresses_list]
utils.run(self._revert, kwargs_list)
snapshot_name, host.mac)

@ -33,8 +33,9 @@ class NodeCollectionTestCase(test.TestCase):
super(NodeCollectionTestCase, self).setUp()
self.mock_cloud_management = mock.Mock(
spec=cloud_management.CloudManagement)
self.mock_power_management = mock.Mock(
spec=power_management.PowerManagement)
self.mock_power_manager = mock.Mock(
spec=power_management.PowerManager)
self.mock_cloud_management.power_manager = self.mock_power_manager
self.hosts = [
node_collection.Host(ip='10.0.0.2', mac='09:7b:74:90:63:c1',
fqdn='node1.com'),
@ -48,7 +49,6 @@ class NodeCollectionTestCase(test.TestCase):
self.node_collection = node_collection.NodeCollection(
cloud_management=self.mock_cloud_management,
power_management=self.mock_power_management,
hosts=copy.deepcopy(self.hosts))
self.hosts2 = [
@ -64,26 +64,17 @@ class NodeCollectionTestCase(test.TestCase):
self.node_collection2 = node_collection.NodeCollection(
cloud_management=self.mock_cloud_management,
power_management=self.mock_power_management,
hosts=copy.deepcopy(self.hosts2))
def test_check_types_wrong_type(self):
collection = MyNodeCollection(None, None, [])
collection = MyNodeCollection(None, [])
self.assertRaises(TypeError, self.node_collection._check_nodes_types,
collection)
self.assertRaises(TypeError, collection._check_nodes_types,
self.node_collection)
def test_check_types_wrong_cloud_management(self):
collection = node_collection.NodeCollection(None, None, [])
self.assertRaises(error.NodeCollectionError,
self.node_collection._check_nodes_types, collection)
self.assertRaises(error.NodeCollectionError,
collection._check_nodes_types, self.node_collection)
def test_check_types_wrong_power_management(self):
collection = node_collection.NodeCollection(
self.mock_cloud_management, None, [])
collection = node_collection.NodeCollection(None, [])
self.assertRaises(error.NodeCollectionError,
self.node_collection._check_nodes_types, collection)
self.assertRaises(error.NodeCollectionError,
@ -187,21 +178,15 @@ class NodeCollectionTestCase(test.TestCase):
def test_poweroff(self):
self.node_collection.poweroff()
self.mock_power_management.poweroff.assert_called_once_with(
['09:7b:74:90:63:c1', '09:7b:74:90:63:c2',
'09:7b:74:90:63:c3', '09:7b:74:90:63:c4'])
self.mock_power_manager.poweroff.assert_called_once_with(self.hosts)
def test_poweron(self):
self.node_collection.poweron()
self.mock_power_management.poweron.assert_called_once_with(
['09:7b:74:90:63:c1', '09:7b:74:90:63:c2',
'09:7b:74:90:63:c3', '09:7b:74:90:63:c4'])
self.mock_power_manager.poweron.assert_called_once_with(self.hosts)
def test_reset(self):
self.node_collection.reset()
self.mock_power_management.reset.assert_called_once_with(
['09:7b:74:90:63:c1', '09:7b:74:90:63:c2',
'09:7b:74:90:63:c3', '09:7b:74:90:63:c4'])
self.mock_power_manager.reset.assert_called_once_with(self.hosts)
def test_reboot(self):
self.node_collection.reboot()
@ -211,12 +196,10 @@ class NodeCollectionTestCase(test.TestCase):
def test_snapshot(self):
self.node_collection.snapshot('foo')
self.mock_power_management.snapshot.assert_called_once_with(
['09:7b:74:90:63:c1', '09:7b:74:90:63:c2',
'09:7b:74:90:63:c3', '09:7b:74:90:63:c4'], 'foo', True)
self.mock_power_manager.snapshot.assert_called_once_with(
self.hosts, 'foo', True)
def test_revert(self):
self.node_collection.revert('foo')
self.mock_power_management.revert.assert_called_once_with(
['09:7b:74:90:63:c1', '09:7b:74:90:63:c2',
'09:7b:74:90:63:c3', '09:7b:74:90:63:c4'], 'foo', True)
self.mock_power_manager.revert.assert_called_once_with(
self.hosts, 'foo', True)

@ -0,0 +1,107 @@
# 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 mock
from os_faults.api import error
from os_faults.api import node_collection
from os_faults.api import power_management
from os_faults.tests.unit import test
class PowerManagerTestCase(test.TestCase):
def setUp(self):
super(PowerManagerTestCase, self).setUp()
self.dummy_driver1 = mock.Mock(spec=power_management.PowerDriver)
self.dummy_driver1.supports.side_effect = lambda host: 'c1' in host.mac
self.dummy_driver2 = mock.Mock(spec=power_management.PowerDriver)
self.dummy_driver2.supports.side_effect = lambda host: 'c2' in host.mac
self.dummy_drivers = [self.dummy_driver1, self.dummy_driver2]
self.hosts = [
node_collection.Host(ip='10.0.0.2', mac='09:7b:74:90:63:c1',
fqdn='node1.com'),
node_collection.Host(ip='10.0.0.3', mac='09:7b:74:90:63:c2',
fqdn='node2.com'),
]
self.pm = power_management.PowerManager()
self.pm.add_driver(self.dummy_driver1)
self.pm.add_driver(self.dummy_driver2)
def test_poweroff(self):
self.pm.poweroff(self.hosts)
self.dummy_driver1.poweroff.called_once_with(host=self.hosts[0])
self.dummy_driver2.poweroff.called_once_with(host=self.hosts[1])
def test_poweron(self):
self.pm.poweron(self.hosts)
self.dummy_driver1.poweron.called_once_with(host=self.hosts[0])
self.dummy_driver2.poweron.called_once_with(host=self.hosts[1])
def test_reset(self):
self.pm.reset(self.hosts)
self.dummy_driver1.reset.called_once_with(host=self.hosts[0])
self.dummy_driver2.reset.called_once_with(host=self.hosts[1])
def test_snapshot(self):
self.pm.snapshot(self.hosts, 'snap1', suspend=False)
self.dummy_driver1.snapshot.called_once_with(host=self.hosts[0],
snapshot_name='snap1',
suspend=False)
self.dummy_driver2.snapshot.called_once_with(host=self.hosts[1],
snapshot_name='snap1',
suspend=False)
def test_revert(self):
self.pm.revert(self.hosts, 'snap1', resume=False)
self.dummy_driver1.revert.called_once_with(host=self.hosts[0],
snapshot_name='snap1',
resume=False)
self.dummy_driver2.revert.called_once_with(host=self.hosts[1],
snapshot_name='snap1',
resume=False)
def test_run_error(self):
self.dummy_driver2.reset.side_effect = Exception()
exc = self.assertRaises(error.PowerManagementError,
self.pm.reset, self.hosts)
self.assertEqual("There are some errors when working the driver. "
"Please, check logs for more details.", str(exc))
def test_run_no_supported_driver(self):
self.dummy_driver2.supports.side_effect = None
self.dummy_driver2.supports.return_value = False
exc = self.assertRaises(error.PowerManagementError,
self.pm.reset, self.hosts)
self.assertEqual("No supported driver found for host "
"Host(ip='10.0.0.3', mac='09:7b:74:90:63:c2', "
"fqdn='node2.com')", str(exc))
def test_run_no_drivers(self):
self.pm = power_management.PowerManager()
exc = self.assertRaises(error.PowerManagementError,
self.pm.reset, self.hosts)
self.assertEqual("No supported driver found for host "
"Host(ip='10.0.0.2', mac='09:7b:74:90:63:c1', "
"fqdn='node1.com')", str(exc))

@ -17,7 +17,6 @@ import ddt
import mock
from os_faults.api import node_collection
from os_faults.api import power_management
from os_faults.drivers import devstack
from os_faults.tests.unit import fakes
from os_faults.tests.unit import test
@ -29,14 +28,11 @@ class DevStackNodeTestCase(test.TestCase):
super(DevStackNodeTestCase, self).setUp()
self.mock_cloud_management = mock.Mock(
spec=devstack.DevStackManagement)
self.mock_power_management = mock.Mock(
spec=power_management.PowerManagement)
self.host = node_collection.Host(
ip='10.0.0.2', mac='09:7b:74:90:63:c1', fqdn='')
self.node_collection = devstack.DevStackNode(
cloud_management=self.mock_cloud_management,
power_management=self.mock_power_management,
hosts=[copy.deepcopy(self.host)])
def test_connect(self):

@ -16,7 +16,6 @@ import copy
import mock
from os_faults.api import node_collection
from os_faults.api import power_management
from os_faults.drivers import fuel
from os_faults.tests.unit import test
@ -26,8 +25,6 @@ class FuelNodeCollectionTestCase(test.TestCase):
def setUp(self):
super(FuelNodeCollectionTestCase, self).setUp()
self.mock_cloud_management = mock.Mock(spec=fuel.FuelManagement)
self.mock_power_management = mock.Mock(
spec=power_management.PowerManagement)
self.hosts = [
node_collection.Host(ip='10.0.0.2', mac='09:7b:74:90:63:c1',
fqdn='node1.com'),
@ -41,7 +38,6 @@ class FuelNodeCollectionTestCase(test.TestCase):
self.node_collection = fuel.FuelNodeCollection(
cloud_management=self.mock_cloud_management,
power_management=self.mock_power_management,
hosts=copy.deepcopy(self.hosts))
def test_connect(self):

@ -15,6 +15,7 @@ import ddt
import mock
from pyghmi import exceptions as pyghmi_exc
from os_faults.api import node_collection
from os_faults.drivers import ipmi
from os_faults import error
from os_faults.tests.unit import test
@ -36,11 +37,21 @@ class IPMIDriverTestCase(test.TestCase):
}
}
self.driver = ipmi.IPMIDriver(self.params)
self.host = node_collection.Host(
ip='10.0.0.2', mac='00:00:00:00:00:00', fqdn='node1.com')
def test__find_bmc_by_mac_address(self):
bmc = self.driver._find_bmc_by_mac_address('00:00:00:00:00:00')
self.assertEqual(bmc, self.params['mac_to_bmc']['00:00:00:00:00:00'])
def test_supports(self):
self.assertTrue(self.driver.supports(self.host))
def test_supports_false(self):
host = node_collection.Host(
ip='10.0.0.2', mac='00:00:00:00:00:01', fqdn='node1.com')
self.assertFalse(self.driver.supports(host))
def test__find_bmc_by_mac_address_mac_address_not_found(self):
self.assertRaises(error.PowerManagementError,
self.driver._find_bmc_by_mac_address,
@ -74,22 +85,12 @@ class IPMIDriverTestCase(test.TestCase):
'00:00:00:00:00:00', 'off', expected_state='off')
@mock.patch('os_faults.drivers.ipmi.IPMIDriver._run_set_power_cmd')
@ddt.data(('_poweroff', 'off'), ('_poweron', 'on'), ('_reset', 'boot'))
def test__driver_actions(self, actions, mock__run_set_power_cmd):
getattr(self.driver, actions[0])('00:00:00:00:00:00')
if actions[0] in ('_poweroff', '_poweron'):
@ddt.data(('poweroff', 'off'), ('poweron', 'on'), ('reset', 'boot'))
def test_driver_actions(self, actions, mock__run_set_power_cmd):
getattr(self.driver, actions[0])(self.host)
if actions[0] in ('poweroff', 'poweron'):
mock__run_set_power_cmd.assert_called_once_with(
'00:00:00:00:00:00', cmd=actions[1], expected_state=actions[1])
else:
mock__run_set_power_cmd.assert_called_once_with(
'00:00:00:00:00:00', cmd=actions[1])
@mock.patch('os_faults.utils.run')
@ddt.data('poweroff', 'poweron', 'reset')
def test_driver_actions(self, action, mock_run):
macs_list = ['00:00:00:00:00:00', '00:00:00:00:00:01']
getattr(self.driver, action)(macs_list)
mock_run.assert_called_once_with(
getattr(self.driver, '_%s' % action),
[{'mac_address': '00:00:00:00:00:00'},
{'mac_address': '00:00:00:00:00:01'}])

@ -14,6 +14,7 @@
import ddt
import mock
from os_faults.api import node_collection
from os_faults.drivers import libvirt_driver
from os_faults import error
from os_faults.tests.unit import test
@ -30,6 +31,8 @@ class LibvirtDriverTestCase(test.TestCase):
self.params = {'connection_uri': 'fake_connection_uri'}
self.driver = libvirt_driver.LibvirtDriver(self.params)
self.host = node_collection.Host(
ip='10.0.0.2', mac='00:00:00:00:00:00', fqdn='node1.com')
@mock.patch('libvirt.open')
def test__get_connection_no_cached_connection(self, mock_libvirt_open):
@ -69,24 +72,40 @@ class LibvirtDriverTestCase(test.TestCase):
self.driver._find_domain_by_mac_address,
'00:00:00:00:00:01')
@mock.patch(DRIVER_PATH + '.LibvirtDriver._get_connection')
def test_supports(self, mock__get_connection):
domain1 = mock.MagicMock()
domain1.XMLDesc.return_value = '52:54:00:ab:64:42'
domain2 = mock.MagicMock()
domain2.XMLDesc.return_value = '00:00:00:00:00:00'
self.driver.conn.listAllDomains.return_value = [domain1, domain2]
self.assertTrue(self.driver.supports(self.host))
@mock.patch(DRIVER_PATH + '.LibvirtDriver._get_connection')
def test_supports_false(self, mock__get_connection):
self.driver.conn.listAllDomains.return_value = []
self.assertFalse(self.driver.supports(self.host))
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
@ddt.data(('_poweroff', 'destroy'), ('_poweron', 'create'),
('_reset', 'reset'))
def test__driver_actions(self, actions, mock__find_domain_by_mac_address):
getattr(self.driver, actions[0])('52:54:00:f9:b8:f9')
@ddt.data(('poweroff', 'destroy'), ('poweron', 'create'),
('reset', 'reset'))
def test_driver_actions(self, actions, mock__find_domain_by_mac_address):
getattr(self.driver, actions[0])(self.host)
domain = mock__find_domain_by_mac_address.return_value
getattr(domain, actions[1]).assert_called_once_with()
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
def test__snapshot(self, mock__find_domain_by_mac_address):
self.driver._snapshot('52:54:00:f9:b8:f9', 'foo', suspend=False)
def test_snapshot(self, mock__find_domain_by_mac_address):
self.driver.snapshot(self.host, 'foo', suspend=False)
domain = mock__find_domain_by_mac_address.return_value
domain.snapshotCreateXML.assert_called_once_with(
'<domainsnapshot><name>foo</name></domainsnapshot>')
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
def test__snapshot_suspend(self, mock__find_domain_by_mac_address):
self.driver._snapshot('52:54:00:f9:b8:f9', 'foo', suspend=True)
def test_snapshot_suspend(self, mock__find_domain_by_mac_address):
self.driver.snapshot(self.host, 'foo', suspend=True)
domain = mock__find_domain_by_mac_address.return_value
domain.assert_has_calls((
mock.call.suspend(),
@ -96,8 +115,8 @@ class LibvirtDriverTestCase(test.TestCase):
))
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
def test__revert(self, mock__find_domain_by_mac_address):
self.driver._revert('52:54:00:f9:b8:f9', 'foo', resume=False)
def test_revert(self, mock__find_domain_by_mac_address):
self.driver.revert(self.host, 'foo', resume=False)
domain = mock__find_domain_by_mac_address.return_value
snapshot = domain.snapshotLookupByName.return_value
domain.snapshotLookupByName.assert_called_once_with('foo')
@ -105,8 +124,8 @@ class LibvirtDriverTestCase(test.TestCase):
self.assertFalse(domain.resume.called)
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
def test__revert_resume(self, mock__find_domain_by_mac_address):
self.driver._revert('52:54:00:f9:b8:f9', 'foo', resume=True)
def test_revert_resume(self, mock__find_domain_by_mac_address):
self.driver.revert(self.host, 'foo', resume=True)
domain = mock__find_domain_by_mac_address.return_value
snapshot = domain.snapshotLookupByName.return_value
domain.snapshotLookupByName.assert_called_once_with('foo')
@ -114,51 +133,15 @@ class LibvirtDriverTestCase(test.TestCase):
domain.resume.assert_called_once_with()
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
def test__revert_destroy(self, mock__find_domain_by_mac_address):
def test_revert_destroy(self, mock__find_domain_by_mac_address):
domain = mock__find_domain_by_mac_address.return_value
domain.isActive.return_value = True
self.driver._revert('52:54:00:f9:b8:f9', 'foo', resume=True)
self.driver.revert(self.host, 'foo', resume=True)
domain.destroy.assert_called_once_with()
@mock.patch(DRIVER_PATH + '.LibvirtDriver._find_domain_by_mac_address')
def test__revert_destroy_nonactive(self, mock__find_domain_by_mac_address):
def test_revert_destroy_nonactive(self, mock__find_domain_by_mac_address):
domain = mock__find_domain_by_mac_address.return_value
domain.isActive.return_value = False
self.driver._revert('52:54:00:f9:b8:f9', 'foo', resume=True)
self.driver.revert(self.host, 'foo', resume=True)
self.assertFalse(domain.destroy.called)
@mock.patch('os_faults.utils.run')
@ddt.data('poweroff', 'poweron', 'reset')
def test_driver_actions(self, action, mock_run):
macs_list = ['52:54:00:f9:b8:f9', '52:54:00:ab:64:42']
getattr(self.driver, action)(macs_list)
mock_run.assert_called_once_with(
getattr(self.driver, '_%s' % action),
[{'mac_address': '52:54:00:f9:b8:f9'},
{'mac_address': '52:54:00:ab:64:42'}])
@mock.patch('os_faults.utils.run')
def test_driver_snapshot(self, mock_run):
macs_list = ['52:54:00:f9:b8:f9', '52:54:00:ab:64:42']
self.driver.snapshot(macs_list, 'foo_snap')
mock_run.assert_called_once_with(
self.driver._snapshot,
[{'mac_address': '52:54:00:f9:b8:f9',
'snapshot_name': 'foo_snap',
'suspend': True},
{'mac_address': '52:54:00:ab:64:42',
'snapshot_name': 'foo_snap',
'suspend': True}])
@mock.patch('os_faults.utils.run')
def test_driver_revert(self, mock_run):
macs_list = ['52:54:00:f9:b8:f9', '52:54:00:ab:64:42']
self.driver.revert(macs_list, 'foo_snap', resume=False)
mock_run.assert_called_once_with(
self.driver._revert,
[{'mac_address': '52:54:00:f9:b8:f9',
'snapshot_name': 'foo_snap',
'resume': False},
{'mac_address': '52:54:00:ab:64:42',
'snapshot_name': 'foo_snap',
'resume': False}])

@ -66,10 +66,11 @@ class OSFaultsTestCase(test.TestCase):
destructor = os_faults.connect(self.cloud_config)
self.assertIsInstance(destructor, fuel.FuelManagement)
self.assertIsInstance(destructor.node_discover, fuel.FuelManagement)
self.assertIsInstance(destructor.power_management,
self.assertEqual(1, len(destructor.power_manager.power_drivers))
self.assertIsInstance(destructor.power_manager.power_drivers[0],
libvirt_driver.LibvirtDriver)
def test_connect_fuel_with_ipmi_and_node_list(self):
def test_connect_fuel_with_ipmi_libvirt_and_node_list(self):
cloud_config = {
'node_discover': {
'driver': 'node_list',
@ -91,24 +92,35 @@ class OSFaultsTestCase(test.TestCase):
'username': 'root',
},
},
'power_management': {
'driver': 'ipmi',
'args': {
'mac_to_bmc': {
'00:00:00:00:00:00': {
'address': '55.55.55.55',
'username': 'foo',
'password': 'bar',
'power_managements': [
{
'driver': 'ipmi',
'args': {
'mac_to_bmc': {
'00:00:00:00:00:00': {
'address': '55.55.55.55',
'username': 'foo',
'password': 'bar',
}
}
}
}, {
'driver': 'libvirt',
'args': {
'connection_uri': "qemu+ssh://user@10.30.20.21/system"
}
}
}
]
}
destructor = os_faults.connect(cloud_config)
self.assertIsInstance(destructor, fuel.FuelManagement)
self.assertIsInstance(destructor.node_discover,
node_list.NodeListDiscover)
self.assertIsInstance(destructor.power_management, ipmi.IPMIDriver)
self.assertEqual(2, len(destructor.power_manager.power_drivers))
self.assertIsInstance(destructor.power_manager.power_drivers[0],
ipmi.IPMIDriver)
self.assertIsInstance(destructor.power_manager.power_drivers[1],
libvirt_driver.LibvirtDriver)
def test_connect_driver_not_found(self):
cloud_config = {
@ -132,7 +144,8 @@ class OSFaultsTestCase(test.TestCase):
with mock.patch('os_faults.open', mock_os_faults_open, create=True):
destructor = os_faults.connect()
self.assertIsInstance(destructor, fuel.FuelManagement)
self.assertIsInstance(destructor.power_management,
self.assertEqual(1, len(destructor.power_manager.power_drivers))
self.assertIsInstance(destructor.power_manager.power_drivers[0],
libvirt_driver.LibvirtDriver)
@mock.patch.dict(os.environ, {'OS_FAULTS_CONFIG': '/my/conf.yaml'})
@ -143,7 +156,8 @@ class OSFaultsTestCase(test.TestCase):
with mock.patch('os_faults.open', mock_os_faults_open, create=True):
destructor = os_faults.connect()
self.assertIsInstance(destructor, fuel.FuelManagement)
self.assertIsInstance(destructor.power_management,
self.assertEqual(1, len(destructor.power_manager.power_drivers))
self.assertIsInstance(destructor.power_manager.power_drivers[0],
libvirt_driver.LibvirtDriver)
mock_os_faults_open.assert_called_once_with('/my/conf.yaml')

@ -14,7 +14,6 @@ import threading
import mock
from os_faults.api import error
from os_faults.tests.unit import test
from os_faults import utils
@ -25,25 +24,12 @@ class MyException(Exception):
class UtilsTestCase(test.TestCase):
def test_run(self):
target = mock.Mock()
utils.run(target, [{'mac_address': '01'}, {'mac_address': '02'}])
target.assert_has_calls([mock.call(mac_address='01'),
mock.call(mac_address='02')], any_order=True)
def test_run_raise_exception(self):
target = mock.Mock()
target.side_effect = MyException()
self.assertRaises(error.PowerManagementError,
utils.run, target, [{'mac_address': '01'},
{'mac_address': '02'}])
def test_start_thread(self):
target = mock.Mock()
target_params = {'param1': 'val1', 'param2': 'val2'}
tw = utils.ThreadsWrapper(target)
tw.start_thread(**target_params)
tw = utils.ThreadsWrapper()
tw.start_thread(target, **target_params)
tw.join_threads()
target.assert_has_calls([mock.call(param1='val1', param2='val2')])
@ -54,18 +40,17 @@ class UtilsTestCase(test.TestCase):
target = mock.Mock()
target.side_effect = MyException()
tw = utils.ThreadsWrapper(target)
tw.start_thread()
tw = utils.ThreadsWrapper()
tw.start_thread(target)
tw.join_threads()
self.assertEqual(type(tw.errors[0]), MyException)
def test_join_threads(self):
target = mock.Mock()
thread_1 = mock.Mock()
thread_2 = mock.Mock()
tw = utils.ThreadsWrapper(target)
tw = utils.ThreadsWrapper()
tw.threads = [thread_1, thread_2]
tw.join_threads()

@ -15,40 +15,26 @@ import functools
import logging
import threading
from os_faults.api import error
LOG = logging.getLogger(__name__)
MACADDR_REGEXP = '^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$'
def run(target, kwargs_list):
tw = ThreadsWrapper(target)
for kwargs in kwargs_list:
tw.start_thread(**kwargs)
tw.join_threads()
if tw.errors:
raise error.PowerManagementError(
'There are some errors when working the driver. '
'Please, check logs for more details.')
class ThreadsWrapper(object):
def __init__(self, target):
self.target = target
def __init__(self):
self.threads = []
self.errors = []
def _target(self, **kwargs):
def _target(self, fn, **kwargs):
try:
self.target(**kwargs)
fn(**kwargs)
except Exception as exc:
LOG.error('Target raised exception: %s', exc)
LOG.error('%s raised exception: %s', fn, exc)
self.errors.append(exc)
def start_thread(self, **kwargs):
thread = threading.Thread(target=self._target, kwargs=kwargs)
def start_thread(self, fn, **kwargs):
thread = threading.Thread(target=self._target,
args=(fn, ), kwargs=kwargs)
thread.start()
self.threads.append(thread)