mirror of
https://github.com/GNS3/gns3-server.git
synced 2025-06-25 01:59:14 +00:00
Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
19c4ec1867 | |||
8019374ed0 | |||
af530be346 | |||
9c3cfc4f4e | |||
c7d878ed9e | |||
49f1ee2e32 | |||
bd4de862c8 | |||
f038735595 | |||
a4f8675c93 | |||
da71f29208 | |||
b53b34d485 | |||
e63da227d0 | |||
c7d9af121f | |||
15babb137d | |||
eccee6b629 | |||
ef95ba1ed8 | |||
2bbdbeaa82 | |||
de2dad20d5 | |||
84c0a17572 | |||
f0edf799b7 | |||
a7be4681d5 | |||
07b982d4db | |||
da1cd9a3e7 | |||
0eafb6f06c | |||
042a69eecf | |||
1885fe62a6 | |||
e481ffa94c | |||
937bbf0131 | |||
d58a6ccda9 | |||
84fb108abb | |||
4455499e00 | |||
763f258465 | |||
d447a04c6a | |||
f358cb45a2 | |||
6b8e93f847 | |||
db95cb5c46 | |||
d6f63d3b7d | |||
7d90a73ed2 | |||
b1b2bbd581 | |||
da7074ea74 | |||
44307b43b9 | |||
febf0f7839 | |||
26d49f19c1 |
35
CHANGELOG
35
CHANGELOG
@ -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
61
conf/gns3_server.conf
Normal 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
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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))
|
||||||
|
@ -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)
|
||||||
|
@ -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")
|
||||||
|
@ -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")
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
2
setup.py
2
setup.py
@ -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"]),
|
||||||
|
@ -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:
|
||||||
|
@ -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"))
|
||||||
|
@ -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):
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user