Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added notification methods #115

Merged
merged 6 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ def enable_switch(self, dpid):

self.notify_switch_enabled(dpid)
self.notify_topology_update()
self.notify_switch_links_status(switch, "link enable")
viniarck marked this conversation as resolved.
Show resolved Hide resolved
return jsonify("Operation successful"), 201

@rest('v3/switches/<dpid>/disable', methods=['POST'])
Expand All @@ -283,6 +284,7 @@ def disable_switch(self, dpid):

self.notify_switch_disabled(dpid)
self.notify_topology_update()
self.notify_switch_links_status(switch, "link disable")
return jsonify("Operation successful"), 201

@rest('v3/switches/<dpid>/metadata')
Expand Down Expand Up @@ -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_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_status(interface, "link enable")
self.topo_controller.upsert_switch(switch.id, switch.as_dict())
self.notify_topology_update()
return jsonify("Operation successful"), 200
Expand All @@ -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_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_status(interface, "link disable")
self.topo_controller.upsert_switch(switch.id, switch.as_dict())
self.notify_topology_update()
return jsonify("Operation successful"), 200
Expand Down Expand Up @@ -764,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")
Expand All @@ -783,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."""
Expand All @@ -806,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):
Expand Down Expand Up @@ -910,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):
Expand Down Expand Up @@ -948,6 +954,13 @@ def notify_switch_enabled(self, dpid):
event = KytosEvent(name=name, content={'dpid': dpid})
self.controller.buffers.app.put(event)

def notify_switch_links_status(self, switch, reason):
"""Send an event to notify the status of a link in a switch"""
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)
viniarck marked this conversation as resolved.
Show resolved Hide resolved

def notify_switch_disabled(self, dpid):
"""Send an event to notify that a switch is disabled."""
name = 'kytos/topology.switch.disabled'
Expand All @@ -961,6 +974,13 @@ def notify_topology_update(self):
self._get_topology()})
self.controller.buffers.app.put(event)

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_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."""
name = 'kytos/topology.'
Expand Down
47 changes: 43 additions & 4 deletions tests/unit/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_status')
@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_status):
"""Test enable_switch."""
dpid = "00:00:00:00:00:00:00:01"
mock_switch = get_switch_mock(dpid)
Expand All @@ -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_status.assert_called()

# fail case
mock_switch.enable.call_count = 0
Expand All @@ -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_status')
@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_status):
"""Test disable_switch."""
dpid = "00:00:00:00:00:00:00:01"
mock_switch = get_switch_mock(dpid)
Expand All @@ -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_status.assert_called()

# fail case
mock_switch.disable.call_count = 0
Expand Down Expand Up @@ -1567,17 +1571,52 @@ 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()
mock_notify_link.assert_called()

assert mock_sleep.call_count == 2

@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)
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

# Without notification
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_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_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

# 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_notify_link_up_if_status.call_count == 1