Merge "Use OOB inspection to fetch MACs for IB inspection"
This commit is contained in:
commit
acd4b451dc
@ -1161,6 +1161,17 @@ class ManagementInterface(BaseInterface):
|
|||||||
raise exception.UnsupportedDriverExtension(
|
raise exception.UnsupportedDriverExtension(
|
||||||
driver=task.node.driver, extension='detect_vendor')
|
driver=task.node.driver, extension='detect_vendor')
|
||||||
|
|
||||||
|
def get_mac_addresses(self, task):
|
||||||
|
"""Get MAC address information for the node.
|
||||||
|
|
||||||
|
:param task: A TaskManager instance containing the node to act on.
|
||||||
|
:raises: UnsupportedDriverExtension
|
||||||
|
:returns: A list of MAC addresses for the node
|
||||||
|
|
||||||
|
"""
|
||||||
|
raise exception.UnsupportedDriverExtension(
|
||||||
|
driver=task.node.driver, extension='get_mac_addresses')
|
||||||
|
|
||||||
|
|
||||||
class InspectInterface(BaseInterface):
|
class InspectInterface(BaseInterface):
|
||||||
"""Interface for inspection-related actions."""
|
"""Interface for inspection-related actions."""
|
||||||
|
@ -34,7 +34,7 @@ from ironic.conductor import utils as cond_utils
|
|||||||
from ironic.conf import CONF
|
from ironic.conf import CONF
|
||||||
from ironic.drivers import base
|
from ironic.drivers import base
|
||||||
from ironic.drivers.modules import deploy_utils
|
from ironic.drivers.modules import deploy_utils
|
||||||
|
from ironic.drivers.modules import inspect_utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -243,6 +243,22 @@ class Inspector(base.InspectInterface):
|
|||||||
:returns: states.INSPECTWAIT
|
:returns: states.INSPECTWAIT
|
||||||
:raises: HardwareInspectionFailure on failure
|
:raises: HardwareInspectionFailure on failure
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
|
enabled_macs = task.driver.management.get_mac_addresses(task)
|
||||||
|
if enabled_macs:
|
||||||
|
inspect_utils.create_ports_if_not_exist(
|
||||||
|
task, enabled_macs, get_mac_address=lambda x: x[0])
|
||||||
|
else:
|
||||||
|
LOG.warning("Not attempting to create any port as no NICs "
|
||||||
|
"were discovered in 'enabled' state for node "
|
||||||
|
"%(node)s: %(mac_data)s",
|
||||||
|
{'mac_data': enabled_macs,
|
||||||
|
'node': task.node.uuid})
|
||||||
|
|
||||||
|
except exception.UnsupportedDriverExtension:
|
||||||
|
LOG.info('Pre-creating ports prior to inspection not supported'
|
||||||
|
' on node %s.', task.node.uuid)
|
||||||
|
|
||||||
ironic_manages_boot = _ironic_manages_boot(
|
ironic_manages_boot = _ironic_manages_boot(
|
||||||
task, raise_exc=CONF.inspector.require_managed_boot)
|
task, raise_exc=CONF.inspector.require_managed_boot)
|
||||||
|
|
||||||
|
@ -160,25 +160,15 @@ class RedfishInspect(base.InspectInterface):
|
|||||||
return states.MANAGEABLE
|
return states.MANAGEABLE
|
||||||
|
|
||||||
def _create_ports(self, task, system):
|
def _create_ports(self, task, system):
|
||||||
if (system.ethernet_interfaces
|
enabled_macs = redfish_utils.get_enabled_macs(task, system)
|
||||||
and system.ethernet_interfaces.summary):
|
if enabled_macs:
|
||||||
macs = system.ethernet_interfaces.summary
|
inspect_utils.create_ports_if_not_exist(
|
||||||
|
task, enabled_macs, get_mac_address=lambda x: x[0])
|
||||||
# Create ports for the discovered NICs being in 'enabled' state
|
|
||||||
enabled_macs = {nic_mac: nic_state
|
|
||||||
for nic_mac, nic_state in macs.items()
|
|
||||||
if nic_state == sushy.STATE_ENABLED}
|
|
||||||
if enabled_macs:
|
|
||||||
inspect_utils.create_ports_if_not_exist(
|
|
||||||
task, enabled_macs, get_mac_address=lambda x: x[0])
|
|
||||||
else:
|
|
||||||
LOG.warning("Not attempting to create any port as no NICs "
|
|
||||||
"were discovered in 'enabled' state for node "
|
|
||||||
"%(node)s: %(mac_data)s",
|
|
||||||
{'mac_data': macs, 'node': task.node.uuid})
|
|
||||||
else:
|
else:
|
||||||
LOG.warning("No NIC information discovered "
|
LOG.warning("Not attempting to create any port as no NICs "
|
||||||
"for node %(node)s", {'node': task.node.uuid})
|
"were discovered in 'enabled' state for node "
|
||||||
|
"%(node)s: %(mac_data)s",
|
||||||
|
{'mac_data': enabled_macs, 'node': task.node.uuid})
|
||||||
|
|
||||||
def _detect_local_gb(self, task, system):
|
def _detect_local_gb(self, task, system):
|
||||||
simple_storage_size = 0
|
simple_storage_size = 0
|
||||||
|
@ -1161,3 +1161,22 @@ class RedfishManagement(base.ManagementInterface):
|
|||||||
self._reset_keys(task, sushy.SECURE_BOOT_RESET_KEYS_DELETE_ALL)
|
self._reset_keys(task, sushy.SECURE_BOOT_RESET_KEYS_DELETE_ALL)
|
||||||
LOG.info('Secure boot keys have been removed from node %s',
|
LOG.info('Secure boot keys have been removed from node %s',
|
||||||
task.node.uuid)
|
task.node.uuid)
|
||||||
|
|
||||||
|
def get_mac_addresses(self, task):
|
||||||
|
"""Get MAC address information for the node.
|
||||||
|
|
||||||
|
:param task: A TaskManager instance containing the node to act on.
|
||||||
|
:raises: RedfishConnectionError when it fails to connect to Redfish
|
||||||
|
:raises: RedfishError on an error from the Sushy library
|
||||||
|
:returns: a dictionary containing MAC addresses of enabled interfaces
|
||||||
|
in a {'mac': 'state'} format
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
system = redfish_utils.get_system(task.node)
|
||||||
|
return redfish_utils.get_enabled_macs(task, system)
|
||||||
|
except sushy.exceptions.SushyError as exc:
|
||||||
|
msg = (_('Failed to get network interface information on node '
|
||||||
|
'%(node)s: %(exc)s')
|
||||||
|
% {'node': task.node.uuid, 'exc': exc})
|
||||||
|
LOG.error(msg)
|
||||||
|
raise exception.RedfishError(error=msg)
|
||||||
|
@ -349,3 +349,26 @@ def _get_connection(node, lambda_fun, *args):
|
|||||||
'node %(node)s. Error: %(error)s',
|
'node %(node)s. Error: %(error)s',
|
||||||
{'address': driver_info['address'],
|
{'address': driver_info['address'],
|
||||||
'node': node.uuid, 'error': e})
|
'node': node.uuid, 'error': e})
|
||||||
|
|
||||||
|
|
||||||
|
def get_enabled_macs(task, system):
|
||||||
|
"""Get information on MAC addresses of enabled ports using Redfish.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
|
:param system: a Redfish System object
|
||||||
|
:returns: a dictionary containing MAC addresses of enabled interfaces
|
||||||
|
in a {'mac': 'state'} format
|
||||||
|
"""
|
||||||
|
|
||||||
|
if (system.ethernet_interfaces
|
||||||
|
and system.ethernet_interfaces.summary):
|
||||||
|
macs = system.ethernet_interfaces.summary
|
||||||
|
|
||||||
|
# Identify ports for the NICs being in 'enabled' state
|
||||||
|
enabled_macs = {nic_mac: nic_state
|
||||||
|
for nic_mac, nic_state in macs.items()
|
||||||
|
if nic_state == sushy.STATE_ENABLED}
|
||||||
|
return enabled_macs
|
||||||
|
else:
|
||||||
|
LOG.warning("No NIC information discovered "
|
||||||
|
"for node %(node)s", {'node': task.node.uuid})
|
||||||
|
@ -17,6 +17,7 @@ import datetime
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from oslo_utils import importutils
|
from oslo_utils import importutils
|
||||||
|
from oslo_utils import units
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
from ironic.common import boot_devices
|
||||||
from ironic.common import boot_modes
|
from ironic.common import boot_modes
|
||||||
@ -56,6 +57,28 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
|||||||
self.chassis_uuid = 'XXX-YYY-ZZZ'
|
self.chassis_uuid = 'XXX-YYY-ZZZ'
|
||||||
self.drive_uuid = 'ZZZ-YYY-XXX'
|
self.drive_uuid = 'ZZZ-YYY-XXX'
|
||||||
|
|
||||||
|
def init_system_mock(self, system_mock, **properties):
|
||||||
|
|
||||||
|
system_mock.reset()
|
||||||
|
|
||||||
|
system_mock.boot.mode = 'uefi'
|
||||||
|
|
||||||
|
system_mock.memory_summary.size_gib = 2
|
||||||
|
|
||||||
|
system_mock.processors.summary = '8', 'MIPS'
|
||||||
|
|
||||||
|
system_mock.simple_storage.disks_sizes_bytes = (
|
||||||
|
1 * units.Gi, units.Gi * 3, units.Gi * 5)
|
||||||
|
system_mock.storage.volumes_sizes_bytes = (
|
||||||
|
2 * units.Gi, units.Gi * 4, units.Gi * 6)
|
||||||
|
|
||||||
|
system_mock.ethernet_interfaces.summary = {
|
||||||
|
'00:11:22:33:44:55': sushy.STATE_ENABLED,
|
||||||
|
'66:77:88:99:AA:BB': sushy.STATE_DISABLED,
|
||||||
|
}
|
||||||
|
|
||||||
|
return system_mock
|
||||||
|
|
||||||
@mock.patch.object(redfish_mgmt, 'sushy', None)
|
@mock.patch.object(redfish_mgmt, 'sushy', None)
|
||||||
def test_loading_error(self):
|
def test_loading_error(self):
|
||||||
self.assertRaisesRegex(
|
self.assertRaisesRegex(
|
||||||
@ -1478,3 +1501,25 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
|||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
exception.UnsupportedDriverExtension,
|
exception.UnsupportedDriverExtension,
|
||||||
task.driver.management.clear_secure_boot_keys, task)
|
task.driver.management.clear_secure_boot_keys, task)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
def test_get_mac_addresses_success(self, mock_get_system):
|
||||||
|
expected_properties = {'00:11:22:33:44:55': 'enabled'}
|
||||||
|
|
||||||
|
self.init_system_mock(mock_get_system.return_value)
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertEqual(expected_properties,
|
||||||
|
task.driver.management.get_mac_addresses(task))
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
def test_get_mac_addresses_no_ports_found(self, mock_get_system):
|
||||||
|
expected_properties = None
|
||||||
|
|
||||||
|
system_mock = self.init_system_mock(mock_get_system.return_value)
|
||||||
|
system_mock.ethernet_interfaces.summary = None
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertEqual(expected_properties,
|
||||||
|
task.driver.management.get_mac_addresses(task))
|
||||||
|
@ -20,7 +20,9 @@ from ironic.common import exception
|
|||||||
from ironic.common import states
|
from ironic.common import states
|
||||||
from ironic.common import utils
|
from ironic.common import utils
|
||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
|
from ironic.drivers.modules import inspect_utils
|
||||||
from ironic.drivers.modules import inspector
|
from ironic.drivers.modules import inspector
|
||||||
|
from ironic.drivers.modules.redfish import utils as redfish_utils
|
||||||
from ironic.tests.unit.db import base as db_base
|
from ironic.tests.unit.db import base as db_base
|
||||||
from ironic.tests.unit.objects import utils as obj_utils
|
from ironic.tests.unit.objects import utils as obj_utils
|
||||||
|
|
||||||
@ -71,7 +73,7 @@ class BaseTestCase(db_base.DbTestCase):
|
|||||||
self.task.shared = False
|
self.task.shared = False
|
||||||
self.task.node = self.node
|
self.task.node = self.node
|
||||||
self.task.driver = mock.Mock(
|
self.task.driver = mock.Mock(
|
||||||
spec=['boot', 'network', 'inspect', 'power'],
|
spec=['boot', 'network', 'inspect', 'power', 'management'],
|
||||||
inspect=self.iface)
|
inspect=self.iface)
|
||||||
self.driver = self.task.driver
|
self.driver = self.task.driver
|
||||||
|
|
||||||
@ -126,14 +128,23 @@ class InspectHardwareTestCase(BaseTestCase):
|
|||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
self.iface.validate, self.task)
|
self.iface.validate, self.task)
|
||||||
|
|
||||||
def test_validate_require_managed_boot(self, mock_client):
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
|
||||||
|
autospec=True)
|
||||||
|
def test_validate_require_managed_boot(self, mock_get_system,
|
||||||
|
mock_create_ports_if_not_exist,
|
||||||
|
mock_client):
|
||||||
CONF.set_override('require_managed_boot', True, group='inspector')
|
CONF.set_override('require_managed_boot', True, group='inspector')
|
||||||
self.driver.boot.validate_inspection.side_effect = (
|
self.driver.boot.validate_inspection.side_effect = (
|
||||||
exception.UnsupportedDriverExtension(''))
|
exception.UnsupportedDriverExtension(''))
|
||||||
self.assertRaises(exception.UnsupportedDriverExtension,
|
self.assertRaises(exception.UnsupportedDriverExtension,
|
||||||
self.iface.validate, self.task)
|
self.iface.validate, self.task)
|
||||||
|
|
||||||
def test_unmanaged_ok(self, mock_client):
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
|
||||||
|
autospec=True)
|
||||||
|
def test_unmanaged_ok(self, mock_create_ports_if_not_exist,
|
||||||
|
mock_get_system, mock_client):
|
||||||
self.driver.boot.validate_inspection.side_effect = (
|
self.driver.boot.validate_inspection.side_effect = (
|
||||||
exception.UnsupportedDriverExtension(''))
|
exception.UnsupportedDriverExtension(''))
|
||||||
mock_introspect = mock_client.return_value.start_introspection
|
mock_introspect = mock_client.return_value.start_introspection
|
||||||
@ -148,7 +159,11 @@ class InspectHardwareTestCase(BaseTestCase):
|
|||||||
self.assertFalse(self.driver.power.set_power_state.called)
|
self.assertFalse(self.driver.power.set_power_state.called)
|
||||||
|
|
||||||
@mock.patch.object(task_manager, 'acquire', autospec=True)
|
@mock.patch.object(task_manager, 'acquire', autospec=True)
|
||||||
def test_unmanaged_error(self, mock_acquire, mock_client):
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
|
||||||
|
autospec=True)
|
||||||
|
def test_unmanaged_error(self, mock_create_ports_if_not_exist,
|
||||||
|
mock_get_system, mock_acquire, mock_client):
|
||||||
mock_acquire.return_value.__enter__.return_value = self.task
|
mock_acquire.return_value.__enter__.return_value = self.task
|
||||||
self.driver.boot.validate_inspection.side_effect = (
|
self.driver.boot.validate_inspection.side_effect = (
|
||||||
exception.UnsupportedDriverExtension(''))
|
exception.UnsupportedDriverExtension(''))
|
||||||
@ -164,7 +179,11 @@ class InspectHardwareTestCase(BaseTestCase):
|
|||||||
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
|
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
|
||||||
self.assertFalse(self.driver.power.set_power_state.called)
|
self.assertFalse(self.driver.power.set_power_state.called)
|
||||||
|
|
||||||
def test_require_managed_boot(self, mock_client):
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
|
||||||
|
autospec=True)
|
||||||
|
def test_require_managed_boot(self, mock_create_ports_if_not_exist,
|
||||||
|
mock_get_system, mock_client):
|
||||||
CONF.set_override('require_managed_boot', True, group='inspector')
|
CONF.set_override('require_managed_boot', True, group='inspector')
|
||||||
self.driver.boot.validate_inspection.side_effect = (
|
self.driver.boot.validate_inspection.side_effect = (
|
||||||
exception.UnsupportedDriverExtension(''))
|
exception.UnsupportedDriverExtension(''))
|
||||||
@ -179,7 +198,11 @@ class InspectHardwareTestCase(BaseTestCase):
|
|||||||
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
|
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
|
||||||
self.assertFalse(self.driver.power.set_power_state.called)
|
self.assertFalse(self.driver.power.set_power_state.called)
|
||||||
|
|
||||||
def test_managed_ok(self, mock_client):
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
|
||||||
|
autospec=True)
|
||||||
|
def test_managed_ok(self, mock_create_ports_if_not_exist,
|
||||||
|
mock_get_system, mock_client):
|
||||||
endpoint = 'http://192.169.0.42:5050/v1'
|
endpoint = 'http://192.169.0.42:5050/v1'
|
||||||
mock_client.return_value.get_endpoint.return_value = endpoint
|
mock_client.return_value.get_endpoint.return_value = endpoint
|
||||||
mock_introspect = mock_client.return_value.start_introspection
|
mock_introspect = mock_client.return_value.start_introspection
|
||||||
@ -200,7 +223,12 @@ class InspectHardwareTestCase(BaseTestCase):
|
|||||||
self.assertFalse(self.driver.network.remove_inspection_network.called)
|
self.assertFalse(self.driver.network.remove_inspection_network.called)
|
||||||
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
|
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
|
||||||
|
|
||||||
def test_managed_custom_params(self, mock_client):
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
|
||||||
|
autospec=True)
|
||||||
|
def test_managed_custom_params(self, mock_get_system,
|
||||||
|
mock_create_ports_if_not_exist,
|
||||||
|
mock_client):
|
||||||
CONF.set_override('extra_kernel_params',
|
CONF.set_override('extra_kernel_params',
|
||||||
'ipa-inspection-collectors=default,logs '
|
'ipa-inspection-collectors=default,logs '
|
||||||
'ipa-collect-dhcp=1',
|
'ipa-collect-dhcp=1',
|
||||||
@ -230,7 +258,12 @@ class InspectHardwareTestCase(BaseTestCase):
|
|||||||
|
|
||||||
@mock.patch('ironic.drivers.modules.deploy_utils.get_ironic_api_url',
|
@mock.patch('ironic.drivers.modules.deploy_utils.get_ironic_api_url',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
def test_managed_fast_track(self, mock_ironic_url, mock_client):
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
|
||||||
|
autospec=True)
|
||||||
|
def test_managed_fast_track(self, mock_create_ports_if_not_exist,
|
||||||
|
mock_get_system, mock_ironic_url,
|
||||||
|
mock_client):
|
||||||
CONF.set_override('fast_track', True, group='deploy')
|
CONF.set_override('fast_track', True, group='deploy')
|
||||||
CONF.set_override('extra_kernel_params',
|
CONF.set_override('extra_kernel_params',
|
||||||
'ipa-inspection-collectors=default,logs '
|
'ipa-inspection-collectors=default,logs '
|
||||||
@ -262,7 +295,12 @@ class InspectHardwareTestCase(BaseTestCase):
|
|||||||
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
|
self.assertFalse(self.driver.boot.clean_up_ramdisk.called)
|
||||||
|
|
||||||
@mock.patch.object(task_manager, 'acquire', autospec=True)
|
@mock.patch.object(task_manager, 'acquire', autospec=True)
|
||||||
def test_managed_error(self, mock_acquire, mock_client):
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
|
||||||
|
autospec=True)
|
||||||
|
def test_managed_error(self, mock_get_system,
|
||||||
|
mock_create_ports_if_not_exist, mock_acquire,
|
||||||
|
mock_client):
|
||||||
endpoint = 'http://192.169.0.42:5050/v1'
|
endpoint = 'http://192.169.0.42:5050/v1'
|
||||||
mock_client.return_value.get_endpoint.return_value = endpoint
|
mock_client.return_value.get_endpoint.return_value = endpoint
|
||||||
mock_acquire.return_value.__enter__.return_value = self.task
|
mock_acquire.return_value.__enter__.return_value = self.task
|
||||||
|
@ -831,6 +831,13 @@ class TestManagementInterface(base.TestCase):
|
|||||||
expected, management.get_indicator_state(
|
expected, management.get_indicator_state(
|
||||||
task_mock, components.CHASSIS, 'led-0'))
|
task_mock, components.CHASSIS, 'led-0'))
|
||||||
|
|
||||||
|
def test_get_mac_addresses(self):
|
||||||
|
management = fake.FakeManagement()
|
||||||
|
task_mock = mock.MagicMock(spec_set=['node'])
|
||||||
|
|
||||||
|
self.assertRaises(exception.UnsupportedDriverExtension,
|
||||||
|
management.get_mac_addresses, task_mock)
|
||||||
|
|
||||||
|
|
||||||
class TestBareDriver(base.TestCase):
|
class TestBareDriver(base.TestCase):
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds support for automatic creation of ports for ``redfish`` enabled
|
||||||
|
bare metal nodes using prior to ironic-inspector introspection. This
|
||||||
|
feature is a part of ``redfish`` management interface.
|
Loading…
Reference in New Issue
Block a user