diff --git a/README.rst b/README.rst index 05aa96ba7..63bbee662 100644 --- a/README.rst +++ b/README.rst @@ -308,6 +308,9 @@ Here are some plugins that can be additionally enabled: reports error, if ``error`` field is set by the ramdisk. ``example`` example plugin logging it's input and output. +``root_device_hint`` + gathers block devices from ramdisk and exposes root device in multiple + runs. Refer to CONTRIBUTING.rst_ for information on how to write your own plugin. @@ -343,6 +346,8 @@ See `1.1.0 release tracking page`_ for details. See `eDeploy blueprint`_ for details. +* Plugin ``root_device_hint`` for in-band root device discovery. + **Known Issues** .. _1.1.0 release tracking page: https://bugs.launchpad.net/ironic-discoverd/+milestone/1.1.0 diff --git a/ironic_discoverd/plugins/root_device_hint.py b/ironic_discoverd/plugins/root_device_hint.py new file mode 100644 index 000000000..260be32e4 --- /dev/null +++ b/ironic_discoverd/plugins/root_device_hint.py @@ -0,0 +1,76 @@ +# 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. + +"""Gather root device hint from recognized block devices.""" + +import logging + +from ironic_discoverd.plugins import base + + +LOG = logging.getLogger('ironic_discoverd.plugins.root_device_hint') + + +class RootDeviceHintHook(base.ProcessingHook): + """Interact with Instack ramdisk for discovery data processing hooks. + + The plugin can figure out the root device in 2 runs. First, it saves the + discovered block device serials in node.extra. The second run will check + the difference between the recently discovered block devices and the + previously saved ones. After saving the root device in node.properties, it + will delete the temporarily saved block device serials in node.extra. + + This way, it helps to figure out the root device hint in cases when + otherwise Ironic doesn't have enough information to do so. Such a usecase + is DRAC RAID configuration where the BMC doesn't provide any useful + information about the created RAID disks. Using this plugin immediately + before and after creating the root RAID device will solve the issue of root + device hints. + """ + + def before_update(self, node, ports, node_info): + if 'root_device' in node.properties: + LOG.info('Root device is already known for the node.') + return [], {} + + if 'block_devices' in node.extra: + # Compare previously discovered devices with the current ones + previous_devices = node.extra['block_devices']['serials'] + current_devices = node_info['block_devices']['serials'] + 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.') + return [], {} + elif len(new_devices) == 0: + LOG.warning('No new devices were found.') + return [], {} + + return [ + {'op': 'remove', + 'path': '/extra/block_devices'}, + {'op': 'add', + 'path': '/properties/root_device', + 'value': {'serial': new_devices[0]}} + ], {} + + else: + # No previously discovered devices - save the discoverd block + # devices in node.extra + return [ + {'op': 'add', + 'path': '/extra/block_devices', + 'value': node_info['block_devices']} + ], {} diff --git a/ironic_discoverd/test/test_plugins_root_device_hint.py b/ironic_discoverd/test/test_plugins_root_device_hint.py new file mode 100644 index 000000000..91ea750ec --- /dev/null +++ b/ironic_discoverd/test/test_plugins_root_device_hint.py @@ -0,0 +1,74 @@ +# 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_discoverd.plugins import root_device_hint +from ironic_discoverd.test import base as test_base + + +class TestRootDeviceHint(test_base.NodeTest): + + def setUp(self): + super(TestRootDeviceHint, self).setUp() + self.hook = root_device_hint.RootDeviceHintHook() + + def test_no_previous_block_devices(self): + node_info = {'block_devices': {'serials': ['foo', 'bar']}} + node_patches, _ = self.hook.before_update(self.node, None, node_info) + + self.assertEqual('add', + node_patches[0]['op']) + self.assertEqual('/extra/block_devices', + node_patches[0]['path']) + self.assertEqual(node_info['block_devices'], + node_patches[0]['value']) + + def test_root_device_found(self): + self.node.extra['block_devices'] = {'serials': ['foo', 'bar']} + node_info = {'block_devices': {'serials': ['foo', 'baz']}} + self.hook.before_processing(node_info) + node_patches, _ = self.hook.before_update(self.node, None, node_info) + + self.assertEqual('remove', + node_patches[0]['op']) + self.assertEqual('/extra/block_devices', + node_patches[0]['path']) + self.assertEqual('add', + node_patches[1]['op']) + self.assertEqual('/properties/root_device', + node_patches[1]['path']) + self.assertEqual({'serial': 'baz'}, + node_patches[1]['value']) + + def test_root_device_already_exposed(self): + self.node.properties['root_device'] = {'serial': 'foo'} + node_info = {'block_devices': {'serials': ['foo', 'baz']}} + self.hook.before_processing(node_info) + node_patches, _ = self.hook.before_update(self.node, None, node_info) + + self.assertEqual(0, len(node_patches)) + + def test_multiple_new_devices(self): + self.node.extra['block_devices'] = {'serials': ['foo', 'bar']} + node_info = {'block_devices': {'serials': ['foo', 'baz', 'qux']}} + self.hook.before_processing(node_info) + node_patches, _ = self.hook.before_update(self.node, None, node_info) + + self.assertEqual(0, len(node_patches)) + + def test_no_new_devices(self): + self.node.extra['block_devices'] = {'serials': ['foo', 'bar']} + node_info = {'block_devices': {'serials': ['foo', 'bar']}} + self.hook.before_processing(node_info) + node_patches, _ = self.hook.before_update(self.node, None, node_info) + + self.assertEqual(0, len(node_patches)) diff --git a/setup.py b/setup.py index 62c609276..47965e17d 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ setup( "ramdisk_error = ironic_discoverd.plugins.standard:RamdiskErrorHook", "example = ironic_discoverd.plugins.example:ExampleProcessingHook", "edeploy = ironic_discoverd.plugins.edeploy:eDeployHook", + "root_device_hint = ironic_discoverd.plugins.root_device_hint:RootDeviceHintHook", ], }, classifiers = [