Merge "Add inspection hooks"
This commit is contained in:
@@ -128,7 +128,14 @@ opts = [
|
|||||||
help=_('Mapping of IP subnet CIDR to physical network. When '
|
help=_('Mapping of IP subnet CIDR to physical network. When '
|
||||||
'the phyical-network inspection hook is enabled, the '
|
'the phyical-network inspection hook is enabled, the '
|
||||||
'"physical_network" property of corresponding '
|
'"physical_network" property of corresponding '
|
||||||
'baremetal ports is populated based on this mapping.'))
|
'baremetal ports is populated based on this mapping.')),
|
||||||
|
cfg.BoolOpt('disk_partitioning_spacing',
|
||||||
|
default=True,
|
||||||
|
help=_('Whether to leave 1 GiB of disk size untouched for '
|
||||||
|
'partitioning. Only has effect when used with the IPA '
|
||||||
|
'as a ramdisk, for older ramdisk local_gb is '
|
||||||
|
'calculated on the ramdisk side. This configuration '
|
||||||
|
'option is used by the "root-device" inspection hook.'))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
84
ironic/drivers/modules/inspector/hooks/raid_device.py
Normal file
84
ironic/drivers/modules/inspector/hooks/raid_device.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.drivers.modules.inspector.hooks import base
|
||||||
|
from ironic.objects import node_inventory
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RaidDeviceHook(base.InspectionHook):
|
||||||
|
"""Hook for learning the root device after RAID creation.
|
||||||
|
|
||||||
|
This hook can figure out the root device in 2 runs. In the first run, the
|
||||||
|
node's inventory is saved as usual, and the hook does not do anything.
|
||||||
|
The second run will check the difference between the recently discovered
|
||||||
|
block devices (as reported by the inspection results) and the previously
|
||||||
|
saved ones (from the previously saved inventory). If there is exactly one
|
||||||
|
new block device, its serial number is saved in node.properties under the
|
||||||
|
'root_device' key.
|
||||||
|
|
||||||
|
This way, it helps to figure out the root device hint in cases when Ironic
|
||||||
|
doesn't have enough information to do so otherwise. One such usecase is
|
||||||
|
DRAC RAID configuration, where the BMC doesn't provide any useful
|
||||||
|
information about the created RAID disks. Using this hook immediately
|
||||||
|
before and after creating the root RAID device will solve the issue of
|
||||||
|
root device hints.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _get_serials(self, inventory):
|
||||||
|
if inventory.get('disks'):
|
||||||
|
return [x['serial'] for x in inventory.get('disks')
|
||||||
|
if x.get('serial')]
|
||||||
|
|
||||||
|
def __call__(self, task, inventory, plugin_data):
|
||||||
|
node = task.node
|
||||||
|
|
||||||
|
if 'root_device' in node.properties:
|
||||||
|
LOG.info('Root device is already known for node %s', node.uuid)
|
||||||
|
return
|
||||||
|
|
||||||
|
current_devices = self._get_serials(inventory)
|
||||||
|
if not current_devices:
|
||||||
|
LOG.warning('No block device information was received from the '
|
||||||
|
'ramdisk for node %s', node.uuid)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
previous_inventory = node_inventory.NodeInventory.get_by_node_id(
|
||||||
|
task.context, node.id)
|
||||||
|
except exception.NodeInventoryNotFound:
|
||||||
|
LOG.debug('Inventory for node %s not found in the database. Raid '
|
||||||
|
'device hook exiting.', task.node.uuid)
|
||||||
|
return
|
||||||
|
previous_devices = self._get_serials(previous_inventory.get(
|
||||||
|
'inventory_data'))
|
||||||
|
|
||||||
|
# Compare previously discovered devices with the current ones
|
||||||
|
new_devices = [device for device in current_devices
|
||||||
|
if device not in previous_devices]
|
||||||
|
if len(new_devices) > 1:
|
||||||
|
LOG.warning('Root device cannot be identified because multiple '
|
||||||
|
'new devices were found for node %s', node.uuid)
|
||||||
|
return
|
||||||
|
elif len(new_devices) == 0:
|
||||||
|
LOG.warning('No new devices were found for node %s', node.uuid)
|
||||||
|
return
|
||||||
|
|
||||||
|
node.set_property('root_device', {'serial': new_devices[0]})
|
||||||
|
node.save()
|
||||||
|
LOG.info('"root_device" property set for node %s', node.uuid)
|
109
ironic/drivers/modules/inspector/hooks/root_device.py
Normal file
109
ironic/drivers/modules/inspector/hooks/root_device.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from ironic_lib import utils as il_utils
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import units
|
||||||
|
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.common.i18n import _
|
||||||
|
from ironic.drivers.modules.inspector.hooks import base
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RootDeviceHook(base.InspectionHook):
|
||||||
|
"""Smarter root disk selection using Ironic root device hints."""
|
||||||
|
|
||||||
|
def _get_skip_list_for_node(self, node, block_devices):
|
||||||
|
skip_list_hints = node.properties.get("skip_block_devices", [])
|
||||||
|
if not skip_list_hints:
|
||||||
|
return
|
||||||
|
skip_list = set()
|
||||||
|
|
||||||
|
for hint in skip_list_hints:
|
||||||
|
skipped_devs = il_utils.find_devices_by_hints(block_devices, hint)
|
||||||
|
excluded_devs = {dev['name'] for dev in skipped_devs}
|
||||||
|
skipped_devices = excluded_devs.difference(skip_list)
|
||||||
|
skip_list = skip_list.union(excluded_devs)
|
||||||
|
|
||||||
|
if skipped_devices:
|
||||||
|
LOG.warning("Using hint %(hint)s skipping devices: %(devs)s",
|
||||||
|
{'hint': hint, 'devs': ','.join(skipped_devices)})
|
||||||
|
return skip_list
|
||||||
|
|
||||||
|
def _process_root_device_hints(self, node, inventory, plugin_data):
|
||||||
|
"""Detect root disk from root device hints and IPA inventory."""
|
||||||
|
|
||||||
|
hints = node.properties.get('root_device')
|
||||||
|
if not hints:
|
||||||
|
LOG.debug('Root device hints are not provided for node %s',
|
||||||
|
node.uuid)
|
||||||
|
return
|
||||||
|
|
||||||
|
skip_list = self._get_skip_list_for_node(node, inventory['disks'])
|
||||||
|
if skip_list:
|
||||||
|
inventory_disks = [d for d in inventory['disks']
|
||||||
|
if d['name'] not in skip_list]
|
||||||
|
else:
|
||||||
|
inventory_disks = inventory['disks']
|
||||||
|
|
||||||
|
try:
|
||||||
|
root_device = il_utils.match_root_device_hints(inventory_disks,
|
||||||
|
hints)
|
||||||
|
except (TypeError, ValueError) as e:
|
||||||
|
raise exception.HardwareInspectionFailure(
|
||||||
|
_('No disks could be found using root device hints %(hints)s '
|
||||||
|
'for node %(node)s because they failed to validate. '
|
||||||
|
'Error: %(error)s') % {'hints': hints, 'node': node.uuid,
|
||||||
|
'error': e})
|
||||||
|
if not root_device:
|
||||||
|
raise exception.HardwareInspectionFailure(_(
|
||||||
|
'No disks satisfied root device hints for node %s') %
|
||||||
|
node.uuid)
|
||||||
|
|
||||||
|
LOG.debug('Disk %(disk)s of size %(size)s satisfies root device '
|
||||||
|
'hints. Node: %s', {'disk': root_device.get('name'),
|
||||||
|
'size': root_device['size'],
|
||||||
|
'node': node.uuid})
|
||||||
|
plugin_data['root_disk'] = root_device
|
||||||
|
|
||||||
|
def __call__(self, task, inventory, plugin_data):
|
||||||
|
"""Process root disk information."""
|
||||||
|
self._process_root_device_hints(task.node, inventory, plugin_data)
|
||||||
|
|
||||||
|
root_disk = plugin_data.get('root_disk')
|
||||||
|
if root_disk:
|
||||||
|
local_gb = root_disk['size'] // units.Gi
|
||||||
|
if not local_gb:
|
||||||
|
LOG.warning('The requested root disk is too small (smaller '
|
||||||
|
'than 1 GiB) or its size cannot be detected. '
|
||||||
|
'Root disk: %s, Node: %s', root_disk,
|
||||||
|
task.node.uuid)
|
||||||
|
else:
|
||||||
|
if CONF.inspector.disk_partitioning_spacing:
|
||||||
|
local_gb -= 1
|
||||||
|
LOG.info('Root disk %(disk)s, local_gb %(local_gb)s GiB, '
|
||||||
|
'Node: %(node)s', {'disk': root_disk,
|
||||||
|
'local_gb': local_gb,
|
||||||
|
'node': task.node.uuid})
|
||||||
|
else:
|
||||||
|
local_gb = 0
|
||||||
|
LOG.info('No root device found for node %s. Assuming node is '
|
||||||
|
'diskless.', task.node.uuid)
|
||||||
|
|
||||||
|
plugin_data['local_gb'] = local_gb
|
||||||
|
task.node.set_property('local_gb', str(local_gb))
|
||||||
|
task.node.save()
|
@@ -0,0 +1,107 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.conductor import task_manager
|
||||||
|
from ironic.conf import CONF
|
||||||
|
from ironic.drivers.modules.inspector.hooks import raid_device \
|
||||||
|
as raid_device_hook
|
||||||
|
from ironic.objects.node_inventory import NodeInventory
|
||||||
|
from ironic.tests.unit.db import base as db_base
|
||||||
|
from ironic.tests.unit.objects import utils as obj_utils
|
||||||
|
|
||||||
|
|
||||||
|
class RaidDeviceTestCase(db_base.DbTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
CONF.set_override('enabled_inspect_interfaces',
|
||||||
|
['agent', 'no-inspect'])
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
inspect_interface='agent')
|
||||||
|
self.inventory_1 = {'disks': [{'name': '/dev/sda', 'serial': '1111'},
|
||||||
|
{'name': '/dev/sdb', 'serial': '2222'}]}
|
||||||
|
self.inventory_2 = {'disks': [{'name': '/dev/sdb', 'serial': '2222'},
|
||||||
|
{'name': '/dev/sdc', 'serial': '3333'}]}
|
||||||
|
self.plugin_data = {'plugin_data': 'fake-plugin-data'}
|
||||||
|
|
||||||
|
def test_root_device_already_set(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
self.node.properties = {'root_device': 'any'}
|
||||||
|
raid_device_hook.RaidDeviceHook().__call__(task,
|
||||||
|
self.inventory_1,
|
||||||
|
self.plugin_data)
|
||||||
|
self.assertEqual(self.node.properties.get('root_device'), 'any')
|
||||||
|
|
||||||
|
def test_no_serials(self):
|
||||||
|
self.inventory_1['disks'][0]['serial'] = None
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
raid_device_hook.RaidDeviceHook().__call__(task,
|
||||||
|
self.inventory_1,
|
||||||
|
self.plugin_data)
|
||||||
|
self.node.refresh()
|
||||||
|
self.assertIsNone(self.node.properties.get('root_device'))
|
||||||
|
|
||||||
|
@mock.patch.object(NodeInventory, 'get_by_node_id', autospec=True)
|
||||||
|
def test_no_previous_inventory(self, mock_get_by_node_id):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
mock_get_by_node_id.side_effect = exception.NodeInventoryNotFound()
|
||||||
|
raid_device_hook.RaidDeviceHook().__call__(task,
|
||||||
|
self.inventory_1,
|
||||||
|
self.plugin_data)
|
||||||
|
self.node.refresh()
|
||||||
|
self.assertIsNone(self.node.properties.get('root_device'))
|
||||||
|
|
||||||
|
@mock.patch.object(NodeInventory, 'get_by_node_id', autospec=True)
|
||||||
|
def test_no_new_root_devices(self, mock_get_by_node_id):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
|
||||||
|
mock_get_by_node_id.return_value = NodeInventory(
|
||||||
|
task.context, id=1, node_id=self.node.id,
|
||||||
|
inventory_data=self.inventory_1, plugin_data=self.plugin_data)
|
||||||
|
|
||||||
|
raid_device_hook.RaidDeviceHook().__call__(task,
|
||||||
|
self.inventory_1,
|
||||||
|
self.plugin_data)
|
||||||
|
self.node.refresh()
|
||||||
|
result = self.node.properties.get('root_device')
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
@mock.patch.object(NodeInventory, 'get_by_node_id', autospec=True)
|
||||||
|
def test_root_device_found(self, mock_get_by_node_id):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
mock_get_by_node_id.return_value = NodeInventory(
|
||||||
|
task.context, id=1, node_id=self.node.id,
|
||||||
|
inventory_data=self.inventory_1, plugin_data=self.plugin_data)
|
||||||
|
raid_device_hook.RaidDeviceHook().__call__(task,
|
||||||
|
self.inventory_2,
|
||||||
|
self.plugin_data)
|
||||||
|
self.node.refresh()
|
||||||
|
result = self.node.properties.get('root_device')
|
||||||
|
self.assertEqual(result, {'serial': '3333'})
|
||||||
|
|
||||||
|
@mock.patch.object(NodeInventory, 'get_by_node_id', autospec=True)
|
||||||
|
def test_multiple_new_root_devices(self, mock_get_by_node_id):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
mock_get_by_node_id.return_value = NodeInventory(
|
||||||
|
task.context, id=1, node_id=self.node.id,
|
||||||
|
inventory_data=self.inventory_1, plugin_data=self.plugin_data)
|
||||||
|
self.inventory_2['disks'].append({'name': '/dev/sdd',
|
||||||
|
'serial': '4444'})
|
||||||
|
raid_device_hook.RaidDeviceHook().__call__(task,
|
||||||
|
self.inventory_2,
|
||||||
|
self.plugin_data)
|
||||||
|
self.node.refresh()
|
||||||
|
result = self.node.properties.get('root_device')
|
||||||
|
self.assertIsNone(result)
|
@@ -0,0 +1,178 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_utils import units
|
||||||
|
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.conductor import task_manager
|
||||||
|
from ironic.conf import CONF
|
||||||
|
from ironic.drivers.modules.inspector.hooks import root_device as \
|
||||||
|
root_device_hook
|
||||||
|
from ironic.tests.unit.db import base as db_base
|
||||||
|
from ironic.tests.unit.objects import utils as obj_utils
|
||||||
|
|
||||||
|
|
||||||
|
class RootDeviceTestCase(db_base.DbTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
CONF.set_override('enabled_inspect_interfaces',
|
||||||
|
['agent', 'no-inspect'])
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
inspect_interface='agent')
|
||||||
|
self.inventory = {'disks': [
|
||||||
|
{'name': '/dev/sda', 'model': 'Model 1', 'size': 1000 * units.Gi,
|
||||||
|
'serial': '1111'},
|
||||||
|
{'name': '/dev/sdb', 'model': 'Model 2', 'size': 10 * units.Gi,
|
||||||
|
'serial': '2222'},
|
||||||
|
{'name': '/dev/sdc', 'model': 'Model 1', 'size': 20 * units.Gi,
|
||||||
|
'serial': '3333'},
|
||||||
|
{'name': '/dev/sdd', 'model': 'Model 3', 'size': 0,
|
||||||
|
'serial': '4444'}]}
|
||||||
|
self.plugin_data = {}
|
||||||
|
|
||||||
|
def test_no_hints(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
root_device_hook.RootDeviceHook().__call__(task,
|
||||||
|
self.inventory,
|
||||||
|
self.plugin_data)
|
||||||
|
self.assertNotIn('root_disk', self.plugin_data)
|
||||||
|
self.assertEqual(self.plugin_data['local_gb'], 0)
|
||||||
|
self.assertEqual(task.node.properties.get('local_gb'), '0')
|
||||||
|
|
||||||
|
def test_root_device_skip_list(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
task.node.properties['root_device'] = {'serial': '1111'}
|
||||||
|
task.node.properties['skip_block_devices'] = [{'size': 1000}]
|
||||||
|
|
||||||
|
self.assertRaisesRegex(exception.HardwareInspectionFailure,
|
||||||
|
'No disks satisfied root device hints for '
|
||||||
|
'node %s' % self.node.id,
|
||||||
|
root_device_hook.RootDeviceHook().__call__,
|
||||||
|
task, self.inventory, self.plugin_data)
|
||||||
|
self.assertNotIn('root_disk', self.plugin_data)
|
||||||
|
self.assertNotIn('local_gb', self.plugin_data)
|
||||||
|
# The default value of the `local_gb` property is left unchanged
|
||||||
|
self.assertEqual(task.node.properties.get('local_gb'), '10')
|
||||||
|
|
||||||
|
def test_first_match_on_skip_list_use_second(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
task.node.properties['root_device'] = {'model': 'Model 1'}
|
||||||
|
task.node.properties['skip_block_devices'] = [{'size': 1000}]
|
||||||
|
root_device_hook.RootDeviceHook().__call__(task,
|
||||||
|
self.inventory,
|
||||||
|
self.plugin_data)
|
||||||
|
task.node.refresh()
|
||||||
|
|
||||||
|
expected_root_device = self.inventory['disks'][2].copy()
|
||||||
|
self.assertEqual(self.plugin_data['root_disk'],
|
||||||
|
expected_root_device)
|
||||||
|
|
||||||
|
expected_local_gb = (expected_root_device['size'] // units.Gi) - 1
|
||||||
|
self.assertEqual(self.plugin_data['local_gb'],
|
||||||
|
expected_local_gb)
|
||||||
|
self.assertEqual(task.node.properties.get('local_gb'),
|
||||||
|
str(expected_local_gb))
|
||||||
|
|
||||||
|
def test_one_matches(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
task.node.properties['root_device'] = {'serial': '1111'}
|
||||||
|
|
||||||
|
root_device_hook.RootDeviceHook().__call__(task,
|
||||||
|
self.inventory,
|
||||||
|
self.plugin_data)
|
||||||
|
task.node.refresh()
|
||||||
|
|
||||||
|
self.assertEqual(self.plugin_data['root_disk'],
|
||||||
|
self.inventory['disks'][0])
|
||||||
|
self.assertEqual(self.plugin_data['local_gb'], 999)
|
||||||
|
self.assertEqual(task.node.properties.get('local_gb'), '999')
|
||||||
|
|
||||||
|
def test_local_gb_without_spacing(self):
|
||||||
|
CONF.set_override('disk_partitioning_spacing', False, 'inspector')
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
task.node.properties['root_device'] = {'serial': '1111'}
|
||||||
|
root_device_hook.RootDeviceHook().__call__(task,
|
||||||
|
self.inventory,
|
||||||
|
self.plugin_data)
|
||||||
|
task.node.refresh()
|
||||||
|
|
||||||
|
self.assertEqual(self.plugin_data['root_disk'],
|
||||||
|
self.inventory['disks'][0])
|
||||||
|
self.assertEqual(self.plugin_data['local_gb'], 1000)
|
||||||
|
self.assertEqual(task.node.properties.get('local_gb'), '1000')
|
||||||
|
|
||||||
|
def test_zero_size(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
task.node.properties['root_device'] = {'name': '/dev/sdd'}
|
||||||
|
root_device_hook.RootDeviceHook().__call__(task,
|
||||||
|
self.inventory,
|
||||||
|
self.plugin_data)
|
||||||
|
task.node.refresh()
|
||||||
|
self.assertEqual(self.plugin_data['root_disk'],
|
||||||
|
self.inventory['disks'][3])
|
||||||
|
self.assertEqual(self.plugin_data['local_gb'], 0)
|
||||||
|
self.assertEqual(task.node.properties.get('local_gb'), '0')
|
||||||
|
|
||||||
|
def test_all_match(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
task.node.properties['root_device'] = {'size': 20,
|
||||||
|
'model': 'Model 1'}
|
||||||
|
root_device_hook.RootDeviceHook().__call__(task,
|
||||||
|
self.inventory,
|
||||||
|
self.plugin_data)
|
||||||
|
task.node.refresh()
|
||||||
|
self.assertEqual(self.plugin_data['root_disk'],
|
||||||
|
self.inventory['disks'][2])
|
||||||
|
self.assertEqual(self.plugin_data['local_gb'], 19)
|
||||||
|
self.assertEqual(task.node.properties.get('local_gb'), '19')
|
||||||
|
|
||||||
|
def test_incorrect_hint(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
task.node.properties['root_device'] = {'size': 20,
|
||||||
|
'model': 'Model 42'}
|
||||||
|
self.assertRaisesRegex(exception.HardwareInspectionFailure,
|
||||||
|
'No disks satisfied root device hints for '
|
||||||
|
'node %s' % task.node.uuid,
|
||||||
|
root_device_hook.RootDeviceHook().__call__,
|
||||||
|
task, self.inventory, self.plugin_data)
|
||||||
|
self.assertNotIn('root_disk', self.plugin_data)
|
||||||
|
self.assertNotIn('local_gb', self.plugin_data)
|
||||||
|
# The default value of the `local_gb` property is unchanged
|
||||||
|
self.assertEqual(task.node.properties.get('local_gb'), '10')
|
||||||
|
|
||||||
|
def test_size_string(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
task.node.properties['root_device'] = {'size': '20'}
|
||||||
|
root_device_hook.RootDeviceHook().__call__(task,
|
||||||
|
self.inventory,
|
||||||
|
self.plugin_data)
|
||||||
|
task.node.refresh()
|
||||||
|
self.assertEqual(self.plugin_data['root_disk'],
|
||||||
|
self.inventory['disks'][2])
|
||||||
|
self.assertEqual(self.plugin_data['local_gb'], 19)
|
||||||
|
self.assertEqual(task.node.properties.get('local_gb'), '19')
|
||||||
|
|
||||||
|
def test_size_invalid(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
|
for bad_size in ('foo', None, {}):
|
||||||
|
task.node.properties['root_device'] = {'size': bad_size}
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
exception.HardwareInspectionFailure,
|
||||||
|
'No disks could be found using root device hints',
|
||||||
|
root_device_hook.RootDeviceHook().__call__,
|
||||||
|
task, self.inventory, self.plugin_data)
|
||||||
|
|
||||||
|
self.assertNotIn('root_disk', self.plugin_data)
|
||||||
|
self.assertNotIn('local_gb', self.plugin_data)
|
||||||
|
# The default value of the `local_gb` property is left
|
||||||
|
# unchanged
|
||||||
|
self.assertEqual(task.node.properties.get('local_gb'), '10')
|
@@ -209,6 +209,8 @@ ironic.inspection.hooks =
|
|||||||
memory = ironic.drivers.modules.inspector.hooks.memory:MemoryHook
|
memory = ironic.drivers.modules.inspector.hooks.memory:MemoryHook
|
||||||
pci-devices = ironic.drivers.modules.inspector.hooks.pci_devices:PciDevicesHook
|
pci-devices = ironic.drivers.modules.inspector.hooks.pci_devices:PciDevicesHook
|
||||||
physical-network = ironic.drivers.modules.inspector.hooks.physical_network:PhysicalNetworkHook
|
physical-network = ironic.drivers.modules.inspector.hooks.physical_network:PhysicalNetworkHook
|
||||||
|
raid-device = ironic.drivers.modules.inspector.hooks.raid_device:RaidDeviceHook
|
||||||
|
root-device = ironic.drivers.modules.inspector.hooks.root_device:RootDeviceHook
|
||||||
|
|
||||||
[egg_info]
|
[egg_info]
|
||||||
tag_build =
|
tag_build =
|
||||||
|
Reference in New Issue
Block a user