Merge "Enable alternative storage for inventory"

This commit is contained in:
Zuul 2023-01-06 12:00:17 +00:00 committed by Gerrit Code Review
commit 7be9046021
4 changed files with 123 additions and 5 deletions

View File

@ -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.

View File

@ -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.')),
]

View File

@ -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

View File

@ -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)