Compare commits

...
62 Commits
Author SHA1 Message Date
Julien Duponchelle 26fc8236c9 1.3.7 2015-06-22 19:06:25 +02:00
Julien Duponchelle 4216c5d2ed Prevent install on Python 2
Fix #236
2015-06-17 14:08:22 +02:00
Julien Duponchelle afa4ba9b55 Crash report key for 1.3.7 2015-06-17 09:30:56 +02:00
grossmj 7980ae9ab1 Update version_info. 2015-06-16 15:00:25 -06:00
grossmj 3495035dbf Bump version to 1.3.7.dev1 2015-06-16 14:39:20 -06:00
Julien Duponchelle a2d4c2427d 1.3.6 2015-06-16 21:56:44 +02:00
Julien Duponchelle 0dae4b6930 1.3.6dev1 2015-06-16 19:32:53 +02:00
Julien Duponchelle 947a732bfb Remove netifaces dependencies due to IOUVM 2015-06-16 19:14:09 +02:00
Julien Duponchelle d88c5648de 1.3.5 2015-06-16 18:56:50 +02:00
Julien Duponchelle 94fbd3fac9 Ignore invalid characters when reading the output of a process
It should happend only when user try to use another binary and
the code of VPCS, dynamips... Will detect it's not the
correct binary.

For example we detect this error after an user used SupperPutty
instead of VPCS.

Fix #235
2015-06-16 15:48:59 +02:00
Julien Duponchelle 07eab6e766 Fix tests 2015-06-11 09:18:02 +02:00
Julien Duponchelle a2833cf276 Turn on / off authentication 2015-06-10 23:14:18 +02:00
Julien Duponchelle 079715bc18 Ensure no colored output on Windows
Fix #228
2015-06-09 10:05:00 +02:00
grossmj 5a32d8a779 Merge remote-tracking branch 'origin/master' 2015-06-08 11:28:58 -06:00
grossmj 9f1705a4f1 Do not stop saving IOS router configs when there is an exception while a project is committed. 2015-06-08 11:28:48 -06:00
Julien Duponchelle 02650fa490 Create a private config file if expected
Fix #217
2015-06-05 16:23:52 +02:00
Julien Duponchelle fff3e1474f Distribute our own version of netifaces working with python 3
Fix #97
2015-06-05 15:59:43 +02:00
Julien Duponchelle d9de1718b7 Fix crash if a private config exist in IOS but no private config file
Fix #217
2015-06-05 15:30:30 +02:00
Julien Duponchelle 78891ae00e Basic Auth support 2015-06-03 15:38:34 +02:00
Julien Duponchelle b344def887 Fix crash when virtualbox list of VMS return an empty line
Fix #206
2015-06-03 11:59:53 +02:00
Julien Duponchelle a1bc815f63 Update crash report key 2015-06-02 20:27:33 +02:00
Julien Duponchelle 668cc3f0a5 1.3.5dev1 2015-06-02 20:05:31 +02:00
Julien Duponchelle 42a8c7147a 1.3.4 release 2015-06-02 19:48:04 +02:00
grossmj a0fe9bb498 Control vm command has to be used instead of modify vm. Fixes #205. 2015-06-02 09:00:37 -06:00
Julien Duponchelle 57f9d875ca Fix AttributeError: 'NIONAT' object has no attribute 'lport' for
VirtualBox

Fix #205
2015-06-02 16:27:48 +02:00
Julien Duponchelle a2e51ac090 Avoid duplicate paths in qemu binary list
Fix #204
2015-06-02 15:35:14 +02:00
Julien Duponchelle 887f9b298e 1.3.4 Changelog 2015-06-02 14:44:49 +02:00
Julien Duponchelle d99047ce72 Drop useless dependencie
Fix #203
2015-06-02 14:40:29 +02:00
Jeremy ad27fdf8b9 Bump version to 1.3.4.dev2 2015-06-01 16:29:49 -06:00
Jeremy 9df290f192 Check if port or adapter is connected before starting/stopping a packet capture. Fixes #196. 2015-06-01 15:42:17 -06:00
Julien Duponchelle 05aafb9538 Revert "Start virtualbox VM one by one" because it doesn't fix the issue
This reverts commit da72a9501a.
2015-06-01 16:16:34 +02:00
Julien Duponchelle da72a9501a Start virtualbox VM one by one
Related to #190
2015-06-01 11:40:42 +02:00
Julien Duponchelle a2dfeab315 Load faulthandler module only for dev build 2015-05-28 12:17:56 +02:00
Julien Duponchelle 16cad8426a Enable faulthandler only for dev build 2015-05-28 12:17:25 +02:00
grossmj 0476f2932e Prevent users to add links to running Qemu VMs and start a capture on running VirtualBox VMs. 2015-05-27 13:56:27 -06:00
grossmj 91c0f05a4e Fixes bug: couldn't set PCMCIA disk1 size for IOS routers. 2015-05-27 10:17:46 -06:00
Julien Duponchelle 74ee73581a Fix crash if you pass an invalid hostname
Fix #198
2015-05-27 17:34:01 +02:00
Julien Duponchelle a86bac4214 Catch VPCS kill errors
Fix #199
2015-05-27 17:21:15 +02:00
Julien Duponchelle 8abf22ef24 Skip network interfaces on Travis 2015-05-27 16:45:39 +02:00
Julien Duponchelle 7cad25eb1a Raise a VirtualBox error if adapter doesn't exists
Fix #195
2015-05-27 16:38:57 +02:00
Julien Duponchelle ecf4e91e55 Ignore VirtualBox VM Name with a carriage return in name
Add tests for get_list of VirtualBox

Fix #200
2015-05-27 16:21:18 +02:00
Julien Duponchelle ea67f4aeb9 Test ok on Windows 2015-05-27 11:12:39 +02:00
Julien Duponchelle c98bcedd39 Cleanup the temporary project after modules have been notified of the
path change
2015-05-26 15:20:14 +02:00
Julien Duponchelle 528bb7a7c6 Do not return error if we can't remove the old project directory 2015-05-26 13:27:12 +02:00
Julien Duponchelle d31420b3e1 Script for starting gns3server in development mode on Windows 2015-05-26 13:06:08 +02:00
Julien Duponchelle 50d7a4f335 Catch encoding errors in windows logger 2015-05-26 13:05:37 +02:00
Julien Duponchelle 4216724d0b Give a reason for travis skip test 2015-05-26 12:00:13 +02:00
Julien Duponchelle c03c66ec48 Fix tests crash on travis 2015-05-26 11:35:06 +02:00
Julien Duponchelle dfd18f9483 Travis install netifaces 2015-05-26 10:56:35 +02:00
Julien Duponchelle 8636d3e337 Use setter for the qemu_path (allow to pass only the binary name) 2015-05-26 09:48:36 +02:00
grossmj c43b26d787 Merge remote-tracking branch 'origin/master' 2015-05-25 19:07:23 -06:00
grossmj 08f82e02a0 Fixes TAP connection when using VPCS. 2015-05-25 19:07:12 -06:00
Julien Duponchelle 33bca1a85c Fix tests on Windows 2015-05-21 12:01:37 +02:00
Julien Duponchelle 4d50d00b3e Fix test suite on Windows 2015-05-21 11:46:55 +02:00
Julien Duponchelle 21cc41fd16 Drop coveralls because it's create trouble to tests run on Windows 2015-05-21 11:05:04 +02:00
Julien Duponchelle f8d95291fa Test interfaces (it seem it's crash on Travis) 2015-05-21 10:45:07 +02:00
Julien Duponchelle 9fa873751d Fix crash launching qemu on OSX from another location.
It's append only when frozen an you launch the server by hand.

Fix #194
2015-05-18 11:58:56 +02:00
Julien Duponchelle 8c9758d16b I'm stupid... Remove fake segfault 2015-05-17 23:10:50 +02:00
Julien Duponchelle 0c5b753211 Add the fault handler in order to try to get a proper crash stack 2015-05-17 12:47:04 +02:00
grossmj 221a35baae Adds NAT NIO in device schema validation so they can return an error that it is not supported. 2015-05-14 20:54:38 -06:00
Julien Duponchelle 5bb870dc0f New crash report key 2015-05-14 19:28:26 +02:00
Julien Duponchelle 76be91d544 1.3.4dev1 2015-05-14 19:23:21 +02:00
33 changed files with 338 additions and 65 deletions

No files matched your search

+38
View File
@@ -1,5 +1,43 @@
# Change Log
## 1.3.7 22/06/2015
* Prevent install on Python 2
## 1.3.6 16/06/2015
* Fix an issue with 1.4dev compatibility
## 1.3.5 16/06/15
* Ignore invalid characters when reading the output of a process
* Turn on / off authentication
* Ensure no colored output on Windows
* Do not stop saving IOS router configs when there is an exception while a project is committed.
* Create a private config file if expected
* Distribute our own version of netifaces working with python 3
* Fix crash if a private config exist in IOS but no private config file
* Basic Auth support
* Fix crash when virtualbox list of VMS return an empty line
## 1.3.4 02/06/15
* Drop useless dependencie dateutil
* Check if port or adapter is connected before starting/stopping a packet capture. Fixes #196.
* Prevent users to add links to running Qemu VMs and start a capture on running VirtualBox VMs.
* Fixes bug: couldn't set PCMCIA disk1 size for IOS routers.
* Fix crash if you pass an invalid hostname
* Catch VPCS kill errors
* Raise a VirtualBox error if adapter doesn't exists
* Ignore VirtualBox VM Name with a carriage return in name
* Cleanup the temporary project after modules have been notified of the path change
* Do not return error if we can't remove the old project directory
* Catch encoding errors in windows logger
* Use setter for the qemu_path (allow to pass only the binary name)
* Fixes TAP connection when using VPCS.
* Fix crash launching qemu on OSX from another location.
* Adds NAT NIO in device schema validation so they can return an error that it is not supported.
## 1.3.3 14/05/15
* Check for empty iourc path.
-1
View File
@@ -6,4 +6,3 @@ pep8==1.5.7
pytest-timeout
pytest-capturelog
pytest-cov
python-coveralls
+2
View File
@@ -0,0 +1,2 @@
SET PYTHONPATH=.
python.exe gns3server/main.py --debug --local
+12 -1
View File
@@ -20,6 +20,7 @@ import sys
import struct
import platform
try:
import raven
RAVEN_AVAILABLE = True
@@ -34,13 +35,23 @@ import logging
log = logging.getLogger(__name__)
# Dev build
if __version__[4] != 0:
import faulthandler
# Display a traceback in case of segfault crash. Usefull when frozen
# Not enabled by default for security reason
log.info("Enable catching segfault")
faulthandler.enable()
class CrashReport:
"""
Report crash to a third party service
"""
DSN = "sync+https://45147533567b4d529ca09c093758681f:12d8b456cdb34d23aba771325aa64ee6@app.getsentry.com/38482"
DSN = "sync+https://3d569add80c74d4faecf90836bcdc1b5:3b5aa0c47c1847bc8d019b5e52ebdd1a@app.getsentry.com/38482"
if hasattr(sys, "frozen"):
cacert = os.path.join(os.getcwd(), "cacert.pem")
if os.path.isfile(cacert):
@@ -84,9 +84,11 @@ class ProjectHandler:
project.name = request.json.get("name", project.name)
project_path = request.json.get("path", project.path)
if project_path != project.path:
old_path = project.path
project.path = project_path
for module in MODULES:
yield from module.instance().project_moved(project)
yield from project.clean_old_path(old_path)
# Very important we need to remove temporary flag after moving the project
project.temporary = request.json.get("temporary", project.temporary)
response.json(project)
@@ -344,7 +344,7 @@ class VirtualBoxHandler:
vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
adapter_number = int(request.match_info["adapter_number"])
pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"])
vm.start_capture(adapter_number, pcap_file_path)
yield from vm.start_capture(adapter_number, pcap_file_path)
response.json({"pcap_file_path": pcap_file_path})
@Route.post(
+7 -1
View File
@@ -186,7 +186,13 @@ def main():
Project.clean_project_directory()
CrashReport.instance()
host = server_config["host"]
try:
host = server_config["host"].encode("idna").decode()
except UnicodeError:
log.critical("Invalid hostname %s", server_config["host"])
return
port = int(server_config["port"])
server = Server.instance(host, port)
try:
+5 -1
View File
@@ -210,7 +210,11 @@ class Dynamips(BaseManager):
# save the configs when the project is committed
for vm in self._vms.copy().values():
if vm.project.id == project.id:
yield from vm.save_configs()
try:
yield from vm.save_configs()
except DynamipsError as e:
log.warning(e)
continue
@property
def dynamips_path(self):
@@ -298,6 +298,9 @@ class EthernetSwitch(Device):
nio = self._nios[port_number]
if not nio:
raise DynamipsError("Port {} is not connected".format(port_number))
data_link_type = data_link_type.lower()
if data_link_type.startswith("dlt_"):
data_link_type = data_link_type[4:]
@@ -324,6 +327,10 @@ class EthernetSwitch(Device):
raise DynamipsError("Port {} is not allocated".format(port_number))
nio = self._nios[port_number]
if not nio:
raise DynamipsError("Port {} is not connected".format(port_number))
yield from nio.unbind_filter("both")
log.info('Ethernet switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
id=self._id,
+16 -1
View File
@@ -836,7 +836,7 @@ class Router(BaseVM):
return self._disk1
@asyncio.coroutine
def disk1(self, disk1):
def set_disk1(self, disk1):
"""
Sets the size (MB) for PCMCIA disk1.
@@ -1303,6 +1303,10 @@ class Router(BaseVM):
nio = adapter.get_nio(port_number)
if not nio:
raise DynamipsError("Port {slot_number}/{port_number} is not connected".format(slot_number=slot_number,
port_number=port_number))
if nio.input_filter[0] is not None and nio.output_filter[0] is not None:
raise DynamipsError("Port {port_number} has already a filter applied on {adapter}".format(adapter=adapter,
port_number=port_number))
@@ -1335,6 +1339,11 @@ class Router(BaseVM):
port_number=port_number))
nio = adapter.get_nio(port_number)
if not nio:
raise DynamipsError("Port {slot_number}/{port_number} is not connected".format(slot_number=slot_number,
port_number=port_number))
yield from nio.unbind_filter("both")
log.info('Router "{name}" [{id}]: stopping packet capture on port {slot_number}/{port_number}'.format(name=self._name,
@@ -1490,6 +1499,9 @@ class Router(BaseVM):
module_workdir = self.project.module_working_directory(self.manager.module_name.lower())
startup_config_base64, private_config_base64 = yield from self.extract_config()
if startup_config_base64:
if not self.startup_config:
self._startup_config = os.path.join("configs", "i{}_startup-config.cfg".format(self._dynamips_id))
try:
config = base64.b64decode(startup_config_base64).decode("utf-8", errors="replace")
config = "!\n" + config.replace("\r", "")
@@ -1501,6 +1513,9 @@ class Router(BaseVM):
raise DynamipsError("Could not save the startup configuration {}: {}".format(config_path, e))
if private_config_base64:
if not self.private_config:
self._private_config = os.path.join("configs", "i{}_private-config.cfg".format(self._dynamips_id))
try:
config = base64.b64decode(private_config_base64).decode("utf-8", errors="replace")
config = "!\n" + config.replace("\r", "")
+9 -5
View File
@@ -146,13 +146,17 @@ class Project:
self._path = path
self._update_temporary_file()
# The order of operation is important because we want to avoid losing
# data
if old_path:
@asyncio.coroutine
def clean_old_path(self, old_path):
"""
Called after a project location change. All the modules should
have been notified before
"""
if self._temporary:
try:
shutil.rmtree(old_path)
yield from wait_run_in_executor(shutil.rmtree, old_path)
except OSError as e:
raise aiohttp.web.HTTPConflict(text="Can't remove temporary directory {}: {}".format(old_path, e))
log.warn("Can't remove temporary directory {}: {}".format(old_path, e))
@property
def name(self):
+12 -8
View File
@@ -47,13 +47,13 @@ class Qemu(BaseManager):
"""
qemus = []
paths = []
paths = set()
try:
paths.append(os.getcwd())
paths.add(os.getcwd())
except FileNotFoundError:
log.warning("The current working directory doesn't exist")
if "PATH" in os.environ:
paths.extend(os.environ["PATH"].split(os.pathsep))
paths.update(os.environ["PATH"].split(os.pathsep))
else:
log.warning("The PATH environment variable doesn't exist")
# look for Qemu binaries in the current working directory and $PATH
@@ -64,17 +64,21 @@ class Qemu(BaseManager):
exec_dir = os.path.dirname(os.path.abspath(sys.executable))
for f in os.listdir(exec_dir):
if f.lower().startswith("qemu"):
paths.append(os.path.join(exec_dir, f))
paths.add(os.path.join(exec_dir, f))
if "PROGRAMFILES(X86)" in os.environ and os.path.exists(os.environ["PROGRAMFILES(X86)"]):
paths.append(os.path.join(os.environ["PROGRAMFILES(X86)"], "qemu"))
paths.add(os.path.join(os.environ["PROGRAMFILES(X86)"], "qemu"))
if "PROGRAMFILES" in os.environ and os.path.exists(os.environ["PROGRAMFILES"]):
paths.append(os.path.join(os.environ["PROGRAMFILES"], "qemu"))
paths.add(os.path.join(os.environ["PROGRAMFILES"], "qemu"))
elif sys.platform.startswith("darwin"):
# add specific locations on Mac OS X regardless of what's in $PATH
paths.extend(["/usr/local/bin", "/opt/local/bin"])
paths.update(["/usr/local/bin", "/opt/local/bin"])
if hasattr(sys, "frozen"):
paths.append(os.path.abspath(os.path.join(os.getcwd(), "../../../qemu/bin/")))
try:
paths.add(os.path.abspath(os.path.join(os.getcwd(), "../../../qemu/bin/")))
# If the user run the server by hand from outside
except FileNotFoundError:
paths.add("/Applications/GNS3.app/Contents/Resources/qemu/bin")
for path in paths:
try:
for f in os.listdir(path):
+4 -2
View File
@@ -69,7 +69,7 @@ class QemuVM(BaseVM):
self._stdout_file = ""
# QEMU VM settings
self._qemu_path = qemu_path
self.qemu_path = qemu_path
self._hda_disk_image = ""
self._hdb_disk_image = ""
self._hdc_disk_image = ""
@@ -741,6 +741,8 @@ class QemuVM(BaseVM):
adapter_number=adapter_number))
if self.is_running():
raise QemuError("Sorry, adding a link to a started Qemu VM is not supported.")
# FIXME: does the code below work? very undocumented feature...
# dynamically configure an UDP tunnel on the QEMU VM adapter
if nio and isinstance(nio, NIOUDP):
if self._legacy_networking:
@@ -751,7 +753,6 @@ class QemuVM(BaseVM):
nio.rport,
nio.rhost))
else:
# FIXME: does it work? very undocumented feature...
# Apparently there is a bug in Qemu...
# netdev_add [user|tap|socket|hubport|netmap],id=str[,prop=value][,...] -- add host network device
# netdev_del id -- remove host network device
@@ -785,6 +786,7 @@ class QemuVM(BaseVM):
adapter_number=adapter_number))
if self.is_running():
# FIXME: does the code below work? very undocumented feature...
# dynamically disable the QEMU VM adapter
yield from self._control_vm("host_net_remove {} gns3-{}".format(adapter_number, adapter_number))
yield from self._control_vm("host_net_add user vlan={},name=gns3-{}".format(adapter_number, adapter_number))
@@ -168,6 +168,8 @@ class VirtualBox(BaseManager):
vms = []
result = yield from self.execute("list", ["vms"])
for line in result:
if len(line) == 0 or line[0] != '"' or line[-1:] != "}":
continue # Broken output (perhaps a carriage return in VM name
vmname, _ = line.rsplit(' ', 1)
vmname = vmname.strip('"')
if vmname == "<inaccessible>":
+27 -10
View File
@@ -799,18 +799,22 @@ class VirtualBoxVM(BaseVM):
try:
adapter = self._ethernet_adapters[adapter_number]
except IndexError:
except KeyError:
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
adapter_number=adapter_number))
vm_state = yield from self._get_vm_state()
if vm_state == "running":
# dynamically configure an UDP tunnel on the VirtualBox adapter
yield from self._control_vm("nic{} generic UDPTunnel".format(adapter_number + 1))
yield from self._control_vm("nicproperty{} sport={}".format(adapter_number + 1, nio.lport))
yield from self._control_vm("nicproperty{} dest={}".format(adapter_number + 1, nio.rhost))
yield from self._control_vm("nicproperty{} dport={}".format(adapter_number + 1, nio.rport))
yield from self._control_vm("setlinkstate{} on".format(adapter_number + 1))
if isinstance(nio, NIOUDP):
# dynamically configure an UDP tunnel on the VirtualBox adapter
yield from self._control_vm("nic{} generic UDPTunnel".format(adapter_number + 1))
yield from self._control_vm("nicproperty{} sport={}".format(adapter_number + 1, nio.lport))
yield from self._control_vm("nicproperty{} dest={}".format(adapter_number + 1, nio.rhost))
yield from self._control_vm("nicproperty{} dport={}".format(adapter_number + 1, nio.rport))
yield from self._control_vm("setlinkstate{} on".format(adapter_number + 1))
elif isinstance(nio, NIONAT):
yield from self._control_vm("nic{} nat".format(adapter_number + 1))
yield from self._control_vm("setlinkstate{} on".format(adapter_number + 1))
adapter.add_nio(0, nio)
log.info("VirtualBox VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name,
@@ -830,7 +834,7 @@ class VirtualBoxVM(BaseVM):
try:
adapter = self._ethernet_adapters[adapter_number]
except IndexError:
except KeyError:
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
adapter_number=adapter_number))
@@ -851,6 +855,7 @@ class VirtualBoxVM(BaseVM):
adapter_number=adapter_number))
return nio
@asyncio.coroutine
def start_capture(self, adapter_number, output_file):
"""
Starts a packet capture.
@@ -861,11 +866,19 @@ class VirtualBoxVM(BaseVM):
try:
adapter = self._ethernet_adapters[adapter_number]
except IndexError:
except KeyError:
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
adapter_number=adapter_number))
vm_state = yield from self._get_vm_state()
if vm_state == "running" or vm_state == "paused" or vm_state == "stuck":
raise VirtualBoxError("Sorry, packet capturing on a started VirtualBox VM is not supported.")
nio = adapter.get_nio(0)
if not nio:
raise VirtualBoxError("Adapter {} is not connected".format(adapter_number))
if nio.capturing:
raise VirtualBoxError("Packet capture is already activated on adapter {adapter_number}".format(adapter_number=adapter_number))
@@ -883,11 +896,15 @@ class VirtualBoxVM(BaseVM):
try:
adapter = self._ethernet_adapters[adapter_number]
except IndexError:
except KeyError:
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
adapter_number=adapter_number))
nio = adapter.get_nio(0)
if not nio:
raise VirtualBoxError("Adapter {} is not connected".format(adapter_number))
nio.stopPacketCapture()
log.info("VirtualBox VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(name=self.name,
+7 -5
View File
@@ -254,7 +254,10 @@ class VPCSVM(BaseVM):
except asyncio.TimeoutError:
if self._process.returncode is None:
log.warn("VPCS process {} is still running... killing it".format(self._process.pid))
self._process.kill()
try:
self._process.kill()
except OSError as e:
raise VPCSError("Can not stop the VPCS process: {}".format(e))
self._process = None
self._started = False
@@ -390,6 +393,9 @@ class VPCSVM(BaseVM):
command = [self.vpcs_path]
command.extend(["-p", str(self._console)]) # listen to console port
command.extend(["-m", str(self._manager.get_mac_id(self.id))]) # the unique ID is used to set the MAC address offset
command.extend(["-i", "1"]) # option to start only one VPC instance
command.extend(["-F"]) # option to avoid the daemonization of VPCS
nio = self._ethernet_adapter.get_nio(0)
if nio:
@@ -404,10 +410,6 @@ class VPCSVM(BaseVM):
command.extend(["-e"])
command.extend(["-d", nio.tap_device])
command.extend(["-m", str(self._manager.get_mac_id(self.id))]) # the unique ID is used to set the MAC address offset
command.extend(["-i", "1"]) # option to start only one VPC instance
command.extend(["-F"]) # option to avoid the daemonization of VPCS
if self.script_file:
command.extend([os.path.basename(self.script_file)])
return command
+11
View File
@@ -218,6 +218,16 @@ DEVICE_NIO_SCHEMA = {
"required": ["type", "ethernet_device"],
"additionalProperties": False
},
"NAT": {
"description": "NAT Network Input/Output",
"properties": {
"type": {
"enum": ["nio_nat"]
},
},
"required": ["type"],
"additionalProperties": False
},
"TAP": {
"description": "TAP Network Input/Output",
"properties": {
@@ -291,6 +301,7 @@ DEVICE_NIO_SCHEMA = {
{"$ref": "#/definitions/UDP"},
{"$ref": "#/definitions/Ethernet"},
{"$ref": "#/definitions/LinuxEthernet"},
{"$ref": "#/definitions/NAT"},
{"$ref": "#/definitions/TAP"},
{"$ref": "#/definitions/UNIX"},
{"$ref": "#/definitions/VDE"},
+4 -1
View File
@@ -51,7 +51,10 @@ def subprocess_check_output(*args, cwd=None, env=None):
output = yield from proc.stdout.read()
if output is None:
return ""
return output.decode("utf-8")
# If we received garbage we ignore invalid characters
# it should happend only when user try to use another binary
# and the code of VPCS, dynamips... Will detect it's not the correct binary
return output.decode("utf-8", errors="ignore")
@asyncio.coroutine
+2 -2
View File
@@ -23,5 +23,5 @@
# or negative for a release candidate or beta (after the base version
# number has been incremented)
__version__ = "1.3.3"
__version_info__ = (1, 3, 3, 0)
__version__ = "1.3.7"
__version_info__ = (1, 3, 7, 0)
+20 -2
View File
@@ -34,7 +34,7 @@ class ColouredFormatter(logging.Formatter):
message = super().format(record)
if not colour:
if not colour or sys.platform.startswith("win"):
return message.replace("#RESET#", "")
level_no = record.levelno
@@ -81,12 +81,30 @@ class ColouredStreamHandler(logging.StreamHandler):
self.handleError(record)
class WinStreamHandler(logging.StreamHandler):
def emit(self, record):
if sys.stdin.encoding != "utf-8":
record = record
stream = self.stream
try:
msg = self.formatter.format(record, stream.isatty())
stream.write(msg.encode(stream.encoding, errors="replace").decode(stream.encoding))
stream.write(self.terminator)
self.flush()
pass
except Exception:
self.handleError(record)
def init_logger(level, logfile=None, quiet=False):
if logfile and len(logfile) > 0:
stream_handler = logging.FileHandler(logfile)
stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {filename}:{lineno} {message}", "%Y-%m-%d %H:%M:%S", "{")
elif sys.platform.startswith("win"):
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler = WinStreamHandler(sys.stdout)
stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {filename}:{lineno} {message}", "%Y-%m-%d %H:%M:%S", "{")
else:
stream_handler = ColouredStreamHandler(sys.stdout)
+32 -1
View File
@@ -83,6 +83,31 @@ class Route(object):
def delete(cls, path, *args, **kw):
return cls._route('DELETE', path, *args, **kw)
@classmethod
def authenticate(cls, request, route, server_config):
"""
Ask user for authentication
:returns: Response if you need to auth the user otherwise None
"""
if not server_config.getboolean("auth", False):
return
user = server_config.get("user", "").strip()
password = server_config.get("password", "").strip()
if len(user) == 0:
return
if "AUTHORIZATION" in request.headers:
if request.headers["AUTHORIZATION"] == aiohttp.helpers.BasicAuth(user, password).encode():
return
response = Response(request=request, route=route)
response.set_status(401)
response.headers["WWW-Authenticate"] = 'Basic realm="GNS3 server"'
return response
@classmethod
def _route(cls, method, path, *args, **kw):
# This block is executed only the first time
@@ -118,6 +143,13 @@ class Route(object):
def control_schema(request):
# This block is executed at each method call
server_config = Config.instance().get_section_config("Server")
# Authenticate
response = cls.authenticate(request, route, server_config)
if response:
return response
# Non API call
if api_version is None:
response = Response(request=request, route=route, output_schema=output_schema)
@@ -127,7 +159,6 @@ class Route(object):
# API call
try:
request = yield from parse_request(request, input_schema)
server_config = Config.instance().get_section_config("Server")
record_file = server_config.get("record")
if record_file:
try:
+1 -2
View File
@@ -1,6 +1,5 @@
netifaces==0.10.4
gns3-netifaces==0.10.4.1
jsonschema==2.4.0
python-dateutil==2.3
aiohttp==0.14.4
Jinja2==2.7.3
raven==5.2.0
+11 -5
View File
@@ -19,6 +19,10 @@ import sys
from setuptools import setup, find_packages
from setuptools.command.test import test as TestCommand
# we only support Python 3 version >= 3.3
if sys.version_info < (3, 3):
raise SystemExit("Python 3.3 or higher is required")
class PyTest(TestCommand):
@@ -34,12 +38,14 @@ class PyTest(TestCommand):
sys.exit(errcode)
dependencies = ["aiohttp>=0.14.4",
"jsonschema>=2.4.0",
"Jinja2>=2.7.3",
"raven>=5.2.0"]
dependencies = [
# "gns3-netifaces>=0.10.4.1",
"aiohttp>=0.14.4",
"jsonschema>=2.4.0",
"Jinja2>=2.7.3",
"raven>=5.2.0"]
#if not sys.platform.startswith("win"):
# if not sys.platform.startswith("win"):
# dependencies.append("netifaces==0.10.4")
if sys.version_info == (3, 3):
+1
View File
@@ -136,6 +136,7 @@ def run_around_tests(monkeypatch):
config = Config.instance()
config.clear()
config.set("Server", "project_directory", tmppath)
config.set("Server", "auth", False)
# Prevent exectuions of the VM if we forgot to mock something
config.set("VirtualBox", "vboxmanage_path", tmppath)
+5
View File
@@ -15,6 +15,9 @@
# 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 os
import pytest
def test_udp_allocation(server, project):
response = server.post('/projects/{}/ports/udp'.format(project.id), {}, example=True)
@@ -22,6 +25,8 @@ def test_udp_allocation(server, project):
assert response.json == {'udp_port': 10000}
# Netfifaces is not available on Travis
@pytest.mark.skipif(os.environ.get("TRAVIS", False) is not False, reason="Not supported on Travis")
def test_interfaces(server):
response = server.get('/interfaces', example=True)
assert response.status == 200
+30 -4
View File
@@ -20,6 +20,7 @@ This test suite check /project endpoint
"""
import uuid
import os
from unittest.mock import patch
from tests.utils import asyncio_patch
@@ -85,18 +86,43 @@ def test_update_temporary_project(server):
assert response.json["temporary"] is False
def test_update_path_project(server, tmpdir):
def test_update_path_project_temporary(server, tmpdir):
os.makedirs(str(tmpdir / "a"))
os.makedirs(str(tmpdir / "b"))
with patch("gns3server.modules.project.Project.is_local", return_value=True):
response = server.post("/projects", {"name": "first_name"})
response = server.post("/projects", {"name": "first_name", "path": str(tmpdir / "a"), "temporary": True})
assert response.status == 201
assert response.json["name"] == "first_name"
query = {"name": "second_name", "path": str(tmpdir)}
query = {"name": "second_name", "path": str(tmpdir / "b")}
response = server.put("/projects/{project_id}".format(project_id=response.json["project_id"]), query, example=True)
assert response.status == 200
assert response.json["path"] == str(tmpdir)
assert response.json["path"] == str(tmpdir / "b")
assert response.json["name"] == "second_name"
assert not os.path.exists(str(tmpdir / "a"))
assert os.path.exists(str(tmpdir / "b"))
def test_update_path_project_non_temporary(server, tmpdir):
os.makedirs(str(tmpdir / "a"))
os.makedirs(str(tmpdir / "b"))
with patch("gns3server.modules.project.Project.is_local", return_value=True):
response = server.post("/projects", {"name": "first_name", "path": str(tmpdir / "a")})
assert response.status == 201
assert response.json["name"] == "first_name"
query = {"name": "second_name", "path": str(tmpdir / "b")}
response = server.put("/projects/{project_id}".format(project_id=response.json["project_id"]), query, example=True)
assert response.status == 200
assert response.json["path"] == str(tmpdir / "b")
assert response.json["name"] == "second_name"
assert os.path.exists(str(tmpdir / "a"))
assert os.path.exists(str(tmpdir / "b"))
def test_update_path_project_non_local(server, tmpdir):
+2 -2
View File
@@ -42,7 +42,7 @@ def test_vpcs_get(server, project, vm):
assert response.route == "/projects/{project_id}/vpcs/vms/{vm_id}"
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == project.id
assert response.json["startup_script_path"] == None
assert response.json["startup_script_path"] is None
def test_vpcs_create_startup_script(server, project):
@@ -51,7 +51,7 @@ def test_vpcs_create_startup_script(server, project):
assert response.route == "/projects/{project_id}/vpcs/vms"
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == project.id
assert response.json["startup_script"] == "ip 192.168.1.2\necho TEST"
assert response.json["startup_script"] == os.linesep.join(["ip 192.168.1.2", "echo TEST"])
assert response.json["startup_script_path"] == "startup.vpc"
+3 -3
View File
@@ -19,7 +19,7 @@
import aiohttp
import os
from unittest.mock import patch
from gns3server.config import Config
def test_index_upload(server):
response = server.get('/upload', api_version=None)
@@ -37,8 +37,8 @@ def test_upload(server, tmpdir):
body.add_field("type", "QEMU")
body.add_field("file", open(str(tmpdir / "test"), "rb"), content_type="application/iou", filename="test2")
with patch("gns3server.config.Config.get_section_config", return_value={"images_path": str(tmpdir)}):
response = server.post('/upload', api_version=None, body=body, raw=True)
Config.instance().set("Server", "images_path", str(tmpdir))
response = server.post('/upload', api_version=None, body=body, raw=True)
with open(str(tmpdir / "QEMU" / "test2")) as f:
assert f.read() == "TEST"
-4
View File
@@ -73,10 +73,6 @@ def test_changing_path_temporary_flag(tmpdir):
assert os.path.exists(os.path.join(p.path, ".gns3_temporary"))
p.path = str(tmpdir)
p.temporary = False
assert not os.path.exists(os.path.join(p.path, ".gns3_temporary"))
assert not os.path.exists(os.path.join(str(tmpdir), ".gns3_temporary"))
assert not os.path.exists(original_path)
def test_temporary_path():
@@ -20,10 +20,14 @@ import pytest
import tempfile
import os
import stat
import asyncio
from unittest.mock import patch
from gns3server.modules.virtualbox import VirtualBox
from gns3server.modules.virtualbox.virtualbox_error import VirtualBoxError
from unittest.mock import patch
from tests.utils import asyncio_patch
@pytest.fixture(scope="module")
@@ -65,3 +69,31 @@ def test_vboxmanage_path(manager, tmpdir):
tmpfile = tempfile.NamedTemporaryFile()
with patch("gns3server.config.Config.get_section_config", return_value={"vboxmanage_path": path}):
assert manager.find_vboxmanage() == path
def test_get_list(manager, loop):
vm_list = ['"Windows 8.1" {27b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}',
'"Carriage',
'Return" {27b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}',
'',
'"<inaccessible>" {42b4d095-ff5f-4ac4-bb9d-5f2c7861c1f1}',
'"Linux Microcore 4.7.1" {ccd8c50b-c172-457d-99fa-dd69371ede0e}']
@asyncio.coroutine
def execute_mock(cmd, args):
if cmd == "list":
return vm_list
else:
if args[0] == "Windows 8.1":
return ["memory=512"]
elif args[0] == "Linux Microcore 4.7.1":
return ["memory=256"]
assert False, "Unknow {} {}".format(cmd, args)
with asyncio_patch("gns3server.modules.virtualbox.VirtualBox.execute") as mock:
mock.side_effect = execute_mock
vms = loop.run_until_complete(asyncio.async(manager.get_list()))
assert vms == [
{"vmname": "Windows 8.1", "ram": 512},
{"vmname": "Linux Microcore 4.7.1", "ram": 256}
]
@@ -54,3 +54,9 @@ def test_vm_invalid_virtualbox_api_version(loop, project, manager):
with pytest.raises(VirtualBoxError):
vm = VirtualBoxVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager, "test", False)
loop.run_until_complete(asyncio.async(vm.create()))
def test_vm_adapter_add_nio_binding_adapter_not_exist(loop, vm, manager, free_console_port):
nio = manager.create_nio(manager.vboxmanage_path, {"type": "nio_udp", "lport": free_console_port, "rport": free_console_port, "rhost": "192.168.1.2"})
with pytest.raises(VirtualBoxError):
loop.run_until_complete(asyncio.async(vm.adapter_add_nio_binding(15, nio)))
+2 -2
View File
@@ -190,9 +190,9 @@ def test_update_startup_script_h(vm):
def test_get_startup_script(vm):
content = "echo GNS3 VPCS\nip 192.168.1.2\n"
content = "echo GNS3 VPCS\nip 192.168.1.2"
vm.startup_script = content
assert vm.startup_script == content
assert vm.startup_script == os.linesep.join(["echo GNS3 VPCS","ip 192.168.1.2"])
def test_get_startup_script_using_default_script(vm):
+24
View File
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 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/>.
from gns3server.utils.interfaces import interfaces
def test_interfaces():
# This test should pass on all platforms without crash
assert isinstance(interfaces(), list)