Compare commits

...
57 Commits
Author SHA1 Message Date
grossmj 878987a26e Release v2.1.18 2019-05-22 16:13:28 +07:00
grossmj c357d52ba4 Revert "Force aiohttp version to 2.3.10 and aiohttp-cors version to 0.5.3"
Ref https://github.com/GNS3/gns3-server/issues/1583
Ref https://github.com/GNS3/gns3-server/issues/1592

This reverts commit 351ed64de9.
2019-05-20 17:23:26 +07:00
grossmj 773bad811a Fix invalid reStructuredText for long description in setup.py 2019-05-17 16:14:19 +07:00
grossmj 6b54cc27da Release v2.1.17 2019-05-17 15:10:28 +07:00
grossmj 351ed64de9 Force aiohttp version to 2.3.10 and aiohttp-cors version to 0.5.3
This is to fix build issue for Ubuntu 19.04 package on Launchpad.
Ref #1583 https://github.com/GNS3/gns3-gui/issues/2774
2019-05-17 09:56:16 +07:00
grossmj f41158173b Development on 2.1.17dev1 2019-04-15 12:41:40 +07:00
grossmj cb5dc36987 Release v2.1.16 2019-04-15 12:00:18 +07:00
grossmj cc1980ac1c Fix broken embedded console for Ethernet switch. Fixes #1574 2019-04-11 17:57:47 +07:00
grossmj 8cd66cfc02 Remove old unused argument option. Fixes #1569 2019-04-09 19:26:07 +07:00
grossmj 645031ee15 Development on 2.1.16dev1 2019-03-21 13:56:11 +08:00
grossmj c70b344133 Release v2.1.15 2019-03-21 11:41:44 +08:00
grossmj e291ec1eb9 Fix IOU symlink issue on remote servers. 2019-03-18 18:11:16 +07:00
grossmj 8e8985c69f Fix vcpus configuration for GNS3 VM on VMware. Ref #2738. 2019-03-18 17:53:14 +07:00
grossmj 4eca82174d Development on 2.1.15dev1 2019-02-27 15:59:16 +07:00
grossmj 32f5dbb645 Release v2.1.14 2019-02-27 14:58:52 +07:00
grossmj 7aae682f0a Fix issue when setting cpuid.corespersocket for the GNS3 VM. Fixes https://github.com/GNS3/gns3-gui/issues/2723 2019-02-27 14:47:45 +07:00
grossmj 879e32eb9a Bump ACPI Shutdown Timeout to 120 seconds. Ref #1536 2019-02-26 23:19:33 +07:00
grossmj 01481cde1f Development on 2.1.14dev1 2019-02-26 18:10:23 +07:00
grossmj b5deb9d177 Release v2.1.13 2019-02-26 16:46:02 +07:00
Jeremy Grossmann e1aa13fa73 Force jsonschema dependency to 2.6.0
This is to fix this issue when starting the (frozen) application on Windows:

```
  File "C:\Python36-x64\lib\site-packages\jsonschema\validators.py", line 505, in <module>
  File "C:\Python36-x64\lib\site-packages\jsonschema\_utils.py", line 57, in load_schema
  File "C:\Python36-x64\lib\pkgutil.py", line 634, in get_data
OSError: [Errno 34] Result too large: 'jsonschema\\schemas\\draft6.json'
```
2019-02-26 15:28:11 +07:00
grossmj 62c51edbae Less aggressive connections to uBridge. Ref #1289 2019-02-22 16:05:31 +07:00
grossmj a13d063aa1 Fix topology images (Pictures) disappearing from projects. Fixes #1514. 2019-02-21 23:58:54 +07:00
grossmj 1ef1872f8e Reset MAC addresses when duplicating a project. Fixes #1522 2019-02-20 16:38:43 +07:00
grossmj 657698a961 Fix create a node from an appliance test. 2019-02-20 11:21:29 +07:00
grossmj 081ba31b50 Fix API call to create a node from an appliance doesn't return the new node data. Fixes #1527 2019-02-20 10:47:33 +07:00
grossmj aea4ae808f Detect invalid environment variable and send a warning when creating a Docker node. Ref #2683 2019-02-19 17:34:10 +07:00
grossmj ae3515434c Do not export/import symlinks for projects. Fixes #2699 2019-02-19 12:43:44 +07:00
grossmj 589c9754e8 Fix symlink not being created for duplicated IOU devices. Fixes https://github.com/GNS3/gns3-gui/issues/2699 2019-02-19 00:09:59 +08:00
grossmj 4ecd3b2015 Configure coresPerSocket value in VMX file for the GNS3 VM. Fixes https://github.com/GNS3/gns3-gui/issues/2688 2019-02-17 23:16:48 +08:00
grossmj 84ee3263ba Count logical CPUs to detect if the number of vCPUs is too high when configuring the GNS3 VM. Fixes #2688. 2019-02-17 23:07:33 +08:00
grossmj 174624121d Fix Qemu VM tests. Ref #1516 2019-02-17 19:53:46 +08:00
grossmj d9a9abf845 Add explicit error when trying to pull a Docker image from Docker Hub without Internet access. Fixes #1506. 2019-02-17 19:21:21 +08:00
grossmj 0b07299472 Fixes double display output in GRUB in QEMU v3.1. Fixes #1516. 2019-02-17 19:03:36 +08:00
grossmj 7fb192699b Development on 2.1.13dev1 2019-01-23 15:42:10 +08:00
grossmj bccdfc97d1 Release 2.1.12 2019-01-23 15:40:38 +08:00
Jeremy Grossmann 792c4310eb Merge pull request #1502 from GNS3/svg-symbols-fixes
Tune how to get the size of SVG images.
2019-01-22 00:41:26 +07:00
grossmj 3e21f96bf9 Fix indentation issue. Ref https://github.com/GNS3/gns3-gui/issues/2674
(cherry picked from commit c14d79a3d5)
2019-01-21 23:51:46 +07:00
grossmj 5f932fee9f Tune how to get the size of SVG images. Ref https://github.com/GNS3/gns3-gui/issues/2674.
* Default for missing height/width is "100%" as defined in the SVG specification
* Better error message, if viewBox attribute is missing
* Removal of "%" in percent more fault tolerant by using rstrip("%")

(cherry picked from commit e3757a8955)
2019-01-21 23:49:43 +07:00
grossmj ef38ccfefa Automatically create a symbolic link to the IOU image in the IOU working directory. Fixes #1484 2018-12-17 22:42:18 -06:00
grossmj 670b6d689a Merge remote-tracking branch 'origin/2.1' into 2.1 2018-12-14 19:53:51 -06:00
grossmj fc75a6b8d3 Fix link pause/filters only work for the first interface of Docker containers. Fixes #1482 2018-12-14 19:53:43 -06:00
Jeremy Grossmann 647ac87c89 Merge pull request #1476 from ddragic/x11vnc
Docker: fix x11vnc not starting
2018-11-29 07:03:58 +07:00
Dušan Dragić 817a053dfc docker_vm: fix x11vnc not starting 2018-11-29 00:37:20 +01:00
Jeremy Grossmann df4e4bec24 Merge pull request #1472 from GNS3/docker-console-resize
Telnet console resize support for Docker VM. Fixes #1471
2018-11-27 15:17:34 +07:00
grossmj 60ac6d2dfe Telnet console resize support for Docker VM. 2018-11-27 15:06:56 +07:00
grossmj c0a0a13bdd Avoid _fix_permissions() to be called twice when stopping Docker VM. Ref #1428 2018-11-26 15:53:24 +07:00
grossmj a3044ede77 Fix _fix_permissions() garbles permissions in Docker VM. Ref #1428 2018-11-25 17:11:42 +07:00
grossmj df3baffd9b Fix "None is not of type 'integer'" when opening project containing a Qemu VM. Fixes #2610. 2018-11-24 19:56:29 +07:00
grossmj e7b8309a80 Fix Docker VNC tests. Ref #1438 2018-11-11 23:25:23 +08:00
grossmj 029344da14 Only require Xtigervnc or Xvfb+x11vnc for Docker with vnc console. Ref #1438 2018-11-11 22:33:58 +08:00
grossmj d0bab9947e Support tigervnc in Docker VM. Ref #1438 2018-11-11 21:07:33 +08:00
grossmj e4a6db8ebc Fix some typos. 2018-10-04 15:22:42 +02:00
grossmj 181a31be32 Update minimum VIX version requirements for VMware. Ref #1415. 2018-10-02 11:22:32 +02:00
grossmj 8d368cd0f6 Development on 2.1.12dev1 2018-09-28 20:50:03 +02:00
grossmj 41d4ecc4b7 Release v2.1.11 2018-09-28 20:47:44 +02:00
grossmj 4d95e0b51f Catch some exceptions. 2018-09-28 15:04:38 +02:00
grossmj 4efdefaf5a Development on 2.1.11dev1 2018-09-15 11:23:18 +02:00
42 changed files with 377 additions and 156 deletions

No files matched your search

+56 -1
View File
@@ -1,5 +1,60 @@
# Change Log
## 2.1.18 22/05/2019
* Revert "Force aiohttp version to 2.3.10 and aiohttp-cors version to 0.5.3" Ref https://github.com/GNS3/gns3-server/issues/1583 Ref https://github.com/GNS3/gns3-server/issues/1592
* Fix invalid reStructuredText for long description in setup.py
## 2.1.17 17/05/2019
* Force aiohttp version to 2.3.10 and aiohttp-cors version to 0.5.3 This is to fix build issue for Ubuntu 19.04 package on Launchpad. Ref #1583 https://github.com/GNS3/gns3-gui/issues/2774
## 2.1.16 15/04/2019
* Fix broken embedded console for Ethernet switch. Fixes #1574
* Remove old unused argument option. Fixes #1569
## 2.1.15 21/03/2019
* Fix IOU symlink issue on remote servers.
* Fix vcpus configuration for GNS3 VM on VMware. Ref #2738.
## 2.1.14 27/02/2019
* Fix issue when setting cpuid.corespersocket for the GNS3 VM. Fixes https://github.com/GNS3/gns3-gui/issues/2723
* Bump ACPI Shutdown Timeout to 120 seconds. Ref #1536
## 2.1.13 26/02/2019
* Force jsonschema dependency to 2.6.0
* Less aggressive connections to uBridge. Ref #1289
* Fix topology images (Pictures) disappearing from projects. Fixes #1514.
* Reset MAC addresses when duplicating a project. Fixes #1522
* Fix API call to create a node from an appliance doesn't return the new node data. Fixes #1527
* Detect invalid environment variable and send a warning when creating a Docker node. Ref #2683
* Do not export/import symlinks for projects. Fixes #2699
* Fix symlink not being created for duplicated IOU devices. Fixes https://github.com/GNS3/gns3-gui/issues/2699
* Configure coresPerSocket value in VMX file for the GNS3 VM. Fixes https://github.com/GNS3/gns3-gui/issues/2688
* Count logical CPUs to detect if the number of vCPUs is too high when configuring the GNS3 VM. Fixes #2688.
* Add explicit error when trying to pull a Docker image from Docker Hub without Internet access. Fixes #1506.
* Fixes double display output in GRUB in QEMU v3.1. Fixes #1516.
## 2.1.12 23/01/2019
* Tune how to get the size of SVG images. Ref https://github.com/GNS3/gns3-gui/issues/2674.
* Automatically create a symbolic link to the IOU image in the IOU working directory. Fixes #1484
* Fix link pause/filters only work for the first interface of Docker containers. Fixes #1482
* Telnet console resize support for Docker VM.
* Fix _fix_permissions() garbles permissions in Docker VM. Ref #1428
* Fix "None is not of type 'integer'" when opening project containing a Qemu VM. Fixes #2610.
* Only require Xtigervnc or Xvfb+x11vnc for Docker with vnc console. Ref #1438
* Support tigervnc in Docker VM. Ref #1438
* Update minimum VIX version requirements for VMware. Ref #1415.
## 2.1.11 28/09/2018
* Catch some exceptions.
## 2.1.10 15/09/2018
* Include locale information and GNS3 VM version in crash reports.
@@ -677,7 +732,7 @@
* Fix naming of IOU serial interfaces
* Improve timeout management
* When exporting debug information export GNS3 VM vmx content
* /debug for exporting debug informations
* /debug for exporting debug information
* Raise error if using a non linked clone VM twice
* Fix a possible deadlock at exit
* Fix import of some old dynamips topologies
+5 -4
View File
@@ -23,7 +23,7 @@ master is the next stable release, you can test it in your day to day activities
Bug fixes or small improvements pull requests go here.
2.x (2.1 for example)
********
*********************
Next major release
*Never* use this branch for production. Pull requests for major new features go here.
@@ -100,7 +100,7 @@ All the init script require the creation of a GNS3 user. You can change it to an
sudo adduser gns3
upstart
~~~~~~~
-------
For ubuntu < 15.04
@@ -113,7 +113,8 @@ You need to copy init/gns3.conf.upstart to /etc/init/gns3.conf
systemd
~~~~~~~~
-------
You need to copy init/gns3.service.systemd to /lib/systemd/system/gns3.service
.. code:: bash
@@ -219,5 +220,5 @@ If you want test coverage:
Security issues
----------------
Please contact us using contact informations available here:
Please contact us using contact form available here:
http://docs.gns3.com/1ON9JBXSeR7Nt2-Qum2o3ZX0GU86BZwlmNSUgvmqNWGY/index.html
+1 -1
View File
@@ -5,7 +5,7 @@
GET /v2/compute/debug
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Return debug informations about the compute
Return debug information about the compute
Response status codes
**********************
+1 -1
View File
@@ -276,7 +276,7 @@ class BaseManager:
destination_dir = destination_node.working_dir
try:
shutil.rmtree(destination_dir)
shutil.copytree(source_node.working_dir, destination_dir)
shutil.copytree(source_node.working_dir, destination_dir, symlinks=True, ignore_dangling_symlinks=True)
except OSError as e:
raise aiohttp.web.HTTPConflict(text="Can't duplicate node data: {}".format(e))
+4 -1
View File
@@ -200,7 +200,10 @@ class Docker(BaseManager):
if progress_callback:
progress_callback("Pulling '{}' from docker hub".format(image))
response = yield from self.http_query("POST", "images/create", params={"fromImage": image}, timeout=None)
try:
response = yield from self.http_query("POST", "images/create", params={"fromImage": image}, timeout=None)
except DockerError as e:
raise DockerError("Could not pull the '{}' image from Docker Hub, please check your Internet connection (original error: {})".format(image, e))
# The pull api will stream status via an HTTP JSON stream
content = ""
while True:
+75 -36
View File
@@ -24,6 +24,7 @@ import shutil
import psutil
import shlex
import aiohttp
import subprocess
import os
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
@@ -82,12 +83,14 @@ class DockerVM(BaseNode):
self._ethernet_adapters = []
self._temporary_directory = None
self._telnet_servers = []
self._x11vnc_process = None
self._vnc_process = None
self._xvfb_process = None
self._console_resolution = console_resolution
self._console_http_path = console_http_path
self._console_http_port = console_http_port
self._console_websocket = None
self._extra_hosts = extra_hosts
self._permissions_fixed = False
self._volumes = []
# Keep a list of created bridge
@@ -344,6 +347,9 @@ class DockerVM(BaseNode):
if self._environment:
for e in self._environment.strip().split("\n"):
e = e.strip()
if e.split("=")[0] == "":
self.project.emit("log.warning", {"message": "{} has invalid environment variable: {}".format(self.name, e)})
continue
if not e.startswith("GNS3_"):
formatted = self._format_env(variables, e)
params["Env"].append(formatted)
@@ -439,12 +445,14 @@ class DockerVM(BaseNode):
if self.console_type == "telnet":
yield from self._start_console()
elif self.console_type == "http" or self.console_type == "https":
yield from self._start_http()
if self.allocate_aux:
yield from self._start_aux()
self._permissions_fixed = False
self.status = "started"
log.info("Docker container '{name}' [{image}] started listen for {console_type} on {console}".format(name=self._name,
image=self._image,
@@ -460,11 +468,10 @@ class DockerVM(BaseNode):
# We can not use the API because docker doesn't expose a websocket api for exec
# https://github.com/GNS3/gns3-gui/issues/1039
try:
process = yield from asyncio.subprocess.create_subprocess_exec(
"docker", "exec", "-i", self._cid, "/gns3/bin/busybox", "script", "-qfc", "while true; do TERM=vt100 /gns3/bin/busybox sh; done", "/dev/null",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
stdin=asyncio.subprocess.PIPE)
process = yield from asyncio.subprocess.create_subprocess_exec("docker", "exec", "-i", self._cid, "/gns3/bin/busybox", "script", "-qfc", "while true; do TERM=vt100 /gns3/bin/busybox sh; done", "/dev/null",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
stdin=asyncio.subprocess.PIPE)
except OSError as e:
raise DockerError("Could not start auxiliary console process: {}".format(e))
server = AsyncioTelnetServer(reader=process.stdout, writer=process.stdin, binary=True, echo=True)
@@ -472,7 +479,7 @@ class DockerVM(BaseNode):
self._telnet_servers.append((yield from asyncio.start_server(server.run, self._manager.port_manager.console_host, self.aux)))
except OSError as 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)
@asyncio.coroutine
def _fix_permissions(self):
@@ -482,6 +489,7 @@ class DockerVM(BaseNode):
"""
state = yield from 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":
# We need to restart it to fix permissions
yield from self.manager.query("POST", "containers/{}/start".format(self._cid))
@@ -509,6 +517,7 @@ class DockerVM(BaseNode):
except OSError as e:
raise DockerError("Could not fix permissions for {}: {}".format(volume, e))
yield from process.wait()
self._permissions_fixed = True
@asyncio.coroutine
def _start_vnc(self):
@@ -517,19 +526,46 @@ class DockerVM(BaseNode):
"""
self._display = self._get_free_display_port()
if shutil.which("Xvfb") is None or shutil.which("x11vnc") is None:
raise DockerError("Please install Xvfb and x11vnc before using VNC support")
self._xvfb_process = yield from asyncio.create_subprocess_exec("Xvfb", "-nolisten", "tcp", ":{}".format(self._display), "-screen", "0", self._console_resolution + "x16")
# We pass a port for TCPV6 due to a crash in X11VNC if not here: https://github.com/GNS3/gns3-server/issues/569
self._x11vnc_process = yield from asyncio.create_subprocess_exec("x11vnc", "-forever", "-nopw", "-shared", "-geometry", self._console_resolution, "-display", "WAIT:{}".format(self._display), "-rfbport", str(self.console), "-rfbportv6", str(self.console), "-noncache", "-listen", self._manager.port_manager.console_host)
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"):
with open(os.path.join(self.working_dir, "vnc.log"), "w") as fd:
self._vnc_process = yield from asyncio.create_subprocess_exec("Xtigervnc",
"-geometry", self._console_resolution,
"-depth", "16",
"-interface", self._manager.port_manager.console_host,
"-rfbport", str(self.console),
"-AlwaysShared",
"-SecurityTypes", "None",
":{}".format(self._display),
stdout=fd, stderr=subprocess.STDOUT)
else:
self._xvfb_process = yield from asyncio.create_subprocess_exec("Xvfb",
"-nolisten",
"tcp", ":{}".format(self._display),
"-screen", "0",
self._console_resolution + "x16")
# We pass a port for TCPV6 due to a crash in X11VNC if not here: https://github.com/GNS3/gns3-server/issues/569
with open(os.path.join(self.working_dir, "vnc.log"), "w") as fd:
self._vnc_process = yield from asyncio.create_subprocess_exec("x11vnc",
"-forever",
"-nopw",
"-shared",
"-geometry", self._console_resolution,
"-display", "WAIT:{}".format(self._display),
"-rfbport", str(self.console),
"-rfbportv6", str(self.console),
"-noncache",
"-listen", self._manager.port_manager.console_host,
stdout=fd, stderr=subprocess.STDOUT)
x11_socket = os.path.join("/tmp/.X11-unix/", "X{}".format(self._display))
yield from wait_for_file_creation(x11_socket)
#monitor_process(self._vnc_process, self._vnc_callback)
#monitor_process(self._xvfb_process, self._xvfb_callback)
#monitor_process(self._x11vnc_process, self._x11vnc_callback)
def _xvfb_callback(self, returncode):
def _vnc_callback(self, returncode):
"""
Called when the process has stopped.
@@ -537,17 +573,7 @@ class DockerVM(BaseNode):
"""
if returncode != 0:
self.project.emit("log.error", {"message": "The Xvfb process has stopped, return code: {}.".format(returncode)})
def _x11vnc_callback(self, returncode):
"""
Called when the process has stopped.
:param returncode: Process returncode
"""
if returncode != 0:
self.project.emit("log.error", {"message": "The x11vnc process has stopped, return code: {}.".format(returncode)})
self.project.emit("log.error", {"message": "The vnc process has stopped, return code: {}.".format(returncode)})
@asyncio.coroutine
def _start_http(self):
@@ -570,6 +596,19 @@ class DockerVM(BaseNode):
])
self._telnet_servers.append((yield from asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)))
@asyncio.coroutine
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.
yield from self._manager.query("POST", "containers/{}/resize?h={}&w={}".format(self._cid, rows, columns))
@asyncio.coroutine
def _start_console(self):
"""
@@ -592,8 +631,7 @@ class DockerVM(BaseNode):
output_stream = asyncio.StreamReader()
input_stream = InputStream()
telnet = AsyncioTelnetServer(reader=output_stream, writer=input_stream, echo=True)
telnet = AsyncioTelnetServer(reader=output_stream, writer=input_stream, echo=True, naws=True, window_size_changed_callback=self._window_size_changed_callback)
try:
self._telnet_servers.append((yield from asyncio.start_server(telnet.run, self._manager.port_manager.console_host, self.console)))
except OSError as e:
@@ -603,7 +641,6 @@ class DockerVM(BaseNode):
input_stream.ws = self._console_websocket
output_stream.feed_data(self.name.encode() + b" console is now available... Press RETURN to get started.\r\n")
asyncio_ensure_future(self._read_console_output(self._console_websocket, output_stream))
@asyncio.coroutine
@@ -678,14 +715,15 @@ class DockerVM(BaseNode):
if state == "paused":
yield from self.unpause()
yield from self._fix_permissions()
if not self._permissions_fixed:
yield from self._fix_permissions()
state = yield from self._get_container_state()
if state != "stopped" or state != "exited":
# t=5 number of seconds to wait before killing the container
try:
yield from self.manager.query("POST", "containers/{}/stop".format(self._cid), params={"t": 5})
log.info("Docker container '{name}' [{image}] stopped".format(
name=self._name, image=self._image))
log.info("Docker container '{name}' [{image}] stopped".format(name=self._name, image=self._image))
except DockerHttp304Error:
# Container is already stopped
pass
@@ -725,12 +763,13 @@ class DockerVM(BaseNode):
if state == "paused" or state == "running":
yield from self.stop()
if self.console_type == "vnc":
if self._x11vnc_process:
if self._vnc_process:
try:
self._x11vnc_process.terminate()
yield from self._x11vnc_process.wait()
self._vnc_process.terminate()
yield from self._vnc_process.wait()
except ProcessLookupError:
pass
if self._xvfb_process:
try:
self._xvfb_process.terminate()
yield from self._xvfb_process.wait()
+18
View File
@@ -517,6 +517,17 @@ class IOUVM(BaseNode):
if "IOURC" not in os.environ:
env["IOURC"] = iourc_path
# create a symbolic link to the image to avoid IOU error "failed code signing checks"
# on newer images, see https://github.com/GNS3/gns3-server/issues/1484
try:
symlink = os.path.join(self.working_dir, os.path.basename(self.path))
if os.path.islink(symlink):
os.unlink(symlink)
os.symlink(self.path, symlink)
except OSError as e:
raise IOUError("Could not create symbolic link: {}".format(e))
command = yield from self._build_command()
try:
log.info("Starting IOU: {}".format(command))
@@ -651,6 +662,13 @@ class IOUVM(BaseNode):
pass
self._iou_process = None
try:
symlink = os.path.join(self.working_dir, os.path.basename(self.path))
if os.path.islink(symlink):
os.unlink(symlink)
except OSError as e:
log.warning("Could not delete symbolic link: {}".format(e))
self._started = False
self.save_configs()
+1 -1
View File
@@ -23,7 +23,7 @@ import logging
log = logging.getLogger(__name__)
# This ports are disallowed by Chrome and Firefox to avoid issues, we skip them as well
# These ports are disallowed by Chrome and Firefox to avoid issues, we skip them as well
BANNED_PORTS = set((1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 77, 79, 87, 95, 101, 102, 103,
104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 139, 143, 179, 389, 465, 512, 513, 514, 515, 526,
530, 531, 532, 540, 556, 563, 587, 601, 636, 993, 995, 2049, 3659, 4045, 6000, 6665, 6666, 6667,
+1 -1
View File
@@ -415,7 +415,7 @@ class Project:
"""
files = []
for dirpath, dirnames, filenames in os.walk(self.path):
for dirpath, dirnames, filenames in os.walk(self.path, followlinks=False):
for filename in filenames:
if not filename.endswith(".ghost"):
path = os.path.relpath(dirpath, self.path)
+1 -1
View File
@@ -186,7 +186,7 @@ class Qemu(BaseManager):
return ""
else:
try:
output = yield from subprocess_check_output(qemu_path, "-version")
output = yield from subprocess_check_output(qemu_path, "-version", "-nographic")
match = re.search("version\s+([0-9a-z\-\.]+)", output)
if match:
version = match.group(1)
+11 -9
View File
@@ -974,7 +974,7 @@ class QemuVM(BaseNode):
try:
if self.acpi_shutdown:
yield from self._control_vm("system_powerdown")
yield from gns3server.utils.asyncio.wait_for_process_termination(self._process, timeout=30)
yield from gns3server.utils.asyncio.wait_for_process_termination(self._process, timeout=120)
else:
self._process.terminate()
yield from gns3server.utils.asyncio.wait_for_process_termination(self._process, timeout=3)
@@ -1601,18 +1601,19 @@ class QemuVM(BaseNode):
return network_options
def _graphic(self):
@asyncio.coroutine
def _disable_graphics(self):
"""
Adds the correct graphic options depending of the OS
Disable graphics depending of the QEMU version
"""
if sys.platform.startswith("win"):
if any(opt in self._options for opt in ["-display", "-nographic", "-curses", "-sdl" "-spice", "-vnc"]):
return []
if len(os.environ.get("DISPLAY", "")) > 0:
return []
if "-nographic" not in self._options:
version = yield from self.manager.get_qemu_version(self.qemu_path)
if version and parse_version(version) >= parse_version("3.0"):
return ["-display", "none"]
else:
return ["-nographic"]
return []
def _run_with_kvm(self, qemu_path, options):
"""
@@ -1678,7 +1679,8 @@ class QemuVM(BaseNode):
raise QemuError("Console type {} is unknown".format(self._console_type))
command.extend(self._monitor_options())
command.extend((yield from self._network_options()))
command.extend(self._graphic())
if self._console_type == "telnet":
command.extend((yield from self._disable_graphics()))
if additional_options:
try:
command.extend(shlex.split(additional_options))
+6
View File
@@ -149,6 +149,7 @@ class VMware(BaseManager):
VIX 1.13 was the release for Player 6.
VIX 1.14 was the release for Player 7.
VIX 1.15 was the release for Workstation Player 12.
VIX 1.17 was the release for Workstation Player 14.
:param player_version: VMware Player major version.
"""
@@ -162,6 +163,8 @@ class VMware(BaseManager):
yield from self.check_vmrun_version(minimum_required_version="1.14.0")
elif player_version >= 12:
yield from self.check_vmrun_version(minimum_required_version="1.15.0")
elif player_version >= 14:
yield from self.check_vmrun_version(minimum_required_version="1.17.0")
self._host_type = "player"
@asyncio.coroutine
@@ -172,6 +175,7 @@ class VMware(BaseManager):
VIX 1.13 was the release for Workstation 10.
VIX 1.14 was the release for Workstation 11.
VIX 1.15 was the release for Workstation Pro 12.
VIX 1.17 was the release for Workstation Pro 14.
:param ws_version: VMware Workstation major version.
"""
@@ -185,6 +189,8 @@ class VMware(BaseManager):
yield from self.check_vmrun_version(minimum_required_version="1.14.0")
elif ws_version >= 12:
yield from self.check_vmrun_version(minimum_required_version="1.15.0")
elif ws_version >= 14:
yield from self.check_vmrun_version(minimum_required_version="1.17.0")
self._host_type = "ws"
@asyncio.coroutine
+1 -1
View File
@@ -315,7 +315,7 @@ class VPCSVM(BaseNode):
if sys.platform.startswith("win32"):
try:
self._process.send_signal(signal.CTRL_BREAK_EVENT)
except OSError:
except (SystemError, OSError):
pass
else:
try:
+7 -3
View File
@@ -446,9 +446,13 @@ class Compute:
self._capabilities = response.json
if response.json["version"].split("-")[0] != __version__.split("-")[0]:
msg = "GNS3 controller version {} is not the same as compute server {} version {}".format(__version__,
self._name,
response.json["version"])
if self._name.startswith("GNS3 VM"):
msg = "GNS3 version {} is not the same as the GNS3 VM version {}. Please upgrade the GNS3 VM.".format(__version__,
response.json["version"])
else:
msg = "GNS3 controller version {} is not the same as compute server {} version {}".format(__version__,
self._name,
response.json["version"])
if __version_info__[3] == 0:
# Stable release
log.error(msg)
+1 -1
View File
@@ -55,7 +55,7 @@ class Drawing:
return self._id
@property
def ressource_filename(self):
def resource_filename(self):
"""
If the svg content has been dump to an external file return is name otherwise None
"""
+14 -4
View File
@@ -31,7 +31,7 @@ log = logging.getLogger(__name__)
@asyncio.coroutine
def export_project(project, temporary_dir, include_images=False, keep_compute_id=False, allow_all_nodes=False):
def export_project(project, temporary_dir, include_images=False, keep_compute_id=False, allow_all_nodes=False, reset_mac_addresses=False):
"""
Export a project to a zip file.
@@ -42,6 +42,7 @@ def export_project(project, temporary_dir, include_images=False, keep_compute_id
:param include images: save OS images to the zip file
:param keep_compute_id: If false replace all compute id by local (standard behavior for .gns3project to make it portable)
:param allow_all_nodes: Allow all nodes type to be include in the zip even if not portable
:param reset_mac_addresses: Reset MAC addresses for every nodes.
:returns: ZipStream object
"""
@@ -61,10 +62,10 @@ def export_project(project, temporary_dir, include_images=False, keep_compute_id
# First we process the .gns3 in order to be sure we don't have an error
for file in os.listdir(project._path):
if file.endswith(".gns3"):
yield from _patch_project_file(project, os.path.join(project._path, file), zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir)
yield from _patch_project_file(project, os.path.join(project._path, file), zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir, reset_mac_addresses)
# Export the local files
for root, dirs, files in os.walk(project._path, topdown=True):
for root, dirs, files in os.walk(project._path, topdown=True, followlinks=False):
files = [f for f in files if _is_exportable(os.path.join(root, f))]
for file in files:
path = os.path.join(root, file)
@@ -125,6 +126,7 @@ def _patch_mtime(path):
new_mtime = file_date.replace(year=1980).timestamp()
os.utime(path, (st.st_atime, new_mtime))
def _is_exportable(path):
"""
:returns: True if file should not be included in the final archive
@@ -134,6 +136,10 @@ def _is_exportable(path):
if path.endswith("snapshots"):
return False
# do not export symlinks
if os.path.islink(path):
return False
# do not export directories of snapshots
if "{sep}snapshots{sep}".format(sep=os.path.sep) in path:
return False
@@ -155,7 +161,7 @@ def _is_exportable(path):
@asyncio.coroutine
def _patch_project_file(project, path, zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir):
def _patch_project_file(project, path, zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir, reset_mac_addresses):
"""
Patch a project file (.gns3) to export a project.
The .gns3 file is renamed to project.gns3
@@ -188,6 +194,10 @@ def _patch_project_file(project, path, zstream, include_images, keep_compute_id,
if "properties" in node and node["node_type"] != "docker":
for prop, value in node["properties"].items():
# reset the MAC address
if reset_mac_addresses and prop in ("mac_addr", "mac_address"):
node["properties"][prop] = None
if node["node_type"] == "iou":
if not prop == "path":
continue
@@ -72,15 +72,19 @@ class VMwareGNS3VM(BaseGNS3VM):
if ram % 4 != 0:
raise GNS3VMError("Allocated memory {} for the GNS3 VM must be a multiple of 4".format(ram))
available_vcpus = psutil.cpu_count(logical=False)
available_vcpus = psutil.cpu_count(logical=True)
if vcpus > available_vcpus:
raise GNS3VMError("You have allocated too many vCPUs for the GNS3 VM! (max available is {} vCPUs)".format(available_vcpus))
try:
pairs = VMware.parse_vmware_file(self._vmx_path)
pairs["numvcpus"] = str(vcpus)
pairs["memsize"] = str(ram)
VMware.write_vmx_file(self._vmx_path, pairs)
if vcpus > 1:
pairs["numvcpus"] = str(vcpus)
cores_per_sockets = int(vcpus / psutil.cpu_count(logical=False))
if cores_per_sockets > 1:
pairs["cpuid.corespersocket"] = str(cores_per_sockets)
pairs["memsize"] = str(ram)
VMware.write_vmx_file(self._vmx_path, pairs)
log.info("GNS3 VM vCPU count set to {} and RAM amount set to {}".format(vcpus, ram))
except OSError as e:
raise GNS3VMError('Could not read/write VMware VMX file "{}": {}'.format(self._vmx_path, e))
+6 -2
View File
@@ -184,9 +184,11 @@ def _move_files_to_compute(compute, project_id, directory, files_path):
location = os.path.join(directory, files_path)
if os.path.exists(location):
for (dirpath, dirnames, filenames) in os.walk(location):
for (dirpath, dirnames, filenames) in os.walk(location, followlinks=False):
for filename in filenames:
path = os.path.join(dirpath, filename)
if os.path.islink(path):
continue
dst = os.path.relpath(path, directory)
yield from _upload_file(compute, project_id, path, dst)
yield from wait_run_in_executor(shutil.rmtree, os.path.join(directory, files_path))
@@ -213,9 +215,11 @@ def _import_images(controller, path):
image_dir = controller.images_path()
root = os.path.join(path, "images")
for (dirpath, dirnames, filenames) in os.walk(root):
for (dirpath, dirnames, filenames) in os.walk(root, followlinks=False):
for filename in filenames:
path = os.path.join(dirpath, filename)
if os.path.islink(path):
continue
dst = os.path.join(image_dir, os.path.relpath(path, root))
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.move(path, dst)
+3 -3
View File
@@ -91,12 +91,12 @@ class Node:
# Update node properties with additional elements
for prop in kwargs:
if prop not in ignore_properties:
if prop and prop not in ignore_properties:
if hasattr(self, prop):
try:
setattr(self, prop, kwargs[prop])
except AttributeError as e:
log.critical("Can't set attribute %s", prop)
log.critical("Cannot set attribute '%s'".format(prop))
raise e
else:
if prop not in self.CONTROLLER_ONLY_PROPERTIES and kwargs[prop] is not None and kwargs[prop] != "":
@@ -254,7 +254,7 @@ class Node:
try:
self._width, self._height, filetype = self._project.controller.symbols.get_size(val)
except (ValueError, OSError) as e:
log.error("Could not write symbol: {}".format(e))
log.error("Could not set symbol: {}".format(e))
# If symbol is invalid we replace it by the default
self.symbol = ":/symbols/computer.svg"
if self._label is None:
+15 -13
View File
@@ -146,7 +146,6 @@ class Project:
}
)
def reset(self):
"""
Called when open/close a project. Cleanup internal stuff
@@ -704,24 +703,26 @@ class Project:
# We don't care if a compute is down at this step
except (ComputeError, aiohttp.web.HTTPError, aiohttp.ClientResponseError, TimeoutError):
pass
self._cleanPictures()
self._clean_pictures()
self._status = "closed"
if not ignore_notification:
self.controller.notification.emit("project.closed", self.__json__())
def _cleanPictures(self):
def _clean_pictures(self):
"""
Delete unused images
Delete unused pictures.
"""
# Project have been deleted
if not os.path.exists(self.path):
# Project have been deleted or is loading or is not opened
if not os.path.exists(self.path) or self._loading or self._status != "opened":
return
try:
pictures = set(os.listdir(self.pictures_directory))
for drawing in self._drawings.values():
try:
pictures.remove(drawing.ressource_filename)
resource_filename = drawing.resource_filename
if resource_filename:
pictures.remove(resource_filename)
except KeyError:
pass
@@ -733,11 +734,12 @@ class Project:
except KeyError:
pass
for pict in pictures:
os.remove(os.path.join(self.pictures_directory, pict))
for pic_filename in pictures:
path = os.path.join(self.pictures_directory, pic_filename)
log.info("Deleting unused picture '{}'".format(path))
os.remove(path)
except OSError as e:
log.warning(str(e))
log.warning("Could not delete unused pictures: {}".format(e))
@asyncio.coroutine
def delete(self):
@@ -929,13 +931,13 @@ class Project:
self.dump()
try:
with tempfile.TemporaryDirectory() as tmpdir:
zipstream = yield from export_project(self, tmpdir, keep_compute_id=True, allow_all_nodes=True)
zipstream = yield from export_project(self, tmpdir, keep_compute_id=True, allow_all_nodes=True, reset_mac_addresses=True)
project_path = os.path.join(tmpdir, "project.gns3p")
yield from wait_run_in_executor(self._create_duplicate_project_file, project_path, zipstream)
with open(project_path, "rb") as f:
project = yield from import_project(self._controller, str(uuid.uuid4()), f, location=location, name=name, keep_compute_id=True)
except (ValueError, OSError, UnicodeEncodeError) as e:
raise aiohttp.web.HTTPConflict(text="Can not duplicate project: {}".format(str(e)))
raise aiohttp.web.HTTPConflict(text="Cannot duplicate project: {}".format(str(e)))
if previous_status == "closed":
yield from self.close()
+1 -1
View File
@@ -57,7 +57,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "https://71472e2845b2422cab117bdfa8af55ec:da50c8f08b9f44d8b905356741c278df@sentry.io/38482"
DSN = "https://2b1412df322748bc8b36639671f1a40e:25804fdd614d498aa429c689f540779e@sentry.io/38482"
if hasattr(sys, "frozen"):
cacert = get_resource("cacert.pem")
if cacert is not None and os.path.isfile(cacert):
@@ -263,10 +263,11 @@ class DockerHandler:
docker_manager = Docker.instance()
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
nio = container.ethernet_adapters[int(request.match_info["adapter_number"])].get_nio(0)
adapter_number = int(request.match_info["adapter_number"])
nio = container.ethernet_adapters[adapter_number].get_nio(0)
if "filters" in request.json and nio:
nio.filters = request.json["filters"]
yield from container.adapter_update_nio_binding(int(request.match_info["port_number"]), nio)
yield from container.adapter_update_nio_binding(adapter_number, nio)
response.set_status(201)
response.json(request.json)
@@ -301,12 +301,9 @@ class IOUHandler:
nio = vm.adapters[adapter_number].get_nio(port_number)
if "filters" in request.json and nio:
nio.filters = request.json["filters"]
yield from vm.adapter_update_nio_binding(
adapter_number,
port_number,
nio)
yield from vm.adapter_update_nio_binding(adapter_number, port_number, nio)
response.set_status(201)
response.json(nio)
response.json(request.json)
@Route.delete(
r"/projects/{project_id}/iou/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
@@ -42,9 +42,9 @@ class ServerHandler:
@Route.get(
r"/debug",
description="Return debug informations about the compute",
description="Return debug information about the compute",
status_codes={
201: "Writed"
201: "Written"
})
def debug(request, response):
response.content_type = "text/plain"
@@ -279,12 +279,13 @@ class VMwareHandler:
vmware_manager = VMware.instance()
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
nio = vm.ethernet_adapters[int(request.match_info["adapter_number"])]
adapter_number = int(request.match_info["adapter_number"])
nio = vm.ethernet_adapters[adapter_number]
if "filters" in request.json and nio:
nio.filters = request.json["filters"]
yield from vm.adapter_update_nio_binding(int(request.match_info["adapter_number"]), nio)
response.set_status(201)
response.json(request.json)
yield from vm.adapter_update_nio_binding(adapter_number, nio)
response.set_status(201)
response.json(request.json)
@Route.delete(
r"/projects/{project_id}/vmware/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
@@ -67,8 +67,9 @@ class ApplianceHandler:
controller = Controller.instance()
project = controller.get_project(request.match_info["project_id"])
yield from project.add_node_from_appliance(request.match_info["appliance_id"],
x=request.json["x"],
y=request.json["y"],
compute_id=request.json.get("compute_id"))
node = yield from project.add_node_from_appliance(request.match_info["appliance_id"],
x=request.json["x"],
y=request.json["y"],
compute_id=request.json.get("compute_id"))
response.set_status(201)
response.json(node)
@@ -59,7 +59,7 @@ class ComputeHandler:
@Route.put(
r"/computes/{compute_id}",
description="Get a compute server information",
description="Update a compute server",
status_codes={
200: "Compute server updated",
400: "Invalid request",
@@ -287,9 +287,9 @@ class ProjectHandler:
try:
with tempfile.TemporaryDirectory() as tmp_dir:
datas = yield from export_project(
project, tmp_dir,
include_images=bool(int(request.query.get("include_images", "0"))))
stream = yield from export_project(project,
tmp_dir,
include_images=bool(int(request.query.get("include_images", "0"))))
# We need to do that now because export could failed and raise an HTTP error
# that why response start need to be the later possible
response.content_type = 'application/gns3project'
@@ -297,7 +297,7 @@ class ProjectHandler:
response.enable_chunked_encoding()
yield from response.prepare(request)
for data in datas:
for data in stream:
response.write(data)
yield from response.drain()
@@ -139,7 +139,7 @@ class ServerHandler:
r"/debug",
description="Dump debug information to disk (debug directory in config directory). Work only for local server",
status_codes={
201: "Writed"
201: "Written"
})
def debug(request, response):
@@ -156,7 +156,7 @@ class ServerHandler:
f.write(ServerHandler._getDebugData())
except Exception as e:
# If something is wrong we log the info to the log and we hope the log will be include correctly to the debug export
log.error("Could not export debug informations {}".format(e), exc_info=1)
log.error("Could not export debug information {}".format(e), exc_info=1)
try:
if Controller.instance().gns3vm.engine == "vmware":
@@ -60,7 +60,7 @@ class SymbolHandler:
r"/symbols/{symbol_id:.+}/raw",
description="Write the symbol file",
status_codes={
200: "Symbol returned"
200: "Symbol written"
},
raw=True)
def upload(request, response):
@@ -78,6 +78,7 @@ class SymbolHandler:
f.write(chunk)
except (UnicodeEncodeError, OSError) as e:
raise aiohttp.web.HTTPConflict(text="Could not write symbol file '{}': {}".format(path, e))
# Reset the symbol list
controller.symbols.list()
response.set_status(204)
-1
View File
@@ -91,7 +91,6 @@ def parse_arguments(argv):
parser.add_argument("--host", help="run on the given host/IP address")
parser.add_argument("--port", help="run on the given port", type=int)
parser.add_argument("--ssl", action="store_true", help="run in SSL mode")
parser.add_argument("--no-ubridge", action="store_false", help="do not use ubridge to handle node connections")
parser.add_argument("--config", help="Configuration file")
parser.add_argument("--certfile", help="SSL cert file")
parser.add_argument("--certkey", help="SSL key file")
+3 -3
View File
@@ -137,7 +137,7 @@ VM_CREATE_SCHEMA = {
},
"mac_addr": {
"description": "Base MAC address",
"type": "string",
"type": ["null", "string"],
"minLength": 1,
"pattern": "^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$"
},
@@ -355,7 +355,7 @@ VM_UPDATE_SCHEMA = {
},
"mac_addr": {
"description": "Base MAC address",
"type": "string",
"type": ["null", "string"],
"minLength": 1,
"pattern": "^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$"
},
@@ -595,7 +595,7 @@ VM_OBJECT_SCHEMA = {
},
"mac_addr": {
"description": "Base MAC address",
"type": "string",
"type": ["null", "string"]
#"minLength": 1,
#"pattern": "^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$"
},
+1 -1
View File
@@ -537,7 +537,7 @@ QEMU_OBJECT_SCHEMA = {
"description": "Console TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
"type": ["integer", "null"]
},
"console_type": {
"description": "Console type",
+3 -2
View File
@@ -69,7 +69,7 @@ class UBridgeHypervisor:
connection_success = False
last_exception = None
while time.time() - begin < timeout:
yield from asyncio.sleep(0.01)
yield from asyncio.sleep(0.1)
try:
self._reader, self._writer = yield from asyncio.open_connection(host, self._port)
except OSError as e:
@@ -84,6 +84,7 @@ class UBridgeHypervisor:
log.info("Connected to uBridge hypervisor after {:.4f} seconds".format(time.time() - begin))
try:
yield from asyncio.sleep(0.1)
version = yield from self.send("hypervisor version")
self._version = version[0].split("-", 1)[0]
except IndexError:
@@ -237,7 +238,7 @@ class UBridgeHypervisor:
.format(host=self._host, port=self._port, command=command, run=self.is_running()))
else:
retries += 1
yield from asyncio.sleep(0.1)
yield from asyncio.sleep(0.5)
continue
retries = 0
buf += chunk.decode("utf-8")
+7 -4
View File
@@ -198,8 +198,8 @@ class UnstoppableEventLoop(EventLoop):
class ShellConnection(TelnetConnection):
def __init__(self, reader, writer, shell, loop):
super(ShellConnection, self).__init__(reader, writer)
def __init__(self, reader, writer, shell, window_size_changed_callback, loop):
super(ShellConnection, self).__init__(reader, writer, window_size_changed_callback)
self._shell = shell
self._loop = loop
self._cli = None
@@ -239,9 +239,12 @@ class ShellConnection(TelnetConnection):
def disconnected(self):
pass
@asyncio.coroutine
def window_size_changed(self, columns, rows):
self._size = Size(rows=rows, columns=columns)
self._cb.terminal_size_changed()
if self._window_size_changed_callback:
yield from self._window_size_changed_callback(columns, rows)
@asyncio.coroutine
def feed(self, data):
@@ -291,8 +294,8 @@ def create_telnet_shell(shell, loop=None):
if loop is None:
loop = asyncio.get_event_loop()
def factory(reader, writer):
return ShellConnection(reader, writer, shell, loop)
def factory(reader, writer, window_size_changed_callback):
return ShellConnection(reader, writer, shell, window_size_changed_callback, loop)
return AsyncioTelnetServer(binary=True, echo=True, naws=True, connection_factory=factory)
+16 -10
View File
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import asyncio
import asyncio.subprocess
import struct
@@ -62,10 +62,11 @@ READ_SIZE = 1024
class TelnetConnection(object):
"""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._reader = reader
self._writer = writer
self._window_size_changed_callback = window_size_changed_callback
@property
def reader(self):
@@ -85,10 +86,13 @@ class TelnetConnection(object):
"""Method called when client is disconnecting"""
pass
@asyncio.coroutine
def window_size_changed(self, columns, rows):
"""Method called when window size changed, only can occur when
`naws` flag is enable in server configuration."""
pass
if self._window_size_changed_callback:
yield from self._window_size_changed_callback(columns, rows)
@asyncio.coroutine
def feed(self, data):
@@ -116,7 +120,7 @@ class TelnetConnection(object):
class AsyncioTelnetServer:
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
:param naws when True make a window size negotiation
@@ -131,6 +135,7 @@ class AsyncioTelnetServer:
self._lock = asyncio.Lock()
self._reader_process = None
self._current_read = None
self._window_size_changed_callback = window_size_changed_callback
self._binary = binary
# If echo is true when the client send data
@@ -139,8 +144,8 @@ class AsyncioTelnetServer:
self._echo = echo
self._naws = naws
def default_connection_factory(reader, writer):
return TelnetConnection(reader, writer)
def default_connection_factory(reader, writer, window_size_changed_callback):
return TelnetConnection(reader, writer, window_size_changed_callback)
if connection_factory is None:
connection_factory = default_connection_factory
@@ -190,7 +195,7 @@ class AsyncioTelnetServer:
@asyncio.coroutine
def run(self, network_reader, network_writer):
# 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
try:
@@ -215,7 +220,7 @@ class AsyncioTelnetServer:
try:
writer.write_eof()
yield from writer.drain()
except ConnectionError:
except (AttributeError, ConnectionError):
continue
@asyncio.coroutine
@@ -307,6 +312,7 @@ class AsyncioTelnetServer:
cmd.append(buffer[location])
return op
@asyncio.coroutine
def _negotiate(self, data, connection):
""" Performs negotiation commands"""
@@ -314,7 +320,7 @@ class AsyncioTelnetServer:
if command == NAWS:
if len(payload) == 4:
columns, rows = struct.unpack(str('!HH'), bytes(payload))
connection.window_size_changed(columns, rows)
yield from connection.window_size_changed(columns, rows)
else:
log.warning('Wrong number of NAWS bytes')
else:
@@ -373,7 +379,7 @@ class AsyncioTelnetServer:
break
# SE command is followed by IAC, remove the last two operations from stack
self._negotiate(negotiation[0:-2], connection)
yield from self._negotiate(negotiation[0:-2], connection)
# This must be a 3-byte TELNET command
else:
+21 -8
View File
@@ -15,6 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import io
import struct
from xml.etree.ElementTree import ElementTree, ParseError
@@ -103,13 +104,21 @@ def get_size(data, default_width=0, default_height=0):
root = tree.getroot()
try:
width_attr = root.attrib.get("width", "0")
height_attr = root.attrib.get("height", "0")
width_attr = root.attrib.get("width", "100%")
height_attr = root.attrib.get("height", "100%")
if width_attr.endswith("%") or height_attr.endswith("%"):
# check to viewBox attribute if width or height value is a percentage
_, _, width_attr, height_attr = root.attrib.get("viewBox").split()
viewbox = root.attrib.get("viewBox")
if not viewbox:
raise ValueError("Invalid SVG file: missing viewBox attribute")
_, _, viewbox_width, viewbox_height = re.split(r'[\s,]+', viewbox)
if width_attr.endswith("%"):
width = _svg_convert_size(viewbox_width, width_attr)
else:
width = _svg_convert_size(width_attr)
if height_attr.endswith("%"):
height = _svg_convert_size(viewbox_height, height_attr)
else:
height = _svg_convert_size(height_attr)
except (AttributeError, IndexError) as e:
raise ValueError("Invalid SVG file: {}".format(e))
@@ -117,11 +126,12 @@ def get_size(data, default_width=0, default_height=0):
return width, height, filetype
def _svg_convert_size(size):
def _svg_convert_size(size, percent=None):
"""
Convert svg size to the px version
:param size: String with the size
:param percent: String with the percentage, None = 100%
"""
# https://www.w3.org/TR/SVG/coords.html#Units
@@ -133,8 +143,11 @@ def _svg_convert_size(size):
"in": 90,
"px": 1
}
if len(size) > 3:
factor = 1.0
if len(size) >= 3:
if size[-2:] in conversion_table:
return round(float(size[:-2]) * conversion_table[size[-2:]])
return round(float(size))
factor = conversion_table[size[-2:]]
size = size[:-2]
if percent:
factor *= float(percent.rstrip("%")) / 100.0
return round(float(size) * factor)
+2 -2
View File
@@ -23,8 +23,8 @@
# or negative for a release candidate or beta (after the base version
# number has been incremented)
__version__ = "2.1.10"
__version_info__ = (2, 1, 10, 0)
__version__ = "2.1.18"
__version_info__ = (2, 1, 18, 0)
# If it's a git checkout try to add the commit
if "dev" in __version__:
+2 -2
View File
@@ -1,4 +1,4 @@
jsonschema>=2.4.0
jsonschema==2.6.0
aiohttp>=2.3.3,<2.4.0 # pyup: ignore
aiohttp-cors>=0.5.3,<0.6.0 # pyup: ignore
yarl>=0.11
@@ -8,4 +8,4 @@ psutil>=3.0.0
zipstream>=1.1.4
typing>=3.5.3.0 # Otherwise yarl fails with python 3.4
prompt-toolkit==1.0.15
async-timeout<3.0.0 # pyup: ignore; 3.0 drops support for python 3.4
async-timeout<3.0.0 # pyup: ignore; 3.0 drops support for python 3.4
+2 -3
View File
@@ -971,13 +971,12 @@ def test_mount_binds(vm, tmpdir):
def test_start_vnc(vm, loop):
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("asyncio.create_subprocess_exec") as mock_exec:
loop.run_until_complete(asyncio.ensure_future(vm._start_vnc()))
assert vm._display is not None
mock_exec.assert_any_call("Xvfb", "-nolisten", "tcp", ":{}".format(vm._display), "-screen", "0", "1280x1024x16")
mock_exec.assert_any_call("x11vnc", "-forever", "-nopw", "-shared", "-geometry", "1280x1024", "-display", "WAIT:{}".format(vm._display), "-rfbport", str(vm.console), "-rfbportv6", str(vm.console), "-noncache", "-listen", "127.0.0.1")
assert mock_exec.call_args[0] == ("Xtigervnc", "-geometry", vm.console_resolution, "-depth", "16", "-interface", "127.0.0.1", "-rfbport", str(vm.console), "-AlwaysShared", "-SecurityTypes", "None", ":{}".format(vm._display))
mock_wait.assert_called_with("/tmp/.X11-unix/X{}".format(vm._display))
+56 -4
View File
@@ -116,6 +116,7 @@ def test_is_running(vm, running_subprocess_mock):
def test_start(loop, vm, running_subprocess_mock):
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0")
with asyncio_patch("gns3server.compute.qemu.QemuVM.start_wrap_console"):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=running_subprocess_mock) as mock:
loop.run_until_complete(asyncio.ensure_future(vm.start()))
@@ -130,6 +131,7 @@ def test_stop(loop, vm, running_subprocess_mock):
future = asyncio.Future()
future.set_result(True)
process.wait.return_value = future
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0")
with asyncio_patch("gns3server.compute.qemu.QemuVM.start_wrap_console"):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=process):
@@ -213,6 +215,7 @@ def test_port_remove_nio_binding(vm, loop):
def test_close(vm, port_manager, loop):
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0")
with asyncio_patch("gns3server.compute.qemu.QemuVM.start_wrap_console"):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()):
loop.run_until_complete(asyncio.ensure_future(vm.start()))
@@ -338,6 +341,7 @@ def test_disk_options(vm, tmpdir, loop, fake_qemu_img_binary):
def test_cdrom_option(vm, tmpdir, loop, fake_qemu_img_binary):
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0")
vm._cdrom_image = str(tmpdir / "test.iso")
open(vm._cdrom_image, "w+").close()
@@ -348,6 +352,7 @@ def test_cdrom_option(vm, tmpdir, loop, fake_qemu_img_binary):
def test_bios_option(vm, tmpdir, loop, fake_qemu_img_binary):
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0")
vm._bios_image = str(tmpdir / "test.img")
open(vm._bios_image, "w+").close()
@@ -454,6 +459,7 @@ def test_control_vm_expect_text(vm, loop, running_subprocess_mock):
def test_build_command(vm, loop, fake_qemu_binary, port_manager):
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0")
os.environ["DISPLAY"] = "0:0"
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
cmd = loop.run_until_complete(asyncio.ensure_future(vm._build_command()))
@@ -477,7 +483,9 @@ def test_build_command(vm, loop, fake_qemu_binary, port_manager):
"-device",
"e1000,mac={},netdev=gns3-0".format(vm._mac_address),
"-netdev",
"socket,id=gns3-0,udp=127.0.0.1:{},localaddr=127.0.0.1:{}".format(nio.rport, nio.lport)
"socket,id=gns3-0,udp=127.0.0.1:{},localaddr=127.0.0.1:{}".format(nio.rport, nio.lport),
"-display",
"none"
]
@@ -486,6 +494,7 @@ def test_build_command_manual_uuid(vm, loop, fake_qemu_binary, port_manager):
If user has set a uuid we keep it
"""
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0")
vm.options = "-uuid e1c307a4-896f-11e6-81a5-3c07547807cc"
os.environ["DISPLAY"] = "0:0"
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
@@ -524,7 +533,8 @@ def test_build_command_kvm(linux_platform, vm, loop, fake_qemu_binary, port_mana
"-device",
"e1000,mac={},netdev=gns3-0".format(vm._mac_address),
"-netdev",
"socket,id=gns3-0,udp=127.0.0.1:{},localaddr=127.0.0.1:{}".format(nio.rport, nio.lport)
"socket,id=gns3-0,udp=127.0.0.1:{},localaddr=127.0.0.1:{}".format(nio.rport, nio.lport),
"-nographic"
]
@@ -560,13 +570,52 @@ def test_build_command_kvm_2_4(linux_platform, vm, loop, fake_qemu_binary, port_
"-device",
"e1000,mac={},netdev=gns3-0".format(vm._mac_address),
"-netdev",
"socket,id=gns3-0,udp=127.0.0.1:{},localaddr=127.0.0.1:{}".format(nio.rport, nio.lport)
"socket,id=gns3-0,udp=127.0.0.1:{},localaddr=127.0.0.1:{}".format(nio.rport, nio.lport),
"-nographic"
]
def test_build_command_kvm_3_1(linux_platform, vm, loop, fake_qemu_binary, port_manager):
"""
Qemu 2.4 introduce an issue with KVM
"""
vm._run_with_kvm = MagicMock(return_value=True)
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0")
os.environ["DISPLAY"] = "0:0"
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
cmd = loop.run_until_complete(asyncio.ensure_future(vm._build_command()))
nio = vm._local_udp_tunnels[0][0]
assert cmd == [
fake_qemu_binary,
"-name",
"test",
"-m",
"256M",
"-smp",
"cpus=1",
"-enable-kvm",
"-machine",
"smm=off",
"-boot",
"order=c",
"-uuid",
vm.id,
"-serial",
"telnet:127.0.0.1:{},server,nowait".format(vm._internal_console_port),
"-net",
"none",
"-device",
"e1000,mac={},netdev=gns3-0".format(vm._mac_address),
"-netdev",
"socket,id=gns3-0,udp=127.0.0.1:{},localaddr=127.0.0.1:{}".format(nio.rport, nio.lport),
"-display",
"none"
]
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
def test_build_command_without_display(vm, loop, fake_qemu_binary):
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.5.0")
os.environ["DISPLAY"] = ""
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
cmd = loop.run_until_complete(asyncio.ensure_future(vm._build_command()))
@@ -575,6 +624,7 @@ def test_build_command_without_display(vm, loop, fake_qemu_binary):
def test_build_command_two_adapters(vm, loop, fake_qemu_binary, port_manager):
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.5.0")
os.environ["DISPLAY"] = "0:0"
vm.adapters = 2
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
@@ -604,7 +654,8 @@ def test_build_command_two_adapters(vm, loop, fake_qemu_binary, port_manager):
"-device",
"e1000,mac={},netdev=gns3-1".format(int_to_macaddress(macaddress_to_int(vm._mac_address) + 1)),
"-netdev",
"socket,id=gns3-1,udp=127.0.0.1:{},localaddr=127.0.0.1:{}".format(nio2.rport, nio2.lport)
"socket,id=gns3-1,udp=127.0.0.1:{},localaddr=127.0.0.1:{}".format(nio2.rport, nio2.lport),
"-nographic"
]
@@ -613,6 +664,7 @@ def test_build_command_two_adapters_mac_address(vm, loop, fake_qemu_binary, port
Should support multiple base vmac address
"""
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.5.0")
vm.adapters = 2
vm.mac_address = "00:00:ab:0e:0f:09"
mac_0 = vm._mac_address
@@ -75,14 +75,13 @@ def test_create_node_from_appliance(http_controller, controller, project, comput
"name": "test",
"symbol": "guest.svg",
"default_name_format": "{name}-{0}",
"server": "example.com"
"compute_id": "example.com"
})}
with asyncio_patch("gns3server.controller.project.Project.add_node_from_appliance") as mock:
with asyncio_patch("gns3server.controller.project.Project.add_node_from_appliance", return_value={"name": "test", "node_type": "qemu", "compute_id": "example.com"}) as mock:
response = http_controller.post("/projects/{}/appliances/{}".format(project.id, id), {
"x": 42,
"y": 12
})
mock.assert_called_with(id, x=42, y=12, compute_id=None)
print(response.body)
assert response.route == "/projects/{project_id}/appliances/{appliance_id}"
assert response.status == 201