From 56d17209b5398f18857e3bdf6b9ccd49cb5f031c Mon Sep 17 00:00:00 2001 From: Italo Valcy Date: Thu, 22 Feb 2024 12:15:42 -0300 Subject: [PATCH 1/7] adding lock for link down handler and to individual EVCs, minor refactor on link down handler --- main.py | 91 +++++++++++++++++++++++++++------------------------ models/evc.py | 7 ++-- 2 files changed, 53 insertions(+), 45 deletions(-) diff --git a/main.py b/main.py index 34b5ce27..d7f54ea8 100755 --- a/main.py +++ b/main.py @@ -68,6 +68,7 @@ def setup(self): self._lock_interfaces = defaultdict(Lock) self.table_group = {"epl": 0, "evpl": 0} self._lock = Lock() + self._lock_handle_link_down = Lock() self.execute_as_loop(settings.DEPLOY_EVCS_INTERVAL) self.load_all_evcs() @@ -800,9 +801,9 @@ def handle_interface_link_down(self, interface): """ Handler for interface link_down events """ + log.info("Event handle_interface_link_down %s", interface) for evc in self.get_evcs_by_svc_level(): with evc.lock: - log.info("Event handle_interface_link_down %s", interface) evc.handle_interface_link_down( interface ) @@ -810,7 +811,8 @@ def handle_interface_link_down(self, interface): @listen_to("kytos/topology.link_down") def on_link_down(self, event): """Change circuit when link is down or under_mantenance.""" - self.handle_link_down(event) + with self._lock_handle_link_down: + self.handle_link_down(event) def handle_link_down(self, event): # pylint: disable=too-many-branches """Change circuit when link is down or under_mantenance.""" @@ -821,31 +823,36 @@ def handle_link_down(self, event): # pylint: disable=too-many-branches evcs_normal = [] check_failover = [] for evc in self.get_evcs_by_svc_level(): - if evc.is_affected_by_link(link): - # if there is no failover path, handles link down the - # tradditional way - if ( - not getattr(evc, 'failover_path', None) or - evc.is_failover_path_affected_by_link(link) - ): - evcs_normal.append(evc) - continue - try: - dpid_flows = evc.get_failover_flows() - # pylint: disable=broad-except - except Exception: - err = traceback.format_exc().replace("\n", ", ") - log.error( - f"Ignore Failover path for {evc} due to error: {err}" - ) - evcs_normal.append(evc) - continue - for dpid, flows in dpid_flows.items(): - switch_flows.setdefault(dpid, []) - switch_flows[dpid].extend(flows) - evcs_with_failover.append(evc) - else: - check_failover.append(evc) + with evc.lock: + if evc.is_affected_by_link(link): + # if there is no failover path, handles link down the + # tradditional way + if ( + not getattr(evc, 'failover_path', None) or + evc.is_failover_path_affected_by_link(link) + ): + evcs_normal.append(evc) + continue + try: + dpid_flows = evc.get_failover_flows() + evc.old_path = evc.current_path + evc.current_path = evc.failover_path + evc.failover_path = Path([]) + evc.sync() + # pylint: disable=broad-except + except Exception: + err = traceback.format_exc().replace("\n", ", ") + log.error( + f"Ignore Failover path for {evc} due to error: {err}" + ) + evcs_normal.append(evc) + continue + for dpid, flows in dpid_flows.items(): + switch_flows.setdefault(dpid, []) + switch_flows[dpid].extend(flows) + evcs_with_failover.append(evc) + elif evc.is_failover_path_affected_by_link(link): + check_failover.append(evc) while switch_flows: offset = settings.BATCH_SIZE or None @@ -866,23 +873,18 @@ def handle_link_down(self, event): # pylint: disable=too-many-branches switch_flows[dpid] = switch_flows[dpid][offset:] time.sleep(settings.BATCH_INTERVAL) - for evc in evcs_with_failover: - with evc.lock: - old_path = evc.current_path - evc.current_path = evc.failover_path - evc.failover_path = old_path - evc.sync() - emit_event(self.controller, "redeployed_link_down", - content=map_evc_event_content(evc)) - log.info( - f"{evc} redeployed with failover due to link down {link.id}" - ) - for evc in evcs_normal: emit_event( self.controller, "evc_affected_by_link_down", - content={"link_id": link.id} | map_evc_event_content(evc) + content={"link": link} | map_evc_event_content(evc) + ) + + for evc in evcs_with_failover: + emit_event(self.controller, "redeployed_link_down", + content=map_evc_event_content(evc, old_path=evc.old_path)) + log.info( + f"{evc} redeployed with failover due to link down {link.id}" ) # After handling the hot path, check if new failover paths are needed. @@ -902,14 +904,16 @@ def on_evc_affected_by_link_down(self, event): def handle_evc_affected_by_link_down(self, event): """Change circuit when link is down or under_mantenance.""" evc = self.circuits.get(event.content["evc_id"]) - link_id = event.content['link_id'] + link = event.content['link'] if not evc: return with evc.lock: + if not evc.is_affected_by_link(link): + return result = evc.handle_link_down() event_name = "error_redeploy_link_down" if result: - log.info(f"{evc} redeployed due to link down {link_id}") + log.info(f"{evc} redeployed due to link down {link.id}") event_name = "redeployed_link_down" emit_event(self.controller, event_name, content=map_evc_event_content(evc)) @@ -922,10 +926,11 @@ def on_evc_deployed(self, event): def handle_evc_deployed(self, event): """Setup failover path on evc deployed.""" evc = self.circuits.get(event.content["evc_id"]) + old_path = event.content.get("old_path") if not evc: return with evc.lock: - evc.setup_failover_path() + evc.setup_failover_path(old_path) @listen_to("kytos/topology.topology_loaded") def on_topology_loaded(self, event): # pylint: disable=unused-argument diff --git a/models/evc.py b/models/evc.py index 44da0f7c..3cc9c4a0 100644 --- a/models/evc.py +++ b/models/evc.py @@ -872,7 +872,7 @@ def deploy_to_path(self, path=None): # pylint: disable=too-many-branches log.info(f"{self} was deployed.") return True - def setup_failover_path(self): + def setup_failover_path(self, old_path=None): """Install flows for the failover path of this EVC. Procedures to deploy: @@ -892,8 +892,11 @@ def setup_failover_path(self): if not self.is_eligible_for_failover_path(): return False + if not old_path: + old_path = self.failover_path + reason = "" - self.remove_path_flows(self.failover_path) + self.remove_path_flows(old_path) self.failover_path = Path([]) for use_path in self.get_failover_path_candidates(): if not use_path: From 59054fd41a792f1e4faa0d2ba38c9e65d7890bc6 Mon Sep 17 00:00:00 2001 From: Italo Valcy Date: Thu, 22 Feb 2024 12:27:46 -0300 Subject: [PATCH 2/7] fix lint errors --- main.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index d7f54ea8..e1b5843c 100755 --- a/main.py +++ b/main.py @@ -843,7 +843,8 @@ def handle_link_down(self, event): # pylint: disable=too-many-branches except Exception: err = traceback.format_exc().replace("\n", ", ") log.error( - f"Ignore Failover path for {evc} due to error: {err}" + "Ignore Failover path for " + f"{evc} due to error: {err}" ) evcs_normal.append(evc) continue @@ -881,8 +882,11 @@ def handle_link_down(self, event): # pylint: disable=too-many-branches ) for evc in evcs_with_failover: - emit_event(self.controller, "redeployed_link_down", - content=map_evc_event_content(evc, old_path=evc.old_path)) + emit_event( + self.controller, + "redeployed_link_down", + content=map_evc_event_content(evc, old_path=evc.old_path) + ) log.info( f"{evc} redeployed with failover due to link down {link.id}" ) From 60a681b9eca5b223c367c505bc3e568e29724020 Mon Sep 17 00:00:00 2001 From: Italo Valcy Date: Thu, 22 Feb 2024 14:41:09 -0300 Subject: [PATCH 3/7] fix unit tests --- tests/unit/test_main.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index c0b38b40..a32ce41a 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -1897,6 +1897,7 @@ def test_handle_link_down(self, emit_event_mock, settings_mock, _): "2": ["flow1", "flow2"], "3": ["flow3", "flow4", "flow5", "flow6"], } + evc4_current_path = evc4.current_path evc5 = MagicMock(id="5", service_level=7, creation_time=1) evc5.is_affected_by_link.return_value = True evc5.is_failover_path_affected_by_link.return_value = False @@ -1974,7 +1975,7 @@ def test_handle_link_down(self, emit_event_mock, settings_mock, _): # evc3 should be handled before evc1 emit_event_mock.assert_has_calls([ call(self.napp.controller, event_name, content={ - "link_id": "123", + "link": link, "evc_id": "6", "name": "name", "metadata": "mock", @@ -1984,7 +1985,7 @@ def test_handle_link_down(self, emit_event_mock, settings_mock, _): "uni_z": uni.as_dict(), }), call(self.napp.controller, event_name, content={ - "link_id": "123", + "link": link, "evc_id": "3", "name": "name", "metadata": "mock", @@ -1994,7 +1995,7 @@ def test_handle_link_down(self, emit_event_mock, settings_mock, _): "uni_z": uni.as_dict(), }), call(self.napp.controller, event_name, content={ - "link_id": "123", + "link": link, "evc_id": "1", "name": "name", "metadata": "mock", @@ -2008,6 +2009,7 @@ def test_handle_link_down(self, emit_event_mock, settings_mock, _): event_name = "redeployed_link_down" emit_event_mock.assert_has_calls([ call(self.napp.controller, event_name, content={ + "old_path": evc4_current_path, "evc_id": "4", "name": "name", "metadata": "mock", @@ -2046,7 +2048,7 @@ def test_handle_evc_affected_by_link_down(self, emit_event_mock): event = KytosEvent(name="e1", content={ "evc_id": "3", - "link_id": "1", + "link": MagicMock(), }) self.napp.handle_evc_affected_by_link_down(event) emit_event_mock.assert_not_called() From a874260833554085f01703a483bc35bf7d77cb55 Mon Sep 17 00:00:00 2001 From: Italo Valcy Date: Thu, 22 Feb 2024 15:07:20 -0300 Subject: [PATCH 4/7] using pool dynamic_single instaed of Lock to guarantee single execution of link down handler --- main.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index e1b5843c..efcee551 100755 --- a/main.py +++ b/main.py @@ -68,7 +68,6 @@ def setup(self): self._lock_interfaces = defaultdict(Lock) self.table_group = {"epl": 0, "evpl": 0} self._lock = Lock() - self._lock_handle_link_down = Lock() self.execute_as_loop(settings.DEPLOY_EVCS_INTERVAL) self.load_all_evcs() @@ -808,11 +807,10 @@ def handle_interface_link_down(self, interface): interface ) - @listen_to("kytos/topology.link_down") + @listen_to("kytos/topology.link_down", pool="dynamic_single") def on_link_down(self, event): """Change circuit when link is down or under_mantenance.""" - with self._lock_handle_link_down: - self.handle_link_down(event) + self.handle_link_down(event) def handle_link_down(self, event): # pylint: disable=too-many-branches """Change circuit when link is down or under_mantenance.""" From cca48044f66edc5ba9ba1b7928f7c95a0001c1df Mon Sep 17 00:00:00 2001 From: Italo Valcy Date: Thu, 22 Feb 2024 15:33:01 -0300 Subject: [PATCH 5/7] remove evc.sync() from the hot path to reduce impact on fast failover --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index efcee551..4542218e 100755 --- a/main.py +++ b/main.py @@ -836,7 +836,6 @@ def handle_link_down(self, event): # pylint: disable=too-many-branches evc.old_path = evc.current_path evc.current_path = evc.failover_path evc.failover_path = Path([]) - evc.sync() # pylint: disable=broad-except except Exception: err = traceback.format_exc().replace("\n", ", ") @@ -880,6 +879,7 @@ def handle_link_down(self, event): # pylint: disable=too-many-branches ) for evc in evcs_with_failover: + evc.sync() emit_event( self.controller, "redeployed_link_down", From 272062d3bf70b329b5a955761116ab940f33fafc Mon Sep 17 00:00:00 2001 From: Italo Valcy Date: Wed, 6 Mar 2024 05:43:42 -0300 Subject: [PATCH 6/7] renamed update_evcs to update_evcs_metadata; created update_evcs to bulk update EVCs dict; delegate evc.setup_failover_path to consistency routine; create new event to cleanup old failover path --- controllers/__init__.py | 33 ++++++++++++++++- main.py | 67 +++++++++++++++++++++------------- models/evc.py | 7 +--- tests/unit/test_controllers.py | 14 +++++-- tests/unit/test_main.py | 40 ++++++++++---------- 5 files changed, 107 insertions(+), 54 deletions(-) diff --git a/controllers/__init__.py b/controllers/__init__.py index 48812993..1b03159c 100644 --- a/controllers/__init__.py +++ b/controllers/__init__.py @@ -121,8 +121,37 @@ def update_evc(self, evc: Dict) -> Optional[Dict]: ) return updated - def update_evcs(self, circuit_ids: list, metadata: dict, action: str): - """Update a bulk of EVC""" + def update_evcs(self, evcs: list[dict]) -> int: + """Update EVCs and return the number of modified documents.""" + if not evcs: + return 0 + + ops = [] + utc_now = datetime.utcnow() + + for evc in evcs: + evc["updated_at"] = utc_now + model = EVCBaseDoc( + **{ + **evc, + **{"_id": evc["id"]} + } + ).dict(exclude_none=True) + ops.append( + UpdateOne( + {"_id": evc["id"]}, + { + "$set": model, + "$setOnInsert": {"inserted_at": utc_now} + }, + ) + ) + return self.db.evcs.bulk_write(ops).modified_count + + def update_evcs_metadata( + self, circuit_ids: list, metadata: dict, action: str + ): + """Bulk update EVCs metadata.""" utc_now = datetime.utcnow() metadata = {f"metadata.{k}": v for k, v in metadata.items()} if action == "add": diff --git a/main.py b/main.py index 4542218e..eb2befae 100755 --- a/main.py +++ b/main.py @@ -126,6 +126,16 @@ def execute_consistency(self): stored_circuits.pop(circuit.id, None) if self.should_be_checked(circuit): circuits_to_check.append(circuit) + # setup failover_path whenever possible + if getattr(circuit, "failover_path", None): + continue + affected_at = getattr(circuit, "affected_by_link_at", None) + if ( + not affected_at or + (now() - affected_at).seconds >= settings.DEPLOY_EVCS_INTERVAL + ): + with circuit.lock: + circuit.setup_failover_path() circuits_checked = EVCDeploy.check_list_traces(circuits_to_check) for circuit in circuits_to_check: is_checked = circuits_checked.get(circuit.id) @@ -470,7 +480,7 @@ def bulk_add_metadata(self, request: Request) -> JSONResponse: data = get_json_or_400(request, self.controller.loop) circuit_ids = data.pop("circuit_ids") - self.mongo_controller.update_evcs(circuit_ids, data, "add") + self.mongo_controller.update_evcs_metadata(circuit_ids, data, "add") fail_evcs = [] for _id in circuit_ids: @@ -511,7 +521,9 @@ def bulk_delete_metadata(self, request: Request) -> JSONResponse: data = get_json_or_400(request, self.controller.loop) key = request.path_params["key"] circuit_ids = data.pop("circuit_ids") - self.mongo_controller.update_evcs(circuit_ids, {key: ""}, "del") + self.mongo_controller.update_evcs_metadata( + circuit_ids, {key: ""}, "del" + ) fail_evcs = [] for _id in circuit_ids: @@ -823,6 +835,7 @@ def handle_link_down(self, event): # pylint: disable=too-many-branches for evc in self.get_evcs_by_svc_level(): with evc.lock: if evc.is_affected_by_link(link): + evc.affected_by_link_at = event.timestamp # if there is no failover path, handles link down the # tradditional way if ( @@ -850,6 +863,8 @@ def handle_link_down(self, event): # pylint: disable=too-many-branches switch_flows[dpid].extend(flows) evcs_with_failover.append(evc) elif evc.is_failover_path_affected_by_link(link): + evc.old_path = evc.failover_path + evc.failover_path = Path([]) check_failover.append(evc) while switch_flows: @@ -878,25 +893,27 @@ def handle_link_down(self, event): # pylint: disable=too-many-branches content={"link": link} | map_evc_event_content(evc) ) + evcs_to_update = [] for evc in evcs_with_failover: - evc.sync() + evcs_to_update.append(evc.as_dict()) emit_event( self.controller, "redeployed_link_down", - content=map_evc_event_content(evc, old_path=evc.old_path) + content=map_evc_event_content(evc) ) log.info( f"{evc} redeployed with failover due to link down {link.id}" ) - - # After handling the hot path, check if new failover paths are needed. - # Note that EVCs affected by link down will generate a KytosEvent for - # deployed|redeployed, which will trigger the failover path setup. - # Thus, we just need to further check the check_failover list for evc in check_failover: - if evc.is_failover_path_affected_by_link(link): - with evc.lock: - evc.setup_failover_path() + evcs_to_update.append(evc.as_dict()) + + self.mongo_controller.update_evcs(evcs_to_update) + + emit_event( + self.controller, + "cleanup_evcs_old_path", + content={"evcs": evcs_with_failover + check_failover} + ) @listen_to("kytos/mef_eline.evc_affected_by_link_down") def on_evc_affected_by_link_down(self, event): @@ -920,19 +937,19 @@ def handle_evc_affected_by_link_down(self, event): emit_event(self.controller, event_name, content=map_evc_event_content(evc)) - @listen_to("kytos/mef_eline.(redeployed_link_(up|down)|deployed)") - def on_evc_deployed(self, event): - """Handle EVC deployed|redeployed_link_down.""" - self.handle_evc_deployed(event) - - def handle_evc_deployed(self, event): - """Setup failover path on evc deployed.""" - evc = self.circuits.get(event.content["evc_id"]) - old_path = event.content.get("old_path") - if not evc: - return - with evc.lock: - evc.setup_failover_path(old_path) + @listen_to("kytos/mef_eline.cleanup_evcs_old_path") + def on_cleanup_evcs_old_path(self, event): + """Handle cleanup evcs old path.""" + self.handle_cleanup_evcs_old_path(event) + + def handle_cleanup_evcs_old_path(self, event): + """Handle cleanup evcs old path.""" + evcs = event.content.get("evcs", []) + for evc in evcs: + if not hasattr(evc, 'old_path'): + continue + evc.remove_path_flows(evc.old_path) + delattr(evc, "old_path") @listen_to("kytos/topology.topology_loaded") def on_topology_loaded(self, event): # pylint: disable=unused-argument diff --git a/models/evc.py b/models/evc.py index 3cc9c4a0..44da0f7c 100644 --- a/models/evc.py +++ b/models/evc.py @@ -872,7 +872,7 @@ def deploy_to_path(self, path=None): # pylint: disable=too-many-branches log.info(f"{self} was deployed.") return True - def setup_failover_path(self, old_path=None): + def setup_failover_path(self): """Install flows for the failover path of this EVC. Procedures to deploy: @@ -892,11 +892,8 @@ def setup_failover_path(self, old_path=None): if not self.is_eligible_for_failover_path(): return False - if not old_path: - old_path = self.failover_path - reason = "" - self.remove_path_flows(old_path) + self.remove_path_flows(self.failover_path) self.failover_path = Path([]) for use_path in self.get_failover_path_candidates(): if not use_path: diff --git a/tests/unit/test_controllers.py b/tests/unit/test_controllers.py index 3789b4ea..a467da56 100644 --- a/tests/unit/test_controllers.py +++ b/tests/unit/test_controllers.py @@ -76,11 +76,19 @@ def test_upsert_evc(self): self.eline.upsert_evc(self.evc_dict) assert self.eline.db.evcs.find_one_and_update.call_count == 1 - def test_update_evcs(self): - """Test update_evcs""" + def test_update_evcs_metadata(self): + """Test update_evcs_metadata""" circuit_ids = ["123", "456", "789"] metadata = {"info": "testing"} - self.eline.update_evcs(circuit_ids, metadata, "add") + self.eline.update_evcs_metadata(circuit_ids, metadata, "add") arg = self.eline.db.evcs.bulk_write.call_args[0][0] assert len(arg) == 3 assert self.eline.db.evcs.bulk_write.call_count == 1 + + def test_update_evcs(self): + """Test update_evcs""" + evc2 = dict(self.evc_dict | {"id": "456"}) + self.eline.update_evcs([self.evc_dict, evc2]) + arg = self.eline.db.evcs.bulk_write.call_args[0][0] + assert len(arg) == 2 + assert self.eline.db.evcs.bulk_write.call_count == 1 diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index a32ce41a..2ee8f4cb 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -1879,6 +1879,8 @@ def test_handle_link_down(self, emit_event_mock, settings_mock, _): evc1.failover_path = None evc2 = MagicMock(id="2", service_level=6, creation_time=1) evc2.is_affected_by_link.return_value = False + evc2.is_failover_path_affected_by_link.return_value = True + evc2.as_dict.return_value = {"id": "2"} evc3 = MagicMock(id="3", service_level=5, creation_time=1, metadata="mock", _active="true", _enabled="true", uni_a=uni, uni_z=uni) @@ -1897,7 +1899,7 @@ def test_handle_link_down(self, emit_event_mock, settings_mock, _): "2": ["flow1", "flow2"], "3": ["flow3", "flow4", "flow5", "flow6"], } - evc4_current_path = evc4.current_path + evc4.as_dict.return_value = {"id": "4"} evc5 = MagicMock(id="5", service_level=7, creation_time=1) evc5.is_affected_by_link.return_value = True evc5.is_failover_path_affected_by_link.return_value = False @@ -1906,6 +1908,7 @@ def test_handle_link_down(self, emit_event_mock, settings_mock, _): "4": ["flow7", "flow8"], "5": ["flow9", "flow10"], } + evc5.as_dict.return_value = {"id": "5"} evc6 = MagicMock(id="6", service_level=8, creation_time=1, metadata="mock", _active="true", _enabled="true", uni_a=uni, uni_z=uni) @@ -2005,11 +2008,12 @@ def test_handle_link_down(self, emit_event_mock, settings_mock, _): "uni_z": uni.as_dict(), }), ]) - evc4.sync.assert_called_once() + self.napp.mongo_controller.update_evcs.assert_called_with( + [{"id": "5"}, {"id": "4"}, {"id": "2"}] + ) event_name = "redeployed_link_down" emit_event_mock.assert_has_calls([ call(self.napp.controller, event_name, content={ - "old_path": evc4_current_path, "evc_id": "4", "name": "name", "metadata": "mock", @@ -2080,19 +2084,17 @@ def test_handle_evc_affected_by_link_down(self, emit_event_mock): } ) - def test_handle_evc_deployed(self): - """Test handle_evc_deployed method.""" - evc = create_autospec(EVC, id="1") - evc.lock = MagicMock() - self.napp.circuits = {"1": evc} + def test_cleanup_evcs_old_path(self): + """Test handle_cleanup_evcs_old_path method.""" + evc1 = create_autospec(EVC, id="1", old_path=["1"]) + evc2 = create_autospec(EVC, id="2", old_path=["2"]) + evc3 = create_autospec(EVC, id="3") - event = KytosEvent(name="e1", content={"evc_id": "2"}) - self.napp.handle_evc_deployed(event) - evc.setup_failover_path.assert_not_called() - - event.content["evc_id"] = "1" - self.napp.handle_evc_deployed(event) - evc.setup_failover_path.assert_called() + event = KytosEvent(name="e1", content={"evcs": [evc1, evc2, evc3]}) + self.napp.handle_cleanup_evcs_old_path(event) + evc1.remove_path_flows.assert_called_with(["1"]) + evc2.remove_path_flows.assert_called_with(["2"]) + evc3.remove_path_flows.assert_not_called() async def test_add_metadata(self, event_loop): """Test method to add metadata""" @@ -2318,12 +2320,12 @@ async def test_add_bulk_metadata(self, event_loop): json=payload ) assert response.status_code == 201 - args = self.napp.mongo_controller.update_evcs.call_args[0] + args = self.napp.mongo_controller.update_evcs_metadata.call_args[0] ids = payload.pop("circuit_ids") assert args[0] == ids assert args[1] == payload assert args[2] == "add" - calls = self.napp.mongo_controller.update_evcs.call_count + calls = self.napp.mongo_controller.update_evcs_metadata.call_count assert calls == 1 evc_mock.extend_metadata.assert_called_with(payload) @@ -2390,11 +2392,11 @@ async def test_delete_bulk_metadata(self, event_loop): json=payload ) assert response.status_code == 200 - args = self.napp.mongo_controller.update_evcs.call_args[0] + args = self.napp.mongo_controller.update_evcs_metadata.call_args[0] assert args[0] == payload["circuit_ids"] assert args[1] == {"metadata1": ""} assert args[2] == "del" - calls = self.napp.mongo_controller.update_evcs.call_count + calls = self.napp.mongo_controller.update_evcs_metadata.call_count assert calls == 1 assert evc_mock.remove_metadata.call_count == 1 From 9fb511b73e192f67ad5c3b59ca791de74422c537 Mon Sep 17 00:00:00 2001 From: Italo Valcy Date: Fri, 8 Mar 2024 10:59:55 -0300 Subject: [PATCH 7/7] adjusting code to make easy to understand some EVC attrs and re-introducing try_setup_failover when EVC gets redeployed --- main.py | 29 ++++++++++++++++------------- models/evc.py | 10 ++++++++++ tests/unit/test_main.py | 2 +- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/main.py b/main.py index eb2befae..fffe49d4 100755 --- a/main.py +++ b/main.py @@ -126,16 +126,7 @@ def execute_consistency(self): stored_circuits.pop(circuit.id, None) if self.should_be_checked(circuit): circuits_to_check.append(circuit) - # setup failover_path whenever possible - if getattr(circuit, "failover_path", None): - continue - affected_at = getattr(circuit, "affected_by_link_at", None) - if ( - not affected_at or - (now() - affected_at).seconds >= settings.DEPLOY_EVCS_INTERVAL - ): - with circuit.lock: - circuit.setup_failover_path() + circuit.try_setup_failover_path() circuits_checked = EVCDeploy.check_list_traces(circuits_to_check) for circuit in circuits_to_check: is_checked = circuits_checked.get(circuit.id) @@ -839,7 +830,7 @@ def handle_link_down(self, event): # pylint: disable=too-many-branches # if there is no failover path, handles link down the # tradditional way if ( - not getattr(evc, 'failover_path', None) or + not evc.failover_path or evc.is_failover_path_affected_by_link(link) ): evcs_normal.append(evc) @@ -937,6 +928,18 @@ def handle_evc_affected_by_link_down(self, event): emit_event(self.controller, event_name, content=map_evc_event_content(evc)) + @listen_to("kytos/mef_eline.(redeployed_link_(up|down)|deployed)") + def on_evc_deployed(self, event): + """Handle EVC deployed|redeployed_link_down.""" + self.handle_evc_deployed(event) + + def handle_evc_deployed(self, event): + """Setup failover path on evc deployed.""" + evc = self.circuits.get(event.content["evc_id"]) + if not evc: + return + evc.try_setup_failover_path() + @listen_to("kytos/mef_eline.cleanup_evcs_old_path") def on_cleanup_evcs_old_path(self, event): """Handle cleanup evcs old path.""" @@ -946,10 +949,10 @@ def handle_cleanup_evcs_old_path(self, event): """Handle cleanup evcs old path.""" evcs = event.content.get("evcs", []) for evc in evcs: - if not hasattr(evc, 'old_path'): + if not evc.old_path: continue evc.remove_path_flows(evc.old_path) - delattr(evc, "old_path") + evc.old_path = Path([]) @listen_to("kytos/topology.topology_loaded") def on_topology_loaded(self, event): # pylint: disable=unused-argument diff --git a/models/evc.py b/models/evc.py index 44da0f7c..a9c6ddd0 100644 --- a/models/evc.py +++ b/models/evc.py @@ -138,6 +138,8 @@ def __init__(self, controller, **kwargs): self.current_links_cache = set() self.primary_links_cache = set() self.backup_links_cache = set() + self.affected_by_link_at = get_time("0001-01-01T00:00:00") + self.old_path = Path([]) self.lock = Lock() @@ -872,6 +874,14 @@ def deploy_to_path(self, path=None): # pylint: disable=too-many-branches log.info(f"{self} was deployed.") return True + def try_setup_failover_path(self, wait=settings.DEPLOY_EVCS_INTERVAL): + """Try setup failover_path whenever possible.""" + if self.failover_path: + return + if (now() - self.affected_by_link_at).seconds >= wait: + with self.lock: + self.setup_failover_path() + def setup_failover_path(self): """Install flows for the failover path of this EVC. diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index 2ee8f4cb..5508a8b2 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -2088,7 +2088,7 @@ def test_cleanup_evcs_old_path(self): """Test handle_cleanup_evcs_old_path method.""" evc1 = create_autospec(EVC, id="1", old_path=["1"]) evc2 = create_autospec(EVC, id="2", old_path=["2"]) - evc3 = create_autospec(EVC, id="3") + evc3 = create_autospec(EVC, id="3", old_path=[]) event = KytosEvent(name="e1", content={"evcs": [evc1, evc2, evc3]}) self.napp.handle_cleanup_evcs_old_path(event)