summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaifeng Wang <kaifeng.w@gmail.com>2018-07-19 19:14:15 +0800
committerKaifeng Wang <kaifeng.w@gmail.com>2018-12-04 10:54:32 +0800
commita8c1d06bd0badf1a24ede8194804c7bee06a9aa0 (patch)
tree222f51e5a1d21c0eade1dc268a89cfbd6d7b1020
parentf7079e9acccfedc6b1bf5a1269869d39da80a700 (diff)
introspection data backend: implements db
Configurable introspection data storage backend [1] is proposed to provide flexible extension of introspection data storage instead of the single support of Swift storage backend. This patch adds database support for using ironic-inspector database as the storage backend. A table named ``introspection_data`` is created to serve as the storage for introspected data. [1] http://specs.openstack.org/openstack/ironic-inspector-specs/specs/configurable-introspection-data-backends.html Change-Id: I8b29b7b86d90823d29b921ebf64acddbcd2d8d0d Story: 1726713 Task: 11373
Notes
Notes (review): Code-Review+2: Dmitry Tantsur <divius.inside@gmail.com> Code-Review+2: Julia Kreger <juliaashleykreger@gmail.com> Workflow+1: Julia Kreger <juliaashleykreger@gmail.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Mon, 31 Dec 2018 17:57:52 +0000 Reviewed-on: https://review.openstack.org/583930 Project: openstack/ironic-inspector Branch: refs/heads/master
-rw-r--r--ironic_inspector/db.py8
-rw-r--r--ironic_inspector/migrations/versions/bf8dec16023c_add_introspection_data_table.py42
-rw-r--r--ironic_inspector/node_cache.py43
-rw-r--r--ironic_inspector/test/unit/test_migrations.py13
-rw-r--r--ironic_inspector/test/unit/test_node_cache.py29
-rw-r--r--ironic_inspector/utils.py4
6 files changed, 138 insertions, 1 deletions
diff --git a/ironic_inspector/db.py b/ironic_inspector/db.py
index 94f0eff..7d7a111 100644
--- a/ironic_inspector/db.py
+++ b/ironic_inspector/db.py
@@ -134,6 +134,14 @@ class RuleAction(Base):
134 return res 134 return res
135 135
136 136
137class IntrospectionData(Base):
138 __tablename__ = 'introspection_data'
139 uuid = Column(String(36), ForeignKey('nodes.uuid'), primary_key=True)
140 processed = Column(Boolean, default=False)
141 data = Column(db_types.JsonEncodedDict(mysql_as_long=True),
142 nullable=True)
143
144
137def init(): 145def init():
138 """Initialize the database. 146 """Initialize the database.
139 147
diff --git a/ironic_inspector/migrations/versions/bf8dec16023c_add_introspection_data_table.py b/ironic_inspector/migrations/versions/bf8dec16023c_add_introspection_data_table.py
new file mode 100644
index 0000000..bceefac
--- /dev/null
+++ b/ironic_inspector/migrations/versions/bf8dec16023c_add_introspection_data_table.py
@@ -0,0 +1,42 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13"""add_introspection_data_table
14
15Revision ID: bf8dec16023c
16Revises: 2970d2d44edc
17Create Date: 2018-07-19 18:51:38.124614
18
19"""
20
21from alembic import op
22from oslo_db.sqlalchemy import types as db_types
23import sqlalchemy as sa
24
25# revision identifiers, used by Alembic.
26revision = 'bf8dec16023c'
27down_revision = '2970d2d44edc'
28branch_labels = None
29depends_on = None
30
31
32def upgrade():
33 op.create_table(
34 'introspection_data',
35 sa.Column('uuid', sa.String(36), sa.ForeignKey('nodes.uuid'),
36 primary_key=True),
37 sa.Column('processed', sa.Boolean, default=False),
38 sa.Column('data', db_types.JsonEncodedDict(mysql_as_long=True).impl,
39 nullable=True),
40 mysql_ENGINE='InnoDB',
41 mysql_DEFAULT_CHARSET='UTF8'
42 )
diff --git a/ironic_inspector/node_cache.py b/ironic_inspector/node_cache.py
index 9365222..0cf7932 100644
--- a/ironic_inspector/node_cache.py
+++ b/ironic_inspector/node_cache.py
@@ -755,7 +755,7 @@ def _delete_node(uuid, session=None):
755 with db.ensure_transaction(session) as session: 755 with db.ensure_transaction(session) as session:
756 db.model_query(db.Attribute, session=session).filter_by( 756 db.model_query(db.Attribute, session=session).filter_by(
757 node_uuid=uuid).delete() 757 node_uuid=uuid).delete()
758 for model in (db.Option, db.Node): 758 for model in (db.Option, db.IntrospectionData, db.Node):
759 db.model_query(model, 759 db.model_query(model,
760 session=session).filter_by(uuid=uuid).delete() 760 session=session).filter_by(uuid=uuid).delete()
761 761
@@ -979,3 +979,44 @@ def get_node_list(ironic=None, marker=None, limit=None):
979 ('started_at', 'uuid'), 979 ('started_at', 'uuid'),
980 marker=marker, sort_dir='desc') 980 marker=marker, sort_dir='desc')
981 return [NodeInfo.from_row(row, ironic=ironic) for row in rows] 981 return [NodeInfo.from_row(row, ironic=ironic) for row in rows]
982
983
984def store_introspection_data(node_id, introspection_data, processed=True):
985 """Store introspection data for this node.
986
987 :param node_id: node UUID.
988 :param introspection_data: A dictionary of introspection data
989 :param processed: Specify the type of introspected data, set to False
990 indicates the data is unprocessed.
991 """
992 with db.ensure_transaction() as session:
993 record = db.model_query(db.IntrospectionData,
994 session=session).filter_by(
995 uuid=node_id, processed=processed).first()
996 if record is None:
997 row = db.IntrospectionData()
998 row.update({'uuid': node_id, 'processed': processed,
999 'data': introspection_data})
1000 session.add(row)
1001 else:
1002 record.update({'data': introspection_data})
1003 session.flush()
1004
1005
1006def get_introspection_data(node_id, processed=True):
1007 """Get introspection data for this node.
1008
1009 :param node_id: node UUID.
1010 :param processed: Specify the type of introspected data, set to False
1011 indicates retrieving the unprocessed data.
1012 :return: A dictionary representation of intropsected data
1013 """
1014 try:
1015 ref = db.model_query(db.IntrospectionData).filter_by(
1016 uuid=node_id, processed=processed).one()
1017 return ref['data']
1018 except orm_errors.NoResultFound:
1019 msg = _('Introspection data not found for node %(node)s, '
1020 'processed=%(processed)s') % {'node': node_id,
1021 'processed': processed}
1022 raise utils.IntrospectionDataNotFound(msg)
diff --git a/ironic_inspector/test/unit/test_migrations.py b/ironic_inspector/test/unit/test_migrations.py
index 5e5fd4b..aa005ce 100644
--- a/ironic_inspector/test/unit/test_migrations.py
+++ b/ironic_inspector/test/unit/test_migrations.py
@@ -441,6 +441,19 @@ class MigrationCheckersMixin(object):
441 n = nodes.select(nodes.c.uuid == 'abcd').execute().first() 441 n = nodes.select(nodes.c.uuid == 'abcd').execute().first()
442 self.assertIsNone(n['manage_boot']) 442 self.assertIsNone(n['manage_boot'])
443 443
444 def _check_bf8dec16023c(self, engine, data):
445 introspection_data = db_utils.get_table(engine, 'introspection_data')
446 col_names = [column.name for column in introspection_data.c]
447 self.assertIn('uuid', col_names)
448 self.assertIn('processed', col_names)
449 self.assertIn('data', col_names)
450 self.assertIsInstance(introspection_data.c.uuid.type,
451 sqlalchemy.types.String)
452 self.assertIsInstance(introspection_data.c.processed.type,
453 sqlalchemy.types.Boolean)
454 self.assertIsInstance(introspection_data.c.data.type,
455 sqlalchemy.types.Text)
456
444 def test_upgrade_and_version(self): 457 def test_upgrade_and_version(self):
445 with patch_with_engine(self.engine): 458 with patch_with_engine(self.engine):
446 self.migration_ext.upgrade('head') 459 self.migration_ext.upgrade('head')
diff --git a/ironic_inspector/test/unit/test_node_cache.py b/ironic_inspector/test/unit/test_node_cache.py
index 9ad9b6f..4e6bed5 100644
--- a/ironic_inspector/test/unit/test_node_cache.py
+++ b/ironic_inspector/test/unit/test_node_cache.py
@@ -331,6 +331,8 @@ class TestNodeCacheCleanUp(test_base.NodeTest):
331 value=v, node_uuid=self.uuid).save(session) 331 value=v, node_uuid=self.uuid).save(session)
332 db.Option(uuid=self.uuid, name='foo', value='bar').save( 332 db.Option(uuid=self.uuid, name='foo', value='bar').save(
333 session) 333 session)
334 db.IntrospectionData(uuid=self.uuid, processed=False,
335 data={'fake': 'data'}).save(session)
334 336
335 def test_no_timeout(self): 337 def test_no_timeout(self):
336 CONF.set_override('timeout', 0) 338 CONF.set_override('timeout', 0)
@@ -358,6 +360,7 @@ class TestNodeCacheCleanUp(test_base.NodeTest):
358 self.assertEqual(len(self.macs), 360 self.assertEqual(len(self.macs),
359 db.model_query(db.Attribute).count()) 361 db.model_query(db.Attribute).count())
360 self.assertEqual(1, db.model_query(db.Option).count()) 362 self.assertEqual(1, db.model_query(db.Option).count())
363 self.assertEqual(1, db.model_query(db.IntrospectionData).count())
361 self.assertFalse(get_lock_mock.called) 364 self.assertFalse(get_lock_mock.called)
362 365
363 @mock.patch.object(node_cache, '_get_lock', autospec=True) 366 @mock.patch.object(node_cache, '_get_lock', autospec=True)
@@ -1256,3 +1259,29 @@ class TestStartIntrospection(test_base.NodeTest):
1256 node_cache.start_introspection, 1259 node_cache.start_introspection,
1257 self.node_info.uuid) 1260 self.node_info.uuid)
1258 self.assertFalse(add_node_mock.called) 1261 self.assertFalse(add_node_mock.called)
1262
1263
1264class TestIntrospectionDataDbStore(test_base.NodeTest):
1265 def setUp(self):
1266 super(TestIntrospectionDataDbStore, self).setUp()
1267 node_cache.add_node(self.node.uuid,
1268 istate.States.processing,
1269 bmc_address='1.2.3.4')
1270
1271 def _test_store_and_get(self, processed=False):
1272 node_cache.store_introspection_data(self.node.uuid,
1273 copy.deepcopy(self.data),
1274 processed=processed)
1275 stored_data = node_cache.get_introspection_data(self.node.uuid,
1276 processed=processed)
1277 self.assertEqual(stored_data, self.data)
1278
1279 def test_store_and_get_unprocessed(self):
1280 self._test_store_and_get(processed=False)
1281
1282 def test_store_and_get_processed(self):
1283 self._test_store_and_get(processed=True)
1284
1285 def test_get_no_data_available(self):
1286 self.assertRaises(utils.IntrospectionDataNotFound,
1287 node_cache.get_introspection_data, self.node.uuid)
diff --git a/ironic_inspector/utils.py b/ironic_inspector/utils.py
index 7fecf6e..69244a4 100644
--- a/ironic_inspector/utils.py
+++ b/ironic_inspector/utils.py
@@ -140,6 +140,10 @@ class NodeStateInvalidEvent(Error):
140 """Invalid event attempted.""" 140 """Invalid event attempted."""
141 141
142 142
143class IntrospectionDataNotFound(NotFoundInCacheError):
144 """Introspection data not found."""
145
146
143def executor(): 147def executor():
144 """Return the current futures executor.""" 148 """Return the current futures executor."""
145 global _EXECUTOR 149 global _EXECUTOR