Compare commits

..

43 Commits

Author SHA1 Message Date
19c4ec1867 1.5.3 2017-01-12 08:18:46 +01:00
8019374ed0 Fix sporadically systemd is unable to start gns3-server
Signed-off-by: Julien Duponchelle <julien@gns3.net>
2017-01-09 10:28:21 +01:00
af530be346 1.5.3dev2 2016-12-20 11:48:55 +01:00
9c3cfc4f4e 1.5.3 rc1 2016-12-20 09:30:52 +01:00
c7d878ed9e Fix TypeError: __init__() got multiple values for argument 'handler' with recent aiohttp
Fix #841
2016-12-20 09:21:02 +01:00
49f1ee2e32 Support aiohttp 1.2 (but not compatible with previous versions)
Fix #840
2016-12-20 09:17:45 +01:00
bd4de862c8 Explain that segfault on IOU is a issue with the image
Fix #739
2016-10-28 11:25:14 +02:00
f038735595 Fix an issue with finding vmrun and vboxmanage
Ref #1575
2016-10-19 17:44:55 +02:00
a4f8675c93 Support named remote servers for VPCS
Fix #722
2016-10-19 16:55:33 +02:00
da71f29208 Merge pull request #726 from GNS3/port_check
When checking for a free port check if the host and 0.0.0.0 are available
2016-10-18 21:02:28 -06:00
b53b34d485 When checking for a free port check if the host and 0.0.0.0 are available
Because some emulators will listen on 0.0.0.0 and not on the host.

Fix #721
2016-10-18 09:59:31 +02:00
e63da227d0 Try pyup.io 2016-10-17 10:27:35 +02:00
c7d9af121f smm=off is only for 64bits
Fix #714
2016-10-04 14:53:09 +02:00
15babb137d Fix set hostname on remote server
Fix #691
2016-09-27 10:40:58 +02:00
eccee6b629 Support unstable ppa for remote install 2016-09-27 10:35:24 +02:00
ef95ba1ed8 Fix sending smm option to qemu
Fix #689
2016-09-26 15:15:38 +02:00
2bbdbeaa82 Workaround a bug with KVM, Qemu >= 2.4 and Intel CPU
Fix #685
2016-09-21 19:25:15 +02:00
de2dad20d5 Renable sleep at Vbox exit bug seem to be back
Fix https://github.com/GNS3/gns3-gui/issues/1444
2016-09-08 18:37:34 +02:00
84c0a17572 Support large project (> 2GB) during export
Fix #670
2016-09-08 11:41:12 +02:00
f0edf799b7 Fix Deleting running telnet docker VM shows error in log
Fix #662
2016-09-07 14:24:56 +02:00
a7be4681d5 Create gns3server/symbols directory
This directory is use only in 2.0 but to simplify packaging
we create it in 1.5.
2016-09-06 09:45:29 +02:00
07b982d4db Fix when closing a container using VNC, root permission are not reset
Fix #659

Signed-off-by: Julien Duponchelle <julien@gns3.net>
2016-09-01 09:28:22 +02:00
da1cd9a3e7 Use $PATH also for dynamips and cleanup some $PATH usages
Fix #655
2016-08-29 11:27:35 +02:00
0eafb6f06c Fix a lock issue with some virtualbox vm
Fix  https://github.com/GNS3/gns3-gui/issues/1444
2016-08-29 10:51:50 +02:00
042a69eecf Raise proper error when you try to load an empty qcow2 file
Fix #637
2016-08-29 10:18:18 +02:00
1885fe62a6 Fix upload form crash
Fix #647
2016-08-29 09:25:02 +02:00
e481ffa94c Search bin from the $PATH for sample configuration file 2016-08-27 18:10:41 +02:00
937bbf0131 Merge pull request #653 from ehlers/master
Update 'Updated systemd unit file and added sample configuration file'
2016-08-27 18:09:35 +02:00
d58a6ccda9 Update 'Updated systemd unit file and added sample configuration file' 2016-08-27 02:00:26 +02:00
84fb108abb Change CR/LF line ending to unix style 2016-08-26 18:45:22 +02:00
4455499e00 Merge pull request #648 from ianc1215/master
Updated systemd unit file and added sample configuration file
2016-08-26 09:28:40 +02:00
763f258465 Updated systemd unit file and added sample configuration file
Rewrote the systemd unit file to fix an issue where the system was not able to create a PID file inside /var/run.

I fixed this by having systemd create a new directory called /var/run/gns3. Then I had systemd change ownership of the directory to gns3:gns3 so the gns3server executable could read and write the PID file. I have tested these changes against Ubuntu 16.04.1 LTS.
2016-08-25 19:24:09 -04:00
d447a04c6a 1.5.3dev1 2016-08-18 22:16:01 +02:00
f358cb45a2 1.5.2 2016-08-18 22:00:39 +02:00
6b8e93f847 Merge pull request #630 from athmane/master
Move utils.vmnet to gns3 namespace
2016-08-03 17:30:12 -06:00
db95cb5c46 Move utils.vmnet to gns3 namespace 2016-07-29 17:53:48 +00:00
d6f63d3b7d Fix Exporting portable projects with QEMU includes base images even when selecting no.
Fix https://github.com/GNS3/gns3-gui/issues/1409
2016-07-28 15:00:04 +02:00
7d90a73ed2 Catch error when md5sum file is corrupted
Fix #622
2016-07-28 12:35:23 +02:00
b1b2bbd581 Merge pull request #625 from fcolista/master
requirements.txt : added support for newer aiohttp version
2016-07-19 17:07:30 +02:00
da7074ea74 requirements.txt : added support for newer aiohttp version 2016-07-19 16:59:47 +02:00
44307b43b9 Improve compaction of .gns3project
Ref #624
2016-07-19 16:38:32 +02:00
febf0f7839 Fix crash when winpcap is not installed
Ref https://github.com/GNS3/gns3-gui/issues/1380
2016-07-12 13:43:08 +02:00
26d49f19c1 1.5.2dev1 2016-07-07 18:58:23 +02:00
35 changed files with 369 additions and 93 deletions

2
.pyup.yml Normal file
View File

@ -0,0 +1,2 @@
branch:
2.0

View File

@ -1,5 +1,40 @@
# Change Log # Change Log
## 1.5.3 12/01/2016
* Fix sporadically systemd is unable to start gns3-server
## 1.5.3 rc1 20/12/2016
* Support aiohttp 1.2 (but not compatible with previous versions)
* Explain that segfault on IOU is a issue with the image
* Fix an issue with finding vmrun and vboxmanage
* Support named remote servers for VPCS
* When checking for a free port check if the host and 0.0.0.0 are available
* smm=off is only for 64bits
* Fix set hostname on remote server
* Fix sending smm option to qemu
* Workaround a bug with KVM, Qemu >= 2.4 and Intel CPU
* Renable sleep at Vbox exit bug seem to be back
* Support large project (> 2GB) during export
* Fix Deleting running telnet docker VM shows error in log
* Fix when closing a container using VNC, root permission are not reset
* Use $PATH also for dynamips and cleanup some $PATH usages
* Fix a lock issue with some virtualbox vm
* Raise proper error when you try to load an empty qcow2 file
* Fix upload form crash
* Search bin from the $PATH for sample configuration file
* Updated systemd unit file and added sample configuration file
## 1.5.2 18/08/2016
* Move utils.vmnet to gns3 namespace
* Fix Exporting portable projects with QEMU includes base images even when selecting no.
* Catch error when md5sum file is corrupted
* requirements.txt : added support for newer aiohttp version
* Improve compaction of .gns3project
* Fix crash when winpcap is not installed
## 1.5.1 07/07/2016 ## 1.5.1 07/07/2016
* Increase the number of interface for docker * Increase the number of interface for docker

61
conf/gns3_server.conf Normal file
View File

@ -0,0 +1,61 @@
[Server]
; IP where the server listen for connections
host = 0.0.0.0
; HTTP port for controlling the servers
port = 3080
; Option to enable SSL encryption
ssl = False
certfile=/home/gns3/.config/GNS3/ssl/server.cert
certkey=/home/gns3/.config/GNS3/ssl/server.key
; Path where devices images are stored
images_path = /home/gns3/GNS3/images
; Path where user projects are stored
projects_path = /home/gns3/GNS3/projects
; Option to automatically send crash reports to the GNS3 team
report_errors = True
; First console port of the range allocated to devices
console_start_port_range = 5000
; Last console port of the range allocated to devices
console_end_port_range = 10000
; First port of the range allocated for inter-device communication. Two ports are allocated per link.
udp_start_port_range = 10000
; Last port of the range allocated for inter-device communication. Two ports are allocated per link
udp_start_end_range = 20000
; uBridge executable location, default: search in PATH
;ubridge_path = ubridge
; Option to enable HTTP authentication.
auth = False
; Username for HTTP authentication.
user = gns3
; Password for HTTP authentication.
password = gns3
[VPCS]
; VPCS executable location, default: search in PATH
;vpcs_path = vpcs
[Dynamips]
; Enable auxiliary console ports on IOS routers
allocate_aux_console_ports = False
mmap_support = True
; Dynamips executable path, default: search in PATH
;dynamips_path = dynamips
sparse_memory_support = True
ghost_ios_support = True
[IOU]
; iouyap executable path, default: search in PATH
;iouyap_path = iouyap
; Path of your .iourc file. If not provided, the file is searched in $HOME/.iourc
iourc_path = /home/gns3/.iourc
; Validate if the iourc license file is correct. If you turn this off and your licence is invalid IOU will not start and no errors will be shown.
license_check = True
[Qemu]
; !! Remember to add the gns3 user to the KVM group, otherwise you will not have read / write permssions to /dev/kvm !!
enable_kvm = True

View File

@ -52,7 +52,7 @@ class CrashReport:
Report crash to a third party service Report crash to a third party service
""" """
DSN = "sync+https://76659dafd45f4e3f8ca0ae7597eac70b:dc0094ac9ae44fe1beacc1e37b2f6041@app.getsentry.com/38482" DSN = "sync+https://700b0c46edb0473baacd2dc318d8de1f:824bd6d75471494ebcb87ce27cfdeade@sentry.io/38482"
if hasattr(sys, "frozen"): if hasattr(sys, "frozen"):
cacert = get_resource("cacert.pem") cacert = get_resource("cacert.pem")
if cacert is not None and os.path.isfile(cacert): if cacert is not None and os.path.isfile(cacert):

View File

@ -368,7 +368,8 @@ class ProjectHandler:
response.content_length = None response.content_length = None
response.start(request) response.start(request)
for data in project.export(include_images=bool(request.GET.get("include_images", "0"))): include_images = bool(int(request.json.get("include_images", "0")))
for data in project.export(include_images=include_images):
response.write(data) response.write(data)
yield from response.drain() yield from response.drain()

View File

@ -54,7 +54,8 @@ class UploadHandler:
@Route.post( @Route.post(
r"/upload", r"/upload",
description="Manage upload of GNS3 images", description="Manage upload of GNS3 images",
api_version=None api_version=None,
raw=True
) )
def upload(request, response): def upload(request, response):
data = yield from request.post() data = yield from request.post()

View File

@ -403,11 +403,10 @@ class BaseVM:
""" """
path = self._manager.config.get_section_config("Server").get("ubridge_path", "ubridge") path = self._manager.config.get_section_config("Server").get("ubridge_path", "ubridge")
if path == "ubridge": path = shutil.which(path)
path = shutil.which("ubridge")
if path is None or len(path) == 0: if path is None or len(path) == 0:
raise VMError("uBridge is not installed") raise VMError("uBridge is not installed or uBridge path is invalid")
return path return path
@asyncio.coroutine @asyncio.coroutine

View File

@ -530,7 +530,11 @@ class DockerVM(BaseVM):
if self._ubridge_hypervisor and self._ubridge_hypervisor.is_running(): if self._ubridge_hypervisor and self._ubridge_hypervisor.is_running():
yield from self._ubridge_hypervisor.stop() yield from self._ubridge_hypervisor.stop()
state = yield from self._get_container_state() try:
state = yield from self._get_container_state()
except DockerHttp404Error:
state = "stopped"
if state == "paused": if state == "paused":
yield from self.unpause() yield from self.unpause()
@ -577,6 +581,9 @@ class DockerVM(BaseVM):
@asyncio.coroutine @asyncio.coroutine
def reset(self): def reset(self):
try: try:
state = yield from self._get_container_state()
if state == "paused" or state == "running":
yield from self.stop()
if self.console_type == "vnc": if self.console_type == "vnc":
if self._x11vnc_process: if self._x11vnc_process:
try: try:
@ -589,9 +596,6 @@ class DockerVM(BaseVM):
yield from self._xvfb_process.wait() yield from self._xvfb_process.wait()
except ProcessLookupError: except ProcessLookupError:
pass pass
state = yield from self._get_container_state()
if state == "paused" or state == "running":
yield from self.stop()
# v 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false. # v 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false.
# force - 1/True/true or 0/False/false, Kill then remove the container. Default false. # force - 1/True/true or 0/False/false, Kill then remove the container. Default false.
yield from self.manager.query("DELETE", "containers/{}".format(self._cid), params={"force": 1, "v": 1}) yield from self.manager.query("DELETE", "containers/{}".format(self._cid), params={"force": 1, "v": 1})
@ -658,7 +662,7 @@ class DockerVM(BaseVM):
if nio.capturing: if nio.capturing:
yield from self._ubridge_hypervisor.send('bridge start_capture bridge{adapter} "{pcap_file}"'.format(adapter=adapter_number, yield from self._ubridge_hypervisor.send('bridge start_capture bridge{adapter} "{pcap_file}"'.format(adapter=adapter_number,
pcap_file=nio.pcap_output_file)) pcap_file=nio.pcap_output_file))
yield from self._ubridge_hypervisor.send('bridge start bridge{adapter}'.format(adapter=adapter_number)) yield from self._ubridge_hypervisor.send('bridge start bridge{adapter}'.format(adapter=adapter_number))

View File

@ -336,16 +336,16 @@ class Dynamips(BaseManager):
def find_dynamips(self): def find_dynamips(self):
# look for Dynamips # look for Dynamips
dynamips_path = self.config.get_section_config("Dynamips").get("dynamips_path") dynamips_path = self.config.get_section_config("Dynamips").get("dynamips_path", "dynamips")
if not dynamips_path: if not os.path.isabs(dynamips_path):
dynamips_path = shutil.which("dynamips") dynamips_path = shutil.which(dynamips_path)
if not dynamips_path: if not dynamips_path:
raise DynamipsError("Could not find Dynamips") raise DynamipsError("Could not find Dynamips")
if not os.path.isfile(dynamips_path): if not os.path.isfile(dynamips_path):
raise DynamipsError("Dynamips {} is not accessible".format(dynamips_path)) raise DynamipsError("Dynamips {} is not accessible".format(dynamips_path))
if not os.access(dynamips_path, os.X_OK): if not os.access(dynamips_path, os.X_OK):
raise DynamipsError("Dynamips is not executable") raise DynamipsError("Dynamips {} is not executable".format(dynamips_path))
self._dynamips_path = dynamips_path self._dynamips_path = dynamips_path
return dynamips_path return dynamips_path

View File

@ -230,9 +230,11 @@ class IOUVM(BaseVM):
:returns: path to IOUYAP :returns: path to IOUYAP
""" """
path = self._manager.config.get_section_config("IOU").get("iouyap_path", "iouyap") search_path = self._manager.config.get_section_config("IOU").get("iouyap_path", "iouyap")
if path == "iouyap": path = shutil.which(search_path)
path = shutil.which("iouyap") # shutil.which return None if the path doesn't exists
if not path:
return search_path
return path return path
@property @property
@ -534,14 +536,19 @@ class IOUVM(BaseVM):
:param returncode: Process returncode :param returncode: Process returncode
""" """
log.info("{} process has stopped, return code: {}".format(process_name, returncode))
self._terminate_process_iou() self._terminate_process_iou()
self._terminate_process_iouyap() self._terminate_process_iouyap()
self._ioucon_thread_stop_event.set() self._ioucon_thread_stop_event.set()
if returncode != 0: if returncode != 0:
self.project.emit("log.error", {"message": "{} process has stopped, return code: {}\n{}".format(process_name, log.info("{} process has stopped, return code: {}".format(process_name, returncode))
returncode, else:
self.read_iou_stdout())}) if returncode == 11:
message = "{} process has stopped, return code: {}. This could be an issue with the image using a different image can fix the issue.\n{}".format(process_name, returncode, self.read_iou_stdout())
else:
message = "{} process has stopped, return code: {}\n{}".format(process_name, returncode, self.read_iou_stdout())
log.warn(message)
self.project.emit("log.error", {"message": message})
def _rename_nvram_file(self): def _rename_nvram_file(self):
""" """

View File

@ -153,6 +153,8 @@ class PortManager:
try: try:
PortManager._check_port(host, port, socket_type) PortManager._check_port(host, port, socket_type)
if host != "0.0.0.0":
PortManager._check_port("0.0.0.0", port, socket_type)
return port return port
except OSError as e: except OSError as e:
last_exception = e last_exception = e

View File

@ -528,7 +528,7 @@ class Project:
:returns: ZipStream object :returns: ZipStream object
""" """
z = zipstream.ZipFile() z = zipstream.ZipFile(allowZip64=True)
# topdown allo to modify the list of directory in order to ignore # topdown allo to modify the list of directory in order to ignore
# directory # directory
for root, dirs, files in os.walk(self._path, topdown=True): for root, dirs, files in os.walk(self._path, topdown=True):
@ -556,9 +556,9 @@ class Project:
# We merge the data from all server in the same project-files directory # We merge the data from all server in the same project-files directory
vm_directory = os.path.join(self._path, "servers", "vm") vm_directory = os.path.join(self._path, "servers", "vm")
if os.path.commonprefix([root, vm_directory]) == vm_directory: if os.path.commonprefix([root, vm_directory]) == vm_directory:
z.write(path, os.path.relpath(path, vm_directory)) z.write(path, os.path.relpath(path, vm_directory), compress_type=zipfile.ZIP_DEFLATED)
else: else:
z.write(path, os.path.relpath(path, self._path)) z.write(path, os.path.relpath(path, self._path), compress_type=zipfile.ZIP_DEFLATED)
return z return z
def _export_images(self, image, type, z): def _export_images(self, image, type, z):

View File

@ -62,7 +62,10 @@ class Qcow2:
with open(self.path, 'rb') as f: with open(self.path, 'rb') as f:
content = f.read(struct.calcsize(struct_format)) content = f.read(struct.calcsize(struct_format))
self.magic, self.version, self.backing_file_offset, self.backing_file_size = struct.unpack_from(struct_format, content) try:
self.magic, self.version, self.backing_file_offset, self.backing_file_size = struct.unpack_from(struct_format, content)
except struct.error:
raise Qcow2Error("Invalid file header for {}".format(self.path))
if self.magic != 1363560955: # The first 4 bytes contain the characters 'Q', 'F', 'I' followed by 0xfb. if self.magic != 1363560955: # The first 4 bytes contain the characters 'Q', 'F', 'I' followed by 0xfb.
raise Qcow2Error("Invalid magic for {}".format(self.path)) raise Qcow2Error("Invalid magic for {}".format(self.path))

View File

@ -1424,6 +1424,11 @@ class QemuVM(BaseVM):
command.extend(["-smp", "cpus={}".format(self._cpus)]) command.extend(["-smp", "cpus={}".format(self._cpus)])
if self._run_with_kvm(self.qemu_path, self._options): if self._run_with_kvm(self.qemu_path, self._options):
command.extend(["-enable-kvm"]) command.extend(["-enable-kvm"])
version = yield from self.manager.get_qemu_version(self.qemu_path)
# Issue on some combo Intel CPU + KVM + Qemu 2.4.0
# https://github.com/GNS3/gns3-server/issues/685
if version and parse_version(version) >= parse_version("2.4.0") and self.platform == "x86_64":
command.extend(["-machine", "smm=off"])
command.extend(["-boot", "order={}".format(self._boot_priority)]) command.extend(["-boot", "order={}".format(self._boot_priority)])
cdrom_option = self._cdrom_option() cdrom_option = self._cdrom_option()
command.extend(cdrom_option) command.extend(cdrom_option)

View File

@ -66,7 +66,10 @@ class VirtualBox(BaseManager):
elif sys.platform.startswith("darwin"): elif sys.platform.startswith("darwin"):
vboxmanage_path = "/Applications/VirtualBox.app/Contents/MacOS/VBoxManage" vboxmanage_path = "/Applications/VirtualBox.app/Contents/MacOS/VBoxManage"
else: else:
vboxmanage_path = shutil.which("vboxmanage") vboxmanage_path = "vboxmanage"
if not os.path.isabs(vboxmanage_path):
vboxmanage_path = shutil.which(vboxmanage_path)
if not vboxmanage_path: if not vboxmanage_path:
raise VirtualBoxError("Could not find VBoxManage") raise VirtualBoxError("Could not find VBoxManage")

View File

@ -30,7 +30,7 @@ import asyncio
from gns3server.utils import parse_version from gns3server.utils import parse_version
from gns3server.utils.telnet_server import TelnetServer from gns3server.utils.telnet_server import TelnetServer
from gns3server.utils.asyncio import wait_for_file_creation, wait_for_named_pipe_creation from gns3server.utils.asyncio import wait_for_file_creation, wait_for_named_pipe_creation, locked_coroutine
from .virtualbox_error import VirtualBoxError from .virtualbox_error import VirtualBoxError
from ..nios.nio_udp import NIOUDP from ..nios.nio_udp import NIOUDP
from ..nios.nio_nat import NIONAT from ..nios.nio_nat import NIONAT
@ -230,7 +230,7 @@ class VirtualBoxVM(BaseVM):
if (yield from self.check_hw_virtualization()): if (yield from self.check_hw_virtualization()):
self._hw_virtualization = True self._hw_virtualization = True
@asyncio.coroutine @locked_coroutine
def stop(self): def stop(self):
""" """
Stops this VirtualBox VM. Stops this VirtualBox VM.
@ -250,7 +250,7 @@ class VirtualBoxVM(BaseVM):
log.debug("Stop result: {}".format(result)) log.debug("Stop result: {}".format(result))
log.info("VirtualBox VM '{name}' [{id}] stopped".format(name=self.name, id=self.id)) log.info("VirtualBox VM '{name}' [{id}] stopped".format(name=self.name, id=self.id))
# yield from asyncio.sleep(0.5) # give some time for VirtualBox to unlock the VM yield from asyncio.sleep(0.5) # give some time for VirtualBox to unlock the VM
try: try:
# deactivate the first serial port # deactivate the first serial port
yield from self._modify_vm("--uart1 off") yield from self._modify_vm("--uart1 off")

View File

@ -105,7 +105,10 @@ class VMware(BaseManager):
elif sys.platform.startswith("darwin"): elif sys.platform.startswith("darwin"):
vmrun_path = "/Applications/VMware Fusion.app/Contents/Library/vmrun" vmrun_path = "/Applications/VMware Fusion.app/Contents/Library/vmrun"
else: else:
vmrun_path = shutil.which("vmrun") vmrun_path = "vmrun"
if not os.path.isabs(vmrun_path):
vmrun_path = shutil.which(vmrun_path)
if not vmrun_path: if not vmrun_path:
raise VMwareError("Could not find VMware vmrun, please make sure it is installed") raise VMwareError("Could not find VMware vmrun, please make sure it is installed")

View File

@ -22,6 +22,7 @@ order to run a VPCS VM.
import os import os
import sys import sys
import socket
import subprocess import subprocess
import signal import signal
import re import re
@ -139,9 +140,11 @@ class VPCSVM(BaseVM):
:returns: path to VPCS :returns: path to VPCS
""" """
path = self._manager.config.get_section_config("VPCS").get("vpcs_path", "vpcs") search_path = self._manager.config.get_section_config("VPCS").get("vpcs_path", "vpcs")
if path == "vpcs": path = shutil.which(search_path)
path = shutil.which("vpcs") # shutil.which return None if the path doesn't exists
if not path:
return search_path
return path return path
@BaseVM.name.setter @BaseVM.name.setter
@ -430,7 +433,10 @@ class VPCSVM(BaseVM):
# UDP tunnel # UDP tunnel
command.extend(["-s", str(nio.lport)]) # source UDP port command.extend(["-s", str(nio.lport)]) # source UDP port
command.extend(["-c", str(nio.rport)]) # destination UDP port command.extend(["-c", str(nio.rport)]) # destination UDP port
command.extend(["-t", nio.rhost]) # destination host try:
command.extend(["-t", socket.gethostbyname(nio.rhost)]) # destination host, we need to resolve the hostname because VPCS doesn't support it
except socket.gaierror as e:
raise VPCSError("Can't resolve hostname {}".format(nio.rhost))
elif isinstance(nio, NIOTAP): elif isinstance(nio, NIOTAP):
# TAP interface # TAP interface

View File

@ -30,7 +30,6 @@ import time
import atexit import atexit
from .web.route import Route from .web.route import Route
from .web.request_handler import RequestHandler
from .config import Config from .config import Config
from .modules import MODULES from .modules import MODULES
from .modules.port_manager import PortManager from .modules.port_manager import PortManager
@ -199,6 +198,11 @@ class Server:
Starts the server. Starts the server.
""" """
server_logger = logging.getLogger('aiohttp.server')
# In debug mode we don't use the standard request log but a more complete in response.py
if log.getEffectiveLevel() == logging.DEBUG:
server_logger.setLevel(logging.CRITICAL)
logger = logging.getLogger("asyncio") logger = logging.getLogger("asyncio")
logger.setLevel(logging.ERROR) logger.setLevel(logging.ERROR)
@ -239,7 +243,7 @@ class Server:
m.port_manager = self._port_manager m.port_manager = self._port_manager
log.info("Starting server on {}:{}".format(self._host, self._port)) log.info("Starting server on {}:{}".format(self._host, self._port))
self._handler = app.make_handler(handler=RequestHandler) self._handler = app.make_handler()
server = self._run_application(self._handler, ssl_context) server = self._run_application(self._handler, ssl_context)
self._loop.run_until_complete(server) self._loop.run_until_complete(server)
self._signal_handling() self._signal_handling()

View File

@ -127,3 +127,24 @@ def wait_for_named_pipe_creation(pipe_path, timeout=60):
else: else:
return return
raise asyncio.TimeoutError() raise asyncio.TimeoutError()
def locked_coroutine(f):
"""
Method decorator that replace asyncio.coroutine that warranty
that this specific method of this class instance will not we
executed twice at the same time
"""
@asyncio.coroutine
def new_function(*args, **kwargs):
# In the instance of the class we will store
# a lock has an attribute.
lock_var_name = "__" + f.__name__ + "_lock"
if not hasattr(args[0], lock_var_name):
setattr(args[0], lock_var_name, asyncio.Lock())
with (yield from getattr(args[0], lock_var_name)):
return (yield from f(*args, **kwargs))
return new_function

View File

@ -36,7 +36,8 @@ def md5sum(path):
try: try:
with open(path + '.md5sum') as f: with open(path + '.md5sum') as f:
return f.read() return f.read()
except OSError: # Unicode error is when user rename an image to .md5sum ....
except (OSError, UnicodeDecodeError):
pass pass
try: try:

View File

@ -138,6 +138,7 @@ def is_interface_up(interface):
# TODO: Windows & OSX support # TODO: Windows & OSX support
return True return True
def _check_windows_service(service_name): def _check_windows_service(service_name):
import pywintypes import pywintypes
@ -154,6 +155,7 @@ def _check_windows_service(service_name):
raise aiohttp.web.HTTPInternalServerError(text="Could not check if the {} service is running: {}".format(service_name, e.strerror)) raise aiohttp.web.HTTPInternalServerError(text="Could not check if the {} service is running: {}".format(service_name, e.strerror))
return True return True
def interfaces(): def interfaces():
""" """
Gets the network interfaces on this server. Gets the network interfaces on this server.
@ -178,13 +180,19 @@ def interfaces():
"mac_address": mac_address}) "mac_address": mac_address})
else: else:
try: try:
service_installed = True
if not _check_windows_service("npf") and not _check_windows_service("npcap"): if not _check_windows_service("npf") and not _check_windows_service("npcap"):
raise aiohttp.web.HTTPInternalServerError("The NPF or Npcap is not installed or running") service_installed = False
results = get_windows_interfaces() else:
results = get_windows_interfaces()
except ImportError: except ImportError:
message = "pywin32 module is not installed, please install it on the server to get the available interface names" message = "pywin32 module is not installed, please install it on the server to get the available interface names"
raise aiohttp.web.HTTPInternalServerError(text=message) raise aiohttp.web.HTTPInternalServerError(text=message)
except Exception as e: except Exception as e:
log.error("uncaught exception {type}".format(type=type(e)), exc_info=1) log.error("uncaught exception {type}".format(type=type(e)), exc_info=1)
raise aiohttp.web.HTTPInternalServerError(text="uncaught exception: {}".format(e)) raise aiohttp.web.HTTPInternalServerError(text="uncaught exception: {}".format(e))
if service_installed is False:
raise aiohttp.web.HTTPInternalServerError(text="The Winpcap or Npcap is not installed or running")
return results return results

View File

@ -23,5 +23,5 @@
# or negative for a release candidate or beta (after the base version # or negative for a release candidate or beta (after the base version
# number has been incremented) # number has been incremented)
__version__ = "1.5.1" __version__ = "1.5.3"
__version_info__ = (1, 5, 1, 0) __version_info__ = (1, 5, 3, 0)

View File

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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 aiohttp.web
import logging
class RequestHandler(aiohttp.web.RequestHandler):
def log_access(self, message, environ, response, time):
# In debug mode we don't use the standard request log but a more complete in response.py
if self.logger.getEffectiveLevel() != logging.DEBUG:
super().log_access(message, environ, response, time)

View File

@ -17,11 +17,13 @@
import sys import sys
import json import json
import jsonschema import urllib
import asyncio import asyncio
import aiohttp import aiohttp
import logging import logging
import traceback import traceback
import jsonschema
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -33,10 +35,11 @@ from ..config import Config
@asyncio.coroutine @asyncio.coroutine
def parse_request(request, input_schema): def parse_request(request, input_schema, raw):
"""Parse body of request and raise HTTP errors in case of problems""" """Parse body of request and raise HTTP errors in case of problems"""
content_length = request.content_length content_length = request.content_length
if content_length is not None and content_length > 0: if content_length is not None and content_length > 0 and not raw:
body = yield from request.read() body = yield from request.read()
try: try:
request.json = json.loads(body.decode('utf-8')) request.json = json.loads(body.decode('utf-8'))
@ -45,13 +48,21 @@ def parse_request(request, input_schema):
raise aiohttp.web.HTTPBadRequest(text="Invalid JSON {}".format(e)) raise aiohttp.web.HTTPBadRequest(text="Invalid JSON {}".format(e))
else: else:
request.json = {} request.json = {}
try:
jsonschema.validate(request.json, input_schema) # Parse the query string
except jsonschema.ValidationError as e: if len(request.query_string) > 0:
log.error("Invalid input query. JSON schema error: {}".format(e.message)) for (k, v) in urllib.parse.parse_qs(request.query_string).items():
raise aiohttp.web.HTTPBadRequest(text="Invalid JSON: {} in schema: {}".format( request.json[k] = v[0]
e.message,
json.dumps(e.schema))) if input_schema:
try:
jsonschema.validate(request.json, input_schema)
except jsonschema.ValidationError as e:
log.error("Invalid input query. JSON schema error: {}".format(e.message))
raise aiohttp.web.HTTPBadRequest(text="Invalid JSON: {} in schema: {}".format(
e.message,
json.dumps(e.schema)))
return request return request
@ -161,12 +172,13 @@ class Route(object):
if api_version is None or raw is True: if api_version is None or raw is True:
response = Response(request=request, route=route, output_schema=output_schema) response = Response(request=request, route=route, output_schema=output_schema)
request = yield from parse_request(request, None, raw)
yield from func(request, response) yield from func(request, response)
return response return response
# API call # API call
try: try:
request = yield from parse_request(request, input_schema) request = yield from parse_request(request, input_schema, raw)
record_file = server_config.get("record") record_file = server_config.get("record")
if record_file: if record_file:
try: try:

View File

@ -1,14 +1,19 @@
[Unit] [Unit]
Description=GNS3 server Description=GNS3 server
Wants=network-online.target
After=network.target network-online.target
[Service] [Service]
Type=forking Type=forking
Environment=statedir=/var/cache/gns3
PIDFile=/var/run/gns3.pid
ExecStart=/usr/local/bin/gns3server --log /var/log/gns3.log \
--pid /var/run/gns3.pid --daemon
Restart=on-abort
User=gns3 User=gns3
Group=gns3
PermissionsStartOnly=true
ExecStartPre=/bin/mkdir -p /var/log/gns3 /var/run/gns3
ExecStartPre=/bin/chown -R gns3:gns3 /var/log/gns3 /var/run/gns3
ExecStart=/usr/local/bin/gns3server --log /var/log/gns3/gns3.log \
--pid /var/run/gns3/gns3.pid --daemon
Restart=on-abort
PIDFile=/var/run/gns3/gns3.pid
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@ -1,5 +1,7 @@
jsonschema>=2.4.0 jsonschema>=2.4.0
aiohttp==0.21.5 aiohttp>=1.2.0
aiohttp_cors>=0.4.0
yarl>=0.7.0
Jinja2>=2.7.3 Jinja2>=2.7.3
raven>=5.2.0 raven>=5.2.0
psutil>=3.0.0 psutil>=3.0.0

View File

@ -26,6 +26,7 @@ function help {
echo "--with-openvpn: Install Open VPN" >&2 echo "--with-openvpn: Install Open VPN" >&2
echo "--with-iou: Install IOU" >&2 echo "--with-iou: Install IOU" >&2
echo "--with-i386-repository: Add i386 repositories require by IOU if they are not available on the system. Warning this will replace your source.list in order to use official ubuntu mirror" >&2 echo "--with-i386-repository: Add i386 repositories require by IOU if they are not available on the system. Warning this will replace your source.list in order to use official ubuntu mirror" >&2
echo "--unstable: Use the GNS3 unstable repository"
echo "--help: This help" >&2 echo "--help: This help" >&2
} }
@ -46,8 +47,9 @@ fi
USE_VPN=0 USE_VPN=0
USE_IOU=0 USE_IOU=0
I386_REPO=0 I386_REPO=0
UNSTABLE=0
TEMP=`getopt -o h --long with-openvpn,with-iou,with-i386-repository,help -n 'gns3-remote-install.sh' -- "$@"` TEMP=`getopt -o h --long with-openvpn,with-iou,with-i386-repository,unstable,help -n 'gns3-remote-install.sh' -- "$@"`
if [ $? != 0 ] if [ $? != 0 ]
then then
help help
@ -70,6 +72,10 @@ while true ; do
I386_REPO=1 I386_REPO=1
shift shift
;; ;;
--unstable)
UNSTABLE=1
shift
;;
-h|--help) -h|--help)
help help
exit 1 exit 1
@ -85,12 +91,23 @@ set -e
export DEBIAN_FRONTEND="noninteractive" export DEBIAN_FRONTEND="noninteractive"
log "Add GNS3 repository" log "Add GNS3 repository"
if [ $UNSTABLE == 1 ]
then
cat <<EOFLIST > /etc/apt/sources.list.d/gns3.list
deb http://ppa.launchpad.net/gns3/unstable/ubuntu trusty main
deb-src http://ppa.launchpad.net/gns3/unstable/ubuntu trusty main
deb http://ppa.launchpad.net/gns3/qemu/ubuntu trusty main
deb-src http://ppa.launchpad.net/gns3/qemu/ubuntu trusty main
EOFLIST
else
cat <<EOFLIST > /etc/apt/sources.list.d/gns3.list cat <<EOFLIST > /etc/apt/sources.list.d/gns3.list
deb http://ppa.launchpad.net/gns3/ppa/ubuntu trusty main deb http://ppa.launchpad.net/gns3/ppa/ubuntu trusty main
deb-src http://ppa.launchpad.net/gns3/ppa/ubuntu trusty main deb-src http://ppa.launchpad.net/gns3/ppa/ubuntu trusty main
deb http://ppa.launchpad.net/gns3/qemu/ubuntu trusty main deb http://ppa.launchpad.net/gns3/qemu/ubuntu trusty main
deb-src http://ppa.launchpad.net/gns3/qemu/ubuntu trusty main deb-src http://ppa.launchpad.net/gns3/qemu/ubuntu trusty main
EOFLIST EOFLIST
fi
if [ $I386_REPO == 1 ] if [ $I386_REPO == 1 ]
then then
@ -142,7 +159,7 @@ then
apt-get install -y gns3-iou apt-get install -y gns3-iou
# Force the host name to gns3vm # Force the host name to gns3vm
hostnamectl set-hostname gns3vm echo gns3vm > /etc/hostname
# Force hostid for IOU # Force hostid for IOU
dd if=/dev/zero bs=4 count=1 of=/etc/hostid dd if=/dev/zero bs=4 count=1 of=/etc/hostid

View File

@ -53,7 +53,7 @@ setup(
entry_points={ entry_points={
"console_scripts": [ "console_scripts": [
"gns3server = gns3server.main:main", "gns3server = gns3server.main:main",
"gns3vmnet = utils.vmnet:main", "gns3vmnet = gns3server.utils.vmnet:main",
] ]
}, },
packages=find_packages(".", exclude=["docs", "tests"]), packages=find_packages(".", exclude=["docs", "tests"]),

View File

@ -25,7 +25,7 @@ import asyncio
import aiohttp import aiohttp
import zipfile import zipfile
from unittest.mock import patch from unittest.mock import patch, MagicMock
from tests.utils import asyncio_patch from tests.utils import asyncio_patch
from gns3server.handlers.api.project_handler import ProjectHandler from gns3server.handlers.api.project_handler import ProjectHandler
@ -306,6 +306,19 @@ def test_export(server, tmpdir, loop, project):
assert content == b"hello" assert content == b"hello"
def test_export_include_image(server, tmpdir, loop, project):
project.export = MagicMock()
response = server.get("/projects/{project_id}/export".format(project_id=project.id), raw=True)
project.export.assert_called_with(include_images=False)
response = server.get("/projects/{project_id}/export?include_images=0".format(project_id=project.id), raw=True)
project.export.assert_called_with(include_images=False)
response = server.get("/projects/{project_id}/export?include_images=1".format(project_id=project.id), raw=True)
project.export.assert_called_with(include_images=True)
def test_import(server, tmpdir, loop, project): def test_import(server, tmpdir, loop, project):
with zipfile.ZipFile(str(tmpdir / "test.zip"), 'w') as myzip: with zipfile.ZipFile(str(tmpdir / "test.zip"), 'w') as myzip:

View File

@ -56,6 +56,12 @@ def test_invalid_file():
Qcow2("tests/resources/nvram_iou") Qcow2("tests/resources/nvram_iou")
def test_invalid_empty_file(tmpdir):
open(str(tmpdir / 'a'), 'w+').close()
with pytest.raises(Qcow2Error):
Qcow2(str(tmpdir / 'a'))
@pytest.mark.skipif(qemu_img() is None, reason="qemu-img is not available") @pytest.mark.skipif(qemu_img() is None, reason="qemu-img is not available")
def test_rebase(tmpdir, loop): def test_rebase(tmpdir, loop):
shutil.copy("tests/resources/empty8G.qcow2", str(tmpdir / "empty16G.qcow2")) shutil.copy("tests/resources/empty8G.qcow2", str(tmpdir / "empty16G.qcow2"))

View File

@ -22,7 +22,7 @@ import os
import sys import sys
import stat import stat
import re import re
from tests.utils import asyncio_patch from tests.utils import asyncio_patch, AsyncioMagicMock
from unittest import mock from unittest import mock
@ -434,6 +434,66 @@ def test_build_command(vm, loop, fake_qemu_binary, port_manager):
] ]
def test_build_command_kvm(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="2.3.2")
os.environ["DISPLAY"] = "0:0"
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
cmd = loop.run_until_complete(asyncio.async(vm._build_command()))
assert cmd == [
fake_qemu_binary,
"-name",
"test",
"-m",
"256M",
"-smp",
"cpus=1",
"-enable-kvm",
"-boot",
"order=c",
"-serial",
"telnet:127.0.0.1:{},server,nowait".format(vm.console),
"-net",
"none",
"-device",
"e1000,mac={}".format(vm._mac_address)
]
def test_build_command_kvm_2_4(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="2.4.2")
os.environ["DISPLAY"] = "0:0"
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
cmd = loop.run_until_complete(asyncio.async(vm._build_command()))
assert cmd == [
fake_qemu_binary,
"-name",
"test",
"-m",
"256M",
"-smp",
"cpus=1",
"-enable-kvm",
"-machine",
"smm=off",
"-boot",
"order=c",
"-serial",
"telnet:127.0.0.1:{},server,nowait".format(vm.console),
"-net",
"none",
"-device",
"e1000,mac={}".format(vm._mac_address)
]
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows") @pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
def test_build_command_without_display(vm, loop, fake_qemu_binary): def test_build_command_without_display(vm, loop, fake_qemu_binary):

View File

@ -21,7 +21,7 @@ import pytest
import sys import sys
from unittest.mock import MagicMock from unittest.mock import MagicMock
from gns3server.utils.asyncio import wait_run_in_executor, subprocess_check_output, wait_for_process_termination from gns3server.utils.asyncio import wait_run_in_executor, subprocess_check_output, wait_for_process_termination, locked_coroutine
def test_wait_run_in_executor(loop): def test_wait_run_in_executor(loop):
@ -67,3 +67,26 @@ def test_wait_for_process_termination(loop):
exec = wait_for_process_termination(process, timeout=0.5) exec = wait_for_process_termination(process, timeout=0.5)
with pytest.raises(asyncio.TimeoutError): with pytest.raises(asyncio.TimeoutError):
loop.run_until_complete(asyncio.async(exec)) loop.run_until_complete(asyncio.async(exec))
def test_lock_decorator(loop):
"""
The test check if the the second call to method_to_lock wait for the
first call to finish
"""
class TestLock:
def __init__(self):
self._test_val = 0
@locked_coroutine
def method_to_lock(self):
res = self._test_val
yield from asyncio.sleep(0.1)
self._test_val += 1
return res
i = TestLock()
res = set(loop.run_until_complete(asyncio.gather(i.method_to_lock(), i.method_to_lock())))
assert res == set((0, 1,)) # We use a set to test this to avoid order issue