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
This commit is contained in:
Anton Studenov 2017-02-27 16:57:41 +03:00
parent 5c9665dfb8
commit bbf5943ea5
4 changed files with 118 additions and 53 deletions

View File

@ -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)

View File

@ -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])

View File

@ -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())

View File

@ -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):