From bbf5943ea5bd04cb9d18c1f8ded0016666d06675 Mon Sep 17 00:00:00 2001 From: Anton Studenov Date: Mon, 27 Feb 2017 16:57:41 +0300 Subject: [PATCH] Allow binding BMC by node fqdn in ipmi driver This patch adds new parameter to ipmi driver called 'fqdn_to_bmc' that allows specifying node fqdns instead of MAC addresses. Change-Id: Icfcde469cb358cf7aaa283b3bc6264c17253d584 --- os_faults/drivers/ipmi.py | 92 ++++++++++++++--------- os_faults/tests/unit/drivers/test_ipmi.py | 68 +++++++++++++---- os_faults/tests/unit/test_registry.py | 10 ++- os_faults/utils.py | 1 + 4 files changed, 118 insertions(+), 53 deletions(-) diff --git a/os_faults/drivers/ipmi.py b/os_faults/drivers/ipmi.py index 1293de8..815813d 100644 --- a/os_faults/drivers/ipmi.py +++ b/os_faults/drivers/ipmi.py @@ -23,6 +23,17 @@ from os_faults import utils LOG = logging.getLogger(__name__) +BMC_SCHEMA = { + 'type': 'object', + 'properties': { + 'address': {'type': 'string'}, + 'username': {'type': 'string'}, + 'password': {'type': 'string'}, + }, + 'required': ['address', 'username', 'password'] +} + + class IPMIDriver(power_management.PowerDriver): """IPMI driver. @@ -42,6 +53,11 @@ class IPMIDriver(power_management.PowerDriver): address: 170.0.10.51 username: admin2 password: Admin_123 + fqdn_to_bmc: + node3.local: + address: 170.0.10.52 + username: admin1 + password: Admin_123 parameters: @@ -63,79 +79,81 @@ class IPMIDriver(power_management.PowerDriver): 'mac_to_bmc': { 'type': 'object', 'patternProperties': { - utils.MACADDR_REGEXP: { - 'type': 'object', - 'properties': { - 'address': {'type': 'string'}, - 'username': {'type': 'string'}, - 'password': {'type': 'string'}, - }, - 'required': ['address', 'username', 'password'] - } + utils.MACADDR_REGEXP: BMC_SCHEMA } - } + }, + 'fqdn_to_bmc': { + 'type': 'object', + 'patternProperties': { + utils.FQDN_REGEXP: BMC_SCHEMA + } + }, }, - 'required': ['mac_to_bmc'], + 'anyOf': [ + {'required': ['mac_to_bmc']}, + {'required': ['fqdn_to_bmc']}, + ], 'additionalProperties': False, } def __init__(self, params): - self.mac_to_bmc = params['mac_to_bmc'] + self.mac_to_bmc = params.get('mac_to_bmc', {}) + self.fqdn_to_bmc = params.get('fqdn_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: - raise error.PowerManagementError( - 'BMC for Node(%s) not found!' % mac_address) + def _find_bmc_by_host(self, host): + if host.mac in self.mac_to_bmc: + return self.mac_to_bmc[host.mac] + if host.fqdn in self.fqdn_to_bmc: + return self.fqdn_to_bmc[host.fqdn] - return self.mac_to_bmc[mac_address] + raise error.PowerManagementError( + 'BMC for {!r} not found!'.format(host)) - def _run_set_power_cmd(self, mac_address, cmd, expected_state=None): - bmc = self._find_bmc_by_mac_address(mac_address) + def _run_set_power_cmd(self, host, cmd, expected_state=None): + bmc = self._find_bmc_by_host(host) try: ipmicmd = ipmi_command.Command(bmc=bmc['address'], userid=bmc['username'], password=bmc['password']) ret = ipmicmd.set_power(cmd, wait=True) except pyghmi_exception.IpmiException: - msg = 'IPMI cmd {!r} failed on bmc {!r}, Node({})'.format( - cmd, bmc['address'], mac_address) + msg = 'IPMI cmd {!r} failed on bmc {!r}, {!r}'.format( + cmd, bmc['address'], host) LOG.error(msg, exc_info=True) raise LOG.debug('IPMI response: {}'.format(ret)) if ret.get('powerstate') != expected_state or 'error' in ret: msg = ('Failed to change power state to {!r} on bmc {!r}, ' - 'Node({})'.format(expected_state, - bmc['address'], - mac_address)) + '{!r}'.format(expected_state, bmc['address'], host)) raise error.PowerManagementError(msg) def supports(self, host): try: - self._find_bmc_by_mac_address(host.mac) + self._find_bmc_by_host(host) except error.PowerManagementError: return False return True 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) + LOG.debug('Power off Node: %s', host) + self._run_set_power_cmd(host, cmd='off', expected_state='off') + LOG.info('Node powered off: %s', host) 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) + LOG.debug('Power on Node: %s', host) + self._run_set_power_cmd(host, cmd='on', expected_state='on') + LOG.info('Node powered on: %s', host) def reset(self, host): - LOG.debug('Reset Node with MAC address: %s', host.mac) + LOG.debug('Reset Node: %s', host) # boot -- If system is off, then 'on', else 'reset' - self._run_set_power_cmd(host.mac, cmd='boot') + self._run_set_power_cmd(host, cmd='boot') # NOTE(astudenov): This command does not wait for node to boot - LOG.info('Node reset: %s', host.mac) + LOG.info('Node reset: %s', host) def shutdown(self, host): - LOG.debug('Shutdown Node with MAC address: %s', host.mac) - self._run_set_power_cmd(host.mac, cmd='shutdown', expected_state='off') - LOG.info('Node is off: %s', host.mac) + LOG.debug('Shutdown Node: %s', host) + self._run_set_power_cmd(host, cmd='shutdown', expected_state='off') + LOG.info('Node is off: %s', host) diff --git a/os_faults/tests/unit/drivers/test_ipmi.py b/os_faults/tests/unit/drivers/test_ipmi.py index ac719e8..5636a5c 100644 --- a/os_faults/tests/unit/drivers/test_ipmi.py +++ b/os_faults/tests/unit/drivers/test_ipmi.py @@ -15,6 +15,7 @@ import ddt import mock from pyghmi import exceptions as pyghmi_exc +import os_faults from os_faults.api import node_collection from os_faults.drivers import ipmi from os_faults import error @@ -34,36 +35,75 @@ class IPMIDriverTestCase(test.TestCase): 'username': 'foo', 'password': 'bar' } + }, + 'fqdn_to_bmc': { + 'node2.com': { + 'address': '55.55.55.56', + 'username': 'ham', + 'password': 'eggs' + } } } 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') + self.host2 = node_collection.Host( + ip='10.0.0.3', mac='00:00:00:00:00:01', fqdn='node2.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']) + @ddt.data( + { + 'mac_to_bmc': { + '00:00:00:00:00:00': { + 'address': '55.55.55.55', + 'username': 'foo', + 'password': 'bar' + } + } + }, + { + 'fqdn_to_bmc': { + 'node2.com': { + 'address': '55.55.55.56', + 'username': 'ham', + 'password': 'eggs' + } + } + }, + { + 'mac_to_bmc': { + '00:00:00:00:00:00': { + 'address': '55.55.55.55', + 'username': 'foo', + 'password': 'bar' + } + }, + 'fqdn_to_bmc': { + 'node2.com': { + 'address': '55.55.55.56', + 'username': 'ham', + 'password': 'eggs' + } + } + }, + ) + def test_init(self, config): + os_faults._init_driver({'driver': 'ipmi', 'args': config}) def test_supports(self): self.assertTrue(self.driver.supports(self.host)) + self.assertTrue(self.driver.supports(self.host2)) 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, - '00:00:00:00:00:01') - @mock.patch('pyghmi.ipmi.command.Command') def test__run_set_power_cmd(self, mock_command): ipmicmd = mock_command.return_value ipmicmd.set_power.return_value = {'powerstate': 'off'} - self.driver._run_set_power_cmd('00:00:00:00:00:00', - 'off', expected_state='off') + self.driver._run_set_power_cmd(self.host, 'off', expected_state='off') ipmicmd.set_power.assert_called_once_with('off', wait=True) @mock.patch('pyghmi.ipmi.command.Command') @@ -73,7 +113,7 @@ class IPMIDriverTestCase(test.TestCase): self.assertRaises(pyghmi_exc.IpmiException, self.driver._run_set_power_cmd, - '00:00:00:00:00:00', 'off', expected_state='off') + self.host, 'off', expected_state='off') @mock.patch('pyghmi.ipmi.command.Command') def test__run_set_power_cmd_unexpected_power_state(self, mock_command): @@ -82,7 +122,7 @@ class IPMIDriverTestCase(test.TestCase): self.assertRaises(error.PowerManagementError, self.driver._run_set_power_cmd, - '00:00:00:00:00:00', 'off', expected_state='off') + self.host, 'off', expected_state='off') @mock.patch('os_faults.drivers.ipmi.IPMIDriver._run_set_power_cmd') @ddt.data(('poweroff', 'off', 'off'), @@ -93,7 +133,7 @@ class IPMIDriverTestCase(test.TestCase): getattr(self.driver, actions[0])(self.host) if len(actions) == 3: mock__run_set_power_cmd.assert_called_once_with( - '00:00:00:00:00:00', cmd=actions[1], expected_state=actions[2]) + self.host, cmd=actions[1], expected_state=actions[2]) else: mock__run_set_power_cmd.assert_called_once_with( - '00:00:00:00:00:00', cmd=actions[1]) + self.host, cmd=actions[1]) diff --git a/os_faults/tests/unit/test_registry.py b/os_faults/tests/unit/test_registry.py index deea30b..ceb915a 100644 --- a/os_faults/tests/unit/test_registry.py +++ b/os_faults/tests/unit/test_registry.py @@ -27,6 +27,14 @@ class TestDriver(base_driver.BaseDriver): class RegistryTestCase(test.TestCase): + def setUp(self): + super(RegistryTestCase, self).setUp() + registry.DRIVERS.clear() # reset global drivers list + + def tearDown(self): + super(RegistryTestCase, self).tearDown() + registry.DRIVERS.clear() # reset global drivers list + @mock.patch('oslo_utils.importutils.import_module') @mock.patch('os.walk') def test_get_drivers(self, mock_os_walk, mock_import_module): @@ -34,6 +42,4 @@ class RegistryTestCase(test.TestCase): mock_os_walk.return_value = [(drivers_folder, [], ['test_driver.py'])] mock_import_module.return_value = sys.modules[__name__] - registry.DRIVERS.clear() # reset global drivers list - self.assertEqual({'test': TestDriver}, registry.get_drivers()) diff --git a/os_faults/utils.py b/os_faults/utils.py index ea9c197..edfe77a 100644 --- a/os_faults/utils.py +++ b/os_faults/utils.py @@ -18,6 +18,7 @@ import threading LOG = logging.getLogger(__name__) MACADDR_REGEXP = '^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$' +FQDN_REGEXP = '.*' class ThreadsWrapper(object):