Compare commits

...

111 Commits

Author SHA1 Message Date
Julien Duponchelle
a7f7a688d3 1.4.0 release 2016-01-12 17:38:45 +01:00
Julien Duponchelle
bb66555896 Merge branch 'master' into 1.4 2016-01-07 18:09:28 +01:00
Julien Duponchelle
37726630ce Fix rare crash when showing the progress dialog
Fix #909
2016-01-07 09:16:42 +01:00
Julien Duponchelle
e26762fce3 1.4.0dev14 2016-01-06 14:34:08 +01:00
Julien Duponchelle
32e59fcce4 Fix crash on Windows when a gui is already running
Fix #908
2016-01-06 14:24:49 +01:00
grossmj
9b43330e95 Add default idle-pc value for c7200-adventerprisek9-mz.155-2.XB. Fixes #389. 2016-01-05 13:00:39 -07:00
Julien Duponchelle
b3b94243b4 1.4.0rc3 2016-01-05 18:30:48 +01:00
Julien Duponchelle
fba1032388 Fix config tests 2016-01-05 18:27:16 +01:00
Julien Duponchelle
ccc0803c5b Add information about antivirus and firewall in case of connection fail 2016-01-05 17:08:19 +01:00
Julien Duponchelle
f0340bcc98 Change link to doc for missing router image 2016-01-05 16:43:17 +01:00
Julien Duponchelle
8d19b8fcbf On windows and OSX experimental Qemu GNS3A support 2016-01-04 21:53:10 +01:00
Vasil Rangelov
dbf66d5a9a Allow appliances to be installed on a local Windows or OSX server, while also allowing them to instead require KVM be supported on the target server, or to disable it even if supported. Also resolve Qemu binary at appliance install time. 2016-01-04 21:53:10 +01:00
Julien Duponchelle
0fa8d61b19 Wait server in thread 2016-01-04 21:50:42 +01:00
Julien Duponchelle
02d326419b Fix local server non avaible in appliance wizard when local server
started by hand.

Ref #904
2016-01-04 14:12:08 +01:00
Julien Duponchelle
608e80a80b Improve pid checks
Fix #900
2016-01-03 20:45:58 +01:00
Julien Duponchelle
d90f11eb86 Add a comment about tests run in a container 2016-01-03 19:54:57 +01:00
Jeremy Grossmann
dc39187091 Merge pull request #894 from boenrobot/port1names
Port names with numbers starting from 1
2015-12-27 20:08:21 -08:00
Jeremy Grossmann
e4cd418533 Merge pull request #898 from boenrobot/diskInterfaceOnAppliance
Disk interfaces on Qemu appliances
2015-12-25 11:09:06 -08:00
Vasil Rangelov
8559469c73 Allow Qemu appliances to optionally specify desired disk interfaces in their configuration (defaults to "ide"). 2015-12-25 04:00:48 +02:00
Julien Duponchelle
e6ba7bdd98 Fix project non closing when you have only remote servers
Fix #895
2015-12-22 16:21:41 +01:00
Julien Duponchelle
784055689f Fix Windows layout not saved in some scenario
Fix #884
2015-12-22 16:11:50 +01:00
Julien Duponchelle
c937811f45 Force gns3 converter >= 1.2.4 2015-12-22 10:45:16 +01:00
Vasil Rangelov
79c8021faa Added the ability to name Qemu/VirtualBox/VMware interfaces with numbers starting from 1, along with named aliases for numbers starting from 0, and a tooltip explaining the possible name format variables. 2015-12-22 00:41:12 +02:00
Julien Duponchelle
a428730f59 Merge pull request #893 from boenrobot/schemaFix1
Added missing Qemu adapters to the topology schema.
2015-12-21 21:12:44 +01:00
Vasil Rangelov
3cad8ea046 Added missing Qemu adapters to the topology schema. 2015-12-21 20:50:27 +02:00
Julien Duponchelle
ab1775e44b Fix Cannot save my topology getting an error message for temporary
topology

Fix #885
2015-12-18 10:10:26 +01:00
Julien Duponchelle
9c3facb07a Fix creation of ASA devices
Fix #890
2015-12-18 10:08:41 +01:00
Julien Duponchelle
e5ae7b2d25 Turn off Docker until 1.5 2015-12-18 09:49:50 +01:00
Julien Duponchelle
60462ff986 Fix display of server preferences on small screen
Fix #882
2015-12-14 16:37:55 +01:00
Julien Duponchelle
39443cd676 Fix If you turn off the local server and close the gui and reopen
preferences you have an issue

Fix #881
2015-12-14 16:28:50 +01:00
Julien Duponchelle
25ab8249ae Zoc 7 support
Fix #879
2015-12-14 16:19:38 +01:00
Julien Duponchelle
6e86c606cc Fix test on windows
Fix #877
2015-12-14 15:07:45 +01:00
Julien Duponchelle
8878b96e74 Merge branch 'master' into unstable 2015-12-11 14:32:32 +01:00
Julien Duponchelle
c3ef2edbab 1.4.0dev13 2015-12-11 14:27:20 +01:00
Julien Duponchelle
f7e398edc3 1.3.14dev1 2015-12-11 14:26:13 +01:00
Julien Duponchelle
8d4fc9585e 1.3.13 2015-12-11 09:22:46 +01:00
Julien Duponchelle
5fe65e26ce 1.3.12 2015-12-11 08:53:14 +01:00
Julien Duponchelle
daaebe7f96 Fix warning when closing GUI
Fix #875
2015-12-11 08:51:12 +01:00
grossmj
f9f6a52e8b Update links for new website. 2015-12-10 15:00:16 -07:00
grossmj
601c0217b9 Update VMware links. 2015-12-10 14:05:39 -07:00
Julien Duponchelle
b0971b4ba3 1.4.0rc2 2015-12-10 19:36:41 +01:00
Julien Duponchelle
5a4549c36c Merge pull request #874 from GNS3/iou_dynamips_gns3a
IOU and dynamips support for gns3a
2015-12-10 19:32:11 +01:00
Julien Duponchelle
fe2cc362f8 Dynamips GNS3A support 2015-12-10 18:17:51 +01:00
Julien Duponchelle
dd0220fd59 Prevent user turning off the Local server when using the GNS3 VM
Fix #873
2015-12-10 13:57:09 +01:00
Julien Duponchelle
26fdd9ef6f Force VM wizard to be modal
Fix #868
2015-12-10 10:31:03 +01:00
Julien Duponchelle
733ee259e5 Fix unicode error when exporting debug informations
Fix #869
2015-12-10 10:25:19 +01:00
Julien Duponchelle
762fecbcff Fix Debug can't be deactivated for current session
Fix #871
2015-12-10 10:19:13 +01:00
Julien Duponchelle
5e85dfe5fd Ignore missing file when reading config
Fix #870
2015-12-10 10:08:29 +01:00
Jeremy Grossmann
e01632d60a Merge pull request #864 from GNS3/drop_webkit
Drop Webkit from 1.3.X
2015-12-09 19:34:15 -08:00
Julien Duponchelle
60916e8b80 IOU gns3a support 2015-12-09 19:49:23 +01:00
Julien Duponchelle
4fe55634ae Support relative path for configuration file.
It's allow futur GNS3A support for IOU and Dynamips
2015-12-09 18:06:07 +01:00
Julien Duponchelle
b1b861c99d Don't kill the server if two gui are running
Fix #865
2015-12-09 15:26:37 +01:00
Julien Duponchelle
07c0474386 Ask user to send bug reports to GNS3.com 2015-12-09 12:17:32 +01:00
Julien Duponchelle
755fb5c8f3 Report bug to GNS3.com 2015-12-09 12:16:49 +01:00
Julien Duponchelle
fdb382874d Avoid crash when cancel connection to a server
Ref #865
2015-12-09 12:04:18 +01:00
Julien Duponchelle
471b3e1009 Fix travis dependencies installation 2015-12-09 09:11:19 +01:00
Julien Duponchelle
f64a226336 1.4.0dev12 2015-12-08 16:27:54 +01:00
Julien Duponchelle
dec257bb6b Fix version number display twice when installing appliance 2015-12-08 16:05:48 +01:00
Julien Duponchelle
e736fbbb87 Drop Webkit from 1.3.X 2015-12-08 11:44:21 +01:00
Julien Duponchelle
aeb42b3ffe Show a warning when you try to save as a remote topology
Ref #831
2015-12-08 11:34:37 +01:00
Julien Duponchelle
1694a57ed9 Fix application restart after self upgrade
Fix #863
2015-12-07 15:39:13 +01:00
Julien Duponchelle
26001463a0 Cleanup server autostart
Fix #862
2015-12-07 11:30:17 +01:00
grossmj
36c1197f1f Merge remote-tracking branch 'origin/unstable' into unstable 2015-12-05 17:26:52 -07:00
grossmj
1fa16936fe Have default console port start from 2000. 2015-12-05 17:26:33 -07:00
Jeremy Grossmann
fca65784ee Merge pull request #852 from GNS3/vnc
Allow to use the VNC port range for console
2015-12-05 16:03:44 -08:00
Julien Duponchelle
8983e6c5a9 Fix crash when opening a new topologies after gns3 converter failure
Fix #858
2015-12-04 15:18:38 +01:00
Julien Duponchelle
c6e06f3941 Fix crash when opening a new topologies after gns3 converter failure
Fix #858
2015-12-04 15:13:03 +01:00
Julien Duponchelle
b7e32a60ce Fix SSH support
Fix #857
2015-12-04 14:16:14 +01:00
Julien Duponchelle
da100e494b Fix bus error when writting on console
Fix #371
2015-12-04 11:37:42 +01:00
Julien Duponchelle
9d7b5bccb8 Fix Ok & Cancel button in preferences are broken
Fix #855
2015-12-03 16:57:15 +01:00
Julien Duponchelle
112b05c3dd Fix After a project load failure you can't open new project
Fix #851
2015-12-03 16:53:41 +01:00
Julien Duponchelle
987e85cce8 More fix around closing the GUI
Fix #854
2015-12-03 16:37:33 +01:00
Julien Duponchelle
8142d0baa7 Fix crash in progress dialog on OSX
Fix #828
2015-12-03 15:45:55 +01:00
Julien Duponchelle
cc32b2661c Kill already running zombie server
Fix #838
2015-12-03 11:14:16 +01:00
Julien Duponchelle
781857f598 Store the pid of the server when started
Refe #838
2015-12-03 10:33:03 +01:00
Jeremy Grossmann
69179dcb63 Merge pull request #849 from GNS3/resize_preferences
Preferences dialog resize
2015-12-02 11:11:32 -08:00
Julien Duponchelle
8017838d60 Fix a crash in rare case after a PyQT garbage collect
Fix #842
2015-12-02 16:13:59 +01:00
Julien Duponchelle
81946493ec Allow to use the VNC port range for console
This PR allow user to have VNC port in the
console port range. It's not a problem because server
can share the range (we have a common list for all tcp ports).

And we add an informations in the settings in order to allow user
to know that 5900 => 6000 will be used.

Fix #846, #839
2015-12-02 16:08:34 +01:00
Julien Duponchelle
a7f40c3d50 Preferences dialog resize
* add a scrollbar if preferences height is too big
* set a dynamics max size depending of the screen

Fix #844
2015-12-02 12:22:28 +01:00
Julien Duponchelle
c28723287f Fix a race condition when opening telnet from apple script
Prevent closing ssh tunnel on OSX when using apple script
this leak port...

Fix #841, #848
2015-12-02 11:21:16 +01:00
Julien Duponchelle
c7a8588647 Experimental support for tabbed terminal on OSX
Fix #841
2015-12-02 11:05:24 +01:00
Julien Duponchelle
4a64261c5d Fix validation error when saving topology with an usage
Fix #829
2015-12-01 18:50:17 +01:00
Julien Duponchelle
5f4365542c Fix another case of not closing windows
Fix #833
2015-12-01 18:47:42 +01:00
Julien Duponchelle
2e2c951ffc Fix GUI doesn't close after connection error to remote server
Fix #833
2015-12-01 16:12:41 +01:00
Julien Duponchelle
2ba7dde326 Add usage text to device template and on hover
Fix #829
2015-12-01 11:04:05 +01:00
Julien Duponchelle
a1579ca86b Fix a rare crash in progress dialog
Fix #835
2015-12-01 09:49:18 +01:00
Julien Duponchelle
81c4ddb85f Fix crash when displaying an error from the update
Fix #836
2015-11-30 21:35:36 +01:00
Julien Duponchelle
285a8d413a Url encode royal tx url 2015-11-30 17:09:43 +01:00
Julien Duponchelle
672c86b38d Use Royal TX URI scheme thanks to @lemonmojo
Fix #832
2015-11-30 16:49:02 +01:00
Julien Duponchelle
905611d2a8 OSX support for Royal TSX
Can't test because I don't have a Royal TSX licence

Ref #832
2015-11-30 15:13:45 +01:00
Julien Duponchelle
339fb22217 Merge branch 'master' into unstable 2015-11-30 14:46:10 +01:00
Julien Duponchelle
a8f5fa5dd5 Set Wireshark 2.0 as default OSX version
Fix #830
2015-11-30 14:37:39 +01:00
Julien Duponchelle
bd365dd6eb iTerm 2.9 support
Fix #817
2015-11-30 14:27:54 +01:00
Jeremy Grossmann
8e4a6169e0 Merge pull request #809 from GNS3/contributing
Contributing guidelines
2015-11-14 20:20:13 -08:00
Jeremy Grossmann
b1790844f3 Update CONTRIBUTING.md 2015-11-14 19:59:15 -07:00
grossmj
4b0050d26c Update debug information dialog. 2015-11-14 18:33:03 -07:00
grossmj
19bf40dc89 Change text for export debug information. 2015-11-14 18:31:46 -07:00
Julien Duponchelle
23cda61d17 1.4.0rc1 2015-11-12 17:48:51 +01:00
Julien Duponchelle
c74ffde65a Rename an appliance if the default name is already taken 2015-11-12 17:34:50 +01:00
Julien Duponchelle
0a9c10e748 Existing image option should be hidden when none is available
Fix #819
2015-11-12 17:02:05 +01:00
Julien Duponchelle
6973eaaa02 Warn users about the need to uncompress the image
Fix #826
2015-11-12 16:18:49 +01:00
Julien Duponchelle
9ce483398b Fix crash when you have no qemu vms and use gns3a
Fix #824
2015-11-12 15:40:23 +01:00
Julien Duponchelle
55744ab129 Change sentry key
Fix #825
2015-11-12 11:20:17 +01:00
Julien Duponchelle
0e1cb47aa1 Merge branch 'master' into unstable 2015-11-12 10:52:31 +01:00
Julien Duponchelle
3bf12753df Fix format_exception() missing 2 required positional arguments: 'value'
and 'tb' in topologyFile

Fix #823
2015-11-12 10:43:04 +01:00
Julien Duponchelle
8907659220 Fix dialog box not returning their result
Fix #818
2015-11-12 09:34:45 +01:00
Julien Duponchelle
a4ccb0b620 Log to console the Qt Message Boxes 2015-11-11 11:58:31 +01:00
Julien Duponchelle
7d845c0ef8 Add informations about GNS3 VM 2015-11-10 12:45:22 +01:00
Julien Duponchelle
8282ec1da6 Contributing file 2015-11-10 12:43:11 +01:00
grossmj
990cb49854 Merge remote-tracking branch 'origin/master' 2015-10-16 15:43:46 -06:00
grossmj
c530924d8a Drops securecrt.vbs 2015-10-16 15:43:31 -06:00
84 changed files with 1513 additions and 404 deletions

View File

@@ -13,6 +13,9 @@ notifications:
# on_success: change
# on_failure: always
install:
- "pip install --upgrade -r dev-requirements.txt"
script:
- docker build -t gns3-gui-test .
- docker run gns3-gui-test

110
CHANGELOG
View File

@@ -1,5 +1,115 @@
# Change Log
## 1.4.0 12/01/2016
* Fix rare crash when showing the progress dialog
* Fix crash on Windows when a gui is already running
* Add default idle-pc value for c7200-adventerprisek9-mz.155-2.XB. Fixes #389.
## 1.4.0rc3 05/01/2016
* Add information about antivirus and firewall in case of connection fail
* Change link to doc for missing router image
* On windows and OSX experimental Qemu GNS3A support
* Wait server in thread
* Fix local server non avaible in appliance wizard when local server started by hand.
* Improve pid checks
* Allow Qemu appliances to optionally specify desired disk interfaces in their configuration (defaults to "ide").
* Fix project non closing when you have only remote servers
* Fix Windows layout not saved in some scenario
* Added the ability to name Qemu/VirtualBox/VMware interfaces with numbers starting from 1, along with named aliases for numbers starting from 0, and a tooltip explaining the possible name format variables.
* Added missing Qemu adapters to the topology schema.
* Fix Cannot save my topology getting an error message for temporary topology
* Fix creation of ASA devices
* Turn off Docker until 1.5
* Fix display of server preferences on small screen
* Fix If you turn off the local server and close the gui and reopen preferences you have an issue
* Zoc 7 support
* Fix warning when closing GUI
* Fix crash when opening a new topologies after gns3 converter failure
## 1.3.13 11/12/2015
* Release server 1.3.13
## 1.3.12 11/12/2015
* Fix warning when closing GUI
* Update links for new website.
* Ask user to send bug reports to GNS3.com
* Fix travis dependencies installation
* Drop Webkit from 1.3.X
* Fix crash when opening a new topologies after gns3 converter failure
* Set Wireshark 2.0 as default OSX version
* iTerm 2.9 support
* Add informations about GNS3 VM
* Drops securecrt.vbs
* Fix analytics report on OSX
* Analytics send windows
* Fix the progress dialog freeze bug
* Fix typo in analytics
* Send stats to GNS3 team
* Licenses compliance.
* Fix error when importing dynamips config from non existent directory
* Fix crash when url is invalid
* Add a debug level 2 in the console
## 1.4.0rc2 10/12/2015
* Prevent user turning off the Local server when using the GNS3 VM
* Force VM wizard to be modal
* Fix unicode error when exporting debug informations
* Fix Debug can't be deactivated for current session
* Support relative path for configuration file.
* Report bug to GNS3.com
* Avoid crash when cancel connection to a server
* Fix version number display twice when installing appliance
* Show a warning when you try to save as a remote topology
* Fix application restart after self upgrade
* Cleanup server autostart
* Have default console port start from 2000.
* Fix crash when opening a new topologies after gns3 converter failure
* Fix SSH support
* Fix bus error when writting on console
* Fix Ok & Cancel button in preferences are broken
* Fix After a project load failure you can't open new project
* More fix around closing the GUI
* Fix crash in progress dialog on OSX
* Kill already running zombie server
* Store the pid of the server when started
* Fix a crash in rare case after a PyQT garbage collect
* Allow to use the VNC port range for console
* Preferences dialog resize
* Fix a race condition when opening telnet from apple script
* Experimental support for tabbed terminal on OSX
* Fix validation error when saving topology with an usage
* Fix another case of not closing windows
* Fix GUI doesn't close after connection error to remote server
* Add usage text to device template and on hover
* Fix a rare crash in progress dialog
* Fix crash when displaying an error from the update
* Url encode royal tx url
* Use Royal TX URI scheme thanks to @lemonmojo
* OSX support for Royal TSX
* Merge branch 'master' into unstable
* Set Wireshark 2.0 as default OSX version
* iTerm 2.9 support
* Update debug information dialog.
* Change text for export debug information.
* Add informations about GNS3 VM
## 1.4.0rc1 12/15/2015
* Rename an appliance if the default name is already taken
* Existing image option should be hidden when none is available
* Warn users about the need to uncompress the image
* Fix crash when you have no qemu vms and use gns3a
* Change sentry key
* Fix format_exception() missing 2 required positional arguments: 'value' and 'tb' in topologyFile
* Fix dialog box not returning their result
* Log to console the Qt Message Boxes
* Drops securecrt.vbs
## 1.4.0b5 02/11/2015
* Fix crash when loading invalid appliance file

50
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,50 @@
# Contributing to GNS3
We welcome contributions and bugs reports from everyone.
We are friendly so don't be afraid to ask questions.
## Bug reports
Before reporting an issue:
* check our website over at https://gns3.com
* check if an issue already exists on https://github.com/GNS3/gns3-gui
* check if an issue already exists on https://github.com/GNS3/gns3-server
Please post on our community website if you are unsure you found a bug,
you will get faster support and be able to exchange with more users.
If you are unsure which project you should create an issue for, just do
it on https://github.com/GNS3/gns3-gui we will take care of the triage.
For bugs specific to the GNS3 VM, please report on https://github.com/GNS3/gns3-vm
## Asking for new features
The best is to start a discussion on the community website in order to get feedback
from the whole community.
## Contributing code
We welcome code contribution from everyone including beginners.
Don't be afraid to submit a half finished or mediocre contribution and we will help you.
Don't hesitate to share your plans before starting working on a contribution, we can help
you to find the best approach.
### Contributors License Agreements
We at GNS3 are eager to work with you. For small changeslittle bugfixes, correcting typos, and the likeplease just submit pull requests to any of our projects. For larger changes, though, we have to ask you to jump through a little hoop.
In particular, in order for us to accept any major patches from you, you will have to electronically sign a statement that indicates two things:
- You are willingly licensing your contributions under the terms of the open source license of the project that youre contributing to.
- You are legally able to license your contributions as stated.
The reason we do this is to ensure, to the extent possible, that we dont “taint” the projects we manage with contributions that turn out to be improper. This protects everyone who wants to use the projects, including you!
More information there: https://github.com/GNS3/cla
### Pull requests
Creating a pull request is the easiest way to contribute code. Do not hesitate to create one early when contributing for new feature in order to get our feedback.

View File

@@ -1,3 +1,4 @@
# Run tests inside a container
FROM ubuntu:vivid
MAINTAINER GNS3 Team

View File

@@ -50,7 +50,7 @@ Finally these commands will install the GUI as well as the rest of the dependenc
Windows
-------
Please use our `all-in-one installer <https://community.gns3.com/community/software/download>`_ to install the stable build.
Please use our `all-in-one installer <https://gns3.com/software/download>`_ to install the stable build.
If you install via source you need to first install:

View File

@@ -208,16 +208,17 @@ class ConsoleCmd(cmd.Cmd):
return
root = logging.getLogger()
ch = logging.StreamHandler(sys.stdout)
if len(args) == 1:
level = int(args[0])
if level == 0:
print("Deactivating debugging")
root.removeHandler(ch)
for handler in root.handlers:
if isinstance(handler, logging.StreamHandler):
root.removeHandler(handler)
root.setLevel(logging.INFO)
else:
root.addHandler(ch)
root.addHandler(logging.StreamHandler(sys.stdout))
if level == 1:
print("Activating debugging")
else:

View File

@@ -49,7 +49,7 @@ class CrashReport:
Report crash to a third party service
"""
DSN = "sync+https://a86f12c42f7746288a81af9a9c1145a4:db8b6973bd2c448ea0a98675119ff8ee@app.getsentry.com/38506"
DSN = "sync+https://3d44e34021504514a5fb0539ae6f8f92:af41562761754b4c9beca492d1b9115d@app.getsentry.com/38506"
if hasattr(sys, "frozen"):
cacert = get_resource("cacert.pem")
if cacert is not None and os.path.isfile(cacert):
@@ -94,7 +94,7 @@ class CrashReport:
"python:encoding": sys.getdefaultencoding(),
"python:frozen": "{}".format(hasattr(sys, "frozen"))
}
context = self._add_qt_informations(context)
context = self._add_qt_information(context)
client.tags_context(context)
try:
report = client.captureException((exception, value, tb))
@@ -103,7 +103,7 @@ class CrashReport:
return
log.info("Crash report sent with event ID: {}".format(client.get_ident(report)))
def _add_qt_informations(self, context):
def _add_qt_information(self, context):
try:
from .qt import QtCore
import sip

View File

@@ -18,9 +18,10 @@
import os
import sys
from ..qt import QtWidgets, QtCore, QtGui
from ..qt import QtWidgets, QtCore, QtGui, qpartial
from ..ui.appliance_wizard_ui import Ui_ApplianceWizard
from ..image_manager import ImageManager
from ..modules import Qemu
from ..registry.appliance import Appliance
from ..registry.registry import Registry
from ..registry.config import Config, ConfigException
@@ -30,7 +31,7 @@ from ..utils.wait_for_lambda_worker import WaitForLambdaWorker
from ..utils.progress_dialog import ProgressDialog
from ..servers import Servers
from ..gns3_vm import GNS3VM
from ..local_config import LocalConfig
class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
@@ -40,13 +41,13 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
self._path = path
self.setupUi(self)
images_directories = list()
images_directories.append(os.path.join(ImageManager.instance().getDirectory(), "QEMU"))
images_directories.append(os.path.dirname(self._path))
download_directory = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
if download_directory != "" and download_directory != os.path.dirname(self._path):
images_directories.append(download_directory)
self._registry = Registry(images_directories)
self._appliance = Appliance(self._registry, self._path)
self._registry.appendImageDirectory(os.path.join(ImageManager.instance().getDirectory(), self._appliance.image_dir_name()))
self.uiApplianceVersionTreeWidget.currentItemChanged.connect(self._applianceVersionCurrentItemChangedSlot)
self.uiRefreshPushButton.clicked.connect(self._refreshVersions)
@@ -61,6 +62,8 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if hasattr(self, "uiLoadBalanceCheckBox"):
self.uiLoadBalanceCheckBox.toggled.connect(self._loadBalanceToggledSlot)
self.uiServerWizardPage.isComplete = self._uiServerWizardPage_isComplete
def initializePage(self, page_id):
"""
Initialize Wizard pages.
@@ -75,6 +78,13 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
symbol = ":/symbols/{}.svg".format(self._appliance["category"])
self.page(page_id).setPixmap(QtWidgets.QWizard.LogoPixmap, QtGui.QPixmap(symbol))
if "qemu" in self._appliance:
type = "qemu"
elif "iou" in self._appliance:
type = "iou"
elif "dynamips" in self._appliance:
type = "dynamips"
if self.page(page_id) == self.uiInfoWizardPage:
self.uiInfoWizardPage.setTitle(self._appliance["product_name"])
self.uiDescriptionLabel.setText(self._appliance["description"])
@@ -84,16 +94,18 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
("Product", "product_name"),
("Vendor", "vendor_name"),
("Status", "status"),
("Maintainer", "maintainer")
("Maintainer", "maintainer"),
("KVM", "kvm")
)
self.uiInfoTreeWidget.clear()
for (name, key) in info:
item = QtWidgets.QTreeWidgetItem([name + ":", self._appliance[key]])
font = item.font(0)
font.setBold(True)
item.setFont(0, font)
self.uiInfoTreeWidget.addTopLevelItem(item)
if key in self._appliance:
item = QtWidgets.QTreeWidgetItem([name + ":", self._appliance[key]])
font = item.font(0)
font.setBold(True)
item.setFont(0, font)
self.uiInfoTreeWidget.addTopLevelItem(item)
elif self.page(page_id) == self.uiServerWizardPage:
self.uiRemoteServersComboBox.clear()
@@ -104,12 +116,12 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
self.uiVMRadioButton.setEnabled(False)
# Qemu has issues on OSX and Windows we disallow usage of the local server
if sys.platform.startswith("darwin") or sys.platform.startswith("win"):
if (sys.platform.startswith("darwin") or sys.platform.startswith("win")) and not LocalConfig.instance().experimental():
self.uiLocalRadioButton.setEnabled(False)
if GNS3VM.instance().isRunning():
self.uiVMRadioButton.setChecked(True)
elif Servers.instance().localServerIsRunning():
elif Servers.instance().localServer().isLocalServerRunning():
self.uiLocalRadioButton.setChecked(True)
elif len(Servers.instance().remoteServers().values()) > 0:
self.uiRemoteRadioButton.setChecked(True)
@@ -119,10 +131,14 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
elif self.page(page_id) == self.uiFilesWizardPage:
self._refreshVersions()
elif self.page(page_id) == self.uiQemuWizardPage:
Qemu.instance().getQemuBinariesFromServer(self._server, qpartial(self._getQemuBinariesFromServerCallback), [self._appliance["qemu"]["arch"]])
elif self.page(page_id) == self.uiSummaryWizardPage:
self.uiSummaryTreeWidget.clear()
for key in self._appliance["qemu"]:
item = QtWidgets.QTreeWidgetItem([key.replace('_', ' ').capitalize() + ":", str(self._appliance["qemu"][key])])
for key in self._appliance[type]:
item = QtWidgets.QTreeWidgetItem([key.replace('_', ' ').capitalize() + ":", str(self._appliance[type][key])])
font = item.font(0)
font.setBold(True)
item.setFont(0, font)
@@ -135,6 +151,9 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
self._appliance.get("usage", ""))
)
def _uiServerWizardPage_isComplete(self):
return self.uiRemoteRadioButton.isEnabled() or self.uiVMRadioButton.isEnabled() or self.uiLocalRadioButton.isEnabled()
def _refreshVersions(self):
"""
Refresh the list of files for different version of the appliance
@@ -236,6 +255,8 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
if data is not None:
if "direct_download_url" in data:
QtGui.QDesktopServices.openUrl(QtCore.QUrl(data["direct_download_url"]))
if "compression" in data:
QtWidgets.QMessageBox.warning(self, "Add appliance", "The image is compressed with {} you need to uncompress it before using it.".format(data["compression"]))
else:
QtGui.QDesktopServices.openUrl(QtCore.QUrl(data["download_url"]))
@@ -254,16 +275,36 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
image = Image(path)
if image.md5sum != disk["md5sum"]:
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "This is not the correct image file.")
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "This is not the correct image file. The MD5 sum is {} and should be {}".format(image.md5sum, disk["md5sum"]))
return
config = Config()
worker = WaitForLambdaWorker(lambda: image.copy(os.path.join(config.images_dir, "QEMU"), disk["filename"]), allowed_exceptions=[OSError])
worker = WaitForLambdaWorker(lambda: image.copy(os.path.join(config.images_dir, self._appliance.image_dir_name()), disk["filename"]), allowed_exceptions=[OSError])
progress_dialog = ProgressDialog(worker, "Add appliance", "Import the appliance...", None, busy=True, parent=self)
if not progress_dialog.exec_():
return
self._refreshVersions()
def _getQemuBinariesFromServerCallback(self, result, error=False, **kwargs):
"""
Callback for getQemuBinariesFromServer.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
QtWidgets.QMessageBox.critical(self, "Qemu binaries", "{}".format(result["message"]))
else:
self.uiQemuListComboBox.clear()
for qemu in result:
if qemu["version"]:
self.uiQemuListComboBox.addItem("{path} (v{version})".format(path=qemu["path"], version=qemu["version"]), qemu["path"])
else:
self.uiQemuListComboBox.addItem("{path}".format(path=qemu["path"]), qemu["path"])
if self.uiQemuListComboBox.count() == 1:
self.next()
def _install(self, version):
"""
Install the appliance to GNS3
@@ -286,6 +327,13 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
else:
server_string = self._server.url()
while len(appliance_configuration["name"]) == 0 or not config.is_name_available(appliance_configuration["name"]):
QtWidgets.QMessageBox.warning(self.parent(), "Add appliance", "The name \"{}\" is already used by another appliance".format(appliance_configuration["name"]))
appliance_configuration["name"], ok = QtWidgets.QInputDialog.getText(self.parent(), "Add appliance", "New name:", QtWidgets.QLineEdit.Normal, appliance_configuration["name"])
appliance_configuration["name"] = appliance_configuration["name"].strip()
appliance_configuration["qemu"]["path"] = self.uiQemuListComboBox.currentData()
worker = WaitForLambdaWorker(lambda: config.add_appliance(appliance_configuration, server_string), allowed_exceptions=[ConfigException, OSError])
progress_dialog = ProgressDialog(worker, "Add appliance", "Install the appliance...", None, busy=True, parent=self)
progress_dialog.show()
@@ -296,7 +344,7 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
progress_dialog = ProgressDialog(worker, "Add appliance", "Install the appliance...", None, busy=True, parent=self)
progress_dialog.show()
if progress_dialog.exec_():
QtWidgets.QMessageBox.information(self.parent(), "Add appliance", "{} {} installed!".format(self._appliance["name"], version))
QtWidgets.QMessageBox.information(self.parent(), "Add appliance", "{} installed!".format(appliance_configuration["name"]))
return True
def validateCurrentPage(self):
@@ -334,8 +382,18 @@ class ApplianceWizard(QtWidgets.QWizard, Ui_ApplianceWizard):
return False
self._server = gns3_vm_server
else:
if (sys.platform.startswith("darwin") or sys.platform.startswith("win")):
reply = QtWidgets.QMessageBox.question(self, "Appliance", "Qemu on Windows and MacOSX is not supported by the GNS3 team. Are you sur to continue?", QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.No:
return False
self._server = Servers.instance().localServer()
elif self.currentPage() == self.uiQemuWizardPage:
if self.uiQemuListComboBox.currentIndex() == -1:
QtWidgets.QMessageBox.critical(self, "Qemu binary", "No compatible Qemu binary selected")
return False
return True
def _vmToggledSlot(self, checked):

View File

@@ -31,7 +31,7 @@ log = logging.getLogger(__name__)
class ExportDebugDialog(QtWidgets.QDialog, Ui_ExportDebugDialog):
"""
This dialog allow user to export informations usefull
This dialog allow user to export useful information
for remote debugging by a GNS3 developers.
"""
@@ -49,7 +49,7 @@ class ExportDebugDialog(QtWidgets.QDialog, Ui_ExportDebugDialog):
self.reject()
return
log.info("Export debug informations to %s", path)
log.info("Export debug information to %s", path)
try:
with ZipFile(path, 'w') as zip:
@@ -67,7 +67,7 @@ class ExportDebugDialog(QtWidgets.QDialog, Ui_ExportDebugDialog):
if os.path.isfile(path):
zip.write(path, filename)
except OSError as e:
QtWidgets.QMessageBox.critical(self, "Debug", "Can't export debug informations: {}".format(str(e)))
QtWidgets.QMessageBox.critical(self, "Debug", "Can't export debug information: {}".format(str(e)))
self.accept()
def _getDebugData(self):
@@ -77,6 +77,11 @@ class ExportDebugDialog(QtWidgets.QDialog, Ui_ExportDebugDialog):
except psutil.AccessDenied:
connections = None
try:
addrs = ["* {}: {}".format(key, val) for key, val in psutil.net_if_addrs().items()]
except UnicodeDecodeError:
addrs = ["INVALID ADDR WITH UNICODE CHARACTERS"]
data = """Version: {version}
OS: {os}
Python: {python}
@@ -99,7 +104,7 @@ Processus:
memory=psutil.virtual_memory(),
cpu=psutil.cpu_times(),
connections=connections,
addrs="\n".join(["* {}: {}".format(key, val) for key, val in psutil.net_if_addrs().items()])
addrs="\n".join(addrs)
)
for proc in psutil.process_iter():
try:

View File

@@ -38,8 +38,16 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
def __init__(self, parent):
super().__init__(parent)
self.setupUi(self)
# We adapt the max size to the screen resolution
height = QtWidgets.QDesktopWidget().screenGeometry().height() - 100
if height > 800:
height = 800
self.setMaximumSize(QtCore.QSize(900, height))
self.resize(900, height)
self.uiTreeWidget.currentItemChanged.connect(self._showPreferencesPageSlot)
self._applyButton = self.uiButtonBox.button(QtWidgets.QDialogButtonBox.Apply)
self._applyButton.clicked.connect(self._applyPreferences)
@@ -54,6 +62,8 @@ class PreferencesDialog(QtWidgets.QDialog, Ui_PreferencesDialog):
# Something has change?
self._modified = False
def _loadPreferencePages(self):
"""
Loads all preference pages (built-ins and from modules).

View File

@@ -66,9 +66,9 @@ class SetupWizard(QtWidgets.QWizard, Ui_SetupWizard):
def _VMwareBannerButtonClickedSlot(self):
if sys.platform.startswith("darwin"):
url = "http://send.onenetworkdirect.net/z/616454/CD225091/"
url = "http://send.onenetworkdirect.net/z/616461/CD225091/"
else:
url = "http://send.onenetworkdirect.net/z/616455/CD225091/"
url = "http://send.onenetworkdirect.net/z/616460/CD225091/"
QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))
def _listVMwareVMsSlot(self):

View File

@@ -160,6 +160,14 @@ class VMWithImagesWizard(VMWizard):
QtWidgets.QMessageBox.critical(self, "Images", "Error while getting the VMs: {}".format(result["message"]))
return
if len(result) == 0:
for radio_button in self._radio_existing_images_buttons:
if radio_button.isChecked():
for button in radio_button.parent().findChildren(QtWidgets.QRadioButton):
if button != radio_button:
button.setChecked(True)
button.hide()
for combo_box in self._images_combo_boxes:
combo_box.clear()
for vm in result:

View File

@@ -35,6 +35,8 @@ class VMWizard(QtWidgets.QWizard):
super().__init__(parent)
self.setupUi(self)
self.setModal(True)
self._devices = devices
self._use_local_server = use_local_server

View File

@@ -295,13 +295,9 @@ class HTTPClient(QtCore.QObject):
if json_data is None or status != 200:
return False
else:
version = json_data.get("version")
local_server = json_data.get("local", False)
if version != __version__:
log.debug("Client version {} differs with server version {}".format(__version__, version))
return False
if not local_server:
log.debug("Running server is not a GNS3 local server (not started with --local)")
version = json_data.get("version", None)
if version is None:
log.debug("Server is not a GNS3 server")
return False
return True
@@ -451,7 +447,10 @@ class HTTPClient(QtCore.QObject):
if len(msg) > 0:
msg = "Cannot connect to {}: {}".format(server, msg)
else:
msg = "Cannot connect to {}".format(server)
if self.isLocal():
msg = "Cannot connect to {}. Please check if GNS3 is allowed in your antivirus and firewall.".format(server)
else:
msg = "Cannot connect to {}".format(server)
log.error(msg)
if callback is not None:
callback({"message": msg}, error=True, server=self)

View File

@@ -228,7 +228,8 @@ class NodeItem():
:param message: error message
"""
self._last_error = "{message}".format(message=message)
if self:
self._last_error = "{message}".format(message=message)
def errorSlot(self, node_id, message):
"""
@@ -239,7 +240,8 @@ class NodeItem():
:param message: error message
"""
self._last_error = "{message}".format(message=message)
if self:
self._last_error = "{message}".format(message=message)
def setCustomToolTip(self):
"""

View File

@@ -21,6 +21,8 @@ import json
import shutil
import copy
import psutil
from .qt import QtCore
from .version import __version__
from .utils import parse_version
@@ -305,3 +307,41 @@ class LocalConfig(QtCore.QObject):
if not hasattr(LocalConfig, "_instance") or LocalConfig._instance is None:
LocalConfig._instance = LocalConfig(config_file=config_file)
return LocalConfig._instance
@staticmethod
def isMainGui():
"""
:returns: Return true if we are the main gui (first gui to start)
"""
my_pid = os.getpid()
pid_path = os.path.join(LocalConfig.configDirectory(), "gns3_gui.pid")
if os.path.exists(pid_path):
try:
with open(pid_path) as f:
pid = int(f.read())
if pid != my_pid:
try:
process = psutil.Process(pid=pid)
ps_name = process.name()
except (OSError, psutil.NoSuchProcess, psutil.AccessDenied):
pass
else:
if "gns3" in ps_name or "python" in ps_name:
# Process run under the same user id
if sys.platform.startswith("win") or process.uids()[0] == os.getuid():
return False
else:
return True
except (OSError, ValueError) as e:
log.critical("Can't read pid file %s: %s", pid_path, str(e))
return False
try:
with open(pid_path, 'w+') as f:
f.write(str(my_pid))
except OSError as e:
log.critical("Can't write pid file %s: %s", pid_path, str(e))
return False
return True

View File

@@ -25,7 +25,7 @@ try:
if gns3.update_manager.UpdateManager().installDownloadedUpdates():
print("Update installed restart the application")
python = sys.executable
os.execl(python, python, * sys.argv)
os.execl(python, *sys.argv)
except Exception as e:
print("Fail update installation: {}".format(str(e)))
@@ -156,7 +156,7 @@ def main():
lines = traceback.format_exception(exception, value, tb)
print("****** Exception detected, traceback information saved in {} ******".format(exception_file_path))
print("\nPLEASE REPORT ON https://community.gns3.com/community/software/bug\n")
print("\nPLEASE REPORT ON https://www.gns3.com\n")
print("".join(lines))
try:
curdate = time.strftime("%d %b %Y %H:%M:%S")

View File

@@ -248,7 +248,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.uiLabInstructionsAction.triggered.connect(self._labInstructionsActionSlot)
self.uiAboutQtAction.triggered.connect(self._aboutQtActionSlot)
self.uiAboutAction.triggered.connect(self._aboutActionSlot)
self.uiExportDebugInformationsAction.triggered.connect(self._exportDebugInformationsSlot)
self.uiExportDebugInformationAction.triggered.connect(self._exportDebugInformationSlot)
self.uiIOUVMConverterAction.triggered.connect(self._IOUVMConverterActionSlot)
# browsers tool bar connections
@@ -459,8 +459,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self._appliance_wizard.exec_()
elif self.checkForUnsavedChanges():
self._open_project_path = path
self._project.project_closed_signal.connect(self._projectClosedContinueLoadPath)
self._project.close()
if self._project.closed():
self._projectClosedContinueLoadPath()
else:
self._project.project_closed_signal.connect(self._projectClosedContinueLoadPath)
self._project.close()
def _projectClosedContinueLoadPath(self):
@@ -853,7 +856,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
Slot to launch a browser pointing to the documentation page.
"""
QtGui.QDesktopServices.openUrl(QtCore.QUrl("http://www.gns3.net/documentation/"))
QtGui.QDesktopServices.openUrl(QtCore.QUrl("https://gns3.com/support/docs"))
def _checkForUpdateActionSlot(self, silent=False):
"""
@@ -910,9 +913,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
dialog.show()
dialog.exec_()
def _exportDebugInformationsSlot(self):
def _exportDebugInformationSlot(self):
"""
Slot to display a window for exporting debug informations
Slot to display a window for exporting debug information
"""
dialog = ExportDebugDialog(self, self._project)
@@ -1030,30 +1033,25 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
log.debug("Close the main Windows")
servers = Servers.instance()
if self._project.closed() and not servers.localServerIsRunning():
if self._project.closed():
log.debug("Project is closed killing server and closing main windows")
self._finish_application_closing(close_windows=False)
event.accept()
self.uiConsoleTextEdit.closeIO()
elif not self._soft_exit or self.checkForUnsavedChanges():
log.debug("Project is not closed asking for project closing")
self._project.project_closed_signal.connect(self._finish_application_closing)
if servers.localServerIsRunning():
self._project.close(local_server_shutdown=True)
else:
self._project.close(local_server_shutdown=False)
if self._project.closed() and not servers.localServerIsRunning():
log.debug("Disconnect all servers")
servers.disconnectAllServers()
event.accept()
self.uiConsoleTextEdit.closeIO()
else:
event.ignore()
self._project.close(local_server_shutdown=True)
event.ignore()
else:
event.ignore()
def _finish_application_closing(self):
def _finish_application_closing(self, close_windows=True):
"""
Handles the event when the main window is closed.
And project closed.
:params closing: True the windows is currently closing do not try to reclose it
"""
log.debug("_finish_application_closing")
@@ -1070,7 +1068,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
time_spent = "{:.0f}".format(time.time() - self._start_time)
log.debug("Time spend in the software is {}".format(time_spent))
self._analytics_client.sendScreenView("Main Window", session_start=False)
self.close()
if close_windows:
self.close()
def checkForUnsavedChanges(self):
"""
@@ -1134,23 +1134,20 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# start and connect to the local server
server = servers.localServer()
if servers.localServerAutoStart():
if server.isLocalServerRunning():
log.info("A local server already running on this host")
else:
if servers.initLocalServer() and servers.startLocalServer():
worker = WaitForConnectionWorker(server.host(), server.port())
progress_dialog = ProgressDialog(worker,
"Local server",
"Connecting to server {} on port {}...".format(server.host(), server.port()),
"Cancel", busy=True, parent=self)
progress_dialog.show()
if not progress_dialog.exec_():
return
else:
if servers.shouldLocalServerAutoStart():
if not servers.localServerAutoStart():
QtWidgets.QMessageBox.critical(self, "Local server", "Could not start the local server process: {}".format(servers.localServerPath()))
return
worker = WaitForConnectionWorker(server.host(), server.port())
progress_dialog = ProgressDialog(worker,
"Local server",
"Connecting to server {} on port {}...".format(server.host(), server.port()),
"Cancel", busy=True, parent=self)
progress_dialog.show()
if not progress_dialog.exec_():
return
# show the setup wizard
with Progress.instance().context(min_duration=0):
setup_wizard = SetupWizard(self)
@@ -1202,6 +1199,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
running_nodes.append(node.name())
return running_nodes
def _isTopologyOnRemoteServer(self):
"""
:returns: Boolean True if topology run on a remote server
"""
topology = Topology.instance()
running_nodes = []
for node in topology.nodes():
if not node.server().isLocal():
return True
return False
def saveProjectAs(self):
"""
Saves a project to another location/name.
@@ -1219,6 +1227,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
MessageBox(self, "Save project", "Please stop the following nodes before saving the topology to a new location", nodes)
return
if self._isTopologyOnRemoteServer() and not self._project.temporary():
MessageBox(self, "Save project", "You can not use the save as function on a remote project for the moment.")
return
if self._project.temporary():
default_project_name = "untitled"
else:
@@ -1326,6 +1338,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
except ImportError as e:
log.error("GNS3 converter is missing: {}".format(str(e)))
QtWidgets.QMessageBox.critical(self, "GNS3 converter", "Please install gns3-converter in order to open old ini-style GNS3 projects")
self._createTemporaryProject()
return
try:
@@ -1341,6 +1354,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
project_name = text
project_dir = os.path.join(self.projectsDirPath(), project_name)
else:
self._createTemporaryProject()
return
for snapshot_def in get_snapshots(path):
@@ -1350,12 +1364,14 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
do_conversion(topology_def, project_name, project_dir, quiet=True)
except ConvertError as e:
QtWidgets.QMessageBox.critical(self, "GNS3 converter", "Could not convert {}: {}".format(path, e))
self._createTemporaryProject()
return
except Exception:
exc_type, exc_value, exc_tb = sys.exc_info()
lines = traceback.format_exception(exc_type, exc_value, exc_tb)
tb = "".join(lines)
MessageBox(self, "GNS3 converter", "Unexpected exception while converting {}".format(path), details=tb)
self._createTemporaryProject()
return
QtWidgets.QMessageBox.information(self, "GNS3 converter", "Your project has been converted to a new format and can be found in: {}".format(project_dir))

View File

@@ -22,6 +22,5 @@ from gns3.modules.vpcs import VPCS
from gns3.modules.virtualbox import VirtualBox
from gns3.modules.qemu import Qemu
from gns3.modules.vmware import VMware
from gns3.modules.docker import Docker
MODULES = [VPCS, Dynamips, IOU, Qemu, VirtualBox, VMware, Docker, Builtin]
MODULES = [VPCS, Dynamips, IOU, Qemu, VirtualBox, VMware, Builtin]

View File

@@ -258,18 +258,16 @@ class Router(VM):
# push the startup-config
if not vm_id and "startup_config" in additional_settings:
if additional_settings["startup_config"] and os.path.isfile(additional_settings["startup_config"]):
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
del additional_settings["startup_config"]
# push the private-config
if not vm_id and "private_config" in additional_settings:
if additional_settings["private_config"] and os.path.isfile(additional_settings["private_config"]):
base_config_content = self._readBaseConfig(additional_settings["private_config"])
if base_config_content is not None:
params["private_config_content"] = base_config_content
base_config_content = self._readBaseConfig(additional_settings["private_config"])
if base_config_content is not None:
params["private_config_content"] = base_config_content
del additional_settings["private_config"]
params.update(additional_settings)
@@ -316,10 +314,9 @@ class Router(VM):
params = {}
if "startup_config" in new_settings:
if new_settings["startup_config"] and os.path.isfile(new_settings["startup_config"]):
base_config_content = self._readBaseConfig(new_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
base_config_content = self._readBaseConfig(new_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
del new_settings["startup_config"]
if "private_config" in new_settings:

View File

@@ -523,7 +523,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
if not startup_config:
settings["startup_config"] = ""
elif startup_config != settings["startup_config"]:
if os.access(startup_config, os.R_OK):
if self._configFileValid(startup_config):
settings["startup_config"] = startup_config
else:
QtWidgets.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
@@ -532,7 +532,7 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
if not private_config:
settings["private_config"] = ""
elif private_config != settings["private_config"]:
if os.access(private_config, os.R_OK):
if self._configFileValid(private_config):
settings["private_config"] = private_config
else:
QtWidgets.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
@@ -636,3 +636,11 @@ class IOSRouterConfigurationPage(QtWidgets.QWidget, Ui_iosRouterConfigPageWidget
if node:
self._checkForLinkConnectedToWIC(wic_number, settings, node)
settings["wic" + str(wic_number)] = None
def _configFileValid(self, path):
"""
Return true if it's a valid configuration file
"""
if not os.path.isabs(path):
path = os.path.join(Servers.instance().localServerSettings()["configs_path"], path)
return os.access(path, os.R_OK)

View File

@@ -73,6 +73,7 @@ PLATFORMS_DEFAULT_NVRAM = {"c1700": 128,
"c3745": 256,
"c7200": 512}
# MD5 checksum done on uncompressed IOS images
DEFAULT_IDLEPC = {"7f4ae12a098391bc0edcaf4f44caaf9d": "0x80358a60", # c1700-adventerprisek9-mz.124-25d
"3aaecd2222e812c16c211bc9f7c77512": "0x824a4dc4", # c1700-adventerprisek9-mz.124-15.T14
"062a32e9e3f59aeec930ea5694fda9c9": "0x80519c48", # c2600-adventerprisek9-mz.124-25d
@@ -87,7 +88,8 @@ DEFAULT_IDLEPC = {"7f4ae12a098391bc0edcaf4f44caaf9d": "0x80358a60", # c1700-adv
"64f8c427ed48fd21bd02cf1ff254c4eb": "0x60c09aa0", # c3725-adventerprisek9-mz.124-15.T14
"ddbaf74274822b50fa9670e10c75b08f": "0x60aa1da0", # c3745-adventerprisek9-mz.124-25d
"4af2e752220ed1397924150ff7bbe4ce": "0x602701e4", # c3745-adventerprisek9-mz.124-15.T14
"6b89d0d804e1f2bb5b8bda66b5692047": "0x606df838"} # c7200-adventerprisek9-mz.124-24.T5
"6b89d0d804e1f2bb5b8bda66b5692047": "0x606df838", # c7200-adventerprisek9-mz.124-24.T5
"dda82f22a39215bc6b27af891e12b8f6": "0x6018c294"} # c7200-adventerprisek9-mz.155-2.XB
# platforms with supported chassis
CHASSIS = {"c1700": ("1720", "1721", "1750", "1751", "1760"),

View File

@@ -129,18 +129,16 @@ class IOUDevice(VM):
# push the startup-config
if "startup_config" in additional_settings:
if additional_settings["startup_config"] and os.path.isfile(additional_settings["startup_config"]):
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
base_config_content = self._readBaseConfig(additional_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
del additional_settings["startup_config"]
# push the startup-config
if "private_config" in additional_settings:
if additional_settings["private_config"] and os.path.isfile(additional_settings["private_config"]):
base_config_content = self._readBaseConfig(additional_settings["private_config"])
if base_config_content is not None:
params["private_config_content"] = base_config_content
base_config_content = self._readBaseConfig(additional_settings["private_config"])
if base_config_content is not None:
params["private_config_content"] = base_config_content
del additional_settings["private_config"]
params = self._addIourcContentToParams(params)
@@ -218,17 +216,15 @@ class IOUDevice(VM):
params = {}
if "startup_config" in new_settings:
if new_settings["startup_config"] and os.path.isfile(new_settings["startup_config"]):
base_config_content = self._readBaseConfig(new_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
base_config_content = self._readBaseConfig(new_settings["startup_config"])
if base_config_content is not None:
params["startup_config_content"] = base_config_content
del new_settings["startup_config"]
if "private_config" in new_settings:
if new_settings["private_config"] and os.path.isfile(new_settings["private_config"]):
base_config_content = self._readBaseConfig(new_settings["private_config"])
if base_config_content is not None:
params["private_config_content"] = base_config_content
base_config_content = self._readBaseConfig(new_settings["private_config"])
if base_config_content is not None:
params["private_config_content"] = base_config_content
del new_settings["private_config"]
for name, value in new_settings.items():

View File

@@ -249,7 +249,7 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
if not startup_config:
settings["startup_config"] = ""
elif startup_config != settings["startup_config"]:
if os.access(startup_config, os.R_OK):
if self._configFileValid(startup_config):
settings["startup_config"] = startup_config
else:
QtWidgets.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
@@ -259,7 +259,7 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
if not private_config:
settings["private_config"] = ""
elif private_config != settings["private_config"]:
if os.access(private_config, os.R_OK):
if self._configFileValid(private_config):
settings["private_config"] = private_config
else:
QtWidgets.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
@@ -296,3 +296,11 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
settings["ethernet_adapters"] = ethernet_adapters
settings["serial_adapters"] = serial_adapters
def _configFileValid(self, path):
"""
Return true if it's a valid configuration file
"""
if not os.path.isabs(path):
path = os.path.join(Servers.instance().localServerSettings()["configs_path"], path)
return os.access(path, os.R_OK)

View File

@@ -249,15 +249,19 @@ class Qemu(Module):
log.info("QEMU module reset")
self._nodes.clear()
def getQemuBinariesFromServer(self, server, callback):
def getQemuBinariesFromServer(self, server, callback, archs=None):
"""
Gets the QEMU binaries list from a server.
:param server: server to send the request to
:param callback: callback for the reply from the server
:param archs: A list of architectures. Only binaries matching the specified architectures are returned.
"""
server.get("/qemu/binaries", callback)
request_body = None
if archs is not None:
request_body = {"archs": archs}
server.get("/qemu/binaries", callback, body=request_body)
def getQemuImgBinariesFromServer(self, server, callback):
"""
@@ -269,6 +273,16 @@ class Qemu(Module):
server.get(r"/qemu/img-binaries", callback)
def getQemuCapabilitiesFromServer(self, server, callback):
"""
Gets the capabilities of Qemu at a server.
:param server: server to send the request to
:param callback: callback for the reply from the server
"""
server.get(r"/qemu/capabilities", callback)
def createDiskImage(self, server, callback, options):
"""
Create a disk image on the remote server

View File

@@ -205,6 +205,7 @@ class QemuVMWizard(VMWithImagesWizard, Ui_QemuVMWizard):
elif self.uiTypeComboBox.currentText() == "ASA 8.4(2)":
settings["adapters"] = 4
settings["initrd"] = self.uiInitrdImageLineEdit.text()
settings["hda_disk_image"] = self.uiHdaDiskImageLineEdit.text()
settings["kernel_image"] = self.uiKernelImageLineEdit.text()
settings["kernel_command_line"] = "ide_generic.probe_mask=0x01 ide_core.chs=0.0:980,16,32 auto nousb console=ttyS0,9600 bigphysarea=65536 ide1=noprobe no-hlt -net nic"
settings["options"] = "-no-kvm -icount auto -hdachs 980,16,32"

View File

@@ -501,14 +501,14 @@ class QemuVMConfigurationPage(QtWidgets.QWidget, Ui_QemuVMConfigPageWidget):
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
port_name_format = self.uiPortNameFormatLineEdit.text()
if '{0}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}")
if '{0}' not in port_name_format and '{port0}' not in port_name_format and '{port1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}, {port0} or {port1}")
else:
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
port_segment_size = self.uiPortSegmentSizeSpinBox.value()
if port_segment_size and '{1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain {1} if the segment size is not 0")
if port_segment_size and '{1}' not in port_name_format and '{segment0}' not in port_name_format and '{segment1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "If the segment size is not 0, the format must contain {1}, {segment0} or {segment1}")
else:
settings["port_segment_size"] = port_segment_size

View File

@@ -51,6 +51,7 @@ class QemuVM(VM):
self._linked_clone = True
self._settings = {"name": "",
"usage": "",
"qemu_path": "",
"hda_disk_image": "",
"hdb_disk_image": "",
@@ -98,7 +99,14 @@ class QemuVM(VM):
if self._first_port_name and adapter_number == 0:
port_name = self._first_port_name
else:
port_name = self._port_name_format.format(interface_number, segment_number)
port_name = self._port_name_format.format(
interface_number,
segment_number,
port0 = interface_number,
port1 = 1 + interface_number,
segment0 = segment_number,
segment1 = 1 + segment_number
)
interface_number += 1
if self._port_segment_size and interface_number % self._port_segment_size == 0:
segment_number += 1
@@ -351,6 +359,9 @@ class QemuVM(VM):
port_info += " {port_name} {port_description}\n".format(port_name=port.name(),
port_description=port.description())
if "usage" in self._settings and len(self._settings["usage"]) > 0:
info += " Usage: {}\n".format(self._settings["usage"])
return info + port_info
def load(self, node_info):

View File

@@ -28,6 +28,7 @@ QEMU_SETTINGS = {
QEMU_VM_SETTINGS = {
"name": "",
"usage": "",
"symbol": ":/symbols/qemu_guest.svg",
"category": Node.end_devices,
"port_name_format": "Ethernet{0}",

View File

@@ -523,6 +523,9 @@
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiPortNameFormatLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;{0} - the port number, from 0 to the number of adapters-1.&lt;/p&gt;&lt;p&gt;{1} - the segment number, from 0 to the number of segments-1.&lt;/p&gt;&lt;p&gt;{port0} - named alias for {0}.&lt;/p&gt;&lt;p&gt;{port1} - the port number, from 1 to the number of adapters.&lt;/p&gt;&lt;p&gt;{segment0} - named alias for {1}.&lt;/p&gt;&lt;p&gt;{segment1} - the segment number, from 1 to the number of segments.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Name format:</string>
</property>

View File

@@ -1,17 +1,14 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/qemu/ui/qemu_vm_configuration_page.ui'
# Form implementation generated from reading ui file 'D:\Vasko\PyCharmProjects\gns3-gui\gns3\modules\qemu\ui\qemu_vm_configuration_page.ui'
#
# Created: Sun Oct 18 19:06:42 2015
# by: PyQt5 UI code generator 5.2.1
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_QemuVMConfigPageWidget(object):
def setupUi(self, QemuVMConfigPageWidget):
QemuVMConfigPageWidget.setObjectName("QemuVMConfigPageWidget")
QemuVMConfigPageWidget.resize(611, 524)
@@ -388,6 +385,10 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiACPIShutdownCheckBox = QtWidgets.QCheckBox(self.groupBox)
self.uiACPIShutdownCheckBox.setObjectName("uiACPIShutdownCheckBox")
self.gridLayout_3.addWidget(self.uiACPIShutdownCheckBox, 2, 0, 1, 2)
self.uiQemuOptionsLineEdit.raise_()
self.uiQemuOptionsLabel.raise_()
self.uiACPIShutdownCheckBox.raise_()
self.uiBaseVMCheckBox.raise_()
self.verticalLayout_2.addWidget(self.groupBox)
spacerItem4 = QtWidgets.QSpacerItem(20, 90, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout_2.addItem(spacerItem4)
@@ -446,6 +447,7 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiAdapterTypesLabel.setText(_translate("QemuVMConfigPageWidget", "Type:"))
self.uiAdaptersLabel.setText(_translate("QemuVMConfigPageWidget", "Adapters:"))
self.uiMacAddrLabel.setText(_translate("QemuVMConfigPageWidget", "Base MAC:"))
self.uiPortNameFormatLabel.setToolTip(_translate("QemuVMConfigPageWidget", "<html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html>"))
self.uiPortNameFormatLabel.setText(_translate("QemuVMConfigPageWidget", "Name format:"))
self.uiFirstPortNameLabel.setText(_translate("QemuVMConfigPageWidget", "First port name:"))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiNetworkTab), _translate("QemuVMConfigPageWidget", "Network"))
@@ -471,3 +473,4 @@ class Ui_QemuVMConfigPageWidget(object):
self.uiBaseVMCheckBox.setText(_translate("QemuVMConfigPageWidget", "Use as a linked base VM"))
self.uiACPIShutdownCheckBox.setText(_translate("QemuVMConfigPageWidget", "Enable ACPI shutdown (experimental)"))
self.uiQemutabWidget.setTabText(self.uiQemutabWidget.indexOf(self.uiAdvancedSettingsTab), _translate("QemuVMConfigPageWidget", "Advanced settings"))

View File

@@ -185,14 +185,14 @@ class VirtualBoxVMConfigurationPage(QtWidgets.QWidget, Ui_virtualBoxVMConfigPage
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
port_name_format = self.uiPortNameFormatLineEdit.text()
if '{0}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}")
if '{0}' not in port_name_format and '{port0}' not in port_name_format and '{port1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}, {port0} or {port1}")
else:
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
port_segment_size = self.uiPortSegmentSizeSpinBox.value()
if port_segment_size and '{1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain {1} if the segment size is not 0")
if port_segment_size and '{1}' not in port_name_format and '{segment0}' not in port_name_format and '{segment1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "If the segment size is not 0, the format must contain {1}, {segment0} or {segment1}")
else:
settings["port_segment_size"] = port_segment_size

View File

@@ -256,6 +256,9 @@
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiPortNameFormatLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;{0} - the port number, from 0 to the number of adapters-1.&lt;/p&gt;&lt;p&gt;{1} - the segment number, from 0 to the number of segments-1.&lt;/p&gt;&lt;p&gt;{port0} - named alias for {0}.&lt;/p&gt;&lt;p&gt;{port1} - the port number, from 1 to the number of adapters.&lt;/p&gt;&lt;p&gt;{segment0} - named alias for {1}.&lt;/p&gt;&lt;p&gt;{segment1} - the segment number, from 1 to the number of segments.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Name format:</string>
</property>

View File

@@ -1,16 +1,14 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/cetko/projects/gns3/gns3-gui/gns3/modules/virtualbox/ui/virtualbox_vm_configuration_page.ui'
# Form implementation generated from reading ui file 'D:\Vasko\PyCharmProjects\gns3-gui\gns3\modules\virtualbox\ui\virtualbox_vm_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.4.2
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_virtualBoxVMConfigPageWidget(object):
def setupUi(self, virtualBoxVMConfigPageWidget):
virtualBoxVMConfigPageWidget.setObjectName("virtualBoxVMConfigPageWidget")
virtualBoxVMConfigPageWidget.resize(510, 463)
@@ -170,6 +168,8 @@ class Ui_virtualBoxVMConfigPageWidget(object):
self.label.setText(_translate("virtualBoxVMConfigPageWidget", "Type:"))
self.uiUseAnyAdapterCheckBox.setText(_translate("virtualBoxVMConfigPageWidget", "Allow GNS3 to use any configured VirtualBox adapter"))
self.uiPortSegmentSizeLabel.setText(_translate("virtualBoxVMConfigPageWidget", "Segment size:"))
self.uiPortNameFormatLabel.setToolTip(_translate("virtualBoxVMConfigPageWidget", "<html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html>"))
self.uiPortNameFormatLabel.setText(_translate("virtualBoxVMConfigPageWidget", "Name format:"))
self.uiFirstPortNameLabel.setText(_translate("virtualBoxVMConfigPageWidget", "First port name:"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab_2), _translate("virtualBoxVMConfigPageWidget", "Network"))

View File

@@ -78,7 +78,14 @@ class VirtualBoxVM(VM):
if self._first_port_name and adapter_number == 0:
port_name = self._first_port_name
else:
port_name = self._port_name_format.format(interface_number, segment_number)
port_name = self._port_name_format.format(
interface_number,
segment_number,
port0 = interface_number,
port1 = 1 + interface_number,
segment0 = segment_number,
segment1 = 1 + segment_number
)
interface_number += 1
if self._port_segment_size and interface_number % self._port_segment_size == 0:
segment_number += 1

View File

@@ -183,14 +183,14 @@ class VMwareVMConfigurationPage(QtWidgets.QWidget, Ui_VMwareVMConfigPageWidget):
settings["category"] = self.uiCategoryComboBox.itemData(self.uiCategoryComboBox.currentIndex())
port_name_format = self.uiPortNameFormatLineEdit.text()
if '{0}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}")
if '{0}' not in port_name_format and '{port0}' not in port_name_format and '{port1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain at least {0}, {port0} or {port1}")
else:
settings["port_name_format"] = self.uiPortNameFormatLineEdit.text()
port_segment_size = self.uiPortSegmentSizeSpinBox.value()
if port_segment_size and '{1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "The format must contain {1} if the segment size is not 0")
if port_segment_size and '{1}' not in port_name_format and '{segment0}' not in port_name_format and '{segment1}' not in port_name_format:
QtWidgets.QMessageBox.critical(self, "Port name format", "If the segment size is not 0, the format must contain {1}, {segment0} or {segment1}")
else:
settings["port_segment_size"] = port_segment_size

View File

@@ -145,6 +145,9 @@
</item>
<item row="2" column="0">
<widget class="QLabel" name="uiPortNameFormatLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;{0} - the port number, from 0 to the number of adapters-1.&lt;/p&gt;&lt;p&gt;{1} - the segment number, from 0 to the number of segments-1.&lt;/p&gt;&lt;p&gt;{port0} - named alias for {0}.&lt;/p&gt;&lt;p&gt;{port1} - the port number, from 1 to the number of adapters.&lt;/p&gt;&lt;p&gt;{segment0} - named alias for {1}.&lt;/p&gt;&lt;p&gt;{segment1} - the segment number, from 1 to the number of segments.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Name format:</string>
</property>

View File

@@ -1,16 +1,14 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/cetko/projects/gns3/gns3-gui/gns3/modules/vmware/ui/vmware_vm_configuration_page.ui'
# Form implementation generated from reading ui file 'D:\Vasko\PyCharmProjects\gns3-gui\gns3\modules\vmware\ui\vmware_vm_configuration_page.ui'
#
# Created by: PyQt5 UI code generator 5.5
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_VMwareVMConfigPageWidget(object):
def setupUi(self, VMwareVMConfigPageWidget):
VMwareVMConfigPageWidget.setObjectName("VMwareVMConfigPageWidget")
VMwareVMConfigPageWidget.resize(494, 381)
@@ -143,6 +141,7 @@ class Ui_VMwareVMConfigPageWidget(object):
self.uiSymbolToolButton.setText(_translate("VMwareVMConfigPageWidget", "&Browse..."))
self.uiCategoryLabel.setText(_translate("VMwareVMConfigPageWidget", "Category:"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab), _translate("VMwareVMConfigPageWidget", "General settings"))
self.uiPortNameFormatLabel.setToolTip(_translate("VMwareVMConfigPageWidget", "<html><head/><body><p>{0} - the port number, from 0 to the number of adapters-1.</p><p>{1} - the segment number, from 0 to the number of segments-1.</p><p>{port0} - named alias for {0}.</p><p>{port1} - the port number, from 1 to the number of adapters.</p><p>{segment0} - named alias for {1}.</p><p>{segment1} - the segment number, from 1 to the number of segments.</p></body></html>"))
self.uiPortNameFormatLabel.setText(_translate("VMwareVMConfigPageWidget", "Name format:"))
self.uiPortSegmentSizeLabel.setText(_translate("VMwareVMConfigPageWidget", "Segment size:"))
self.uiAdaptersLabel.setText(_translate("VMwareVMConfigPageWidget", "Adapters:"))
@@ -151,3 +150,4 @@ class Ui_VMwareVMConfigPageWidget(object):
self.uiFirstPortNameLabel.setText(_translate("VMwareVMConfigPageWidget", "First port name:"))
self.uiUseUbridgeCheckBox.setText(_translate("VMwareVMConfigPageWidget", "Use uBridge for network connections"))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.tab_2), _translate("VMwareVMConfigPageWidget", "Network"))

View File

@@ -81,7 +81,14 @@ class VMwareVM(VM):
if self._first_port_name and adapter_number == 0:
port_name = self._first_port_name
else:
port_name = self._port_name_format.format(interface_number, segment_number)
port_name = self._port_name_format.format(
interface_number,
segment_number,
port0 = interface_number,
port1 = 1 + interface_number,
segment0 = segment_number,
segment1 = 1 + segment_number
)
interface_number += 1
if self._port_segment_size and interface_number % self._port_segment_size == 0:
segment_number += 1

View File

@@ -77,7 +77,7 @@ class NodesView(QtWidgets.QTreeWidget):
self.sortByColumn(0, QtCore.Qt.AscendingOrder)
if not self.topLevelItemCount() and category == Node.routers:
QtWidgets.QMessageBox.warning(self, 'Routers', 'No routers have been configured.<br>You must provide your own router images in order to use GNS3.<br><br><a href="https://community.gns3.com/community/software/documentation">Show documentation</a>')
QtWidgets.QMessageBox.warning(self, 'Routers', 'No routers have been configured.<br>You must provide your own router images in order to use GNS3.<br><br><a href="https://gns3.com/support/docs/adding-ios-or-iou-qemu-virtual-2">Show documentation</a>')
# TODO: would be nicer to use QErrorMessage but the link cannot be clicked by default
#QtWidgets.QErrorMessage.qtHandler().showMessage('No routers have been configured.<br>You must provide your own router images in order to use GNS3.<br><br><a href="https://community.gns3.com/community/software/documentation">Show documentation</a>')

View File

@@ -142,6 +142,10 @@ class ServerPreferencesPage(QtWidgets.QWidget, Ui_ServerPreferencesPageWidget):
"""
if state:
if not self.uiLocalServerAutoStartCheckBox.isChecked():
QtWidgets.QMessageBox.critical(self, "GNS3 VM", "The local server need to be enable in order to use the GNS3 VM")
self.uiEnableVMCheckBox.setChecked(False)
return
self.uiGNS3VMSettingsGroupBox.setEnabled(True)
self._refreshVMListSlot()
else:
@@ -188,6 +192,10 @@ class ServerPreferencesPage(QtWidgets.QWidget, Ui_ServerPreferencesPageWidget):
self.uiConsolePortRangeGroupBox.setEnabled(True)
self.uiUDPPortRangeGroupBox.setEnabled(True)
else:
if self.uiEnableVMCheckBox.isChecked():
QtWidgets.QMessageBox.critical(self, "GNS3 VM", "The local server need to be enable in order to use the GNS3 VM. Please turn off the GNS3 VM before turning off the local server.")
self.uiLocalServerAutoStartCheckBox.setChecked(True)
return
self.uiGeneralSettingsGroupBox.setEnabled(False)
self.uiConsolePortRangeGroupBox.setEnabled(False)
self.uiUDPPortRangeGroupBox.setEnabled(False)
@@ -447,11 +455,6 @@ class ServerPreferencesPage(QtWidgets.QWidget, Ui_ServerPreferencesPageWidget):
new_local_server_settings["udp_end_port_range"]))
return
if 5900 <= new_local_server_settings["console_start_port_range"] <= 6000 or 5900 <= new_local_server_settings["console_end_port_range"] <= 6000 \
or new_local_server_settings["console_start_port_range"] < 5900 and new_local_server_settings["console_end_port_range"] > 6000:
QtWidgets.QMessageBox.critical(self, "Port range", "Console port range between 5900 and 6000 is reserved for VNC")
return
if new_local_server_settings["auto_start"]:
if not os.path.isfile(new_local_server_settings["path"]):
QtWidgets.QMessageBox.critical(self, "Local server", "Could not find local server {}".format(new_local_server_settings["path"]))

View File

@@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import time
from contextlib import contextmanager
from .qt import QtCore, QtWidgets, Qt, QtNetwork
@@ -41,7 +42,14 @@ class Progress(QtCore.QObject):
from .main_window import MainWindow
self._parent = MainWindow.instance()
self._stimer = QtCore.QTimer()
# Timer called for refreshing the progress dialog status
self._rtimer = QtCore.QTimer()
self._rtimer.timeout.connect(self.update)
self._rtimer.start(500)
# When in millisecond we start to show the progress dialog
self._display_start_time = 0
self._finished_query_during_display = 0
self._queries = {}
# QtCore.Qt.QueuedConnection warranty that we execute the slot
@@ -56,10 +64,11 @@ class Progress(QtCore.QObject):
self._allow_cancel_query = False
self._enable = True
self._mutex = QtCore.QMutex()
def _addQuerySlot(self, query_id, explanation, response):
self._queries[query_id] = {"explanation": explanation, "current": 0, "maximum": 0, "response": response}
self.show()
def _removeQuerySlot(self, query_id):
@@ -67,11 +76,6 @@ class Progress(QtCore.QObject):
if query_id in self._queries:
del self._queries[query_id]
if len(self._queries) == 0:
self.hide()
else:
self.show()
def progress_dialog(self):
return self._progress_dialog
@@ -80,7 +84,6 @@ class Progress(QtCore.QObject):
if query_id in self._queries:
self._queries[query_id]["current"] = current
self._queries[query_id]["maximum"] = maximum
self.show()
def setAllowCancelQuery(self, allow_cancel_query):
self._allow_cancel_query = allow_cancel_query
@@ -95,8 +98,13 @@ class Progress(QtCore.QObject):
for query in self._queries.copy().values():
query["response"].abort()
def show(self):
def update(self):
if len(self._queries) == 0 and (time.time() * 1000) >= self._display_start_time + self._minimum_duration:
self.hide()
return
self.show()
def show(self):
if self._progress_dialog is None or self._progress_dialog.wasCanceled():
progress_dialog = QtWidgets.QProgressDialog("Waiting for server response", None, 0, 0, self._parent)
progress_dialog.canceled.connect(self._cancelSlot)
@@ -111,7 +119,8 @@ class Progress(QtCore.QObject):
self._progress_dialog = progress_dialog
self._finished_query_during_display = 0
start_timer = True
self._display_start_time = time.time() * 1000
self._progress_dialog.show()
else:
start_timer = False
progress_dialog = self._progress_dialog
@@ -130,14 +139,6 @@ class Progress(QtCore.QObject):
text = list(self._queries.values())[0]["explanation"]
progress_dialog.setLabelText(text)
if start_timer:
self._stimer.singleShot(self._minimum_duration, self._show_dialog)
def _show_dialog(self):
if self._progress_dialog is not None and self._enable:
self._progress_dialog.show()
self._progress_dialog.exec_()
def hide(self):
"""
Hide and cancel the progress dialog

View File

@@ -139,7 +139,8 @@ class Project(QtCore.QObject):
assert self._files_dir is not None
assert self._name is not None
except AssertionError:
lines = traceback.format_exception(sys.exc_info())
exc_type, exc_value, exc_tb = sys.exc_info()
lines = traceback.format_exception(exc_type, exc_value, exc_tb)
tb = "".join(lines)
log.debug("Assertion detected: {}".format(tb))
raise

View File

@@ -24,14 +24,17 @@ import sys
from .qt import QtCore, QtGui, QtWidgets
class MultipleRedirection:
class MultipleRedirection(QtCore.QObject):
writed = QtCore.pyqtSignal(str)
def __init__(self, console, stdout):
super().__init__()
self.console = console
self.stdout = stdout
def write(self, str):
self.console.write(str)
self.writed.emit(str)
if self.stdout.encoding is None:
str = str.encode("ascii", "ignore").decode("ascii", "ignore")
elif self.stdout.encoding != "UTF-8":
@@ -79,6 +82,7 @@ class PyCutExt(QtWidgets.QTextEdit):
# capture all interactive input/output
self._old_stdout = sys.stdout
sys.stdout = MultipleRedirection(self, sys.stdout)
sys.stdout.writed.connect(self.write)
# sys.stderr = MultipleRedirection((sys.stderr, self))
self._old_stdin = sys.stdin
sys.stdin = self

View File

@@ -74,7 +74,6 @@ from PyQt5.QtWidgets import QFileDialog as OldFileDialog
class QFileDialog(OldFileDialog):
@staticmethod
def getExistingDirectory(parent=None, caption='', dir='', options=OldFileDialog.ShowDirsOnly):
path = OldFileDialog.getExistingDirectory(parent, caption, dir, options)
@@ -123,6 +122,32 @@ class StatsQtWidgetsQDialog(QtWidgets.QDialog):
QtWidgets.QDialog = StatsQtWidgetsQDialog
class LogQMessageBox(QtWidgets.QMessageBox):
"""
Replace the standard message box for logging errors to console. And
show a stack trace when a critical message box is shown in debug mode
"""
@staticmethod
def critical(parent, title, message, *args):
log.critical(message, stack_info=LogQMessageBox.stack_info())
return super(QtWidgets.QMessageBox, QtWidgets.QMessageBox).critical(parent, title, message, *args)
@staticmethod
def warning(parent, title, message, *args):
log.warning(message)
return super(QtWidgets.QMessageBox, QtWidgets.QMessageBox).warning(parent, title, message, *args)
@staticmethod
def stack_info():
"""
Show stack trace
"""
return logging.getLogger().getEffectiveLevel() == logging.DEBUG
QtWidgets.QMessageBox = LogQMessageBox
class StatsQtWidgetsQWizard(QtWidgets.QWizard):
"""
Send stats from all the QWizard

View File

@@ -49,7 +49,7 @@ class Appliance(collections.Mapping):
"""
if "registry_version" not in self._appliance:
raise ApplianceError("Invalid appliance configuration please report the issue on https://github.com/GNS3/gns3-registry")
if self._appliance["registry_version"] != 1:
if self._appliance["registry_version"] > 2:
raise ApplianceError("Please update GNS3 in order to install this appliance")
def __getitem__(self, key):
@@ -74,6 +74,9 @@ class Appliance(collections.Mapping):
for file in self._appliance["images"]:
file = copy.copy(file)
if "idlepc" in version:
file["idlepc"] = version["idlepc"]
if "/" in filename:
parent, name = filename.split("/")
filename = os.path.join(parent, name)
@@ -133,3 +136,14 @@ class Appliance(collections.Mapping):
return True
except ApplianceError:
return False
def image_dir_name(self):
"""
:returns: The name of directory where image should be located
"""
if "qemu" in self._appliance:
return "QEMU"
if "iou" in self._appliance:
return "IOU"
return "IOS"

View File

@@ -95,6 +95,16 @@ class Config:
home = os.path.expanduser("~")
return os.path.join(home, ".config", appname, filename)
def is_name_available(self, name):
"""
:param name: Appliance name
:returns: True if name is not already used
"""
for item in self._config["Qemu"].get("vms", []):
if item["name"] == name:
return False
return True
def add_appliance(self, appliance_config, server):
"""
Add appliance to the user configuration
@@ -107,6 +117,10 @@ class Config:
"server": server,
"name": appliance_config["name"]
}
if "usage" in appliance_config:
new_config["usage"] = appliance_config["usage"]
if appliance_config["category"] == "guest":
new_config["category"] = 2
elif appliance_config["category"] == "router":
@@ -118,42 +132,6 @@ class Config:
elif appliance_config["category"] == "multilayer_switch":
new_config["category"] = 1
# Raise error if VM already exists
for item in self._config["Qemu"]["vms"]:
if item["name"] == new_config["name"]:
raise ConfigException("{} already exists".format(item["name"]))
if "qemu" in appliance_config:
self._add_qemu_config(new_config, appliance_config)
return
raise ConfigException("{} no configuration found for Qemu".format(item["name"]))
def _add_qemu_config(self, new_config, appliance_config):
new_config["adapter_type"] = appliance_config["qemu"]["adapter_type"]
new_config["adapters"] = appliance_config["qemu"]["adapters"]
new_config["cpu_throttling"] = appliance_config["qemu"].get("cpu_throttling", 0)
new_config["ram"] = appliance_config["qemu"]["ram"]
new_config["console_type"] = appliance_config["qemu"]["console_type"]
new_config["legacy_networking"] = False
new_config["process_priority"] = appliance_config["qemu"].get("process_priority", "normal")
options = appliance_config["qemu"].get("options", "")
if "-nographic" not in options:
options += " -nographic"
new_config["options"] = options.strip()
new_config["hda_disk_image"] = appliance_config["qemu"].get("hda_disk_image", "")
new_config["hdb_disk_image"] = appliance_config["qemu"].get("hdb_disk_image", "")
new_config["hdc_disk_image"] = appliance_config["qemu"].get("hdc_disk_image", "")
new_config["hdd_disk_image"] = appliance_config["qemu"].get("hdd_disk_image", "")
new_config["cdrom_image"] = appliance_config["qemu"].get("cdrom_image", "")
new_config["initrd"] = appliance_config["qemu"].get("initrd", "")
new_config["kernel_command_line"] = appliance_config["qemu"].get("kernel_command_line", "")
new_config["kernel_image"] = appliance_config["qemu"].get("kernel_image", "")
new_config["qemu_path"] = "qemu-system-{}".format(appliance_config["qemu"]["arch"])
if "symbol" in appliance_config:
new_config["symbol"] = self._set_symbol(appliance_config["symbol"])
@@ -169,8 +147,101 @@ class Config:
elif appliance_config["category"] == "firewall":
new_config["symbol"] = ":/symbols/firewall.svg"
# Raise error if VM already exists
if not self.is_name_available(new_config["name"]):
raise ConfigException("{} already exists".format(new_config["name"]))
if "qemu" in appliance_config:
self._add_qemu_config(new_config, appliance_config)
return
if "iou" in appliance_config:
self._add_iou_config(new_config, appliance_config)
return
if "dynamips" in appliance_config:
self._add_dynamips_config(new_config, appliance_config)
return
raise ConfigException("{} no configuration found for know emulators".format(new_config["name"]))
def _add_dynamips_config(self, new_config, appliance_config):
new_config["auto_delete_disks"] = True
new_config["disk0"] = 0
new_config["disk1"] = 0
new_config["exec_area"] = 64
new_config["idlemax"] = 500
new_config["idlesleep"] = 30
new_config["system_id"] = "FTX0945W0MY"
new_config["sparsemem"] = True
new_config["private_config"] = ""
new_config["mac_addr"] = ""
new_config["iomem"] = 5
new_config["mmap"] = True
for key, value in appliance_config["dynamips"].items():
new_config[key] = value
for image in appliance_config["images"]:
new_config[image["type"]] = self._relative_image_path(image["filename"], image["path"])
new_config[image["type"]] = self._relative_image_path("IOS", image["filename"], image["path"])
new_config["idlepc"] = image["idlepc"]
log.debug("Add appliance Dynamips: %s", str(new_config))
self._config["Dynamips"]["routers"].append(new_config)
def _add_iou_config(self, new_config, appliance_config):
new_config["ethernet_adapters"] = appliance_config["iou"]["ethernet_adapters"]
new_config["serial_adapters"] = appliance_config["iou"]["serial_adapters"]
new_config["startup_config"] = appliance_config["iou"]["startup_config"]
new_config["private_config"] = ""
new_config["l1_keepalives"] = False
new_config["use_default_iou_values"] = True
new_config["nvram"] = appliance_config["iou"]["nvram"]
new_config["ram"] = appliance_config["iou"]["ram"]
for image in appliance_config["images"]:
new_config[image["type"]] = self._relative_image_path("IOU", image["filename"], image["path"])
new_config["path"] = new_config["image"]
log.debug("Add appliance IOU: %s", str(new_config))
self._config["IOU"]["devices"].append(new_config)
def _add_qemu_config(self, new_config, appliance_config):
new_config["adapter_type"] = appliance_config["qemu"]["adapter_type"]
new_config["adapters"] = appliance_config["qemu"]["adapters"]
new_config["cpu_throttling"] = appliance_config["qemu"].get("cpu_throttling", 0)
new_config["ram"] = appliance_config["qemu"]["ram"]
new_config["console_type"] = appliance_config["qemu"]["console_type"]
new_config["legacy_networking"] = False
new_config["process_priority"] = appliance_config["qemu"].get("process_priority", "normal")
options = appliance_config["qemu"].get("options", "")
if "-nographic" not in options:
options += " -nographic"
if appliance_config["qemu"].get("kvm", "allow") == "disable" and "-no-kvm" not in options:
options += " -no-kvm"
new_config["options"] = options.strip()
for image in appliance_config["images"]:
new_config[image["type"]] = self._relative_image_path("QEMU", image["filename"], image["path"])
new_config.setdefault("hda_disk_image", "")
new_config.setdefault("hdb_disk_image", "")
new_config.setdefault("hdc_disk_image", "")
new_config.setdefault("hdd_disk_image", "")
new_config.setdefault("cdrom_image", "")
new_config.setdefault("initrd", "")
new_config.setdefault("kernel_image", "")
new_config["hda_disk_interface"] = appliance_config["qemu"].get("hda_disk_interface", "ide")
new_config["hdb_disk_interface"] = appliance_config["qemu"].get("hdb_disk_interface", "ide")
new_config["hdc_disk_interface"] = appliance_config["qemu"].get("hdc_disk_interface", "ide")
new_config["hdd_disk_interface"] = appliance_config["qemu"].get("hdd_disk_interface", "ide")
new_config["kernel_command_line"] = appliance_config["qemu"].get("kernel_command_line", "")
if "path" in appliance_config["qemu"]:
new_config["qemu_path"] = appliance_config["qemu"]["path"]
else:
new_config["qemu_path"] = "qemu-system-{}".format(appliance_config["qemu"]["arch"])
if "boot_priority" in appliance_config:
new_config["boot_priority"] = appliance_config["boot_priority"]
@@ -210,13 +281,16 @@ class Config:
except OSError:
return None
def _relative_image_path(self, filename, path):
def _relative_image_path(self, image_dir_type, filename, path):
"""
:param image_dir_type: Type of image directory
:param filename: Filename at the end of the processus
:param path: Full path to the file
:returns: Path relative to image directory.
Copy the image to the directory if not already in the directory
"""
images_dir = os.path.join(self.images_dir, "QEMU")
images_dir = os.path.join(self.images_dir, image_dir_type)
path = os.path.abspath(path)
if os.path.commonprefix([images_dir, path]) == images_dir:
return path.replace(images_dir, '').strip('/\\')
@@ -226,7 +300,7 @@ class Config:
base_file = re.split(r'[/\\]', filename)[0]
else:
base_file = filename
Image(path).copy(os.path.join(self.images_dir, "QEMU"), base_file)
Image(path).copy(images_dir, base_file)
return filename
def save(self):

View File

@@ -33,6 +33,14 @@ class Registry:
def __init__(self, images_dirs):
self._images_dirs = images_dirs
def appendImageDirectory(self, image_directory):
"""
Add a folder to the list of we need to scan
:param image_directory: Folder we need to add
"""
self._images_dirs.append(image_directory)
def search_image_file(self, filename, md5sum, size):
"""
Search an image based on its MD5 checksum

View File

@@ -397,9 +397,31 @@
"console_type": { "$ref": "#/definitions/mandatory_string" },
"monitor": { "$ref": "#/definitions/network_port" },
"name": { "$ref": "#/definitions/mandatory_string" },
"usage": { "type": "string" },
"acpi_shutdown": { "type": "boolean" },
"adapter_type": {
"enum": ["e1000", "i82551", "i82557b", "i82559er", "ne2k_pci", "pcnet", "rtl8139", "virtio", "virtio-net-pci"]
"enum": [
"e1000",
"i82550",
"i82551",
"i82557a",
"i82557b",
"i82557c",
"i82558a",
"i82558b",
"i82559a",
"i82559b",
"i82559c",
"i82559er",
"i82562",
"i82801",
"ne2k_pci",
"pcnet",
"rtl8139",
"virtio",
"virtio-net-pci",
"vmxnet3"
]
},
"adapters": {"type": "integer", "minimum": 1},
"cpu_throttling": {"type": "integer", "minimum": 0, "maximum": 100},
@@ -573,7 +595,7 @@
"idlepc": {"type": "string", "pattern": "^0x[0-9a-f]{8}"},
"idlesleep": {"type": "integer", "minimum": 1},
"image": { "$ref": "#/definitions/mandatory_string" },
"image_md5sum": { "$ref": "#/definitions/md5" },
"image_md5sum": { "$ref": "#/definitions/md5" },
"mac_addr": { "$ref": "#/definitions/mandatory_string" },
"midplane": { "enum": ["std", "vxr"] },
"chassis": {

View File

@@ -32,6 +32,7 @@ import subprocess
import binascii
import stat
import struct
import psutil
from .qt import QtNetwork, QtWidgets
from .network_client import getNetworkClientInstance, getNetworkUrl
@@ -67,6 +68,7 @@ class Servers():
self._network_manager.sslErrors.connect(self._handleSslErrors)
self._remote_server_iter_pos = 0
self._loadSettings()
self._pid_path = os.path.join(LocalConfig.configDirectory(), "gns3_server.pid")
self.registerLocalServer()
def registerLocalServer(self):
@@ -347,7 +349,7 @@ class Servers():
vm_settings = self._settings["vm"]
vm_settings.update(settings)
def localServerAutoStart(self):
def shouldLocalServerAutoStart(self):
"""
Returns either the local server
is automatically started on startup.
@@ -366,6 +368,46 @@ class Servers():
return self._settings["local_server"]["path"]
def _killAlreadyRunningServer(self):
"""
Kill a running zombie server (started by a gui that no longer exists)
This will not kill server started by hand.
"""
try:
if os.path.exists(self._pid_path):
with open(self._pid_path) as f:
pid = int(f.read())
process = psutil.Process(pid=pid)
log.info("Kill already running server with PID %d", pid)
process.kill()
except (OSError, psutil.NoSuchProcess, psutil.AccessDenied):
# Permission issue, or process no longer exists
return
def localServerAutoStart(self):
"""
Try to start the embed gns3 server.
"""
# We check if two gui are not launched at the same time
# to avoid killing the server of the other GUI
if not LocalConfig.isMainGui():
log.info("Not the main GUI, will not autostart the server")
return True
if self.localServer().isLocalServerRunning():
log.info("A local server already running on this host")
# Try to kill the server. The server can be still running after
# if the server was started by hand
self._killAlreadyRunningServer()
if not self.localServer().isLocalServerRunning():
if not self.initLocalServer():
return False
if not self.startLocalServer():
return False
return True
def initLocalServer(self):
"""
Initialize the local server.
@@ -469,7 +511,7 @@ class Servers():
pass
except OSError as e:
log.warn("could not delete server log file {}: {}".format(logpath, e))
command += ' --log="{}"'.format(logpath)
command += ' --log="{}" --pid="{}"'.format(logpath, self._pid_path)
log.info("Starting local server process with {}".format(command))
try:

View File

@@ -75,6 +75,20 @@ elif sys.platform.startswith("darwin"):
" -e 'delay 1'"
" -e 'end repeat'"
" -e 'end tell'",
'Terminal tabbed (experimental)': "osascript -e 'tell application \"Terminal\"'"
" -e 'activate'"
" -e 'tell application \"System Events\" to tell process \"Terminal\" to keystroke \"t\" using command down'"
" -e 'if (the (count of the window) = 0) then'"
" -e 'repeat while contents of selected tab of window 1 starts with linefeed'"
" -e 'delay 0.01'"
" -e 'end repeat'"
" -e 'tell application \"System Events\" to keystroke \"n\" using command down'"
" -e 'end if'"
" -e 'repeat while the busy of window 1 = true'"
" -e 'delay 0.01'"
" -e 'end repeat'"
" -e 'do script \"echo -n -e \\\"\\\\033]0;%d\\\\007\\\" ; telnet %h %p ; exit\" in window 1'"
" -e 'end tell'",
'iTerm': "osascript -e 'tell application \"iTerm\"'"
" -e 'activate'"
" -e 'if (count of terminals) = 0 then'"
@@ -93,8 +107,29 @@ elif sys.platform.startswith("darwin"):
" -e ' end tell'"
" -e 'end tell'"
" -e 'end tell'",
'iTerm 2.9': "osascript -e 'tell application \"iTerm\"'"
" -e 'activate'"
" -e 'if (count of windows) = 0 then'"
" -e ' set t to (create window with default profile)'"
" -e 'else'"
" -e ' set t to current window'"
" -e 'end if'"
" -e 'tell t'"
" -e ' create tab with default profile'"
" -e ' set s to current session'"
" -e ' tell s'"
" -e ' write text \"telnet %h %p\"'"
" -e ' delay 1'"
" -e ' repeat while s exists'"
" -e ' delay 1'"
" -e ' end repeat'"
" -e ' end tell'"
" -e 'end tell'"
" -e 'end tell'",
'Royal TSX': "open 'rtsx://telnet%3A%2F%2F%h:%p'",
'SecureCRT': '/Applications/SecureCRT.app/Contents/MacOS/SecureCRT /N "%d" /T /TELNET %h %p',
'ZOC 6': '/Applications/zoc6.app/Contents/MacOS/zoc6 "/TELNET:%h:%p" /TABBED "/TITLE:%d"'
'ZOC 6': '/Applications/zoc6.app/Contents/MacOS/zoc6 "/TELNET:%h:%p" /TABBED "/TITLE:%d"',
'ZOC 7': '/Applications/zoc7.app/Contents/MacOS/zoc7 "/TELNET:%h:%p" /TABBED "/TITLE:%d"'
}
# default Mac OS X Telnet console command
@@ -167,7 +202,8 @@ elif sys.platform.startswith("darwin"):
" -e ' display dialog \"WARNING OSX VNC support is limited if you have trouble connecting to a device please use an alternative client like Chicken of the VNC.\" buttons {\"OK\"} default button 1 with icon caution with title \"GNS3\"'"
" -e ' open location \"vnc://%h:%p\"'"
" -e 'end tell'",
'Chicken of the VNC': "/Applications/Chicken\ of\ the\ VNC.app/Contents/MacOS/Chicken\ of\ the\ VNC %h:%p"
'Chicken of the VNC': "/Applications/Chicken\ of\ the\ VNC.app/Contents/MacOS/Chicken\ of\ the\ VNC %h:%p",
'Royal TSX': "open 'rtsx://vnc%3A%2F%2F%h:%p'",
}
# default Mac OS X VNC console command
@@ -193,7 +229,8 @@ if sys.platform.startswith("win"):
elif sys.platform.startswith("darwin"):
# Mac OS X
PRECONFIGURED_PACKET_CAPTURE_READER_COMMANDS = {WIRESHARK_NORMAL_CAPTURE: "/usr/bin/open -a /Applications/Wireshark.app %c",
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0 %c | /Applications/Wireshark.app/Contents/Resources/bin/wireshark -o "gui.window_title:%d" -k -i -'}
"Wireshark V1.X Live Traffic Capture": 'tail -f -c +0 %c | /Applications/Wireshark.app/Contents/Resources/bin/wireshark -o "gui.window_title:%d" -k -i -',
WIRESHARK_LIVE_TRAFFIC_CAPTURE: 'tail -f -c +0 %c | /Applications/Wireshark.app/Contents/MacOS/Wireshark -o "gui.window_title:%d" -k -i -'}
elif sys.platform.startswith("freebsd"):
# FreeBSD
@@ -240,7 +277,8 @@ GENERAL_SETTINGS = {
"debug_level": 0,
"recent_files": [],
"geometry": "",
"state": ""
"state": "",
"debug_level": 0,
}
GRAPHICS_VIEW_SETTINGS = {
@@ -268,7 +306,7 @@ SERVERS_SETTINGS = {
"auth": True,
"user": "",
"password": "",
"console_start_port_range": 2001,
"console_start_port_range": 2000,
"console_end_port_range": 7000,
"udp_start_port_range": 10000,
"udp_end_port_range": 20000,

View File

@@ -34,7 +34,6 @@ class SSHConnectionThread(QtCore.QThread):
super().__init__(parent)
def run(self):
log.info("SSH connection to %s with key %s", self._ssh_client.url(), self._ssh_client.ssh_key())
port = Endpoint.find_unused_port(1000, 10000)
if port is None:
self.error_signal.emit("No port available in order to create SSH tunnel")
@@ -79,7 +78,7 @@ class SSHClient(HTTPClient):
assert settings["ssh_key"] is not None
super().__init__(settings, network_manager)
def connect(self, query, callback):
def _connect(self, query):
"""
Initialize the connection
@@ -87,15 +86,16 @@ class SSHClient(HTTPClient):
:param callback: User callback when connection is finish
"""
log.info("SSH connection to %s with key %s", self.url(), self.ssh_key())
thread = SSHConnectionThread(self, parent=self)
thread.error_signal.connect(lambda msg: self._connectionError(callback, msg))
thread.connected_signal.connect(lambda: super(SSHClient, self).connect(query, callback))
thread.error_signal.connect(lambda msg: self._connectionError(None, msg))
thread.connected_signal.connect(lambda: super(SSHClient, self)._connect(query ))
thread.start()
def getTunnel(self, port):
"""
Get a tunnel to the remote port.
For HTTP standard client it's the same port. For SSH it will be
For HTTP standard client it's the same port. For SSH it will be different
:param port: Remote port
:returns: Tuple host, port to connect

0
gns3/static/.keep Normal file
View File

View File

@@ -1,9 +0,0 @@
<html>
<head>
<title>GNS3 Jungle</title>
</head>
<body leftmargin=0 marginwidth=0 topmargin=0 marginheight=0>
<a href="https://community.gns3.com"><img width="200" height="200" src="images/gns3_jungle.png"></a>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -30,10 +30,10 @@ from .main_window import MainWindow
import logging
log = logging.getLogger(__name__)
console_mutex = QtCore.QMutex()
class ConsoleThread(QtCore.QThread):
consoleDone = QtCore.pyqtSignal(str, str, int)
consoleError = QtCore.pyqtSignal(str)
def __init__(self, parent, command, name, server, port):
@@ -67,15 +67,23 @@ class ConsoleThread(QtCore.QThread):
command = command.replace("%p", str(port))
command = command.replace("%d", self._name)
# If the console use an apple script we lock to avoid multiple console
# to interact at the same time
if sys.platform.startswith("darwin") and "osascript" in command:
console_mutex.lock()
try:
self.exec_command(command)
except (OSError, subprocess.SubprocessError) as e:
pass
# log.warning('could not start Telnet console "{}": {}'.format(self._command, e))
finally:
# emit signal upon completion
self.consoleDone.emit(self._name, host, port)
self._server.releaseTunnel(port)
log.info('Telnet console {}:{} closed'.format(host, port))
if sys.platform.startswith("darwin") and "osascript" in command:
console_mutex.unlock()
else:
#TODO: For apple script we can't detect when the console is closed. This mean we leak a port each time you close the console
self._server.releaseTunnel(port)
def nodeTelnetConsole(name, server, port):
@@ -91,10 +99,8 @@ def nodeTelnetConsole(name, server, port):
if not command:
return
# FIXME: do we still need to run the console from a thread?
log.info('Starting telnet console in thread "{}"'.format(command))
console_thread = ConsoleThread(MainWindow.instance(), command, name, server, port)
# console_thread.consoleDone.connect(callback)
console_thread.consoleError.connect(_consoleErrorSlot)
console_thread.start()

View File

@@ -154,7 +154,7 @@
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>On Windows and OSX the local server is not supported. Please use the GNS3 VM.</string>
<string/>
</property>
</widget>
</item>
@@ -588,6 +588,39 @@
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiQemuWizardPage">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Qemu settings</string>
</property>
<property name="subTitle">
<string>Please choose the qemu binary that we will use for running this appliance.</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="uiQemuListLabel">
<property name="text">
<string>Qemu binary:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="uiQemuListComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiUsageWizardPage">
<property name="title">
<string>Usage</string>
@@ -602,7 +635,7 @@
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'.Helvetica Neue DeskInterface'; font-size:13pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'.SF NS Text'; font-size:13pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;The default username/password is admin/admin. A default configuration is present.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>

View File

@@ -2,15 +2,13 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/appliance_wizard.ui'
#
# Created by: PyQt5 UI code generator 5.5
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ApplianceWizard(object):
def setupUi(self, ApplianceWizard):
ApplianceWizard.setObjectName("ApplianceWizard")
ApplianceWizard.resize(688, 469)
@@ -46,7 +44,7 @@ class Ui_ApplianceWizard(object):
self.gridLayout_4.addWidget(self.uiInfoTreeWidget, 1, 0, 1, 1)
self.uiDescriptionLabel = QtWidgets.QLabel(self.uiInfoWizardPage)
self.uiDescriptionLabel.setScaledContents(False)
self.uiDescriptionLabel.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
self.uiDescriptionLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.uiDescriptionLabel.setWordWrap(True)
self.uiDescriptionLabel.setObjectName("uiDescriptionLabel")
self.gridLayout_4.addWidget(self.uiDescriptionLabel, 0, 0, 1, 1)
@@ -56,6 +54,7 @@ class Ui_ApplianceWizard(object):
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.uiServerWizardPage)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.label = QtWidgets.QLabel(self.uiServerWizardPage)
self.label.setText("")
self.label.setObjectName("label")
self.verticalLayout_2.addWidget(self.label)
self.uiServerTypeGroupBox = QtWidgets.QGroupBox(self.uiServerWizardPage)
@@ -176,6 +175,27 @@ class Ui_ApplianceWizard(object):
self.uiSummaryTreeWidget.header().setStretchLastSection(True)
self.gridLayout_2.addWidget(self.uiSummaryTreeWidget, 0, 0, 1, 1)
ApplianceWizard.addPage(self.uiSummaryWizardPage)
self.uiQemuWizardPage = QtWidgets.QWizardPage()
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiQemuWizardPage.sizePolicy().hasHeightForWidth())
self.uiQemuWizardPage.setSizePolicy(sizePolicy)
self.uiQemuWizardPage.setObjectName("uiQemuWizardPage")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.uiQemuWizardPage)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.uiQemuListLabel = QtWidgets.QLabel(self.uiQemuWizardPage)
self.uiQemuListLabel.setObjectName("uiQemuListLabel")
self.horizontalLayout_2.addWidget(self.uiQemuListLabel)
self.uiQemuListComboBox = QtWidgets.QComboBox(self.uiQemuWizardPage)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiQemuListComboBox.sizePolicy().hasHeightForWidth())
self.uiQemuListComboBox.setSizePolicy(sizePolicy)
self.uiQemuListComboBox.setObjectName("uiQemuListComboBox")
self.horizontalLayout_2.addWidget(self.uiQemuListComboBox)
ApplianceWizard.addPage(self.uiQemuWizardPage)
self.uiUsageWizardPage = QtWidgets.QWizardPage()
self.uiUsageWizardPage.setObjectName("uiUsageWizardPage")
self.gridLayout_3 = QtWidgets.QGridLayout(self.uiUsageWizardPage)
@@ -210,7 +230,6 @@ class Ui_ApplianceWizard(object):
self.uiDescriptionLabel.setText(_translate("ApplianceWizard", "NX-OSv is a reference platform for an implementation of the Cisco Nexus operating system, based on the Nexus 7000-series platforms, running as a full virtual machine on a hypervisor."))
self.uiServerWizardPage.setTitle(_translate("ApplianceWizard", "Server"))
self.uiServerWizardPage.setSubTitle(_translate("ApplianceWizard", "Please choose a server type to run your new Appliance."))
self.label.setText(_translate("ApplianceWizard", "On Windows and OSX the local server is not supported. Please use the GNS3 VM."))
self.uiServerTypeGroupBox.setTitle(_translate("ApplianceWizard", "Server type"))
self.uiRemoteRadioButton.setText(_translate("ApplianceWizard", "Remote"))
self.uiVMRadioButton.setText(_translate("ApplianceWizard", "GNS3 VM"))
@@ -272,12 +291,15 @@ class Ui_ApplianceWizard(object):
self.uiSummaryTreeWidget.topLevelItem(5).setText(0, _translate("ApplianceWizard", "kernel command line"))
self.uiSummaryTreeWidget.topLevelItem(5).setText(1, _translate("ApplianceWizard", "user=gns3"))
self.uiSummaryTreeWidget.setSortingEnabled(__sortingEnabled)
self.uiQemuWizardPage.setTitle(_translate("ApplianceWizard", "Qemu settings"))
self.uiQemuWizardPage.setSubTitle(_translate("ApplianceWizard", "Please choose the qemu binary that we will use for running this appliance."))
self.uiQemuListLabel.setText(_translate("ApplianceWizard", "Qemu binary:"))
self.uiUsageWizardPage.setTitle(_translate("ApplianceWizard", "Usage"))
self.uiUsageWizardPage.setSubTitle(_translate("ApplianceWizard", "Please read the following instructions in order to use your new appliance."))
self.uiUsageTextEdit.setHtml(_translate("ApplianceWizard", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'.Helvetica Neue DeskInterface\'; font-size:13pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:\'Ubuntu\'; font-size:11pt;\">The default username/password is admin/admin. A default configuration is present.</span></p></body></html>"))
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'.SF NS Text\'; font-size:13pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:\'Ubuntu\'; font-size:11pt;\">The default username/password is admin/admin. A default configuration is present.</span></p></body></html>"))
from . import resources_rc

View File

@@ -9,12 +9,12 @@
<rect>
<x>0</x>
<y>0</y>
<width>592</width>
<height>223</height>
<width>555</width>
<height>215</height>
</rect>
</property>
<property name="windowTitle">
<string>Export debug informations</string>
<string>Export debug information</string>
</property>
<property name="modal">
<bool>true</bool>
@@ -26,7 +26,7 @@
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;We will export debug informations. &lt;span style=&quot; font-weight:600;&quot;&gt;Be carefull&lt;/span&gt; this file can contain &lt;span style=&quot; font-weight:600;&quot;&gt;private informations&lt;/span&gt; about your topologies, GNS3 settings or your computer (list of running process for example). You can unzip the file in order to control the content.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;You need to&lt;span style=&quot; font-weight:600;&quot;&gt; save the project before&lt;/span&gt; exporting the informations.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;Thanks a lot to helping the GNS3 community.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This will export a debug information file. You must first&lt;span style=&quot; font-weight:600;&quot;&gt; save a project before&lt;/span&gt; you are allowed to export the debug data.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Be aware&lt;/span&gt; this file can contain &lt;span style=&quot; font-weight:600;&quot;&gt;private information&lt;/span&gt; about your project, your GNS3 settings or your computer (list of running processes, opened ports etc.). You can unzip the file in order to edit its content.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;Thanks a lot for helping GNS3.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>

View File

@@ -1,20 +1,19 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/export_debug_dialog.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/export_debug_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.5.1
# Created: Sat Nov 14 18:32:47 2015
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ExportDebugDialog(object):
def setupUi(self, ExportDebugDialog):
ExportDebugDialog.setObjectName("ExportDebugDialog")
ExportDebugDialog.setWindowModality(QtCore.Qt.WindowModal)
ExportDebugDialog.resize(592, 223)
ExportDebugDialog.resize(555, 215)
ExportDebugDialog.setModal(True)
self.verticalLayout = QtWidgets.QVBoxLayout(ExportDebugDialog)
self.verticalLayout.setContentsMargins(-1, -1, 12, -1)
@@ -22,7 +21,7 @@ class Ui_ExportDebugDialog(object):
self.label = QtWidgets.QLabel(ExportDebugDialog)
self.label.setTextFormat(QtCore.Qt.RichText)
self.label.setScaledContents(False)
self.label.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
self.label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.label.setWordWrap(True)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
@@ -50,7 +49,6 @@ class Ui_ExportDebugDialog(object):
self.uiOkButton.setObjectName("uiOkButton")
self.horizontalLayout.addWidget(self.uiOkButton)
self.verticalLayout.addLayout(self.horizontalLayout)
self.label.raise_()
self.retranslateUi(ExportDebugDialog)
self.uiCancelButton.clicked.connect(ExportDebugDialog.reject)
@@ -58,8 +56,8 @@ class Ui_ExportDebugDialog(object):
def retranslateUi(self, ExportDebugDialog):
_translate = QtCore.QCoreApplication.translate
ExportDebugDialog.setWindowTitle(_translate("ExportDebugDialog", "Export debug informations"))
self.label.setText(_translate("ExportDebugDialog", "<html><head/><body><p>We will export debug informations. <span style=\" font-weight:600;\">Be carefull</span> this file can contain <span style=\" font-weight:600;\">private informations</span> about your topologies, GNS3 settings or your computer (list of running process for example). You can unzip the file in order to control the content.</p><p><br/>You need to<span style=\" font-weight:600;\"> save the project before</span> exporting the informations.</p><p><br/>Thanks a lot to helping the GNS3 community.</p></body></html>"))
ExportDebugDialog.setWindowTitle(_translate("ExportDebugDialog", "Export debug information"))
self.label.setText(_translate("ExportDebugDialog", "<html><head/><body><p>This will export a debug information file. You must first<span style=\" font-weight:600;\"> save a project before</span> you are allowed to export the debug data.</p><p><span style=\" font-weight:600;\">Be aware</span> this file can contain <span style=\" font-weight:600;\">private information</span> about your project, your GNS3 settings or your computer (list of running processes, opened ports etc.). You can unzip the file in order to edit its content.</p><p><br/>Thanks a lot for helping GNS3.</p></body></html>"))
self.uiCancelButton.setText(_translate("ExportDebugDialog", "Cancel"))
from . import resources_rc

View File

@@ -26,6 +26,12 @@
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QGroupBox" name="uiLocalPathsGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Local paths</string>
</property>
@@ -158,6 +164,12 @@
</item>
<item>
<widget class="QGroupBox" name="uiStyleGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Style</string>
</property>
@@ -170,6 +182,12 @@
</item>
<item>
<widget class="QGroupBox" name="uiConfigurationFileGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Configuration file</string>
</property>

View File

@@ -1,17 +1,14 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/general_preferences_page.ui'
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/general_preferences_page.ui'
#
# Created: Thu Oct 15 21:17:06 2015
# by: PyQt5 UI code generator 5.2.1
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_GeneralPreferencesPageWidget(object):
def setupUi(self, GeneralPreferencesPageWidget):
GeneralPreferencesPageWidget.setObjectName("GeneralPreferencesPageWidget")
GeneralPreferencesPageWidget.resize(538, 623)
@@ -24,6 +21,11 @@ class Ui_GeneralPreferencesPageWidget(object):
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.uiGeneralTab)
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.uiLocalPathsGroupBox = QtWidgets.QGroupBox(self.uiGeneralTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiLocalPathsGroupBox.sizePolicy().hasHeightForWidth())
self.uiLocalPathsGroupBox.setSizePolicy(sizePolicy)
self.uiLocalPathsGroupBox.setObjectName("uiLocalPathsGroupBox")
self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.uiLocalPathsGroupBox)
self.verticalLayout_7.setObjectName("verticalLayout_7")
@@ -95,6 +97,11 @@ class Ui_GeneralPreferencesPageWidget(object):
self.verticalLayout_7.addLayout(self.horizontalLayout_7)
self.verticalLayout_5.addWidget(self.uiLocalPathsGroupBox)
self.uiStyleGroupBox = QtWidgets.QGroupBox(self.uiGeneralTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiStyleGroupBox.sizePolicy().hasHeightForWidth())
self.uiStyleGroupBox.setSizePolicy(sizePolicy)
self.uiStyleGroupBox.setObjectName("uiStyleGroupBox")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.uiStyleGroupBox)
self.verticalLayout_4.setObjectName("verticalLayout_4")
@@ -103,6 +110,11 @@ class Ui_GeneralPreferencesPageWidget(object):
self.verticalLayout_4.addWidget(self.uiStyleComboBox)
self.verticalLayout_5.addWidget(self.uiStyleGroupBox)
self.uiConfigurationFileGroupBox = QtWidgets.QGroupBox(self.uiGeneralTab)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiConfigurationFileGroupBox.sizePolicy().hasHeightForWidth())
self.uiConfigurationFileGroupBox.setSizePolicy(sizePolicy)
self.uiConfigurationFileGroupBox.setObjectName("uiConfigurationFileGroupBox")
self.gridLayout = QtWidgets.QGridLayout(self.uiConfigurationFileGroupBox)
self.gridLayout.setObjectName("gridLayout")
@@ -429,3 +441,4 @@ class Ui_GeneralPreferencesPageWidget(object):
self.uiSlowStartAllSpinBox.setSuffix(_translate("GeneralPreferencesPageWidget", " seconds"))
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.tab), _translate("GeneralPreferencesPageWidget", "Miscellaneous"))
self.uiRestoreDefaultsPushButton.setText(_translate("GeneralPreferencesPageWidget", "Restore defaults"))

View File

@@ -415,8 +415,8 @@ class Ui_MainWindow(object):
self.uiOpenApplianceAction = QtWidgets.QAction(MainWindow)
self.uiOpenApplianceAction.setIcon(icon1)
self.uiOpenApplianceAction.setObjectName("uiOpenApplianceAction")
self.uiExportDebugInformationsAction = QtWidgets.QAction(MainWindow)
self.uiExportDebugInformationsAction.setObjectName("uiExportDebugInformationsAction")
self.uiExportDebugInformationAction = QtWidgets.QAction(MainWindow)
self.uiExportDebugInformationAction.setObjectName("uiExportDebugInformationAction")
self.uiEditMenu.addAction(self.uiSelectAllAction)
self.uiEditMenu.addAction(self.uiSelectNoneAction)
self.uiEditMenu.addSeparator()
@@ -442,7 +442,7 @@ class Ui_MainWindow(object):
self.uiHelpMenu.addAction(self.uiCheckForUpdateAction)
self.uiHelpMenu.addAction(self.uiSetupWizard)
self.uiHelpMenu.addAction(self.uiLabInstructionsAction)
self.uiHelpMenu.addAction(self.uiExportDebugInformationsAction)
self.uiHelpMenu.addAction(self.uiExportDebugInformationAction)
self.uiHelpMenu.addAction(self.uiAboutQtAction)
self.uiHelpMenu.addAction(self.uiAboutAction)
self.uiViewMenu.addAction(self.uiActionFullscreen)
@@ -676,7 +676,7 @@ class Ui_MainWindow(object):
self.uiSetupWizard.setText(_translate("MainWindow", "&Setup Wizard"))
self.uiIOUVMConverterAction.setText(_translate("MainWindow", "IOU VM Converter"))
self.uiOpenApplianceAction.setText(_translate("MainWindow", "Import appliance"))
self.uiExportDebugInformationsAction.setText(_translate("MainWindow", "Export debug informations"))
self.uiExportDebugInformationAction.setText(_translate("MainWindow", "Export debug information"))
from ..console_view import ConsoleView
from ..graphics_view import GraphicsView

View File

@@ -6,37 +6,40 @@
<rect>
<x>0</x>
<y>0</y>
<width>590</width>
<height>534</height>
<width>900</width>
<height>600</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>2</horstretch>
<verstretch>2</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>900</width>
<height>600</height>
</size>
</property>
<property name="windowTitle">
<string>Preferences</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QGridLayout">
<item row="2" column="1" colspan="2">
<widget class="QDialogButtonBox" name="uiButtonBox">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="Line" name="uiLine">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<item>
<widget class="QTreeWidget" name="uiTreeWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
@@ -70,15 +73,8 @@
</column>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="Line" name="uiLine">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="0" column="2">
<layout class="QVBoxLayout">
<item>
<layout class="QVBoxLayout" name="vbox">
<property name="spacing">
<number>3</number>
</property>
@@ -104,23 +100,52 @@
</widget>
</item>
<item>
<widget class="QStackedWidget" name="uiStackedWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>680</width>
<height>519</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QStackedWidget" name="uiStackedWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<widget class="QWidget" name="uiPageWidget"/>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="uiButtonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
<widget class="QWidget" name="uiPageWidget"/>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>uiButtonBox</tabstop>
</tabstops>
<resources>
<include location="../../resources/resources.qrc"/>
</resources>
@@ -132,12 +157,12 @@
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>270</y>
<x>540</x>
<y>571</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
<x>449</x>
<y>299</y>
</hint>
</hints>
</connection>
@@ -148,12 +173,12 @@
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>276</y>
<x>540</x>
<y>571</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
<x>449</x>
<y>299</y>
</hint>
</hints>
</connection>

View File

@@ -2,32 +2,31 @@
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/preferences_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.4.2
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_PreferencesDialog(object):
def setupUi(self, PreferencesDialog):
PreferencesDialog.setObjectName("PreferencesDialog")
PreferencesDialog.resize(590, 534)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
PreferencesDialog.resize(900, 600)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(2)
sizePolicy.setVerticalStretch(2)
sizePolicy.setHeightForWidth(PreferencesDialog.sizePolicy().hasHeightForWidth())
PreferencesDialog.setSizePolicy(sizePolicy)
PreferencesDialog.setMaximumSize(QtCore.QSize(900, 600))
PreferencesDialog.setSizeGripEnabled(False)
PreferencesDialog.setModal(True)
self.gridlayout = QtWidgets.QGridLayout(PreferencesDialog)
self.gridlayout.setObjectName("gridlayout")
self.uiButtonBox = QtWidgets.QDialogButtonBox(PreferencesDialog)
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
self.uiButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply | QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
self.uiButtonBox.setCenterButtons(False)
self.uiButtonBox.setObjectName("uiButtonBox")
self.gridlayout.addWidget(self.uiButtonBox, 2, 1, 1, 2)
self.horizontalLayout = QtWidgets.QHBoxLayout(PreferencesDialog)
self.horizontalLayout.setObjectName("horizontalLayout")
self.uiLine = QtWidgets.QFrame(PreferencesDialog)
self.uiLine.setFrameShape(QtWidgets.QFrame.HLine)
self.uiLine.setFrameShadow(QtWidgets.QFrame.Sunken)
self.uiLine.setObjectName("uiLine")
self.horizontalLayout.addWidget(self.uiLine)
self.uiTreeWidget = QtWidgets.QTreeWidget(PreferencesDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
@@ -44,15 +43,10 @@ class Ui_PreferencesDialog(object):
self.uiTreeWidget.setObjectName("uiTreeWidget")
self.uiTreeWidget.headerItem().setText(0, "1")
self.uiTreeWidget.header().setVisible(False)
self.gridlayout.addWidget(self.uiTreeWidget, 0, 0, 1, 1)
self.uiLine = QtWidgets.QFrame(PreferencesDialog)
self.uiLine.setFrameShape(QtWidgets.QFrame.HLine)
self.uiLine.setFrameShadow(QtWidgets.QFrame.Sunken)
self.uiLine.setObjectName("uiLine")
self.gridlayout.addWidget(self.uiLine, 1, 0, 1, 3)
self.vboxlayout = QtWidgets.QVBoxLayout()
self.vboxlayout.setSpacing(3)
self.vboxlayout.setObjectName("vboxlayout")
self.horizontalLayout.addWidget(self.uiTreeWidget)
self.vbox = QtWidgets.QVBoxLayout()
self.vbox.setSpacing(3)
self.vbox.setObjectName("vbox")
self.uiTitleLabel = QtWidgets.QLabel(PreferencesDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(1)
@@ -67,8 +61,16 @@ class Ui_PreferencesDialog(object):
self.uiTitleLabel.setFont(font)
self.uiTitleLabel.setFrameShape(QtWidgets.QFrame.Box)
self.uiTitleLabel.setObjectName("uiTitleLabel")
self.vboxlayout.addWidget(self.uiTitleLabel)
self.uiStackedWidget = QtWidgets.QStackedWidget(PreferencesDialog)
self.vbox.addWidget(self.uiTitleLabel)
self.scrollArea = QtWidgets.QScrollArea(PreferencesDialog)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setObjectName("scrollArea")
self.scrollAreaWidgetContents_2 = QtWidgets.QWidget()
self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0, 680, 519))
self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2")
self.verticalLayout = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_2)
self.verticalLayout.setObjectName("verticalLayout")
self.uiStackedWidget = QtWidgets.QStackedWidget(self.scrollAreaWidgetContents_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(1)
sizePolicy.setVerticalStretch(1)
@@ -78,8 +80,16 @@ class Ui_PreferencesDialog(object):
self.uiPageWidget = QtWidgets.QWidget()
self.uiPageWidget.setObjectName("uiPageWidget")
self.uiStackedWidget.addWidget(self.uiPageWidget)
self.vboxlayout.addWidget(self.uiStackedWidget)
self.gridlayout.addLayout(self.vboxlayout, 0, 2, 1, 1)
self.verticalLayout.addWidget(self.uiStackedWidget)
self.scrollArea.setWidget(self.scrollAreaWidgetContents_2)
self.vbox.addWidget(self.scrollArea)
self.uiButtonBox = QtWidgets.QDialogButtonBox(PreferencesDialog)
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
self.uiButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.uiButtonBox.setCenterButtons(False)
self.uiButtonBox.setObjectName("uiButtonBox")
self.vbox.addWidget(self.uiButtonBox)
self.horizontalLayout.addLayout(self.vbox)
self.retranslateUi(PreferencesDialog)
self.uiButtonBox.accepted.connect(PreferencesDialog.accept)

View File

@@ -22,8 +22,14 @@
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTabWidget" name="uiServerPreferenceTabWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="uiLocalTabWidget">
<attribute name="title">
@@ -48,7 +54,7 @@
<string>Enable local server</string>
</property>
<property name="checked">
<bool>true</bool>
<bool>false</bool>
</property>
</widget>
</item>
@@ -64,6 +70,9 @@
<string>General settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item row="1" column="0">
<widget class="QLabel" name="uiLocalServerPathLabel">
<property name="text">
@@ -162,7 +171,7 @@
<item>
<widget class="QGroupBox" name="uiConsolePortRangeGroupBox">
<property name="title">
<string>Console port range</string>
<string>Console port range (5900 =&gt; 6000 is shared with VNC)</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
@@ -312,7 +321,7 @@
<string>Enable the local GNS3 VM</string>
</property>
<property name="checked">
<bool>true</bool>
<bool>false</bool>
</property>
</widget>
</item>
@@ -322,6 +331,9 @@
<string>Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="uiVirtualizationSoftwarLabel">
<property name="text">
@@ -437,7 +449,7 @@
<item row="1" column="1">
<widget class="QLineEdit" name="uiVMPasswordLineEdit">
<property name="inputMethodHints">
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText</set>
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData</set>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
@@ -561,6 +573,12 @@
</item>
<item row="0" column="0" colspan="2">
<widget class="QTreeWidget" name="uiRemoteServersTreeWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="columnCount">
<number>4</number>
</property>
@@ -601,7 +619,7 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentText" stdset="0">
<property name="currentText">
<string>HTTP</string>
</property>
<item>
@@ -703,7 +721,7 @@
<item row="6" column="1">
<widget class="QLineEdit" name="uiRemoteServerPasswordLineEdit">
<property name="inputMethodHints">
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText</set>
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData</set>
</property>
<property name="text">
<string/>

View File

@@ -1,17 +1,14 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/server_preferences_page.ui'
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/ui/server_preferences_page.ui'
#
# Created: Sun Nov 1 18:17:05 2015
# by: PyQt5 UI code generator 5.2.1
# Created by: PyQt5 UI code generator 5.5.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ServerPreferencesPageWidget(object):
def setupUi(self, ServerPreferencesPageWidget):
ServerPreferencesPageWidget.setObjectName("ServerPreferencesPageWidget")
ServerPreferencesPageWidget.resize(500, 609)
@@ -19,6 +16,11 @@ class Ui_ServerPreferencesPageWidget(object):
self.verticalLayout_2 = QtWidgets.QVBoxLayout(ServerPreferencesPageWidget)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.uiServerPreferenceTabWidget = QtWidgets.QTabWidget(ServerPreferencesPageWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiServerPreferenceTabWidget.sizePolicy().hasHeightForWidth())
self.uiServerPreferenceTabWidget.setSizePolicy(sizePolicy)
self.uiServerPreferenceTabWidget.setObjectName("uiServerPreferenceTabWidget")
self.uiLocalTabWidget = QtWidgets.QWidget()
self.uiLocalTabWidget.setObjectName("uiLocalTabWidget")
@@ -31,7 +33,7 @@ class Ui_ServerPreferencesPageWidget(object):
sizePolicy.setHeightForWidth(self.uiLocalServerAutoStartCheckBox.sizePolicy().hasHeightForWidth())
self.uiLocalServerAutoStartCheckBox.setSizePolicy(sizePolicy)
self.uiLocalServerAutoStartCheckBox.setMinimumSize(QtCore.QSize(0, 0))
self.uiLocalServerAutoStartCheckBox.setChecked(True)
self.uiLocalServerAutoStartCheckBox.setChecked(False)
self.uiLocalServerAutoStartCheckBox.setObjectName("uiLocalServerAutoStartCheckBox")
self.verticalLayout.addWidget(self.uiLocalServerAutoStartCheckBox)
self.uiGeneralSettingsGroupBox = QtWidgets.QGroupBox(self.uiLocalTabWidget)
@@ -42,6 +44,7 @@ class Ui_ServerPreferencesPageWidget(object):
self.uiGeneralSettingsGroupBox.setSizePolicy(sizePolicy)
self.uiGeneralSettingsGroupBox.setObjectName("uiGeneralSettingsGroupBox")
self.gridLayout = QtWidgets.QGridLayout(self.uiGeneralSettingsGroupBox)
self.gridLayout.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize)
self.gridLayout.setObjectName("gridLayout")
self.uiLocalServerPathLabel = QtWidgets.QLabel(self.uiGeneralSettingsGroupBox)
self.uiLocalServerPathLabel.setObjectName("uiLocalServerPathLabel")
@@ -137,6 +140,10 @@ class Ui_ServerPreferencesPageWidget(object):
self.verticalLayout.addWidget(self.uiUDPPortRangeGroupBox)
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem2)
self.uiGeneralSettingsGroupBox.raise_()
self.uiConsolePortRangeGroupBox.raise_()
self.uiUDPPortRangeGroupBox.raise_()
self.uiLocalServerAutoStartCheckBox.raise_()
self.uiServerPreferenceTabWidget.addTab(self.uiLocalTabWidget, "")
self.uiGNS3VMTabWidget = QtWidgets.QWidget()
self.uiGNS3VMTabWidget.setObjectName("uiGNS3VMTabWidget")
@@ -149,12 +156,13 @@ class Ui_ServerPreferencesPageWidget(object):
sizePolicy.setHeightForWidth(self.uiEnableVMCheckBox.sizePolicy().hasHeightForWidth())
self.uiEnableVMCheckBox.setSizePolicy(sizePolicy)
self.uiEnableVMCheckBox.setMinimumSize(QtCore.QSize(0, 0))
self.uiEnableVMCheckBox.setChecked(True)
self.uiEnableVMCheckBox.setChecked(False)
self.uiEnableVMCheckBox.setObjectName("uiEnableVMCheckBox")
self.verticalLayout_3.addWidget(self.uiEnableVMCheckBox)
self.uiGNS3VMSettingsGroupBox = QtWidgets.QGroupBox(self.uiGNS3VMTabWidget)
self.uiGNS3VMSettingsGroupBox.setObjectName("uiGNS3VMSettingsGroupBox")
self.gridLayout_2 = QtWidgets.QGridLayout(self.uiGNS3VMSettingsGroupBox)
self.gridLayout_2.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize)
self.gridLayout_2.setObjectName("gridLayout_2")
self.uiVirtualizationSoftwarLabel = QtWidgets.QLabel(self.uiGNS3VMSettingsGroupBox)
self.uiVirtualizationSoftwarLabel.setObjectName("uiVirtualizationSoftwarLabel")
@@ -209,7 +217,7 @@ class Ui_ServerPreferencesPageWidget(object):
self.uiVMPasswordLabel.setObjectName("uiVMPasswordLabel")
self.gridLayout_3.addWidget(self.uiVMPasswordLabel, 1, 0, 1, 1)
self.uiVMPasswordLineEdit = QtWidgets.QLineEdit(self.groupBox)
self.uiVMPasswordLineEdit.setInputMethodHints(QtCore.Qt.ImhHiddenText | QtCore.Qt.ImhNoAutoUppercase | QtCore.Qt.ImhNoPredictiveText)
self.uiVMPasswordLineEdit.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText|QtCore.Qt.ImhSensitiveData)
self.uiVMPasswordLineEdit.setEchoMode(QtWidgets.QLineEdit.Password)
self.uiVMPasswordLineEdit.setObjectName("uiVMPasswordLineEdit")
self.gridLayout_3.addWidget(self.uiVMPasswordLineEdit, 1, 1, 1, 1)
@@ -262,6 +270,11 @@ class Ui_ServerPreferencesPageWidget(object):
spacerItem6 = QtWidgets.QSpacerItem(390, 12, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_5.addItem(spacerItem6, 10, 0, 1, 2)
self.uiRemoteServersTreeWidget = QtWidgets.QTreeWidget(self.uiRemoteTabWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiRemoteServersTreeWidget.sizePolicy().hasHeightForWidth())
self.uiRemoteServersTreeWidget.setSizePolicy(sizePolicy)
self.uiRemoteServersTreeWidget.setColumnCount(4)
self.uiRemoteServersTreeWidget.setObjectName("uiRemoteServersTreeWidget")
self.uiRemoteServersTreeWidget.headerItem().setText(0, "Protocol")
@@ -321,7 +334,7 @@ class Ui_ServerPreferencesPageWidget(object):
self.uiRemoteServerSSHPortSpinBox.setObjectName("uiRemoteServerSSHPortSpinBox")
self.gridLayout_5.addWidget(self.uiRemoteServerSSHPortSpinBox, 7, 1, 1, 1)
self.uiRemoteServerPasswordLineEdit = QtWidgets.QLineEdit(self.uiRemoteTabWidget)
self.uiRemoteServerPasswordLineEdit.setInputMethodHints(QtCore.Qt.ImhHiddenText | QtCore.Qt.ImhNoAutoUppercase | QtCore.Qt.ImhNoPredictiveText)
self.uiRemoteServerPasswordLineEdit.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText|QtCore.Qt.ImhSensitiveData)
self.uiRemoteServerPasswordLineEdit.setText("")
self.uiRemoteServerPasswordLineEdit.setEchoMode(QtWidgets.QLineEdit.Password)
self.uiRemoteServerPasswordLineEdit.setObjectName("uiRemoteServerPasswordLineEdit")
@@ -329,6 +342,22 @@ class Ui_ServerPreferencesPageWidget(object):
self.uiRemoteServerPasswordLabel = QtWidgets.QLabel(self.uiRemoteTabWidget)
self.uiRemoteServerPasswordLabel.setObjectName("uiRemoteServerPasswordLabel")
self.gridLayout_5.addWidget(self.uiRemoteServerPasswordLabel, 6, 0, 1, 1)
self.uiRemoteServerProtocolComboBox.raise_()
self.uiRemoteServerHostLabel.raise_()
self.uiRemoteServerPortLineEdit.raise_()
self.uiRemoteServerPortLabel.raise_()
self.uiRemoteServerPortSpinBox.raise_()
self.uiRAMLimitLabel.raise_()
self.uiRAMLimitSpinBox.raise_()
self.uiRemoteServerUserLabel.raise_()
self.uiRemoteServerUserLineEdit.raise_()
self.uiRemoteServerSSHPortLabel.raise_()
self.uiRemoteServerSSHPortSpinBox.raise_()
self.uiRemoteServerSSHKeyLabel.raise_()
self.uiRemoteServersTreeWidget.raise_()
self.uiRemoteServerProtocolLabel.raise_()
self.uiRemoteServerPasswordLineEdit.raise_()
self.uiRemoteServerPasswordLabel.raise_()
self.uiServerPreferenceTabWidget.addTab(self.uiRemoteTabWidget, "")
self.uiLoadBalancingTabWidget = QtWidgets.QWidget()
self.uiLoadBalancingTabWidget.setObjectName("uiLoadBalancingTabWidget")
@@ -364,7 +393,7 @@ class Ui_ServerPreferencesPageWidget(object):
self.verticalLayout_2.addLayout(self.horizontalLayout_2)
self.retranslateUi(ServerPreferencesPageWidget)
self.uiServerPreferenceTabWidget.setCurrentIndex(0)
self.uiServerPreferenceTabWidget.setCurrentIndex(2)
QtCore.QMetaObject.connectSlotsByName(ServerPreferencesPageWidget)
ServerPreferencesPageWidget.setTabOrder(self.uiServerPreferenceTabWidget, self.uiLocalServerAutoStartCheckBox)
ServerPreferencesPageWidget.setTabOrder(self.uiLocalServerAutoStartCheckBox, self.uiLocalServerPathLineEdit)
@@ -418,7 +447,7 @@ class Ui_ServerPreferencesPageWidget(object):
self.uiLocalServerPortLabel.setText(_translate("ServerPreferencesPageWidget", "Port:"))
self.uiConsoleConnectionsToAnyIPCheckBox.setText(_translate("ServerPreferencesPageWidget", "Allow console connections to any local IP address"))
self.uiLocalServerAuthCheckBox.setText(_translate("ServerPreferencesPageWidget", "Protect server with password (recommended)"))
self.uiConsolePortRangeGroupBox.setTitle(_translate("ServerPreferencesPageWidget", "Console port range"))
self.uiConsolePortRangeGroupBox.setTitle(_translate("ServerPreferencesPageWidget", "Console port range (5900 => 6000 is shared with VNC)"))
self.uiConsolePortRangeLabel.setText(_translate("ServerPreferencesPageWidget", "to"))
self.uiUDPPortRangeGroupBox.setTitle(_translate("ServerPreferencesPageWidget", "UDP tunneling port range"))
self.uiUDPPortRangeLabel.setText(_translate("ServerPreferencesPageWidget", "to"))
@@ -445,7 +474,7 @@ class Ui_ServerPreferencesPageWidget(object):
self.uiDeleteRemoteServerPushButton.setText(_translate("ServerPreferencesPageWidget", "&Delete"))
self.uiRemoteServersTreeWidget.headerItem().setText(3, _translate("ServerPreferencesPageWidget", "User"))
self.uiRemoteServerProtocolLabel.setText(_translate("ServerPreferencesPageWidget", "Protocol:"))
self.uiRemoteServerProtocolComboBox.setProperty("currentText", _translate("ServerPreferencesPageWidget", "HTTP"))
self.uiRemoteServerProtocolComboBox.setCurrentText(_translate("ServerPreferencesPageWidget", "HTTP"))
self.uiRemoteServerProtocolComboBox.setItemText(0, _translate("ServerPreferencesPageWidget", "HTTP"))
self.uiRemoteServerProtocolComboBox.setItemText(1, _translate("ServerPreferencesPageWidget", "HTTPS"))
self.uiRemoteServerProtocolComboBox.setItemText(2, _translate("ServerPreferencesPageWidget", "SSH"))
@@ -463,3 +492,4 @@ class Ui_ServerPreferencesPageWidget(object):
self.uiRendezVousHashingRadioButton.setText(_translate("ServerPreferencesPageWidget", "Rendezvous hashing"))
self.uiServerPreferenceTabWidget.setTabText(self.uiServerPreferenceTabWidget.indexOf(self.uiLoadBalancingTabWidget), _translate("ServerPreferencesPageWidget", "Load Balancing"))
self.uiRestoreDefaultsPushButton.setText(_translate("ServerPreferencesPageWidget", "Restore defaults"))

View File

@@ -99,7 +99,7 @@ class UpdateManager(QtCore.QObject):
network_reply = self.sender()
if network_reply.error() != QtNetwork.QNetworkReply.NoError:
if not self._silent:
QtWidgets.QMessageBox.critical(self, "Check For Update", "Cannot check for update: {}".format(network_reply.errorString()))
QtWidgets.QMessageBox.critical(self._parent, "Check For Update", "Cannot check for update: {}".format(network_reply.errorString()))
return
try:
latest_release = bytes(network_reply.readAll()).decode("utf-8").rstrip()

View File

@@ -50,21 +50,18 @@ class ProgressDialog(QtWidgets.QProgressDialog):
super().__init__(label_text, cancel_button_text, minimum, maximum, parent)
self._thread = QtCore.QThread(self)
self.setModal(True)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
self._errors = []
self.setWindowTitle(title)
self._worker = worker
self.canceled.connect(self._worker.cancel)
self.canceled.connect(self._canceledSlot)
self.finished.connect(self.close)
self.destroyed.connect(self._cleanup)
# self.canceled.connect(self._cancel)
# create the thread and set the self._worker
self._thread = QtCore.QThread(self)
self._worker.moveToThread(self._thread)
# connect self._worker signals
self._worker.updated.connect(self._updateProgress)
self._worker.error.connect(self._error)
self._worker.finished.connect(self._cleanup)
@@ -75,6 +72,10 @@ class ProgressDialog(QtWidgets.QProgressDialog):
self._thread.started.connect(self._worker.run)
self._thread.start()
def _canceledSlot(self):
self._worker.cancel()
self._cleanup()
def __del__(self):
self._cleanup()
@@ -102,7 +103,7 @@ class ProgressDialog(QtWidgets.QProgressDialog):
:param value: value for the progress bar (integer)
"""
if self._thread is not None:
if self is not None and self._thread is not None:
# It seem in some cases this is called on a deleted object and crash
if not sip.isdeleted(self):
self.setValue(value)

View File

@@ -20,7 +20,6 @@ Thread to wait for the GNS3 VM.
"""
import os
import time
import socket
import subprocess
@@ -144,8 +143,7 @@ class WaitForVMWorker(QtCore.QObject):
return True
return False
@staticmethod
def _waitForServer(vm_server, endpoint, retry=0):
def _waitForServer(self, vm_server, endpoint, retry=0):
"""
Wait for a VM server to reply to a request.
@@ -159,7 +157,7 @@ class WaitForVMWorker(QtCore.QObject):
status, json_data = vm_server.getSynchronous(endpoint, timeout=1)
if status != 0:
break
time.sleep(1)
self.thread().sleep(1)
retry -= 1
return status, json_data

View File

@@ -25,5 +25,5 @@ or negative for a release candidate or beta (after the base version
number has been incremented)
"""
__version__ = "1.4.0dev11"
__version_info__ = (1, 4, 0, 11)
__version__ = "1.4.0"
__version_info__ = (1, 4, 0, 0)

View File

@@ -19,6 +19,9 @@
Base class for VM classes.
"""
import os
from gns3.servers import Servers
from .node import Node
import logging
@@ -261,6 +264,15 @@ class VM(Node):
:returns: config content
"""
if config_path is None or len(config_path.strip()) == 0:
return None
if not os.path.isabs(config_path):
config_path = os.path.join(Servers.instance().localServerSettings()["configs_path"], config_path)
if not os.path.isfile(config_path):
return None
try:
with open(config_path, "rb") as f:
log.info("Opening configuration file: {}".format(config_path))

View File

@@ -2,4 +2,4 @@ jsonschema>=2.4.0
paramiko>=1.15.1
raven>=5.2.0
psutil>=2.2.1
gns3-converter>=1.2.3
gns3-converter>=1.2.4

View File

@@ -51,7 +51,7 @@ setup(
install_requires=[
"jsonschema>=2.4.0",
"paramiko>=1.15.1",
"gns3-converter>=1.2.3",
"gns3-converter>=1.2.4",
"raven>=5.2.0",
"psutil>=2.2.1",
],

View File

@@ -200,6 +200,8 @@ def main_window():
@pytest.fixture
def images_dir(tmpdir):
os.makedirs(os.path.join(str(tmpdir), "images", "QEMU"), exist_ok=True)
os.makedirs(os.path.join(str(tmpdir), "images", "IOS"), exist_ok=True)
os.makedirs(os.path.join(str(tmpdir), "images", "IOU"), exist_ok=True)
return os.path.join(str(tmpdir), "images")
@@ -221,6 +223,30 @@ def linux_microcore_img(images_dir):
return path
@pytest.fixture
def iou_l3(images_dir):
"""
Create a fake image and return the path. The md5sum of the file will be 5d41402abc4b2a76b9719d911017c592
"""
path = os.path.join(images_dir, "IOU", "i86bi-linux-l3-adventerprisek9-15.4.1T.bin")
with open(path, 'w+') as f:
f.write("hello")
return path
@pytest.fixture
def cisco_3745(images_dir):
"""
Create a fake image and return the path. The md5sum of the file will be 5d41402abc4b2a76b9719d911017c592
"""
path = os.path.join(images_dir, "IOS", "c3745-adventerprisek9-mz.124-25d.image")
with open(path, 'w+') as f:
f.write("hello")
return path
def pytest_configure(config):
"""
Use to detect in code if we are running from pytest

View File

@@ -0,0 +1,47 @@
{
"category": "router",
"status": "experimental",
"maintainer": "GNS3 Team",
"name": "Cisco 3745",
"vendor_name": "Cisco",
"product_name": "3745",
"vendor_url": "http://www.cisco.com",
"description": "Cisco 3745 Multiservice Access Router",
"registry_version": 2,
"maintainer_email": "developers@gns3.net",
"documentation_url": "http://www.cisco.com/c/en/us/support/routers/3745-multiservice-access-router/model.html",
"dynamips": {
"chassis": "",
"platform": "c3745",
"ram": 256,
"nvram": 256,
"startup_config": "ios_base_startup-config.txt",
"slot0": "GT96100-FE",
"slot1": "NM-1FE-TX",
"slot2": "NM-4T",
"slot3": "",
"slot4": "",
"wic0": "WIC-1T",
"wic1": "WIC-1T",
"wic2": "WIC-1T"
},
"versions": [
{
"images": {
"image": "c3745-adventerprisek9-mz.124-25d.image"
},
"idlepc": "0x60aa1da0",
"name": "124-25d"
}
],
"images": [
{
"filesize": 82053028,
"md5sum": "ddbaf74274822b50fa9670e10c75b08f",
"version": "124-25d",
"filename": "c3745-adventerprisek9-mz.124-25d.image"
}
]
}

View File

@@ -0,0 +1,37 @@
{
"category": "router",
"status": "experimental",
"maintainer": "GNS3 Team",
"name": "Cisco IOU L3",
"vendor_name": "Cisco",
"product_name": "Cisco IOU L3",
"vendor_url": "http://www.cisco.com",
"description": "Cisco IOS on UNIX Layer 3 image.",
"registry_version": 2,
"maintainer_email": "developers@gns3.net",
"iou": {
"ethernet_adapters": 2,
"serial_adapters": 2,
"nvram": 128,
"ram": 256,
"startup_config": "iou_l3_base_startup-config.txt"
},
"versions": [
{
"images": {
"image": "i86bi-linux-l3-adventerprisek9-15.4.1T.bin"
},
"name": "15.4.1T"
}
],
"images": [
{
"filesize": 152677848,
"md5sum": "5d41402abc4b2a76b9719d911017c592",
"version": "15.4.1T",
"filename": "i86bi-linux-l3-adventerprisek9-15.4.1T.bin"
}
]
}

View File

@@ -2,39 +2,40 @@
"name": "Micro Core Linux",
"category": "guest",
"description": "Micro Core Linux® i is a smaller variant of Tiny Core without a graphical desktop.\n\nIt's provide a complete Linux system in few MB.",
"vendor_name": "Team Tiny Core",
"vendor_url": "http://distro.ibiblio.org/tinycorelinux",
"documentation_url": "http://wiki.tinycorelinux.net/",
"vendor_name": "Team Tiny Core",
"vendor_url": "http://distro.ibiblio.org/tinycorelinux",
"documentation_url": "http://wiki.tinycorelinux.net/",
"product_name": "Micro Core Linux",
"product_url": "http://distro.ibiblio.org/tinycorelinux",
"registry_version": 1,
"product_url": "http://distro.ibiblio.org/tinycorelinux",
"registry_version": 1,
"status": "stable",
"maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net",
"usage": "Just start the appliance",
"qemu": {
"adapter_type": "e1000",
"adapters": 1,
"ram": 32,
"arch": "i386",
"console_type": "telnet"
"console_type": "telnet"
},
"images": [
"images": [
{
"filename": "linux-microcore-3.4.1.img",
"version": "3.4.1",
"md5sum": "5d41402abc4b2a76b9719d911017c592",
"filesize": 5,
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-3.4.1.img"
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-3.4.1.img"
},
{
"filename": "linux-microcore-4.0.2-clean.img",
"version": "4.0.2",
"md5sum": "e13d0d1c0b3999ae2386bba70417930c",
"filesize": 26411008,
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
"filesize": 26411008,
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-4.0.2-clean.img"
}
],

View File

@@ -56,10 +56,9 @@ def test_check_config(tmpdir, registry):
with pytest.raises(ApplianceError):
Appliance(registry, test_path)
with open(test_path, "w+", encoding="utf-8") as f:
f.write('{"registry_version": 2}')
with pytest.raises(ApplianceError):
with open(test_path, "w+", encoding="utf-8") as f:
f.write('{"registry_version": 42}')
Appliance(registry, test_path)
Appliance(registry, "tests/registry/appliances/microcore-linux.json")
@@ -76,6 +75,18 @@ def test_resolve_version(tmpdir):
assert new_config["versions"][0]["images"] == {"hda_disk_image": hda}
def test_resolve_version_dynamips(tmpdir):
with open("tests/registry/appliances/cisco-3745.gns3a", encoding="utf-8") as f:
config = json.load(f)
hda = config["images"][0]
hda["idlepc"] = "0x60aa1da0"
new_config = Appliance(registry, "tests/registry/appliances/cisco-3745.gns3a")
assert new_config["versions"][0]["images"] == {"image": hda}
def test_resolve_version_invalid_file(tmpdir):
with pytest.raises(ApplianceError):
@@ -121,3 +132,10 @@ def test_is_version_installable(linux_microcore_img, microcore_appliance):
assert microcore_appliance.is_version_installable("3.4.1")
assert not microcore_appliance.is_version_installable("4.0.2")
def test_image_dir_name(microcore_appliance):
assert Appliance(registry, "tests/registry/appliances/microcore-linux.json").image_dir_name() == "QEMU"
assert Appliance(registry, "tests/registry/appliances/cisco-iou-l3.gns3a").image_dir_name() == "IOU"

View File

@@ -51,16 +51,12 @@ def empty_config(tmpdir, images_dir, symbols_dir):
"ghost_ios_support": True,
"mmap_support": True,
"routers": [
{
}
],
"sparse_memory_support": True,
"use_local_server": True
},
"IOU": {
"appliances": [
{
}
"devices": [
],
"iourc_path": "/Users/noplay/code/gns3/gns3-vagrant/images/iou/iourc.txt",
"iouyap_path": "",
@@ -115,6 +111,82 @@ def test_list_servers_remote_servers(tmpdir):
assert config.servers == ["local", "http://darkside.moon:4242"]
def test_add_appliance_iou(empty_config, iou_l3):
with open("tests/registry/appliances/cisco-iou-l3.gns3a", encoding="utf-8") as f:
config = json.load(f)
config["images"] = [
{
"type": "image",
"filename": "i86bi-linux-l3-adventerprisek9-15.4.1T.bin",
"path": iou_l3
}
]
empty_config.add_appliance(config, "local")
assert empty_config._config["IOU"]["devices"][0] == {
"category": 0,
"symbol": ":/symbols/router.svg",
"server": "local",
"name": "Cisco IOU L3",
"l1_keepalives": False,
"nvram": 128,
"private_config": "",
"ram": 256,
"serial_adapters": 2,
"ethernet_adapters": 2,
"use_default_iou_values": True,
"startup_config": "iou_l3_base_startup-config.txt",
"image": os.path.basename(iou_l3),
"path": os.path.basename(iou_l3)
}
def test_add_appliance_dynamips(empty_config, cisco_3745):
with open("tests/registry/appliances/cisco-3745.gns3a", encoding="utf-8") as f:
config = json.load(f)
config["images"] = [
{
"type": "image",
"filename": "c3745-adventerprisek9-mz.124-25d.image",
"path": cisco_3745,
"idlepc": "0x60aa1da0"
}
]
empty_config.add_appliance(config, "local")
assert empty_config._config["Dynamips"]["routers"][0] == {
"auto_delete_disks": True,
"category": 0,
"chassis": "",
"disk0": 0,
"disk1": 0,
"exec_area": 64,
"idlemax": 500,
"idlepc": "0x60aa1da0",
"idlesleep": 30,
"image": "c3745-adventerprisek9-mz.124-25d.image",
"iomem": 5,
"mac_addr": "",
"mmap": True,
"name": "Cisco 3745",
"nvram": 256,
"platform": "c3745",
"private_config": "",
"ram": 256,
"server": "local",
"slot0": "GT96100-FE",
"slot1": "NM-1FE-TX",
"slot2": "NM-4T",
"slot3": "",
"slot4": "",
"sparsemem": True,
"startup_config": "ios_base_startup-config.txt",
"symbol": ":/symbols/router.svg",
"system_id": "FTX0945W0MY",
"wic0": "WIC-1T",
"wic1": "WIC-1T",
"wic2": "WIC-1T"
}
def test_add_appliance_guest(empty_config, linux_microcore_img):
with open("tests/registry/appliances/microcore-linux.json", encoding="utf-8") as f:
config = json.load(f)
@@ -146,8 +218,13 @@ def test_add_appliance_guest(empty_config, linux_microcore_img):
"options": "-nographic",
"process_priority": "normal",
"qemu_path": "qemu-system-i386",
"usage": "Just start the appliance",
"ram": 32,
"server": "local"
"server": "local",
"hda_disk_interface": "ide",
"hdb_disk_interface": "ide",
"hdc_disk_interface": "ide",
"hdd_disk_interface": "ide"
}
@@ -296,7 +373,11 @@ def test_add_appliance_router_two_disk(empty_config, images_dir):
"qemu_path": "qemu-system-x86_64",
"ram": 2048,
"console_type": "telnet",
"server": "local"
"server": "local",
"hda_disk_interface": "ide",
"hdb_disk_interface": "ide",
"hdc_disk_interface": "ide",
"hdd_disk_interface": "ide"
}
@@ -354,7 +435,7 @@ def test_add_appliance_ova(empty_config, tmpdir, images_dir):
]
empty_config.add_appliance(config, "local")
assert empty_config._config["Qemu"]["vms"][0]["hda_disk_image"] == "junos-vsrx-12.1X47-D10.4-domestic.ova/junos-vsrx-12.1X47-D10.4-domestic-disk1.vmdk"
assert empty_config._config["Qemu"]["vms"][0]["hda_disk_image"] == os.path.join("junos-vsrx-12.1X47-D10.4-domestic.ova", "junos-vsrx-12.1X47-D10.4-domestic-disk1.vmdk")
def test_add_appliance_path_non_relative_to_images_dir(empty_config, tmpdir, images_dir):
@@ -394,43 +475,62 @@ def test_save(empty_config, linux_microcore_img):
assert "Micro Core" in f.read()
def test_is_name_available(empty_config, linux_microcore_img):
with open("tests/registry/appliances/microcore-linux.json", encoding="utf-8") as f:
config = json.load(f)
config["images"] = [
{
"type": "hda_disk_image",
"filename": "linux-microcore-3.4.1.img",
"path": linux_microcore_img
}
]
assert empty_config.is_name_available(config["name"]) is True
empty_config.add_appliance(config, "local")
empty_config.save()
assert empty_config.is_name_available(config["name"]) is False
def test_relative_image_path(empty_config, images_dir, tmpdir):
# Image in image directory no need to copy it
open(os.path.join(images_dir, "QEMU", "a"), "w+").close()
with patch("gns3.registry.image.Image.copy") as mock:
assert empty_config._relative_image_path("a", os.path.join(images_dir, "QEMU", "a")) == "a"
assert empty_config._relative_image_path("QEMU", "a", os.path.join(images_dir, "QEMU", "a")) == "a"
assert not mock.called
# Image in image directory no need to copy it but with a different file name
open(os.path.join(images_dir, "QEMU", "a"), "w+").close()
with patch("gns3.registry.image.Image.copy") as mock:
assert empty_config._relative_image_path("h", os.path.join(images_dir, "QEMU", "a")) == "a"
assert empty_config._relative_image_path("QEMU", "h", os.path.join(images_dir, "QEMU", "a")) == "a"
assert not mock.called
# Image outside image directory we need to copy it
open(str(tmpdir / "b"), "w+").close()
with patch("gns3.registry.image.Image.copy") as mock:
assert empty_config._relative_image_path("b", str(tmpdir / "b")) == "b"
assert empty_config._relative_image_path("QEMU", "b", str(tmpdir / "b")) == "b"
assert mock.called
# OVA in images directory no need to copy
os.makedirs(os.path.join(images_dir, "QEMU", "c.ova"))
open(os.path.join(images_dir, "QEMU", "c.ova", "c.vmdk"), "w+").close()
with patch("gns3.registry.image.Image.copy") as mock:
assert empty_config._relative_image_path("c.ova/c.vmdk", os.path.join(images_dir, "QEMU", "c.ova", "c.vmdk")) == "c.ova/c.vmdk"
assert empty_config._relative_image_path("QEMU", "c.ova/c.vmdk", os.path.join(images_dir, "QEMU", "c.ova", "c.vmdk")) == os.path.join("c.ova", "c.vmdk")
assert not mock.called
# OVA outside images directory need to copy
os.makedirs(os.path.join(str(tmpdir), "QEMU", "d.ova"))
open(os.path.join(str(tmpdir), "QEMU", "d.ova", "d.vmdk"), "w+").close()
with patch("gns3.registry.image.Image.copy") as mock:
assert empty_config._relative_image_path("d.ova/d.vmdk", os.path.join(str(tmpdir), "QEMU", "d.ova", "d.vmdk")) == "d.ova/d.vmdk"
assert empty_config._relative_image_path("QEMU", "d.ova/d.vmdk", os.path.join(str(tmpdir), "QEMU", "d.ova", "d.vmdk")) == "d.ova/d.vmdk"
assert mock.called
# OVA in images directory no need to copy but with a different file name
os.makedirs(os.path.join(images_dir, "QEMU", "e.ova"))
open(os.path.join(images_dir, "QEMU", "e.ova", "c.vmdk"), "w+").close()
with patch("gns3.registry.image.Image.copy") as mock:
assert empty_config._relative_image_path("x.ova/c.vmdk", os.path.join(images_dir, "QEMU", "e.ova", "c.vmdk")) == "e.ova/c.vmdk"
assert empty_config._relative_image_path("QEMU", "x.ova/c.vmdk", os.path.join(images_dir, "QEMU", "e.ova", "c.vmdk")) == os.path.join("e.ova", "c.vmdk")
assert not mock.called

View File

@@ -99,7 +99,7 @@ def test_addMissingImageOVAWithMultipleVMDK(image_manager, remote_server, images
assert args[2] == 'QEMU'
args, kwargs = mock.call_args_list[1]
assert args[0] == str(images_dir / 'QEMU' / 'test.ova' / 'test.vmdk')
assert os.path.normpath(args[0]) == str(images_dir / 'QEMU' / 'test.ova' / 'test.vmdk')
assert args[1] == remote_server
assert args[2] == 'QEMU'

View File

@@ -19,7 +19,7 @@ import pytest
import sys
import os
import json
from unittest.mock import patch
from unittest.mock import patch, MagicMock
from gns3.local_config import LocalConfig
@@ -228,3 +228,45 @@ def test_migrate13ConfigOldOsxServerPath(tmpdir):
# The old config should not be erased in order to avoid losing data when rollback to 1.3
assert local_config._settings["LocalServer"]["path"] == "/Applications/GNS3.app/Contents/Resources/server/Contents/MacOS/gns3server"
assert local_config._settings["Servers"]["local_server"]["path"] == "/Applications/GNS3.app/Contents/MacOS/gns3server"
def test_isMainGui_pid_file_not_exist(tmpdir):
with patch("gns3.local_config.LocalConfig.configDirectory") as mock_config_directory:
mock_config_directory.return_value = str(tmpdir)
assert LocalConfig().isMainGui() is True
assert os.path.exists(str(tmpdir / "gns3_gui.pid"))
def test_isMainGui_pid_file_exist_but_different(tmpdir):
with open(str(tmpdir / "gns3_gui.pid"), "w+") as f:
f.write("42")
mock_process = MagicMock()
mock_process.name.return_value = "gns3.exe"
mock_process.uids.return_value = (os.getuid(), os.getuid(), os.getuid())
with patch("psutil.Process", return_value=mock_process) as mock:
with patch("gns3.local_config.LocalConfig.configDirectory") as mock_config_directory:
mock_config_directory.return_value = str(tmpdir)
assert LocalConfig().isMainGui() is False
def test_isMainGui_pid_file_exist_but_different_proces_dead(tmpdir):
with open(str(tmpdir / "gns3_gui.pid"), "w+") as f:
f.write("42")
mock_process = MagicMock()
with patch("psutil.Process", return_value=mock_process) as mock:
mock.name.side_effect = (lambda: exec('raise(OSError())'))
with patch("gns3.local_config.LocalConfig.configDirectory") as mock_config_directory:
mock_config_directory.return_value = str(tmpdir)
assert LocalConfig().isMainGui() is True
def test_isMainGui_pid_file_exist_but_same_pid(tmpdir):
with open(str(tmpdir / "gns3_gui.pid"), "w+") as f:
f.write("42")
with patch("os.getpid", return_value=42):
with patch("gns3.local_config.LocalConfig.configDirectory") as mock_config_directory:
mock_config_directory.return_value = str(tmpdir)
assert LocalConfig().isMainGui() is True

View File

@@ -15,6 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import json
import pytest
import binascii
@@ -208,3 +209,50 @@ def test_handle_handleSslErrors():
'user': 'root'
}
]
@pytest.mark.skipif(sys.platform.startswith('win') is True, reason='Not for windows')
def test_startLocalServer(tmpdir, local_config):
local_server_path = str(tmpdir / "gns3server")
open(local_server_path, "w+").close()
with open(str(tmpdir / "test.cfg"), "w+") as f:
json.dump({
"Servers": {
"local_server": {
"path": local_server_path,
}
},
"version": "1.4"
}, f)
local_config.setConfigFilePath(str(tmpdir / "test.cfg"))
Servers._instance = None
with patch("gns3.local_config.LocalConfig.configDirectory") as mock_local_config:
mock_local_config.return_value = str(tmpdir)
with patch("subprocess.Popen") as mock:
Servers.instance().startLocalServer()
mock.assert_called_with([local_server_path,
'--host=127.0.0.1',
'--port=8000',
'--local',
'--debug',
'--log=' + str(tmpdir / "gns3_server.log"),
'--pid=' + str(tmpdir / "gns3_server.pid")
])
def test_killAlreadyRunningServer(tmpdir):
with patch("gns3.local_config.LocalConfig.configDirectory") as mock_local_config:
mock_local_config.return_value = str(tmpdir)
with open(str(tmpdir / "gns3_server.pid"), "w+") as f:
f.write("42")
mock_process = MagicMock()
with patch("psutil.Process", return_value=mock_process) as mock:
Servers.instance()._killAlreadyRunningServer()
mock.assert_called_with(pid=42)
assert mock_process.kill.called

View File

@@ -92,3 +92,18 @@ def test_deleteNIO(vpcs_device):
args, kwargs = mock_delete.call_args
assert args[0] == "/vpcs/vms/{vm_id}/adapters/0/ports/0/nio".format(vm_id=vpcs_device.vm_id())
def test_readBaseConfig(vpcs_device, tmpdir):
assert vpcs_device._readBaseConfig("") is None
with open(str(tmpdir / "test.cfg"), "w+") as f:
f.write("42")
assert vpcs_device._readBaseConfig(str(tmpdir / "test.cfg")) == "42"
def test_readBaseConfigRelative(vpcs_device, tmpdir):
with open(str(tmpdir / "test.cfg"), "w+") as f:
f.write("42")
with patch('gns3.servers.Servers.localServerSettings', return_value={'configs_path': str(tmpdir)}):
assert vpcs_device._readBaseConfig(str("test.cfg")) == "42"