From 61b04cfd38654b13edfcaff253008eb06274247e Mon Sep 17 00:00:00 2001 From: zshi Date: Thu, 12 Oct 2017 15:14:58 +0800 Subject: [PATCH] BIOS Settings: Add DB API Adds the following methods to DB API: * create_bios_setting_list * update_bios_setting_list * delete_bios_setting * get_bios_setting * get_bios_setting_list Adds two exceptions: * BIOSSettingAlreadyExists * BIOSSettingNotFound This patch also deletes BIOS setting records associated with a node when it's destroyed. Co-Authored-By: Yolanda Robla Mota Change-Id: Ibbca0a30baaae1e19e015b4dddbbac0189ff80ba Story: #1712032 --- ironic/common/exception.py | 8 ++ ironic/db/api.py | 83 ++++++++++++++ ironic/db/sqlalchemy/api.py | 71 ++++++++++++ ironic/tests/unit/db/test_bios_settings.py | 123 +++++++++++++++++++++ ironic/tests/unit/db/utils.py | 39 +++++++ 5 files changed, 324 insertions(+) create mode 100644 ironic/tests/unit/db/test_bios_settings.py diff --git a/ironic/common/exception.py b/ironic/common/exception.py index 19cd982048..f33cac85f9 100644 --- a/ironic/common/exception.py +++ b/ironic/common/exception.py @@ -775,3 +775,11 @@ class InstanceUnrescueFailure(IronicException): class XClarityError(IronicException): _msg_fmt = _("XClarity exception occurred. Error: %(error)s") + + +class BIOSSettingAlreadyExists(Conflict): + _msg_fmt = _('A BIOS setting %(name)s for node %(node)s already exists.') + + +class BIOSSettingNotFound(NotFound): + _msg_fmt = _("Node %(node)s doesn't have a BIOS setting '%(name)s'") diff --git a/ironic/db/api.py b/ironic/db/api.py index 273ef6059e..9d9b55d96a 100644 --- a/ironic/db/api.py +++ b/ironic/db/api.py @@ -995,3 +995,86 @@ class Connection(object): :returns: True if the trait exists otherwise False. :raises: NodeNotFound if the node is not found. """ + + @abc.abstractmethod + def create_bios_setting_list(self, node_id, settings, version): + """Create a list of BIOSSetting records for a given node. + + :param node_id: The node id. + :param settings: A list of BIOS Settings to be created. + + :: + + [ + { + 'name': String, + 'value': String, + }, + { + 'name': String, + 'value': String, + }, + ... + ] + :param version: the version of the object.BIOSSetting. + :returns: A list of BIOSSetting object. + :raises: NodeNotFound if the node is not found. + :raises: BIOSSettingAlreadyExists if any of the setting records + already exists. + """ + + @abc.abstractmethod + def update_bios_setting_list(self, node_id, settings, version): + """Update a list of BIOSSetting records. + + :param node_id: The node id. + :param settings: A list of BIOS Settings to be updated. + + :: + + [ + { + 'name': String, + 'value': String, + }, + { + 'name': String, + 'value': String, + }, + ... + ] + :param version: the version of the object.BIOSSetting. + :returns: A list of BIOSSetting objects. + :raises: NodeNotFound if the node is not found. + :raises: BIOSSettingNotFound if any of the settings is not found. + """ + + @abc.abstractmethod + def delete_bios_setting(self, node_id, name): + """Delete BIOS setting. + + :param node_id: The node id. + :param name: String containing name of bios setting to be deleted. + :raises: NodeNotFound if the node is not found. + :raises: BIOSSettingNotFound if the bios setting is not found. + """ + + @abc.abstractmethod + def get_bios_setting(self, node_id, name): + """Retrieve BIOS setting value. + + :param node_id: The node id. + :param name: String containing name of bios setting to be retrieved. + :returns: The BIOSSetting object. + :raises: NodeNotFound if the node is not found. + :raises: BIOSSettingNotFound if the bios setting is not found. + """ + + @abc.abstractmethod + def get_bios_setting_list(self, node_id): + """Retrieve BIOS settings of a given node. + + :param node_id: The node id. + :returns: A list of BIOSSetting objects. + :raises: NodeNotFound if the node is not found. + """ diff --git a/ironic/db/sqlalchemy/api.py b/ironic/db/sqlalchemy/api.py index 7e6eecd272..36f54dbe1f 100644 --- a/ironic/db/sqlalchemy/api.py +++ b/ironic/db/sqlalchemy/api.py @@ -445,6 +445,11 @@ class Connection(api.Connection): models.VolumeTarget).filter_by(node_id=node_id) volume_target_query.delete() + # delete all bios attached to the node + bios_settings_query = model_query( + models.BIOSSetting).filter_by(node_id=node_id) + bios_settings_query.delete() + query.delete() def update_node(self, node_id, values): @@ -1383,3 +1388,69 @@ class Connection(api.Connection): q = model_query( models.NodeTrait).filter_by(node_id=node_id, trait=trait) return model_query(q.exists()).scalar() + + @oslo_db_api.retry_on_deadlock + def create_bios_setting_list(self, node_id, settings, version): + self._check_node_exists(node_id) + bios_settings = [] + with _session_for_write() as session: + try: + for setting in settings: + bios_setting = models.BIOSSetting( + node_id=node_id, + name=setting['name'], + value=setting['value'], + version=version) + bios_settings.append(bios_setting) + session.add(bios_setting) + session.flush() + except db_exc.DBDuplicateEntry: + raise exception.BIOSSettingAlreadyExists( + node=node_id, name=setting['name']) + return bios_settings + + @oslo_db_api.retry_on_deadlock + def update_bios_setting_list(self, node_id, settings, version): + self._check_node_exists(node_id) + bios_settings = [] + with _session_for_write() as session: + try: + for setting in settings: + query = model_query(models.BIOSSetting).filter_by( + node_id=node_id, name=setting['name']) + ref = query.one() + ref.update({'value': setting['value'], + 'version': version}) + bios_settings.append(ref) + session.flush() + except NoResultFound: + raise exception.BIOSSettingNotFound( + node=node_id, name=setting['name']) + return bios_settings + + @oslo_db_api.retry_on_deadlock + def delete_bios_setting(self, node_id, name): + self._check_node_exists(node_id) + with _session_for_write(): + count = model_query(models.BIOSSetting).filter_by( + node_id=node_id, name=name).delete() + if count == 0: + raise exception.BIOSSettingNotFound( + node=node_id, name=name) + + def get_bios_setting(self, node_id, name): + self._check_node_exists(node_id) + query = model_query(models.BIOSSetting).filter_by( + node_id=node_id, name=name) + try: + ref = query.one() + except NoResultFound: + raise exception.BIOSSettingNotFound(node=node_id, name=name) + return ref + + def get_bios_setting_list(self, node_id): + self._check_node_exists(node_id) + result = (model_query(models.BIOSSetting) + .filter_by(node_id=node_id) + .all()) + return result diff --git a/ironic/tests/unit/db/test_bios_settings.py b/ironic/tests/unit/db/test_bios_settings.py new file mode 100644 index 0000000000..5e68146599 --- /dev/null +++ b/ironic/tests/unit/db/test_bios_settings.py @@ -0,0 +1,123 @@ +# 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. + +"""Tests for manipulating BIOSSetting via the DB API""" + +from ironic.common import exception +from ironic.tests.unit.db import base +from ironic.tests.unit.db import utils as db_utils + + +class DbBIOSSettingTestCase(base.DbTestCase): + + def setUp(self): + super(DbBIOSSettingTestCase, self).setUp() + self.node = db_utils.create_test_node() + + def test_get_bios_setting(self): + db_utils.create_test_bios_setting(node_id=self.node.id) + result = self.dbapi.get_bios_setting(self.node.id, 'virtualization') + self.assertEqual(result['node_id'], self.node.id) + self.assertEqual(result['name'], 'virtualization') + self.assertEqual(result['value'], 'on') + self.assertEqual(result['version'], '1.0') + + def test_get_bios_setting_node_not_exist(self): + self.assertRaises(exception.NodeNotFound, + self.dbapi.get_bios_setting, + '456', + 'virtualization') + + def test_get_bios_setting_setting_not_exist(self): + db_utils.create_test_bios_setting(node_id=self.node.id) + self.assertRaises(exception.BIOSSettingNotFound, + self.dbapi.get_bios_setting, + self.node.id, 'bios_name') + + def test_get_bios_setting_list(self): + db_utils.create_test_bios_setting(node_id=self.node.id) + result = self.dbapi.get_bios_setting_list( + node_id=self.node.id) + self.assertEqual(result[0]['node_id'], self.node.id) + self.assertEqual(result[0]['name'], 'virtualization') + self.assertEqual(result[0]['value'], 'on') + self.assertEqual(result[0]['version'], '1.0') + self.assertEqual(len(result), 1) + + def test_get_bios_setting_list_node_not_exist(self): + self.assertRaises(exception.NodeNotFound, + self.dbapi.get_bios_setting_list, + '456') + + def test_delete_bios_setting(self): + db_utils.create_test_bios_setting(node_id=self.node.id) + self.dbapi.delete_bios_setting(self.node.id, 'virtualization') + self.assertRaises(exception.BIOSSettingNotFound, + self.dbapi.get_bios_setting, + self.node.id, 'virtualization') + + def test_delete_bios_setting_node_not_exist(self): + self.assertRaises(exception.NodeNotFound, + self.dbapi.delete_bios_setting, + '456', 'virtualization') + + def test_delete_bios_setting_setting_not_exist(self): + db_utils.create_test_bios_setting(node_id=self.node.id) + self.assertRaises(exception.BIOSSettingNotFound, + self.dbapi.delete_bios_setting, + self.node.id, 'hyperthread') + + def test_create_bios_setting_list(self): + settings = db_utils.get_test_bios_setting_setting_list() + result = self.dbapi.create_bios_setting_list( + self.node.id, settings, '1.0') + self.assertItemsEqual(['virtualization', 'hyperthread', 'numlock'], + [setting.name for setting in result]) + self.assertItemsEqual(['on', 'enabled', 'off'], + [setting.value for setting in result]) + + def test_create_bios_setting_list_duplicate(self): + settings = db_utils.get_test_bios_setting_setting_list() + self.dbapi.create_bios_setting_list(self.node.id, settings, '1.0') + self.assertRaises(exception.BIOSSettingAlreadyExists, + self.dbapi.create_bios_setting_list, + self.node.id, settings, '1.0') + + def test_create_bios_setting_list_node_not_exist(self): + self.assertRaises(exception.NodeNotFound, + self.dbapi.create_bios_setting_list, + '456', [], '1.0') + + def test_update_bios_setting_list(self): + settings = db_utils.get_test_bios_setting_setting_list() + self.dbapi.create_bios_setting_list(self.node.id, settings, '1.0') + settings = [{'name': 'virtualization', 'value': 'off'}, + {'name': 'hyperthread', 'value': 'disabled'}, + {'name': 'numlock', 'value': 'on'}] + result = self.dbapi.update_bios_setting_list( + self.node.id, settings, '1.0') + self.assertItemsEqual(['off', 'disabled', 'on'], + [setting.value for setting in result]) + + def test_update_bios_setting_list_setting_not_exist(self): + settings = db_utils.get_test_bios_setting_setting_list() + self.dbapi.create_bios_setting_list(self.node.id, settings, '1.0') + for setting in settings: + setting['name'] = 'bios_name' + self.assertRaises(exception.BIOSSettingNotFound, + self.dbapi.update_bios_setting_list, + self.node.id, settings, '1.0') + + def test_update_bios_setting_list_node_not_exist(self): + self.assertRaises(exception.NodeNotFound, + self.dbapi.update_bios_setting_list, + '456', [], '1.0') diff --git a/ironic/tests/unit/db/utils.py b/ironic/tests/unit/db/utils.py index 5aea3fccd8..57d4563425 100644 --- a/ironic/tests/unit/db/utils.py +++ b/ironic/tests/unit/db/utils.py @@ -544,3 +544,42 @@ def create_test_node_traits(traits, **kw): :returns: a list of test NodeTrait DB objects. """ return [create_test_node_trait(trait=trait, **kw) for trait in traits] + + +def create_test_bios_setting(**kw): + """Create test bios entry in DB and return BIOSSetting DB object. + + Function to be used to create test BIOSSetting object in the database. + + :param kw: kwargs with overriding values for node bios settings. + :returns: Test BIOSSetting DB object. + + """ + bios_setting = get_test_bios_setting(**kw) + dbapi = db_api.get_instance() + node_id = bios_setting['node_id'] + version = bios_setting['version'] + settings = [{'name': bios_setting['name'], + 'value': bios_setting['value']}] + return dbapi.create_bios_setting_list(node_id, settings, version)[0] + + +def get_test_bios_setting(**kw): + return { + 'node_id': kw.get('node_id', '123'), + 'name': kw.get('name', 'virtualization'), + 'value': kw.get('value', 'on'), + # TODO(zshi) change default version to + # bios_setting.BIOSSetting.VERSION + 'version': kw.get('version', '1.0'), + 'created_at': kw.get('created_at'), + 'updated_at': kw.get('updated_at'), + } + + +def get_test_bios_setting_setting_list(): + return [ + {'name': 'virtualization', 'value': 'on'}, + {'name': 'hyperthread', 'value': 'enabled'}, + {'name': 'numlock', 'value': 'off'} + ]