From 21a5c5d3f2b3dd87fa850e557280d1d1d59ab919 Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 6 Aug 2024 20:33:54 +0200 Subject: [PATCH 1/9] Development on 2.2.50.dev1 --- gns3server/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/version.py b/gns3server/version.py index 5d76983b..b15708b1 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,8 +23,8 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "2.2.49" -__version_info__ = (2, 2, 49, 0) +__version__ = "2.2.50.dev1" +__version_info__ = (2, 2, 50, 99) if "dev" in __version__: try: From 69a5b16badf4eab8034899ea8282b0015f841902 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 11 Aug 2024 01:34:30 +0200 Subject: [PATCH 2/9] Upgrade aiohttp to v3.10.3 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 829a7733..2dec22a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ jsonschema>=4.23,<4.24 -aiohttp>=3.9.5,<3.10 +aiohttp>=3.10.3,<3.11 aiohttp-cors>=0.7.0,<0.8 aiofiles>=24.1.0,<25.0 Jinja2>=3.1.4,<3.2 From 3792901dc7fb550165c8a69bbd1c5048677152bd Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 18 Sep 2024 16:30:22 +0700 Subject: [PATCH 3/9] Support for configuring MAC address in Docker containers --- gns3server/compute/docker/docker_vm.py | 64 +++++++++++++++++-- gns3server/controller/node.py | 10 ++- .../handlers/api/compute/docker_handler.py | 2 +- gns3server/schemas/docker.py | 12 ++++ gns3server/schemas/docker_template.py | 9 +++ tests/compute/docker/test_docker_vm.py | 2 + 6 files changed, 90 insertions(+), 9 deletions(-) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index 8794ddb0..bb94816e 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -33,7 +33,7 @@ from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer from gns3server.utils.asyncio.raw_command_server import AsyncioRawCommandServer from gns3server.utils.asyncio import wait_for_file_creation from gns3server.utils.asyncio import monitor_process -from gns3server.utils.get_resource import get_resource +from gns3server.utils import macaddress_to_int, int_to_macaddress from gns3server.ubridge.ubridge_error import UbridgeError, UbridgeNamespaceError from ..base_node import BaseNode @@ -83,6 +83,7 @@ class DockerVM(BaseNode): self._environment = environment self._cid = None self._ethernet_adapters = [] + self._mac_address = "" self._temporary_directory = None self._telnet_servers = [] self._vnc_process = None @@ -106,6 +107,8 @@ class DockerVM(BaseNode): else: self.adapters = adapters + self.mac_address = "" # this will generate a MAC address + log.debug("{module}: {name} [{image}] initialized.".format(module=self.manager.module_name, name=self.name, image=self._image)) @@ -119,6 +122,7 @@ class DockerVM(BaseNode): "project_id": self._project.id, "image": self._image, "adapters": self.adapters, + "mac_address": self.mac_address, "console": self.console, "console_type": self.console_type, "console_resolution": self.console_resolution, @@ -149,6 +153,36 @@ class DockerVM(BaseNode): def ethernet_adapters(self): return self._ethernet_adapters + @property + def mac_address(self): + """ + Returns the MAC address for this Docker container. + + :returns: adapter type (string) + """ + + return self._mac_address + + @mac_address.setter + def mac_address(self, mac_address): + """ + Sets the MAC address for this Docker container. + + :param mac_address: MAC address + """ + + if not mac_address: + # use the node UUID to generate a random MAC address + self._mac_address = "02:42:%s:%s:%s:00" % (self.id[2:4], self.id[4:6], self.id[6:8]) + else: + self._mac_address = mac_address + + log.info('Docker container "{name}" [{id}]: MAC address changed to {mac_addr}'.format( + name=self._name, + id=self._id, + mac_addr=self._mac_address) + ) + @property def start_command(self): return self._start_command @@ -914,15 +948,33 @@ class DockerVM(BaseNode): bridge_name = 'bridge{}'.format(adapter_number) await self._ubridge_send('bridge create {}'.format(bridge_name)) self._bridges.add(bridge_name) - await self._ubridge_send('bridge add_nio_tap bridge{adapter_number} {hostif}'.format(adapter_number=adapter_number, - hostif=adapter.host_ifc)) + await self._ubridge_send('bridge add_nio_tap bridge{adapter_number} {hostif}'.format( + adapter_number=adapter_number, + hostif=adapter.host_ifc) + ) + + mac_address = int_to_macaddress(macaddress_to_int(self._mac_address) + adapter_number) + custom_adapter = self._get_custom_adapter_settings(adapter_number) + custom_mac_address = custom_adapter.get("mac_address") + if custom_mac_address: + mac_address = custom_mac_address + + try: + await self._ubridge_send('docker set_mac_addr {ifc} {mac}'.format(ifc=adapter.host_ifc, mac=mac_address)) + except UbridgeError: + log.warning("Could not set MAC address %s on interface %s", mac_address, adapter.host_ifc) + log.debug("Move container %s adapter %s to namespace %s", self.name, adapter.host_ifc, self._namespace) try: - await self._ubridge_send('docker move_to_ns {ifc} {ns} eth{adapter}'.format(ifc=adapter.host_ifc, - ns=self._namespace, - adapter=adapter_number)) + await self._ubridge_send('docker move_to_ns {ifc} {ns} eth{adapter}'.format( + ifc=adapter.host_ifc, + ns=self._namespace, + adapter=adapter_number) + ) except UbridgeError as e: raise UbridgeNamespaceError(e) + else: + log.info("Created adapter %s with MAC address %s in namespace %s", adapter_number, mac_address, self._namespace) if nio: await self._connect_nio(adapter_number, nio) diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index fd3e625b..eb5bad69 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -26,8 +26,8 @@ import os from .compute import ComputeConflict, ComputeError from .ports.port_factory import PortFactory, StandardPortFactory, DynamipsPortFactory from ..utils.images import images_directories +from ..utils import macaddress_to_int, int_to_macaddress from ..config import Config -from ..utils.qt import qt_font_to_style import logging @@ -663,7 +663,13 @@ class Node: break port_name = "eth{}".format(adapter_number) port_name = custom_adapter_settings.get("port_name", port_name) - self._ports.append(PortFactory(port_name, 0, adapter_number, 0, "ethernet", short_name=port_name)) + mac_address = custom_adapter_settings.get("mac_address") + if not mac_address and "mac_address" in self._properties: + mac_address = int_to_macaddress(macaddress_to_int(self._properties["mac_address"]) + adapter_number) + + port = PortFactory(port_name, 0, adapter_number, 0, "ethernet", short_name=port_name) + port.mac_address = mac_address + self._ports.append(port) elif self._node_type in ("ethernet_switch", "ethernet_hub"): # Basic node we don't want to have adapter number port_number = 0 diff --git a/gns3server/handlers/api/compute/docker_handler.py b/gns3server/handlers/api/compute/docker_handler.py index 68516c8f..c726d52f 100644 --- a/gns3server/handlers/api/compute/docker_handler.py +++ b/gns3server/handlers/api/compute/docker_handler.py @@ -317,7 +317,7 @@ class DockerHandler: props = [ "name", "console", "aux", "console_type", "console_resolution", "console_http_port", "console_http_path", "start_command", - "environment", "adapters", "extra_hosts", "extra_volumes" + "environment", "adapters", "mac_address", "custom_adapters", "extra_hosts", "extra_volumes" ] changed = False diff --git a/gns3server/schemas/docker.py b/gns3server/schemas/docker.py index 6cea166a..07125809 100644 --- a/gns3server/schemas/docker.py +++ b/gns3server/schemas/docker.py @@ -85,6 +85,12 @@ DOCKER_CREATE_SCHEMA = { "minimum": 0, "maximum": 99, }, + "mac_address": { + "description": "Docker container base MAC address", + "type": ["string", "null"], + "minLength": 1, + "pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$" + }, "environment": { "description": "Docker environment variables", "type": ["string", "null"], @@ -187,6 +193,12 @@ DOCKER_OBJECT_SCHEMA = { "minimum": 0, "maximum": 99, }, + "mac_address": { + "description": "Docker container base MAC address", + "type": ["string", "null"], + "minLength": 1, + "pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$" + }, "usage": { "description": "How to use the Docker container", "type": "string", diff --git a/gns3server/schemas/docker_template.py b/gns3server/schemas/docker_template.py index 0e04bbd1..838e1023 100644 --- a/gns3server/schemas/docker_template.py +++ b/gns3server/schemas/docker_template.py @@ -38,6 +38,15 @@ DOCKER_TEMPLATE_PROPERTIES = { "maximum": 99, "default": 1 }, + "mac_address": { + "description": "Docker container base MAC address", + "type": ["string", "null"], + "anyOf": [ + {"pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$"}, + {"pattern": "^$"} + ], + "default": "", + }, "start_command": { "description": "Docker CMD entry", "type": "string", diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index 81825eba..6a5b7643 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -48,6 +48,7 @@ async def vm(compute_project, manager): vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") vm._cid = "e90e34656842" vm.allocate_aux = False + vm.mac_address = '02:42:3d:b7:93:00' return vm @@ -60,6 +61,7 @@ def test_json(vm, compute_project): 'project_id': compute_project.id, 'node_id': vm.id, 'adapters': 1, + 'mac_address': '02:42:3d:b7:93:00', 'console': vm.console, 'console_type': 'telnet', 'console_resolution': '1024x768', From 842949428067e68f933d0eb1801bdc189dfb0b57 Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 19 Sep 2024 10:19:07 +0700 Subject: [PATCH 4/9] Test base MAC address for Docker VMs --- tests/compute/docker/test_docker_vm.py | 32 +++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index 6a5b7643..8519f56d 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -1187,7 +1187,37 @@ async def test_add_ubridge_connection(vm): call.send('bridge start bridge0') ] assert 'bridge0' in vm._bridges - # We need to check any_order ortherwise mock is confused by asyncio + # We need to check any_order otherwise mock is confused by asyncio + vm._ubridge_hypervisor.assert_has_calls(calls, any_order=True) + + +async def test_add_ubridge_connections_with_base_mac_address(vm): + + vm._ubridge_hypervisor = MagicMock() + vm._namespace = 42 + vm.adapters = 2 + vm.mac_address = "02:42:42:42:42:00" + + nio_params = { + "type": "nio_udp", + "lport": 4242, + "rport": 4343, + "rhost": "127.0.0.1"} + + nio = vm.manager.create_nio(nio_params) + await vm._add_ubridge_connection(nio, 0) + + nio = vm.manager.create_nio(nio_params) + await vm._add_ubridge_connection(nio, 1) + + calls = [ + call.send('bridge create bridge0'), + call.send('bridge create bridge1'), + call.send('docker set_mac_addr tap-gns3-e0 02:42:42:42:42:00'), + call.send('docker set_mac_addr tap-gns3-e0 02:42:42:42:42:01') + ] + + # We need to check any_order otherwise mock is confused by asyncio vm._ubridge_hypervisor.assert_has_calls(calls, any_order=True) From 22f022cc22d13bdc48662384e6b3c942a7740fe7 Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 19 Sep 2024 10:40:22 +0700 Subject: [PATCH 5/9] Fix for running Docker containers with user namespaces enabled --- gns3server/compute/docker/docker_vm.py | 1 + tests/compute/docker/test_docker_vm.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index bb94816e..fff4e435 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -384,6 +384,7 @@ class DockerVM(BaseNode): "Privileged": True, "Binds": self._mount_binds(image_infos), }, + "UsernsMode": "host", "Volumes": {}, "Env": ["container=docker"], # Systemd compliant: https://github.com/GNS3/gns3-server/issues/573 "Cmd": [], diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index 8519f56d..3afea049 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -109,6 +109,7 @@ async def test_create(compute_project, manager): ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", @@ -147,6 +148,7 @@ async def test_create_with_tag(compute_project, manager): ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", @@ -189,6 +191,7 @@ async def test_create_vnc(compute_project, manager): ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", @@ -318,6 +321,7 @@ async def test_create_start_cmd(compute_project, manager): ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "Entrypoint": ["/gns3/init.sh"], "Cmd": ["/bin/ls"], @@ -416,6 +420,7 @@ async def test_create_image_not_available(compute_project, manager): ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", @@ -459,6 +464,7 @@ async def test_create_with_user(compute_project, manager): ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", @@ -542,6 +548,7 @@ async def test_create_with_extra_volumes_duplicate_1_image(compute_project, mana ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", @@ -581,6 +588,7 @@ async def test_create_with_extra_volumes_duplicate_2_user(compute_project, manag ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", @@ -620,6 +628,7 @@ async def test_create_with_extra_volumes_duplicate_3_subdir(compute_project, man ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", @@ -659,6 +668,7 @@ async def test_create_with_extra_volumes_duplicate_4_backslash(compute_project, ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", @@ -697,6 +707,7 @@ async def test_create_with_extra_volumes_duplicate_5_subdir_issue_1595(compute_p ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", @@ -735,6 +746,7 @@ async def test_create_with_extra_volumes_duplicate_6_subdir_issue_1595(compute_p ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", @@ -781,6 +793,7 @@ async def test_create_with_extra_volumes(compute_project, manager): ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", @@ -1029,6 +1042,7 @@ async def test_update(vm): ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", @@ -1097,6 +1111,7 @@ async def test_update_running(vm): ], "Privileged": True }, + "UsernsMode": "host", "Volumes": {}, "NetworkDisabled": True, "Hostname": "test", From c41c11eb3453d642261dc2002999acecf763ff15 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 22 Sep 2024 18:29:04 +0700 Subject: [PATCH 6/9] Backport auxiliary console support for Qemu, Docker and Dynamips nodes --- gns3server/compute/base_node.py | 183 +++++++++++------- gns3server/compute/docker/docker_vm.py | 10 +- gns3server/compute/dynamips/__init__.py | 4 + gns3server/compute/dynamips/nodes/c1700.py | 6 +- gns3server/compute/dynamips/nodes/c2600.py | 6 +- gns3server/compute/dynamips/nodes/c2691.py | 6 +- gns3server/compute/dynamips/nodes/c3600.py | 6 +- gns3server/compute/dynamips/nodes/c3725.py | 6 +- gns3server/compute/dynamips/nodes/c3745.py | 6 +- gns3server/compute/dynamips/nodes/c7200.py | 6 +- gns3server/compute/dynamips/nodes/router.py | 12 +- gns3server/compute/qemu/qemu_vm.py | 70 ++++--- gns3server/controller/node.py | 32 +++ .../handlers/api/compute/docker_handler.py | 36 ++-- .../api/compute/dynamips_vm_handler.py | 23 ++- .../handlers/api/compute/qemu_handler.py | 20 +- gns3server/schemas/docker.py | 10 +- gns3server/schemas/docker_template.py | 5 + gns3server/schemas/dynamips_template.py | 5 + gns3server/schemas/dynamips_vm.py | 12 ++ gns3server/schemas/node.py | 10 + gns3server/schemas/qemu.py | 32 +++ gns3server/schemas/qemu_template.py | 5 + tests/compute/docker/test_docker_vm.py | 7 +- tests/compute/test_base_node.py | 7 +- tests/controller/test_node.py | 4 + 26 files changed, 374 insertions(+), 155 deletions(-) diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py index 639a5d41..c45f287b 100644 --- a/gns3server/compute/base_node.py +++ b/gns3server/compute/base_node.py @@ -50,14 +50,16 @@ class BaseNode: :param node_id: Node instance identifier :param project: Project instance :param manager: parent node manager - :param console: TCP console port - :param aux: TCP aux console port - :param allocate_aux: Boolean if true will allocate an aux console port + :param console: console TCP port + :param console_type: console type + :param aux: auxiliary console TCP port + :param aux_type: auxiliary console type :param linked_clone: The node base image is duplicate/overlay (Each node data are independent) :param wrap_console: The console is wrapped using AsyncioTelnetServer + :param wrap_aux: The auxiliary console is wrapped using AsyncioTelnetServer """ - def __init__(self, name, node_id, project, manager, console=None, console_type="telnet", aux=None, allocate_aux=False, linked_clone=True, wrap_console=False): + def __init__(self, name, node_id, project, manager, console=None, console_type="telnet", aux=None, aux_type="none", linked_clone=True, wrap_console=False, wrap_aux=False): self._name = name self._usage = "" @@ -68,22 +70,25 @@ class BaseNode: self._console = console self._aux = aux self._console_type = console_type + self._aux_type = aux_type self._temporary_directory = None self._hw_virtualization = False self._ubridge_hypervisor = None self._closed = False self._node_status = "stopped" self._command_line = "" - self._allocate_aux = allocate_aux self._wrap_console = wrap_console - self._wrapper_telnet_server = None + self._wrap_aux = wrap_aux + self._wrapper_telnet_servers = [] self._wrap_console_reader = None self._wrap_console_writer = None self._internal_console_port = None + self._internal_aux_port = None self._custom_adapters = [] self._ubridge_require_privileged_access = False if self._console is not None: + # use a previously allocated console port if console_type == "vnc": vnc_console_start_port_range, vnc_console_end_port_range = self._get_vnc_console_port_range() self._console = self._manager.port_manager.reserve_tcp_port( @@ -97,25 +102,45 @@ class BaseNode: else: self._console = self._manager.port_manager.reserve_tcp_port(self._console, self._project) - # We need to allocate aux before giving a random console port if self._aux is not None: - self._aux = self._manager.port_manager.reserve_tcp_port(self._aux, self._project) + # use a previously allocated auxiliary console port + if aux_type == "vnc": + # VNC is a special case and the range must be 5900-6000 + self._aux = self._manager.port_manager.reserve_tcp_port( + self._aux, self._project, port_range_start=5900, port_range_end=6000 + ) + elif aux_type == "none": + self._aux = None + else: + self._aux = self._manager.port_manager.reserve_tcp_port(self._aux, self._project) if self._console is None: + # allocate a new console if console_type == "vnc": vnc_console_start_port_range, vnc_console_end_port_range = self._get_vnc_console_port_range() self._console = self._manager.port_manager.get_free_tcp_port( self._project, port_range_start=vnc_console_start_port_range, - port_range_end=vnc_console_end_port_range) + port_range_end=vnc_console_end_port_range, + ) elif console_type != "none": self._console = self._manager.port_manager.get_free_tcp_port(self._project) + if self._aux is None: + # allocate a new auxiliary console + if aux_type == "vnc": + # VNC is a special case and the range must be 5900-6000 + self._aux = self._manager.port_manager.get_free_tcp_port( + self._project, port_range_start=5900, port_range_end=6000 + ) + elif aux_type != "none": + self._aux = self._manager.port_manager.get_free_tcp_port(self._project) + if self._wrap_console: self._internal_console_port = self._manager.port_manager.get_free_tcp_port(self._project) - if self._aux is None and allocate_aux: - self._aux = self._manager.port_manager.get_free_tcp_port(self._project) + if self._wrap_aux: + self._internal_aux_port = self._manager.port_manager.get_free_tcp_port(self._project) log.debug("{module}: {name} [{id}] initialized. Console port {console}".format(module=self.manager.module_name, name=self.name, @@ -343,6 +368,9 @@ class BaseNode: if self._aux: self._manager.port_manager.release_tcp_port(self._aux, self._project) self._aux = None + if self._wrap_aux: + self._manager.port_manager.release_tcp_port(self._internal_aux_port, self._project) + self._internal_aux_port = None self._closed = True return True @@ -366,56 +394,49 @@ class BaseNode: return vnc_console_start_port_range, vnc_console_end_port_range - async def start_wrap_console(self): - """ - Start a telnet proxy for the console allowing multiple telnet clients - to be connected at the same time - """ + async def _wrap_telnet_proxy(self, internal_port, external_port): - if not self._wrap_console or self._console_type != "telnet": - return remaining_trial = 60 while True: try: - (self._wrap_console_reader, self._wrap_console_writer) = await asyncio.open_connection( - host="127.0.0.1", - port=self._internal_console_port - ) + (reader, writer) = await asyncio.open_connection(host="127.0.0.1", port=internal_port) break except (OSError, ConnectionRefusedError) as e: if remaining_trial <= 0: raise e await asyncio.sleep(0.1) remaining_trial -= 1 - await AsyncioTelnetServer.write_client_intro(self._wrap_console_writer, echo=True) - server = AsyncioTelnetServer( - reader=self._wrap_console_reader, - writer=self._wrap_console_writer, - binary=True, - echo=True - ) + await AsyncioTelnetServer.write_client_intro(writer, echo=True) + server = AsyncioTelnetServer(reader=reader, writer=writer, binary=True, echo=True) # warning: this will raise OSError exception if there is a problem... - self._wrapper_telnet_server = await asyncio.start_server( - server.run, - self._manager.port_manager.console_host, - self.console - ) + telnet_server = await asyncio.start_server(server.run, self._manager.port_manager.console_host, external_port) + self._wrapper_telnet_servers.append(telnet_server) + + async def start_wrap_console(self): + """ + Start a Telnet proxy servers for the console and auxiliary console allowing multiple telnet clients + to be connected at the same time + """ + + if self._wrap_console and self._console_type == "telnet": + await self._wrap_telnet_proxy(self._internal_console_port, self.console) + log.info("New Telnet proxy server for console started (internal port = {}, external port = {})".format(self._internal_console_port, + self.console)) + + if self._wrap_aux and self._aux_type == "telnet": + await self._wrap_telnet_proxy(self._internal_aux_port, self.aux) + log.info("New Telnet proxy server for auxiliary console started (internal port = {}, external port = {})".format(self._internal_aux_port, + self.aux)) async def stop_wrap_console(self): """ - Stops the telnet proxy. + Stops the telnet proxy servers. """ - if self._wrapper_telnet_server: - self._wrap_console_writer.close() - if sys.version_info >= (3, 7, 0): - try: - await self._wrap_console_writer.wait_closed() - except ConnectionResetError: - pass - self._wrapper_telnet_server.close() - await self._wrapper_telnet_server.wait_closed() - self._wrapper_telnet_server = None + for telnet_proxy_server in self._wrapper_telnet_servers: + telnet_proxy_server.close() + await telnet_proxy_server.wait_closed() + self._wrapper_telnet_servers = [] async def reset_wrap_console(self): """ @@ -492,22 +513,6 @@ class BaseNode: return ws - @property - def allocate_aux(self): - """ - :returns: Boolean allocate or not an aux console - """ - - return self._allocate_aux - - @allocate_aux.setter - def allocate_aux(self, allocate_aux): - """ - :returns: Boolean allocate or not an aux console - """ - - self._allocate_aux = allocate_aux - @property def aux(self): """ @@ -526,18 +531,25 @@ class BaseNode: :params aux: Console port (integer) or None to free the port """ - if aux == self._aux: + if aux == self._aux or self._aux_type == "none": return + if self._aux_type == "vnc" and aux is not None and aux < 5900: + raise NodeError("VNC auxiliary console require a port superior or equal to 5900, current port is {}".format(aux)) + if self._aux: self._manager.port_manager.release_tcp_port(self._aux, self._project) self._aux = None if aux is not None: - self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project) - log.info("{module}: '{name}' [{id}]: aux port set to {port}".format(module=self.manager.module_name, - name=self.name, - id=self.id, - port=aux)) + if self._aux_type == "vnc": + self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project, port_range_start=5900, port_range_end=6000) + else: + self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project) + + log.info("{module}: '{name}' [{id}]: auxiliary console port set to {port}".format(module=self.manager.module_name, + name=self.name, + id=self.id, + port=aux)) @property def console(self): @@ -625,6 +637,43 @@ class BaseNode: console_type=console_type, console=self.console)) + @property + def aux_type(self): + """ + Returns the auxiliary console type for this node. + :returns: aux type (string) + """ + + return self._aux_type + + @aux_type.setter + def aux_type(self, aux_type): + """ + Sets the auxiliary console type for this node. + :param aux_type: console type (string) + """ + + print("SET AUX TYPE", aux_type) + if aux_type != self._aux_type: + # get a new port if the aux type change + if self._aux: + self._manager.port_manager.release_tcp_port(self._aux, self._project) + if aux_type == "none": + # no need to allocate a port when the auxiliary console type is none + self._aux = None + elif aux_type == "vnc": + # VNC is a special case and the range must be 5900-6000 + self._aux = self._manager.port_manager.get_free_tcp_port(self._project, 5900, 6000) + else: + self._aux = self._manager.port_manager.get_free_tcp_port(self._project) + + self._aux_type = aux_type + log.info("{module}: '{name}' [{id}]: console type set to {aux_type} (auxiliary console port is {aux})".format(module=self.manager.module_name, + name=self.name, + id=self.id, + aux_type=aux_type, + aux=self.aux)) + @property def ubridge(self): """ diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index fff4e435..d3c5e79b 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -60,8 +60,9 @@ class DockerVM(BaseNode): :param manager: Manager instance :param image: Docker image :param console: TCP console port - :param console_type: Console type + :param console_type: console type :param aux: TCP aux console port + :param aux_type: auxiliary console type :param console_resolution: Resolution of the VNC display :param console_http_port: Port to redirect HTTP queries :param console_http_path: Url part with the path of the web interface @@ -70,10 +71,10 @@ class DockerVM(BaseNode): """ def __init__(self, name, node_id, project, manager, image, console=None, aux=None, start_command=None, - adapters=None, environment=None, console_type="telnet", console_resolution="1024x768", + adapters=None, environment=None, console_type="telnet", aux_type="none", console_resolution="1024x768", console_http_port=80, console_http_path="/", extra_hosts=None, extra_volumes=[]): - super().__init__(name, node_id, project, manager, console=console, aux=aux, allocate_aux=True, console_type=console_type) + super().__init__(name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type) # force the latest image if no version is specified if ":" not in image: @@ -129,6 +130,7 @@ class DockerVM(BaseNode): "console_http_port": self.console_http_port, "console_http_path": self.console_http_path, "aux": self.aux, + "aux_type": self.aux_type, "start_command": self.start_command, "status": self.status, "environment": self.environment, @@ -546,7 +548,7 @@ class DockerVM(BaseNode): elif self.console_type == "http" or self.console_type == "https": await self._start_http() - if self.allocate_aux: + if self.aux_type != "none": await self._start_aux() self._permissions_fixed = False diff --git a/gns3server/compute/dynamips/__init__.py b/gns3server/compute/dynamips/__init__.py index ce1d8722..0f81a2bb 100644 --- a/gns3server/compute/dynamips/__init__.py +++ b/gns3server/compute/dynamips/__init__.py @@ -527,6 +527,10 @@ class Dynamips(BaseManager): if usage is not None and usage != vm.usage: vm.usage = usage + aux_type = settings.get("aux_type") + if aux_type is not None and aux_type != vm.aux_type: + vm.aux_type = aux_type + # update the configs if needed await self.set_vm_configs(vm, settings) diff --git a/gns3server/compute/dynamips/nodes/c1700.py b/gns3server/compute/dynamips/nodes/c1700.py index cb4b8537..cdc0f343 100644 --- a/gns3server/compute/dynamips/nodes/c1700.py +++ b/gns3server/compute/dynamips/nodes/c1700.py @@ -40,15 +40,17 @@ class C1700(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port + :param console_type: console type :param aux: auxiliary console port + :param aux_type: auxiliary console type :param chassis: chassis for this router: 1720, 1721, 1750, 1751 or 1760 (default = 1720). 1710 is not supported. """ - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis="1720"): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="1720"): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c1700") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c1700") # Set default values for this platform (must be the same as Dynamips) self._ram = 64 diff --git a/gns3server/compute/dynamips/nodes/c2600.py b/gns3server/compute/dynamips/nodes/c2600.py index b065607b..e2c3ea13 100644 --- a/gns3server/compute/dynamips/nodes/c2600.py +++ b/gns3server/compute/dynamips/nodes/c2600.py @@ -42,7 +42,9 @@ class C2600(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port + :param console_type: console type :param aux: auxiliary console port + :param aux_type: auxiliary console type :param chassis: chassis for this router: 2610, 2611, 2620, 2621, 2610XM, 2611XM 2620XM, 2621XM, 2650XM or 2651XM (default = 2610). @@ -61,9 +63,9 @@ class C2600(Router): "2650XM": C2600_MB_1FE, "2651XM": C2600_MB_2FE} - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis="2610"): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="2610"): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c2600") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c2600") # Set default values for this platform (must be the same as Dynamips) self._ram = 64 diff --git a/gns3server/compute/dynamips/nodes/c2691.py b/gns3server/compute/dynamips/nodes/c2691.py index 8441881f..c946b391 100644 --- a/gns3server/compute/dynamips/nodes/c2691.py +++ b/gns3server/compute/dynamips/nodes/c2691.py @@ -40,12 +40,14 @@ class C2691(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port + :param console_type: console type :param aux: auxiliary console port + :param aux_type: auxiliary console type """ - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis=None): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis=None): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c2691") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c2691") # Set default values for this platform (must be the same as Dynamips) self._ram = 128 diff --git a/gns3server/compute/dynamips/nodes/c3600.py b/gns3server/compute/dynamips/nodes/c3600.py index 984a5621..a5341f6e 100644 --- a/gns3server/compute/dynamips/nodes/c3600.py +++ b/gns3server/compute/dynamips/nodes/c3600.py @@ -39,14 +39,16 @@ class C3600(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port + :param console_type: console type :param aux: auxiliary console port + :param aux_type: auxiliary console type :param chassis: chassis for this router: 3620, 3640 or 3660 (default = 3640). """ - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis="3640"): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="3640"): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c3600") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3600") # Set default values for this platform (must be the same as Dynamips) self._ram = 128 diff --git a/gns3server/compute/dynamips/nodes/c3725.py b/gns3server/compute/dynamips/nodes/c3725.py index be194cf5..5ba52e47 100644 --- a/gns3server/compute/dynamips/nodes/c3725.py +++ b/gns3server/compute/dynamips/nodes/c3725.py @@ -40,12 +40,14 @@ class C3725(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port + :param console_type: console type :param aux: auxiliary console port + :param aux_type: auxiliary console type """ - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis=None): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis=None): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c3725") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3725") # Set default values for this platform (must be the same as Dynamips) self._ram = 128 diff --git a/gns3server/compute/dynamips/nodes/c3745.py b/gns3server/compute/dynamips/nodes/c3745.py index 9087a98f..cdbc6b49 100644 --- a/gns3server/compute/dynamips/nodes/c3745.py +++ b/gns3server/compute/dynamips/nodes/c3745.py @@ -40,12 +40,14 @@ class C3745(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port + :param console_type: console type :param aux: auxiliary console port + :param aux_type: auxiliary console type """ - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis=None): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis=None): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c3745") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3745") # Set default values for this platform (must be the same as Dynamips) self._ram = 128 diff --git a/gns3server/compute/dynamips/nodes/c7200.py b/gns3server/compute/dynamips/nodes/c7200.py index 155bc385..6ebf9abb 100644 --- a/gns3server/compute/dynamips/nodes/c7200.py +++ b/gns3server/compute/dynamips/nodes/c7200.py @@ -42,13 +42,15 @@ class C7200(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port + :param console_type: console type :param aux: auxiliary console port + :param aux_type: auxiliary console type :param npe: Default NPE """ - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, npe="npe-400", chassis=None): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", npe="npe-400", chassis=None): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c7200") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c7200") # Set default values for this platform (must be the same as Dynamips) self._ram = 256 diff --git a/gns3server/compute/dynamips/nodes/router.py b/gns3server/compute/dynamips/nodes/router.py index e9fe08ef..69553881 100644 --- a/gns3server/compute/dynamips/nodes/router.py +++ b/gns3server/compute/dynamips/nodes/router.py @@ -52,7 +52,9 @@ class Router(BaseNode): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port + :param console_type: console type :param aux: auxiliary console port + :param aux_type: auxiliary console type :param platform: Platform of this router """ @@ -61,9 +63,9 @@ class Router(BaseNode): 2: "running", 3: "suspended"} - def __init__(self, name, node_id, project, manager, dynamips_id=None, console=None, console_type="telnet", aux=None, platform="c7200", hypervisor=None, ghost_flag=False): + def __init__(self, name, node_id, project, manager, dynamips_id=None, console=None, console_type="telnet", aux=None, aux_type="none", platform="c7200", hypervisor=None, ghost_flag=False): - super().__init__(name, node_id, project, manager, console=console, console_type=console_type, aux=aux, allocate_aux=aux) + super().__init__(name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type) self._working_directory = os.path.join(self.project.module_working_directory(self.manager.module_name.lower()), self.id) try: @@ -166,6 +168,7 @@ class Router(BaseNode): "console": self.console, "console_type": self.console_type, "aux": self.aux, + "aux_type": self.aux_type, "mac_addr": self._mac_addr, "system_id": self._system_id} @@ -223,15 +226,14 @@ class Router(BaseNode): platform=self._platform, id=self._id)) - if self._console: + if self._console is not None: await self._hypervisor.send('vm set_con_tcp_port "{name}" {console}'.format(name=self._name, console=self._console)) if self.aux is not None: await self._hypervisor.send('vm set_aux_tcp_port "{name}" {aux}'.format(name=self._name, aux=self.aux)) # get the default base MAC address - mac_addr = await self._hypervisor.send('{platform} get_mac_addr "{name}"'.format(platform=self._platform, - name=self._name)) + mac_addr = await self._hypervisor.send('{platform} get_mac_addr "{name}"'.format(platform=self._platform, name=self._name)) self._mac_addr = mac_addr[0] self._hypervisor.devices.append(self) diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 99b2d711..984c8b06 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -76,9 +76,9 @@ class QemuVM(BaseNode): :param platform: Platform to emulate """ - def __init__(self, name, node_id, project, manager, linked_clone=True, qemu_path=None, console=None, console_type="telnet", platform=None): + def __init__(self, name, node_id, project, manager, linked_clone=True, qemu_path=None, console=None, console_type="telnet", aux=None, aux_type="none", platform=None): - super().__init__(name, node_id, project, manager, console=console, console_type=console_type, linked_clone=linked_clone, wrap_console=True) + super().__init__(name, node_id, project, manager, console=console, console_type=console_type, linked_clone=linked_clone, aux=aux, aux_type=aux_type, wrap_console=True, wrap_aux=True) server_config = manager.config.get_section_config("Server") self._host = server_config.get("host", "127.0.0.1") self._monitor_host = server_config.get("monitor_host", "127.0.0.1") @@ -1658,24 +1658,24 @@ class QemuVM(BaseNode): super(QemuVM, QemuVM).console_type.__set__(self, new_console_type) - def _serial_options(self): + def _serial_options(self, internal_console_port, external_console_port): - if self._console: - return ["-serial", "telnet:127.0.0.1:{},server,nowait".format(self._internal_console_port)] + if external_console_port: + return ["-serial", "telnet:127.0.0.1:{},server,nowait".format(internal_console_port)] else: return [] - def _vnc_options(self): + def _vnc_options(self, port): - if self._console: - vnc_port = self._console - 5900 # subtract by 5900 to get the display number + if port: + vnc_port = port - 5900 # subtract by 5900 to get the display number return ["-vnc", "{}:{}".format(self._manager.port_manager.console_host, vnc_port)] else: return [] - def _spice_options(self): + def _spice_options(self, port): - if self._console: + if port: console_host = self._manager.port_manager.console_host if console_host == "0.0.0.0": try: @@ -1687,15 +1687,15 @@ class QemuVM(BaseNode): except OSError as e: raise QemuError("Could not check if IPv6 is enabled: {}".format(e)) return ["-spice", - "addr={},port={},disable-ticketing".format(console_host, self._console), + "addr={},port={},disable-ticketing".format(console_host, port), "-vga", "qxl"] else: return [] - def _spice_with_agent_options(self): + def _spice_with_agent_options(self, port): - spice_options = self._spice_options() - if self._console: + spice_options = self._spice_options(port) + if spice_options: # agent options (mouse/screen) agent_options = ["-device", "virtio-serial", "-chardev", "spicevmc,id=vdagent,debug=0,name=vdagent", @@ -1707,6 +1707,36 @@ class QemuVM(BaseNode): spice_options.extend(folder_sharing_options) return spice_options + def _console_options(self): + + if self._console_type == "telnet" and self._wrap_console: + return self._serial_options(self._internal_console_port, self.console) + elif self._console_type == "vnc": + return self._vnc_options(self.console) + elif self._console_type == "spice": + return self._spice_options(self.console) + elif self._console_type == "spice+agent": + return self._spice_with_agent_options(self.console) + elif self._console_type != "none": + raise QemuError("Console type {} is unknown".format(self._console_type)) + + def _aux_options(self): + + if self._aux_type != "none" and self._aux_type == self._console_type: + raise QemuError("Auxiliary console type {} cannot be the same as console type".format(self._aux_type)) + + if self._aux_type == "telnet" and self._wrap_aux: + return self._serial_options(self._internal_aux_port, self.aux) + elif self._aux_type == "vnc": + return self._vnc_options(self.aux) + elif self._aux_type == "spice": + return self._spice_options(self.aux) + elif self._aux_type == "spice+agent": + return self._spice_with_agent_options(self.aux) + elif self._aux_type != "none": + raise QemuError("Auxiliary console type {} is unknown".format(self._aux_type)) + return [] + def _monitor_options(self): if self._monitor: @@ -2408,16 +2438,8 @@ class QemuVM(BaseNode): command.extend(self._linux_boot_options()) if "-uuid" not in additional_options: command.extend(["-uuid", self._id]) - if self._console_type == "telnet": - command.extend(self._serial_options()) - elif self._console_type == "vnc": - command.extend(self._vnc_options()) - elif self._console_type == "spice": - command.extend(self._spice_options()) - elif self._console_type == "spice+agent": - command.extend(self._spice_with_agent_options()) - elif self._console_type != "none": - raise QemuError("Console type {} is unknown".format(self._console_type)) + command.extend(self._console_options()) + command.extend(self._aux_options()) command.extend(self._monitor_options()) command.extend((await self._network_options())) if self.on_close != "save_vm_state": diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index eb5bad69..658a27f4 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -68,6 +68,8 @@ class Node: self.name = name self._console = None self._console_type = None + self._aux = None + self._aux_type = None self._properties = None self._command_line = None self._node_directory = None @@ -161,6 +163,14 @@ class Node: def console(self, val): self._console = val + @property + def aux(self): + return self._aux + + @aux.setter + def aux(self, val): + self._aux = val + @property def console_type(self): return self._console_type @@ -169,6 +179,14 @@ class Node: def console_type(self, val): self._console_type = val + @property + def aux_type(self): + return self._aux_type + + @aux_type.setter + def aux_type(self, val): + self._aux_type = val + @property def console_auto_start(self): return self._console_auto_start @@ -430,6 +448,8 @@ class Node: for key, value in response.items(): if key == "console": self._console = value + elif key == "aux": + self._aux = value elif key == "node_directory": self._node_directory = value elif key == "command_line": @@ -438,6 +458,8 @@ class Node: self._status = value elif key == "console_type": self._console_type = value + elif key == "aux_type": + self._aux_type = value elif key == "name": self.name = value elif key in ["node_id", "project_id", "console_host", @@ -480,6 +502,12 @@ class Node: if self._console_type and self._node_type not in ("cloud", "nat", "ethernet_hub", "frame_relay_switch", "atm_switch"): # console_type is not supported by all builtin nodes excepting Ethernet switch data["console_type"] = self._console_type + if self._aux: + # aux is optional for builtin nodes + data["aux"] = self._aux + if self._aux_type and self._node_type not in ("cloud", "nat", "ethernet_switch", "ethernet_hub", "frame_relay_switch", "atm_switch"): + # aux_type is not supported by all builtin nodes + data["aux_type"] = self._aux_type if self.custom_adapters: data["custom_adapters"] = self.custom_adapters @@ -709,6 +737,8 @@ class Node: "console": self._console, "console_type": self._console_type, "console_auto_start": self._console_auto_start, + "aux": self._aux, + "aux_type": self._aux_type, "properties": self._properties, "label": self._label, "x": self._x, @@ -746,6 +776,8 @@ class Node: "console_host": console_host, "console_type": self._console_type, "console_auto_start": self._console_auto_start, + "aux": self._aux, + "aux_type": self._aux_type, "command_line": self._command_line, "properties": self._properties, "status": self._status, diff --git a/gns3server/handlers/api/compute/docker_handler.py b/gns3server/handlers/api/compute/docker_handler.py index c726d52f..270b383b 100644 --- a/gns3server/handlers/api/compute/docker_handler.py +++ b/gns3server/handlers/api/compute/docker_handler.py @@ -48,21 +48,25 @@ class DockerHandler: output=DOCKER_OBJECT_SCHEMA) async def create(request, response): docker_manager = Docker.instance() - container = await docker_manager.create_node(request.json.pop("name"), - request.match_info["project_id"], - request.json.get("node_id"), - image=request.json.pop("image"), - start_command=request.json.get("start_command"), - environment=request.json.get("environment"), - adapters=request.json.get("adapters"), - console=request.json.get("console"), - console_type=request.json.get("console_type"), - console_resolution=request.json.get("console_resolution", "1024x768"), - console_http_port=request.json.get("console_http_port", 80), - console_http_path=request.json.get("console_http_path", "/"), - aux=request.json.get("aux"), - extra_hosts=request.json.get("extra_hosts"), - extra_volumes=request.json.get("extra_volumes")) + container = await docker_manager.create_node( + request.json.pop("name"), + request.match_info["project_id"], + request.json.get("node_id"), + image=request.json.pop("image"), + start_command=request.json.get("start_command"), + environment=request.json.get("environment"), + adapters=request.json.get("adapters"), + console=request.json.get("console"), + console_type=request.json.get("console_type"), + console_resolution=request.json.get("console_resolution", "1024x768"), + console_http_port=request.json.get("console_http_port", 80), + console_http_path=request.json.get("console_http_path", "/"), + aux=request.json.get("aux"), + aux_type=request.json.pop("aux_type", "none"), + extra_hosts=request.json.get("extra_hosts"), + extra_volumes=request.json.get("extra_volumes") + ) + for name, value in request.json.items(): if name != "node_id": if hasattr(container, name) and getattr(container, name) != value: @@ -315,7 +319,7 @@ class DockerHandler: container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) props = [ - "name", "console", "aux", "console_type", "console_resolution", + "name", "console", "console_type", "aux", "aux_type", "console_resolution", "console_http_port", "console_http_path", "start_command", "environment", "adapters", "mac_address", "custom_adapters", "extra_hosts", "extra_volumes" ] diff --git a/gns3server/handlers/api/compute/dynamips_vm_handler.py b/gns3server/handlers/api/compute/dynamips_vm_handler.py index 7750e896..ead9c6f8 100644 --- a/gns3server/handlers/api/compute/dynamips_vm_handler.py +++ b/gns3server/handlers/api/compute/dynamips_vm_handler.py @@ -69,16 +69,19 @@ class DynamipsVMHandler: default_chassis = None if platform in DEFAULT_CHASSIS: default_chassis = DEFAULT_CHASSIS[platform] - vm = await dynamips_manager.create_node(request.json.pop("name"), - request.match_info["project_id"], - request.json.get("node_id"), - dynamips_id=request.json.get("dynamips_id"), - platform=platform, - console=request.json.get("console"), - console_type=request.json.get("console_type", "telnet"), - aux=request.json.get("aux"), - chassis=request.json.pop("chassis", default_chassis), - node_type="dynamips") + vm = await dynamips_manager.create_node( + request.json.pop("name"), + request.match_info["project_id"], + request.json.get("node_id"), + dynamips_id=request.json.get("dynamips_id"), + platform=platform, + console=request.json.get("console"), + console_type=request.json.get("console_type", "telnet"), + aux=request.json.get("aux"), + aux_type=request.json.get("aux_type", "none"), + chassis=request.json.pop("chassis", default_chassis), + node_type="dynamips" + ) await dynamips_manager.update_vm_settings(vm, request.json) response.set_status(201) response.json(vm) diff --git a/gns3server/handlers/api/compute/qemu_handler.py b/gns3server/handlers/api/compute/qemu_handler.py index 6fcf5aee..bc29d712 100644 --- a/gns3server/handlers/api/compute/qemu_handler.py +++ b/gns3server/handlers/api/compute/qemu_handler.py @@ -66,14 +66,18 @@ class QEMUHandler: async def create(request, response): qemu = Qemu.instance() - vm = await qemu.create_node(request.json.pop("name"), - request.match_info["project_id"], - request.json.pop("node_id", None), - linked_clone=request.json.get("linked_clone", True), - qemu_path=request.json.pop("qemu_path", None), - console=request.json.pop("console", None), - console_type=request.json.pop("console_type", "telnet"), - platform=request.json.pop("platform", None)) + vm = await qemu.create_node( + request.json.pop("name"), + request.match_info["project_id"], + request.json.pop("node_id", None), + linked_clone=request.json.get("linked_clone", True), + qemu_path=request.json.pop("qemu_path", None), + console=request.json.pop("console", None), + console_type=request.json.pop("console_type", "telnet"), + aux=request.json.pop("aux", None), + aux_type=request.json.pop("aux_type", "none"), + platform=request.json.pop("platform", None) + ) for name, value in request.json.items(): if hasattr(vm, name) and getattr(vm, name) != value: diff --git a/gns3server/schemas/docker.py b/gns3server/schemas/docker.py index 07125809..abecfef2 100644 --- a/gns3server/schemas/docker.py +++ b/gns3server/schemas/docker.py @@ -65,6 +65,10 @@ DOCKER_CREATE_SCHEMA = { "maximum": 65535, "type": ["integer", "null"] }, + "aux_type": { + "description": "Auxiliary console type", + "enum": ["telnet", "none"] + }, "usage": { "description": "How to use the Docker container", "type": "string", @@ -143,7 +147,7 @@ DOCKER_OBJECT_SCHEMA = { "description": "Auxiliary TCP port", "minimum": 1, "maximum": 65535, - "type": "integer" + "type": ["integer", "null"] }, "console": { "description": "Console TCP port", @@ -160,6 +164,10 @@ DOCKER_OBJECT_SCHEMA = { "description": "Console type", "enum": ["telnet", "vnc", "http", "https", "none"] }, + "aux_type": { + "description": "Auxiliary console type", + "enum": ["telnet", "none"] + }, "console_http_port": { "description": "Internal port in the container for the HTTP server", "type": "integer", diff --git a/gns3server/schemas/docker_template.py b/gns3server/schemas/docker_template.py index 838e1023..6969bb14 100644 --- a/gns3server/schemas/docker_template.py +++ b/gns3server/schemas/docker_template.py @@ -67,6 +67,11 @@ DOCKER_TEMPLATE_PROPERTIES = { "type": "boolean", "default": False, }, + "aux_type": { + "description": "Auxiliary console type", + "enum": ["telnet", "none"], + "default": "none" + }, "console_http_port": { "description": "Internal port in the container for the HTTP server", "type": "integer", diff --git a/gns3server/schemas/dynamips_template.py b/gns3server/schemas/dynamips_template.py index 61a41fc1..b9b5fc4b 100644 --- a/gns3server/schemas/dynamips_template.py +++ b/gns3server/schemas/dynamips_template.py @@ -120,6 +120,11 @@ DYNAMIPS_TEMPLATE_PROPERTIES = { "description": "Automatically start the console when the node has started", "type": "boolean", "default": False + }, + "aux_type": { + "description": "Auxiliary console type", + "enum": ["telnet", "none"], + "default": "none" } } diff --git a/gns3server/schemas/dynamips_vm.py b/gns3server/schemas/dynamips_vm.py index 1fc939a6..2e0b32f9 100644 --- a/gns3server/schemas/dynamips_vm.py +++ b/gns3server/schemas/dynamips_vm.py @@ -178,6 +178,10 @@ VM_CREATE_SCHEMA = { "minimum": 1, "maximum": 65535 }, + "aux_type": { + "description": "Auxiliary console type", + "enum": ["telnet", "none"] + }, "mac_addr": { "description": "Base MAC address", "type": ["null", "string"], @@ -400,6 +404,10 @@ VM_UPDATE_SCHEMA = { "minimum": 1, "maximum": 65535 }, + "aux_type": { + "description": "Auxiliary console type", + "enum": ["telnet", "none"] + }, "mac_addr": { "description": "Base MAC address", "type": ["null", "string"], @@ -644,6 +652,10 @@ VM_OBJECT_SCHEMA = { "minimum": 1, "maximum": 65535 }, + "aux_type": { + "description": "Auxiliary console type", + "enum": ["telnet", "none"] + }, "mac_addr": { "description": "Base MAC address", "type": ["null", "string"] diff --git a/gns3server/schemas/node.py b/gns3server/schemas/node.py index 5d3cc11a..d7d92583 100644 --- a/gns3server/schemas/node.py +++ b/gns3server/schemas/node.py @@ -159,6 +159,16 @@ NODE_OBJECT_SCHEMA = { "description": "Automatically start the console when the node has started", "type": "boolean" }, + "aux": { + "description": "Auxiliary console TCP port", + "minimum": 1, + "maximum": 65535, + "type": ["integer", "null"] + }, + "aux_type": { + "description": "Auxiliary console type", + "enum": ["vnc", "telnet", "http", "https", "spice", "spice+agent", "none", None] + }, "properties": { "description": "Properties specific to an emulator", "type": "object" diff --git a/gns3server/schemas/qemu.py b/gns3server/schemas/qemu.py index 3fb75f30..5db85a42 100644 --- a/gns3server/schemas/qemu.py +++ b/gns3server/schemas/qemu.py @@ -67,6 +67,16 @@ QEMU_CREATE_SCHEMA = { "description": "Console type", "enum": ["telnet", "vnc", "spice", "spice+agent", "none"] }, + "aux": { + "description": "Auxiliary TCP port", + "minimum": 1, + "maximum": 65535, + "type": ["integer", "null"] + }, + "aux_type": { + "description": "Auxiliary console type", + "enum": ["telnet", "vnc", "spice", "spice+agent", "none"] + }, "hda_disk_image": { "description": "QEMU hda disk image path", "type": "string", @@ -265,6 +275,16 @@ QEMU_UPDATE_SCHEMA = { "description": "Console type", "enum": ["telnet", "vnc", "spice", "spice+agent", "none"] }, + "aux": { + "description": "Auxiliary TCP port", + "minimum": 1, + "maximum": 65535, + "type": ["integer", "null"] + }, + "aux_type": { + "description": "Auxiliary console type", + "enum": ["telnet", "vnc", "spice", "spice+agent", "none"] + }, "linked_clone": { "description": "Whether the VM is a linked clone or not", "type": "boolean" @@ -579,6 +599,16 @@ QEMU_OBJECT_SCHEMA = { "description": "Console type", "enum": ["telnet", "vnc", "spice","spice+agent", "none"] }, + "aux": { + "description": "Auxiliary TCP port", + "minimum": 1, + "maximum": 65535, + "type": ["integer", "null"] + }, + "aux_type": { + "description": "Auxiliary console type", + "enum": ["telnet", "vnc", "spice", "spice+agent", "none"] + }, "initrd": { "description": "QEMU initrd path", "type": "string", @@ -659,6 +689,7 @@ QEMU_OBJECT_SCHEMA = { "qemu_path", "platform", "console_type", + "aux_type", "hda_disk_image", "hdb_disk_image", "hdc_disk_image", @@ -682,6 +713,7 @@ QEMU_OBJECT_SCHEMA = { "adapter_type", "mac_address", "console", + "aux", "initrd", "kernel_image", "initrd_md5sum", diff --git a/gns3server/schemas/qemu_template.py b/gns3server/schemas/qemu_template.py index 0fc16b3d..e5749253 100644 --- a/gns3server/schemas/qemu_template.py +++ b/gns3server/schemas/qemu_template.py @@ -103,6 +103,11 @@ QEMU_TEMPLATE_PROPERTIES = { "type": "boolean", "default": False }, + "aux_type": { + "description": "Auxiliary console type", + "enum": ["telnet", "vnc", "spice", "spice+agent", "none"], + "default": "none" + }, "boot_priority": { "description": "QEMU boot priority", "enum": ["c", "d", "n", "cn", "cd", "dn", "dc", "nc", "nd"], diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index 3afea049..b2971145 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -45,9 +45,8 @@ async def manager(port_manager): @pytest.fixture(scope="function") async def vm(compute_project, manager): - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", aux_type="none") vm._cid = "e90e34656842" - vm.allocate_aux = False vm.mac_address = '02:42:3d:b7:93:00' return vm @@ -64,6 +63,7 @@ def test_json(vm, compute_project): 'mac_address': '02:42:3d:b7:93:00', 'console': vm.console, 'console_type': 'telnet', + 'aux_type': 'none', 'console_resolution': '1024x768', 'console_http_port': 80, 'console_http_path': '/', @@ -875,7 +875,7 @@ async def test_start(vm, manager, free_console_port, tmpdir): assert vm.status != "started" vm.adapters = 1 - vm.allocate_aux = True + vm.aux_type = "telnet" vm._start_aux = AsyncioMagicMock() vm._get_container_state = AsyncioMagicMock(return_value="stopped") @@ -1428,6 +1428,7 @@ async def test_start_vnc_missing(vm): async def test_start_aux(vm): + vm.aux_type = "telnet" with asyncio_patch("asyncio.subprocess.create_subprocess_exec", return_value=MagicMock()) as mock_exec: await vm._start_aux() mock_exec.assert_called_with( diff --git a/tests/compute/test_base_node.py b/tests/compute/test_base_node.py index 50060e3a..1cfbd11f 100644 --- a/tests/compute/test_base_node.py +++ b/tests/compute/test_base_node.py @@ -102,7 +102,7 @@ def test_aux(compute_project, manager, port_manager): aux = port_manager.get_free_tcp_port(compute_project) port_manager.release_tcp_port(aux, compute_project) - node = DockerVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager, "ubuntu", aux=aux) + node = DockerVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager, "ubuntu", aux=aux, aux_type="telnet") assert node.aux == aux node.aux = None assert node.aux is None @@ -114,12 +114,13 @@ def test_allocate_aux(compute_project, manager): assert node.aux is None # Docker has an aux port by default - node = DockerVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager, "ubuntu") + node = DockerVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager, "ubuntu", aux_type="telnet") assert node.aux is not None -def test_change_aux_port(node, port_manager): +def test_change_aux_port(compute_project, manager, port_manager): + node = DockerVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager, "ubuntu", aux_type="telnet") port1 = port_manager.get_free_tcp_port(node.project) port2 = port_manager.get_free_tcp_port(node.project) port_manager.release_tcp_port(port1, node.project) diff --git a/tests/controller/test_node.py b/tests/controller/test_node.py index 11749289..1269fa8e 100644 --- a/tests/controller/test_node.py +++ b/tests/controller/test_node.py @@ -121,6 +121,8 @@ def test_json(node, compute): "console": node.console, "console_type": node.console_type, "console_host": str(compute.console_host), + "aux": node.aux, + "aux_type": node.aux_type, "command_line": None, "node_directory": None, "properties": node.properties, @@ -158,6 +160,8 @@ def test_json(node, compute): "name": "demo", "console": node.console, "console_type": node.console_type, + "aux": node.aux, + "aux_type": node.aux_type, "properties": node.properties, "x": node.x, "y": node.y, From 74782d413ff7facf8b0b0d649fdbd65abcfe59a4 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 22 Sep 2024 21:41:10 +0700 Subject: [PATCH 7/9] Change method to allocate AUX console for existing Dynamips nodes --- gns3server/compute/base_node.py | 1 - gns3server/compute/dynamips/__init__.py | 4 ---- gns3server/compute/dynamips/nodes/router.py | 22 ++++++++++++++++++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py index c45f287b..d5785d17 100644 --- a/gns3server/compute/base_node.py +++ b/gns3server/compute/base_node.py @@ -653,7 +653,6 @@ class BaseNode: :param aux_type: console type (string) """ - print("SET AUX TYPE", aux_type) if aux_type != self._aux_type: # get a new port if the aux type change if self._aux: diff --git a/gns3server/compute/dynamips/__init__.py b/gns3server/compute/dynamips/__init__.py index 0f81a2bb..ce1d8722 100644 --- a/gns3server/compute/dynamips/__init__.py +++ b/gns3server/compute/dynamips/__init__.py @@ -527,10 +527,6 @@ class Dynamips(BaseManager): if usage is not None and usage != vm.usage: vm.usage = usage - aux_type = settings.get("aux_type") - if aux_type is not None and aux_type != vm.aux_type: - vm.aux_type = aux_type - # update the configs if needed await self.set_vm_configs(vm, settings) diff --git a/gns3server/compute/dynamips/nodes/router.py b/gns3server/compute/dynamips/nodes/router.py index 69553881..15a6aa6f 100644 --- a/gns3server/compute/dynamips/nodes/router.py +++ b/gns3server/compute/dynamips/nodes/router.py @@ -992,7 +992,27 @@ class Router(BaseNode): """ self.aux = aux - await self._hypervisor.send('vm set_aux_tcp_port "{name}" {aux}'.format(name=self._name, aux=aux)) + await self._hypervisor.send('vm set_aux_tcp_port "{name}" {aux}'.format(name=self._name, aux=self._aux)) + + async def set_aux_type(self, aux_type): + """ + Sets the aux type. + + :param aux_type: auxiliary console type + """ + + if self.aux_type != aux_type: + status = await self.get_status() + if status == "running": + raise DynamipsError('"{name}" must be stopped to change the auxiliary console type to {aux_type}'.format( + name=self._name, + aux_type=aux_type) + ) + + self.aux_type = aux_type + + if self._aux and aux_type == "telnet": + await self._hypervisor.send('vm set_aux_tcp_port "{name}" {aux}'.format(name=self._name, aux=self._aux)) async def reset_console(self): """ From af6f34b2caa13fd078a4dd16114cceae9f7539ba Mon Sep 17 00:00:00 2001 From: Jeremy Grossmann Date: Mon, 23 Sep 2024 13:10:58 +0700 Subject: [PATCH 8/9] Revert "Backport auxiliary console support for Qemu, Docker and Dynamips nodes" --- gns3server/compute/base_node.py | 182 +++++++----------- gns3server/compute/docker/docker_vm.py | 10 +- gns3server/compute/dynamips/nodes/c1700.py | 6 +- gns3server/compute/dynamips/nodes/c2600.py | 6 +- gns3server/compute/dynamips/nodes/c2691.py | 6 +- gns3server/compute/dynamips/nodes/c3600.py | 6 +- gns3server/compute/dynamips/nodes/c3725.py | 6 +- gns3server/compute/dynamips/nodes/c3745.py | 6 +- gns3server/compute/dynamips/nodes/c7200.py | 6 +- gns3server/compute/dynamips/nodes/router.py | 34 +--- gns3server/compute/qemu/qemu_vm.py | 70 +++---- gns3server/controller/node.py | 32 --- .../handlers/api/compute/docker_handler.py | 36 ++-- .../api/compute/dynamips_vm_handler.py | 23 +-- .../handlers/api/compute/qemu_handler.py | 20 +- gns3server/schemas/docker.py | 10 +- gns3server/schemas/docker_template.py | 5 - gns3server/schemas/dynamips_template.py | 5 - gns3server/schemas/dynamips_vm.py | 12 -- gns3server/schemas/node.py | 10 - gns3server/schemas/qemu.py | 32 --- gns3server/schemas/qemu_template.py | 5 - tests/compute/docker/test_docker_vm.py | 7 +- tests/compute/test_base_node.py | 7 +- tests/controller/test_node.py | 4 - 25 files changed, 156 insertions(+), 390 deletions(-) diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py index d5785d17..639a5d41 100644 --- a/gns3server/compute/base_node.py +++ b/gns3server/compute/base_node.py @@ -50,16 +50,14 @@ class BaseNode: :param node_id: Node instance identifier :param project: Project instance :param manager: parent node manager - :param console: console TCP port - :param console_type: console type - :param aux: auxiliary console TCP port - :param aux_type: auxiliary console type + :param console: TCP console port + :param aux: TCP aux console port + :param allocate_aux: Boolean if true will allocate an aux console port :param linked_clone: The node base image is duplicate/overlay (Each node data are independent) :param wrap_console: The console is wrapped using AsyncioTelnetServer - :param wrap_aux: The auxiliary console is wrapped using AsyncioTelnetServer """ - def __init__(self, name, node_id, project, manager, console=None, console_type="telnet", aux=None, aux_type="none", linked_clone=True, wrap_console=False, wrap_aux=False): + def __init__(self, name, node_id, project, manager, console=None, console_type="telnet", aux=None, allocate_aux=False, linked_clone=True, wrap_console=False): self._name = name self._usage = "" @@ -70,25 +68,22 @@ class BaseNode: self._console = console self._aux = aux self._console_type = console_type - self._aux_type = aux_type self._temporary_directory = None self._hw_virtualization = False self._ubridge_hypervisor = None self._closed = False self._node_status = "stopped" self._command_line = "" + self._allocate_aux = allocate_aux self._wrap_console = wrap_console - self._wrap_aux = wrap_aux - self._wrapper_telnet_servers = [] + self._wrapper_telnet_server = None self._wrap_console_reader = None self._wrap_console_writer = None self._internal_console_port = None - self._internal_aux_port = None self._custom_adapters = [] self._ubridge_require_privileged_access = False if self._console is not None: - # use a previously allocated console port if console_type == "vnc": vnc_console_start_port_range, vnc_console_end_port_range = self._get_vnc_console_port_range() self._console = self._manager.port_manager.reserve_tcp_port( @@ -102,45 +97,25 @@ class BaseNode: else: self._console = self._manager.port_manager.reserve_tcp_port(self._console, self._project) + # We need to allocate aux before giving a random console port if self._aux is not None: - # use a previously allocated auxiliary console port - if aux_type == "vnc": - # VNC is a special case and the range must be 5900-6000 - self._aux = self._manager.port_manager.reserve_tcp_port( - self._aux, self._project, port_range_start=5900, port_range_end=6000 - ) - elif aux_type == "none": - self._aux = None - else: - self._aux = self._manager.port_manager.reserve_tcp_port(self._aux, self._project) + self._aux = self._manager.port_manager.reserve_tcp_port(self._aux, self._project) if self._console is None: - # allocate a new console if console_type == "vnc": vnc_console_start_port_range, vnc_console_end_port_range = self._get_vnc_console_port_range() self._console = self._manager.port_manager.get_free_tcp_port( self._project, port_range_start=vnc_console_start_port_range, - port_range_end=vnc_console_end_port_range, - ) + port_range_end=vnc_console_end_port_range) elif console_type != "none": self._console = self._manager.port_manager.get_free_tcp_port(self._project) - if self._aux is None: - # allocate a new auxiliary console - if aux_type == "vnc": - # VNC is a special case and the range must be 5900-6000 - self._aux = self._manager.port_manager.get_free_tcp_port( - self._project, port_range_start=5900, port_range_end=6000 - ) - elif aux_type != "none": - self._aux = self._manager.port_manager.get_free_tcp_port(self._project) - if self._wrap_console: self._internal_console_port = self._manager.port_manager.get_free_tcp_port(self._project) - if self._wrap_aux: - self._internal_aux_port = self._manager.port_manager.get_free_tcp_port(self._project) + if self._aux is None and allocate_aux: + self._aux = self._manager.port_manager.get_free_tcp_port(self._project) log.debug("{module}: {name} [{id}] initialized. Console port {console}".format(module=self.manager.module_name, name=self.name, @@ -368,9 +343,6 @@ class BaseNode: if self._aux: self._manager.port_manager.release_tcp_port(self._aux, self._project) self._aux = None - if self._wrap_aux: - self._manager.port_manager.release_tcp_port(self._internal_aux_port, self._project) - self._internal_aux_port = None self._closed = True return True @@ -394,49 +366,56 @@ class BaseNode: return vnc_console_start_port_range, vnc_console_end_port_range - async def _wrap_telnet_proxy(self, internal_port, external_port): + async def start_wrap_console(self): + """ + Start a telnet proxy for the console allowing multiple telnet clients + to be connected at the same time + """ + if not self._wrap_console or self._console_type != "telnet": + return remaining_trial = 60 while True: try: - (reader, writer) = await asyncio.open_connection(host="127.0.0.1", port=internal_port) + (self._wrap_console_reader, self._wrap_console_writer) = await asyncio.open_connection( + host="127.0.0.1", + port=self._internal_console_port + ) break except (OSError, ConnectionRefusedError) as e: if remaining_trial <= 0: raise e await asyncio.sleep(0.1) remaining_trial -= 1 - await AsyncioTelnetServer.write_client_intro(writer, echo=True) - server = AsyncioTelnetServer(reader=reader, writer=writer, binary=True, echo=True) + await AsyncioTelnetServer.write_client_intro(self._wrap_console_writer, echo=True) + server = AsyncioTelnetServer( + reader=self._wrap_console_reader, + writer=self._wrap_console_writer, + binary=True, + echo=True + ) # warning: this will raise OSError exception if there is a problem... - telnet_server = await asyncio.start_server(server.run, self._manager.port_manager.console_host, external_port) - self._wrapper_telnet_servers.append(telnet_server) - - async def start_wrap_console(self): - """ - Start a Telnet proxy servers for the console and auxiliary console allowing multiple telnet clients - to be connected at the same time - """ - - if self._wrap_console and self._console_type == "telnet": - await self._wrap_telnet_proxy(self._internal_console_port, self.console) - log.info("New Telnet proxy server for console started (internal port = {}, external port = {})".format(self._internal_console_port, - self.console)) - - if self._wrap_aux and self._aux_type == "telnet": - await self._wrap_telnet_proxy(self._internal_aux_port, self.aux) - log.info("New Telnet proxy server for auxiliary console started (internal port = {}, external port = {})".format(self._internal_aux_port, - self.aux)) + self._wrapper_telnet_server = await asyncio.start_server( + server.run, + self._manager.port_manager.console_host, + self.console + ) async def stop_wrap_console(self): """ - Stops the telnet proxy servers. + Stops the telnet proxy. """ - for telnet_proxy_server in self._wrapper_telnet_servers: - telnet_proxy_server.close() - await telnet_proxy_server.wait_closed() - self._wrapper_telnet_servers = [] + if self._wrapper_telnet_server: + self._wrap_console_writer.close() + if sys.version_info >= (3, 7, 0): + try: + await self._wrap_console_writer.wait_closed() + except ConnectionResetError: + pass + self._wrapper_telnet_server.close() + await self._wrapper_telnet_server.wait_closed() + self._wrapper_telnet_server = None async def reset_wrap_console(self): """ @@ -513,6 +492,22 @@ class BaseNode: return ws + @property + def allocate_aux(self): + """ + :returns: Boolean allocate or not an aux console + """ + + return self._allocate_aux + + @allocate_aux.setter + def allocate_aux(self, allocate_aux): + """ + :returns: Boolean allocate or not an aux console + """ + + self._allocate_aux = allocate_aux + @property def aux(self): """ @@ -531,25 +526,18 @@ class BaseNode: :params aux: Console port (integer) or None to free the port """ - if aux == self._aux or self._aux_type == "none": + if aux == self._aux: return - if self._aux_type == "vnc" and aux is not None and aux < 5900: - raise NodeError("VNC auxiliary console require a port superior or equal to 5900, current port is {}".format(aux)) - if self._aux: self._manager.port_manager.release_tcp_port(self._aux, self._project) self._aux = None if aux is not None: - if self._aux_type == "vnc": - self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project, port_range_start=5900, port_range_end=6000) - else: - self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project) - - log.info("{module}: '{name}' [{id}]: auxiliary console port set to {port}".format(module=self.manager.module_name, - name=self.name, - id=self.id, - port=aux)) + self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project) + log.info("{module}: '{name}' [{id}]: aux port set to {port}".format(module=self.manager.module_name, + name=self.name, + id=self.id, + port=aux)) @property def console(self): @@ -637,42 +625,6 @@ class BaseNode: console_type=console_type, console=self.console)) - @property - def aux_type(self): - """ - Returns the auxiliary console type for this node. - :returns: aux type (string) - """ - - return self._aux_type - - @aux_type.setter - def aux_type(self, aux_type): - """ - Sets the auxiliary console type for this node. - :param aux_type: console type (string) - """ - - if aux_type != self._aux_type: - # get a new port if the aux type change - if self._aux: - self._manager.port_manager.release_tcp_port(self._aux, self._project) - if aux_type == "none": - # no need to allocate a port when the auxiliary console type is none - self._aux = None - elif aux_type == "vnc": - # VNC is a special case and the range must be 5900-6000 - self._aux = self._manager.port_manager.get_free_tcp_port(self._project, 5900, 6000) - else: - self._aux = self._manager.port_manager.get_free_tcp_port(self._project) - - self._aux_type = aux_type - log.info("{module}: '{name}' [{id}]: console type set to {aux_type} (auxiliary console port is {aux})".format(module=self.manager.module_name, - name=self.name, - id=self.id, - aux_type=aux_type, - aux=self.aux)) - @property def ubridge(self): """ diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index d3c5e79b..fff4e435 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -60,9 +60,8 @@ class DockerVM(BaseNode): :param manager: Manager instance :param image: Docker image :param console: TCP console port - :param console_type: console type + :param console_type: Console type :param aux: TCP aux console port - :param aux_type: auxiliary console type :param console_resolution: Resolution of the VNC display :param console_http_port: Port to redirect HTTP queries :param console_http_path: Url part with the path of the web interface @@ -71,10 +70,10 @@ class DockerVM(BaseNode): """ def __init__(self, name, node_id, project, manager, image, console=None, aux=None, start_command=None, - adapters=None, environment=None, console_type="telnet", aux_type="none", console_resolution="1024x768", + adapters=None, environment=None, console_type="telnet", console_resolution="1024x768", console_http_port=80, console_http_path="/", extra_hosts=None, extra_volumes=[]): - super().__init__(name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type) + super().__init__(name, node_id, project, manager, console=console, aux=aux, allocate_aux=True, console_type=console_type) # force the latest image if no version is specified if ":" not in image: @@ -130,7 +129,6 @@ class DockerVM(BaseNode): "console_http_port": self.console_http_port, "console_http_path": self.console_http_path, "aux": self.aux, - "aux_type": self.aux_type, "start_command": self.start_command, "status": self.status, "environment": self.environment, @@ -548,7 +546,7 @@ class DockerVM(BaseNode): elif self.console_type == "http" or self.console_type == "https": await self._start_http() - if self.aux_type != "none": + if self.allocate_aux: await self._start_aux() self._permissions_fixed = False diff --git a/gns3server/compute/dynamips/nodes/c1700.py b/gns3server/compute/dynamips/nodes/c1700.py index cdc0f343..cb4b8537 100644 --- a/gns3server/compute/dynamips/nodes/c1700.py +++ b/gns3server/compute/dynamips/nodes/c1700.py @@ -40,17 +40,15 @@ class C1700(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port - :param console_type: console type :param aux: auxiliary console port - :param aux_type: auxiliary console type :param chassis: chassis for this router: 1720, 1721, 1750, 1751 or 1760 (default = 1720). 1710 is not supported. """ - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="1720"): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis="1720"): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c1700") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c1700") # Set default values for this platform (must be the same as Dynamips) self._ram = 64 diff --git a/gns3server/compute/dynamips/nodes/c2600.py b/gns3server/compute/dynamips/nodes/c2600.py index e2c3ea13..b065607b 100644 --- a/gns3server/compute/dynamips/nodes/c2600.py +++ b/gns3server/compute/dynamips/nodes/c2600.py @@ -42,9 +42,7 @@ class C2600(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port - :param console_type: console type :param aux: auxiliary console port - :param aux_type: auxiliary console type :param chassis: chassis for this router: 2610, 2611, 2620, 2621, 2610XM, 2611XM 2620XM, 2621XM, 2650XM or 2651XM (default = 2610). @@ -63,9 +61,9 @@ class C2600(Router): "2650XM": C2600_MB_1FE, "2651XM": C2600_MB_2FE} - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="2610"): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis="2610"): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c2600") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c2600") # Set default values for this platform (must be the same as Dynamips) self._ram = 64 diff --git a/gns3server/compute/dynamips/nodes/c2691.py b/gns3server/compute/dynamips/nodes/c2691.py index c946b391..8441881f 100644 --- a/gns3server/compute/dynamips/nodes/c2691.py +++ b/gns3server/compute/dynamips/nodes/c2691.py @@ -40,14 +40,12 @@ class C2691(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port - :param console_type: console type :param aux: auxiliary console port - :param aux_type: auxiliary console type """ - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis=None): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis=None): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c2691") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c2691") # Set default values for this platform (must be the same as Dynamips) self._ram = 128 diff --git a/gns3server/compute/dynamips/nodes/c3600.py b/gns3server/compute/dynamips/nodes/c3600.py index a5341f6e..984a5621 100644 --- a/gns3server/compute/dynamips/nodes/c3600.py +++ b/gns3server/compute/dynamips/nodes/c3600.py @@ -39,16 +39,14 @@ class C3600(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port - :param console_type: console type :param aux: auxiliary console port - :param aux_type: auxiliary console type :param chassis: chassis for this router: 3620, 3640 or 3660 (default = 3640). """ - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="3640"): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis="3640"): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3600") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c3600") # Set default values for this platform (must be the same as Dynamips) self._ram = 128 diff --git a/gns3server/compute/dynamips/nodes/c3725.py b/gns3server/compute/dynamips/nodes/c3725.py index 5ba52e47..be194cf5 100644 --- a/gns3server/compute/dynamips/nodes/c3725.py +++ b/gns3server/compute/dynamips/nodes/c3725.py @@ -40,14 +40,12 @@ class C3725(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port - :param console_type: console type :param aux: auxiliary console port - :param aux_type: auxiliary console type """ - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis=None): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis=None): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3725") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c3725") # Set default values for this platform (must be the same as Dynamips) self._ram = 128 diff --git a/gns3server/compute/dynamips/nodes/c3745.py b/gns3server/compute/dynamips/nodes/c3745.py index cdbc6b49..9087a98f 100644 --- a/gns3server/compute/dynamips/nodes/c3745.py +++ b/gns3server/compute/dynamips/nodes/c3745.py @@ -40,14 +40,12 @@ class C3745(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port - :param console_type: console type :param aux: auxiliary console port - :param aux_type: auxiliary console type """ - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis=None): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis=None): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3745") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c3745") # Set default values for this platform (must be the same as Dynamips) self._ram = 128 diff --git a/gns3server/compute/dynamips/nodes/c7200.py b/gns3server/compute/dynamips/nodes/c7200.py index 6ebf9abb..155bc385 100644 --- a/gns3server/compute/dynamips/nodes/c7200.py +++ b/gns3server/compute/dynamips/nodes/c7200.py @@ -42,15 +42,13 @@ class C7200(Router): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port - :param console_type: console type :param aux: auxiliary console port - :param aux_type: auxiliary console type :param npe: Default NPE """ - def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", npe="npe-400", chassis=None): + def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, npe="npe-400", chassis=None): - super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c7200") + super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c7200") # Set default values for this platform (must be the same as Dynamips) self._ram = 256 diff --git a/gns3server/compute/dynamips/nodes/router.py b/gns3server/compute/dynamips/nodes/router.py index 15a6aa6f..e9fe08ef 100644 --- a/gns3server/compute/dynamips/nodes/router.py +++ b/gns3server/compute/dynamips/nodes/router.py @@ -52,9 +52,7 @@ class Router(BaseNode): :param manager: Parent VM Manager :param dynamips_id: ID to use with Dynamips :param console: console port - :param console_type: console type :param aux: auxiliary console port - :param aux_type: auxiliary console type :param platform: Platform of this router """ @@ -63,9 +61,9 @@ class Router(BaseNode): 2: "running", 3: "suspended"} - def __init__(self, name, node_id, project, manager, dynamips_id=None, console=None, console_type="telnet", aux=None, aux_type="none", platform="c7200", hypervisor=None, ghost_flag=False): + def __init__(self, name, node_id, project, manager, dynamips_id=None, console=None, console_type="telnet", aux=None, platform="c7200", hypervisor=None, ghost_flag=False): - super().__init__(name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type) + super().__init__(name, node_id, project, manager, console=console, console_type=console_type, aux=aux, allocate_aux=aux) self._working_directory = os.path.join(self.project.module_working_directory(self.manager.module_name.lower()), self.id) try: @@ -168,7 +166,6 @@ class Router(BaseNode): "console": self.console, "console_type": self.console_type, "aux": self.aux, - "aux_type": self.aux_type, "mac_addr": self._mac_addr, "system_id": self._system_id} @@ -226,14 +223,15 @@ class Router(BaseNode): platform=self._platform, id=self._id)) - if self._console is not None: + if self._console: await self._hypervisor.send('vm set_con_tcp_port "{name}" {console}'.format(name=self._name, console=self._console)) if self.aux is not None: await self._hypervisor.send('vm set_aux_tcp_port "{name}" {aux}'.format(name=self._name, aux=self.aux)) # get the default base MAC address - mac_addr = await self._hypervisor.send('{platform} get_mac_addr "{name}"'.format(platform=self._platform, name=self._name)) + mac_addr = await self._hypervisor.send('{platform} get_mac_addr "{name}"'.format(platform=self._platform, + name=self._name)) self._mac_addr = mac_addr[0] self._hypervisor.devices.append(self) @@ -992,27 +990,7 @@ class Router(BaseNode): """ self.aux = aux - await self._hypervisor.send('vm set_aux_tcp_port "{name}" {aux}'.format(name=self._name, aux=self._aux)) - - async def set_aux_type(self, aux_type): - """ - Sets the aux type. - - :param aux_type: auxiliary console type - """ - - if self.aux_type != aux_type: - status = await self.get_status() - if status == "running": - raise DynamipsError('"{name}" must be stopped to change the auxiliary console type to {aux_type}'.format( - name=self._name, - aux_type=aux_type) - ) - - self.aux_type = aux_type - - if self._aux and aux_type == "telnet": - await self._hypervisor.send('vm set_aux_tcp_port "{name}" {aux}'.format(name=self._name, aux=self._aux)) + await self._hypervisor.send('vm set_aux_tcp_port "{name}" {aux}'.format(name=self._name, aux=aux)) async def reset_console(self): """ diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 984c8b06..99b2d711 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -76,9 +76,9 @@ class QemuVM(BaseNode): :param platform: Platform to emulate """ - def __init__(self, name, node_id, project, manager, linked_clone=True, qemu_path=None, console=None, console_type="telnet", aux=None, aux_type="none", platform=None): + def __init__(self, name, node_id, project, manager, linked_clone=True, qemu_path=None, console=None, console_type="telnet", platform=None): - super().__init__(name, node_id, project, manager, console=console, console_type=console_type, linked_clone=linked_clone, aux=aux, aux_type=aux_type, wrap_console=True, wrap_aux=True) + super().__init__(name, node_id, project, manager, console=console, console_type=console_type, linked_clone=linked_clone, wrap_console=True) server_config = manager.config.get_section_config("Server") self._host = server_config.get("host", "127.0.0.1") self._monitor_host = server_config.get("monitor_host", "127.0.0.1") @@ -1658,24 +1658,24 @@ class QemuVM(BaseNode): super(QemuVM, QemuVM).console_type.__set__(self, new_console_type) - def _serial_options(self, internal_console_port, external_console_port): + def _serial_options(self): - if external_console_port: - return ["-serial", "telnet:127.0.0.1:{},server,nowait".format(internal_console_port)] + if self._console: + return ["-serial", "telnet:127.0.0.1:{},server,nowait".format(self._internal_console_port)] else: return [] - def _vnc_options(self, port): + def _vnc_options(self): - if port: - vnc_port = port - 5900 # subtract by 5900 to get the display number + if self._console: + vnc_port = self._console - 5900 # subtract by 5900 to get the display number return ["-vnc", "{}:{}".format(self._manager.port_manager.console_host, vnc_port)] else: return [] - def _spice_options(self, port): + def _spice_options(self): - if port: + if self._console: console_host = self._manager.port_manager.console_host if console_host == "0.0.0.0": try: @@ -1687,15 +1687,15 @@ class QemuVM(BaseNode): except OSError as e: raise QemuError("Could not check if IPv6 is enabled: {}".format(e)) return ["-spice", - "addr={},port={},disable-ticketing".format(console_host, port), + "addr={},port={},disable-ticketing".format(console_host, self._console), "-vga", "qxl"] else: return [] - def _spice_with_agent_options(self, port): + def _spice_with_agent_options(self): - spice_options = self._spice_options(port) - if spice_options: + spice_options = self._spice_options() + if self._console: # agent options (mouse/screen) agent_options = ["-device", "virtio-serial", "-chardev", "spicevmc,id=vdagent,debug=0,name=vdagent", @@ -1707,36 +1707,6 @@ class QemuVM(BaseNode): spice_options.extend(folder_sharing_options) return spice_options - def _console_options(self): - - if self._console_type == "telnet" and self._wrap_console: - return self._serial_options(self._internal_console_port, self.console) - elif self._console_type == "vnc": - return self._vnc_options(self.console) - elif self._console_type == "spice": - return self._spice_options(self.console) - elif self._console_type == "spice+agent": - return self._spice_with_agent_options(self.console) - elif self._console_type != "none": - raise QemuError("Console type {} is unknown".format(self._console_type)) - - def _aux_options(self): - - if self._aux_type != "none" and self._aux_type == self._console_type: - raise QemuError("Auxiliary console type {} cannot be the same as console type".format(self._aux_type)) - - if self._aux_type == "telnet" and self._wrap_aux: - return self._serial_options(self._internal_aux_port, self.aux) - elif self._aux_type == "vnc": - return self._vnc_options(self.aux) - elif self._aux_type == "spice": - return self._spice_options(self.aux) - elif self._aux_type == "spice+agent": - return self._spice_with_agent_options(self.aux) - elif self._aux_type != "none": - raise QemuError("Auxiliary console type {} is unknown".format(self._aux_type)) - return [] - def _monitor_options(self): if self._monitor: @@ -2438,8 +2408,16 @@ class QemuVM(BaseNode): command.extend(self._linux_boot_options()) if "-uuid" not in additional_options: command.extend(["-uuid", self._id]) - command.extend(self._console_options()) - command.extend(self._aux_options()) + if self._console_type == "telnet": + command.extend(self._serial_options()) + elif self._console_type == "vnc": + command.extend(self._vnc_options()) + elif self._console_type == "spice": + command.extend(self._spice_options()) + elif self._console_type == "spice+agent": + command.extend(self._spice_with_agent_options()) + elif self._console_type != "none": + raise QemuError("Console type {} is unknown".format(self._console_type)) command.extend(self._monitor_options()) command.extend((await self._network_options())) if self.on_close != "save_vm_state": diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index 658a27f4..eb5bad69 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -68,8 +68,6 @@ class Node: self.name = name self._console = None self._console_type = None - self._aux = None - self._aux_type = None self._properties = None self._command_line = None self._node_directory = None @@ -163,14 +161,6 @@ class Node: def console(self, val): self._console = val - @property - def aux(self): - return self._aux - - @aux.setter - def aux(self, val): - self._aux = val - @property def console_type(self): return self._console_type @@ -179,14 +169,6 @@ class Node: def console_type(self, val): self._console_type = val - @property - def aux_type(self): - return self._aux_type - - @aux_type.setter - def aux_type(self, val): - self._aux_type = val - @property def console_auto_start(self): return self._console_auto_start @@ -448,8 +430,6 @@ class Node: for key, value in response.items(): if key == "console": self._console = value - elif key == "aux": - self._aux = value elif key == "node_directory": self._node_directory = value elif key == "command_line": @@ -458,8 +438,6 @@ class Node: self._status = value elif key == "console_type": self._console_type = value - elif key == "aux_type": - self._aux_type = value elif key == "name": self.name = value elif key in ["node_id", "project_id", "console_host", @@ -502,12 +480,6 @@ class Node: if self._console_type and self._node_type not in ("cloud", "nat", "ethernet_hub", "frame_relay_switch", "atm_switch"): # console_type is not supported by all builtin nodes excepting Ethernet switch data["console_type"] = self._console_type - if self._aux: - # aux is optional for builtin nodes - data["aux"] = self._aux - if self._aux_type and self._node_type not in ("cloud", "nat", "ethernet_switch", "ethernet_hub", "frame_relay_switch", "atm_switch"): - # aux_type is not supported by all builtin nodes - data["aux_type"] = self._aux_type if self.custom_adapters: data["custom_adapters"] = self.custom_adapters @@ -737,8 +709,6 @@ class Node: "console": self._console, "console_type": self._console_type, "console_auto_start": self._console_auto_start, - "aux": self._aux, - "aux_type": self._aux_type, "properties": self._properties, "label": self._label, "x": self._x, @@ -776,8 +746,6 @@ class Node: "console_host": console_host, "console_type": self._console_type, "console_auto_start": self._console_auto_start, - "aux": self._aux, - "aux_type": self._aux_type, "command_line": self._command_line, "properties": self._properties, "status": self._status, diff --git a/gns3server/handlers/api/compute/docker_handler.py b/gns3server/handlers/api/compute/docker_handler.py index 270b383b..c726d52f 100644 --- a/gns3server/handlers/api/compute/docker_handler.py +++ b/gns3server/handlers/api/compute/docker_handler.py @@ -48,25 +48,21 @@ class DockerHandler: output=DOCKER_OBJECT_SCHEMA) async def create(request, response): docker_manager = Docker.instance() - container = await docker_manager.create_node( - request.json.pop("name"), - request.match_info["project_id"], - request.json.get("node_id"), - image=request.json.pop("image"), - start_command=request.json.get("start_command"), - environment=request.json.get("environment"), - adapters=request.json.get("adapters"), - console=request.json.get("console"), - console_type=request.json.get("console_type"), - console_resolution=request.json.get("console_resolution", "1024x768"), - console_http_port=request.json.get("console_http_port", 80), - console_http_path=request.json.get("console_http_path", "/"), - aux=request.json.get("aux"), - aux_type=request.json.pop("aux_type", "none"), - extra_hosts=request.json.get("extra_hosts"), - extra_volumes=request.json.get("extra_volumes") - ) - + container = await docker_manager.create_node(request.json.pop("name"), + request.match_info["project_id"], + request.json.get("node_id"), + image=request.json.pop("image"), + start_command=request.json.get("start_command"), + environment=request.json.get("environment"), + adapters=request.json.get("adapters"), + console=request.json.get("console"), + console_type=request.json.get("console_type"), + console_resolution=request.json.get("console_resolution", "1024x768"), + console_http_port=request.json.get("console_http_port", 80), + console_http_path=request.json.get("console_http_path", "/"), + aux=request.json.get("aux"), + extra_hosts=request.json.get("extra_hosts"), + extra_volumes=request.json.get("extra_volumes")) for name, value in request.json.items(): if name != "node_id": if hasattr(container, name) and getattr(container, name) != value: @@ -319,7 +315,7 @@ class DockerHandler: container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) props = [ - "name", "console", "console_type", "aux", "aux_type", "console_resolution", + "name", "console", "aux", "console_type", "console_resolution", "console_http_port", "console_http_path", "start_command", "environment", "adapters", "mac_address", "custom_adapters", "extra_hosts", "extra_volumes" ] diff --git a/gns3server/handlers/api/compute/dynamips_vm_handler.py b/gns3server/handlers/api/compute/dynamips_vm_handler.py index ead9c6f8..7750e896 100644 --- a/gns3server/handlers/api/compute/dynamips_vm_handler.py +++ b/gns3server/handlers/api/compute/dynamips_vm_handler.py @@ -69,19 +69,16 @@ class DynamipsVMHandler: default_chassis = None if platform in DEFAULT_CHASSIS: default_chassis = DEFAULT_CHASSIS[platform] - vm = await dynamips_manager.create_node( - request.json.pop("name"), - request.match_info["project_id"], - request.json.get("node_id"), - dynamips_id=request.json.get("dynamips_id"), - platform=platform, - console=request.json.get("console"), - console_type=request.json.get("console_type", "telnet"), - aux=request.json.get("aux"), - aux_type=request.json.get("aux_type", "none"), - chassis=request.json.pop("chassis", default_chassis), - node_type="dynamips" - ) + vm = await dynamips_manager.create_node(request.json.pop("name"), + request.match_info["project_id"], + request.json.get("node_id"), + dynamips_id=request.json.get("dynamips_id"), + platform=platform, + console=request.json.get("console"), + console_type=request.json.get("console_type", "telnet"), + aux=request.json.get("aux"), + chassis=request.json.pop("chassis", default_chassis), + node_type="dynamips") await dynamips_manager.update_vm_settings(vm, request.json) response.set_status(201) response.json(vm) diff --git a/gns3server/handlers/api/compute/qemu_handler.py b/gns3server/handlers/api/compute/qemu_handler.py index bc29d712..6fcf5aee 100644 --- a/gns3server/handlers/api/compute/qemu_handler.py +++ b/gns3server/handlers/api/compute/qemu_handler.py @@ -66,18 +66,14 @@ class QEMUHandler: async def create(request, response): qemu = Qemu.instance() - vm = await qemu.create_node( - request.json.pop("name"), - request.match_info["project_id"], - request.json.pop("node_id", None), - linked_clone=request.json.get("linked_clone", True), - qemu_path=request.json.pop("qemu_path", None), - console=request.json.pop("console", None), - console_type=request.json.pop("console_type", "telnet"), - aux=request.json.pop("aux", None), - aux_type=request.json.pop("aux_type", "none"), - platform=request.json.pop("platform", None) - ) + vm = await qemu.create_node(request.json.pop("name"), + request.match_info["project_id"], + request.json.pop("node_id", None), + linked_clone=request.json.get("linked_clone", True), + qemu_path=request.json.pop("qemu_path", None), + console=request.json.pop("console", None), + console_type=request.json.pop("console_type", "telnet"), + platform=request.json.pop("platform", None)) for name, value in request.json.items(): if hasattr(vm, name) and getattr(vm, name) != value: diff --git a/gns3server/schemas/docker.py b/gns3server/schemas/docker.py index abecfef2..07125809 100644 --- a/gns3server/schemas/docker.py +++ b/gns3server/schemas/docker.py @@ -65,10 +65,6 @@ DOCKER_CREATE_SCHEMA = { "maximum": 65535, "type": ["integer", "null"] }, - "aux_type": { - "description": "Auxiliary console type", - "enum": ["telnet", "none"] - }, "usage": { "description": "How to use the Docker container", "type": "string", @@ -147,7 +143,7 @@ DOCKER_OBJECT_SCHEMA = { "description": "Auxiliary TCP port", "minimum": 1, "maximum": 65535, - "type": ["integer", "null"] + "type": "integer" }, "console": { "description": "Console TCP port", @@ -164,10 +160,6 @@ DOCKER_OBJECT_SCHEMA = { "description": "Console type", "enum": ["telnet", "vnc", "http", "https", "none"] }, - "aux_type": { - "description": "Auxiliary console type", - "enum": ["telnet", "none"] - }, "console_http_port": { "description": "Internal port in the container for the HTTP server", "type": "integer", diff --git a/gns3server/schemas/docker_template.py b/gns3server/schemas/docker_template.py index 6969bb14..838e1023 100644 --- a/gns3server/schemas/docker_template.py +++ b/gns3server/schemas/docker_template.py @@ -67,11 +67,6 @@ DOCKER_TEMPLATE_PROPERTIES = { "type": "boolean", "default": False, }, - "aux_type": { - "description": "Auxiliary console type", - "enum": ["telnet", "none"], - "default": "none" - }, "console_http_port": { "description": "Internal port in the container for the HTTP server", "type": "integer", diff --git a/gns3server/schemas/dynamips_template.py b/gns3server/schemas/dynamips_template.py index b9b5fc4b..61a41fc1 100644 --- a/gns3server/schemas/dynamips_template.py +++ b/gns3server/schemas/dynamips_template.py @@ -120,11 +120,6 @@ DYNAMIPS_TEMPLATE_PROPERTIES = { "description": "Automatically start the console when the node has started", "type": "boolean", "default": False - }, - "aux_type": { - "description": "Auxiliary console type", - "enum": ["telnet", "none"], - "default": "none" } } diff --git a/gns3server/schemas/dynamips_vm.py b/gns3server/schemas/dynamips_vm.py index 2e0b32f9..1fc939a6 100644 --- a/gns3server/schemas/dynamips_vm.py +++ b/gns3server/schemas/dynamips_vm.py @@ -178,10 +178,6 @@ VM_CREATE_SCHEMA = { "minimum": 1, "maximum": 65535 }, - "aux_type": { - "description": "Auxiliary console type", - "enum": ["telnet", "none"] - }, "mac_addr": { "description": "Base MAC address", "type": ["null", "string"], @@ -404,10 +400,6 @@ VM_UPDATE_SCHEMA = { "minimum": 1, "maximum": 65535 }, - "aux_type": { - "description": "Auxiliary console type", - "enum": ["telnet", "none"] - }, "mac_addr": { "description": "Base MAC address", "type": ["null", "string"], @@ -652,10 +644,6 @@ VM_OBJECT_SCHEMA = { "minimum": 1, "maximum": 65535 }, - "aux_type": { - "description": "Auxiliary console type", - "enum": ["telnet", "none"] - }, "mac_addr": { "description": "Base MAC address", "type": ["null", "string"] diff --git a/gns3server/schemas/node.py b/gns3server/schemas/node.py index d7d92583..5d3cc11a 100644 --- a/gns3server/schemas/node.py +++ b/gns3server/schemas/node.py @@ -159,16 +159,6 @@ NODE_OBJECT_SCHEMA = { "description": "Automatically start the console when the node has started", "type": "boolean" }, - "aux": { - "description": "Auxiliary console TCP port", - "minimum": 1, - "maximum": 65535, - "type": ["integer", "null"] - }, - "aux_type": { - "description": "Auxiliary console type", - "enum": ["vnc", "telnet", "http", "https", "spice", "spice+agent", "none", None] - }, "properties": { "description": "Properties specific to an emulator", "type": "object" diff --git a/gns3server/schemas/qemu.py b/gns3server/schemas/qemu.py index 5db85a42..3fb75f30 100644 --- a/gns3server/schemas/qemu.py +++ b/gns3server/schemas/qemu.py @@ -67,16 +67,6 @@ QEMU_CREATE_SCHEMA = { "description": "Console type", "enum": ["telnet", "vnc", "spice", "spice+agent", "none"] }, - "aux": { - "description": "Auxiliary TCP port", - "minimum": 1, - "maximum": 65535, - "type": ["integer", "null"] - }, - "aux_type": { - "description": "Auxiliary console type", - "enum": ["telnet", "vnc", "spice", "spice+agent", "none"] - }, "hda_disk_image": { "description": "QEMU hda disk image path", "type": "string", @@ -275,16 +265,6 @@ QEMU_UPDATE_SCHEMA = { "description": "Console type", "enum": ["telnet", "vnc", "spice", "spice+agent", "none"] }, - "aux": { - "description": "Auxiliary TCP port", - "minimum": 1, - "maximum": 65535, - "type": ["integer", "null"] - }, - "aux_type": { - "description": "Auxiliary console type", - "enum": ["telnet", "vnc", "spice", "spice+agent", "none"] - }, "linked_clone": { "description": "Whether the VM is a linked clone or not", "type": "boolean" @@ -599,16 +579,6 @@ QEMU_OBJECT_SCHEMA = { "description": "Console type", "enum": ["telnet", "vnc", "spice","spice+agent", "none"] }, - "aux": { - "description": "Auxiliary TCP port", - "minimum": 1, - "maximum": 65535, - "type": ["integer", "null"] - }, - "aux_type": { - "description": "Auxiliary console type", - "enum": ["telnet", "vnc", "spice", "spice+agent", "none"] - }, "initrd": { "description": "QEMU initrd path", "type": "string", @@ -689,7 +659,6 @@ QEMU_OBJECT_SCHEMA = { "qemu_path", "platform", "console_type", - "aux_type", "hda_disk_image", "hdb_disk_image", "hdc_disk_image", @@ -713,7 +682,6 @@ QEMU_OBJECT_SCHEMA = { "adapter_type", "mac_address", "console", - "aux", "initrd", "kernel_image", "initrd_md5sum", diff --git a/gns3server/schemas/qemu_template.py b/gns3server/schemas/qemu_template.py index e5749253..0fc16b3d 100644 --- a/gns3server/schemas/qemu_template.py +++ b/gns3server/schemas/qemu_template.py @@ -103,11 +103,6 @@ QEMU_TEMPLATE_PROPERTIES = { "type": "boolean", "default": False }, - "aux_type": { - "description": "Auxiliary console type", - "enum": ["telnet", "vnc", "spice", "spice+agent", "none"], - "default": "none" - }, "boot_priority": { "description": "QEMU boot priority", "enum": ["c", "d", "n", "cn", "cd", "dn", "dc", "nc", "nd"], diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index b2971145..3afea049 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -45,8 +45,9 @@ async def manager(port_manager): @pytest.fixture(scope="function") async def vm(compute_project, manager): - vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", aux_type="none") + vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest") vm._cid = "e90e34656842" + vm.allocate_aux = False vm.mac_address = '02:42:3d:b7:93:00' return vm @@ -63,7 +64,6 @@ def test_json(vm, compute_project): 'mac_address': '02:42:3d:b7:93:00', 'console': vm.console, 'console_type': 'telnet', - 'aux_type': 'none', 'console_resolution': '1024x768', 'console_http_port': 80, 'console_http_path': '/', @@ -875,7 +875,7 @@ async def test_start(vm, manager, free_console_port, tmpdir): assert vm.status != "started" vm.adapters = 1 - vm.aux_type = "telnet" + vm.allocate_aux = True vm._start_aux = AsyncioMagicMock() vm._get_container_state = AsyncioMagicMock(return_value="stopped") @@ -1428,7 +1428,6 @@ async def test_start_vnc_missing(vm): async def test_start_aux(vm): - vm.aux_type = "telnet" with asyncio_patch("asyncio.subprocess.create_subprocess_exec", return_value=MagicMock()) as mock_exec: await vm._start_aux() mock_exec.assert_called_with( diff --git a/tests/compute/test_base_node.py b/tests/compute/test_base_node.py index 1cfbd11f..50060e3a 100644 --- a/tests/compute/test_base_node.py +++ b/tests/compute/test_base_node.py @@ -102,7 +102,7 @@ def test_aux(compute_project, manager, port_manager): aux = port_manager.get_free_tcp_port(compute_project) port_manager.release_tcp_port(aux, compute_project) - node = DockerVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager, "ubuntu", aux=aux, aux_type="telnet") + node = DockerVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager, "ubuntu", aux=aux) assert node.aux == aux node.aux = None assert node.aux is None @@ -114,13 +114,12 @@ def test_allocate_aux(compute_project, manager): assert node.aux is None # Docker has an aux port by default - node = DockerVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager, "ubuntu", aux_type="telnet") + node = DockerVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager, "ubuntu") assert node.aux is not None -def test_change_aux_port(compute_project, manager, port_manager): +def test_change_aux_port(node, port_manager): - node = DockerVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager, "ubuntu", aux_type="telnet") port1 = port_manager.get_free_tcp_port(node.project) port2 = port_manager.get_free_tcp_port(node.project) port_manager.release_tcp_port(port1, node.project) diff --git a/tests/controller/test_node.py b/tests/controller/test_node.py index 1269fa8e..11749289 100644 --- a/tests/controller/test_node.py +++ b/tests/controller/test_node.py @@ -121,8 +121,6 @@ def test_json(node, compute): "console": node.console, "console_type": node.console_type, "console_host": str(compute.console_host), - "aux": node.aux, - "aux_type": node.aux_type, "command_line": None, "node_directory": None, "properties": node.properties, @@ -160,8 +158,6 @@ def test_json(node, compute): "name": "demo", "console": node.console, "console_type": node.console_type, - "aux": node.aux, - "aux_type": node.aux_type, "properties": node.properties, "x": node.x, "y": node.y, From 7b5d123ad8937a28719cc122fe46b3308a506fd0 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 23 Sep 2024 13:23:03 +0700 Subject: [PATCH 9/9] Improve error message when a project cannot be parsed. --- gns3server/controller/topology.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py index 74dc4990..8f623092 100644 --- a/gns3server/controller/topology.py +++ b/gns3server/controller/topology.py @@ -188,7 +188,7 @@ def load_topology(path): try: _check_topology_schema(topo) except aiohttp.web.HTTPConflict as e: - log.error("Can't load the topology %s", path) + log.error("Can't load the topology %s, please check using the debug mode...", path) raise e if changed: