diff --git a/api-ref/source/introspection-api-v1-introspection-management.inc b/api-ref/source/introspection-api-v1-introspection-management.inc index 69acb7aac..dd5bee9e1 100644 --- a/api-ref/source/introspection-api-v1-introspection-management.inc +++ b/api-ref/source/introspection-api-v1-introspection-management.inc @@ -70,6 +70,48 @@ The response will contain introspection data in the form of json string. :language: javascript +Get Unprocessed Introspection data +================================== + +.. rest_method:: GET /v1/introspection/{node_id}/data/unprocessed + +Return stored raw (unprocessed) data from introspection. + +.. versionadded:: 1.17 + Unprocessed introspection data can now be retrieved. + +.. note:: + We do not provide any backward compatibility guarantees regarding the + format and contents of the stored data. Notably, it depends on the ramdisk + used and plugins enabled both in the ramdisk and in inspector itself. + +Normal response codes: 200 + +Error codes: + +* 400 - bad request +* 401, 403 - missing or invalid authentication +* 404 - data cannot be found or data storage not configured + +Request +------- + +.. rest_parameters:: parameters.yaml + + - node_id: node_id + + +Response +-------- + +The response will contain introspection data in the form of json string. + +**Example JSON representation of an introspection data:** + +.. literalinclude:: samples/api-v1-data-introspection-response.json + :language: javascript + + Reapply Introspection on data ============================= diff --git a/doc/source/user/http-api.rst b/doc/source/user/http-api.rst index 0ee140f7f..2def507bb 100644 --- a/doc/source/user/http-api.rst +++ b/doc/source/user/http-api.rst @@ -134,6 +134,32 @@ details about the inventory key, refer to the :ironic-python-agent-doc:`ironic-python-agent documentation `. +.. note:: + We do not provide any backward compatibility guarantees regarding the + format and contents of the stored data, other than the ``inventory``. + Notably, it depends on the ramdisk + used and plugins enabled both in the ramdisk and in inspector itself. + +Get Unprocessed Introspection Data +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``GET /v1/introspection//data/unprocessed`` get raw (unprocessed) data +from introspection. + +Requires X-Auth-Token header with Keystone token for authentication. + +Response: + +* 200 - OK +* 400 - bad request +* 401, 403 - missing or invalid authentication +* 404 - data cannot be found or data storage not configured + +Response body: JSON dictionary with introspection data. For more +details about the inventory key, refer to the +:ironic-python-agent-doc:`ironic-python-agent documentation +`. + .. note:: We do not provide any backward compatibility guarantees regarding the format and contents of the stored data, other than the ``inventory``. @@ -403,3 +429,4 @@ Version History in the actions of introspection rules. * **1.15** allows reapply with provided introspection data from request. * **1.16** adds ``scope`` field to introspection rule. +* **1.17** adds ``GET /v1/introspection//data/unprocessed``. diff --git a/ironic_inspector/main.py b/ironic_inspector/main.py index 054186e32..edb282438 100644 --- a/ironic_inspector/main.py +++ b/ironic_inspector/main.py @@ -43,7 +43,7 @@ _wsgi_app = _app.wsgi_app LOG = utils.getProcessingLogger(__name__) MINIMUM_API_VERSION = (1, 0) -CURRENT_API_VERSION = (1, 16) +CURRENT_API_VERSION = (1, 17) DEFAULT_API_VERSION = CURRENT_API_VERSION _LOGGING_EXCLUDED_KEYS = ('logs',) @@ -386,14 +386,12 @@ def api_introspection_abort(node_id): return _generate_empty_response(202) -@api('/v1/introspection//data', rule="introspection:data", - methods=['GET']) -def api_introspection_data(node_id): +def _get_data(node_id, processed): try: if not uuidutils.is_uuid_like(node_id): node = ir_utils.get_node(node_id, fields=['uuid']) node_id = node.uuid - res = process.get_introspection_data(node_id) + res = process.get_introspection_data(node_id, processed=processed) return res, 200, {'Content-Type': 'application/json'} except utils.IntrospectionDataStoreDisabled: return error_response(_('Inspector is not configured to store data. ' @@ -402,6 +400,18 @@ def api_introspection_data(node_id): code=404) +@api('/v1/introspection//data', rule="introspection:data", + methods=['GET']) +def api_introspection_data(node_id): + return _get_data(node_id, True) + + +@api('/v1/introspection//data/unprocessed', rule="introspection:data", + methods=['GET']) +def api_introspection_unprocessed_data(node_id): + return _get_data(node_id, False) + + @api('/v1/introspection//data/unprocessed', rule="introspection:reapply", methods=['POST']) def api_introspection_reapply(node_id): diff --git a/ironic_inspector/test/functional.py b/ironic_inspector/test/functional.py index 38d3d55e2..f794995fd 100644 --- a/ironic_inspector/test/functional.py +++ b/ironic_inspector/test/functional.py @@ -177,8 +177,9 @@ class Base(base.NodeTest): def call_get_status(self, uuid, **kwargs): return self.call('get', '/v1/introspection/%s' % uuid, **kwargs).json() - def call_get_data(self, uuid, **kwargs): - return self.call('get', '/v1/introspection/%s/data' % uuid, + def call_get_data(self, uuid, processed=True, **kwargs): + return self.call('get', '/v1/introspection/%s/data%s' + % (uuid, '' if processed else '/unprocessed'), **kwargs).json() @_query_string('marker', 'limit') @@ -626,6 +627,10 @@ class Test(Base): data = self.call_get_data(self.uuid) self.assertEqual(self.data['inventory'], data['inventory']) + self.assertIn('all_interfaces', data) + raw = self.call_get_data(self.uuid, processed=False) + self.assertEqual(self.data['inventory'], raw['inventory']) + self.assertNotIn('all_interfaces', raw) res = self.call_reapply(self.uuid) self.assertEqual(202, res.status_code) diff --git a/ironic_inspector/test/unit/test_main.py b/ironic_inspector/test/unit/test_main.py index 2f71e40fa..4bed53d61 100644 --- a/ironic_inspector/test/unit/test_main.py +++ b/ironic_inspector/test/unit/test_main.py @@ -371,6 +371,19 @@ class TestApiGetData(BaseAPITest): self.assertEqual(self.introspection_data, json.loads(res.data.decode('utf-8'))) + @mock.patch.object(swift, 'SwiftAPI', autospec=True) + def test_get_unprocessed_data_from_swift(self, swift_mock): + CONF.set_override('store_data', 'swift', 'processing') + swift_conn = swift_mock.return_value + swift_conn.get_object.return_value = json.dumps( + self.introspection_data) + res = self.app.get('/v1/introspection/%s/data/unprocessed' % self.uuid) + name = 'inspector_data-%s-UNPROCESSED' % self.uuid + swift_conn.get_object.assert_called_once_with(name) + self.assertEqual(200, res.status_code) + self.assertEqual(self.introspection_data, + json.loads(res.data.decode('utf-8'))) + @mock.patch.object(intros_data_plugin, 'DatabaseStore', autospec=True) def test_get_introspection_data_from_db(self, db_mock): @@ -389,6 +402,11 @@ class TestApiGetData(BaseAPITest): res = self.app.get('/v1/introspection/%s/data' % self.uuid) self.assertEqual(404, res.status_code) + def test_unprocessed_data_not_stored(self): + CONF.set_override('store_data', 'none', 'processing') + res = self.app.get('/v1/introspection/%s/data/unprocessed' % self.uuid) + self.assertEqual(404, res.status_code) + @mock.patch.object(ir_utils, 'get_node', autospec=True) @mock.patch.object(main.process, 'get_introspection_data', autospec=True) def test_with_name(self, process_mock, get_mock): diff --git a/releasenotes/notes/unprocessed-07842e56eb60e253.yaml b/releasenotes/notes/unprocessed-07842e56eb60e253.yaml new file mode 100644 index 000000000..e95dcdba9 --- /dev/null +++ b/releasenotes/notes/unprocessed-07842e56eb60e253.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The new API ``GET /v1/introspection//data/unprocessed`` allows + retrieving raw (unprocessed) data if data store is enabled.