Merge "Enable alternative storage for inventory"
This commit is contained in:
commit
7be9046021
@ -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.
|
||||||
|
|
||||||
|
@ -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.')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user