Skip to content

Commit c41c11e

Browse files
committed
Backport auxiliary console support for Qemu, Docker and Dynamips nodes
1 parent 1f09a3e commit c41c11e

26 files changed

+374
-155
lines changed

gns3server/compute/base_node.py

+116-67
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,16 @@ class BaseNode:
5050
:param node_id: Node instance identifier
5151
:param project: Project instance
5252
:param manager: parent node manager
53-
:param console: TCP console port
54-
:param aux: TCP aux console port
55-
:param allocate_aux: Boolean if true will allocate an aux console port
53+
:param console: console TCP port
54+
:param console_type: console type
55+
:param aux: auxiliary console TCP port
56+
:param aux_type: auxiliary console type
5657
:param linked_clone: The node base image is duplicate/overlay (Each node data are independent)
5758
:param wrap_console: The console is wrapped using AsyncioTelnetServer
59+
:param wrap_aux: The auxiliary console is wrapped using AsyncioTelnetServer
5860
"""
5961

60-
def __init__(self, name, node_id, project, manager, console=None, console_type="telnet", aux=None, allocate_aux=False, linked_clone=True, wrap_console=False):
62+
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):
6163

6264
self._name = name
6365
self._usage = ""
@@ -68,22 +70,25 @@ def __init__(self, name, node_id, project, manager, console=None, console_type="
6870
self._console = console
6971
self._aux = aux
7072
self._console_type = console_type
73+
self._aux_type = aux_type
7174
self._temporary_directory = None
7275
self._hw_virtualization = False
7376
self._ubridge_hypervisor = None
7477
self._closed = False
7578
self._node_status = "stopped"
7679
self._command_line = ""
77-
self._allocate_aux = allocate_aux
7880
self._wrap_console = wrap_console
79-
self._wrapper_telnet_server = None
81+
self._wrap_aux = wrap_aux
82+
self._wrapper_telnet_servers = []
8083
self._wrap_console_reader = None
8184
self._wrap_console_writer = None
8285
self._internal_console_port = None
86+
self._internal_aux_port = None
8387
self._custom_adapters = []
8488
self._ubridge_require_privileged_access = False
8589

8690
if self._console is not None:
91+
# use a previously allocated console port
8792
if console_type == "vnc":
8893
vnc_console_start_port_range, vnc_console_end_port_range = self._get_vnc_console_port_range()
8994
self._console = self._manager.port_manager.reserve_tcp_port(
@@ -97,25 +102,45 @@ def __init__(self, name, node_id, project, manager, console=None, console_type="
97102
else:
98103
self._console = self._manager.port_manager.reserve_tcp_port(self._console, self._project)
99104

100-
# We need to allocate aux before giving a random console port
101105
if self._aux is not None:
102-
self._aux = self._manager.port_manager.reserve_tcp_port(self._aux, self._project)
106+
# use a previously allocated auxiliary console port
107+
if aux_type == "vnc":
108+
# VNC is a special case and the range must be 5900-6000
109+
self._aux = self._manager.port_manager.reserve_tcp_port(
110+
self._aux, self._project, port_range_start=5900, port_range_end=6000
111+
)
112+
elif aux_type == "none":
113+
self._aux = None
114+
else:
115+
self._aux = self._manager.port_manager.reserve_tcp_port(self._aux, self._project)
103116

104117
if self._console is None:
118+
# allocate a new console
105119
if console_type == "vnc":
106120
vnc_console_start_port_range, vnc_console_end_port_range = self._get_vnc_console_port_range()
107121
self._console = self._manager.port_manager.get_free_tcp_port(
108122
self._project,
109123
port_range_start=vnc_console_start_port_range,
110-
port_range_end=vnc_console_end_port_range)
124+
port_range_end=vnc_console_end_port_range,
125+
)
111126
elif console_type != "none":
112127
self._console = self._manager.port_manager.get_free_tcp_port(self._project)
113128

129+
if self._aux is None:
130+
# allocate a new auxiliary console
131+
if aux_type == "vnc":
132+
# VNC is a special case and the range must be 5900-6000
133+
self._aux = self._manager.port_manager.get_free_tcp_port(
134+
self._project, port_range_start=5900, port_range_end=6000
135+
)
136+
elif aux_type != "none":
137+
self._aux = self._manager.port_manager.get_free_tcp_port(self._project)
138+
114139
if self._wrap_console:
115140
self._internal_console_port = self._manager.port_manager.get_free_tcp_port(self._project)
116141

117-
if self._aux is None and allocate_aux:
118-
self._aux = self._manager.port_manager.get_free_tcp_port(self._project)
142+
if self._wrap_aux:
143+
self._internal_aux_port = self._manager.port_manager.get_free_tcp_port(self._project)
119144

120145
log.debug("{module}: {name} [{id}] initialized. Console port {console}".format(module=self.manager.module_name,
121146
name=self.name,
@@ -343,6 +368,9 @@ async def close(self):
343368
if self._aux:
344369
self._manager.port_manager.release_tcp_port(self._aux, self._project)
345370
self._aux = None
371+
if self._wrap_aux:
372+
self._manager.port_manager.release_tcp_port(self._internal_aux_port, self._project)
373+
self._internal_aux_port = None
346374

347375
self._closed = True
348376
return True
@@ -366,56 +394,49 @@ def _get_vnc_console_port_range(self):
366394

367395
return vnc_console_start_port_range, vnc_console_end_port_range
368396

369-
async def start_wrap_console(self):
370-
"""
371-
Start a telnet proxy for the console allowing multiple telnet clients
372-
to be connected at the same time
373-
"""
397+
async def _wrap_telnet_proxy(self, internal_port, external_port):
374398

375-
if not self._wrap_console or self._console_type != "telnet":
376-
return
377399
remaining_trial = 60
378400
while True:
379401
try:
380-
(self._wrap_console_reader, self._wrap_console_writer) = await asyncio.open_connection(
381-
host="127.0.0.1",
382-
port=self._internal_console_port
383-
)
402+
(reader, writer) = await asyncio.open_connection(host="127.0.0.1", port=internal_port)
384403
break
385404
except (OSError, ConnectionRefusedError) as e:
386405
if remaining_trial <= 0:
387406
raise e
388407
await asyncio.sleep(0.1)
389408
remaining_trial -= 1
390-
await AsyncioTelnetServer.write_client_intro(self._wrap_console_writer, echo=True)
391-
server = AsyncioTelnetServer(
392-
reader=self._wrap_console_reader,
393-
writer=self._wrap_console_writer,
394-
binary=True,
395-
echo=True
396-
)
409+
await AsyncioTelnetServer.write_client_intro(writer, echo=True)
410+
server = AsyncioTelnetServer(reader=reader, writer=writer, binary=True, echo=True)
397411
# warning: this will raise OSError exception if there is a problem...
398-
self._wrapper_telnet_server = await asyncio.start_server(
399-
server.run,
400-
self._manager.port_manager.console_host,
401-
self.console
402-
)
412+
telnet_server = await asyncio.start_server(server.run, self._manager.port_manager.console_host, external_port)
413+
self._wrapper_telnet_servers.append(telnet_server)
414+
415+
async def start_wrap_console(self):
416+
"""
417+
Start a Telnet proxy servers for the console and auxiliary console allowing multiple telnet clients
418+
to be connected at the same time
419+
"""
420+
421+
if self._wrap_console and self._console_type == "telnet":
422+
await self._wrap_telnet_proxy(self._internal_console_port, self.console)
423+
log.info("New Telnet proxy server for console started (internal port = {}, external port = {})".format(self._internal_console_port,
424+
self.console))
425+
426+
if self._wrap_aux and self._aux_type == "telnet":
427+
await self._wrap_telnet_proxy(self._internal_aux_port, self.aux)
428+
log.info("New Telnet proxy server for auxiliary console started (internal port = {}, external port = {})".format(self._internal_aux_port,
429+
self.aux))
403430

404431
async def stop_wrap_console(self):
405432
"""
406-
Stops the telnet proxy.
433+
Stops the telnet proxy servers.
407434
"""
408435

409-
if self._wrapper_telnet_server:
410-
self._wrap_console_writer.close()
411-
if sys.version_info >= (3, 7, 0):
412-
try:
413-
await self._wrap_console_writer.wait_closed()
414-
except ConnectionResetError:
415-
pass
416-
self._wrapper_telnet_server.close()
417-
await self._wrapper_telnet_server.wait_closed()
418-
self._wrapper_telnet_server = None
436+
for telnet_proxy_server in self._wrapper_telnet_servers:
437+
telnet_proxy_server.close()
438+
await telnet_proxy_server.wait_closed()
439+
self._wrapper_telnet_servers = []
419440

420441
async def reset_wrap_console(self):
421442
"""
@@ -492,22 +513,6 @@ async def telnet_forward(telnet_reader):
492513

493514
return ws
494515

495-
@property
496-
def allocate_aux(self):
497-
"""
498-
:returns: Boolean allocate or not an aux console
499-
"""
500-
501-
return self._allocate_aux
502-
503-
@allocate_aux.setter
504-
def allocate_aux(self, allocate_aux):
505-
"""
506-
:returns: Boolean allocate or not an aux console
507-
"""
508-
509-
self._allocate_aux = allocate_aux
510-
511516
@property
512517
def aux(self):
513518
"""
@@ -526,18 +531,25 @@ def aux(self, aux):
526531
:params aux: Console port (integer) or None to free the port
527532
"""
528533

529-
if aux == self._aux:
534+
if aux == self._aux or self._aux_type == "none":
530535
return
531536

537+
if self._aux_type == "vnc" and aux is not None and aux < 5900:
538+
raise NodeError("VNC auxiliary console require a port superior or equal to 5900, current port is {}".format(aux))
539+
532540
if self._aux:
533541
self._manager.port_manager.release_tcp_port(self._aux, self._project)
534542
self._aux = None
535543
if aux is not None:
536-
self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project)
537-
log.info("{module}: '{name}' [{id}]: aux port set to {port}".format(module=self.manager.module_name,
538-
name=self.name,
539-
id=self.id,
540-
port=aux))
544+
if self._aux_type == "vnc":
545+
self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project, port_range_start=5900, port_range_end=6000)
546+
else:
547+
self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project)
548+
549+
log.info("{module}: '{name}' [{id}]: auxiliary console port set to {port}".format(module=self.manager.module_name,
550+
name=self.name,
551+
id=self.id,
552+
port=aux))
541553

542554
@property
543555
def console(self):
@@ -625,6 +637,43 @@ def console_type(self, console_type):
625637
console_type=console_type,
626638
console=self.console))
627639

640+
@property
641+
def aux_type(self):
642+
"""
643+
Returns the auxiliary console type for this node.
644+
:returns: aux type (string)
645+
"""
646+
647+
return self._aux_type
648+
649+
@aux_type.setter
650+
def aux_type(self, aux_type):
651+
"""
652+
Sets the auxiliary console type for this node.
653+
:param aux_type: console type (string)
654+
"""
655+
656+
print("SET AUX TYPE", aux_type)
657+
if aux_type != self._aux_type:
658+
# get a new port if the aux type change
659+
if self._aux:
660+
self._manager.port_manager.release_tcp_port(self._aux, self._project)
661+
if aux_type == "none":
662+
# no need to allocate a port when the auxiliary console type is none
663+
self._aux = None
664+
elif aux_type == "vnc":
665+
# VNC is a special case and the range must be 5900-6000
666+
self._aux = self._manager.port_manager.get_free_tcp_port(self._project, 5900, 6000)
667+
else:
668+
self._aux = self._manager.port_manager.get_free_tcp_port(self._project)
669+
670+
self._aux_type = aux_type
671+
log.info("{module}: '{name}' [{id}]: console type set to {aux_type} (auxiliary console port is {aux})".format(module=self.manager.module_name,
672+
name=self.name,
673+
id=self.id,
674+
aux_type=aux_type,
675+
aux=self.aux))
676+
628677
@property
629678
def ubridge(self):
630679
"""

gns3server/compute/docker/docker_vm.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,9 @@ class DockerVM(BaseNode):
6060
:param manager: Manager instance
6161
:param image: Docker image
6262
:param console: TCP console port
63-
:param console_type: Console type
63+
:param console_type: console type
6464
:param aux: TCP aux console port
65+
:param aux_type: auxiliary console type
6566
:param console_resolution: Resolution of the VNC display
6667
:param console_http_port: Port to redirect HTTP queries
6768
:param console_http_path: Url part with the path of the web interface
@@ -70,10 +71,10 @@ class DockerVM(BaseNode):
7071
"""
7172

7273
def __init__(self, name, node_id, project, manager, image, console=None, aux=None, start_command=None,
73-
adapters=None, environment=None, console_type="telnet", console_resolution="1024x768",
74+
adapters=None, environment=None, console_type="telnet", aux_type="none", console_resolution="1024x768",
7475
console_http_port=80, console_http_path="/", extra_hosts=None, extra_volumes=[]):
7576

76-
super().__init__(name, node_id, project, manager, console=console, aux=aux, allocate_aux=True, console_type=console_type)
77+
super().__init__(name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type)
7778

7879
# force the latest image if no version is specified
7980
if ":" not in image:
@@ -129,6 +130,7 @@ def __json__(self):
129130
"console_http_port": self.console_http_port,
130131
"console_http_path": self.console_http_path,
131132
"aux": self.aux,
133+
"aux_type": self.aux_type,
132134
"start_command": self.start_command,
133135
"status": self.status,
134136
"environment": self.environment,
@@ -546,7 +548,7 @@ async def start(self):
546548
elif self.console_type == "http" or self.console_type == "https":
547549
await self._start_http()
548550

549-
if self.allocate_aux:
551+
if self.aux_type != "none":
550552
await self._start_aux()
551553

552554
self._permissions_fixed = False

gns3server/compute/dynamips/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,10 @@ async def update_vm_settings(self, vm, settings):
527527
if usage is not None and usage != vm.usage:
528528
vm.usage = usage
529529

530+
aux_type = settings.get("aux_type")
531+
if aux_type is not None and aux_type != vm.aux_type:
532+
vm.aux_type = aux_type
533+
530534
# update the configs if needed
531535
await self.set_vm_configs(vm, settings)
532536

gns3server/compute/dynamips/nodes/c1700.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,17 @@ class C1700(Router):
4040
:param manager: Parent VM Manager
4141
:param dynamips_id: ID to use with Dynamips
4242
:param console: console port
43+
:param console_type: console type
4344
:param aux: auxiliary console port
45+
:param aux_type: auxiliary console type
4446
:param chassis: chassis for this router:
4547
1720, 1721, 1750, 1751 or 1760 (default = 1720).
4648
1710 is not supported.
4749
"""
4850

49-
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis="1720"):
51+
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="1720"):
5052

51-
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c1700")
53+
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c1700")
5254

5355
# Set default values for this platform (must be the same as Dynamips)
5456
self._ram = 64

gns3server/compute/dynamips/nodes/c2600.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ class C2600(Router):
4242
:param manager: Parent VM Manager
4343
:param dynamips_id: ID to use with Dynamips
4444
:param console: console port
45+
:param console_type: console type
4546
:param aux: auxiliary console port
47+
:param aux_type: auxiliary console type
4648
:param chassis: chassis for this router:
4749
2610, 2611, 2620, 2621, 2610XM, 2611XM
4850
2620XM, 2621XM, 2650XM or 2651XM (default = 2610).
@@ -61,9 +63,9 @@ class C2600(Router):
6163
"2650XM": C2600_MB_1FE,
6264
"2651XM": C2600_MB_2FE}
6365

64-
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, chassis="2610"):
66+
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="2610"):
6567

66-
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, platform="c2600")
68+
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c2600")
6769

6870
# Set default values for this platform (must be the same as Dynamips)
6971
self._ram = 64

0 commit comments

Comments
 (0)