summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2023-01-06 12:00:17 +0000
committerGerrit Code Review <review@openstack.org>2023-01-06 12:00:17 +0000
commit7be9046021301b56c524eb7f91dd74cff0645571 (patch)
tree1d5efddaabf25efa69e2a48b9a96fa57f7034460
parentbed680951a55d2a0782c1e9abda6cd954045a577 (diff)
parent973d3b6ea671b15583925eaa93ea988d16734499 (diff)
downloadironic-7be9046021301b56c524eb7f91dd74cff0645571.tar.gz
Merge "Enable alternative storage for inventory"
-rw-r--r--ironic/common/swift.py25
-rw-r--r--ironic/conf/inspector.py11
-rw-r--r--ironic/drivers/modules/inspector.py46
-rw-r--r--ironic/tests/unit/drivers/modules/test_inspector.py46
4 files changed, 123 insertions, 5 deletions
diff --git a/ironic/common/swift.py b/ironic/common/swift.py
index 8a98c32d2..dde94fb18 100644
--- a/ironic/common/swift.py
+++ b/ironic/common/swift.py
@@ -111,6 +111,31 @@ class SwiftAPI(object):
return obj_uuid
+ def create_object_from_data(self, object, data, container):
+ """Uploads a given string to Swift.
+
+ :param object: The name of the object in Swift
+ :param data: string data to put in the object
+ :param container: The name of the container for the object.
+ Defaults to the value set in the configuration options.
+ :returns: The Swift UUID of the object
+ :raises: utils.Error, if any operation with Swift fails.
+ """
+ try:
+ self.connection.put_container(container)
+ except swift_exceptions.ClientException as e:
+ operation = _("put container")
+ raise exception.SwiftOperationError(operation=operation, error=e)
+
+ try:
+ obj_uuid = self.connection.create_object(
+ container, object, data=data)
+ except swift_exceptions.ClientException as e:
+ operation = _("put object")
+ raise exception.SwiftOperationError(operation=operation, error=e)
+
+ return obj_uuid
+
def get_temp_url(self, container, obj, timeout):
"""Returns the temp url for the given Swift object.
diff --git a/ironic/conf/inspector.py b/ironic/conf/inspector.py
index a7f89c994..bee6a3e67 100644
--- a/ironic/conf/inspector.py
+++ b/ironic/conf/inspector.py
@@ -39,6 +39,17 @@ opts = [
'managed by ironic. Set this to True if your '
'installation of ironic-inspector does not have a '
'separate PXE boot environment.')),
+ cfg.StrOpt('inventory_data_backend',
+ help=_('The storage backend for storing introspection data.'),
+ choices=[('none', _('introspection data will not be stored')),
+ ('database', _('introspection data stored in an SQL '
+ 'database')),
+ ('swift', _('introspection data stored in Swift'))],
+ default='database'),
+ cfg.StrOpt('swift_inventory_data_container',
+ default='introspection_data_container',
+ help=_('The Swift introspection data container to store '
+ 'the inventory data.')),
]
diff --git a/ironic/drivers/modules/inspector.py b/ironic/drivers/modules/inspector.py
index 45f3a87f5..20911cbaa 100644
--- a/ironic/drivers/modules/inspector.py
+++ b/ironic/drivers/modules/inspector.py
@@ -28,6 +28,7 @@ from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import keystone
from ironic.common import states
+from ironic.common import swift
from ironic.common import utils
from ironic.conductor import periodics
from ironic.conductor import task_manager
@@ -43,6 +44,7 @@ LOG = logging.getLogger(__name__)
_INSPECTOR_SESSION = None
# Internal field to mark whether ironic or inspector manages boot for the node
_IRONIC_MANAGES_BOOT = 'inspector_manage_boot'
+_OBJECT_NAME_PREFIX = 'inspector_data'
def _get_inspector_session(**kwargs):
@@ -365,14 +367,31 @@ def _check_status(task):
_inspection_error_handler(task, error)
elif status.is_finished:
_clean_up(task)
+ # If store_data == 'none', do not store the data
+ store_data = CONF.inspector.inventory_data_backend
+ if store_data == 'none':
+ LOG.debug('Introspection data storage is disabled, the data will '
+ 'not be saved for node %(node)s', {'node': node.uuid})
+ return
introspection_data = inspector_client.get_introspection_data(
node.uuid, processed=True)
inventory_data = introspection_data.pop("inventory")
plugin_data = introspection_data
- node_inventory.NodeInventory(
- node_id=node.id,
- inventory_data=inventory_data,
- plugin_data=plugin_data).create()
+ if store_data == 'database':
+ node_inventory.NodeInventory(
+ node_id=node.id,
+ inventory_data=inventory_data,
+ plugin_data=plugin_data).create()
+ LOG.info('Introspection data was stored in database for node '
+ '%(node)s', {'node': node.uuid})
+ if store_data == 'swift':
+ swift_object_name = store_introspection_data(
+ node_uuid=node.uuid,
+ inventory_data=inventory_data,
+ plugin_data=plugin_data)
+ LOG.info('Introspection data was stored for node %(node)s in Swift'
+ ' object %(obj_name)s-inventory and %(obj_name)s-plugin',
+ {'node': node.uuid, 'obj_name': swift_object_name})
def _clean_up(task):
@@ -387,3 +406,22 @@ def _clean_up(task):
LOG.info('Inspection finished successfully for node %s',
task.node.uuid)
task.process_event('done')
+
+
+def store_introspection_data(node_uuid, inventory_data, plugin_data):
+ """Uploads introspection data to Swift.
+
+ :param data: data to store in Swift
+ :param node_id: ID of the Ironic node that the data came from
+ :returns: name of the Swift object that the data is stored in
+ """
+ swift_api = swift.SwiftAPI()
+ swift_object_name = '%s-%s' % (_OBJECT_NAME_PREFIX, node_uuid)
+ container = CONF.inspector.swift_inventory_data_container
+ swift_api.create_object_from_data(swift_object_name + '-inventory',
+ inventory_data,
+ container)
+ swift_api.create_object_from_data(swift_object_name + '-plugin',
+ plugin_data,
+ container)
+ return swift_object_name
diff --git a/ironic/tests/unit/drivers/modules/test_inspector.py b/ironic/tests/unit/drivers/modules/test_inspector.py
index 5eb702e41..00de10189 100644
--- a/ironic/tests/unit/drivers/modules/test_inspector.py
+++ b/ironic/tests/unit/drivers/modules/test_inspector.py
@@ -19,6 +19,7 @@ import openstack
from ironic.common import context
from ironic.common import exception
from ironic.common import states
+from ironic.common import swift
from ironic.common import utils
from ironic.conductor import task_manager
from ironic.drivers.modules import inspect_utils
@@ -553,7 +554,9 @@ class CheckStatusTestCase(BaseTestCase):
self.task)
self.driver.boot.clean_up_ramdisk.assert_called_once_with(self.task)
- def test_status_ok_store_inventory(self, mock_client):
+ def test_status_ok_store_inventory_in_db(self, mock_client):
+ CONF.set_override('inventory_data_backend', 'database',
+ group='inspector')
mock_get = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=True,
error=None,
@@ -571,7 +574,47 @@ class CheckStatusTestCase(BaseTestCase):
self.assertEqual({"disks": [{"name": "/dev/vda"}]},
stored["plugin_data"])
+ @mock.patch.object(swift, 'SwiftAPI', autospec=True)
+ def test_status_ok_store_inventory_in_swift(self,
+ swift_api_mock, mock_client):
+ CONF.set_override('inventory_data_backend', 'swift', group='inspector')
+ CONF.set_override(
+ 'swift_inventory_data_container', 'introspection_data',
+ group='inspector')
+ mock_get = mock_client.return_value.get_introspection
+ mock_get.return_value = mock.Mock(is_finished=True,
+ error=None,
+ spec=['is_finished', 'error'])
+ mock_get_data = mock_client.return_value.get_introspection_data
+ fake_inventory_data = {"cpu": "amd"}
+ fake_plugin_data = {"disks": [{"name": "/dev/vda"}]}
+ mock_get_data.return_value = {
+ "inventory": fake_inventory_data, **fake_plugin_data}
+ swift_obj_mock = swift_api_mock.return_value
+ object_name = 'inspector_data-' + str(self.node.uuid)
+ inspector._check_status(self.task)
+ mock_get.assert_called_once_with(self.node.uuid)
+ mock_get_data.assert_called_once_with(self.node.uuid, processed=True)
+ container = 'introspection_data'
+ swift_obj_mock.create_object_from_data.assert_has_calls([
+ mock.call(object_name + '-inventory', fake_inventory_data,
+ container),
+ mock.call(object_name + '-plugin', fake_plugin_data, container)])
+
+ def test_status_ok_store_inventory_nostore(self, mock_client):
+ CONF.set_override('inventory_data_backend', 'none', group='inspector')
+ mock_get = mock_client.return_value.get_introspection
+ mock_get.return_value = mock.Mock(is_finished=True,
+ error=None,
+ spec=['is_finished', 'error'])
+ mock_get_data = mock_client.return_value.get_introspection_data
+ inspector._check_status(self.task)
+ mock_get.assert_called_once_with(self.node.uuid)
+ mock_get_data.assert_not_called()
+
def test_status_error_dont_store_inventory(self, mock_client):
+ CONF.set_override('inventory_data_backend', 'database',
+ group='inspector')
mock_get = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=True,
error='boom',
@@ -593,4 +636,5 @@ class InspectHardwareAbortTestCase(BaseTestCase):
mock_abort = mock_client.return_value.abort_introspection
mock_abort.side_effect = RuntimeError('boom')
self.assertRaises(RuntimeError, self.iface.abort, self.task)
+
mock_abort.assert_called_once_with(self.node.uuid)