From 399ffda676491ccab9a49a679816970fe8f60b1a Mon Sep 17 00:00:00 2001 From: Yuiko Takada Date: Mon, 29 Jun 2015 23:58:50 +0900 Subject: [PATCH] Periodically check nodes' existance Add below procedure to periodic_clean_up() in order to sync data with Ironic. * Get Ironic's node list by executing python-ironicclient's node.list() * Compare above Ironic-node-list and cache * If node in cache is not exist in Ironic-node-list, delete data from cache. Change-Id: I606b0a76559b8bd2845d58146086b1019712dc34 Implements: blueprint delete-db-api --- ironic_inspector/main.py | 9 +++++ ironic_inspector/node_cache.py | 42 ++++++++++++++++++---- ironic_inspector/test/functional.py | 1 + ironic_inspector/test/test_node_cache.py | 45 ++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/ironic_inspector/main.py b/ironic_inspector/main.py index e0458dcfd..d94fcf1a9 100644 --- a/ironic_inspector/main.py +++ b/ironic_inspector/main.py @@ -174,11 +174,20 @@ def periodic_clean_up(period): # pragma: no cover try: if node_cache.clean_up(): firewall.update_filters() + sync_with_ironic() except Exception: LOG.exception(_LE('Periodic clean up of node cache failed')) eventlet.greenthread.sleep(period) +def sync_with_ironic(): + ironic = utils.get_client() + # TODO(yuikotakada): pagination + ironic_nodes = ironic.node.list(limit=0) + ironic_node_uuids = {node.uuid for node in ironic_nodes} + node_cache.delete_nodes_not_in_list(ironic_node_uuids) + + def init(): if utils.get_auth_strategy() != 'noauth': utils.add_auth_middleware(app) diff --git a/ironic_inspector/node_cache.py b/ironic_inspector/node_cache.py index 362bc7f25..4a3a635e4 100644 --- a/ironic_inspector/node_cache.py +++ b/ironic_inspector/node_cache.py @@ -183,13 +183,7 @@ def add_node(uuid, **attributes): """ started_at = time.time() with db.ensure_transaction() as session: - (db.model_query(db.Node, session=session).filter_by(uuid=uuid). - delete()) - (db.model_query(db.Attribute, session=session).filter_by(uuid=uuid). - delete(synchronize_session=False)) - (db.model_query(db.Option, session=session).filter_by(uuid=uuid). - delete()) - + _delete_node(uuid) db.Node(uuid=uuid, started_at=started_at).save(session) node_info = NodeInfo(uuid=uuid, started_at=started_at) @@ -201,12 +195,46 @@ def add_node(uuid, **attributes): return node_info +def delete_nodes_not_in_list(uuids): + """Delete nodes which don't exist in Ironic node UUIDs. + + :param uuids: Ironic node UUIDs + """ + inspector_uuids = _list_node_uuids() + for uuid in inspector_uuids - uuids: + LOG.warn(_LW('Node %s was deleted from Ironic, dropping from Ironic ' + 'Inspector database'), uuid) + _delete_node(uuid) + + +def _delete_node(uuid, session=None): + """Delete information about a node. + + :param uuid: Ironic node UUID + """ + with db.ensure_transaction(session) as session: + (db.model_query(db.Node, session=session).filter_by(uuid=uuid). + delete()) + (db.model_query(db.Attribute, session=session).filter_by(uuid=uuid). + delete(synchronize_session=False)) + (db.model_query(db.Option, session=session).filter_by(uuid=uuid). + delete()) + + def active_macs(): """List all MAC's that are on introspection right now.""" return ({x.value for x in db.model_query(db.Attribute.value). filter_by(name=MACS_ATTRIBUTE)}) +def _list_node_uuids(): + """Get all nodes' uuid from cache. + + :returns: Set of nodes' uuid. + """ + return {x.uuid for x in db.model_query(db.Node.uuid)} + + def get_node(uuid): """Get node from cache by it's UUID. diff --git a/ironic_inspector/test/functional.py b/ironic_inspector/test/functional.py index b6842e014..17dfe8004 100644 --- a/ironic_inspector/test/functional.py +++ b/ironic_inspector/test/functional.py @@ -57,6 +57,7 @@ class Base(base.NodeTest): self.cli.reset_mock() self.cli.node.get.return_value = self.node self.cli.node.update.return_value = self.node + self.cli.node.list.return_value = [self.node] # https://github.com/openstack/ironic-inspector/blob/master/HTTP-API.rst # noqa self.data = { diff --git a/ironic_inspector/test/test_node_cache.py b/ironic_inspector/test/test_node_cache.py index 4d8b89610..9e843f9e7 100644 --- a/ironic_inspector/test/test_node_cache.py +++ b/ironic_inspector/test/test_node_cache.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import time import unittest @@ -56,6 +57,41 @@ class TestNodeCache(test_base.NodeTest): ('mac', self.macs[1], self.uuid)], [(row.name, row.value, row.uuid) for row in res]) + def test__delete_node(self): + session = db.get_session() + with session.begin(): + db.Node(uuid=self.node.uuid).save(session) + db.Attribute(name='mac', value='11:22:11:22:11:22', + uuid=self.uuid).save(session) + data = {'s': 'value', 'b': True, 'i': 42} + encoded = json.dumps(data) + db.Option(uuid=self.uuid, name='name', value=encoded).save( + session) + + node_cache._delete_node(self.uuid) + session = db.get_session() + row_node = db.model_query(db.Node).filter_by( + uuid=self.uuid).first() + self.assertIsNone(row_node) + row_attribute = db.model_query(db.Attribute).filter_by( + uuid=self.uuid).first() + self.assertIsNone(row_attribute) + row_option = db.model_query(db.Option).filter_by( + uuid=self.uuid).first() + self.assertIsNone(row_option) + + @mock.patch.object(node_cache, '_list_node_uuids') + @mock.patch.object(node_cache, '_delete_node') + def test_delete_nodes_not_in_list(self, mock__delete_node, + mock__list_node_uuids): + uuid2 = 'uuid2' + uuids = {self.uuid} + mock__list_node_uuids.return_value = {self.uuid, uuid2} + session = db.get_session() + with session.begin(): + node_cache.delete_nodes_not_in_list(uuids) + mock__delete_node.assert_called_once_with(uuid2) + def test_add_node_duplicate_mac(self): session = db.get_session() with session.begin(): @@ -78,6 +114,15 @@ class TestNodeCache(test_base.NodeTest): self.assertEqual({'11:22:11:22:11:22', '22:11:22:11:22:11'}, node_cache.active_macs()) + def test__list_node_uuids(self): + session = db.get_session() + with session.begin(): + db.Node(uuid=self.node.uuid).save(session) + db.Node(uuid='uuid2').save(session) + + node_uuid_list = node_cache._list_node_uuids() + self.assertEqual({self.uuid, 'uuid2'}, node_uuid_list) + def test_add_attribute(self): session = db.get_session() with session.begin():