diff --git a/gns3server/api/routes/controller/nodes.py b/gns3server/api/routes/controller/nodes.py index 413839459..d0b27f81f 100644 --- a/gns3server/api/routes/controller/nodes.py +++ b/gns3server/api/routes/controller/nodes.py @@ -243,14 +243,25 @@ async def reload_all_nodes(project: Project = Depends(dep_project)) -> None: raise +# Node types that need live host interface data from compute +_HOST_INTERFACE_NODE_TYPES = {"cloud", "nat"} + + @router.get("/{node_id}", response_model=schemas.Node, dependencies=[Depends(has_privilege("Node.Audit"))]) -def get_node(node: Node = Depends(dep_node)) -> schemas.Node: +async def get_node(node: Node = Depends(dep_node)) -> schemas.Node: """ Return a node from a given project. Required privilege: Node.Audit """ + if node.node_type in _HOST_INTERFACE_NODE_TYPES: + try: + response = await node.get() + await node.parse_node_response(response.json) + except Exception: + # If compute is unreachable, still return cached data + log.warning(f"Could not refresh node {node.id} from compute, returning cached data") return node.asdict() diff --git a/gns3server/compute/builtin/nodes/cloud.py b/gns3server/compute/builtin/nodes/cloud.py index d71669198..62cfb45ba 100644 --- a/gns3server/compute/builtin/nodes/cloud.py +++ b/gns3server/compute/builtin/nodes/cloud.py @@ -82,6 +82,9 @@ class Cloud(BaseNode): host_interfaces = [] network_interfaces = gns3server.utils.interfaces.interfaces() for interface in network_interfaces: + # Hide GNS3 internal bridges (e.g. EthernetSwitch kernel bridges) + if interface["name"].lower().startswith("gns3"): + continue host_interfaces.append( { "name": interface["name"], diff --git a/gns3server/compute/builtin/nodes/nat.py b/gns3server/compute/builtin/nodes/nat.py index f833f4e50..31b96b9fe 100644 --- a/gns3server/compute/builtin/nodes/nat.py +++ b/gns3server/compute/builtin/nodes/nat.py @@ -87,6 +87,23 @@ class Nat(Cloud): return True def asdict(self): + + nat_interface = self._ports_mapping[0].get("interface", "") if self._ports_mapping else "" + + host_interfaces = [] + network_interfaces = gns3server.utils.interfaces.interfaces() + for interface in network_interfaces: + if interface["name"] == nat_interface: + host_interfaces.append( + { + "name": interface["name"], + "type": interface["type"], + "special": interface["special"], + "ip_addresses": interface.get("ip_addresses", []), + } + ) + break + return { "name": self.name, "usage": self.usage, @@ -94,4 +111,5 @@ class Nat(Cloud): "project_id": self.project.id, "status": "started", "ports_mapping": self.ports_mapping, + "interfaces": host_interfaces, } diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index 3378f055f..2046c7cd0 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -639,6 +639,14 @@ class Node: except asyncio.TimeoutError: raise ControllerTimeoutError(f"Timeout when reset console {self._name}") + async def get(self, path="", **kwargs): + """ + HTTP get on the node + """ + return await self._compute.get( + f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}{path}", **kwargs + ) + async def post(self, path, data=None, **kwargs): """ HTTP post on the node diff --git a/tests/compute/builtin/nodes/test_nat.py b/tests/compute/builtin/nodes/test_nat.py index 60f2da958..bd89aeafe 100644 --- a/tests/compute/builtin/nodes/test_nat.py +++ b/tests/compute/builtin/nodes/test_nat.py @@ -37,7 +37,15 @@ def test_json_gns3vm(on_gns3vm, compute_project): "port_number": 0, "type": "ethernet" } - ] + ], + "interfaces": [ + { + "name": "virbr0", + "type": "ethernet", + "special": True, + "ip_addresses": [], + }, + ], } @@ -47,39 +55,55 @@ def test_json_darwin(darwin_platform, compute_project): {"name": "eth0", "special": False, "type": "ethernet"}, {"name": "vmnet8", "special": True, "type": "ethernet"}]): nat = Nat("nat1", str(uuid.uuid4()), compute_project, MagicMock()) - assert nat.asdict() == { - "name": "nat1", - "usage": "", - "node_id": nat.id, - "project_id": compute_project.id, - "status": "started", - "ports_mapping": [ - { - "interface": "vmnet8", - "name": "nat0", - "port_number": 0, - "type": "ethernet" - } - ] - } + assert nat.asdict() == { + "name": "nat1", + "usage": "", + "node_id": nat.id, + "project_id": compute_project.id, + "status": "started", + "ports_mapping": [ + { + "interface": "vmnet8", + "name": "nat0", + "port_number": 0, + "type": "ethernet" + } + ], + "interfaces": [ + { + "name": "vmnet8", + "type": "ethernet", + "special": True, + "ip_addresses": [], + }, + ], + } def test_json_windows_with_full_name_of_interface(windows_platform, project): with patch("gns3server.utils.interfaces.interfaces", return_value=[ {"name": "VMware Network Adapter VMnet8", "special": True, "type": "ethernet"}]): nat = Nat("nat1", str(uuid.uuid4()), project, MagicMock()) - assert nat.asdict() == { - "name": "nat1", - "usage": "", - "node_id": nat.id, - "project_id": project.id, - "status": "started", - "ports_mapping": [ - { - "interface": "VMware Network Adapter VMnet8", - "name": "nat0", - "port_number": 0, - "type": "ethernet" - } - ] - } + assert nat.asdict() == { + "name": "nat1", + "usage": "", + "node_id": nat.id, + "project_id": project.id, + "status": "started", + "ports_mapping": [ + { + "interface": "VMware Network Adapter VMnet8", + "name": "nat0", + "port_number": 0, + "type": "ethernet" + } + ], + "interfaces": [ + { + "name": "VMware Network Adapter VMnet8", + "type": "ethernet", + "special": True, + "ip_addresses": [], + }, + ], + }