mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-24 17:28:08 +00:00
Merge branch '2.1' into 2.2
# Conflicts: # gns3server/compute/docker/docker_vm.py # gns3server/utils/asyncio/telnet_server.py
This commit is contained in:
commit
56412b35e0
@ -89,6 +89,7 @@ class DockerVM(BaseNode):
|
|||||||
self._console_http_port = console_http_port
|
self._console_http_port = console_http_port
|
||||||
self._console_websocket = None
|
self._console_websocket = None
|
||||||
self._extra_hosts = extra_hosts
|
self._extra_hosts = extra_hosts
|
||||||
|
self._permissions_fixed = False
|
||||||
self._display = None
|
self._display = None
|
||||||
self._closing = False
|
self._closing = False
|
||||||
|
|
||||||
@ -459,6 +460,7 @@ class DockerVM(BaseNode):
|
|||||||
if self.allocate_aux:
|
if self.allocate_aux:
|
||||||
await self._start_aux()
|
await self._start_aux()
|
||||||
|
|
||||||
|
self._permissions_fixed = False
|
||||||
self.status = "started"
|
self.status = "started"
|
||||||
log.info("Docker container '{name}' [{image}] started listen for {console_type} on {console}".format(name=self._name,
|
log.info("Docker container '{name}' [{image}] started listen for {console_type} on {console}".format(name=self._name,
|
||||||
image=self._image,
|
image=self._image,
|
||||||
@ -485,7 +487,7 @@ class DockerVM(BaseNode):
|
|||||||
self._telnet_servers.append((await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.aux)))
|
self._telnet_servers.append((await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.aux)))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise DockerError("Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host, self.aux, e))
|
raise DockerError("Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host, self.aux, e))
|
||||||
log.debug("Docker container '%s' started listen for auxilary telnet on %d", self.name, self.aux)
|
log.debug("Docker container '%s' started listen for auxiliary telnet on %d", self.name, self.aux)
|
||||||
|
|
||||||
async def _fix_permissions(self):
|
async def _fix_permissions(self):
|
||||||
"""
|
"""
|
||||||
@ -494,6 +496,7 @@ class DockerVM(BaseNode):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
state = await self._get_container_state()
|
state = await self._get_container_state()
|
||||||
|
log.info("Docker container '{name}' fix ownership, state = {state}".format(name=self._name, state=state))
|
||||||
if state == "stopped" or state == "exited":
|
if state == "stopped" or state == "exited":
|
||||||
# We need to restart it to fix permissions
|
# We need to restart it to fix permissions
|
||||||
await self.manager.query("POST", "containers/{}/start".format(self._cid))
|
await self.manager.query("POST", "containers/{}/start".format(self._cid))
|
||||||
@ -521,12 +524,17 @@ class DockerVM(BaseNode):
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise DockerError("Could not fix permissions for {}: {}".format(volume, e))
|
raise DockerError("Could not fix permissions for {}: {}".format(volume, e))
|
||||||
await process.wait()
|
await process.wait()
|
||||||
|
self._permissions_fixed = True
|
||||||
|
|
||||||
async def _start_vnc_process(self, restart=False):
|
async def _start_vnc_process(self, restart=False):
|
||||||
"""
|
"""
|
||||||
Starts the VNC process.
|
Starts the VNC process.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self._display = self._get_free_display_port()
|
||||||
|
if not (shutil.which("Xtigervnc") or shutil.which("Xvfb") and shutil.which("x11vnc")):
|
||||||
|
raise DockerError("Please install tigervnc-standalone-server (recommended) or Xvfb + x11vnc before using VNC support")
|
||||||
|
|
||||||
if shutil.which("Xtigervnc"):
|
if shutil.which("Xtigervnc"):
|
||||||
with open(os.path.join(self.working_dir, "vnc.log"), "w") as fd:
|
with open(os.path.join(self.working_dir, "vnc.log"), "w") as fd:
|
||||||
self._vnc_process = await asyncio.create_subprocess_exec("Xtigervnc",
|
self._vnc_process = await asyncio.create_subprocess_exec("Xtigervnc",
|
||||||
@ -607,6 +615,19 @@ class DockerVM(BaseNode):
|
|||||||
])
|
])
|
||||||
self._telnet_servers.append((await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)))
|
self._telnet_servers.append((await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)))
|
||||||
|
|
||||||
|
async def _window_size_changed_callback(self, columns, rows):
|
||||||
|
"""
|
||||||
|
Called when the console window size has been changed.
|
||||||
|
(when naws is enabled in the Telnet server)
|
||||||
|
|
||||||
|
:param columns: number of columns
|
||||||
|
:param rows: number of rows
|
||||||
|
"""
|
||||||
|
|
||||||
|
# resize the container TTY.
|
||||||
|
await self._manager.query("POST", "containers/{}/resize?h={}&w={}".format(self._cid, rows, columns))
|
||||||
|
|
||||||
|
|
||||||
async def _start_console(self):
|
async def _start_console(self):
|
||||||
"""
|
"""
|
||||||
Starts streaming the console via telnet
|
Starts streaming the console via telnet
|
||||||
@ -627,8 +648,7 @@ class DockerVM(BaseNode):
|
|||||||
|
|
||||||
output_stream = asyncio.StreamReader()
|
output_stream = asyncio.StreamReader()
|
||||||
input_stream = InputStream()
|
input_stream = InputStream()
|
||||||
|
telnet = AsyncioTelnetServer(reader=output_stream, writer=input_stream, echo=True, naws=True, window_size_changed_callback=self._window_size_changed_callback)
|
||||||
telnet = AsyncioTelnetServer(reader=output_stream, writer=input_stream, echo=True)
|
|
||||||
try:
|
try:
|
||||||
self._telnet_servers.append((await asyncio.start_server(telnet.run, self._manager.port_manager.console_host, self.console)))
|
self._telnet_servers.append((await asyncio.start_server(telnet.run, self._manager.port_manager.console_host, self.console)))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
@ -716,14 +736,15 @@ class DockerVM(BaseNode):
|
|||||||
if state == "paused":
|
if state == "paused":
|
||||||
await self.unpause()
|
await self.unpause()
|
||||||
|
|
||||||
|
if not self._permissions_fixed:
|
||||||
await self._fix_permissions()
|
await self._fix_permissions()
|
||||||
|
|
||||||
state = await self._get_container_state()
|
state = await self._get_container_state()
|
||||||
if state != "stopped" or state != "exited":
|
if state != "stopped" or state != "exited":
|
||||||
# t=5 number of seconds to wait before killing the container
|
# t=5 number of seconds to wait before killing the container
|
||||||
try:
|
try:
|
||||||
await self.manager.query("POST", "containers/{}/stop".format(self._cid), params={"t": 5})
|
await self.manager.query("POST", "containers/{}/stop".format(self._cid), params={"t": 5})
|
||||||
log.info("Docker container '{name}' [{image}] stopped".format(
|
log.info("Docker container '{name}' [{image}] stopped".format(name=self._name, image=self._image))
|
||||||
name=self._name, image=self._image))
|
|
||||||
except DockerHttp304Error:
|
except DockerHttp304Error:
|
||||||
# Container is already stopped
|
# Container is already stopped
|
||||||
pass
|
pass
|
||||||
|
@ -59,10 +59,11 @@ READ_SIZE = 1024
|
|||||||
|
|
||||||
class TelnetConnection(object):
|
class TelnetConnection(object):
|
||||||
"""Default implementation of telnet connection which may but may not be used."""
|
"""Default implementation of telnet connection which may but may not be used."""
|
||||||
def __init__(self, reader, writer):
|
def __init__(self, reader, writer, window_size_changed_callback=None):
|
||||||
self.is_closing = False
|
self.is_closing = False
|
||||||
self._reader = reader
|
self._reader = reader
|
||||||
self._writer = writer
|
self._writer = writer
|
||||||
|
self._window_size_changed_callback = window_size_changed_callback
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def reader(self):
|
def reader(self):
|
||||||
@ -80,10 +81,12 @@ class TelnetConnection(object):
|
|||||||
"""Method called when client is disconnecting"""
|
"""Method called when client is disconnecting"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def window_size_changed(self, columns, rows):
|
async def window_size_changed(self, columns, rows):
|
||||||
"""Method called when window size changed, only can occur when
|
"""Method called when window size changed, only can occur when
|
||||||
`naws` flag is enable in server configuration."""
|
`naws` flag is enable in server configuration."""
|
||||||
pass
|
|
||||||
|
if self._window_size_changed_callback:
|
||||||
|
await self._window_size_changed_callback(columns, rows)
|
||||||
|
|
||||||
async def feed(self, data):
|
async def feed(self, data):
|
||||||
"""
|
"""
|
||||||
@ -110,7 +113,7 @@ class TelnetConnection(object):
|
|||||||
class AsyncioTelnetServer:
|
class AsyncioTelnetServer:
|
||||||
MAX_NEGOTIATION_READ = 10
|
MAX_NEGOTIATION_READ = 10
|
||||||
|
|
||||||
def __init__(self, reader=None, writer=None, binary=True, echo=False, naws=False, connection_factory=None):
|
def __init__(self, reader=None, writer=None, binary=True, echo=False, naws=False, window_size_changed_callback=None, connection_factory=None):
|
||||||
"""
|
"""
|
||||||
Initializes telnet server
|
Initializes telnet server
|
||||||
:param naws when True make a window size negotiation
|
:param naws when True make a window size negotiation
|
||||||
@ -125,6 +128,7 @@ class AsyncioTelnetServer:
|
|||||||
self._lock = asyncio.Lock()
|
self._lock = asyncio.Lock()
|
||||||
self._reader_process = None
|
self._reader_process = None
|
||||||
self._current_read = None
|
self._current_read = None
|
||||||
|
self._window_size_changed_callback = window_size_changed_callback
|
||||||
|
|
||||||
self._binary = binary
|
self._binary = binary
|
||||||
# If echo is true when the client send data
|
# If echo is true when the client send data
|
||||||
@ -133,8 +137,8 @@ class AsyncioTelnetServer:
|
|||||||
self._echo = echo
|
self._echo = echo
|
||||||
self._naws = naws
|
self._naws = naws
|
||||||
|
|
||||||
def default_connection_factory(reader, writer):
|
def default_connection_factory(reader, writer, window_size_changed_callback):
|
||||||
return TelnetConnection(reader, writer)
|
return TelnetConnection(reader, writer, window_size_changed_callback)
|
||||||
|
|
||||||
if connection_factory is None:
|
if connection_factory is None:
|
||||||
connection_factory = default_connection_factory
|
connection_factory = default_connection_factory
|
||||||
@ -181,7 +185,7 @@ class AsyncioTelnetServer:
|
|||||||
|
|
||||||
async def run(self, network_reader, network_writer):
|
async def run(self, network_reader, network_writer):
|
||||||
# Keep track of connected clients
|
# Keep track of connected clients
|
||||||
connection = self._connection_factory(network_reader, network_writer)
|
connection = self._connection_factory(network_reader, network_writer, self._window_size_changed_callback)
|
||||||
self._connections[network_writer] = connection
|
self._connections[network_writer] = connection
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -293,14 +297,14 @@ class AsyncioTelnetServer:
|
|||||||
cmd.append(buffer[location])
|
cmd.append(buffer[location])
|
||||||
return op
|
return op
|
||||||
|
|
||||||
def _negotiate(self, data, connection):
|
async def _negotiate(self, data, connection):
|
||||||
""" Performs negotiation commands"""
|
""" Performs negotiation commands"""
|
||||||
|
|
||||||
command, payload = data[0], data[1:]
|
command, payload = data[0], data[1:]
|
||||||
if command == NAWS:
|
if command == NAWS:
|
||||||
if len(payload) == 4:
|
if len(payload) == 4:
|
||||||
columns, rows = struct.unpack(str('!HH'), bytes(payload))
|
columns, rows = struct.unpack(str('!HH'), bytes(payload))
|
||||||
connection.window_size_changed(columns, rows)
|
await connection.window_size_changed(columns, rows)
|
||||||
else:
|
else:
|
||||||
log.warning('Wrong number of NAWS bytes')
|
log.warning('Wrong number of NAWS bytes')
|
||||||
else:
|
else:
|
||||||
@ -358,7 +362,7 @@ class AsyncioTelnetServer:
|
|||||||
break
|
break
|
||||||
|
|
||||||
# SE command is followed by IAC, remove the last two operations from stack
|
# SE command is followed by IAC, remove the last two operations from stack
|
||||||
self._negotiate(negotiation[0:-2], connection)
|
await self._negotiate(negotiation[0:-2], connection)
|
||||||
|
|
||||||
# This must be a 3-byte TELNET command
|
# This must be a 3-byte TELNET command
|
||||||
else:
|
else:
|
||||||
|
@ -970,7 +970,7 @@ def test_mount_binds(vm, tmpdir):
|
|||||||
|
|
||||||
def test_start_vnc(vm, loop):
|
def test_start_vnc(vm, loop):
|
||||||
vm.console_resolution = "1280x1024"
|
vm.console_resolution = "1280x1024"
|
||||||
with patch("shutil.which", return_value="/bin/x"):
|
with patch("shutil.which", return_value="/bin/Xtigervnc"):
|
||||||
with asyncio_patch("gns3server.compute.docker.docker_vm.wait_for_file_creation") as mock_wait:
|
with asyncio_patch("gns3server.compute.docker.docker_vm.wait_for_file_creation") as mock_wait:
|
||||||
with asyncio_patch("asyncio.create_subprocess_exec") as mock_exec:
|
with asyncio_patch("asyncio.create_subprocess_exec") as mock_exec:
|
||||||
loop.run_until_complete(asyncio.ensure_future(vm._start_vnc()))
|
loop.run_until_complete(asyncio.ensure_future(vm._start_vnc()))
|
||||||
|
Loading…
Reference in New Issue
Block a user