From 539f945d0113dddd8e9c7013d859385dae6249f0 Mon Sep 17 00:00:00 2001 From: Aldo Ortega Date: Tue, 29 Nov 2022 12:00:12 -0500 Subject: [PATCH 1/6] Added notification methods - Added switch links enable/disable notifications - Added interface links enable/disable notifications --- main.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/main.py b/main.py index f70cdcc..4d3887a 100644 --- a/main.py +++ b/main.py @@ -269,6 +269,7 @@ def enable_switch(self, dpid): self.notify_switch_enabled(dpid) self.notify_topology_update() + self.notify_switch_links_enable(switch) return jsonify("Operation successful"), 201 @rest('v3/switches//disable', methods=['POST']) @@ -283,6 +284,7 @@ def disable_switch(self, dpid): self.notify_switch_disabled(dpid) self.notify_topology_update() + self.notify_switch_links_disable(switch) return jsonify("Operation successful"), 201 @rest('v3/switches//metadata') @@ -357,12 +359,14 @@ def enable_interface(self, interface_enable_id=None, dpid=None): interface = switch.interfaces[interface_number] self.topo_controller.enable_interface(interface.id) interface.enable() + self.notify_interface_link_enable(interface) except KeyError: msg = f"Switch {dpid} interface {interface_number} not found" return jsonify(msg), 404 else: for interface in switch.interfaces.copy().values(): interface.enable() + self.notify_interface_link_enable(interface) self.topo_controller.upsert_switch(switch.id, switch.as_dict()) self.notify_topology_update() return jsonify("Operation successful"), 200 @@ -385,12 +389,14 @@ def disable_interface(self, interface_disable_id=None, dpid=None): interface = switch.interfaces[interface_number] self.topo_controller.disable_interface(interface.id) interface.disable() + self.notify_interface_link_disable(interface) except KeyError: msg = f"Switch {dpid} interface {interface_number} not found" return jsonify(msg), 404 else: for interface in switch.interfaces.copy().values(): interface.disable() + self.notify_interface_link_disable(interface) self.topo_controller.upsert_switch(switch.id, switch.as_dict()) self.notify_topology_update() return jsonify("Operation successful"), 200 @@ -948,12 +954,27 @@ def notify_switch_enabled(self, dpid): event = KytosEvent(name=name, content={'dpid': dpid}) self.controller.buffers.app.put(event) + def notify_switch_links_enable(self, switch): + """Send an event to notify the status of a link in a switch""" + for interface in switch.interfaces.values(): + link = self._get_link_from_interface(interface) + if link: + if link.status == EntityStatus.UP: + self.notify_link_status_change(link, reason="link up") + def notify_switch_disabled(self, dpid): """Send an event to notify that a switch is disabled.""" name = 'kytos/topology.switch.disabled' event = KytosEvent(name=name, content={'dpid': dpid}) self.controller.buffers.app.put(event) + def notify_switch_links_disable(self, switch): + """Send an event to notify the status of a link in a switch""" + for interface in switch.interfaces.values(): + link = self._get_link_from_interface(interface) + if link: + self.notify_link_status_change(link, reason="link down") + def notify_topology_update(self): """Send an event to notify about updates on the topology.""" name = 'kytos/topology.updated' @@ -961,6 +982,21 @@ def notify_topology_update(self): self._get_topology()}) self.controller.buffers.app.put(event) + def notify_interface_link_enable(self, interface): + """Send an event to notify the status of a link from + an interface.""" + link = self._get_link_from_interface(interface) + if link: + if link.status == EntityStatus.UP: + self.notify_link_status_change(link, reason="link up") + + def notify_interface_link_disable(self, interface): + """Send an event to notify the status of a link from + an interface.""" + link = self._get_link_from_interface(interface) + if link: + self.notify_link_status_change(link, reason="link down") + def notify_link_status_change(self, link, reason='not given'): """Send an event to notify about a status change on a link.""" name = 'kytos/topology.' From ecef2f93100d8f68625ce8206940ba5362925340 Mon Sep 17 00:00:00 2001 From: Aldo Ortega Date: Tue, 29 Nov 2022 13:47:42 -0500 Subject: [PATCH 2/6] Added tests -Fixed lint --- main.py | 4 +- tests/unit/test_main.py | 91 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 4d3887a..18b73d3 100644 --- a/main.py +++ b/main.py @@ -983,7 +983,7 @@ def notify_topology_update(self): self.controller.buffers.app.put(event) def notify_interface_link_enable(self, interface): - """Send an event to notify the status of a link from + """Send an event to notify the status of a link from an interface.""" link = self._get_link_from_interface(interface) if link: @@ -991,7 +991,7 @@ def notify_interface_link_enable(self, interface): self.notify_link_status_change(link, reason="link up") def notify_interface_link_disable(self, interface): - """Send an event to notify the status of a link from + """Send an event to notify the status of a link from an interface.""" link = self._get_link_from_interface(interface) if link: diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index 8101b05..d1e5e9f 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -623,8 +623,9 @@ def test_fail_load_link(self, get_link_or_create_mock): with self.assertRaises(RestoreError): self.napp._load_link(link_attrs_fail) + @patch('napps.kytos.topology.main.Main.notify_switch_links_enable') @patch('napps.kytos.topology.main.Main.notify_topology_update') - def test_enable_switch(self, mock_notify_topo): + def test_enable_switch(self, mock_notify_topo, mock_sw_l_enable): """Test enable_switch.""" dpid = "00:00:00:00:00:00:00:01" mock_switch = get_switch_mock(dpid) @@ -637,6 +638,7 @@ def test_enable_switch(self, mock_notify_topo): self.assertEqual(mock_switch.enable.call_count, 1) self.napp.topo_controller.enable_switch.assert_called_once_with(dpid) mock_notify_topo.assert_called() + mock_sw_l_enable.assert_called() # fail case mock_switch.enable.call_count = 0 @@ -646,8 +648,9 @@ def test_enable_switch(self, mock_notify_topo): self.assertEqual(response.status_code, 404, response.data) self.assertEqual(mock_switch.enable.call_count, 0) + @patch('napps.kytos.topology.main.Main.notify_switch_links_disable') @patch('napps.kytos.topology.main.Main.notify_topology_update') - def test_disable_switch(self, mock_notify_topo): + def test_disable_switch(self, mock_notify_topo, mock_sw_l_disable): """Test disable_switch.""" dpid = "00:00:00:00:00:00:00:01" mock_switch = get_switch_mock(dpid) @@ -660,6 +663,7 @@ def test_disable_switch(self, mock_notify_topo): self.assertEqual(mock_switch.disable.call_count, 1) self.napp.topo_controller.disable_switch.assert_called_once_with(dpid) mock_notify_topo.assert_called() + mock_sw_l_disable.assert_called() # fail case mock_switch.disable.call_count = 0 @@ -1581,3 +1585,86 @@ def test_notify_link_up_if_status( mock_notify_link.assert_called() assert mock_sleep.call_count == 2 + + @patch('napps.kytos.topology.main.Main.notify_link_status_change') + @patch('napps.kytos.topology.main.Main._get_link_from_interface') + def test_notify_switch_links_enable(self, *args): + """Test switch links notification when enable""" + (mock_get_link_from_interface, + mock_notify_link_status_change) = args + dpid = "00:00:00:00:00:00:00:01" + mock_switch = get_switch_mock(dpid) + mock_switch.interfaces = {1: "interface"} + mock_link = MagicMock() + mock_link.status = EntityStatus.UP + mock_get_link_from_interface.return_value = mock_link + self.napp.notify_switch_links_enable(mock_switch) + + assert mock_get_link_from_interface.call_count == 1 + assert mock_notify_link_status_change.call_count == 1 + + # Without notification + mock_link.status = EntityStatus.DOWN + mock_get_link_from_interface.return_value = mock_link + self.napp.notify_switch_links_enable(mock_switch) + assert mock_get_link_from_interface.call_count == 2 + assert mock_notify_link_status_change.call_count == 1 + + @patch('napps.kytos.topology.main.Main.notify_link_status_change') + @patch('napps.kytos.topology.main.Main._get_link_from_interface') + def test_notify_switch_links_disable(self, *args): + """Test switch links notification when disable""" + (mock_get_link_from_interface, + mock_notify_link_status_change) = args + dpid = "00:00:00:00:00:00:00:01" + mock_switch = get_switch_mock(dpid) + mock_switch.interfaces = {1: "interface"} + mock_get_link_from_interface.return_value = 1 + self.napp.notify_switch_links_disable(mock_switch) + + assert mock_get_link_from_interface.call_count == 1 + assert mock_notify_link_status_change.call_count == 1 + + # Without notification + mock_get_link_from_interface.return_value = 0 + self.napp.notify_switch_links_disable(mock_switch) + assert mock_get_link_from_interface.call_count == 2 + assert mock_notify_link_status_change.call_count == 1 + + @patch('napps.kytos.topology.main.Main.notify_link_status_change') + @patch('napps.kytos.topology.main.Main._get_link_from_interface') + def test_notify_interface_link_enable(self, *args): + """Test interface links notification when enable""" + (mock_get_link_from_interface, + mock_notify_link_status_change) = args + mock_link = MagicMock() + mock_link.status = EntityStatus.UP + mock_get_link_from_interface.return_value = mock_link + self.napp.notify_interface_link_enable(MagicMock()) + assert mock_get_link_from_interface.call_count == 1 + assert mock_notify_link_status_change.call_count == 1 + + # Without notification + mock_link = MagicMock() + mock_link.status = EntityStatus.DOWN + mock_get_link_from_interface.return_value = mock_link + self.napp.notify_interface_link_enable(MagicMock()) + assert mock_get_link_from_interface.call_count == 2 + assert mock_notify_link_status_change.call_count == 1 + + @patch('napps.kytos.topology.main.Main.notify_link_status_change') + @patch('napps.kytos.topology.main.Main._get_link_from_interface') + def test_notify_interface_link_disable(self, *args): + """Test interface links notification when disable""" + (mock_get_link_from_interface, + mock_notify_link_status_change) = args + mock_get_link_from_interface.return_value = 1 + self.napp.notify_interface_link_disable(MagicMock()) + assert mock_get_link_from_interface.call_count == 1 + assert mock_notify_link_status_change.call_count == 1 + + # Without notification + mock_get_link_from_interface.return_value = 0 + self.napp.notify_interface_link_disable(MagicMock()) + assert mock_get_link_from_interface.call_count == 2 + assert mock_notify_link_status_change.call_count == 1 From e1af40891fc1ed3348ee7c050eefe579f69c2a85 Mon Sep 17 00:00:00 2001 From: Aldo Ortega Date: Tue, 29 Nov 2022 20:21:44 -0500 Subject: [PATCH 3/6] Merged methods --- main.py | 52 +++++++------------- tests/unit/test_main.py | 102 +++++++++++----------------------------- 2 files changed, 45 insertions(+), 109 deletions(-) diff --git a/main.py b/main.py index 18b73d3..329ff25 100644 --- a/main.py +++ b/main.py @@ -269,7 +269,7 @@ def enable_switch(self, dpid): self.notify_switch_enabled(dpid) self.notify_topology_update() - self.notify_switch_links_enable(switch) + self.notify_switch_links_status(switch, "link enable") return jsonify("Operation successful"), 201 @rest('v3/switches//disable', methods=['POST']) @@ -284,7 +284,7 @@ def disable_switch(self, dpid): self.notify_switch_disabled(dpid) self.notify_topology_update() - self.notify_switch_links_disable(switch) + self.notify_switch_links_status(switch, "link disable") return jsonify("Operation successful"), 201 @rest('v3/switches//metadata') @@ -359,14 +359,14 @@ def enable_interface(self, interface_enable_id=None, dpid=None): interface = switch.interfaces[interface_number] self.topo_controller.enable_interface(interface.id) interface.enable() - self.notify_interface_link_enable(interface) + self.notify_interface_link_status(interface, "link enable") except KeyError: msg = f"Switch {dpid} interface {interface_number} not found" return jsonify(msg), 404 else: for interface in switch.interfaces.copy().values(): interface.enable() - self.notify_interface_link_enable(interface) + self.notify_interface_link_status(interface, "link enable") self.topo_controller.upsert_switch(switch.id, switch.as_dict()) self.notify_topology_update() return jsonify("Operation successful"), 200 @@ -389,14 +389,14 @@ def disable_interface(self, interface_disable_id=None, dpid=None): interface = switch.interfaces[interface_number] self.topo_controller.disable_interface(interface.id) interface.disable() - self.notify_interface_link_disable(interface) + self.notify_interface_link_status(interface, "link disable") except KeyError: msg = f"Switch {dpid} interface {interface_number} not found" return jsonify(msg), 404 else: for interface in switch.interfaces.copy().values(): interface.disable() - self.notify_interface_link_disable(interface) + self.notify_interface_link_status(interface, "link disable") self.topo_controller.upsert_switch(switch.id, switch.as_dict()) self.notify_topology_update() return jsonify("Operation successful"), 200 @@ -770,12 +770,12 @@ def link_status_hook_link_up_timer(self, link) -> Optional[EntityStatus]: return EntityStatus.DOWN return None - def notify_link_up_if_status(self, link) -> None: + def notify_link_up_if_status(self, link, reason="link up") -> None: """Tries to notify link up and topology changes based on its status Currently, it needs to wait up to a timer.""" time.sleep(self.link_up_timer) - if link.status != EntityStatus.UP: + if link.status != EntityStatus.UP and reason != "link disable": return with self._links_notify_lock[link.id]: notified_at = link.get_metadata("notified_up_at") @@ -789,7 +789,7 @@ def notify_link_up_if_status(self, link) -> None: link.update_metadata(key, now()) self.topo_controller.add_link_metadata(link.id, {key: notified_at}) self.notify_topology_update() - self.notify_link_status_change(link, reason="link up") + self.notify_link_status_change(link, reason) def handle_link_up(self, interface): """Handle link up for an interface.""" @@ -812,7 +812,7 @@ def handle_link_up(self, interface): link.extend_metadata(metadata) link.activate() self.topo_controller.activate_link(link.id, **metadata) - self.notify_link_up_if_status(link) + self.notify_link_up_if_status(link, "link up") @listen_to('.*.switch.interface.link_down') def on_interface_link_down(self, event): @@ -916,7 +916,7 @@ def add_links(self, event): } link.extend_metadata(metadata) self.topo_controller.upsert_link(link.id, link.as_dict()) - self.notify_link_up_if_status(link) + self.notify_link_up_if_status(link, "link up") @listen_to('.*.of_lldp.network_status.updated') def on_lldp_status_updated(self, event): @@ -954,13 +954,12 @@ def notify_switch_enabled(self, dpid): event = KytosEvent(name=name, content={'dpid': dpid}) self.controller.buffers.app.put(event) - def notify_switch_links_enable(self, switch): + def notify_switch_links_status(self, switch, reason): """Send an event to notify the status of a link in a switch""" - for interface in switch.interfaces.values(): - link = self._get_link_from_interface(interface) - if link: - if link.status == EntityStatus.UP: - self.notify_link_status_change(link, reason="link up") + with self._links_lock: + for link in self.links.values(): + if switch in (link.endpoint_a.switch, link.endpoint_b.switch): + self.notify_link_up_if_status(link, reason) def notify_switch_disabled(self, dpid): """Send an event to notify that a switch is disabled.""" @@ -968,13 +967,6 @@ def notify_switch_disabled(self, dpid): event = KytosEvent(name=name, content={'dpid': dpid}) self.controller.buffers.app.put(event) - def notify_switch_links_disable(self, switch): - """Send an event to notify the status of a link in a switch""" - for interface in switch.interfaces.values(): - link = self._get_link_from_interface(interface) - if link: - self.notify_link_status_change(link, reason="link down") - def notify_topology_update(self): """Send an event to notify about updates on the topology.""" name = 'kytos/topology.updated' @@ -982,20 +974,12 @@ def notify_topology_update(self): self._get_topology()}) self.controller.buffers.app.put(event) - def notify_interface_link_enable(self, interface): - """Send an event to notify the status of a link from - an interface.""" - link = self._get_link_from_interface(interface) - if link: - if link.status == EntityStatus.UP: - self.notify_link_status_change(link, reason="link up") - - def notify_interface_link_disable(self, interface): + def notify_interface_link_status(self, interface, reason): """Send an event to notify the status of a link from an interface.""" link = self._get_link_from_interface(interface) if link: - self.notify_link_status_change(link, reason="link down") + self.notify_link_up_if_status(link, reason) def notify_link_status_change(self, link, reason='not given'): """Send an event to notify about a status change on a link.""" diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index d1e5e9f..5798bf6 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -623,9 +623,9 @@ def test_fail_load_link(self, get_link_or_create_mock): with self.assertRaises(RestoreError): self.napp._load_link(link_attrs_fail) - @patch('napps.kytos.topology.main.Main.notify_switch_links_enable') + @patch('napps.kytos.topology.main.Main.notify_switch_links_status') @patch('napps.kytos.topology.main.Main.notify_topology_update') - def test_enable_switch(self, mock_notify_topo, mock_sw_l_enable): + def test_enable_switch(self, mock_notify_topo, mock_sw_l_status): """Test enable_switch.""" dpid = "00:00:00:00:00:00:00:01" mock_switch = get_switch_mock(dpid) @@ -638,7 +638,7 @@ def test_enable_switch(self, mock_notify_topo, mock_sw_l_enable): self.assertEqual(mock_switch.enable.call_count, 1) self.napp.topo_controller.enable_switch.assert_called_once_with(dpid) mock_notify_topo.assert_called() - mock_sw_l_enable.assert_called() + mock_sw_l_status.assert_called() # fail case mock_switch.enable.call_count = 0 @@ -648,9 +648,9 @@ def test_enable_switch(self, mock_notify_topo, mock_sw_l_enable): self.assertEqual(response.status_code, 404, response.data) self.assertEqual(mock_switch.enable.call_count, 0) - @patch('napps.kytos.topology.main.Main.notify_switch_links_disable') + @patch('napps.kytos.topology.main.Main.notify_switch_links_status') @patch('napps.kytos.topology.main.Main.notify_topology_update') - def test_disable_switch(self, mock_notify_topo, mock_sw_l_disable): + def test_disable_switch(self, mock_notify_topo, mock_sw_l_status): """Test disable_switch.""" dpid = "00:00:00:00:00:00:00:01" mock_switch = get_switch_mock(dpid) @@ -663,7 +663,7 @@ def test_disable_switch(self, mock_notify_topo, mock_sw_l_disable): self.assertEqual(mock_switch.disable.call_count, 1) self.napp.topo_controller.disable_switch.assert_called_once_with(dpid) mock_notify_topo.assert_called() - mock_sw_l_disable.assert_called() + mock_sw_l_status.assert_called() # fail case mock_switch.disable.call_count = 0 @@ -1571,14 +1571,14 @@ def test_notify_link_up_if_status( link = MagicMock(status=EntityStatus.UP) link.get_metadata.return_value = now() - assert not self.napp.notify_link_up_if_status(link) + assert not self.napp.notify_link_up_if_status(link, "link up") link.update_metadata.assert_not_called() mock_notify_topo.assert_not_called() mock_notify_link.assert_not_called() link = MagicMock(status=EntityStatus.UP) link.get_metadata.return_value = now() - timedelta(seconds=60) - assert not self.napp.notify_link_up_if_status(link) + assert not self.napp.notify_link_up_if_status(link, "link up") link.update_metadata.assert_called() self.napp.topo_controller.add_link_metadata.assert_called() mock_notify_topo.assert_called() @@ -1586,85 +1586,37 @@ def test_notify_link_up_if_status( assert mock_sleep.call_count == 2 - @patch('napps.kytos.topology.main.Main.notify_link_status_change') - @patch('napps.kytos.topology.main.Main._get_link_from_interface') - def test_notify_switch_links_enable(self, *args): - """Test switch links notification when enable""" - (mock_get_link_from_interface, - mock_notify_link_status_change) = args - dpid = "00:00:00:00:00:00:00:01" - mock_switch = get_switch_mock(dpid) - mock_switch.interfaces = {1: "interface"} - mock_link = MagicMock() - mock_link.status = EntityStatus.UP - mock_get_link_from_interface.return_value = mock_link - self.napp.notify_switch_links_enable(mock_switch) - - assert mock_get_link_from_interface.call_count == 1 - assert mock_notify_link_status_change.call_count == 1 - - # Without notification - mock_link.status = EntityStatus.DOWN - mock_get_link_from_interface.return_value = mock_link - self.napp.notify_switch_links_enable(mock_switch) - assert mock_get_link_from_interface.call_count == 2 - assert mock_notify_link_status_change.call_count == 1 - - @patch('napps.kytos.topology.main.Main.notify_link_status_change') - @patch('napps.kytos.topology.main.Main._get_link_from_interface') - def test_notify_switch_links_disable(self, *args): - """Test switch links notification when disable""" - (mock_get_link_from_interface, - mock_notify_link_status_change) = args + @patch('napps.kytos.topology.main.Main.notify_link_up_if_status') + def test_notify_switch_links_status(self, mock_notify_link_up_if_status): + """Test switch links notification when switch status change""" dpid = "00:00:00:00:00:00:00:01" mock_switch = get_switch_mock(dpid) - mock_switch.interfaces = {1: "interface"} - mock_get_link_from_interface.return_value = 1 - self.napp.notify_switch_links_disable(mock_switch) + link1 = MagicMock() + link1.endpoint_a.switch = mock_switch + self.napp.links = {1: link1} + self.napp.notify_switch_links_status(mock_switch, "link enable") - assert mock_get_link_from_interface.call_count == 1 - assert mock_notify_link_status_change.call_count == 1 + assert mock_notify_link_up_if_status.call_count == 1 # Without notification - mock_get_link_from_interface.return_value = 0 - self.napp.notify_switch_links_disable(mock_switch) - assert mock_get_link_from_interface.call_count == 2 - assert mock_notify_link_status_change.call_count == 1 + link1.endpoint_a.switch = None + self.napp.notify_switch_links_status(mock_switch, "link disable") + assert mock_notify_link_up_if_status.call_count == 1 - @patch('napps.kytos.topology.main.Main.notify_link_status_change') + @patch('napps.kytos.topology.main.Main.notify_link_up_if_status') @patch('napps.kytos.topology.main.Main._get_link_from_interface') - def test_notify_interface_link_enable(self, *args): + def test_notify_interface_link_status(self, *args): """Test interface links notification when enable""" (mock_get_link_from_interface, - mock_notify_link_status_change) = args + mock_notify_link_up_if_status) = args mock_link = MagicMock() - mock_link.status = EntityStatus.UP mock_get_link_from_interface.return_value = mock_link - self.napp.notify_interface_link_enable(MagicMock()) + self.napp.notify_interface_link_status(MagicMock(), "link enable") assert mock_get_link_from_interface.call_count == 1 - assert mock_notify_link_status_change.call_count == 1 - - # Without notification - mock_link = MagicMock() - mock_link.status = EntityStatus.DOWN - mock_get_link_from_interface.return_value = mock_link - self.napp.notify_interface_link_enable(MagicMock()) - assert mock_get_link_from_interface.call_count == 2 - assert mock_notify_link_status_change.call_count == 1 - - @patch('napps.kytos.topology.main.Main.notify_link_status_change') - @patch('napps.kytos.topology.main.Main._get_link_from_interface') - def test_notify_interface_link_disable(self, *args): - """Test interface links notification when disable""" - (mock_get_link_from_interface, - mock_notify_link_status_change) = args - mock_get_link_from_interface.return_value = 1 - self.napp.notify_interface_link_disable(MagicMock()) - assert mock_get_link_from_interface.call_count == 1 - assert mock_notify_link_status_change.call_count == 1 + assert mock_notify_link_up_if_status.call_count == 1 # Without notification - mock_get_link_from_interface.return_value = 0 - self.napp.notify_interface_link_disable(MagicMock()) + mock_get_link_from_interface.return_value = None + self.napp.notify_interface_link_status(MagicMock(), "link enable") assert mock_get_link_from_interface.call_count == 2 - assert mock_notify_link_status_change.call_count == 1 + assert mock_notify_link_up_if_status.call_count == 1 From ea30fefdebcad7ef4298ac182235f3b6de2b8f01 Mon Sep 17 00:00:00 2001 From: Aldo Ortega Date: Wed, 30 Nov 2022 14:07:49 -0500 Subject: [PATCH 4/6] Changed link notification call --- main.py | 12 +++++++++--- tests/unit/test_main.py | 22 ++++++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/main.py b/main.py index 329ff25..a847ff3 100644 --- a/main.py +++ b/main.py @@ -775,7 +775,7 @@ def notify_link_up_if_status(self, link, reason="link up") -> None: Currently, it needs to wait up to a timer.""" time.sleep(self.link_up_timer) - if link.status != EntityStatus.UP and reason != "link disable": + if link.status != EntityStatus.UP: return with self._links_notify_lock[link.id]: notified_at = link.get_metadata("notified_up_at") @@ -959,7 +959,10 @@ def notify_switch_links_status(self, switch, reason): with self._links_lock: for link in self.links.values(): if switch in (link.endpoint_a.switch, link.endpoint_b.switch): - self.notify_link_up_if_status(link, reason) + if reason == "link enable": + self.notify_link_up_if_status(link, reason) + else: + self.notify_link_status_change(link, reason) def notify_switch_disabled(self, dpid): """Send an event to notify that a switch is disabled.""" @@ -979,7 +982,10 @@ def notify_interface_link_status(self, interface, reason): an interface.""" link = self._get_link_from_interface(interface) if link: - self.notify_link_up_if_status(link, reason) + if reason == "link enable": + self.notify_link_up_if_status(link, reason) + else: + self.notify_link_status_change(link, reason) def notify_link_status_change(self, link, reason='not given'): """Send an event to notify about a status change on a link.""" diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index 5798bf6..79f833f 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -1586,37 +1586,51 @@ def test_notify_link_up_if_status( assert mock_sleep.call_count == 2 + @patch('napps.kytos.topology.main.Main.notify_link_status_change') @patch('napps.kytos.topology.main.Main.notify_link_up_if_status') - def test_notify_switch_links_status(self, mock_notify_link_up_if_status): + def test_notify_switch_links_status(self, *args): """Test switch links notification when switch status change""" + (mock_notify_link_up_if_status, + mock_notify_link_status_change) = args dpid = "00:00:00:00:00:00:00:01" mock_switch = get_switch_mock(dpid) link1 = MagicMock() link1.endpoint_a.switch = mock_switch self.napp.links = {1: link1} + self.napp.notify_switch_links_status(mock_switch, "link enable") + assert mock_notify_link_up_if_status.call_count == 1 + self.napp.notify_switch_links_status(mock_switch, "link disable") assert mock_notify_link_up_if_status.call_count == 1 + assert mock_notify_link_status_change.call_count == 1 # Without notification link1.endpoint_a.switch = None - self.napp.notify_switch_links_status(mock_switch, "link disable") + self.napp.notify_switch_links_status(mock_switch, "link enable") assert mock_notify_link_up_if_status.call_count == 1 + @patch('napps.kytos.topology.main.Main.notify_link_status_change') @patch('napps.kytos.topology.main.Main.notify_link_up_if_status') @patch('napps.kytos.topology.main.Main._get_link_from_interface') def test_notify_interface_link_status(self, *args): """Test interface links notification when enable""" (mock_get_link_from_interface, - mock_notify_link_up_if_status) = args + mock_notify_link_up_if_status, + mock_notify_link_status_change) = args mock_link = MagicMock() mock_get_link_from_interface.return_value = mock_link self.napp.notify_interface_link_status(MagicMock(), "link enable") assert mock_get_link_from_interface.call_count == 1 assert mock_notify_link_up_if_status.call_count == 1 + self.napp.notify_interface_link_status(MagicMock(), "link disable") + assert mock_get_link_from_interface.call_count == 2 + assert mock_notify_link_up_if_status.call_count == 1 + assert mock_notify_link_status_change.call_count == 1 + # Without notification mock_get_link_from_interface.return_value = None self.napp.notify_interface_link_status(MagicMock(), "link enable") - assert mock_get_link_from_interface.call_count == 2 + assert mock_get_link_from_interface.call_count == 3 assert mock_notify_link_up_if_status.call_count == 1 From 86ff214e5f06a07f4d62583072ca1218ec6c6242 Mon Sep 17 00:00:00 2001 From: Aldo Ortega Date: Wed, 30 Nov 2022 14:20:56 -0500 Subject: [PATCH 5/6] Updated changelog --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f212c7e..7d27340 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ Added - Publish event ``kytos/topology.current`` for topology reconciliation - Subscribed to event ``kytos/topology.get`` to publish the current topology - Added ``notified_up_at`` internal reserved metadata +- Enabling/disabling a switch or an interface will send ``link_up`` and ``link_down`` notifications Changed ======= From 7033216e8612e63e9fe3e370b1c2a78c22ff89f5 Mon Sep 17 00:00:00 2001 From: Aldo Ortega Date: Wed, 30 Nov 2022 21:29:44 -0500 Subject: [PATCH 6/6] Created listener. - Updated readme.rst --- README.rst | 16 ++++++++++++++ main.py | 33 ++++++++++++++++++++--------- tests/integration/test_main.py | 3 ++- tests/unit/test_main.py | 38 +++++++++++++++++----------------- 4 files changed, 60 insertions(+), 30 deletions(-) diff --git a/README.rst b/README.rst index aa16dce..6e7843d 100644 --- a/README.rst +++ b/README.rst @@ -63,6 +63,7 @@ Subscribed - ``kytos/.*.liveness.(up|down)`` - ``kytos/.*.liveness.disabled`` - ``kytos/topology.get`` +- ``kytos/topology.notify_link_up_if_status`` Published @@ -196,3 +197,18 @@ Content: :target: https://github.com/kytos-ng/topology .. |Tag| image:: https://img.shields.io/github/tag/kytos-ng/topology.svg :target: https://github.com/kytos-ng/topology/tags + + +kytos/topology.notify_link_up_if_status +~~~~~~~~~~~~~~~~~~~~~~~~ + +Event reporting that the link was changed to 'down'. It contains the link instance. + +Content: + +.. code-block:: python3 + + { + 'reason': 'link_enabled' + 'link': + } diff --git a/main.py b/main.py index a847ff3..39573b6 100644 --- a/main.py +++ b/main.py @@ -269,7 +269,7 @@ def enable_switch(self, dpid): self.notify_switch_enabled(dpid) self.notify_topology_update() - self.notify_switch_links_status(switch, "link enable") + self.notify_switch_links_status(switch, "link enabled") return jsonify("Operation successful"), 201 @rest('v3/switches//disable', methods=['POST']) @@ -284,7 +284,7 @@ def disable_switch(self, dpid): self.notify_switch_disabled(dpid) self.notify_topology_update() - self.notify_switch_links_status(switch, "link disable") + self.notify_switch_links_status(switch, "link disabled") return jsonify("Operation successful"), 201 @rest('v3/switches//metadata') @@ -359,14 +359,14 @@ def enable_interface(self, interface_enable_id=None, dpid=None): interface = switch.interfaces[interface_number] self.topo_controller.enable_interface(interface.id) interface.enable() - self.notify_interface_link_status(interface, "link enable") + self.notify_interface_link_status(interface, "link enabled") except KeyError: msg = f"Switch {dpid} interface {interface_number} not found" return jsonify(msg), 404 else: for interface in switch.interfaces.copy().values(): interface.enable() - self.notify_interface_link_status(interface, "link enable") + self.notify_interface_link_status(interface, "link enabled") self.topo_controller.upsert_switch(switch.id, switch.as_dict()) self.notify_topology_update() return jsonify("Operation successful"), 200 @@ -389,14 +389,14 @@ def disable_interface(self, interface_disable_id=None, dpid=None): interface = switch.interfaces[interface_number] self.topo_controller.disable_interface(interface.id) interface.disable() - self.notify_interface_link_status(interface, "link disable") + self.notify_interface_link_status(interface, "link disabled") except KeyError: msg = f"Switch {dpid} interface {interface_number} not found" return jsonify(msg), 404 else: for interface in switch.interfaces.copy().values(): interface.disable() - self.notify_interface_link_status(interface, "link disable") + self.notify_interface_link_status(interface, "link disabled") self.topo_controller.upsert_switch(switch.id, switch.as_dict()) self.notify_topology_update() return jsonify("Operation successful"), 200 @@ -959,8 +959,11 @@ def notify_switch_links_status(self, switch, reason): with self._links_lock: for link in self.links.values(): if switch in (link.endpoint_a.switch, link.endpoint_b.switch): - if reason == "link enable": - self.notify_link_up_if_status(link, reason) + if reason == "link enabled": + name = 'kytos/topology.notify_link_up_if_status' + content = {'reason': reason, "link": link} + event = KytosEvent(name=name, content=content) + self.controller.buffers.app.put(event) else: self.notify_link_status_change(link, reason) @@ -982,8 +985,11 @@ def notify_interface_link_status(self, interface, reason): an interface.""" link = self._get_link_from_interface(interface) if link: - if reason == "link enable": - self.notify_link_up_if_status(link, reason) + if reason == "link enabled": + name = 'kytos/topology.notify_link_up_if_status' + content = {'reason': reason, "link": link} + event = KytosEvent(name=name, content=content) + self.controller.buffers.app.put(event) else: self.notify_link_status_change(link, reason) @@ -1024,6 +1030,13 @@ def notify_metadata_changes(self, obj, action): self.controller.buffers.app.put(event) log.debug(f'Metadata from {obj.id} was {action}.') + @listen_to('kytos/topology.notify_link_up_if_status') + def on_notify_link_up_if_status(self, event): + """Tries to notify link up and topology changes""" + link = event.content["link"] + reason = event.content["reason"] + self.notify_link_up_if_status(link, reason) + @listen_to('.*.switch.port.created') def on_notify_port_created(self, event): """Notify when a port is created.""" diff --git a/tests/integration/test_main.py b/tests/integration/test_main.py index 5eba8f0..8305cf1 100644 --- a/tests/integration/test_main.py +++ b/tests/integration/test_main.py @@ -154,7 +154,8 @@ def test_get_event_listeners(self): '.*.switch.interface.link_down', '.*.switch.interface.link_up', '.*.switch.(new|reconnected)', - '.*.switch.port.created'] + '.*.switch.port.created', + 'kytos/topology.notify_link_up_if_status'] self.assertCountEqual(expected_events, actual_events) def test_verify_api_urls(self): diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index 79f833f..e281edc 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -87,7 +87,8 @@ def test_get_event_listeners(self): 'kytos/.*.liveness.(up|down)', 'kytos/.*.liveness.disabled', 'kytos/topology.get', - '.*.switch.port.created'] + '.*.switch.port.created', + 'kytos/topology.notify_link_up_if_status'] actual_events = self.napp.listeners() self.assertCountEqual(expected_events, actual_events) @@ -1587,50 +1588,49 @@ def test_notify_link_up_if_status( assert mock_sleep.call_count == 2 @patch('napps.kytos.topology.main.Main.notify_link_status_change') - @patch('napps.kytos.topology.main.Main.notify_link_up_if_status') - def test_notify_switch_links_status(self, *args): + def test_notify_switch_links_status(self, mock_notify_link_status_change): """Test switch links notification when switch status change""" - (mock_notify_link_up_if_status, - mock_notify_link_status_change) = args + buffers_app_mock = MagicMock() + self.napp.controller.buffers.app = buffers_app_mock dpid = "00:00:00:00:00:00:00:01" mock_switch = get_switch_mock(dpid) link1 = MagicMock() link1.endpoint_a.switch = mock_switch self.napp.links = {1: link1} - self.napp.notify_switch_links_status(mock_switch, "link enable") - assert mock_notify_link_up_if_status.call_count == 1 + self.napp.notify_switch_links_status(mock_switch, "link enabled") + assert self.napp.controller.buffers.app.put.call_count == 1 - self.napp.notify_switch_links_status(mock_switch, "link disable") - assert mock_notify_link_up_if_status.call_count == 1 + self.napp.notify_switch_links_status(mock_switch, "link disabled") + assert self.napp.controller.buffers.app.put.call_count == 1 assert mock_notify_link_status_change.call_count == 1 # Without notification link1.endpoint_a.switch = None - self.napp.notify_switch_links_status(mock_switch, "link enable") - assert mock_notify_link_up_if_status.call_count == 1 + self.napp.notify_switch_links_status(mock_switch, "link enabled") + assert self.napp.controller.buffers.app.put.call_count == 1 @patch('napps.kytos.topology.main.Main.notify_link_status_change') - @patch('napps.kytos.topology.main.Main.notify_link_up_if_status') @patch('napps.kytos.topology.main.Main._get_link_from_interface') def test_notify_interface_link_status(self, *args): """Test interface links notification when enable""" (mock_get_link_from_interface, - mock_notify_link_up_if_status, mock_notify_link_status_change) = args + buffers_app_mock = MagicMock() + self.napp.controller.buffers.app = buffers_app_mock mock_link = MagicMock() mock_get_link_from_interface.return_value = mock_link - self.napp.notify_interface_link_status(MagicMock(), "link enable") + self.napp.notify_interface_link_status(MagicMock(), "link enabled") assert mock_get_link_from_interface.call_count == 1 - assert mock_notify_link_up_if_status.call_count == 1 + assert self.napp.controller.buffers.app.put.call_count == 1 - self.napp.notify_interface_link_status(MagicMock(), "link disable") + self.napp.notify_interface_link_status(MagicMock(), "link disabled") assert mock_get_link_from_interface.call_count == 2 - assert mock_notify_link_up_if_status.call_count == 1 assert mock_notify_link_status_change.call_count == 1 + assert self.napp.controller.buffers.app.put.call_count == 1 # Without notification mock_get_link_from_interface.return_value = None - self.napp.notify_interface_link_status(MagicMock(), "link enable") + self.napp.notify_interface_link_status(MagicMock(), "link enabled") assert mock_get_link_from_interface.call_count == 3 - assert mock_notify_link_up_if_status.call_count == 1 + assert self.napp.controller.buffers.app.put.call_count == 1