mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-05-30 15:30:31 +03:00
Compare commits
425 Commits
v1.2
...
v1.3.0beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52bef05107 | ||
|
|
261f70c034 | ||
|
|
c5e53125b5 | ||
|
|
304e84f901 | ||
|
|
ed4e4a1d93 | ||
|
|
ae00038493 | ||
|
|
d6a60db1b7 | ||
|
|
a7fa3762aa | ||
|
|
53a2b37c08 | ||
|
|
161f7cd514 | ||
|
|
6816dc772b | ||
|
|
b72df57201 | ||
|
|
1bfdfc4535 | ||
|
|
4149ed361b | ||
|
|
2e1b7e940b | ||
|
|
683c75d308 | ||
|
|
8e8efbb805 | ||
|
|
67da57e7f6 | ||
|
|
1be5f9f748 | ||
|
|
1ee5260d8c | ||
|
|
c017586694 | ||
|
|
ce8df3c833 | ||
|
|
b2a93c04c6 | ||
|
|
2b19ba9572 | ||
|
|
3649ea612f | ||
|
|
4dd9d3b18d | ||
|
|
db307ca37d | ||
|
|
5769927760 | ||
|
|
e827e9be39 | ||
|
|
b799e04c39 | ||
|
|
0a1c45ef3b | ||
|
|
d06b6df34d | ||
|
|
b01854ec72 | ||
|
|
89d2d28ccb | ||
|
|
77f9bd931c | ||
|
|
9ae0779523 | ||
|
|
8d3952fcc9 | ||
|
|
0f759d763d | ||
|
|
22c7ae1053 | ||
|
|
f937aa374f | ||
|
|
2a72cb9700 | ||
|
|
200bc94870 | ||
|
|
168fad78a4 | ||
|
|
741cd4a857 | ||
|
|
68102d1523 | ||
|
|
be72b8c9fd | ||
|
|
0d000cf19b | ||
|
|
0ecf21813e | ||
|
|
e840fbd67d | ||
|
|
e85ea6a3ef | ||
|
|
f80c6ae6f3 | ||
|
|
5050cfa7e8 | ||
|
|
23d87cd926 | ||
|
|
c5a2974d53 | ||
|
|
3aa0736478 | ||
|
|
92f6aab290 | ||
|
|
12322b4be0 | ||
|
|
7a1b0e7a48 | ||
|
|
1914522c3a | ||
|
|
ad07bd9bb9 | ||
|
|
75d2c6686d | ||
|
|
9253b7f841 | ||
|
|
60970134e6 | ||
|
|
ba7136f70a | ||
|
|
dc2c592ee1 | ||
|
|
f910eb7946 | ||
|
|
d72db0db77 | ||
|
|
a251ec7739 | ||
|
|
a55d0e3b04 | ||
|
|
5dc5f31d07 | ||
|
|
f97efabcdb | ||
|
|
ba7995ab3f | ||
|
|
bb1723e2bb | ||
|
|
95866fa5c2 | ||
|
|
8a19456657 | ||
|
|
4e24a9f4dd | ||
|
|
7a5f2412c0 | ||
|
|
cf334b1ea4 | ||
|
|
eb5a723991 | ||
|
|
02e71a79bf | ||
|
|
8eae452765 | ||
|
|
05dea91c93 | ||
|
|
3f4dd04461 | ||
|
|
c2bbf98947 | ||
|
|
65be634999 | ||
|
|
c027648cb7 | ||
|
|
96ff09885d | ||
|
|
cc08b18e9e | ||
|
|
73186a771e | ||
|
|
c7a266cdfc | ||
|
|
a74b749421 | ||
|
|
37a87dbdee | ||
|
|
054b9e3dd2 | ||
|
|
9694eb5940 | ||
|
|
dafb36e3e9 | ||
|
|
364c771b96 | ||
|
|
93ba13b6a9 | ||
|
|
1969ad8dd9 | ||
|
|
0f1713ef32 | ||
|
|
4ddda18d5b | ||
|
|
7df1e92a36 | ||
|
|
998794e11e | ||
|
|
06036c41cd | ||
|
|
cab6b7783b | ||
|
|
8abfe5ff7d | ||
|
|
9ce152bc42 | ||
|
|
fb8ce37e4d | ||
|
|
b16d756941 | ||
|
|
72ad516140 | ||
|
|
56ed695544 | ||
|
|
f567ab8b11 | ||
|
|
968b879b0b | ||
|
|
05ffa8bc03 | ||
|
|
ac4bfcb098 | ||
|
|
2be612e042 | ||
|
|
1b1d7d9481 | ||
|
|
12ab8612cb | ||
|
|
71696ab48b | ||
|
|
6d7e05a9e0 | ||
|
|
82e13c7bed | ||
|
|
df27824e3f | ||
|
|
fac0ccfe68 | ||
|
|
d38536ff75 | ||
|
|
56e0cafe0d | ||
|
|
c55bbe339f | ||
|
|
84ed6cd722 | ||
|
|
dbcf8beda9 | ||
|
|
dbe37d6bc0 | ||
|
|
73b5652b5c | ||
|
|
2fea220ed8 | ||
|
|
8457854483 | ||
|
|
f00ad62b96 | ||
|
|
cd65f4c4d7 | ||
|
|
f9f4c59e55 | ||
|
|
402505e2cb | ||
|
|
4b13e4f7c6 | ||
|
|
d0e9cc44e0 | ||
|
|
9af4e0e15e | ||
|
|
c2f77ba39f | ||
|
|
7db4e397f0 | ||
|
|
399db108f3 | ||
|
|
9de5e5f984 | ||
|
|
30505269a6 | ||
|
|
7b1f1268fc | ||
|
|
fd23957e2e | ||
|
|
98dc5890fd | ||
|
|
4a1eeeedd1 | ||
|
|
bf3d11cbdc | ||
|
|
4e055a3eb0 | ||
|
|
e703f220ca | ||
|
|
9e4ec60d47 | ||
|
|
6ea960f2b7 | ||
|
|
2d98d34137 | ||
|
|
42c2aec975 | ||
|
|
b0f062234b | ||
|
|
16b3cb581b | ||
|
|
65e5bc8da0 | ||
|
|
82815cd697 | ||
|
|
87cabfeaeb | ||
|
|
4dcd5712e8 | ||
|
|
04a63d5e17 | ||
|
|
6b330eccef | ||
|
|
3b5a0a3067 | ||
|
|
9c9c216f6c | ||
|
|
e4cdecf653 | ||
|
|
544e11ac9c | ||
|
|
3fe6f27c57 | ||
|
|
c35a911d36 | ||
|
|
e7bb823677 | ||
|
|
f79d7b8550 | ||
|
|
11e5ebeacf | ||
|
|
2ed3da2328 | ||
|
|
3d0a30e38c | ||
|
|
a8a8cd8158 | ||
|
|
b518520dd6 | ||
|
|
9e7662c255 | ||
|
|
0b70872163 | ||
|
|
cc3999e9f6 | ||
|
|
aa3e137c9a | ||
|
|
c5dbf2d54d | ||
|
|
5770cfad3b | ||
|
|
9204ffae78 | ||
|
|
5340ea400c | ||
|
|
990ccee91d | ||
|
|
837885cb04 | ||
|
|
af00609edc | ||
|
|
8cf974701a | ||
|
|
82cd586950 | ||
|
|
e7f74373dd | ||
|
|
a9bb8f7130 | ||
|
|
68a084911d | ||
|
|
d02bb86083 | ||
|
|
408291403b | ||
|
|
5afe8d2805 | ||
|
|
1482b65c64 | ||
|
|
84173e2933 | ||
|
|
a0bd0c422e | ||
|
|
6ebbc369ca | ||
|
|
72fa14cb8f | ||
|
|
7bed153cc8 | ||
|
|
07f0ccacb6 | ||
|
|
b95f75354e | ||
|
|
15bf587616 | ||
|
|
e6dc1b5b09 | ||
|
|
0839b06ec2 | ||
|
|
67e9410f43 | ||
|
|
789409f74f | ||
|
|
141d588596 | ||
|
|
3a6a11bab3 | ||
|
|
bc17f8c865 | ||
|
|
9b434d4928 | ||
|
|
56bbe64b35 | ||
|
|
5504e0e7b7 | ||
|
|
890c211fe2 | ||
|
|
14be61b5d8 | ||
|
|
5455689f9b | ||
|
|
68e10ea066 | ||
|
|
c2d3a2d94f | ||
|
|
3905b9e089 | ||
|
|
ce15caed4f | ||
|
|
1c51219f40 | ||
|
|
85d8446e93 | ||
|
|
83b5812ade | ||
|
|
488e26af6d | ||
|
|
41549ae60d | ||
|
|
1ede748d81 | ||
|
|
5913ba9491 | ||
|
|
caaad2da85 | ||
|
|
143318dd34 | ||
|
|
1d48e27c80 | ||
|
|
c6ea09a031 | ||
|
|
d75eca55ad | ||
|
|
e8432d0000 | ||
|
|
276f9dbfdb | ||
|
|
cf1a5ba382 | ||
|
|
c551db1556 | ||
|
|
0caf3ea31a | ||
|
|
a1bd6e34cc | ||
|
|
c8d1c60bd8 | ||
|
|
41b2f64db4 | ||
|
|
3fc011e75e | ||
|
|
42a31a4479 | ||
|
|
b044ff5f35 | ||
|
|
843463a97f | ||
|
|
c14765f69d | ||
|
|
35e1b194c1 | ||
|
|
912c2aeb03 | ||
|
|
25eeea20c7 | ||
|
|
67f5964aff | ||
|
|
0bf7933d4d | ||
|
|
c5e868dc1b | ||
|
|
5f7e80643a | ||
|
|
bcd85eec82 | ||
|
|
55667b78df | ||
|
|
0ac429a0b6 | ||
|
|
bbc51b83b8 | ||
|
|
d0c709fa2f | ||
|
|
0dfcbb778b | ||
|
|
6ed70380b6 | ||
|
|
4d844a1608 | ||
|
|
9af650def7 | ||
|
|
1935bbf510 | ||
|
|
eadfd7fd48 | ||
|
|
1c33ad4527 | ||
|
|
ac6cc76d67 | ||
|
|
6adc8014da | ||
|
|
138df7c2c0 | ||
|
|
efb8cbc9f4 | ||
|
|
f0d03f2196 | ||
|
|
ed4077b1ff | ||
|
|
20fe982df2 | ||
|
|
25c33b69f3 | ||
|
|
722c8f38d1 | ||
|
|
3bb01d8eaf | ||
|
|
d4d7c64442 | ||
|
|
4c559f20a3 | ||
|
|
81fa1bc43f | ||
|
|
0c63f37a8e | ||
|
|
90d20b7e1a | ||
|
|
1cad2a94ec | ||
|
|
92d95bd585 | ||
|
|
39da7cbe22 | ||
|
|
061c603831 | ||
|
|
8fb92a316a | ||
|
|
527a571bf6 | ||
|
|
b2e81e3070 | ||
|
|
1a389d68c5 | ||
|
|
5908b4aaf2 | ||
|
|
684781aadd | ||
|
|
2b771772e2 | ||
|
|
ff439a2b0b | ||
|
|
ea8bcae586 | ||
|
|
3e7fff0c35 | ||
|
|
81bd3b4893 | ||
|
|
68e9842bd9 | ||
|
|
54552031d8 | ||
|
|
bfabccc60d | ||
|
|
17fe1df029 | ||
|
|
e10ccea86c | ||
|
|
fa470bd8bf | ||
|
|
9b7a2cda15 | ||
|
|
d0d60d26da | ||
|
|
b2c14c1218 | ||
|
|
2d59dc2c72 | ||
|
|
bb48cdf0a0 | ||
|
|
e00dcbcd92 | ||
|
|
5ab7d6e94e | ||
|
|
d1da9adc88 | ||
|
|
1455babd62 | ||
|
|
fc8529323c | ||
|
|
f9130794ee | ||
|
|
e62d6c0edd | ||
|
|
8d1dc4b090 | ||
|
|
acf6cf6ea2 | ||
|
|
d9ee44f90a | ||
|
|
bd6da5db9a | ||
|
|
4b3ade9b48 | ||
|
|
9daed7e0d4 | ||
|
|
4ef61e7af1 | ||
|
|
851f2d0517 | ||
|
|
5e5e04de8e | ||
|
|
e8b2f952af | ||
|
|
85b5d10e5a | ||
|
|
b916ca7bfb | ||
|
|
46190154f6 | ||
|
|
194552d2d3 | ||
|
|
a736cbc4d5 | ||
|
|
0310ecdfd0 | ||
|
|
28bbc8bbe9 | ||
|
|
f36ef66623 | ||
|
|
16ba51aa8c | ||
|
|
52847d1fad | ||
|
|
4733cc8a3e | ||
|
|
aecf61135f | ||
|
|
252d86eb70 | ||
|
|
438b0fe9d3 | ||
|
|
f6c58b5a28 | ||
|
|
dee2f94c38 | ||
|
|
1fc4dec5ca | ||
|
|
d7ab12bc61 | ||
|
|
1f50612a16 | ||
|
|
43715f1e34 | ||
|
|
1bf0ff69d8 | ||
|
|
abf992dfb8 | ||
|
|
707dfee696 | ||
|
|
e8c4f059c0 | ||
|
|
1a6f80f2df | ||
|
|
47ae310ac7 | ||
|
|
923c61f9c7 | ||
|
|
18b8c558cd | ||
|
|
84a091e380 | ||
|
|
1399098e30 | ||
|
|
593f8add5d | ||
|
|
2f26624f29 | ||
|
|
14c901d219 | ||
|
|
2b2e45ca45 | ||
|
|
868e9a322e | ||
|
|
d2f3f58de1 | ||
|
|
a69b3fcd11 | ||
|
|
a3505ee7f2 | ||
|
|
efe5e0e2c4 | ||
|
|
8745527c5d | ||
|
|
78b40df71e | ||
|
|
9675619ab9 | ||
|
|
68ca2c2be6 | ||
|
|
835ecbb410 | ||
|
|
22756a3c13 | ||
|
|
3c2fb04ed8 | ||
|
|
db5fb840a6 | ||
|
|
65b05d707b | ||
|
|
1b972df7e2 | ||
|
|
40266c275d | ||
|
|
197db35c80 | ||
|
|
8771e1ace7 | ||
|
|
e6e1275ad2 | ||
|
|
417dc0859b | ||
|
|
806e1f29bb | ||
|
|
9724f5769d | ||
|
|
5a0c8914c4 | ||
|
|
c2e0a30da8 | ||
|
|
45ae4f20a0 | ||
|
|
398826d7ef | ||
|
|
14e5f3bf74 | ||
|
|
57b1cbf41b | ||
|
|
51a0d88b9b | ||
|
|
16d4d7d1ea | ||
|
|
2affb1513d | ||
|
|
214d4b2a9e | ||
|
|
2f486d979b | ||
|
|
023c5fb99a | ||
|
|
329ed371f9 | ||
|
|
0577bfbde3 | ||
|
|
3ce5c35143 | ||
|
|
9258ef4bb3 | ||
|
|
e02facc170 | ||
|
|
4dc76926ea | ||
|
|
ba6be7e987 | ||
|
|
6b2da1ec97 | ||
|
|
6aa1b515c7 | ||
|
|
5d64ec1c8f | ||
|
|
e1b81fb931 | ||
|
|
c91752598c | ||
|
|
708515925f | ||
|
|
a61745f868 | ||
|
|
2ca7af34df | ||
|
|
bd7e6f4e3e | ||
|
|
32e86d461e | ||
|
|
66ea5ad979 | ||
|
|
ebfa80d444 | ||
|
|
4247cc819d | ||
|
|
1f9f5bd734 | ||
|
|
426d791eea | ||
|
|
30d99b812c | ||
|
|
981e55bc4d | ||
|
|
bab1090a25 | ||
|
|
84b5181fd8 | ||
|
|
222ebb58c0 | ||
|
|
f5ad3a6a2e | ||
|
|
422af60827 | ||
|
|
9127321540 | ||
|
|
dcae059480 | ||
|
|
6d6603e013 | ||
|
|
90fe8078c3 | ||
|
|
367fb32114 | ||
|
|
b2b82afd71 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -47,3 +47,6 @@ nosetests.xml
|
||||
|
||||
# Gedit Temp Files
|
||||
*~
|
||||
|
||||
# Qt creator
|
||||
*.autosave
|
||||
|
||||
37
.travis.yml
37
.travis.yml
@@ -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"
|
||||
|
||||
138
CHANGELOG
Normal file
138
CHANGELOG
Normal file
@@ -0,0 +1,138 @@
|
||||
# Change Log
|
||||
|
||||
## 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.
|
||||
17
README.rst
17
README.rst
@@ -1,6 +1,13 @@
|
||||
GNS3-gui
|
||||
========
|
||||
|
||||
.. 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 (beta stage).
|
||||
|
||||
Linux (Debian based)
|
||||
@@ -67,3 +74,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
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
-rrequirements.txt
|
||||
|
||||
pep8
|
||||
pytest
|
||||
pytest-pythonpath # useful for running tests outside tox
|
||||
pytest-timeout
|
||||
pytest-capturelog
|
||||
|
||||
@@ -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. """
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
252
gns3/cloud_builder.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
77
gns3/crash_report.py
Normal file
77
gns3/crash_report.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# -*- 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 raven
|
||||
import struct
|
||||
import platform
|
||||
|
||||
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://399087fc600b4a2984874af1cd57124c:a0f8323c923f4246917699c9519f2ff2@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):
|
||||
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))
|
||||
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
|
||||
@@ -21,6 +21,7 @@ from ..ui.about_dialog_ui import Ui_AboutDialog
|
||||
|
||||
|
||||
class AboutDialog(QtGui.QDialog, Ui_AboutDialog):
|
||||
|
||||
"""
|
||||
About dialog.
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -39,7 +41,9 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
|
||||
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._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"])
|
||||
self._timer = QtCore.QTimer(self)
|
||||
self._timer.timeout.connect(self._loadFinishedSlot)
|
||||
self._timer.setSingleShot(True)
|
||||
@@ -62,7 +66,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):
|
||||
@@ -87,13 +91,7 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
|
||||
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)
|
||||
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("file://{}".format(getting_started)))
|
||||
|
||||
@@ -23,6 +23,7 @@ from ..ui.idlepc_dialog_ui import Ui_IdlePCDialog
|
||||
|
||||
|
||||
class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
|
||||
|
||||
"""
|
||||
Idle-PC dialog.
|
||||
"""
|
||||
@@ -67,11 +68,10 @@ 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"]
|
||||
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)
|
||||
|
||||
def done(self, result):
|
||||
@@ -84,4 +84,3 @@ class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
|
||||
if result:
|
||||
self._applySlot()
|
||||
QtGui.QDialog.done(self, result)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -24,6 +24,7 @@ from ..ui.node_configurator_dialog_ui import Ui_NodeConfiguratorDialog
|
||||
|
||||
|
||||
class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
|
||||
|
||||
"""
|
||||
Node configurator implementation.
|
||||
|
||||
@@ -65,7 +66,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)
|
||||
@@ -165,7 +166,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 +202,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 +271,7 @@ class ConfigurationPageItem(QtGui.QTreeWidgetItem):
|
||||
|
||||
|
||||
class ConfigurationError(Exception):
|
||||
|
||||
"""
|
||||
Exception to be raised when a configuration error occurs.
|
||||
"""
|
||||
|
||||
@@ -30,6 +30,7 @@ from ..settings import ENABLE_CLOUD
|
||||
|
||||
|
||||
class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
|
||||
|
||||
"""
|
||||
Preferences dialog implementation.
|
||||
|
||||
@@ -64,7 +65,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 +105,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())
|
||||
|
||||
@@ -33,6 +33,7 @@ from ..node import Node
|
||||
|
||||
|
||||
class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
|
||||
|
||||
"""
|
||||
Snapshots dialog implementation.
|
||||
|
||||
@@ -145,7 +146,7 @@ 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.
|
||||
# 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)
|
||||
@@ -153,7 +154,7 @@ class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
|
||||
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):
|
||||
|
||||
@@ -24,6 +24,7 @@ from ..ui.style_editor_dialog_ui import Ui_StyleEditorDialog
|
||||
|
||||
|
||||
class StyleEditorDialog(QtGui.QDialog, Ui_StyleEditorDialog):
|
||||
|
||||
"""
|
||||
Style editor dialog.
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -39,7 +39,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 +57,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GraphicsView(QtGui.QGraphicsView):
|
||||
|
||||
"""
|
||||
Graphics view that displays the scene.
|
||||
|
||||
@@ -124,46 +125,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 +164,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):
|
||||
"""
|
||||
@@ -462,7 +440,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():
|
||||
@@ -712,26 +690,49 @@ 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(), "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)
|
||||
@@ -779,6 +780,17 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
style_action.triggered.connect(self.styleActionSlot)
|
||||
menu.addAction(style_action)
|
||||
|
||||
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)
|
||||
|
||||
# item must have no parent
|
||||
if True in list(map(lambda item: item.parentItem() is None, items)):
|
||||
|
||||
@@ -851,6 +863,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 +894,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,6 +908,10 @@ 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
|
||||
@@ -888,7 +921,13 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
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
|
||||
@@ -929,6 +968,82 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
if self.consoleToNode(item.node()):
|
||||
continue
|
||||
|
||||
def auxConsoleActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the auxiliary console action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, NodeItem):
|
||||
if self.consoleToNode(item.node(), aux=True):
|
||||
continue
|
||||
|
||||
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) 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]
|
||||
config_path = QtGui.QFileDialog.getSaveFileName(self, "Export config")
|
||||
if hasattr(item.node(), "importPrivateConfig"):
|
||||
private_config_path = QtGui.QFileDialog.getSaveFileName(self, "Export private-config")
|
||||
item.node().exportConfig(config_path, private_config_path)
|
||||
else:
|
||||
item.node().exportConfig(config_path)
|
||||
|
||||
def captureActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the capture action in the
|
||||
@@ -964,53 +1079,46 @@ 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 _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)
|
||||
QtGui.QMessageBox.information(self, "Auto Idle-PC", "Idle-PC value {} has been applied on {}".format(idlepc, router.name()))
|
||||
|
||||
def duplicateActionSlot(self):
|
||||
"""
|
||||
@@ -1066,6 +1174,26 @@ 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 = self.scene().selectedItems()[0].y()
|
||||
for item in self.scene().selectedItems():
|
||||
item.setPos(item.x(), horizontal_pos)
|
||||
|
||||
def verticalAlignmentSlot(self):
|
||||
"""
|
||||
Slot to receive events from the vertical align action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
vertical_position = self.scene().selectedItems()[0].x()
|
||||
for item in self.scene().selectedItems():
|
||||
item.setPos(vertical_position, item.y())
|
||||
|
||||
def raiseLayerActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the raise one layer action in the
|
||||
@@ -1084,11 +1212,15 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
show_message = True
|
||||
for item in self.scene().selectedItems():
|
||||
if item.parentItem() is None:
|
||||
current_zvalue = item.zValue()
|
||||
item.setZValue(current_zvalue - 1)
|
||||
item.update()
|
||||
if item.zValue() == -1 and show_message:
|
||||
QtGui.QMessageBox.information(self, "Layer position", "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")
|
||||
show_message = False
|
||||
|
||||
def deleteActionSlot(self):
|
||||
"""
|
||||
@@ -1127,7 +1259,6 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
"""
|
||||
|
||||
try:
|
||||
log.debug('In createNode')
|
||||
node_module = None
|
||||
for module in MODULES:
|
||||
instance = module.instance()
|
||||
@@ -1139,7 +1270,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 +1282,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)
|
||||
|
||||
355
gns3/http_client.py
Normal file
355
gns3/http_client.py
Normal file
@@ -0,0 +1,355 @@
|
||||
# -*- 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 uuid
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from functools import partial
|
||||
|
||||
from .version import __version__
|
||||
from .qt import QtCore, QtNetwork
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
# TODO: move this to properties? (many references to .host and .port in the code)
|
||||
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
|
||||
# self.check_server_version()
|
||||
|
||||
# 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 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=context), {})
|
||||
|
||||
def _callbackConnect(self, method, path, callback, body, 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 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
|
||||
self.executeHTTPQuery(method, path, callback, body)
|
||||
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)
|
||||
|
||||
response.finished.connect(partial(self._processResponse, response, callback, context))
|
||||
|
||||
def _processResponse(self, response, callback, context):
|
||||
|
||||
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
|
||||
error_message = response.errorString()
|
||||
log.info("Response error: {}".format(error_message))
|
||||
body = bytes(response.readAll()).decode()
|
||||
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()
|
||||
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()
|
||||
|
||||
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
|
||||
@@ -24,6 +24,7 @@ from .shape_item import ShapeItem
|
||||
|
||||
|
||||
class EllipseItem(ShapeItem, QtGui.QGraphicsEllipseItem):
|
||||
|
||||
"""
|
||||
Class to draw an ellipse on the scene.
|
||||
"""
|
||||
|
||||
@@ -26,6 +26,7 @@ from ..ports.port import Port
|
||||
|
||||
|
||||
class EthernetLinkItem(LinkItem):
|
||||
|
||||
"""
|
||||
Ethernet link for the scene.
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ from ..qt import QtCore, QtGui
|
||||
|
||||
|
||||
class ImageItem(QtGui.QGraphicsPixmapItem):
|
||||
|
||||
"""
|
||||
Class to insert an image on the scene.
|
||||
"""
|
||||
|
||||
@@ -27,6 +27,7 @@ from ..qt import QtCore, QtGui
|
||||
|
||||
|
||||
class LinkItem(QtGui.QGraphicsPathItem):
|
||||
|
||||
"""
|
||||
Base class for link items.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -24,6 +24,7 @@ from .shape_item import ShapeItem
|
||||
|
||||
|
||||
class RectangleItem(ShapeItem, QtGui.QGraphicsRectItem):
|
||||
|
||||
"""
|
||||
Class to draw a rectangle on the scene.
|
||||
"""
|
||||
|
||||
@@ -27,6 +27,7 @@ from ..ports.port import Port
|
||||
|
||||
|
||||
class SerialLinkItem(LinkItem):
|
||||
|
||||
"""
|
||||
Serial link for the scene.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
184
gns3/jsonrpc.py
184
gns3/jsonrpc.py
@@ -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
|
||||
@@ -28,6 +28,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Link(QtCore.QObject):
|
||||
|
||||
"""
|
||||
Link implementation.
|
||||
|
||||
|
||||
196
gns3/local_config.py
Normal file
196
gns3/local_config.py
Normal 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") 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") 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") 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
131
gns3/local_server_config.py
Normal 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.ConfigParser()
|
||||
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 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') as fp:
|
||||
self._config.write(fp)
|
||||
except 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
|
||||
90
gns3/logger.py
Normal file
90
gns3/logger.py
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/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()
|
||||
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()
|
||||
124
gns3/main.py
124
gns3/main.py
@@ -24,6 +24,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 +87,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):
|
||||
|
||||
@@ -103,12 +112,13 @@ def main():
|
||||
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 +133,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 +146,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 +160,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 +168,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)
|
||||
|
||||
|
||||
1052
gns3/main_window.py
1052
gns3/main_window.py
File diff suppressed because it is too large
Load Diff
@@ -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]
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -36,17 +36,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 +59,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 +85,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.
|
||||
|
||||
@@ -211,53 +213,55 @@ 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_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 +313,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:
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -25,6 +25,7 @@ from ..ui.cloud_configuration_page_ui import Ui_cloudConfigPageWidget
|
||||
|
||||
|
||||
class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
|
||||
|
||||
"""
|
||||
QWidget configuration page for clouds.
|
||||
"""
|
||||
@@ -105,7 +106,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 +155,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)
|
||||
|
||||
@@ -212,7 +213,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 +269,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 +326,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 +382,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)
|
||||
|
||||
@@ -433,7 +434,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)
|
||||
|
||||
|
||||
@@ -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_cloudConfigPageWidget(object):
|
||||
|
||||
def setupUi(self, cloudConfigPageWidget):
|
||||
cloudConfigPageWidget.setObjectName(_fromUtf8("cloudConfigPageWidget"))
|
||||
cloudConfigPageWidget.resize(542, 500)
|
||||
@@ -418,4 +421,3 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_6), _translate("cloudConfigPageWidget", "NIO NULL", None))
|
||||
self.uiNameLabel.setText(_translate("cloudConfigPageWidget", "Name:", None))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_7), _translate("cloudConfigPageWidget", "Misc.", None))
|
||||
|
||||
|
||||
@@ -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,165 @@ 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")
|
||||
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 +286,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 +317,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,23 +350,10 @@ 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"]))
|
||||
|
||||
# TODO: improve this part
|
||||
settings = {}
|
||||
# set initial settings like the chassis or an Idle-PC value etc.
|
||||
if "chassis" in ios_router and ios_router["chassis"]:
|
||||
@@ -473,10 +374,10 @@ class Dynamips(Module):
|
||||
if "nvram" in ios_router and ios_router["nvram"]:
|
||||
settings["nvram"] = ios_router["nvram"]
|
||||
|
||||
if "disk0" in ios_router and ios_router["disk0"]:
|
||||
if "disk0" in ios_router:
|
||||
settings["disk0"] = ios_router["disk0"]
|
||||
|
||||
if "disk1" in ios_router and ios_router["disk1"]:
|
||||
if "disk1" in ios_router:
|
||||
settings["disk1"] = ios_router["disk1"]
|
||||
|
||||
for slot_id in range(0, 7):
|
||||
@@ -488,11 +389,11 @@ class Dynamips(Module):
|
||||
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)
|
||||
base_name = "R"
|
||||
if "slot1" in settings and settings["slot1"] == "NM-16ESW":
|
||||
# must be an EtherSwitch router
|
||||
base_name = "ESW"
|
||||
node.setup(ios_router["path"], ios_router["ram"], additional_settings=settings, base_name=base_name)
|
||||
else:
|
||||
node.setup()
|
||||
|
||||
@@ -513,36 +414,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 +428,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 +439,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):
|
||||
"""
|
||||
@@ -648,7 +525,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(),
|
||||
|
||||
@@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,15 +22,19 @@ Wizard for IOS routers.
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import hashlib
|
||||
|
||||
from functools import partial
|
||||
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
|
||||
@@ -52,6 +56,7 @@ PLATFORM_TO_CLASS = {
|
||||
|
||||
|
||||
class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
|
||||
"""
|
||||
Wizard to create an IOS router.
|
||||
|
||||
@@ -74,10 +79,24 @@ 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.
|
||||
# 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 +124,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 +158,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):
|
||||
"""
|
||||
@@ -162,36 +185,69 @@ 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.
|
||||
"""
|
||||
|
||||
from gns3.main_window import MainWindow
|
||||
main_window = MainWindow.instance()
|
||||
server = Servers.instance().localServer()
|
||||
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.setup(ios_image, ram, name="AUTOIDLEPC")
|
||||
self._router.created_signal.connect(self.createdSlot)
|
||||
router = router_class(module, server, main_window.project())
|
||||
router.setup(ios_image, ram, name="AUTOIDLEPC")
|
||||
callback = partial(self.createdSlot, router)
|
||||
router.created_signal.connect(callback)
|
||||
self.uiIdlePCFinderPushButton.setEnabled(False)
|
||||
|
||||
def createdSlot(self, node_id):
|
||||
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, router, node_id):
|
||||
"""
|
||||
The node for the auto Idle-PC has been created.
|
||||
|
||||
:param router: IOS router instance
|
||||
:param node_id: not used
|
||||
"""
|
||||
|
||||
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()
|
||||
router.computeAutoIdlepc(self._computeAutoIdlepcCallback)
|
||||
|
||||
def _computeAutoIdlepcCallback(self, result, error=False):
|
||||
def _computeAutoIdlepcCallback(self, result, error=False, context=None, **kwargs):
|
||||
"""
|
||||
Callback for computeAutoIdlepc.
|
||||
|
||||
@@ -199,19 +255,14 @@ 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()
|
||||
|
||||
router = context["router"]
|
||||
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))
|
||||
router.delete()
|
||||
|
||||
def _iosImageBrowserSlot(self):
|
||||
"""
|
||||
@@ -227,7 +278,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 +296,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)
|
||||
@@ -270,7 +324,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 +344,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 +373,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 +388,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 +412,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.
|
||||
@@ -360,31 +435,42 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
"""
|
||||
|
||||
path = self.uiIOSImageLineEdit.text()
|
||||
image = os.path.basename(path)
|
||||
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,
|
||||
"startup_config": get_default_base_config(self._base_startup_config_template),
|
||||
"private_config": get_default_base_config(self._base_private_config_template),
|
||||
"ram": self.uiRamSpinBox.value(),
|
||||
"nvram": PLATFORMS_DEFAULT_NVRAM[platform],
|
||||
"idlepc": self.uiIdlepcLineEdit.text(),
|
||||
"image": os.path.basename(path),
|
||||
"platform": self.uiPlatformComboBox.currentText(),
|
||||
"image": image,
|
||||
"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["category"] = Node.switches
|
||||
|
||||
if image.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 +492,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)
|
||||
|
||||
@@ -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,33 +92,36 @@ 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():
|
||||
@@ -178,16 +132,15 @@ class ATMSwitch(Node):
|
||||
"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 +150,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 +158,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 +166,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 +177,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 +193,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 +261,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 +289,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 +303,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):
|
||||
"""
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
211
gns3/modules/dynamips/nodes/device.py
Normal file
211
gns3/modules/dynamips/nodes/device.py
Normal 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()
|
||||
@@ -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()))
|
||||
else:
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
"""
|
||||
EtherSwitch router implementation (based on Dynamips c3745).
|
||||
This is legacy code, kept only to support topologies made with GNS3 < 1.2.2
|
||||
"""
|
||||
|
||||
import sys
|
||||
@@ -27,6 +28,7 @@ from gns3.node import Node
|
||||
|
||||
|
||||
class EtherSwitchRouter(Router):
|
||||
|
||||
"""
|
||||
EtherSwitch router.
|
||||
|
||||
@@ -64,6 +66,7 @@ class EtherSwitchRouter(Router):
|
||||
if not name:
|
||||
name = self.allocateName("ESW")
|
||||
|
||||
self._settings["name"] = name
|
||||
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)
|
||||
|
||||
@@ -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,34 +92,37 @@ 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():
|
||||
@@ -178,16 +133,15 @@ class FrameRelaySwitch(Node):
|
||||
"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 +151,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 +159,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 +167,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 +179,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 +195,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 +211,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 +242,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 +271,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 +285,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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
@@ -374,13 +397,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 +418,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,53 +436,39 @@ 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")
|
||||
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["path"] = path
|
||||
settings["image"] = path # os.path.basename(path)
|
||||
|
||||
else:
|
||||
del settings["name"]
|
||||
@@ -470,6 +479,25 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
|
||||
del settings["private_config"]
|
||||
del settings["image"]
|
||||
|
||||
if not node:
|
||||
startup_config = self.uiStartupConfigLineEdit.text().strip()
|
||||
if startup_config and 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")
|
||||
else:
|
||||
settings["startup_config"] = ""
|
||||
|
||||
private_config = self.uiPrivateConfigLineEdit.text().strip()
|
||||
if private_config and 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")
|
||||
else:
|
||||
settings["private_config"] = ""
|
||||
|
||||
# get the platform and chassis if applicable
|
||||
platform = settings["platform"]
|
||||
if "chassis" in settings:
|
||||
|
||||
@@ -22,17 +22,18 @@ 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_thread import FileCopyThread
|
||||
|
||||
from .. import Dynamips
|
||||
from ..settings import IOS_ROUTER_SETTINGS
|
||||
@@ -43,7 +44,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 +99,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 +114,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]["path"],
|
||||
'images/' + os.path.relpath(self._ios_routers[key]["path"],
|
||||
self._main_window.settings().imagesDirPath())
|
||||
)]
|
||||
)
|
||||
upload_thread.completed.connect(self._imageUploadComplete)
|
||||
upload_thread.start()
|
||||
except Exception as e:
|
||||
@@ -181,6 +172,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 +183,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 +194,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 +212,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,7 +250,7 @@ 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):
|
||||
@@ -273,21 +268,25 @@ 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))
|
||||
thread = FileCopyThread(path, destination_path)
|
||||
progress_dialog = ProgressDialog(thread, "IOS image", "Copying {}".format(os.path.basename(path)), "Cancel", busy=True, parent=parent)
|
||||
thread.deleteLater()
|
||||
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
|
||||
|
||||
@@ -317,46 +316,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.
|
||||
@@ -481,16 +440,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
|
||||
|
||||
@@ -21,63 +21,20 @@ 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,
|
||||
}
|
||||
@@ -100,9 +57,9 @@ 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",
|
||||
@@ -137,14 +94,39 @@ IOS_ROUTER_SETTING_TYPES = {
|
||||
}
|
||||
|
||||
# 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 +173,7 @@ C7200_PAS = (
|
||||
IO_C7200 = ("C7200-IO-FE",
|
||||
"C7200-IO-2FE",
|
||||
"C7200-IO-GE-E"
|
||||
)
|
||||
)
|
||||
|
||||
"""
|
||||
Build the adapter compatibility matrix:
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>419</width>
|
||||
<height>522</height>
|
||||
<width>435</width>
|
||||
<height>510</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>
|
||||
|
||||
@@ -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: Wed Dec 24 17:35:25 2014
|
||||
# by: PyQt4 UI code generator 4.10.4
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
@@ -17,16 +17,19 @@ 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_iosRouterConfigPageWidget(object):
|
||||
|
||||
def setupUi(self, iosRouterConfigPageWidget):
|
||||
iosRouterConfigPageWidget.setObjectName(_fromUtf8("iosRouterConfigPageWidget"))
|
||||
iosRouterConfigPageWidget.resize(419, 522)
|
||||
iosRouterConfigPageWidget.resize(435, 510)
|
||||
self.vboxlayout = QtGui.QVBoxLayout(iosRouterConfigPageWidget)
|
||||
self.vboxlayout.setObjectName(_fromUtf8("vboxlayout"))
|
||||
self.uiTabWidget = QtGui.QTabWidget(iosRouterConfigPageWidget)
|
||||
@@ -566,11 +569,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))
|
||||
@@ -635,4 +638,3 @@ class Ui_iosRouterConfigPageWidget(object):
|
||||
self.uiSensor4Label.setText(_translate("iosRouterConfigPageWidget", "NPE outlet:", None))
|
||||
self.uiSensor4SpinBox.setSuffix(_translate("iosRouterConfigPageWidget", " C", None))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiEnvironmentPageWidget), _translate("iosRouterConfigPageWidget", "Environment", None))
|
||||
|
||||
|
||||
@@ -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>&Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</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">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/ios_router_wizard.ui'
|
||||
#
|
||||
# Created: Wed Oct 22 16:46:37 2014
|
||||
# Created: Fri Mar 13 11:09:58 2015
|
||||
# by: PyQt4 UI code generator 4.10.4
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
@@ -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"))
|
||||
@@ -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,6 +302,7 @@ 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.uiRamLabel.setText(_translate("IOSRouterWizard", "Default RAM:", 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))
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ from .decompress_ios import decompressIOS
|
||||
|
||||
|
||||
class DecompressIOSThread(QtCore.QThread):
|
||||
|
||||
"""
|
||||
Thread to decompress an IOS image.
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ from gns3.qt import QtGui
|
||||
|
||||
|
||||
class TreeWidgetItem(QtGui.QTreeWidgetItem):
|
||||
|
||||
"""
|
||||
QTreeWidgetItem reimplementation to allow numeric sort.
|
||||
"""
|
||||
|
||||
@@ -19,11 +19,13 @@
|
||||
IOU module implementation.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from gns3.qt import QtCore, QtGui
|
||||
from gns3.node import Node
|
||||
from gns3.local_server_config import LocalServerConfig
|
||||
from gns3.local_config import LocalConfig
|
||||
|
||||
from ..module import Module
|
||||
from ..module_error import ModuleError
|
||||
@@ -36,6 +38,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IOU(Module):
|
||||
|
||||
"""
|
||||
IOU module.
|
||||
"""
|
||||
@@ -46,9 +49,6 @@ class IOU(Module):
|
||||
self._settings = {}
|
||||
self._nodes = []
|
||||
self._iou_devices = {}
|
||||
self._servers = []
|
||||
self._working_dir = ""
|
||||
self._images_dir = ""
|
||||
self._iou_images_cache = {}
|
||||
|
||||
# load the settings
|
||||
@@ -60,133 +60,99 @@ class IOU(Module):
|
||||
Loads the settings from the persistent settings file.
|
||||
"""
|
||||
|
||||
# load the settings
|
||||
local_config = LocalConfig.instance()
|
||||
|
||||
# restore the IOU settings from QSettings (for backward compatibility)
|
||||
legacy_settings = {}
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup(self.__class__.__name__)
|
||||
for name, value in IOU_SETTINGS.items():
|
||||
self._settings[name] = settings.value(name, value, type=IOU_SETTING_TYPES[name])
|
||||
for name in IOU_SETTINGS.keys():
|
||||
if settings.contains(name):
|
||||
legacy_settings[name] = settings.value(name, type=IOU_SETTING_TYPES[name])
|
||||
if "iourc" in legacy_settings:
|
||||
legacy_settings["iourc_path"] = legacy_settings["iourc"]
|
||||
del legacy_settings["iourc"]
|
||||
settings.remove("")
|
||||
settings.endGroup()
|
||||
|
||||
if legacy_settings:
|
||||
local_config.saveSectionSettings(self.__class__.__name__, legacy_settings)
|
||||
self._settings = local_config.loadSectionSettings(self.__class__.__name__, IOU_SETTINGS)
|
||||
|
||||
if sys.platform.startswith("linux") and not os.path.exists(self._settings["iouyap_path"]):
|
||||
iouyap_path = shutil.which("iouyap")
|
||||
if iouyap_path:
|
||||
self._settings["iouyap_path"] = iouyap_path
|
||||
|
||||
# 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 = {
|
||||
"iourc_path": self._settings["iourc_path"],
|
||||
"iouyap_path": self._settings["iouyap_path"],
|
||||
"license_check": self._settings["license_check"]
|
||||
}
|
||||
config = LocalServerConfig.instance()
|
||||
config.saveSettings(self.__class__.__name__, server_settings)
|
||||
|
||||
def _loadIOUDevices(self):
|
||||
"""
|
||||
Load the IOU devices from the persistent settings file.
|
||||
"""
|
||||
|
||||
local_config = LocalConfig.instance()
|
||||
|
||||
# restore the VirtualBox settings from QSettings (for backward compatibility)
|
||||
iou_devices = []
|
||||
# load the settings
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup("IOUDevices")
|
||||
|
||||
# load the IOU images
|
||||
# load the IOU devices
|
||||
size = settings.beginReadArray("iou_device")
|
||||
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._iou_devices or not name or not server:
|
||||
continue
|
||||
self._iou_devices[key] = {}
|
||||
device = {}
|
||||
for setting_name, default_value in IOU_DEVICE_SETTINGS.items():
|
||||
self._iou_devices[key][setting_name] = settings.value(setting_name, default_value, IOU_DEVICE_SETTING_TYPES[setting_name])
|
||||
|
||||
device[setting_name] = settings.value(setting_name, default_value, IOU_DEVICE_SETTING_TYPES[setting_name])
|
||||
iou_devices.append(device)
|
||||
settings.endArray()
|
||||
settings.remove("")
|
||||
settings.endGroup()
|
||||
|
||||
if iou_devices:
|
||||
local_config.saveSectionSettings(self.__class__.__name__, {"devices": iou_devices})
|
||||
|
||||
settings = local_config.settings()
|
||||
if "devices" in settings.get(self.__class__.__name__, {}):
|
||||
for device in settings[self.__class__.__name__]["devices"]:
|
||||
name = device.get("name")
|
||||
server = device.get("server")
|
||||
key = "{server}:{name}".format(server=server, name=name)
|
||||
if key in self._iou_devices or not name or not server:
|
||||
continue
|
||||
device_settings = IOU_DEVICE_SETTINGS.copy()
|
||||
device_settings.update(device)
|
||||
self._iou_devices[key] = device_settings
|
||||
|
||||
# keep things sync
|
||||
self._saveIOUDevices()
|
||||
|
||||
def _saveIOUDevices(self):
|
||||
"""
|
||||
Saves the IOU devices to the persistent settings file.
|
||||
"""
|
||||
|
||||
# save the settings
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup("IOUDevices")
|
||||
settings.remove("")
|
||||
|
||||
# save the IOU images
|
||||
settings.beginWriteArray("iou_device", len(self._iou_devices))
|
||||
index = 0
|
||||
for ios_image in self._iou_devices.values():
|
||||
settings.setArrayIndex(index)
|
||||
for name, value in ios_image.items():
|
||||
settings.setValue(name, value)
|
||||
index += 1
|
||||
settings.endArray()
|
||||
settings.endGroup()
|
||||
|
||||
def setProjectFilesDir(self, path):
|
||||
"""
|
||||
Sets the project files directory path this module.
|
||||
|
||||
:param path: path to the local project files directory
|
||||
"""
|
||||
|
||||
self._working_dir = path
|
||||
log.info("local working directory for IOU 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, "IOU")
|
||||
|
||||
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 IOU 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 IOU 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__, {"devices": list(self._iou_devices.values())})
|
||||
|
||||
def addNode(self, node):
|
||||
"""
|
||||
@@ -235,25 +201,6 @@ class IOU(Module):
|
||||
|
||||
return self._settings
|
||||
|
||||
def _base64iourc(self, iourc_path):
|
||||
"""
|
||||
Get the content of the IOURC file base64 encoded.
|
||||
|
||||
:param config_path: path to the iourc file.
|
||||
|
||||
:returns: base64 encoded string
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(iourc_path, "r", errors="replace") as f:
|
||||
log.info("opening iourc file: {}".format(iourc_path))
|
||||
config = f.read()
|
||||
encoded = "".join(base64.encodestring(config.encode("utf-8")).decode("utf-8").split())
|
||||
return encoded
|
||||
except OSError as e:
|
||||
log.warn("could not base64 encode {}: {}".format(iourc_path, e))
|
||||
return ""
|
||||
|
||||
def setSettings(self, settings):
|
||||
"""
|
||||
Sets the module settings
|
||||
@@ -261,82 +208,25 @@ class IOU(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:
|
||||
if "iourc" in params:
|
||||
# encode the iourc file in base64
|
||||
params["iourc"] = self._base64iourc(params["iourc"])
|
||||
for server in self._servers:
|
||||
# send the local working directory only if this is a local server
|
||||
if server.isLocal():
|
||||
params.update({"working_dir": self._working_dir})
|
||||
else:
|
||||
if "iouyap" in params:
|
||||
del params["iouyap"] # do not send iouyap 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("iou.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 IOU settings to server {}:{}".format(server.host, server.port))
|
||||
params = self._settings.copy()
|
||||
|
||||
# encode the iourc file in base64
|
||||
params["iourc"] = self._base64iourc(params["iourc"])
|
||||
|
||||
# send the local working directory only if this is a local server
|
||||
if server.isLocal():
|
||||
params.update({"working_dir": self._working_dir})
|
||||
else:
|
||||
if "iouyap" in params:
|
||||
del params["iouyap"] # do not send iouyap 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("iou.settings", params)
|
||||
|
||||
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 self._settings["iourc"] or not os.path.isfile(self._settings["iourc"]):
|
||||
if server.isLocal() and (not self._settings["iourc_path"] or not os.path.isfile(self._settings["iourc_path"])):
|
||||
raise ModuleError("The path to IOURC must be configured")
|
||||
|
||||
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):
|
||||
"""
|
||||
@@ -402,27 +292,8 @@ class IOU(Module):
|
||||
"""
|
||||
|
||||
log.info("IOU module reset")
|
||||
for server in self._servers:
|
||||
if server.connected():
|
||||
server.send_notification("iou.reset")
|
||||
self._servers.clear()
|
||||
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 "id" in params:
|
||||
for node in self._nodes:
|
||||
if node.id() == params["id"]:
|
||||
message = "node {}: {}".format(node.name(), params["message"])
|
||||
self.notification_signal.emit(message, params["details"])
|
||||
node.stop()
|
||||
|
||||
def exportConfigs(self, directory):
|
||||
"""
|
||||
Exports all configs for all nodes to a directory.
|
||||
@@ -431,8 +302,8 @@ class IOU(Module):
|
||||
"""
|
||||
|
||||
for node in self._nodes:
|
||||
if hasattr(node, "exportConfig") and node.initialized():
|
||||
node.exportConfig(directory)
|
||||
if node.initialized():
|
||||
node.exportConfigToDirectory(directory)
|
||||
|
||||
def importConfigs(self, directory):
|
||||
"""
|
||||
@@ -442,8 +313,8 @@ class IOU(Module):
|
||||
"""
|
||||
|
||||
for node in self._nodes:
|
||||
if hasattr(node, "importConfig") and node.initialized():
|
||||
node.importConfig(directory)
|
||||
if node.initialized():
|
||||
node.importConfigFromDirectory(directory)
|
||||
|
||||
def findAlternativeIOUImage(self, image):
|
||||
"""
|
||||
@@ -524,7 +395,7 @@ class IOU(Module):
|
||||
"default_symbol": iou_device["default_symbol"],
|
||||
"hover_symbol": iou_device["hover_symbol"],
|
||||
"categories": [iou_device["category"]]
|
||||
}
|
||||
}
|
||||
)
|
||||
return nodes
|
||||
|
||||
|
||||
@@ -21,11 +21,12 @@ Wizard for IOU devices.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pkg_resources
|
||||
|
||||
from gns3.qt import QtGui
|
||||
from gns3.node import Node
|
||||
from gns3.servers import Servers
|
||||
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.iou_device_wizard_ui import Ui_IOUDeviceWizard
|
||||
@@ -33,6 +34,7 @@ from .. import IOU
|
||||
|
||||
|
||||
class IOUDeviceWizard(QtGui.QWizard, Ui_IOUDeviceWizard):
|
||||
|
||||
"""
|
||||
Wizard to create an IOU device.
|
||||
|
||||
@@ -76,6 +78,9 @@ class IOUDeviceWizard(QtGui.QWizard, Ui_IOUDeviceWizard):
|
||||
if not ENABLE_CLOUD:
|
||||
self.uiCloudRadioButton.hide()
|
||||
|
||||
# location of the base config templates
|
||||
self._base_iou_l2_config_template = get_resource(os.path.join("configs", "iou_l2_base_initial-config.txt"))
|
||||
self._base_iou_l3_config_template = get_resource(os.path.join("configs", "iou_l3_base_initial-config.txt"))
|
||||
|
||||
def _remoteServerToggledSlot(self, checked):
|
||||
"""
|
||||
@@ -154,6 +159,10 @@ class IOUDeviceWizard(QtGui.QWizard, Ui_IOUDeviceWizard):
|
||||
if iou_device["name"] == name:
|
||||
QtGui.QMessageBox.critical(self, "Name", "{} is already used, please choose another name".format(name))
|
||||
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 IOS on UNIX preferences")
|
||||
return False
|
||||
return True
|
||||
|
||||
def getSettings(self):
|
||||
@@ -165,41 +174,37 @@ class IOUDeviceWizard(QtGui.QWizard, Ui_IOUDeviceWizard):
|
||||
|
||||
path = self.uiIOUImageLineEdit.text()
|
||||
|
||||
initial_config = ""
|
||||
if self.uiTypeComboBox.currentText() == "L2 image":
|
||||
# set the default L2 base initial-config
|
||||
resource_name = "configs/iou_l2_base_initial-config.txt"
|
||||
if hasattr(sys, "frozen") and os.path.isfile(resource_name):
|
||||
initial_config = os.path.normpath(resource_name)
|
||||
elif pkg_resources.resource_exists("gns3", resource_name):
|
||||
iou_base_config_path = pkg_resources.resource_filename("gns3", resource_name)
|
||||
initial_config = os.path.normpath(iou_base_config_path)
|
||||
default_base_config = get_default_base_config(self._base_iou_l2_config_template)
|
||||
if default_base_config:
|
||||
initial_config = default_base_config
|
||||
default_symbol = ":/symbols/multilayer_switch.normal.svg"
|
||||
hover_symbol = ":/symbols/multilayer_switch.selected.svg"
|
||||
category = Node.switches
|
||||
ethernet_adapters = 4
|
||||
serial_adapters = 0
|
||||
else:
|
||||
# set the default L3 base initial-config
|
||||
resource_name = "configs/iou_l3_base_initial-config.txt"
|
||||
if hasattr(sys, "frozen") and os.path.isfile(resource_name):
|
||||
initial_config = os.path.normpath(resource_name)
|
||||
elif pkg_resources.resource_exists("gns3", resource_name):
|
||||
iou_base_config_path = pkg_resources.resource_filename("gns3", resource_name)
|
||||
initial_config = os.path.normpath(iou_base_config_path)
|
||||
default_base_config = get_default_base_config(self._base_iou_l3_config_template)
|
||||
if default_base_config:
|
||||
initial_config = default_base_config
|
||||
default_symbol = ":/symbols/router.normal.svg"
|
||||
hover_symbol = ":/symbols/router.selected.svg"
|
||||
category = Node.routers
|
||||
ethernet_adapters = 2
|
||||
serial_adapters = 2
|
||||
|
||||
if IOU.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, "IOU device", "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"
|
||||
|
||||
settings = {
|
||||
@@ -207,6 +212,8 @@ class IOUDeviceWizard(QtGui.QWizard, Ui_IOUDeviceWizard):
|
||||
"path": path,
|
||||
"image": os.path.basename(path),
|
||||
"initial_config": initial_config,
|
||||
"ethernet_adapters": ethernet_adapters,
|
||||
"serial_adapters": serial_adapters,
|
||||
"default_symbol": default_symbol,
|
||||
"category": category,
|
||||
"hover_symbol": hover_symbol,
|
||||
|
||||
@@ -20,9 +20,10 @@ IOU device implementation.
|
||||
"""
|
||||
|
||||
import os
|
||||
import base64
|
||||
import re
|
||||
from gns3.vm import VM
|
||||
from gns3.node import Node
|
||||
from gns3.ports.port import Port
|
||||
from gns3.servers import Servers
|
||||
from gns3.ports.ethernet_port import EthernetPort
|
||||
from gns3.ports.serial_port import SerialPort
|
||||
from gns3.utils.normalize_filename import normalize_filename
|
||||
@@ -32,19 +33,23 @@ import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IOUDevice(Node):
|
||||
class IOUDevice(VM):
|
||||
|
||||
"""
|
||||
IOU device.
|
||||
|
||||
: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)
|
||||
URL_PREFIX = "iou"
|
||||
|
||||
def __init__(self, module, server, project):
|
||||
VM.__init__(self, server, server, project)
|
||||
|
||||
log.info("IOU instance is being created")
|
||||
self._iou_id = None
|
||||
self._vm_id = None
|
||||
self._defaults = {}
|
||||
self._inital_settings = None
|
||||
self._loading = False
|
||||
@@ -62,7 +67,7 @@ class IOUDevice(Node):
|
||||
"serial_adapters": IOU_DEVICE_SETTINGS["serial_adapters"],
|
||||
"console": None}
|
||||
|
||||
#self._occupied_slots = []
|
||||
# self._occupied_slots = []
|
||||
self._addAdapters(2, 2)
|
||||
|
||||
# save the default settings
|
||||
@@ -78,19 +83,21 @@ class IOUDevice(Node):
|
||||
|
||||
nb_adapters = nb_ethernet_adapters + nb_serial_adapters
|
||||
for slot_number in range(0, nb_adapters):
|
||||
# if slot_number in self._occupied_slots:
|
||||
# continue
|
||||
# if slot_number in self._occupied_slots:
|
||||
# continue
|
||||
for port_number in range(0, 4):
|
||||
if slot_number < nb_ethernet_adapters:
|
||||
port = EthernetPort
|
||||
else:
|
||||
port = SerialPort
|
||||
port_name = port.longNameType() + str(slot_number) + "/" + str(port_number)
|
||||
short_name = port.shortNameType() + str(slot_number) + "/" + str(port_number)
|
||||
new_port = port(port_name)
|
||||
new_port.setShortName(short_name)
|
||||
new_port.setPortNumber(port_number)
|
||||
new_port.setSlotNumber(slot_number)
|
||||
new_port.setAdapterNumber(slot_number)
|
||||
new_port.setPacketCaptureSupported(True)
|
||||
#self._occupied_slots.append(slot_number)
|
||||
# self._occupied_slots.append(slot_number)
|
||||
self._ports.append(new_port)
|
||||
log.debug("port {} has been added".format(port_name))
|
||||
|
||||
@@ -102,19 +109,20 @@ class IOUDevice(Node):
|
||||
"""
|
||||
|
||||
for port in self._ports.copy():
|
||||
if (port.slotNumber() >= nb_ethernet_adapters and port.linkType() == "Ethernet") or \
|
||||
(port.slotNumber() >= nb_serial_adapters and port.linkType() == "Serial"):
|
||||
#self._occupied_slots.remove(port.slotNumber())
|
||||
if (port.adapterNumber() >= nb_ethernet_adapters and port.linkType() == "Ethernet") or \
|
||||
(port.adapterNumber() >= nb_serial_adapters and port.linkType() == "Serial"):
|
||||
# self._occupied_slots.remove(port.adapterNumber())
|
||||
self._ports.remove(port)
|
||||
log.info("port {} has been removed".format(port.name()))
|
||||
|
||||
def setup(self, iou_path, name=None, console=None, iou_id=None, initial_settings={}, base_name="IOU"):
|
||||
def setup(self, iou_path, name=None, console=None, vm_id=None, initial_settings={}, base_name="IOU", initial_config=None):
|
||||
"""
|
||||
Setups this IOU device.
|
||||
|
||||
:param iou_path: path to an IOU image
|
||||
:param name: optional name
|
||||
:param console: optional TCP console port
|
||||
:param initial_config: path to initial configuration file
|
||||
"""
|
||||
|
||||
# let's create a unique name if none has been chosen
|
||||
@@ -125,11 +133,12 @@ class IOUDevice(Node):
|
||||
self.error_signal.emit(self.id(), "could not allocate a name for this IOU device")
|
||||
return
|
||||
|
||||
self._settings["name"] = name
|
||||
params = {"name": name,
|
||||
"path": iou_path}
|
||||
|
||||
if iou_id:
|
||||
params["iou_id"] = iou_id
|
||||
if vm_id:
|
||||
params["vm_id"] = vm_id
|
||||
|
||||
if console:
|
||||
params["console"] = self._settings["console"] = console
|
||||
@@ -141,9 +150,12 @@ class IOUDevice(Node):
|
||||
if initial_settings:
|
||||
self._inital_settings = initial_settings
|
||||
|
||||
self._server.send_message("iou.create", params, self._setupCallback)
|
||||
if initial_config:
|
||||
params["initial_config_content"] = self._readBaseConfig(initial_config)
|
||||
|
||||
def _setupCallback(self, result, error=False):
|
||||
self.httpPost("/iou/vms", self._setupCallback, body=params)
|
||||
|
||||
def _setupCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for setup.
|
||||
|
||||
@@ -153,18 +165,21 @@ class IOUDevice(Node):
|
||||
|
||||
if error:
|
||||
log.error("error while setting up {}: {}".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"])
|
||||
return
|
||||
|
||||
self._iou_id = result["id"]
|
||||
if not self._iou_id:
|
||||
self._vm_id = result["vm_id"]
|
||||
if not self._vm_id:
|
||||
self.error_signal.emit(self.id(), "returned ID from server is null")
|
||||
return
|
||||
|
||||
# update the settings using the defaults sent by the server
|
||||
for name, value in result.items():
|
||||
if name in self._settings and self._settings[name] != value:
|
||||
log.info("IOU instance setting up and updating {} from '{}' to '{}'".format(name, self._settings[name], value))
|
||||
log.info("IOU instance {} setting up and updating {} from '{}' to '{}'".format(self.name(),
|
||||
name,
|
||||
self._settings[name],
|
||||
value))
|
||||
self._settings[name] = value
|
||||
|
||||
# update the node with setup initial settings if any
|
||||
@@ -178,55 +193,6 @@ class IOUDevice(Node):
|
||||
self.created_signal.emit(self.id())
|
||||
self._module.addNode(self)
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Deletes this IOU instance.
|
||||
"""
|
||||
|
||||
log.debug("IOU device {} is being deleted".format(self.name()))
|
||||
# first delete all the links attached to this node
|
||||
self.delete_links_signal.emit()
|
||||
if self._iou_id:
|
||||
self._server.send_message("iou.delete", {"id": self._iou_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)
|
||||
|
||||
def _base64Config(self, config_path):
|
||||
"""
|
||||
Get the base64 encoded config from a file.
|
||||
|
||||
:param config_path: path to the configuration file.
|
||||
|
||||
:returns: base64 encoded string
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(config_path, "r", errors="replace") as f:
|
||||
log.info("opening configuration file: {}".format(config_path))
|
||||
config = f.read()
|
||||
config = '!\n' + config.replace('\r', "")
|
||||
encoded = "".join(base64.encodestring(config.encode("utf-8")).decode("utf-8").split())
|
||||
return encoded
|
||||
except OSError as e:
|
||||
log.warn("could not base64 encode {}: {}".format(config_path, e))
|
||||
return ""
|
||||
|
||||
def update(self, new_settings):
|
||||
"""
|
||||
Updates the settings for this IOU device.
|
||||
@@ -238,19 +204,19 @@ class IOUDevice(Node):
|
||||
self.error_signal.emit(self.id(), 'Name "{}" is already used by another node'.format(new_settings["name"]))
|
||||
return
|
||||
|
||||
params = {"id": self._iou_id}
|
||||
params = {}
|
||||
for name, value in new_settings.items():
|
||||
if name in self._settings and self._settings[name] != value:
|
||||
params[name] = value
|
||||
|
||||
if "initial_config" in new_settings and self._settings["initial_config"] != new_settings["initial_config"] \
|
||||
and not self.server().isLocal() and os.path.isfile(new_settings["initial_config"]):
|
||||
params["initial_config_base64"] = self._base64Config(new_settings["initial_config"])
|
||||
if "initial_config" in new_settings and self._settings["initial_config"] != new_settings["initial_config"]:
|
||||
params["initial_config_content"] = self._readBaseConfig(new_settings["initial_config"])
|
||||
del params["initial_config"]
|
||||
|
||||
log.debug("{} is updating settings: {}".format(self.name(), params))
|
||||
self._server.send_message("iou.update", params, self._updateCallback)
|
||||
self.httpPut("/iou/vms/{vm_id}".format(vm_id=self._vm_id), self._updateCallback, body=params)
|
||||
|
||||
def _updateCallback(self, result, error=False):
|
||||
def _updateCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for update.
|
||||
|
||||
@@ -260,7 +226,7 @@ class IOUDevice(Node):
|
||||
|
||||
if error:
|
||||
log.error("error while deleting {}: {}".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"])
|
||||
return
|
||||
|
||||
updated = False
|
||||
@@ -278,7 +244,7 @@ class IOUDevice(Node):
|
||||
|
||||
if nb_adapters_changed:
|
||||
log.debug("number of adapters has changed: Ethernet={} Serial={}".format(self._settings["ethernet_adapters"], self._settings["serial_adapters"]))
|
||||
#TODO: dynamically add/remove adapters
|
||||
# TODO: dynamically add/remove adapters
|
||||
self._ports.clear()
|
||||
self._addAdapters(self._settings["ethernet_adapters"], self._settings["serial_adapters"])
|
||||
|
||||
@@ -292,178 +258,6 @@ class IOUDevice(Node):
|
||||
log.info("IOU device {} has been updated".format(self.name()))
|
||||
self.updated_signal.emit()
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Starts this IOU instance.
|
||||
"""
|
||||
|
||||
if self.status() == Node.started:
|
||||
log.debug("{} is already running".format(self.name()))
|
||||
return
|
||||
|
||||
log.debug("{} is starting".format(self.name()))
|
||||
self._server.send_message("iou.start", {"id": self._iou_id}, self._startCallback)
|
||||
|
||||
def _startCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for start.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while starting {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
else:
|
||||
log.info("{} has started".format(self.name()))
|
||||
self.setStatus(Node.started)
|
||||
for port in self._ports:
|
||||
# set ports as started
|
||||
port.setStatus(Port.started)
|
||||
self.started_signal.emit()
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stops this IOU instance.
|
||||
"""
|
||||
|
||||
if self.status() == Node.stopped:
|
||||
log.debug("{} is already stopped".format(self.name()))
|
||||
return
|
||||
|
||||
log.debug("{} is stopping".format(self.name()))
|
||||
self._server.send_message("iou.stop", {"id": self._iou_id}, self._stopCallback)
|
||||
|
||||
def _stopCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for stop.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while stopping {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
else:
|
||||
log.info("{} has stopped".format(self.name()))
|
||||
self.setStatus(Node.stopped)
|
||||
for port in self._ports:
|
||||
# set ports as stopped
|
||||
port.setStatus(Port.stopped)
|
||||
self.stopped_signal.emit()
|
||||
|
||||
def reload(self):
|
||||
"""
|
||||
Reloads this IOU instance.
|
||||
"""
|
||||
|
||||
log.debug("{} is being reloaded".format(self.name()))
|
||||
self._server.send_message("iou.reload", {"id": self._iou_id}, self._reloadCallback)
|
||||
|
||||
def _reloadCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for reload.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while suspending {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
else:
|
||||
log.info("{} has reloaded".format(self.name()))
|
||||
|
||||
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("iou.allocate_udp_port", {"id": self._iou_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 IOU instance.
|
||||
|
||||
:param port: Port instance
|
||||
:param nio: NIO instance
|
||||
"""
|
||||
|
||||
params = {"id": self._iou_id,
|
||||
"slot": port.slotNumber(),
|
||||
"port": port.portNumber(),
|
||||
"port_id": port.id()}
|
||||
|
||||
params["nio"] = self.getNIOInfo(nio)
|
||||
log.debug("{} is adding an {}: {}".format(self.name(), nio, params))
|
||||
self._server.send_message("iou.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 IOU instance
|
||||
|
||||
:param port: Port instance
|
||||
"""
|
||||
|
||||
params = {"id": self._iou_id,
|
||||
"port": port.portNumber(),
|
||||
"slot": port.slotNumber()}
|
||||
|
||||
log.debug("{} is deleting an NIO: {}".format(self.name(), params))
|
||||
self._server.send_message("iou.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.
|
||||
@@ -473,37 +267,41 @@ class IOUDevice(Node):
|
||||
:param data_link_type: PCAP data link type
|
||||
"""
|
||||
|
||||
params = {"id": self._iou_id,
|
||||
"port_id": port.id(),
|
||||
"port": port.portNumber(),
|
||||
"slot": port.slotNumber(),
|
||||
"capture_file_name": capture_file_name,
|
||||
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._server.send_message("iou.start_capture", params, self._startPacketCaptureCallback)
|
||||
self.httpPost("/iou/vms/{vm_id}/adapters/{adapter_number}/ports/{port_number}/start_capture".format(
|
||||
vm_id=self._vm_id,
|
||||
adapter_number=port.adapterNumber(),
|
||||
port_number=port.portNumber()
|
||||
),
|
||||
self._startPacketCaptureCallback,
|
||||
body=params,
|
||||
context={
|
||||
"port": port
|
||||
})
|
||||
|
||||
def _startPacketCaptureCallback(self, result, error=False):
|
||||
def _startPacketCaptureCallback(self, result, error=False, context={}, **kwargs):
|
||||
"""
|
||||
Callback for starting a packet capture.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
:param context: Pass a context to the response callback
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while starting capture {}: {}".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:
|
||||
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
|
||||
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):
|
||||
"""
|
||||
@@ -512,32 +310,34 @@ class IOUDevice(Node):
|
||||
:param port: Port instance
|
||||
"""
|
||||
|
||||
params = {"id": self._iou_id,
|
||||
"port_id": port.id(),
|
||||
"port": port.portNumber(),
|
||||
"slot": port.slotNumber()}
|
||||
log.debug("{} is stopping a packet capture on {}".format(self.name(), port.name()))
|
||||
self.httpPost("/iou/vms/{vm_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture".format(
|
||||
vm_id=self._vm_id,
|
||||
adapter_number=port.adapterNumber(),
|
||||
port_number=port.portNumber()
|
||||
),
|
||||
self._stopPacketCaptureCallback,
|
||||
context={
|
||||
"port": port
|
||||
})
|
||||
|
||||
log.debug("{} is stopping a packet capture on {}: {}".format(self.name(), port.name(), params))
|
||||
self._server.send_message("iou.stop_capture", params, self._stopPacketCaptureCallback)
|
||||
|
||||
def _stopPacketCaptureCallback(self, result, error=False):
|
||||
def _stopPacketCaptureCallback(self, result, error=False, context={}, **kwargs):
|
||||
"""
|
||||
Callback for stopping a packet capture.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
:param context: Pass a context to the response callback
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while stopping capture {}: {}".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:
|
||||
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
|
||||
port = context["port"]
|
||||
log.info("{} has successfully stopped capturing packets on {}".format(self.name(), port.name()))
|
||||
port.stopPacketCapture()
|
||||
self.updated_signal.emit()
|
||||
|
||||
def info(self):
|
||||
"""
|
||||
@@ -558,14 +358,14 @@ class IOUDevice(Node):
|
||||
nvram=self._settings["nvram"])
|
||||
|
||||
info = """Device {name} is {state}
|
||||
Node ID is {id}, server's IOU device ID is {iou_id}
|
||||
Node ID is {id}, server's IOU device ID is {vm_id}
|
||||
Hardware is Cisco IOU generic device with {memories_info}
|
||||
Device's server runs on {host}:{port}, console is on port {console}
|
||||
Image is {image_name}
|
||||
{nb_ethernet} Ethernet adapters and {nb_serial} serial adapters installed
|
||||
""".format(name=self.name(),
|
||||
id=self.id(),
|
||||
iou_id=self._iou_id,
|
||||
vm_id=self._vm_id,
|
||||
state=state,
|
||||
memories_info=memories_info,
|
||||
host=self._server.host,
|
||||
@@ -594,7 +394,7 @@ class IOUDevice(Node):
|
||||
"""
|
||||
|
||||
iou = {"id": self.id(),
|
||||
"iou_id": self._iou_id,
|
||||
"vm_id": self._vm_id,
|
||||
"type": self.__class__.__name__,
|
||||
"description": str(self),
|
||||
"properties": {},
|
||||
@@ -611,17 +411,17 @@ class IOUDevice(Node):
|
||||
for port in self._ports:
|
||||
ports.append(port.dump())
|
||||
|
||||
# make the IOU path relative
|
||||
image_path = iou["properties"]["path"]
|
||||
if self.server().isLocal():
|
||||
if os.path.commonprefix([image_path, self._module.imageFilesDir()]) == self._module.imageFilesDir():
|
||||
# save only the image name if it is stored the images directory
|
||||
iou["properties"]["path"] = os.path.basename(image_path)
|
||||
else:
|
||||
iou["properties"]["path"] = image_path
|
||||
|
||||
return iou
|
||||
|
||||
def _imageFilesDir(self):
|
||||
"""
|
||||
Returns the location of IOU images.
|
||||
"""
|
||||
|
||||
servers = Servers.instance()
|
||||
local_server = servers.localServerSettings()
|
||||
return os.path.join(local_server["images_path"], "IOU")
|
||||
|
||||
def load(self, node_info):
|
||||
"""
|
||||
Loads an IOU device representation
|
||||
@@ -631,39 +431,41 @@ class IOUDevice(Node):
|
||||
"""
|
||||
|
||||
self.node_info = node_info
|
||||
iou_id = node_info.get("iou_id")
|
||||
# for backward compatibility
|
||||
vm_id = node_info.get("iou_id")
|
||||
if not vm_id:
|
||||
vm_id = node_info["vm_id"]
|
||||
settings = node_info["properties"]
|
||||
name = settings.pop("name")
|
||||
path = settings.pop("path")
|
||||
|
||||
if self.server().isLocal():
|
||||
# check and update the path to use the image in the images directory
|
||||
updated_path = os.path.join(self._module.imageFilesDir(), path)
|
||||
updated_path = os.path.join(self._imageFilesDir(), path)
|
||||
if os.path.isfile(updated_path):
|
||||
path = updated_path
|
||||
elif not os.path.isfile(path):
|
||||
path = self._module.findAlternativeIOUImage(path)
|
||||
|
||||
console = settings.pop("console")
|
||||
console = settings.pop("console", None)
|
||||
self.updated_signal.connect(self._updatePortSettings)
|
||||
# block the created signal, it will be triggered when loading is completely done
|
||||
self._loading = True
|
||||
log.info("iou device {} is loading".format(name))
|
||||
self.setName(name)
|
||||
self.setup(path, name, console, iou_id, settings)
|
||||
self.setup(path, name, console, vm_id, settings)
|
||||
|
||||
def _updatePortSettings(self):
|
||||
"""
|
||||
Updates port settings when loading a topology.
|
||||
"""
|
||||
|
||||
self.updated_signal.disconnect(self._updatePortSettings)
|
||||
# update the port with the correct names and IDs
|
||||
if "ports" in self.node_info:
|
||||
ports = self.node_info["ports"]
|
||||
for topology_port in ports:
|
||||
for port in self._ports:
|
||||
if topology_port["port_number"] == port.portNumber() and topology_port["slot_number"] == port.slotNumber():
|
||||
if topology_port["port_number"] == port.portNumber() and (topology_port.get("adapter_number", None) == port.adapterNumber() or topology_port.get("slot_number", None) == port.adapterNumber()):
|
||||
port.setName(topology_port["name"])
|
||||
port.setId(topology_port["id"])
|
||||
|
||||
@@ -675,42 +477,91 @@ class IOUDevice(Node):
|
||||
self._inital_settings = None
|
||||
self._loading = False
|
||||
|
||||
def exportConfig(self, directory):
|
||||
def exportConfig(self, config_export_path):
|
||||
"""
|
||||
Exports the initial-config
|
||||
|
||||
:param config_export_path: export path for the initial-config
|
||||
"""
|
||||
|
||||
self.httpGet("/iou/vms/{vm_id}/initial_config".format(
|
||||
vm_id=self._vm_id,
|
||||
),
|
||||
self._exportConfigCallback,
|
||||
context={
|
||||
"path": config_export_path
|
||||
})
|
||||
|
||||
def _exportConfigCallback(self, result, error=False, context={}, **kwargs):
|
||||
"""
|
||||
Callback for exportConfig.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
path = context["path"]
|
||||
if error:
|
||||
log.error("error while exporting {} initial-config: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["message"])
|
||||
else:
|
||||
if "content" in result is not None:
|
||||
try:
|
||||
with open(path, "wb") as f:
|
||||
log.info("saving {} initial-config to {}".format(self.name(), path))
|
||||
f.write(result["content"].encode("utf-8"))
|
||||
except OSError as e:
|
||||
self.error_signal.emit(self.id(), "Could not export initial-config to {}: {}".format(path, e))
|
||||
|
||||
def exportConfigToDirectory(self, directory):
|
||||
"""
|
||||
Exports the initial-config to a directory.
|
||||
|
||||
:param directory: destination directory path
|
||||
"""
|
||||
|
||||
self._export_directory = directory
|
||||
self._server.send_message("iou.export_config", {"id": self._iou_id}, self._exportConfigCallback)
|
||||
self.httpGet("/iou/vms/{vm_id}/initial_config".format(
|
||||
vm_id=self._vm_id,
|
||||
),
|
||||
self._exportConfigToDirectoryCallback,
|
||||
context={
|
||||
"directory": directory
|
||||
})
|
||||
|
||||
def _exportConfigCallback(self, result, error=False):
|
||||
def _exportConfigToDirectoryCallback(self, result, error=False, context={}, **kwargs):
|
||||
"""
|
||||
Callback for exportConfigs.
|
||||
Callback for exportConfigToDirectory.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
export_directory = context["directory"]
|
||||
if error:
|
||||
log.error("error while exporting {} initial-config: {}".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 "initial_config_base64" in result:
|
||||
config_path = os.path.join(self._export_directory, normalize_filename(self.name())) + "_initial-config.cfg"
|
||||
config = base64.decodebytes(result["initial_config_base64"].encode("utf-8"))
|
||||
if "content" in result:
|
||||
config_path = os.path.join(export_directory, normalize_filename(self.name())) + "_initial-config.cfg"
|
||||
try:
|
||||
with open(config_path, "wb") as f:
|
||||
log.info("saving {} initial-config to {}".format(self.name(), config_path))
|
||||
f.write(config)
|
||||
f.write(result["content"].encode("utf-8"))
|
||||
except OSError as e:
|
||||
self.error_signal.emit(self.id(), "could not export initial-config to {}: {}".format(config_path, e))
|
||||
|
||||
self._export_directory = None
|
||||
def importConfig(self, path):
|
||||
"""
|
||||
Imports an initial-config.
|
||||
|
||||
def importConfig(self, directory):
|
||||
:param path: path to the initial config
|
||||
"""
|
||||
|
||||
new_settings = {"initial_config": path}
|
||||
self.update(new_settings)
|
||||
|
||||
def importConfigFromDirectory(self, directory):
|
||||
"""
|
||||
Imports an initial-config from a directory.
|
||||
|
||||
@@ -773,6 +624,23 @@ class IOUDevice(Node):
|
||||
from .pages.iou_device_configuration_page import iouDeviceConfigurationPage
|
||||
return iouDeviceConfigurationPage
|
||||
|
||||
@staticmethod
|
||||
def validateHostname(hostname):
|
||||
"""
|
||||
Checks if the hostname is valid.
|
||||
|
||||
:param hostname: hostname to check
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
# 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.
|
||||
if re.search(r"""^[\-\w]+$""", hostname) and len(hostname) <= 63:
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def defaultSymbol():
|
||||
"""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user