From 7aac631fbc14b5629890b09b62b1c5269c0f7c82 Mon Sep 17 00:00:00 2001 From: Lucas Alvares Gomes Date: Fri, 22 Jul 2016 15:42:16 +0100 Subject: [PATCH] Add parse_root_device_hints to utils.py This patch is adding a function called parse_root_device_hints to the utils.py module. This function is responsible for parsing the root device hints dictionary from the node's properties attribute. Both Ironic and Ironic Python Agent project have similar functions so adding it to ironic-lib would make it easier to share code between both projects and fix bugs in only one place. Change-Id: Ida6d20d1fdb40e50fe33ffec1c953286d4cbc2b7 Partial-Bug: #1605631 --- ironic_lib/tests/test_utils.py | 49 +++++++++++++++++++++++++++ ironic_lib/utils.py | 62 ++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/ironic_lib/tests/test_utils.py b/ironic_lib/tests/test_utils.py index f07e508..b8d1aa2 100644 --- a/ironic_lib/tests/test_utils.py +++ b/ironic_lib/tests/test_utils.py @@ -279,3 +279,52 @@ class IsHttpUrlTestCase(test_base.BaseTestCase): self.assertTrue(utils.is_http_url('HTTPS://127.3.2.1')) self.assertFalse(utils.is_http_url('Zm9vYmFy')) self.assertFalse(utils.is_http_url('11111111')) + + +class ParseRootDeviceTestCase(test_base.BaseTestCase): + + def setUp(self): + super(ParseRootDeviceTestCase, self).setUp() + self.root_device = { + 'wwn': '123456', 'model': 'foo-model', 'size': 12345, + 'serial': 'foo-serial', 'vendor': 'foo-vendor', 'name': '/dev/sda', + 'wwn_with_extension': '123456111', 'wwn_vendor_extension': '111', + 'rotational': True} + + def test_parse_root_device_hints(self): + result = utils.parse_root_device_hints(self.root_device) + self.assertEqual(self.root_device, result) + + def test_parse_root_device_hints_no_hints(self): + result = utils.parse_root_device_hints({}) + self.assertIsNone(result) + + def test_parse_root_device_hints_convert_size(self): + result = utils.parse_root_device_hints({'size': '12345'}) + self.assertEqual({'size': 12345}, result) + + def test_parse_root_device_hints_invalid_size(self): + for value in ('not-int', -123, 0): + self.assertRaises(ValueError, utils.parse_root_device_hints, + {'size': value}) + + def _parse_root_device_hints_convert_rotational(self, values, + expected_value): + for value in values: + result = utils.parse_root_device_hints({'rotational': value}) + self.assertEqual({'rotational': expected_value}, result) + + def test_parse_root_device_hints_convert_rotational(self): + self._parse_root_device_hints_convert_rotational( + (True, 'true', 'on', 'y', 'yes'), True) + + self._parse_root_device_hints_convert_rotational( + (False, 'false', 'off', 'n', 'no'), False) + + def test_parse_root_device_hints_invalid_rotational(self): + self.assertRaises(ValueError, utils.parse_root_device_hints, + {'rotational': 'not-bool'}) + + def test_parse_root_device_hints_non_existent_hint(self): + self.assertRaises(ValueError, utils.parse_root_device_hints, + {'non-existent': 'foo'}) diff --git a/ironic_lib/utils.py b/ironic_lib/utils.py index d731d22..9f32631 100644 --- a/ironic_lib/utils.py +++ b/ironic_lib/utils.py @@ -18,6 +18,7 @@ """Utilities and helper functions.""" +import copy import errno import logging import os @@ -25,7 +26,9 @@ import os from oslo_concurrency import processutils from oslo_config import cfg from oslo_utils import excutils +from oslo_utils import strutils +from ironic_lib.common.i18n import _ from ironic_lib.common.i18n import _LE from ironic_lib.common.i18n import _LW from ironic_lib import exception @@ -43,6 +46,11 @@ CONF.register_opts(utils_opts, group='ironic_lib') LOG = logging.getLogger(__name__) +VALID_ROOT_DEVICE_HINTS = set(('size', 'model', 'wwn', 'serial', 'vendor', + 'wwn_with_extension', 'wwn_vendor_extension', + 'name', 'rotational')) + + def execute(*cmd, **kwargs): """Convenience wrapper around oslo's execute() method. @@ -159,3 +167,57 @@ def is_http_url(url): def list_opts(): """Entry point for oslo-config-generator.""" return [('ironic_lib', utils_opts)] + + +def parse_root_device_hints(root_device): + """Parse the root_device property of a node. + + Parses and validates the root_device property of a node. These are + hints for how a node's root device is created. The 'size' hint + should be a positive integer. The 'rotational' hint should be a + Boolean value. + + :param root_device: the root_device dictionary from the node's property. + :returns: a dictionary with the root device hints parsed or + None if there are no hints. + :raises: ValueError, if some information is invalid. + + """ + if not root_device: + return + + root_device = copy.deepcopy(root_device) + + invalid_hints = set(root_device) - VALID_ROOT_DEVICE_HINTS + if invalid_hints: + raise ValueError( + _('The hints "%(invalid_hints)s" are invalid. ' + 'Valid hints are: "%(valid_hints)s"') % + {'invalid_hints': ', '.join(invalid_hints), + 'valid_hints': ', '.join(VALID_ROOT_DEVICE_HINTS)}) + + if 'size' in root_device: + try: + size = int(root_device['size']) + except ValueError: + raise ValueError( + _('Root device hint "size" is not an integer value. ' + 'Current value: %s') % root_device['size']) + + if size <= 0: + raise ValueError( + _('Root device hint "size" should be a positive integer. ' + 'Current value: %d') % size) + + root_device['size'] = size + + if 'rotational' in root_device: + try: + root_device['rotational'] = strutils.bool_from_string( + root_device['rotational'], strict=True) + except ValueError: + raise ValueError( + _('Root device hint "rotational" is not a Boolean value. ' + 'Current value: %s') % root_device['rotational']) + + return root_device