diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 85e8b86e..a47023f3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,6 +21,21 @@ Removed Security ======== +[5.0.0] - 2021-11.05 +******************** + +Changed +======= +- ``stored_flows`` are now indexed by cookie, issue 34 +- Changed the ``flow_persistence`` data structured on storehouse +- Refactored the consistency checks methods accordingly to use cookie indexes + + +Deprecated +========== +- The prior ``flow_persistence`` data structure isn't supported anymore. It's required to delete the ``kytos.flow.persistence`` folder, upgrading won't be supported this time. + + [4.1.2] - 2021-11.03 ******************** diff --git a/kytos.json b/kytos.json index df6e5546..85ef7625 100644 --- a/kytos.json +++ b/kytos.json @@ -3,7 +3,7 @@ "username": "kytos", "name": "flow_manager", "description": "Manage switches' flows through a REST API.", - "version": "4.1.2", + "version": "5.0.0", "napp_dependencies": ["kytos/of_core", "kytos/storehouse"], "license": "MIT", "url": "https://github.com/kytos/flow_manager.git", diff --git a/main.py b/main.py index 60ea2d20..df54dbf2 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,8 @@ """kytos/flow_manager NApp installs, lists and deletes switch flows.""" # pylint: disable=relative-beyond-top-level -from collections import OrderedDict +import itertools +from collections import OrderedDict, defaultdict from copy import deepcopy from threading import Lock @@ -104,9 +105,8 @@ def setup(self): self._storehouse_lock = Lock() # Format of stored flow data: - # {'flow_persistence': {'dpid_str': {'flow_list': [ - # {'command': '', - # 'flow': {flow_dict}}]}}} + # {'flow_persistence': {'dpid_str': {cookie_val: [ + # {'flow': {flow_dict}}]}}} self.stored_flows = {} self.resent_flows = set() @@ -122,6 +122,10 @@ def shutdown(self): """Shutdown routine of the NApp.""" log.debug("flow-manager stopping") + def stored_flows_list(self, dpid): + """Ordered list of all stored flows given a dpid.""" + return itertools.chain(*list(self.stored_flows[dpid].values())) + @listen_to("kytos/of_core.handshake.completed") def resend_stored_flows(self, event): """Resend stored Flows.""" @@ -135,11 +139,9 @@ def resend_stored_flows(self, event): log.debug(f"Flow already resent to the switch {dpid}") return if dpid in self.stored_flows: - flow_list = self.stored_flows[dpid]["flow_list"] - for flow in flow_list: - command = flow["command"] + for flow in self.stored_flows_list(dpid): flows_dict = {"flows": [flow["flow"]]} - self._install_flows(command, flows_dict, [switch]) + self._install_flows("add", flows_dict, [switch]) self.resent_flows.add(dpid) log.info(f"Flows resent to Switch {dpid}") @@ -160,94 +162,96 @@ def is_ignored(field, ignored_range): return True return False - def consistency_ignored_check(self, flow): - """Check if the flow is in the list of flows ignored by consistency. - - Check by `cookie` range and `table_id` range. - Return True if the flow is in the ignored range, otherwise return - False. - """ - # Check by cookie - if self.is_ignored(flow.cookie, self.cookie_ignored_range): - return True - - # Check by `table_id` - if self.is_ignored(flow.table_id, self.tab_id_ignored_range): - return True - return False - @listen_to("kytos/of_core.flow_stats.received") def on_flow_stats_check_consistency(self, event): """Check the consistency of a switch upon receiving flow stats.""" - if not ENABLE_CONSISTENCY_CHECK: + self.check_consistency(event.content["switch"]) + + def check_consistency(self, switch): + """Check consistency of stored and installed flows given a switch.""" + if not ENABLE_CONSISTENCY_CHECK or not switch.is_enabled(): return - switch = event.content["switch"] - if switch.is_enabled(): - self.check_storehouse_consistency(switch) - if switch.dpid in self.stored_flows: - self.check_switch_consistency(switch) + log.debug(f"check_consistency on switch {switch.id} has started") + self.check_storehouse_consistency(switch) + if switch.dpid in self.stored_flows: + self.check_switch_consistency(switch) + log.debug(f"check_consistency on switch {switch.id} is done") + + @staticmethod + def switch_flows_by_cookie(switch): + """Build switch.flows indexed by cookie.""" + installed_flows = defaultdict(list) + for cookie, flows in itertools.groupby(switch.flows, lambda x: x.cookie): + for flow in flows: + installed_flows[cookie].append(flow) + return installed_flows def check_switch_consistency(self, switch): - """Check consistency of installed flows for a specific switch.""" + """Check consistency of stored flows for a specific switch.""" dpid = switch.dpid - - # Flows stored in storehouse - stored_flows = self.stored_flows[dpid]["flow_list"] - serializer = FlowFactory.get_class(switch) + installed_flows = self.switch_flows_by_cookie(switch) - for stored_flow in stored_flows: - stored_time = get_time(stored_flow.get("created_at", "0001-01-01T00:00:00")) - if (now() - stored_time).seconds <= STATS_INTERVAL: - continue - command = stored_flow["command"] - stored_flow_obj = serializer.from_dict(stored_flow["flow"], switch) - - flow = {"flows": [stored_flow["flow"]]} + for cookie, stored_flows in self.stored_flows[dpid].items(): + for stored_flow in stored_flows: + stored_time = get_time( + stored_flow.get("created_at", "0001-01-01T00:00:00") + ) + if (now() - stored_time).seconds <= STATS_INTERVAL: + continue + stored_flow_obj = serializer.from_dict(stored_flow["flow"], switch) + if stored_flow_obj in installed_flows[cookie]: + continue - if stored_flow_obj not in switch.flows: - if command == "add": - log.info("A consistency problem was detected in " f"switch {dpid}.") - self._install_flows(command, flow, [switch], save=False) - log.info( - f"Flow forwarded to switch {dpid} to be " - f"installed. Flow: {flow}" - ) + log.info(f"Consistency check: missing flow on switch {dpid}.") + flow = {"flows": [stored_flow["flow"]]} + self._install_flows("add", flow, [switch], save=False) + log.info( + f"Flow forwarded to switch {dpid} to be installed. Flow: {flow}" + ) def check_storehouse_consistency(self, switch): """Check consistency of installed flows for a specific switch.""" dpid = switch.dpid - for installed_flow in switch.flows: - - # Check if the flow is in the ignored flow list - if self.consistency_ignored_check(installed_flow): + for cookie, flows in self.switch_flows_by_cookie(switch).items(): + if self.is_ignored(cookie, self.cookie_ignored_range): continue - if dpid not in self.stored_flows: - log.info("A consistency problem was detected in " f"switch {dpid}.") - flow = {"flows": [installed_flow.as_dict()]} - command = "delete_strict" - self._install_flows(command, flow, [switch], save=False) - log.info( - f"Flow forwarded to switch {dpid} to be deleted." f" Flow: {flow}" - ) - else: - serializer = FlowFactory.get_class(switch) - stored_flows = self.stored_flows[dpid]["flow_list"] - stored_flows_list = [ - serializer.from_dict(stored_flow["flow"], switch) - for stored_flow in stored_flows - ] + serializer = FlowFactory.get_class(switch) + stored_flows_list = [ + serializer.from_dict(stored_flow["flow"], switch) + for stored_flow in self.stored_flows.get(dpid, {}).get(cookie, []) + ] + log.debug( + f"stored_flows_list on switch {switch.id} by cookie: {hex(cookie)}: " + f"{self.stored_flows.get(dpid, {}).get(cookie, [])}" + ) + + for installed_flow in flows: + if self.is_ignored(installed_flow.table_id, self.tab_id_ignored_range): + continue + + if dpid not in self.stored_flows: + log.info( + f"Consistency check: alien flow on switch {dpid}, dpid" + " not indexed" + ) + flow = {"flows": [installed_flow.as_dict()]} + command = "delete_strict" + self._install_flows(command, flow, [switch], save=False) + log.info( + f"Flow forwarded to switch {dpid} to be deleted. Flow: {flow}" + ) + continue if installed_flow not in stored_flows_list: - log.info("A consistency problem was detected in " f"switch {dpid}.") + log.info(f"Consistency check: alien flow on switch {dpid}") flow = {"flows": [installed_flow.as_dict()]} command = "delete_strict" self._install_flows(command, flow, [switch], save=False) log.info( - f"Flow forwarded to switch {dpid} to be deleted." - f" Flow: {flow}" + f"Flow forwarded to switch {dpid} to be deleted. Flow: {flow}" ) # pylint: disable=attribute-defined-outside-init @@ -263,77 +267,94 @@ def _load_flows(self): else: log.info("Flows loaded.") - def _store_changed_flows(self, command, flow, switch): - """Store changed flows. - - Args: - command: Flow command to be installed - flow: Flows to be stored - switch: Switch target - """ + def _del_matched_flows_store(self, flow_dict, switch): + """Try to delete matching stored flows given a flow dict.""" stored_flows_box = deepcopy(self.stored_flows) - # if the flow has a destination dpid it can be stored. - if not switch: - log.info( - "The Flow cannot be stored, the destination switch " - f"have not been specified: {switch}" - ) + + if switch.id not in stored_flows_box: return - installed_flow = {} - installed_flow["command"] = command - installed_flow["flow"] = flow - installed_flow["created_at"] = now().strftime("%Y-%m-%dT%H:%M:%S") - should_persist_flow = command == "add" - deleted_flows_idxs = set() - serializer = FlowFactory.get_class(switch) - installed_flow_obj = serializer.from_dict(flow, switch) + cookies = ( + self.stored_flows[switch.id].keys() + if flow_dict.get("cookie") is None + else [int(flow_dict.get("cookie", 0))] + ) - if switch.id not in stored_flows_box: - # Switch not stored, add to box. - if should_persist_flow: - stored_flows_box[switch.id] = {"flow_list": [installed_flow]} - else: - stored_flows = stored_flows_box[switch.id].get("flow_list", []) - # Check if flow already stored - for i, stored_flow in enumerate(stored_flows): - stored_flow_obj = serializer.from_dict(stored_flow["flow"], switch) + has_deleted_any_flow = False + for cookie in cookies: + stored_flows = stored_flows_box[switch.id].get(cookie, []) + if not stored_flows: + continue + deleted_flows_idxs = set() + for i, stored_flow in enumerate(stored_flows): version = switch.connection.protocol.version - - if installed_flow["command"] == "delete": - # No strict match - if match_flow(flow, version, stored_flow["flow"]): - deleted_flows_idxs.add(i) - - elif installed_flow_obj == stored_flow_obj: - if stored_flow["command"] == installed_flow["command"]: - log.debug("Data already stored.") - return - # Flow with inconsistency in "command" fields : Remove the - # old instruction. This happens when there is a stored - # instruction to install the flow, but the new instruction - # is to remove it. In this case, the old instruction is - # removed and the new one is stored. - stored_flow["command"] = installed_flow.get("command") + # No strict match + if match_flow(flow_dict, version, stored_flow["flow"]): deleted_flows_idxs.add(i) - break - - if deleted_flows_idxs: - stored_flows = [ - flow - for i, flow in enumerate(stored_flows) - if i not in deleted_flows_idxs - ] - if should_persist_flow: - stored_flows.append(installed_flow) - stored_flows_box[switch.id]["flow_list"] = stored_flows + + if not deleted_flows_idxs: + continue + + stored_flows = [ + flow + for i, flow in enumerate(stored_flows) + if i not in deleted_flows_idxs + ] + has_deleted_any_flow = True + + if stored_flows: + stored_flows_box[switch.id][cookie] = stored_flows + else: + stored_flows_box[switch.id].pop(cookie, None) + + if has_deleted_any_flow: + stored_flows_box["id"] = "flow_persistence" + self.storehouse.save_flow(stored_flows_box) + del stored_flows_box["id"] + self.stored_flows = deepcopy(stored_flows_box) + + # pylint: disable=fixme + def _add_flow_store(self, flow_dict, switch): + """Try to add a flow dict in the store.""" + installed_flow = {} + installed_flow["flow"] = flow_dict + installed_flow["created_at"] = now().strftime("%Y-%m-%dT%H:%M:%S") + + stored_flows_box = deepcopy(self.stored_flows) + cookie = int(flow_dict.get("cookie", 0)) + if switch.id not in stored_flows_box: + stored_flows_box[switch.id] = OrderedDict() + + # TODO handle issue 23 (overlapping FlowMod add) + if not stored_flows_box[switch.id].get(cookie): + stored_flows_box[switch.id][cookie] = [installed_flow] + else: + stored_flows_box[switch.id][cookie].append(installed_flow) stored_flows_box["id"] = "flow_persistence" self.storehouse.save_flow(stored_flows_box) del stored_flows_box["id"] self.stored_flows = deepcopy(stored_flows_box) + def _store_changed_flows(self, command, flow_dict, switch): + """Store changed flows. + + Args: + command: Flow command to be installed + flow: flow dict to be stored + switch: Switch target + """ + cmd_handlers = { + "add": self._add_flow_store, + "delete": self._del_matched_flows_store, + } + if command not in cmd_handlers: + raise ValueError( + f"Invalid command: {command}, supported: {list(cmd_handlers.keys())}" + ) + return cmd_handlers[command](flow_dict, switch) + @rest("v2/flows") @rest("v2/flows/") def list(self, dpid=None): diff --git a/setup.py b/setup.py index 6b6bc1be..2369af2a 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ BASE_ENV = Path(os.environ.get("VIRTUAL_ENV", "/")) NAPP_NAME = "flow_manager" -NAPP_VERSION = "4.1.2" +NAPP_VERSION = "5.0.0" # Kytos var folder VAR_PATH = BASE_ENV / "var" / "lib" / "kytos" diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index 4e1d9aff..0addd547 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -336,10 +336,9 @@ def test_resend_stored_flows(self, mock_install_flows): mock_event = MagicMock() flow = {"command": "add", "flow": MagicMock()} - flows = {"flow_list": [flow]} mock_event.content = {"switch": switch} self.napp.controller.switches = {dpid: switch} - self.napp.stored_flows = {dpid: flows} + self.napp.stored_flows = {dpid: {0: [flow]}} self.napp.resend_stored_flows(mock_event) mock_install_flows.assert_called() @@ -365,16 +364,15 @@ def test_store_changed_flows(self, mock_save_flow, _): flows = {"flow": flow} command = "add" - flow_list = { - "flow_list": [ + stored_flows = { + 84114964: [ { "match_fields": match_fields, - "command": "delete", "flow": flow, } ] } - self.napp.stored_flows = {dpid: flow_list} + self.napp.stored_flows = {dpid: stored_flows} self.napp._store_changed_flows(command, flows, switch) mock_save_flow.assert_called() @@ -398,41 +396,114 @@ def test_check_switch_consistency_add(self, *args): flow_1 = MagicMock() flow_1.as_dict.return_value = {"flow_1": "data"} - flow_list = [{"command": "add", "flow": {"flow_1": "data"}}] + stored_flows = [{"flow": {"flow_1": "data"}}] serializer = MagicMock() serializer.flow.cookie.return_value = 0 mock_flow_factory.return_value = serializer - self.napp.stored_flows = {dpid: {"flow_list": flow_list}} + self.napp.stored_flows = {dpid: {0: stored_flows}} self.napp.check_switch_consistency(switch) mock_install_flows.assert_called() @patch("napps.kytos.flow_manager.main.Main._install_flows") @patch("napps.kytos.flow_manager.main.FlowFactory.get_class") - def test_check_switch_consistency_delete(self, *args): + def test_check_switch_flow_not_missing(self, *args): """Test check_switch_consistency method. - This test checks the case when a flow is missing in switch and have the - DELETE command. + This test checks the case when flow is not missing. """ (mock_flow_factory, mock_install_flows) = args dpid = "00:00:00:00:00:00:00:01" switch = get_switch_mock(dpid, 0x04) flow_1 = MagicMock() - flow_1.as_dict.return_value = {"flow_1": "data"} + flow_dict = { + "flow": { + "priority": 10, + "cookie": 84114904, + "match": { + "ipv4_src": "192.168.1.1", + "ipv4_dst": "192.168.0.2", + }, + "actions": [], + } + } + flow_1.cookie = 84114904 + flow_1.as_dict.return_value = flow_dict - flow_list = [{"command": "delete", "flow": {"flow_1": "data"}}] serializer = MagicMock() serializer.from_dict.return_value = flow_1 switch.flows = [flow_1] - mock_flow_factory.return_value = serializer - self.napp.stored_flows = {dpid: {"flow_list": flow_list}} + self.napp.stored_flows = { + dpid: { + 84114904: [ + { + "flow": { + "priority": 10, + "cookie": 84114904, + "match": { + "ipv4_src": "192.168.1.1", + "ipv4_dst": "192.168.0.2", + }, + "actions": [], + } + } + ] + } + } self.napp.check_switch_consistency(switch) mock_install_flows.assert_not_called() + @patch("napps.kytos.flow_manager.main.Main._install_flows") + @patch("napps.kytos.flow_manager.main.FlowFactory.get_class") + def test_check_switch_flow_missing(self, *args): + """Test check_switch_consistency method. + + This test checks the case when flow is missing. + """ + (mock_flow_factory, mock_install_flows) = args + dpid = "00:00:00:00:00:00:00:01" + switch = get_switch_mock(dpid, 0x04) + + flow_1 = MagicMock() + flow_dict = { + "flow": { + "match": { + "in_port": 1, + }, + "actions": [], + } + } + flow_1.cookie = 0 + flow_1.as_dict.return_value = flow_dict + + serializer = MagicMock() + serializer.from_dict.return_value = flow_1 + + switch.flows = [flow_1] + mock_flow_factory.return_value = serializer + self.napp.stored_flows = { + dpid: { + 84114904: [ + { + "flow": { + "priority": 10, + "cookie": 84114904, + "match": { + "ipv4_src": "192.168.1.1", + "ipv4_dst": "192.168.0.2", + }, + "actions": [], + } + } + ] + } + } + self.napp.check_switch_consistency(switch) + mock_install_flows.assert_called() + @patch("napps.kytos.flow_manager.main.Main._install_flows") @patch("napps.kytos.flow_manager.main.FlowFactory.get_class") def test_check_switch_consistency_ignore(self, *args): @@ -450,18 +521,19 @@ def test_check_switch_consistency_ignore(self, *args): flow_1 = MagicMock() flow_1.as_dict.return_value = {"flow_1": "data"} - flow_list = [ - { - "command": "add", - "created_at": now().strftime("%Y-%m-%dT%H:%M:%S"), - "flow": {"flow_1": "data"}, - } - ] + stored_flows = { + 0: [ + { + "created_at": now().strftime("%Y-%m-%dT%H:%M:%S"), + "flow": {"flow_1": "data"}, + } + ] + } serializer = MagicMock() serializer.flow.cookie.return_value = 0 mock_flow_factory.return_value = serializer - self.napp.stored_flows = {dpid: {"flow_list": flow_list}} + self.napp.stored_flows = {dpid: stored_flows} self.napp.check_switch_consistency(switch) mock_install_flows.assert_not_called() @@ -483,11 +555,11 @@ def test_check_storehouse_consistency(self, *args): switch.flows = [flow_1] - flow_list = [{"command": "add", "flow": {"flow_2": "data", "cookie": 1}}] + stored_flows = [{"flow": {"flow_2": "data", "cookie": 1}}] serializer = flow_1 mock_flow_factory.return_value = serializer - self.napp.stored_flows = {dpid: {"flow_list": flow_list}} + self.napp.stored_flows = {dpid: {0: stored_flows}} self.napp.check_storehouse_consistency(switch) mock_install_flows.assert_called() @@ -505,7 +577,6 @@ def test_no_strict_delete_with_cookie(self, *args): switch = get_switch_mock(dpid, 0x04) switch.id = dpid stored_flow = { - "command": "add", "flow": { "actions": [{"action_type": "output", "port": 4294967293}], "match": {"dl_vlan": 3799, "dl_type": 35020}, @@ -515,13 +586,13 @@ def test_no_strict_delete_with_cookie(self, *args): "cookie": 6191162389751548793, "cookie_mask": 18446744073709551615, } - flow_list = {"flow_list": [stored_flow]} + stored_flows = {0: [stored_flow]} command = "delete" - self.napp.stored_flows = {dpid: flow_list} + self.napp.stored_flows = {dpid: stored_flows} self.napp._store_changed_flows(command, flow_to_install, switch) - mock_save_flow.assert_called() - self.assertDictEqual(self.napp.stored_flows[dpid]["flow_list"][0], stored_flow) + mock_save_flow.assert_not_called() + self.assertDictEqual(self.napp.stored_flows[dpid][0][0], stored_flow) @patch("napps.kytos.flow_manager.main.Main._install_flows") @patch("napps.kytos.flow_manager.main.FlowFactory.get_class") @@ -536,7 +607,6 @@ def test_no_strict_delete(self, *args): switch = get_switch_mock(dpid, 0x04) switch.id = dpid stored_flow = { - "command": "add", "flow": { "actions": [{"action_type": "set_vlan", "vlan_id": 300}], "cookie": 6191162389751548793, @@ -544,7 +614,6 @@ def test_no_strict_delete(self, *args): }, } stored_flow2 = { - "command": "add", "flow": { "actions": [], "cookie": 4961162389751548787, @@ -555,9 +624,12 @@ def test_no_strict_delete(self, *args): "cookie": 6191162389751548793, "cookie_mask": 18446744073709551615, } - flow_list = {"flow_list": [stored_flow, stored_flow2]} + stored_flows = { + 6191162389751548793: [stored_flow], + 4961162389751548787: [stored_flow2], + } command = "delete" - self.napp.stored_flows = {dpid: flow_list} + self.napp.stored_flows = {dpid: stored_flows} self.napp._store_changed_flows(command, flow_to_install, switch) mock_save_flow.assert_called() @@ -575,43 +647,45 @@ def test_no_strict_delete_with_ipv4(self, *args): dpid = "00:00:00:00:00:00:00:01" switch = get_switch_mock(dpid, 0x04) switch.id = dpid - stored_flow = { - "command": "add", - "flow": { - "priority": 10, - "cookie": 84114904, - "match": { - "ipv4_src": "192.168.1.1", - "ipv4_dst": "192.168.0.2", - }, - "actions": [], - }, - } - stored_flow2 = { - "command": "add", - "flow": { - "actions": [], - "cookie": 4961162389751548787, - "match": {"in_port": 2}, - }, - } flow_to_install = {"match": {"ipv4_src": "192.168.1.1"}} - flow_list = {"flow_list": [stored_flow, stored_flow2]} + stored_flows = { + 84114904: [ + { + "flow": { + "priority": 10, + "cookie": 84114904, + "match": { + "ipv4_src": "192.168.1.1", + "ipv4_dst": "192.168.0.2", + }, + "actions": [], + } + } + ], + 4961162389751548787: [ + { + "flow": { + "actions": [], + "cookie": 4961162389751548787, + "match": {"in_port": 2}, + } + } + ], + } command = "delete" - self.napp.stored_flows = {dpid: flow_list} + self.napp.stored_flows = {dpid: stored_flows} self.napp._store_changed_flows(command, flow_to_install, switch) mock_save_flow.assert_called() expected_stored = { - "flow_list": [ + 4961162389751548787: [ { - "command": "add", "flow": { "actions": [], "cookie": 4961162389751548787, "match": {"in_port": 2}, }, - } + }, ] } self.assertDictEqual(self.napp.stored_flows[dpid], expected_stored) @@ -630,9 +704,8 @@ def test_no_strict_delete_in_port(self, *args): switch.id = dpid flow_to_install = {"match": {"in_port": 1}} stored_flow = { - "flow_list": [ + 0: [ { - "command": "add", "flow": { "priority": 10, "cookie": 84114904, @@ -644,14 +717,12 @@ def test_no_strict_delete_in_port(self, *args): }, }, { - "command": "add", "flow": { "actions": [], "match": {"in_port": 2}, }, }, { - "command": "add", "flow": { "priority": 20, "cookie": 84114904, @@ -671,9 +742,8 @@ def test_no_strict_delete_in_port(self, *args): mock_save_flow.assert_called() expected_stored = { - "flow_list": [ + 0: [ { - "command": "add", "flow": {"actions": [], "match": {"in_port": 2}}, } ] @@ -694,24 +764,20 @@ def test_no_strict_delete_all_if_empty_match(self, *args): switch.id = dpid flow_to_install = {"match": {}} stored_flow = { - "flow_list": [ + 0: [ { - "command": "add", "flow": { "priority": 10, - "cookie": 84114904, "match": { "in_port": 1, "dl_vlan": 100, }, "actions": [], - }, + } }, { - "command": "add", "flow": { "priority": 20, - "cookie": 84114904, "match": { "in_port": 1, "dl_vlan": 102, @@ -727,7 +793,7 @@ def test_no_strict_delete_all_if_empty_match(self, *args): self.napp._store_changed_flows(command, flow_to_install, switch) mock_save_flow.assert_called() - expected_stored = {"flow_list": []} + expected_stored = {} self.assertDictEqual(self.napp.stored_flows[dpid], expected_stored) @patch("napps.kytos.flow_manager.main.Main._install_flows") @@ -743,7 +809,6 @@ def test_no_strict_delete_with_ipv4_fail(self, *args): switch = get_switch_mock(dpid, 0x04) switch.id = dpid stored_flow = { - "command": "add", "flow": { "priority": 10, "cookie": 84114904, @@ -755,7 +820,6 @@ def test_no_strict_delete_with_ipv4_fail(self, *args): }, } stored_flow2 = { - "command": "add", "flow": { "actions": [], "cookie": 4961162389751548787, @@ -763,16 +827,15 @@ def test_no_strict_delete_with_ipv4_fail(self, *args): }, } flow_to_install = {"match": {"ipv4_src": "192.168.20.20"}} - flow_list = {"flow_list": [stored_flow, stored_flow2]} + stored_flows = {0: [stored_flow, stored_flow2]} command = "delete" - self.napp.stored_flows = {dpid: flow_list} + self.napp.stored_flows = {dpid: stored_flows} self.napp._store_changed_flows(command, flow_to_install, switch) - mock_save_flow.assert_called() + mock_save_flow.assert_not_called() expected_stored = { - "flow_list": [ + 0: [ { - "command": "add", "flow": { "priority": 10, "cookie": 84114904, @@ -784,7 +847,6 @@ def test_no_strict_delete_with_ipv4_fail(self, *args): }, }, { - "command": "add", "flow": { "actions": [], "cookie": 4961162389751548787, @@ -854,13 +916,13 @@ def test_no_strict_delete_of10(self, *args): }, } flow_to_install = {"match": {"in_port": 80, "wildcards": 4194303}} - flow_list = {"flow_list": [stored_flow, stored_flow2]} + stored_flows = {0: [stored_flow, stored_flow2]} command = "delete" - self.napp.stored_flows = {dpid: flow_list} + self.napp.stored_flows = {dpid: stored_flows} self.napp._store_changed_flows(command, flow_to_install, switch) mock_save_flow.assert_called() - self.assertEqual(len(self.napp.stored_flows[dpid]["flow_list"]), 0) + self.assertEqual(len(self.napp.stored_flows[dpid]), 0) @patch("napps.kytos.flow_manager.main.Main._install_flows") @patch("napps.kytos.flow_manager.main.FlowFactory.get_class") @@ -890,7 +952,7 @@ def test_consistency_cookie_ignored_range(self, *args): flow.as_dict.return_value = {"flow_1": "data", "cookie": cookie} switch.flows = [flow] mock_flow_factory.return_value = flow - self.napp.stored_flows = {dpid: {"flow_list": flow}} + self.napp.stored_flows = {dpid: {0: [flow]}} self.napp.check_storehouse_consistency(switch) self.assertEqual(mock_install_flows.call_count, called) @@ -918,6 +980,6 @@ def test_consistency_table_id_ignored_range(self, *args): flow.as_dict.return_value = {"flow_1": "data", "cookie": table_id} switch.flows = [flow] mock_flow_factory.return_value = flow - self.napp.stored_flows = {dpid: {"flow_list": flow}} + self.napp.stored_flows = {dpid: {0: [flow]}} self.napp.check_storehouse_consistency(switch) self.assertEqual(mock_install_flows.call_count, called)