Merge "IPMI double bridging functionality"

This commit is contained in:
Jenkins 2014-08-19 17:59:36 +00:00 committed by Gerrit Code Review
commit 0cc2702538
5 changed files with 610 additions and 97 deletions

View File

@ -73,7 +73,22 @@ OPTIONAL_PROPERTIES = {
'ipmi_password': _("password. Optional."),
'ipmi_priv_level': _("privilege level; default is ADMINISTRATOR. One of "
"%s. Optional.") % ', '.join(VALID_PRIV_LEVELS),
'ipmi_username': _("username; default is NULL user. Optional.")
'ipmi_username': _("username; default is NULL user. Optional."),
'ipmi_bridging': _("bridging_type; default is \"no\". One of \"single\", "
"\"dual\", \"no\". Optional."),
'ipmi_transit_channel': _("transit channel for bridged request. Required "
"only if ipmi_bridging is set to \"dual\"."),
'ipmi_transit_address': _("transit address for bridged request. Required "
"only if ipmi_bridging is set to \"dual\"."),
'ipmi_target_channel': _("destination channel for bridged request. "
"Required only if ipmi_bridging is set to "
"\"single\" or \"dual\"."),
'ipmi_target_address': _("destination address for bridged request. "
"Required only if ipmi_bridging is set "
"to \"single\" or \"dual\"."),
'ipmi_local_address': _("local IPMB address for bridged requests. "
"Used only if ipmi_bridging is set "
"to \"single\" or \"dual\". Optional.")
}
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
@ -81,49 +96,76 @@ CONSOLE_PROPERTIES = {
'ipmi_terminal_port': _("node's UDP port to connect to. Only required for "
"console access.")
}
BRIDGING_OPTIONS = [('local_address', '-m'),
('transit_channel', '-B'), ('transit_address', '-T'),
('target_channel', '-b'), ('target_address', '-t')]
LAST_CMD_TIME = {}
TIMING_SUPPORT = None
SINGLE_BRIDGE_SUPPORT = None
DUAL_BRIDGE_SUPPORT = None
def _is_timing_supported(is_supported=None):
# shim to allow module variable to be mocked in unit tests
global TIMING_SUPPORT
if (TIMING_SUPPORT is None) and (is_supported is not None):
TIMING_SUPPORT = is_supported
return TIMING_SUPPORT
ipmitool_command_options = {
'timing': ['ipmitool', '-N', '0', '-R', '0', '-h'],
'single_bridge': ['ipmitool', '-m', '0', '-b', '0', '-t', '0', '-h'],
'dual_bridge': ['ipmitool', '-m', '0', '-b', '0', '-t', '0',
'-B', '0', '-T', '0', '-h']}
def check_timing_support():
"""Check the installed version of ipmitool for -N -R option support.
def _check_option_support(options):
"""Checks if the specific ipmitool options are supported on host.
Support was added in 1.8.12 for the -N -R options, which enable
more precise control over timing of ipmi packets. Prior to this,
the default behavior was to retry each command up to 18 times at
1 to 5 second intervals.
http://ipmitool.cvs.sourceforge.net/viewvc/ipmitool/ipmitool/ChangeLog?revision=1.37 # noqa
This method updates the module-level variables indicating whether
an option is supported so that it is accessible by any driver
interface class in this module. It is intended to be called from
the __init__ method of such classes only.
This method updates the module-level TIMING_SUPPORT variable so that
it is accessible by any driver interface class in this module. It is
intended to be called from the __init__ method of such classes only.
:returns: boolean indicating whether support for -N -R is present
:param options: list of ipmitool options to be checked
:raises: OSError
"""
if _is_timing_supported() is None:
# Directly check ipmitool for support of -N and -R options. Because
# of the way ipmitool processes' command line options, if the local
# ipmitool does not support setting the timing options, the command
# below will fail.
try:
out, err = utils.execute(*['ipmitool', '-N', '0', '-R', '0', '-h'])
except processutils.ProcessExecutionError:
# the local ipmitool does not support the -N and -R options.
_is_timing_supported(False)
else:
# looks like ipmitool supports timing options.
_is_timing_supported(True)
for opt in options:
if _is_option_supported(opt) is None:
try:
cmd = ipmitool_command_options[opt]
out, err = utils.execute(*cmd)
except processutils.ProcessExecutionError:
# the local ipmitool does not support the command.
_is_option_supported(opt, False)
else:
# looks like ipmitool supports the command.
_is_option_supported(opt, True)
def _is_option_supported(option, is_supported=None):
"""Indicates whether the particular ipmitool option is supported.
:param option: specific ipmitool option
:param is_supported: Optional Boolean. when specified, this value
is assigned to the module-level variable indicating
whether the option is supported. Used only if a value
is not already assigned.
:returns: True, indicates the option is supported
:returns: False, indicates the option is not supported
:returns: None, indicates that it is not aware whether the option
is supported
"""
global SINGLE_BRIDGE_SUPPORT
global DUAL_BRIDGE_SUPPORT
global TIMING_SUPPORT
if option == 'single_bridge':
if (SINGLE_BRIDGE_SUPPORT is None) and (is_supported is not None):
SINGLE_BRIDGE_SUPPORT = is_supported
return SINGLE_BRIDGE_SUPPORT
elif option == 'dual_bridge':
if (DUAL_BRIDGE_SUPPORT is None) and (is_supported is not None):
DUAL_BRIDGE_SUPPORT = is_supported
return DUAL_BRIDGE_SUPPORT
elif option == 'timing':
if (TIMING_SUPPORT is None) and (is_supported is not None):
TIMING_SUPPORT = is_supported
return TIMING_SUPPORT
def _console_pwfile_path(uuid):
@ -164,6 +206,7 @@ def _parse_driver_info(node):
"""
info = node.driver_info or {}
bridging_types = ['single', 'dual']
missing_info = [key for key in REQUIRED_PROPERTIES if not info.get(key)]
if missing_info:
raise exception.MissingParameterValue(_(
@ -176,6 +219,12 @@ def _parse_driver_info(node):
password = info.get('ipmi_password')
port = info.get('ipmi_terminal_port')
priv_level = info.get('ipmi_priv_level', 'ADMINISTRATOR')
bridging_type = info.get('ipmi_bridging', 'no')
local_address = info.get('ipmi_local_address')
transit_channel = info.get('ipmi_transit_channel')
transit_address = info.get('ipmi_transit_address')
target_channel = info.get('ipmi_target_channel')
target_address = info.get('ipmi_target_address')
if port:
try:
@ -184,6 +233,46 @@ def _parse_driver_info(node):
raise exception.InvalidParameterValue(_(
"IPMI terminal port is not an integer."))
# check if ipmi_bridging has proper value
if bridging_type == 'no':
# if bridging is not selected, then set all bridging params to None
local_address = transit_channel = transit_address = \
target_channel = target_address = None
elif bridging_type in bridging_types:
# check if the particular bridging option is supported on host
if not _is_option_supported('%s_bridge' % bridging_type):
raise exception.InvalidParameterValue(_(
"Value for ipmi_bridging is provided as %s, but IPMI "
"bridging is not supported by the IPMI utility installed "
"on host. Ensure ipmitool version is > 1.8.11"
) % bridging_type)
# ensure that all the required parameters are provided
params_undefined = [param for param, value in [
("ipmi_target_channel", target_channel),
('ipmi_target_address', target_address)] if value is None]
if bridging_type == 'dual':
params_undefined2 = [param for param, value in [
("ipmi_transit_channel", transit_channel),
('ipmi_transit_address', transit_address)
] if value is None]
params_undefined.extend(params_undefined2)
else:
# if single bridging was selected, set dual bridge params to None
transit_channel = transit_address = None
# If the required parameters were not provided,
# raise an exception
if params_undefined:
raise exception.MissingParameterValue(_(
"%(param)s not provided") % {'param': params_undefined})
else:
raise exception.InvalidParameterValue(_(
"Invalid value for ipmi_bridging: %(bridging_type)s,"
" the valid value can be one of: %(bridging_types)s"
) % {'bridging_type': bridging_type,
'bridging_types': bridging_types + ['no']})
if priv_level not in VALID_PRIV_LEVELS:
valid_priv_lvls = ', '.join(VALID_PRIV_LEVELS)
raise exception.InvalidParameterValue(_(
@ -197,8 +286,13 @@ def _parse_driver_info(node):
'password': password,
'port': port,
'uuid': node.uuid,
'priv_level': priv_level
}
'priv_level': priv_level,
'local_address': local_address,
'transit_channel': transit_channel,
'transit_address': transit_address,
'target_channel': target_channel,
'target_address': target_address
}
def _exec_ipmitool(driver_info, command):
@ -221,13 +315,17 @@ def _exec_ipmitool(driver_info, command):
driver_info['address'],
'-L', driver_info['priv_level']
]
if driver_info['username']:
args.append('-U')
args.append(driver_info['username'])
for name, option in BRIDGING_OPTIONS:
if driver_info[name] is not None:
args.append(option)
args.append(driver_info[name])
# specify retry timing more precisely, if supported
if _is_timing_supported():
if _is_option_supported('timing'):
num_tries = max(
(CONF.ipmi.retry_timeout // CONF.ipmi.min_command_interval), 1)
args.append('-R')
@ -453,12 +551,12 @@ class IPMIPower(base.PowerInterface):
def __init__(self):
try:
check_timing_support()
_check_option_support(['timing', 'single_bridge', 'dual_bridge'])
except OSError:
raise exception.DriverLoadError(
driver=self.__class__.__name__,
reason="Unable to locate usable ipmitool command in "
"the system path when checking ipmitool version")
reason=_("Unable to locate usable ipmitool command in "
"the system path when checking ipmitool version"))
def get_properties(self):
return COMMON_PROPERTIES
@ -544,12 +642,12 @@ class IPMIManagement(base.ManagementInterface):
def __init__(self):
try:
check_timing_support()
_check_option_support(['timing', 'single_bridge', 'dual_bridge'])
except OSError:
raise exception.DriverLoadError(
driver=self.__class__.__name__,
reason="Unable to locate usable ipmitool command in "
"the system path when checking ipmitool version")
reason=_("Unable to locate usable ipmitool command in "
"the system path when checking ipmitool version"))
def validate(self, task):
"""Check that 'driver_info' contains IPMI credentials.
@ -685,6 +783,15 @@ class IPMIManagement(base.ManagementInterface):
class VendorPassthru(base.VendorInterface):
def __init__(self):
try:
_check_option_support(['single_bridge', 'dual_bridge'])
except OSError:
raise exception.DriverLoadError(
driver=self.__class__.__name__,
reason=_("Unable to locate usable ipmitool command in "
"the system path when checking ipmitool version"))
@task_manager.require_exclusive_lock
def _send_raw_bytes(self, task, raw_bytes):
"""Send raw bytes to the BMC. Bytes should be a string of bytes.
@ -813,12 +920,12 @@ class IPMIShellinaboxConsole(base.ConsoleInterface):
def __init__(self):
try:
check_timing_support()
_check_option_support(['timing', 'single_bridge', 'dual_bridge'])
except OSError:
raise exception.DriverLoadError(
driver=self.__class__.__name__,
reason="Unable to locate usable ipmitool command in "
"the system path when checking ipmitool version")
reason=_("Unable to locate usable ipmitool command in "
"the system path when checking ipmitool version"))
def get_properties(self):
d = COMMON_PROPERTIES.copy()
@ -862,6 +969,12 @@ class IPMIShellinaboxConsole(base.ConsoleInterface):
'address': driver_info['address'],
'user': driver_info['username'],
'pwfile': pw_file}
for name, option in BRIDGING_OPTIONS:
if driver_info[name] is not None:
ipmi_cmd = " ".join([ipmi_cmd,
option, driver_info[name]])
if CONF.debug:
ipmi_cmd += " -v"
ipmi_cmd += " sol activate"

View File

@ -2109,7 +2109,11 @@ class ManagerTestProperties(tests_db_base.DbTestCase):
def test_driver_properties_fake_ipmitool(self):
expected = ['ipmi_address', 'ipmi_terminal_port',
'ipmi_password', 'ipmi_priv_level',
'ipmi_username']
'ipmi_username', 'ipmi_bridging',
'ipmi_transit_channel', 'ipmi_transit_address',
'ipmi_target_channel', 'ipmi_target_address',
'ipmi_local_address'
]
self._check_driver_properties("fake_ipmitool", expected)
def test_driver_properties_fake_ipminative(self):
@ -2134,9 +2138,12 @@ class ManagerTestProperties(tests_db_base.DbTestCase):
def test_driver_properties_pxe_ipmitool(self):
expected = ['ipmi_address', 'ipmi_terminal_port',
'pxe_deploy_kernel', 'pxe_deploy_ramdisk',
'ipmi_password', 'ipmi_priv_level',
'ipmi_username']
'ipmi_username', 'ipmi_bridging', 'ipmi_transit_channel',
'ipmi_transit_address', 'ipmi_target_channel',
'ipmi_target_address', 'ipmi_local_address',
'pxe_deploy_kernel', 'pxe_deploy_ramdisk'
]
self._check_driver_properties("pxe_ipmitool", expected)
def test_driver_properties_pxe_ipminative(self):

View File

@ -21,7 +21,18 @@ def get_test_ipmi_info():
return {
"ipmi_address": "1.2.3.4",
"ipmi_username": "admin",
"ipmi_password": "fake",
"ipmi_password": "fake"
}
def get_test_ipmi_bridging_parameters():
return {
"ipmi_bridging": "dual",
"ipmi_local_address": "0x20",
"ipmi_transit_channel": "0",
"ipmi_transit_address": "0x82",
"ipmi_target_channel": "7",
"ipmi_target_address": "0x72"
}

View File

@ -52,41 +52,164 @@ CONF.import_opt('min_command_interval',
INFO_DICT = db_utils.get_test_ipmi_info()
# BRIDGE_INFO_DICT will have all the bridging parameters appended
BRIDGE_INFO_DICT = INFO_DICT.copy()
BRIDGE_INFO_DICT.update(db_utils.get_test_ipmi_bridging_parameters())
class IPMIToolCheckTimingTestCase(base.TestCase):
@mock.patch.object(ipmi, '_is_timing_supported')
class IPMIToolCheckOptionSupportedTestCase(base.TestCase):
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(utils, 'execute')
def test_check_timing_pass(self, mock_exc, mock_timing):
def test_check_timing_pass(self, mock_exc, mock_support):
mock_exc.return_value = (None, None)
mock_timing.return_value = None
expected = [mock.call(), mock.call(True)]
mock_support.return_value = None
expected = [mock.call('timing'),
mock.call('timing', True)]
ipmi.check_timing_support()
ipmi._check_option_support(['timing'])
self.assertTrue(mock_exc.called)
self.assertEqual(expected, mock_timing.call_args_list)
self.assertEqual(expected, mock_support.call_args_list)
@mock.patch.object(ipmi, '_is_timing_supported')
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(utils, 'execute')
def test_check_timing_fail(self, mock_exc, mock_timing):
def test_check_timing_fail(self, mock_exc, mock_support):
mock_exc.side_effect = processutils.ProcessExecutionError()
mock_timing.return_value = None
expected = [mock.call(), mock.call(False)]
mock_support.return_value = None
expected = [mock.call('timing'),
mock.call('timing', False)]
ipmi.check_timing_support()
ipmi._check_option_support(['timing'])
self.assertTrue(mock_exc.called)
self.assertEqual(expected, mock_timing.call_args_list)
self.assertEqual(expected, mock_support.call_args_list)
@mock.patch.object(ipmi, '_is_timing_supported')
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(utils, 'execute')
def test_check_timing_no_ipmitool(self, mock_exc, mock_timing):
def test_check_timing_no_ipmitool(self, mock_exc, mock_support):
mock_exc.side_effect = OSError()
mock_timing.return_value = None
expected = [mock.call()]
mock_support.return_value = None
expected = [mock.call('timing')]
self.assertRaises(OSError, ipmi.check_timing_support)
self.assertRaises(OSError, ipmi._check_option_support, ['timing'])
self.assertTrue(mock_exc.called)
self.assertEqual(expected, mock_timing.call_args_list)
self.assertEqual(expected, mock_support.call_args_list)
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(utils, 'execute')
def test_check_single_bridge_pass(self, mock_exc, mock_support):
mock_exc.return_value = (None, None)
mock_support.return_value = None
expected = [mock.call('single_bridge'),
mock.call('single_bridge', True)]
ipmi._check_option_support(['single_bridge'])
self.assertTrue(mock_exc.called)
self.assertEqual(expected, mock_support.call_args_list)
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(utils, 'execute')
def test_check_single_bridge_fail(self, mock_exc, mock_support):
mock_exc.side_effect = processutils.ProcessExecutionError()
mock_support.return_value = None
expected = [mock.call('single_bridge'),
mock.call('single_bridge', False)]
ipmi._check_option_support(['single_bridge'])
self.assertTrue(mock_exc.called)
self.assertEqual(expected, mock_support.call_args_list)
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(utils, 'execute')
def test_check_single_bridge_no_ipmitool(self, mock_exc,
mock_support):
mock_exc.side_effect = OSError()
mock_support.return_value = None
expected = [mock.call('single_bridge')]
self.assertRaises(OSError, ipmi._check_option_support,
['single_bridge'])
self.assertTrue(mock_exc.called)
self.assertEqual(expected, mock_support.call_args_list)
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(utils, 'execute')
def test_check_dual_bridge_pass(self, mock_exc, mock_support):
mock_exc.return_value = (None, None)
mock_support.return_value = None
expected = [mock.call('dual_bridge'),
mock.call('dual_bridge', True)]
ipmi._check_option_support(['dual_bridge'])
self.assertTrue(mock_exc.called)
self.assertEqual(expected, mock_support.call_args_list)
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(utils, 'execute')
def test_check_dual_bridge_fail(self, mock_exc, mock_support):
mock_exc.side_effect = processutils.ProcessExecutionError()
mock_support.return_value = None
expected = [mock.call('dual_bridge'),
mock.call('dual_bridge', False)]
ipmi._check_option_support(['dual_bridge'])
self.assertTrue(mock_exc.called)
self.assertEqual(expected, mock_support.call_args_list)
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(utils, 'execute')
def test_check_dual_bridge_no_ipmitool(self, mock_exc, mock_support):
mock_exc.side_effect = OSError()
mock_support.return_value = None
expected = [mock.call('dual_bridge')]
self.assertRaises(OSError, ipmi._check_option_support,
['dual_bridge'])
self.assertTrue(mock_exc.called)
self.assertEqual(expected, mock_support.call_args_list)
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(utils, 'execute')
def test_check_all_options_pass(self, mock_exc, mock_support):
mock_exc.return_value = (None, None)
mock_support.return_value = None
expected = [
mock.call('timing'), mock.call('timing', True),
mock.call('single_bridge'),
mock.call('single_bridge', True),
mock.call('dual_bridge'), mock.call('dual_bridge', True)]
ipmi._check_option_support(['timing', 'single_bridge', 'dual_bridge'])
self.assertTrue(mock_exc.called)
self.assertEqual(expected, mock_support.call_args_list)
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(utils, 'execute')
def test_check_all_options_fail(self, mock_exc, mock_support):
mock_exc.side_effect = processutils.ProcessExecutionError()
mock_support.return_value = None
expected = [
mock.call('timing'), mock.call('timing', False),
mock.call('single_bridge'),
mock.call('single_bridge', False),
mock.call('dual_bridge'),
mock.call('dual_bridge', False)]
ipmi._check_option_support(['timing', 'single_bridge', 'dual_bridge'])
self.assertTrue(mock_exc.called)
self.assertEqual(expected, mock_support.call_args_list)
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(utils, 'execute')
def test_check_all_options_no_ipmitool(self, mock_exc, mock_support):
mock_exc.side_effect = OSError()
mock_support.return_value = None
# exception is raised once ipmitool was not found for an command
expected = [mock.call('timing')]
self.assertRaises(OSError, ipmi._check_option_support,
['timing', 'single_bridge', 'dual_bridge'])
self.assertTrue(mock_exc.called)
self.assertEqual(expected, mock_support.call_args_list)
@mock.patch.object(time, 'sleep')
@ -113,10 +236,9 @@ class IPMIToolPrivateMethodTestCase(base.TestCase):
def test__parse_driver_info(self, mock_sleep):
# make sure we get back the expected things
self.assertIsNotNone(self.info.get('address'))
self.assertIsNotNone(self.info.get('username'))
self.assertIsNotNone(self.info.get('password'))
self.assertIsNotNone(self.info.get('uuid'))
_OPTIONS = ['address', 'username', 'password', 'uuid']
for option in _OPTIONS:
self.assertIsNotNone(self.info.get(option))
info = dict(INFO_DICT)
@ -149,11 +271,170 @@ class IPMIToolPrivateMethodTestCase(base.TestCase):
ipmi._parse_driver_info,
node)
@mock.patch.object(ipmi, '_is_timing_supported')
@mock.patch.object(ipmi, '_is_option_supported')
def test__parse_driver_info_with_invalid_bridging_type(self,
mock_support, mock_sleep):
info = BRIDGE_INFO_DICT.copy()
# make sure error is raised when ipmi_bridging has unexpected value
info['ipmi_bridging'] = 'junk'
node = obj_utils.get_test_node(self.context, driver_info=info)
self.assertRaises(exception.InvalidParameterValue,
ipmi._parse_driver_info,
node)
self.assertFalse(mock_support.called)
@mock.patch.object(ipmi, '_is_option_supported')
def test__parse_driver_info_with_no_bridging(self,
mock_support, mock_sleep):
_OPTIONS = ['address', 'username', 'password', 'uuid']
_BRIDGING_OPTIONS = ['local_address', 'transit_channel',
'transit_address',
'target_channel', 'target_address']
info = BRIDGE_INFO_DICT.copy()
info['ipmi_bridging'] = 'no'
node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
driver_info=info)
ret = ipmi._parse_driver_info(node)
# ensure that _is_option_supported was not called
self.assertFalse(mock_support.called)
# check if we got all the required options
for option in _OPTIONS:
self.assertIsNotNone(ret[option])
# test the default value for 'priv_level'
self.assertEqual('ADMINISTRATOR', ret['priv_level'])
# check if bridging parameters were set to None
for option in _BRIDGING_OPTIONS:
self.assertIsNone(ret[option])
@mock.patch.object(ipmi, '_is_option_supported')
def test__parse_driver_info_with_dual_bridging_pass(self,
mock_support, mock_sleep):
_OPTIONS = ['address', 'username', 'password', 'uuid',
'local_address', 'transit_channel', 'transit_address',
'target_channel', 'target_address']
node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
driver_info=BRIDGE_INFO_DICT)
expected = [mock.call('dual_bridge')]
# test double bridging and make sure we get back expected result
mock_support.return_value = True
ret = ipmi._parse_driver_info(node)
self.assertEqual(expected, mock_support.call_args_list)
for option in _OPTIONS:
self.assertIsNotNone(ret[option])
# test the default value for 'priv_level'
self.assertEqual('ADMINISTRATOR', ret['priv_level'])
info = BRIDGE_INFO_DICT.copy()
# ipmi_local_address / ipmi_username / ipmi_password are not mandatory
for optional_arg in ['ipmi_local_address', 'ipmi_username',
'ipmi_password']:
del info[optional_arg]
node = obj_utils.get_test_node(self.context, driver_info=info)
ipmi._parse_driver_info(node)
self.assertEqual(mock.call('dual_bridge'), mock_support.call_args)
@mock.patch.object(ipmi, '_is_option_supported')
def test__parse_driver_info_with_dual_bridging_not_supported(self,
mock_support, mock_sleep):
node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
driver_info=BRIDGE_INFO_DICT)
# if dual bridge is not supported then check if error is raised
mock_support.return_value = False
self.assertRaises(exception.InvalidParameterValue,
ipmi._parse_driver_info, node)
mock_support.assert_called_once_with('dual_bridge')
@mock.patch.object(ipmi, '_is_option_supported')
def test__parse_driver_info_with_dual_bridging_missing_parameters(self,
mock_support, mock_sleep):
info = BRIDGE_INFO_DICT.copy()
mock_support.return_value = True
# make sure error is raised when dual bridging is selected and the
# required parameters for dual bridging are not provided
for param in ['ipmi_transit_channel', 'ipmi_target_address',
'ipmi_transit_address', 'ipmi_target_channel']:
del info[param]
node = obj_utils.get_test_node(self.context, driver_info=info)
self.assertRaises(exception.MissingParameterValue,
ipmi._parse_driver_info, node)
self.assertEqual(mock.call('dual_bridge'),
mock_support.call_args)
@mock.patch.object(ipmi, '_is_option_supported')
def test__parse_driver_info_with_single_bridging_pass(self,
mock_support, mock_sleep):
_OPTIONS = ['address', 'username', 'password', 'uuid',
'local_address', 'target_channel', 'target_address']
info = BRIDGE_INFO_DICT.copy()
info['ipmi_bridging'] = 'single'
node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
driver_info=info)
expected = [mock.call('single_bridge')]
# test single bridging and make sure we get back expected things
mock_support.return_value = True
ret = ipmi._parse_driver_info(node)
self.assertEqual(expected, mock_support.call_args_list)
for option in _OPTIONS:
self.assertIsNotNone(ret[option])
# test the default value for 'priv_level'
self.assertEqual('ADMINISTRATOR', ret['priv_level'])
# check if dual bridge params are set to None
self.assertIsNone(ret['transit_channel'])
self.assertIsNone(ret['transit_address'])
# ipmi_local_address / ipmi_username / ipmi_password are not mandatory
for optional_arg in ['ipmi_local_address', 'ipmi_username',
'ipmi_password']:
del info[optional_arg]
node = obj_utils.get_test_node(self.context, driver_info=info)
ipmi._parse_driver_info(node)
self.assertEqual(mock.call('single_bridge'),
mock_support.call_args)
@mock.patch.object(ipmi, '_is_option_supported')
def test__parse_driver_info_with_single_bridging_not_supported(self,
mock_support, mock_sleep):
info = BRIDGE_INFO_DICT.copy()
info['ipmi_bridging'] = 'single'
node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
driver_info=info)
# if single bridge is not supported then check if error is raised
mock_support.return_value = False
self.assertRaises(exception.InvalidParameterValue,
ipmi._parse_driver_info, node)
mock_support.assert_called_once_with('single_bridge')
@mock.patch.object(ipmi, '_is_option_supported')
def test__parse_driver_info_with_single_bridging_missing_parameters(
self, mock_support, mock_sleep):
info = dict(BRIDGE_INFO_DICT)
info['ipmi_bridging'] = 'single'
mock_support.return_value = True
# make sure error is raised when single bridging is selected and the
# required parameters for single bridging are not provided
for param in ['ipmi_target_channel', 'ipmi_target_address']:
del info[param]
node = obj_utils.get_test_node(self.context, driver_info=info)
self.assertRaises(exception.MissingParameterValue,
ipmi._parse_driver_info,
node)
self.assertEqual(mock.call('single_bridge'),
mock_support.call_args)
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(ipmi, '_make_password_file', autospec=True)
@mock.patch.object(utils, 'execute', autospec=True)
def test__exec_ipmitool_first_call_to_address(self, mock_exec, mock_pwf,
mock_timing_support, mock_sleep):
mock_support, mock_sleep):
ipmi.LAST_CMD_TIME = {}
pw_file_handle = tempfile.NamedTemporaryFile()
pw_file = pw_file_handle.name
@ -168,21 +449,22 @@ class IPMIToolPrivateMethodTestCase(base.TestCase):
'A', 'B', 'C',
]
mock_timing_support.return_value = False
mock_support.return_value = False
mock_pwf.return_value = file_handle
mock_exec.return_value = (None, None)
ipmi._exec_ipmitool(self.info, 'A B C')
mock_support.assert_called_once_with('timing')
mock_pwf.assert_called_once_with(self.info['password'])
mock_exec.assert_called_once_with(*args)
self.assertFalse(mock_sleep.called)
@mock.patch.object(ipmi, '_is_timing_supported')
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(ipmi, '_make_password_file', autospec=True)
@mock.patch.object(utils, 'execute', autospec=True)
def test__exec_ipmitool_second_call_to_address_sleep(self, mock_exec,
mock_pwf, mock_timing_support, mock_sleep):
mock_pwf, mock_support, mock_sleep):
ipmi.LAST_CMD_TIME = {}
pw_file_handle1 = tempfile.NamedTemporaryFile()
pw_file1 = pw_file_handle1.name
@ -209,7 +491,9 @@ class IPMIToolPrivateMethodTestCase(base.TestCase):
'D', 'E', 'F',
]]
mock_timing_support.return_value = False
expected = [mock.call('timing'),
mock.call('timing')]
mock_support.return_value = False
mock_pwf.side_effect = iter([file_handle1, file_handle2])
mock_exec.side_effect = iter([(None, None), (None, None)])
@ -218,13 +502,14 @@ class IPMIToolPrivateMethodTestCase(base.TestCase):
ipmi._exec_ipmitool(self.info, 'D E F')
self.assertTrue(mock_sleep.called)
self.assertEqual(expected, mock_support.call_args_list)
mock_exec.assert_called_with(*args[1])
@mock.patch.object(ipmi, '_is_timing_supported')
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(ipmi, '_make_password_file', autospec=True)
@mock.patch.object(utils, 'execute', autospec=True)
def test__exec_ipmitool_second_call_to_address_no_sleep(self, mock_exec,
mock_pwf, mock_timing_support, mock_sleep):
mock_pwf, mock_support, mock_sleep):
ipmi.LAST_CMD_TIME = {}
pw_file_handle1 = tempfile.NamedTemporaryFile()
pw_file1 = pw_file_handle1.name
@ -251,7 +536,9 @@ class IPMIToolPrivateMethodTestCase(base.TestCase):
'D', 'E', 'F',
]]
mock_timing_support.return_value = False
expected = [mock.call('timing'),
mock.call('timing')]
mock_support.return_value = False
mock_pwf.side_effect = iter([file_handle1, file_handle2])
mock_exec.side_effect = iter([(None, None), (None, None)])
@ -262,13 +549,14 @@ class IPMIToolPrivateMethodTestCase(base.TestCase):
CONF.ipmi.min_command_interval)
ipmi._exec_ipmitool(self.info, 'D E F')
self.assertFalse(mock_sleep.called)
self.assertEqual(expected, mock_support.call_args_list)
mock_exec.assert_called_with(*args[1])
@mock.patch.object(ipmi, '_is_timing_supported')
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(ipmi, '_make_password_file', autospec=True)
@mock.patch.object(utils, 'execute', autospec=True)
def test__exec_ipmitool_two_calls_to_diff_address(self, mock_exec,
mock_pwf, mock_timing_support, mock_sleep):
mock_pwf, mock_support, mock_sleep):
ipmi.LAST_CMD_TIME = {}
pw_file_handle1 = tempfile.NamedTemporaryFile()
pw_file1 = pw_file_handle1.name
@ -295,7 +583,9 @@ class IPMIToolPrivateMethodTestCase(base.TestCase):
'D', 'E', 'F',
]]
mock_timing_support.return_value = False
expected = [mock.call('timing'),
mock.call('timing')]
mock_support.return_value = False
mock_pwf.side_effect = iter([file_handle1, file_handle2])
mock_exec.side_effect = iter([(None, None), (None, None)])
@ -304,13 +594,14 @@ class IPMIToolPrivateMethodTestCase(base.TestCase):
self.info['address'] = '127.127.127.127'
ipmi._exec_ipmitool(self.info, 'D E F')
self.assertFalse(mock_sleep.called)
self.assertEqual(expected, mock_support.call_args_list)
mock_exec.assert_called_with(*args[1])
@mock.patch.object(ipmi, '_is_timing_supported')
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(ipmi, '_make_password_file', autospec=True)
@mock.patch.object(utils, 'execute', autospec=True)
def test__exec_ipmitool_without_timing(self, mock_exec, mock_pwf,
mock_timing_support, mock_sleep):
mock_support, mock_sleep):
pw_file_handle = tempfile.NamedTemporaryFile()
pw_file = pw_file_handle.name
file_handle = open(pw_file, "w")
@ -325,20 +616,21 @@ class IPMIToolPrivateMethodTestCase(base.TestCase):
'A', 'B', 'C',
]
mock_timing_support.return_value = False
mock_support.return_value = False
mock_pwf.return_value = file_handle
mock_exec.return_value = (None, None)
ipmi._exec_ipmitool(self.info, 'A B C')
mock_support.assert_called_once_with('timing')
mock_pwf.assert_called_once_with(self.info['password'])
mock_exec.assert_called_once_with(*args)
@mock.patch.object(ipmi, '_is_timing_supported')
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(ipmi, '_make_password_file', autospec=True)
@mock.patch.object(utils, 'execute', autospec=True)
def test__exec_ipmitool_with_timing(self, mock_exec, mock_pwf,
mock_timing_support, mock_sleep):
mock_support, mock_sleep):
pw_file_handle = tempfile.NamedTemporaryFile()
pw_file = pw_file_handle.name
file_handle = open(pw_file, "w")
@ -354,20 +646,21 @@ class IPMIToolPrivateMethodTestCase(base.TestCase):
'A', 'B', 'C',
]
mock_timing_support.return_value = True
mock_support.return_value = True
mock_pwf.return_value = file_handle
mock_exec.return_value = (None, None)
ipmi._exec_ipmitool(self.info, 'A B C')
mock_support.assert_called_once_with('timing')
mock_pwf.assert_called_once_with(self.info['password'])
mock_exec.assert_called_once_with(*args)
@mock.patch.object(ipmi, '_is_timing_supported')
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(ipmi, '_make_password_file', autospec=True)
@mock.patch.object(utils, 'execute', autospec=True)
def test__exec_ipmitool_without_username(self, mock_exec, mock_pwf,
mock_timing_support, mock_sleep):
mock_support, mock_sleep):
self.info['username'] = None
pw_file_handle = tempfile.NamedTemporaryFile()
pw_file = pw_file_handle.name
@ -381,18 +674,104 @@ class IPMIToolPrivateMethodTestCase(base.TestCase):
'A', 'B', 'C',
]
mock_timing_support.return_value = False
mock_support.return_value = False
mock_pwf.return_value = file_handle
mock_exec.return_value = (None, None)
ipmi._exec_ipmitool(self.info, 'A B C')
mock_support.assert_called_once_with('timing')
self.assertTrue(mock_pwf.called)
mock_exec.assert_called_once_with(*args)
@mock.patch.object(ipmi, '_is_timing_supported')
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(ipmi, '_make_password_file', autospec=True)
@mock.patch.object(utils, 'execute', autospec=True)
def test__exec_ipmitool_with_dual_bridging(self,
mock_exec, mock_pwf,
mock_support,
mock_sleep):
node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
driver_info=BRIDGE_INFO_DICT)
# when support for dual bridge command is called returns True
mock_support.return_value = True
info = ipmi._parse_driver_info(node)
pw_file_handle = tempfile.NamedTemporaryFile()
pw_file = pw_file_handle.name
file_handle = open(pw_file, "w")
args = [
'ipmitool',
'-I', 'lanplus',
'-H', info['address'],
'-L', info['priv_level'],
'-U', info['username'],
'-m', info['local_address'],
'-B', info['transit_channel'],
'-T', info['transit_address'],
'-b', info['target_channel'],
'-t', info['target_address'],
'-f', file_handle,
'A', 'B', 'C',
]
expected = [mock.call('dual_bridge'),
mock.call('timing')]
# When support for timing command is called returns False
mock_support.return_value = False
mock_pwf.return_value = file_handle
mock_exec.return_value = (None, None)
ipmi._exec_ipmitool(info, 'A B C')
self.assertEqual(expected, mock_support.call_args_list)
self.assertTrue(mock_pwf.called)
mock_exec.assert_called_once_with(*args)
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(ipmi, '_make_password_file', autospec=True)
@mock.patch.object(utils, 'execute', autospec=True)
def test__exec_ipmitool_with_single_bridging(self,
mock_exec, mock_pwf,
mock_support,
mock_sleep):
single_bridge_info = dict(BRIDGE_INFO_DICT)
single_bridge_info['ipmi_bridging'] = 'single'
node = obj_utils.get_test_node(self.context, driver='fake_ipmitool',
driver_info=single_bridge_info)
# when support for single bridge command is called returns True
mock_support.return_value = True
info = ipmi._parse_driver_info(node)
info['transit_channel'] = info['transit_address'] = None
pw_file_handle = tempfile.NamedTemporaryFile()
pw_file = pw_file_handle.name
file_handle = open(pw_file, "w")
args = [
'ipmitool',
'-I', 'lanplus',
'-H', info['address'],
'-L', info['priv_level'],
'-U', info['username'],
'-m', info['local_address'],
'-b', info['target_channel'],
'-t', info['target_address'],
'-f', file_handle,
'A', 'B', 'C',
]
expected = [mock.call('single_bridge'),
mock.call('timing')]
# When support for timing command is called returns False
mock_support.return_value = False
mock_pwf.return_value = file_handle
mock_exec.return_value = (None, None)
ipmi._exec_ipmitool(info, 'A B C')
self.assertEqual(expected, mock_support.call_args_list)
self.assertTrue(mock_pwf.called)
mock_exec.assert_called_once_with(*args)
@mock.patch.object(ipmi, '_is_option_supported')
@mock.patch.object(ipmi, '_make_password_file', autospec=True)
@mock.patch.object(utils, 'execute', autospec=True)
def test__exec_ipmitool_exception(self, mock_exec, mock_pwf,
mock_timing_support, mock_sleep):
mock_support, mock_sleep):
pw_file_handle = tempfile.NamedTemporaryFile()
pw_file = pw_file_handle.name
file_handle = open(pw_file, "w")
@ -406,12 +785,13 @@ class IPMIToolPrivateMethodTestCase(base.TestCase):
'A', 'B', 'C',
]
mock_timing_support.return_value = False
mock_support.return_value = False
mock_pwf.return_value = file_handle
mock_exec.side_effect = processutils.ProcessExecutionError("x")
self.assertRaises(processutils.ProcessExecutionError,
ipmi._exec_ipmitool,
self.info, 'A B C')
mock_support.assert_called_once_with('timing')
mock_pwf.assert_called_once_with(self.info['password'])
mock_exec.assert_called_once_with(*args)

View File

@ -55,6 +55,8 @@ if 'ironic.drivers.modules.seamicro' in sys.modules:
# __init__. We bypass that check in order to run the unit tests, which do not
# depend on 'ipmitool' being on the system.
ipmitool.TIMING_SUPPORT = False
ipmitool.DUAL_BRIDGE_SUPPORT = False
ipmitool.SINGLE_BRIDGE_SUPPORT = False
pyghmi = importutils.try_import("pyghmi")
if not pyghmi: