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:
parent
5c9665dfb8
commit
bbf5943ea5
@ -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)
|
||||
|
@ -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])
|
||||
|
@ -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())
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user