diff --git a/gns3server/controller/link.py b/gns3server/controller/link.py index 3432a831..91c06a10 100644 --- a/gns3server/controller/link.py +++ b/gns3server/controller/link.py @@ -122,6 +122,7 @@ class Link: self._streaming_pcap = None self._created = False self._link_type = "ethernet" + self._suspend = False self._filters = {} @property @@ -131,6 +132,15 @@ class Link: """ return self._filters + def get_active_filters(self): + """ + Return the active filters. + If the node is suspend the filters will be override + """ + if self._suspend: + return {"frequency_drop": [-1]} + return self._filters + @asyncio.coroutine def update_filters(self, filters): """ @@ -155,6 +165,12 @@ class Link: if self._created: yield from self.update() + @asyncio.coroutine + def update_suspend(self, value): + if value != self._suspend: + self._suspend = value + yield from self.update() + @property def created(self): """ @@ -401,7 +417,8 @@ class Link: return { "nodes": res, "link_id": self._id, - "filters": self._filters + "filters": self._filters, + "suspend": self._suspend } return { "nodes": res, @@ -411,5 +428,6 @@ class Link: "capture_file_name": self._capture_file_name, "capture_file_path": self.capture_file_path, "link_type": self._link_type, - "filters": self._filters + "filters": self._filters, + "suspend": self._suspend } diff --git a/gns3server/controller/udp_link.py b/gns3server/controller/udp_link.py index 798f97c5..24c00c49 100644 --- a/gns3server/controller/udp_link.py +++ b/gns3server/controller/udp_link.py @@ -66,9 +66,9 @@ class UDPLink(Link): node2_filters = {} filter_node = self._get_filter_node() if filter_node == node1: - node1_filters = self._filters + node1_filters = self.get_active_filters() elif filter_node == node2: - node2_filters = self._filters + node2_filters = self.get_active_filters() # Create the tunnel on both side self._link_data.append({ @@ -106,12 +106,12 @@ class UDPLink(Link): if node1 == filter_node: adapter_number1 = self._nodes[0]["adapter_number"] port_number1 = self._nodes[0]["port_number"] - self._link_data[0]["filters"] = self._filters + self._link_data[0]["filters"] = self.get_active_filters() yield from node1.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), data=self._link_data[0], timeout=120) elif node2 == filter_node: adapter_number2 = self._nodes[1]["adapter_number"] port_number2 = self._nodes[1]["port_number"] - self._link_data[1]["filters"] = self._filters + self._link_data[1]["filters"] = self.get_active_filters() yield from node2.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), data=self._link_data[1], timeout=221) @asyncio.coroutine diff --git a/gns3server/handlers/api/controller/link_handler.py b/gns3server/handlers/api/controller/link_handler.py index df229e01..6ec5e466 100644 --- a/gns3server/handlers/api/controller/link_handler.py +++ b/gns3server/handlers/api/controller/link_handler.py @@ -62,7 +62,10 @@ class LinkHandler: project = yield from Controller.instance().get_loaded_project(request.match_info["project_id"]) link = yield from project.add_link() - yield from link.update_filters(request.json.get("filters", {})) + if "filters" in request.json: + yield from link.update_filters(request.json["filters"]) + if "suspend" in request.json: + yield from link.update_suspend(request.json["suspend"]) try: for node in request.json["nodes"]: yield from link.add_node(project.get_node(node["node_id"]), @@ -110,7 +113,10 @@ class LinkHandler: project = yield from Controller.instance().get_loaded_project(request.match_info["project_id"]) link = project.get_link(request.match_info["link_id"]) - yield from link.update_filters(request.json.get("filters", {})) + if "filters" in request.json: + yield from link.update_filters(request.json["filters"]) + if "suspend" in request.json: + yield from link.update_suspend(request.json["suspend"]) if "nodes" in request.json: yield from link.update_nodes(request.json["nodes"]) response.set_status(201) diff --git a/gns3server/schemas/link.py b/gns3server/schemas/link.py index d3e66dee..5c4a0585 100644 --- a/gns3server/schemas/link.py +++ b/gns3server/schemas/link.py @@ -64,6 +64,10 @@ LINK_OBJECT_SCHEMA = { "additionalProperties": False } }, + "suspend": { + "type": "boolean", + "description": "Link has been turned off" + }, "filters": FILTER_OBJECT_SCHEMA, "capturing": { "description": "Read only property. True if a capture running on the link", diff --git a/tests/controller/test_link.py b/tests/controller/test_link.py index f90cd058..97d9bcd6 100644 --- a/tests/controller/test_link.py +++ b/tests/controller/test_link.py @@ -231,6 +231,7 @@ def test_json(async_run, project, compute, link): } ], "filters": {}, + "suspend": False, "link_type": "ethernet", "capturing": False, "capture_file_name": None, @@ -264,7 +265,8 @@ def test_json(async_run, project, compute, link): } } ], - "filters": {} + "filters": {}, + "suspend": False } diff --git a/tests/controller/test_udp_link.py b/tests/controller/test_udp_link.py index 5c738b4b..ea8d9388 100644 --- a/tests/controller/test_udp_link.py +++ b/tests/controller/test_udp_link.py @@ -384,3 +384,68 @@ def test_update(async_run, project): "bpf": ["icmp[icmptype] == 8"] } }, timeout=120) + + +def test_update_suspend(async_run, project): + compute1 = MagicMock() + compute2 = MagicMock() + + node1 = Node(project, compute1, "node1", node_type="vpcs") + node1._ports = [EthernetPort("E0", 0, 0, 4)] + node2 = Node(project, compute2, "node2", node_type="vpcs") + node2._ports = [EthernetPort("E0", 0, 3, 1)] + + @asyncio.coroutine + def subnet_callback(compute2): + """ + Fake subnet callback + """ + return ("192.168.1.1", "192.168.1.2") + + compute1.get_ip_on_same_subnet.side_effect = subnet_callback + + link = UDPLink(project) + async_run(link.add_node(node1, 0, 4)) + async_run(link.update_filters({"latency": [10]})) + async_run(link.update_suspend(True)) + + @asyncio.coroutine + def compute1_callback(path, data={}, **kwargs): + """ + Fake server + """ + if "/ports/udp" in path: + response = MagicMock() + response.json = {"udp_port": 1024} + return response + + @asyncio.coroutine + def compute2_callback(path, data={}, **kwargs): + """ + Fake server + """ + if "/ports/udp" in path: + response = MagicMock() + response.json = {"udp_port": 2048} + return response + + compute1.post.side_effect = compute1_callback + compute1.host = "example.com" + compute2.post.side_effect = compute2_callback + compute2.host = "example.org" + async_run(link.add_node(node2, 3, 1)) + + compute1.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id), data={ + "lport": 1024, + "rhost": "192.168.1.2", + "rport": 2048, + "type": "nio_udp", + "filters": {"frequency_drop": [-1]} + }, timeout=120) + compute2.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/3/ports/1/nio".format(project.id, node2.id), data={ + "lport": 2048, + "rhost": "192.168.1.1", + "rport": 1024, + "type": "nio_udp", + "filters": {} + }, timeout=120) diff --git a/tests/handlers/api/controller/test_link.py b/tests/handlers/api/controller/test_link.py index 1e3c38b1..319e3dc3 100644 --- a/tests/handlers/api/controller/test_link.py +++ b/tests/handlers/api/controller/test_link.py @@ -128,6 +128,64 @@ def test_create_link_failure(http_controller, tmpdir, project, compute, async_ru assert len(project.links) == 0 +def test_update_link_suspend(http_controller, tmpdir, project, compute, async_run): + response = MagicMock() + response.json = {"console": 2048} + compute.post = AsyncioMagicMock(return_value=response) + + node1 = async_run(project.add_node(compute, "node1", None, node_type="qemu")) + node1._ports = [EthernetPort("E0", 0, 0, 3)] + node2 = async_run(project.add_node(compute, "node2", None, node_type="qemu")) + node2._ports = [EthernetPort("E0", 0, 2, 4)] + + with asyncio_patch("gns3server.controller.udp_link.UDPLink.create"): + response = http_controller.post("/projects/{}/links".format(project.id), { + "nodes": [ + { + "node_id": node1.id, + "adapter_number": 0, + "port_number": 3, + "label": { + "text": "Text", + "x": 42, + "y": 0 + } + }, + { + "node_id": node2.id, + "adapter_number": 2, + "port_number": 4 + } + ] + }) + link_id = response.json["link_id"] + assert response.json["nodes"][0]["label"]["x"] == 42 + response = http_controller.put("/projects/{}/links/{}".format(project.id, link_id), { + "nodes": [ + { + "node_id": node1.id, + "adapter_number": 0, + "port_number": 3, + "label": { + "text": "Hello", + "x": 64, + "y": 0 + } + }, + { + "node_id": node2.id, + "adapter_number": 2, + "port_number": 4 + } + ], + "suspend": True + }) + assert response.status == 201 + assert response.json["nodes"][0]["label"]["x"] == 64 + assert response.json["suspend"] + assert response.json["filters"] == {} + + def test_update_link(http_controller, tmpdir, project, compute, async_run): response = MagicMock() response.json = {"console": 2048}