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 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): def get_temp_url(self, container, obj, timeout):
"""Returns the temp url for the given Swift object. """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 ' 'managed by ironic. Set this to True if your '
'installation of ironic-inspector does not have a ' 'installation of ironic-inspector does not have a '
'separate PXE boot environment.')), '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.i18n import _
from ironic.common import keystone from ironic.common import keystone
from ironic.common import states from ironic.common import states
from ironic.common import swift
from ironic.common import utils from ironic.common import utils
from ironic.conductor import periodics from ironic.conductor import periodics
from ironic.conductor import task_manager from ironic.conductor import task_manager
@ -43,6 +44,7 @@ LOG = logging.getLogger(__name__)
_INSPECTOR_SESSION = None _INSPECTOR_SESSION = None
# Internal field to mark whether ironic or inspector manages boot for the node # Internal field to mark whether ironic or inspector manages boot for the node
_IRONIC_MANAGES_BOOT = 'inspector_manage_boot' _IRONIC_MANAGES_BOOT = 'inspector_manage_boot'
_OBJECT_NAME_PREFIX = 'inspector_data'
def _get_inspector_session(**kwargs): def _get_inspector_session(**kwargs):
@ -365,14 +367,31 @@ def _check_status(task):
_inspection_error_handler(task, error) _inspection_error_handler(task, error)
elif status.is_finished: elif status.is_finished:
_clean_up(task) _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( introspection_data = inspector_client.get_introspection_data(
node.uuid, processed=True) node.uuid, processed=True)
inventory_data = introspection_data.pop("inventory") inventory_data = introspection_data.pop("inventory")
plugin_data = introspection_data plugin_data = introspection_data
node_inventory.NodeInventory( if store_data == 'database':
node_id=node.id, node_inventory.NodeInventory(
inventory_data=inventory_data, node_id=node.id,
plugin_data=plugin_data).create() 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): def _clean_up(task):
@ -387,3 +406,22 @@ def _clean_up(task):
LOG.info('Inspection finished successfully for node %s', LOG.info('Inspection finished successfully for node %s',
task.node.uuid) task.node.uuid)
task.process_event('done') 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 context
from ironic.common import exception from ironic.common import exception
from ironic.common import states from ironic.common import states
from ironic.common import swift
from ironic.common import utils from ironic.common import utils
from ironic.conductor import task_manager from ironic.conductor import task_manager
from ironic.drivers.modules import inspect_utils from ironic.drivers.modules import inspect_utils
@ -553,7 +554,9 @@ class CheckStatusTestCase(BaseTestCase):
self.task) self.task)
self.driver.boot.clean_up_ramdisk.assert_called_once_with(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 = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=True, mock_get.return_value = mock.Mock(is_finished=True,
error=None, error=None,
@ -571,7 +574,47 @@ class CheckStatusTestCase(BaseTestCase):
self.assertEqual({"disks": [{"name": "/dev/vda"}]}, self.assertEqual({"disks": [{"name": "/dev/vda"}]},
stored["plugin_data"]) 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): 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 = mock_client.return_value.get_introspection
mock_get.return_value = mock.Mock(is_finished=True, mock_get.return_value = mock.Mock(is_finished=True,
error='boom', error='boom',
@ -593,4 +636,5 @@ class InspectHardwareAbortTestCase(BaseTestCase):
mock_abort = mock_client.return_value.abort_introspection mock_abort = mock_client.return_value.abort_introspection
mock_abort.side_effect = RuntimeError('boom') mock_abort.side_effect = RuntimeError('boom')
self.assertRaises(RuntimeError, self.iface.abort, self.task) self.assertRaises(RuntimeError, self.iface.abort, self.task)
mock_abort.assert_called_once_with(self.node.uuid) mock_abort.assert_called_once_with(self.node.uuid)