Compare commits

..

175 Commits

Author SHA1 Message Date
26fc8236c9 1.3.7 2015-06-22 19:06:25 +02:00
4216c5d2ed Prevent install on Python 2
Fix #236
2015-06-17 14:08:22 +02:00
afa4ba9b55 Crash report key for 1.3.7 2015-06-17 09:30:56 +02:00
7980ae9ab1 Update version_info. 2015-06-16 15:00:25 -06:00
3495035dbf Bump version to 1.3.7.dev1 2015-06-16 14:39:20 -06:00
a2d4c2427d 1.3.6 2015-06-16 21:56:44 +02:00
0dae4b6930 1.3.6dev1 2015-06-16 19:32:53 +02:00
947a732bfb Remove netifaces dependencies due to IOUVM 2015-06-16 19:14:09 +02:00
d88c5648de 1.3.5 2015-06-16 18:56:50 +02:00
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
07eab6e766 Fix tests 2015-06-11 09:18:02 +02:00
a2833cf276 Turn on / off authentication 2015-06-10 23:14:18 +02:00
079715bc18 Ensure no colored output on Windows
Fix #228
2015-06-09 10:05:00 +02:00
5a32d8a779 Merge remote-tracking branch 'origin/master' 2015-06-08 11:28:58 -06:00
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
02650fa490 Create a private config file if expected
Fix #217
2015-06-05 16:23:52 +02:00
fff3e1474f Distribute our own version of netifaces working with python 3
Fix #97
2015-06-05 15:59:43 +02:00
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
78891ae00e Basic Auth support 2015-06-03 15:38:34 +02:00
b344def887 Fix crash when virtualbox list of VMS return an empty line
Fix #206
2015-06-03 11:59:53 +02:00
a1bc815f63 Update crash report key 2015-06-02 20:27:33 +02:00
668cc3f0a5 1.3.5dev1 2015-06-02 20:05:31 +02:00
42a8c7147a 1.3.4 release 2015-06-02 19:48:04 +02:00
a0fe9bb498 Control vm command has to be used instead of modify vm. Fixes #205. 2015-06-02 09:00:37 -06:00
57f9d875ca Fix AttributeError: 'NIONAT' object has no attribute 'lport' for
VirtualBox

Fix #205
2015-06-02 16:27:48 +02:00
a2e51ac090 Avoid duplicate paths in qemu binary list
Fix #204
2015-06-02 15:35:14 +02:00
887f9b298e 1.3.4 Changelog 2015-06-02 14:44:49 +02:00
d99047ce72 Drop useless dependencie
Fix #203
2015-06-02 14:40:29 +02:00
ad27fdf8b9 Bump version to 1.3.4.dev2 2015-06-01 16:29:49 -06:00
9df290f192 Check if port or adapter is connected before starting/stopping a packet capture. Fixes #196. 2015-06-01 15:42:17 -06:00
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
da72a9501a Start virtualbox VM one by one
Related to #190
2015-06-01 11:40:42 +02:00
a2dfeab315 Load faulthandler module only for dev build 2015-05-28 12:17:56 +02:00
16cad8426a Enable faulthandler only for dev build 2015-05-28 12:17:25 +02:00
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
91c0f05a4e Fixes bug: couldn't set PCMCIA disk1 size for IOS routers. 2015-05-27 10:17:46 -06:00
74ee73581a Fix crash if you pass an invalid hostname
Fix #198
2015-05-27 17:34:01 +02:00
a86bac4214 Catch VPCS kill errors
Fix #199
2015-05-27 17:21:15 +02:00
8abf22ef24 Skip network interfaces on Travis 2015-05-27 16:45:39 +02:00
7cad25eb1a Raise a VirtualBox error if adapter doesn't exists
Fix #195
2015-05-27 16:38:57 +02:00
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
ea67f4aeb9 Test ok on Windows 2015-05-27 11:12:39 +02:00
c98bcedd39 Cleanup the temporary project after modules have been notified of the
path change
2015-05-26 15:20:14 +02:00
528bb7a7c6 Do not return error if we can't remove the old project directory 2015-05-26 13:27:12 +02:00
d31420b3e1 Script for starting gns3server in development mode on Windows 2015-05-26 13:06:08 +02:00
50d7a4f335 Catch encoding errors in windows logger 2015-05-26 13:05:37 +02:00
4216724d0b Give a reason for travis skip test 2015-05-26 12:00:13 +02:00
c03c66ec48 Fix tests crash on travis 2015-05-26 11:35:06 +02:00
dfd18f9483 Travis install netifaces 2015-05-26 10:56:35 +02:00
8636d3e337 Use setter for the qemu_path (allow to pass only the binary name) 2015-05-26 09:48:36 +02:00
c43b26d787 Merge remote-tracking branch 'origin/master' 2015-05-25 19:07:23 -06:00
08f82e02a0 Fixes TAP connection when using VPCS. 2015-05-25 19:07:12 -06:00
33bca1a85c Fix tests on Windows 2015-05-21 12:01:37 +02:00
4d50d00b3e Fix test suite on Windows 2015-05-21 11:46:55 +02:00
21cc41fd16 Drop coveralls because it's create trouble to tests run on Windows 2015-05-21 11:05:04 +02:00
f8d95291fa Test interfaces (it seem it's crash on Travis) 2015-05-21 10:45:07 +02:00
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
8c9758d16b I'm stupid... Remove fake segfault 2015-05-17 23:10:50 +02:00
0c5b753211 Add the fault handler in order to try to get a proper crash stack 2015-05-17 12:47:04 +02:00
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
5bb870dc0f New crash report key 2015-05-14 19:28:26 +02:00
76be91d544 1.3.4dev1 2015-05-14 19:23:21 +02:00
078b72cafd Version 1.3.3 2015-05-14 18:57:30 +02:00
b2457e0b3b Check for empty iourc path. 2015-05-13 16:05:54 -06:00
2531a05adc Merge remote-tracking branch 'origin/master' 2015-05-13 15:54:03 -06:00
dd9f62158f Fixes bugs with IOS router configs. Fixes #354. 2015-05-13 15:53:58 -06:00
a3c0f0754e Fix crash 2015-05-13 23:27:51 +02:00
157bc18ebd Use a temporary directory as egg cache
We have use with broken permission on their
system. We try to workaround the issue.

Fix #182
2015-05-13 14:29:03 +02:00
3704911c2d Fix tests 2015-05-13 10:19:50 +02:00
1e38b11f34 Catch crash error in IOU in case of permission denied
Fix #186
2015-05-13 10:16:24 +02:00
bebdadc465 Bump version to 1.3.3.dev3 2015-05-07 11:52:17 -06:00
b0ce091a4c 1.3.3rc1 2015-05-07 16:03:51 +02:00
d21469a916 Return an error if an adapter slot doesn't exist on an IOS router. 2015-05-06 17:22:07 -06:00
b57a023394 NIO NAT support for VirtualBox VMs. 2015-05-06 15:21:39 -06:00
a929dfea38 Merge remote-tracking branch 'origin/master' 2015-05-06 14:59:35 -06:00
fcff2d0813 NIO NAT support for QEMU VMs (user mode back-end is used). 2015-05-06 14:59:01 -06:00
ed39afbf3d Throw an error if user put an invalid port range in config file
Fix #117
2015-05-06 10:40:51 +02:00
3ba4789ba6 New crash report key
Fix #180
2015-05-06 09:55:14 +02:00
17b93e6a89 Bump version to 1.3.3dev2 2015-05-05 14:49:03 -06:00
4b21135ba7 Turn off configuration parser interpolation 2015-05-05 11:53:33 +02:00
a3f00e1f45 Catch configuration file parsing errors
Fix #176
2015-05-05 11:44:35 +02:00
b7dac1bec4 Force closing the event loop to avoid warning with Python 3.4.3
Fix #177
2015-05-05 11:33:47 +02:00
18c4154376 Catch error when you can't mark a project as no longer temporary
Fix #172
2015-05-05 10:51:51 +02:00
22efc7488f Catch BrokenPipeError for OSX frozen server
Fix #166
2015-05-05 10:46:09 +02:00
9eeb8910fb Match how IOU initial-config is set for VPCS VM. 2015-05-04 21:54:56 -06:00
71e2586e17 Refactors how startup-config and private-config are handled for IOS routers. 2015-05-04 18:42:32 -06:00
ee2dada88b Fix tests 2015-05-04 21:29:28 +02:00
c4054cf810 Catch the "WinError 0 The operation completed successfully" exception at a higher level. 2015-05-04 12:14:04 -06:00
d2d91ebdea Fix temporary project not cleanup with save as 2015-05-04 14:04:57 +02:00
0dea63c9ea If image is not found in VM directory look in images folder 2015-05-04 10:57:08 +02:00
3467b42ab5 Ordered MAC addresses for QEMU based VMs. 2015-05-03 13:18:18 -06:00
65103e9332 Merge remote-tracking branch 'origin/master' 2015-05-03 11:41:09 -06:00
f6bc823b58 Fixes #171. 2015-05-03 11:40:55 -06:00
151788e48a Force utf-8 configuraton files reading
Fix #170
2015-05-01 17:49:16 +02:00
6b70fa9794 Do not list file starting with a . in upload handler 2015-05-01 10:55:08 +02:00
359abb0286 Revert "Merge branch 'unstable'"
This reverts commit 929c337e8b, reversing
changes made to b9bc73fd01.
2015-04-30 16:43:30 +02:00
d18293ae7c Fixes list images in VirtualBox. 2015-04-29 22:17:17 -06:00
929c337e8b Merge branch 'unstable'
Conflicts:
	gns3server/modules/virtualbox/__init__.py
	gns3server/version.py
	tests/modules/test_manager.py
2015-04-29 16:29:45 -06:00
b9bc73fd01 Do not crash when closing a project if VirtualBox is not accessible
Fix #164
2015-04-29 14:24:27 +02:00
e75fbc9d73 Catch connection reset errors
Fix #162
2015-04-29 11:15:32 +02:00
0311a0086e Fixes typo. 2015-04-28 22:16:15 -06:00
461e3ce53f 1.3.3dev1 2015-04-28 21:49:48 +02:00
1b4613fbaf Version 1.3.2 2015-04-28 21:06:04 +02:00
30ff5510d9 Merge remote-tracking branch 'origin/master' 2015-04-28 12:02:33 -06:00
cc03017739 Cleanup the VirtualBox Media Manager after closing a project. Fixes #145. 2015-04-28 12:02:21 -06:00
bad740d32a Fix test on Linux 2015-04-28 15:31:00 +02:00
a884af983f Avoid Cygwin warning with VPCS on Windows. 2015-04-27 22:23:27 -06:00
4f021054e0 Merge pull request #158 from GNS3/keep_iou_config
Do not erase the IOU initial-config if there is one when creating the IOU VM.
2015-04-27 18:37:17 -06:00
8503472c77 Close VirtualBox VM linked clone disks after the VM is unregistered. Fixes #145. 2015-04-27 17:10:32 -06:00
e7ae1776f4 Final fixes for windows test suite 2015-04-27 23:28:12 +02:00
3f26ada081 Comment broken test 2015-04-27 23:20:01 +02:00
77f54848e3 Fix some tests on Windows 2015-04-27 23:12:13 +02:00
bf3444933e Fix test qemu now raise 409 if nio_ethernet 2015-04-27 22:54:24 +02:00
f208b472a1 TAP interface support for QEMU VMs. Fixes #153. 2015-04-27 14:38:15 -06:00
b6a935aeb8 Return an explicit error when a NIO type is not supported by a VM. 2015-04-27 14:19:17 -06:00
324a4f73d0 Do not erase the IOU config 2015-04-27 18:22:54 +02:00
d5ae4750e9 Do not load IOU handler on Windows during tests
Fix #159
2015-04-27 16:21:56 +02:00
4df95efdec Skip IOU test on Windows
Fix #159
2015-04-27 15:09:42 +02:00
834a554fea Fix VPCS tests 2015-04-27 10:14:46 +02:00
271cb527d4 Explicit utf-8 decoding. 2015-04-26 21:19:39 -06:00
6edf1e3649 Check NIO exists when stopping an IOU capture. 2015-04-26 21:15:15 -06:00
017997e0a3 Fixes c7200 NPE setting. 2015-04-26 18:35:12 -06:00
3e6996903f Fixes VPCS process termination. 2015-04-26 12:57:06 -06:00
da2b895c99 Catch FileNotFoundError exception in os.getcwd() 2015-04-26 12:49:29 -06:00
683a512917 Fixes #150. 2015-04-25 15:20:15 -06:00
80a0e0ebf7 Explicit utf-8 encoding where necessary to avoid Unicode errors on Windows (we require/set an utf-8 locale on other systems). 2015-04-25 11:58:34 -06:00
d68bf1c263 Removes unnecessary sleep in VirtualBox VM. 2015-04-25 09:36:28 -06:00
fa544ef888 Fixes #270. Relative paths management with empty ones. 2015-04-24 17:27:32 -06:00
ee1e5b8204 Merge pull request #156 from GNS3/daemon
Server daemonization support.
2015-04-24 16:38:59 -06:00
8f6e5b4ad8 Merge branch 'unstable' into daemon
Conflicts:
	gns3server/main.py
2015-04-24 16:37:56 -06:00
24bfd8ab53 New crash report key and doesn't send report for developers 2015-04-24 18:30:31 +02:00
14cc01bb8b Merge pull request #152 from GNS3/api_list_images
API in order to get the list of IOU, Dynamips, Qemu images.
2015-04-23 17:20:15 -06:00
40ce22222e Merge branch 'unstable' into api_list_images
Conflicts:
	gns3server/handlers/api/iou_handler.py
2015-04-23 17:19:37 -06:00
7e991cc404 Merge pull request #154 from GNS3/wireshark_remote_capture
Support for Wireshark remote packet captures.
2015-04-23 16:17:44 -06:00
83f2509cfe Cleanup exceptions 2015-04-23 16:56:46 +02:00
6b862b8397 Correctly show the host in templates
Fix #157
2015-04-23 14:32:10 +02:00
fac0f5ecd9 Proper daemon support 2015-04-23 11:10:02 +02:00
3680c40e23 Catch COM errors when connecting to WMI. 2015-04-23 00:03:44 -06:00
30f6263146 Don't assume the PATH environment variable exists. 2015-04-22 21:42:36 -06:00
161adb781b Use UUIDs instead of the VM names for VirtualBox pipe paths. 2015-04-22 20:29:52 -06:00
2e39265da1 Merge branch 'master' into unstable 2015-04-22 17:34:17 +02:00
9c549b175f Add --log options for daemon support 2015-04-22 17:28:58 +02:00
fc289fd868 Basic upstart script 2015-04-22 17:13:39 +02:00
bf618d321c Max 100 thread executor 2015-04-22 10:39:43 +02:00
8b879c0614 Support wireshark remote capture 2015-04-20 19:27:07 +02:00
cf0adf56a8 Merge branch 'master' into unstable 2015-04-20 19:26:27 +02:00
531e95463c Add qemu-kvm to the list of binary 2015-04-20 10:12:17 +02:00
3926390d30 Fix tests broken by a previous commit 2015-04-18 09:22:37 +02:00
343e007809 Ignore the "OSError: [WinError 0] The operation completed successfully" exception in Windows. 2015-04-16 20:17:06 -06:00
c6dbf296cf Merge remote-tracking branch 'origin/master' 2015-04-16 13:24:48 -06:00
dfdc18b20c Merge branch 'master' into unstable 2015-04-16 18:38:59 +02:00
bca90bc563 API in order to get the list of IOU, Dynamips, Qemu images 2015-04-16 18:32:12 +02:00
b5e01f7560 Fix IOU licence check flag 2015-04-16 09:51:02 +02:00
4136c29b0f Config paths are not used when updating Dynamips or IOU VM settings. 2015-04-15 19:50:40 -06:00
aeab9780d8 Fixes initial-configs that were not restored when opening a project containing IOU VMs. 2015-04-15 19:49:40 -06:00
5a4ffae6a2 Merge branch 'master' into unstable 2015-04-15 16:44:09 +02:00
e367f95f96 Drop darwin specific tests 2015-04-15 16:42:26 +02:00
789e24795e Merge branch 'master' into unstable 2015-04-15 16:29:54 +02:00
26a7f83db2 Remove the workaround for dynamips OSX 2015-04-15 15:58:31 +02:00
def453c116 Restore "iourc_path" until I speak with jeremy about it 2015-04-15 15:50:34 +02:00
997f7cbd6f Fix noise in logs 2015-04-15 15:40:07 +02:00
750958bd12 Fix tests 2015-04-15 14:33:51 +02:00
aab4a7243b Merge remote-tracking branch 'origin/master' 2015-04-14 18:21:42 -06:00
aa2472fb30 Rewrote image search
This code is more generic and support all cases. Previously
we had bug where the user lost his image path if the image
was not located in image directory.
2015-04-14 18:46:55 +02:00
e51a129216 Prevent parallel execution of VBox commands
In theory it should not be a problem.
But It's create issues like this one:

Fix: https://github.com/GNS3/gns3-gui/issues/261
2015-04-14 15:00:45 +02:00
6ec081c774 Include tests in Pypi package
Require by gentoo maintainer
2015-04-14 14:53:01 +02:00
55fed02299 Fix a crash when in some cases you can't access to VBOX state
Fix #137
2015-04-14 14:35:48 +02:00
45ca995dea Fix crash if VirtualBox doesn't return API version
Fix #136
2015-04-14 14:32:44 +02:00
af942dc419 Fix a crash in VirtualBox vm creation
Fix #138
2015-04-14 14:24:13 +02:00
1d5dc2ecf0 1.4.0 dev1 2015-04-13 10:48:14 +02:00
443842e9b8 Allocate random names for Dynamips NIOs. 2015-04-12 18:14:45 -06:00
78bc6e29a8 Explicitly delete Dynamips NIOs and unmap VCs for ATM and Frame-Relay switches. 2015-04-12 18:09:53 -06:00
de5e8f852d Cleaner and generic way to set Qemu & IOU VM settings. 2015-04-12 15:09:37 -06:00
c99998d73c Fix version 2015-04-12 11:08:30 +02:00
c4963abcba 1.3.2 dev1 2015-04-11 13:59:22 +02:00
85 changed files with 1534 additions and 1014 deletions

101
CHANGELOG
View File

@ -1,5 +1,106 @@
# 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.
* Fixes bugs with IOS router configs. Fixes #354.
* Use a temporary directory as egg cache
* Catch crash error in IOU in case of permission denied
## 1.3.3rc1 07/05/2015
* Return an error if an adapter slot doesn't exist on an IOS router.
* NIO NAT support for VirtualBox VMs.
* NIO NAT support for QEMU VMs (user mode back-end is used).
* Throw an error if user put an invalid port range in config file
* Turn off configuration parser interpolation
* Catch configuration file parsing errors
* Force closing the event loop to avoid warning with Python 3.4.3
* Catch error when you can't mark a project as no longer temporary
* Catch BrokenPipeError for OSX frozen server
* Match how IOU initial-config is set for VPCS VM.
* Refactors how startup-config and private-config are handled for IOS routers.
* Catch the "WinError 0 The operation completed successfully" exception at a higher level.
* Fix temporary project not cleanup with save as
* If image is not found in VM directory look in images folder
* Ordered MAC addresses for QEMU based VMs.
* Merge remote-tracking branch 'origin/master'
* Force utf-8 configuraton files reading
* Do not list file starting with a . in upload handler
* Do not crash when closing a project if VirtualBox is not accessible
* Catch connection reset errors
## 1.3.2 28/04/2015
* Cleanup the VirtualBox Media Manager after closing a project.
* Avoid Cygwin warning with VPCS on Windows.
* Close VirtualBox VM linked clone disks after the VM is unregistered.
* TAP interface support for QEMU VMs.
* Return an explicit error when a NIO type is not supported by a VM.
* Do not erase the IOU config
* Explicit utf-8 decoding.
* Check NIO exists when stopping an IOU capture.
* Fixes c7200 NPE setting.
* Fixes VPCS process termination.
* Catch FileNotFoundError exception in os.getcwd()
* Explicit utf-8 encoding where necessary to avoid Unicode errors on Windows (we require/set an utf-8 locale on other systems).
* Fixes #270. Relative paths management with empty ones.
* New crash report key and doesn't send report for developers
* Catch COM errors when connecting to WMI.
* Don't assume the PATH environment variable exists.
* Use UUIDs instead of the VM names for VirtualBox pipe paths.
* Add --log options for daemon support
* Basic upstart script
* Add qemu-kvm to the list of binary
* Fix IOU licence check flag
* Config paths are not used when updating Dynamips or IOU VM settings.
* Fixes initial-configs that were not restored when opening a project containing IOU VMs.
* Prevent parallel execution of VBox commands
* Fix a crash when in some cases you can't access to VBOX state
* Fix crash if VirtualBox doesn't return API version
* Fix a crash in VirtualBox vm creation
* Allocate random names for Dynamips NIOs.
* Explicitly delete Dynamips NIOs and unmap VCs for ATM and Frame-Relay switches.
## 1.3.1 11/04/2015
* Release

View File

@ -4,7 +4,7 @@ include INSTALL
include LICENSE
include MANIFEST.in
include tox.ini
recursive-exclude tests *
recursive-include tests *
recursive-exclude docs *
recursive-include gns3server *
recursive-exclude * __pycache__

View File

@ -26,6 +26,12 @@ unstable
********
*Never* use this branch for production. Major new features pull requests goes here.
Linux
-----
GNS3 is perhaps packaged for your distribution:
* Gentoo: https://packages.gentoo.org/package/net-misc/gns3-server
Linux (Debian based)
--------------------
@ -62,11 +68,34 @@ To run tests use:
py.test -v
Run as daemon
***************
You will found init sample script for various systems
inside the init directory.
upstart
~~~~~~~
For ubuntu < 15.04
You need to copy init/gns3.conf.upstart to /etc/init/gns3.conf
.. code:: bash
sudo chown root /etc/init/gns3.conf
sudo service gns3 start
Windows
-------
Please use our all-in-one installer.
If you install it via source you need to install also:
https://sourceforge.net/projects/pywin32/
Mac OS X
--------

View File

@ -6,4 +6,3 @@ pep8==1.5.7
pytest-timeout
pytest-capturelog
pytest-cov
python-coveralls

2
gns3server.bat Normal file
View File

@ -0,0 +1,2 @@
SET PYTHONPATH=.
python.exe gns3server/main.py --debug --local

View File

@ -92,7 +92,7 @@ class Config(object):
def clear(self):
"""Restart with a clean config"""
self._config = configparser.ConfigParser()
self._config = configparser.RawConfigParser()
# Override config from command line even if we modify the config file and live reload it.
self._override_config = {}
@ -135,7 +135,11 @@ class Config(object):
Read the configuration files.
"""
parsed_files = self._config.read(self._files)
try:
parsed_files = self._config.read(self._files, encoding="utf-8")
except configparser.Error as e:
log.error("Can't parse configuration file: %s", str(e))
return
if not parsed_files:
log.warning("No configuration file could be found or read")
else:

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://90fe04368a3e4109b584a116ee03ca87:268ef86c65cf4e8fa41d4c7fb1c70b72@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):
@ -55,6 +66,9 @@ class CrashReport:
def capture_exception(self, request=None):
if not RAVEN_AVAILABLE:
return
if os.path.exists(".git"):
log.warning("A .git directory exist crash report is turn off for developers")
return
server_config = Config.instance().get_section_config("Server")
if server_config.getboolean("report_errors"):
if self._client is None:

View File

@ -31,4 +31,6 @@ from gns3server.handlers.upload_handler import UploadHandler
from gns3server.handlers.index_handler import IndexHandler
if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1":
from gns3server.handlers.api.iou_handler import IOUHandler
# IOU runs only on Linux but testsuite work on UNIX platform
if not sys.platform.startswith("win"):
from gns3server.handlers.api.iou_handler import IOUHandler

View File

@ -181,10 +181,8 @@ class DynamipsDeviceHandler:
dynamips_manager = Dynamips.instance()
device = dynamips_manager.get_device(request.match_info["device_id"], project_id=request.match_info["project_id"])
port_number = int(request.match_info["port_number"])
if asyncio.iscoroutinefunction(device.remove_nio):
yield from device.remove_nio(port_number)
else:
device.remove_nio(port_number)
nio = yield from device.remove_nio(port_number)
yield from nio.delete()
response.set_status(204)
@Route.post(

View File

@ -19,13 +19,14 @@ import os
import base64
from ...web.route import Route
from ...schemas.nio import NIO_SCHEMA
from ...schemas.dynamips_vm import VM_CREATE_SCHEMA
from ...schemas.dynamips_vm import VM_UPDATE_SCHEMA
from ...schemas.dynamips_vm import VM_CAPTURE_SCHEMA
from ...schemas.dynamips_vm import VM_OBJECT_SCHEMA
from ...schemas.dynamips_vm import VM_NIO_SCHEMA
from ...schemas.dynamips_vm import VM_CONFIGS_SCHEMA
from ...modules.dynamips import Dynamips
from ...modules.dynamips.dynamips_error import DynamipsError
from ...modules.project_manager import ProjectManager
DEFAULT_CHASSIS = {
@ -256,8 +257,8 @@ class DynamipsVMHandler:
404: "Instance doesn't exist"
},
description="Add a NIO to a Dynamips VM instance",
input=VM_NIO_SCHEMA,
output=VM_NIO_SCHEMA)
input=NIO_SCHEMA,
output=NIO_SCHEMA)
def create_nio(request, response):
dynamips_manager = Dynamips.instance()
@ -290,7 +291,8 @@ class DynamipsVMHandler:
vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
slot_number = int(request.match_info["adapter_number"])
port_number = int(request.match_info["port_number"])
yield from vm.slot_remove_nio_binding(slot_number, port_number)
nio = yield from vm.slot_remove_nio_binding(slot_number, port_number)
yield from nio.delete()
response.set_status(204)
@Route.post(
@ -357,13 +359,41 @@ class DynamipsVMHandler:
project_id=request.match_info["project_id"])
startup_config_base64, private_config_base64 = yield from vm.extract_config()
module_workdir = vm.project.module_working_directory(dynamips_manager.module_name.lower())
result = {}
if startup_config_base64:
startup_config_content = base64.b64decode(startup_config_base64).decode(errors='replace')
startup_config_content = base64.b64decode(startup_config_base64).decode("utf-8", errors='replace')
result["startup_config_content"] = startup_config_content
else:
# nvram doesn't contain anything if the router has not been started at least once
# in this case just use the startup-config file
if vm.startup_config:
startup_config_path = os.path.join(module_workdir, vm.startup_config)
if os.path.isfile(startup_config_path):
try:
with open(startup_config_path, "rb") as f:
content = f.read().decode("utf-8", errors='replace')
if content:
result["startup_config_content"] = content
except OSError as e:
raise DynamipsError("Could not read the startup-config {}: {}".format(startup_config_path, e))
if private_config_base64:
private_config_content = base64.b64decode(private_config_base64).decode(errors='replace')
private_config_content = base64.b64decode(private_config_base64).decode("utf-8", errors='replace')
result["private_config_content"] = private_config_content
else:
# nvram doesn't contain anything if the router has not been started at least once
# in this case just use the private-config file
if vm.private_config:
private_config_path = os.path.join(module_workdir, vm.private_config)
if os.path.isfile(private_config_path):
try:
with open(private_config_path, "rb") as f:
content = f.read().decode("utf-8", errors='replace')
if content:
result["private_config_content"] = content
except OSError as e:
raise DynamipsError("Could not read the private-config {}: {}".format(private_config_path, e))
response.set_status(200)
response.json(result)

View File

@ -19,11 +19,10 @@ import os
from aiohttp.web import HTTPConflict
from ...web.route import Route
from ...modules.port_manager import PortManager
from ...schemas.nio import NIO_SCHEMA
from ...schemas.iou import IOU_CREATE_SCHEMA
from ...schemas.iou import IOU_UPDATE_SCHEMA
from ...schemas.iou import IOU_OBJECT_SCHEMA
from ...schemas.iou import IOU_NIO_SCHEMA
from ...schemas.iou import IOU_CAPTURE_SCHEMA
from ...schemas.iou import IOU_INITIAL_CONFIG_SCHEMA
from ...modules.iou import IOU
@ -52,20 +51,16 @@ class IOUHandler:
def create(request, response):
iou = IOU.instance()
vm = yield from iou.create_vm(request.json["name"],
vm = yield from iou.create_vm(request.json.pop("name"),
request.match_info["project_id"],
request.json.get("vm_id"),
console=request.json.get("console"),
serial_adapters=request.json.get("serial_adapters"),
ethernet_adapters=request.json.get("ethernet_adapters"),
ram=request.json.get("ram"),
nvram=request.json.get("nvram"),
use_default_iou_values=request.json.get("use_default_iou_values"),
l1_keepalives=request.json.get("l1_keepalives"),
initial_config=request.json.get("initial_config_content"),
iourc_content=request.json.get("iourc_content")
)
vm.path = request.json.get("path", vm.path)
console=request.json.get("console"))
for name, value in request.json.items():
if hasattr(vm, name) and getattr(vm, name) != value:
if name == "initial_config_content" and (vm.initial_config_content and len(vm.initial_config_content) > 0):
continue
setattr(vm, name, value)
response.set_status(201)
response.json(vm)
@ -109,18 +104,10 @@ class IOUHandler:
iou_manager = IOU.instance()
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
vm.name = request.json.get("name", vm.name)
vm.console = request.json.get("console", vm.console)
vm.path = request.json.get("path", vm.path)
vm.ethernet_adapters = request.json.get("ethernet_adapters", vm.ethernet_adapters)
vm.serial_adapters = request.json.get("serial_adapters", vm.serial_adapters)
vm.ram = request.json.get("ram", vm.ram)
vm.nvram = request.json.get("nvram", vm.nvram)
vm.use_default_iou_values = request.json.get("use_default_iou_values", vm.use_default_iou_values)
vm.l1_keepalives = request.json.get("l1_keepalives", vm.l1_keepalives)
vm.initial_config = request.json.get("initial_config_content", vm.initial_config)
vm.iourc_content = request.json.get("iourc_content", None)
for name, value in request.json.items():
if hasattr(vm, name) and getattr(vm, name) != value:
setattr(vm, name, value)
response.json(vm)
@classmethod
@ -215,12 +202,15 @@ class IOUHandler:
404: "Instance doesn't exist"
},
description="Add a NIO to a IOU instance",
input=IOU_NIO_SCHEMA,
output=IOU_NIO_SCHEMA)
input=NIO_SCHEMA,
output=NIO_SCHEMA)
def create_nio(request, response):
iou_manager = IOU.instance()
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
nio_type = request.json["type"]
if nio_type not in ("nio_udp", "nio_tap", "nio_generic_ethernet"):
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
nio = iou_manager.create_nio(vm.iouyap_path, request.json)
vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), int(request.match_info["port_number"]), nio)
response.set_status(201)
@ -273,7 +263,7 @@ class IOUHandler:
pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"])
if not vm.is_running():
raise HTTPConflict(text="You can't capture the traffic on a non started VM")
raise HTTPConflict(text="Cannot capture traffic on a non started VM")
yield from vm.start_capture(adapter_number, port_number, pcap_file_path, request.json["data_link_type"])
response.json({"pcap_file_path": str(pcap_file_path)})
@ -298,7 +288,7 @@ class IOUHandler:
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
if not vm.is_running():
raise HTTPConflict(text="You can't capture the traffic on a non started VM")
raise HTTPConflict(text="Cannot capture traffic on a non started VM")
adapter_number = int(request.match_info["adapter_number"])
port_number = int(request.match_info["port_number"])
@ -320,4 +310,4 @@ class IOUHandler:
vm = iou_manager.get_vm(request.match_info["vm_id"],
project_id=request.match_info["project_id"])
response.set_status(200)
response.json({"content": vm.initial_config})
response.json({"content": vm.initial_config_content})

View File

@ -81,13 +81,16 @@ class ProjectHandler:
pm = ProjectManager.instance()
project = pm.get_project(request.match_info["project_id"])
project.temporary = request.json.get("temporary", project.temporary)
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)
@classmethod

View File

@ -15,11 +15,12 @@
# 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 aiohttp.web import HTTPConflict
from ...web.route import Route
from ...schemas.nio import NIO_SCHEMA
from ...schemas.qemu import QEMU_CREATE_SCHEMA
from ...schemas.qemu import QEMU_UPDATE_SCHEMA
from ...schemas.qemu import QEMU_OBJECT_SCHEMA
from ...schemas.qemu import QEMU_NIO_SCHEMA
from ...schemas.qemu import QEMU_BINARY_LIST_SCHEMA
from ...modules.qemu import Qemu
@ -41,24 +42,21 @@ class QEMUHandler:
400: "Invalid request",
409: "Conflict"
},
description="Create a new Qemu.instance",
description="Create a new Qemu VM instance",
input=QEMU_CREATE_SCHEMA,
output=QEMU_OBJECT_SCHEMA)
def create(request, response):
qemu = Qemu.instance()
vm = yield from qemu.create_vm(request.json["name"],
vm = yield from qemu.create_vm(request.json.pop("name"),
request.match_info["project_id"],
request.json.get("vm_id"),
qemu_path=request.json.get("qemu_path"),
console=request.json.get("console"))
# Clear already used keys
map(request.json.__delitem__, ["name", "project_id", "vm_id",
"qemu_path", "console"])
for field in request.json:
setattr(vm, field, request.json[field])
for name, value in request.json.items():
if hasattr(vm, name) and getattr(vm, name) != value:
setattr(vm, name, value)
response.set_status(201)
response.json(vm)
@ -75,7 +73,7 @@ class QEMUHandler:
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Get a Qemu.instance",
description="Get a Qemu VM instance",
output=QEMU_OBJECT_SCHEMA)
def show(request, response):
@ -96,15 +94,17 @@ class QEMUHandler:
404: "Instance doesn't exist",
409: "Conflict"
},
description="Update a Qemu.instance",
description="Update a Qemu VM instance",
input=QEMU_UPDATE_SCHEMA,
output=QEMU_OBJECT_SCHEMA)
def update(request, response):
qemu_manager = Qemu.instance()
vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
for field in request.json:
setattr(vm, field, request.json[field])
for name, value in request.json.items():
if hasattr(vm, name) and getattr(vm, name) != value:
setattr(vm, name, value)
response.json(vm)
@ -120,7 +120,7 @@ class QEMUHandler:
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Delete a Qemu.instance")
description="Delete a Qemu VM instance")
def delete(request, response):
yield from Qemu.instance().delete_vm(request.match_info["vm_id"])
@ -138,7 +138,7 @@ class QEMUHandler:
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Start a Qemu.instance")
description="Start a Qemu VM instance")
def start(request, response):
qemu_manager = Qemu.instance()
@ -158,7 +158,7 @@ class QEMUHandler:
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Stop a Qemu.instance")
description="Stop a Qemu VM instance")
def stop(request, response):
qemu_manager = Qemu.instance()
@ -178,7 +178,7 @@ class QEMUHandler:
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Reload a Qemu.instance")
description="Reload a Qemu VM instance")
def reload(request, response):
qemu_manager = Qemu.instance()
@ -198,7 +198,7 @@ class QEMUHandler:
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Suspend a Qemu.instance")
description="Suspend a Qemu VM instance")
def suspend(request, response):
qemu_manager = Qemu.instance()
@ -218,7 +218,7 @@ class QEMUHandler:
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Resume a Qemu.instance")
description="Resume a Qemu VM instance")
def resume(request, response):
qemu_manager = Qemu.instance()
@ -239,13 +239,16 @@ class QEMUHandler:
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Add a NIO to a Qemu.instance",
input=QEMU_NIO_SCHEMA,
output=QEMU_NIO_SCHEMA)
description="Add a NIO to a Qemu VM instance",
input=NIO_SCHEMA,
output=NIO_SCHEMA)
def create_nio(request, response):
qemu_manager = Qemu.instance()
vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
nio_type = request.json["type"]
if nio_type not in ("nio_udp", "nio_tap", "nio_nat"):
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
nio = qemu_manager.create_nio(vm.qemu_path, request.json)
yield from vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio)
response.set_status(201)
@ -265,7 +268,7 @@ class QEMUHandler:
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Remove a NIO from a Qemu.instance")
description="Remove a NIO from a Qemu VM instance")
def delete_nio(request, response):
qemu_manager = Qemu.instance()

View File

@ -16,10 +16,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from aiohttp.web import HTTPConflict
from ...web.route import Route
from ...schemas.nio import NIO_SCHEMA
from ...schemas.virtualbox import VBOX_CREATE_SCHEMA
from ...schemas.virtualbox import VBOX_UPDATE_SCHEMA
from ...schemas.virtualbox import VBOX_NIO_SCHEMA
from ...schemas.virtualbox import VBOX_CAPTURE_SCHEMA
from ...schemas.virtualbox import VBOX_OBJECT_SCHEMA
from ...modules.virtualbox import VirtualBox
@ -79,8 +80,9 @@ class VirtualBoxHandler:
yield from vm.set_ram(ram)
for name, value in request.json.items():
if hasattr(vm, name) and getattr(vm, name) != value:
setattr(vm, name, value)
if name != "vm_id":
if hasattr(vm, name) and getattr(vm, name) != value:
setattr(vm, name, value)
response.set_status(201)
response.json(vm)
@ -285,12 +287,15 @@ class VirtualBoxHandler:
404: "Instance doesn't exist"
},
description="Add a NIO to a VirtualBox VM instance",
input=VBOX_NIO_SCHEMA,
output=VBOX_NIO_SCHEMA)
input=NIO_SCHEMA,
output=NIO_SCHEMA)
def create_nio(request, response):
vbox_manager = VirtualBox.instance()
vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
nio_type = request.json["type"]
if nio_type not in ("nio_udp", "nio_nat"):
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
nio = vbox_manager.create_nio(vbox_manager.vboxmanage_path, request.json)
yield from vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio)
response.set_status(201)
@ -339,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(

View File

@ -15,11 +15,12 @@
# 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 aiohttp.web import HTTPConflict
from ...web.route import Route
from ...schemas.nio import NIO_SCHEMA
from ...schemas.vpcs import VPCS_CREATE_SCHEMA
from ...schemas.vpcs import VPCS_UPDATE_SCHEMA
from ...schemas.vpcs import VPCS_OBJECT_SCHEMA
from ...schemas.vpcs import VPCS_NIO_SCHEMA
from ...modules.vpcs import VPCS
@ -191,12 +192,15 @@ class VPCSHandler:
404: "Instance doesn't exist"
},
description="Add a NIO to a VPCS instance",
input=VPCS_NIO_SCHEMA,
output=VPCS_NIO_SCHEMA)
input=NIO_SCHEMA,
output=NIO_SCHEMA)
def create_nio(request, response):
vpcs_manager = VPCS.instance()
vm = vpcs_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
nio_type = request.json["type"]
if nio_type not in ("nio_udp", "nio_tap"):
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
nio = vpcs_manager.create_nio(vm.vpcs_path, request.json)
vm.port_add_nio_binding(int(request.match_info["port_number"]), nio)
response.set_status(201)

View File

@ -36,8 +36,9 @@ class UploadHandler:
try:
for root, _, files in os.walk(UploadHandler.image_directory()):
for filename in files:
image_file = os.path.join(root, filename)
uploaded_files.append(image_file)
if not filename.startswith("."):
image_file = os.path.join(root, filename)
uploaded_files.append(image_file)
except OSError:
pass
iourc_path = os.path.join(os.path.expanduser("~/"), ".iourc")

View File

@ -16,6 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# WARNING
# Due to buggy user machines we choose to put this as the first loading modules
# otherwise the egg cache is initialized in his standard location and
# if is not writetable the application crash. It's the user fault
# because one day the user as used sudo to run an egg and break his
# filesystem permissions, but it's a common mistake.
import gns3server.utils.get_resource
import os
import datetime
import sys
@ -92,6 +102,7 @@ def parse_arguments(argv, config):
"quiet": config.getboolean("quiet", False),
"debug": config.getboolean("debug", False),
"live": config.getboolean("live", False),
"logfile": config.getboolean("logfile", ""),
}
parser = argparse.ArgumentParser(description="GNS3 server version {}".format(__version__))
@ -109,6 +120,7 @@ def parse_arguments(argv, config):
parser.add_argument("-d", "--debug", action="store_true", help="show debug logs")
parser.add_argument("--live", action="store_true", help="enable code live reload")
parser.add_argument("--shell", action="store_true", help="start a shell inside the server (debugging purpose only you need to install ptpython before)")
parser.add_argument("--log", help="send output to logfile instead of console")
return parser.parse_args(argv)
@ -141,7 +153,7 @@ def main():
if args.debug:
level = logging.DEBUG
user_log = init_logger(level, quiet=args.quiet)
user_log = init_logger(level, logfile=args.log, quiet=args.quiet)
user_log.info("GNS3 server version {}".format(__version__))
current_year = datetime.date.today().year
user_log.info("Copyright (c) 2007-{} GNS3 Technologies Inc.".format(current_year))
@ -174,11 +186,21 @@ 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:
server.run()
except OSError as e:
# This is to ignore OSError: [WinError 0] The operation completed successfully exception on Windows.
if not sys.platform.startswith("win") and not e.winerror == 0:
raise
except Exception as e:
log.critical("Critical error while running the server: {}".format(e), exc_info=1)
CrashReport.instance().capture_exception()

View File

@ -25,6 +25,8 @@ from .qemu import Qemu
MODULES = [VPCS, VirtualBox, Dynamips, Qemu]
if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1":
# IOU runs only on Linux
from .iou import IOU
MODULES.append(IOU)
# IOU runs only on Linux but testsuite work on UNIX platform
if not sys.platform.startswith("win"):
from .iou import IOU
MODULES.append(IOU)

View File

@ -34,6 +34,7 @@ from .project_manager import ProjectManager
from .nios.nio_udp import NIOUDP
from .nios.nio_tap import NIOTAP
from .nios.nio_nat import NIONAT
from .nios.nio_generic_ethernet import NIOGenericEthernet
@ -364,10 +365,63 @@ class BaseManager:
nio = NIOUDP(lport, rhost, rport)
elif nio_settings["type"] == "nio_tap":
tap_device = nio_settings["tap_device"]
if not self._has_privileged_access(executable):
raise aiohttp.web.HTTPForbidden(text="{} has no privileged access to {}.".format(executable, tap_device))
#FIXME: check for permissions on tap device
#if not self._has_privileged_access(executable):
# raise aiohttp.web.HTTPForbidden(text="{} has no privileged access to {}.".format(executable, tap_device))
nio = NIOTAP(tap_device)
elif nio_settings["type"] == "nio_generic_ethernet":
nio = NIOGenericEthernet(nio_settings["ethernet_device"])
elif nio_settings["type"] == "nio_nat":
nio = NIONAT()
assert nio is not None
return nio
def get_abs_image_path(self, path):
"""
Get the absolute path of an image
:param path: file path
:return: file path
"""
if not path:
return ""
img_directory = self.get_images_directory()
if not os.path.isabs(path):
s = os.path.split(path)
path = os.path.normpath(os.path.join(img_directory, *s))
# Compatibility with old topologies we look in parent directory
# We look at first in new location
if not os.path.exists(path):
old_path = os.path.normpath(os.path.join(img_directory, '..', *s))
if os.path.exists(old_path):
return old_path
return path
return path
def get_relative_image_path(self, path):
"""
Get a path relative to images directory path
or an abspath if the path is not located inside
image directory
:param path: file path
:return: file path
"""
if not path:
return ""
img_directory = self.get_images_directory()
path = self.get_abs_image_path(path)
if os.path.dirname(path) == img_directory:
return os.path.basename(path)
return path
def get_images_directory(self):
"""
Get the image directory on disk
"""
raise NotImplementedError

View File

@ -208,9 +208,13 @@ class Dynamips(BaseManager):
"""
# save the configs when the project is committed
for vm in self._vms.values():
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):
@ -410,6 +414,8 @@ class Dynamips(BaseManager):
nio = NIOVDE(node.hypervisor, control_file, local_file)
elif nio_settings["type"] == "nio_null":
nio = NIONull(node.hypervisor)
else:
raise aiohttp.web.HTTPConflict(text="NIO of type {} is not supported".format(nio_settings["type"]))
yield from nio.create()
return nio
@ -471,31 +477,42 @@ class Dynamips(BaseManager):
if hasattr(vm, "set_{}".format(name)):
setter = getattr(vm, "set_{}".format(name))
yield from setter(value)
elif name.startswith("slot") and value in ADAPTER_MATRIX:
slot_id = int(name[-1])
adapter_name = value
adapter = ADAPTER_MATRIX[adapter_name]()
if vm.slots[slot_id] and not isinstance(vm.slots[slot_id], type(adapter)):
yield from vm.slot_remove_binding(slot_id)
if not isinstance(vm.slots[slot_id], type(adapter)):
yield from vm.slot_add_binding(slot_id, adapter)
try:
if vm.slots[slot_id] and not isinstance(vm.slots[slot_id], type(adapter)):
yield from vm.slot_remove_binding(slot_id)
if not isinstance(vm.slots[slot_id], type(adapter)):
yield from vm.slot_add_binding(slot_id, adapter)
except IndexError:
raise DynamipsError("Slot {} doesn't exist on this router".format(slot_id))
elif name.startswith("slot") and value is None:
slot_id = int(name[-1])
if vm.slots[slot_id]:
yield from vm.slot_remove_binding(slot_id)
try:
if vm.slots[slot_id]:
yield from vm.slot_remove_binding(slot_id)
except IndexError:
raise DynamipsError("Slot {} doesn't exist on this router".format(slot_id))
elif name.startswith("wic") and value in WIC_MATRIX:
wic_slot_id = int(name[-1])
wic_name = value
wic = WIC_MATRIX[wic_name]()
if vm.slots[0].wics[wic_slot_id] and not isinstance(vm.slots[0].wics[wic_slot_id], type(wic)):
yield from vm.uninstall_wic(wic_slot_id)
if not isinstance(vm.slots[0].wics[wic_slot_id], type(wic)):
yield from vm.install_wic(wic_slot_id, wic)
try:
if vm.slots[0].wics[wic_slot_id] and not isinstance(vm.slots[0].wics[wic_slot_id], type(wic)):
yield from vm.uninstall_wic(wic_slot_id)
if not isinstance(vm.slots[0].wics[wic_slot_id], type(wic)):
yield from vm.install_wic(wic_slot_id, wic)
except IndexError:
raise DynamipsError("WIC slot {} doesn't exist on this router".format(wic_slot_id))
elif name.startswith("wic") and value is None:
wic_slot_id = int(name[-1])
if vm.slots[0].wics and vm.slots[0].wics[wic_slot_id]:
yield from vm.uninstall_wic(wic_slot_id)
try:
if vm.slots[0].wics and vm.slots[0].wics[wic_slot_id]:
yield from vm.uninstall_wic(wic_slot_id)
except IndexError:
raise DynamipsError("WIC slot {} doesn't exist on this router".format(wic_slot_id))
mmap_support = self.config.get_section_config("Dynamips").getboolean("mmap_support", True)
if mmap_support is False:
@ -521,38 +538,34 @@ class Dynamips(BaseManager):
default_startup_config_path = os.path.join(module_workdir, "configs", "i{}_startup-config.cfg".format(vm.dynamips_id))
default_private_config_path = os.path.join(module_workdir, "configs", "i{}_private-config.cfg".format(vm.dynamips_id))
startup_config_path = settings.get("startup_config")
startup_config_content = settings.get("startup_config_content")
if startup_config_content:
startup_config_path = self._create_config(vm, startup_config_content, default_startup_config_path)
if startup_config_path:
yield from vm.set_configs(startup_config_path)
elif startup_config_content:
startup_config_path = self._create_config(vm, default_startup_config_path, startup_config_content)
yield from vm.set_configs(startup_config_path)
else:
startup_config_path = settings.get("startup_config")
if startup_config_path:
yield from vm.set_configs(startup_config_path)
private_config_path = settings.get("private_config")
private_config_content = settings.get("private_config_content")
if private_config_content:
private_config_path = self._create_config(vm, private_config_content, default_private_config_path)
if private_config_path:
yield from vm.set_configs(vm.startup_config, private_config_path)
elif private_config_content:
private_config_path = self._create_config(vm, default_private_config_path, private_config_content)
yield from vm.set_configs(vm.startup_config, private_config_path)
else:
private_config_path = settings.get("private_config")
if private_config_path:
yield from vm.set_configs(vm.startup_config, private_config_path)
def _create_config(self, vm, content, path):
def _create_config(self, vm, path, content=None):
"""
Creates a config file.
:param vm: VM instance
:param content: config content
:param path: path to the destination config file
:param content: config content
:returns: relative path to the created config file
"""
log.info("Creating config file {}".format(path))
content = "!\n" + content.replace("\r", "")
content = content.replace('%h', vm.name)
config_dir = os.path.dirname(path)
try:
os.makedirs(config_dir, exist_ok=True)
@ -561,7 +574,10 @@ class Dynamips(BaseManager):
try:
with open(path, "wb") as f:
f.write(content.encode("utf-8"))
if content:
content = "!\n" + content.replace("\r", "")
content = content.replace('%h', vm.name)
f.write(content.encode("utf-8"))
except OSError as e:
raise DynamipsError("Could not create config file {}: {}".format(path, e))
@ -616,3 +632,9 @@ class Dynamips(BaseManager):
if was_auto_started:
yield from vm.stop()
return validated_idlepc
def get_images_directory(self):
"""
Return the full path of the images directory on disk
"""
return os.path.join(os.path.expanduser(self.config.get_section_config("Server").get("images_path", "~/GNS3/images")), "IOS")

View File

@ -276,15 +276,16 @@ class DynamipsHypervisor:
while True:
try:
try:
line = yield from self._reader.readline()
#line = yield from self._reader.readline() # this can lead to ValueError: Line is too long
chunk = yield from self._reader.read(1024) # match to Dynamips' buffer size
except asyncio.CancelledError:
# task has been canceled but continue to read
# any remaining data sent by the hypervisor
continue
if not line:
if not chunk:
raise DynamipsError("No data returned from {host}:{port}, Dynamips process running: {run}"
.format(host=self._host, port=self._port, run=self.is_running()))
buf += line.decode()
buf += chunk.decode("utf-8")
except OSError as e:
raise DynamipsError("Lost communication with {host}:{port} :{error}, Dynamips process running: {run}"
.format(host=self._host, port=self._port, error=e, run=self.is_running()))

View File

@ -158,8 +158,8 @@ class Hypervisor(DynamipsHypervisor):
output = ""
if self._stdout_file and os.access(self._stdout_file, os.R_OK):
try:
with open(self._stdout_file, errors="replace") as file:
output = file.read()
with open(self._stdout_file, "rb") as file:
output = file.read().decode("utf-8", errors="replace")
except OSError as e:
log.warn("could not read {}: {}".format(self._stdout_file, e))
return output

View File

@ -20,6 +20,7 @@ Interface for FIFO NIOs.
"""
import asyncio
import uuid
from .nio import NIO
import logging
@ -34,24 +35,12 @@ class NIOFIFO(NIO):
:param hypervisor: Dynamips hypervisor instance
"""
_instance_count = 0
def __init__(self, hypervisor):
# create an unique ID and name
nio_id = NIOFIFO._instance_count
NIOFIFO._instance_count += 1
name = 'nio_fifo' + str(nio_id)
# create an unique name
name = 'fifo-{}'.format(uuid.uuid4())
super().__init__(name, hypervisor)
@classmethod
def reset(cls):
"""
Reset the instance count.
"""
cls._instance_count = 0
@asyncio.coroutine
def create(self):

View File

@ -20,6 +20,7 @@ Interface for generic Ethernet NIOs (PCAP library).
"""
import asyncio
import uuid
from .nio import NIO
import logging
@ -35,25 +36,13 @@ class NIOGenericEthernet(NIO):
:param ethernet_device: Ethernet device name (e.g. eth0)
"""
_instance_count = 0
def __init__(self, hypervisor, ethernet_device):
# create an unique ID and name
nio_id = NIOGenericEthernet._instance_count
NIOGenericEthernet._instance_count += 1
name = 'nio_gen_eth' + str(nio_id)
# create an unique name
name = 'generic_ethernet-{}'.format(uuid.uuid4())
self._ethernet_device = ethernet_device
super().__init__(name, hypervisor)
@classmethod
def reset(cls):
"""
Reset the instance count.
"""
cls._instance_count = 0
@asyncio.coroutine
def create(self):

View File

@ -20,6 +20,7 @@ Interface for Linux Ethernet NIOs (Linux only).
"""
import asyncio
import uuid
from .nio import NIO
import logging
@ -35,25 +36,12 @@ class NIOLinuxEthernet(NIO):
:param ethernet_device: Ethernet device name (e.g. eth0)
"""
_instance_count = 0
def __init__(self, hypervisor, ethernet_device):
# create an unique ID and name
nio_id = NIOLinuxEthernet._instance_count
NIOLinuxEthernet._instance_count += 1
name = 'nio_linux_eth' + str(nio_id)
# create an unique name
name = 'linux_ethernet-{}'.format(uuid.uuid4())
self._ethernet_device = ethernet_device
super().__init__(name, hypervisor)
@classmethod
def reset(cls):
"""
Reset the instance count.
"""
cls._instance_count = 0
@asyncio.coroutine
def create(self):

View File

@ -20,6 +20,7 @@ Interface for multicast NIOs.
"""
import asyncio
import uuid
from .nio import NIO
import logging
@ -36,27 +37,15 @@ class NIOMcast(NIO):
:param port: port for binding
"""
_instance_count = 0
def __init__(self, hypervisor, group, port):
# create an unique ID and name
nio_id = NIOMcast._instance_count
NIOMcast._instance_count += 1
name = 'nio_mcast' + str(nio_id)
# create an unique name
name = 'mcast-{}'.format(uuid.uuid4())
self._group = group
self._port = port
self._ttl = 1 # default TTL
super().__init__(name, hypervisor)
@classmethod
def reset(cls):
"""
Reset the instance count.
"""
cls._instance_count = 0
@asyncio.coroutine
def create(self):

View File

@ -20,6 +20,7 @@ Interface for dummy NIOs (mostly for tests).
"""
import asyncio
import uuid
from .nio import NIO
import logging
@ -34,24 +35,12 @@ class NIONull(NIO):
:param hypervisor: Dynamips hypervisor instance
"""
_instance_count = 0
def __init__(self, hypervisor):
# create an unique ID and name
nio_id = NIONull._instance_count
NIONull._instance_count += 1
name = 'nio_null' + str(nio_id)
# create an unique name
name = 'null-{}'.format(uuid.uuid4())
super().__init__(name, hypervisor)
@classmethod
def reset(cls):
"""
Reset the instance count.
"""
cls._instance_count = 0
@asyncio.coroutine
def create(self):

View File

@ -20,6 +20,7 @@ Interface for TAP NIOs (UNIX based OSes only).
"""
import asyncio
import uuid
from .nio import NIO
import logging
@ -35,25 +36,13 @@ class NIOTAP(NIO):
:param tap_device: TAP device name (e.g. tap0)
"""
_instance_count = 0
def __init__(self, hypervisor, tap_device):
# create an unique ID and name
nio_id = NIOTAP._instance_count
NIOTAP._instance_count += 1
name = 'nio_tap' + str(nio_id)
# create an unique name
name = 'tap-{}'.format(uuid.uuid4())
self._tap_device = tap_device
super().__init__(name, hypervisor)
@classmethod
def reset(cls):
"""
Reset the instance count.
"""
cls._instance_count = 0
@asyncio.coroutine
def create(self):

View File

@ -20,6 +20,7 @@ Interface for UDP NIOs.
"""
import asyncio
import uuid
from .nio import NIO
import logging
@ -37,27 +38,15 @@ class NIOUDP(NIO):
:param rport: remote port number
"""
_instance_count = 0
def __init__(self, hypervisor, lport, rhost, rport):
# create an unique ID and name
nio_id = NIOUDP._instance_count
NIOUDP._instance_count += 1
name = 'nio_udp' + str(nio_id)
# create an unique name
name = 'udp-{}'.format(uuid.uuid4())
self._lport = lport
self._rhost = rhost
self._rport = rport
super().__init__(name, hypervisor)
@classmethod
def reset(cls):
"""
Reset the instance count.
"""
cls._instance_count = 0
@asyncio.coroutine
def create(self):

View File

@ -20,6 +20,7 @@ Interface for UNIX NIOs (Unix based OSes only).
"""
import asyncio
import uuid
from .nio import NIO
import logging
@ -36,26 +37,14 @@ class NIOUNIX(NIO):
:param remote_file: remote UNIX socket filename
"""
_instance_count = 0
def __init__(self, hypervisor, local_file, remote_file):
# create an unique ID and name
nio_id = NIOUNIX._instance_count
NIOUNIX._instance_count += 1
name = 'nio_unix' + str(nio_id)
# create an unique name
name = 'unix-{}'.format(uuid.uuid4())
self._local_file = local_file
self._remote_file = remote_file
super().__init__(name, hypervisor)
@classmethod
def reset(cls):
"""
Reset the instance count.
"""
cls._instance_count = 0
@asyncio.coroutine
def create(self):

View File

@ -20,6 +20,7 @@ Interface for VDE (Virtual Distributed Ethernet) NIOs (Unix based OSes only).
"""
import asyncio
import uuid
from .nio import NIO
import logging
@ -36,26 +37,14 @@ class NIOVDE(NIO):
:param local_file: VDE local filename
"""
_instance_count = 0
def __init__(self, hypervisor, control_file, local_file):
# create an unique ID and name
nio_id = NIOVDE._instance_count
NIOVDE._instance_count += 1
name = 'nio_vde' + str(nio_id)
# create an unique name
name = 'vde-{}'.format(uuid.uuid4())
self._control_file = control_file
self._local_file = local_file
super().__init__(name, hypervisor)
@classmethod
def reset(cls):
"""
Reset the instance count.
"""
cls._instance_count = 0
@asyncio.coroutine
def create(self):

View File

@ -150,6 +150,7 @@ class ATMSwitch(Device):
self._nios[port_number] = nio
@asyncio.coroutine
def remove_nio(self, port_number):
"""
Removes the specified NIO as member of this ATM switch.
@ -160,6 +161,23 @@ class ATMSwitch(Device):
if port_number not in self._nios:
raise DynamipsError("Port {} is not allocated".format(port_number))
# remove VCs mapped with the port
for source, destination in self._mappings.copy().items():
if len(source) == 3 and len(destination) == 3:
# remove the virtual channels mapped with this port/nio
source_port, source_vpi, source_vci = source
destination_port, destination_vpi, destination_vci = destination
if port_number == source_port:
yield from self.unmap_pvc(source_port, source_vpi, source_vci, destination_port, destination_vpi, destination_vci)
yield from self.unmap_pvc(destination_port, destination_vpi, destination_vci, source_port, source_vpi, source_vci)
else:
# remove the virtual paths mapped with this port/nio
source_port, source_vpi = source
destination_port, destination_vpi = destination
if port_number == source_port:
yield from self.unmap_vp(source_port, source_vpi, destination_port, destination_vpi)
yield from self.unmap_vp(destination_port, destination_vpi, source_port, source_vpi)
nio = self._nios[port_number]
if isinstance(nio, NIOUDP):
self.manager.port_manager.release_udp_port(nio.lport, self._project)

View File

@ -120,7 +120,7 @@ class C7200(Router):
npe-225, npe-300, npe-400 and npe-g2 (PowerPC c7200 only)
"""
if self.is_running():
if (yield from self.is_running()):
raise DynamipsError("Cannot change NPE on running router")
yield from self._hypervisor.send('c7200 set_npe "{name}" {npe}'.format(name=self._name, npe=npe))

View File

@ -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,

View File

@ -149,6 +149,7 @@ class FrameRelaySwitch(Device):
self._nios[port_number] = nio
@asyncio.coroutine
def remove_nio(self, port_number):
"""
Removes the specified NIO as member of this Frame Relay switch.
@ -161,6 +162,14 @@ class FrameRelaySwitch(Device):
if port_number not in self._nios:
raise DynamipsError("Port {} is not allocated".format(port_number))
# remove VCs mapped with the port
for source, destination in self._mappings.copy().items():
source_port, source_dlci = source
destination_port, destination_dlci = destination
if port_number == source_port:
yield from self.unmap_vc(source_port, source_dlci, destination_port, destination_dlci)
yield from self.unmap_vc(destination_port, destination_dlci, source_port, source_dlci)
nio = self._nios[port_number]
if isinstance(nio, NIOUDP):
self.manager.port_manager.release_udp_port(nio.lport, self._project)

View File

@ -151,10 +151,7 @@ class Router(BaseVM):
"system_id": self._system_id}
# return the relative path if the IOS image is in the images_path directory
server_config = self.manager.config.get_section_config("Server")
relative_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "IOS", self._image)
if os.path.exists(relative_image):
router_info["image"] = os.path.basename(self._image)
router_info["image"] = self.manager.get_relative_image_path(self._image)
# add the slots
slot_number = 0
@ -172,14 +169,6 @@ class Router(BaseVM):
return router_info
@classmethod
def reset(cls):
"""
Resets the instance count and the allocated instances list.
"""
cls._dynamips_ids.clear()
@property
def dynamips_id(self):
"""
@ -427,9 +416,7 @@ class Router(BaseVM):
:param image: path to IOS image file
"""
if not os.path.isabs(image):
server_config = self.manager.config.get_section_config("Server")
image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "IOS", image)
image = self.manager.get_abs_image_path(image)
if not os.path.isfile(image):
raise DynamipsError("IOS image '{}' is not accessible".format(image))
@ -849,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.
@ -1316,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))
@ -1348,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,
@ -1409,7 +1405,7 @@ class Router(BaseVM):
startup_config_path = os.path.join(module_workdir, "configs", "i{}_startup-config.cfg".format(self._dynamips_id))
if os.path.isfile(startup_config_path):
try:
with open(startup_config_path, "r+", errors="replace") as f:
with open(startup_config_path, "r+", encoding="utf-8", errors="replace") as f:
old_config = f.read()
new_config = old_config.replace(self.name, new_name)
f.seek(0)
@ -1422,7 +1418,7 @@ class Router(BaseVM):
private_config_path = os.path.join(module_workdir, "configs", "i{}_private-config.cfg".format(self._dynamips_id))
if os.path.isfile(private_config_path):
try:
with open(private_config_path, "r+", errors="replace") as f:
with open(private_config_path, "r+", encoding="utf-8", errors="replace") as f:
old_config = f.read()
new_config = old_config.replace(self.name, new_name)
f.seek(0)
@ -1449,6 +1445,17 @@ class Router(BaseVM):
private_config = private_config.replace("\\", '/')
if self._startup_config != startup_config or self._private_config != private_config:
self._startup_config = startup_config
self._private_config = private_config
module_workdir = self.project.module_working_directory(self.manager.module_name.lower())
private_config_path = os.path.join(module_workdir, private_config)
try:
if not os.path.getsize(private_config_path):
# an empty private-config can prevent a router to boot.
private_config = ''
except OSError as e:
raise DynamipsError("Cannot access the private-config {}: {}".format(private_config_path, e))
yield from self._hypervisor.send('vm set_config "{name}" "{startup}" "{private}"'.format(name=self._name,
startup=startup_config,
@ -1458,15 +1465,11 @@ class Router(BaseVM):
id=self._id,
startup=startup_config))
self._startup_config = startup_config
if private_config:
log.info('Router "{name}" [{id}]: has a new private-config set: "{private}"'.format(name=self._name,
id=self._id,
private=private_config))
self._private_config = private_config
@asyncio.coroutine
def extract_config(self):
"""
@ -1496,8 +1499,11 @@ 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(errors='replace')
config = base64.b64decode(startup_config_base64).decode("utf-8", errors="replace")
config = "!\n" + config.replace("\r", "")
config_path = os.path.join(module_workdir, self.startup_config)
with open(config_path, "wb") as f:
@ -1507,8 +1513,11 @@ 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(errors='replace')
config = base64.b64decode(private_config_base64).decode("utf-8", errors="replace")
config = "!\n" + config.replace("\r", "")
config_path = os.path.join(module_workdir, self.private_config)
with open(config_path, "wb") as f:

View File

@ -91,3 +91,9 @@ class IOU(BaseManager):
"""
return os.path.join("iou", "device-{}".format(legacy_vm_id))
def get_images_directory(self):
"""
Return the full path of the images directory on disk
"""
return os.path.join(os.path.expanduser(self.config.get_section_config("Server").get("images_path", "~/GNS3/images")), "IOU")

View File

@ -61,25 +61,9 @@ class IOUVM(BaseVM):
:param project: Project instance
:param manager: Manager instance
:param console: TCP console port
:params ethernet_adapters: number of ethernet adapters
:params serial_adapters: number of serial adapters
:params ram: amount of RAM in MB
:params nvram: amount of NVRAM in KB
:params l1_keepalives: always keep the Ethernet interfaces up
:params initial_config: content of the initial configuration file
:params iourc_content: content of the iourc file if no licence is installed on the machine
"""
def __init__(self, name, vm_id, project, manager,
console=None,
ram=None,
nvram=None,
use_default_iou_values=None,
ethernet_adapters=None,
serial_adapters=None,
l1_keepalives=None,
initial_config=None,
iourc_content=None):
def __init__(self, name, vm_id, project, manager, console=None):
super().__init__(name, vm_id, project, manager, console=console)
@ -94,17 +78,13 @@ class IOUVM(BaseVM):
# IOU settings
self._ethernet_adapters = []
self._serial_adapters = []
self.ethernet_adapters = 2 if ethernet_adapters is None else ethernet_adapters # one adapter = 4 interfaces
self.serial_adapters = 2 if serial_adapters is None else serial_adapters # one adapter = 4 interfaces
self._use_default_iou_values = True if use_default_iou_values is None else use_default_iou_values # for RAM & NVRAM values
self._nvram = 128 if nvram is None else nvram # Kilobytes
self.ethernet_adapters = 2 # one adapter = 4 interfaces
self.serial_adapters = 2 # one adapter = 4 interfaces
self._use_default_iou_values = True # for RAM & NVRAM values
self._nvram = 128 # Kilobytes
self._initial_config = ""
self._ram = 256 if ram is None else ram # Megabytes
self._l1_keepalives = False if l1_keepalives is None else l1_keepalives # used to overcome the always-up Ethernet interfaces (not supported by all IOSes).
self.iourc_content = iourc_content
if initial_config is not None:
self.initial_config = initial_config
self._ram = 256 # Megabytes
self._l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes).
@asyncio.coroutine
def close(self):
@ -145,14 +125,7 @@ class IOUVM(BaseVM):
:param path: path to the IOU image executable
"""
if not os.path.isabs(path):
server_config = self.manager.config.get_section_config("Server")
relative_path = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), path)
if not os.path.exists(relative_path):
relative_path = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "IOU", path)
path = relative_path
self._path = path
self._path = self.manager.get_abs_image_path(path)
# In 1.2 users uploaded images to the images roots
# after the migration their images are inside images/IOU
@ -236,15 +209,11 @@ class IOUVM(BaseVM):
"nvram": self._nvram,
"l1_keepalives": self._l1_keepalives,
"initial_config": self.relative_initial_config_file,
"use_default_iou_values": self._use_default_iou_values,
"iourc_path": self.iourc_path}
"iourc_path": self.iourc_path,
"use_default_iou_values": self._use_default_iou_values}
# return the relative path if the IOU image is in the images_path directory
server_config = self.manager.config.get_section_config("Server")
relative_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "IOU", self.path)
if os.path.exists(relative_image):
iou_vm_info["path"] = os.path.basename(self.path)
iou_vm_info["path"] = self.manager.get_relative_image_path(self.path)
return iou_vm_info
@property
@ -348,9 +317,9 @@ class IOUVM(BaseVM):
"""
if self.initial_config_file:
content = self.initial_config
content = self.initial_config_content
content = content.replace(self._name, new_name)
self.initial_config = content
self.initial_config_content = content
super(IOUVM, IOUVM).name.__set__(self, new_name)
@ -363,8 +332,8 @@ class IOUVM(BaseVM):
def iourc_content(self):
try:
with open(os.path.join(self.temporary_directory, "iourc")) as f:
return f.read()
with open(os.path.join(self.temporary_directory, "iourc"), "rb") as f:
return f.read().decode("utf-8")
except OSError:
return None
@ -374,8 +343,8 @@ class IOUVM(BaseVM):
if value is not None:
path = os.path.join(self.temporary_directory, "iourc")
try:
with open(path, "w+") as f:
f.write(value)
with open(path, "wb+") as f:
f.write(value.encode("utf-8"))
except OSError as e:
raise IOUError("Could not write the iourc file {}: {}".format(path, e))
@ -403,18 +372,20 @@ class IOUVM(BaseVM):
Checks for a valid IOU key in the iourc file (paranoid mode).
"""
license_check = self._manager.config.get_section_config("IOU").getboolean("license_check", False)
if license_check:
license_check = self._manager.config.get_section_config("IOU").getboolean("license_check", True)
if license_check is False:
return
config = configparser.ConfigParser()
try:
with open(self.iourc_path) as f:
with open(self.iourc_path, encoding="utf-8") as f:
config.read_file(f)
except OSError as e:
raise IOUError("Could not open iourc file {}: {}".format(self.iourc_path, e))
except configparser.Error as e:
raise IOUError("Could not parse iourc file {}: {}".format(self.iourc_path, e))
except UnicodeDecodeError as e:
raise IOUError("Invalid iourc file {}: {}".format(self.iourc_path, e))
if "license" not in config:
raise IOUError("License section not found in iourc file {}".format(self.iourc_path))
hostname = socket.gethostname()
@ -462,11 +433,14 @@ class IOUVM(BaseVM):
yield from self._library_check()
self._rename_nvram_file()
try:
self._rename_nvram_file()
except OSError as e:
raise IOUError("Could not rename nvram files: {}".format(e))
iourc_path = self.iourc_path
if iourc_path is None:
raise IOUError("Could not find a iourc file (IOU license)")
if not iourc_path:
raise IOUError("Could not find an iourc file (IOU license)")
if not os.path.isfile(iourc_path):
raise IOUError("The iourc path '{}' is not a regular file".format(iourc_path))
@ -486,7 +460,7 @@ class IOUVM(BaseVM):
log.info("Starting IOU: {}".format(self._command))
self._iou_stdout_file = os.path.join(self.working_dir, "iou.log")
log.info("Logging to {}".format(self._iou_stdout_file))
with open(self._iou_stdout_file, "w") as fd:
with open(self._iou_stdout_file, "w", encoding="utf-8") as fd:
self._iou_process = yield from asyncio.create_subprocess_exec(*self._command,
stdout=fd,
stderr=subprocess.STDOUT,
@ -530,7 +504,7 @@ class IOUVM(BaseVM):
log.info("starting iouyap: {}".format(command))
self._iouyap_stdout_file = os.path.join(self.working_dir, "iouyap.log")
log.info("logging to {}".format(self._iouyap_stdout_file))
with open(self._iouyap_stdout_file, "w") as fd:
with open(self._iouyap_stdout_file, "w", encoding="utf-8") as fd:
self._iouyap_process = yield from asyncio.create_subprocess_exec(*command,
stdout=fd,
stderr=subprocess.STDOUT,
@ -596,7 +570,7 @@ class IOUVM(BaseVM):
bay_id += 1
try:
with open(iouyap_ini, "w") as config_file:
with open(iouyap_ini, "w", encoding="utf-8") as config_file:
config.write(config_file)
log.info("IOU {name} [id={id}]: iouyap.ini updated".format(name=self._name,
id=self._id))
@ -618,7 +592,6 @@ class IOUVM(BaseVM):
self._ioucon_thread = None
self._terminate_process_iou()
if self._iou_process.returncode is None:
try:
yield from gns3server.utils.asyncio.wait_for_process_termination(self._iou_process, timeout=3)
@ -702,7 +675,7 @@ class IOUVM(BaseVM):
netmap_path = os.path.join(self.working_dir, "NETMAP")
try:
with open(netmap_path, "w") as f:
with open(netmap_path, "w", encoding="utf-8") as f:
for bay in range(0, 16):
for unit in range(0, 4):
f.write("{iouyap_id}:{bay}/{unit}{iou_id:>5d}:{bay}/{unit}\n".format(iouyap_id=str(self.application_id + 512),
@ -772,8 +745,8 @@ class IOUVM(BaseVM):
output = ""
if self._iou_stdout_file:
try:
with open(self._iou_stdout_file, errors="replace") as file:
output = file.read()
with open(self._iou_stdout_file, "rb") as file:
output = file.read().decode("utf-8", errors="replace")
except OSError as e:
log.warn("could not read {}: {}".format(self._iou_stdout_file, e))
return output
@ -787,8 +760,8 @@ class IOUVM(BaseVM):
output = ""
if self._iouyap_stdout_file:
try:
with open(self._iouyap_stdout_file, errors="replace") as file:
output = file.read()
with open(self._iouyap_stdout_file, "rb") as file:
output = file.read().decode("utf-8", errors="replace")
except OSError as e:
log.warn("could not read {}: {}".format(self._iouyap_stdout_file, e))
return output
@ -970,7 +943,7 @@ class IOUVM(BaseVM):
log.warn("could not determine if layer 1 keepalive messages are supported by {}: {}".format(os.path.basename(self._path), e))
@property
def initial_config(self):
def initial_config_content(self):
"""
Returns the content of the current initial-config file.
"""
@ -980,13 +953,13 @@ class IOUVM(BaseVM):
return None
try:
with open(config_file) as f:
return f.read()
with open(config_file, "rb") as f:
return f.read().decode("utf-8", errors="replace")
except OSError as e:
raise IOUError("Can't read configuration file '{}'".format(config_file))
raise IOUError("Can't read configuration file '{}': {}".format(config_file, e))
@initial_config.setter
def initial_config(self, initial_config):
@initial_config_content.setter
def initial_config_content(self, initial_config):
"""
Update the initial config
@ -994,23 +967,23 @@ class IOUVM(BaseVM):
"""
try:
script_file = os.path.join(self.working_dir, "initial-config.cfg")
initial_config_path = os.path.join(self.working_dir, "initial-config.cfg")
if initial_config is None:
initial_config = ''
# We disallow erasing the initial config file
if len(initial_config) == 0 and os.path.exists(script_file):
if len(initial_config) == 0 and os.path.exists(initial_config_path):
return
with open(script_file, 'w+') as f:
with open(initial_config_path, 'w+', encoding='utf-8') as f:
if len(initial_config) == 0:
f.write('')
else:
initial_config = initial_config.replace("%h", self._name)
f.write(initial_config)
except OSError as e:
raise IOUError("Can't write initial configuration file '{}': {}".format(script_file, e))
raise IOUError("Can't write initial configuration file '{}': {}".format(initial_config_path, e))
@property
def initial_config_file(self):
@ -1101,6 +1074,10 @@ class IOUVM(BaseVM):
port_number=port_number))
nio = adapter.get_nio(port_number)
if not nio:
raise IOUError("NIO {port_number} does not exist in adapter {adapter}".format(adapter=adapter,
port_number=port_number))
nio.stopPacketCapture()
log.info('IOU "{name}" [{id}]: stopping packet capture on {adapter_number}/{port_number}'.format(name=self._name,
id=self._id,

View File

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 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/>.
"""
Interface for NAT NIOs.
"""
from .nio import NIO
class NIONAT(NIO):
"""
NAT NIO.
"""
def __init__(self):
super().__init__()
def __str__(self):
return "NIO TAP"
def __json__(self):
return {"type": "nio_nat"}

View File

@ -140,7 +140,7 @@ class PortManager:
"""
if end_port < start_port:
raise Exception("Invalid port range {}-{}".format(start_port, end_port))
raise HTTPConflict(text="Invalid port range {}-{}".format(start_port, end_port))
if socket_type == "UDP":
socket_type = socket.SOCK_DGRAM
@ -252,19 +252,14 @@ class PortManager:
project.record_udp_port(port)
log.debug("UDP port {} has been reserved".format(port))
def release_udp_port(self, port, project, force_remove=False):
def release_udp_port(self, port, project):
"""
Release a specific UDP port number
:param port: UDP port number
:param project: Project instance
:param force_remove: Force port removal even on Darwnin
"""
# A bug with Dynamips on Darwin which doesn't correctly free UDP ports, they are freed only when changing the project
if sys.platform.startswith("darwin") and force_remove is False:
return
if port in self._used_udp_ports:
self._used_udp_ports.remove(port)
project.remove_udp_port(port)

View File

@ -102,6 +102,7 @@ class Project:
server_config = Config.instance().get_section_config("Server")
path = os.path.expanduser(server_config.get("projects_path", "~/GNS3/projects"))
path = os.path.normpath(path)
try:
os.makedirs(path, exist_ok=True)
except OSError as e:
@ -138,9 +139,25 @@ class Project:
if path != self._path and self.is_local() is False:
raise aiohttp.web.HTTPForbidden(text="You are not allowed to modify the project directory location")
old_path = None
if hasattr(self, "_path"):
old_path = self._path
self._path = path
self._update_temporary_file()
@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:
yield from wait_run_in_executor(shutil.rmtree, old_path)
except OSError as e:
log.warn("Can't remove temporary directory {}: {}".format(old_path, e))
@property
def name(self):
@ -227,7 +244,10 @@ class Project:
raise aiohttp.web.HTTPInternalServerError(text="Could not create temporary project: {}".format(e))
else:
if os.path.exists(os.path.join(self._path, ".gns3_temporary")):
os.remove(os.path.join(self._path, ".gns3_temporary"))
try:
os.remove(os.path.join(self._path, ".gns3_temporary"))
except OSError as e:
raise aiohttp.web.HTTPInternalServerError(text="Could not mark project as no longer temporary: {}".format(e))
def module_working_directory(self, module_name):
"""
@ -365,7 +385,7 @@ class Project:
for port in self._used_tcp_ports.copy():
port_manager.release_tcp_port(port, self)
for port in self._used_udp_ports.copy():
port_manager.release_udp_port(port, self, force_remove=True)
port_manager.release_udp_port(port, self)
@asyncio.coroutine
def commit(self):

View File

@ -30,6 +30,9 @@ from ..base_manager import BaseManager
from .qemu_error import QemuError
from .qemu_vm import QemuVM
import logging
log = logging.getLogger(__name__)
class Qemu(BaseManager):
@ -44,7 +47,15 @@ class Qemu(BaseManager):
"""
qemus = []
paths = [os.getcwd()] + os.environ["PATH"].split(os.pathsep)
paths = set()
try:
paths.add(os.getcwd())
except FileNotFoundError:
log.warning("The current working directory doesn't exist")
if "PATH" in os.environ:
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
if sys.platform.startswith("win"):
# add specific Windows paths
@ -53,21 +64,25 @@ 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):
if (f.startswith("qemu-system") or f == "qemu" or f == "qemu.exe") and \
if (f.startswith("qemu-system") or f.startswith("qemu-kvm") or f == "qemu" or f == "qemu.exe") and \
os.access(os.path.join(path, f), os.X_OK) and \
os.path.isfile(os.path.join(path, f)):
qemu_path = os.path.join(path, f)
@ -112,3 +127,9 @@ class Qemu(BaseManager):
"""
return os.path.join("qemu", "vm-{}".format(legacy_vm_id))
def get_images_directory(self):
"""
Return the full path of the images directory on disk
"""
return os.path.join(os.path.expanduser(self.config.get_section_config("Server").get("images_path", "~/GNS3/images")), "QEMU")

View File

@ -23,7 +23,6 @@ order to run a QEMU VM.
import sys
import os
import shutil
import random
import subprocess
import shlex
import asyncio
@ -32,6 +31,8 @@ import socket
from .qemu_error import QemuError
from ..adapters.ethernet_adapter import EthernetAdapter
from ..nios.nio_udp import NIOUDP
from ..nios.nio_tap import NIOTAP
from ..nios.nio_nat import NIONAT
from ..base_vm import BaseVM
from ...schemas.qemu import QEMU_OBJECT_SCHEMA
@ -68,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 = ""
@ -148,14 +149,10 @@ class QemuVM(BaseVM):
:param hda_disk_image: QEMU hda disk image path
"""
if not os.path.isabs(hda_disk_image):
server_config = self.manager.config.get_section_config("Server")
hda_disk_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "QEMU", hda_disk_image)
self._hda_disk_image = self.manager.get_abs_image_path(hda_disk_image)
log.info('QEMU VM "{name}" [{id}] has set the QEMU hda disk image path to {disk_image}'.format(name=self._name,
id=self._id,
disk_image=hda_disk_image))
self._hda_disk_image = hda_disk_image
disk_image=self._hda_disk_image))
@property
def hdb_disk_image(self):
@ -175,14 +172,10 @@ class QemuVM(BaseVM):
:param hdb_disk_image: QEMU hdb disk image path
"""
if not os.path.isabs(hdb_disk_image):
server_config = self.manager.config.get_section_config("Server")
hdb_disk_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "QEMU", hdb_disk_image)
self._hdb_disk_image = self.manager.get_abs_image_path(hdb_disk_image)
log.info('QEMU VM "{name}" [{id}] has set the QEMU hdb disk image path to {disk_image}'.format(name=self._name,
id=self._id,
disk_image=hdb_disk_image))
self._hdb_disk_image = hdb_disk_image
disk_image=self._hdb_disk_image))
@property
def hdc_disk_image(self):
@ -202,14 +195,10 @@ class QemuVM(BaseVM):
:param hdc_disk_image: QEMU hdc disk image path
"""
if not os.path.isabs(hdc_disk_image):
server_config = self.manager.config.get_section_config("Server")
hdc_disk_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "QEMU", hdc_disk_image)
self._hdc_disk_image = self.manager.get_abs_image_path(hdc_disk_image)
log.info('QEMU VM "{name}" [{id}] has set the QEMU hdc disk image path to {disk_image}'.format(name=self._name,
id=self._id,
disk_image=hdc_disk_image))
self._hdc_disk_image = hdc_disk_image
disk_image=self._hdc_disk_image))
@property
def hdd_disk_image(self):
@ -229,14 +218,11 @@ class QemuVM(BaseVM):
:param hdd_disk_image: QEMU hdd disk image path
"""
if not os.path.isabs(hdd_disk_image):
server_config = self.manager.config.get_section_config("Server")
hdd_disk_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "QEMU", hdd_disk_image)
self._hdd_disk_image = hdd_disk_image
self._hdd_disk_image = self.manager.get_abs_image_path(hdd_disk_image)
log.info('QEMU VM "{name}" [{id}] has set the QEMU hdd disk image path to {disk_image}'.format(name=self._name,
id=self._id,
disk_image=hdd_disk_image))
self._hdd_disk_image = hdd_disk_image
disk_image=self._hdd_disk_image))
@property
def adapters(self):
@ -589,7 +575,7 @@ class QemuVM(BaseVM):
log.info("Starting QEMU: {}".format(self._command))
self._stdout_file = os.path.join(self.working_dir, "qemu.log")
log.info("logging to {}".format(self._stdout_file))
with open(self._stdout_file, "w") as fd:
with open(self._stdout_file, "w", encoding="utf-8") as fd:
self._process = yield from asyncio.create_subprocess_exec(*self._command,
stdout=fd,
stderr=subprocess.STDOUT,
@ -659,7 +645,7 @@ class QemuVM(BaseVM):
break
for expect in expected:
if expect in line:
result = line.decode().strip()
result = line.decode("utf-8").strip()
break
except EOFError as e:
log.warn("Could not read from QEMU monitor: {}".format(e))
@ -755,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:
@ -765,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
@ -778,9 +765,9 @@ class QemuVM(BaseVM):
adapter.add_nio(0, nio)
log.info('QEMU VM "{name}" [{id}]: {nio} added to adapter {adapter_number}'.format(name=self._name,
id=self._id,
nio=nio,
adapter_number=adapter_number))
id=self._id,
nio=nio,
adapter_number=adapter_number))
@asyncio.coroutine
def adapter_remove_nio_binding(self, adapter_number):
@ -799,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))
@ -832,8 +820,8 @@ class QemuVM(BaseVM):
output = ""
if self._stdout_file:
try:
with open(self._stdout_file, errors="replace") as file:
output = file.read()
with open(self._stdout_file, "rb") as file:
output = file.read().decode("utf-8", errors="replace")
except OSError as e:
log.warn("Could not read {}: {}".format(self._stdout_file, e))
return output
@ -995,40 +983,47 @@ class QemuVM(BaseVM):
return options
def _get_random_mac(self, adapter_number):
# TODO: let users specify a base mac address
return "00:00:ab:%02x:%02x:%02d" % (random.randint(0x00, 0xff), random.randint(0x00, 0xff), adapter_number)
def _network_options(self):
network_options = []
adapter_number = 0
for adapter in self._ethernet_adapters:
mac = self._get_random_mac(adapter_number)
if self._legacy_networking:
network_options.extend(["-net", "nic,vlan={},macaddr={},model={}".format(adapter_number, mac, self._adapter_type)])
else:
network_options.extend(["-device", "{},mac={},netdev=gns3-{}".format(self._adapter_type, mac, adapter_number)])
network_options.extend(["-net", "none"]) # we do not want any user networking back-end if no adapter is connected.
for adapter_number, adapter in enumerate(self._ethernet_adapters):
# TODO: let users specify a base mac address
mac = "00:00:ab:%s:%s:%02x" % (self.id[-4:-2], self.id[-2:], adapter_number)
nio = adapter.get_nio(0)
if nio and isinstance(nio, NIOUDP):
if self._legacy_networking:
network_options.extend(["-net", "udp,vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number,
adapter_number,
nio.lport,
nio.rport,
nio.rhost)])
if self._legacy_networking:
# legacy QEMU networking syntax (-net)
if nio:
network_options.extend(["-net", "nic,vlan={},macaddr={},model={}".format(adapter_number, mac, self._adapter_type)])
if isinstance(nio, NIOUDP):
network_options.extend(["-net", "udp,vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number,
adapter_number,
nio.lport,
nio.rport,
nio.rhost)])
elif isinstance(nio, NIOTAP):
network_options.extend(["-net", "tap,name=gns3-{},ifname={}".format(adapter_number, nio.tap_device)])
elif isinstance(nio, NIONAT):
network_options.extend(["-net", "user,vlan={},name=gns3-{}".format(adapter_number, adapter_number)])
else:
network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number,
nio.rhost,
nio.rport,
self._host,
nio.lport)])
network_options.extend(["-net", "nic,vlan={},macaddr={},model={}".format(adapter_number, mac, self._adapter_type)])
else:
if self._legacy_networking:
network_options.extend(["-net", "user,vlan={},name=gns3-{}".format(adapter_number, adapter_number)])
# newer QEMU networking syntax
if nio:
network_options.extend(["-device", "{},mac={},netdev=gns3-{}".format(self._adapter_type, mac, adapter_number)])
if isinstance(nio, NIOUDP):
network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number,
nio.rhost,
nio.rport,
self._host,
nio.lport)])
elif isinstance(nio, NIOTAP):
network_options.extend(["-netdev", "tap,id=gns3-{},ifname={}".format(adapter_number, nio.tap_device)])
elif isinstance(nio, NIONAT):
network_options.extend(["-netdev", "user,id=gns3-{}".format(adapter_number)])
else:
network_options.extend(["-netdev", "user,id=gns3-{}".format(adapter_number)])
adapter_number += 1
network_options.extend(["-device", "{},mac={}".format(self._adapter_type, mac)])
return network_options
@ -1068,23 +1063,6 @@ class QemuVM(BaseVM):
command.extend(self._graphic())
return command
def _get_relative_disk_image_path(self, disk_image):
"""
Returns a relative image path if the disk image is in the images directory.
:param disk_image: path to the disk image
:returns: relative or full path
"""
if disk_image:
# return the relative path if the disk image is in the images_path directory
server_config = self.manager.config.get_section_config("Server")
relative_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "QEMU", disk_image)
if os.path.exists(relative_image):
return os.path.basename(disk_image)
return disk_image
def __json__(self):
answer = {
"project_id": self.project.id,
@ -1095,11 +1073,11 @@ class QemuVM(BaseVM):
if field not in answer:
answer[field] = getattr(self, field)
answer["hda_disk_image"] = self._get_relative_disk_image_path(self._hda_disk_image)
answer["hdb_disk_image"] = self._get_relative_disk_image_path(self._hdb_disk_image)
answer["hdc_disk_image"] = self._get_relative_disk_image_path(self._hdc_disk_image)
answer["hdd_disk_image"] = self._get_relative_disk_image_path(self._hdd_disk_image)
answer["initrd"] = self._get_relative_disk_image_path(self._initrd)
answer["kernel_image"] = self._get_relative_disk_image_path(self._kernel_image)
answer["hda_disk_image"] = self.manager.get_relative_image_path(self._hda_disk_image)
answer["hdb_disk_image"] = self.manager.get_relative_image_path(self._hdb_disk_image)
answer["hdc_disk_image"] = self.manager.get_relative_image_path(self._hdc_disk_image)
answer["hdd_disk_image"] = self.manager.get_relative_image_path(self._hdd_disk_image)
answer["initrd"] = self.manager.get_relative_image_path(self._initrd)
answer["kernel_image"] = self.manager.get_relative_image_path(self._kernel_image)
return answer

View File

@ -41,6 +41,7 @@ class VirtualBox(BaseManager):
super().__init__()
self._vboxmanage_path = None
self._execute_lock = asyncio.Lock()
@property
def vboxmanage_path(self):
@ -82,34 +83,81 @@ class VirtualBox(BaseManager):
@asyncio.coroutine
def execute(self, subcommand, args, timeout=60):
vboxmanage_path = self.vboxmanage_path
if not vboxmanage_path:
vboxmanage_path = self.find_vboxmanage()
command = [vboxmanage_path, "--nologo", subcommand]
command.extend(args)
log.debug("Executing VBoxManage with command: {}".format(command))
# We use a lock prevent parallel execution due to strange errors
# reported by a user and reproduced by us.
# https://github.com/GNS3/gns3-gui/issues/261
with (yield from self._execute_lock):
vboxmanage_path = self.vboxmanage_path
if not vboxmanage_path:
vboxmanage_path = self.find_vboxmanage()
command = [vboxmanage_path, "--nologo", subcommand]
command.extend(args)
log.debug("Executing VBoxManage with command: {}".format(command))
try:
vbox_user = self.config.get_section_config("VirtualBox").get("vbox_user")
if vbox_user:
# TODO: test & review this part
sudo_command = "sudo -i -u {}".format(vbox_user) + " ".join(command)
process = yield from asyncio.create_subprocess_shell(sudo_command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
else:
process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
except (OSError, subprocess.SubprocessError) as e:
raise VirtualBoxError("Could not execute VBoxManage: {}".format(e))
try:
stdout_data, stderr_data = yield from asyncio.wait_for(process.communicate(), timeout=timeout)
except asyncio.TimeoutError:
raise VirtualBoxError("VBoxManage has timed out after {} seconds!".format(timeout))
if process.returncode:
vboxmanage_error = stderr_data.decode("utf-8", errors="ignore")
raise VirtualBoxError("VirtualBox has returned an error: {}".format(vboxmanage_error))
return stdout_data.decode("utf-8", errors="ignore").splitlines()
@asyncio.coroutine
def _find_inaccessible_hdd_files(self):
"""
Finds inaccessible disk files (to clean up the VirtualBox media manager)
"""
hdds = []
try:
vbox_user = self.config.get_section_config("VirtualBox").get("vbox_user")
if vbox_user:
# TODO: test & review this part
sudo_command = "sudo -i -u {}".format(vbox_user) + " ".join(command)
process = yield from asyncio.create_subprocess_shell(sudo_command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
else:
process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
except (OSError, subprocess.SubprocessError) as e:
raise VirtualBoxError("Could not execute VBoxManage: {}".format(e))
properties = yield from self.execute("list", ["hdds"])
# If VirtualBox is not available we have no inaccessible hdd
except VirtualBoxError:
return hdds
try:
stdout_data, stderr_data = yield from asyncio.wait_for(process.communicate(), timeout=timeout)
except asyncio.TimeoutError:
raise VirtualBoxError("VBoxManage has timed out after {} seconds!".format(timeout))
flag_inaccessible = False
for prop in properties:
try:
name, value = prop.split(':', 1)
except ValueError:
continue
if name.strip() == "State" and value.strip() == "inaccessible":
flag_inaccessible = True
if flag_inaccessible and name.strip() == "Location":
hdds.append(value.strip())
flag_inaccessible = False
return reversed(hdds)
if process.returncode:
# only the first line of the output is useful
vboxmanage_error = stderr_data.decode("utf-8", errors="ignore")
raise VirtualBoxError("VirtualBox has returned an error: {}".format(vboxmanage_error))
@asyncio.coroutine
def project_closed(self, project):
"""
Called when a project is closed.
return stdout_data.decode("utf-8", errors="ignore").splitlines()
:param project: Project instance
"""
yield from super().project_closed(project)
hdd_files_to_close = yield from self._find_inaccessible_hdd_files()
for hdd_file in hdd_files_to_close:
log.info("Closing VirtualBox VM disk file {}".format(os.path.basename(hdd_file)))
try:
yield from self.execute("closemedium", ["disk", hdd_file])
except VirtualBoxError as e:
log.warning("Could not close VirtualBox VM disk file {}: {}".format(os.path.basename(hdd_file), e))
continue
@asyncio.coroutine
def get_list(self):
@ -120,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>":
@ -128,6 +178,7 @@ class VirtualBox(BaseManager):
if not extra_data[0].strip() == "Value: yes":
# get the amount of RAM
info_results = yield from self.execute("showvminfo", [vmname, "--machinereadable"])
ram = 0
for info in info_results:
try:
name, value = info.split('=', 1)

View File

@ -31,6 +31,7 @@ import asyncio
from pkg_resources import parse_version
from .virtualbox_error import VirtualBoxError
from ..nios.nio_udp import NIOUDP
from ..nios.nio_nat import NIONAT
from ..adapters.ethernet_adapter import EthernetAdapter
from .telnet_server import TelnetServer # TODO: port TelnetServer to asyncio
from ..base_vm import BaseVM
@ -105,9 +106,10 @@ class VirtualBoxVM(BaseVM):
results = yield from self.manager.execute("showvminfo", [self._vmname, "--machinereadable"])
for info in results:
name, value = info.split('=', 1)
if name == "VMState":
return value.strip('"')
if '=' in info:
name, value = info.split('=', 1)
if name == "VMState":
return value.strip('"')
raise VirtualBoxError("Could not get VM state for {}".format(self._vmname))
@asyncio.coroutine
@ -139,6 +141,8 @@ class VirtualBoxVM(BaseVM):
def create(self):
yield from self._get_system_properties()
if "API version" not in self._system_properties:
raise VirtualBoxError("Can't access to VirtualBox API Version")
if parse_version(self._system_properties["API version"]) < parse_version("4_3"):
raise VirtualBoxError("The VirtualBox API version is lower than 4.3")
log.info("VirtualBox VM '{name}' [{id}] created".format(name=self.name, id=self.id))
@ -206,7 +210,7 @@ class VirtualBoxVM(BaseVM):
log.info("VirtualBox VM '{name}' [{id}] stopped".format(name=self.name, id=self.id))
log.debug("Stop result: {}".format(result))
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:
# deactivate the first serial port
yield from self._modify_vm("--uart1 off")
@ -273,7 +277,7 @@ class VirtualBoxVM(BaseVM):
hdd_info_file = os.path.join(self.working_dir, self._vmname, "hdd_info.json")
try:
with open(hdd_info_file, "r") as f:
with open(hdd_info_file, "r", encoding="utf-8") as f:
hdd_table = json.load(f)
except OSError as e:
raise VirtualBoxError("Could not read HDD info file: {}".format(e))
@ -351,7 +355,7 @@ class VirtualBoxVM(BaseVM):
if hdd_table:
try:
hdd_info_file = os.path.join(self.working_dir, self._vmname, "hdd_info.json")
with open(hdd_info_file, "w") as f:
with open(hdd_info_file, "w", encoding="utf-8") as f:
json.dump(hdd_table, f, indent=4)
except OSError as e:
log.warning("VirtualBox VM '{name}' [{id}] could not write HHD info file: {error}".format(name=self.name,
@ -583,12 +587,14 @@ class VirtualBoxVM(BaseVM):
:returns: pipe path (string)
"""
p = re.compile('\s+', re.UNICODE)
pipe_name = p.sub("_", self._vmname)
if sys.platform.startswith("win"):
pipe_name = r"\\.\pipe\VBOX\{}".format(pipe_name)
pipe_name = r"\\.\pipe\gns3_vbox\{}".format(self.id)
else:
pipe_name = os.path.join(tempfile.gettempdir(), "pipe_{}".format(pipe_name))
pipe_name = os.path.join(tempfile.gettempdir(), "gns3_vbox", "{}".format(self.id))
try:
os.makedirs(os.path.dirname(pipe_name), exist_ok=True)
except OSError as e:
raise VirtualBoxError("Could not create the VirtualBox pipe directory: {}".format(e))
return pipe_name
@asyncio.coroutine
@ -654,12 +660,12 @@ class VirtualBoxVM(BaseVM):
yield from self._modify_vm("--cableconnected{} off".format(adapter_number + 1))
nio = self._ethernet_adapters[adapter_number].get_nio(0)
if nio:
if not self._use_any_adapter and attachment not in ("none", "null", "generic"):
if not isinstance(nio, NIONAT) and not self._use_any_adapter and attachment not in ("none", "null", "generic"):
raise VirtualBoxError("Attachment ({}) already configured on adapter {}. "
"Please set it to 'Not attached' to allow GNS3 to use it.".format(attachment,
adapter_number + 1))
yield from self._modify_vm("--nictrace{} off".format(adapter_number + 1))
yield from self._modify_vm("--nictrace{} off".format(adapter_number + 1))
vbox_adapter_type = "82540EM"
if self._adapter_type == "PCnet-PCI II (Am79C970A)":
vbox_adapter_type = "Am79C970A"
@ -676,13 +682,17 @@ class VirtualBoxVM(BaseVM):
args = [self._vmname, "--nictype{}".format(adapter_number + 1), vbox_adapter_type]
yield from self.manager.execute("modifyvm", args)
log.debug("setting UDP params on adapter {}".format(adapter_number))
yield from self._modify_vm("--nic{} generic".format(adapter_number + 1))
yield from self._modify_vm("--nicgenericdrv{} UDPTunnel".format(adapter_number + 1))
yield from self._modify_vm("--nicproperty{} sport={}".format(adapter_number + 1, nio.lport))
yield from self._modify_vm("--nicproperty{} dest={}".format(adapter_number + 1, nio.rhost))
yield from self._modify_vm("--nicproperty{} dport={}".format(adapter_number + 1, nio.rport))
yield from self._modify_vm("--cableconnected{} on".format(adapter_number + 1))
if isinstance(nio, NIOUDP):
log.debug("setting UDP params on adapter {}".format(adapter_number))
yield from self._modify_vm("--nic{} generic".format(adapter_number + 1))
yield from self._modify_vm("--nicgenericdrv{} UDPTunnel".format(adapter_number + 1))
yield from self._modify_vm("--nicproperty{} sport={}".format(adapter_number + 1, nio.lport))
yield from self._modify_vm("--nicproperty{} dest={}".format(adapter_number + 1, nio.rhost))
yield from self._modify_vm("--nicproperty{} dport={}".format(adapter_number + 1, nio.rport))
yield from self._modify_vm("--cableconnected{} on".format(adapter_number + 1))
elif isinstance(nio, NIONAT):
yield from self._modify_vm("--nic{} nat".format(adapter_number + 1))
yield from self._modify_vm("--cableconnected{} on".format(adapter_number + 1))
if nio.capturing:
yield from self._modify_vm("--nictrace{} on".format(adapter_number + 1))
@ -789,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,
@ -820,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))
@ -841,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.
@ -851,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))
@ -873,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,

View File

@ -62,9 +62,9 @@ class VPCS(BaseManager):
"""
vm = self.get_vm(vm_id)
i = self._used_mac_ids[vm_id]
self._free_mac_ids[vm.project.id].insert(0, i)
if vm_id in self._used_mac_ids:
i = self._used_mac_ids[vm_id]
self._free_mac_ids[vm.project.id].insert(0, i)
del self._used_mac_ids[vm_id]
yield from super().close_vm(vm_id, *args, **kwargs)
return vm

View File

@ -27,6 +27,7 @@ import signal
import re
import asyncio
import shutil
import gns3server.utils.asyncio
from pkg_resources import parse_version
from .vpcs_error import VPCSError
@ -166,8 +167,8 @@ class VPCSVM(BaseVM):
return None
try:
with open(script_file) as f:
return f.read()
with open(script_file, "rb") as f:
return f.read().decode("utf-8", errors="replace")
except OSError as e:
raise VPCSError('Cannot read the startup script file "{}": {}'.format(script_file, e))
@ -180,15 +181,15 @@ class VPCSVM(BaseVM):
"""
try:
script_file = os.path.join(self.working_dir, 'startup.vpc')
with open(script_file, 'w+') as f:
startup_script_path = os.path.join(self.working_dir, 'startup.vpc')
with open(startup_script_path, "w+", encoding='utf-8') as f:
if startup_script is None:
f.write('')
else:
startup_script = startup_script.replace("%h", self._name)
f.write(startup_script)
except OSError as e:
raise VPCSError('Cannot write the startup script file "{}": {}'.format(self.script_file, e))
raise VPCSError('Cannot write the startup script file "{}": {}'.format(startup_script_path, e))
@asyncio.coroutine
def _check_vpcs_version(self):
@ -226,7 +227,7 @@ class VPCSVM(BaseVM):
flags = 0
if sys.platform.startswith("win32"):
flags = subprocess.CREATE_NEW_PROCESS_GROUP
with open(self._vpcs_stdout_file, "w") as fd:
with open(self._vpcs_stdout_file, "w", encoding="utf-8") as fd:
self._process = yield from asyncio.create_subprocess_exec(*self._command,
stdout=fd,
stderr=subprocess.STDOUT,
@ -247,12 +248,16 @@ class VPCSVM(BaseVM):
if self.is_running():
self._terminate_process()
try:
yield from asyncio.wait_for(self._process.wait(), timeout=3)
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()
if self._process.returncode is None:
try:
yield from gns3server.utils.asyncio.wait_for_process_termination(self._process, timeout=3)
except asyncio.TimeoutError:
if self._process.returncode is None:
log.warn("VPCS process {} is still running... killing it".format(self._process.pid))
try:
self._process.kill()
except OSError as e:
raise VPCSError("Can not stop the VPCS process: {}".format(e))
self._process = None
self._started = False
@ -290,8 +295,8 @@ class VPCSVM(BaseVM):
output = ""
if self._vpcs_stdout_file:
try:
with open(self._vpcs_stdout_file, errors="replace") as file:
output = file.read()
with open(self._vpcs_stdout_file, "rb") as file:
output = file.read().decode("utf-8", errors="replace")
except OSError as e:
log.warn("Could not read {}: {}".format(self._vpcs_stdout_file, e))
return output
@ -388,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:
@ -402,12 +410,8 @@ 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([self.script_file])
command.extend([os.path.basename(self.script_file)])
return command
@property

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"},

View File

@ -284,20 +284,10 @@ VM_UPDATE_SCHEMA = {
"type": "string",
"minLength": 1,
},
"startup_config": {
"description": "path to the IOS startup configuration file",
"type": "string",
"minLength": 1,
},
"startup_config_content": {
"description": "Content of IOS startup configuration file",
"type": "string",
},
"private_config": {
"description": "path to the IOS private configuration file",
"type": "string",
"minLength": 1,
},
"private_config_content": {
"description": "Content of IOS private configuration file",
"type": "string",
@ -483,147 +473,6 @@ VM_UPDATE_SCHEMA = {
"additionalProperties": False,
}
VM_NIO_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to add a NIO for a Dynamips VM instance",
"type": "object",
"definitions": {
"UDP": {
"description": "UDP Network Input/Output",
"properties": {
"type": {
"enum": ["nio_udp"]
},
"lport": {
"description": "Local port",
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"rhost": {
"description": "Remote host",
"type": "string",
"minLength": 1
},
"rport": {
"description": "Remote port",
"type": "integer",
"minimum": 1,
"maximum": 65535
}
},
"required": ["type", "lport", "rhost", "rport"],
"additionalProperties": False
},
"Ethernet": {
"description": "Generic Ethernet Network Input/Output",
"properties": {
"type": {
"enum": ["nio_generic_ethernet"]
},
"ethernet_device": {
"description": "Ethernet device name e.g. eth0",
"type": "string",
"minLength": 1
},
},
"required": ["type", "ethernet_device"],
"additionalProperties": False
},
"LinuxEthernet": {
"description": "Linux Ethernet Network Input/Output",
"properties": {
"type": {
"enum": ["nio_linux_ethernet"]
},
"ethernet_device": {
"description": "Ethernet device name e.g. eth0",
"type": "string",
"minLength": 1
},
},
"required": ["type", "ethernet_device"],
"additionalProperties": False
},
"TAP": {
"description": "TAP Network Input/Output",
"properties": {
"type": {
"enum": ["nio_tap"]
},
"tap_device": {
"description": "TAP device name e.g. tap0",
"type": "string",
"minLength": 1
},
},
"required": ["type", "tap_device"],
"additionalProperties": False
},
"UNIX": {
"description": "UNIX Network Input/Output",
"properties": {
"type": {
"enum": ["nio_unix"]
},
"local_file": {
"description": "path to the UNIX socket file (local)",
"type": "string",
"minLength": 1
},
"remote_file": {
"description": "path to the UNIX socket file (remote)",
"type": "string",
"minLength": 1
},
},
"required": ["type", "local_file", "remote_file"],
"additionalProperties": False
},
"VDE": {
"description": "VDE Network Input/Output",
"properties": {
"type": {
"enum": ["nio_vde"]
},
"control_file": {
"description": "path to the VDE control file",
"type": "string",
"minLength": 1
},
"local_file": {
"description": "path to the VDE control file",
"type": "string",
"minLength": 1
},
},
"required": ["type", "control_file", "local_file"],
"additionalProperties": False
},
"NULL": {
"description": "NULL Network Input/Output",
"properties": {
"type": {
"enum": ["nio_null"]
},
},
"required": ["type"],
"additionalProperties": False
},
},
"oneOf": [
{"$ref": "#/definitions/UDP"},
{"$ref": "#/definitions/Ethernet"},
{"$ref": "#/definitions/LinuxEthernet"},
{"$ref": "#/definitions/TAP"},
{"$ref": "#/definitions/UNIX"},
{"$ref": "#/definitions/VDE"},
{"$ref": "#/definitions/NULL"},
],
"additionalProperties": True,
"required": ["type"]
}
VM_CAPTURE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to start a packet capture on a Dynamips VM instance port",
@ -896,5 +745,4 @@ VM_CONFIGS_SCHEMA = {
},
},
"additionalProperties": False,
"required": ["startup_config_content", "private_config_content"]
}

View File

@ -70,8 +70,12 @@ IOU_CREATE_SCHEMA = {
"description": "Use default IOU values",
"type": ["boolean", "null"]
},
"initial_config": {
"description": "Path to the initial configuration of IOU",
"type": ["string", "null"]
},
"initial_config_content": {
"description": "Initial configuration of the IOU",
"description": "Initial configuration of IOU",
"type": ["string", "null"]
},
"iourc_content": {
@ -124,7 +128,7 @@ IOU_UPDATE_SCHEMA = {
"type": ["boolean", "null"]
},
"initial_config_content": {
"description": "Initial configuration of the IOU",
"description": "Initial configuration of IOU",
"type": ["string", "null"]
},
"use_default_iou_values": {
@ -210,78 +214,6 @@ IOU_OBJECT_SCHEMA = {
"required": ["name", "vm_id", "console", "project_id", "path", "serial_adapters", "ethernet_adapters", "ram", "nvram", "l1_keepalives", "initial_config", "use_default_iou_values"]
}
IOU_NIO_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to add a NIO for a IOU instance",
"type": "object",
"definitions": {
"UDP": {
"description": "UDP Network Input/Output",
"properties": {
"type": {
"enum": ["nio_udp"]
},
"lport": {
"description": "Local port",
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"rhost": {
"description": "Remote host",
"type": "string",
"minLength": 1
},
"rport": {
"description": "Remote port",
"type": "integer",
"minimum": 1,
"maximum": 65535
}
},
"required": ["type", "lport", "rhost", "rport"],
"additionalProperties": False
},
"Ethernet": {
"description": "Generic Ethernet Network Input/Output",
"properties": {
"type": {
"enum": ["nio_generic_ethernet"]
},
"ethernet_device": {
"description": "Ethernet device name e.g. eth0",
"type": "string",
"minLength": 1
},
},
"required": ["type", "ethernet_device"],
"additionalProperties": False
},
"TAP": {
"description": "TAP Network Input/Output",
"properties": {
"type": {
"enum": ["nio_tap"]
},
"tap_device": {
"description": "TAP device name e.g. tap0",
"type": "string",
"minLength": 1
},
},
"required": ["type", "tap_device"],
"additionalProperties": False
},
},
"oneOf": [
{"$ref": "#/definitions/UDP"},
{"$ref": "#/definitions/Ethernet"},
{"$ref": "#/definitions/TAP"},
],
"additionalProperties": True,
"required": ["type"]
}
IOU_CAPTURE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to start a packet capture on a IOU instance",

169
gns3server/schemas/nio.py Normal file
View File

@ -0,0 +1,169 @@
# -*- 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/>.
NIO_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to add a NIO for a VM instance",
"type": "object",
"definitions": {
"UDP": {
"description": "UDP Network Input/Output",
"properties": {
"type": {
"enum": ["nio_udp"]
},
"lport": {
"description": "Local port",
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"rhost": {
"description": "Remote host",
"type": "string",
"minLength": 1
},
"rport": {
"description": "Remote port",
"type": "integer",
"minimum": 1,
"maximum": 65535
}
},
"required": ["type", "lport", "rhost", "rport"],
"additionalProperties": False
},
"Ethernet": {
"description": "Generic Ethernet Network Input/Output",
"properties": {
"type": {
"enum": ["nio_generic_ethernet"]
},
"ethernet_device": {
"description": "Ethernet device name e.g. eth0",
"type": "string",
"minLength": 1
},
},
"required": ["type", "ethernet_device"],
"additionalProperties": False
},
"LinuxEthernet": {
"description": "Linux Ethernet Network Input/Output",
"properties": {
"type": {
"enum": ["nio_linux_ethernet"]
},
"ethernet_device": {
"description": "Ethernet device name e.g. eth0",
"type": "string",
"minLength": 1
},
},
"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": {
"type": {
"enum": ["nio_tap"]
},
"tap_device": {
"description": "TAP device name e.g. tap0",
"type": "string",
"minLength": 1
},
},
"required": ["type", "tap_device"],
"additionalProperties": False
},
"UNIX": {
"description": "UNIX Network Input/Output",
"properties": {
"type": {
"enum": ["nio_unix"]
},
"local_file": {
"description": "path to the UNIX socket file (local)",
"type": "string",
"minLength": 1
},
"remote_file": {
"description": "path to the UNIX socket file (remote)",
"type": "string",
"minLength": 1
},
},
"required": ["type", "local_file", "remote_file"],
"additionalProperties": False
},
"VDE": {
"description": "VDE Network Input/Output",
"properties": {
"type": {
"enum": ["nio_vde"]
},
"control_file": {
"description": "path to the VDE control file",
"type": "string",
"minLength": 1
},
"local_file": {
"description": "path to the VDE control file",
"type": "string",
"minLength": 1
},
},
"required": ["type", "control_file", "local_file"],
"additionalProperties": False
},
"NULL": {
"description": "NULL Network Input/Output",
"properties": {
"type": {
"enum": ["nio_null"]
},
},
"required": ["type"],
"additionalProperties": False
},
},
"oneOf": [
{"$ref": "#/definitions/UDP"},
{"$ref": "#/definitions/Ethernet"},
{"$ref": "#/definitions/LinuxEthernet"},
{"$ref": "#/definitions/NAT"},
{"$ref": "#/definitions/TAP"},
{"$ref": "#/definitions/UNIX"},
{"$ref": "#/definitions/VDE"},
{"$ref": "#/definitions/NULL"},
],
"additionalProperties": True,
"required": ["type"]
}

View File

@ -211,62 +211,6 @@ QEMU_UPDATE_SCHEMA = {
"additionalProperties": False,
}
QEMU_NIO_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to add a NIO for a QEMU instance",
"type": "object",
"definitions": {
"UDP": {
"description": "UDP Network Input/Output",
"properties": {
"type": {
"enum": ["nio_udp"]
},
"lport": {
"description": "Local port",
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"rhost": {
"description": "Remote host",
"type": "string",
"minLength": 1
},
"rport": {
"description": "Remote port",
"type": "integer",
"minimum": 1,
"maximum": 65535
}
},
"required": ["type", "lport", "rhost", "rport"],
"additionalProperties": False
},
"Ethernet": {
"description": "Generic Ethernet Network Input/Output",
"properties": {
"type": {
"enum": ["nio_generic_ethernet"]
},
"ethernet_device": {
"description": "Ethernet device name e.g. eth0",
"type": "string",
"minLength": 1
},
},
"required": ["type", "ethernet_device"],
"additionalProperties": False
},
},
"oneOf": [
{"$ref": "#/definitions/UDP"},
{"$ref": "#/definitions/Ethernet"},
],
"additionalProperties": True,
"required": ["type"]
}
QEMU_OBJECT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation for a QEMU VM instance",

View File

@ -139,46 +139,6 @@ VBOX_UPDATE_SCHEMA = {
"additionalProperties": False,
}
VBOX_NIO_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to add a NIO for a VirtualBox VM instance",
"type": "object",
"definitions": {
"UDP": {
"description": "UDP Network Input/Output",
"properties": {
"type": {
"enum": ["nio_udp"]
},
"lport": {
"description": "Local port",
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"rhost": {
"description": "Remote host",
"type": "string",
"minLength": 1
},
"rport": {
"description": "Remote port",
"type": "integer",
"minimum": 1,
"maximum": 65535
}
},
"required": ["type", "lport", "rhost", "rport"],
"additionalProperties": False
},
},
"oneOf": [
{"$ref": "#/definitions/UDP"},
],
"additionalProperties": True,
"required": ["type"]
}
VBOX_CAPTURE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to start a packet capture on a VirtualBox VM instance port",

View File

@ -75,62 +75,6 @@ VPCS_UPDATE_SCHEMA = {
"additionalProperties": False,
}
VPCS_NIO_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to add a NIO for a VPCS instance",
"type": "object",
"definitions": {
"UDP": {
"description": "UDP Network Input/Output",
"properties": {
"type": {
"enum": ["nio_udp"]
},
"lport": {
"description": "Local port",
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"rhost": {
"description": "Remote host",
"type": "string",
"minLength": 1
},
"rport": {
"description": "Remote port",
"type": "integer",
"minimum": 1,
"maximum": 65535
}
},
"required": ["type", "lport", "rhost", "rport"],
"additionalProperties": False
},
"TAP": {
"description": "TAP Network Input/Output",
"properties": {
"type": {
"enum": ["nio_tap"]
},
"tap_device": {
"description": "TAP device name e.g. tap0",
"type": "string",
"minLength": 1
},
},
"required": ["type", "tap_device"],
"additionalProperties": False
},
},
"oneOf": [
{"$ref": "#/definitions/UDP"},
{"$ref": "#/definitions/TAP"},
],
"additionalProperties": True,
"required": ["type"]
}
VPCS_OBJECT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "VPCS instance",

View File

@ -27,6 +27,7 @@ import aiohttp
import functools
import types
import time
import atexit
from .web.route import Route
from .web.request_handler import RequestHandler
@ -173,6 +174,18 @@ class Server:
return
yield from embed(globals(), locals(), return_asyncio_coroutine=True, patch_stdout=True)
def _exit_handling(self):
def close_asyncio_loop():
loop = None
try:
loop = asyncio.get_event_loop()
except AttributeError:
pass
if loop is not None:
loop.close()
atexit.register(close_asyncio_loop)
def run(self):
"""
Starts the server.
@ -216,6 +229,8 @@ class Server:
self._loop.run_until_complete(self._run_application(self._handler, ssl_context))
self._signal_handling()
self._exit_handling()
if server_config.getboolean("live"):
log.info("Code live reload is enabled, watching for file changes")
self._loop.call_later(1, self._reload_hook)

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

View File

@ -0,0 +1,41 @@
# -*- 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 tempfile
import pkg_resources
import atexit
import logging
log = logging.getLogger(__name__)
try:
egg_cache_dir = tempfile.mkdtemp()
pkg_resources.set_extraction_path(egg_cache_dir)
except ValueError:
# If the path is already set the module throw an error
pass
@atexit.register
def clean_egg_cache():
try:
import shutil
log.debug("Clean egg cache %s", egg_cache_dir)
shutil.rmtree(egg_cache_dir)
except Exception:
# We don't care if we can not cleanup
pass

View File

@ -58,10 +58,11 @@ def get_windows_interfaces():
import win32com.client
import pywintypes
locator = win32com.client.Dispatch("WbemScripting.SWbemLocator")
service = locator.ConnectServer(".", "root\cimv2")
interfaces = []
try:
locator = win32com.client.Dispatch("WbemScripting.SWbemLocator")
service = locator.ConnectServer(".", "root\cimv2")
# more info on Win32_NetworkAdapter: http://msdn.microsoft.com/en-us/library/aa394216%28v=vs.85%29.aspx
for adapter in service.InstancesOf("Win32_NetworkAdapter"):
if adapter.NetConnectionStatus == 2 or adapter.NetConnectionStatus == 7:
@ -70,7 +71,7 @@ def get_windows_interfaces():
interfaces.append({"id": npf_interface,
"name": adapter.NetConnectionID})
except (AttributeError, pywintypes.com_error):
log.warn("could not use the COM service to retrieve interface info, trying using the registry...")
log.warn("Could not use the COM service to retrieve interface info, trying using the registry...")
return _get_windows_interfaces_from_registry()
return interfaces

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.1"
__version_info__ = (1, 3, 1, 0)
__version__ = "1.3.7"
__version_info__ = (1, 3, 7, 0)

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
@ -74,13 +74,37 @@ class ColouredStreamHandler(logging.StreamHandler):
stream.write(msg)
stream.write(self.terminator)
self.flush()
# On OSX when frozen flush raise a BrokenPipeError
except BrokenPipeError:
pass
except Exception:
self.handleError(record)
def init_logger(level, quiet=False):
if sys.platform.startswith("win"):
stream_handler = logging.StreamHandler(sys.stdout)
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 = WinStreamHandler(sys.stdout)
stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {filename}:{lineno} {message}", "%Y-%m-%d %H:%M:%S", "{")
else:
stream_handler = ColouredStreamHandler(sys.stdout)

View File

@ -30,10 +30,11 @@ renderer = jinja2.Environment(loader=jinja2.PackageLoader('gns3server', 'templat
class Response(aiohttp.web.Response):
def __init__(self, route=None, output_schema=None, headers={}, **kwargs):
def __init__(self, request=None, route=None, output_schema=None, headers={}, **kwargs):
self._route = route
self._output_schema = output_schema
self._request = request
headers['X-Route'] = self._route
headers['Server'] = "Python/{0[0]}.{0[1]} GNS3/{1}".format(sys.version_info, __version__)
super().__init__(headers=headers, **kwargs)
@ -70,6 +71,7 @@ class Response(aiohttp.web.Response):
"""
template = renderer.get_template(template_filename)
kwargs["gns3_version"] = __version__
kwargs["gns3_host"] = self._request.host
self.html(template.render(**kwargs))
def json(self, answer):

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,52 +143,63 @@ 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(route=route, output_schema=output_schema)
response = Response(request=request, route=route, output_schema=output_schema)
yield from func(request, response)
return response
# 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:
with open(record_file, "a") as f:
with open(record_file, "a", encoding="utf-8") as f:
f.write("curl -X {} 'http://{}{}' -d '{}'".format(request.method, request.host, request.path_qs, json.dumps(request.json)))
f.write("\n")
except OSError as e:
log.warn("Could not write to the record file {}: {}".format(record_file, e))
response = Response(route=route, output_schema=output_schema)
response = Response(request=request, route=route, output_schema=output_schema)
yield from func(request, response)
except aiohttp.web.HTTPBadRequest as e:
response = Response(route=route)
response = Response(request=request, route=route)
response.set_status(e.status)
response.json({"message": e.text, "status": e.status, "path": route, "request": request.json})
except aiohttp.web.HTTPException as e:
response = Response(route=route)
response = Response(request=request, route=route)
response.set_status(e.status)
response.json({"message": e.text, "status": e.status})
except VMError as e:
log.error("VM error detected: {type}".format(type=type(e)), exc_info=1)
response = Response(route=route)
response = Response(request=request, route=route)
response.set_status(409)
response.json({"message": str(e), "status": 409})
except asyncio.futures.CancelledError as e:
log.error("Request canceled")
response = Response(route=route)
response = Response(request=request, route=route)
response.set_status(408)
response.json({"message": "Request canceled", "status": 408})
except aiohttp.ClientDisconnectedError:
log.error("Client disconnected")
response = Response(route=route)
response = Response(request=request, route=route)
response.set_status(408)
response.json({"message": "Client disconnected", "status": 408})
except ConnectionResetError:
log.error("Client connection reset")
response = Response(request=request, route=route)
response.set_status(408)
response.json({"message": "Connection reset", "status": 408})
except Exception as e:
log.error("Uncaught exception detected: {type}".format(type=type(e)), exc_info=1)
response = Response(route=route)
response = Response(request=request, route=route)
response.set_status(500)
CrashReport.instance().capture_exception(request)
exc_type, exc_value, exc_tb = sys.exc_info()

19
init/gns3.conf.upstart Normal file
View File

@ -0,0 +1,19 @@
description "GNS3 server"
author "GNS3 Team"
start on filesystem or runlevel [2345]
stop on shutdown
script
echo $$ > /var/run/gns3.pid
exec start-stop-daemon --start -c gns3 --exec /usr/local/bin/gns3server --log /var/log/gns3.log
end script
pre-start script
echo "[`date`] GNS3 Starting" >> /var/log/gns3.log
end script
pre-stop script
rm /var/run/gns3.pid
echo "[`date`] GNS3 Stopping" >> /var/log/gns3.log
end script

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

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):

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)

View File

@ -18,10 +18,14 @@
import pytest
import os
import stat
import sys
import uuid
from tests.utils import asyncio_patch
from unittest.mock import patch, MagicMock, PropertyMock
pytestmark = pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
@pytest.fixture
def fake_iou_bin(tmpdir):
@ -91,11 +95,32 @@ def test_iou_create_with_params(server, project, base_params):
assert "initial-config.cfg" in response.json["initial_config"]
with open(initial_config_file(project, response.json)) as f:
assert f.read() == params["initial_config_content"]
assert f.read() == "hostname test"
assert "iourc" in response.json["iourc_path"]
def test_iou_create_initial_config_already_exist(server, project, base_params):
"""We don't erase an initial config if already exist at project creation"""
vm_id = str(uuid.uuid4())
initial_config_file_path = initial_config_file(project, {'vm_id': vm_id})
with open(initial_config_file_path, 'w+') as f:
f.write("echo hello")
params = base_params
params["vm_id"] = vm_id
params["initial_config_content"] = "hostname test"
response = server.post("/projects/{project_id}/iou/vms".format(project_id=project.id), params, example=True)
assert response.status == 201
assert response.route == "/projects/{project_id}/iou/vms"
assert "initial-config.cfg" in response.json["initial_config"]
with open(initial_config_file(project, response.json)) as f:
assert f.read() == "echo hello"
def test_iou_get(server, project, vm):
response = server.get("/projects/{project_id}/iou/vms/{vm_id}".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), example=True)
assert response.status == 200

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

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):

View File

@ -150,10 +150,7 @@ def test_qemu_nio_create_ethernet(server, vm):
"ethernet_device": "eth0",
},
example=True)
assert response.status == 201
assert response.route == "/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio"
assert response.json["type"] == "nio_generic_ethernet"
assert response.json["ethernet_device"] == "eth0"
assert response.status == 409
def test_qemu_delete_nio(server, vm):

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"

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"

View File

@ -18,6 +18,7 @@
import pytest
import tempfile
import sys
from gns3server.modules.dynamips import Dynamips
from gns3server.modules.dynamips.dynamips_error import DynamipsError
@ -36,7 +37,7 @@ def test_vm_invalid_dynamips_path(manager):
with pytest.raises(DynamipsError):
manager.find_dynamips()
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported by Windows")
def test_vm_non_executable_dynamips_path(manager):
tmpfile = tempfile.NamedTemporaryFile()
with patch("gns3server.config.Config.get_section_config", return_value={"dynamips_path": tmpfile.name}):

View File

@ -17,14 +17,29 @@
import pytest
from unittest.mock import patch
import uuid
import os
import sys
pytestmark = pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
if not sys.platform.startswith("win"):
from gns3server.modules.iou import IOU
from gns3server.modules.iou.iou_error import IOUError
from gns3server.modules.iou import IOU
from gns3server.modules.iou.iou_error import IOUError
from gns3server.modules.project_manager import ProjectManager
@pytest.fixture(scope="function")
def iou(port_manager):
# Cleanup the IOU object
IOU._instance = None
iou = IOU.instance()
iou.port_manager = port_manager
return iou
def test_get_application_id(loop, project, port_manager):
# Cleanup the IOU object
IOU._instance = None
@ -71,3 +86,8 @@ def test_get_application_id_no_id_available(loop, project, port_manager):
vm_id = str(uuid.uuid4())
loop.run_until_complete(iou.create_vm("PC {}".format(i), project.id, vm_id))
assert iou.get_application_id(vm_id) == i
def test_get_images_directory(iou, tmpdir):
with patch("gns3server.config.Config.get_section_config", return_value={"images_path": str(tmpdir)}):
assert iou.get_images_directory() == str(tmpdir / "IOU")

View File

@ -21,13 +21,19 @@ import asyncio
import os
import stat
import socket
import sys
from tests.utils import asyncio_patch
from unittest.mock import patch, MagicMock, PropertyMock
from gns3server.modules.iou.iou_vm import IOUVM
from gns3server.modules.iou.iou_error import IOUError
from gns3server.modules.iou import IOU
pytestmark = pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
if not sys.platform.startswith("win"):
from gns3server.modules.iou.iou_vm import IOUVM
from gns3server.modules.iou.iou_error import IOUError
from gns3server.modules.iou import IOU
from gns3server.config import Config
@ -81,10 +87,11 @@ def test_vm(project, manager):
assert vm.id == "00010203-0405-0607-0809-0a0b0c0d0e0f"
def test_vm_initial_config(project, manager):
vm = IOUVM("test", "00010203-0405-0607-0808-0a0b0c0d0e0f", project, manager, initial_config="hostname %h")
def test_vm_initial_config_content(project, manager):
vm = IOUVM("test", "00010203-0405-0607-0808-0a0b0c0d0e0f", project, manager)
vm.initial_config_content = "hostname %h"
assert vm.name == "test"
assert vm.initial_config == "hostname test"
assert vm.initial_config_content == "hostname test"
assert vm.id == "00010203-0405-0607-0808-0a0b0c0d0e0f"
@ -286,10 +293,10 @@ def test_update_initial_config_empty(vm):
assert f.read() == content
def test_update_initial_config_h(vm):
def test_update_initial_config_content_hostname(vm):
content = "hostname %h\n"
vm.name = "pc1"
vm.initial_config = content
vm.initial_config_content = content
with open(vm.initial_config_file) as f:
assert f.read() == "hostname pc1\n"

View File

@ -18,6 +18,7 @@
import os
import stat
import asyncio
import sys
from gns3server.modules.qemu import Qemu
from tests.utils import asyncio_patch
@ -27,12 +28,15 @@ def test_get_qemu_version(loop):
with asyncio_patch("gns3server.modules.qemu.subprocess_check_output", return_value="QEMU emulator version 2.2.0, Copyright (c) 2003-2008 Fabrice Bellard") as mock:
version = loop.run_until_complete(asyncio.async(Qemu._get_qemu_version("/tmp/qemu-test")))
assert version == "2.2.0"
if sys.platform.startswith("win"):
assert version == ""
else:
assert version == "2.2.0"
def test_binary_list(loop):
files_to_create = ["qemu-system-x86", "qemu-system-x42", "hello"]
files_to_create = ["qemu-system-x86", "qemu-system-x42", "qemu-kvm", "hello"]
for file_to_create in files_to_create:
path = os.path.join(os.environ["PATH"], file_to_create)
@ -43,11 +47,17 @@ def test_binary_list(loop):
with asyncio_patch("gns3server.modules.qemu.subprocess_check_output", return_value="QEMU emulator version 2.2.0, Copyright (c) 2003-2008 Fabrice Bellard") as mock:
qemus = loop.run_until_complete(asyncio.async(Qemu.binary_list()))
assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x86"), "version": "2.2.0"} in qemus
assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x42"), "version": "2.2.0"} in qemus
assert {"path": os.path.join(os.environ["PATH"], "hello"), "version": "2.2.0"} not in qemus
if sys.platform.startswith("win"):
version = ""
else:
version = "2.2.0"
assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x86"), "version": version} in qemus
assert {"path": os.path.join(os.environ["PATH"], "qemu-kvm"), "version": version} in qemus
assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x42"), "version": version} in qemus
assert {"path": os.path.join(os.environ["PATH"], "hello"), "version": version} not in qemus
def test_get_legacy_vm_workdir():
assert Qemu.get_legacy_vm_workdir(42, "bla") == "qemu/vm-42"
assert Qemu.get_legacy_vm_workdir(42, "bla") == os.path.join("qemu", "vm-42")

View File

@ -19,6 +19,7 @@ import pytest
import aiohttp
import asyncio
import os
import sys
import stat
import re
from tests.utils import asyncio_patch
@ -51,7 +52,10 @@ def fake_qemu_img_binary():
@pytest.fixture
def fake_qemu_binary():
bin_path = os.path.join(os.environ["PATH"], "qemu_x42")
if sys.platform.startswith("win"):
bin_path = os.path.join(os.environ["PATH"], "qemu_x42.EXE")
else:
bin_path = os.path.join(os.environ["PATH"], "qemu_x42")
with open(bin_path, "w+") as f:
f.write("1")
os.chmod(bin_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
@ -178,8 +182,9 @@ def test_set_qemu_path(vm, tmpdir, fake_qemu_binary):
f.write("1")
# Raise because file is not executable
with pytest.raises(QemuError):
vm.qemu_path = path
if not sys.platform.startswith("win"):
with pytest.raises(QemuError):
vm.qemu_path = path
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
@ -204,6 +209,7 @@ def test_disk_options(vm, loop, fake_qemu_img_binary):
assert args == (fake_qemu_img_binary, "create", "-f", "qcow2", os.path.join(vm.working_dir, "flash.qcow2"), "256M")
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
def test_set_process_priority(vm, loop, fake_qemu_img_binary):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
@ -254,26 +260,26 @@ def test_control_vm_expect_text(vm, loop, running_subprocess_mock):
def test_build_command(vm, loop, fake_qemu_binary, port_manager):
os.environ["DISPLAY"] = "0:0"
with patch("gns3server.modules.qemu.qemu_vm.QemuVM._get_random_mac", return_value="00:00:ab:7e:b5:00"):
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",
"256",
"-hda",
os.path.join(vm.working_dir, "flash.qcow2"),
"-serial",
"telnet:127.0.0.1:{},server,nowait".format(vm.console),
"-device",
"e1000,mac=00:00:ab:7e:b5:00,netdev=gns3-0",
"-netdev",
"user,id=gns3-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",
"256",
"-hda",
os.path.join(vm.working_dir, "flash.qcow2"),
"-serial",
"telnet:127.0.0.1:{},server,nowait".format(vm.console),
"-net",
"none",
"-device",
"e1000,mac=00:00:ab:0e:0f:00"
]
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
def test_build_command_without_display(vm, loop, fake_qemu_binary):
os.environ["DISPLAY"] = ""
@ -282,7 +288,9 @@ def test_build_command_without_display(vm, loop, fake_qemu_binary):
assert "-nographic" in cmd
def test_build_command_witht_invalid_options(vm, loop, fake_qemu_binary):
# Windows accept this kind of mistake
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
def test_build_command_with_invalid_options(vm, loop, fake_qemu_binary):
vm.options = "'test"
with pytest.raises(QemuError):
@ -306,6 +314,7 @@ def test_hdb_disk_image(vm, tmpdir):
vm.hdb_disk_image = "test"
assert vm.hdb_disk_image == str(tmpdir / "QEMU" / "test")
def test_hdc_disk_image(vm, tmpdir):
with patch("gns3server.config.Config.get_section_config", return_value={"images_path": str(tmpdir)}):
@ -314,6 +323,7 @@ def test_hdc_disk_image(vm, tmpdir):
vm.hdc_disk_image = "test"
assert vm.hdc_disk_image == str(tmpdir / "QEMU" / "test")
def test_hdd_disk_image(vm, tmpdir):
with patch("gns3server.config.Config.get_section_config", return_value={"images_path": str(tmpdir)}):

View File

@ -22,6 +22,15 @@ from unittest.mock import patch
from gns3server.modules.vpcs import VPCS
from gns3server.modules.qemu import Qemu
@pytest.fixture(scope="function")
def qemu(port_manager):
Qemu._instance = None
qemu = Qemu.instance()
qemu.port_manager = port_manager
return qemu
def test_create_vm_new_topology(loop, project, port_manager):
@ -82,3 +91,40 @@ def test_create_vm_old_topology(loop, project, tmpdir, port_manager):
vm_dir = os.path.join(project_dir, "project-files", "vpcs", vm.id)
with open(os.path.join(vm_dir, "startup.vpc")) as f:
assert f.read() == "1"
def test_get_abs_image_path(qemu, tmpdir):
os.makedirs(str(tmpdir / "QEMU"))
path1 = str(tmpdir / "test1.bin")
open(path1, 'w+').close()
path2 = str(tmpdir / "QEMU" / "test2.bin")
open(path2, 'w+').close()
with patch("gns3server.config.Config.get_section_config", return_value={"images_path": str(tmpdir)}):
assert qemu.get_abs_image_path(path1) == path1
assert qemu.get_abs_image_path("test1.bin") == path1
assert qemu.get_abs_image_path(path2) == path2
assert qemu.get_abs_image_path("test2.bin") == path2
assert qemu.get_abs_image_path("../test1.bin") == path1
# We look at first in new location
path2 = str(tmpdir / "QEMU" / "test1.bin")
open(path2, 'w+').close()
assert qemu.get_abs_image_path("test1.bin") == path2
def test_get_relative_image_path(qemu, tmpdir):
os.makedirs(str(tmpdir / "QEMU"))
path1 = str(tmpdir / "test1.bin")
open(path1, 'w+').close()
path2 = str(tmpdir / "QEMU" / "test2.bin")
open(path2, 'w+').close()
with patch("gns3server.config.Config.get_section_config", return_value={"images_path": str(tmpdir)}):
assert qemu.get_relative_image_path(path1) == path1
assert qemu.get_relative_image_path("test1.bin") == path1
assert qemu.get_relative_image_path(path2) == "test2.bin"
assert qemu.get_relative_image_path("test2.bin") == "test2.bin"
assert qemu.get_relative_image_path("../test1.bin") == path1

View File

@ -39,7 +39,6 @@ def test_reserve_udp_port():
pm.reserve_udp_port(4242, project)
@pytest.mark.skipif(sys.platform == 'darwin', reason="not working on darwin")
def test_release_udp_port():
pm = PortManager()
project = Project()
@ -48,12 +47,11 @@ def test_release_udp_port():
pm.reserve_udp_port(4242, project)
@pytest.mark.skipif(sys.platform != 'darwin', reason="requires darwin")
def test_release_darwin_udp_port():
"""Due to dynamips / darwin bug we didn't free the port"""
pm = PortManager()
project = Project()
pm.reserve_udp_port(4242, project)
pm.release_udp_port(4242, project)
def test_find_unused_port():
p = PortManager().find_unused_port(1000, 10000)
assert p is not None
def test_find_unused_port_invalid_range():
with pytest.raises(aiohttp.web.HTTPConflict):
pm.reserve_udp_port(4242, project)
p = PortManager().find_unused_port(10000, 1000)

View File

@ -69,15 +69,10 @@ def test_changing_path_temporary_flag(tmpdir):
with patch("gns3server.modules.project.Project.is_local", return_value=True):
p = Project(temporary=True)
assert os.path.exists(p.path)
original_path = p.path
assert os.path.exists(os.path.join(p.path, ".gns3_temporary"))
p.temporary = False
assert not os.path.exists(os.path.join(p.path, ".gns3_temporary"))
with open(str(tmpdir / ".gns3_temporary"), "w+") as f:
f.write("1")
p.path = str(tmpdir)
assert not os.path.exists(os.path.join(str(tmpdir), ".gns3_temporary"))
def test_temporary_path():

View File

@ -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}
]

View File

@ -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)))

View File

@ -19,6 +19,7 @@ import pytest
import aiohttp
import asyncio
import os
import sys
from tests.utils import asyncio_patch
@ -97,9 +98,14 @@ def test_stop(loop, vm):
loop.run_until_complete(asyncio.async(vm.start()))
assert vm.is_running()
loop.run_until_complete(asyncio.async(vm.stop()))
with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"):
loop.run_until_complete(asyncio.async(vm.stop()))
assert vm.is_running() is False
process.terminate.assert_called_with()
if sys.platform.startswith("win"):
process.send_signal.assert_called_with(1)
else:
process.terminate.assert_called_with()
def test_reload(loop, vm):
@ -117,9 +123,15 @@ def test_reload(loop, vm):
vm.port_add_nio_binding(0, nio)
loop.run_until_complete(asyncio.async(vm.start()))
assert vm.is_running()
loop.run_until_complete(asyncio.async(vm.reload()))
with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"):
loop.run_until_complete(asyncio.async(vm.reload()))
assert vm.is_running() is True
process.terminate.assert_called_with()
if sys.platform.startswith("win"):
process.send_signal.assert_called_with(1)
else:
process.terminate.assert_called_with()
def test_add_nio_binding_udp(vm):
@ -135,13 +147,13 @@ def test_add_nio_binding_tap(vm):
assert nio.tap_device == "test"
def test_add_nio_binding_tap_no_privileged_access(vm):
with patch("gns3server.modules.base_manager.BaseManager._has_privileged_access", return_value=False):
with pytest.raises(aiohttp.web.HTTPForbidden):
nio = VPCS.instance().create_nio(vm.vpcs_path, {"type": "nio_tap", "tap_device": "test"})
vm.port_add_nio_binding(0, nio)
assert vm._ethernet_adapter.ports[0] is None
# def test_add_nio_binding_tap_no_privileged_access(vm):
# with patch("gns3server.modules.base_manager.BaseManager._has_privileged_access", return_value=False):
# with pytest.raises(aiohttp.web.HTTPForbidden):
# nio = VPCS.instance().create_nio(vm.vpcs_path, {"type": "nio_tap", "tap_device": "test"})
# vm.port_add_nio_binding(0, nio)
# assert vm._ethernet_adapter.ports[0] is None
#
def test_port_remove_nio_binding(vm):
nio = VPCS.instance().create_nio(vm.vpcs_path, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
@ -178,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):
@ -190,8 +202,8 @@ def test_get_startup_script_using_default_script(vm):
vm._script_file = None
filepath = os.path.join(vm.working_dir, 'startup.vpc')
with open(filepath, 'w+') as f:
assert f.write(content)
with open(filepath, 'wb+') as f:
assert f.write(content.encode("utf-8"))
assert vm.startup_script == content
assert vm.script_file == filepath

View File

@ -18,6 +18,7 @@
import asyncio
import pytest
import sys
from unittest.mock import MagicMock
from gns3server.utils.asyncio import wait_run_in_executor, subprocess_check_output, wait_for_process_termination
@ -43,6 +44,7 @@ def test_exception_wait_run_in_executor(loop):
result = loop.run_until_complete(asyncio.async(exec))
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
def test_subprocess_check_output(loop, tmpdir, restore_original_path):
path = str(tmpdir / "test")

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)