Compare commits

...

662 Commits
v1.2 ... v1.3.3

Author SHA1 Message Date
Julien Duponchelle
2b86c24175 Version 1.3.3 2015-05-14 19:00:01 +02:00
grossmj
0ba01ddcdd New inline help text for the idle-pc dialog. 2015-05-14 10:13:05 -06:00
Jeremy
abc6ae2dca Reactivate auto idle-pc in device contextual menu + save a chosen idle-pc value in template. 2015-05-13 17:58:24 -06:00
Jeremy
e3d47e1e5d Adds name to the thank you section. 2015-05-13 17:19:51 -06:00
Jeremy
17dae79660 Prevent users to use VirtualBox linked clone VMs in temporary projects (for now). 2015-05-13 16:53:03 -06:00
Jeremy
d6336710b8 Merge remote-tracking branch 'origin/master' 2015-05-13 16:04:01 -06:00
Jeremy
ed25a4191d Fixes #337. 2015-05-13 16:03:56 -06:00
Julien Duponchelle
5723097cbe Fix crash 2015-05-13 23:27:09 +02:00
Julien Duponchelle
38b600520f Capture error if the command is invalid
Fix #358
2015-05-13 17:23:17 +02:00
Julien Duponchelle
0fae016d15 Cleanup egg cache when exit 2015-05-13 14:33:59 +02:00
Julien Duponchelle
74b4013002 Fix a crash in console when you used non UTF-8 terminal
Fix #351
2015-05-11 14:34:18 +02:00
Julien Duponchelle
eede8bdc2f Fix crash during save as
Fix #350
2015-05-11 14:28:20 +02:00
grossmj
7db0de7ccc Change title when exporting an IOS startup-config. 2015-05-10 20:39:10 -06:00
grossmj
364cde1287 Updates bug reporting link. 2015-05-09 17:38:19 -06:00
grossmj
c532d74f8b Removes analytics client on closing. 2015-05-08 18:35:18 -06:00
Jeremy
2303c54ac5 Bump version to 1.3.3.dev3 2015-05-07 11:52:17 -06:00
Julien Duponchelle
6762ce347d Release 1.3.3rc1 2015-05-07 16:00:32 +02:00
Julien Duponchelle
2e884339a8 Another fix for the link creation issue on OSX
https://community.gns3.com/message/26786
2015-05-07 15:43:13 +02:00
Julien Duponchelle
3bdb18a2b4 Another broken pipe error catched for OSX
Fix https://github.com/GNS3/gns3-server/issues/166
2015-05-07 15:35:15 +02:00
Julien Duponchelle
aaf2b7e206 Prevent a topology made for next version to be open in previous version
Fix #345
2015-05-07 10:50:40 +02:00
Julien Duponchelle
ef7a711f07 Check if the local server is really a local server
Fix #344
2015-05-07 10:33:26 +02:00
grossmj
dc95bad4aa Merge remote-tracking branch 'origin/master' 2015-05-06 14:59:35 -06:00
grossmj
327d0277fc NIO NAT support for QEMU VMs (user mode back-end is used). 2015-05-06 14:59:01 -06:00
Julien Duponchelle
72add39182 Fix PyQT4 download link 2015-05-06 13:40:00 +02:00
Vasil Rangelov
509a946c92 Modified version requirements, so that they require the dependency versions as minimums.
Added some more detailed instructions for compilation on Windows.

Conflicts:
	requirements.txt
2015-05-06 13:38:41 +02:00
Julien Duponchelle
b10946f0e8 PEP8 2015-05-06 13:21:17 +02:00
Julien Duponchelle
6bcb1ab113 Prevent user to enter a None port
I don't know how they force the None value in the input field...

Fix #334, #333
2015-05-06 11:06:50 +02:00
Julien Duponchelle
9dc22142ef Change crash report key
Fix #336
2015-05-06 10:58:43 +02:00
Julien Duponchelle
e23818b3b4 Typo 2015-05-06 10:52:29 +02:00
Julien Duponchelle
0eef72b6e3 Fix broken pipe error on OSX when frozen
Fix https://github.com/GNS3/gns3-server/issues/166
2015-05-06 10:47:03 +02:00
grossmj
00995e3a6a Merge remote-tracking branch 'origin/master' 2015-05-05 14:49:13 -06:00
grossmj
089dad31c0 Bump version to 1.3.3dev2 2015-05-05 14:49:03 -06:00
Jeremy Grossmann
b79d36e0eb Merge pull request #332 from GNS3/prevent_double_link
Prevent the same link to be created twice.
2015-05-05 14:37:34 -06:00
Julien Duponchelle
07a78b55cb Prevent the same link created twice 2015-05-05 22:27:34 +02:00
grossmj
7aa6f8fa9d Merge remote-tracking branch 'origin/master' 2015-05-05 12:29:05 -06:00
grossmj
47eaa1ac96 Project loading: names and IDs must be assigned to ports on the client side after nodes have been created. Fixes #326.
Fixes config updating for IOS router and IOU devices.
2015-05-05 12:28:56 -06:00
Julien Duponchelle
0866632b2f Fix a crash when dropping a .gns3
Fix #328
2015-05-05 19:58:20 +02:00
Julien Duponchelle
31b341cb68 Cleanup VPCS code
Fix #324
2015-05-05 14:16:07 +02:00
Julien Duponchelle
7c62945560 Turn off config parser interpolation
Fix #322
2015-05-05 11:50:05 +02:00
Julien Duponchelle
59fb3c1dbe Support unicode characters in regex 2015-05-05 10:21:05 +02:00
grossmj
ea86e2990b Fixes duplicate entries for "Recent files" on Windows. Fixes #316. 2015-05-04 22:59:46 -06:00
grossmj
b2e0ec4130 Fixes VPCS multi-host. Fixes #318. 2015-05-04 22:20:03 -06:00
grossmj
de8a73efb7 Fixes issues when importing configs for IOS, IOU and VPCS. Fixes #314. 2015-05-04 21:42:50 -06:00
grossmj
df95c2c85d Fixes issue when console setting present in IOS router templates. 2015-05-04 20:32:00 -06:00
Jeremy
df322ceacf Do not send empty settings when creating VMs. 2015-05-04 19:03:35 -06:00
Jeremy
78df3fc12c Do not set a default private-config when creating a new IOS router template. Fixes #317. 2015-05-04 18:47:56 -06:00
Jeremy
b1a1824522 Refactors how startup-config and private-config are handled for IOS routers. 2015-05-04 18:42:32 -06:00
Jeremy
ffab68e809 Fixes IOU and QEMU tests. 2015-05-04 16:07:10 -06:00
Jeremy
a4a242d58b Makes sure all IOS router settings are saved in the project file & simplify loading from a project. 2015-05-04 14:09:09 -06:00
Jeremy
b488764b79 Makes sure all VirtualBox VM settings are saved in the project file & simplify loading from a project. 2015-05-04 12:44:50 -06:00
Jeremy
38848f43e4 Makes sure all VPCS VM settings are saved in the project file & simplify loading from a project. 2015-05-04 12:05:35 -06:00
grossmj
9ffea5cadb Merge remote-tracking branch 'origin/master' 2015-05-04 11:20:08 -06:00
grossmj
9e05675823 Makes sure all IOU VM settings are saved in the project file & simplify loading from a project. 2015-05-04 11:20:08 -06:00
grossmj
3b4efc21a6 Makes sure all QEMU VM settings are saved in the project file & simplify loading from a project. 2015-05-04 11:19:30 -06:00
Julien Duponchelle
beaf31ae05 Fix save as by correctly renaming VM uuid project directory
Fix https://github.com/GNS3/gns3-server/issues/173
2015-05-04 17:23:29 +02:00
Julien Duponchelle
631e0b0741 Fix save as duplicate the .gns3 file
Fix #247
2015-05-04 13:29:14 +02:00
Julien Duponchelle
c11db9f0af Another assert topology fixe
If you can not create the initial project directory the GUI
the project is in an dirty state.

Fix #319
2015-05-04 11:47:33 +02:00
Julien Duponchelle
9ce91345a7 Drop debug 2015-05-04 11:19:14 +02:00
Julien Duponchelle
a6653a3419 Prevent user to enter bad hostname
Fix #311
2015-05-04 11:16:34 +02:00
grossmj
8483b0d08e Fixes an issue when the IOU VM template has a console setting. 2015-05-03 18:10:17 -06:00
grossmj
b46be2445a Releasing adding a link. Fixes #235. 2015-05-03 14:03:20 -06:00
grossmj
d45328576f THis should fix RuntimeError: wrapped C/C++ object of type QNetworkReply has been deleted. 2015-05-03 11:55:56 -06:00
Julien Duponchelle
a31cc33c67 Do not crash if terminal doesn't support UTF-8
Fix #306
2015-05-02 15:41:18 +02:00
Julien Duponchelle
b874147b9c Fix windows build
Fix #304
2015-05-02 14:23:19 +02:00
Julien Duponchelle
09046ab89e Fix test suite 2015-04-30 16:59:14 +02:00
grossmj
62bff009c1 Fixes "show only devices with captures" in the topology summary. 2015-04-28 19:30:44 -06:00
Julien Duponchelle
0c3ea4b05d 1.3.3dev1 2015-04-28 21:48:53 +02:00
Julien Duponchelle
9dde2fbcf8 Version 1.3.2 2015-04-28 21:09:33 +02:00
grossmj
740041a844 Fixes bug when IOS configs are not in VM settings. 2015-04-28 12:49:01 -06:00
grossmj
31bcb8c82d Fixes small issue with Qemu VM monitor. 2015-04-28 12:12:50 -06:00
grossmj
425ef9f059 Fixes #297. 2015-04-28 10:56:18 -06:00
grossmj
e059ecc6c4 Fixes issue when only one port is added after a QEMU VM is created. Fixes #296. 2015-04-28 09:09:35 -06:00
grossmj
425a8c864b Avoid Cygwin warning with VPCS on Windows. 2015-04-27 22:23:27 -06:00
grossmj
56c2b1d9b5 Merge remote-tracking branch 'origin/master' 2015-04-27 21:18:08 -06:00
grossmj
ab74d19584 Fixes issues with QThread handling. 2015-04-27 21:17:56 -06:00
Jeremy
3e197b72a8 Fixes missing title + icon in layer position warning message box. 2015-04-27 19:11:37 -06:00
Jeremy
863e1a2e20 Allows the warning message box to be displayed once only when moving an object to a background layer. 2015-04-27 19:05:21 -06:00
Jeremy
a00c8e0a53 Fixes small issue with old monitor setting. 2015-04-27 18:58:42 -06:00
Jeremy
3b98206942 Check the config path is set when creating a IOU or IOS router. 2015-04-27 18:53:07 -06:00
Jeremy Grossmann
f6cee7297a Merge pull request #292 from GNS3/prevent_empty_file
Prevents writing empty topologies.
2015-04-27 16:33:29 -06:00
Jeremy
6792e3d701 Removes residual link when a NIO cannot be created on the server. Fixes #294. 2015-04-27 15:20:37 -06:00
Jeremy
df1e5aef9b Fixes #294. 2015-04-27 14:46:37 -06:00
Julien Duponchelle
6c3d2926b7 Fix VPCS tests 2015-04-27 21:00:52 +02:00
Julien Duponchelle
302fcfc7d7 Do not crash if an antivirus intercept a message and send non UTF-8
Fix #295
2015-04-27 17:34:39 +02:00
grossmj
42dd9969e4 Avoid C++ runtime error when progress dialog is finished. 2015-04-27 09:30:26 -06:00
grossmj
6a69ce26e3 Merge remote-tracking branch 'origin/master' 2015-04-27 09:17:57 -06:00
grossmj
8d6db96a4c Move FileCopyThread to FileCopyWorker. 2015-04-27 09:17:44 -06:00
Julien Duponchelle
e61a6bae7e If project loading fail fallback to real temporary project
It should avoid the issue with assert in topologyFile
2015-04-27 16:54:12 +02:00
Julien Duponchelle
37989854a7 Do not crash if rotation is a string
Some users have this error. Not sure if it's a bug
or a tutorial which explain how to modify .gns3 files.

Fix #293
2015-04-27 16:37:26 +02:00
Julien Duponchelle
c70f8441e2 I think it's prevent empty topologies
Fix #290
2015-04-27 11:15:27 +02:00
grossmj
f9073055e9 Explicit utf-8 decoding. 2015-04-26 21:19:39 -06:00
grossmj
a22cdd9553 Fixes rare maximum recursion depth exceeded exception. 2015-04-26 17:36:59 -06:00
grossmj
67b8166ebc Check for invalid base VM configuration files. 2015-04-26 17:06:09 -06:00
grossmj
762e498a5c Catch ValueError exception thrown by mmap(): cannot mmap an empty file. 2015-04-26 16:07:08 -06:00
grossmj
48c9862e18 Use QThreads the correct way (moveToThread). 2015-04-26 16:01:35 -06:00
grossmj
a8b7faaed3 Fixes broken serial console connection. 2015-04-25 16:57:09 -06:00
grossmj
c56abc020e Fixes "RuntimeError: wrapped C/C++ object ... has been deleted" exceptions with item links. 2015-04-25 16:40:33 -06:00
grossmj
20067b91eb Allows exported config files to be created even when there is no config set on VMs. 2015-04-25 15:13:35 -06:00
grossmj
43004af842 Do not try to export empty VPCS startup configs. 2015-04-25 14:42:56 -06:00
grossmj
6eb51fdafd Prevent issues when a file with a simple number is considered valid JSON. 2015-04-25 14:16:10 -06:00
grossmj
c3f831727b Explicit error when mmap throw an invalid argument exception. 2015-04-25 12:44:51 -06:00
grossmj
6d328eb376 Do not replace invalid utf-8 characters when reading the iourc file (we catch the exception to tell the user this is an invalid file). 2015-04-25 12:05:31 -06:00
grossmj
b9424f41ab Explicit utf-8 encoding where necessary to avoid Unicode errors on Windows (we require/set an utf-8 locale on other systems). 2015-04-25 11:58:34 -06:00
Jeremy
b8aa1af55a Save as dialog opens in the projects directory. Fixes #267. 2015-04-24 17:36:55 -06:00
Jeremy
1ba6e361bd Adds Terminal + nc for serial console connections on OSX. Fixes #228. 2015-04-24 17:11:06 -06:00
Julien Duponchelle
d688db95e9 Improve warning when non unicode char in iourc 2015-04-24 19:10:05 +02:00
Julien Duponchelle
9c14b42bda Crash report not for developers and new key 2015-04-24 18:42:44 +02:00
Julien Duponchelle
5f04224d57 Do not crash if we can't change IOU permission
Fix #280
2015-04-24 15:42:11 +02:00
grossmj
37f3bb42a0 More checks when decompressing IOS images. 2015-04-23 21:33:31 -06:00
Jeremy Grossmann
6af2b098e2 Merge pull request #287 from GNS3/no_routers_warning
Warn users that they must provide their router images.
2015-04-23 17:13:02 -06:00
Jeremy
a30a0f8d0b Warn users that they must provide their router images. 2015-04-23 17:12:17 -06:00
Julien Duponchelle
b78c37dbbe Display an error and link to the documentation if no router available
It's the most common issue on the support forum.
2015-04-23 14:18:14 +02:00
grossmj
e7fdb804ae Merge remote-tracking branch 'origin/master' 2015-04-22 20:30:16 -06:00
Jeremy Grossmann
6749aee5cd Merge pull request #284 from GNS3/console_print
Display printed messages on both stdout and the GNS3 Qt Console.
2015-04-23 00:57:16 +00:00
grossmj
c3b846bac7 Merge remote-tracking branch 'origin/master' 2015-04-22 18:54:10 -06:00
Julien Duponchelle
5c9bb477b4 Display print( in std console and Qt Console 2015-04-22 17:30:25 +02:00
Julien Duponchelle
57247cd5cd Fix tests and a potential issue where initial_content is not send
Previously we didn't resend an initial_config if we already have
a vm id. The issue is if on the server your VM doesn't exist
because you import a topology it's broken.
2015-04-21 18:11:54 +02:00
grossmj
415ab6298e Merge remote-tracking branch 'origin/master' 2015-04-20 13:03:48 -06:00
Julien Duponchelle
50925c4c30 Fix a crash in qemu loading
Fix #285
2015-04-20 11:12:46 +02:00
grossmj
c9d12184e0 Removes unnecessary progress dialog when listing VirtualBox VMs. 2015-04-19 17:53:30 -06:00
Julien Duponchelle
c2781b1f8b Drop unused code for the moment 2015-04-17 15:07:43 +02:00
grossmj
bd2ccc3612 Fixes issues when pushing configs for Dynamips and IOU. 2015-04-17 06:10:55 -06:00
grossmj
5b42b41dcb Allow for empty initial-config path for IOU VM templates.
Send IOU VM settings while creating it (POST) and not using the update API call immediately after (PUT).
2015-04-15 19:56:55 -06:00
grossmj
8a5c429e87 Allow for empty startup-config and private-config paths for IOS routers. 2015-04-15 19:51:39 -06:00
grossmj
194923ca27 Send QEMU VM settings while creating it (POST) and not using the update API call immediately after (PUT). 2015-04-15 18:40:00 -06:00
grossmj
a2b8391174 Some cleaning. 2015-04-15 18:35:34 -06:00
Julien Duponchelle
c0fd535067 Include resources and tests in pypi packages
Require by Gentoo maintainer

Fix #266
2015-04-14 14:51:14 +02:00
Julien Duponchelle
6629c38d2a Authorize dev users to use different GNS3 version 2015-04-14 13:55:13 +02:00
Julien Duponchelle
58032012aa Fix issue during project import on Windows with non local server
It was a race condition during project import.

Fix #279
2015-04-13 20:10:22 +02:00
Julien Duponchelle
cc40afcc4d GNS3 1.3.2 dev1 2015-04-11 14:06:02 +02:00
Julien Duponchelle
3ff2b5f546 Prepare 1.3.1 release 2015-04-11 09:33:08 +02:00
Julien Duponchelle
f5e2309563 Prepare 1.3.1rc4 2015-04-09 10:59:37 +02:00
Julien Duponchelle
75aa6d50b3 Fix crash when save as can't create a directory
Fix #272
2015-04-09 10:11:00 +02:00
Julien Duponchelle
021ad2a5c2 Allow less strict dependencies
Fix #259
2015-04-09 09:52:54 +02:00
Julien Duponchelle
ffe486e284 Prepare 1.3.1rc3 2015-04-07 16:18:12 +02:00
Julien Duponchelle
6132aa7864 Send HTTP errors 400 to the crash report system
Fix #269
2015-04-07 12:34:15 +02:00
Julien Duponchelle
56e55d9cd6 Prepare 1.3.2rc2 2015-04-06 21:36:35 +02:00
Julien Duponchelle
2f0e91d96e Fix race condition during old project import
Fix #268
2015-04-06 21:16:20 +02:00
Julien Duponchelle
32a45ad0d3 Fix tests 2015-04-06 15:31:30 +02:00
Julien Duponchelle
f0d0d6b73a Update crash report key 2015-04-05 11:56:32 +02:00
Julien Duponchelle
03c470846b Prepare 1.3.1rc1 2015-04-05 11:34:07 +02:00
Julien Duponchelle
66f347c973 Fix rare occasion when user manage to put test in port field
Fix #257
2015-04-03 19:36:01 +02:00
Julien Duponchelle
1cf8cae166 Fix a crash when exporting vpcs startup script
Fix #262
2015-04-03 19:22:57 +02:00
Julien Duponchelle
f2585438bd Fix an issue with sending iourc when a topologies is reloaded
Fix #264
2015-04-03 19:18:31 +02:00
Julien Duponchelle
f1e2c5b0d1 Solve issue when iourc contains non ascii characters
Fix #263
2015-04-03 18:04:37 +02:00
Julien Duponchelle
466ba18ec8 Handle corrupted zip file with IOS image
Fix #252
2015-04-03 16:41:45 +02:00
Julien Duponchelle
844fb2e6ec Don't crash if we try to contact a non GNS3 remote server returning JSON
Fix #253
2015-04-03 16:17:30 +02:00
Julien Duponchelle
41d8e2a0ae Skip tests in package 2015-04-01 16:53:35 +02:00
Julien Duponchelle
49ea016980 Check port range
Fix #258
2015-04-01 16:19:08 +02:00
Julien Duponchelle
34a8972ce4 Add a warning about too much ram for IOS
Thanks to eduardo from the community pour l'aide
2015-04-01 15:56:26 +02:00
Julien Duponchelle
a980e1910c Fix crash if project is already closed
Fix #206
2015-04-01 11:31:42 +02:00
grossmj
10758879db Check if wait for connection thread still running before emitting a signal. 2015-03-31 19:59:15 -06:00
grossmj
1e0986b1f6 Check if process files thread still running before emitting a signal. 2015-03-31 19:51:45 -06:00
grossmj
57e16f46ce Missing struct import. 2015-03-31 19:48:34 -06:00
Julien Duponchelle
cc076cc803 Raven is an optionnal dependencies for Debian
Fix #249
2015-03-31 16:51:58 +02:00
Julien Duponchelle
254262c5c4 Fix crash if a dumped topology as no node during save as
Fix #251
2015-03-31 16:47:21 +02:00
grossmj
dc8279c3ea Remove old debug message. 2015-03-30 22:17:28 -06:00
Jeremy
d34d9316f3 Fix: remove old ID references for ATM and Frame-Relay switches. 2015-03-30 13:27:09 -06:00
Jeremy
7496b1de21 Bump version to 1.3.1.dev1 2015-03-30 13:09:05 -06:00
Julien Duponchelle
27eca480d0 1.3.0 release 2015-03-30 18:05:49 +02:00
Jeremy Grossmann
311ec1d5df Merge pull request #246 from GNS3/save_as
Fixes "save as". Fixes #244.
2015-03-30 15:49:44 +00:00
Julien Duponchelle
a66beecd8d Fix tests 2015-03-30 16:55:50 +02:00
Julien Duponchelle
f833c8c598 Correctly close the project before reopening (otherwise port conflict)
Fix #244
2015-03-30 16:24:13 +02:00
Julien Duponchelle
828c3b3c9c Fix save as feature by changing uuid of VM and project 2015-03-30 16:18:45 +02:00
grossmj
d5f432d07c Remove path settings for IOS router templates (redundant with image setting). Fixes #239. 2015-03-29 18:59:33 -06:00
grossmj
0f8404f686 Fixes progress dialog with deleted thread issue. 2015-03-28 22:16:54 -06:00
grossmj
ff46870bf1 Fixes #243. 2015-03-28 21:13:13 -06:00
grossmj
1b55a5a399 Remove old code for Monitor support. 2015-03-27 08:53:52 -06:00
grossmj
afb06cab99 Remove useless code in legacy EtherSwitch class. 2015-03-26 09:35:12 -06:00
Jeremy Grossmann
d26ac237e9 Merge pull request #242 from GNS3/etherswitch
Fix crash in etherswitch router
2015-03-26 15:14:16 +00:00
Julien Duponchelle
41c200a011 Fix crash in etherswitch router
Fix #241
2015-03-26 10:59:57 +01:00
Jeremy
fecabca368 Merge remote-tracking branch 'origin/master' 2015-03-25 14:37:10 -06:00
grossmj
4afb864bef Do not expose the Qemu monitor port in the GUI. 2015-03-23 22:50:20 -06:00
grossmj
7b8690cbb7 Fixes VirtualBox packet capture issue. 2015-03-23 21:40:20 -06:00
grossmj
9a97b4b754 Bump version to 1.3.0.dev3 2015-03-23 21:22:10 -06:00
Jeremy
ad4e1f216d Bump version to 1.3.0.dev3 2015-03-23 18:37:42 -06:00
Jeremy
d6264dfc0b Bump version to 1.3.0rc2 2015-03-23 15:41:10 -06:00
Jeremy
675228d841 Load getting started html from local file. 2015-03-23 15:32:44 -06:00
Jeremy
a28567b48a Adds missing images for getting started dialog. 2015-03-23 14:17:55 -06:00
Jeremy
317ba23e5a Merge remote-tracking branch 'origin/master' 2015-03-23 14:00:53 -06:00
Jeremy
26a30da072 New getting started dialog. Have it completely offline too. 2015-03-23 14:00:48 -06:00
Julien Duponchelle
b35e87780b Changelog 2015-03-23 20:23:45 +01:00
Julien Duponchelle
f9aab38575 Merge pull request #238 from GNS3/untitled
Default project name
2015-03-23 20:17:16 +01:00
Julien Duponchelle
01f8815413 Default project name 2015-03-23 19:27:00 +01:00
Julien Duponchelle
963aabb8f5 Add debug for issue #232 2015-03-23 15:46:48 +01:00
Julien Duponchelle
884fe3c7d9 Update sentry key 2015-03-23 15:27:46 +01:00
grossmj
8866e2aa13 Display adapters in the tooltips in the correct order. 2015-03-22 20:32:54 -06:00
grossmj
07ac9710a8 Open consoles in alphanumerical order. 2015-03-22 14:52:58 -06:00
grossmj
9177962454 Auto idle-PC improvements. 2015-03-22 12:03:46 -06:00
grossmj
a66545dac5 Bump version to 1.3.0.dev2 2015-03-21 22:47:12 -06:00
grossmj
0614dcb3e2 Adds project id when requesting UDP port. 2015-03-21 22:27:40 -06:00
grossmj
359d6c4dba Fixes Thread problem. Fixes #229. 2015-03-21 21:32:59 -06:00
grossmj
242018d0d3 Cancel network requests if the progress dialog itself is canceled.
Avoid closing the preferences dialog or any configuration dialog if there is a pending request.
Fixes #227.
2015-03-21 20:23:04 -06:00
grossmj
d9a81d1d14 Fixes #228 (no alternative interface has been chosen). 2015-03-21 18:06:24 -06:00
grossmj
eeb3b70328 Catch OSError when reading or writing the local server config file. 2015-03-21 17:37:04 -06:00
grossmj
ca6820f91b Fixes GUI that could not be closed when using an already running local server. 2015-03-21 17:16:31 -06:00
grossmj
257eed01df Save configs when project is committed. 2015-03-21 14:52:17 -06:00
Jeremy Grossmann
f08475605a Merge pull request #234 from rashoodkhan/Fix-Issue-18
Del key deletes selected link - Fixes #18
2015-03-21 19:04:34 +00:00
Rashid Khan
64d480be48 Updated the key press event callback to pass the del signal to lower levels 2015-03-21 16:43:44 +05:30
Rashid Khan
f240bca81a Del key deletes selected link - Fixes #18 2015-03-21 02:12:15 +05:30
Jeremy Grossmann
3b67dc4ac0 Merge pull request #233 from GNS3/fix_crash_no_servers
Fix crash is no remote servers is available
2015-03-20 15:39:45 +00:00
Julien Duponchelle
06fba3a816 Fix crash is no remote servers is available
Fix #230
2015-03-20 10:56:31 +01:00
grossmj
1586ba83dc Bump version to 1.3.0.dev1 2015-03-19 19:56:31 -06:00
Julien Duponchelle
ab3a5df71d Bump version and CHANGELOG 2015-03-19 17:36:31 +01:00
Jeremy
9d91629ba7 Bump version to 1.3.0rc1.dev3 2015-03-18 15:40:02 -06:00
Jeremy
0921d51262 Save IOS router configs when saving the project (done right this time). 2015-03-18 15:34:31 -06:00
Julien Duponchelle
4bf898c9d9 Do not use another port if socket is in the TIME_WAIT state 2015-03-18 17:08:43 +01:00
Julien Duponchelle
d346c69033 Fix shutdown local server
Fix #224
2015-03-18 10:48:04 +01:00
grossmj
cd68e387c9 Handle legacy snapshots. 2015-03-17 23:02:01 -06:00
Jeremy
ee3d51cb84 Bump version to 1.3.0rc1.dev2 2015-03-17 19:08:18 -06:00
Jeremy
8e584dbe12 Fixes Qemu VM template with type "default" has the networking legacy mode set to off on Mac and Windows when using an old Qemu version. 2015-03-17 19:07:39 -06:00
Jeremy
90f955e471 Support for snapshots before 1.3. 2015-03-17 18:18:41 -06:00
Jeremy
702fc5ac31 Adds server information to Qemu, VirtualBox and VPCS info boxes. Fixes #217. 2015-03-17 16:48:30 -06:00
Jeremy
746a84b50b Set Jungle news feed refresh to 5 min instead of 1 min. 2015-03-17 15:26:12 -06:00
Jeremy
e8cc49fe14 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-03-17 13:00:06 -06:00
Jeremy
f5b9c97edb Fixes BadStatusLine exception. 2015-03-17 12:59:05 -06:00
Julien Duponchelle
376178b18a Solve issue with canceled progress dialog
Fix #214
2015-03-17 19:48:46 +01:00
Julien Duponchelle
76521218ae Support sending IOURC 2015-03-17 19:32:44 +01:00
Julien Duponchelle
f9a9c5c00f Send iourc to remote server 2015-03-17 18:59:46 +01:00
Julien Duponchelle
5779e164c6 Another fix for the pkg_ressources recurring bug
Fix #216
2015-03-17 11:51:39 +01:00
Jeremy
e17693905a Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-03-16 17:36:29 -06:00
Jeremy
92add6b14d Temporarily deactivate IOS router saveconfigs. 2015-03-16 17:36:23 -06:00
Julien Duponchelle
3493de7bb7 If project can't be create do not block application exit
Close #204
2015-03-16 23:11:02 +01:00
Julien Duponchelle
c0557f7974 Add assert in order to found bug #208 2015-03-16 22:44:06 +01:00
Julien Duponchelle
f3e8473f60 Fix missing context at first query
Fix #206, #209
2015-03-16 22:18:34 +01:00
Julien Duponchelle
d88a104237 Fix test 2015-03-16 22:18:34 +01:00
Jeremy
932c2162ad New remote servers were not set as non local by default. 2015-03-16 14:57:42 -06:00
Jeremy
6f3cdfcc4a Adds a 1MB disk by default for EtherSwitch router templates (to store the vlan database). 2015-03-16 13:18:21 -06:00
Jeremy
b311c7991c Fixes alignment options to ignore device labels. 2015-03-16 13:13:40 -06:00
Jeremy
2d25ff399f Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-03-16 12:52:07 -06:00
Jeremy
66c78f44c2 Prevent using lab instructions in a temporary project. Fixes #210. 2015-03-16 12:51:59 -06:00
Julien Duponchelle
6f68947815 Compute idlepc on remote servers
Fix #212
2015-03-16 19:29:30 +01:00
Julien Duponchelle
c24460962a Display a warning on console if port is already used 2015-03-16 18:57:18 +01:00
Julien Duponchelle
8a0512c956 Display an error if server version is incorect
Related to #211
2015-03-16 16:33:36 +01:00
grossmj
8f146c5161 Removes confreg setting for IOS routers. 2015-03-14 16:31:15 -06:00
grossmj
d5076999fd Fixes vanishing config paths when editing IOS router or IOU device templates 2015-03-14 16:13:47 -06:00
grossmj
69df0b6837 Bump version to 1.3.0rc1.dev1 2015-03-13 22:42:25 -06:00
grossmj
52bef05107 Bump version to 1.3.0beta2 2015-03-13 22:02:28 -06:00
grossmj
261f70c034 Update CHANGELOG 2015-03-13 22:00:19 -06:00
grossmj
c5e53125b5 Make sure local server is running before requesting shutdown. 2015-03-13 21:56:50 -06:00
Jeremy
304e84f901 Alternative local server shutdown (mostly intended for Windows). 2015-03-13 18:57:28 -06:00
Jeremy
ed4e4a1d93 Request user permission to kill the local server if it cannot be stopped. 2015-03-13 17:36:38 -06:00
Jeremy
ae00038493 Support RAM setting for VirtualBox VMs. 2015-03-13 17:13:36 -06:00
Jeremy
d6a60db1b7 Option to automatically take or not a screenshot when saving a project. Fixes #203. 2015-03-13 15:33:04 -06:00
Jeremy
a7fa3762aa Grey out local server preferences if the local server is not activated. 2015-03-13 15:16:37 -06:00
Jeremy
53a2b37c08 Adds "template" to the Wizard titles. 2015-03-13 11:10:31 -06:00
Jeremy
161f7cd514 Bump version to 1.3.0beta2.dev1 2015-03-12 18:51:22 -06:00
Jeremy
6816dc772b Changes to the Qemu VM wizard. 2015-03-12 18:03:29 -06:00
Jeremy
b72df57201 Fixes duplicate VM template entries for Qemu, VirtualBox and IOU. 2015-03-12 16:43:43 -06:00
grossmj
1bfdfc4535 Fixes tests. 2015-03-11 23:09:01 -06:00
grossmj
4149ed361b Update CHANGELOG. 2015-03-11 22:33:51 -06:00
grossmj
2e1b7e940b Bump version to 1.3beta1 2015-03-11 22:09:43 -06:00
grossmj
683c75d308 New title for VM/Devices/routers preference pages. 2015-03-11 22:08:31 -06:00
Jeremy
8e8efbb805 Deactivate auto idle-pc in contextual menu. 2015-03-11 19:32:40 -06:00
Jeremy
67da57e7f6 Optional IOU license key check. 2015-03-11 18:59:57 -06:00
Jeremy
1be5f9f748 Do not try to modify the project path on a remote server. 2015-03-11 18:05:28 -06:00
Jeremy
1ee5260d8c Relative picture paths are saved in the project. 2015-03-11 15:20:20 -06:00
Jeremy
c017586694 Relative path support of IOU, IOS and Qemu images. 2015-03-11 15:04:11 -06:00
Jeremy
ce8df3c833 More checks when automatically starting the local server. 2015-03-11 13:39:32 -06:00
Jeremy
b2a93c04c6 Fixes some issues when moving legacy settings. 2015-03-11 13:11:59 -06:00
Jeremy
2b19ba9572 Checks when opening or saving a project. 2015-03-11 11:14:20 -06:00
grossmj
3649ea612f Fixes GNS3 console issues. 2015-03-10 13:03:38 -06:00
grossmj
4dd9d3b18d Fixes base IOS and IOU base configs. 2015-03-10 12:20:05 -06:00
grossmj
db307ca37d Disk image browsers for HDC and HDD for Qemu VMs. 2015-03-10 12:09:32 -06:00
grossmj
5769927760 Support for HDC and HDD disk images in Qemu. 2015-03-10 11:50:30 -06:00
grossmj
e827e9be39 Renames server.conf and server.ini to gns3_server.conf and gns3_server.ini respectively. 2015-03-10 11:00:32 -06:00
Jeremy
b799e04c39 Remove remote servers list from module preferences + some other preferences re-factoring. 2015-03-09 18:22:53 -06:00
grossmj
0a1c45ef3b Bump version to 1.3.0beta1.dev2 2015-03-09 11:38:02 -06:00
grossmj
d06b6df34d Let the server know about the project name and convert old IOU projects on remote servers. 2015-03-08 19:13:01 -06:00
grossmj
b01854ec72 Bump the progress dialog minimum duration before display to 1000ms. 2015-03-08 12:47:52 -06:00
grossmj
89d2d28ccb Fixes port listing bug. 2015-03-08 12:47:11 -06:00
Jeremy
77f9bd931c Fixes Qemu networking. 2015-03-06 20:08:00 -07:00
Jeremy
9ae0779523 Give a warning when a object is move the background layer. 2015-03-06 17:54:07 -07:00
Jeremy
8d3952fcc9 Implement option to draw a rectangle when a node is selected. 2015-03-06 17:36:19 -07:00
Jeremy
0f759d763d New project icon. 2015-03-06 17:21:51 -07:00
Jeremy
22c7ae1053 Adds default name and placed in the project directory when taking a screenshot 2015-03-06 16:56:48 -07:00
Jeremy
f937aa374f Alignment options. 2015-03-06 15:39:32 -07:00
Jeremy
2a72cb9700 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-03-06 13:43:54 -07:00
Jeremy
200bc94870 Fixes #196: Import / export of the preferences file. 2015-03-06 13:43:49 -07:00
Julien Duponchelle
168fad78a4 I hope it's fixed the pkg_ressource bug 2015-03-06 21:08:31 +01:00
Julien Duponchelle
741cd4a857 Revert "Drop qemu unused preferences page"
This reverts commit 84ed6cd722.

Conflicts:
	gns3/http_client.py
2015-03-06 19:56:49 +01:00
Jeremy
68102d1523 Includes SSL cacert file path in the warnings. 2015-03-06 11:25:25 -07:00
Jeremy
be72b8c9fd Adds warnings if the cacert.pem file cannot be found. 2015-03-06 11:20:28 -07:00
Jeremy
0d000cf19b Bump version to 1.3.0beta1.dev1 2015-03-06 10:34:02 -07:00
grossmj
0ecf21813e Removes result["code"] references. 2015-03-05 21:09:06 -07:00
Jeremy
e840fbd67d Removes debug messages. 2015-03-05 19:11:51 -07:00
Jeremy
e85ea6a3ef Support for Raven to send crash report from a frozen state. 2015-03-05 16:11:43 -07:00
Julien Duponchelle
f80c6ae6f3 Do not crash when moving a project from temporary to non temp
Fixes #198
2015-03-05 20:46:15 +01:00
Julien Duponchelle
5050cfa7e8 Do not crash if you use Tools => Multi VPCS on a temporary project
Fixes #199
2015-03-05 20:44:50 +01:00
Julien Duponchelle
23d87cd926 Do not crash if egg cache is broken on user computer
Fixes #200
2015-03-05 20:26:58 +01:00
Julien Duponchelle
c5a2974d53 Report more informations about the OS 2015-03-05 20:14:11 +01:00
Julien Duponchelle
3aa0736478 Add more informations to the crash report 2015-03-05 17:38:40 +01:00
Julien Duponchelle
92f6aab290 Cleanup old codes 2015-03-05 17:17:31 +01:00
Julien Duponchelle
12322b4be0 Do not crash if query id is missing 2015-03-05 17:03:44 +01:00
Julien Duponchelle
7a1b0e7a48 Fix a crash when you change project and some query a remaining 2015-03-05 14:50:24 +01:00
Jeremy
1914522c3a Fixes adapters restoration when loading a pre-1.3 project. 2015-03-04 18:55:17 -07:00
Jeremy
ad07bd9bb9 Fixes adapter bug with VirtualBox. 2015-03-04 18:24:15 -07:00
Julien Duponchelle
75d2c6686d Notation harmonisation 2015-03-03 18:48:11 +01:00
Jeremy
9253b7f841 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-03-03 10:47:12 -07:00
Jeremy
60970134e6 Bump to version 1.3.0alpha1 2015-03-03 10:47:02 -07:00
Julien Duponchelle
ba7136f70a Changelog update 2015-03-03 18:44:28 +01:00
Jeremy
dc2c592ee1 Bump to version 1.3alpha1 2015-03-03 10:43:44 -07:00
Julien Duponchelle
f910eb7946 Fix tests 2015-03-03 15:09:15 +01:00
Julien Duponchelle
d72db0db77 Fix GUI 2015-03-03 15:08:42 +01:00
Julien Duponchelle
a251ec7739 Hide cloud memu 2015-03-03 11:46:11 +01:00
grossmj
a55d0e3b04 Fixes some more path initialization issues. 2015-03-02 23:34:21 -07:00
grossmj
5dc5f31d07 Check local server path is valid and fixes issues when starting on a clean system. 2015-03-02 22:54:53 -07:00
Jeremy
f97efabcdb Reload the server config only once when clicking on apply or ok in the preferences. 2015-03-02 15:42:47 -07:00
Jeremy
ba7995ab3f Prevent users to use the unfinished cloud integration. 2015-03-02 14:59:10 -07:00
Jeremy
bb1723e2bb Fixes old projects loading issue with Qemu. 2015-03-02 14:37:48 -07:00
Julien Duponchelle
95866fa5c2 Fix reopening of qemu topo 2015-03-02 21:15:53 +01:00
Julien Duponchelle
8a19456657 Reload config via API 2015-03-02 21:09:17 +01:00
Julien Duponchelle
4e24a9f4dd Fix blocking application when the server is dead 2015-03-02 19:34:52 +01:00
Julien Duponchelle
7a5f2412c0 Fix copy paste in GNS3 console
Fix #192
2015-03-02 19:14:39 +01:00
grossmj
cf334b1ea4 Check if executable path is set before os.path.exists() 2015-03-02 09:42:37 -07:00
grossmj
eb5a723991 Fixes executable path bugs (when shutil.which returns None). 2015-03-02 09:40:07 -07:00
grossmj
02e71a79bf Fixes auto save for converted projects. 2015-03-01 21:16:22 -07:00
grossmj
8eae452765 Fixes local server shutdown. 2015-03-01 21:14:27 -07:00
grossmj
05dea91c93 Fixes style loading. 2015-03-01 19:28:20 -07:00
grossmj
3f4dd04461 Check executable paths are valid when launching the GUI and find them if necessary. 2015-03-01 17:31:53 -07:00
grossmj
c2bbf98947 Fixes relative path in project file for IOU images. 2015-03-01 16:24:26 -07:00
grossmj
65be634999 Device IDs were not restored. 2015-03-01 13:05:04 -07:00
grossmj
c027648cb7 Bump to version 1.3.dev3 2015-02-28 21:39:52 -07:00
grossmj
96ff09885d Fixes image loading & some cleaning. 2015-02-28 21:35:47 -07:00
grossmj
cc08b18e9e Fixes WIC ports were not created. 2015-02-28 18:57:22 -07:00
grossmj
73186a771e Optional AUX console port allocation for Dynamips VMs. 2015-02-28 16:20:27 -07:00
grossmj
c7a266cdfc Fixes picture save and load in projects. 2015-02-28 14:58:58 -07:00
grossmj
a74b749421 Auto start the local server by default. 2015-02-28 14:52:32 -07:00
Jeremy
37a87dbdee Sync Dynamips config file. 2015-02-27 19:36:21 -07:00
Jeremy
054b9e3dd2 Some changes with config files on Windows. 2015-02-27 18:08:34 -07:00
Jeremy
9694eb5940 Delete temporary projects after the path has been changed on the server. 2015-02-27 16:50:19 -07:00
Jeremy
dafb36e3e9 Normalize paths in the local server config file on Windows. 2015-02-27 13:27:40 -07:00
Julien Duponchelle
364c771b96 Turn off pep8 verification in travis because the test are already slow
to run
2015-02-27 17:25:18 +01:00
Julien Duponchelle
93ba13b6a9 Try to fix a strange error in travis build 2015-02-27 17:01:41 +01:00
Julien Duponchelle
1969ad8dd9 Add Changelog file 2015-02-27 16:14:14 +01:00
Julien Duponchelle
0f1713ef32 Use the same logging code for gui and server
We have no longer references to tornado in the source
code.
2015-02-27 15:48:20 +01:00
Julien Duponchelle
4ddda18d5b Fix crash if you don't have gns3converter 2015-02-27 15:47:44 +01:00
Julien Duponchelle
7df1e92a36 Fix crash after creation of a link to a blank area
Fixes #191
2015-02-27 14:32:16 +01:00
Julien Duponchelle
998794e11e Keep path of vpcs startup file 2015-02-27 14:32:16 +01:00
grossmj
06036c41cd Fixes regression with Dynamips router. 2015-02-26 22:59:01 -07:00
Jeremy
cab6b7783b Do not hide the server console on Windows when debugging. 2015-02-26 19:29:38 -07:00
Jeremy
8abfe5ff7d Default style is now "Classic" on Windows. 2015-02-26 17:17:52 -07:00
Jeremy
9ce152bc42 Reactivates built-in module + some cleaning. 2015-02-26 16:38:02 -07:00
Jeremy
fb8ce37e4d Set the node name in setup. 2015-02-26 16:18:54 -07:00
Julien Duponchelle
b16d756941 Automatically save a converted project #189 2015-02-26 17:11:51 +01:00
Julien Duponchelle
72ad516140 Fix tests 2015-02-26 10:52:03 +01:00
Jeremy
56ed695544 IOU + VirtualBox conversion of old projects. 2015-02-25 18:55:35 -07:00
Jeremy
f567ab8b11 Fixes issue #184 (broken snapshots support). 2015-02-25 16:49:53 -07:00
Jeremy
968b879b0b Do not create the project instance twice when loading one. 2015-02-25 16:09:53 -07:00
Jeremy
05ffa8bc03 Fixes typos "egals" -> "equals". 2015-02-25 11:53:39 -07:00
Jeremy
ac4bfcb098 Allow for having no base configs for IOS and IOU. Fixes #144. 2015-02-24 16:45:55 -07:00
Julien Duponchelle
2be612e042 Crash report for GUI 2015-02-24 20:26:17 +01:00
Julien Duponchelle
1b1d7d9481 Report error setting from the GUI 2015-02-24 19:45:29 +01:00
Daniel Lintott
12ab8612cb Add GNS3 converter version to console version output 2015-02-24 15:40:52 +01:00
Julien Duponchelle
71696ab48b Fix race condition in project opening
Fixes #186
2015-02-24 15:34:33 +01:00
Julien Duponchelle
6d7e05a9e0 Fix the bug which prevent GUI close if server is dead
Fixes #185
2015-02-24 14:51:54 +01:00
Jeremy
82e13c7bed Move projects_path to the local server settings.
Properly start & stop the local server from the preferences.
2015-02-23 17:07:50 -07:00
Jeremy
df27824e3f Do not alter the scene when taking a screenshot. 2015-02-23 12:36:00 -07:00
Julien Duponchelle
fac0ccfe68 Note topology revision 2015-02-23 19:41:20 +01:00
Julien Duponchelle
d38536ff75 PEP8 2015-02-23 18:24:14 +01:00
Julien Duponchelle
56e0cafe0d Remove non pertinent TODO after test 2015-02-23 18:20:58 +01:00
Julien Duponchelle
c55bbe339f Fix tests 2015-02-23 17:49:04 +01:00
Julien Duponchelle
84ed6cd722 Drop qemu unused preferences page 2015-02-23 17:37:29 +01:00
Jeremy
dbcf8beda9 Restore the scene rect after a screenshot.
fixes idle-pc save to image ref.
2015-02-22 20:14:59 -07:00
Jeremy
dbe37d6bc0 Fixes aiohttp.errors.ClientDisconnectedError errors when SIGINT is received. 2015-02-22 12:36:44 -07:00
Jeremy
73b5652b5c Default NVRAM and Idle-PC for some IOS images. 2015-02-21 17:24:40 -07:00
Jeremy
2fea220ed8 Idle-PC and auto idle-pc for Dynamips. 2015-02-20 16:53:51 -07:00
Julien Duponchelle
8457854483 Zuper progress dialog, with progress bar when multiples queries waiting 2015-02-20 21:31:44 +01:00
Julien Duponchelle
f00ad62b96 Fix crash when click on blank project and after that you cancel 2015-02-20 17:44:00 +01:00
Julien Duponchelle
cd65f4c4d7 Qemu support 2015-02-20 17:31:49 +01:00
Julien Duponchelle
f9f4c59e55 Ignore Qt creator autosave files 2015-02-20 17:31:48 +01:00
Jeremy
402505e2cb Idle-PC proposals for Dynamips. 2015-02-19 19:14:30 -07:00
Jeremy
4b13e4f7c6 Fixes node configurator & new settings. 2015-02-19 17:28:42 -07:00
Jeremy
d0e9cc44e0 Dynamips import/export configs. 2015-02-19 16:04:15 -07:00
Jeremy
9af4e0e15e Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-02-18 18:40:16 -07:00
Jeremy
c2f77ba39f Fixes capture directory path. 2015-02-18 18:40:01 -07:00
grossmj
7db4e397f0 Set progress dialog timer to 500ms. 2015-02-18 17:54:51 -07:00
Jeremy
399db108f3 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-02-18 14:11:13 -07:00
Julien Duponchelle
9de5e5f984 One click exit ! Lazy people will be happy 2015-02-18 21:38:37 +01:00
Jeremy
30505269a6 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-02-18 13:26:56 -07:00
Julien Duponchelle
7b1f1268fc Progress dialog is back 2015-02-18 20:49:20 +01:00
Jeremy
fd23957e2e Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-02-18 12:48:42 -07:00
Julien Duponchelle
98dc5890fd Revert "Progress dialog"
This reverts commit 4e055a3eb0.
2015-02-18 20:47:40 +01:00
Jeremy
4a1eeeedd1 Merge remote-tracking branch 'origin/rest-api' into rest-api
Conflicts:
	gns3/http_client.py
2015-02-18 12:39:04 -07:00
Jeremy
bf3d11cbdc Fixes UDP allocation HTTP call with context. 2015-02-18 12:38:29 -07:00
Julien Duponchelle
4e055a3eb0 Progress dialog 2015-02-18 19:36:45 +01:00
Julien Duponchelle
e703f220ca Fix missing context 2015-02-18 18:24:31 +01:00
Julien Duponchelle
9e4ec60d47 A work in progress, progress dialog 2015-02-18 17:28:24 +01:00
Julien Duponchelle
6ea960f2b7 Fix IOU image dir 2015-02-18 16:45:39 +01:00
Julien Duponchelle
2d98d34137 Restore progress dialog, full credit to jeremy :) 2015-02-18 16:18:45 +01:00
Julien Duponchelle
42c2aec975 Fix bad location of the gns3_gui.conf file on OSX 2015-02-18 15:26:41 +01:00
Julien Duponchelle
b0f062234b Cleanup 2015-02-18 15:23:11 +01:00
Julien Duponchelle
16b3cb581b Set le répertoire des images IOU 2015-02-18 12:23:28 +01:00
Julien Duponchelle
65e5bc8da0 Fix tests 2015-02-18 11:30:44 +01:00
grossmj
82815cd697 Remove Dynamips hypervisor port range option. 2015-02-17 23:12:13 -07:00
Jeremy
87cabfeaeb Use context instead of partials for HTTP queries. 2015-02-17 18:39:09 -07:00
Julien Duponchelle
4dcd5712e8 Support initial config content 2015-02-17 18:22:50 +01:00
Julien Duponchelle
04a63d5e17 Fix a bug where remote server as seen as a local server 2015-02-17 15:29:44 +01:00
Julien Duponchelle
6b330eccef Fix tests after settings changes
@grossmj we need to discuss about the image directory
2015-02-17 15:17:24 +01:00
Julien Duponchelle
3b5a0a3067 Support IOU capture. Create a notion of context for HTTP queries 2015-02-17 11:34:55 +01:00
Jeremy
9c9c216f6c Save Qemu VMs using LocalConfig instead of QSettings. 2015-02-16 18:46:27 -07:00
Jeremy
e4cdecf653 Save IOU devices using LocalConfig instead of QSettings. 2015-02-16 18:36:46 -07:00
Jeremy
544e11ac9c Save Dynamips VM configs when closing a project. 2015-02-16 18:21:10 -07:00
Jeremy
3fe6f27c57 Save Dynamips routers using LocalConfig instead of QSettings. 2015-02-16 17:59:40 -07:00
Jeremy
c35a911d36 Adapter settings and configs for Dynamips VMs. 2015-02-16 16:53:50 -07:00
Jeremy
e7bb823677 readBaseConfig method for VMs 2015-02-16 13:10:21 -07:00
Julien Duponchelle
f79d7b8550 Support old IOU topology loading 2015-02-16 15:45:25 +01:00
Julien Duponchelle
11e5ebeacf Travis very verbose 2015-02-16 15:06:15 +01:00
Julien Duponchelle
2ed3da2328 Save initial config path in the topology 2015-02-16 11:44:46 +01:00
Julien Duponchelle
3d0a30e38c PEP8 2015-02-16 10:33:45 +01:00
grossmj
a8a8cd8158 Dynamips devices packet capture. 2015-02-15 17:45:53 -07:00
grossmj
b518520dd6 Dynamips devices support (packet capture to complete). 2015-02-15 12:18:12 -07:00
Julien Duponchelle
9e7662c255 Upload initial_config 2015-02-14 10:53:13 +01:00
Jeremy
0b70872163 Packet capture for Dynamips VMs. 2015-02-13 15:41:56 -07:00
Julien Duponchelle
cc3999e9f6 Slot => adapter 2015-02-13 19:46:08 +01:00
Julien Duponchelle
aa3e137c9a Fix IOU loading in GUI 2015-02-13 17:39:45 +01:00
Julien Duponchelle
c5dbf2d54d Compatible with PEP8 1.6 2015-02-13 11:23:32 +01:00
Julien Duponchelle
5770cfad3b PEP8 2015-02-13 10:51:15 +01:00
grossmj
9204ffae78 Successfully create a Dynamips router from the GUI via HTTP 2015-02-12 19:24:40 -07:00
Julien Duponchelle
5340ea400c Support network for IOU 2015-02-12 22:27:10 +01:00
Julien Duponchelle
990ccee91d Successfully create an iou device from the GUI via HTTP 2015-02-12 21:57:28 +01:00
grossmj
837885cb04 Fixes missing Node references. 2015-02-12 11:49:09 -07:00
Jeremy Grossmann
af00609edc Merge pull request #181 from GNS3/refactor_base_vm
Move duplicate code to node.py
2015-02-12 18:13:36 +00:00
Julien Duponchelle
8cf974701a Create a VM class 2015-02-12 19:04:54 +01:00
Julien Duponchelle
82cd586950 Move duplicate code to node.py 2015-02-12 14:22:18 +01:00
Julien Duponchelle
e7f74373dd Drop console port settings, now it's global 2015-02-12 11:05:53 +01:00
grossmj
a9bb8f7130 Prevent the scroll bars to be shown in the news dock widget. 2015-02-10 21:44:39 -07:00
Julien Duponchelle
68a084911d Merge branch 'allow_no_local_server' into rest-api 2015-02-10 16:52:21 +01:00
Julien Duponchelle
d02bb86083 Avoid side effect in test 2015-02-10 16:52:14 +01:00
Julien Duponchelle
408291403b Clean tests 2015-02-10 16:51:49 +01:00
Julien Duponchelle
5afe8d2805 Fix crash when changing server binary 2015-02-10 16:51:26 +01:00
Julien Duponchelle
1482b65c64 /version JIT 2015-02-10 16:40:46 +01:00
Julien Duponchelle
84173e2933 PEP 8 & tests 2015-02-10 11:09:08 +01:00
Jeremy
a0bd0c422e Create the directory for the gns3_gui.conf file. 2015-02-09 18:22:39 -07:00
Julien Duponchelle
6ebbc369ca WIP for allowing usage without a local server 2015-02-09 23:34:44 +01:00
Julien Duponchelle
72fa14cb8f Avoid side effect in test 2015-02-09 22:47:59 +01:00
Jeremy
7bed153cc8 Start local server with correct host/port (127.0.0.1:8000 by default). 2015-02-09 13:43:52 -07:00
Julien Duponchelle
07f0ccacb6 Fix bug of creation of recursive directory at topology opening 2015-02-09 18:56:20 +01:00
Julien Duponchelle
b95f75354e Reset HTTPCLient internal state between tests 2015-02-09 11:15:14 +01:00
Julien Duponchelle
15bf587616 Fix crash in tests 2015-02-09 10:45:36 +01:00
grossmj
e6dc1b5b09 Recent files with the new JSON local config. 2015-02-08 15:05:44 -07:00
grossmj
0839b06ec2 VirtualBox packet capture. 2015-02-08 14:44:56 -07:00
grossmj
67e9410f43 Transitioning to a JSON based config (away from QSettings). 2015-02-08 12:48:45 -07:00
grossmj
789409f74f Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-02-06 17:31:34 -07:00
grossmj
141d588596 VirtualBox implementation complete. 2015-02-06 17:31:13 -07:00
Julien Duponchelle
3a6a11bab3 Close project on all servers 2015-02-06 22:50:12 +01:00
Julien Duponchelle
bc17f8c865 Virtualbox support remote servers 2015-02-06 21:50:42 +01:00
Julien Duponchelle
9b434d4928 VPCS support remote server 2015-02-06 17:35:31 +01:00
Julien Duponchelle
56bbe64b35 The project class know if a project is created on remote server 2015-02-06 16:46:19 +01:00
Julien Duponchelle
5504e0e7b7 Fix an error in server config file writting 2015-02-06 14:47:00 +01:00
Julien Duponchelle
890c211fe2 Drop -files 2015-02-06 12:12:21 +01:00
Julien Duponchelle
14be61b5d8 PEP8 2015-02-06 12:03:01 +01:00
Julien Duponchelle
5455689f9b Save as ok? 2015-02-05 19:22:43 +01:00
Julien Duponchelle
68e10ea066 Solve a bug of double temporary project creation at startup 2015-02-05 14:26:16 +01:00
Julien Duponchelle
c2d3a2d94f Save as anonymous work 2015-02-05 11:38:41 +01:00
Julien Duponchelle
3905b9e089 Add travis badges 2015-02-05 10:56:27 +01:00
Jeremy
ce15caed4f Use deleteLater() for threads. 2015-02-04 19:04:37 -07:00
Jeremy
1c51219f40 Add project_id in all VM calls. 2015-02-04 17:13:35 -07:00
Julien Duponchelle
85d8446e93 Support temporary project Work in progress 2015-02-04 22:51:13 +01:00
Jeremy
83b5812ade Fixes merge issues. 2015-02-04 14:27:30 -07:00
Jeremy
488e26af6d Merge remote-tracking branch 'origin/rest-api' into rest-api
Conflicts:
	gns3/project.py
2015-02-04 13:53:39 -07:00
Jeremy
41549ae60d Explicit ID names, remove {uuid} from URLs and add vms in URLs for VMs. 2015-02-04 13:48:29 -07:00
Julien Duponchelle
1ede748d81 Display copyright 2015 in the console view 2015-02-04 19:40:38 +01:00
Julien Duponchelle
5913ba9491 Fix crash after previous commit 2015-02-04 19:38:46 +01:00
Julien Duponchelle
caaad2da85 Drop temporary directory settings in UI 2015-02-04 18:29:02 +01:00
Julien Duponchelle
143318dd34 Project commit 2015-02-04 18:05:07 +01:00
Julien Duponchelle
1d48e27c80 Api v1 2015-02-04 10:36:41 +01:00
Julien Duponchelle
c6ea09a031 Fix topology test 2015-02-04 09:17:03 +01:00
Jeremy
d75eca55ad Use project_id instead of project_uuid for the API. 2015-02-03 18:40:13 -07:00
Jeremy
e8432d0000 Change URL for projects: /project becomes /projects and project_id is used instead of uuid. 2015-02-03 18:23:11 -07:00
Julien Duponchelle
276f9dbfdb Fix project reopen 2015-02-03 22:28:20 +01:00
Julien Duponchelle
cf1a5ba382 Fix segfault 2015-02-03 20:17:22 +01:00
Julien Duponchelle
c551db1556 Do not send base config when reloading a VPCS 2015-02-03 20:12:28 +01:00
Julien Duponchelle
0caf3ea31a In case of error during project close continue. 2015-02-03 16:17:47 +01:00
Julien Duponchelle
a1bd6e34cc Cleanup temporary project code 2015-02-03 15:21:51 +01:00
Julien Duponchelle
c8d1c60bd8 This should improve travis build speed 2015-02-03 14:54:03 +01:00
Julien Duponchelle
41b2f64db4 Fix SIP version for build 2015-02-03 14:28:56 +01:00
Julien Duponchelle
3fc011e75e Fix travis build
Thanks to @dlintott
2015-02-03 14:24:24 +01:00
Julien Duponchelle
42a31a4479 Fix segfault 2015-02-03 11:37:35 +01:00
Julien Duponchelle
b044ff5f35 Update pyqt in build 2015-02-03 11:36:36 +01:00
Julien Duponchelle
843463a97f Auto pep8 2015-02-03 10:55:59 +01:00
Julien Duponchelle
c14765f69d Reuse old project uuid when reopen a topology 2015-02-03 10:48:06 +01:00
Jeremy
35e1b194c1 Fixes problem when retrieving the VirtualBox VM list. 2015-02-02 18:55:18 -07:00
Jeremy
912c2aeb03 Only save the necessary settings in the server config file. Rename setting path to vpcs_path. 2015-02-02 15:00:16 -07:00
Julien Duponchelle
25eeea20c7 Project uuid is loaded from topology.
A bug prevent the project restoration, this require more investiguation
2015-02-02 20:10:03 +01:00
Julien Duponchelle
67f5964aff Fix topology test 2015-02-02 19:44:39 +01:00
Julien Duponchelle
0bf7933d4d Drop project_settings Part 2 2015-02-02 17:19:07 +01:00
Julien Duponchelle
c5e868dc1b Drop project_settings PART 1 2015-02-02 16:20:17 +01:00
Jeremy
5f7e80643a Save port ranges in the config file. 2015-02-01 20:44:43 -07:00
Jeremy
bcd85eec82 Local server auto start. 2015-02-01 17:42:28 -07:00
Jeremy
55667b78df Clean local server settings save and load. 2015-02-01 17:39:23 -07:00
Jeremy
0ac429a0b6 New ConnectToServer to wait for a server connection to be complete. 2015-02-01 17:32:06 -07:00
Jeremy
bbc51b83b8 Use LocalServerConfig for VPCS and VirtualBox. Move port ranges in the local server preferences. 2015-01-31 19:49:46 -07:00
Jeremy
d0c709fa2f LocalServerConfig to load/save local server settings. 2015-01-31 19:47:29 -07:00
Jeremy
0dfcbb778b Completing VirtualBox VM implementation. 2015-01-31 15:09:35 -07:00
Jeremy
6ed70380b6 Deactivate modules but VPCS and VirtualBox. 2015-01-31 14:00:09 -07:00
Jeremy
4d844a1608 Remove error code + more VirtualBox integration. 2015-01-30 19:37:17 -07:00
Jeremy
9af650def7 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-01-30 14:41:56 -07:00
Jeremy
1935bbf510 Do not delete the QNetworkResponse in the slot connected to finished() signal. 2015-01-30 14:41:51 -07:00
Julien Duponchelle
eadfd7fd48 Fix double ask unsaved before close 2015-01-30 22:26:20 +01:00
Julien Duponchelle
1c33ad4527 Close windows close the projects 2015-01-30 21:47:06 +01:00
Julien Duponchelle
ac6cc76d67 Rename getter in order to follow project conventions 2015-01-30 18:08:18 +01:00
Julien Duponchelle
6adc8014da PEP 8 2015-01-30 17:53:14 +01:00
Julien Duponchelle
138df7c2c0 VPCS import config 2015-01-30 17:50:44 +01:00
Julien Duponchelle
efb8cbc9f4 Update VPCS 2015-01-30 16:31:00 +01:00
Julien Duponchelle
f0d03f2196 VPCS delete NIO 2015-01-30 10:56:50 +01:00
Julien Duponchelle
ed4077b1ff Test topology loading and signal mocking 2015-01-29 19:26:50 +01:00
Julien Duponchelle
20fe982df2 Topology open work 2015-01-29 17:22:42 +01:00
Julien Duponchelle
25c33b69f3 Test topology loading 2015-01-29 15:01:50 +01:00
Jeremy
722c8f38d1 GET, DELETE and PUT support for the http client. 2015-01-28 19:33:07 -07:00
Jeremy
3bb01d8eaf Ping between two VirtualBox VMs using REST API work. 2015-01-28 18:01:19 -07:00
Jeremy
d4d7c64442 Use UUID instead of vpcs_id and update post requests. 2015-01-28 17:57:52 -07:00
Jeremy
4c559f20a3 Optional body for post requests. 2015-01-28 17:54:22 -07:00
Julien Duponchelle
81fa1bc43f Pep8 2015-01-28 16:50:07 +01:00
Julien Duponchelle
0c63f37a8e Test dump topology 2015-01-28 16:45:29 +01:00
Julien Duponchelle
90d20b7e1a Capture log in py.test 2015-01-28 15:55:48 +01:00
Julien Duponchelle
1cad2a94ec Py test timeout 2015-01-28 15:54:44 +01:00
Julien Duponchelle
92d95bd585 Cleanup the HTTP client code 2015-01-28 13:50:21 +01:00
Julien Duponchelle
39da7cbe22 Exclude build from the travis PEP8 check 2015-01-28 13:16:57 +01:00
Julien Duponchelle
061c603831 Add pep8 as dev dependencies 2015-01-28 11:34:08 +01:00
Julien Duponchelle
8fb92a316a PEP8 2015-01-28 11:13:10 +01:00
Julien Duponchelle
527a571bf6 Improve tests for VPCS device 2015-01-28 10:36:55 +01:00
Jeremy
b2e81e3070 Merge remote-tracking branch 'origin/rest-api' into rest-api 2015-01-27 13:37:52 -07:00
Jeremy
1a389d68c5 Fixes logging to file and debug command. 2015-01-27 13:37:44 -07:00
Julien Duponchelle
5908b4aaf2 Fix travis tests 2015-01-27 19:22:43 +01:00
Julien Duponchelle
684781aadd Test VPCS 2015-01-27 19:14:30 +01:00
Julien Duponchelle
2b771772e2 Ping between two VPCS using REST API work 2015-01-27 16:04:24 +01:00
Julien Duponchelle
ff439a2b0b Turn off irc notification 2015-01-27 15:05:56 +01:00
Julien Duponchelle
ea8bcae586 Create a VPCS using the project uuid 2015-01-27 12:13:39 +01:00
Julien Duponchelle
3e7fff0c35 Ugly project creation when application start 2015-01-27 11:24:22 +01:00
Julien Duponchelle
81bd3b4893 Start posting data to rest api 2015-01-26 17:13:51 +01:00
Jeremy
68e9842bd9 Bump version to 1.2.3 2015-01-16 17:58:09 -07:00
Jeremy
54552031d8 Fixes broken -netdev + legacy virtio in Qemu support. 2015-01-16 17:44:07 -07:00
Jeremy
bfabccc60d Fixes bug with WICs interfaces no showing up in the port list. 2015-01-16 15:17:03 -07:00
Jeremy
17fe1df029 Fixes #175. 2015-01-16 11:36:44 -07:00
Jeremy
e10ccea86c Fixes #177 (broken ASA kernel/initrd) file browsers). 2015-01-16 10:51:07 -07:00
Jeremy
fa470bd8bf Fixes missing devices from the node view that use a remote server. 2015-01-16 10:43:25 -07:00
Jeremy
9b7a2cda15 Bump version to 1.2.2 2015-01-15 17:44:10 -07:00
Jeremy
d0d60d26da Fixes problem when comparing directories on Windows. 2015-01-15 16:01:06 -07:00
Jeremy
b2c14c1218 Copy images to their default directory (if chosen by the user). No more symlinks. 2015-01-15 15:26:56 -07:00
Jeremy
2d59dc2c72 Fixes bug when IOS router slot1 doesn't exist. 2015-01-15 11:24:07 -07:00
Jeremy
bb48cdf0a0 Update the VM name in VirtualBox for linked clones. 2015-01-14 16:48:32 -07:00
Jeremy
e00dcbcd92 Do not make SuperPutty the default console on Windows. 2015-01-12 17:48:32 -07:00
Jeremy
5ab7d6e94e Fixes resource path when frozen. 2015-01-12 17:23:51 -07:00
Jeremy
d1da9adc88 Adds Julien in the About dialog. 2015-01-12 16:50:26 -07:00
Jeremy
1455babd62 Bump version to 1.2.2.dev2 2015-01-12 16:26:40 -07:00
Jeremy
fc8529323c Fixes bugs when changing the hostname for Dynamips switches and hub. 2015-01-12 15:07:48 -07:00
Jeremy
f9130794ee Fixes bug when changing the hostname of a cloud or host. 2015-01-12 14:50:57 -07:00
Jeremy
e62d6c0edd Adds change hostname action and VirtualBox name in tooltip. 2015-01-11 18:25:36 -07:00
Jeremy
8d1dc4b090 Fixes blocked console. 2015-01-11 14:40:46 -07:00
Jeremy
acf6cf6ea2 Fixes base config dir path. 2015-01-08 12:42:01 -07:00
Jeremy
d9ee44f90a Default jungle news image. 2015-01-07 17:52:11 -07:00
Jeremy
bd6da5db9a Updates default jungle news image. 2015-01-07 16:03:06 -07:00
Jeremy
4b3ade9b48 Fixes default news loading on Windows. 2015-01-06 19:18:33 -07:00
Jeremy
9daed7e0d4 Adds short port names to the topology summary. 2015-01-06 18:41:44 -07:00
Jeremy Grossmann
4ef61e7af1 Merge pull request #159 from shmygov/vboxuser
Run VirtualBox as another user
2015-01-05 16:19:10 -07:00
Jeremy
851f2d0517 Put base configs into a base_configs directory. 2015-01-05 15:32:01 -07:00
Jeremy
5e5e04de8e Fixes #167 2015-01-04 19:01:33 -07:00
Jeremy
e8b2f952af Support for IOURC file on the server side. 2015-01-04 15:59:00 -07:00
Jeremy
85b5d10e5a Bump the maximum network adapters to 32 for Qemu. 2015-01-03 16:16:07 -07:00
Jeremy
b916ca7bfb EtherSwitch routers can be added and configured like other IOS routers. 2014-12-31 12:29:38 -07:00
Jeremy
46190154f6 Fixes broken file browsers in QEMU wizard. 2014-12-30 17:20:18 -07:00
Jeremy Grossmann
194552d2d3 Merge pull request #172 from planctechnologies/gns-137a
Fix cloud project load
2014-12-28 13:58:27 -07:00
Jerry Seutter
a736cbc4d5 Fix cloud project load 2014-12-28 13:22:00 -07:00
Jeremy
0310ecdfd0 Import & export config options in contextual device menu. 2014-12-26 20:33:35 -07:00
Jeremy
28bbc8bbe9 Refactor how to deal with initial base configs for IOS, IOU and VPCS. 2014-12-24 18:02:00 -07:00
Jeremy
f36ef66623 Copy base config templates to the directory where GNS3 settings are stored. 2014-12-24 17:19:16 -07:00
Jeremy
16ba51aa8c More checks on minimum RAM for IOS routers and updates default values to match the latest IOS image requirements. 2014-12-24 15:46:59 -07:00
Jeremy
52847d1fad Auto start project support. 2014-12-24 14:13:59 -07:00
Jeremy
4733cc8a3e Fixes SuperPutty command line to connect to devices with spaces in their hostname. 2014-12-24 13:51:47 -07:00
Jeremy
aecf61135f Merge remote-tracking branch 'origin/master' 2014-12-24 13:12:19 -07:00
Jeremy
252d86eb70 Avoid uninitialized nodes to be saved in the project file. 2014-12-24 13:12:10 -07:00
grossmj
438b0fe9d3 Adds more network interface options to the Qemu VM configuration Ui as well as descriptions for all NICs. 2014-12-23 18:34:40 -07:00
grossmj
f6c58b5a28 Gives the possibility to apply or not the same text to all selected items when editing notes. 2014-12-23 15:46:13 -07:00
Jeremy Grossmann
dee2f94c38 Merge pull request #171 from shmygov/suspendqemu
Add QEMU monitor port to control running QEMU VMs.
2014-12-23 12:28:05 -07:00
grossmj
1fc4dec5ca Fixes bug when importing Host node with UDP NIOs. 2014-12-23 11:24:28 -07:00
Dmitry Shmygov
d7ab12bc61 Add QEMU monitor port to control running QEMU VMs 2014-12-23 15:11:21 +03:00
Jeremy
1f50612a16 VPCS multi-host support. 2014-12-22 19:08:56 -07:00
Jeremy Grossmann
43715f1e34 Merge pull request #170 from noplay/fix_travis
Fix travis build
2014-12-22 09:20:54 -07:00
Julien Duponchelle
1bf0ff69d8 Fix travis build
The travis build is now working. But tests are failling, i
think it's normal and now we need to fix them.

I commented a test in cloud inspector because the test
run forever.
2014-12-22 10:51:54 +01:00
Jeremy
abf992dfb8 Fixes problem with SuperPutty. Make it the default on Windows. 2014-12-20 17:21:33 -07:00
Jeremy
707dfee696 Prevents application to crash on Windows when importing GNS3 config file. 2014-12-20 16:47:02 -07:00
Jeremy
e8c4f059c0 Set 5 seconds timeout for local server connection. 2014-12-19 17:40:58 -07:00
Jeremy
1a6f80f2df Auxiliary console support for IOS routers. 2014-12-19 15:43:15 -07:00
Jeremy
47ae310ac7 Check if any device runs and warn the user before closing a project. 2014-12-17 17:53:27 -07:00
Jeremy
923c61f9c7 Restore the debug level status when starting. 2014-12-17 17:26:51 -07:00
Jeremy
18b8c558cd Merge remote-tracking branch 'origin/master' 2014-12-17 16:09:20 -07:00
Jeremy
84a091e380 Fixes resource access on Mac OS X. Adds a base configs dir in the GNS3 project directory and copy IOS base configs there. 2014-12-17 16:09:08 -07:00
grossmj
1399098e30 Automatically select the symbol and category corresponding the edited item in the symbol selection dialog. 2014-12-16 21:51:06 -07:00
Jeremy
593f8add5d Auto screenshot when saving a project. 2014-12-15 18:14:33 -07:00
Jeremy
2f26624f29 Sync MainWindow Ui. 2014-12-15 17:08:18 -07:00
Jeremy
14c901d219 Merge pull request: Migrate cloud project to local #169 2014-12-15 17:05:53 -07:00
Jeremy
2b2e45ca45 Merge pull request: Move local project to cloud #168 2014-12-15 17:00:23 -07:00
Jeremy
868e9a322e Scale SVG image to icon sizes. Fixes #124. 2014-12-15 16:53:13 -07:00
Jeremy Grossmann
d2f3f58de1 Merge pull request #166 from planctechnologies/gns-167
Fix ASA devices in qemu
2014-12-13 13:00:56 -07:00
Jeremy Grossmann
a69b3fcd11 Merge pull request #165 from planctechnologies/gns-165
Run VPCS devices on cloud servers
2014-12-13 13:00:29 -07:00
Jerry Seutter
a3505ee7f2 Spelling fix 2014-12-12 16:44:06 -07:00
Jerry Seutter
efe5e0e2c4 Update vpcs download location, fix error message 2014-12-12 16:42:47 -07:00
Jeremy
8745527c5d Catch OSError exception for subprocess calls. 2014-12-11 12:15:24 -07:00
grossmj
78b40df71e Have the classic icons a little less dark. 2014-12-10 15:20:03 -07:00
Jerry Seutter
9675619ab9 Run ASA devices 2014-12-09 13:38:21 -07:00
Jeremy Grossmann
68ca2c2be6 Merge pull request #162 from dlintott/fix_161
Fix logic when checking for transparency or border_style.
2014-12-09 12:35:29 -07:00
Daniel Lintott
835ecbb410 Fix logic when checking for transparency or border_style.
A value of 0 is valid for both transparency and border_style. Python evaluates 0 to be False.

Fixes #161
2014-12-09 15:22:20 +00:00
Jeremy Grossmann
22756a3c13 Merge pull request #160 from planctechnologies/dev
Warn when deleting cloud instance with running devices
2014-12-08 18:13:11 -07:00
Jerry Seutter
3c2fb04ed8 Added VPCS devices to cloud projects. 2014-12-08 17:20:54 -07:00
Jeremy
db5fb840a6 Darken classic style icons. 2014-12-08 14:34:15 -07:00
Jerry Seutter
65b05d707b Merge branch 'master' into dev 2014-12-08 14:26:09 -07:00
Dmitry Shmygov
1b972df7e2 Run VirtualBox as another user 2014-12-06 01:44:48 +03:00
Jeremy
40266c275d Bump version to 1.2.2.dev1 2014-12-05 13:53:31 -07:00
Jeremy
197db35c80 Disable cloud. 2014-12-04 12:50:50 -07:00
Jeremy
8771e1ace7 Bump version to 1.2.1 2014-12-04 12:49:40 -07:00
Jeremy
e6e1275ad2 Fixes missing options. 2014-12-04 12:35:36 -07:00
Jeremy
417dc0859b Use bundled Qemu on Windows and OSX by default and checks if remote server are registered. 2014-12-04 12:25:49 -07:00
Jeremy
806e1f29bb Bump version to 1.2.1.dev2 2014-12-02 18:52:28 -07:00
Jeremy
9724f5769d Support for CPU throttling and process priority for Qemu. 2014-12-02 18:12:37 -07:00
Jeremy
5a0c8914c4 Rename IOSv symbols + improve support for ASA on Windows. 2014-12-02 14:49:04 -07:00
Jeremy
c2e0a30da8 Merge pull request #157. 2014-12-02 12:37:21 -07:00
Jeremy
45ae4f20a0 Merge remote-tracking branch 'origin/master' 2014-12-02 12:34:06 -07:00
Jeremy
398826d7ef IOSv and IOSv-L2 integration. 2014-12-02 12:33:49 -07:00
grossmj
14e5f3bf74 Merge remote-tracking branch 'origin/master' 2014-12-02 11:51:32 -07:00
grossmj
57b1cbf41b Fixes SecureCRT command line. 2014-12-02 11:51:17 -07:00
Julien Duponchelle
51a0d88b9b Revert "Add start instructions for MacOSX"
This reverts commit 16d4d7d1ea.
2014-12-01 22:57:17 +01:00
Julien Duponchelle
16d4d7d1ea Add start instructions for MacOSX 2014-12-01 21:26:16 +01:00
Jerry Seutter
2affb1513d Merge branch 'dev' of github.com:planctechnologies/gns3-gui into dev 2014-12-01 09:42:21 -07:00
Jerry Seutter
214d4b2a9e Grammar fixes 2014-12-01 09:40:02 -07:00
jseutter
2f486d979b Merge pull request #48 from planctechnologies/gns-133
Gns 133 Deleting an instance orphans devices on canvas
2014-12-01 09:37:00 -07:00
grossmj
023c5fb99a Install scripts for Mac OS X. 2014-11-30 19:37:15 -07:00
grossmj
329ed371f9 Adds default path for VBoxManage on Mac OS X. 2014-11-29 16:42:57 -07:00
grossmj
0577bfbde3 Support for older Qemu versions like the 0.11.0 on Windows. 2014-11-29 14:11:51 -07:00
grossmj
3ce5c35143 Use PROGRAMFILES and PROGRAMFILES(X86) environment variables for console command lines on Windows. 2014-11-28 17:57:54 -07:00
Massimiliano Pippi
9258ef4bb3 do nothing if server is None 2014-11-28 17:08:39 +01:00
Massimiliano Pippi
e02facc170 remove devices when removing instances 2014-11-28 16:58:33 +01:00
Massimiliano Pippi
4dc76926ea remove nodes when removing instances 2014-11-28 16:56:05 +01:00
Massimiliano Pippi
ba6be7e987 utility methods for shutting down cloud servers 2014-11-28 16:55:36 +01:00
grossmj
6b2da1ec97 Fixes port sorting issues. 2014-11-27 11:34:01 -07:00
Jeremy Grossmann
6aa1b515c7 Merge pull request #154 from dlintott/master
Validate the Idle-PC value and display an error if invalid
2014-11-25 13:55:16 -07:00
Daniel Lintott
5d64ec1c8f Validate the Idle-PC value and display an error if invalid 2014-11-25 20:34:43 +00:00
Jeremy
e1b81fb931 Fixes PyQt module usage. 2014-11-25 11:54:35 -07:00
Jeremy Grossmann
c91752598c Merge pull request #153 from planctechnologies/dev
Remove image access server references
2014-11-25 11:51:07 -07:00
grossmj
708515925f Do not put space in VirtualBox linked VM names. 2014-11-24 20:11:34 -07:00
Jeremy
a61745f868 Fixes C7200 IO cards insert/remove issues and makes C7200-IO-FE the default. 2014-11-24 17:02:00 -07:00
Jeremy
2ca7af34df Load project from command line. Fixes #151. 2014-11-24 14:21:51 -07:00
jseutter
bd7e6f4e3e Merge pull request #47 from planctechnologies/gns-136
Gns 136 gui won't exit when telnet session open to device
2014-11-24 10:49:51 -07:00
jseutter
32e86d461e Merge pull request #46 from planctechnologies/gns-132
Gns 132 Remove IAS (Image Access Server) code
2014-11-24 10:23:37 -07:00
Massimiliano Pippi
66ea5ad979 close cloud server connections when gns3 exits 2014-11-24 12:47:56 +01:00
Massimiliano Pippi
ebfa80d444 return the list of cloud servers as a property 2014-11-24 12:47:06 +01:00
Massimiliano Pippi
4247cc819d removed references to the IAS server 2014-11-24 12:18:42 +01:00
Massimiliano Pippi
1f9f5bd734 hardcode Ubuntu image in cloud preferences 2014-11-24 12:11:36 +01:00
Jeremy
426d791eea Fixes #48, #152. 2014-11-23 19:26:37 -07:00
grossmj
30d99b812c Merge remote-tracking branch 'origin/master' 2014-11-23 19:04:35 -07:00
grossmj
981e55bc4d Use the Qt binding abstraction layer. 2014-11-23 19:04:11 -07:00
Jeremy
bab1090a25 Use SubprocessError to catch Subprocess exceptions. 2014-11-22 17:45:04 -07:00
Jeremy
84b5181fd8 Support for full screen mode (View -> Fullscreen). 2014-11-21 16:52:06 -07:00
Jeremy
222ebb58c0 Bump to version 1.2.1.dev1 and fixes vboxmanage lookup on Windows. 2014-11-20 19:01:00 -07:00
Jerry Seutter
f5ad3a6a2e Merge branch 'master' into dev 2014-11-18 16:21:39 -07:00
Jerry Seutter
422af60827 Enabling cloud functionality 2014-11-18 16:21:17 -07:00
Jerry Seutter
9127321540 Merge branch 'master' into dev 2014-11-18 16:18:16 -07:00
jseutter
dcae059480 Merge pull request #44 from planctechnologies/gns-130
Gns 130: QThreads missing a parent QObject
2014-11-17 09:24:53 -07:00
jseutter
6d6603e013 Merge pull request #45 from planctechnologies/gns-131
Fix bug where new instances would not build.
2014-11-17 09:24:35 -07:00
Massimiliano Pippi
90fe8078c3 fixed transitions for custom instance states, fixes gns-131 2014-11-17 16:47:56 +01:00
Massimiliano Pippi
367fb32114 instance QThreads passing a parent 2014-11-17 14:24:03 +01:00
Massimiliano Pippi
b2b82afd71 fixed super() call, propagate parent 2014-11-17 14:23:52 +01:00
338 changed files with 29139 additions and 30909 deletions

3
.gitignore vendored
View File

@@ -47,3 +47,6 @@ nosetests.xml
# Gedit Temp Files
*~
# Qt creator
*.autosave

View File

@@ -1,24 +1,33 @@
language: python
#New container architecture
#http://docs.travis-ci.com/user/workers/container-based-infrastructure/
#sudo: false
python:
- "3.3"
- "3.4"
install:
- "pip install -r requirements.txt --use-mirrors"
- "pip install tox"
cache:
apt: true
directories:
- build
script: "python setup.py test"
branches:
only:
- master
before_install:
- sh scripts/prepare_travis.sh
notifications:
email: false
irc:
channels:
- "chat.freenode.net#gns3"
on_success: change
on_failure: always
email:
- julien@gns3.net
#irc:
# channels:
# - "chat.freenode.net#gns3"
# on_success: change
# on_failure: always
install:
- "pip install -r dev-requirements.txt"
script:
- "xvfb-run py.test -vv" # Run tests in a fake X server
# - "pep8 --exclude=build,.git,ui"

313
CHANGELOG Normal file
View File

@@ -0,0 +1,313 @@
# Change Log
## 1.3.3 14/05/2015
* New inline help text for the idle-pc dialog.
* Reactivate auto idle-pc in device contextual menu + save a chosen idle-pc value in template.
* Adds name to the thank you section.
* Prevent users to use VirtualBox linked clone VMs in temporary projects (for now).
* Capture error if the command is invalid
* Cleanup egg cache when exit
* Fix a crash in console when you used non UTF-8 terminal
* Fix crash during save as
* Change title when exporting an IOS startup-config.
* Removes analytics client on closing.
## 1.3.3rc1 07/05/2015
* Catch broken pipe error catched for OSX
* Prevent a topology made for next version to be open in previous version
* Check if the local server is really a local server
* NIO NAT support for QEMU VMs (user mode back-end is used).
* Modified version requirements, so that they require the dependency versions as minimums. Added some more detailed instructions for compilation on Windows.
* Prevent user to enter a None port
* Fix broken pipe error on OSX when frozen
* Prevent the same link created twice on OSX
* Project loading: names and IDs must be assigned to ports on the client side after nodes have been created. Fixes #326. Fixes config updating for IOS router and IOU devices.
* Fix a crash when dropping a .gns3
* Cleanup VPCS code
* Turn off config parser interpolation
* Support unicode characters in regex
* Fixes duplicate entries for "Recent files" on Windows. Fixes #316.
* Fixes VPCS multi-host. Fixes #318.
* Fixes issues when importing configs for IOS, IOU and VPCS. Fixes #314.
* Fixes issue when console setting present in IOS router templates.
* Do not send empty settings when creating VMs.
* Do not set a default private-config when creating a new IOS router template. Fixes #317.
* Refactors how startup-config and private-config are handled for IOS routers.
* Fixes IOU and QEMU tests.
* Makes sure all IOS router settings are saved in the project file & simplify loading from a project.
* Makes sure all VirtualBox VM settings are saved in the project file & simplify loading from a project.
* Makes sure all VPCS VM settings are saved in the project file & simplify loading from a project.
* Makes sure all IOU VM settings are saved in the project file & simplify loading from a project.
* Makes sure all QEMU VM settings are saved in the project file & simplify loading from a project.
* Fix save as by correctly renaming VM uuid project directory
* Fix save as duplicate the .gns3 file
* Fix broken project in Another assert topology fixe
* Prevent user to enter bad hostname
* Fixes an issue when the IOU VM template has a console setting.
* Releasing adding a link. Fixes #235.
* Fix RuntimeError: wrapped C/C++ object of type QNetworkReply has been deleted.
* Do not crash if terminal doesn't support UTF-8
* Fix windows build
* Fixes "show only devices with captures" in the topology summary.
## 1.3.2 28/04/2015
* Fixes bug when IOS configs are not in VM settings.
* Fixes small issue with Qemu VM monitor.
* Fixes issue when only one port is added after a QEMU VM is created. Fixes #296.
* Avoid Cygwin warning with VPCS on Windows.
* Fixes issues with QThread handling.
* Fixes missing title + icon in layer position warning message box.
* Allows the warning message box to be displayed once only when moving an object to a background layer.
* Fixes small issue with old monitor setting.
* Check the config path is set when creating a IOU or IOS router.
* Removes residual link when a NIO cannot be created on the server. Fixes #294.
* Fix VPCS tests
* Do not crash if an antivirus intercept a message and send non UTF-8
* Avoid C++ runtime error when progress dialog is finished.
* Merge remote-tracking branch 'origin/master'
* Move FileCopyThread to FileCopyWorker.
* If project loading fail fallback to real temporary project
* Do not crash if rotation is a string
* I think it's prevent empty topologies
* Explicit utf-8 decoding.
* Fixes rare maximum recursion depth exceeded exception.
* Check for invalid base VM configuration files.
* Catch ValueError exception thrown by mmap(): cannot mmap an empty file.
* Use QThreads the correct way (moveToThread).
* Fixes broken serial console connection.
* Fixes "RuntimeError: wrapped C/C++ object ... has been deleted" exceptions with item links.
* Allows exported config files to be created even when there is no config set on VMs.
* Do not try to export empty VPCS startup configs.
* Prevent issues when a file with a simple number is considered valid JSON.
* Explicit error when mmap throw an invalid argument exception.
* Do not replace invalid utf-8 characters when reading the iourc file (we catch the exception to tell the user this is an invalid file).
* Explicit utf-8 encoding where necessary to avoid Unicode errors on Windows (we require/set an utf-8 locale on other systems).
* Save as dialog opens in the projects directory. Fixes #267.
* Adds Terminal + nc for serial console connections on OSX. Fixes #228.
* Improve warning when non unicode char in iourc
* Crash report not for developers and new key
* Do not crash if we can't change IOU permission
* More checks when decompressing IOS images.
* Warn users that they must provide their router images.
* Display an error and link to the documentation if no router available
* Display print( in std console and Qt Console
* Fix tests and a potential issue where initial_content is not send
* Fix a crash in qemu loading
* Removes unnecessary progress dialog when listing VirtualBox VMs.
* Fixes issues when pushing configs for Dynamips and IOU.
* Allow for empty initial-config path for IOU VM templates. Send IOU VM settings while creating it (POST) and not using the update API call immediately after (PUT).
* Allow for empty startup-config and private-config paths for IOS routers.
* Send QEMU VM settings while creating it (POST) and not using the update API call immediately after (PUT).
* Include resources and tests in pypi packages
* Fix issue during project import on Windows with non local server
## 1.3.1 11/04/2015
* Release
## 1.3.1rc4 09/04/2015
* Fix crash when save as can't create a directory
* Allow less strict dependencies
## 1.3.1rc3 07/04/2015
* Send HTTP errors 400 to the crash report system
## 1.3.1rc2 06/04/2015
* Fix race condition during old project import
## 1.3.1rc1 05/04/2015
* Fix rare occasion when user manage to put text in port field
* Fix a crash when exporting vpcs startup script
* Fix an issue with sending iourc when a topologies is reloaded
* Solve issue when iourc contains non ascii characters
* Handle corrupted zip file with IOS image
* Don't crash if we try to contact a non GNS3 remote server returning JSON
* Skip tests in package
* Check port range
* Add a warning about too much ram for IOS
* Fix crash if project is already closed
* Check if wait for connection thread still running before emitting a signal.
* Check if process files thread still running before emitting a signal.
* Raven is an optionnal dependencies for Debian
* Fix crash if a dumped topology as no node during save as
* Fix: remove old ID references for ATM and Frame-Relay switches.
## 1.3.0 30/03/2015
* Fix etherswitch router
* Fix issues with progress dialog
* Fix save as
## 1.3.0rc2 23/03/2015
* Fix crash when in same occasion the project name is missing
* Update sentry key
* Display adapters in the tooltips in the correct order.
* Open consoles in alphanumerical order.
* Auto idle-PC improvements.
* Adds project id when requesting UDP port.
* Fixes Thread problem. Fixes #229.
* Cancel network requests if the progress dialog itself is canceled. Avoid closing the preferences dialog or any configuration dialog if there is a pending request. Fixes #227.
* Fixes #228 (no alternative interface has been chosen).
* Catch OSError when reading or writing the local server config file.
* Fixes GUI that could not be closed when using an already running local server.
* Save configs when project is committed.
* Del key deletes selected link
* Fix crash is no remote servers is available
## 1.3.0rc1 19/03/2015
* Handle legacy snapshots
* Add server informations for Qemu, VirtualBox and VPCS info boxes
* Support sending IOURC from client to remote servers
* Fixes crash when quick restart the client
* Add 1MB disk for EtherSwitch router templates (to store the vlan database)
* Fixes alignment options to ignore devices labels
* Compute IDLEPC on remote servers
* Prevent using lab instruction in a temporary project
* Display a warning on console if server port is already in used
* Display an error if server version is incorrect
## 1.3.0beta2 13/03/2015
* Alternative local server shutdown (faster GUI closing on Windows).
* Grey out local server preferences if the local server is not activated.
* Adds "template" to the Wizard titles.
* Option to automatically take or not a screenshot when saving a project.
* Support RAM setting for VirtualBox VMs.
* Fixed duplicate VM template entries for Qemu, VirtualBox and IOU.
## 1.3.0beta1 11/03/2015
* New title for VMs/Devices/routers preference pages.
* Deactivate auto idle-pc in contextual menu while we think about a better implementation.
* Optional IOU license key check.
* Relative picture paths are saved in projects.
* Relative path support of IOU, IOS and Qemu images.
* More checks when automatically starting the local server and find an alternative port if needed.
* Support for HDC and HDD disk images in Qemu.
* Fixed base IOS and IOU base configs.
* Fixed GNS3 console issues.
* Renamed server.conf and server.ini to gns3_server.conf and gns3_server.ini respectively.
* Remove remote servers list from module preferences + some other prefences re-factoring.
* Automatically convert old projects on remote servers.
* Bump the progress dialog minimum duration before display to 1000ms.
* Fixed port listing bug with Cloud and Host nodes.
* Fixed Qemu networking.
* Give a warning when a object is move the background layer.
* Option to draw a rectangle when a node is selected.
* New project icon (little yellow indicator).
* Default name for screenshot file is "screenshot".
* Alignment options (horizontal & vertical).
* Fixed import / export of the preferences file.
* Fixed pkg_ressource bug.
* Brought back Qemu preferences page.
* Include SSL cacert file with GNS3 Windows exe and Mac OS App to send crash report using HTTPS.
* Fixed adapter bug with VirtualBox.
* Fixed various errors when a project was not initialized.
## 1.3.0alpha1 03/03/2015
* No more console port and UDP tunneling settings by type of module
* Fixe save
* Settings are stored as JSON
* All communication with servers display a waiting dialog
* Add a revision number in the topology file
* Qemu can run on a server without graphical interface
* Automated crash reports
* You can now copy paste from the GNS 3 console
## 1.2.3 2015/01/17
* Fixed temporary files path setting in general preferences which was not used.
* Fixed missing devices from the node view when they use a remote server.
* Fixed broken ASA kernel/initrd file browsers.
* Fixed bug with WICs interfaces no showing up in the port list.
## 1.2.2 2015/01/16
### Small improvements / new features
* EtherSwitch routers can be added and configured like other IOS routers.
* Change hostname option in the contextual device menu.
* Import & export config options in contextual device menu.
* Auto screenshot when saving a project.
* Auto start project support (you have to manually edit your .gns3 project file).
* Changes to the IOU L2 initial-config (16 Ethernet interfaces, no shutdown by default and 0 serial interfaces).
* Upgraded SuperPutty to version 1.4.0.5 in the all-in-one installer.
* Possibility to apply or not the same text to all selected items when editing notes.
* Base configs are now stored in the GNS3 config directory.
* Short port names in the topology summary.
* Added the VirtualBox VM name in VirtualBox device tooltips.
* Set 5 seconds timeout for local server connections.
* Check if any device runs and warn the user before closing a project.
* Restore the debug level status when starting.
* Automatically select the symbol and category corresponding the edited item in the symbol selection dialog.
* Scale SVG images to icon sizes.
* Console switching from local/remote to remote/local while a VirtualBox VM is running.
* Default Jungle dock location is now bottom right corner.
### Bug fixes
* Fixed the default jungle news loading on Windows.
* Fixed SuperPutty integration (not the default, still have to select it in the preferences).
* Avoid uninitialized nodes to be saved in the project file.
Prevent GNS3 to crash on Windows when importing GNS3 config file.
* Fixed resource access on Mac OS X.
* Fixed transparency or border style restoration for ellipses and rectangles.
* Support spaces in the controller name of VirtualBox clones.
* Ignore Unicode errors when executing vboxmanage.
* Get Windows interface list from the registry if the COM service fails.
## 1.2.1 2014/12/04
* Support for full screen mode (View -> Fullscreen).
* Bundled Qemu 0.13.0 in the Windows all-in-one. Default for all local Qemu VMs.
* Bundled Qemu 0.14.1 in the Mac OS X App. Default for all local Qemu VMs.
* Changed ASA defaults to use Qemu 0.13.0 (on Windows), have 4 interfaces and CPU throttling to 65%.
* Fixed SecureCRT command line when space in the device name.
* Fixed port sorting issues.
* Added default path for VBoxManage on Mac OS X
* Upgraded gns3-converter to version 1.1.1 for Windows all-in-one and Mac OS X DMG.
* New idle-PC field validation.
* Possibility to load the project from command line (or double-click on a project on Windows).
* Fixed Unicode error when using VirtualBox VM with a name containing non-english characters.
## 1.2 2014/11/20
* New GUI styles: charcoal (default) & classic. Changing GUI Preferences
* Integration of GNS3 converter (allows old .net topologies to be opened).
* Allow Qemu VM to have no interface.
* Automatically extract IOS configs when a project is closed.
* Show the cancel button in Wizards on Mac OS X.
* Fix crash on Windows 32-bit.
* Fix "new project" bug when using the GNS3 IOU VM.
* Fix "could not find unused port" WinError 10013 bug
* qemu-system-i386 is the new default on 32-bit platforms.
* Option to deactivate the new project dialog at startup.
* Add "open a project" and "recent projects" buttons to the new project dialog.
* Fix platform detection issue with some Cisco IOS image file name.
* Add delay (default 500 ms) when Console to all nodes.
* Check for duplicate node names in Preferences.
* Fix bug when editing a Qemu VM configured to run on a remote server.
* News dock widget is smaller.
* Fix SecureCRT issue when disconnecting from an IOU device on Windows.
* Update VPCS to version 0.6 in the all-in-one installer.
## 1.1 2014/11/20
* Fixed broken cloud.
* Fixed broken remote server.
* Fixed Qemu binaries not showing up when editing a Qemu VM.
* Fixed EtherSwitch (until we come with a default template for it).
* Serial console for local VirtualBox.
* Warning message when creating an IOU device with a remote server in the Wizard.
* New Idle-PC dialog.

View File

@@ -4,8 +4,8 @@ include INSTALL
include LICENSE
include MANIFEST.in
include tox.ini
recursive-exclude tests *
recursive-include docs *
recursive-include tests *
recursive-include gns3 *
recursive-include resources *
recursive-exclude * __pycache__
recursive-exclude * *.py[co]

View File

@@ -1,7 +1,14 @@
GNS3-gui
========
GNS3 GUI repository (beta stage).
.. image:: https://travis-ci.org/GNS3/gns3-gui.svg?branch=master
:target: https://travis-ci.org/GNS3/gns3-gui
.. image:: https://img.shields.io/pypi/v/gns3-gui.svg
:target: https://pypi.python.org/pypi/gns3-gui
GNS3 GUI repository.
Linux (Debian based)
--------------------
@@ -36,7 +43,23 @@ Finally these commands will install the GUI as well as the rest of the dependenc
Windows
-------
Please use our all-in-one installer.
Please use our `all-in-one installer <https://community.gns3.com/community/software/download>`_ to install the stable build.
If you install via source you need to first install:
- Python (3.3 or above) - https://www.python.org/downloads/windows/
- Pywin32 - https://sourceforge.net/projects/pywin32/
- Qt4 - http://www.qt.io/download-open-source/
- PyQt4 - http://www.riverbankcomputing.com/software/pyqt/download
- PyCrypto (which if you compile from source, requires Visual Studio 2010 with GMP or MPIR libraries)
And finally, call
.. code:: bash
python setup.py install
to install the remaining dependencies.
Mac OS X
--------
@@ -67,3 +90,13 @@ Finally, install both the GUI & server from the source.
python3 setup.py install
Or follow this `HOWTO that uses MacPorts <http://binarynature.blogspot.ca/2014/05/install-gns3-early-release-on-mac-os-x.html>`_.
Developement
-------------
If you want to update the interface, modify the .ui files using QT tools. And:
.. code:: bash
cd scripts
python build_pyqt.py

View File

@@ -1,4 +1,7 @@
-rrequirements.txt
pep8
pytest
pytest-pythonpath # useful for running tests outside tox
pytest-timeout
pytest-capturelog

View File

@@ -141,7 +141,7 @@ class BaseCloudCtrl(object):
self._handle_exception(status, error_text)
else:
log.error("create_instance method raised an exception: {}".format(e))
log.error('image id {}'.format(image))
log.error('image id {}'.format(image_id))
def delete_instance(self, instance):
""" Delete the specified instance. Returns True or False. """
@@ -170,11 +170,7 @@ class BaseCloudCtrl(object):
def list_instances(self):
""" Return a list of instances in the current region. """
try:
return self.driver.list_nodes()
except Exception as e:
log.error("list_instances returned an error: {}".format(e))
return self.driver.list_nodes()
def create_key_pair(self, name):
""" Create and return a new Key Pair. """

View File

@@ -1,45 +1,67 @@
""" Exception classes for CloudCtrl classes. """
class ApiError(Exception):
""" Raised when the server returns 500 Compute Error. """
pass
class BadRequest(Exception):
""" Raised when the server returns 400 Bad Request. """
pass
class ComputeFault(Exception):
""" Raised when the server returns 400|500 Compute Fault. """
pass
class Forbidden(Exception):
""" Raised when the server returns 403 Forbidden. """
pass
class ItemNotFound(Exception):
""" Raised when the server returns 404 Not Found. """
pass
class KeyPairExists(Exception):
""" Raised when the server returns 409 Conflict Key pair exists. """
pass
class MethodNotAllowed(Exception):
""" Raised when the server returns 405 Method Not Allowed. """
pass
class OverLimit(Exception):
""" Raised when the server returns 413 Over Limit. """
pass
class ServerCapacityUnavailable(Exception):
""" Raised when the server returns 503 Server Capacity Uavailable. """
pass
class ServiceUnavailable(Exception):
""" Raised when the server returns 503 Service Unavailable. """
pass
class Unauthorized(Exception):
""" Raised when the server returns 401 Unauthorized. """
pass

View File

@@ -42,11 +42,9 @@ class RackspaceCtrl(BaseCloudCtrl):
""" Controller class for interacting with Rackspace API. """
def __init__(self, username, api_key, gns3_ias_url):
def __init__(self, username, api_key, *args, **kwargs):
super(RackspaceCtrl, self).__init__(username, api_key)
self.gns3_ias_url = gns3_ias_url
# set this up so it can be swapped out with a mock for testing
self.post_fn = requests.post
self.driver_cls = get_driver(Provider.RACKSPACE)
@@ -225,55 +223,6 @@ class RackspaceCtrl(BaseCloudCtrl):
self.region = region
return True
def _get_shared_images(self, username, region, gns3_version):
"""
Given a GNS3 version, ask gns3-ias to share compatible images
Response:
[{"created_at": "", "schema": "", "status": "", "member_id": "", "image_id": "", "updated_at": ""},]
or, if access was already asked
[{"image_id": "", "member_id": "", "status": "ALREADYREQUESTED"},]
"""
endpoint = self.gns3_ias_url+"/images/grant_access"
params = {
"user_id": username,
"user_region": region.upper(),
"gns3_version": gns3_version,
}
try:
response = requests.get(endpoint, params=params)
except requests.ConnectionError:
raise ApiError("Unable to connect to IAS")
status = response.status_code
if status == 200:
return response.json()
elif status == 404:
raise ItemNotFound()
else:
raise ApiError("IAS status code: %d" % status)
def list_images(self):
"""
Return a dictionary containing RackSpace server images
retrieved from gns3-ias server
"""
if not (self.tenant_id and self.region):
return {}
try:
shared_images = self._get_shared_images(self.tenant_id, self.region, __version__)
images = {}
for i in shared_images:
images[i['image_id']] = i['image_name']
return images
except ItemNotFound:
return {}
except ApiError as e:
log.error('Error while retrieving image list: %s' % e)
return {}
def get_image(self, image_id):
return self.driver.get_image(image_id)
@@ -290,12 +239,11 @@ def get_provider(cloud_settings):
username = cloud_settings['cloud_user_name']
apikey = cloud_settings['cloud_api_key']
region = cloud_settings['cloud_region']
ias_url = cloud_settings['gns3_ias_url']
except KeyError as e:
log.error("Unable to create cloud provider: {}".format(e))
return
provider = RackspaceCtrl(username, apikey, ias_url)
provider = RackspaceCtrl(username, apikey)
if not provider.authenticate():
log.error("Authentication failed for cloud provider")

View File

@@ -4,16 +4,14 @@ import json
from socket import error as socket_error
import logging
import os
import select
import tempfile
import time
import zipfile
from PyQt4.QtCore import QThread
from PyQt4.QtCore import pyqtSignal
from ..qt import QtCore
from .exceptions import KeyPairExists
from .rackspace_ctrl import RackspaceCtrl, get_provider
from .rackspace_ctrl import get_provider
from ..topology import Topology
from ..servers import Servers
@@ -28,11 +26,14 @@ def ssh_client(host, key_string):
"""
import paramiko
class AllowAndForgetPolicy(paramiko.MissingHostKeyPolicy):
"""
Custom policy for server host keys: we simply accept the key
the server sent to us without storing it.
"""
def missing_host_key(self, *args, **kwargs):
"""
According to MissingHostKeyPolicy protocol, to accept
@@ -48,21 +49,25 @@ def ssh_client(host, key_string):
client.connect(hostname=host, username="root", pkey=key)
yield client
except socket_error as e:
log.error("SSH connection error to {}: {}".format(host, e))
log.debug("SSH connection socket error to {}: {}".format(host, e))
yield None
except Exception as e:
log.debug("SSH connection error to {}: {}".format(host, e))
yield None
finally:
client.close()
class ListInstancesThread(QThread):
class ListInstancesThread(QtCore.QThread):
"""
Helper class to retrieve data from the provider in a separate thread,
avoid freezing the gui
"""
instancesReady = pyqtSignal(object)
instancesReady = QtCore.pyqtSignal(object)
def __init__(self, parent, provider):
super(QThread, self).__init__(parent)
super().__init__(parent)
self._provider = provider
def run(self):
@@ -74,14 +79,15 @@ class ListInstancesThread(QThread):
log.info('list_instances error: {}'.format(e))
class CreateInstanceThread(QThread):
class CreateInstanceThread(QtCore.QThread):
"""
Helper class to create instances in a separate thread
"""
instanceCreated = pyqtSignal(object, object)
instanceCreated = QtCore.pyqtSignal(object, object)
def __init__(self, parent, provider, name, flavor_id, image_id):
super(QThread, self).__init__(parent)
super().__init__(parent)
self._provider = provider
self._name = name
self._flavor_id = flavor_id
@@ -104,14 +110,15 @@ class CreateInstanceThread(QThread):
self.instanceCreated.emit(i, k)
class DeleteInstanceThread(QThread):
class DeleteInstanceThread(QtCore.QThread):
"""
Helper class to remove an instance in a separate thread
"""
instanceDeleted = pyqtSignal(object)
instanceDeleted = QtCore.pyqtSignal(object)
def __init__(self, parent, provider, instance):
super(QThread, self).__init__(parent)
super().__init__(parent)
self._provider = provider
self._instance = instance
@@ -120,12 +127,13 @@ class DeleteInstanceThread(QThread):
self.instanceDeleted.emit(self._instance)
class StartGNS3ServerThread(QThread):
class StartGNS3ServerThread(QtCore.QThread):
"""
Perform an SSH connection to the instances in a separate thread,
outside the GUI event loop, and start GNS3 server
"""
gns3server_started = pyqtSignal(str, str, str)
gns3server_started = QtCore.pyqtSignal(str, str, str)
# This is for testing without pushing to github
# commands = '''
@@ -162,15 +170,18 @@ cd /opt/gns3/gns3-server; git checkout dev; git pull
cd /opt/gns3/gns3-server; pip3 install -r dev-requirements.txt
cd /opt/gns3/gns3-server; python3 ./setup.py install
ln -sf /usr/bin/dynamips /usr/local/bin/dynamips
wget 'https://github.com/GNS3/iouyap/releases/download/0.95/iouyap.tar.gz'
tar xzf iouyap.tar.gz -C /usr/local/bin
wget 'https://github.com/GNS3/iouyap/releases/download/0.95/iouyap-64-bit.tar.gz'
tar xzf iouyap-64-bit.tar.gz -C /usr/local/bin
python -c 'import struct; open("/etc/hostid", "w").write(struct.pack("i", 00000000))'
hostname gns3-iouvm # set hostname for iou
wget -O vpcs http://sourceforge.net/projects/vpcs/files/0.6/vpcs_0.6_Linux64/download
cp vpcs /usr/local/bin/vpcs
chmod a+x /usr/local/bin/vpcs
killall python3 gns3server gns3dms
'''
def __init__(self, parent, host, private_key_string, server_id, username, api_key, region, dead_time):
super(QThread, self).__init__(parent)
super().__init__(parent)
self._host = host
self._private_key_string = private_key_string
self._server_id = server_id
@@ -203,7 +214,6 @@ killall python3 gns3server gns3dms
log.debug('stderr: {}'.format(stderr_data.decode('utf-8')))
return stdout_data, stderr_data
def run(self):
# We might be attempting a connection before the instance is fully booted, so retry
# when the ssh connection fails.
@@ -240,16 +250,17 @@ killall python3 gns3server gns3dms
self.gns3server_started.emit(str(self._server_id), str(self._host), str(response))
class WSConnectThread(QThread):
class WSConnectThread(QtCore.QThread):
"""
Establish a websocket connection with the remote gns3server
instance. Run outside the GUI event loop.
"""
established = pyqtSignal(str)
established = QtCore.pyqtSignal(str)
def __init__(self, parent, provider, server_id, host, port, ca_file,
auth_user, auth_password, ssh_pkey, instance_id):
super(QThread, self).__init__(parent)
super().__init__(parent)
self._provider = provider
self._server_id = server_id
self._host = host
@@ -278,18 +289,19 @@ class WSConnectThread(QThread):
self.established.emit(self._server_id)
class UploadProjectThread(QThread):
class UploadProjectThread(QtCore.QThread):
"""
Zip and Upload project to the cloud
"""
# signals to update the progress dialog.
error = pyqtSignal(str, bool)
completed = pyqtSignal()
update = pyqtSignal(int)
error = QtCore.pyqtSignal(str, bool)
completed = QtCore.pyqtSignal()
update = QtCore.pyqtSignal(int)
def __init__(self, cloud_settings, project_path, images_path):
super().__init__()
def __init__(self, parent, cloud_settings, project_path, images_path):
super().__init__(parent)
self.cloud_settings = cloud_settings
self.project_path = project_path
self.images_path = images_path
@@ -318,7 +330,7 @@ class UploadProjectThread(QThread):
self.completed.emit()
except Exception as e:
log.exception("Error exporting project to cloud")
self.error.emit("Error exporting project: {}".format(str(e)), True)
self.error.emit("Error exporting project: {}".format(e), True)
def zip_project_dir(self):
"""
@@ -353,41 +365,59 @@ class UploadProjectThread(QThread):
self.quit()
class UploadFilesThread(QThread):
"""
Upload multiple files to cloud files
class UploadFilesThread(QtCore.QThread):
uploads - A list of 2-tuples of (local_src_path, remote_dst_path)
"""
Uploads files to cloud files
:param cloud_settings:
:param files_to_upload: list of tuples of (file path, file name to save in cloud)
"""
completed = pyqtSignal()
error = QtCore.pyqtSignal(str, bool)
completed = QtCore.pyqtSignal()
update = QtCore.pyqtSignal(int)
def __init__(self, parent, cloud_settings, uploads):
super(QThread, self).__init__(parent)
def __init__(self, parent, cloud_settings, files_to_upload):
super().__init__(parent)
self._cloud_settings = cloud_settings
self._uploads = uploads
self._files_to_upload = files_to_upload
def run(self):
for src, dst in self._uploads:
log.debug('Upload from {} to {}'.format(src, dst))
provider = get_provider(self._cloud_settings)
provider.upload_file(src, dst)
log.debug('Upload image completed')
self.update.emit(0)
try:
for i, file_to_upload in enumerate(self._files_to_upload):
provider = get_provider(self._cloud_settings)
log.debug('Uploading image {} to cloud as {}'.format(file_to_upload[0], file_to_upload[1]))
provider.upload_file(file_to_upload[0], file_to_upload[1])
self.update.emit((i + 1) * 100 / len(self._files_to_upload))
log.debug('Uploading image completed')
except Exception as e:
log.exception("Error uploading images to cloud")
self.error.emit("Error uploading images: {}".format(e), True)
self.completed.emit()
def stop(self):
self.quit()
class DownloadProjectThread(QtCore.QThread):
class DownloadProjectThread(QThread):
"""
Downloads project from cloud storage
"""
# signals to update the progress dialog.
error = pyqtSignal(str, bool)
completed = pyqtSignal()
update = pyqtSignal(int)
error = QtCore.pyqtSignal(str, bool)
completed = QtCore.pyqtSignal()
update = QtCore.pyqtSignal(int)
def __init__(self, cloud_project_file_name, project_dest_path, images_dest_path, cloud_settings):
super().__init__()
def __init__(self, parent, cloud_project_file_name, project_dest_path, images_dest_path, cloud_settings):
super().__init__(parent)
self.project_name = cloud_project_file_name
self.project_dest_path = project_dest_path
self.images_dest_path = images_dest_path
@@ -427,24 +457,66 @@ class DownloadProjectThread(QThread):
self.completed.emit()
except Exception as e:
log.exception("Error importing project from cloud")
self.error.emit("Error importing project: {}".format(str(e)), True)
self.error.emit("Error importing project: {}".format(e), True)
def stop(self):
self.quit()
class DeleteProjectThread(QThread):
class DownloadImagesThread(QtCore.QThread):
"""
Downloads multiple files from cloud files
"""
error = QtCore.pyqtSignal(str, bool)
completed = QtCore.pyqtSignal()
update = QtCore.pyqtSignal(int)
def __init__(self, cloud_settings, images_dest_path, image_names):
super().__init__()
self._cloud_settings = cloud_settings
self._images_dest_path = images_dest_path
self._image_names = image_names
def run(self):
self.update.emit(0)
try:
provider = get_provider(self._cloud_settings)
image_names_in_cloud = provider.find_storage_image_names(self._image_names)
for i, image in enumerate(self._image_names):
dest_path = os.path.join(self._images_dest_path, *image_names_in_cloud[image].split('/')[1:])
if not os.path.exists(os.path.dirname(dest_path)):
os.makedirs(os.path.dirname(dest_path))
provider.download_file(image_names_in_cloud[image], dest_path)
self.update.emit(i * 100 / len(self._image_names))
self.completed.emit()
except Exception as e:
log.exception("Error importing project from cloud")
self.error.emit("Error importing project: {}".format(e), True)
def stop(self):
self.quit()
class DeleteProjectThread(QtCore.QThread):
"""
Deletes project from cloud storage
"""
# signals to update the progress dialog.
error = pyqtSignal(str, bool)
completed = pyqtSignal()
update = pyqtSignal(int)
error = QtCore.pyqtSignal(str, bool)
completed = QtCore.pyqtSignal()
update = QtCore.pyqtSignal(int)
def __init__(self, project_file_name, cloud_settings):
super().__init__()
def __init__(self, parent, project_file_name, cloud_settings):
super().__init__(parent)
self.project_file_name = project_file_name
self.cloud_settings = cloud_settings
@@ -455,7 +527,7 @@ class DeleteProjectThread(QThread):
self.completed.emit()
except Exception as e:
log.exception("Error deleting project")
self.error.emit("Error deleting project: {}".format(str(e)), True)
self.error.emit("Error deleting project: {}".format(e), True)
def stop(self):
pass

252
gns3/cloud_builder.py Normal file
View File

@@ -0,0 +1,252 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
"""
from PyQt4.QtCore import pyqtSignal
from PyQt4.QtCore import QThread
import ast
import logging
import os
import time
from .cloud.utils import ssh_client
from .cloud.exceptions import KeyPairExists
from .servers import Servers
from .topology import Topology
log = logging.getLogger(__name__)
class CloudBuilder(QThread):
"""
"""
# Notify with progress amount and instance_id
progressUpdate = pyqtSignal(object, str)
# Notify with current state and instance_id
stateChange = pyqtSignal(object, str)
# Notify when instance is ready with instance_id
buildComplete = pyqtSignal(str)
# Notify when the instance has been created with instance and keypair
instanceCreated = pyqtSignal(object, object)
# Notify when the public ip is available with ip and instance_id
instanceHasIP = pyqtSignal(str, str)
# Notify when instance id exists with builder and instance_id
instanceIdExists = pyqtSignal(object, str)
def __init__(self, parent, cloud_provider, ca_dir):
super(QThread, self).__init__(parent)
# Store our parent so it can be passed to threads we spawn.
self._parent = parent
self._provider = cloud_provider
self._ca_dir = ca_dir
self._start_at_create = False
self._start_at_setup = False
self._instance = None
def startAtCreate(self, instance_name, flavor_id, image_id):
self._start_at_create = True
self._instance_name = instance_name
self._flavor_id = flavor_id
self._image_id = image_id
def startAtSetup(self, instance, keypair):
self._start_at_setup = True
self._instance = instance
self._key_pair = keypair
def run(self):
try:
log.debug('CloudBuilder.run')
if self._start_at_create:
log.debug('CloudBuilder._start_at_create')
self._createInstance(self._provider, self._instance_name, self._flavor_id,
self._image_id)
log.debug('got here 3')
if self._start_at_setup:
log.debug('CloudBuilder start at setup')
self._instanceCreated(self._instance, self._key_pair)
except Exception:
log.exception("CloudBuilder trapped an exception:")
log.error('CloudBuilder stopped in error state.')
def _createInstance(self, provider, name, flavor_id, image_id):
log.debug("Creating cloud keypair with name {}".format(name))
key_pair = None
while key_pair is None:
try:
key_pair = provider.create_key_pair(name)
except KeyPairExists:
log.debug("Deleting old key pair with name {}.".format(name))
self._provider.delete_key_pair_by_name(name)
except Exception as e:
log.debug("create_key_pair exception {}".format(e))
log.debug("Creating cloud server with name {}".format(name))
instance = None
while instance is None:
try:
instance = self._provider.create_instance(name, flavor_id, image_id, key_pair)
except Exception as e:
log.debug("create_instance exception {}".format(e))
log.debug("Cloud server {} created".format(name))
self._instanceCreated(instance, key_pair)
def _instanceCreated(self, instance, key_pair):
log.debug('CloudBuilder._instanceCreated {}'.format(instance.id))
self._instance = instance
self._instance_id = instance.id
self._key_pair = key_pair
self.instanceIdExists.emit(self, instance.id)
self.instanceCreated.emit(instance, key_pair)
self._waitForPublicIP()
def _waitForPublicIP(self):
public_ip = None
while public_ip is None:
time.sleep(10)
try:
instance = self._provider.get_instance(self._instance)
# Look for public ip address
for ip in instance.public_ips:
# Don't use the ipv6 address
if ':' not in ip:
public_ip = ip
break
except Exception as e:
log.debug('list_instances error: {}'.format(e))
# updated info, keep it.
self._instance = instance
self._public_ip = public_ip
self.instanceHasIP.emit(self._public_ip, self._instance.id)
time.sleep(60)
self._startGNS3Server(1800)
def _startGNS3Server(self, dead_time):
commands = '''
DEBIAN_FRONTEND=noninteractive dpkg --configure -a
DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386
DEBIAN_FRONTEND=noninteractive apt-get -y update
DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confnew" --force-yes -fuy dist-upgrade
DEBIAN_FRONTEND=noninteractive apt-get -y install git python3-setuptools python3-netifaces python3-pip python3-zmq dynamips qemu-system
DEBIAN_FRONTEND=noninteractive apt-get -y install libc6:i386 libstdc++6:i386 libssl1.0.0:i386
ln -s /lib/i386-linux-gnu/libcrypto.so.1.0.0 /lib/i386-linux-gnu/libcrypto.so.4
mkdir -p /opt/gns3
cd /opt/gns3; git clone https://github.com/planctechnologies/gns3-server.git
cd /opt/gns3/gns3-server; git checkout dev; git pull
cd /opt/gns3/gns3-server; pip3 install -r dev-requirements.txt
cd /opt/gns3/gns3-server; python3 ./setup.py install
ln -sf /usr/bin/dynamips /usr/local/bin/dynamips
wget 'https://github.com/GNS3/iouyap/releases/download/0.95/iouyap.tar.gz'
tar xzf iouyap.tar.gz -C /usr/local/bin
python -c 'import struct; open("/etc/hostid", "w").write(struct.pack("i", 00000000))'
hostname gns3-iouvm # set hostname for iou
wget 'http://downloads.sourceforge.net/project/vpcs/0.6/vpcs_0.6_Linux64'
cp vpcs_0.6_Linux64 /usr/local/bin/vpcs
chmod a+x /usr/local/bin/vpcs
killall python3 gns3server gns3dms
'''
def exec_command(client, cmd, wait_time=-1):
cmd += '; exit $?'
stdout_data = b''
stderr_data = b''
log.debug('cmd: {}'.format(cmd))
# Send the command (non-blocking)
stdin, stdout, stderr = client.exec_command(cmd)
# Wait for the command to terminate
wait = int(wait_time)
while not stdout.channel.exit_status_ready() and wait != 0:
time.sleep(1)
wait -= 1
stdout_data = stdout.read()
stderr_data = stderr.read()
log.debug('exit status: {}'.format(stdout.channel.exit_status))
log.debug('stdout: {}'.format(stdout_data.decode('utf-8')))
log.debug('stderr: {}'.format(stderr_data.decode('utf-8')))
return stdout_data, stderr_data
# We might be attempting a connection before the instance is fully booted, so retry
# when the ssh connection fails.
ssh_connected = False
response = None
while not ssh_connected:
with ssh_client(self._public_ip, self._key_pair.private_key) as client:
if client is None:
time.sleep(1)
continue
ssh_connected = True
for cmd in [l for l in commands.splitlines() if l.strip()]:
exec_command(client, cmd)
data = {
'instance_id': self._instance_id,
'cloud_user_name': self._provider.username,
'cloud_api_key': self._provider.api_key,
'cloud_region': self._provider.region,
'dead_time': dead_time,
}
# TODO: Properly escape the data portion of the command line
start_cmd = '/usr/bin/python3 /opt/gns3/gns3-server/gns3server/start_server.py -d -v --ip={} --data="{}" 2>/tmp/gns3-stderr.log'.format(self._public_ip, data)
stdout, stderr = exec_command(client, start_cmd, wait_time=15)
response = stdout.decode('utf-8')
log.debug(response)
data = ast.literal_eval(response)
# TODO: have the server return the port it is running on
port = 8000
username = data['WEB_USERNAME']
password = data['WEB_PASSWORD']
ssl_cert = ''.join(data['SSL_CRT'])
ca_filename = 'cloud_server_{}.crt'.format(self._public_ip)
ca_dir = self._ca_dir
ca_file = os.path.join(ca_dir, ca_filename)
try:
os.makedirs(ca_dir)
except FileExistsError:
pass
with open(ca_file, 'wb') as ca_fh:
ca_fh.write(ssl_cert.encode('utf-8'))
topology = Topology.instance()
top_instance = topology.getInstance(self._instance_id)
top_instance.set_later_attributes(self._public_ip, port, ssl_cert, ca_file)
servers = Servers.instance()
server = servers.getCloudServer(self._public_ip, port, ca_file, username, password,
self._key_pair.private_key, self._instance_id)
servers.save()
log.debug('Cloud server gns3server started.')
self.buildComplete.emit(self._instance_id)

View File

@@ -1,25 +1,20 @@
# -*- coding: utf-8 -*-
import ast
from collections import namedtuple
import logging
import os
from PyQt4.QtGui import QWidget
from PyQt4.QtGui import QIcon
from PyQt4.QtGui import QMenu
from PyQt4.QtGui import QAction
from PyQt4.QtGui import QInputDialog
from PyQt4.QtCore import QAbstractTableModel
from PyQt4.QtCore import QModelIndex
from PyQt4.QtCore import QTimer
from PyQt4.QtCore import pyqtSignal
from PyQt4.Qt import Qt
import json
from .cloud.utils import (ListInstancesThread, CreateInstanceThread, DeleteInstanceThread,
StartGNS3ServerThread, WSConnectThread)
from libcloud.compute.types import NodeState
from .qt import QtCore, QtGui
from .cloud.utils import (ListInstancesThread, DeleteInstanceThread)
from .topology import Topology
from .servers import Servers
# this widget was promoted on Creator, must use absolute imports
from gns3.ui.cloud_inspector_view_ui import Ui_CloudInspectorView
from gns3.cloud_builder import CloudBuilder
from gns3.cloud_instances import CloudInstances
log = logging.getLogger(__name__)
@@ -28,18 +23,21 @@ POLLING_TIMER = 10000 # in milliseconds
class RunningInstanceState(NodeState):
"""
GNS3 states for running instances
"""
GNS3SERVER_STARTING = 10
GNS3SERVER_STARTED = 11
WS_CONNECTED = 12
GNS3SERVER_STARTING = -1
GNS3SERVER_STARTED = -2
WS_CONNECTED = -3
class InstanceTableModel(QAbstractTableModel):
class InstanceTableModel(QtCore.QAbstractTableModel):
"""
A custom table model storing data of cloud instances
"""
def __init__(self, *args, **kwargs):
super(InstanceTableModel, self).__init__(*args, **kwargs)
self._header_data = ['Instance', '', 'Size', 'Devices'] # status has an empty header label
@@ -80,12 +78,12 @@ class InstanceTableModel(QAbstractTableModel):
instance = self._instances.get(self._ids[index.row()])
col = index.column()
if role == Qt.DecorationRole:
if role == QtCore.Qt.DecorationRole:
if col == 1:
# status
return QIcon(self._get_status_icon_path(instance))
return QtGui.QIcon(self._get_status_icon_path(instance))
elif role == Qt.DisplayRole:
elif role == QtCore.Qt.DisplayRole:
if col == 0:
# name
return instance.name
@@ -112,7 +110,7 @@ class InstanceTableModel(QAbstractTableModel):
return None
def headerData(self, section, orientation, role=None):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
try:
return self._header_data[section]
except IndexError:
@@ -120,9 +118,9 @@ class InstanceTableModel(QAbstractTableModel):
return super(InstanceTableModel, self).headerData(section, orientation, role)
def addInstance(self, instance):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
if not len(self._instances):
self.beginInsertColumns(QModelIndex(), 0, self._width-1)
self.beginInsertColumns(QtCore.QModelIndex(), 0, self._width - 1)
self.endInsertColumns()
self._ids.append(instance.id)
self._instances[instance.id] = instance
@@ -143,7 +141,7 @@ class InstanceTableModel(QAbstractTableModel):
def removeInstanceById(self, instance_id):
try:
index = self._ids.index(instance_id)
self.beginRemoveRows(QModelIndex(), index, index)
self.beginRemoveRows(QtCore.QModelIndex(), index, index)
del self._instances[instance_id]
del self._ids[index]
self.endRemoveRows()
@@ -160,7 +158,7 @@ class InstanceTableModel(QAbstractTableModel):
for field in field_names:
setattr(current, field, getattr(instance, field))
first_index = self.createIndex(index, 0)
last_index = self.createIndex(index, self.columnCount()-1)
last_index = self.createIndex(index, self.columnCount() - 1)
self.dataChanged.emit(first_index, last_index)
else:
self.addInstance(instance)
@@ -169,7 +167,8 @@ class InstanceTableModel(QAbstractTableModel):
return self._instances.get(instance_id, None)
class CloudInspectorView(QWidget, Ui_CloudInspectorView):
class CloudInspectorView(QtGui.QWidget, Ui_CloudInspectorView):
"""
Table view showing data coming from InstanceTableModel
@@ -177,10 +176,10 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
instanceSelected(int) Emitted when users click and select an instance on the inspector.
Param int is the ID of the instance
"""
instanceSelected = pyqtSignal(str)
instanceSelected = QtCore.pyqtSignal(str)
def __init__(self, parent):
super(QWidget, self).__init__(parent)
super(QtGui.QWidget, self).__init__(parent)
self.setupUi(self)
self._provider = None
@@ -191,21 +190,21 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
self._model = InstanceTableModel() # shortcut for self.uiInstancesTableView.model()
self.uiInstancesTableView.setModel(self._model)
self.uiInstancesTableView.verticalHeader().hide()
self.uiInstancesTableView.setContextMenuPolicy(Qt.CustomContextMenu)
self.uiInstancesTableView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.uiInstancesTableView.horizontalHeader().setStretchLastSection(True)
# connections
self.uiInstancesTableView.customContextMenuRequested.connect(self._contextMenu)
self.uiInstancesTableView.clicked.connect(self._rowChanged)
self.uiCreateInstanceButton.clicked.connect(self._create_new_instance)
self._pollingTimer = QTimer(self)
self._pollingTimer = QtCore.QTimer(self)
self._pollingTimer.timeout.connect(self._polling_slot)
# map flavor ids to combobox indexes
self.flavor_index_id = []
# TODO: Delete me
self._running = {}
# A dictionary of {image_id, CloudBuilder}
self._builders = {}
def _get_flavor_index(self, flavor_id):
try:
@@ -213,17 +212,17 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
except ValueError:
return -1
def load(self, main_win, instances):
def load(self, main_win, instance_ids):
"""
Fill the model data layer with instances retrieved through libcloud
Fill the model data layer with instance info loaded from the topology file
"""
self._main_window = main_win
self._provider = main_win.cloudProvider
self._settings = main_win.cloudSettings()
log.info('CloudInspectorView.load')
for i in instances:
self._project_instances_id.append(i["id"])
for instance_id in instance_ids:
self._project_instances_id.append(instance_id)
update_thread = ListInstancesThread(self, self._provider)
update_thread.instancesReady.connect(self._update_model)
@@ -253,10 +252,10 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
def _contextMenu(self, pos):
# create actions
delete_action = QAction("Delete", self)
delete_action = QtGui.QAction("Delete", self)
delete_action.triggered.connect(self._deleteSelectedInstance)
# create context menu and add actions
menu = QMenu(self.uiInstancesTableView)
menu = QtGui.QMenu(self.uiInstancesTableView)
menu.addAction(delete_action)
# show the menu
menu.popup(self.uiInstancesTableView.viewport().mapToGlobal(pos))
@@ -269,12 +268,29 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
if len(sel) and self._provider is not None:
index = sel[0].row()
instance = self._model.getInstance(index)
delete_thread = DeleteInstanceThread(self, self._provider, instance)
delete_thread.instanceDeleted.connect(self._main_window.remove_instance_from_project)
delete_thread.start()
instance.name = 'Deleting...'
self._model.updateInstanceFields(instance, ['name',])
# warn user this is destructive
msg = "Do you want to remove the instance and any devices running on it?"
proceed = QtGui.QMessageBox.question(self, 'Warning', msg,
QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
if proceed == QtGui.QMessageBox.Yes:
# disconnect and remove the server
servers = Servers.instance()
cs = servers.cloudServerById(instance.id)
if cs is not None:
servers.removeCloudServer(cs)
# remove instance from the the topology
topology = Topology.instance()
topology.removeInstance(instance.id)
delete_thread = DeleteInstanceThread(self, self._provider, instance)
delete_thread.instanceDeleted.connect(self._main_window.remove_instance_from_project)
delete_thread.start()
instance.name = 'Deleting...'
self._model.updateInstanceFields(instance, ['name'])
def _rowChanged(self, index):
"""
@@ -301,126 +317,60 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
update_thread.instancesReady.connect(self._update_model)
update_thread.start()
def _gns3server_started_slot(self, id, host_ip, start_response):
def _instanceBuilt(self, id):
"""
This slot is called when the StartGNS3ServerThread succesfully started
the server.
:param id: the id of the instance
:param host_ip: the host ip of the instance
:param start_response: the output of the server start script on the remote host
This slot is called when instance has finished building.
"""
# instance state transition: GNS3SERVER_STARTING --> GNS3SERVER_STARTED
instance = self._model.getInstanceById(id)
instance.state = RunningInstanceState.GNS3SERVER_STARTED
self._model.updateInstanceFields(instance, ['state'])
data = ast.literal_eval(start_response)
# TODO: have the server return the port it is running on
port = 8000
username = data['WEB_USERNAME']
password = data['WEB_PASSWORD']
ssl_cert = ''.join(data['SSL_CRT'])
ca_filename = 'cloud_server_{}.crt'.format(host_ip)
# TODO: Move this directory into projectSettings.
ca_dir = os.path.join(self._main_window.projectSettings()["project_files_dir"], "keys")
ca_file = os.path.join(ca_dir, ca_filename)
try:
os.makedirs(ca_dir)
except FileExistsError:
pass
with open(ca_file, 'wb') as ca_fh:
ca_fh.write(ssl_cert.encode('utf-8'))
topology = Topology.instance()
top_instance = topology.getInstance(id)
top_instance.set_later_attributes(host_ip, port, ssl_cert, ca_file)
ssh_pkey = top_instance.private_key
log.debug('Cloud server gns3server started.')
wss_thread = WSConnectThread(self, self._provider, id, host_ip, port, ca_file,
username, password, ssh_pkey, id)
wss_thread.established.connect(self._wss_connected_slot)
wss_thread.start()
def _wss_connected_slot(self, id):
"""
This slot is called when the WSConnectThread successfully connected to
the websocket on the remote host
"""
# instance state transition: GNS3SERVER_STARTED --> WS_CONNECTED
instance = self._model.getInstanceById(id)
instance.state = RunningInstanceState.WS_CONNECTED
self._model.updateInstanceFields(instance, ['state'])
def _get_public_ip(self, ip_list):
"""
Pick the ipv4 address from the list of ip addresses that the instance
has.
"""
for ip in ip_list:
log.debug('Cloud server ip {}'.format(ip))
# Don't use the ipv6 address
if ':' not in ip:
log.debug('Chose {} as public ip'.format(ip))
return ip
return None
if self._main_window.loading_cloud_project:
project = self._main_window.project()
path = project.topologyPath()
with open(path, "r") as f:
json_topology = json.load(f)
topology = Topology.instance()
topology.load(json_topology)
self._main_window.loading_cloud_project = False
def _update_model(self, instances):
if not instances:
return
# Filter instances to only those in the current project
project_instances = [i for i in instances if i.id in self._project_instances_id]
# populate underlying model if this is the first call
if self._model.rowCount() == 0 and len(instances) > 0:
self._populate_model(instances)
if self._model.rowCount() == 0 and len(project_instances) > 0:
self._populate_model(project_instances)
self._rebuild_instances(project_instances)
instance_manager = CloudInstances.instance()
instance_manager.update_instances(instances)
# filter instances to only those in the current project
project_instances = [i for i in instances if i.id in self._project_instances_id]
for i in project_instances:
if i.state != RunningInstanceState.RUNNING:
self._model.updateInstanceFields(i, ['state'])
# cleanup removed instances
# Clean up removed instances
real = set(i.id for i in project_instances)
current = set(self._model.instanceIds)
for i in current.difference(real):
self._model.removeInstanceById(i)
self.uiInstancesTableView.resizeColumnsToContents()
# start gns3server if needed
# Update instance status
for i in project_instances:
# get the real instance state from self._model
# get the customized instance state from self._model
model_instance = self._model.getInstanceById(i.id)
if model_instance.state == RunningInstanceState.RUNNING:
# instance state transition: RUNNING --> GNS3SERVER_STARTING
model_instance.state = RunningInstanceState.GNS3SERVER_STARTING
self._model.updateInstanceFields(model_instance, ['state'])
# start GNS3 server and deadman switch
public_ip = self._get_public_ip(i.public_ips)
instance_manager.update_host_for_instance(i.id, public_ip)
topology_instance = instance_manager.get_instance(i.id)
ssh_thread = StartGNS3ServerThread(
self, public_ip, topology_instance.private_key, i.id,
self._provider.username, self._provider.api_key, self._provider.region,
1800)
ssh_thread.gns3server_started.connect(self._gns3server_started_slot)
ssh_thread.start()
# update model instance state if needed
if i.state != RunningInstanceState.RUNNING:
self._model.updateInstanceFields(i, ['state'])
def _populate_model(self, instances):
log.info('CloudInspectorView._populate_model')
self._model.flavors = self._provider.list_flavors()
# filter instances for current project
project_instances = [i for i in instances if i.id in self._project_instances_id]
for i in project_instances:
self._model.addInstance(i)
for inst in instances:
self._model.addInstance(inst)
self.uiInstancesTableView.resizeColumnsToContents()
def _create_new_instance(self):
@@ -428,16 +378,50 @@ class CloudInspectorView(QWidget, Ui_CloudInspectorView):
flavor_id = self.flavor_index_id[idx]
image_id = self._settings['default_image']
name, ok = QInputDialog.getText(self,
"New instance",
"Choose a name for the instance and press Ok,\n"
"then wait for the instance to appear in the inspector.")
name, ok = QtGui.QInputDialog.getText(self,
"New instance",
"Choose a name for the instance and press Ok,\n"
"then wait for the instance to appear in the inspector.")
if ok:
if not name.endswith("-gns3"):
name += "-gns3"
self.createInstance(name, flavor_id, image_id)
create_thread = CreateInstanceThread(self, self._provider, name, flavor_id, image_id)
create_thread.instanceCreated.connect(self._main_window.add_instance_to_project)
create_thread.instanceCreated.connect(CloudInstances.instance().add_instance)
create_thread.start()
def createInstance(self, instance_name, flavor_id, image_id):
if not instance_name.endswith("-gns3"):
instance_name += "-gns3"
# TODO: Add a keys_dir to projectSettings
ca_dir = os.path.join(self._main_window.projectSettings()["project_files_dir"], "keys")
builder = CloudBuilder(self, self._provider, ca_dir)
builder.startAtCreate(instance_name, flavor_id, image_id)
builder.instanceCreated.connect(self._main_window.add_instance_to_project)
builder.instanceCreated.connect(CloudInstances.instance().add_instance)
builder.instanceIdExists.connect(self._associateBuilderWithInstance)
builder.instanceHasIP.connect(CloudInstances.instance().update_host_for_instance)
builder.buildComplete.connect(self._instanceBuilt)
builder.start()
return builder
def _associateBuilderWithInstance(self, builder, instance_id):
self._builders[instance_id] = builder
def _rebuild_instances(self, instances):
# TODO: Add a keys_dir to projectSettings
ca_dir = os.path.join(self._main_window.projectSettings()["project_files_dir"], "keys")
for instance in instances:
log.debug('CloudInspectorView._rebuild_instances {}'.format(instance.name))
builder = CloudBuilder(self, self._provider, ca_dir)
cloud_instance = CloudInstances.instance().get_instance(instance.id)
public_key = cloud_instance.public_key
private_key = cloud_instance.private_key
# Fake a KeyPair object because we don't store it.
keypair = namedtuple('KeyPair', ['private_key', 'public_key'])(private_key, public_key)
builder.startAtSetup(instance, keypair)
builder.instanceCreated.connect(self._main_window.add_instance_to_project)
builder.instanceCreated.connect(CloudInstances.instance().add_instance)
builder.instanceIdExists.connect(self._associateBuilderWithInstance)
builder.instanceHasIP.connect(CloudInstances.instance().update_host_for_instance)
builder.buildComplete.connect(self._instanceBuilt)
builder.start()
return builder

View File

@@ -28,6 +28,7 @@ log = logging.getLogger(__name__)
class CloudInstances(QtCore.QObject):
"""
This class stores the instances that gns3 gui has started. This can be different than the list
of instances in the topology that can be changed when switching projects. This list is not touched
@@ -38,7 +39,6 @@ class CloudInstances(QtCore.QObject):
super(CloudInstances, self).__init__(*args, **kwargs)
self._instances = []
@staticmethod
def instance():
"""
@@ -64,29 +64,38 @@ class CloudInstances(QtCore.QObject):
def add_instance(self, instance, keypair):
if instance is None:
return
ti = TopologyInstance(instance.name, instance.id, instance.extra['flavorId'],
instance.extra['imageId'], keypair.private_key, keypair.public_key)
self._instances.append(ti)
self.save()
existing = self.get_instance(instance.id)
if existing is None:
ti = TopologyInstance(instance.name, instance.id, instance.extra['flavorId'],
instance.extra['imageId'], keypair.private_key, keypair.public_key)
self._instances.append(ti)
self.save()
def update_instances(self, instances):
"""
Compare with the existing list of instances to purge instances that no
longer exist.
"""
save_needed = False
# Look for instances that have been deleted
for static in self._instances:
for stored in self._instances:
found = False
for dynamic in instances:
if static.id == dynamic.id:
if stored.id == dynamic.id:
found = True
break
if not found:
self._instances.remove(static)
self._instances.remove(stored)
save_needed = True
if save_needed:
self.save()
def update_host_for_instance(self, instance_id, host):
def update_host_for_instance(self, host, instance_id):
"""
Update the public IP for the instance.
"""
for instance in self.instances:
if instance.id == instance_id:
if instance.host != host:

View File

@@ -6,7 +6,7 @@ no service password-encryption
hostname %h
!
ip cef
no ip domain lookup
no ip domain-lookup
no ip icmp rate-limit unreachable
ip tcp synwait 5
no cdp log mismatch duplex

View File

@@ -8,7 +8,7 @@ hostname %h
!
ip cef
no ip routing
no ip domain lookup
no ip domain-lookup
no ip icmp rate-limit unreachable
ip tcp synwait 5
no cdp log mismatch duplex

View File

@@ -30,83 +30,83 @@ ip tcp synwait-time 5
!
interface Ethernet0/0
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet0/1
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet0/2
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet0/3
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet1/0
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet1/1
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet1/2
no ip address
shutdown
no shutdown
duplex auto
!
interface Ethernet1/3
no ip address
shutdown
no shutdown
duplex auto
!
interface Serial2/0
interface Ethernet2/0
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial2/1
interface Ethernet2/1
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial2/2
interface Ethernet2/2
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial2/3
interface Ethernet2/3
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial3/0
interface Ethernet3/0
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial3/1
interface Ethernet3/1
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial3/2
interface Ethernet3/2
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Serial3/3
interface Ethernet3/3
no ip address
shutdown
serial restart-delay 0
no shutdown
duplex auto
!
interface Vlan1
no ip address

View File

@@ -13,7 +13,7 @@ no ip icmp rate-limit unreachable
!
!
ip cef
no ip domain lookup
no ip domain-lookup
!
!
ip tcp synwait-time 5

View File

@@ -28,6 +28,10 @@ import json
from .qt import QtCore
from .node import Node
from .version import __version__
try:
from gns3converter import __version__ as gns3converter_version
except ImportError:
gns3converter_version = "Not installed"
class ConsoleCmd(cmd.Cmd):
@@ -45,6 +49,7 @@ class ConsoleCmd(cmd.Cmd):
if hasattr(sys, "frozen"):
compiled = "(compiled)"
print("GNS3 version is {} {}".format(__version__, compiled))
print("GNS3 Converter version is {}".format(gns3converter_version))
print("Python version is {}.{}.{} ({}-bit) with {} encoding".format(sys.version_info[0],
sys.version_info[1],
sys.version_info[2],
@@ -210,16 +215,15 @@ class ConsoleCmd(cmd.Cmd):
ch = logging.StreamHandler(sys.stdout)
if len(args) == 1:
try:
level = int(args[0])
if level == 0:
print("Deactivating debugging")
root.removeHandler(ch)
else:
print("Activating debugging")
root.addHandler(ch)
except:
print(self.do_debug.__doc__)
level = int(args[0])
if level == 0:
print("Deactivating debugging")
root.removeHandler(ch)
else:
print("Activating debugging")
root.addHandler(ch)
from .main_window import MainWindow
MainWindow.instance().setSettings({"debug_level": level})
else:
print(self.do_debug.__doc__)
@@ -259,6 +263,10 @@ class ConsoleCmd(cmd.Cmd):
:param params: list of parameters
"""
if self._topology.project is None:
print("Sorry, the project hasn't been saved yet")
return
topology = self._topology.dump()
if len(params) == 1:
# print out whole topology

View File

@@ -19,6 +19,7 @@ import platform
import sys
import struct
import inspect
import datetime
from .topology import Topology
from .version import __version__
from .console_cmd import ConsoleCmd
@@ -36,8 +37,9 @@ class ConsoleView(PyCutExt, ConsoleCmd):
# Set introduction message
bitness = struct.calcsize("P") * 8
current_year = datetime.date.today().year
self.intro = "GNS3 management console. Running GNS3 version {} on {} ({}-bit).\n" \
"Copyright (c) 2006-2014 GNS3 Technologies.".format(__version__, platform.system(), bitness)
"Copyright (c) 2006-{} GNS3 Technologies.".format(__version__, platform.system(), bitness, current_year)
# Parent class initialization
try:
@@ -69,7 +71,7 @@ class ConsoleView(PyCutExt, ConsoleCmd):
For exception handling purposes
(see exception hook in the program entry point).
"""
return False
def onKeyPress_Tab(self):
@@ -170,7 +172,7 @@ class ConsoleView(PyCutExt, ConsoleCmd):
self.write(text, warning=True)
self.write("\n")
def writeServerError(self, node_id, code, message):
def writeServerError(self, node_id, message):
"""
Write server error messages coming from the server.
@@ -181,15 +183,15 @@ class ConsoleView(PyCutExt, ConsoleCmd):
node = Topology.instance().getNode(node_id)
server = name = ""
if node and node.name():
name = " {}:".format(node.name())
if node:
if node.name():
name = " {}:".format(node.name())
server = "from {}:{}".format(node.server().host,
node.server().port)
node.server().port)
text = "Server error [{code}] {server}:{name} {message}".format(code=code,
server=server,
name=name,
message=message)
text = "Server error {server}:{name} {message}".format(server=server,
name=name,
message=message)
self.write(text, error=True)
self.write("\n")
@@ -203,12 +205,12 @@ class ConsoleView(PyCutExt, ConsoleCmd):
self.pointer = 0
if len(self.line):
self.history.append(self.line)
try:
self.lines.append(self.line)
source = "\n".join(self.lines)
self.more = self.onecmd(source)
except Exception as e:
print("Unknown error: {}".format(e))
try:
self.lines.append(self.line)
source = "\n".join(self.lines)
self.more = self.onecmd(source)
except Exception as e:
print("Unknown error: {}".format(e))
self.write(self.prompt)
self.lines = []

90
gns3/crash_report.py Normal file
View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import platform
import struct
try:
import raven
RAVEN_AVAILABLE = True
except ImportError:
# raven is not installed with deb package in order to simplify packaging
RAVEN_AVAILABLE = False
from .version import __version__
from .servers import Servers
import logging
log = logging.getLogger(__name__)
class CrashReport:
"""
Report crash to a third party service
"""
DSN = "sync+https://c9baccac3bec4664812e9172d9b39050:597eab5e1f9042ae99daf2ddcc371ed2@app.getsentry.com/38506"
if hasattr(sys, "frozen"):
cacert = os.path.join(os.getcwd(), "cacert.pem")
if os.path.isfile(cacert):
DSN += "?ca_certs={}".format(cacert)
else:
log.warning("The SSL certificate bundle file '{}' could not be found".format(cacert))
_instance = None
def __init__(self):
self._client = None
def captureException(self, exception, value, tb):
if not RAVEN_AVAILABLE:
return
if os.path.exists(".git"):
log.warning("A .git directory exist crash report is turn off for developers")
return
local_server = Servers.instance().localServerSettings()
if local_server["report_errors"]:
if self._client is None:
self._client = raven.Client(CrashReport.DSN, release=__version__)
self._client.tags_context({
"os:name": platform.system(),
"os:release": platform.release(),
"os:win_32": " ".join(platform.win32_ver()),
"os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
"os:linux": " ".join(platform.linux_distribution()),
"python:version": "{}.{}.{}".format(sys.version_info[0],
sys.version_info[1],
sys.version_info[2]),
"python:bit": struct.calcsize("P") * 8,
"python:encoding": sys.getdefaultencoding(),
"python:frozen": "{}".format(hasattr(sys, "frozen"))
})
try:
report = self._client.captureException((exception, value, tb))
except Exception as e:
log.error("Can't send crash report to Sentry: {}".format(e))
return
log.info("Crash report sent with event ID: {}".format(self._client.get_ident(report)))
@classmethod
def instance(cls):
if cls._instance is None:
cls._instance = CrashReport()
return cls._instance

View File

@@ -21,6 +21,7 @@ from ..ui.about_dialog_ui import Ui_AboutDialog
class AboutDialog(QtGui.QDialog, Ui_AboutDialog):
"""
About dialog.
"""

View File

@@ -23,7 +23,9 @@ from ..qt import QtGui
from ..ui.configuration_dialog_ui import Ui_configurationDialog
from .node_configurator_dialog import ConfigurationError
class ConfigurationDialog(QtGui.QDialog, Ui_configurationDialog):
"""
Configuration dialog implementation.
@@ -61,4 +63,3 @@ class ConfigurationDialog(QtGui.QDialog, Ui_configurationDialog):
except ConfigurationError:
return
QtGui.QDialog.accept(self)

View File

@@ -20,6 +20,7 @@ from ..ui.exec_command_dialog_ui import Ui_ExecCommandDialog
class ExecCommandDialog(QtGui.QDialog, Ui_ExecCommandDialog):
"""
Execute a command and display its output.
"""

View File

@@ -17,13 +17,15 @@
import os
import sys
import pkg_resources
from ..qt import QtCore, QtGui, QtWebKit
from ..ui.getting_started_dialog_ui import Ui_GettingStartedDialog
from ..utils.get_resource import get_resource
from ..local_config import LocalConfig
class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
"""
GettingStarted dialog.
"""
@@ -38,13 +40,16 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
self.adjustSize()
self.uiWebView.page().setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks)
self.uiWebView.linkClicked.connect(self._urlClickedSlot)
self.uiWebView.loadFinished.connect(self._loadFinishedSlot)
self.uiCheckBox.setChecked(QtCore.QSettings().value("GUI/hide_getting_started_dialog", False, type=bool))
self._timer = QtCore.QTimer(self)
self._timer.timeout.connect(self._loadFinishedSlot)
self._timer.setSingleShot(True)
self._timer.start(5000)
self.uiWebView.load(QtCore.QUrl("http://start.gns3.net"))
self._local_config = LocalConfig.instance()
gui_settings = self._local_config.loadSectionSettings("GUI", {"hide_getting_started_dialog": False})
self.uiCheckBox.setChecked(gui_settings["hide_getting_started_dialog"])
getting_started = get_resource(os.path.join("static", "getting_started.html"))
if getting_started and not (sys.platform.startswith("win") and not sys.maxsize > 2 ** 32):
# do not show the page on Windows 32-bit (crash when no Internet connection)
self.uiWebView.load(QtCore.QUrl.fromLocalFile(getting_started))
else:
self.uiCheckBox.setChecked(True)
self.accept()
def showit(self):
"""
@@ -62,7 +67,7 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
:param result: ignored
"""
QtCore.QSettings().setValue("GUI/hide_getting_started_dialog", self.uiCheckBox.isChecked())
self._local_config.saveSectionSettings("GUI", {"hide_getting_started_dialog": self.uiCheckBox.isChecked()})
QtGui.QDialog.done(self, result)
def _urlClickedSlot(self, url):
@@ -74,29 +79,3 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
if QtGui.QDesktopServices.openUrl(url) is False:
QtGui.QMessageBox.critical(self, "Getting started", "Failed to open the URL: {}".format(url))
def _loadFinishedSlot(self, result=False):
"""
Slot called when the web page has been loaded.
:param result: boolean
"""
self.uiWebView.loadFinished.disconnect(self._loadFinishedSlot)
self._timer.stop()
self._timer.timeout.disconnect()
if result is False:
# load a local resource if the page is not available
resource_name = os.path.join("static", "getting_started.html")
getting_started = None
if hasattr(sys, "frozen") and os.path.isfile(resource_name):
getting_started = os.path.normpath(resource_name)
elif pkg_resources.resource_exists("gns3", resource_name):
getting_started_page = pkg_resources.resource_filename("gns3", resource_name)
getting_started = os.path.normpath(getting_started_page)
if getting_started and not (sys.platform.startswith("win") and not sys.maxsize > 2 ** 32):
# do not show the page on Windows 32-bit (crash when no Internet connection)
self.uiWebView.load(QtCore.QUrl("file://{}".format(getting_started)))
else:
self.uiCheckBox.setChecked(True)
self.accept()

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 os
import re
from ..qt import QtGui
@@ -23,6 +24,7 @@ from ..ui.idlepc_dialog_ui import Ui_IdlePCDialog
class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
"""
Idle-PC dialog.
"""
@@ -51,10 +53,13 @@ class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
Shows the help for Idle-PC.
"""
help_text = "Finding the right idlepc value is a trial and error process, consisting of applying " \
"different Idle-PC values and monitoring the CPU usage.\n\nBest Idle-PC values are usually " \
"obtained when IOS is in idle state, the following message being displayed " \
"on the console: {} con0 is now available ... Press RETURN to get started.".format(self._router.name())
help_text = """Best Idle-PC values are obtained when IOS is in idle state, after the "Press RETURN to get started" message has appeared on the console, messages have finished displaying on the console and you have have actually pressed the RETURN key.
Finding the right idle-pc value is a trial and error process, consisting of applying different Idle-PC values and monitoring the CPU usage.
Select each value that appears in the list and click Apply, and note the CPU usage a few moments later. When you have found the value that minimises the CPU usage, apply that value.
"""
QtGui.QMessageBox.information(self, "Hints for Idle-PC", help_text)
def _applySlot(self):
@@ -67,13 +72,15 @@ class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
return
idlepc = self.uiComboBox.itemData(self.uiComboBox.currentIndex())
# apply Idle-PC to all routers with the same IOS image
ios_image = self._router.settings()["image"]
ios_image = os.path.basename(self._router.settings()["image"])
for node in Topology.instance().nodes():
if hasattr(node, "idlepcs") and node.settings()["image"] == ios_image:
if hasattr(node, "idlepc") and node.settings()["image"] == ios_image:
node.setIdlepc(idlepc)
# apply the idle-pc to templates with the same IOS image
self._router.module().updateImageIdlepc(ios_image, idlepc)
def done(self, result):
"""
Called when the dialog is closed.
@@ -84,4 +91,3 @@ class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
if result:
self._applySlot()
QtGui.QDialog.done(self, result)

View File

@@ -9,6 +9,7 @@ from ..utils.progress_dialog import ProgressDialog
class ImportCloudProjectDialog(QtGui.QDialog, Ui_ImportCloudProjectDialog):
"""
Import cloud project dialog implementation.
"""
@@ -34,6 +35,7 @@ class ImportCloudProjectDialog(QtGui.QDialog, Ui_ImportCloudProjectDialog):
project_file_name = self.projects[self.listWidget.currentItem().text()]
download_thread = DownloadProjectThread(
self,
project_file_name,
self.project_dest_path,
self.images_dest_path,
@@ -59,7 +61,7 @@ class ImportCloudProjectDialog(QtGui.QDialog, Ui_ImportCloudProjectDialog):
)
if button_clicked == QtGui.QMessageBox.Yes:
delete_project_thread = DeleteProjectThread(project_file_name, self.cloud_settings)
delete_project_thread = DeleteProjectThread(self, project_file_name, self.cloud_settings)
progress_dialog = ProgressDialog(delete_project_thread, "Deleting project", "Deleting project files...",
"Cancel", parent=self)
progress_dialog.show()

View File

@@ -16,13 +16,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import shutil
from ..qt import QtCore, QtGui
from ..ui.new_project_dialog_ui import Ui_NewProjectDialog
from ..settings import ENABLE_CLOUD
class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
"""
New project dialog.
@@ -37,7 +37,7 @@ class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
self.setupUi(self)
self._main_window = parent
self._project_settings = parent.projectSettings().copy()
self._project_settings = {}
default_project_name = "untitled"
self.uiNameLineEdit.setText(default_project_name)
self.uiLocationLineEdit.setText(os.path.join(self._main_window.projectsDirPath(), default_project_name))
@@ -139,10 +139,7 @@ class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
self._project_settings["project_name"] = project_name
self._project_settings["project_path"] = os.path.join(project_location, project_name + ".gns3")
self._project_settings["project_files_dir"] = os.path.join(project_location, project_name + "-files")
self._project_settings["project_files_dir"] = project_location
self._project_settings["project_type"] = project_type
# delete all the project files
shutil.rmtree(self._project_settings["project_files_dir"], ignore_errors=True)
QtGui.QDialog.done(self, result)

View File

@@ -19,11 +19,15 @@
Dialog to configure and update node settings using widget pages.
"""
from gns3.http_client import HTTPClient
from gns3.progress import Progress
from ..qt import QtCore, QtGui
from ..ui.node_configurator_dialog_ui import Ui_NodeConfiguratorDialog
class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
"""
Node configurator implementation.
@@ -53,6 +57,7 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
self.splitter.setSizes([250, 600])
self.uiNodesTreeWidget.itemClicked.connect(self.showConfigurationPageSlot)
HTTPClient.setProgressCallback(Progress(self, min_duration=0))
def _loadNodeItems(self):
"""
@@ -65,7 +70,7 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
continue
group_name = " {} group".format(str(node_item.node()))
parent = group_name
if not parent in self._parent_items:
if parent not in self._parent_items:
item = QtGui.QTreeWidgetItem(self.uiNodesTreeWidget, [group_name])
item.setIcon(0, QtGui.QIcon(node_item.node().defaultSymbol()))
item.setExpanded(True)
@@ -131,14 +136,17 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
"""
try:
from gns3.main_window import MainWindow
if button == self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply):
self.applySettings()
elif button == self.uiButtonBox.button(QtGui.QDialogButtonBox.Reset):
self.resetSettings()
elif button == self.uiButtonBox.button(QtGui.QDialogButtonBox.Cancel):
HTTPClient.setProgressCallback(Progress(MainWindow.instance()))
QtGui.QDialog.reject(self)
else:
self.applySettings()
HTTPClient.setProgressCallback(Progress(MainWindow.instance()))
QtGui.QDialog.accept(self)
except ConfigurationError:
pass
@@ -165,7 +173,7 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
page.saveSettings(settings, node, group=True)
for index in range(0, item.childCount()):
child = item.child(index)
#child.node().update(settings) #TODO: delete
# child.node().update(settings) #TODO: delete
child.settings().update(settings)
# update the nodes with the settings
@@ -201,6 +209,7 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
class ConfigurationPageItem(QtGui.QTreeWidgetItem):
"""
Item for the QTreeWidget instance.
Store temporary node settings configured in a page widget.
@@ -269,6 +278,7 @@ class ConfigurationPageItem(QtGui.QTreeWidgetItem):
class ConfigurationError(Exception):
"""
Exception to be raised when a configuration error occurs.
"""

View File

@@ -27,9 +27,12 @@ from ..pages.cloud_preferences_page import CloudPreferencesPage
from ..pages.packet_capture_preferences_page import PacketCapturePreferencesPage
from ..modules import MODULES
from ..settings import ENABLE_CLOUD
from ..http_client import HTTPClient
from ..progress import Progress
class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
"""
Preferences dialog implementation.
@@ -48,6 +51,7 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
# select the first available page
self.uiTreeWidget.setCurrentItem(self._items[0])
HTTPClient.setProgressCallback(Progress(self, min_duration=0))
def _loadPreferencePages(self):
"""
@@ -64,7 +68,7 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
pages.append(CloudPreferencesPage)
for page in pages:
preferences_page = page()
preferences_page = page(self)
preferences_page.loadPreferences()
name = preferences_page.windowTitle()
item = QtGui.QTreeWidgetItem(self.uiTreeWidget)
@@ -104,8 +108,12 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
current = previous
preferences_page = current.data(0, QtCore.Qt.UserRole)
name = preferences_page.windowTitle()
self.uiTitleLabel.setText("{} preferences".format(name))
accessible_name = preferences_page.accessibleName()
if accessible_name:
self.uiTitleLabel.setText(accessible_name)
else:
name = preferences_page.windowTitle()
self.uiTitleLabel.setText("{} preferences".format(name))
index = self.uiStackedWidget.indexOf(preferences_page)
widget = self.uiStackedWidget.widget(index)
self.uiStackedWidget.setMinimumSize(widget.size())
@@ -131,6 +139,8 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
Closes this dialog.
"""
from gns3.main_window import MainWindow
HTTPClient.setProgressCallback(Progress(MainWindow.instance()))
QtGui.QDialog.reject(self)
def accept(self):
@@ -144,4 +154,6 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
main_window.uiNodesDockWidget.setWindowTitle("")
if self._applyPreferences():
from gns3.main_window import MainWindow
HTTPClient.setProgressCallback(Progress(MainWindow.instance()))
QtGui.QDialog.accept(self)

View File

@@ -26,13 +26,14 @@ import os
from ..qt import QtCore, QtGui
from ..utils.progress_dialog import ProgressDialog
from ..utils.process_files_thread import ProcessFilesThread
from ..utils.process_files_worker import ProcessFilesWorker
from ..ui.snapshots_dialog_ui import Ui_SnapshotsDialog
from ..topology import Topology
from ..node import Node
class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
"""
Snapshots dialog implementation.
@@ -45,7 +46,7 @@ class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
self.setupUi(self)
self._project_path = project_path
self._project_files_dir = project_files_dir
self._project_files_dir = os.path.join(project_files_dir, "project-files")
self.uiCreatePushButton.clicked.connect(self._createSnapshotSlot)
self.uiDeletePushButton.clicked.connect(self._deleteSnapshotSlot)
@@ -94,9 +95,8 @@ class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
MainWindow.instance().saveProject(self._project_path)
snapshot_name = "{name}_{date}".format(name=snapshot_name, date=time.strftime("%d%m%y_%H%M%S"))
snapshot_dir = os.path.join(self._project_files_dir, "snapshots", snapshot_name)
thread = ProcessFilesThread(os.path.dirname(self._project_path), snapshot_dir, skip_dirs=["snapshots"])
thread.deleteLater()
progress_dialog = ProgressDialog(thread, "Creating snapshot", "Copying project files...", "Cancel", parent=self)
worker = ProcessFilesWorker(os.path.dirname(self._project_path), snapshot_dir, skip_dirs=["snapshots"])
progress_dialog = ProgressDialog(worker, "Creating snapshot", "Copying project files...", "Cancel", parent=self)
progress_dialog.show()
progress_dialog.exec_()
self._listSnaphosts()
@@ -145,15 +145,35 @@ class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
if hasattr(node, "start") and node.status() == Node.started:
node.stop()
#FIXME: problably a bug when restoring a snapshot and the project name has changed.
thread = ProcessFilesThread(snapshot_path, os.path.dirname(self._project_path), skip_dirs=["snapshots"])
thread.deleteLater()
progress_dialog = ProgressDialog(thread, "Restoring snapshot", "Copying project files...", "Cancel", parent=self)
progress_dialog.show()
progress_dialog.exec_()
project_name, _ = os.path.splitext(os.path.basename(self._project_path))
legacy_project_files_dir = os.path.join(snapshot_path, "{}-files".format(project_name))
if os.path.exists(legacy_project_files_dir):
# support for pre 1.3 snapshots
for root, dirs, _ in os.walk(self._project_files_dir):
dirs[:] = [d for d in dirs if d not in "snapshots"]
for project_subdir in dirs:
project_subdir_path = os.path.join(root, project_subdir)
shutil.rmtree(project_subdir_path, ignore_errors=True)
dirs = os.listdir(legacy_project_files_dir)
for snapshot_subdir in dirs:
snapshot_subdir_path = os.path.join(legacy_project_files_dir, snapshot_subdir)
worker = ProcessFilesWorker(snapshot_subdir_path, os.path.join(self._project_files_dir, snapshot_subdir))
progress_dialog = ProgressDialog(worker, "Restoring snapshot", "Copying project files...", "Cancel", parent=self)
progress_dialog.show()
progress_dialog.exec_()
os.remove(self._project_path)
shutil.copy(os.path.join(snapshot_path, os.path.basename(self._project_path)), self._project_path)
else:
worker = ProcessFilesWorker(snapshot_path, os.path.dirname(self._project_path), skip_dirs=["snapshots"])
progress_dialog = ProgressDialog(worker, "Restoring snapshot", "Copying project files...", "Cancel", parent=self)
progress_dialog.show()
progress_dialog.exec_()
from ..main_window import MainWindow
MainWindow.instance().loadProject(self._project_path)
MainWindow.instance().loadSnapshot(self._project_path)
self.accept()
def _snapshotDoubleClickedSlot(self, item):

View File

@@ -24,6 +24,7 @@ from ..ui.style_editor_dialog_ui import Ui_StyleEditorDialog
class StyleEditorDialog(QtGui.QDialog, Ui_StyleEditorDialog):
"""
Style editor dialog.

View File

@@ -25,6 +25,7 @@ from ..node import Node
class SymbolSelectionDialog(QtGui.QDialog, Ui_SymbolSelectionDialog):
"""
Symbol selection dialog.
@@ -32,7 +33,7 @@ class SymbolSelectionDialog(QtGui.QDialog, Ui_SymbolSelectionDialog):
:param items: list of items
"""
def __init__(self, parent, items=None):
def __init__(self, parent, items=None, symbol=None, category=None):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
@@ -40,6 +41,8 @@ class SymbolSelectionDialog(QtGui.QDialog, Ui_SymbolSelectionDialog):
self._items = items
self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply).clicked.connect(self._applyPreferencesSlot)
selected_symbol = symbol
selected_category = category
if not self._items:
self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply).hide()
@@ -50,20 +53,39 @@ class SymbolSelectionDialog(QtGui.QDialog, Ui_SymbolSelectionDialog):
"Security devices": Node.security_devices
}
index = 0
for name, category in categories.items():
self.uiCategoryComboBox.addItem(name, category)
if category == selected_category:
self.uiCategoryComboBox.setCurrentIndex(index)
index += 1
else:
self.uiCategoryLabel.hide()
self.uiCategoryComboBox.hide()
custom_symbol = items[0].defaultRenderer().objectName()
if not custom_symbol:
symbol_name = items[0].node().defaultSymbol()
else:
symbol_name = custom_symbol
selected_symbol = symbol_name
self.uiSymbolListWidget.setIconSize(QtCore.QSize(64, 64))
symbol_resources = QtCore.QResource(":/symbols")
for symbol in symbol_resources.children():
if symbol.endswith('.normal.svg'):
if symbol.endswith(".normal.svg"):
name = symbol[:-11]
item = QtGui.QListWidgetItem(self.uiSymbolListWidget)
item.setText(name)
item.setIcon(QtGui.QIcon(':/symbols/' + symbol))
resource_path = ":/symbols/" + symbol
svg_renderer = QtSvg.QSvgRenderer(resource_path)
if resource_path == selected_symbol:
self.uiSymbolListWidget.setCurrentItem(item)
image = QtGui.QImage(64, 64, QtGui.QImage.Format_ARGB32)
# Set the ARGB to 0 to prevent rendering artifacts
image.fill(0x00000000)
svg_renderer.render(QtGui.QPainter(image))
icon = QtGui.QIcon(QtGui.QPixmap.fromImage(image))
item.setIcon(icon)
def _applyPreferencesSlot(self):
"""

View File

@@ -24,6 +24,7 @@ from ..ui.text_editor_dialog_ui import Ui_TextEditorDialog
class TextEditorDialog(QtGui.QDialog, Ui_TextEditorDialog):
"""
Text editor dialog.
@@ -53,6 +54,10 @@ class TextEditorDialog(QtGui.QDialog, Ui_TextEditorDialog):
if not first_item.editable():
self.uiPlainTextEdit.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
if len(self._items) == 1:
self.uiApplyTextToAllItemsCheckBox.setChecked(True)
self.uiApplyTextToAllItemsCheckBox.hide()
def _setFontSlot(self):
"""
Slot to select the font.
@@ -82,7 +87,7 @@ class TextEditorDialog(QtGui.QDialog, Ui_TextEditorDialog):
item.setDefaultTextColor(self._color)
item.setFont(self.uiPlainTextEdit.font())
item.setRotation(self.uiRotationSpinBox.value())
if item.editable():
if item.editable() and self.uiApplyTextToAllItemsCheckBox.isChecked():
item.setPlainText(self.uiPlainTextEdit.toPlainText())
def done(self, result):

View File

@@ -22,6 +22,7 @@ Graphical view on the scene where items are drawn.
import logging
import os
import pickle
import functools
from .qt import QtCore, QtGui, QtNetwork
from .servers import Servers
@@ -39,7 +40,7 @@ from .dialogs.style_editor_dialog import StyleEditorDialog
from .dialogs.text_editor_dialog import TextEditorDialog
from .dialogs.symbol_selection_dialog import SymbolSelectionDialog
from .dialogs.idlepc_dialog import IdlePCDialog
from .utils.connect_to_server import ConnectToServer
from .local_config import LocalConfig
# link items
from .items.link_item import LinkItem
@@ -57,6 +58,7 @@ log = logging.getLogger(__name__)
class GraphicsView(QtGui.QGraphicsView):
"""
Graphics view that displays the scene.
@@ -81,6 +83,8 @@ class GraphicsView(QtGui.QGraphicsView):
self._dragging = False
self._last_mouse_position = None
self._topology = Topology.instance()
self._background_warning_msgbox = QtGui.QErrorMessage(self)
self._background_warning_msgbox.setWindowTitle("Layer position")
# set the scene
scene = QtGui.QGraphicsScene(parent=self)
@@ -124,46 +128,27 @@ class GraphicsView(QtGui.QGraphicsView):
# clear all objects on the scene
self.scene().clear()
def updateProjectFilesDir(self, path):
"""
Updates the project files directory path for all modules.
:param path: path to the local project files directory.
"""
try:
for module in MODULES:
instance = module.instance()
instance.setProjectFilesDir(path)
except ModuleError as e:
QtGui.QMessageBox.critical(self, "Local projects directory", "{}".format(e))
def updateImageFilesDir(self, path):
"""
Updates the image files directory path for all modules.
:param path: path to the local images files directory.
"""
try:
for module in MODULES:
instance = module.instance()
instance.setImageFilesDir(path)
except ModuleError as e:
QtGui.QMessageBox.critical(self, "Local images directory", "{}".format(e))
def _loadSettings(self):
"""
Loads the settings from the persistent settings file.
"""
# restore settings
local_config = LocalConfig.instance()
# restore the graphics view settings from QSettings (for backward compatibility)
legacy_settings = {}
settings = QtCore.QSettings()
settings.beginGroup(self.__class__.__name__)
for name, value in GRAPHICS_VIEW_SETTINGS.items():
self._settings[name] = settings.value(name, value, type=GRAPHICS_VIEW_SETTING_TYPES[name])
for name in GRAPHICS_VIEW_SETTINGS.keys():
if settings.contains(name):
legacy_settings[name] = settings.value(name, type=GRAPHICS_VIEW_SETTING_TYPES[name])
settings.remove("")
settings.endGroup()
if legacy_settings:
local_config.saveSectionSettings(self.__class__.__name__, legacy_settings)
self._settings = local_config.loadSectionSettings(self.__class__.__name__, GRAPHICS_VIEW_SETTINGS)
def settings(self):
"""
Returns the graphics view settings.
@@ -182,11 +167,7 @@ class GraphicsView(QtGui.QGraphicsView):
# save the settings
self._settings.update(new_settings)
settings = QtCore.QSettings()
settings.beginGroup(self.__class__.__name__)
for name, value in self._settings.items():
settings.setValue(name, value)
settings.endGroup()
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
def addingLinkSlot(self, enabled):
"""
@@ -199,9 +180,9 @@ class GraphicsView(QtGui.QGraphicsView):
if enabled:
self.setCursor(QtCore.Qt.CrossCursor)
else:
if self._newlink:
if self._newlink and self._newlink in self.scene().items():
self.scene().removeItem(self._newlink)
self._newlink = None
self._newlink = None
self.setCursor(QtCore.Qt.ArrowCursor)
self._adding_link = enabled
@@ -277,9 +258,9 @@ class GraphicsView(QtGui.QGraphicsView):
# connect the signals that let the graphics view knows about events such as
# a new link creation or deletion.
link.add_link_signal.connect(self.addLinkSlot)
link.delete_link_signal.connect(self.deleteLinkSlot)
self._topology.addLink(link)
if self._topology.addLink(link):
link.add_link_signal.connect(self.addLinkSlot)
link.delete_link_signal.connect(self.deleteLinkSlot)
def addLinkSlot(self, link_id):
"""
@@ -426,9 +407,10 @@ class GraphicsView(QtGui.QGraphicsView):
QtGui.QMessageBox.critical(self, "Connection", "Server {} cannot communicate with server {}, most likely because your local server host binding is set to a local address".format(source_host, destination_host))
return
self.scene().removeItem(self._newlink)
self.addLink(source_item.node(), source_port, destination_item.node(), destination_port)
if self._newlink in self.scene().items():
self.scene().removeItem(self._newlink)
self._newlink = None
self.addLink(source_item.node(), source_port, destination_item.node(), destination_port)
def mousePressEvent(self, event):
"""
@@ -462,7 +444,7 @@ class GraphicsView(QtGui.QGraphicsView):
item.setSelected(True)
elif is_not_link and event.button() == QtCore.Qt.RightButton and not self._adding_link:
if item:
#Prevent right clicking on a selected item from de-selecting all other items
# Prevent right clicking on a selected item from de-selecting all other items
if not item.isSelected():
if not event.modifiers() & QtCore.Qt.ControlModifier:
for it in self.scene().items():
@@ -479,6 +461,10 @@ class GraphicsView(QtGui.QGraphicsView):
# when more than one item is selected display the contextual menu even if mouse is not above an item
elif len(self.scene().selectedItems()) > 1:
self._showDeviceContextualMenu(QtGui.QCursor.pos())
elif is_not_link and self._adding_link and event.button() == QtCore.Qt.RightButton:
# send a escape key to the main window to cancel the link addition
key = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Escape, QtCore.Qt.NoModifier)
QtGui.QApplication.sendEvent(self._main_window, key)
elif item and isinstance(item, NodeItem) and self._adding_link and event.button() == QtCore.Qt.LeftButton:
self._userNodeLinking(event, item)
elif event.button() == QtCore.Qt.LeftButton and self._adding_note:
@@ -565,6 +551,7 @@ class GraphicsView(QtGui.QGraphicsView):
QtGui.QGraphicsView.keyPressEvent(self, event)
return
self.deleteActionSlot()
QtGui.QGraphicsView.keyPressEvent(self, event)
else:
QtGui.QGraphicsView.keyPressEvent(self, event)
@@ -586,7 +573,7 @@ class GraphicsView(QtGui.QGraphicsView):
hBar.setValue(hBar.value() + (delta.x() if QtGui.QApplication.isRightToLeft() else -delta.x()))
vBar.setValue(vBar.value() - delta.y())
self._last_mouse_position = mapped_global_pos
if self._adding_link and self._newlink:
if self._adding_link and self._newlink and self._newlink in self.scene().items():
# update the mouse position when the user is adding a link.
self._newlink.setMousePoint(self.mapToScene(event.pos()))
event.ignore()
@@ -673,6 +660,9 @@ class GraphicsView(QtGui.QGraphicsView):
else:
self.createNode(node_data, event.pos())
elif event.mimeData().hasFormat("text/uri-list") and event.mimeData().hasUrls():
# This should not arrive but we received bug report with it...
if len(event.mimeData().urls()) == 0:
return
if len(event.mimeData().urls()) > 1:
QtGui.QMessageBox.critical(self, "Project files", "Please drop only one file")
return
@@ -712,31 +702,66 @@ class GraphicsView(QtGui.QGraphicsView):
configure_action.triggered.connect(self.configureActionSlot)
menu.addAction(configure_action)
# Action: Change hostname
change_hostname_action = QtGui.QAction("Change hostname", menu)
change_hostname_action.setIcon(QtGui.QIcon(':/icons/show-hostname.svg'))
self.connect(change_hostname_action, QtCore.SIGNAL('triggered()'), self.changeHostnameActionSlot)
menu.addAction(change_hostname_action)
# Action: Change symbol
change_symbol_action = QtGui.QAction("Change symbol", menu)
change_symbol_action.setIcon(QtGui.QIcon(':/icons/node_conception.svg'))
self.connect(change_symbol_action, QtCore.SIGNAL('triggered()'), self.changeSymbolActionSlot)
menu.addAction(change_symbol_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "console"), items)):
console_action = QtGui.QAction("Console", menu)
console_action.setIcon(QtGui.QIcon(':/icons/console.svg'))
console_action.triggered.connect(self.consoleActionSlot)
menu.addAction(console_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "auxConsole"), items)):
aux_console_action = QtGui.QAction("Auxiliary console", menu)
aux_console_action.setIcon(QtGui.QIcon(':/icons/aux-console.svg'))
aux_console_action.triggered.connect(self.auxConsoleActionSlot)
menu.addAction(aux_console_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "importConfig"), items)):
import_config_action = QtGui.QAction("Import config", menu)
import_config_action.setIcon(QtGui.QIcon(':/icons/import_config.svg'))
import_config_action.triggered.connect(self.importConfigActionSlot)
menu.addAction(import_config_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "exportConfig"), items)):
export_config_action = QtGui.QAction("Export config", menu)
export_config_action.setIcon(QtGui.QIcon(':/icons/export_config.svg'))
export_config_action.triggered.connect(self.exportConfigActionSlot)
menu.addAction(export_config_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "saveConfig"), items)):
save_config_action = QtGui.QAction("Save config", menu)
save_config_action.setIcon(QtGui.QIcon(':/icons/save.svg'))
save_config_action.triggered.connect(self.saveConfigActionSlot)
menu.addAction(save_config_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "startPacketCapture"), items)):
capture_action = QtGui.QAction("Capture", menu)
capture_action.setIcon(QtGui.QIcon(':/icons/inspect.svg'))
capture_action.triggered.connect(self.captureActionSlot)
menu.addAction(capture_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "idlepcs"), items)):
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "idlepc"), items)):
idlepc_action = QtGui.QAction("Idle-PC", menu)
idlepc_action.setIcon(QtGui.QIcon(':/icons/calculate.svg'))
idlepc_action.triggered.connect(self.idlepcActionSlot)
menu.addAction(idlepc_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "idlepc"), items)):
auto_idlepc_action = QtGui.QAction("Auto Idle-PC", menu)
auto_idlepc_action.setIcon(QtGui.QIcon(':/icons/calculate.svg'))
auto_idlepc_action.triggered.connect(self.autoIdlepcActionSlot)
menu.addAction(auto_idlepc_action)
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "start"), items)):
start_action = QtGui.QAction("Start", menu)
start_action.setIcon(QtGui.QIcon(':/icons/start.svg'))
@@ -782,6 +807,17 @@ class GraphicsView(QtGui.QGraphicsView):
# item must have no parent
if True in list(map(lambda item: item.parentItem() is None, items)):
if len(items) > 1:
horizontal_align_action = QtGui.QAction("Align horizontally", menu)
horizontal_align_action.setIcon(QtGui.QIcon(':/icons/horizontally.svg'))
horizontal_align_action.triggered.connect(self.horizontalAlignmentSlot)
menu.addAction(horizontal_align_action)
vertical_align_action = QtGui.QAction("Align vertically", menu)
vertical_align_action.setIcon(QtGui.QIcon(':/icons/vertically.svg'))
vertical_align_action.triggered.connect(self.verticalAlignmentSlot)
menu.addAction(vertical_align_action)
raise_layer_action = QtGui.QAction("Raise one layer", menu)
raise_layer_action.setIcon(QtGui.QIcon(':/icons/raise_z_value.svg'))
raise_layer_action.triggered.connect(self.raiseLayerActionSlot)
@@ -851,6 +887,22 @@ class GraphicsView(QtGui.QGraphicsView):
if items:
self.configureSlot(items)
def changeHostnameActionSlot(self):
"""
Slot to receive events from the change hostname action in the
contextual menu.
"""
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and item.node().initialized():
new_hostname, ok = QtGui.QInputDialog.getText(self, "Change hostname", "Hostname:", QtGui.QLineEdit.Normal, item.node().name())
if ok:
if hasattr(item.node(), "validateHostname"):
if not item.node().validateHostname(new_hostname):
QtGui.QMessageBox.critical(self, "Change hostname", "Invalid name detected for this node: {}".format(new_hostname))
continue
item.node().update({"name": new_hostname})
def changeSymbolActionSlot(self):
"""
Slot to receive events from the change symbol action in the
@@ -866,11 +918,12 @@ class GraphicsView(QtGui.QGraphicsView):
dialog.show()
dialog.exec_()
def consoleToNode(self, node):
def consoleToNode(self, node, aux=False):
"""
Start a console application to connect to a node.
:param node: Node instance
:param aux: auxiliary console mode
:returns: False if the console application could not be started
"""
@@ -879,16 +932,26 @@ class GraphicsView(QtGui.QGraphicsView):
# returns True to ignore this node.
return True
if aux and not hasattr(node, "auxConsole"):
# returns True to ignore this node.
return True
if hasattr(node, "serialConsole") and node.serialConsole():
try:
from .serial_console import serialConsole
serialConsole(node.name())
serialConsole(node.name(), node.serialPipe())
except (OSError, ValueError) as e:
QtGui.QMessageBox.critical(self, "Console", "Cannot start serial console application: {}".format(e))
return False
else:
name = node.name()
console_port = node.console()
if aux:
console_port = node.auxConsole()
if console_port is None:
QtGui.QMessageBox.critical(self, "Console", "AUX console port not allocated for {}".format(name))
return False
else:
console_port = node.console()
console_host = node.server().host
try:
from .telnet_console import telnetConsole
@@ -918,16 +981,139 @@ class GraphicsView(QtGui.QGraphicsView):
return False
return True
def consoleFromItems(self, items):
"""
Console from scene items.
:param items: Item instances
"""
nodes = {}
for item in items:
if isinstance(item, NodeItem) and hasattr(item.node(), "console") and item.node().initialized() and item.node().status() == Node.started:
node = item.node()
nodes[node.name()] = node
delay = self._main_window.settings()["delay_console_all"]
counter = 0
for name in sorted(nodes.keys()):
node = nodes[name]
callback = functools.partial(self.consoleToNode, node)
self._main_window.run_later(counter, callback)
counter += delay
def consoleActionSlot(self):
"""
Slot to receive events from the console action in the
contextual menu.
"""
self.consoleFromItems(self.scene().selectedItems())
def auxConsoleFromItems(self, items):
"""
Aux console from scene items.
:param items: Item instances
"""
nodes = {}
for item in items:
if isinstance(item, NodeItem) and hasattr(item.node(), "auxConsole") and item.node().initialized() and item.node().status() == Node.started:
node = item.node()
nodes[node.name()] = node
delay = self._main_window.settings()["delay_console_all"]
counter = 0
for name in sorted(nodes.keys()):
node = nodes[name]
callback = functools.partial(self.consoleToNode, node, aux=True)
self._main_window.run_later(counter, callback)
counter += delay
def auxConsoleActionSlot(self):
"""
Slot to receive events from the auxiliary console action in the
contextual menu.
"""
self.auxConsoleFromItems(self.scene().selectedItems())
def importConfigActionSlot(self):
"""
Slot to receive events from the import config action in the
contextual menu.
"""
items = []
for item in self.scene().selectedItems():
if isinstance(item, NodeItem):
if self.consoleToNode(item.node()):
continue
if isinstance(item, NodeItem) and hasattr(item.node(), "importConfig") and item.node().initialized():
items.append(item)
if not items:
return
if len(items) > 1:
path = QtGui.QFileDialog.getExistingDirectory(self, "Import directory", ".", QtGui.QFileDialog.ShowDirsOnly)
if path:
for item in items:
item.node().importConfigFromDirectory(path)
else:
item = items[0]
path, _ = QtGui.QFileDialog.getOpenFileNameAndFilter(self,
"Import config",
".",
"All files (*.*);;Config files (*.cfg)",
"Config files (*.cfg)")
if path:
item.node().importConfig(path)
if hasattr(item.node(), "importPrivateConfig"):
path, _ = QtGui.QFileDialog.getOpenFileNameAndFilter(self,
"Import private-config",
".",
"All files (*.*);;Config files (*.cfg)",
"Config files (*.cfg)")
if path:
item.node().importPrivateConfig(path)
def exportConfigActionSlot(self):
"""
Slot to receive events from the export config action in the
contextual menu.
"""
items = []
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "exportConfig") and item.node().initialized():
items.append(item)
if not items:
return
if len(items) > 1:
path = QtGui.QFileDialog.getExistingDirectory(self, "Export directory", ".", QtGui.QFileDialog.ShowDirsOnly)
if path:
for item in items:
item.node().exportConfigToDirectory(path)
else:
item = items[0]
if hasattr(item.node(), "importPrivateConfig"):
config_path = QtGui.QFileDialog.getSaveFileName(self, "Export startup-config")
private_config_path = QtGui.QFileDialog.getSaveFileName(self, "Export private-config")
item.node().exportConfig(config_path, private_config_path)
else:
config_path = QtGui.QFileDialog.getSaveFileName(self, "Export config")
item.node().exportConfig(config_path)
def saveConfigActionSlot(self):
"""
Slot to receive events from the save config action in the
contextual menu.
"""
for item in self.scene().selectedItems():
if isinstance(item, NodeItem) and hasattr(item.node(), "saveConfig") and item.node().initialized():
item.node().saveConfig()
def captureActionSlot(self):
"""
@@ -964,53 +1150,64 @@ class GraphicsView(QtGui.QGraphicsView):
QtGui.QMessageBox.critical(self, "Idle-PC", "Please select only one router")
return
item = items[0]
if isinstance(item, NodeItem) and hasattr(item.node(), "idlepcs") and item.node().initialized():
if isinstance(item, NodeItem) and hasattr(item.node(), "idlepc") and item.node().initialized():
router = item.node()
idlepc = router.idlepc()
router.computeIdlepcs()
# question = QtGui.QMessageBox.question(self, "Auto Idle-PC", "Would you like to automatically find a suitable Idle-PC value (but not optimal)?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
#TODO: improve to show progress over 10 seconds
self._idlepc_progress_dialog = QtGui.QProgressDialog("Computing values...", "Cancel", 0, 0, parent=self)
self._idlepc_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
self._idlepc_progress_dialog.setWindowTitle("Idle-PC")
# if question == QtGui.QMessageBox.Yes:
# router.computeAutoIdlepc(self._autoIdlepcCallback)
# else:
router.computeIdlepcs(self._idlepcCallback)
def cancel():
router.idlepc_signal.disconnect(self._showIdlepcProposals)
router.server_error_signal.disconnect(self._showIdlepcError)
router.setIdlepc(idlepc)
self._idlepc_progress_dialog.canceled.connect(cancel)
router.idlepc_signal.connect(self._showIdlepcProposals)
router.server_error_signal.connect(self._showIdlepcError)
self._idlepc_progress_dialog.show()
def _showIdlepcError(self, node_id, code, message):
def _idlepcCallback(self, result, error=False, context={}, **kwargs):
"""
Shows an error message if the Idle-PC values cannot be computed.
Slot to allow the user to select an idle-pc value.
"""
self._idlepc_progress_dialog.reject()
QtGui.QMessageBox.critical(self, "Idle-PC", "Error: {}".format(message))
router = self.scene().selectedItems()[0].node()
router.server_error_signal.disconnect(self._showIdlepcError)
router.idlepc_signal.disconnect(self._showIdlepcProposals)
if error:
QtGui.QMessageBox.critical(self, "Idle-PC", "Error: {}".format(result["message"]))
else:
router = context["router"]
log.info("{} has received Idle-PC proposals".format(router.name()))
idlepcs = result
if idlepcs and idlepcs[0] != "0x0":
dialog = IdlePCDialog(router, idlepcs, parent=self)
dialog.show()
dialog.exec_()
else:
QtGui.QMessageBox.critical(self, "Idle-PC", "Sorry no Idle-PC values could be computed, please check again with Cisco IOS in a different state")
def _showIdlepcProposals(self):
def autoIdlepcActionSlot(self):
"""
Slot to receive events from the auto idlepc action in the
contextual menu.
"""
items = self.scene().selectedItems()
if len(items) != 1:
QtGui.QMessageBox.critical(self, "Auto Idle-PC", "Please select only one router")
return
item = items[0]
if isinstance(item, NodeItem) and hasattr(item.node(), "idlepc") and item.node().initialized():
router = item.node()
router.computeAutoIdlepc(self._autoIdlepcCallback)
def _autoIdlepcCallback(self, result, error=False, context={}, **kwargs):
"""
Slot to allow the user to select an idlepc value.
"""
self._idlepc_progress_dialog.accept()
router = self.scene().selectedItems()[0].node()
router.idlepc_signal.disconnect(self._showIdlepcProposals)
router.server_error_signal.disconnect(self._showIdlepcError)
idlepcs = router.idlepcs()
if idlepcs and idlepcs[0] != "0x0":
dialog = IdlePCDialog(router, idlepcs, parent=self)
dialog.show()
dialog.exec_()
if error:
QtGui.QMessageBox.critical(self, "Auto Idle-PC", "Error: {}".format(result["message"]))
else:
QtGui.QMessageBox.critical(self, "Idle-PC", "Sorry no Idle-PC values could be computed, please check again with Cisco IOS in a different state")
router = context["router"]
idlepc = result["idlepc"]
log.info("{} has received the auto idle-pc value: {}".format(router.name(), idlepc))
router.setIdlepc(idlepc)
# apply the idle-pc to templates with the same IOS image
ios_image = os.path.basename(router.settings()["image"])
router.module().updateImageIdlepc(ios_image, idlepc)
QtGui.QMessageBox.information(self, "Auto Idle-PC", "Idle-PC value {} has been applied on {}".format(idlepc, router.name()))
def duplicateActionSlot(self):
"""
@@ -1066,6 +1263,32 @@ class GraphicsView(QtGui.QGraphicsView):
text_edit_dialog.show()
text_edit_dialog.exec_()
def horizontalAlignmentSlot(self):
"""
Slot to receive events from the horizontal align action in the
contextual menu.
"""
horizontal_pos = None
for item in self.scene().selectedItems():
if item.parentItem() is None:
if horizontal_pos is None:
horizontal_pos = item.y()
item.setPos(item.x(), horizontal_pos)
def verticalAlignmentSlot(self):
"""
Slot to receive events from the vertical align action in the
contextual menu.
"""
vertical_position = None
for item in self.scene().selectedItems():
if item.parentItem() is None:
if vertical_position is None:
vertical_position = item.x()
item.setPos(vertical_position, item.y())
def raiseLayerActionSlot(self):
"""
Slot to receive events from the raise one layer action in the
@@ -1089,6 +1312,8 @@ class GraphicsView(QtGui.QGraphicsView):
current_zvalue = item.zValue()
item.setZValue(current_zvalue - 1)
item.update()
if item.zValue() == -1:
self._background_warning_msgbox.showMessage("Object moved to a background layer. You will now have to use the right-click action to select this object in the future and raise it to layer 0 to be able to move it")
def deleteActionSlot(self):
"""
@@ -1127,7 +1352,6 @@ class GraphicsView(QtGui.QGraphicsView):
"""
try:
log.debug('In createNode')
node_module = None
for module in MODULES:
instance = module.instance()
@@ -1139,7 +1363,7 @@ class GraphicsView(QtGui.QGraphicsView):
if not node_module:
raise ModuleError("Could not find any module for {}".format(node_class))
if not "server" in node_data:
if "server" not in node_data:
server = node_module.allocateServer(node_class)
elif node_data["server"] == "local":
server = Servers.instance().localServer()
@@ -1151,10 +1375,11 @@ class GraphicsView(QtGui.QGraphicsView):
except ValueError:
raise ModuleError("Wrong format for server: '{}', please recreate the node in preferences".format(node_data["server"]))
server = Servers.instance().getRemoteServer(host, port)
if not server.connected() and ConnectToServer(self, server) is False:
if server is None:
return
node = node_module.createNode(node_class, server)
node = node_module.createNode(node_class, server, self._main_window.project())
node.error_signal.connect(self._main_window.uiConsoleTextEdit.writeError)
node.warning_signal.connect(self._main_window.uiConsoleTextEdit.writeWarning)
node.server_error_signal.connect(self._main_window.uiConsoleTextEdit.writeServerError)

407
gns3/http_client.py Normal file
View File

@@ -0,0 +1,407 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import json
import http
import uuid
import urllib.parse
import urllib.request
from functools import partial
from .version import __version__, __version_info__
from .qt import QtCore, QtNetwork
import logging
log = logging.getLogger(__name__)
class HttpBadRequest(Exception):
pass
class HTTPClient(QtCore.QObject):
"""
HTTP client.
:param url: URL to connect to the server
:param network_manager: A QT network manager
"""
_instance_count = 1
# Callback class used for displaying progress
_progress_callback = None
connected_signal = QtCore.Signal()
connection_error_signal = QtCore.Signal(str)
def __init__(self, url, network_manager):
super().__init__()
self._url = url
self._version = ""
url_settings = urllib.parse.urlparse(url)
self.scheme = url_settings.scheme
self.host = url_settings.netloc.split(":")[0]
self.port = url_settings.port
self._connected = False
self._local = True
self._cloud = False
self._network_manager = network_manager
# create an unique ID
self._id = HTTPClient._instance_count
HTTPClient._instance_count += 1
def notify_progress_start_query(self, query_id):
"""
Called when a query start
"""
if HTTPClient._progress_callback:
HTTPClient._progress_callback.add_query_signal.emit(query_id, "Waiting for {scheme}://{host}:{port}".format(scheme=self.scheme, host=self.host, port=self.port))
def notify_progress_end_query(cls, query_id):
"""
Called when a query is over
"""
if HTTPClient._progress_callback:
HTTPClient._progress_callback.remove_query_signal.emit(query_id)
@classmethod
def setProgressCallback(cls, progress_callback):
"""
:param progress_callback: A progress callback instance
"""
cls._progress_callback = progress_callback
@staticmethod
def reset():
"""Reset HTTP client internal variables"""
HTTPClient._instance_count = 0
def url(self):
"""Returns current server url"""
return "{scheme}://{host}:{port}".format(scheme=self.scheme, host=self.host, port=self.port)
def id(self):
"""
Returns this HTTP Client identifier.
:returns: HTTP client identifier (string)
"""
return self._id
def setLocal(self, value):
"""
Sets either this is a connection to a local server or not.
:param value: boolean
"""
self._local = value
def isLocal(self):
"""
Returns either this is a connection to a local server or not.
:returns: boolean
"""
return self._local
def connected(self):
"""
Returns if the client is connected.
:returns: True or False
"""
return self._connected
def close(self):
"""
Closes the connection with the server.
"""
self._connected = False
def isServerRunning(self):
"""
Check if a server is already running on this host.
:returns: boolean
"""
try:
url = "{scheme}://{host}:{port}/v1/version".format(scheme=self.scheme, host=self.host, port=self.port)
response = urllib.request.urlopen(url, timeout=2)
content_type = response.getheader("CONTENT-TYPE")
if response.status == 200 and content_type == "application/json":
content = response.read()
json_data = json.loads(content.decode("utf-8"))
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)")
return False
return True
except (OSError, urllib.error.HTTPError, http.client.BadStatusLine) as e:
log.debug("No GNS3 server is already running on {}:{}: {}".format(self.host, self.port, e))
return False
def get(self, path, callback, context={}):
"""
HTTP GET on the remote server
:param path: Remote path
:param callback: callback method to call when the server replies
:param context: Pass a context to the response callback
"""
self.createHTTPQuery("GET", path, callback, context=context)
def put(self, path, callback, body={}, context={}):
"""
HTTP PUT on the remote server
:param path: Remote path
:param callback: callback method to call when the server replies
:param context: Pass a context to the response callback
:param body: params to send (dictionary)
"""
self.createHTTPQuery("PUT", path, callback, context=context, body=body)
def post(self, path, callback, body={}, context={}):
"""
HTTP POST on the remote server
:param path: Remote path
:param callback: callback method to call when the server replies
:param context: Pass a context to the response callback
:param body: params to send (dictionary)
"""
self.createHTTPQuery("POST", path, callback, context=context, body=body)
def delete(self, path, callback, context={}):
"""
HTTP DELETE on the remote server
:param path: Remote path
:param callback: callback method to call when the server replies
:param context: Pass a context to the response callback
"""
self.createHTTPQuery("DELETE", path, callback, context=context)
def _request(self, url):
"""
Get a QNetworkRequest object. You can mock this
if you want low level mocking.
:param url: Url of remote ressource (QtCore.QUrl)
:returns: QT Network request (QtNetwork.QNetworkRequest)
"""
return QtNetwork.QNetworkRequest(url)
def createHTTPQuery(self, method, path, callback, body={}, context={}):
"""
Call the remote server, if not connected, check connection before
:param method: HTTP method
:param path: Remote path
:param body: params to send (dictionary)
:param callback: callback method to call when the server replies
:param context: Pass a context to the response callback
"""
if self._connected:
self.executeHTTPQuery(method, path, callback, body, context=context)
else:
log.info("Connection to {}:{}".format(self.host, self.port))
self.executeHTTPQuery("GET", "/version", partial(self._callbackConnect, method, path, callback, body, context), {})
def _callbackConnect(self, method, path, callback, body, original_context, params, error=False, **kwargs):
"""
Callback after /version response. Continue execution of query
:param method: HTTP method
:param path: Remote path
:param body: params to send (dictionary)
:param original_context: Original context
:param callback: callback method to call when the server replies
"""
if error is not False:
msg = "Can't connect to server {}://{}:{}".format(self.scheme, self.host, self.port)
if callback is not None:
callback({"message": msg}, error=True, server=self)
return
if "version" not in params or "local" not in params:
msg = "The remote server {}://{}:{} is not a GNS 3 server".format(self.scheme, self.host, self.port)
log.error(msg)
if callback is not None:
callback({"message": msg}, error=True, server=self)
return
if params["version"] != __version__:
msg = "Client version {} differs with server version {}".format(__version__, params["version"])
log.error(msg)
# Official release
if __version_info__[3] == 0:
if callback is not None:
callback({"message": msg}, error=True, server=self)
return
else:
print(msg)
print("WARNING: Use a different client and server version can create bugs. Use it at your own risk.")
if params["local"] != self.isLocal():
msg = "Running server is not a GNS3 local server (not started with --local)"
log.error(msg)
if callback is not None:
callback({"message": msg}, error=True, server=self)
return
self.executeHTTPQuery(method, path, callback, body, context=original_context)
self._connected = True
self._version = params["version"]
def executeHTTPQuery(self, method, path, callback, body, context={}):
"""
Call the remote server
:param method: HTTP method
:param path: Remote path
:param body: params to send (dictionary)
:param callback: callback method to call when the server replies
:param context: Pass a context to the response callback
"""
import copy
context = copy.copy(context)
context["query_id"] = str(uuid.uuid4())
self.notify_progress_start_query(context["query_id"])
log.debug("{method} {scheme}://{host}:{port}/v1{path} {body}".format(method=method, scheme=self.scheme, host=self.host, port=self.port, path=path, body=body))
url = QtCore.QUrl("{scheme}://{host}:{port}/v1{path}".format(scheme=self.scheme, host=self.host, port=self.port, path=path))
request = self._request(url)
request.setRawHeader("Content-Type", "application/json")
request.setRawHeader("Content-Length", str(len(body)))
request.setRawHeader("User-Agent", "GNS3 QT Client v{version}".format(version=__version__))
if method == "GET":
response = self._network_manager.get(request)
if method == "PUT":
body = json.dumps(body)
request.setRawHeader("Content-Type", "application/json")
request.setRawHeader("Content-Length", str(len(body)))
response = self._network_manager.put(request, body)
if method == "POST":
body = json.dumps(body)
request.setRawHeader("Content-Type", "application/json")
request.setRawHeader("Content-Length", str(len(body)))
response = self._network_manager.post(request, body)
if method == "DELETE":
response = self._network_manager.deleteResource(request)
if HTTPClient._progress_callback and HTTPClient._progress_callback.progress_dialog():
request_canceled = partial(self._requestCanceled, response, context)
HTTPClient._progress_callback.progress_dialog().canceled.connect(request_canceled)
response.finished.connect(partial(self._processResponse, response, callback, context))
def _requestCanceled(self, response, context):
if response.isRunning():
response.abort()
if "query_id" in context:
self.notify_progress_end_query(context["query_id"])
def _processResponse(self, response, callback, context):
status = None
body = None
if "query_id" in context:
self.notify_progress_end_query(context["query_id"])
if response.error() != QtNetwork.QNetworkReply.NoError:
error_code = response.error()
if error_code < 200:
self._connected = False
else:
status = response.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
error_message = response.errorString()
log.info("Response error: {}".format(error_message))
try:
body = bytes(response.readAll()).decode("utf-8")
# Some time anti-virus intercept our query and reply with garbage content
except UnicodeError:
body = None
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
if callback is not None:
if not body or content_type != "application/json":
callback({"message": error_message}, error=True, server=self, context=context)
else:
log.debug(body)
callback(json.loads(body), error=True, server=self, context=context)
else:
status = response.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
log.debug("Decoding response from {} response {}".format(response.url().toString(), status))
body = bytes(response.readAll()).decode("utf-8")
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
log.debug(body)
if body and content_type == "application/json":
params = json.loads(body)
else:
params = {}
if callback is not None:
if status >= 400:
callback(params, error=True, server=self, context=context)
else:
callback(params, server=self, context=context)
# response.deleteLater()
if status == 400:
raise HttpBadRequest(body)
def dump(self):
"""
Returns a representation of this server.
:returns: dictionary
"""
return {"id": self._id,
"host": self.host,
"port": self.port,
"local": self._local,
"cloud": self._cloud}
def isCloud(self):
return False

View File

@@ -24,6 +24,7 @@ from .shape_item import ShapeItem
class EllipseItem(ShapeItem, QtGui.QGraphicsEllipseItem):
"""
Class to draw an ellipse on the scene.
"""

View File

@@ -26,6 +26,7 @@ from ..ports.port import Port
class EthernetLinkItem(LinkItem):
"""
Ethernet link for the scene.

View File

@@ -23,6 +23,7 @@ from ..qt import QtCore, QtGui
class ImageItem(QtGui.QGraphicsPixmapItem):
"""
Class to insert an image on the scene.
"""

View File

@@ -27,6 +27,7 @@ from ..qt import QtCore, QtGui
class LinkItem(QtGui.QGraphicsPathItem):
"""
Base class for link items.
@@ -235,6 +236,18 @@ class LinkItem(QtGui.QGraphicsPathItem):
self._hovered = False
self.adjust()
def keyPressEvent(self, event):
"""
Handles all key press events
:param event: QKeyEvent
"""
#On pressing backspace or delete key, the selected link gets deleted
if event.key() == QtCore.Qt.Key_Delete or event.key() == QtCore.Qt.Key_Backspace:
self._deleteActionSlot()
return
def _deleteActionSlot(self):
"""
Slot to receive events from the delete action in the

View File

@@ -24,6 +24,7 @@ from .note_item import NoteItem
class NodeItem(QtSvg.QGraphicsSvgItem):
"""
Node for the scene.
@@ -93,6 +94,10 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
# from the server.
self._last_error = None
from ..main_window import MainWindow
self._main_window = MainWindow.instance()
self._settings = self._main_window.uiGraphicsView.settings()
def defaultRenderer(self):
"""
Returns the default QSvgRenderer.
@@ -225,7 +230,9 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
"""
if self._node_label:
self._node_label.setPlainText(self._node.name())
if self._node_label.toPlainText() != self._node.name():
self._node_label.setPlainText(self._node.name())
self._centerLabel()
self.setUnsavedState()
# update the link tooltips in case the
@@ -253,13 +260,12 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
self.scene().removeItem(self)
self.setUnsavedState()
def serverErrorSlot(self, node_id, code, message):
def serverErrorSlot(self, node_id, message):
"""
Slot to receive events from the attached Node instance
when the node has received an error from the server.
:param node_id: node identifier
:param code: error code
:param message: error message
"""
@@ -308,6 +314,19 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
self._node_label = label
def _centerLabel(self):
"""
Centers the node label.
"""
text_rect = self._node_label.boundingRect()
text_middle = text_rect.topRight() / 2
node_rect = self.boundingRect()
node_middle = node_rect.topRight() / 2
label_x_pos = node_middle.x() - text_middle.x()
label_y_pos = -25
self._node_label.setPos(label_x_pos, label_y_pos)
def _showLabel(self):
"""
Shows the node label on the scene.
@@ -317,13 +336,7 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
self._node_label = NoteItem(self)
self._node_label.setEditable(False)
self._node_label.setPlainText(self._node.name())
text_rect = self._node_label.boundingRect()
text_middle = text_rect.topRight() / 2
node_rect = self.boundingRect()
node_middle = node_rect.topRight() / 2
label_x_pos = node_middle.x() - text_middle.x()
label_y_pos = -25
self._node_label.setPos(label_x_pos, label_y_pos)
self._centerLabel()
def connectToPort(self, unavailable_ports=[]):
"""
@@ -341,29 +354,36 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
QtGui.QMessageBox.critical(self.scene().parent(), "Link", "No port available, please configure this device")
return None
# sort by port name
port_names = {}
# sort the ports
ports_dict = {}
for port in ports:
port_names[port.name()] = port
if port.adapterNumber() is not None:
# make the port number unique (special case with WICs).
port_number = port.portNumber()
if port_number >= 16:
port_number *= 4
ports_dict[(port.adapterNumber() * 16) + port_number] = port
elif port.portNumber()is not None:
ports_dict[port.portNumber()] = port
else:
ports_dict[port.name()] = port
try:
# try a numeric sort first
ports = sorted(port_names.keys(), key=int)
ports = sorted(ports_dict.keys(), key=int)
except ValueError:
# fall back to a classic sort
ports = sorted(port_names.keys())
ports = sorted(ports_dict.keys())
# show a contextual menu for the user to choose a port
for port in ports:
port_object = port_names[port]
port_object = ports_dict[port]
if port in unavailable_ports:
# this port cannot be chosen by the user (grayed out)
action = menu.addAction(QtGui.QIcon(':/icons/led_green.svg'), port)
action = menu.addAction(QtGui.QIcon(':/icons/led_green.svg'), port_object.name())
action.setDisabled(True)
elif port_object.isFree():
menu.addAction(QtGui.QIcon(':/icons/led_red.svg'), port)
menu.addAction(QtGui.QIcon(':/icons/led_red.svg'), port_object.name())
else:
menu.addAction(QtGui.QIcon(':/icons/led_green.svg'), port)
menu.addAction(QtGui.QIcon(':/icons/led_green.svg'), port_object.name())
menu.triggered.connect(self.selectedPortSlot)
menu.exec_(QtGui.QCursor.pos())
@@ -418,7 +438,8 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
"""
# don't show the selection rectangle
option.state = QtGui.QStyle.State_None
if not self._settings["draw_rectangle_selected_item"]:
option.state = QtGui.QStyle.State_None
QtSvg.QGraphicsSvgItem.paint(self, painter, option, widget)
if not self._initialized or self.show_layer:
@@ -470,10 +491,10 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
# dynamically change the renderer when this node item is hovered.
if not self.isSelected():
self.setSharedRenderer(self._hover_renderer)
#effect = QtGui.QGraphicsColorizeEffect()
#effect.setColor(QtGui.QColor("black"))
#effect.setStrength(0.8)
#self.setGraphicsEffect(effect)
# effect = QtGui.QGraphicsColorizeEffect()
# effect.setColor(QtGui.QColor("black"))
# effect.setStrength(0.8)
# self.setGraphicsEffect(effect)
def hoverLeaveEvent(self, event):
"""
@@ -485,4 +506,4 @@ class NodeItem(QtSvg.QGraphicsSvgItem):
# dynamically change the renderer back to the default when this node item is not hovered anymore.
if not self.isSelected():
self.setSharedRenderer(self._default_renderer)
#self.graphicsEffect().setEnabled(False)
# self.graphicsEffect().setEnabled(False)

View File

@@ -23,6 +23,7 @@ from ..qt import QtCore, QtGui
class NoteItem(QtGui.QGraphicsTextItem):
"""
Text note for the QGraphicsView.
@@ -77,9 +78,9 @@ class NoteItem(QtGui.QGraphicsTextItem):
"""
self._editable = value
#if not self._editable:
# if not self._editable:
# self.setFlag(self.ItemIsSelectable, enabled=False)
#else:
# else:
# self.setFlag(self.ItemIsSelectable)
def keyPressEvent(self, event):
@@ -234,7 +235,7 @@ class NoteItem(QtGui.QGraphicsTextItem):
if color:
self.setDefaultTextColor(QtGui.QColor(color))
if rotation is not None:
self.setRotation(rotation)
self.setRotation(float(rotation))
if z is not None:
self.setZValue(z)

View File

@@ -24,6 +24,7 @@ from .shape_item import ShapeItem
class RectangleItem(ShapeItem, QtGui.QGraphicsRectItem):
"""
Class to draw a rectangle on the scene.
"""

View File

@@ -27,6 +27,7 @@ from ..ports.port import Port
class SerialLinkItem(LinkItem):
"""
Serial link for the scene.

View File

@@ -23,6 +23,7 @@ from ..qt import QtCore, QtGui
class ShapeItem:
"""
Base class to draw shapes on the scene.
"""
@@ -280,7 +281,7 @@ class ShapeItem:
color = QtGui.QColor(color)
else:
color = QtGui.QColor(255, 255, 255)
if transparency:
if transparency is not None:
color.setAlpha(transparency)
self.setBrush(QtGui.QBrush(color))
@@ -294,7 +295,7 @@ class ShapeItem:
pen.setColor(border_color)
if border_width is not None:
pen.setWidth(int(border_width))
if border_style:
if border_style is not None:
pen.setStyle(QtCore.Qt.PenStyle(border_style))
self.setPen(pen)

View File

@@ -1,184 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
JSON-RPC protocol implementation.
http://www.jsonrpc.org/specification
"""
import json
import uuid
class JSONRPCObject(object):
"""
Base object for JSON-RPC requests, responses,
notifications and errors.
"""
def __init__(self):
return JSONRPCEncoder().default(self)
def __str__(self, *args, **kwargs):
return json.dumps(self, cls=JSONRPCEncoder)
def __call__(self):
return JSONRPCEncoder().default(self)
class JSONRPCEncoder(json.JSONEncoder):
"""
Creates the JSON-RPC message.
"""
def default(self, obj):
"""
Returns a Python dictionary corresponding to a JSON-RPC message.
"""
if isinstance(obj, JSONRPCObject):
message = {"jsonrpc": 2.0}
for field in dir(obj):
if not field.startswith('_'):
value = getattr(obj, field)
message[field] = value
return message
return json.JSONEncoder.default(self, obj)
class JSONRPCInvalidRequest(JSONRPCObject):
"""
Error response for an invalid request.
"""
def __init__(self):
JSONRPCObject.__init__(self)
self.id = None
self.error = {"code": -32600, "message": "Invalid Request"}
class JSONRPCMethodNotFound(JSONRPCObject):
"""
Error response for an method not found.
:param request_id: JSON-RPC identifier
"""
def __init__(self, request_id):
JSONRPCObject.__init__(self)
self.id = request_id
self.error = {"code": -32601, "message": "Method not found"}
class JSONRPCInvalidParams(JSONRPCObject):
"""
Error response for invalid parameters.
:param request_id: JSON-RPC identifier
"""
def __init__(self, request_id):
JSONRPCObject.__init__(self)
self.id = request_id
self.error = {"code": -32602, "message": "Invalid params"}
class JSONRPCInternalError(JSONRPCObject):
"""
Error response for an internal error.
:param request_id: JSON-RPC identifier (optional)
"""
def __init__(self, request_id=None):
JSONRPCObject.__init__(self)
self.id = request_id
self.error = {"code": -32603, "message": "Internal error"}
class JSONRPCParseError(JSONRPCObject):
"""
Error response for parsing error.
"""
def __init__(self):
JSONRPCObject.__init__(self)
self.id = None
self.error = {"code": -32700, "message": "Parse error"}
class JSONRPCCustomError(JSONRPCObject):
"""
Error response for an custom error.
:param code: JSON-RPC error code
:param message: JSON-RPC error message
:param request_id: JSON-RPC identifier (optional)
"""
def __init__(self, code, message, request_id=None):
JSONRPCObject.__init__(self)
self.id = request_id
self.error = {"code": code, "message": message}
class JSONRPCResponse(JSONRPCObject):
"""
JSON-RPC successful response.
:param result: JSON-RPC result
:param request_id: JSON-RPC identifier
"""
def __init__(self, result, request_id):
JSONRPCObject.__init__(self)
self.id = request_id
self.result = result
class JSONRPCRequest(JSONRPCObject):
"""
JSON-RPC request.
:param method: JSON-RPC destination method
:param params: JSON-RPC params for the corresponding method (optional)
:param request_id: JSON-RPC identifier (generated by default)
"""
def __init__(self, method, params=None, request_id=None):
JSONRPCObject.__init__(self)
if request_id == None:
request_id = str(uuid.uuid4())
self.id = request_id
self.method = method
if params:
self.params = params
class JSONRPCNotification(JSONRPCObject):
"""
JSON-RPC notification.
:param method: JSON-RPC destination method
:param params: JSON-RPC params for the corresponding method (optional)
"""
def __init__(self, method, params=None):
JSONRPCObject.__init__(self)
self.method = method
if params:
self.params = params

View File

@@ -28,6 +28,7 @@ log = logging.getLogger(__name__)
class Link(QtCore.QObject):
"""
Link implementation.
@@ -136,10 +137,12 @@ class Link(QtCore.QObject):
self._destination_port.name()))
# delete the NIOs on both source and destination nodes
self._source_node.deleteNIO(self._source_port)
if self._source_port.nio():
self._source_node.deleteNIO(self._source_port)
self._source_port.setFree()
self._source_node.updated_signal.emit()
self._destination_node.deleteNIO(self._destination_port)
if self._destination_port.nio():
self._destination_node.deleteNIO(self._destination_port)
self._destination_port.setFree()
self._destination_node.updated_signal.emit()
@@ -343,10 +346,6 @@ class Link(QtCore.QObject):
# ignore TypeError: 'method' object is not connected
pass
self._source_node.deleteNIO(self._source_port)
self._source_port.setFree()
self._source_node.updated_signal.emit()
elif self._destination_node.id() != node_id:
try:
# the source node has canceled its NIO allocation
@@ -355,10 +354,6 @@ class Link(QtCore.QObject):
# ignore TypeError: 'method' object is not connected
pass
self._destination_node.deleteNIO(self._destination_port)
self._destination_port.setFree()
self._destination_node.updated_signal.emit()
self._source_node.nio_cancel_signal.disconnect(self.cancelNIOSlot)
self._destination_node.nio_cancel_signal.disconnect(self.cancelNIOSlot)
else:
@@ -371,6 +366,7 @@ class Link(QtCore.QObject):
self._source_nio_active = False
self._destination_nio_active = False
self.deleteLink()
def dump(self):
"""

196
gns3/local_config.py Normal file
View File

@@ -0,0 +1,196 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import json
from .version import __version__
import logging
log = logging.getLogger(__name__)
class LocalConfig:
"""
Handles the local GUI settings.
"""
def __init__(self):
self._settings = {}
if sys.platform.startswith("win"):
filename = "gns3_gui.ini"
else:
filename = "gns3_gui.conf"
if sys.platform.startswith("darwin"):
appname = "gns3.net"
else:
appname = "GNS3"
if sys.platform.startswith("win"):
# On windows, the system wide configuration file location is %COMMON_APPDATA%/GNS3/gns3_gui.conf
common_appdata = os.path.expandvars("%COMMON_APPDATA%")
system_wide_config_file = os.path.join(common_appdata, appname, filename)
# On windows, the user specific configuration file location is %APPDATA%/GNS3/gns3_gui.conf
appdata = os.path.expandvars("%APPDATA%")
self._config_file = os.path.join(appdata, appname, filename)
else:
# On UNIX-like platforms, the system wide configuration file location is /etc/xdg/GNS3/gns3_gui.conf
system_wide_config_file = os.path.join("/etc/xdg", appname, filename)
# On UNIX-like platforms, the user specific configuration file location is /etc/xdg/GNS3/gns3_gui.conf
home = os.path.expanduser("~")
self._config_file = os.path.join(home, ".config", appname, filename)
# First load system wide settings
if os.path.exists(system_wide_config_file):
self._settings = self._readConfig(system_wide_config_file)
if not self._settings:
log.warning("No system wide settings loaded from {}".format(system_wide_config_file))
config_file_in_cwd = os.path.join(os.getcwd(), filename)
if os.path.exists(config_file_in_cwd):
# use any config file present in the current working directory
self._config_file = config_file_in_cwd
elif not os.path.exists(self._config_file):
try:
# create the config file if it doesn't exist
os.makedirs(os.path.dirname(self._config_file), exist_ok=True)
with open(self._config_file, "w", encoding="utf-8") as f:
json.dump({"version": __version__, "type": "settings"}, f)
except OSError as e:
log.error("Could not create the config file {}: {}".format(self._config_file, e))
user_settings = self._readConfig(self._config_file)
# overwrite system wide settings with user specific ones
self._settings.update(user_settings)
self._writeConfig()
def _readConfig(self, config_path):
"""
Read the configuration file.
"""
try:
with open(config_path, "r", encoding="utf-8") as f:
return json.load(f)
except (ValueError, OSError) as e:
log.error("Could not read the config file {}: {}".format(self._config_file, e))
return dict()
def _writeConfig(self):
"""
Write the configuration file.
"""
try:
with open(self._config_file, "w", encoding="utf-8") as f:
json.dump(self._settings, f, sort_keys=True, indent=4)
except (ValueError, OSError) as e:
log.error("Could not write the config file {}: {}".format(self._config_file, e))
def configFilePath(self):
"""
Returns the config file path.
:returns: path to the config file.
"""
return self._config_file
def setConfigFilePath(self, config_file):
"""
Set a new config file
:returns: path to the config file.
"""
self._settings = self._readConfig(self._config_file)
self._config_file = config_file
def settings(self):
"""
Get the settings.
:returns: settings (dict)
"""
return self._readConfig(self._config_file)
def setSettings(self, settings):
"""
Save the settings.
:param settings: settings to save (dict)
"""
self._settings.update(settings)
self._writeConfig()
def loadSectionSettings(self, section, default_settings):
"""
Get all the settings from a given section.
:param default_settings: setting names and default values (dict)
:returns: settings (dict)
"""
settings = self.settings().get(section, dict())
# use default values for missing settings
for name, value in default_settings.items():
if name not in settings:
settings[name] = value
if section not in self._settings:
self._settings[section] = {}
self._settings[section].update(settings)
return settings
def saveSectionSettings(self, section, settings):
"""
Save all the settings in a given section.
:param section: section name
:param settings: settings to save (dict)
"""
if section not in self._settings:
self._settings[section] = {}
self._settings[section].update(settings)
self._writeConfig()
@staticmethod
def instance():
"""
Singleton to return only on instance of LocalConfig.
:returns: instance of LocalConfig
"""
if not hasattr(LocalConfig, "_instance"):
LocalConfig._instance = LocalConfig()
return LocalConfig._instance

131
gns3/local_server_config.py Normal file
View File

@@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import configparser
from gns3.qt import QtCore
import logging
log = logging.getLogger(__name__)
class LocalServerConfig:
"""
Local server configuration.
"""
def __init__(self):
self._config = configparser.RawConfigParser()
if sys.platform.startswith("win"):
filename = "gns3_server.ini"
else:
filename = "gns3_server.conf"
self._config_file = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), filename)
try:
# create the config file if it doesn't exist
open(self._config_file, "a").close()
except OSError as e:
log.error("Could not create the local server configuration {}: {}".format(self._config_file, e))
self.readConfig()
def readConfig(self):
"""
Read the configuration file.
"""
try:
self._config.read(self._config_file)
except (OSError, configparser.Error) as e:
log.error("Could not read the local server configuration {}: {}".format(self._config_file, e))
def writeConfig(self):
"""
Write the configuration file.
"""
try:
log.debug("Write configuration file %s", self._config_file)
with open(self._config_file, "w", encoding="utf-8") as fp:
self._config.write(fp)
except (OSError, configparser.Error) as e:
log.error("Could not write the local server configuration {}: {}".format(self._config_file, e))
def loadSettings(self, section, default_settings, types):
"""
Get all the settings from a given section.
:param section: section name
:param default_settings: setting names and default values (dict)
:param types: setting types (dict)
:returns: settings (dict)
"""
if section not in self._config:
self._config[section] = {}
settings = {}
for name, default in default_settings.items():
if types[name] is int:
settings[name] = self._config[section].getint(name, default)
elif types[name] is bool:
settings[name] = self._config[section].getboolean(name, default)
elif types[name] is float:
settings[name] = self._config[section].getfloat(name, default)
else:
settings[name] = self._config[section].get(name, default)
# sync with the config file
self.saveSettings(section, settings)
return settings
def saveSettings(self, section, settings):
"""
Save all the settings in a given section.
:param section: section name
:param settings: settings to save (dict)
"""
changed = False
if section not in self._config:
self._config[section] = {}
changed = True
for name, value in settings.items():
if name not in self._config[section] or self._config[section][name] != str(value):
self._config[section][name] = str(value)
changed = True
if changed:
self.writeConfig()
@staticmethod
def instance():
"""
Singleton to return only on instance of LocalServerConfig.
:returns: instance of Config
"""
if not hasattr(LocalServerConfig, "_instance"):
LocalServerConfig._instance = LocalServerConfig()
return LocalServerConfig._instance

93
gns3/logger.py Normal file
View File

@@ -0,0 +1,93 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Provide a pretty logging on console"""
import logging
import sys
class ColouredFormatter(logging.Formatter):
RESET = '\x1B[0m'
RED = '\x1B[31m'
YELLOW = '\x1B[33m'
GREEN = '\x1B[32m'
PINK = '\x1b[35m'
def format(self, record, colour=False):
message = super().format(record)
if not colour:
return message.replace("#RESET#", "")
level_no = record.levelno
if level_no >= logging.CRITICAL:
colour = self.RED
elif level_no >= logging.ERROR:
colour = self.RED
elif level_no >= logging.WARNING:
colour = self.YELLOW
elif level_no >= logging.INFO:
colour = self.GREEN
elif level_no >= logging.DEBUG:
colour = self.PINK
else:
colour = self.RESET
message = message.replace("#RESET#", self.RESET)
message = '{colour}{message}{reset}'.format(colour=colour, message=message, reset=self.RESET)
return message
class ColouredStreamHandler(logging.StreamHandler):
def format(self, record, colour=False):
if not isinstance(self.formatter, ColouredFormatter):
self.formatter = ColouredFormatter()
return self.formatter.format(record, colour)
def emit(self, record):
stream = self.stream
try:
msg = self.format(record, stream.isatty())
stream.write(msg)
stream.write(self.terminator)
self.flush()
# On OSX when frozen flush raise a BrokenPipeError
except BrokenPipeError:
pass
except Exception:
self.handleError(record)
def init_logger(level, quiet=False):
if sys.platform.startswith("win"):
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {filename}:{lineno} {message}", "%Y-%m-%d %H:%M:%S", "{")
else:
stream_handler = ColouredStreamHandler(sys.stdout)
stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {filename}:{lineno}#RESET# {message}", "%Y-%m-%d %H:%M:%S", "{")
logging.basicConfig(level=level, handlers=[stream_handler])
logging.getLogger().addHandler(stream_handler)
return logging.getLogger()

View File

@@ -16,6 +16,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# WARNING
# Due to buggy user machines we choose to put this as the first loading modules
# otherwise the egg cache is initialized in his standard location and
# if is not writetable the application crash. It's the user fault
# because one day the user as used sudo to run an egg and break his
# filesystem permissions, but it's a common mistake.
from gns3.utils.get_resource import get_resource
import datetime
import sys
import os
@@ -24,6 +34,11 @@ import time
import locale
import argparse
from gns3.logger import init_logger
from gns3.crash_report import CrashReport
import logging
log = logging.getLogger(__name__)
@@ -82,10 +97,14 @@ def main():
"""
parser = argparse.ArgumentParser()
parser.add_argument('--version', help="show the version", action='version', version=__version__)
parser.add_argument('--debug', help="print out debug messages", action='store_true', default=False)
parser.add_argument("project", help="load a GNS3 project (.gns3)", metavar="path", nargs="?")
parser.add_argument("--version", help="show the version", action="version", version=__version__)
parser.add_argument("--debug", help="print out debug messages", action="store_true", default=False)
options = parser.parse_args()
exception_file_path = "exception.log"
exception_file_path = "exceptions.log"
if options.project and hasattr(sys, "frozen"):
os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
def exceptionHook(exception, value, tb):
@@ -94,21 +113,22 @@ 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/support/bug\n")
print("\nPLEASE REPORT ON https://community.gns3.com/community/software/bug\n")
print("".join(lines))
try:
curdate = time.strftime("%d %b %Y %H:%M:%S")
logfile = open(exception_file_path, "a")
logfile = open(exception_file_path, "a", encoding="utf-8")
logfile.write("=== GNS3 {} traceback on {} ===\n".format(__version__, curdate))
logfile.write("".join(lines))
logfile.close()
except OSError as e:
print("Could not save traceback to {}: {}".format(exception_file_path, e))
print("Could not save traceback to {}: {}".format(os.path.normpath(exception_file_path), e))
if not sys.stdout.isatty():
# if stdout is not a tty (redirected to the console view),
# then print the exception on stderr too.
print("".join(lines), file=sys.stderr)
CrashReport.instance().captureException(exception, value, tb)
# catch exceptions to write them in a file
sys.excepthook = exceptionHook
@@ -123,7 +143,8 @@ def main():
elif sys.version_info[0] == 3 and sys.version_info < (3, 3):
raise RuntimeError("Python 3.3 or higher is required")
version = lambda version_string: [int(i) for i in version_string.split('.')]
def version(version_string):
return [int(i) for i in version_string.split('.')]
if version(QtCore.QT_VERSION_STR) < version("4.6"):
raise RuntimeError("Requirement is Qt version 4.6 or higher, got version {}".format(QtCore.QT_VERSION_STR))
@@ -135,13 +156,6 @@ def main():
if DEFAULT_BINDING == "PySide" and version(QtCore.BINDING_VERSION_STR) < version("1.0"):
raise RuntimeError("Requirement is PySide version 1.0 or higher, got version {}".format(QtCore.BINDING_VERSION_STR))
try:
# if tornado is present then enable pretty logging.
import tornado.log
tornado.log.enable_pretty_logging()
except ImportError:
pass
# check for the correct locale
# (UNIX/Linux only)
locale_check()
@@ -156,7 +170,7 @@ def main():
if sys.platform.startswith('win') or sys.platform.startswith('darwin'):
QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
if sys.platform.startswith('win'):
if sys.platform.startswith('win') and hasattr(sys, "frozen"):
try:
import win32console
import win32con
@@ -164,61 +178,53 @@ def main():
except ImportError:
raise RuntimeError("Python for Windows extensions must be installed.")
try:
win32console.AllocConsole()
console_window = win32console.GetConsoleWindow()
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
except win32console.error as e:
print("warning: could not allocate console: {}".format(e))
exit_code = MainWindow.exit_code_reboot
while exit_code == MainWindow.exit_code_reboot:
exit_code = 0
app = QtGui.QApplication(sys.argv)
# this info is necessary for QSettings
app.setOrganizationName("GNS3")
app.setOrganizationDomain("gns3.net")
app.setApplicationName("GNS3")
app.setApplicationVersion(__version__)
# save client logging info to a file
logfile = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), "GNS3_client.log") # FIXME: does it work?
try:
if not options.debug:
try:
os.makedirs(os.path.dirname(QtCore.QSettings().fileName()))
except FileExistsError:
pass
handler = logging.FileHandler(logfile, "w")
if options.debug:
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
if len(root_logger.handlers) > 0:
root_handler = root_logger.handlers[0]
else:
root_handler = logging.StreamHandler()
root_logger.addHandler(root_handler)
root_handler.setLevel(logging.DEBUG)
else:
handler.setLevel(logging.INFO)
log.info('Log level: {}'.format(logging.getLevelName(log.getEffectiveLevel())))
# hide the console
console_window = win32console.GetConsoleWindow()
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
except win32console.error as e:
print("warning: could not allocate console: {}".format(e))
formatter = logging.Formatter("[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d] %(message)s",
datefmt="%y%m%d %H:%M:%S")
handler.setFormatter(formatter)
log.addHandler(handler)
except OSError as e:
log.warn("could not log to {}: {}".format(logfile, e))
app = QtGui.QApplication(sys.argv)
# update the exception file path to have it in the same directory as the settings file.
exception_file_path = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), exception_file_path)
# this info is necessary for QSettings
app.setOrganizationName("GNS3")
app.setOrganizationDomain("gns3.net")
app.setApplicationName("GNS3")
app.setApplicationVersion(__version__)
mainwindow = MainWindow.instance()
mainwindow.show()
exit_code = app.exec_()
delattr(MainWindow, "_instance")
app.deleteLater()
formatter = logging.Formatter("[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d] %(message)s",
datefmt="%y%m%d %H:%M:%S")
# on debug enable logging to stdout
if options.debug:
root_logger = init_logger(logging.DEBUG)
else:
root_logger = init_logger(logging.INFO)
# save client logging info to a file
logfile = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), "gns3_gui.log")
try:
try:
os.makedirs(os.path.dirname(QtCore.QSettings().fileName()))
except FileExistsError:
pass
handler = logging.FileHandler(logfile, "w")
root_logger.addHandler(handler)
except OSError as e:
log.warn("could not log to {}: {}".format(logfile, e))
log.info('Log level: {}'.format(logging.getLevelName(log.getEffectiveLevel())))
# update the exception file path to have it in the same directory as the settings file.
exception_file_path = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), exception_file_path)
mainwindow = MainWindow(options.project)
mainwindow.show()
exit_code = app.exec_()
delattr(MainWindow, "_instance")
app.deleteLater()
sys.exit(exit_code)

File diff suppressed because it is too large Load Diff

View File

@@ -22,4 +22,4 @@ from gns3.modules.vpcs import VPCS
from gns3.modules.virtualbox import VirtualBox
from gns3.modules.qemu import Qemu
MODULES = [Builtin, VPCS, Dynamips, IOU, VirtualBox, Qemu]
MODULES = [VPCS, Dynamips, IOU, VirtualBox, Qemu, Builtin]

View File

@@ -19,7 +19,6 @@
Built-in module implementation.
"""
import os
from gns3.qt import QtGui
from gns3.servers import Servers
from ..module import Module
@@ -33,6 +32,7 @@ log = logging.getLogger(__name__)
class Builtin(Module):
"""
Built-in module.
"""
@@ -41,54 +41,6 @@ class Builtin(Module):
Module.__init__(self)
self._nodes = []
self._servers = []
def setProjectFilesDir(self, path):
"""
Sets the project files directory path this module.
:param path: path to the local project files directory
"""
pass # not used by this module
def setImageFilesDir(self, path):
"""
Sets the image files directory path this module.
:param path: path to the local image files directory
"""
pass # not used by this module
def addServer(self, server):
"""
Adds a server to be used by this module.
:param server: WebSocketClient instance
"""
log.info("adding server {}:{} to built-in module".format(server.host, server.port))
self._servers.append(server)
def removeServer(self, server):
"""
Removes a server from being used by this module.
:param server: WebSocketClient instance
"""
log.info("removing server {}:{} from built-in module".format(server.host, server.port))
self._servers.remove(server)
def servers(self):
"""
Returns all the servers used by this module.
:returns: list of WebSocketClient instances
"""
return self._servers
def addNode(self, node):
"""
@@ -109,13 +61,21 @@ class Builtin(Module):
if node in self._nodes:
self._nodes.remove(node)
def reset(self):
"""
Resets the module.
"""
log.info("Built-in module reset")
self._nodes.clear()
def allocateServer(self, node_class):
"""
Allocates a server.
:param node_class: Node object
:returns: allocated server (WebSocketClient instance)
:returns: allocated server (HTTPClient instance)
"""
# check all other modules to find if they
@@ -137,7 +97,7 @@ class Builtin(Module):
if not all(using_local_server) and len(remote_servers):
# a module is not using a local server
if not True in using_local_server and len(remote_servers) == 1:
if True not in using_local_server and len(remote_servers) == 1:
# no module is using a local server and there is only one
# remote server available, so no need to ask the user.
return next(iter(servers))
@@ -147,7 +107,7 @@ class Builtin(Module):
for remote_server in remote_servers:
server_list.append("{}".format(remote_server))
#TODO: move this to graphics_view
# TODO: move this to graphics_view
from gns3.main_window import MainWindow
mainwindow = MainWindow.instance()
(selection, ok) = QtGui.QInputDialog.getItem(mainwindow, "Server", "Please choose a server", server_list, 0, False)
@@ -160,29 +120,19 @@ class Builtin(Module):
raise ModuleError("Please select a server")
return local_server
def createNode(self, node_class, server):
def createNode(self, node_class, server, project):
"""
Creates a new node.
:param node_class: Node object
:param server: WebSocketClient instance
:param server: HTTPClient instance
:param project: Project instance
"""
log.info("creating node {}".format(node_class))
if not server.connected():
try:
log.info("reconnecting to server {}:{}".format(server.host, server.port))
server.reconnect()
except OSError as e:
raise ModuleError("Could not connect to server {}:{}: {}".format(server.host,
server.port,
e))
if server not in self._servers:
self.addServer(server)
# create an instance of the node class
return node_class(self, server)
return node_class(self, server, project)
def setupNode(self, node, node_name):
"""
@@ -195,13 +145,6 @@ class Builtin(Module):
log.info("configuring node {}".format(node))
node.setup()
def reset(self):
"""
Resets the servers.
"""
self._servers.clear()
@staticmethod
def findAlternativeInterface(node, missing_interface):
@@ -218,6 +161,8 @@ class Builtin(Module):
available_interfaces, 0, False)
if ok:
return selection
QtGui.QMessageBox.warning(mainwindow, "Cloud interface", "No alternative interface chosen to replace {} on this host, this may lead to issues".format(missing_interface))
return None
else:
QtGui.QMessageBox.critical(mainwindow, "Cloud interface", "Could not find interface {} on this host".format(missing_interface))
return missing_interface

View File

@@ -25,6 +25,7 @@ from gns3.node import Node
from gns3.ports.port import Port
from gns3.nios.nio_generic_ethernet import NIOGenericEthernet
from gns3.nios.nio_linux_ethernet import NIOLinuxEthernet
from gns3.nios.nio_nat import NIONAT
from gns3.nios.nio_udp import NIOUDP
from gns3.nios.nio_tap import NIOTAP
from gns3.nios.nio_unix import NIOUNIX
@@ -36,17 +37,19 @@ log = logging.getLogger(__name__)
class Cloud(Node):
"""
Dynamips cloud.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
_name_instance_count = 1
def __init__(self, module, server):
Node.__init__(self, server)
def __init__(self, module, server, project):
Node.__init__(self, module, server, project)
log.info("cloud is being created")
# create an unique id and name
@@ -57,11 +60,10 @@ class Cloud(Node):
self.setStatus(Node.started) # this is an always-on node
self._defaults = {}
self._ports = []
self._module = module
self._initial_settings = None
self._settings = {"nios": [],
self._settings = {"name": name,
"interfaces": {},
"name": name}
"nios": []}
def delete(self):
"""
@@ -84,9 +86,10 @@ class Cloud(Node):
if initial_settings:
self._initial_settings = initial_settings
self._server.send_message("builtin.interfaces", None, self._setupCallback)
def _setupCallback(self, result, error=False):
self._server.get("/interfaces", self._setupCallback)
def _setupCallback(self, result, error=False, **kwargs):
"""
Callback for setup.
@@ -150,6 +153,19 @@ class Cloud(Node):
return NIOLinuxEthernet(linux_device)
return None
def _createNIONAT(self, nio):
"""
Creates a NIO NAT.
:param nio: nio string
"""
match = re.search(r"""^nio_nat:(.+)$""", nio)
if match:
identifier = match.group(1)
return NIONAT(identifier)
return None
def _createNIOTAP(self, nio):
"""
Creates a NIO TAP.
@@ -211,53 +227,57 @@ class Cloud(Node):
:param new_settings: settings dictionary
"""
nios = new_settings["nios"]
updated = False
# add ports
for nio in nios:
if nio in self._settings["nios"]:
# port already created for this NIO
continue
nio_object = None
if nio.lower().startswith("nio_udp"):
nio_object = self._createNIOUDP(nio)
if nio.lower().startswith("nio_gen_eth"):
nio_object = self._createNIOGenericEthernet(nio)
if nio.lower().startswith("nio_gen_linux"):
nio_object = self._createNIOLinuxEthernet(nio)
if nio.lower().startswith("nio_tap"):
nio_object = self._createNIOTAP(nio)
if nio.lower().startswith("nio_unix"):
nio_object = self._createNIOUNIX(nio)
if nio.lower().startswith("nio_vde"):
nio_object = self._createNIOVDE(nio)
if nio.lower().startswith("nio_null"):
nio_object = self._createNIONull(nio)
if nio_object == None:
log.error("Could not create NIO object from {}".format(nio))
continue
port = Port(nio, nio_object, stub=True)
port.setStatus(Port.started)
self._ports.append(port)
updated = True
log.debug("port {} has been added".format(nio))
if "nios" in new_settings:
nios = new_settings["nios"]
# delete ports
for nio in self._settings["nios"]:
if nio not in nios:
for port in self._ports.copy():
if port.name() == nio:
self._ports.remove(port)
updated = True
log.debug("port {} has been deleted".format(nio))
break
# add ports
for nio in nios:
if nio in self._settings["nios"]:
# port already created for this NIO
continue
nio_object = None
if nio.lower().startswith("nio_udp"):
nio_object = self._createNIOUDP(nio)
if nio.lower().startswith("nio_gen_eth"):
nio_object = self._createNIOGenericEthernet(nio)
if nio.lower().startswith("nio_gen_linux"):
nio_object = self._createNIOLinuxEthernet(nio)
if nio.lower().startswith("nio_nat"):
nio_object = self._createNIONAT(nio)
if nio.lower().startswith("nio_tap"):
nio_object = self._createNIOTAP(nio)
if nio.lower().startswith("nio_unix"):
nio_object = self._createNIOUNIX(nio)
if nio.lower().startswith("nio_vde"):
nio_object = self._createNIOVDE(nio)
if nio.lower().startswith("nio_null"):
nio_object = self._createNIONull(nio)
if nio_object is None:
log.error("Could not create NIO object from {}".format(nio))
continue
port = Port(nio, nio_object, stub=True)
port.setStatus(Port.started)
self._ports.append(port)
updated = True
log.debug("port {} has been added".format(nio))
# delete ports
for nio in self._settings["nios"]:
if nio not in nios:
for port in self._ports.copy():
if port.name() == nio:
self._ports.remove(port)
updated = True
log.debug("port {} has been deleted".format(nio))
break
self._settings["nios"] = new_settings["nios"].copy()
if "name" in new_settings and new_settings["name"] != self.name():
self._settings["name"] = new_settings["name"]
updated = True
self._settings["nios"] = new_settings["nios"].copy()
if updated:
log.info("cloud {} has been updated".format(self.name()))
self.updated_signal.emit()
@@ -309,7 +329,7 @@ This is a pseudo-device for external connections
"properties": {"name": self.name(),
"nios": self._settings["nios"]},
"server_id": self._server.id(),
}
}
# add the ports
if self._ports:
@@ -357,11 +377,12 @@ This is a pseudo-device for external connections
break
if not available_interface:
alternative_interface = self._module.findAlternativeInterface(self, topology_port_name)
if topology_port["name"] in self._settings["nios"]:
self._settings["nios"].remove(topology_port["name"])
topology_port["name"] = topology_port["name"].replace(topology_port_name, alternative_interface)
port.setName(topology_port["name"])
self._settings["nios"].append(topology_port["name"])
if alternative_interface:
if topology_port["name"] in self._settings["nios"]:
self._settings["nios"].remove(topology_port["name"])
topology_port["name"] = topology_port["name"].replace(topology_port_name, alternative_interface)
port.setName(topology_port["name"])
self._settings["nios"].append(topology_port["name"])
log.info("cloud {} has been created".format(self.name()))
self.setInitialized(True)

View File

@@ -24,27 +24,44 @@ log = logging.getLogger(__name__)
class Host(Cloud):
"""
Pseudo host based on a Dynamips Cloud.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
_name_instance_count = 1
def __init__(self, module, server):
Cloud.__init__(self, module, server)
def __init__(self, module, server, project):
Cloud.__init__(self, module, server, project)
log.info("host is being created")
# create an unique id and name
self._name_id = Host._name_instance_count
Host._name_instance_count += 1
name = "Host {}".format(self._name_id)
name = "Host{}".format(self._name_id)
self._settings["name"] = name
self.created_signal.connect(self._autoConfigure)
def setup(self, name=None, initial_settings={}):
"""
Setups this host.
:param name: optional name for this host
"""
if name:
self._settings["name"] = name
if initial_settings:
self._initial_settings = initial_settings
else:
self.created_signal.connect(self._autoConfigure)
self._server.get("/interfaces", self._setupCallback)
def _autoConfigure(self, node_id):
"""

View File

@@ -25,6 +25,7 @@ from ..ui.cloud_configuration_page_ui import Ui_cloudConfigPageWidget
class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
"""
QWidget configuration page for clouds.
"""
@@ -47,6 +48,12 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
self.uiAddLinuxEthernetPushButton.clicked.connect(self._linuxEthernetAddSlot)
self.uiDeleteLinuxEthernetPushButton.clicked.connect(self._linuxEthernetDeleteSlot)
# connect NIO NAT slots
self.uiNIONATListWidget.currentRowChanged.connect(self._NIONATSelectedSlot)
self.uiNIONATListWidget.itemSelectionChanged.connect(self._NIONATChangedSlot)
self.uiAddNIONATPushButton.clicked.connect(self._NIONATAddSlot)
self.uiDeleteNIONATPushButton.clicked.connect(self._NIONATDeleteSlot)
# connect NIO UDP slots
self.uiNIOUDPListWidget.currentRowChanged.connect(self._NIOUDPSelectedSlot)
self.uiNIOUDPListWidget.itemSelectionChanged.connect(self._NIOUDPChangedSlot)
@@ -105,7 +112,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
interface = self.uiGenericEthernetLineEdit.text()
if interface:
nio = "nio_gen_eth:{interface}".format(interface=interface)
if not nio in self._nios:
if nio not in self._nios:
self.uiGenericEthernetListWidget.addItem(nio)
self._nios.append(nio)
@@ -154,7 +161,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
interface = self.uiLinuxEthernetLineEdit.text()
if interface:
nio = "nio_gen_linux:{interface}".format(interface=interface)
if not nio in self._nios:
if nio not in self._nios:
self.uiLinuxEthernetListWidget.addItem(nio)
self._nios.append(nio)
@@ -175,9 +182,65 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
self._nios.remove(nio)
self.uiLinuxEthernetListWidget.takeItem(self.uiLinuxEthernetListWidget.currentRow())
def _NIONATSelectedSlot(self, index):
"""
Loads a selected NAT NIO.
:param index: ignored
"""
item = self.uiNIONATListWidget.currentItem()
if item:
nio = item.text()
match = re.search(r"""^nio_nat:(.+)$""", nio)
if match:
self.uiNIONATIdentiferLineEdit.setText(match.group(1))
def _NIONATChangedSlot(self):
"""
Enables the use of the delete button.
"""
item = self.uiNIONATListWidget.currentItem()
if item:
self.uiDeleteNIONATPushButton.setEnabled(True)
else:
self.uiDeleteNIONATPushButton.setEnabled(False)
def _NIONATAddSlot(self):
"""
Adds a new NAT NIO.
"""
identifier = self.uiNIONATIdentiferLineEdit.text()
if identifier:
nio = "nio_nat:{}".format(identifier)
if nio not in self._nios:
self.uiNIONATListWidget.addItem(nio)
self._nios.append(nio)
def _NIONATDeleteSlot(self):
"""
Deletes a NAT NIO.
"""
item = self.uiNIONATListWidget.currentItem()
if item:
nio = item.text()
# check we can delete that NIO
node_ports = self._node.ports()
for node_port in node_ports:
if node_port.name() == nio and not node_port.isFree():
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
return
self._nios.remove(nio)
self.uiNIONATListWidget.takeItem(self.uiNIONATListWidget.currentRow())
def _NIOUDPSelectedSlot(self, index):
"""
Loads a selected UDP.
:param index: ignored
"""
item = self.uiNIOUDPListWidget.currentItem()
@@ -212,7 +275,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
nio = "nio_udp:{lport}:{rhost}:{rport}".format(lport=local_port,
rhost=remote_host,
rport=remote_port)
if not nio in self._nios:
if nio not in self._nios:
self.uiNIOUDPListWidget.addItem(nio)
self._nios.append(nio)
self.uiLocalPortSpinBox.setValue(local_port + 1)
@@ -268,7 +331,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
tap_interface = self.uiNIOTAPLineEdit.text()
if tap_interface:
nio = "nio_tap:{}".format(tap_interface.lower())
if not nio in self._nios:
if nio not in self._nios:
self.uiNIOTAPListWidget.addItem(nio)
self._nios.append(nio)
@@ -325,7 +388,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
if local_file and remote_file:
nio = "nio_unix:{local}:{remote}".format(local=local_file,
remote=remote_file)
if not nio in self._nios:
if nio not in self._nios:
self.uiNIOUNIXListWidget.addItem(nio)
self._nios.append(nio)
@@ -381,7 +444,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
local_file = self.uiVDELocalFileLineEdit.text()
if local_file and control_file:
nio = "nio_vde:{control}:{local}".format(control=control_file, local=local_file)
if not nio in self._nios:
if nio not in self._nios:
self.uiNIOVDEListWidget.addItem(nio)
self._nios.append(nio)
@@ -405,6 +468,8 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
def _NIONullSelectedSlot(self, index):
"""
Loads a selected NULL NIO.
:param index: ignored
"""
item = self.uiNIONullListWidget.currentItem()
@@ -433,7 +498,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
identifier = self.uiNIONullIdentiferLineEdit.text()
if identifier:
nio = "nio_null:{}".format(identifier)
if not nio in self._nios:
if nio not in self._nios:
self.uiNIONullListWidget.addItem(nio)
self._nios.append(nio)

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>542</width>
<height>500</height>
<width>653</width>
<height>478</height>
</rect>
</property>
<property name="windowTitle">
@@ -15,13 +15,13 @@
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<widget class="QTabWidget" name="uiNIOsTabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<widget class="QWidget" name="NIOEthernetTab">
<attribute name="title">
<string>NIO Ethernet</string>
<string>Ethernet</string>
</attribute>
<layout class="QVBoxLayout">
<item>
@@ -135,9 +135,104 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<widget class="QWidget" name="NIONATTab">
<attribute name="title">
<string>NIO UDP</string>
<string>NAT</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="uiNIONATSettingsGroupBox">
<property name="title">
<string>Settings</string>
</property>
<layout class="QGridLayout" name="_2">
<item row="0" column="0">
<widget class="QLabel" name="uiNIONATIdentifierLabel">
<property name="text">
<string>Local identifier:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLineEdit" name="uiNIONATIdentiferLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="2" rowspan="3">
<widget class="QGroupBox" name="uiNIONATListGroupBox">
<property name="title">
<string>NIOs</string>
</property>
<layout class="QVBoxLayout" name="_3">
<item>
<widget class="QListWidget" name="uiNIONATListWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="uiAddNIONATPushButton">
<property name="text">
<string>&amp;Add</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="uiDeleteNIONATPushButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Delete</string>
</property>
</widget>
</item>
<item row="2" column="0" rowspan="2">
<spacer name="spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>294</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="2">
<spacer name="spacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>194</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="NIOUDPTab">
<attribute name="title">
<string>UDP</string>
</attribute>
<layout class="QGridLayout">
<item row="0" column="0" colspan="2">
@@ -265,9 +360,9 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_3">
<widget class="QWidget" name="NIOTAPTab">
<attribute name="title">
<string>NIO TAP</string>
<string>TAP</string>
</attribute>
<layout class="QVBoxLayout">
<item>
@@ -317,9 +412,9 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_4">
<widget class="QWidget" name="NIOUnixTab">
<attribute name="title">
<string>NIO UNIX</string>
<string>UNIX</string>
</attribute>
<layout class="QGridLayout">
<item row="0" column="0" colspan="2">
@@ -440,9 +535,9 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_5">
<widget class="QWidget" name="NIOVDETab">
<attribute name="title">
<string>NIO VDE</string>
<string>VDE</string>
</attribute>
<layout class="QGridLayout">
<item row="0" column="0" colspan="2">
@@ -563,9 +658,9 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_6">
<widget class="QWidget" name="NIONullTab">
<attribute name="title">
<string>NIO NULL</string>
<string>NULL</string>
</attribute>
<layout class="QGridLayout">
<item row="0" column="0" colspan="2">
@@ -575,9 +670,9 @@
</property>
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<widget class="QLabel" name="uiNIONullIdentifierLabel">
<property name="text">
<string>Identifier:</string>
<string>Local identifier:</string>
</property>
</widget>
</item>
@@ -658,7 +753,7 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_7">
<widget class="QWidget" name="MiscTab">
<attribute name="title">
<string>Misc.</string>
</attribute>

View File

@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/workspace/git/gns3-gui/gns3/modules/dynamips/ui/cloud_configuration_page.ui'
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/cloud_configuration_page.ui'
#
# Created: Mon Mar 17 17:42:16 2014
# by: PyQt4 UI code generator 4.10
# Created: Tue May 5 21:08:19 2015
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -26,16 +26,16 @@ except AttributeError:
class Ui_cloudConfigPageWidget(object):
def setupUi(self, cloudConfigPageWidget):
cloudConfigPageWidget.setObjectName(_fromUtf8("cloudConfigPageWidget"))
cloudConfigPageWidget.resize(542, 500)
cloudConfigPageWidget.resize(653, 478)
self.vboxlayout = QtGui.QVBoxLayout(cloudConfigPageWidget)
self.vboxlayout.setObjectName(_fromUtf8("vboxlayout"))
self.tabWidget = QtGui.QTabWidget(cloudConfigPageWidget)
self.tabWidget.setObjectName(_fromUtf8("tabWidget"))
self.tab = QtGui.QWidget()
self.tab.setObjectName(_fromUtf8("tab"))
self.vboxlayout1 = QtGui.QVBoxLayout(self.tab)
self.uiNIOsTabWidget = QtGui.QTabWidget(cloudConfigPageWidget)
self.uiNIOsTabWidget.setObjectName(_fromUtf8("uiNIOsTabWidget"))
self.NIOEthernetTab = QtGui.QWidget()
self.NIOEthernetTab.setObjectName(_fromUtf8("NIOEthernetTab"))
self.vboxlayout1 = QtGui.QVBoxLayout(self.NIOEthernetTab)
self.vboxlayout1.setObjectName(_fromUtf8("vboxlayout1"))
self.uiGenericEthernetGroupBox = QtGui.QGroupBox(self.tab)
self.uiGenericEthernetGroupBox = QtGui.QGroupBox(self.NIOEthernetTab)
self.uiGenericEthernetGroupBox.setObjectName(_fromUtf8("uiGenericEthernetGroupBox"))
self.gridlayout = QtGui.QGridLayout(self.uiGenericEthernetGroupBox)
self.gridlayout.setObjectName(_fromUtf8("gridlayout"))
@@ -62,7 +62,7 @@ class Ui_cloudConfigPageWidget(object):
self.uiGenericEthernetListWidget.setObjectName(_fromUtf8("uiGenericEthernetListWidget"))
self.gridlayout.addWidget(self.uiGenericEthernetListWidget, 2, 0, 1, 3)
self.vboxlayout1.addWidget(self.uiGenericEthernetGroupBox)
self.uiLinuxEthernetGroupBox = QtGui.QGroupBox(self.tab)
self.uiLinuxEthernetGroupBox = QtGui.QGroupBox(self.NIOEthernetTab)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
@@ -95,12 +95,57 @@ class Ui_cloudConfigPageWidget(object):
self.vboxlayout1.addWidget(self.uiLinuxEthernetGroupBox)
spacerItem = QtGui.QSpacerItem(21, 16, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
self.vboxlayout1.addItem(spacerItem)
self.tabWidget.addTab(self.tab, _fromUtf8(""))
self.tab_2 = QtGui.QWidget()
self.tab_2.setObjectName(_fromUtf8("tab_2"))
self.gridlayout2 = QtGui.QGridLayout(self.tab_2)
self.uiNIOsTabWidget.addTab(self.NIOEthernetTab, _fromUtf8(""))
self.NIONATTab = QtGui.QWidget()
self.NIONATTab.setObjectName(_fromUtf8("NIONATTab"))
self.gridLayout_2 = QtGui.QGridLayout(self.NIONATTab)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.uiNIONATSettingsGroupBox = QtGui.QGroupBox(self.NIONATTab)
self.uiNIONATSettingsGroupBox.setObjectName(_fromUtf8("uiNIONATSettingsGroupBox"))
self._2 = QtGui.QGridLayout(self.uiNIONATSettingsGroupBox)
self._2.setObjectName(_fromUtf8("_2"))
self.uiNIONATIdentifierLabel = QtGui.QLabel(self.uiNIONATSettingsGroupBox)
self.uiNIONATIdentifierLabel.setObjectName(_fromUtf8("uiNIONATIdentifierLabel"))
self._2.addWidget(self.uiNIONATIdentifierLabel, 0, 0, 1, 1)
self.uiNIONATIdentiferLineEdit = QtGui.QLineEdit(self.uiNIONATSettingsGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiNIONATIdentiferLineEdit.sizePolicy().hasHeightForWidth())
self.uiNIONATIdentiferLineEdit.setSizePolicy(sizePolicy)
self.uiNIONATIdentiferLineEdit.setObjectName(_fromUtf8("uiNIONATIdentiferLineEdit"))
self._2.addWidget(self.uiNIONATIdentiferLineEdit, 1, 0, 1, 1)
self.gridLayout_2.addWidget(self.uiNIONATSettingsGroupBox, 0, 0, 1, 2)
self.uiNIONATListGroupBox = QtGui.QGroupBox(self.NIONATTab)
self.uiNIONATListGroupBox.setObjectName(_fromUtf8("uiNIONATListGroupBox"))
self._3 = QtGui.QVBoxLayout(self.uiNIONATListGroupBox)
self._3.setObjectName(_fromUtf8("_3"))
self.uiNIONATListWidget = QtGui.QListWidget(self.uiNIONATListGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiNIONATListWidget.sizePolicy().hasHeightForWidth())
self.uiNIONATListWidget.setSizePolicy(sizePolicy)
self.uiNIONATListWidget.setObjectName(_fromUtf8("uiNIONATListWidget"))
self._3.addWidget(self.uiNIONATListWidget)
self.gridLayout_2.addWidget(self.uiNIONATListGroupBox, 0, 2, 3, 1)
self.uiAddNIONATPushButton = QtGui.QPushButton(self.NIONATTab)
self.uiAddNIONATPushButton.setObjectName(_fromUtf8("uiAddNIONATPushButton"))
self.gridLayout_2.addWidget(self.uiAddNIONATPushButton, 1, 0, 1, 1)
self.uiDeleteNIONATPushButton = QtGui.QPushButton(self.NIONATTab)
self.uiDeleteNIONATPushButton.setEnabled(False)
self.uiDeleteNIONATPushButton.setObjectName(_fromUtf8("uiDeleteNIONATPushButton"))
self.gridLayout_2.addWidget(self.uiDeleteNIONATPushButton, 1, 1, 1, 1)
spacerItem1 = QtGui.QSpacerItem(20, 294, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem1, 2, 0, 2, 1)
spacerItem2 = QtGui.QSpacerItem(20, 194, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem2, 3, 2, 1, 1)
self.uiNIOsTabWidget.addTab(self.NIONATTab, _fromUtf8(""))
self.NIOUDPTab = QtGui.QWidget()
self.NIOUDPTab.setObjectName(_fromUtf8("NIOUDPTab"))
self.gridlayout2 = QtGui.QGridLayout(self.NIOUDPTab)
self.gridlayout2.setObjectName(_fromUtf8("gridlayout2"))
self.uiNIOUDPSettingsGroupBox = QtGui.QGroupBox(self.tab_2)
self.uiNIOUDPSettingsGroupBox = QtGui.QGroupBox(self.NIOUDPTab)
self.uiNIOUDPSettingsGroupBox.setObjectName(_fromUtf8("uiNIOUDPSettingsGroupBox"))
self.gridlayout3 = QtGui.QGridLayout(self.uiNIOUDPSettingsGroupBox)
self.gridlayout3.setObjectName(_fromUtf8("gridlayout3"))
@@ -143,7 +188,7 @@ class Ui_cloudConfigPageWidget(object):
self.uiRemotePortSpinBox.setObjectName(_fromUtf8("uiRemotePortSpinBox"))
self.gridlayout3.addWidget(self.uiRemotePortSpinBox, 2, 1, 1, 1)
self.gridlayout2.addWidget(self.uiNIOUDPSettingsGroupBox, 0, 0, 1, 2)
self.uiNIOUDPListGroupBox = QtGui.QGroupBox(self.tab_2)
self.uiNIOUDPListGroupBox = QtGui.QGroupBox(self.NIOUDPTab)
self.uiNIOUDPListGroupBox.setObjectName(_fromUtf8("uiNIOUDPListGroupBox"))
self.vboxlayout2 = QtGui.QVBoxLayout(self.uiNIOUDPListGroupBox)
self.vboxlayout2.setObjectName(_fromUtf8("vboxlayout2"))
@@ -151,21 +196,21 @@ class Ui_cloudConfigPageWidget(object):
self.uiNIOUDPListWidget.setObjectName(_fromUtf8("uiNIOUDPListWidget"))
self.vboxlayout2.addWidget(self.uiNIOUDPListWidget)
self.gridlayout2.addWidget(self.uiNIOUDPListGroupBox, 0, 2, 2, 1)
self.uiAddNIOUDPPushButton = QtGui.QPushButton(self.tab_2)
self.uiAddNIOUDPPushButton = QtGui.QPushButton(self.NIOUDPTab)
self.uiAddNIOUDPPushButton.setObjectName(_fromUtf8("uiAddNIOUDPPushButton"))
self.gridlayout2.addWidget(self.uiAddNIOUDPPushButton, 1, 0, 1, 1)
self.uiDeleteNIOUDPPushButton = QtGui.QPushButton(self.tab_2)
self.uiDeleteNIOUDPPushButton = QtGui.QPushButton(self.NIOUDPTab)
self.uiDeleteNIOUDPPushButton.setEnabled(False)
self.uiDeleteNIOUDPPushButton.setObjectName(_fromUtf8("uiDeleteNIOUDPPushButton"))
self.gridlayout2.addWidget(self.uiDeleteNIOUDPPushButton, 1, 1, 1, 1)
spacerItem1 = QtGui.QSpacerItem(20, 211, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout2.addItem(spacerItem1, 2, 1, 1, 1)
self.tabWidget.addTab(self.tab_2, _fromUtf8(""))
self.tab_3 = QtGui.QWidget()
self.tab_3.setObjectName(_fromUtf8("tab_3"))
self.vboxlayout3 = QtGui.QVBoxLayout(self.tab_3)
spacerItem3 = QtGui.QSpacerItem(20, 211, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout2.addItem(spacerItem3, 2, 1, 1, 1)
self.uiNIOsTabWidget.addTab(self.NIOUDPTab, _fromUtf8(""))
self.NIOTAPTab = QtGui.QWidget()
self.NIOTAPTab.setObjectName(_fromUtf8("NIOTAPTab"))
self.vboxlayout3 = QtGui.QVBoxLayout(self.NIOTAPTab)
self.vboxlayout3.setObjectName(_fromUtf8("vboxlayout3"))
self.uiNIOTAPGroupBox = QtGui.QGroupBox(self.tab_3)
self.uiNIOTAPGroupBox = QtGui.QGroupBox(self.NIOTAPTab)
self.uiNIOTAPGroupBox.setObjectName(_fromUtf8("uiNIOTAPGroupBox"))
self.gridlayout4 = QtGui.QGridLayout(self.uiNIOTAPGroupBox)
self.gridlayout4.setObjectName(_fromUtf8("gridlayout4"))
@@ -183,14 +228,14 @@ class Ui_cloudConfigPageWidget(object):
self.uiNIOTAPListWidget.setObjectName(_fromUtf8("uiNIOTAPListWidget"))
self.gridlayout4.addWidget(self.uiNIOTAPListWidget, 1, 0, 1, 3)
self.vboxlayout3.addWidget(self.uiNIOTAPGroupBox)
spacerItem2 = QtGui.QSpacerItem(20, 191, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.vboxlayout3.addItem(spacerItem2)
self.tabWidget.addTab(self.tab_3, _fromUtf8(""))
self.tab_4 = QtGui.QWidget()
self.tab_4.setObjectName(_fromUtf8("tab_4"))
self.gridlayout5 = QtGui.QGridLayout(self.tab_4)
spacerItem4 = QtGui.QSpacerItem(20, 191, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.vboxlayout3.addItem(spacerItem4)
self.uiNIOsTabWidget.addTab(self.NIOTAPTab, _fromUtf8(""))
self.NIOUnixTab = QtGui.QWidget()
self.NIOUnixTab.setObjectName(_fromUtf8("NIOUnixTab"))
self.gridlayout5 = QtGui.QGridLayout(self.NIOUnixTab)
self.gridlayout5.setObjectName(_fromUtf8("gridlayout5"))
self.uiNIOUNIXSettingsGroupBox = QtGui.QGroupBox(self.tab_4)
self.uiNIOUNIXSettingsGroupBox = QtGui.QGroupBox(self.NIOUnixTab)
self.uiNIOUNIXSettingsGroupBox.setObjectName(_fromUtf8("uiNIOUNIXSettingsGroupBox"))
self.gridlayout6 = QtGui.QGridLayout(self.uiNIOUNIXSettingsGroupBox)
self.gridlayout6.setObjectName(_fromUtf8("gridlayout6"))
@@ -223,7 +268,7 @@ class Ui_cloudConfigPageWidget(object):
self.gridlayout8.addWidget(self.uiRemoteFileLineEdit, 1, 0, 1, 1)
self.gridlayout6.addLayout(self.gridlayout8, 1, 0, 1, 1)
self.gridlayout5.addWidget(self.uiNIOUNIXSettingsGroupBox, 0, 0, 1, 2)
self.uiNIOUNIXListGroupBox = QtGui.QGroupBox(self.tab_4)
self.uiNIOUNIXListGroupBox = QtGui.QGroupBox(self.NIOUnixTab)
self.uiNIOUNIXListGroupBox.setObjectName(_fromUtf8("uiNIOUNIXListGroupBox"))
self.vboxlayout4 = QtGui.QVBoxLayout(self.uiNIOUNIXListGroupBox)
self.vboxlayout4.setObjectName(_fromUtf8("vboxlayout4"))
@@ -236,23 +281,23 @@ class Ui_cloudConfigPageWidget(object):
self.uiNIOUNIXListWidget.setObjectName(_fromUtf8("uiNIOUNIXListWidget"))
self.vboxlayout4.addWidget(self.uiNIOUNIXListWidget)
self.gridlayout5.addWidget(self.uiNIOUNIXListGroupBox, 0, 2, 3, 1)
self.uiAddNIOUNIXPushButton = QtGui.QPushButton(self.tab_4)
self.uiAddNIOUNIXPushButton = QtGui.QPushButton(self.NIOUnixTab)
self.uiAddNIOUNIXPushButton.setObjectName(_fromUtf8("uiAddNIOUNIXPushButton"))
self.gridlayout5.addWidget(self.uiAddNIOUNIXPushButton, 1, 0, 1, 1)
self.uiDeleteNIOUNIXPushButton = QtGui.QPushButton(self.tab_4)
self.uiDeleteNIOUNIXPushButton = QtGui.QPushButton(self.NIOUnixTab)
self.uiDeleteNIOUNIXPushButton.setEnabled(False)
self.uiDeleteNIOUNIXPushButton.setObjectName(_fromUtf8("uiDeleteNIOUNIXPushButton"))
self.gridlayout5.addWidget(self.uiDeleteNIOUNIXPushButton, 1, 1, 1, 1)
spacerItem3 = QtGui.QSpacerItem(160, 190, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
self.gridlayout5.addItem(spacerItem3, 2, 0, 2, 2)
spacerItem4 = QtGui.QSpacerItem(196, 132, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout5.addItem(spacerItem4, 3, 2, 1, 1)
self.tabWidget.addTab(self.tab_4, _fromUtf8(""))
self.tab_5 = QtGui.QWidget()
self.tab_5.setObjectName(_fromUtf8("tab_5"))
self.gridlayout9 = QtGui.QGridLayout(self.tab_5)
spacerItem5 = QtGui.QSpacerItem(160, 190, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
self.gridlayout5.addItem(spacerItem5, 2, 0, 2, 2)
spacerItem6 = QtGui.QSpacerItem(196, 132, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout5.addItem(spacerItem6, 3, 2, 1, 1)
self.uiNIOsTabWidget.addTab(self.NIOUnixTab, _fromUtf8(""))
self.NIOVDETab = QtGui.QWidget()
self.NIOVDETab.setObjectName(_fromUtf8("NIOVDETab"))
self.gridlayout9 = QtGui.QGridLayout(self.NIOVDETab)
self.gridlayout9.setObjectName(_fromUtf8("gridlayout9"))
self.uiNIOVDESettingsGroupBox = QtGui.QGroupBox(self.tab_5)
self.uiNIOVDESettingsGroupBox = QtGui.QGroupBox(self.NIOVDETab)
self.uiNIOVDESettingsGroupBox.setObjectName(_fromUtf8("uiNIOVDESettingsGroupBox"))
self.gridlayout10 = QtGui.QGridLayout(self.uiNIOVDESettingsGroupBox)
self.gridlayout10.setObjectName(_fromUtf8("gridlayout10"))
@@ -285,7 +330,7 @@ class Ui_cloudConfigPageWidget(object):
self.gridlayout12.addWidget(self.uiVDELocalFileLineEdit, 1, 0, 1, 1)
self.gridlayout10.addLayout(self.gridlayout12, 1, 0, 1, 1)
self.gridlayout9.addWidget(self.uiNIOVDESettingsGroupBox, 0, 0, 1, 2)
self.uiNIOVDEListGroupBox = QtGui.QGroupBox(self.tab_5)
self.uiNIOVDEListGroupBox = QtGui.QGroupBox(self.NIOVDETab)
self.uiNIOVDEListGroupBox.setObjectName(_fromUtf8("uiNIOVDEListGroupBox"))
self.vboxlayout5 = QtGui.QVBoxLayout(self.uiNIOVDEListGroupBox)
self.vboxlayout5.setObjectName(_fromUtf8("vboxlayout5"))
@@ -298,29 +343,29 @@ class Ui_cloudConfigPageWidget(object):
self.uiNIOVDEListWidget.setObjectName(_fromUtf8("uiNIOVDEListWidget"))
self.vboxlayout5.addWidget(self.uiNIOVDEListWidget)
self.gridlayout9.addWidget(self.uiNIOVDEListGroupBox, 0, 2, 3, 1)
self.uiAddNIOVDEPushButton = QtGui.QPushButton(self.tab_5)
self.uiAddNIOVDEPushButton = QtGui.QPushButton(self.NIOVDETab)
self.uiAddNIOVDEPushButton.setObjectName(_fromUtf8("uiAddNIOVDEPushButton"))
self.gridlayout9.addWidget(self.uiAddNIOVDEPushButton, 1, 0, 1, 1)
self.uiDeleteNIOVDEPushButton = QtGui.QPushButton(self.tab_5)
self.uiDeleteNIOVDEPushButton = QtGui.QPushButton(self.NIOVDETab)
self.uiDeleteNIOVDEPushButton.setEnabled(False)
self.uiDeleteNIOVDEPushButton.setObjectName(_fromUtf8("uiDeleteNIOVDEPushButton"))
self.gridlayout9.addWidget(self.uiDeleteNIOVDEPushButton, 1, 1, 1, 1)
spacerItem5 = QtGui.QSpacerItem(161, 201, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
self.gridlayout9.addItem(spacerItem5, 2, 0, 2, 2)
spacerItem6 = QtGui.QSpacerItem(196, 132, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout9.addItem(spacerItem6, 3, 2, 1, 1)
self.tabWidget.addTab(self.tab_5, _fromUtf8(""))
self.tab_6 = QtGui.QWidget()
self.tab_6.setObjectName(_fromUtf8("tab_6"))
self.gridlayout13 = QtGui.QGridLayout(self.tab_6)
spacerItem7 = QtGui.QSpacerItem(161, 201, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
self.gridlayout9.addItem(spacerItem7, 2, 0, 2, 2)
spacerItem8 = QtGui.QSpacerItem(196, 132, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout9.addItem(spacerItem8, 3, 2, 1, 1)
self.uiNIOsTabWidget.addTab(self.NIOVDETab, _fromUtf8(""))
self.NIONullTab = QtGui.QWidget()
self.NIONullTab.setObjectName(_fromUtf8("NIONullTab"))
self.gridlayout13 = QtGui.QGridLayout(self.NIONullTab)
self.gridlayout13.setObjectName(_fromUtf8("gridlayout13"))
self.uiNIONullSettingsGroupBox = QtGui.QGroupBox(self.tab_6)
self.uiNIONullSettingsGroupBox = QtGui.QGroupBox(self.NIONullTab)
self.uiNIONullSettingsGroupBox.setObjectName(_fromUtf8("uiNIONullSettingsGroupBox"))
self.gridlayout14 = QtGui.QGridLayout(self.uiNIONullSettingsGroupBox)
self.gridlayout14.setObjectName(_fromUtf8("gridlayout14"))
self.label_9 = QtGui.QLabel(self.uiNIONullSettingsGroupBox)
self.label_9.setObjectName(_fromUtf8("label_9"))
self.gridlayout14.addWidget(self.label_9, 0, 0, 1, 1)
self.uiNIONullIdentifierLabel = QtGui.QLabel(self.uiNIONullSettingsGroupBox)
self.uiNIONullIdentifierLabel.setObjectName(_fromUtf8("uiNIONullIdentifierLabel"))
self.gridlayout14.addWidget(self.uiNIONullIdentifierLabel, 0, 0, 1, 1)
self.uiNIONullIdentiferLineEdit = QtGui.QLineEdit(self.uiNIONullSettingsGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -330,7 +375,7 @@ class Ui_cloudConfigPageWidget(object):
self.uiNIONullIdentiferLineEdit.setObjectName(_fromUtf8("uiNIONullIdentiferLineEdit"))
self.gridlayout14.addWidget(self.uiNIONullIdentiferLineEdit, 1, 0, 1, 1)
self.gridlayout13.addWidget(self.uiNIONullSettingsGroupBox, 0, 0, 1, 2)
self.uiNIONullListGroupBox = QtGui.QGroupBox(self.tab_6)
self.uiNIONullListGroupBox = QtGui.QGroupBox(self.NIONullTab)
self.uiNIONullListGroupBox.setObjectName(_fromUtf8("uiNIONullListGroupBox"))
self.vboxlayout6 = QtGui.QVBoxLayout(self.uiNIONullListGroupBox)
self.vboxlayout6.setObjectName(_fromUtf8("vboxlayout6"))
@@ -343,35 +388,35 @@ class Ui_cloudConfigPageWidget(object):
self.uiNIONullListWidget.setObjectName(_fromUtf8("uiNIONullListWidget"))
self.vboxlayout6.addWidget(self.uiNIONullListWidget)
self.gridlayout13.addWidget(self.uiNIONullListGroupBox, 0, 2, 3, 1)
self.uiAddNIONullPushButton = QtGui.QPushButton(self.tab_6)
self.uiAddNIONullPushButton = QtGui.QPushButton(self.NIONullTab)
self.uiAddNIONullPushButton.setObjectName(_fromUtf8("uiAddNIONullPushButton"))
self.gridlayout13.addWidget(self.uiAddNIONullPushButton, 1, 0, 1, 1)
self.uiDeleteNIONullPushButton = QtGui.QPushButton(self.tab_6)
self.uiDeleteNIONullPushButton = QtGui.QPushButton(self.NIONullTab)
self.uiDeleteNIONullPushButton.setEnabled(False)
self.uiDeleteNIONullPushButton.setObjectName(_fromUtf8("uiDeleteNIONullPushButton"))
self.gridlayout13.addWidget(self.uiDeleteNIONullPushButton, 1, 1, 1, 1)
spacerItem7 = QtGui.QSpacerItem(20, 261, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout13.addItem(spacerItem7, 2, 0, 2, 2)
spacerItem8 = QtGui.QSpacerItem(20, 181, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout13.addItem(spacerItem8, 3, 2, 1, 1)
self.tabWidget.addTab(self.tab_6, _fromUtf8(""))
self.tab_7 = QtGui.QWidget()
self.tab_7.setObjectName(_fromUtf8("tab_7"))
self.gridLayout = QtGui.QGridLayout(self.tab_7)
spacerItem9 = QtGui.QSpacerItem(20, 261, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout13.addItem(spacerItem9, 2, 0, 2, 2)
spacerItem10 = QtGui.QSpacerItem(20, 181, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridlayout13.addItem(spacerItem10, 3, 2, 1, 1)
self.uiNIOsTabWidget.addTab(self.NIONullTab, _fromUtf8(""))
self.MiscTab = QtGui.QWidget()
self.MiscTab.setObjectName(_fromUtf8("MiscTab"))
self.gridLayout = QtGui.QGridLayout(self.MiscTab)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.uiNameLabel = QtGui.QLabel(self.tab_7)
self.uiNameLabel = QtGui.QLabel(self.MiscTab)
self.uiNameLabel.setObjectName(_fromUtf8("uiNameLabel"))
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
self.uiNameLineEdit = QtGui.QLineEdit(self.tab_7)
self.uiNameLineEdit = QtGui.QLineEdit(self.MiscTab)
self.uiNameLineEdit.setObjectName(_fromUtf8("uiNameLineEdit"))
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
spacerItem9 = QtGui.QSpacerItem(20, 399, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem9, 1, 1, 1, 1)
self.tabWidget.addTab(self.tab_7, _fromUtf8(""))
self.vboxlayout.addWidget(self.tabWidget)
spacerItem11 = QtGui.QSpacerItem(20, 399, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem11, 1, 1, 1, 1)
self.uiNIOsTabWidget.addTab(self.MiscTab, _fromUtf8(""))
self.vboxlayout.addWidget(self.uiNIOsTabWidget)
self.retranslateUi(cloudConfigPageWidget)
self.tabWidget.setCurrentIndex(0)
self.uiNIOsTabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(cloudConfigPageWidget)
def retranslateUi(self, cloudConfigPageWidget):
@@ -382,7 +427,13 @@ class Ui_cloudConfigPageWidget(object):
self.uiLinuxEthernetGroupBox.setTitle(_translate("cloudConfigPageWidget", "Linux Ethernet NIO (Linux only, root access required)", None))
self.uiAddLinuxEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteLinuxEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("cloudConfigPageWidget", "NIO Ethernet", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOEthernetTab), _translate("cloudConfigPageWidget", "Ethernet", None))
self.uiNIONATSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
self.uiNIONATIdentifierLabel.setText(_translate("cloudConfigPageWidget", "Identifier:", None))
self.uiNIONATListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
self.uiAddNIONATPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteNIONATPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIONATTab), _translate("cloudConfigPageWidget", "NAT", None))
self.uiNIOUDPSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
self.uiLocalPortLabel.setText(_translate("cloudConfigPageWidget", "Local port:", None))
self.uiRemoteHostLabel.setText(_translate("cloudConfigPageWidget", "Remote host:", None))
@@ -391,31 +442,31 @@ class Ui_cloudConfigPageWidget(object):
self.uiNIOUDPListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
self.uiAddNIOUDPPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteNIOUDPPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("cloudConfigPageWidget", "NIO UDP", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOUDPTab), _translate("cloudConfigPageWidget", "UDP", None))
self.uiNIOTAPGroupBox.setTitle(_translate("cloudConfigPageWidget", "TAP interface (require root access)", None))
self.uiAddNIOTAPPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteNIOTAPPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("cloudConfigPageWidget", "NIO TAP", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOTAPTab), _translate("cloudConfigPageWidget", "TAP", None))
self.uiNIOUNIXSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
self.uiLocalFileLabel.setText(_translate("cloudConfigPageWidget", "Local file:", None))
self.uiRemoteFileLabel.setText(_translate("cloudConfigPageWidget", "Remote file:", None))
self.uiNIOUNIXListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
self.uiAddNIOUNIXPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteNIOUNIXPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), _translate("cloudConfigPageWidget", "NIO UNIX", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOUnixTab), _translate("cloudConfigPageWidget", "UNIX", None))
self.uiNIOVDESettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
self.uiVDEControlFileLabel.setText(_translate("cloudConfigPageWidget", "Control file:", None))
self.uiVDELocalFileLabel.setText(_translate("cloudConfigPageWidget", "Local file:", None))
self.uiNIOVDEListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
self.uiAddNIOVDEPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteNIOVDEPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_5), _translate("cloudConfigPageWidget", "NIO VDE", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOVDETab), _translate("cloudConfigPageWidget", "VDE", None))
self.uiNIONullSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
self.label_9.setText(_translate("cloudConfigPageWidget", "Identifier:", None))
self.uiNIONullIdentifierLabel.setText(_translate("cloudConfigPageWidget", "Identifier:", None))
self.uiNIONullListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
self.uiAddNIONullPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
self.uiDeleteNIONullPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_6), _translate("cloudConfigPageWidget", "NIO NULL", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIONullTab), _translate("cloudConfigPageWidget", "NULL", None))
self.uiNameLabel.setText(_translate("cloudConfigPageWidget", "Name:", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_7), _translate("cloudConfigPageWidget", "Misc.", None))
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.MiscTab), _translate("cloudConfigPageWidget", "Misc.", None))

View File

@@ -19,12 +19,14 @@
Dynamips module implementation.
"""
import sys
import os
import glob
import shutil
from gns3.qt import QtCore, QtGui
from gns3.servers import Servers
from gns3.node import Node
from gns3.local_config import LocalConfig
from gns3.local_server_config import LocalServerConfig
from ..module import Module
from ..module_error import ModuleError
@@ -55,12 +57,12 @@ PLATFORM_TO_CLASS = {
"c7200": C7200
}
import logging
log = logging.getLogger(__name__)
class Dynamips(Module):
"""
Dynamips module.
"""
@@ -70,204 +72,166 @@ class Dynamips(Module):
self._settings = {}
self._ios_routers = {}
self._servers = []
self._nodes = []
self._working_dir = ""
self._images_dir = ""
self._ios_images_cache = {}
# load the settings and IOS images.
self._loadSettings()
self._loadIOSRouters()
@staticmethod
def _findDynamips(self):
"""
Finds the Dynamips path.
:return: path to Dynamips
"""
if sys.platform.startswith("win") and hasattr(sys, "frozen"):
dynamips_path = os.path.join(os.getcwd(), "dynamips", "dynamips.exe")
elif sys.platform.startswith("darwin") and hasattr(sys, "frozen"):
dynamips_path = os.path.join(os.getcwd(), "dynamips")
else:
dynamips_path = shutil.which("dynamips")
if dynamips_path is None:
return ""
return dynamips_path
def _loadSettings(self):
"""
Loads the settings from the persistent settings file.
"""
# load the settings
local_config = LocalConfig.instance()
# restore the Dynamips settings from QSettings (for backward compatibility)
legacy_settings = {}
settings = QtCore.QSettings()
settings.beginGroup(self.__class__.__name__)
for name, value in DYNAMIPS_SETTINGS.items():
self._settings[name] = settings.value(name, value, type=DYNAMIPS_SETTING_TYPES[name])
for name in DYNAMIPS_SETTINGS.keys():
if settings.contains(name):
legacy_settings[name] = settings.value(name, type=DYNAMIPS_SETTING_TYPES[name])
settings.remove("")
settings.endGroup()
if legacy_settings:
local_config.saveSectionSettings(self.__class__.__name__, legacy_settings)
self._settings = local_config.loadSectionSettings(self.__class__.__name__, DYNAMIPS_SETTINGS)
if not os.path.exists(self._settings["dynamips_path"]):
self._settings["dynamips_path"] = self._findDynamips(self)
# keep the config file sync
self._saveSettings()
def _saveSettings(self):
"""
Saves the settings to the persistent settings file.
"""
# save the settings
settings = QtCore.QSettings()
settings.beginGroup(self.__class__.__name__)
for name, value in self._settings.items():
settings.setValue(name, value)
settings.endGroup()
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
# save some settings to the local server config file
server_settings = {
"allocate_aux_console_ports": self._settings["allocate_aux_console_ports"],
"ghost_ios_support": self._settings["ghost_ios_support"],
"sparse_memory_support": self._settings["sparse_memory_support"],
"mmap_support": self._settings["mmap_support"],
}
if self._settings["dynamips_path"]:
server_settings["dynamips_path"] = os.path.normpath(self._settings["dynamips_path"])
config = LocalServerConfig.instance()
config.saveSettings(self.__class__.__name__, server_settings)
def _loadIOSRouters(self):
"""
Load the IOS routers from the persistent settings file.
"""
local_config = LocalConfig.instance()
# restore the Dynamips VM settings from QSettings (for backward compatibility)
ios_routers = []
# load the settings
settings = QtCore.QSettings()
settings.beginGroup("IOSRouters")
# load the IOS images
# load the VMs
size = settings.beginReadArray("ios_router")
for index in range(0, size):
settings.setArrayIndex(index)
name = settings.value("name")
server = settings.value("server")
key = "{server}:{name}".format(server=server, name=name)
if key in self._ios_routers or not name or not server:
continue
self._ios_routers[key] = {}
router = {}
for setting_name, default_value in IOS_ROUTER_SETTINGS.items():
self._ios_routers[key][setting_name] = settings.value(setting_name, default_value, IOS_ROUTER_SETTING_TYPES[setting_name])
router[setting_name] = settings.value(setting_name, default_value, IOS_ROUTER_SETTING_TYPES[setting_name])
for slot_id in range(0, 7):
slot = "slot{}".format(slot_id)
if settings.contains(slot):
self._ios_routers[key][slot] = settings.value(slot, "")
router[slot] = settings.value(slot, "")
for wic_id in range(0, 3):
wic = "wic{}".format(wic_id)
if settings.contains(wic):
self._ios_routers[key][wic] = settings.value(wic, "")
router[wic] = settings.value(wic, "")
platform = self._ios_routers[key]["platform"]
chassis = self._ios_routers[key]["chassis"]
platform = router["platform"]
chassis = router["chassis"]
if platform == "c7200":
self._ios_routers[key]["midplane"] = settings.value("midplane", "vxr")
self._ios_routers[key]["npe"] = settings.value("npe", "npe-400")
self._ios_routers[key]["slot0"] = settings.value("slot0", "C7200-IO-FE")
router["midplane"] = settings.value("midplane", "vxr")
router["npe"] = settings.value("npe", "npe-400")
router["slot0"] = settings.value("slot0", "C7200-IO-FE")
else:
self._ios_routers[key]["iomem"] = 5
router["iomem"] = 5
if platform in ("c3725", "c3725", "c2691"):
self._ios_routers[key]["slot0"] = settings.value("slot0", "GT96100-FE")
router["slot0"] = settings.value("slot0", "GT96100-FE")
elif platform == "c3600" and chassis == "3660":
self._ios_routers[key]["slot0"] = settings.value("slot0", "Leopard-2FE")
router["slot0"] = settings.value("slot0", "Leopard-2FE")
elif platform == "c2600" and chassis == "2610":
self._ios_routers[key]["slot0"] = settings.value("slot0", "C2600-MB-1E")
router["slot0"] = settings.value("slot0", "C2600-MB-1E")
elif platform == "c2600" and chassis == "2611":
self._ios_routers[key]["slot0"] = settings.value("slot0", "C2600-MB-2E")
router["slot0"] = settings.value("slot0", "C2600-MB-2E")
elif platform == "c2600" and chassis in ("2620", "2610XM", "2620XM", "2650XM"):
self._ios_routers[key]["slot0"] = settings.value("slot0", "C2600-MB-1FE")
router["slot0"] = settings.value("slot0", "C2600-MB-1FE")
elif platform == "c2600" and chassis in ("2621", "2611XM", "2621XM", "2651XM"):
self._ios_routers[key]["slot0"] = settings.value("slot0", "C2600-MB-2FE")
router["slot0"] = settings.value("slot0", "C2600-MB-2FE")
elif platform == "c1700" and chassis in ("1720", "1721", "1750", "1751", "1760"):
self._ios_routers[key]["slot0"] = settings.value("slot0", "C1700-MB-1FE")
router["slot0"] = settings.value("slot0", "C1700-MB-1FE")
elif platform == "c1700" and chassis in ("1751", "1760"):
self._ios_routers[key]["slot0"] = settings.value("slot0", "C1700-MB-WIC1")
router["slot0"] = settings.value("slot0", "C1700-MB-WIC1")
ios_routers.append(router)
settings.endArray()
settings.remove("")
settings.endGroup()
if ios_routers:
local_config.saveSectionSettings(self.__class__.__name__, {"routers": ios_routers})
settings = local_config.settings()
if "routers" in settings.get(self.__class__.__name__, {}):
for router in settings[self.__class__.__name__]["routers"]:
name = router.get("name")
server = router.get("server")
router["image"] = router.get("path", router["image"]) # for compatibility before version 1.3
key = "{server}:{name}".format(server=server, name=name)
if key in self._ios_routers or not name or not server:
continue
self._ios_routers[key] = router
# keep things sync
self._saveIOSRouters()
def _saveIOSRouters(self):
"""
Saves the IOS routers to the persistent settings file.
"""
# save the settings
settings = QtCore.QSettings()
settings.beginGroup("IOSRouters")
settings.remove("")
# save the IOS images
settings.beginWriteArray("ios_router", len(self._ios_routers))
index = 0
for ios_router in self._ios_routers.values():
settings.setArrayIndex(index)
for name, value in ios_router.items():
settings.setValue(name, value)
index += 1
settings.endArray()
settings.endGroup()
def _delete_dynamips_files(self):
"""
Deletes useless local Dynamips files from the working directory
"""
files = glob.glob(os.path.join(self._working_dir, "dynamips", "*.ghost"))
files += glob.glob(os.path.join(self._working_dir, "dynamips", "*_lock"))
files += glob.glob(os.path.join(self._working_dir, "dynamips", "ilt_*"))
files += glob.glob(os.path.join(self._working_dir, "dynamips", "c[0-9][0-9][0-9][0-9]_*_rommon_vars"))
files += glob.glob(os.path.join(self._working_dir, "dynamips", "c[0-9][0-9][0-9][0-9]_*_ssa"))
for file in files:
try:
log.debug("deleting file {}".format(file))
os.remove(file)
except OSError as e:
log.warn("could not delete file {}: {}".format(file, e))
continue
def setProjectFilesDir(self, path):
"""
Sets the project files directory path this module.
:param path: path to the local project files directory
"""
#self._delete_dynamips_files() #FIXME: cause issues
self._working_dir = path
log.info("local working directory for Dynamips module: {}".format(self._working_dir))
# update the server with the new working directory / project name
for server in self._servers:
if server.connected():
self._sendSettings(server)
def setImageFilesDir(self, path):
"""
Sets the image files directory path this module.
:param path: path to the local image files directory
"""
self._images_dir = os.path.join(path, "IOS")
def imageFilesDir(self):
"""
Returns the files directory path this module.
:returns: path to the local image files directory
"""
return self._images_dir
def addServer(self, server):
"""
Adds a server to be used by this module.
:param server: WebSocketClient instance
"""
log.info("adding server {}:{} to Dynamips module".format(server.host, server.port))
self._servers.append(server)
self._sendSettings(server)
def removeServer(self, server):
"""
Removes a server from being used by this module.
:param server: WebSocketClient instance
"""
log.info("removing server {}:{} from Dynamips module".format(server.host, server.port))
self._servers.remove(server)
def servers(self):
"""
Returns all the servers used by this module.
:returns: list of WebSocketClient instances
"""
return self._servers
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, {"routers": list(self._ios_routers.values())})
def addNode(self, node):
"""
@@ -323,55 +287,16 @@ class Dynamips(Module):
:param settings: module settings (dictionary)
"""
params = {}
for name, value in settings.items():
if name in self._settings and self._settings[name] != value:
params[name] = value
if params:
for server in self._servers:
if server.isLocal():
params.update({"working_dir": self._working_dir})
else:
if "path" in params:
del params["path"] # do not send Dynamips path to remote servers
project_name = os.path.basename(self._working_dir)
if project_name.endswith("-files"):
project_name = project_name[:-6]
params.update({"project_name": project_name})
server.send_notification("dynamips.settings", params)
self._settings.update(settings)
self._saveSettings()
def _sendSettings(self, server):
"""
Sends the module settings to the server.
:param server: WebSocketClient instance
"""
log.info("sending Dynamips settings to server {}:{}".format(server.host, server.port))
params = self._settings.copy()
# send the local working directory only if this is a local server
if server.isLocal():
params.update({"working_dir": self._working_dir})
else:
if "path" in params:
del params["path"] # do not send Dynamips path to remote servers
project_name = os.path.basename(self._working_dir)
if project_name.endswith("-files"):
project_name = project_name[:-6]
params.update({"project_name": project_name})
server.send_notification("dynamips.settings", params)
def allocateServer(self, node_class, use_cloud=False):
"""
Allocates a server.
:param node_class: Node object
:returns: allocated server (WebSocketClient instance)
:returns: allocated server (HTTPClient instance)
"""
# allocate a server for the node
@@ -393,29 +318,19 @@ class Dynamips(Module):
raise ModuleError("No remote server is configured")
return server
def createNode(self, node_class, server):
def createNode(self, node_class, server, project):
"""
Creates a new node.
:param node_class: Node object
:param server: WebSocketClient instance
:param server: HTTPClient instance
:param project: Project instance
"""
log.info("creating node {}".format(node_class))
if not server.connected():
try:
log.info("reconnecting to server {}:{}".format(server.host, server.port))
server.reconnect()
except OSError as e:
raise ModuleError("Could not connect to server {}:{}: {}".format(server.host,
server.port,
e))
if server not in self._servers:
self.addServer(server)
# create an instance of the node class
return node_class(self, server)
return node_class(self, server, project)
def setupNode(self, node, node_name):
"""
@@ -436,63 +351,26 @@ class Dynamips(Module):
ios_router = self._ios_routers[ios_key]
break
# hack for EtherSwitch router
if isinstance(node, EtherSwitchRouter) and node.server() == Servers.instance().localServer():
for info in self._ios_routers.values():
if info["platform"] == "c3725" and info["server"] == "local":
ios_router = {
"platform": "c3725",
"path": info["path"],
"ram": info["ram"],
"startup_config": info["startup_config"],
}
break
if not ios_router:
raise ModuleError("Please create an c3725 IOS router in order to use an EtherSwitch router")
if not ios_router:
raise ModuleError("No IOS router for platform {}".format(node.settings()["platform"]))
settings = {}
# set initial settings like the chassis or an Idle-PC value etc.
if "chassis" in ios_router and ios_router["chassis"]:
settings["chassis"] = ios_router["chassis"]
if "idlepc" in ios_router and ios_router["idlepc"]:
settings["idlepc"] = ios_router["idlepc"]
if ios_router["startup_config"]:
settings["startup_config"] = ios_router["startup_config"]
if "private_config" in ios_router and ios_router["private_config"]:
settings["private_config"] = ios_router["private_config"]
vm_settings = {}
for setting_name, value in ios_router.items():
if setting_name in node.settings() and setting_name != "name" and value != "" and value is not None:
vm_settings[setting_name] = value
if ios_router["platform"] == "c7200":
settings["midplane"] = ios_router["midplane"]
settings["npe"] = ios_router["npe"]
elif "iomem" in ios_router:
settings["iomem"] = ios_router["iomem"]
base_name = "R"
if "slot1" in vm_settings and vm_settings["slot1"] == "NM-16ESW":
# must be an EtherSwitch router
base_name = "ESW"
if "nvram" in ios_router and ios_router["nvram"]:
settings["nvram"] = ios_router["nvram"]
if "console" in vm_settings:
# Older GNS3 versions may have a console setting in the VM template
del vm_settings["console"]
if "disk0" in ios_router and ios_router["disk0"]:
settings["disk0"] = ios_router["disk0"]
if "disk1" in ios_router and ios_router["disk1"]:
settings["disk1"] = ios_router["disk1"]
for slot_id in range(0, 7):
slot = "slot{}".format(slot_id)
if slot in ios_router:
settings[slot] = ios_router[slot]
for wic_id in range(0, 3):
wic = "wic{}".format(wic_id)
if wic in ios_router:
settings[wic] = ios_router[wic]
if node.server().isCloud():
settings["cloud_path"] = "images/IOS"
node.setup(ios_router["image"], ios_router["ram"], initial_settings=settings)
else:
node.setup(ios_router["path"], ios_router["ram"], initial_settings=settings)
ram = vm_settings.pop("ram")
image = vm_settings.pop("image")
node.setup(image, ram, additional_settings=vm_settings, base_name=base_name)
else:
node.setup()
@@ -505,7 +383,7 @@ class Dynamips(Module):
"""
for ios_router in self._ios_routers.values():
if ios_router["path"] == image_path:
if os.path.basename(ios_router["image"]) == image_path:
if ios_router["idlepc"] != idlepc:
ios_router["idlepc"] = idlepc
self._saveIOSRouters()
@@ -513,36 +391,12 @@ class Dynamips(Module):
def reset(self):
"""
Resets the servers and nodes.
Resets the module.
"""
log.info("Dynamips module reset")
for server in self._servers:
if server.connected():
server.send_notification("dynamips.reset")
self._servers.clear()
for node in self._nodes:
node.reset()
self._nodes.clear()
def notification(self, destination, params):
"""
To received notifications from the server.
:param destination: JSON-RPC method
:param params: JSON-RPC params
"""
if "devices" in params:
for node in self._nodes:
for device in params["devices"]:
if node.name() == device:
message = "node {}: {}".format(node.name(), params["message"])
self.notification_signal.emit(message, params["details"])
if hasattr(node, "stop"):
node.stop()
def exportConfigs(self, directory):
"""
Exports all configs for all nodes to a directory.
@@ -551,8 +405,8 @@ class Dynamips(Module):
"""
for node in self._nodes:
if hasattr(node, "exportConfigs") and node.initialized():
node.exportConfigs(directory)
if isinstance(node, Router) and node.initialized():
node.exportConfigToDirectory(directory)
def importConfigs(self, directory):
"""
@@ -562,8 +416,8 @@ class Dynamips(Module):
"""
for node in self._nodes:
if hasattr(node, "importConfigs") and node.initialized():
node.importConfigs(directory)
if isinstance(node, Router) and node.initialized():
node.importConfigFromDirectory(directory)
def findAlternativeIOSImage(self, image, node):
"""
@@ -582,7 +436,7 @@ class Dynamips(Module):
mainwindow = MainWindow.instance()
ios_routers = self.iosRouters()
candidate_ios_images = {}
alternative_image = {"path": image,
alternative_image = {"image": image,
"ram": None,
"idlepc": None}
@@ -596,8 +450,8 @@ class Dynamips(Module):
"IOS image", "IOS image {} could not be found\nPlease select an alternative from your existing images:".format(image),
list(candidate_ios_images.keys()), 0, False)
if ok:
ios_image = candidate_ios_images[selection]
alternative_image["path"] = ios_router["path"]
ios_image = candidate_ios_images[selection] # FIXME
alternative_image["image"] = ios_router["image"]
alternative_image["ram"] = ios_router["ram"]
alternative_image["idlepc"] = ios_router["idlepc"]
self._ios_images_cache[image] = alternative_image
@@ -606,9 +460,9 @@ class Dynamips(Module):
# no registered IOS image is used, let's just ask for an IOS image path
QtGui.QMessageBox.critical(mainwindow, "IOS image", "Could not find the {} IOS image \nPlease select a similar IOS image!".format(image))
from .pages.ios_router_preferences_page import IOSRouterPreferencesPage
path = IOSRouterPreferencesPage.getIOSImage(mainwindow)
if path:
alternative_image["path"] = path
image_path = IOSRouterPreferencesPage.getIOSImage(mainwindow)
if image_path:
alternative_image["image"] = image_path
self._ios_images_cache[image] = alternative_image
return alternative_image
@@ -648,7 +502,7 @@ class Dynamips(Module):
server = "{}:{}".format(remote_server.host, remote_server.port)
nodes = []
for node_class in [EtherSwitchRouter, EthernetSwitch, EthernetHub, FrameRelaySwitch, ATMSwitch]:
for node_class in [EthernetSwitch, EthernetHub, FrameRelaySwitch, ATMSwitch]:
nodes.append(
{"class": node_class.__name__,
"name": node_class.symbolName(),

View File

@@ -55,8 +55,8 @@ ADAPTER_MATRIX = {"C1700-MB-1FE": {"nb_ports": 1,
"port": FastEthernetPort},
"C7200-IO-FE": {"nb_ports": 1,
"wics": 0,
"port": FastEthernetPort},
"wics": 0,
"port": FastEthernetPort},
"C7200-IO-GE-E": {"nb_ports": 1,
"wics": 0,
@@ -125,4 +125,4 @@ ADAPTER_MATRIX = {"C1700-MB-1FE": {"nb_ports": 1,
"PA-POS-OC3": {"nb_ports": 1,
"wics": 0,
"port": POSPort},
}
}

View File

@@ -22,15 +22,18 @@ Wizard for IOS routers.
import sys
import os
import re
import hashlib
from gns3.qt import QtCore, QtGui
from gns3.servers import Servers
from gns3.utils.message_box import MessageBox
from gns3.node import Node
from gns3.utils.run_in_terminal import RunInTerminal
from gns3.utils.get_resource import get_resource
from gns3.utils.get_default_base_config import get_default_base_config
from ....settings import ENABLE_CLOUD
from ..ui.ios_router_wizard_ui import Ui_IOSRouterWizard
from ..settings import PLATFORMS_DEFAULT_RAM, CHASSIS, ADAPTER_MATRIX, WIC_MATRIX
from ..settings import PLATFORMS_DEFAULT_RAM, PLATFORMS_DEFAULT_NVRAM, DEFAULT_IDLEPC, CHASSIS, ADAPTER_MATRIX, WIC_MATRIX
from .. import Dynamips
from ..nodes.c1700 import C1700
from ..nodes.c2600 import C2600
@@ -50,8 +53,12 @@ PLATFORM_TO_CLASS = {
"c7200": C7200
}
import logging
log = logging.getLogger(__name__)
class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
"""
Wizard to create an IOS router.
@@ -74,10 +81,25 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
self.uiIOSImageToolButton.clicked.connect(self._iosImageBrowserSlot)
self.uiTestIOSImagePushButton.clicked.connect(self._testIOSImageSlot)
self.uiIdlePCFinderPushButton.clicked.connect(self._idlePCFinderSlot)
self.uiEtherSwitchCheckBox.stateChanged.connect(self._etherSwitchSlot)
self.uiPlatformComboBox.currentIndexChanged[str].connect(self._platformChangedSlot)
self.uiPlatformComboBox.addItems(list(PLATFORMS_DEFAULT_RAM.keys()))
#FIXME: hide because of issue on Windows.
self._router = None
# Validate the Idle PC value
self._idle_valid = False
idle_pc_rgx = QtCore.QRegExp("^(0x[0-9a-fA-F]{8})?$")
validator = QtGui.QRegExpValidator(idle_pc_rgx, self)
self.uiIdlepcLineEdit.setValidator(validator)
self.uiIdlepcLineEdit.textChanged.connect(self._idlePCValidateSlot)
self.uiIdlepcLineEdit.textChanged.emit(self.uiIdlepcLineEdit.text())
# location of the base config templates
self._base_startup_config_template = get_resource(os.path.join("configs", "ios_base_startup-config.txt"))
self._base_private_config_template = get_resource(os.path.join("configs", "ios_base_private-config.txt"))
self._base_etherswitch_startup_config_template = get_resource(os.path.join("configs", "ios_etherswitch_startup-config.txt"))
# FIXME: hide because of issue on Windows.
self.uiTestIOSImagePushButton.hide()
# Mandatory fields
@@ -105,7 +127,6 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
if not ENABLE_CLOUD:
self.uiCloudRadioButton.hide()
def _remoteServerToggledSlot(self, checked):
"""
Slot for when the remote server radio button is toggled.
@@ -140,6 +161,11 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
self.uiChassisComboBox.clear()
if platform in CHASSIS:
self.uiChassisComboBox.addItems(CHASSIS[platform])
if platform not in ("c2600", "c3600", "c2691", "c3725", "c3745"):
self.uiEtherSwitchCheckBox.setChecked(False)
self.uiEtherSwitchCheckBox.hide()
else:
self.uiEtherSwitchCheckBox.show()
def _testIOSImageSlot(self):
"""
@@ -149,7 +175,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
platform = self.uiPlatformComboBox.currentText()
ram = self.uiRamSpinBox.value()
ios_image = self.uiIOSImageLineEdit.text()
dynamips = os.path.realpath(Dynamips.instance().settings()["path"])
dynamips = os.path.realpath(Dynamips.instance().settings()["image"])
if not os.path.exists(dynamips):
QtGui.QMessageBox.critical(self, "IOS image", "Could not find Dynamips executable: {}".format(dynamips))
return
@@ -162,22 +188,58 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
except OSError as e:
QtGui.QMessageBox.critical(self, "IOS image", "Could not test the IOS image: {}".format(e))
def _idlePCValidateSlot(self):
"""
Slot to validate the entered Idle-PC Value
"""
validator = self.uiIdlepcLineEdit.validator()
text_input = self.uiIdlepcLineEdit.text()
state = validator.validate(text_input, len(text_input))[0]
if state == QtGui.QValidator.Acceptable:
color = '#A2C964' # green
self._idle_valid = True
elif state == QtGui.QValidator.Intermediate:
color = '#fff79a' # yellow
self._idle_valid = False
else:
color = '#f6989d' # red
self._idle_valid = False
self.uiIdlepcLineEdit.setStyleSheet('QLineEdit { background-color: %s }' % color)
def _idlePCFinderSlot(self):
"""
Slot for the idle-PC finder.
"""
server = Servers.instance().localServer()
from gns3.main_window import MainWindow
main_window = MainWindow.instance()
server = Servers.instance().getServerFromString(self.getSettings()["server"])
module = Dynamips.instance()
platform = self.uiPlatformComboBox.currentText()
ios_image = self.uiIOSImageLineEdit.text()
ram = self.uiRamSpinBox.value()
router_class = PLATFORM_TO_CLASS[platform]
self._router = router_class(module, server)
self._router = router_class(module, server, main_window.project())
self._router.setup(ios_image, ram, name="AUTOIDLEPC")
self._router.created_signal.connect(self.createdSlot)
self._router.server_error_signal.connect(self.serverErrorSlot)
self.uiIdlePCFinderPushButton.setEnabled(False)
def _etherSwitchSlot(self, state):
"""
Slot if the EtherSwitch option is chosen or not.
:param state: boolean
"""
if state:
# forces the name to EtherSwitch
self.uiNameLineEdit.setText("EtherSwitch router")
# self.uiNameLineEdit.setEnabled(False)
else:
self.uiNameLineEdit.setText(self.uiPlatformComboBox.currentText())
# self.uiNameLineEdit.setEnabled(True)
def createdSlot(self, node_id):
"""
The node for the auto Idle-PC has been created.
@@ -186,12 +248,18 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
"""
self._router.computeAutoIdlepc(self._computeAutoIdlepcCallback)
self._auto_idlepc_progress_dialog = QtGui.QProgressDialog("Searching for an Idle-PC value...", "Cancel", 0, 0, parent=self)
self._auto_idlepc_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
self._auto_idlepc_progress_dialog.setWindowTitle("Idle-PC finder")
self._auto_idlepc_progress_dialog.show()
def _computeAutoIdlepcCallback(self, result, error=False):
def serverErrorSlot(self, node_id, message):
"""
The auto idle-pc node could not be created.
:param node_id: not used
:param message: error message from the server.
"""
QtGui.QMessageBox.critical(self, "Idle-PC finder", "Could not create IOS router: {}".format(message))
def _computeAutoIdlepcCallback(self, result, error=False, **kwargs):
"""
Callback for computeAutoIdlepc.
@@ -199,19 +267,15 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
:param error: indicates an error (boolean)
"""
self._router.delete()
if self._auto_idlepc_progress_dialog.wasCanceled():
return
self._auto_idlepc_progress_dialog.accept()
if self._router:
self._router.delete()
self._router = None
if error:
QtGui.QMessageBox.critical(self, "Idle-PC finder", "Error: ".format(result["message"]))
QtGui.QMessageBox.critical(self, "Idle-PC finder", "Error: {}".format(result["message"]))
else:
if result["idlepc"] and result["idlepc"] != "0x0":
self.uiIdlepcLineEdit.setText(result["idlepc"])
else:
logs = "\n".join(result["logs"])
MessageBox(self, "Idle-PC finder", "Could not find an Idle-PC value", details=logs)
idlepc = result["idlepc"]
self.uiIdlepcLineEdit.setText(idlepc)
QtGui.QMessageBox.information(self, "Idle-PC finder", "Idle-PC value {} has been found suitable for your IOS image".format(idlepc))
def _iosImageBrowserSlot(self):
"""
@@ -227,7 +291,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
# try to guess the platform
image = os.path.basename(path)
match = re.match("^(c[0-9]+)\\-\w+", image.lower())
match = re.match("^(c[0-9]+)p?\\-\w+", image.lower())
if not match:
QtGui.QMessageBox.warning(self, "IOS image", "Could not detect the platform, make sure this is a valid IOS image!")
return
@@ -245,6 +309,9 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
QtGui.QMessageBox.warning(self, "IOS image", "This IOS image is for the {} platform/chassis and is not supported by this application!".format(detected_platform))
return
if image.lower().startswith("c7200p"):
QtGui.QMessageBox.warning(self, "IOS image", "This IOS image is for c7200 PowerPC routers and is not recommended. Please use an IOS image that do not start with c7200p.")
index = self.uiPlatformComboBox.findText(detected_platform)
if index != -1:
self.uiPlatformComboBox.setCurrentIndex(index)
@@ -253,6 +320,12 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
if index != -1:
self.uiChassisComboBox.setCurrentIndex(index)
def done(self, result):
if self._router:
self._router.delete()
QtGui.QWizard.done(self, result)
def _populateAdapters(self, platform, chassis):
"""
Loads the adapter and WIC configuration.
@@ -270,7 +343,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
for slot_number, slot_adapters in ADAPTER_MATRIX[platform][chassis].items():
self._widget_slots[slot_number].setEnabled(True)
if type(slot_adapters) == str:
if isinstance(slot_adapters, str):
# only one default adapter for this slot.
self._widget_slots[slot_number].addItem(slot_adapters)
else:
@@ -290,6 +363,17 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
wic_list = list(wics)
self._widget_wics[wic_number].addItems([""] + wic_list)
def _md5sum(self, filename):
with open(filename, "rb") as fd:
m = hashlib.md5()
while True:
data = fd.read(8192)
if not data:
break
m.update(data)
return m.hexdigest()
def initializePage(self, page_id):
if self.page(page_id) == self.uiServerWizardPage:
@@ -308,7 +392,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
path = self.uiIOSImageLineEdit.text()
if os.path.isfile(path):
minimum_required_ram = IOSRouterPreferencesPage.getMinimumRequiredRAM(path)
if minimum_required_ram > PLATFORMS_DEFAULT_RAM[platform]:
if minimum_required_ram >= PLATFORMS_DEFAULT_RAM[platform]:
self.uiRamSpinBox.setValue(minimum_required_ram)
else:
self.uiRamSpinBox.setValue(PLATFORMS_DEFAULT_RAM[platform])
@@ -323,10 +407,22 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
self._populateAdapters(platform, chassis)
if platform == "c7200":
self.uiSlot0comboBox.setCurrentIndex(self.uiSlot0comboBox.findText("C7200-IO-FE"))
if self.uiEtherSwitchCheckBox.isChecked():
self.uiSlot1comboBox.setCurrentIndex(self.uiSlot1comboBox.findText("NM-16ESW"))
elif self.page(page_id) == self.uiIdlePCWizardPage:
path = self.uiIOSImageLineEdit.text()
if os.path.isfile(path):
try:
md5sum = self._md5sum(path)
if md5sum in DEFAULT_IDLEPC:
self.uiIdlepcLineEdit.setText(DEFAULT_IDLEPC[md5sum])
except OSError:
pass
def validateCurrentPage(self):
"""
Validates the IOS name.
Validates the IOS name and checks validation state for Idle-PC value
"""
if self.currentPage() == self.uiNamePlatformWizardPage:
@@ -335,23 +431,21 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
if ios_router["name"] == name:
QtGui.QMessageBox.critical(self, "Name", "{} is already used, please choose another name".format(name))
return False
if self.currentPage() == self.uiMemoryWizardPage and self.uiPlatformComboBox.currentText() == "c7200":
if self.uiRamSpinBox.value() > 512:
QtGui.QMessageBox.critical(self, "c7200 RAM requirement", "c7200 routers with NPE-400 are limited to 512MB of RAM")
return False
if self.currentPage() == self.uiIdlePCWizardPage:
if not self._idle_valid:
idle_pc = self.uiIdlepcLineEdit.text()
QtGui.QMessageBox.critical(self, "Idle-PC", "{} is not a valid Idle-PC value ".format(idle_pc))
return False
if self.currentPage() == self.uiServerWizardPage and self.uiRemoteRadioButton.isChecked():
if not Servers.instance().remoteServers():
QtGui.QMessageBox.critical(self, "Remote server", "There is no remote server registered in Dynamips preferences")
return False
return True
# minimum_required_ram = self._getMinimumRequiredRAM(path)
# if minimum_required_ram > ram:
# QtGui.QMessageBox.warning(self, "IOS image", "There is not sufficient RAM allocated to this IOS image, recommended RAM is {} MB".format(minimum_required_ram))
#
# # basename doesn't work on Unix with Windows paths
# if not sys.platform.startswith('win') and len(path) > 2 and path[1] == ":":
# import ntpath
# image = ntpath.basename(path)
# else:
# image = os.path.basename(path)
#
# if image.startswith("c7200p"):
# QtGui.QMessageBox.warning(self, "IOS image", "This IOS image is for the c7200 platform with NPE-G2 and using it is not recommended.\nPlease use an IOS image that do not start with c7200p.")
def getSettings(self):
"""
Returns the settings set in this Wizard.
@@ -359,32 +453,42 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
:return: settings dict
"""
path = self.uiIOSImageLineEdit.text()
image = self.uiIOSImageLineEdit.text()
if Dynamips.instance().settings()["use_local_server"] or self.uiLocalRadioButton.isChecked():
server = "local"
elif self.uiRemoteRadioButton.isChecked():
if self.uiLoadBalanceCheckBox.isChecked():
server = next(iter(Servers.instance()))
if not server:
QtGui.QMessageBox.critical(self, "IOS router", "No remote server available!")
return
server = "{}:{}".format(server.host, server.port)
else:
server = self.uiRemoteServersComboBox.currentText()
else: # Cloud is selected
else: # Cloud is selected
server = "cloud"
platform = self.uiPlatformComboBox.currentText()
settings = {
"name": self.uiNameLineEdit.text(),
"path": path,
"image": image,
"startup_config": get_default_base_config(self._base_startup_config_template),
"ram": self.uiRamSpinBox.value(),
"nvram": PLATFORMS_DEFAULT_NVRAM[platform],
"idlepc": self.uiIdlepcLineEdit.text(),
"image": os.path.basename(path),
"platform": self.uiPlatformComboBox.currentText(),
"platform": platform,
"chassis": self.uiChassisComboBox.currentText(),
"server": server,
}
if self.uiEtherSwitchCheckBox.isChecked():
settings["startup_config"] = get_default_base_config(self._base_etherswitch_startup_config_template)
settings["default_symbol"] = ":/symbols/multilayer_switch.normal.svg"
settings["hover_symbol"] = ":/symbols/multilayer_switch.selected.svg"
settings["disk0"] = 1 # adds 1MB disk to store vlan.dat
settings["category"] = Node.switches
image_file = os.path.basename(image)
if image_file.lower().startswith("c7200p"):
settings["npe"] = "npe-g2"
for slot_id, widget in self._widget_slots.items():
if widget.isEnabled():
settings["slot{}".format(slot_id)] = widget.currentText()
@@ -406,4 +510,4 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
if platform not in WIC_MATRIX:
# skip the WIC modules page if the platform doesn't support any.
return self.uiNetworkAdaptersWizardPage.nextId() + 1
return QtGui.QWizard.nextId(self)
return QtGui.QWizard.nextId(self)

View File

@@ -23,35 +23,36 @@ Asynchronously sends JSON messages to the GNS3 server and receives responses wit
import re
from gns3.node import Node
from gns3.ports.atm_port import ATMPort
from .device import Device
import logging
log = logging.getLogger(__name__)
class ATMSwitch(Node):
class ATMSwitch(Device):
"""
Dynamips ATM switch.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server):
Node.__init__(self, server)
def __init__(self, module, server, project):
log.info("ATM switch is being created")
Device.__init__(self, module, server, project)
self.setStatus(Node.started) # this is an always-on node
self._atmsw_id = None
self._ports = []
self._module = module
self._settings = {"name": "",
"mappings": {}}
def setup(self, name=None, initial_ports=[], initial_mappings={}):
def setup(self, name=None, device_id=None, initial_ports=[], initial_mappings={}):
"""
Setups this ATM switch.
:param name: optional name for this switch.
:param device_id: device identifier on the server
:param initial_ports: ports to be automatically added when creating this ATM switch
:param initial_mappings: mappings to be automatically added when creating this ATM switch
"""
@@ -78,61 +79,11 @@ class ATMSwitch(Node):
port.setPacketCaptureSupported(True)
self._ports.append(port)
params = {"name": name}
self._server.send_message("dynamips.atmsw.create", params, self._setupCallback)
def _setupCallback(self, result, error=False):
"""
Callback for setup.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while setting up {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
return
self._atmsw_id = result["id"]
if not self._atmsw_id:
self.error_signal.emit(self.id(), "returned ID from server is null")
return
self._settings["name"] = result["name"]
log.info("ATM switch {} has been created".format(self.name()))
self.setInitialized(True)
self.created_signal.emit(self.id())
self._module.addNode(self)
def delete(self):
"""
Deletes this ATM switch.
"""
log.debug("ATM switch {} is being deleted".format(self.name()))
# first delete all the links attached to this node
self.delete_links_signal.emit()
if self._atmsw_id:
self._server.send_message("dynamips.atmsw.delete", {"id": self._atmsw_id}, self._deleteCallback)
else:
self.deleted_signal.emit()
self._module.removeNode(self)
def _deleteCallback(self, result, error=False):
"""
Callback for delete.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
log.info("ATM switch {} has been deleted".format(self.name()))
self.deleted_signal.emit()
self._module.removeNode(self)
params = {"name": name,
"device_type": "atm_switch"}
if device_id:
params["device_id"] = device_id
self.httpPost("/dynamips/devices", self._setupCallback, body=params)
def update(self, new_settings):
"""
@@ -141,53 +92,54 @@ class ATMSwitch(Node):
:param new_settings: settings dictionary
"""
ports_to_create = []
mapping = new_settings["mappings"]
updated = False
for source, destination in mapping.items():
source_port = source.split(":")[0]
destination_port = destination.split(":")[0]
if source_port not in ports_to_create:
ports_to_create.append(source_port)
if destination_port not in ports_to_create:
ports_to_create.append(destination_port)
if "mappings" in new_settings:
ports_to_create = []
mapping = new_settings["mappings"]
for port in self._ports.copy():
if port.isFree():
for source, destination in mapping.items():
source_port = source.split(":")[0]
destination_port = destination.split(":")[0]
if source_port not in ports_to_create:
ports_to_create.append(source_port)
if destination_port not in ports_to_create:
ports_to_create.append(destination_port)
for port in self._ports.copy():
if port.isFree():
updated = True
self._ports.remove(port)
else:
ports_to_create.remove(port.name())
for port_name in ports_to_create:
port = ATMPort(port_name)
port.setPortNumber(int(port_name))
port.setStatus(ATMPort.started)
port.setPacketCaptureSupported(True)
self._ports.append(port)
updated = True
self._ports.remove(port)
else:
ports_to_create.remove(port.name())
log.debug("port {} has been added".format(port_name))
for port_name in ports_to_create:
port = ATMPort(port_name)
port.setPortNumber(int(port_name))
port.setStatus(ATMPort.started)
port.setPacketCaptureSupported(True)
self._ports.append(port)
updated = True
log.debug("port {} has been added".format(port_name))
self._settings["mappings"] = new_settings["mappings"].copy()
params = {}
if "name" in new_settings and new_settings["name"] != self.name():
if self.hasAllocatedName(new_settings["name"]):
self.error_signal.emit(self.id(), 'Name "{}" is already used by another node'.format(new_settings["name"]))
return
params = {"id": self._atmsw_id,
"name": new_settings["name"]}
params["name"] = new_settings["name"]
updated = True
self._settings["mappings"] = new_settings["mappings"].copy()
if updated:
if params:
log.debug("{} is being updated: {}".format(self.name(), params))
self._server.send_message("dynamips.atmsw.update", params, self._updateCallback)
self.httpPut("/dynamips/devices/{device_id}".format(device_id=self._device_id), self._updateCallback, body=params)
else:
log.info("ATM switch {} has been updated".format(self.name()))
self.updated_signal.emit()
def _updateCallback(self, result, error=False):
def _updateCallback(self, result, error=False, **kwargs):
"""
Callback for update.
@@ -197,7 +149,7 @@ class ATMSwitch(Node):
if error:
log.error("error while updating {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
self.server_error_signal.emit(self.id(), result["message"])
else:
if "name" in result:
self._settings["name"] = result["name"]
@@ -205,33 +157,6 @@ class ATMSwitch(Node):
log.info("{} has been updated".format(self.name()))
self.updated_signal.emit()
def allocateUDPPort(self, port_id):
"""
Requests an UDP port allocation.
:param port_id: port identifier
"""
log.debug("{} is requesting an UDP port allocation".format(self.name()))
self._server.send_message("dynamips.atmsw.allocate_udp_port", {"id": self._atmsw_id, "port_id": port_id}, self._allocateUDPPortCallback)
def _allocateUDPPortCallback(self, result, error=False):
"""
Callback for allocateUDPPort.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while allocating an UDP port for {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
port_id = result["port_id"]
lport = result["lport"]
log.debug("{} has allocated UDP port {}".format(self.name(), lport))
self.allocate_udp_nio_signal.emit(self.id(), port_id, lport)
def addNIO(self, port, nio):
"""
Adds a new NIO on the specified port for this ATM switch.
@@ -240,11 +165,7 @@ class ATMSwitch(Node):
:param nio: NIO instance
"""
params = {"id": self._atmsw_id,
"port": port.portNumber(),
"port_id": port.id()}
params["nio"] = self.getNIOInfo(nio)
params = {"nio": self.getNIOInfo(nio)}
params["mappings"] = {}
for source, destination in self._settings["mappings"].items():
source_port = source.split(":")[0]
@@ -255,124 +176,13 @@ class ATMSwitch(Node):
params["mappings"][destination] = source
log.debug("{} is adding an {}: {}".format(self.name(), nio, params))
self._server.send_message("dynamips.atmsw.add_nio", params, self._addNIOCallback)
def _addNIOCallback(self, result, error=False):
"""
Callback for addNIO.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while adding an UDP NIO for {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
self.nio_cancel_signal.emit(self.id())
else:
log.debug("{} has added a new NIO: {}".format(self.name(), result))
self.nio_signal.emit(self.id(), result["port_id"])
def deleteNIO(self, port):
"""
Deletes an NIO from the specified port on this switch.
:param port: Port instance
"""
params = {"id": self._atmsw_id,
"port": port.portNumber()}
log.debug("{} is deleting an NIO: {}".format(self.name(), params))
self._server.send_message("dynamips.atmsw.delete_nio", params, self.deleteNIOCallback)
def deleteNIOCallback(self, result, error=False):
"""
Callback for deleteNIO.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting NIO {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
return
log.debug("{} has deleted a NIO: {}".format(self.name(), result))
def startPacketCapture(self, port, capture_file_name, data_link_type):
"""
Starts a packet capture.
:param port: Port instance
:param capture_file_name: PCAP capture file path
:param data_link_type: PCAP data link type
"""
params = {"id": self._atmsw_id,
"port_id": port.id(),
"port": port.portNumber(),
"capture_file_name": capture_file_name,
"data_link_type": data_link_type}
log.debug("{} is starting a packet capture on {}: {}".format(self.name(), port.name(), params))
self._server.send_message("dynamips.atmsw.start_capture", params, self._startPacketCaptureCallback)
def _startPacketCaptureCallback(self, result, error=False):
"""
Callback for starting a packet capture.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while starting capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
for port in self._ports:
if port.id() == result["port_id"]:
log.info("{} has successfully started capturing packets on {}".format(self.name(), port.name()))
try:
port.startPacketCapture(result["capture_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
break
def stopPacketCapture(self, port):
"""
Stops a packet capture.
:param port: Port instance
"""
params = {"id": self._atmsw_id,
"port_id": port.id(),
"port": port.portNumber()}
log.debug("{} is stopping a packet capture on {}: {}".format(self.name(), port.name(), params))
self._server.send_message("dynamips.atmsw.stop_capture", params, self._stopPacketCaptureCallback)
def _stopPacketCaptureCallback(self, result, error=False):
"""
Callback for stopping a packet capture.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while stopping capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
for port in self._ports:
if port.id() == result["port_id"]:
log.info("{} has successfully stopped capturing packets on {}".format(self.name(), port.name()))
port.stopPacketCapture()
self.updated_signal.emit()
break
self.httpPost("/{prefix}/devices/{device_id}/ports/{port}/nio".format(
port=port.portNumber(),
prefix=self.URL_PREFIX,
device_id=self._device_id),
self._addNIOCallback,
context={"port_id": port.id()},
body=params)
def info(self):
"""
@@ -382,12 +192,13 @@ class ATMSwitch(Node):
"""
info = """ATM switch {name} is always-on
Node ID is {id}, server's ATM switch ID is {atmsw_id}
Local node ID is {id}
Server's Device ID is {device_id}
Hardware is Dynamips emulated simple ATM switch
Switch's server runs on {host}:{port}
""".format(name=self.name(),
id=self.id(),
atmsw_id=self._atmsw_id,
device_id=self._device_id,
host=self._server.host,
port=self._server.port)
@@ -449,11 +260,11 @@ class ATMSwitch(Node):
"""
atmsw = {"id": self.id(),
"device_id": self._device_id,
"type": self.__class__.__name__,
"description": str(self),
"properties": {"name": self.name()},
"server_id": self._server.id(),
}
"server_id": self._server.id()}
if self._settings["mappings"]:
atmsw["properties"]["mappings"] = self._settings["mappings"]
@@ -477,6 +288,10 @@ class ATMSwitch(Node):
settings = node_info["properties"]
name = settings.pop("name")
# pre-1.3 projects have no device id, set to 1 to have
# a proper project conversion on the server side
device_id = node_info.get("device_id", 1)
mappings = {}
if "mappings" in settings:
mappings = settings["mappings"]
@@ -487,7 +302,7 @@ class ATMSwitch(Node):
log.info("ATM switch {} is loading".format(name))
self.setName(name)
self.setup(name, ports, mappings)
self.setup(name, device_id, ports, mappings)
def name(self):
"""

View File

@@ -23,34 +23,33 @@ from .router import Router
class C1700(Router):
"""
Dynamips c1700 router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server, chassis="1720"):
Router.__init__(self, module, server, platform="c1700")
def __init__(self, module, server, project, chassis="1720"):
self._platform_settings = {"ram": 64,
"nvram": 32,
"disk0": 0,
"disk1": 0,
"chassis": "1720",
"iomem": 15,
"clock_divisor": 8,
"slot0": "C1700-MB-1FE"}
Router.__init__(self, module, server, project, platform="c1700")
c1700_settings = {"ram": 128,
"nvram": 32,
"disk0": 0,
"disk1": 0,
"chassis": "1720",
"iomem": 15,
"clock_divisor": 8,
"slot0": "C1700-MB-1FE"}
# set the default adapter for slot 1 for these chassis
if chassis in ['1751', '1760']:
self._platform_settings["slot1"] = "C1700-MB-WIC1"
c1700_settings["slot1"] = "C1700-MB-WIC1"
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
self._settings.update(c1700_settings)
def __str__(self):

View File

@@ -23,11 +23,13 @@ from .router import Router
class C2600(Router):
"""
Dynamips c2600 router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
# get the default slot0 adapter based on the chassis
@@ -42,25 +44,22 @@ class C2600(Router):
"2650XM": "C2600-MB-1FE",
"2651XM": "C2600-MB-2FE"}
def __init__(self, module, server, chassis="2610"):
Router.__init__(self, module, server, platform="c2600")
def __init__(self, module, server, project, chassis="2610"):
self._platform_settings = {"ram": 64,
"nvram": 128,
"disk0": 0,
"disk1": 0,
"chassis": chassis,
"iomem": 15,
"clock_divisor": 8}
Router.__init__(self, module, server, project, platform="c2600")
c2600_settings = {"ram": 128,
"nvram": 128,
"disk0": 0,
"disk1": 0,
"chassis": chassis,
"iomem": 15,
"clock_divisor": 8}
# set the default adapter for slot 0
self._platform_settings["slot0"] = self.chassis_to_default_adapter[chassis]
c2600_settings["slot0"] = self.chassis_to_default_adapter[chassis]
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
self._settings.update(c2600_settings)
def __str__(self):

View File

@@ -23,29 +23,28 @@ from .router import Router
class C2691(Router):
"""
Dynamips c2691 router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server):
Router.__init__(self, module, server, platform="c2691")
def __init__(self, module, server, project):
self._platform_settings = {"ram": 128,
"nvram": 112,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
Router.__init__(self, module, server, project, platform="c2691")
c2691_settings = {"ram": 192,
"nvram": 112,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
self._settings.update(c2691_settings)
def __str__(self):

View File

@@ -23,33 +23,32 @@ from .router import Router
class C3600(Router):
"""
Dynamips c3600 router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server, chassis="3640"):
Router.__init__(self, module, server, platform="c3600")
def __init__(self, module, server, project, chassis="3640"):
self._platform_settings = {"ram": 128,
"nvram": 128,
"disk0": 0,
"disk1": 0,
"chassis": chassis,
"iomem": 5,
"clock_divisor": 4}
Router.__init__(self, module, server, project, platform="c3600")
c3600_settings = {"ram": 192,
"nvram": 128,
"disk0": 0,
"disk1": 0,
"chassis": chassis,
"iomem": 5,
"clock_divisor": 4}
# chassis 3660 has a default adapter
if chassis == "3660":
self._platform_settings["slot0"] = "Leopard-2FE"
c3600_settings["slot0"] = "Leopard-2FE"
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
self._settings.update(c3600_settings)
def __str__(self):

View File

@@ -23,29 +23,28 @@ from .router import Router
class C3725(Router):
"""
Dynamips c3725 router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server):
Router.__init__(self, module, server, platform="c3725")
def __init__(self, module, server, project):
self._platform_settings = {"ram": 128,
"nvram": 112,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
Router.__init__(self, module, server, project, platform="c3725")
c3725_settings = {"ram": 128,
"nvram": 112,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
self._settings.update(c3725_settings)
def __str__(self):

View File

@@ -23,29 +23,28 @@ from .router import Router
class C3745(Router):
"""
Dynamips c3745 router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server):
Router.__init__(self, module, server, platform="c3745")
def __init__(self, module, server, project):
self._platform_settings = {"ram": 128,
"nvram": 304,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
Router.__init__(self, module, server, project, platform="c3745")
c3745_settings = {"ram": 256,
"nvram": 304,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
self._settings.update(c3745_settings)
def __str__(self):

View File

@@ -23,37 +23,36 @@ from .router import Router
class C7200(Router):
"""
Dynamips c7200 router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server, npe="npe-400"):
Router.__init__(self, module, server, platform="c7200")
def __init__(self, module, server, project, npe="npe-400"):
self._platform_settings = {"ram": 256,
"nvram": 128,
"disk0": 64,
"disk1": 0,
"npe": npe,
"midplane": "vxr",
"clock_divisor": 4,
"sensors": [22, 22, 22, 22],
"power_supplies": [1, 1]}
Router.__init__(self, module, server, project, platform="c7200")
c7200_settings = {"ram": 512,
"nvram": 128,
"disk0": 64,
"disk1": 0,
"npe": npe,
"midplane": "vxr",
"clock_divisor": 4,
"sensors": [22, 22, 22, 22],
"power_supplies": [1, 1]}
# first slot is a mandatory Input/Output controller (based on NPE type)
if npe == "npe-g2":
self._platform_settings["slot0"] = "C7200-IO-GE-E"
c7200_settings["slot0"] = "C7200-IO-GE-E"
else:
self._platform_settings["slot0"] = "C7200-IO-2FE"
c7200_settings["slot0"] = "C7200-IO-FE"
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
self._settings.update(c7200_settings)
def __str__(self):

View File

@@ -0,0 +1,211 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Base class for Device classes.
"""
from gns3.node import Node
import logging
log = logging.getLogger(__name__)
class Device(Node):
URL_PREFIX = "dynamips"
def __init__(self, module, server, project):
super().__init__(module, server, project)
self._device_id = None
def device_id(self):
"""
Return the ID of this device
:returns: identifier (string)
"""
return self._device_id
def _setupCallback(self, result, error=False, **kwargs):
"""
Callback for setup.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while setting up {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return
self._device_id = result["device_id"]
self._settings["name"] = result["name"]
log.info("{} has been created".format(self.name()))
self.setInitialized(True)
self.created_signal.emit(self.id())
self._module.addNode(self)
def delete(self):
"""
Deletes this Device instance.
"""
log.debug("{} is being deleted".format(self.name()))
# first delete all the links attached to this node
self.delete_links_signal.emit()
if self._device_id and self._server.connected():
self.httpDelete("/{prefix}/devices/{device_id}".format(prefix=self.URL_PREFIX, device_id=self._device_id),
self._deleteCallback)
else:
self.deleted_signal.emit()
self._module.removeNode(self)
def _deleteCallback(self, result, error=False, **kwargs):
"""
Callback for delete.
:param result: server response (dict)
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
log.info("{} has been deleted".format(self.name()))
self.deleted_signal.emit()
self._module.removeNode(self)
def _addNIOCallback(self, result, error=False, context={}, **kwargs):
"""
Callback for addNIO.
:param result: server response (dict)
:param error: indicates an error (boolean)
"""
if error:
log.error("error while adding an UDP NIO for {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
self.nio_cancel_signal.emit(self.id())
else:
self.nio_signal.emit(self.id(), context["port_id"])
def deleteNIO(self, port):
"""
Deletes an NIO from the specified port on this Device instance
:param port: Port instance
"""
log.debug("{} is deleting an NIO".format(self.name()))
self.httpDelete("/{prefix}/devices/{device_id}/ports/{port}/nio".format(prefix=self.URL_PREFIX,
port=port.portNumber(),
device_id=self._device_id),
self._deleteNIOCallback)
def _deleteNIOCallback(self, result, error=False, **kwargs):
"""
Callback for deleteNIO.
:param result: server response (dict)
:param error: indicates an error (boolean)
"""
if error:
log.error("Error while deleting NIO {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
return
log.debug("{} has deleted a NIO: {}".format(self.name(), result))
def startPacketCapture(self, port, capture_file_name, data_link_type):
"""
Starts a packet capture.
:param port: Port instance
:param capture_file_name: PCAP capture file path
:param data_link_type: PCAP data link type
"""
params = {"capture_file_name": capture_file_name,
"data_link_type": data_link_type}
log.debug("{} is starting a packet capture on {}: {}".format(self.name(), port.name(), params))
self.httpPost("/{prefix}/devices/{device_id}/ports/{port}/start_capture".format(
port=port.portNumber(),
prefix=self.URL_PREFIX,
device_id=self._device_id),
self._startPacketCaptureCallback,
context={"port": port},
body=params)
def _startPacketCaptureCallback(self, result, error=False, context={}, **kwargs):
"""
Callback for starting a packet capture.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while starting capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
else:
port = context["port"]
log.info("{} has successfully started capturing packets on {}".format(self.name(), port.name()))
try:
port.startPacketCapture(result["pcap_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
def stopPacketCapture(self, port):
"""
Stops a packet capture.
:param port: Port instance
"""
log.debug("{} is stopping a packet capture on {}".format(self.name(), port.name()))
self.httpPost("/{prefix}/devices/{device_id}/ports/{port}/stop_capture".format(
port=port.portNumber(),
prefix=self.URL_PREFIX,
device_id=self._device_id),
self._stopPacketCaptureCallback,
context={"port": port})
def _stopPacketCaptureCallback(self, result, error=False, context={}, **kwargs):
"""
Callback for stopping a packet capture.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while stopping capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["message"])
else:
port = context["port"]
log.info("{} has successfully stopped capturing packets on {}".format(self.name(), port.name()))
port.stopPacketCapture()
self.updated_signal.emit()

View File

@@ -22,36 +22,37 @@ Asynchronously sends JSON messages to the GNS3 server and receives responses wit
from gns3.node import Node
from gns3.ports.ethernet_port import EthernetPort
from .device import Device
import logging
log = logging.getLogger(__name__)
class EthernetHub(Node):
class EthernetHub(Device):
"""
Dynamips Ethernet hub.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server):
Node.__init__(self, server)
def __init__(self, module, server, project):
log.info("Ethernet hub is being created")
Device.__init__(self, module, server, project)
self.setStatus(Node.started) # this is an always-on node
self._ethhub_id = None
self._module = module
self._ports = []
self._settings = {"name": "",
"ports": []}
def setup(self, name=None, initial_ports=[]):
def setup(self, name=None, device_id=None, initial_ports=[]):
"""
Setups this hub.
:param name: optional name for this hub
:param initial_ports: ports to be automatically added when creating this hub
:param device_id: device identifier on the server
:param initial_ports: ports to automatically be added when creating this hub
"""
# let's create a unique name if none has been chosen
@@ -62,6 +63,7 @@ class EthernetHub(Node):
self.error_signal.emit(self.id(), "could not allocate a name for this Ethernet hub")
return
self._settings["name"] = name
if not initial_ports:
# default configuration if no initial ports
for port_number in range(1, 9):
@@ -69,7 +71,7 @@ class EthernetHub(Node):
initial_ports.append({"name": str(port_number),
"port_number": port_number})
# add initial ports
# add the initial ports
for initial_port in initial_ports:
port = EthernetPort(initial_port["name"])
port.setPortNumber(initial_port["port_number"])
@@ -80,61 +82,11 @@ class EthernetHub(Node):
self._ports.append(port)
self._settings["ports"].append(port.portNumber())
params = {"name": name}
self._server.send_message("dynamips.ethhub.create", params, self._setupCallback)
def _setupCallback(self, result, error=False):
"""
Callback for setup.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while setting up {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
return
self._ethhub_id = result["id"]
if not self._ethhub_id:
self.error_signal.emit(self.id(), "returned ID from server is null")
return
self._settings["name"] = result["name"]
log.info("Ethernet hub {} has been created".format(self.name()))
self.setInitialized(True)
self.created_signal.emit(self.id())
self._module.addNode(self)
def delete(self):
"""
Deletes this Ethernet hub.
"""
log.debug("Ethernet hub {} is being deleted".format(self.name()))
# first delete all the links attached to this node
self.delete_links_signal.emit()
if self._ethhub_id:
self._server.send_message("dynamips.ethhub.delete", {"id": self._ethhub_id}, self._deleteCallback)
else:
self.deleted_signal.emit()
self._module.removeNode(self)
def _deleteCallback(self, result, error=False):
"""
Callback for delete.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
log.info("{} has been deleted".format(self.name()))
self.deleted_signal.emit()
self._module.removeNode(self)
params = {"name": name,
"device_type": "ethernet_hub"}
if device_id:
params["device_id"] = device_id
self.httpPost("/dynamips/devices", self._setupCallback, body=params)
def update(self, new_settings):
"""
@@ -143,50 +95,50 @@ class EthernetHub(Node):
:param new_settings: settings dictionary
"""
ports_to_create = []
ports = new_settings["ports"]
updated = False
for port_number in ports:
if port_number not in ports_to_create:
ports_to_create.append(port_number)
if "ports" in new_settings:
ports_to_create = []
ports = new_settings["ports"]
for port_number in ports:
if port_number not in ports_to_create:
ports_to_create.append(port_number)
for port in self._ports.copy():
if port.isFree():
self._ports.remove(port)
for port in self._ports.copy():
if port.isFree():
self._ports.remove(port)
updated = True
log.debug("port {} has been removed".format(port.name()))
elif port.name() in ports_to_create:
ports_to_create.remove(port.name())
for port_name in ports_to_create:
port = EthernetPort(port_name)
port.setPortNumber(int(port_name))
port.setStatus(EthernetPort.started)
port.setPacketCaptureSupported(True)
self._ports.append(port)
updated = True
log.debug("port {} has been removed".format(port.name()))
else:
ports_to_create.remove(port.name())
log.debug("port {} has been added".format(port_name))
for port_name in ports_to_create:
port = EthernetPort(port_name)
port.setPortNumber(int(port_name))
port.setStatus(EthernetPort.started)
port.setPacketCaptureSupported(True)
self._ports.append(port)
updated = True
log.debug("port {} has been added".format(port_name))
self._settings["ports"] = new_settings["ports"].copy()
params = {}
if "name" in new_settings and new_settings["name"] != self.name():
if self.hasAllocatedName(new_settings["name"]):
self.error_signal.emit(self.id(), 'Name "{}" is already used by another node'.format(new_settings["name"]))
return
params = {"id": self._ethhub_id,
"name": new_settings["name"]}
params["name"] = new_settings["name"]
updated = True
self._settings["ports"] = new_settings["ports"].copy()
if updated:
if params:
log.debug("{} is being updated: {}".format(self.name(), params))
self._server.send_message("dynamips.ethhub.update", params, self._updateCallback)
self.httpPut("/dynamips/devices/{device_id}".format(device_id=self._device_id), self._updateCallback, body=params)
else:
log.info("{} has been updated".format(self.name()))
self.updated_signal.emit()
def _updateCallback(self, result, error=False):
def _updateCallback(self, result, error=False, **kwargs):
"""
Callback for update.
@@ -196,7 +148,7 @@ class EthernetHub(Node):
if error:
log.error("error while updating {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
self.server_error_signal.emit(self.id(), result["message"])
else:
if "name" in result:
self._settings["name"] = result["name"]
@@ -204,164 +156,24 @@ class EthernetHub(Node):
log.info("{} has been updated".format(self.name()))
self.updated_signal.emit()
def allocateUDPPort(self, port_id):
"""
Requests an UDP port allocation.
:param port_id: port identifier
"""
log.debug("{} is requesting an UDP port allocation".format(self.name()))
self._server.send_message("dynamips.ethhub.allocate_udp_port", {"id": self._ethhub_id, "port_id": port_id}, self._allocateUDPPortCallback)
def _allocateUDPPortCallback(self, result, error=False):
"""
Callback for allocateUDPPort.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while allocating an UDP port for {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
port_id = result["port_id"]
lport = result["lport"]
log.debug("{} has allocated UDP port {}".format(self.name(), port_id, lport))
self.allocate_udp_nio_signal.emit(self.id(), port_id, lport)
def addNIO(self, port, nio):
"""
Adds a new NIO on the specified port for this hub.
Adds a new NIO on the specified port for this Ethernet hub.
:param port: Port instance
:param nio: NIO instance
"""
params = {"id": self._ethhub_id,
"port": port.portNumber(),
"port_id": port.id()}
params = {}
params["nio"] = self.getNIOInfo(nio)
log.debug("{} is adding an {}: {}".format(self.name(), nio, params))
self._server.send_message("dynamips.ethhub.add_nio", params, self._addNIOCallback)
def _addNIOCallback(self, result, error=False):
"""
Callback for addNIO.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while adding an UDP NIO for {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
self.nio_cancel_signal.emit(self.id())
else:
self.nio_signal.emit(self.id(), result["port_id"])
def deleteNIO(self, port):
"""
Deletes an NIO from the specified port on this hub.
:param port: Port instance
"""
params = {"id": self._ethhub_id,
"port": port.portNumber()}
log.debug("{} is deleting an NIO: {}".format(self.name(), params))
self._server.send_message("dynamips.ethhub.delete_nio", params, self._deleteNIOCallback)
def _deleteNIOCallback(self, result, error=False):
"""
Callback for deleteNIO.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting NIO {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
return
log.debug("{} has deleted a NIO: {}".format(self.name(), result))
def startPacketCapture(self, port, capture_file_name, data_link_type):
"""
Starts a packet capture.
:param port: Port instance
:param capture_file_name: PCAP capture file path
:param data_link_type: PCAP data link type
"""
params = {"id": self._ethhub_id,
"port_id": port.id(),
"port": port.portNumber(),
"capture_file_name": capture_file_name,
"data_link_type": data_link_type}
log.debug("{} is starting a packet capture on {}: {}".format(self.name(), port.name(), params))
self._server.send_message("dynamips.ethhub.start_capture", params, self._startPacketCaptureCallback)
def _startPacketCaptureCallback(self, result, error=False):
"""
Callback for starting a packet capture.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while starting capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
for port in self._ports:
if port.id() == result["port_id"]:
log.info("{} has successfully started capturing packets on {}".format(self.name(), port.name()))
try:
port.startPacketCapture(result["capture_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
break
def stopPacketCapture(self, port):
"""
Stops a packet capture.
:param port: Port instance
"""
params = {"id": self._ethhub_id,
"port_id": port.id(),
"port": port.portNumber()}
log.debug("{} is stopping a packet capture on {}: {}".format(self.name(), port.name(), params))
self._server.send_message("dynamips.ethhub.stop_capture", params, self._stopPacketCaptureCallback)
def _stopPacketCaptureCallback(self, result, error=False):
"""
Callback for stopping a packet capture.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while stopping capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
for port in self._ports:
if port.id() == result["port_id"]:
log.info("{} has successfully stopped capturing packets on {}".format(self.name(), port.name()))
port.stopPacketCapture()
self.updated_signal.emit()
break
self.httpPost("/{prefix}/devices/{device_id}/ports/{port}/nio".format(
port=port.portNumber(),
prefix=self.URL_PREFIX,
device_id=self._device_id),
self._addNIOCallback,
context={"port_id": port.id()},
body=params)
def info(self):
"""
@@ -371,12 +183,13 @@ class EthernetHub(Node):
"""
info = """Ethernet hub {name} is always-on
Node ID is {id}, server's Ethernet hub ID is {ethhub_id}
Local node ID is {id}
Server's Device ID is {device_id}
Hardware is Dynamips emulated simple Ethernet hub
Hub's server runs on {host}:{port}
""".format(name=self.name(),
id=self.id(),
ethhub_id=self._ethhub_id,
device_id=self._device_id,
host=self._server.host,
port=self._server.port)
@@ -399,11 +212,11 @@ class EthernetHub(Node):
"""
hub = {"id": self.id(),
"device_id": self._device_id,
"type": self.__class__.__name__,
"description": str(self),
"properties": {"name": self.name()},
"server_id": self._server.id(),
}
"server_id": self._server.id()}
# add the ports
if self._ports:
@@ -424,6 +237,10 @@ class EthernetHub(Node):
settings = node_info["properties"]
name = settings.pop("name")
# pre-1.3 projects have no device id, set to 1 to have
# a proper project conversion on the server side
device_id = node_info.get("device_id", 1)
# create the ports with the correct port numbers and IDs
ports = []
if "ports" in node_info:
@@ -431,7 +248,7 @@ class EthernetHub(Node):
log.info("Ethernet hub {} is loading".format(name))
self.setName(name)
self.setup(name, ports)
self.setup(name, device_id, ports)
def name(self):
"""

View File

@@ -22,35 +22,36 @@ Asynchronously sends JSON messages to the GNS3 server and receives responses wit
from gns3.node import Node
from gns3.ports.ethernet_port import EthernetPort
from .device import Device
import logging
log = logging.getLogger(__name__)
class EthernetSwitch(Node):
class EthernetSwitch(Device):
"""
Dynamips Ethernet switch.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server):
Node.__init__(self, server)
def __init__(self, module, server, project):
log.info("Ethernet switch is being created")
Device.__init__(self, module, server, project)
self.setStatus(Node.started) # this is an always-on node
self._ethsw_id = None
self._module = module
self._ports = []
self._settings = {"name": "",
"ports": {}}
def setup(self, name=None, initial_ports=[]):
def setup(self, name=None, device_id=None, initial_ports=[]):
"""
Setups this Ethernet switch.
:param name: optional name for this switch
:param device_id: device identifier on the server
:param initial_ports: ports to be automatically added when creating this switch
"""
@@ -62,6 +63,7 @@ class EthernetSwitch(Node):
self.error_signal.emit(self.id(), "could not allocate a name for this Ethernet switch")
return
self._settings["name"] = name
if not initial_ports:
# default configuration if no initial ports
for port_number in range(1, 9):
@@ -83,61 +85,11 @@ class EthernetSwitch(Node):
self._settings["ports"][port.portNumber()] = {"type": initial_port["type"],
"vlan": initial_port["vlan"]}
params = {"name": name}
self._server.send_message("dynamips.ethsw.create", params, self._setupCallback)
def _setupCallback(self, result, error=False):
"""
Callback for setup.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while setting up {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
return
self._ethsw_id = result["id"]
if not self._ethsw_id:
self.error_signal.emit(self.id(), "returned ID from server is null")
return
self._settings["name"] = result["name"]
log.info("Ethernet switch {} has been created".format(self.name()))
self.setInitialized(True)
self.created_signal.emit(self.id())
self._module.addNode(self)
def delete(self):
"""
Deletes this Ethernet switch.
"""
log.debug("Ethernet switch {} is being deleted".format(self.name()))
# first delete all the links attached to this node
self.delete_links_signal.emit()
if self._ethsw_id:
self._server.send_message("dynamips.ethsw.delete", {"id": self._ethsw_id}, self._deleteCallback)
else:
self.deleted_signal.emit()
self._module.removeNode(self)
def _deleteCallback(self, result, error=False):
"""
Callback for delete.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
log.info("Ethernet switch {} has been deleted".format(self.name()))
self.deleted_signal.emit()
self._module.removeNode(self)
params = {"name": name,
"device_type": "ethernet_switch"}
if device_id:
params["device_id"] = device_id
self.httpPost("/dynamips/devices", self._setupCallback, body=params)
def update(self, new_settings):
"""
@@ -146,31 +98,45 @@ class EthernetSwitch(Node):
:param new_settings: settings dictionary
"""
ports_to_update = {}
ports = new_settings["ports"]
updated = False
for port_number in ports.keys():
if port_number in self._settings["ports"]:
if self._settings["ports"][port_number] != ports[port_number]:
for port in self._ports:
if port.portNumber() == port_number and not port.isFree():
ports_to_update[port_number] = ports[port_number]
break
continue
port = EthernetPort(str(port_number))
port.setPortNumber(port_number)
port.setStatus(EthernetPort.started)
port.setPacketCaptureSupported(True)
self._ports.append(port)
updated = True
log.debug("port {} has been added".format(port_number))
params = {}
if "ports" in new_settings:
ports_to_update = {}
ports = new_settings["ports"]
params = {"id": self._ethsw_id}
if ports_to_update:
params["ports"] = {}
for port_number, info in ports_to_update.items():
params["ports"][port_number] = info
updated = True
for port_number in ports.keys():
if port_number in self._settings["ports"]:
if self._settings["ports"][port_number] != ports[port_number]:
for port in self._ports:
if port.portNumber() == port_number and not port.isFree():
ports_to_update[port_number] = ports[port_number]
break
continue
port = EthernetPort(str(port_number))
port.setPortNumber(port_number)
port.setStatus(EthernetPort.started)
port.setPacketCaptureSupported(True)
self._ports.append(port)
updated = True
log.debug("port {} has been added".format(port_number))
if ports_to_update:
params["ports"] = []
for port_number, info in ports_to_update.items():
info["port"] = port_number
params["ports"].append(info)
updated = True
# delete ports that are not configured
for port_number in self._settings["ports"].keys():
if port_number not in new_settings["ports"]:
for port in self._ports.copy():
if port.portNumber() == port_number:
self._ports.remove(port)
log.debug("port {} has been removed".format(port.name()))
break
self._settings["ports"] = new_settings["ports"].copy()
if "name" in new_settings and new_settings["name"] != self.name():
if self.hasAllocatedName(new_settings["name"]):
@@ -179,21 +145,14 @@ class EthernetSwitch(Node):
params["name"] = new_settings["name"]
updated = True
# delete ports that are not configured
for port_number in self._settings["ports"].keys():
if port_number not in new_settings["ports"]:
for port in self._ports.copy():
if port.portNumber() == port_number:
self._ports.remove(port)
log.debug("port {} has been removed".format(port.name()))
break
self._settings["ports"] = new_settings["ports"].copy()
if updated:
log.debug("{} is being updated: {}".format(self.name(), params))
self._server.send_message("dynamips.ethsw.update", params, self._updateCallback)
self.httpPut("/dynamips/devices/{device_id}".format(device_id=self._device_id), self._updateCallback, body=params)
else:
log.info("{} has been updated".format(self.name()))
self.updated_signal.emit()
def _updateCallback(self, result, error=False):
def _updateCallback(self, result, error=False, **kwargs):
"""
Callback for update.
@@ -203,7 +162,7 @@ class EthernetSwitch(Node):
if error:
log.error("error while updating {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
self.server_error_signal.emit(self.id(), result["message"])
else:
if "name" in result:
self._settings["name"] = result["name"]
@@ -211,33 +170,6 @@ class EthernetSwitch(Node):
log.info("{} has been updated".format(self.name()))
self.updated_signal.emit()
def allocateUDPPort(self, port_id):
"""
Requests an UDP port allocation.
:param port_id: port identifier
"""
log.debug("{} is requesting an UDP port allocation".format(self.name()))
self._server.send_message("dynamips.ethsw.allocate_udp_port", {"id": self._ethsw_id, "port_id": port_id}, self._allocateUDPPortCallback)
def _allocateUDPPortCallback(self, result, error=False):
"""
Callback for allocateUDPPort.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while allocating an UDP port for {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
port_id = result["port_id"]
lport = result["lport"]
log.debug("{} has allocated UDP port {}".format(self.name(), lport))
self.allocate_udp_nio_signal.emit(self.id(), port_id, lport)
def addNIO(self, port, nio):
"""
Adds a new NIO on the specified port for this switch.
@@ -246,133 +178,20 @@ class EthernetSwitch(Node):
:param nio: NIO instance
"""
port_info = self._settings["ports"][port.portNumber()]
params = {"id": self._ethsw_id,
"port": port.portNumber(),
"port_id": port.id(),
"vlan": port_info["vlan"],
"port_type": port_info["type"]}
params = {}
params["nio"] = self.getNIOInfo(nio)
port_info = self._settings["ports"][port.portNumber()]
port_settings = {"vlan": port_info["vlan"],
"type": port_info["type"]}
params["port_settings"] = port_settings
log.debug("{} is adding an {}: {}".format(self.name(), nio, params))
self._server.send_message("dynamips.ethsw.add_nio", params, self._addNIOCallback)
def _addNIOCallback(self, result, error=False):
"""
Callback for addNIO.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while adding an UDP NIO for {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
self.nio_cancel_signal.emit(self.id())
else:
log.debug("{} has added a new NIO: {}".format(self.name(), result))
self.nio_signal.emit(self.id(), result["port_id"])
def deleteNIO(self, port):
"""
Deletes an NIO from the specified port on this switch.
:param port: Port instance
"""
params = {"id": self._ethsw_id,
"port": port.portNumber()}
log.debug("{} is deleting an NIO: {}".format(self.name(), params))
self._server.send_message("dynamips.ethsw.delete_nio", params, self._deleteNIOCallback)
def _deleteNIOCallback(self, result, error=False):
"""
Callback for deleteNIO.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting NIO {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
return
log.debug("{} has deleted a NIO: {}".format(self.name(), result))
def startPacketCapture(self, port, capture_file_name, data_link_type):
"""
Starts a packet capture.
:param port: Port instance
:param capture_file_name: PCAP capture file path
:param data_link_type: PCAP data link type
"""
params = {"id": self._ethsw_id,
"port_id": port.id(),
"port": port.portNumber(),
"capture_file_name": capture_file_name,
"data_link_type": data_link_type}
log.debug("{} is starting a packet capture on {}: {}".format(self.name(), port.name(), params))
self._server.send_message("dynamips.ethsw.start_capture", params, self._startPacketCaptureCallback)
def _startPacketCaptureCallback(self, result, error=False):
"""
Callback for starting a packet capture.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while starting capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
for port in self._ports:
if port.id() == result["port_id"]:
log.info("{} has successfully started capturing packets on {}".format(self.name(), port.name()))
try:
port.startPacketCapture(result["capture_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
break
def stopPacketCapture(self, port):
"""
Stops a packet capture.
:param port: Port instance
"""
params = {"id": self._ethsw_id,
"port_id": port.id(),
"port": port.portNumber()}
log.debug("{} is stopping a packet capture on {}: {}".format(self.name(), port.name(), params))
self._server.send_message("dynamips.ethsw.stop_capture", params, self._stopPacketCaptureCallback)
def _stopPacketCaptureCallback(self, result, error=False):
"""
Callback for stopping a packet capture.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while stopping capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
for port in self._ports:
if port.id() == result["port_id"]:
log.info("{} has successfully stopped capturing packets on {}".format(self.name(), port.name()))
port.stopPacketCapture()
self.updated_signal.emit()
break
self.httpPost("/{prefix}/devices/{device_id}/ports/{port}/nio".format(
port=port.portNumber(),
prefix=self.URL_PREFIX,
device_id=self._device_id),
self._addNIOCallback,
context={"port_id": port.id()},
body=params)
def info(self):
"""
@@ -382,12 +201,13 @@ class EthernetSwitch(Node):
"""
info = """Ethernet switch {name} is always-on
Node ID is {id}, server's Ethernet switch ID is {ethsw_id}
Local node ID is {id}
Server's Device ID is {device_id}
Hardware is Dynamips emulated simple Ethernet switch
Switch's server runs on {host}:{port}
""".format(name=self.name(),
id=self.id(),
ethsw_id=self._ethsw_id,
device_id=self._device_id,
host=self._server.host,
port=self._server.port)
@@ -421,11 +241,11 @@ class EthernetSwitch(Node):
"""
switch = {"id": self.id(),
"device_id": self._device_id,
"type": self.__class__.__name__,
"description": str(self),
"properties": {"name": self.name()},
"server_id": self._server.id(),
}
"server_id": self._server.id()}
# add the ports
if self._ports:
@@ -450,13 +270,17 @@ class EthernetSwitch(Node):
settings = node_info["properties"]
name = settings.pop("name")
# pre-1.3 projects have no device id, set to 1 to have
# a proper project conversion on the server side
device_id = node_info.get("device_id", 1)
ports = []
if "ports" in node_info:
ports = node_info["ports"]
log.info("Ethernet switch {} is loading".format(name))
self.setName(name)
self.setup(name, ports)
self.setup(name, device_id, ports)
def name(self):
"""

View File

@@ -17,63 +17,36 @@
"""
EtherSwitch router implementation (based on Dynamips c3745).
This is legacy code, kept only to support topologies made with GNS3 < 1.2.2
"""
import sys
import os
import pkg_resources
from .router import Router
from gns3.node import Node
class EtherSwitchRouter(Router):
"""
EtherSwitch router.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server):
Router.__init__(self, module, server, platform="c3725")
def __init__(self, module, server, project):
Router.__init__(self, module, server, project, platform="c3725")
self._platform_settings = {"ram": 128,
"nvram": 304,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
self._etherswitch_settings = {"ram": 128,
"nvram": 304,
"disk0": 16,
"disk1": 0,
"iomem": 5,
"clock_divisor": 8,
"slot0": "GT96100-FE"}
# merge platform settings with the generic ones
self._settings.update(self._platform_settings)
# save the default settings
self._defaults = self._settings.copy()
def setup(self, image, ram, name=None, router_id=None, initial_settings={}):
"""
Setups this router.
:param image: IOS image path
:param ram: amount of RAM
:param name: optional name for this router
:param initial_settings: other additional and not mandatory settings
"""
# let's create a unique name if none has been chosen
if not name:
name = self.allocateName("ESW")
resource_name = "configs/ios_etherswitch_startup-config.txt"
if hasattr(sys, "frozen") and os.path.isfile(resource_name):
startup_config = os.path.normpath(resource_name)
elif pkg_resources.resource_exists("gns3", resource_name):
ios_etherswitch_config_path = pkg_resources.resource_filename("gns3", resource_name)
startup_config = os.path.normpath(ios_etherswitch_config_path)
initial_settings.update({"slot1": "NM-16ESW", # add the EtherSwitch module
"startup_config": startup_config}) # add the EtherSwitch startup-config
Router.setup(self, image, ram, name, router_id, initial_settings)
self._settings.update(self._etherswitch_settings)
@staticmethod
def defaultSymbol():

View File

@@ -22,35 +22,36 @@ Asynchronously sends JSON messages to the GNS3 server and receives responses wit
from gns3.node import Node
from gns3.ports.frame_relay_port import FrameRelayPort
from .device import Device
import logging
log = logging.getLogger(__name__)
class FrameRelaySwitch(Node):
class FrameRelaySwitch(Device):
"""
Dynamips Frame-Relay switch.
:param module: parent module for this node
:param server: GNS3 server instance
:param project: Project instance
"""
def __init__(self, module, server):
Node.__init__(self, server)
def __init__(self, module, server, project):
log.info("Frame-Relay switch is being created")
Device.__init__(self, module, server, project)
self.setStatus(Node.started) # this is an always-on node
self._frsw_id = None
self._ports = []
self._module = module
self._settings = {"name": "",
"mappings": {}}
def setup(self, name=None, initial_ports=[], initial_mappings={}):
def setup(self, name=None, device_id=None, initial_ports=[], initial_mappings={}):
"""
Setups this Frame Relay switch.
:param name: name for this switch.
:param device_id: device identifier on the server
:param initial_ports: ports to be automatically added when creating this Frame relay switch
:param initial_mappings: mappings to be automatically added when creating this Frame relay switch
"""
@@ -63,6 +64,7 @@ class FrameRelaySwitch(Node):
self.error_signal.emit(self.id(), "could not allocate a name for this Frame Relay switch")
return
self._settings["name"] = name
if initial_mappings:
# add initial mappings
self._settings["mappings"] = initial_mappings.copy()
@@ -77,61 +79,11 @@ class FrameRelaySwitch(Node):
port.setPacketCaptureSupported(True)
self._ports.append(port)
params = {"name": name}
self._server.send_message("dynamips.frsw.create", params, self._setupCallback)
def _setupCallback(self, result, error=False):
"""
Callback for setup.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while setting up {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
return
self._frsw_id = result["id"]
if not self._frsw_id:
self.error_signal.emit(self.id(), "returned ID from server is null")
return
self._settings["name"] = result["name"]
log.info("Frame Relay switch {} has been created".format(self.name()))
self.setInitialized(True)
self.created_signal.emit(self.id())
self._module.addNode(self)
def delete(self):
"""
Deletes this Frame Relay switch.
"""
log.debug("Frame Relay switch {} is being deleted".format(self.name()))
# first delete all the links attached to this node
self.delete_links_signal.emit()
if self._frsw_id:
self._server.send_message("dynamips.frsw.delete", {"id": self._frsw_id}, self._deleteCallback)
else:
self.deleted_signal.emit()
self._module.removeNode(self)
def _deleteCallback(self, result, error=False):
"""
Callback for the delete method.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
log.info("{} has been deleted".format(self.name()))
self.deleted_signal.emit()
self._module.removeNode(self)
params = {"name": name,
"device_type": "frame_relay_switch"}
if device_id:
params["device_id"] = device_id
self.httpPost("/dynamips/devices", self._setupCallback, body=params)
def update(self, new_settings):
"""
@@ -140,54 +92,55 @@ class FrameRelaySwitch(Node):
:param new_settings: settings dictionary
"""
ports_to_create = []
mapping = new_settings["mappings"]
updated = False
for source, destination in mapping.items():
source_port = source.split(":")[0]
destination_port = destination.split(":")[0]
if source_port not in ports_to_create:
ports_to_create.append(source_port)
if destination_port not in ports_to_create:
ports_to_create.append(destination_port)
if "mappings" in new_settings:
ports_to_create = []
mapping = new_settings["mappings"]
for port in self._ports.copy():
if port.isFree():
self._ports.remove(port)
for source, destination in mapping.items():
source_port = source.split(":")[0]
destination_port = destination.split(":")[0]
if source_port not in ports_to_create:
ports_to_create.append(source_port)
if destination_port not in ports_to_create:
ports_to_create.append(destination_port)
for port in self._ports.copy():
if port.isFree():
self._ports.remove(port)
updated = True
log.debug("port {} has been removed".format(port.name()))
else:
ports_to_create.remove(port.name())
for port_name in ports_to_create:
port = FrameRelayPort(port_name)
port.setPortNumber(int(port_name))
port.setStatus(FrameRelayPort.started)
port.setPacketCaptureSupported(True)
self._ports.append(port)
updated = True
log.debug("port {} has been removed".format(port.name()))
else:
ports_to_create.remove(port.name())
log.debug("port {} has been added".format(port_name))
for port_name in ports_to_create:
port = FrameRelayPort(port_name)
port.setPortNumber(int(port_name))
port.setStatus(FrameRelayPort.started)
port.setPacketCaptureSupported(True)
self._ports.append(port)
updated = True
log.debug("port {} has been added".format(port_name))
self._settings["mappings"] = new_settings["mappings"].copy()
params = {}
if "name" in new_settings and new_settings["name"] != self.name():
if self.hasAllocatedName(new_settings["name"]):
self.error_signal.emit(self.id(), 'Name "{}" is already used by another node'.format(new_settings["name"]))
return
params = {"id": self._frsw_id,
"name": new_settings["name"]}
params["name"] = new_settings["name"]
updated = True
self._settings["mappings"] = new_settings["mappings"].copy()
if updated:
if params:
log.debug("{} is being updated: {}".format(self.name(), params))
self._server.send_message("dynamips.frsw.update", params, self._updateCallback)
self.httpPut("/dynamips/devices/{device_id}".format(device_id=self._device_id), self._updateCallback, body=params)
else:
log.info("{} has been updated".format(self.name()))
self.updated_signal.emit()
def _updateCallback(self, result, error=False):
def _updateCallback(self, result, error=False, **kwargs):
"""
Callback for update.
@@ -197,7 +150,7 @@ class FrameRelaySwitch(Node):
if error:
log.error("error while updating {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
self.server_error_signal.emit(self.id(), result["message"])
else:
if "name" in result:
self._settings["name"] = result["name"]
@@ -205,31 +158,6 @@ class FrameRelaySwitch(Node):
log.info("{} has been updated".format(self.name()))
self.updated_signal.emit()
def allocateUDPPort(self, port_id):
"""
Requests an UDP port allocation.
"""
log.debug("{} is requesting an UDP port allocation".format(self.name()))
self._server.send_message("dynamips.frsw.allocate_udp_port", {"id": self._frsw_id, "port_id": port_id}, self._allocateUDPPortCallback)
def _allocateUDPPortCallback(self, result, error=False):
"""
Callback for allocateUDPPort.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while allocating an UDP port for {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
port_id = result["port_id"]
lport = result["lport"]
log.debug("{} has allocated UDP port {}".format(self.name(), lport))
self.allocate_udp_nio_signal.emit(self.id(), port_id, lport)
def addNIO(self, port, nio):
"""
Adds a new NIO on the specified port for this Frame Relay switch.
@@ -238,11 +166,7 @@ class FrameRelaySwitch(Node):
:param nio: NIO instance
"""
params = {"id": self._frsw_id,
"port": port.portNumber(),
"port_id": port.id()}
params["nio"] = self.getNIOInfo(nio)
params = {"nio": self.getNIOInfo(nio)}
params["mappings"] = {}
for source, destination in self._settings["mappings"].items():
source_port = source.split(":")[0]
@@ -254,124 +178,13 @@ class FrameRelaySwitch(Node):
log.debug("{} is adding an UDP NIO: {}".format(self.name(), params))
log.debug("{} is adding an {}: {}".format(self.name(), nio, params))
self._server.send_message("dynamips.frsw.add_nio", params, self._addNIOCallback)
def _addNIOCallback(self, result, error=False):
"""
Callback for addNIO.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while adding an UDP NIO for {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
self.nio_cancel_signal.emit(self.id())
else:
log.debug("{} has added a new NIO: {}".format(self.name(), result))
self.nio_signal.emit(self.id(), result["port_id"])
def deleteNIO(self, port):
"""
Deletes an NIO from the specified port on this switch.
:param port: Port instance
"""
params = {"id": self._frsw_id,
"port": port.portNumber()}
log.debug("{} is deleting an NIO: {}".format(self.name(), params))
self._server.send_message("dynamips.frsw.delete_nio", params, self._deleteNIOCallback)
def _deleteNIOCallback(self, result, error=False):
"""
Callback for deleteNIO.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while deleting NIO {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
return
log.debug("{} has deleted a NIO: {}".format(self.name(), result))
def startPacketCapture(self, port, capture_file_name, data_link_type):
"""
Starts a packet capture.
:param port: Port instance
:param capture_file_name: PCAP capture file path
:param data_link_type: PCAP data link type
"""
params = {"id": self._frsw_id,
"port_id": port.id(),
"port": port.portNumber(),
"capture_file_name": capture_file_name,
"data_link_type": data_link_type}
log.debug("{} is starting a packet capture on {}: {}".format(self.name(), port.name(), params))
self._server.send_message("dynamips.frsw.start_capture", params, self._startPacketCaptureCallback)
def _startPacketCaptureCallback(self, result, error=False):
"""
Callback for starting a packet capture.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while starting capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
for port in self._ports:
if port.id() == result["port_id"]:
log.info("{} has successfully started capturing packets on {}".format(self.name(), port.name()))
try:
port.startPacketCapture(result["capture_file_path"])
except OSError as e:
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
self.updated_signal.emit()
break
def stopPacketCapture(self, port):
"""
Stops a packet capture.
:param port: Port instance
"""
params = {"id": self._frsw_id,
"port_id": port.id(),
"port": port.portNumber()}
log.debug("{} is stopping a packet capture on {}: {}".format(self.name(), port.name(), params))
self._server.send_message("dynamips.frsw.stop_capture", params, self._stopPacketCaptureCallback)
def _stopPacketCaptureCallback(self, result, error=False):
"""
Callback for stopping a packet capture.
:param result: server response
:param error: indicates an error (boolean)
"""
if error:
log.error("error while stopping capture {}: {}".format(self.name(), result["message"]))
self.server_error_signal.emit(self.id(), result["code"], result["message"])
else:
for port in self._ports:
if port.id() == result["port_id"]:
log.info("{} has successfully stopped capturing packets on {}".format(self.name(), port.name()))
port.stopPacketCapture()
self.updated_signal.emit()
break
self.httpPost("/{prefix}/devices/{device_id}/ports/{port}/nio".format(
port=port.portNumber(),
prefix=self.URL_PREFIX,
device_id=self._device_id),
self._addNIOCallback,
context={"port_id": port.id()},
body=params)
def info(self):
"""
@@ -381,12 +194,13 @@ class FrameRelaySwitch(Node):
"""
info = """Frame relay switch {name} is always-on
Node ID is {id}, server's frame relay switch ID is {frsw_id}
Local node ID is {id}
Server's Device ID is {device_id}
Hardware is Dynamips emulated simple Frame relay switch
Switch's server runs on {host}:{port}
""".format(name=self.name(),
id=self.id(),
frsw_id=self._frsw_id,
device_id=self._device_id,
host=self._server.host,
port=self._server.port)
@@ -396,7 +210,7 @@ class FrameRelaySwitch(Node):
port_info += " Port {} is empty\n".format(port.name())
else:
port_info += " Port {name} {description}\n".format(name=port.name(),
description=port.description())
description=port.description())
for source, destination in self._settings["mappings"].items():
source_port, source_dlci = source.split(":")
@@ -427,6 +241,7 @@ class FrameRelaySwitch(Node):
"""
frsw = {"id": self.id(),
"device_id": self._device_id,
"type": self.__class__.__name__,
"description": str(self),
"properties": {"name": self.name()},
@@ -455,6 +270,10 @@ class FrameRelaySwitch(Node):
settings = node_info["properties"]
name = settings.pop("name")
# pre-1.3 projects have no device id, set to 1 to have
# a proper project conversion on the server side
device_id = node_info.get("device_id", 1)
mappings = {}
if "mappings" in settings:
mappings = settings["mappings"]
@@ -465,7 +284,7 @@ class FrameRelaySwitch(Node):
log.info("Frame-Relay switch {} is loading".format(name))
self.setName(name)
self.setup(name, ports, mappings)
self.setup(name, device_id, ports, mappings)
def name(self):
"""

File diff suppressed because it is too large Load Diff

View File

@@ -25,6 +25,7 @@ from ..ui.atm_bridge_configuration_page_ui import Ui_atmBridgeConfigPageWidget
class ATMBridgeConfigurationPage(QtGui.QWidget, Ui_atmBridgeConfigPageWidget):
"""
QWidget configuration page for ATM bridges.
"""
@@ -74,7 +75,7 @@ class ATMBridgeConfigurationPage(QtGui.QWidget, Ui_atmBridgeConfigPageWidget):
"""
item = self.uiMappingTreeWidget.currentItem()
if item != None:
if item is not None:
self.uiDeletePushButton.setEnabled(True)
else:
self.uiDeletePushButton.setEnabled(False)

View File

@@ -25,6 +25,7 @@ from ..ui.atm_switch_configuration_page_ui import Ui_atmSwitchConfigPageWidget
class ATMSwitchConfigurationPage(QtGui.QWidget, Ui_atmSwitchConfigPageWidget):
"""
QWidget configuration page for ATM switches.
"""
@@ -85,7 +86,7 @@ class ATMSwitchConfigurationPage(QtGui.QWidget, Ui_atmSwitchConfigPageWidget):
"""
item = self.uiMappingTreeWidget.currentItem()
if item != None:
if item is not None:
self.uiDeletePushButton.setEnabled(True)
else:
self.uiDeletePushButton.setEnabled(False)

View File

@@ -29,6 +29,7 @@ from ..settings import DYNAMIPS_SETTINGS
class DynamipsPreferencesPage(QtGui.QWidget, Ui_DynamipsPreferencesPageWidget):
"""
QWidget preference page for Dynamips.
"""
@@ -40,24 +41,19 @@ class DynamipsPreferencesPage(QtGui.QWidget, Ui_DynamipsPreferencesPageWidget):
# connect signals
self.uiDynamipsPathToolButton.clicked.connect(self._dynamipsPathBrowserSlot)
self.uiAllocateHypervisorPerDeviceCheckBox.stateChanged.connect(self._allocateHypervisorPerDeviceSlot)
self.uiGhostIOSSupportCheckBox.stateChanged.connect(self._ghostIOSSupportSlot)
self.uiRestoreDefaultsPushButton.clicked.connect(self._restoreDefaultsSlot)
self.uiUseLocalServercheckBox.stateChanged.connect(self._useLocalServerSlot)
self.uiTestSettingsPushButton.clicked.connect(self._testSettingsSlot)
#FIXME: temporally hide test button
self.uiTestSettingsPushButton.hide()
def _dynamipsPathBrowserSlot(self):
"""
Slot to open a file browser and select Dynamips executable.
"""
filter = ""
file_filter = ""
if sys.platform.startswith("win"):
filter = "Executable (*.exe);;All files (*.*)"
path = QtGui.QFileDialog.getOpenFileName(self, "Select Dynamips", ".", filter)
file_filter = "Executable (*.exe);;All files (*.*)"
path = QtGui.QFileDialog.getOpenFileName(self, "Select Dynamips", ".", file_filter)
if not path:
return
@@ -67,25 +63,6 @@ class DynamipsPreferencesPage(QtGui.QWidget, Ui_DynamipsPreferencesPageWidget):
self.uiDynamipsPathLineEdit.setText(path)
def _testSettingsSlot(self):
QtGui.QMessageBox.critical(self, "Test settings", "Sorry, not yet implemented!")
def _allocateHypervisorPerDeviceSlot(self, state):
"""
Slot to enable or not the memory usage limit per hypervisor and
the per IOS allocation, based if the user want one hypervisor per IOS router.
:param state: state of the allocate hypervisor per device checkBox
"""
if state:
self.uiMemoryUsageLimitPerHypervisorSpinBox.setEnabled(False)
self.uiAllocateHypervisorPerIOSCheckBox.setEnabled(False)
else:
self.uiMemoryUsageLimitPerHypervisorSpinBox.setEnabled(True)
self.uiAllocateHypervisorPerIOSCheckBox.setEnabled(True)
def _ghostIOSSupportSlot(self, state):
"""
Slot to have the mmap checkBox checked if ghost IOS is checked.
@@ -106,13 +83,23 @@ class DynamipsPreferencesPage(QtGui.QWidget, Ui_DynamipsPreferencesPageWidget):
def _useLocalServerSlot(self, state):
"""
Slot to enable or not the QTreeWidget for remote servers.
Slot to enable or not local server settings.
"""
if state:
self.uiRemoteServersTreeWidget.setEnabled(False)
self.uiDynamipsPathLineEdit.setEnabled(True)
self.uiDynamipsPathToolButton.setEnabled(True)
self.uiAllocateAuxConsolePortsCheckBox.setEnabled(True)
self.uiGhostIOSSupportCheckBox.setEnabled(True)
self.uiMmapSupportCheckBox.setEnabled(True)
self.uiSparseMemorySupportCheckBox.setEnabled(True)
else:
self.uiRemoteServersTreeWidget.setEnabled(True)
self.uiDynamipsPathLineEdit.setEnabled(False)
self.uiDynamipsPathToolButton.setEnabled(False)
self.uiAllocateAuxConsolePortsCheckBox.setEnabled(False)
self.uiGhostIOSSupportCheckBox.setEnabled(False)
self.uiMmapSupportCheckBox.setEnabled(False)
self.uiSparseMemorySupportCheckBox.setEnabled(False)
def _populateWidgets(self, settings):
"""
@@ -121,40 +108,13 @@ class DynamipsPreferencesPage(QtGui.QWidget, Ui_DynamipsPreferencesPageWidget):
:param settings: Dynamips settings
"""
self.uiDynamipsPathLineEdit.setText(settings["path"])
self.uiHypervisorStartPortSpinBox.setValue(settings["hypervisor_start_port_range"])
self.uiHypervisorEndPortSpinBox.setValue(settings["hypervisor_end_port_range"])
self.uiConsoleStartPortSpinBox.setValue(settings["console_start_port_range"])
self.uiConsoleEndPortSpinBox.setValue(settings["console_end_port_range"])
self.uiAuxStartPortSpinBox.setValue(settings["aux_start_port_range"])
self.uiAuxEndPortSpinBox.setValue(settings["aux_end_port_range"])
self.uiUDPStartPortSpinBox.setValue(settings["udp_start_port_range"])
self.uiUDPEndPortSpinBox.setValue(settings["udp_end_port_range"])
self.uiDynamipsPathLineEdit.setText(settings["dynamips_path"])
self.uiAllocateAuxConsolePortsCheckBox.setChecked(settings["allocate_aux_console_ports"])
self.uiUseLocalServercheckBox.setChecked(settings["use_local_server"])
self.uiAllocateHypervisorPerDeviceCheckBox.setChecked(settings["allocate_hypervisor_per_device"])
self.uiMemoryUsageLimitPerHypervisorSpinBox.setValue(settings["memory_usage_limit_per_hypervisor"])
self.uiAllocateHypervisorPerIOSCheckBox.setChecked(settings["allocate_hypervisor_per_ios_image"])
self.uiGhostIOSSupportCheckBox.setChecked(settings["ghost_ios_support"])
self.uiMmapSupportCheckBox.setChecked(settings["mmap_support"])
self.uiJITSharingSupportCheckBox.setChecked(settings["jit_sharing_support"])
self.uiSparseMemorySupportCheckBox.setChecked(settings["sparse_memory_support"])
def _updateRemoteServersSlot(self):
"""
Adds/Updates the available remote servers.
"""
servers = Servers.instance()
self.uiRemoteServersTreeWidget.clear()
for server in servers.remoteServers().values():
host = server.host
port = server.port
item = QtGui.QTreeWidgetItem(self.uiRemoteServersTreeWidget)
item.setText(0, host)
item.setText(1, str(port))
self.uiRemoteServersTreeWidget.resizeColumnToContents(0)
def loadPreferences(self):
"""
Loads the Dynamips preferences.
@@ -163,31 +123,16 @@ class DynamipsPreferencesPage(QtGui.QWidget, Ui_DynamipsPreferencesPageWidget):
dynamips_settings = Dynamips.instance().settings()
self._populateWidgets(dynamips_settings)
servers = Servers.instance()
servers.updated_signal.connect(self._updateRemoteServersSlot)
self._updateRemoteServersSlot()
def savePreferences(self):
"""
Saves the Dynamips preferences.
"""
new_settings = {}
new_settings["path"] = self.uiDynamipsPathLineEdit.text()
new_settings["hypervisor_start_port_range"] = self.uiHypervisorStartPortSpinBox.value()
new_settings["hypervisor_end_port_range"] = self.uiHypervisorEndPortSpinBox.value()
new_settings["console_start_port_range"] = self.uiConsoleStartPortSpinBox.value()
new_settings["console_end_port_range"] = self.uiConsoleEndPortSpinBox.value()
new_settings["aux_start_port_range"] = self.uiAuxStartPortSpinBox.value()
new_settings["aux_end_port_range"] = self.uiAuxEndPortSpinBox.value()
new_settings["udp_start_port_range"] = self.uiUDPStartPortSpinBox.value()
new_settings["udp_end_port_range"] = self.uiUDPEndPortSpinBox.value()
new_settings["dynamips_path"] = self.uiDynamipsPathLineEdit.text()
new_settings["allocate_aux_console_ports"] = self.uiAllocateAuxConsolePortsCheckBox.isChecked()
new_settings["use_local_server"] = self.uiUseLocalServercheckBox.isChecked()
new_settings["allocate_hypervisor_per_device"] = self.uiAllocateHypervisorPerDeviceCheckBox.isChecked()
new_settings["memory_usage_limit_per_hypervisor"] = self.uiMemoryUsageLimitPerHypervisorSpinBox.value()
new_settings["allocate_hypervisor_per_ios_image"] = self.uiAllocateHypervisorPerIOSCheckBox.isChecked()
new_settings["ghost_ios_support"] = self.uiGhostIOSSupportCheckBox.isChecked()
new_settings["mmap_support"] = self.uiMmapSupportCheckBox.isChecked()
new_settings["jit_sharing_support"] = self.uiJITSharingSupportCheckBox.isChecked()
new_settings["sparse_memory_support"] = self.uiSparseMemorySupportCheckBox.isChecked()
Dynamips.instance().setSettings(new_settings)

View File

@@ -25,6 +25,7 @@ from ..ui.ethernet_hub_configuration_page_ui import Ui_ethernetHubConfigPageWidg
class EthernetHubConfigurationPage(QtGui.QWidget, Ui_ethernetHubConfigPageWidget):
"""
QWidget configuration page for Ethernet hubs.
"""

View File

@@ -25,6 +25,7 @@ from ..ui.ethernet_switch_configuration_page_ui import Ui_ethernetSwitchConfigPa
class EthernetSwitchConfigurationPage(QtGui.QWidget, Ui_ethernetSwitchConfigPageWidget):
"""
QWidget configuration page for Ethernet switches.
"""

View File

@@ -24,6 +24,7 @@ from ..ui.frame_relay_switch_configuration_page_ui import Ui_frameRelaySwitchCon
class FrameRelaySwitchConfigurationPage(QtGui.QWidget, Ui_frameRelaySwitchConfigPageWidget):
"""
QWidget configuration page for Frame Relay switches.
"""
@@ -65,7 +66,7 @@ class FrameRelaySwitchConfigurationPage(QtGui.QWidget, Ui_frameRelaySwitchConfig
"""
item = self.uiMappingTreeWidget.currentItem()
if item != None:
if item is not None:
self.uiDeletePushButton.setEnabled(True)
else:
self.uiDeletePushButton.setEnabled(False)
@@ -108,7 +109,7 @@ class FrameRelaySwitchConfigurationPage(QtGui.QWidget, Ui_frameRelaySwitchConfig
item = self.uiMappingTreeWidget.currentItem()
if item:
#connected_ports = self.node.getConnectedInterfaceList()
# connected_ports = self.node.getConnectedInterfaceList()
source = item.text(0)
source_port = int(source.split(':')[0])
destination = item.text(1)

View File

@@ -21,16 +21,15 @@ Configuration page for Dynamips IOS routers.
import os
import re
import sys
import pkg_resources
from gns3.qt import QtGui
from gns3.qt import QtCore, QtGui
from gns3.dialogs.node_configurator_dialog import ConfigurationError
from ..ui.ios_router_configuration_page_ui import Ui_iosRouterConfigPageWidget
from ..settings import CHASSIS, ADAPTER_MATRIX, WIC_MATRIX
class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
"""
QWidget configuration page for IOS routers.
"""
@@ -56,6 +55,31 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
self.uiPrivateConfigToolButton.clicked.connect(self._privateConfigBrowserSlot)
self.uiIOSImageToolButton.clicked.connect(self._iosImageBrowserSlot)
self._idle_valid = False
idle_pc_rgx = QtCore.QRegExp("^(0x[0-9a-fA-F]{8})?$")
validator = QtGui.QRegExpValidator(idle_pc_rgx, self)
self.uiIdlepcLineEdit.setValidator(validator)
self.uiIdlepcLineEdit.textChanged.connect(self._idlePCValidateSlot)
self.uiIdlepcLineEdit.textChanged.emit(self.uiIdlepcLineEdit.text())
def _idlePCValidateSlot(self):
"""
Slot to validate the entered Idle-PC Value
"""
validator = self.uiIdlepcLineEdit.validator()
state = validator.validate(self.uiIdlepcLineEdit.text(), 0)[0]
if state == QtGui.QValidator.Acceptable:
color = '#A2C964' # green
self._idle_valid = True
elif state == QtGui.QValidator.Intermediate:
color = '#fff79a' # yellow
self._idle_valid = False
else:
color = '#f6989d' # red
self._idle_valid = False
self.uiIdlepcLineEdit.setStyleSheet('QLineEdit { background-color: %s }' % color)
def _iosImageBrowserSlot(self):
"""
Slot to open a file browser and select an IOU image.
@@ -98,10 +122,7 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
Slot to open a file browser and select a startup-config file.
"""
if hasattr(sys, "frozen"):
config_dir = "configs"
else:
config_dir = pkg_resources.resource_filename("gns3", "configs")
config_dir = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), "base_configs")
path = QtGui.QFileDialog.getOpenFileName(self, "Select a startup configuration", config_dir)
if not path:
return
@@ -118,10 +139,7 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
Slot to open a file browser and select a private-config file.
"""
if hasattr(sys, "frozen"):
config_dir = "configs"
else:
config_dir = pkg_resources.resource_filename("gns3", "configs")
config_dir = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), "base_configs")
path = QtGui.QFileDialog.getOpenFileName(self, "Select a private configuration", config_dir)
if not path:
return
@@ -151,16 +169,17 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
for slot_number, slot_adapters in ADAPTER_MATRIX[platform][chassis].items():
self._widget_slots[slot_number].setEnabled(True)
if type(slot_adapters) == str:
if isinstance(slot_adapters, str):
# only one default adapter for this slot.
self._widget_slots[slot_number].addItem(slot_adapters)
# elif platform == "c7200" and slot_number == 0 and settings["slot0"] != None:
# # special case
# self._widget_slots[slot_number].addItem(settings["slot0"])
else:
# list of adapters
module_list = list(slot_adapters)
self._widget_slots[slot_number].addItems([""] + module_list)
if platform == "c7200" and slot_number == 0:
# special case
self._widget_slots[slot_number].addItems(module_list)
else:
self._widget_slots[slot_number].addItems([""] + module_list)
# set the combox box to the correct slot adapter if configured.
if settings["slot" + str(slot_number)]:
@@ -205,17 +224,14 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
self.uiConsolePortSpinBox.hide()
if "aux" in settings:
self.uiAuxPortSpinBox.setValue(settings["aux"])
if settings["aux"] is None:
self.uiAuxPortSpinBox.setValue(0)
else:
self.uiAuxPortSpinBox.setValue(settings["aux"])
else:
self.uiAuxPortLabel.hide()
self.uiAuxPortSpinBox.hide()
# load the startup-config
self.uiStartupConfigLineEdit.setText(settings["startup_config"])
# load the private-config
self.uiPrivateConfigLineEdit.setText(settings["private_config"])
# load the MAC address setting
self.uiBaseMACLineEdit.setInputMask("HHHH.HHHH.HHHH;_")
@@ -235,12 +251,6 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
self.uiIOSImageLabel.hide()
self.uiIOSImageLineEdit.hide()
self.uiIOSImageToolButton.hide()
self.uiStartupConfigLabel.hide()
self.uiStartupConfigLineEdit.hide()
self.uiStartupConfigToolButton.hide()
self.uiPrivateConfigLabel.hide()
self.uiPrivateConfigLineEdit.hide()
self.uiPrivateConfigToolButton.hide()
self.uiConsolePortLabel.hide()
self.uiConsolePortSpinBox.hide()
self.uiAuxPortLabel.hide()
@@ -248,6 +258,19 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
self.uiBaseMacLabel.hide()
self.uiBaseMACLineEdit.hide()
if not node:
# load the startup-config
self.uiStartupConfigLineEdit.setText(settings["startup_config"])
# load the private-config
self.uiPrivateConfigLineEdit.setText(settings["private_config"])
else:
self.uiStartupConfigLabel.hide()
self.uiStartupConfigLineEdit.hide()
self.uiStartupConfigToolButton.hide()
self.uiPrivateConfigLabel.hide()
self.uiPrivateConfigLineEdit.hide()
self.uiPrivateConfigToolButton.hide()
# show the platform and chassis if applicable
platform = settings["platform"]
self.uiPlatformTextLabel.setText(platform)
@@ -294,7 +317,7 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
else:
self.uiPowerSupply2ComboBox.setCurrentIndex(1)
else:
self.uiTabWidget.removeTab(4) # environment tab
self.uiTabWidget.removeTab(4) # environment tab
# all platforms but c7200 have the iomem feature
# let"s hide these widgets.
@@ -323,9 +346,6 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
# load the system ID (processor board ID in IOS) setting
self.uiSystemIdLineEdit.setText(settings["system_id"])
# load the configuration register setting
self.uiConfregLineEdit.setText(settings["confreg"])
if "exec_area" in settings:
# load the exec area setting
self.uiExecAreaSpinBox.setValue(settings["exec_area"])
@@ -374,13 +394,13 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
node_ports = node.ports()
for node_port in node_ports:
# ports > 15 are WICs ones.
if node_port.slotNumber() == slot_number and node_port.portNumber() <= 15 and not node_port.isFree():
if node_port.adapterNumber() == slot_number and node_port.portNumber() <= 15 and not node_port.isFree():
adapter = settings["slot" + str(slot_number)]
index = self._widget_slots[slot_number].findText(adapter)
if index != -1:
self._widget_slots[slot_number].setCurrentIndex(index)
QtGui.QMessageBox.critical(self, node.name(), "A link is connected to port {} on adapter {}, please remove it first".format(node_port.name(),
adapter))
adapter))
raise ConfigurationError()
def _checkForLinkConnectedToWIC(self, wic_number, settings, node):
@@ -395,7 +415,7 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
node_ports = node.ports()
for node_port in node_ports:
# ports > 15 are WICs ones.
if node_port.slotNumber() == wic_number and node_port.portNumber() > 15 and not node_port.isFree():
if node_port.adapterNumber() == wic_number and node_port.portNumber() > 15 and not node_port.isFree():
wic = settings["wic" + str(wic_number)]
index = self._widget_wics[wic_number].findText(wic)
if index != -1:
@@ -413,63 +433,69 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
:param group: indicates the settings apply to a group of routers
"""
#print("saving {}".format(group))
# these settings cannot be shared by nodes and updated
# in the node configurator.
if not group:
# Check if the Idle-PC value has been validated okay
if not self._idle_valid:
idle_pc = self.uiIdlepcLineEdit.text()
QtGui.QMessageBox.critical(self, "Idle-PC", "{} is not a valid Idle-PC value ".format(idle_pc))
raise ConfigurationError()
# set the device name
name = self.uiNameLineEdit.text()
if not name:
QtGui.QMessageBox.critical(self, "Name", "IOS router name cannot be empty!")
elif node and not re.search(r"""^[\-\w]+$""", name):
# IOS names must start with a letter, end with a letter or digit, and
# have as interior characters only letters, digits, and hyphens.
# They must be 63 characters or fewer.
elif node and not node.validateHostname(name):
QtGui.QMessageBox.critical(self, "Name", "Invalid name detected for IOS router: {}".format(name))
else:
settings["name"] = name
settings["console"] = self.uiConsolePortSpinBox.value()
settings["aux"] = self.uiAuxPortSpinBox.value()
startup_config = self.uiStartupConfigLineEdit.text()
if startup_config != settings["startup_config"]:
if os.access(startup_config, os.R_OK):
settings["startup_config"] = startup_config
else:
QtGui.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
private_config = self.uiPrivateConfigLineEdit.text()
if private_config != settings["private_config"]:
if os.access(private_config, os.R_OK):
settings["private_config"] = private_config
else:
QtGui.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
if "console" in settings:
settings["console"] = self.uiConsolePortSpinBox.value()
aux = self.uiAuxPortSpinBox.value()
if aux:
settings["aux"] = aux
# check and save the base MAC address
#mac = self.uiBaseMACLineEdit.text()
#if mac and not re.search(r"""^([0-9a-fA-F]{4}\.){2}[0-9a-fA-F]{4}$""", mac):
# mac = self.uiBaseMACLineEdit.text()
# if mac and not re.search(r"""^([0-9a-fA-F]{4}\.){2}[0-9a-fA-F]{4}$""", mac):
# QtGui.QMessageBox.critical(self, "MAC address", "Invalid MAC address (format required: hhhh.hhhh.hhhh)")
#elif mac != "":
# elif mac != "":
# settings["mac_addr"] = mac
# save the IOS image path
path = self.uiIOSImageLineEdit.text()
#settings["path"] = path
settings["image"] = path#os.path.basename(path)
settings["image"] = self.uiIOSImageLineEdit.text()
else:
del settings["name"]
del settings["console"]
del settings["aux"]
del settings["mac_addr"]
del settings["startup_config"]
del settings["private_config"]
if "startup_config" in settings:
del settings["startup_config"]
if "private_config" in settings:
del settings["private_config"]
del settings["image"]
if not node:
startup_config = self.uiStartupConfigLineEdit.text().strip()
if not startup_config:
settings["startup_config"] = ""
elif startup_config != settings["startup_config"]:
if os.access(startup_config, os.R_OK):
settings["startup_config"] = startup_config
else:
QtGui.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
private_config = self.uiPrivateConfigLineEdit.text().strip()
if not private_config:
settings["private_config"] = ""
elif private_config != settings["private_config"]:
if os.access(private_config, os.R_OK):
settings["private_config"] = private_config
else:
QtGui.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
# get the platform and chassis if applicable
platform = settings["platform"]
if "chassis" in settings:
@@ -511,10 +537,6 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
# save the system ID (processor board ID in IOS) setting
settings["system_id"] = self.uiSystemIdLineEdit.text()
# save the configuration register setting
# TODO: check the format? 0xnnnn
settings["confreg"] = self.uiConfregLineEdit.text()
# save the exec area setting
settings["exec_area"] = self.uiExecAreaSpinBox.value()

View File

@@ -22,17 +22,17 @@ Configuration page for IOS router preferences.
import os
import copy
import sys
import pkg_resources
import shutil
import math
import zipfile
import logging
from gns3.qt import QtCore, QtGui
from gns3.main_window import MainWindow
from gns3.utils.progress_dialog import ProgressDialog
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
from gns3.dialogs.configuration_dialog import ConfigurationDialog
from gns3.cloud.utils import UploadFilesThread
from gns3.utils.progress_dialog import ProgressDialog
from gns3.utils.file_copy_worker import FileCopyWorker
from .. import Dynamips
from ..settings import IOS_ROUTER_SETTINGS
@@ -43,7 +43,11 @@ from ..pages.ios_router_configuration_page import IOSRouterConfigurationPage
from ..dialogs.ios_router_wizard import IOSRouterWizard
log = logging.getLogger(__name__)
class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget):
"""
QWidget preference page for IOS routers.
"""
@@ -94,25 +98,6 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
ios_settings = wizard.getSettings()
key = "{server}:{name}".format(server=ios_settings["server"], name=ios_settings["name"])
# set the default base startup-config
resource_name = "configs/ios_base_startup-config.txt"
if hasattr(sys, "frozen") and os.path.isfile(resource_name):
startup_config = os.path.normpath(resource_name)
elif pkg_resources.resource_exists("gns3", resource_name):
ios_base_config_path = pkg_resources.resource_filename("gns3", resource_name)
startup_config = os.path.normpath(ios_base_config_path)
# set the default base private-config
resource_name = "configs/ios_base_private-config.txt"
if hasattr(sys, "frozen") and os.path.isfile(resource_name):
private_config = os.path.normpath(resource_name)
elif pkg_resources.resource_exists("gns3", resource_name):
ios_base_config_path = pkg_resources.resource_filename("gns3", resource_name)
private_config = os.path.normpath(ios_base_config_path)
ios_settings["startup_config"] = startup_config
ios_settings["private_config"] = private_config
self._ios_routers[key] = IOS_ROUTER_SETTINGS.copy()
self._ios_routers[key].update(ios_settings)
@@ -128,10 +113,15 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
self._upload_image_progress_dialog.setWindowTitle("IOS image upload")
self._upload_image_progress_dialog.show()
try:
src = self._ios_routers[key]['path']
# Eg: images/IOS/c3745.img
dst = 'images/IOS/{}'.format(self._ios_routers[key]['image'])
upload_thread = UploadFilesThread(self, MainWindow.instance().cloudSettings(), [(src, dst)])
upload_thread = UploadFilesThread(
self,
cloud_settings=MainWindow.instance().cloudSettings(),
files_to_upload=[(
self._ios_routers[key]["image"],
'images/' + os.path.relpath(self._ios_routers[key]["image"],
self._main_window.settings().imagesDirPath())
)]
)
upload_thread.completed.connect(self._imageUploadComplete)
upload_thread.start()
except Exception as e:
@@ -181,6 +171,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
dialog.show()
if dialog.exec_():
if ios_router["name"] != item.text(0):
# rename the IOS router
new_key = "{server}:{name}".format(server=ios_router["server"], name=ios_router["name"])
if new_key in self._ios_routers:
QtGui.QMessageBox.critical(self, "IOS router", "IOS router name {} already exists for server {}".format(ios_router["name"],
@@ -191,6 +182,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
del self._ios_routers[key]
item.setText(0, ios_router["name"])
item.setData(0, QtCore.Qt.UserRole, new_key)
self._refreshInfo(ios_router)
def _iosRouterDeleteSlot(self):
@@ -201,6 +193,8 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
item = self.uiIOSRoutersTreeWidget.currentItem()
if item:
key = item.data(0, QtCore.Qt.UserRole)
ios_router = self._ios_routers[key]
del self._ios_routers[key]
self.uiIOSRoutersTreeWidget.takeTopLevelItem(self.uiIOSRoutersTreeWidget.indexOfTopLevelItem(item))
if self._ios_routers == {}:
@@ -217,7 +211,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
:return: path to the IOS image or None
"""
destination_directory = os.path.join(MainWindow.instance().settings()["images_path"], "IOS")
destination_directory = os.path.join(MainWindow.instance().imagesDirPath(), "IOS")
path, _ = QtGui.QFileDialog.getOpenFileNameAndFilter(parent,
"Select an IOS image",
destination_directory,
@@ -255,10 +249,15 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
except FileExistsError:
pass
except OSError as e:
QtGui.QMessageBox.critical(parent, "IOS images directory", "Could not create the IOS images directory {}: {}".format(destination_directory, str(e)))
QtGui.QMessageBox.critical(parent, "IOS images directory", "Could not create the IOS images directory {}: {}".format(destination_directory, e))
return
if isIOSCompressed(path):
compressed = False
try:
compressed = isIOSCompressed(path)
except (OSError, ValueError):
pass # ignore errors if we cannot find out the IOS image is compressed.
if compressed:
reply = QtGui.QMessageBox.question(parent, "IOS image", "Would you like to decompress this IOS image?",
QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
@@ -273,21 +272,24 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
path = decompressed_image_path
thread.wait()
if os.path.dirname(path) != destination_directory:
if os.path.normpath(os.path.dirname(path)) != destination_directory:
# the IOS image is not in the default images directory
new_destination_path = os.path.join(destination_directory, os.path.basename(path))
try:
# try to create a symbolic link to it
symlink_path = new_destination_path
os.symlink(path, symlink_path)
path = symlink_path
except (OSError, NotImplementedError):
# if unsuccessful, then copy the IOS image itself
try:
shutil.copyfile(path, new_destination_path)
path = new_destination_path
except OSError:
pass
reply = QtGui.QMessageBox.question(parent,
"IOS image",
"Would you like to copy {} to the default images directory".format(os.path.basename(path)),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
destination_path = os.path.join(destination_directory, os.path.basename(path))
worker = FileCopyWorker(path, destination_path)
progress_dialog = ProgressDialog(worker, "IOS image", "Copying {}".format(os.path.basename(path)), "Cancel", busy=True, parent=parent)
progress_dialog.show()
progress_dialog.exec_()
errors = progress_dialog.errors()
if errors:
QtGui.QMessageBox.critical(parent, "IOS image", "{}".format("".join(errors)))
else:
path = destination_path
return path
@@ -309,7 +311,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
decompressed_size += zip_info.file_size
else:
decompressed_size = os.path.getsize(path)
except OSError:
except (zipfile.BadZipFile, OSError):
return 0
# get the size in MB
@@ -317,46 +319,6 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
# round up to the closest multiple of 32 (step of the RAM SpinBox)
return math.ceil(decompressed_size / 32) * 32
def _startupConfigBrowserSlot(self):
"""
Slot to open a file browser and select a startup-config file.
"""
if hasattr(sys, "frozen"):
config_dir = "configs"
else:
config_dir = pkg_resources.resource_filename("gns3", "configs")
path = QtGui.QFileDialog.getOpenFileName(self, "Select a startup configuration", config_dir)
if not path:
return
if not os.access(path, os.R_OK):
QtGui.QMessageBox.critical(self, "Startup configuration", "Cannot read {}".format(path))
return
self.uiStartupConfigLineEdit.clear()
self.uiStartupConfigLineEdit.setText(path)
def _privateConfigBrowserSlot(self):
"""
Slot to open a file browser and select a private-config file.
"""
if hasattr(sys, "frozen"):
config_dir = "configs"
else:
config_dir = pkg_resources.resource_filename("gns3", "configs")
path = QtGui.QFileDialog.getOpenFileName(self, "Select a private configuration", config_dir)
if not path:
return
if not os.access(path, os.R_OK):
QtGui.QMessageBox.critical(self, "Private configuration", "Cannot read {}".format(path))
return
self.uiPrivateConfigLineEdit.clear()
self.uiPrivateConfigLineEdit.setText(path)
def _decompressIOSSlot(self):
"""
Slot to decompress an IOS image.
@@ -366,12 +328,20 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
if item:
key = item.data(0, QtCore.Qt.UserRole)
ios_router = self._ios_routers[key]
path = ios_router["path"]
path = ios_router["image"]
if not os.path.isfile(path):
QtGui.QMessageBox.critical(self, "IOS image", "IOS image file {} is does not exist".format(path))
return
if not isIOSCompressed(path):
QtGui.QMessageBox.critical(self, "IOS image", "IOS image {} is not compressed".format(os.path.basename(path)))
try:
if not isIOSCompressed(path):
QtGui.QMessageBox.critical(self, "IOS image", "IOS image {} is not compressed".format(os.path.basename(path)))
return
except (OSError, ValueError) as e:
# errno 22, invalid argument means the file system where the IOS image is located doesn't support mmap
if e.errno == 22:
QtGui.QMessageBox.critical(self, "IOS image", "IOS image {} cannot be memory mapped, most likely because the file system doesn't support it".format(os.path.basename(path)))
else:
QtGui.QMessageBox.critical(self, "IOS image", "Could not determine if the IOS image is compressed: {}".format(e))
return
decompressed_image_path = os.path.splitext(path)[0] + ".image"
@@ -386,8 +356,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
"Cancel", busy=True, parent=self)
progress_dialog.show()
if progress_dialog.exec_() is not False:
ios_router["path"] = decompressed_image_path
ios_router["image"] = os.path.basename(decompressed_image_path)
ios_router["image"] = decompressed_image_path
self._refreshInfo(ios_router)
thread.wait()
@@ -481,16 +450,16 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
Change a symbol for an IOS router.
"""
dialog = SymbolSelectionDialog(self)
dialog.show()
if dialog.exec_():
normal_symbol, selected_symbol = dialog.getSymbols()
category = dialog.getCategory()
item = self.uiIOSRoutersTreeWidget.currentItem()
if item:
item = self.uiIOSRoutersTreeWidget.currentItem()
if item:
key = item.data(0, QtCore.Qt.UserRole)
ios_router = self._ios_routers[key]
dialog = SymbolSelectionDialog(self, symbol=ios_router["default_symbol"], category=ios_router["category"])
dialog.show()
if dialog.exec_():
normal_symbol, selected_symbol = dialog.getSymbols()
category = dialog.getCategory()
item.setIcon(0, QtGui.QIcon(normal_symbol))
key = item.data(0, QtCore.Qt.UserRole)
ios_router = self._ios_routers[key]
ios_router["default_symbol"] = normal_symbol
ios_router["hover_symbol"] = selected_symbol
ios_router["category"] = category

View File

@@ -21,70 +21,26 @@ Default Dynamips settings.
from gns3.node import Node
import sys
import os
# default path to Dynamips executable
if sys.platform.startswith("win"):
DEFAULT_DYNAMIPS_PATH = r"dynamips\dynamips.exe"
elif sys.platform.startswith("darwin") and hasattr(sys, "frozen"):
DEFAULT_DYNAMIPS_PATH = os.path.join(os.getcwd(), "dynamips")
else:
paths = [os.getcwd()] + os.environ["PATH"].split(os.pathsep)
# look for dynamips in the current working directory and $PATH
DEFAULT_DYNAMIPS_PATH = "dynamips"
for path in paths:
try:
if "dynamips" in os.listdir(path) and os.access(os.path.join(path, "dynamips"), os.X_OK):
DEFAULT_DYNAMIPS_PATH = os.path.join(path, "dynamips")
break
except OSError:
continue
DYNAMIPS_SETTINGS = {
"path": DEFAULT_DYNAMIPS_PATH,
"hypervisor_start_port_range": 7200,
"hypervisor_end_port_range": 7700,
"console_start_port_range": 2001,
"console_end_port_range": 2500,
"aux_start_port_range": 2501,
"aux_end_port_range": 3000,
"udp_start_port_range": 10001,
"udp_end_port_range": 20000,
"dynamips_path": "",
"allocate_aux_console_ports": False,
"use_local_server": True,
"allocate_hypervisor_per_device": True,
"memory_usage_limit_per_hypervisor": 1024,
"allocate_hypervisor_per_ios_image": True,
"ghost_ios_support": True,
"jit_sharing_support": False,
"sparse_memory_support": True,
"mmap_support": True,
}
DYNAMIPS_SETTING_TYPES = {
"path": str,
"hypervisor_start_port_range": int,
"hypervisor_end_port_range": int,
"console_start_port_range": int,
"console_end_port_range": int,
"aux_start_port_range": int,
"aux_end_port_range": int,
"udp_start_port_range": int,
"udp_end_port_range": int,
"dynamips_path": str,
"allocate_aux_console_ports": bool,
"use_local_server": bool,
"allocate_hypervisor_per_device": bool,
"memory_usage_limit_per_hypervisor": int,
"allocate_hypervisor_per_ios_image": bool,
"ghost_ios_support": bool,
"jit_sharing_support": bool,
"sparse_memory_support": bool,
"mmap_support": bool,
}
IOS_ROUTER_SETTINGS = {
"name": "",
"path": "",
"image": "",
"default_symbol": ":/symbols/router.normal.svg",
"hover_symbol": ":/symbols/router.selected.svg",
@@ -100,18 +56,16 @@ IOS_ROUTER_SETTINGS = {
"mmap": True,
"sparsemem": True,
"ram": 128,
"nvram": 256,
"nvram": 128,
"mac_addr": "",
"disk0": 1,
"disk0": 0,
"disk1": 0,
"confreg": "0x2102",
"system_id": "FTX0945W0MY",
"server": "local"
}
IOS_ROUTER_SETTING_TYPES = {
"name": str,
"path": str,
"image": str,
"default_symbol": str,
"hover_symbol": str,
@@ -131,20 +85,44 @@ IOS_ROUTER_SETTING_TYPES = {
"mac_addr": str,
"disk0": int,
"disk1": int,
"confreg": str,
"system_id": str,
"server": str
}
# supported platforms with the default RAM value
PLATFORMS_DEFAULT_RAM = {"c1700": 64,
"c2600": 64,
"c2691": 128,
"c3600": 128,
PLATFORMS_DEFAULT_RAM = {"c1700": 160,
"c2600": 160,
"c2691": 192,
"c3600": 192,
"c3725": 128,
"c3745": 128,
"c3745": 256,
"c7200": 512}
# supported platforms with the default NVRAM value
PLATFORMS_DEFAULT_NVRAM = {"c1700": 128,
"c2600": 128,
"c2691": 256,
"c3600": 256,
"c3725": 256,
"c3745": 256,
"c7200": 512}
DEFAULT_IDLEPC = {"7f4ae12a098391bc0edcaf4f44caaf9d": "0x80358a60", # c1700-adventerprisek9-mz.124-25d
"3aaecd2222e812c16c211bc9f7c77512": "0x824a4dc4", # c1700-adventerprisek9-mz.124-15.T14
"062a32e9e3f59aeec930ea5694fda9c9": "0x80519c48", # c2600-adventerprisek9-mz.124-25d
"483e3a579a5144ec23f2f160d4b0c0e2": "0x8027ec88", # c2600-adventerprisek9-mz.124-15.T14
"37b444b29191630e5b688f002de2171c": "0x603a8bac", # c3620-a3jk8s-mz.122-26c
"493c4ef6578801d74d715e7d11596964": "0x6050b114", # c3640-a3js-mz.124-25d
"b88ee1b2ed182737395db2df27f34a33": "0x606071f8", # c3660-a3jk9s-mz.124-25d
"daed99f508fd42dbaacf711e560643ed": "0x6076e0b4", # c3660-a3jk9s-mz.124-15.T14
"8dc8486065de63883f29c85825a2f18c": "0x60a48cb8", # c2691-adventerprisek9-mz.124-25d
"e7ee5a4a57ed1433e5f73ba6e7695c90": "0x60bcf9f8", # c2691-adventerprisek9-mz.124-15.T14
"606484061b9e52e71d4f4ddab9af19e7": "0x602467a4", # c3725-adventerprisek9-mz.124-25d
"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
# platforms with supported chassis
CHASSIS = {"c1700": ("1720", "1721", "1750", "1751", "1760"),
"c2600": ("2610", "2611", "2620", "2621", "2610XM", "2611XM", "2620XM", "2621XM", "2650XM", "2651XM"),
@@ -191,7 +169,7 @@ C7200_PAS = (
IO_C7200 = ("C7200-IO-FE",
"C7200-IO-2FE",
"C7200-IO-GE-E"
)
)
"""
Build the adapter compatibility matrix:

View File

@@ -17,13 +17,16 @@ except AttributeError:
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_atmBridgeConfigPageWidget(object):
def setupUi(self, atmBridgeConfigPageWidget):
atmBridgeConfigPageWidget.setObjectName(_fromUtf8("atmBridgeConfigPageWidget"))
atmBridgeConfigPageWidget.resize(432, 358)
@@ -164,4 +167,3 @@ class Ui_atmBridgeConfigPageWidget(object):
self.uiDeletePushButton.setText(_translate("atmBridgeConfigPageWidget", "&Delete", None))
self.uiGeneralGroupBox.setTitle(_translate("atmBridgeConfigPageWidget", "General", None))
self.uiNameLabel.setText(_translate("atmBridgeConfigPageWidget", "Name:", None))

View File

@@ -17,13 +17,16 @@ except AttributeError:
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_atmSwitchConfigPageWidget(object):
def setupUi(self, atmSwitchConfigPageWidget):
atmSwitchConfigPageWidget.setObjectName(_fromUtf8("atmSwitchConfigPageWidget"))
atmSwitchConfigPageWidget.resize(459, 419)
@@ -198,4 +201,3 @@ class Ui_atmSwitchConfigPageWidget(object):
self.uiDestinationPortLabel.setText(_translate("atmSwitchConfigPageWidget", "Port:", None))
self.uiDestinationVPILabel.setText(_translate("atmSwitchConfigPageWidget", "VPI:", None))
self.uiDestinationVCILabel.setText(_translate("atmSwitchConfigPageWidget", "VCI:", None))

View File

@@ -23,11 +23,35 @@
<attribute name="title">
<string>General settings</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="uiUseLocalServercheckBox">
<property name="text">
<string>Use the local server</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="uiDynamipsPathLabel">
<property name="text">
<string>Path to Dynamips:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="uiDynamipsPathLineEdit"/>
<widget class="QLineEdit" name="uiDynamipsPathLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="uiDynamipsPathToolButton">
@@ -41,14 +65,14 @@
</item>
</layout>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="uiDynamipsPathLabel">
<item>
<widget class="QCheckBox" name="uiAllocateAuxConsolePortsCheckBox">
<property name="text">
<string>Path to Dynamips:</string>
<string>Allocate AUX console ports</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<item>
<spacer name="spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -61,162 +85,6 @@
</property>
</spacer>
</item>
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="uiConsolePortRangeGroupBox">
<property name="title">
<string>Console port range for routers</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QSpinBox" name="uiConsoleStartPortSpinBox">
<property name="suffix">
<string notr="true"> TCP</string>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>2001</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="uiConsolePortRangeLabel">
<property name="text">
<string>to</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="uiConsoleEndPortSpinBox">
<property name="suffix">
<string notr="true"> TCP</string>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>2500</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QGroupBox" name="uiAuxPortRangeGroupBox">
<property name="title">
<string>Auxiliary console port range for routers</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QSpinBox" name="uiAuxStartPortSpinBox">
<property name="suffix">
<string notr="true"> TCP</string>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>2501</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="uiAuxPortRangeLabel">
<property name="text">
<string>to</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="uiAuxEndPortSpinBox">
<property name="suffix">
<string notr="true"> TCP</string>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>3000</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="uiServerSettingsTabWidget">
<attribute name="title">
<string>Server settings</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QCheckBox" name="uiUseLocalServercheckBox">
<property name="text">
<string>Always use the local server</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="uiRemoteServersGroupBox">
<property name="title">
<string>Remote servers</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QTreeWidget" name="uiRemoteServersTreeWidget">
<property name="enabled">
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<column>
<property name="text">
<string>Host</string>
</property>
</column>
<column>
<property name="text">
<string>Port</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="uiAdvancedSettingsTabWidget">
@@ -224,174 +92,6 @@
<string>Advanced settings</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="uiHypervisorAllocationGroupBox">
<property name="title">
<string>Hypervisor allocation</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="uiAllocateHypervisorPerDeviceCheckBox">
<property name="text">
<string>Allocate a new hypervisor for each device</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="uiMemoryUsageLimitPerHypervisorLabel">
<property name="text">
<string>Memory usage limit per hypervisor:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="uiMemoryUsageLimitPerHypervisorSpinBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="suffix">
<string> MiB</string>
</property>
<property name="maximum">
<number>1000000</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value">
<number>512</number>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="uiAllocateHypervisorPerIOSCheckBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Allocate a new hypervisor per IOS image</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="uiHypervisorPortRangeGroupBox">
<property name="title">
<string>Dynamips hypervisor port range</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QSpinBox" name="uiHypervisorStartPortSpinBox">
<property name="suffix">
<string notr="true"> TCP</string>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>7200</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="uiHypervisorPortRangeLabel">
<property name="text">
<string>to</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="uiHypervisorEndPortSpinBox">
<property name="suffix">
<string notr="true"> TCP</string>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>7700</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="uiUDPPortRangeGroupBox">
<property name="title">
<string>UDP tunneling port range</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QSpinBox" name="uiUDPStartPortSpinBox">
<property name="suffix">
<string notr="true"> UDP</string>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>10001</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="uiUDPPortRangeLabel">
<property name="text">
<string>to</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="uiUDPEndPortSpinBox">
<property name="suffix">
<string notr="true"> UDP</string>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>20000</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>147</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="uiMemoryUsageOptimisationGroupBox">
<property name="title">
@@ -424,22 +124,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="uiJITSharingSupportCheckBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>The JIT sharing feature allows router instances to share JIT blocks, instead of recompiling multiple times in a non-shared way.</string>
</property>
<property name="text">
<string>Enable JIT sharing support (unstable)</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="uiSparseMemorySupportCheckBox">
<property name="toolTip">
@@ -472,9 +156,6 @@
</layout>
<zorder>spacer_2</zorder>
<zorder>uiMemoryUsageOptimisationGroupBox</zorder>
<zorder>uiHypervisorAllocationGroupBox</zorder>
<zorder>uiHypervisorPortRangeGroupBox</zorder>
<zorder>uiUDPPortRangeGroupBox</zorder>
</widget>
</widget>
</item>
@@ -493,13 +174,6 @@
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="uiTestSettingsPushButton">
<property name="text">
<string>Test settings</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="uiRestoreDefaultsPushButton">
<property name="text">
@@ -514,12 +188,8 @@
<tabstops>
<tabstop>uiDynamipsPathLineEdit</tabstop>
<tabstop>uiDynamipsPathToolButton</tabstop>
<tabstop>uiAllocateHypervisorPerDeviceCheckBox</tabstop>
<tabstop>uiMemoryUsageLimitPerHypervisorSpinBox</tabstop>
<tabstop>uiAllocateHypervisorPerIOSCheckBox</tabstop>
<tabstop>uiGhostIOSSupportCheckBox</tabstop>
<tabstop>uiMmapSupportCheckBox</tabstop>
<tabstop>uiJITSharingSupportCheckBox</tabstop>
<tabstop>uiSparseMemorySupportCheckBox</tabstop>
</tabstops>
<resources/>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/dynamips_preferences_page.ui'
#
# Created: Sun Oct 19 11:35:54 2014
# Created: Mon Mar 9 17:56:06 2015
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -33,159 +33,40 @@ class Ui_DynamipsPreferencesPageWidget(object):
self.uiTabWidget.setObjectName(_fromUtf8("uiTabWidget"))
self.uiGeneralSettingsTabWidget = QtGui.QWidget()
self.uiGeneralSettingsTabWidget.setObjectName(_fromUtf8("uiGeneralSettingsTabWidget"))
self.gridLayout = QtGui.QGridLayout(self.uiGeneralSettingsTabWidget)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.verticalLayout_2 = QtGui.QVBoxLayout(self.uiGeneralSettingsTabWidget)
self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2"))
self.uiUseLocalServercheckBox = QtGui.QCheckBox(self.uiGeneralSettingsTabWidget)
self.uiUseLocalServercheckBox.setChecked(True)
self.uiUseLocalServercheckBox.setObjectName(_fromUtf8("uiUseLocalServercheckBox"))
self.verticalLayout_2.addWidget(self.uiUseLocalServercheckBox)
self.uiDynamipsPathLabel = QtGui.QLabel(self.uiGeneralSettingsTabWidget)
self.uiDynamipsPathLabel.setObjectName(_fromUtf8("uiDynamipsPathLabel"))
self.verticalLayout_2.addWidget(self.uiDynamipsPathLabel)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
self.uiDynamipsPathLineEdit = QtGui.QLineEdit(self.uiGeneralSettingsTabWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiDynamipsPathLineEdit.sizePolicy().hasHeightForWidth())
self.uiDynamipsPathLineEdit.setSizePolicy(sizePolicy)
self.uiDynamipsPathLineEdit.setObjectName(_fromUtf8("uiDynamipsPathLineEdit"))
self.horizontalLayout.addWidget(self.uiDynamipsPathLineEdit)
self.uiDynamipsPathToolButton = QtGui.QToolButton(self.uiGeneralSettingsTabWidget)
self.uiDynamipsPathToolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
self.uiDynamipsPathToolButton.setObjectName(_fromUtf8("uiDynamipsPathToolButton"))
self.horizontalLayout.addWidget(self.uiDynamipsPathToolButton)
self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 2)
self.uiDynamipsPathLabel = QtGui.QLabel(self.uiGeneralSettingsTabWidget)
self.uiDynamipsPathLabel.setObjectName(_fromUtf8("uiDynamipsPathLabel"))
self.gridLayout.addWidget(self.uiDynamipsPathLabel, 0, 0, 1, 2)
self.verticalLayout_2.addLayout(self.horizontalLayout)
self.uiAllocateAuxConsolePortsCheckBox = QtGui.QCheckBox(self.uiGeneralSettingsTabWidget)
self.uiAllocateAuxConsolePortsCheckBox.setObjectName(_fromUtf8("uiAllocateAuxConsolePortsCheckBox"))
self.verticalLayout_2.addWidget(self.uiAllocateAuxConsolePortsCheckBox)
spacerItem = QtGui.QSpacerItem(390, 193, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 5, 0, 1, 2)
self.uiConsolePortRangeGroupBox = QtGui.QGroupBox(self.uiGeneralSettingsTabWidget)
self.uiConsolePortRangeGroupBox.setObjectName(_fromUtf8("uiConsolePortRangeGroupBox"))
self.horizontalLayout_5 = QtGui.QHBoxLayout(self.uiConsolePortRangeGroupBox)
self.horizontalLayout_5.setObjectName(_fromUtf8("horizontalLayout_5"))
self.uiConsoleStartPortSpinBox = QtGui.QSpinBox(self.uiConsolePortRangeGroupBox)
self.uiConsoleStartPortSpinBox.setSuffix(_fromUtf8(" TCP"))
self.uiConsoleStartPortSpinBox.setMaximum(65535)
self.uiConsoleStartPortSpinBox.setProperty("value", 2001)
self.uiConsoleStartPortSpinBox.setObjectName(_fromUtf8("uiConsoleStartPortSpinBox"))
self.horizontalLayout_5.addWidget(self.uiConsoleStartPortSpinBox)
self.uiConsolePortRangeLabel = QtGui.QLabel(self.uiConsolePortRangeGroupBox)
self.uiConsolePortRangeLabel.setObjectName(_fromUtf8("uiConsolePortRangeLabel"))
self.horizontalLayout_5.addWidget(self.uiConsolePortRangeLabel)
self.uiConsoleEndPortSpinBox = QtGui.QSpinBox(self.uiConsolePortRangeGroupBox)
self.uiConsoleEndPortSpinBox.setSuffix(_fromUtf8(" TCP"))
self.uiConsoleEndPortSpinBox.setMaximum(65535)
self.uiConsoleEndPortSpinBox.setProperty("value", 2500)
self.uiConsoleEndPortSpinBox.setObjectName(_fromUtf8("uiConsoleEndPortSpinBox"))
self.horizontalLayout_5.addWidget(self.uiConsoleEndPortSpinBox)
spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout_5.addItem(spacerItem1)
self.gridLayout.addWidget(self.uiConsolePortRangeGroupBox, 2, 0, 1, 2)
self.uiAuxPortRangeGroupBox = QtGui.QGroupBox(self.uiGeneralSettingsTabWidget)
self.uiAuxPortRangeGroupBox.setObjectName(_fromUtf8("uiAuxPortRangeGroupBox"))
self.horizontalLayout_6 = QtGui.QHBoxLayout(self.uiAuxPortRangeGroupBox)
self.horizontalLayout_6.setObjectName(_fromUtf8("horizontalLayout_6"))
self.uiAuxStartPortSpinBox = QtGui.QSpinBox(self.uiAuxPortRangeGroupBox)
self.uiAuxStartPortSpinBox.setSuffix(_fromUtf8(" TCP"))
self.uiAuxStartPortSpinBox.setMaximum(65535)
self.uiAuxStartPortSpinBox.setProperty("value", 2501)
self.uiAuxStartPortSpinBox.setObjectName(_fromUtf8("uiAuxStartPortSpinBox"))
self.horizontalLayout_6.addWidget(self.uiAuxStartPortSpinBox)
self.uiAuxPortRangeLabel = QtGui.QLabel(self.uiAuxPortRangeGroupBox)
self.uiAuxPortRangeLabel.setObjectName(_fromUtf8("uiAuxPortRangeLabel"))
self.horizontalLayout_6.addWidget(self.uiAuxPortRangeLabel)
self.uiAuxEndPortSpinBox = QtGui.QSpinBox(self.uiAuxPortRangeGroupBox)
self.uiAuxEndPortSpinBox.setSuffix(_fromUtf8(" TCP"))
self.uiAuxEndPortSpinBox.setMaximum(65535)
self.uiAuxEndPortSpinBox.setProperty("value", 3000)
self.uiAuxEndPortSpinBox.setObjectName(_fromUtf8("uiAuxEndPortSpinBox"))
self.horizontalLayout_6.addWidget(self.uiAuxEndPortSpinBox)
spacerItem2 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout_6.addItem(spacerItem2)
self.gridLayout.addWidget(self.uiAuxPortRangeGroupBox, 3, 0, 1, 2)
self.verticalLayout_2.addItem(spacerItem)
self.uiTabWidget.addTab(self.uiGeneralSettingsTabWidget, _fromUtf8(""))
self.uiServerSettingsTabWidget = QtGui.QWidget()
self.uiServerSettingsTabWidget.setObjectName(_fromUtf8("uiServerSettingsTabWidget"))
self.gridLayout_2 = QtGui.QGridLayout(self.uiServerSettingsTabWidget)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.uiUseLocalServercheckBox = QtGui.QCheckBox(self.uiServerSettingsTabWidget)
self.uiUseLocalServercheckBox.setChecked(True)
self.uiUseLocalServercheckBox.setObjectName(_fromUtf8("uiUseLocalServercheckBox"))
self.gridLayout_2.addWidget(self.uiUseLocalServercheckBox, 0, 0, 1, 1)
self.uiRemoteServersGroupBox = QtGui.QGroupBox(self.uiServerSettingsTabWidget)
self.uiRemoteServersGroupBox.setObjectName(_fromUtf8("uiRemoteServersGroupBox"))
self.horizontalLayout_3 = QtGui.QHBoxLayout(self.uiRemoteServersGroupBox)
self.horizontalLayout_3.setObjectName(_fromUtf8("horizontalLayout_3"))
self.uiRemoteServersTreeWidget = QtGui.QTreeWidget(self.uiRemoteServersGroupBox)
self.uiRemoteServersTreeWidget.setEnabled(False)
self.uiRemoteServersTreeWidget.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
self.uiRemoteServersTreeWidget.setObjectName(_fromUtf8("uiRemoteServersTreeWidget"))
self.horizontalLayout_3.addWidget(self.uiRemoteServersTreeWidget)
self.gridLayout_2.addWidget(self.uiRemoteServersGroupBox, 1, 0, 1, 1)
self.uiTabWidget.addTab(self.uiServerSettingsTabWidget, _fromUtf8(""))
self.uiAdvancedSettingsTabWidget = QtGui.QWidget()
self.uiAdvancedSettingsTabWidget.setObjectName(_fromUtf8("uiAdvancedSettingsTabWidget"))
self.verticalLayout_3 = QtGui.QVBoxLayout(self.uiAdvancedSettingsTabWidget)
self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3"))
self.uiHypervisorAllocationGroupBox = QtGui.QGroupBox(self.uiAdvancedSettingsTabWidget)
self.uiHypervisorAllocationGroupBox.setObjectName(_fromUtf8("uiHypervisorAllocationGroupBox"))
self.verticalLayout_2 = QtGui.QVBoxLayout(self.uiHypervisorAllocationGroupBox)
self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2"))
self.uiAllocateHypervisorPerDeviceCheckBox = QtGui.QCheckBox(self.uiHypervisorAllocationGroupBox)
self.uiAllocateHypervisorPerDeviceCheckBox.setChecked(True)
self.uiAllocateHypervisorPerDeviceCheckBox.setObjectName(_fromUtf8("uiAllocateHypervisorPerDeviceCheckBox"))
self.verticalLayout_2.addWidget(self.uiAllocateHypervisorPerDeviceCheckBox)
self.uiMemoryUsageLimitPerHypervisorLabel = QtGui.QLabel(self.uiHypervisorAllocationGroupBox)
self.uiMemoryUsageLimitPerHypervisorLabel.setObjectName(_fromUtf8("uiMemoryUsageLimitPerHypervisorLabel"))
self.verticalLayout_2.addWidget(self.uiMemoryUsageLimitPerHypervisorLabel)
self.uiMemoryUsageLimitPerHypervisorSpinBox = QtGui.QSpinBox(self.uiHypervisorAllocationGroupBox)
self.uiMemoryUsageLimitPerHypervisorSpinBox.setEnabled(False)
self.uiMemoryUsageLimitPerHypervisorSpinBox.setMaximum(1000000)
self.uiMemoryUsageLimitPerHypervisorSpinBox.setSingleStep(128)
self.uiMemoryUsageLimitPerHypervisorSpinBox.setProperty("value", 512)
self.uiMemoryUsageLimitPerHypervisorSpinBox.setObjectName(_fromUtf8("uiMemoryUsageLimitPerHypervisorSpinBox"))
self.verticalLayout_2.addWidget(self.uiMemoryUsageLimitPerHypervisorSpinBox)
self.uiAllocateHypervisorPerIOSCheckBox = QtGui.QCheckBox(self.uiHypervisorAllocationGroupBox)
self.uiAllocateHypervisorPerIOSCheckBox.setEnabled(False)
self.uiAllocateHypervisorPerIOSCheckBox.setChecked(True)
self.uiAllocateHypervisorPerIOSCheckBox.setObjectName(_fromUtf8("uiAllocateHypervisorPerIOSCheckBox"))
self.verticalLayout_2.addWidget(self.uiAllocateHypervisorPerIOSCheckBox)
self.verticalLayout_3.addWidget(self.uiHypervisorAllocationGroupBox)
self.uiHypervisorPortRangeGroupBox = QtGui.QGroupBox(self.uiAdvancedSettingsTabWidget)
self.uiHypervisorPortRangeGroupBox.setObjectName(_fromUtf8("uiHypervisorPortRangeGroupBox"))
self.horizontalLayout_4 = QtGui.QHBoxLayout(self.uiHypervisorPortRangeGroupBox)
self.horizontalLayout_4.setObjectName(_fromUtf8("horizontalLayout_4"))
self.uiHypervisorStartPortSpinBox = QtGui.QSpinBox(self.uiHypervisorPortRangeGroupBox)
self.uiHypervisorStartPortSpinBox.setSuffix(_fromUtf8(" TCP"))
self.uiHypervisorStartPortSpinBox.setMaximum(65535)
self.uiHypervisorStartPortSpinBox.setProperty("value", 7200)
self.uiHypervisorStartPortSpinBox.setObjectName(_fromUtf8("uiHypervisorStartPortSpinBox"))
self.horizontalLayout_4.addWidget(self.uiHypervisorStartPortSpinBox)
self.uiHypervisorPortRangeLabel = QtGui.QLabel(self.uiHypervisorPortRangeGroupBox)
self.uiHypervisorPortRangeLabel.setObjectName(_fromUtf8("uiHypervisorPortRangeLabel"))
self.horizontalLayout_4.addWidget(self.uiHypervisorPortRangeLabel)
self.uiHypervisorEndPortSpinBox = QtGui.QSpinBox(self.uiHypervisorPortRangeGroupBox)
self.uiHypervisorEndPortSpinBox.setSuffix(_fromUtf8(" TCP"))
self.uiHypervisorEndPortSpinBox.setMaximum(65535)
self.uiHypervisorEndPortSpinBox.setProperty("value", 7700)
self.uiHypervisorEndPortSpinBox.setObjectName(_fromUtf8("uiHypervisorEndPortSpinBox"))
self.horizontalLayout_4.addWidget(self.uiHypervisorEndPortSpinBox)
spacerItem3 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout_4.addItem(spacerItem3)
self.verticalLayout_3.addWidget(self.uiHypervisorPortRangeGroupBox)
self.uiUDPPortRangeGroupBox = QtGui.QGroupBox(self.uiAdvancedSettingsTabWidget)
self.uiUDPPortRangeGroupBox.setObjectName(_fromUtf8("uiUDPPortRangeGroupBox"))
self.horizontalLayout_7 = QtGui.QHBoxLayout(self.uiUDPPortRangeGroupBox)
self.horizontalLayout_7.setObjectName(_fromUtf8("horizontalLayout_7"))
self.uiUDPStartPortSpinBox = QtGui.QSpinBox(self.uiUDPPortRangeGroupBox)
self.uiUDPStartPortSpinBox.setSuffix(_fromUtf8(" UDP"))
self.uiUDPStartPortSpinBox.setMaximum(65535)
self.uiUDPStartPortSpinBox.setProperty("value", 10001)
self.uiUDPStartPortSpinBox.setObjectName(_fromUtf8("uiUDPStartPortSpinBox"))
self.horizontalLayout_7.addWidget(self.uiUDPStartPortSpinBox)
self.uiUDPPortRangeLabel = QtGui.QLabel(self.uiUDPPortRangeGroupBox)
self.uiUDPPortRangeLabel.setObjectName(_fromUtf8("uiUDPPortRangeLabel"))
self.horizontalLayout_7.addWidget(self.uiUDPPortRangeLabel)
self.uiUDPEndPortSpinBox = QtGui.QSpinBox(self.uiUDPPortRangeGroupBox)
self.uiUDPEndPortSpinBox.setSuffix(_fromUtf8(" UDP"))
self.uiUDPEndPortSpinBox.setMaximum(65535)
self.uiUDPEndPortSpinBox.setProperty("value", 20000)
self.uiUDPEndPortSpinBox.setObjectName(_fromUtf8("uiUDPEndPortSpinBox"))
self.horizontalLayout_7.addWidget(self.uiUDPEndPortSpinBox)
spacerItem4 = QtGui.QSpacerItem(147, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout_7.addItem(spacerItem4)
self.verticalLayout_3.addWidget(self.uiUDPPortRangeGroupBox)
self.uiMemoryUsageOptimisationGroupBox = QtGui.QGroupBox(self.uiAdvancedSettingsTabWidget)
self.uiMemoryUsageOptimisationGroupBox.setObjectName(_fromUtf8("uiMemoryUsageOptimisationGroupBox"))
self.verticalLayout = QtGui.QVBoxLayout(self.uiMemoryUsageOptimisationGroupBox)
@@ -198,27 +79,19 @@ class Ui_DynamipsPreferencesPageWidget(object):
self.uiMmapSupportCheckBox.setChecked(True)
self.uiMmapSupportCheckBox.setObjectName(_fromUtf8("uiMmapSupportCheckBox"))
self.verticalLayout.addWidget(self.uiMmapSupportCheckBox)
self.uiJITSharingSupportCheckBox = QtGui.QCheckBox(self.uiMemoryUsageOptimisationGroupBox)
self.uiJITSharingSupportCheckBox.setEnabled(True)
self.uiJITSharingSupportCheckBox.setChecked(False)
self.uiJITSharingSupportCheckBox.setObjectName(_fromUtf8("uiJITSharingSupportCheckBox"))
self.verticalLayout.addWidget(self.uiJITSharingSupportCheckBox)
self.uiSparseMemorySupportCheckBox = QtGui.QCheckBox(self.uiMemoryUsageOptimisationGroupBox)
self.uiSparseMemorySupportCheckBox.setChecked(False)
self.uiSparseMemorySupportCheckBox.setObjectName(_fromUtf8("uiSparseMemorySupportCheckBox"))
self.verticalLayout.addWidget(self.uiSparseMemorySupportCheckBox)
self.verticalLayout_3.addWidget(self.uiMemoryUsageOptimisationGroupBox)
spacerItem5 = QtGui.QSpacerItem(390, 12, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.verticalLayout_3.addItem(spacerItem5)
spacerItem1 = QtGui.QSpacerItem(390, 12, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.verticalLayout_3.addItem(spacerItem1)
self.uiTabWidget.addTab(self.uiAdvancedSettingsTabWidget, _fromUtf8(""))
self.vboxlayout.addWidget(self.uiTabWidget)
self.horizontalLayout_2 = QtGui.QHBoxLayout()
self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2"))
spacerItem6 = QtGui.QSpacerItem(164, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem6)
self.uiTestSettingsPushButton = QtGui.QPushButton(DynamipsPreferencesPageWidget)
self.uiTestSettingsPushButton.setObjectName(_fromUtf8("uiTestSettingsPushButton"))
self.horizontalLayout_2.addWidget(self.uiTestSettingsPushButton)
spacerItem2 = QtGui.QSpacerItem(164, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem2)
self.uiRestoreDefaultsPushButton = QtGui.QPushButton(DynamipsPreferencesPageWidget)
self.uiRestoreDefaultsPushButton.setObjectName(_fromUtf8("uiRestoreDefaultsPushButton"))
self.horizontalLayout_2.addWidget(self.uiRestoreDefaultsPushButton)
@@ -228,47 +101,24 @@ class Ui_DynamipsPreferencesPageWidget(object):
self.uiTabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(DynamipsPreferencesPageWidget)
DynamipsPreferencesPageWidget.setTabOrder(self.uiDynamipsPathLineEdit, self.uiDynamipsPathToolButton)
DynamipsPreferencesPageWidget.setTabOrder(self.uiDynamipsPathToolButton, self.uiAllocateHypervisorPerDeviceCheckBox)
DynamipsPreferencesPageWidget.setTabOrder(self.uiAllocateHypervisorPerDeviceCheckBox, self.uiMemoryUsageLimitPerHypervisorSpinBox)
DynamipsPreferencesPageWidget.setTabOrder(self.uiMemoryUsageLimitPerHypervisorSpinBox, self.uiAllocateHypervisorPerIOSCheckBox)
DynamipsPreferencesPageWidget.setTabOrder(self.uiAllocateHypervisorPerIOSCheckBox, self.uiGhostIOSSupportCheckBox)
DynamipsPreferencesPageWidget.setTabOrder(self.uiDynamipsPathToolButton, self.uiGhostIOSSupportCheckBox)
DynamipsPreferencesPageWidget.setTabOrder(self.uiGhostIOSSupportCheckBox, self.uiMmapSupportCheckBox)
DynamipsPreferencesPageWidget.setTabOrder(self.uiMmapSupportCheckBox, self.uiJITSharingSupportCheckBox)
DynamipsPreferencesPageWidget.setTabOrder(self.uiJITSharingSupportCheckBox, self.uiSparseMemorySupportCheckBox)
DynamipsPreferencesPageWidget.setTabOrder(self.uiMmapSupportCheckBox, self.uiSparseMemorySupportCheckBox)
def retranslateUi(self, DynamipsPreferencesPageWidget):
DynamipsPreferencesPageWidget.setWindowTitle(_translate("DynamipsPreferencesPageWidget", "Dynamips", None))
self.uiDynamipsPathToolButton.setText(_translate("DynamipsPreferencesPageWidget", "&Browse...", None))
self.uiUseLocalServercheckBox.setText(_translate("DynamipsPreferencesPageWidget", "Use the local server", None))
self.uiDynamipsPathLabel.setText(_translate("DynamipsPreferencesPageWidget", "Path to Dynamips:", None))
self.uiConsolePortRangeGroupBox.setTitle(_translate("DynamipsPreferencesPageWidget", "Console port range for routers", None))
self.uiConsolePortRangeLabel.setText(_translate("DynamipsPreferencesPageWidget", "to", None))
self.uiAuxPortRangeGroupBox.setTitle(_translate("DynamipsPreferencesPageWidget", "Auxiliary console port range for routers", None))
self.uiAuxPortRangeLabel.setText(_translate("DynamipsPreferencesPageWidget", "to", None))
self.uiDynamipsPathToolButton.setText(_translate("DynamipsPreferencesPageWidget", "&Browse...", None))
self.uiAllocateAuxConsolePortsCheckBox.setText(_translate("DynamipsPreferencesPageWidget", "Allocate AUX console ports", None))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiGeneralSettingsTabWidget), _translate("DynamipsPreferencesPageWidget", "General settings", None))
self.uiUseLocalServercheckBox.setText(_translate("DynamipsPreferencesPageWidget", "Always use the local server", None))
self.uiRemoteServersGroupBox.setTitle(_translate("DynamipsPreferencesPageWidget", "Remote servers", None))
self.uiRemoteServersTreeWidget.headerItem().setText(0, _translate("DynamipsPreferencesPageWidget", "Host", None))
self.uiRemoteServersTreeWidget.headerItem().setText(1, _translate("DynamipsPreferencesPageWidget", "Port", None))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiServerSettingsTabWidget), _translate("DynamipsPreferencesPageWidget", "Server settings", None))
self.uiHypervisorAllocationGroupBox.setTitle(_translate("DynamipsPreferencesPageWidget", "Hypervisor allocation", None))
self.uiAllocateHypervisorPerDeviceCheckBox.setText(_translate("DynamipsPreferencesPageWidget", "Allocate a new hypervisor for each device", None))
self.uiMemoryUsageLimitPerHypervisorLabel.setText(_translate("DynamipsPreferencesPageWidget", "Memory usage limit per hypervisor:", None))
self.uiMemoryUsageLimitPerHypervisorSpinBox.setSuffix(_translate("DynamipsPreferencesPageWidget", " MiB", None))
self.uiAllocateHypervisorPerIOSCheckBox.setText(_translate("DynamipsPreferencesPageWidget", "Allocate a new hypervisor per IOS image", None))
self.uiHypervisorPortRangeGroupBox.setTitle(_translate("DynamipsPreferencesPageWidget", "Dynamips hypervisor port range", None))
self.uiHypervisorPortRangeLabel.setText(_translate("DynamipsPreferencesPageWidget", "to", None))
self.uiUDPPortRangeGroupBox.setTitle(_translate("DynamipsPreferencesPageWidget", "UDP tunneling port range", None))
self.uiUDPPortRangeLabel.setText(_translate("DynamipsPreferencesPageWidget", "to", None))
self.uiMemoryUsageOptimisationGroupBox.setTitle(_translate("DynamipsPreferencesPageWidget", "Memory usage optimisation", None))
self.uiGhostIOSSupportCheckBox.setToolTip(_translate("DynamipsPreferencesPageWidget", "The ghost IOS feature is a solution that helps to decrease memory usage by sharing an IOS image between different router instances.", None))
self.uiGhostIOSSupportCheckBox.setText(_translate("DynamipsPreferencesPageWidget", "Enable ghost IOS support", None))
self.uiMmapSupportCheckBox.setToolTip(_translate("DynamipsPreferencesPageWidget", "The mmap feature tells Dynamips to use disk files instead of real memory for router instances.", None))
self.uiMmapSupportCheckBox.setText(_translate("DynamipsPreferencesPageWidget", "Enable mmap support", None))
self.uiJITSharingSupportCheckBox.setToolTip(_translate("DynamipsPreferencesPageWidget", "The JIT sharing feature allows router instances to share JIT blocks, instead of recompiling multiple times in a non-shared way.", None))
self.uiJITSharingSupportCheckBox.setText(_translate("DynamipsPreferencesPageWidget", "Enable JIT sharing support (unstable)", None))
self.uiSparseMemorySupportCheckBox.setToolTip(_translate("DynamipsPreferencesPageWidget", "The sparse memory feature reduces the amount of virtual memory used by router instances.", None))
self.uiSparseMemorySupportCheckBox.setText(_translate("DynamipsPreferencesPageWidget", "Enable sparse memory support", None))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiAdvancedSettingsTabWidget), _translate("DynamipsPreferencesPageWidget", "Advanced settings", None))
self.uiTestSettingsPushButton.setText(_translate("DynamipsPreferencesPageWidget", "Test settings", None))
self.uiRestoreDefaultsPushButton.setText(_translate("DynamipsPreferencesPageWidget", "Restore defaults", None))

View File

@@ -17,13 +17,16 @@ except AttributeError:
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_ethernetHubConfigPageWidget(object):
def setupUi(self, ethernetHubConfigPageWidget):
ethernetHubConfigPageWidget.setObjectName(_fromUtf8("ethernetHubConfigPageWidget"))
ethernetHubConfigPageWidget.resize(381, 270)
@@ -70,4 +73,3 @@ class Ui_ethernetHubConfigPageWidget(object):
self.uiSettingsGroupBox.setTitle(_translate("ethernetHubConfigPageWidget", "Settings", None))
self.uiNameLabel.setText(_translate("ethernetHubConfigPageWidget", "Name:", None))
self.uiPortsLabel.setText(_translate("ethernetHubConfigPageWidget", "Number of ports:", None))

View File

@@ -17,13 +17,16 @@ except AttributeError:
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_ethernetSwitchConfigPageWidget(object):
def setupUi(self, ethernetSwitchConfigPageWidget):
ethernetSwitchConfigPageWidget.setObjectName(_fromUtf8("ethernetSwitchConfigPageWidget"))
ethernetSwitchConfigPageWidget.resize(397, 315)
@@ -138,4 +141,3 @@ class Ui_ethernetSwitchConfigPageWidget(object):
self.uiPortTypeComboBox.setItemText(2, _translate("ethernetSwitchConfigPageWidget", "qinq", None))
self.uiAddPushButton.setText(_translate("ethernetSwitchConfigPageWidget", "&Add", None))
self.uiDeletePushButton.setText(_translate("ethernetSwitchConfigPageWidget", "&Delete", None))

View File

@@ -17,13 +17,16 @@ except AttributeError:
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_frameRelaySwitchConfigPageWidget(object):
def setupUi(self, frameRelaySwitchConfigPageWidget):
frameRelaySwitchConfigPageWidget.setObjectName(_fromUtf8("frameRelaySwitchConfigPageWidget"))
frameRelaySwitchConfigPageWidget.resize(499, 405)
@@ -161,4 +164,3 @@ class Ui_frameRelaySwitchConfigPageWidget(object):
self.uiDestinationDLCILabel.setText(_translate("frameRelaySwitchConfigPageWidget", "DLCI:", None))
self.uiAddPushButton.setText(_translate("frameRelaySwitchConfigPageWidget", "&Add", None))
self.uiDeletePushButton.setText(_translate("frameRelaySwitchConfigPageWidget", "&Delete", None))

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>419</width>
<height>522</height>
<width>449</width>
<height>491</height>
</rect>
</property>
<property name="windowTitle">
@@ -65,7 +65,7 @@
<item row="3" column="0">
<widget class="QLabel" name="uiIOSImageLabel">
<property name="text">
<string>IOS image:</string>
<string>IOS image path:</string>
</property>
</widget>
</item>
@@ -89,7 +89,7 @@
<item row="4" column="0">
<widget class="QLabel" name="uiStartupConfigLabel">
<property name="text">
<string>Startup-config:</string>
<string>Initial startup-config:</string>
</property>
</widget>
</item>
@@ -113,7 +113,7 @@
<item row="5" column="0">
<widget class="QLabel" name="uiPrivateConfigLabel">
<property name="text">
<string>Private-config:</string>
<string>Initial private-config:</string>
</property>
</widget>
</item>
@@ -615,13 +615,6 @@
<item row="0" column="1">
<widget class="QLineEdit" name="uiSystemIdLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="uiConfregLabel">
<property name="text">
<string>Confreg:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
@@ -629,14 +622,14 @@
</property>
</widget>
</item>
<item row="2" column="0">
<item row="1" column="0">
<widget class="QLabel" name="uiBaseMacLabel">
<property name="text">
<string>Base MAC:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="1" column="1">
<widget class="QLineEdit" name="uiBaseMACLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@@ -649,19 +642,6 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="uiConfregLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>0x2102</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -1008,7 +988,6 @@
<tabstop>uiWic0comboBox</tabstop>
<tabstop>uiWic1comboBox</tabstop>
<tabstop>uiWic2comboBox</tabstop>
<tabstop>uiConfregLineEdit</tabstop>
<tabstop>uiBaseMACLineEdit</tabstop>
<tabstop>uiExecAreaSpinBox</tabstop>
</tabstops>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/ios_router_configuration_page.ui'
#
# Created: Fri Oct 10 10:43:48 2014
# Created: Sat Mar 14 16:29:27 2015
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -26,7 +26,7 @@ except AttributeError:
class Ui_iosRouterConfigPageWidget(object):
def setupUi(self, iosRouterConfigPageWidget):
iosRouterConfigPageWidget.setObjectName(_fromUtf8("iosRouterConfigPageWidget"))
iosRouterConfigPageWidget.resize(419, 522)
iosRouterConfigPageWidget.resize(449, 491)
self.vboxlayout = QtGui.QVBoxLayout(iosRouterConfigPageWidget)
self.vboxlayout.setObjectName(_fromUtf8("vboxlayout"))
self.uiTabWidget = QtGui.QTabWidget(iosRouterConfigPageWidget)
@@ -361,15 +361,12 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiSystemIdLineEdit = QtGui.QLineEdit(self.uiSystemGroupBox)
self.uiSystemIdLineEdit.setObjectName(_fromUtf8("uiSystemIdLineEdit"))
self.gridLayout_6.addWidget(self.uiSystemIdLineEdit, 0, 1, 1, 1)
self.uiConfregLabel = QtGui.QLabel(self.uiSystemGroupBox)
self.uiConfregLabel.setObjectName(_fromUtf8("uiConfregLabel"))
self.gridLayout_6.addWidget(self.uiConfregLabel, 1, 0, 1, 1)
self.label = QtGui.QLabel(self.uiSystemGroupBox)
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout_6.addWidget(self.label, 0, 0, 1, 1)
self.uiBaseMacLabel = QtGui.QLabel(self.uiSystemGroupBox)
self.uiBaseMacLabel.setObjectName(_fromUtf8("uiBaseMacLabel"))
self.gridLayout_6.addWidget(self.uiBaseMacLabel, 2, 0, 1, 1)
self.gridLayout_6.addWidget(self.uiBaseMacLabel, 1, 0, 1, 1)
self.uiBaseMACLineEdit = QtGui.QLineEdit(self.uiSystemGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -378,15 +375,7 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiBaseMACLineEdit.setSizePolicy(sizePolicy)
self.uiBaseMACLineEdit.setText(_fromUtf8(""))
self.uiBaseMACLineEdit.setObjectName(_fromUtf8("uiBaseMACLineEdit"))
self.gridLayout_6.addWidget(self.uiBaseMACLineEdit, 2, 1, 1, 1)
self.uiConfregLineEdit = QtGui.QLineEdit(self.uiSystemGroupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiConfregLineEdit.sizePolicy().hasHeightForWidth())
self.uiConfregLineEdit.setSizePolicy(sizePolicy)
self.uiConfregLineEdit.setObjectName(_fromUtf8("uiConfregLineEdit"))
self.gridLayout_6.addWidget(self.uiConfregLineEdit, 1, 1, 1, 1)
self.gridLayout_6.addWidget(self.uiBaseMACLineEdit, 1, 1, 1, 1)
self.verticalLayout_4.addWidget(self.uiSystemGroupBox)
self.uiOptimizationsGroupBox = QtGui.QGroupBox(self.uiAdvancedPageWidget)
self.uiOptimizationsGroupBox.setObjectName(_fromUtf8("uiOptimizationsGroupBox"))
@@ -557,8 +546,7 @@ class Ui_iosRouterConfigPageWidget(object):
iosRouterConfigPageWidget.setTabOrder(self.uiSlot6comboBox, self.uiWic0comboBox)
iosRouterConfigPageWidget.setTabOrder(self.uiWic0comboBox, self.uiWic1comboBox)
iosRouterConfigPageWidget.setTabOrder(self.uiWic1comboBox, self.uiWic2comboBox)
iosRouterConfigPageWidget.setTabOrder(self.uiWic2comboBox, self.uiConfregLineEdit)
iosRouterConfigPageWidget.setTabOrder(self.uiConfregLineEdit, self.uiBaseMACLineEdit)
iosRouterConfigPageWidget.setTabOrder(self.uiWic2comboBox, self.uiBaseMACLineEdit)
iosRouterConfigPageWidget.setTabOrder(self.uiBaseMACLineEdit, self.uiExecAreaSpinBox)
def retranslateUi(self, iosRouterConfigPageWidget):
@@ -566,11 +554,11 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiNameLabel.setText(_translate("iosRouterConfigPageWidget", "Name:", None))
self.uiPlatformLabel.setText(_translate("iosRouterConfigPageWidget", "Platform:", None))
self.uiChassisLabel.setText(_translate("iosRouterConfigPageWidget", "Chassis:", None))
self.uiIOSImageLabel.setText(_translate("iosRouterConfigPageWidget", "IOS image:", None))
self.uiIOSImageLabel.setText(_translate("iosRouterConfigPageWidget", "IOS image path:", None))
self.uiIOSImageToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse...", None))
self.uiStartupConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Startup-config:", None))
self.uiStartupConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Initial startup-config:", None))
self.uiStartupConfigToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse...", None))
self.uiPrivateConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Private-config:", None))
self.uiPrivateConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Initial private-config:", None))
self.uiPrivateConfigToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse...", None))
self.uiConsolePortLabel.setText(_translate("iosRouterConfigPageWidget", "Console port:", None))
self.uiAuxPortLabel.setText(_translate("iosRouterConfigPageWidget", "Aux port:", None))
@@ -604,10 +592,8 @@ class Ui_iosRouterConfigPageWidget(object):
self.uiWic2Label.setText(_translate("iosRouterConfigPageWidget", "wic 2:", None))
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiSlotsPageWidget), _translate("iosRouterConfigPageWidget", "Slots", None))
self.uiSystemGroupBox.setTitle(_translate("iosRouterConfigPageWidget", "System", None))
self.uiConfregLabel.setText(_translate("iosRouterConfigPageWidget", "Confreg:", None))
self.label.setText(_translate("iosRouterConfigPageWidget", "System ID:", None))
self.uiBaseMacLabel.setText(_translate("iosRouterConfigPageWidget", "Base MAC:", None))
self.uiConfregLineEdit.setText(_translate("iosRouterConfigPageWidget", "0x2102", None))
self.uiOptimizationsGroupBox.setTitle(_translate("iosRouterConfigPageWidget", "Optimisations", None))
self.uiExecAreaLabel.setText(_translate("iosRouterConfigPageWidget", "Exec area:", None))
self.uiExecAreaSpinBox.setSuffix(_translate("iosRouterConfigPageWidget", " MiB", None))

View File

@@ -6,13 +6,16 @@
<rect>
<x>0</x>
<y>0</y>
<width>672</width>
<height>521</height>
<width>560</width>
<height>518</height>
</rect>
</property>
<property name="windowTitle">
<string>IOS routers</string>
</property>
<property name="accessibleName">
<string>IOS router templates</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" rowspan="2">
<widget class="QTreeWidget" name="uiIOSRoutersTreeWidget">
@@ -56,6 +59,12 @@
</item>
<item row="0" column="1">
<widget class="QTreeWidget" name="uiIOSRouterInfoTreeWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="indentation">
<number>10</number>
</property>
@@ -112,7 +121,7 @@
<bool>false</bool>
</property>
<property name="text">
<string>Delete</string>
<string>&amp;Delete</string>
</property>
</widget>
</item>

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/ios_router_preferences_page.ui'
#
# Created: Wed Nov 19 18:57:20 2014
# Created: Wed Mar 11 22:03:56 2015
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -26,7 +26,7 @@ except AttributeError:
class Ui_IOSRouterPreferencesPageWidget(object):
def setupUi(self, IOSRouterPreferencesPageWidget):
IOSRouterPreferencesPageWidget.setObjectName(_fromUtf8("IOSRouterPreferencesPageWidget"))
IOSRouterPreferencesPageWidget.resize(672, 521)
IOSRouterPreferencesPageWidget.resize(560, 518)
self.gridLayout = QtGui.QGridLayout(IOSRouterPreferencesPageWidget)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.uiIOSRoutersTreeWidget = QtGui.QTreeWidget(IOSRouterPreferencesPageWidget)
@@ -48,6 +48,11 @@ class Ui_IOSRouterPreferencesPageWidget(object):
self.uiIOSRoutersTreeWidget.header().setVisible(False)
self.gridLayout.addWidget(self.uiIOSRoutersTreeWidget, 0, 0, 2, 1)
self.uiIOSRouterInfoTreeWidget = QtGui.QTreeWidget(IOSRouterPreferencesPageWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.uiIOSRouterInfoTreeWidget.sizePolicy().hasHeightForWidth())
self.uiIOSRouterInfoTreeWidget.setSizePolicy(sizePolicy)
self.uiIOSRouterInfoTreeWidget.setIndentation(10)
self.uiIOSRouterInfoTreeWidget.setAllColumnsShowFocus(True)
self.uiIOSRouterInfoTreeWidget.setObjectName(_fromUtf8("uiIOSRouterInfoTreeWidget"))
@@ -77,10 +82,11 @@ class Ui_IOSRouterPreferencesPageWidget(object):
def retranslateUi(self, IOSRouterPreferencesPageWidget):
IOSRouterPreferencesPageWidget.setWindowTitle(_translate("IOSRouterPreferencesPageWidget", "IOS routers", None))
IOSRouterPreferencesPageWidget.setAccessibleName(_translate("IOSRouterPreferencesPageWidget", "IOS router templates", None))
self.uiIOSRouterInfoTreeWidget.headerItem().setText(0, _translate("IOSRouterPreferencesPageWidget", "1", None))
self.uiIOSRouterInfoTreeWidget.headerItem().setText(1, _translate("IOSRouterPreferencesPageWidget", "2", None))
self.uiNewIOSRouterPushButton.setText(_translate("IOSRouterPreferencesPageWidget", "&New", None))
self.uiDecompressIOSPushButton.setText(_translate("IOSRouterPreferencesPageWidget", "&Decompress", None))
self.uiEditIOSRouterPushButton.setText(_translate("IOSRouterPreferencesPageWidget", "&Edit", None))
self.uiDeleteIOSRouterPushButton.setText(_translate("IOSRouterPreferencesPageWidget", "Delete", None))
self.uiDeleteIOSRouterPushButton.setText(_translate("IOSRouterPreferencesPageWidget", "&Delete", None))

View File

@@ -6,12 +6,12 @@
<rect>
<x>0</x>
<y>0</y>
<width>517</width>
<width>585</width>
<height>398</height>
</rect>
</property>
<property name="windowTitle">
<string>New IOS router</string>
<string>New IOS router template</string>
</property>
<property name="modal">
<bool>true</bool>
@@ -183,6 +183,13 @@
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="uiEtherSwitchCheckBox">
<property name="text">
<string>This is an EtherSwitch router</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="uiMemoryWizardPage">
@@ -190,7 +197,7 @@
<string>Memory</string>
</property>
<property name="subTitle">
<string>Please check the amount of memory (RAM) that you allocate to IOS. Not enough RAM could prevent IOS to start.</string>
<string>Please check the amount of memory (RAM) that you allocate to IOS. Too much or not enough RAM could prevent IOS to start.</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
@@ -232,10 +239,10 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<item row="1" column="0" colspan="3">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://tools.cisco.com/ITDIT/CFN/jsp/SearchBySoftware.jsp&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Check for minimum RAM requirement&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://tools.cisco.com/ITDIT/CFN/jsp/SearchBySoftware.jsp&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Check for minimum and maximum RAM requirement&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>false</bool>

View File

@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/ios_router_wizard.ui'
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/ios_router_wizard.ui'
#
# Created: Wed Oct 22 16:46:37 2014
# by: PyQt4 UI code generator 4.10.4
# Created: Wed Apr 1 15:15:16 2015
# by: PyQt4 UI code generator 4.11.3
#
# WARNING! All changes made in this file will be lost!
@@ -26,7 +26,7 @@ except AttributeError:
class Ui_IOSRouterWizard(object):
def setupUi(self, IOSRouterWizard):
IOSRouterWizard.setObjectName(_fromUtf8("IOSRouterWizard"))
IOSRouterWizard.resize(517, 398)
IOSRouterWizard.resize(585, 398)
IOSRouterWizard.setModal(True)
self.uiServerWizardPage = QtGui.QWizardPage()
self.uiServerWizardPage.setObjectName(_fromUtf8("uiServerWizardPage"))
@@ -111,6 +111,9 @@ class Ui_IOSRouterWizard(object):
self.uiChassisLabel = QtGui.QLabel(self.uiNamePlatformWizardPage)
self.uiChassisLabel.setObjectName(_fromUtf8("uiChassisLabel"))
self.gridLayout.addWidget(self.uiChassisLabel, 2, 0, 1, 1)
self.uiEtherSwitchCheckBox = QtGui.QCheckBox(self.uiNamePlatformWizardPage)
self.uiEtherSwitchCheckBox.setObjectName(_fromUtf8("uiEtherSwitchCheckBox"))
self.gridLayout.addWidget(self.uiEtherSwitchCheckBox, 3, 0, 1, 2)
IOSRouterWizard.addPage(self.uiNamePlatformWizardPage)
self.uiMemoryWizardPage = QtGui.QWizardPage()
self.uiMemoryWizardPage.setObjectName(_fromUtf8("uiMemoryWizardPage"))
@@ -138,7 +141,7 @@ class Ui_IOSRouterWizard(object):
self.label.setWordWrap(False)
self.label.setOpenExternalLinks(True)
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout_2.addWidget(self.label, 1, 0, 1, 2)
self.gridLayout_2.addWidget(self.label, 1, 0, 1, 3)
IOSRouterWizard.addPage(self.uiMemoryWizardPage)
self.uiNetworkAdaptersWizardPage = QtGui.QWizardPage()
self.uiNetworkAdaptersWizardPage.setObjectName(_fromUtf8("uiNetworkAdaptersWizardPage"))
@@ -280,7 +283,7 @@ class Ui_IOSRouterWizard(object):
IOSRouterWizard.setTabOrder(self.uiNameLineEdit, self.uiPlatformComboBox)
def retranslateUi(self, IOSRouterWizard):
IOSRouterWizard.setWindowTitle(_translate("IOSRouterWizard", "New IOS router", None))
IOSRouterWizard.setWindowTitle(_translate("IOSRouterWizard", "New IOS router template", None))
self.uiServerWizardPage.setTitle(_translate("IOSRouterWizard", "Server", None))
self.uiServerWizardPage.setSubTitle(_translate("IOSRouterWizard", "Please choose a server type to run your new IOS router.", None))
self.uiServerTypeGroupBox.setTitle(_translate("IOSRouterWizard", "Server type", None))
@@ -299,12 +302,13 @@ class Ui_IOSRouterWizard(object):
self.uiTypeLabel.setText(_translate("IOSRouterWizard", "Platform:", None))
self.uiNameLabel.setText(_translate("IOSRouterWizard", "Name:", None))
self.uiChassisLabel.setText(_translate("IOSRouterWizard", "Chassis:", None))
self.uiEtherSwitchCheckBox.setText(_translate("IOSRouterWizard", "This is an EtherSwitch router", None))
self.uiMemoryWizardPage.setTitle(_translate("IOSRouterWizard", "Memory", None))
self.uiMemoryWizardPage.setSubTitle(_translate("IOSRouterWizard", "Please check the amount of memory (RAM) that you allocate to IOS. Not enough RAM could prevent IOS to start.", None))
self.uiMemoryWizardPage.setSubTitle(_translate("IOSRouterWizard", "Please check the amount of memory (RAM) that you allocate to IOS. Too much or not enough RAM could prevent IOS to start.", None))
self.uiRamLabel.setText(_translate("IOSRouterWizard", "Default RAM:", None))
self.uiRamSpinBox.setSuffix(_translate("IOSRouterWizard", " MiB", None))
self.uiTestIOSImagePushButton.setText(_translate("IOSRouterWizard", "&Test IOS image", None))
self.label.setText(_translate("IOSRouterWizard", "<html><head/><body><p><a href=\"http://tools.cisco.com/ITDIT/CFN/jsp/SearchBySoftware.jsp\"><span style=\" text-decoration: underline; color:#0000ff;\">Check for minimum RAM requirement</span></a></p></body></html>", None))
self.label.setText(_translate("IOSRouterWizard", "<html><head/><body><p><a href=\"http://tools.cisco.com/ITDIT/CFN/jsp/SearchBySoftware.jsp\"><span style=\" text-decoration: underline; color:#0000ff;\">Check for minimum and maximum RAM requirement</span></a></p></body></html>", None))
self.uiNetworkAdaptersWizardPage.setTitle(_translate("IOSRouterWizard", "Network adapters", None))
self.uiNetworkAdaptersWizardPage.setSubTitle(_translate("IOSRouterWizard", "Please choose the default network adapters that should be inserted into every new instance of this router.", None))
self.uiSlot0Label.setText(_translate("IOSRouterWizard", "slot 0:", None))
@@ -320,7 +324,7 @@ class Ui_IOSRouterWizard(object):
self.uiWic1Label.setText(_translate("IOSRouterWizard", "wic 1:", None))
self.uiWic2Label.setText(_translate("IOSRouterWizard", "wic 2:", None))
self.uiIdlePCWizardPage.setTitle(_translate("IOSRouterWizard", "Idle-PC", None))
self.uiIdlePCWizardPage.setSubTitle(_translate("IOSRouterWizard", "An Idle-PC value is necessary to prevent IOS to use 100% of your processor or one of its core.", None))
self.uiIdlePCWizardPage.setSubTitle(_translate("IOSRouterWizard", "An idle-pc value is necessary to prevent IOS to use 100% of your processor or one of its core.", None))
self.uiIdlepcLabel.setText(_translate("IOSRouterWizard", "Idle-PC:", None))
self.uiIdlePCFinderPushButton.setText(_translate("IOSRouterWizard", "Idle-PC finder", None))

View File

@@ -32,10 +32,7 @@ def isIOSCompressed(ios_image):
:returns: boolean
"""
try:
fd = open(ios_image, "r+b")
except OSError:
return False
fd = open(ios_image, "rb")
mapped_file = mmap.mmap(fd.fileno(), 0)
# look for ZIP 'end of central directory' signature

View File

@@ -19,11 +19,14 @@
Thread to wait for an IOS image to be decompressed.
"""
import zipfile
from gns3.qt import QtCore
from .decompress_ios import decompressIOS
class DecompressIOSThread(QtCore.QThread):
"""
Thread to decompress an IOS image.
@@ -51,7 +54,10 @@ class DecompressIOSThread(QtCore.QThread):
self._is_running = True
try:
decompressIOS(self._ios_image, self._destination_file)
except OSError as e:
except zipfile.BadZipFile as e:
self.error.emit("File {} is corrupted {}".format(self._ios_image, e), True)
return
except (OSError, MemoryError) as e:
self.error.emit("Could not decompress {}: {}".format(self._ios_image, e), True)
return

View File

@@ -19,6 +19,7 @@ from gns3.qt import QtGui
class TreeWidgetItem(QtGui.QTreeWidgetItem):
"""
QTreeWidgetItem reimplementation to allow numeric sort.
"""

Some files were not shown because too many files have changed in this diff Show More