Merge "Discover IPv6 BMC address"
This commit is contained in:
commit
55e3266c89
@ -18,6 +18,7 @@ import functools
|
||||
import json
|
||||
from multiprocessing.pool import ThreadPool
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import time
|
||||
|
||||
@ -32,6 +33,7 @@ import psutil
|
||||
import pyudev
|
||||
import six
|
||||
import stevedore
|
||||
import yaml
|
||||
|
||||
from ironic_python_agent import encoding
|
||||
from ironic_python_agent import errors
|
||||
@ -380,6 +382,9 @@ class HardwareManager(object):
|
||||
def get_bmc_address(self):
|
||||
raise errors.IncompatibleHardwareMethodError()
|
||||
|
||||
def get_bmc_v6address(self):
|
||||
raise errors.IncompatibleHardwareMethodError()
|
||||
|
||||
def get_boot_info(self):
|
||||
raise errors.IncompatibleHardwareMethodError()
|
||||
|
||||
@ -493,6 +498,7 @@ class HardwareManager(object):
|
||||
hardware_info['disks'] = self.list_block_devices()
|
||||
hardware_info['memory'] = self.get_memory()
|
||||
hardware_info['bmc_address'] = self.get_bmc_address()
|
||||
hardware_info['bmc_v6address'] = self.get_bmc_v6address()
|
||||
hardware_info['system_vendor'] = self.get_system_vendor_info()
|
||||
hardware_info['boot'] = self.get_boot_info()
|
||||
return hardware_info
|
||||
@ -1138,6 +1144,76 @@ class GenericHardwareManager(HardwareManager):
|
||||
|
||||
return '0.0.0.0'
|
||||
|
||||
def get_bmc_v6address(self):
|
||||
"""Attempt to detect BMC v6 address
|
||||
|
||||
:return: IPv6 address of lan channel or ::/0 in case none of them is
|
||||
configured properly. May return None value if it cannot
|
||||
interract with system tools or critical error occurs.
|
||||
"""
|
||||
# These modules are rarely loaded automatically
|
||||
utils.try_execute('modprobe', 'ipmi_msghandler')
|
||||
utils.try_execute('modprobe', 'ipmi_devintf')
|
||||
utils.try_execute('modprobe', 'ipmi_si')
|
||||
|
||||
null_address_re = re.compile(r'^::(/\d{1,3})*$')
|
||||
|
||||
def get_addr(channel, dynamic=False):
|
||||
cmd = "ipmitool lan6 print {} {}_addr".format(
|
||||
channel, 'dynamic' if dynamic else 'static')
|
||||
try:
|
||||
out, e = utils.execute(cmd, shell=True)
|
||||
except processutils.ProcessExecutionError:
|
||||
return
|
||||
|
||||
# NOTE: More likely ipmitool was not intended to return
|
||||
# stdout in yaml format. Fortunately, output of
|
||||
# dynamic_addr and static_addr commands is a valid yaml.
|
||||
try:
|
||||
out = yaml.safe_load(out.strip())
|
||||
except yaml.YAMLError as e:
|
||||
LOG.warning('Cannot process output of "%(cmd)s" '
|
||||
'command: %(e)s', {'cmd': cmd, 'e': e})
|
||||
return
|
||||
|
||||
for addr_dict in out.values():
|
||||
address = addr_dict['Address']
|
||||
if dynamic:
|
||||
enabled = addr_dict['Source/Type'] in ['DHCPv6', 'SLAAC']
|
||||
else:
|
||||
enabled = addr_dict['Enabled']
|
||||
|
||||
if addr_dict['Status'] == 'active' and enabled \
|
||||
and not null_address_re.match(address):
|
||||
return address
|
||||
|
||||
try:
|
||||
# From all the channels 0-15, only 1-7 can be assigned to different
|
||||
# types of communication media and protocols and effectively used
|
||||
for channel in range(1, 8):
|
||||
addr_mode, e = utils.execute(
|
||||
r"ipmitool lan6 print {} enables | "
|
||||
r"awk '/IPv6\/IPv4 Addressing Enables[ \t]*:/"
|
||||
r"{{print $NF}}'".format(channel), shell=True)
|
||||
if addr_mode.strip() not in ['ipv6', 'both']:
|
||||
continue
|
||||
|
||||
address = get_addr(channel, dynamic=True) or get_addr(channel)
|
||||
if not address:
|
||||
continue
|
||||
|
||||
try:
|
||||
return str(netaddr.IPNetwork(address).ip)
|
||||
except netaddr.AddrFormatError:
|
||||
LOG.warning('Invalid IP address: %s', address)
|
||||
continue
|
||||
except (processutils.ProcessExecutionError, OSError) as e:
|
||||
# Not error, because it's normal in virtual environment
|
||||
LOG.warning("Cannot get BMC v6 address: %s", e)
|
||||
return
|
||||
|
||||
return '::/0'
|
||||
|
||||
def get_clean_steps(self, node, ports):
|
||||
return [
|
||||
{
|
||||
|
@ -392,6 +392,37 @@ ATA Security is: Unavailable
|
||||
""") # noqa
|
||||
|
||||
|
||||
IPMITOOL_LAN6_PRINT_DYNAMIC_ADDR = """
|
||||
IPv6 Dynamic Address 0:
|
||||
Source/Type: DHCPv6
|
||||
Address: 2001:1234:1234:1234:1234:1234:1234:1234/64
|
||||
Status: active
|
||||
IPv6 Dynamic Address 1:
|
||||
Source/Type: DHCPv6
|
||||
Address: ::/0
|
||||
Status: active
|
||||
IPv6 Dynamic Address 2:
|
||||
Source/Type: DHCPv6
|
||||
Address: ::/0
|
||||
Status: active
|
||||
"""
|
||||
|
||||
IPMITOOL_LAN6_PRINT_STATIC_ADDR = """
|
||||
IPv6 Static Address 0:
|
||||
Enabled: yes
|
||||
Address: 2001:5678:5678:5678:5678:5678:5678:5678/64
|
||||
Status: active
|
||||
IPv6 Static Address 1:
|
||||
Enabled: no
|
||||
Address: ::/0
|
||||
Status: disabled
|
||||
IPv6 Static Address 2:
|
||||
Enabled: no
|
||||
Address: ::/0
|
||||
Status: disabled
|
||||
"""
|
||||
|
||||
|
||||
class FakeHardwareManager(hardware.GenericHardwareManager):
|
||||
def __init__(self, hardware_support):
|
||||
self._hardware_support = hardware_support
|
||||
@ -1126,6 +1157,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
||||
current_boot_mode='bios', pxe_interface='boot:if')
|
||||
|
||||
self.hardware.get_bmc_address = mock.Mock()
|
||||
self.hardware.get_bmc_v6address = mock.Mock()
|
||||
self.hardware.get_system_vendor_info = mock.Mock()
|
||||
|
||||
hardware_info = self.hardware.list_hardware_info()
|
||||
@ -2085,6 +2117,101 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
||||
mocked_execute.return_value = '', ''
|
||||
self.assertEqual('0.0.0.0', self.hardware.get_bmc_address())
|
||||
|
||||
@mock.patch.object(utils, 'try_execute', autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_get_bmc_v6address_not_enabled(self, mocked_execute, mte):
|
||||
mocked_execute.side_effect = [('ipv4\n', '')] * 7
|
||||
self.assertEqual('::/0', self.hardware.get_bmc_v6address())
|
||||
|
||||
@mock.patch.object(utils, 'try_execute', autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_get_bmc_v6address_dynamic_address(self, mocked_execute, mte):
|
||||
mocked_execute.side_effect = [
|
||||
('ipv6\n', ''),
|
||||
(IPMITOOL_LAN6_PRINT_DYNAMIC_ADDR, '')
|
||||
]
|
||||
self.assertEqual('2001:1234:1234:1234:1234:1234:1234:1234',
|
||||
self.hardware.get_bmc_v6address())
|
||||
|
||||
@mock.patch.object(utils, 'try_execute', autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_get_bmc_v6address_static_address_both(self, mocked_execute, mte):
|
||||
dynamic_disabled = \
|
||||
IPMITOOL_LAN6_PRINT_DYNAMIC_ADDR.replace('active', 'disabled')
|
||||
mocked_execute.side_effect = [
|
||||
('both\n', ''),
|
||||
(dynamic_disabled, ''),
|
||||
(IPMITOOL_LAN6_PRINT_STATIC_ADDR, '')
|
||||
]
|
||||
self.assertEqual('2001:5678:5678:5678:5678:5678:5678:5678',
|
||||
self.hardware.get_bmc_v6address())
|
||||
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_get_bmc_v6address_virt(self, mocked_execute):
|
||||
mocked_execute.side_effect = processutils.ProcessExecutionError()
|
||||
self.assertIsNone(self.hardware.get_bmc_v6address())
|
||||
|
||||
@mock.patch.object(utils, 'try_execute', autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_get_bmc_v6address_invalid_enables(self, mocked_execute, mte):
|
||||
def side_effect(*args, **kwargs):
|
||||
if args[0].startswith('ipmitool lan6 print'):
|
||||
return '', 'Failed to get IPv6/IPv4 Addressing Enables'
|
||||
|
||||
mocked_execute.side_effect = side_effect
|
||||
self.assertEqual('::/0', self.hardware.get_bmc_v6address())
|
||||
|
||||
@mock.patch.object(utils, 'try_execute', autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_get_bmc_v6address_invalid_get_address(self, mocked_execute, mte):
|
||||
def side_effect(*args, **kwargs):
|
||||
if args[0].startswith('ipmitool lan6 print'):
|
||||
if args[0].endswith('dynamic_addr') \
|
||||
or args[0].endswith('static_addr'):
|
||||
raise processutils.ProcessExecutionError()
|
||||
return 'ipv6', ''
|
||||
|
||||
mocked_execute.side_effect = side_effect
|
||||
self.assertEqual('::/0', self.hardware.get_bmc_v6address())
|
||||
|
||||
@mock.patch.object(hardware, 'LOG', autospec=True)
|
||||
@mock.patch.object(utils, 'try_execute', autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_get_bmc_v6address_impitool_invalid_stdout_format(
|
||||
self, mocked_execute, mte, mocked_log):
|
||||
def side_effect(*args, **kwargs):
|
||||
if args[0].startswith('ipmitool lan6 print'):
|
||||
if args[0].endswith('dynamic_addr') \
|
||||
or args[0].endswith('static_addr'):
|
||||
return 'Invalid\n\tyaml', ''
|
||||
return 'ipv6', ''
|
||||
|
||||
mocked_execute.side_effect = side_effect
|
||||
self.assertEqual('::/0', self.hardware.get_bmc_v6address())
|
||||
one_call = mock.call('Cannot process output of "%(cmd)s" '
|
||||
'command: %(e)s', mock.ANY)
|
||||
mocked_log.warning.assert_has_calls([one_call] * 14)
|
||||
|
||||
@mock.patch.object(utils, 'try_execute', autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_get_bmc_v6address_channel_7(self, mocked_execute, mte):
|
||||
def side_effect(*args, **kwargs):
|
||||
if not args[0].startswith('ipmitool lan6 print 7'):
|
||||
# ipv6 is not enabled for channels 1-6
|
||||
if 'enables |' in args[0]:
|
||||
return '', ''
|
||||
else:
|
||||
if 'enables |' in args[0]:
|
||||
return 'ipv6', ''
|
||||
if args[0].endswith('dynamic_addr'):
|
||||
raise processutils.ProcessExecutionError()
|
||||
elif args[0].endswith('static_addr'):
|
||||
return IPMITOOL_LAN6_PRINT_STATIC_ADDR, ''
|
||||
|
||||
mocked_execute.side_effect = side_effect
|
||||
self.assertEqual('2001:5678:5678:5678:5678:5678:5678:5678',
|
||||
self.hardware.get_bmc_v6address())
|
||||
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_get_system_vendor_info(self, mocked_execute):
|
||||
mocked_execute.return_value = LSHW_JSON_OUTPUT
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Discover IPv6 BMC address and store it in "bmc_v6address"
|
||||
field of hardware inventory sent back to inspector.
|
Loading…
Reference in New Issue
Block a user