mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-05-29 15:00:31 +03:00
Compare commits
662 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b86c24175 | ||
|
|
0ba01ddcdd | ||
|
|
abc6ae2dca | ||
|
|
e3d47e1e5d | ||
|
|
17dae79660 | ||
|
|
d6336710b8 | ||
|
|
ed25a4191d | ||
|
|
5723097cbe | ||
|
|
38b600520f | ||
|
|
0fae016d15 | ||
|
|
74b4013002 | ||
|
|
eede8bdc2f | ||
|
|
7db0de7ccc | ||
|
|
364cde1287 | ||
|
|
c532d74f8b | ||
|
|
2303c54ac5 | ||
|
|
6762ce347d | ||
|
|
2e884339a8 | ||
|
|
3bdb18a2b4 | ||
|
|
aaf2b7e206 | ||
|
|
ef7a711f07 | ||
|
|
dc95bad4aa | ||
|
|
327d0277fc | ||
|
|
72add39182 | ||
|
|
509a946c92 | ||
|
|
b10946f0e8 | ||
|
|
6bcb1ab113 | ||
|
|
9dc22142ef | ||
|
|
e23818b3b4 | ||
|
|
0eef72b6e3 | ||
|
|
00995e3a6a | ||
|
|
089dad31c0 | ||
|
|
b79d36e0eb | ||
|
|
07a78b55cb | ||
|
|
7aa6f8fa9d | ||
|
|
47eaa1ac96 | ||
|
|
0866632b2f | ||
|
|
31b341cb68 | ||
|
|
7c62945560 | ||
|
|
59fb3c1dbe | ||
|
|
ea86e2990b | ||
|
|
b2e0ec4130 | ||
|
|
de8a73efb7 | ||
|
|
df95c2c85d | ||
|
|
df322ceacf | ||
|
|
78df3fc12c | ||
|
|
b1a1824522 | ||
|
|
ffab68e809 | ||
|
|
a4a242d58b | ||
|
|
b488764b79 | ||
|
|
38848f43e4 | ||
|
|
9ffea5cadb | ||
|
|
9e05675823 | ||
|
|
3b4efc21a6 | ||
|
|
beaf31ae05 | ||
|
|
631e0b0741 | ||
|
|
c11db9f0af | ||
|
|
9ce91345a7 | ||
|
|
a6653a3419 | ||
|
|
8483b0d08e | ||
|
|
b46be2445a | ||
|
|
d45328576f | ||
|
|
a31cc33c67 | ||
|
|
b874147b9c | ||
|
|
09046ab89e | ||
|
|
62bff009c1 | ||
|
|
0c3ea4b05d | ||
|
|
9dde2fbcf8 | ||
|
|
740041a844 | ||
|
|
31bcb8c82d | ||
|
|
425ef9f059 | ||
|
|
e059ecc6c4 | ||
|
|
425a8c864b | ||
|
|
56c2b1d9b5 | ||
|
|
ab74d19584 | ||
|
|
3e197b72a8 | ||
|
|
863e1a2e20 | ||
|
|
a00c8e0a53 | ||
|
|
3b98206942 | ||
|
|
f6cee7297a | ||
|
|
6792e3d701 | ||
|
|
df1e5aef9b | ||
|
|
6c3d2926b7 | ||
|
|
302fcfc7d7 | ||
|
|
42dd9969e4 | ||
|
|
6a69ce26e3 | ||
|
|
8d6db96a4c | ||
|
|
e61a6bae7e | ||
|
|
37989854a7 | ||
|
|
c70f8441e2 | ||
|
|
f9073055e9 | ||
|
|
a22cdd9553 | ||
|
|
67b8166ebc | ||
|
|
762e498a5c | ||
|
|
48c9862e18 | ||
|
|
a8b7faaed3 | ||
|
|
c56abc020e | ||
|
|
20067b91eb | ||
|
|
43004af842 | ||
|
|
6eb51fdafd | ||
|
|
c3f831727b | ||
|
|
6d328eb376 | ||
|
|
b9424f41ab | ||
|
|
b8aa1af55a | ||
|
|
1ba6e361bd | ||
|
|
d688db95e9 | ||
|
|
9c14b42bda | ||
|
|
5f04224d57 | ||
|
|
37f3bb42a0 | ||
|
|
6af2b098e2 | ||
|
|
a30a0f8d0b | ||
|
|
b78c37dbbe | ||
|
|
e7fdb804ae | ||
|
|
6749aee5cd | ||
|
|
c3b846bac7 | ||
|
|
5c9bb477b4 | ||
|
|
57247cd5cd | ||
|
|
415ab6298e | ||
|
|
50925c4c30 | ||
|
|
c9d12184e0 | ||
|
|
c2781b1f8b | ||
|
|
bd2ccc3612 | ||
|
|
5b42b41dcb | ||
|
|
8a5c429e87 | ||
|
|
194923ca27 | ||
|
|
a2b8391174 | ||
|
|
c0fd535067 | ||
|
|
6629c38d2a | ||
|
|
58032012aa | ||
|
|
cc40afcc4d | ||
|
|
3ff2b5f546 | ||
|
|
f5e2309563 | ||
|
|
75aa6d50b3 | ||
|
|
021ad2a5c2 | ||
|
|
ffe486e284 | ||
|
|
6132aa7864 | ||
|
|
56e55d9cd6 | ||
|
|
2f0e91d96e | ||
|
|
32a45ad0d3 | ||
|
|
f0d0d6b73a | ||
|
|
03c470846b | ||
|
|
66f347c973 | ||
|
|
1cf8cae166 | ||
|
|
f2585438bd | ||
|
|
f1e2c5b0d1 | ||
|
|
466ba18ec8 | ||
|
|
844fb2e6ec | ||
|
|
41d8e2a0ae | ||
|
|
49ea016980 | ||
|
|
34a8972ce4 | ||
|
|
a980e1910c | ||
|
|
10758879db | ||
|
|
1e0986b1f6 | ||
|
|
57e16f46ce | ||
|
|
cc076cc803 | ||
|
|
254262c5c4 | ||
|
|
dc8279c3ea | ||
|
|
d34d9316f3 | ||
|
|
7496b1de21 | ||
|
|
27eca480d0 | ||
|
|
311ec1d5df | ||
|
|
a66beecd8d | ||
|
|
f833c8c598 | ||
|
|
828c3b3c9c | ||
|
|
d5f432d07c | ||
|
|
0f8404f686 | ||
|
|
ff46870bf1 | ||
|
|
1b55a5a399 | ||
|
|
afb06cab99 | ||
|
|
d26ac237e9 | ||
|
|
41c200a011 | ||
|
|
fecabca368 | ||
|
|
4afb864bef | ||
|
|
7b8690cbb7 | ||
|
|
9a97b4b754 | ||
|
|
ad4e1f216d | ||
|
|
d6264dfc0b | ||
|
|
675228d841 | ||
|
|
a28567b48a | ||
|
|
317ba23e5a | ||
|
|
26a30da072 | ||
|
|
b35e87780b | ||
|
|
f9aab38575 | ||
|
|
01f8815413 | ||
|
|
963aabb8f5 | ||
|
|
884fe3c7d9 | ||
|
|
8866e2aa13 | ||
|
|
07ac9710a8 | ||
|
|
9177962454 | ||
|
|
a66545dac5 | ||
|
|
0614dcb3e2 | ||
|
|
359d6c4dba | ||
|
|
242018d0d3 | ||
|
|
d9a81d1d14 | ||
|
|
eeb3b70328 | ||
|
|
ca6820f91b | ||
|
|
257eed01df | ||
|
|
f08475605a | ||
|
|
64d480be48 | ||
|
|
f240bca81a | ||
|
|
3b67dc4ac0 | ||
|
|
06fba3a816 | ||
|
|
1586ba83dc | ||
|
|
ab3a5df71d | ||
|
|
9d91629ba7 | ||
|
|
0921d51262 | ||
|
|
4bf898c9d9 | ||
|
|
d346c69033 | ||
|
|
cd68e387c9 | ||
|
|
ee3d51cb84 | ||
|
|
8e584dbe12 | ||
|
|
90f955e471 | ||
|
|
702fc5ac31 | ||
|
|
746a84b50b | ||
|
|
e8cc49fe14 | ||
|
|
f5b9c97edb | ||
|
|
376178b18a | ||
|
|
76521218ae | ||
|
|
f9a9c5c00f | ||
|
|
5779e164c6 | ||
|
|
e17693905a | ||
|
|
92add6b14d | ||
|
|
3493de7bb7 | ||
|
|
c0557f7974 | ||
|
|
f3e8473f60 | ||
|
|
d88a104237 | ||
|
|
932c2162ad | ||
|
|
6f3cdfcc4a | ||
|
|
b311c7991c | ||
|
|
2d25ff399f | ||
|
|
66c78f44c2 | ||
|
|
6f68947815 | ||
|
|
c24460962a | ||
|
|
8a0512c956 | ||
|
|
8f146c5161 | ||
|
|
d5076999fd | ||
|
|
69df0b6837 | ||
|
|
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"
|
||||
|
||||
313
CHANGELOG
Normal file
313
CHANGELOG
Normal file
@@ -0,0 +1,313 @@
|
||||
# Change Log
|
||||
|
||||
## 1.3.3 14/05/2015
|
||||
|
||||
* New inline help text for the idle-pc dialog.
|
||||
* Reactivate auto idle-pc in device contextual menu + save a chosen idle-pc value in template.
|
||||
* Adds name to the thank you section.
|
||||
* Prevent users to use VirtualBox linked clone VMs in temporary projects (for now).
|
||||
* Capture error if the command is invalid
|
||||
* Cleanup egg cache when exit
|
||||
* Fix a crash in console when you used non UTF-8 terminal
|
||||
* Fix crash during save as
|
||||
* Change title when exporting an IOS startup-config.
|
||||
* Removes analytics client on closing.
|
||||
|
||||
## 1.3.3rc1 07/05/2015
|
||||
|
||||
* Catch broken pipe error catched for OSX
|
||||
* Prevent a topology made for next version to be open in previous version
|
||||
* Check if the local server is really a local server
|
||||
* NIO NAT support for QEMU VMs (user mode back-end is used).
|
||||
* Modified version requirements, so that they require the dependency versions as minimums. Added some more detailed instructions for compilation on Windows.
|
||||
* Prevent user to enter a None port
|
||||
* Fix broken pipe error on OSX when frozen
|
||||
* Prevent the same link created twice on OSX
|
||||
* Project loading: names and IDs must be assigned to ports on the client side after nodes have been created. Fixes #326. Fixes config updating for IOS router and IOU devices.
|
||||
* Fix a crash when dropping a .gns3
|
||||
* Cleanup VPCS code
|
||||
* Turn off config parser interpolation
|
||||
* Support unicode characters in regex
|
||||
* Fixes duplicate entries for "Recent files" on Windows. Fixes #316.
|
||||
* Fixes VPCS multi-host. Fixes #318.
|
||||
* Fixes issues when importing configs for IOS, IOU and VPCS. Fixes #314.
|
||||
* Fixes issue when console setting present in IOS router templates.
|
||||
* Do not send empty settings when creating VMs.
|
||||
* Do not set a default private-config when creating a new IOS router template. Fixes #317.
|
||||
* Refactors how startup-config and private-config are handled for IOS routers.
|
||||
* Fixes IOU and QEMU tests.
|
||||
* Makes sure all IOS router settings are saved in the project file & simplify loading from a project.
|
||||
* Makes sure all VirtualBox VM settings are saved in the project file & simplify loading from a project.
|
||||
* Makes sure all VPCS VM settings are saved in the project file & simplify loading from a project.
|
||||
* Makes sure all IOU VM settings are saved in the project file & simplify loading from a project.
|
||||
* Makes sure all QEMU VM settings are saved in the project file & simplify loading from a project.
|
||||
* Fix save as by correctly renaming VM uuid project directory
|
||||
* Fix save as duplicate the .gns3 file
|
||||
* Fix broken project in Another assert topology fixe
|
||||
* Prevent user to enter bad hostname
|
||||
* Fixes an issue when the IOU VM template has a console setting.
|
||||
* Releasing adding a link. Fixes #235.
|
||||
* Fix RuntimeError: wrapped C/C++ object of type QNetworkReply has been deleted.
|
||||
* Do not crash if terminal doesn't support UTF-8
|
||||
* Fix windows build
|
||||
* Fixes "show only devices with captures" in the topology summary.
|
||||
|
||||
## 1.3.2 28/04/2015
|
||||
|
||||
* Fixes bug when IOS configs are not in VM settings.
|
||||
* Fixes small issue with Qemu VM monitor.
|
||||
* Fixes issue when only one port is added after a QEMU VM is created. Fixes #296.
|
||||
* Avoid Cygwin warning with VPCS on Windows.
|
||||
* Fixes issues with QThread handling.
|
||||
* Fixes missing title + icon in layer position warning message box.
|
||||
* Allows the warning message box to be displayed once only when moving an object to a background layer.
|
||||
* Fixes small issue with old monitor setting.
|
||||
* Check the config path is set when creating a IOU or IOS router.
|
||||
* Removes residual link when a NIO cannot be created on the server. Fixes #294.
|
||||
* Fix VPCS tests
|
||||
* Do not crash if an antivirus intercept a message and send non UTF-8
|
||||
* Avoid C++ runtime error when progress dialog is finished.
|
||||
* Merge remote-tracking branch 'origin/master'
|
||||
* Move FileCopyThread to FileCopyWorker.
|
||||
* If project loading fail fallback to real temporary project
|
||||
* Do not crash if rotation is a string
|
||||
* I think it's prevent empty topologies
|
||||
* Explicit utf-8 decoding.
|
||||
* Fixes rare maximum recursion depth exceeded exception.
|
||||
* Check for invalid base VM configuration files.
|
||||
* Catch ValueError exception thrown by mmap(): cannot mmap an empty file.
|
||||
* Use QThreads the correct way (moveToThread).
|
||||
* Fixes broken serial console connection.
|
||||
* Fixes "RuntimeError: wrapped C/C++ object ... has been deleted" exceptions with item links.
|
||||
* Allows exported config files to be created even when there is no config set on VMs.
|
||||
* Do not try to export empty VPCS startup configs.
|
||||
* Prevent issues when a file with a simple number is considered valid JSON.
|
||||
* Explicit error when mmap throw an invalid argument exception.
|
||||
* Do not replace invalid utf-8 characters when reading the iourc file (we catch the exception to tell the user this is an invalid file).
|
||||
* Explicit utf-8 encoding where necessary to avoid Unicode errors on Windows (we require/set an utf-8 locale on other systems).
|
||||
* Save as dialog opens in the projects directory. Fixes #267.
|
||||
* Adds Terminal + nc for serial console connections on OSX. Fixes #228.
|
||||
* Improve warning when non unicode char in iourc
|
||||
* Crash report not for developers and new key
|
||||
* Do not crash if we can't change IOU permission
|
||||
* More checks when decompressing IOS images.
|
||||
* Warn users that they must provide their router images.
|
||||
* Display an error and link to the documentation if no router available
|
||||
* Display print( in std console and Qt Console
|
||||
* Fix tests and a potential issue where initial_content is not send
|
||||
* Fix a crash in qemu loading
|
||||
* Removes unnecessary progress dialog when listing VirtualBox VMs.
|
||||
* Fixes issues when pushing configs for Dynamips and IOU.
|
||||
* Allow for empty initial-config path for IOU VM templates. Send IOU VM settings while creating it (POST) and not using the update API call immediately after (PUT).
|
||||
* Allow for empty startup-config and private-config paths for IOS routers.
|
||||
* Send QEMU VM settings while creating it (POST) and not using the update API call immediately after (PUT).
|
||||
* Include resources and tests in pypi packages
|
||||
* Fix issue during project import on Windows with non local server
|
||||
|
||||
## 1.3.1 11/04/2015
|
||||
|
||||
* Release
|
||||
|
||||
## 1.3.1rc4 09/04/2015
|
||||
|
||||
* Fix crash when save as can't create a directory
|
||||
* Allow less strict dependencies
|
||||
|
||||
## 1.3.1rc3 07/04/2015
|
||||
|
||||
* Send HTTP errors 400 to the crash report system
|
||||
|
||||
## 1.3.1rc2 06/04/2015
|
||||
|
||||
* Fix race condition during old project import
|
||||
|
||||
## 1.3.1rc1 05/04/2015
|
||||
|
||||
* Fix rare occasion when user manage to put text in port field
|
||||
* Fix a crash when exporting vpcs startup script
|
||||
* Fix an issue with sending iourc when a topologies is reloaded
|
||||
* Solve issue when iourc contains non ascii characters
|
||||
* Handle corrupted zip file with IOS image
|
||||
* Don't crash if we try to contact a non GNS3 remote server returning JSON
|
||||
* Skip tests in package
|
||||
* Check port range
|
||||
* Add a warning about too much ram for IOS
|
||||
* Fix crash if project is already closed
|
||||
* Check if wait for connection thread still running before emitting a signal.
|
||||
* Check if process files thread still running before emitting a signal.
|
||||
* Raven is an optionnal dependencies for Debian
|
||||
* Fix crash if a dumped topology as no node during save as
|
||||
* Fix: remove old ID references for ATM and Frame-Relay switches.
|
||||
|
||||
## 1.3.0 30/03/2015
|
||||
|
||||
* Fix etherswitch router
|
||||
* Fix issues with progress dialog
|
||||
* Fix save as
|
||||
|
||||
## 1.3.0rc2 23/03/2015
|
||||
|
||||
* Fix crash when in same occasion the project name is missing
|
||||
* Update sentry key
|
||||
* Display adapters in the tooltips in the correct order.
|
||||
* Open consoles in alphanumerical order.
|
||||
* Auto idle-PC improvements.
|
||||
* Adds project id when requesting UDP port.
|
||||
* Fixes Thread problem. Fixes #229.
|
||||
* Cancel network requests if the progress dialog itself is canceled. Avoid closing the preferences dialog or any configuration dialog if there is a pending request. Fixes #227.
|
||||
* Fixes #228 (no alternative interface has been chosen).
|
||||
* Catch OSError when reading or writing the local server config file.
|
||||
* Fixes GUI that could not be closed when using an already running local server.
|
||||
* Save configs when project is committed.
|
||||
* Del key deletes selected link
|
||||
* Fix crash is no remote servers is available
|
||||
|
||||
## 1.3.0rc1 19/03/2015
|
||||
|
||||
* Handle legacy snapshots
|
||||
* Add server informations for Qemu, VirtualBox and VPCS info boxes
|
||||
* Support sending IOURC from client to remote servers
|
||||
* Fixes crash when quick restart the client
|
||||
* Add 1MB disk for EtherSwitch router templates (to store the vlan database)
|
||||
* Fixes alignment options to ignore devices labels
|
||||
* Compute IDLEPC on remote servers
|
||||
* Prevent using lab instruction in a temporary project
|
||||
* Display a warning on console if server port is already in used
|
||||
* Display an error if server version is incorrect
|
||||
|
||||
## 1.3.0beta2 13/03/2015
|
||||
|
||||
* Alternative local server shutdown (faster GUI closing on Windows).
|
||||
* Grey out local server preferences if the local server is not activated.
|
||||
* Adds "template" to the Wizard titles.
|
||||
* Option to automatically take or not a screenshot when saving a project.
|
||||
* Support RAM setting for VirtualBox VMs.
|
||||
* Fixed duplicate VM template entries for Qemu, VirtualBox and IOU.
|
||||
|
||||
## 1.3.0beta1 11/03/2015
|
||||
|
||||
* New title for VMs/Devices/routers preference pages.
|
||||
* Deactivate auto idle-pc in contextual menu while we think about a better implementation.
|
||||
* Optional IOU license key check.
|
||||
* Relative picture paths are saved in projects.
|
||||
* Relative path support of IOU, IOS and Qemu images.
|
||||
* More checks when automatically starting the local server and find an alternative port if needed.
|
||||
* Support for HDC and HDD disk images in Qemu.
|
||||
* Fixed base IOS and IOU base configs.
|
||||
* Fixed GNS3 console issues.
|
||||
* Renamed server.conf and server.ini to gns3_server.conf and gns3_server.ini respectively.
|
||||
* Remove remote servers list from module preferences + some other prefences re-factoring.
|
||||
* Automatically convert old projects on remote servers.
|
||||
* Bump the progress dialog minimum duration before display to 1000ms.
|
||||
* Fixed port listing bug with Cloud and Host nodes.
|
||||
* Fixed Qemu networking.
|
||||
* Give a warning when a object is move the background layer.
|
||||
* Option to draw a rectangle when a node is selected.
|
||||
* New project icon (little yellow indicator).
|
||||
* Default name for screenshot file is "screenshot".
|
||||
* Alignment options (horizontal & vertical).
|
||||
* Fixed import / export of the preferences file.
|
||||
* Fixed pkg_ressource bug.
|
||||
* Brought back Qemu preferences page.
|
||||
* Include SSL cacert file with GNS3 Windows exe and Mac OS App to send crash report using HTTPS.
|
||||
* Fixed adapter bug with VirtualBox.
|
||||
* Fixed various errors when a project was not initialized.
|
||||
|
||||
## 1.3.0alpha1 03/03/2015
|
||||
|
||||
* No more console port and UDP tunneling settings by type of module
|
||||
* Fixe save
|
||||
* Settings are stored as JSON
|
||||
* All communication with servers display a waiting dialog
|
||||
* Add a revision number in the topology file
|
||||
* Qemu can run on a server without graphical interface
|
||||
* Automated crash reports
|
||||
* You can now copy paste from the GNS 3 console
|
||||
|
||||
|
||||
## 1.2.3 2015/01/17
|
||||
|
||||
* Fixed temporary files path setting in general preferences which was not used.
|
||||
* Fixed missing devices from the node view when they use a remote server.
|
||||
* Fixed broken ASA kernel/initrd file browsers.
|
||||
* Fixed bug with WICs interfaces no showing up in the port list.
|
||||
|
||||
## 1.2.2 2015/01/16
|
||||
|
||||
### Small improvements / new features
|
||||
|
||||
* EtherSwitch routers can be added and configured like other IOS routers.
|
||||
* Change hostname option in the contextual device menu.
|
||||
* Import & export config options in contextual device menu.
|
||||
* Auto screenshot when saving a project.
|
||||
* Auto start project support (you have to manually edit your .gns3 project file).
|
||||
* Changes to the IOU L2 initial-config (16 Ethernet interfaces, no shutdown by default and 0 serial interfaces).
|
||||
* Upgraded SuperPutty to version 1.4.0.5 in the all-in-one installer.
|
||||
* Possibility to apply or not the same text to all selected items when editing notes.
|
||||
* Base configs are now stored in the GNS3 config directory.
|
||||
* Short port names in the topology summary.
|
||||
* Added the VirtualBox VM name in VirtualBox device tooltips.
|
||||
* Set 5 seconds timeout for local server connections.
|
||||
* Check if any device runs and warn the user before closing a project.
|
||||
* Restore the debug level status when starting.
|
||||
* Automatically select the symbol and category corresponding the edited item in the symbol selection dialog.
|
||||
* Scale SVG images to icon sizes.
|
||||
* Console switching from local/remote to remote/local while a VirtualBox VM is running.
|
||||
* Default Jungle dock location is now bottom right corner.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed the default jungle news loading on Windows.
|
||||
* Fixed SuperPutty integration (not the default, still have to select it in the preferences).
|
||||
* Avoid uninitialized nodes to be saved in the project file.
|
||||
Prevent GNS3 to crash on Windows when importing GNS3 config file.
|
||||
* Fixed resource access on Mac OS X.
|
||||
* Fixed transparency or border style restoration for ellipses and rectangles.
|
||||
* Support spaces in the controller name of VirtualBox clones.
|
||||
* Ignore Unicode errors when executing vboxmanage.
|
||||
* Get Windows interface list from the registry if the COM service fails.
|
||||
|
||||
## 1.2.1 2014/12/04
|
||||
|
||||
* Support for full screen mode (View -> Fullscreen).
|
||||
* Bundled Qemu 0.13.0 in the Windows all-in-one. Default for all local Qemu VMs.
|
||||
* Bundled Qemu 0.14.1 in the Mac OS X App. Default for all local Qemu VMs.
|
||||
* Changed ASA defaults to use Qemu 0.13.0 (on Windows), have 4 interfaces and CPU throttling to 65%.
|
||||
* Fixed SecureCRT command line when space in the device name.
|
||||
* Fixed port sorting issues.
|
||||
* Added default path for VBoxManage on Mac OS X
|
||||
* Upgraded gns3-converter to version 1.1.1 for Windows all-in-one and Mac OS X DMG.
|
||||
* New idle-PC field validation.
|
||||
* Possibility to load the project from command line (or double-click on a project on Windows).
|
||||
* Fixed Unicode error when using VirtualBox VM with a name containing non-english characters.
|
||||
|
||||
## 1.2 2014/11/20
|
||||
|
||||
* New GUI styles: charcoal (default) & classic. Changing GUI Preferences
|
||||
* Integration of GNS3 converter (allows old .net topologies to be opened).
|
||||
* Allow Qemu VM to have no interface.
|
||||
* Automatically extract IOS configs when a project is closed.
|
||||
* Show the cancel button in Wizards on Mac OS X.
|
||||
* Fix crash on Windows 32-bit.
|
||||
* Fix "new project" bug when using the GNS3 IOU VM.
|
||||
* Fix "could not find unused port" WinError 10013 bug
|
||||
* qemu-system-i386 is the new default on 32-bit platforms.
|
||||
* Option to deactivate the new project dialog at startup.
|
||||
* Add "open a project" and "recent projects" buttons to the new project dialog.
|
||||
* Fix platform detection issue with some Cisco IOS image file name.
|
||||
* Add delay (default 500 ms) when Console to all nodes.
|
||||
* Check for duplicate node names in Preferences.
|
||||
* Fix bug when editing a Qemu VM configured to run on a remote server.
|
||||
* News dock widget is smaller.
|
||||
* Fix SecureCRT issue when disconnecting from an IOU device on Windows.
|
||||
* Update VPCS to version 0.6 in the all-in-one installer.
|
||||
|
||||
## 1.1 2014/11/20
|
||||
|
||||
* Fixed broken cloud.
|
||||
* Fixed broken remote server.
|
||||
* Fixed Qemu binaries not showing up when editing a Qemu VM.
|
||||
* Fixed EtherSwitch (until we come with a default template for it).
|
||||
* Serial console for local VirtualBox.
|
||||
* Warning message when creating an IOU device with a remote server in the Wizard.
|
||||
* New Idle-PC dialog.
|
||||
@@ -4,8 +4,8 @@ include INSTALL
|
||||
include LICENSE
|
||||
include MANIFEST.in
|
||||
include tox.ini
|
||||
recursive-exclude tests *
|
||||
recursive-include docs *
|
||||
recursive-include tests *
|
||||
recursive-include gns3 *
|
||||
recursive-include resources *
|
||||
recursive-exclude * __pycache__
|
||||
recursive-exclude * *.py[co]
|
||||
|
||||
37
README.rst
37
README.rst
@@ -1,7 +1,14 @@
|
||||
GNS3-gui
|
||||
========
|
||||
|
||||
GNS3 GUI repository (beta stage).
|
||||
.. image:: https://travis-ci.org/GNS3/gns3-gui.svg?branch=master
|
||||
:target: https://travis-ci.org/GNS3/gns3-gui
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/gns3-gui.svg
|
||||
:target: https://pypi.python.org/pypi/gns3-gui
|
||||
|
||||
|
||||
GNS3 GUI repository.
|
||||
|
||||
Linux (Debian based)
|
||||
--------------------
|
||||
@@ -36,7 +43,23 @@ Finally these commands will install the GUI as well as the rest of the dependenc
|
||||
Windows
|
||||
-------
|
||||
|
||||
Please use our all-in-one installer.
|
||||
Please use our `all-in-one installer <https://community.gns3.com/community/software/download>`_ to install the stable build.
|
||||
|
||||
If you install via source you need to first install:
|
||||
|
||||
- Python (3.3 or above) - https://www.python.org/downloads/windows/
|
||||
- Pywin32 - https://sourceforge.net/projects/pywin32/
|
||||
- Qt4 - http://www.qt.io/download-open-source/
|
||||
- PyQt4 - http://www.riverbankcomputing.com/software/pyqt/download
|
||||
- PyCrypto (which if you compile from source, requires Visual Studio 2010 with GMP or MPIR libraries)
|
||||
|
||||
And finally, call
|
||||
|
||||
.. code:: bash
|
||||
|
||||
python setup.py install
|
||||
|
||||
to install the remaining dependencies.
|
||||
|
||||
Mac OS X
|
||||
--------
|
||||
@@ -67,3 +90,13 @@ Finally, install both the GUI & server from the source.
|
||||
python3 setup.py install
|
||||
|
||||
Or follow this `HOWTO that uses MacPorts <http://binarynature.blogspot.ca/2014/05/install-gns3-early-release-on-mac-os-x.html>`_.
|
||||
|
||||
Developement
|
||||
-------------
|
||||
|
||||
If you want to update the interface, modify the .ui files using QT tools. And:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
cd scripts
|
||||
python build_pyqt.py
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
90
gns3/crash_report.py
Normal file
90
gns3/crash_report.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import struct
|
||||
|
||||
try:
|
||||
import raven
|
||||
RAVEN_AVAILABLE = True
|
||||
except ImportError:
|
||||
# raven is not installed with deb package in order to simplify packaging
|
||||
RAVEN_AVAILABLE = False
|
||||
|
||||
|
||||
from .version import __version__
|
||||
from .servers import Servers
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CrashReport:
|
||||
|
||||
"""
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "sync+https://c9baccac3bec4664812e9172d9b39050:597eab5e1f9042ae99daf2ddcc371ed2@app.getsentry.com/38506"
|
||||
if hasattr(sys, "frozen"):
|
||||
cacert = os.path.join(os.getcwd(), "cacert.pem")
|
||||
if os.path.isfile(cacert):
|
||||
DSN += "?ca_certs={}".format(cacert)
|
||||
else:
|
||||
log.warning("The SSL certificate bundle file '{}' could not be found".format(cacert))
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
self._client = None
|
||||
|
||||
def captureException(self, exception, value, tb):
|
||||
if not RAVEN_AVAILABLE:
|
||||
return
|
||||
if os.path.exists(".git"):
|
||||
log.warning("A .git directory exist crash report is turn off for developers")
|
||||
return
|
||||
local_server = Servers.instance().localServerSettings()
|
||||
if local_server["report_errors"]:
|
||||
if self._client is None:
|
||||
self._client = raven.Client(CrashReport.DSN, release=__version__)
|
||||
self._client.tags_context({
|
||||
"os:name": platform.system(),
|
||||
"os:release": platform.release(),
|
||||
"os:win_32": " ".join(platform.win32_ver()),
|
||||
"os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
|
||||
"os:linux": " ".join(platform.linux_distribution()),
|
||||
"python:version": "{}.{}.{}".format(sys.version_info[0],
|
||||
sys.version_info[1],
|
||||
sys.version_info[2]),
|
||||
"python:bit": struct.calcsize("P") * 8,
|
||||
"python:encoding": sys.getdefaultencoding(),
|
||||
"python:frozen": "{}".format(hasattr(sys, "frozen"))
|
||||
})
|
||||
try:
|
||||
report = self._client.captureException((exception, value, tb))
|
||||
except Exception as e:
|
||||
log.error("Can't send crash report to Sentry: {}".format(e))
|
||||
return
|
||||
log.info("Crash report sent with event ID: {}".format(self._client.get_ident(report)))
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = CrashReport()
|
||||
return cls._instance
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -38,13 +40,16 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
|
||||
self.adjustSize()
|
||||
self.uiWebView.page().setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks)
|
||||
self.uiWebView.linkClicked.connect(self._urlClickedSlot)
|
||||
self.uiWebView.loadFinished.connect(self._loadFinishedSlot)
|
||||
self.uiCheckBox.setChecked(QtCore.QSettings().value("GUI/hide_getting_started_dialog", False, type=bool))
|
||||
self._timer = QtCore.QTimer(self)
|
||||
self._timer.timeout.connect(self._loadFinishedSlot)
|
||||
self._timer.setSingleShot(True)
|
||||
self._timer.start(5000)
|
||||
self.uiWebView.load(QtCore.QUrl("http://start.gns3.net"))
|
||||
self._local_config = LocalConfig.instance()
|
||||
gui_settings = self._local_config.loadSectionSettings("GUI", {"hide_getting_started_dialog": False})
|
||||
self.uiCheckBox.setChecked(gui_settings["hide_getting_started_dialog"])
|
||||
getting_started = get_resource(os.path.join("static", "getting_started.html"))
|
||||
if getting_started and not (sys.platform.startswith("win") and not sys.maxsize > 2 ** 32):
|
||||
# do not show the page on Windows 32-bit (crash when no Internet connection)
|
||||
self.uiWebView.load(QtCore.QUrl.fromLocalFile(getting_started))
|
||||
else:
|
||||
self.uiCheckBox.setChecked(True)
|
||||
self.accept()
|
||||
|
||||
def showit(self):
|
||||
"""
|
||||
@@ -62,7 +67,7 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
|
||||
:param result: ignored
|
||||
"""
|
||||
|
||||
QtCore.QSettings().setValue("GUI/hide_getting_started_dialog", self.uiCheckBox.isChecked())
|
||||
self._local_config.saveSectionSettings("GUI", {"hide_getting_started_dialog": self.uiCheckBox.isChecked()})
|
||||
QtGui.QDialog.done(self, result)
|
||||
|
||||
def _urlClickedSlot(self, url):
|
||||
@@ -74,29 +79,3 @@ class GettingStartedDialog(QtGui.QDialog, Ui_GettingStartedDialog):
|
||||
|
||||
if QtGui.QDesktopServices.openUrl(url) is False:
|
||||
QtGui.QMessageBox.critical(self, "Getting started", "Failed to open the URL: {}".format(url))
|
||||
|
||||
def _loadFinishedSlot(self, result=False):
|
||||
"""
|
||||
Slot called when the web page has been loaded.
|
||||
|
||||
:param result: boolean
|
||||
"""
|
||||
|
||||
self.uiWebView.loadFinished.disconnect(self._loadFinishedSlot)
|
||||
self._timer.stop()
|
||||
self._timer.timeout.disconnect()
|
||||
if result is False:
|
||||
# load a local resource if the page is not available
|
||||
resource_name = os.path.join("static", "getting_started.html")
|
||||
getting_started = None
|
||||
if hasattr(sys, "frozen") and os.path.isfile(resource_name):
|
||||
getting_started = os.path.normpath(resource_name)
|
||||
elif pkg_resources.resource_exists("gns3", resource_name):
|
||||
getting_started_page = pkg_resources.resource_filename("gns3", resource_name)
|
||||
getting_started = os.path.normpath(getting_started_page)
|
||||
if getting_started and not (sys.platform.startswith("win") and not sys.maxsize > 2 ** 32):
|
||||
# do not show the page on Windows 32-bit (crash when no Internet connection)
|
||||
self.uiWebView.load(QtCore.QUrl("file://{}".format(getting_started)))
|
||||
else:
|
||||
self.uiCheckBox.setChecked(True)
|
||||
self.accept()
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from ..qt import QtGui
|
||||
@@ -23,6 +24,7 @@ from ..ui.idlepc_dialog_ui import Ui_IdlePCDialog
|
||||
|
||||
|
||||
class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
|
||||
|
||||
"""
|
||||
Idle-PC dialog.
|
||||
"""
|
||||
@@ -51,10 +53,13 @@ class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
|
||||
Shows the help for Idle-PC.
|
||||
"""
|
||||
|
||||
help_text = "Finding the right idlepc value is a trial and error process, consisting of applying " \
|
||||
"different Idle-PC values and monitoring the CPU usage.\n\nBest Idle-PC values are usually " \
|
||||
"obtained when IOS is in idle state, the following message being displayed " \
|
||||
"on the console: {} con0 is now available ... Press RETURN to get started.".format(self._router.name())
|
||||
help_text = """Best Idle-PC values are obtained when IOS is in idle state, after the "Press RETURN to get started" message has appeared on the console, messages have finished displaying on the console and you have have actually pressed the RETURN key.
|
||||
|
||||
Finding the right idle-pc value is a trial and error process, consisting of applying different Idle-PC values and monitoring the CPU usage.
|
||||
|
||||
Select each value that appears in the list and click Apply, and note the CPU usage a few moments later. When you have found the value that minimises the CPU usage, apply that value.
|
||||
"""
|
||||
|
||||
QtGui.QMessageBox.information(self, "Hints for Idle-PC", help_text)
|
||||
|
||||
def _applySlot(self):
|
||||
@@ -67,13 +72,15 @@ class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
|
||||
return
|
||||
|
||||
idlepc = self.uiComboBox.itemData(self.uiComboBox.currentIndex())
|
||||
|
||||
# apply Idle-PC to all routers with the same IOS image
|
||||
ios_image = self._router.settings()["image"]
|
||||
ios_image = os.path.basename(self._router.settings()["image"])
|
||||
for node in Topology.instance().nodes():
|
||||
if hasattr(node, "idlepcs") and node.settings()["image"] == ios_image:
|
||||
if hasattr(node, "idlepc") and node.settings()["image"] == ios_image:
|
||||
node.setIdlepc(idlepc)
|
||||
|
||||
# apply the idle-pc to templates with the same IOS image
|
||||
self._router.module().updateImageIdlepc(ios_image, idlepc)
|
||||
|
||||
def done(self, result):
|
||||
"""
|
||||
Called when the dialog is closed.
|
||||
@@ -84,4 +91,3 @@ class IdlePCDialog(QtGui.QDialog, Ui_IdlePCDialog):
|
||||
if result:
|
||||
self._applySlot()
|
||||
QtGui.QDialog.done(self, result)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -19,11 +19,15 @@
|
||||
Dialog to configure and update node settings using widget pages.
|
||||
"""
|
||||
|
||||
from gns3.http_client import HTTPClient
|
||||
from gns3.progress import Progress
|
||||
|
||||
from ..qt import QtCore, QtGui
|
||||
from ..ui.node_configurator_dialog_ui import Ui_NodeConfiguratorDialog
|
||||
|
||||
|
||||
class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
|
||||
|
||||
"""
|
||||
Node configurator implementation.
|
||||
|
||||
@@ -53,6 +57,7 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
|
||||
self.splitter.setSizes([250, 600])
|
||||
|
||||
self.uiNodesTreeWidget.itemClicked.connect(self.showConfigurationPageSlot)
|
||||
HTTPClient.setProgressCallback(Progress(self, min_duration=0))
|
||||
|
||||
def _loadNodeItems(self):
|
||||
"""
|
||||
@@ -65,7 +70,7 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
|
||||
continue
|
||||
group_name = " {} group".format(str(node_item.node()))
|
||||
parent = group_name
|
||||
if not parent in self._parent_items:
|
||||
if parent not in self._parent_items:
|
||||
item = QtGui.QTreeWidgetItem(self.uiNodesTreeWidget, [group_name])
|
||||
item.setIcon(0, QtGui.QIcon(node_item.node().defaultSymbol()))
|
||||
item.setExpanded(True)
|
||||
@@ -131,14 +136,17 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
|
||||
"""
|
||||
|
||||
try:
|
||||
from gns3.main_window import MainWindow
|
||||
if button == self.uiButtonBox.button(QtGui.QDialogButtonBox.Apply):
|
||||
self.applySettings()
|
||||
elif button == self.uiButtonBox.button(QtGui.QDialogButtonBox.Reset):
|
||||
self.resetSettings()
|
||||
elif button == self.uiButtonBox.button(QtGui.QDialogButtonBox.Cancel):
|
||||
HTTPClient.setProgressCallback(Progress(MainWindow.instance()))
|
||||
QtGui.QDialog.reject(self)
|
||||
else:
|
||||
self.applySettings()
|
||||
HTTPClient.setProgressCallback(Progress(MainWindow.instance()))
|
||||
QtGui.QDialog.accept(self)
|
||||
except ConfigurationError:
|
||||
pass
|
||||
@@ -165,7 +173,7 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
|
||||
page.saveSettings(settings, node, group=True)
|
||||
for index in range(0, item.childCount()):
|
||||
child = item.child(index)
|
||||
#child.node().update(settings) #TODO: delete
|
||||
# child.node().update(settings) #TODO: delete
|
||||
child.settings().update(settings)
|
||||
|
||||
# update the nodes with the settings
|
||||
@@ -201,6 +209,7 @@ class NodeConfiguratorDialog(QtGui.QDialog, Ui_NodeConfiguratorDialog):
|
||||
|
||||
|
||||
class ConfigurationPageItem(QtGui.QTreeWidgetItem):
|
||||
|
||||
"""
|
||||
Item for the QTreeWidget instance.
|
||||
Store temporary node settings configured in a page widget.
|
||||
@@ -269,6 +278,7 @@ class ConfigurationPageItem(QtGui.QTreeWidgetItem):
|
||||
|
||||
|
||||
class ConfigurationError(Exception):
|
||||
|
||||
"""
|
||||
Exception to be raised when a configuration error occurs.
|
||||
"""
|
||||
|
||||
@@ -27,9 +27,12 @@ from ..pages.cloud_preferences_page import CloudPreferencesPage
|
||||
from ..pages.packet_capture_preferences_page import PacketCapturePreferencesPage
|
||||
from ..modules import MODULES
|
||||
from ..settings import ENABLE_CLOUD
|
||||
from ..http_client import HTTPClient
|
||||
from ..progress import Progress
|
||||
|
||||
|
||||
class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
|
||||
|
||||
"""
|
||||
Preferences dialog implementation.
|
||||
|
||||
@@ -48,6 +51,7 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
|
||||
|
||||
# select the first available page
|
||||
self.uiTreeWidget.setCurrentItem(self._items[0])
|
||||
HTTPClient.setProgressCallback(Progress(self, min_duration=0))
|
||||
|
||||
def _loadPreferencePages(self):
|
||||
"""
|
||||
@@ -64,7 +68,7 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
|
||||
pages.append(CloudPreferencesPage)
|
||||
|
||||
for page in pages:
|
||||
preferences_page = page()
|
||||
preferences_page = page(self)
|
||||
preferences_page.loadPreferences()
|
||||
name = preferences_page.windowTitle()
|
||||
item = QtGui.QTreeWidgetItem(self.uiTreeWidget)
|
||||
@@ -104,8 +108,12 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
|
||||
current = previous
|
||||
|
||||
preferences_page = current.data(0, QtCore.Qt.UserRole)
|
||||
name = preferences_page.windowTitle()
|
||||
self.uiTitleLabel.setText("{} preferences".format(name))
|
||||
accessible_name = preferences_page.accessibleName()
|
||||
if accessible_name:
|
||||
self.uiTitleLabel.setText(accessible_name)
|
||||
else:
|
||||
name = preferences_page.windowTitle()
|
||||
self.uiTitleLabel.setText("{} preferences".format(name))
|
||||
index = self.uiStackedWidget.indexOf(preferences_page)
|
||||
widget = self.uiStackedWidget.widget(index)
|
||||
self.uiStackedWidget.setMinimumSize(widget.size())
|
||||
@@ -131,6 +139,8 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
|
||||
Closes this dialog.
|
||||
"""
|
||||
|
||||
from gns3.main_window import MainWindow
|
||||
HTTPClient.setProgressCallback(Progress(MainWindow.instance()))
|
||||
QtGui.QDialog.reject(self)
|
||||
|
||||
def accept(self):
|
||||
@@ -144,4 +154,6 @@ class PreferencesDialog(QtGui.QDialog, Ui_PreferencesDialog):
|
||||
main_window.uiNodesDockWidget.setWindowTitle("")
|
||||
|
||||
if self._applyPreferences():
|
||||
from gns3.main_window import MainWindow
|
||||
HTTPClient.setProgressCallback(Progress(MainWindow.instance()))
|
||||
QtGui.QDialog.accept(self)
|
||||
|
||||
@@ -26,13 +26,14 @@ import os
|
||||
|
||||
from ..qt import QtCore, QtGui
|
||||
from ..utils.progress_dialog import ProgressDialog
|
||||
from ..utils.process_files_thread import ProcessFilesThread
|
||||
from ..utils.process_files_worker import ProcessFilesWorker
|
||||
from ..ui.snapshots_dialog_ui import Ui_SnapshotsDialog
|
||||
from ..topology import Topology
|
||||
from ..node import Node
|
||||
|
||||
|
||||
class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
|
||||
|
||||
"""
|
||||
Snapshots dialog implementation.
|
||||
|
||||
@@ -45,7 +46,7 @@ class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
|
||||
self.setupUi(self)
|
||||
|
||||
self._project_path = project_path
|
||||
self._project_files_dir = project_files_dir
|
||||
self._project_files_dir = os.path.join(project_files_dir, "project-files")
|
||||
|
||||
self.uiCreatePushButton.clicked.connect(self._createSnapshotSlot)
|
||||
self.uiDeletePushButton.clicked.connect(self._deleteSnapshotSlot)
|
||||
@@ -94,9 +95,8 @@ class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
|
||||
MainWindow.instance().saveProject(self._project_path)
|
||||
snapshot_name = "{name}_{date}".format(name=snapshot_name, date=time.strftime("%d%m%y_%H%M%S"))
|
||||
snapshot_dir = os.path.join(self._project_files_dir, "snapshots", snapshot_name)
|
||||
thread = ProcessFilesThread(os.path.dirname(self._project_path), snapshot_dir, skip_dirs=["snapshots"])
|
||||
thread.deleteLater()
|
||||
progress_dialog = ProgressDialog(thread, "Creating snapshot", "Copying project files...", "Cancel", parent=self)
|
||||
worker = ProcessFilesWorker(os.path.dirname(self._project_path), snapshot_dir, skip_dirs=["snapshots"])
|
||||
progress_dialog = ProgressDialog(worker, "Creating snapshot", "Copying project files...", "Cancel", parent=self)
|
||||
progress_dialog.show()
|
||||
progress_dialog.exec_()
|
||||
self._listSnaphosts()
|
||||
@@ -145,15 +145,35 @@ class SnapshotsDialog(QtGui.QDialog, Ui_SnapshotsDialog):
|
||||
if hasattr(node, "start") and node.status() == Node.started:
|
||||
node.stop()
|
||||
|
||||
#FIXME: problably a bug when restoring a snapshot and the project name has changed.
|
||||
thread = ProcessFilesThread(snapshot_path, os.path.dirname(self._project_path), skip_dirs=["snapshots"])
|
||||
thread.deleteLater()
|
||||
progress_dialog = ProgressDialog(thread, "Restoring snapshot", "Copying project files...", "Cancel", parent=self)
|
||||
progress_dialog.show()
|
||||
progress_dialog.exec_()
|
||||
project_name, _ = os.path.splitext(os.path.basename(self._project_path))
|
||||
legacy_project_files_dir = os.path.join(snapshot_path, "{}-files".format(project_name))
|
||||
if os.path.exists(legacy_project_files_dir):
|
||||
# support for pre 1.3 snapshots
|
||||
for root, dirs, _ in os.walk(self._project_files_dir):
|
||||
dirs[:] = [d for d in dirs if d not in "snapshots"]
|
||||
for project_subdir in dirs:
|
||||
project_subdir_path = os.path.join(root, project_subdir)
|
||||
shutil.rmtree(project_subdir_path, ignore_errors=True)
|
||||
|
||||
dirs = os.listdir(legacy_project_files_dir)
|
||||
for snapshot_subdir in dirs:
|
||||
snapshot_subdir_path = os.path.join(legacy_project_files_dir, snapshot_subdir)
|
||||
worker = ProcessFilesWorker(snapshot_subdir_path, os.path.join(self._project_files_dir, snapshot_subdir))
|
||||
progress_dialog = ProgressDialog(worker, "Restoring snapshot", "Copying project files...", "Cancel", parent=self)
|
||||
progress_dialog.show()
|
||||
progress_dialog.exec_()
|
||||
|
||||
os.remove(self._project_path)
|
||||
shutil.copy(os.path.join(snapshot_path, os.path.basename(self._project_path)), self._project_path)
|
||||
|
||||
else:
|
||||
worker = ProcessFilesWorker(snapshot_path, os.path.dirname(self._project_path), skip_dirs=["snapshots"])
|
||||
progress_dialog = ProgressDialog(worker, "Restoring snapshot", "Copying project files...", "Cancel", parent=self)
|
||||
progress_dialog.show()
|
||||
progress_dialog.exec_()
|
||||
|
||||
from ..main_window import MainWindow
|
||||
MainWindow.instance().loadProject(self._project_path)
|
||||
MainWindow.instance().loadSnapshot(self._project_path)
|
||||
self.accept()
|
||||
|
||||
def _snapshotDoubleClickedSlot(self, item):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -22,6 +22,7 @@ Graphical view on the scene where items are drawn.
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
import functools
|
||||
|
||||
from .qt import QtCore, QtGui, QtNetwork
|
||||
from .servers import Servers
|
||||
@@ -39,7 +40,7 @@ from .dialogs.style_editor_dialog import StyleEditorDialog
|
||||
from .dialogs.text_editor_dialog import TextEditorDialog
|
||||
from .dialogs.symbol_selection_dialog import SymbolSelectionDialog
|
||||
from .dialogs.idlepc_dialog import IdlePCDialog
|
||||
from .utils.connect_to_server import ConnectToServer
|
||||
from .local_config import LocalConfig
|
||||
|
||||
# link items
|
||||
from .items.link_item import LinkItem
|
||||
@@ -57,6 +58,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GraphicsView(QtGui.QGraphicsView):
|
||||
|
||||
"""
|
||||
Graphics view that displays the scene.
|
||||
|
||||
@@ -81,6 +83,8 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
self._dragging = False
|
||||
self._last_mouse_position = None
|
||||
self._topology = Topology.instance()
|
||||
self._background_warning_msgbox = QtGui.QErrorMessage(self)
|
||||
self._background_warning_msgbox.setWindowTitle("Layer position")
|
||||
|
||||
# set the scene
|
||||
scene = QtGui.QGraphicsScene(parent=self)
|
||||
@@ -124,46 +128,27 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
# clear all objects on the scene
|
||||
self.scene().clear()
|
||||
|
||||
def updateProjectFilesDir(self, path):
|
||||
"""
|
||||
Updates the project files directory path for all modules.
|
||||
|
||||
:param path: path to the local project files directory.
|
||||
"""
|
||||
|
||||
try:
|
||||
for module in MODULES:
|
||||
instance = module.instance()
|
||||
instance.setProjectFilesDir(path)
|
||||
except ModuleError as e:
|
||||
QtGui.QMessageBox.critical(self, "Local projects directory", "{}".format(e))
|
||||
|
||||
def updateImageFilesDir(self, path):
|
||||
"""
|
||||
Updates the image files directory path for all modules.
|
||||
|
||||
:param path: path to the local images files directory.
|
||||
"""
|
||||
|
||||
try:
|
||||
for module in MODULES:
|
||||
instance = module.instance()
|
||||
instance.setImageFilesDir(path)
|
||||
except ModuleError as e:
|
||||
QtGui.QMessageBox.critical(self, "Local images directory", "{}".format(e))
|
||||
|
||||
def _loadSettings(self):
|
||||
"""
|
||||
Loads the settings from the persistent settings file.
|
||||
"""
|
||||
|
||||
# restore settings
|
||||
local_config = LocalConfig.instance()
|
||||
# restore the graphics view settings from QSettings (for backward compatibility)
|
||||
legacy_settings = {}
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup(self.__class__.__name__)
|
||||
for name, value in GRAPHICS_VIEW_SETTINGS.items():
|
||||
self._settings[name] = settings.value(name, value, type=GRAPHICS_VIEW_SETTING_TYPES[name])
|
||||
for name in GRAPHICS_VIEW_SETTINGS.keys():
|
||||
if settings.contains(name):
|
||||
legacy_settings[name] = settings.value(name, type=GRAPHICS_VIEW_SETTING_TYPES[name])
|
||||
settings.remove("")
|
||||
settings.endGroup()
|
||||
|
||||
if legacy_settings:
|
||||
local_config.saveSectionSettings(self.__class__.__name__, legacy_settings)
|
||||
|
||||
self._settings = local_config.loadSectionSettings(self.__class__.__name__, GRAPHICS_VIEW_SETTINGS)
|
||||
|
||||
def settings(self):
|
||||
"""
|
||||
Returns the graphics view settings.
|
||||
@@ -182,11 +167,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
|
||||
# save the settings
|
||||
self._settings.update(new_settings)
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup(self.__class__.__name__)
|
||||
for name, value in self._settings.items():
|
||||
settings.setValue(name, value)
|
||||
settings.endGroup()
|
||||
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
|
||||
|
||||
def addingLinkSlot(self, enabled):
|
||||
"""
|
||||
@@ -199,9 +180,9 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
if enabled:
|
||||
self.setCursor(QtCore.Qt.CrossCursor)
|
||||
else:
|
||||
if self._newlink:
|
||||
if self._newlink and self._newlink in self.scene().items():
|
||||
self.scene().removeItem(self._newlink)
|
||||
self._newlink = None
|
||||
self._newlink = None
|
||||
self.setCursor(QtCore.Qt.ArrowCursor)
|
||||
self._adding_link = enabled
|
||||
|
||||
@@ -277,9 +258,9 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
|
||||
# connect the signals that let the graphics view knows about events such as
|
||||
# a new link creation or deletion.
|
||||
link.add_link_signal.connect(self.addLinkSlot)
|
||||
link.delete_link_signal.connect(self.deleteLinkSlot)
|
||||
self._topology.addLink(link)
|
||||
if self._topology.addLink(link):
|
||||
link.add_link_signal.connect(self.addLinkSlot)
|
||||
link.delete_link_signal.connect(self.deleteLinkSlot)
|
||||
|
||||
def addLinkSlot(self, link_id):
|
||||
"""
|
||||
@@ -426,9 +407,10 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
QtGui.QMessageBox.critical(self, "Connection", "Server {} cannot communicate with server {}, most likely because your local server host binding is set to a local address".format(source_host, destination_host))
|
||||
return
|
||||
|
||||
self.scene().removeItem(self._newlink)
|
||||
self.addLink(source_item.node(), source_port, destination_item.node(), destination_port)
|
||||
if self._newlink in self.scene().items():
|
||||
self.scene().removeItem(self._newlink)
|
||||
self._newlink = None
|
||||
self.addLink(source_item.node(), source_port, destination_item.node(), destination_port)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
"""
|
||||
@@ -462,7 +444,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
item.setSelected(True)
|
||||
elif is_not_link and event.button() == QtCore.Qt.RightButton and not self._adding_link:
|
||||
if item:
|
||||
#Prevent right clicking on a selected item from de-selecting all other items
|
||||
# Prevent right clicking on a selected item from de-selecting all other items
|
||||
if not item.isSelected():
|
||||
if not event.modifiers() & QtCore.Qt.ControlModifier:
|
||||
for it in self.scene().items():
|
||||
@@ -479,6 +461,10 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
# when more than one item is selected display the contextual menu even if mouse is not above an item
|
||||
elif len(self.scene().selectedItems()) > 1:
|
||||
self._showDeviceContextualMenu(QtGui.QCursor.pos())
|
||||
elif is_not_link and self._adding_link and event.button() == QtCore.Qt.RightButton:
|
||||
# send a escape key to the main window to cancel the link addition
|
||||
key = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Escape, QtCore.Qt.NoModifier)
|
||||
QtGui.QApplication.sendEvent(self._main_window, key)
|
||||
elif item and isinstance(item, NodeItem) and self._adding_link and event.button() == QtCore.Qt.LeftButton:
|
||||
self._userNodeLinking(event, item)
|
||||
elif event.button() == QtCore.Qt.LeftButton and self._adding_note:
|
||||
@@ -565,6 +551,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
QtGui.QGraphicsView.keyPressEvent(self, event)
|
||||
return
|
||||
self.deleteActionSlot()
|
||||
QtGui.QGraphicsView.keyPressEvent(self, event)
|
||||
else:
|
||||
QtGui.QGraphicsView.keyPressEvent(self, event)
|
||||
|
||||
@@ -586,7 +573,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
hBar.setValue(hBar.value() + (delta.x() if QtGui.QApplication.isRightToLeft() else -delta.x()))
|
||||
vBar.setValue(vBar.value() - delta.y())
|
||||
self._last_mouse_position = mapped_global_pos
|
||||
if self._adding_link and self._newlink:
|
||||
if self._adding_link and self._newlink and self._newlink in self.scene().items():
|
||||
# update the mouse position when the user is adding a link.
|
||||
self._newlink.setMousePoint(self.mapToScene(event.pos()))
|
||||
event.ignore()
|
||||
@@ -673,6 +660,9 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
else:
|
||||
self.createNode(node_data, event.pos())
|
||||
elif event.mimeData().hasFormat("text/uri-list") and event.mimeData().hasUrls():
|
||||
# This should not arrive but we received bug report with it...
|
||||
if len(event.mimeData().urls()) == 0:
|
||||
return
|
||||
if len(event.mimeData().urls()) > 1:
|
||||
QtGui.QMessageBox.critical(self, "Project files", "Please drop only one file")
|
||||
return
|
||||
@@ -712,31 +702,66 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
configure_action.triggered.connect(self.configureActionSlot)
|
||||
menu.addAction(configure_action)
|
||||
|
||||
# Action: Change hostname
|
||||
change_hostname_action = QtGui.QAction("Change hostname", menu)
|
||||
change_hostname_action.setIcon(QtGui.QIcon(':/icons/show-hostname.svg'))
|
||||
self.connect(change_hostname_action, QtCore.SIGNAL('triggered()'), self.changeHostnameActionSlot)
|
||||
menu.addAction(change_hostname_action)
|
||||
|
||||
# Action: Change symbol
|
||||
change_symbol_action = QtGui.QAction("Change symbol", menu)
|
||||
change_symbol_action.setIcon(QtGui.QIcon(':/icons/node_conception.svg'))
|
||||
self.connect(change_symbol_action, QtCore.SIGNAL('triggered()'), self.changeSymbolActionSlot)
|
||||
menu.addAction(change_symbol_action)
|
||||
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "console"), items)):
|
||||
console_action = QtGui.QAction("Console", menu)
|
||||
console_action.setIcon(QtGui.QIcon(':/icons/console.svg'))
|
||||
console_action.triggered.connect(self.consoleActionSlot)
|
||||
menu.addAction(console_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "auxConsole"), items)):
|
||||
aux_console_action = QtGui.QAction("Auxiliary console", menu)
|
||||
aux_console_action.setIcon(QtGui.QIcon(':/icons/aux-console.svg'))
|
||||
aux_console_action.triggered.connect(self.auxConsoleActionSlot)
|
||||
menu.addAction(aux_console_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "importConfig"), items)):
|
||||
import_config_action = QtGui.QAction("Import config", menu)
|
||||
import_config_action.setIcon(QtGui.QIcon(':/icons/import_config.svg'))
|
||||
import_config_action.triggered.connect(self.importConfigActionSlot)
|
||||
menu.addAction(import_config_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "exportConfig"), items)):
|
||||
export_config_action = QtGui.QAction("Export config", menu)
|
||||
export_config_action.setIcon(QtGui.QIcon(':/icons/export_config.svg'))
|
||||
export_config_action.triggered.connect(self.exportConfigActionSlot)
|
||||
menu.addAction(export_config_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "saveConfig"), items)):
|
||||
save_config_action = QtGui.QAction("Save config", menu)
|
||||
save_config_action.setIcon(QtGui.QIcon(':/icons/save.svg'))
|
||||
save_config_action.triggered.connect(self.saveConfigActionSlot)
|
||||
menu.addAction(save_config_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "startPacketCapture"), items)):
|
||||
capture_action = QtGui.QAction("Capture", menu)
|
||||
capture_action.setIcon(QtGui.QIcon(':/icons/inspect.svg'))
|
||||
capture_action.triggered.connect(self.captureActionSlot)
|
||||
menu.addAction(capture_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "idlepcs"), items)):
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "idlepc"), items)):
|
||||
idlepc_action = QtGui.QAction("Idle-PC", menu)
|
||||
idlepc_action.setIcon(QtGui.QIcon(':/icons/calculate.svg'))
|
||||
idlepc_action.triggered.connect(self.idlepcActionSlot)
|
||||
menu.addAction(idlepc_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "idlepc"), items)):
|
||||
auto_idlepc_action = QtGui.QAction("Auto Idle-PC", menu)
|
||||
auto_idlepc_action.setIcon(QtGui.QIcon(':/icons/calculate.svg'))
|
||||
auto_idlepc_action.triggered.connect(self.autoIdlepcActionSlot)
|
||||
menu.addAction(auto_idlepc_action)
|
||||
|
||||
if True in list(map(lambda item: isinstance(item, NodeItem) and hasattr(item.node(), "start"), items)):
|
||||
start_action = QtGui.QAction("Start", menu)
|
||||
start_action.setIcon(QtGui.QIcon(':/icons/start.svg'))
|
||||
@@ -782,6 +807,17 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
# item must have no parent
|
||||
if True in list(map(lambda item: item.parentItem() is None, items)):
|
||||
|
||||
if len(items) > 1:
|
||||
horizontal_align_action = QtGui.QAction("Align horizontally", menu)
|
||||
horizontal_align_action.setIcon(QtGui.QIcon(':/icons/horizontally.svg'))
|
||||
horizontal_align_action.triggered.connect(self.horizontalAlignmentSlot)
|
||||
menu.addAction(horizontal_align_action)
|
||||
|
||||
vertical_align_action = QtGui.QAction("Align vertically", menu)
|
||||
vertical_align_action.setIcon(QtGui.QIcon(':/icons/vertically.svg'))
|
||||
vertical_align_action.triggered.connect(self.verticalAlignmentSlot)
|
||||
menu.addAction(vertical_align_action)
|
||||
|
||||
raise_layer_action = QtGui.QAction("Raise one layer", menu)
|
||||
raise_layer_action.setIcon(QtGui.QIcon(':/icons/raise_z_value.svg'))
|
||||
raise_layer_action.triggered.connect(self.raiseLayerActionSlot)
|
||||
@@ -851,6 +887,22 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
if items:
|
||||
self.configureSlot(items)
|
||||
|
||||
def changeHostnameActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the change hostname action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, NodeItem) and item.node().initialized():
|
||||
new_hostname, ok = QtGui.QInputDialog.getText(self, "Change hostname", "Hostname:", QtGui.QLineEdit.Normal, item.node().name())
|
||||
if ok:
|
||||
if hasattr(item.node(), "validateHostname"):
|
||||
if not item.node().validateHostname(new_hostname):
|
||||
QtGui.QMessageBox.critical(self, "Change hostname", "Invalid name detected for this node: {}".format(new_hostname))
|
||||
continue
|
||||
item.node().update({"name": new_hostname})
|
||||
|
||||
def changeSymbolActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the change symbol action in the
|
||||
@@ -866,11 +918,12 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
|
||||
def consoleToNode(self, node):
|
||||
def consoleToNode(self, node, aux=False):
|
||||
"""
|
||||
Start a console application to connect to a node.
|
||||
|
||||
:param node: Node instance
|
||||
:param aux: auxiliary console mode
|
||||
|
||||
:returns: False if the console application could not be started
|
||||
"""
|
||||
@@ -879,16 +932,26 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
# returns True to ignore this node.
|
||||
return True
|
||||
|
||||
if aux and not hasattr(node, "auxConsole"):
|
||||
# returns True to ignore this node.
|
||||
return True
|
||||
|
||||
if hasattr(node, "serialConsole") and node.serialConsole():
|
||||
try:
|
||||
from .serial_console import serialConsole
|
||||
serialConsole(node.name())
|
||||
serialConsole(node.name(), node.serialPipe())
|
||||
except (OSError, ValueError) as e:
|
||||
QtGui.QMessageBox.critical(self, "Console", "Cannot start serial console application: {}".format(e))
|
||||
return False
|
||||
else:
|
||||
name = node.name()
|
||||
console_port = node.console()
|
||||
if aux:
|
||||
console_port = node.auxConsole()
|
||||
if console_port is None:
|
||||
QtGui.QMessageBox.critical(self, "Console", "AUX console port not allocated for {}".format(name))
|
||||
return False
|
||||
else:
|
||||
console_port = node.console()
|
||||
console_host = node.server().host
|
||||
try:
|
||||
from .telnet_console import telnetConsole
|
||||
@@ -918,16 +981,139 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
return False
|
||||
return True
|
||||
|
||||
def consoleFromItems(self, items):
|
||||
"""
|
||||
Console from scene items.
|
||||
|
||||
:param items: Item instances
|
||||
"""
|
||||
|
||||
nodes = {}
|
||||
for item in items:
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "console") and item.node().initialized() and item.node().status() == Node.started:
|
||||
node = item.node()
|
||||
nodes[node.name()] = node
|
||||
|
||||
delay = self._main_window.settings()["delay_console_all"]
|
||||
counter = 0
|
||||
for name in sorted(nodes.keys()):
|
||||
node = nodes[name]
|
||||
callback = functools.partial(self.consoleToNode, node)
|
||||
self._main_window.run_later(counter, callback)
|
||||
counter += delay
|
||||
|
||||
def consoleActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the console action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
self.consoleFromItems(self.scene().selectedItems())
|
||||
|
||||
def auxConsoleFromItems(self, items):
|
||||
"""
|
||||
Aux console from scene items.
|
||||
|
||||
:param items: Item instances
|
||||
"""
|
||||
|
||||
nodes = {}
|
||||
for item in items:
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "auxConsole") and item.node().initialized() and item.node().status() == Node.started:
|
||||
node = item.node()
|
||||
nodes[node.name()] = node
|
||||
|
||||
delay = self._main_window.settings()["delay_console_all"]
|
||||
counter = 0
|
||||
for name in sorted(nodes.keys()):
|
||||
node = nodes[name]
|
||||
callback = functools.partial(self.consoleToNode, node, aux=True)
|
||||
self._main_window.run_later(counter, callback)
|
||||
counter += delay
|
||||
|
||||
def auxConsoleActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the auxiliary console action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
self.auxConsoleFromItems(self.scene().selectedItems())
|
||||
|
||||
def importConfigActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the import config action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
items = []
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, NodeItem):
|
||||
if self.consoleToNode(item.node()):
|
||||
continue
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "importConfig") and item.node().initialized():
|
||||
items.append(item)
|
||||
|
||||
if not items:
|
||||
return
|
||||
|
||||
if len(items) > 1:
|
||||
path = QtGui.QFileDialog.getExistingDirectory(self, "Import directory", ".", QtGui.QFileDialog.ShowDirsOnly)
|
||||
if path:
|
||||
for item in items:
|
||||
item.node().importConfigFromDirectory(path)
|
||||
else:
|
||||
item = items[0]
|
||||
path, _ = QtGui.QFileDialog.getOpenFileNameAndFilter(self,
|
||||
"Import config",
|
||||
".",
|
||||
"All files (*.*);;Config files (*.cfg)",
|
||||
"Config files (*.cfg)")
|
||||
if path:
|
||||
item.node().importConfig(path)
|
||||
if hasattr(item.node(), "importPrivateConfig"):
|
||||
path, _ = QtGui.QFileDialog.getOpenFileNameAndFilter(self,
|
||||
"Import private-config",
|
||||
".",
|
||||
"All files (*.*);;Config files (*.cfg)",
|
||||
"Config files (*.cfg)")
|
||||
if path:
|
||||
item.node().importPrivateConfig(path)
|
||||
|
||||
def exportConfigActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the export config action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
items = []
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "exportConfig") and item.node().initialized():
|
||||
items.append(item)
|
||||
|
||||
if not items:
|
||||
return
|
||||
|
||||
if len(items) > 1:
|
||||
path = QtGui.QFileDialog.getExistingDirectory(self, "Export directory", ".", QtGui.QFileDialog.ShowDirsOnly)
|
||||
if path:
|
||||
for item in items:
|
||||
item.node().exportConfigToDirectory(path)
|
||||
else:
|
||||
item = items[0]
|
||||
if hasattr(item.node(), "importPrivateConfig"):
|
||||
config_path = QtGui.QFileDialog.getSaveFileName(self, "Export startup-config")
|
||||
private_config_path = QtGui.QFileDialog.getSaveFileName(self, "Export private-config")
|
||||
item.node().exportConfig(config_path, private_config_path)
|
||||
else:
|
||||
config_path = QtGui.QFileDialog.getSaveFileName(self, "Export config")
|
||||
item.node().exportConfig(config_path)
|
||||
|
||||
def saveConfigActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the save config action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
for item in self.scene().selectedItems():
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "saveConfig") and item.node().initialized():
|
||||
item.node().saveConfig()
|
||||
|
||||
def captureActionSlot(self):
|
||||
"""
|
||||
@@ -964,53 +1150,64 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
QtGui.QMessageBox.critical(self, "Idle-PC", "Please select only one router")
|
||||
return
|
||||
item = items[0]
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "idlepcs") and item.node().initialized():
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "idlepc") and item.node().initialized():
|
||||
router = item.node()
|
||||
idlepc = router.idlepc()
|
||||
router.computeIdlepcs()
|
||||
# question = QtGui.QMessageBox.question(self, "Auto Idle-PC", "Would you like to automatically find a suitable Idle-PC value (but not optimal)?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
|
||||
|
||||
#TODO: improve to show progress over 10 seconds
|
||||
self._idlepc_progress_dialog = QtGui.QProgressDialog("Computing values...", "Cancel", 0, 0, parent=self)
|
||||
self._idlepc_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
self._idlepc_progress_dialog.setWindowTitle("Idle-PC")
|
||||
# if question == QtGui.QMessageBox.Yes:
|
||||
# router.computeAutoIdlepc(self._autoIdlepcCallback)
|
||||
# else:
|
||||
router.computeIdlepcs(self._idlepcCallback)
|
||||
|
||||
def cancel():
|
||||
router.idlepc_signal.disconnect(self._showIdlepcProposals)
|
||||
router.server_error_signal.disconnect(self._showIdlepcError)
|
||||
router.setIdlepc(idlepc)
|
||||
|
||||
self._idlepc_progress_dialog.canceled.connect(cancel)
|
||||
router.idlepc_signal.connect(self._showIdlepcProposals)
|
||||
router.server_error_signal.connect(self._showIdlepcError)
|
||||
self._idlepc_progress_dialog.show()
|
||||
|
||||
def _showIdlepcError(self, node_id, code, message):
|
||||
def _idlepcCallback(self, result, error=False, context={}, **kwargs):
|
||||
"""
|
||||
Shows an error message if the Idle-PC values cannot be computed.
|
||||
Slot to allow the user to select an idle-pc value.
|
||||
"""
|
||||
|
||||
self._idlepc_progress_dialog.reject()
|
||||
QtGui.QMessageBox.critical(self, "Idle-PC", "Error: {}".format(message))
|
||||
router = self.scene().selectedItems()[0].node()
|
||||
router.server_error_signal.disconnect(self._showIdlepcError)
|
||||
router.idlepc_signal.disconnect(self._showIdlepcProposals)
|
||||
if error:
|
||||
QtGui.QMessageBox.critical(self, "Idle-PC", "Error: {}".format(result["message"]))
|
||||
else:
|
||||
router = context["router"]
|
||||
log.info("{} has received Idle-PC proposals".format(router.name()))
|
||||
idlepcs = result
|
||||
if idlepcs and idlepcs[0] != "0x0":
|
||||
dialog = IdlePCDialog(router, idlepcs, parent=self)
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
else:
|
||||
QtGui.QMessageBox.critical(self, "Idle-PC", "Sorry no Idle-PC values could be computed, please check again with Cisco IOS in a different state")
|
||||
|
||||
def _showIdlepcProposals(self):
|
||||
def autoIdlepcActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the auto idlepc action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
items = self.scene().selectedItems()
|
||||
if len(items) != 1:
|
||||
QtGui.QMessageBox.critical(self, "Auto Idle-PC", "Please select only one router")
|
||||
return
|
||||
item = items[0]
|
||||
if isinstance(item, NodeItem) and hasattr(item.node(), "idlepc") and item.node().initialized():
|
||||
router = item.node()
|
||||
router.computeAutoIdlepc(self._autoIdlepcCallback)
|
||||
|
||||
def _autoIdlepcCallback(self, result, error=False, context={}, **kwargs):
|
||||
"""
|
||||
Slot to allow the user to select an idlepc value.
|
||||
"""
|
||||
|
||||
self._idlepc_progress_dialog.accept()
|
||||
router = self.scene().selectedItems()[0].node()
|
||||
router.idlepc_signal.disconnect(self._showIdlepcProposals)
|
||||
router.server_error_signal.disconnect(self._showIdlepcError)
|
||||
idlepcs = router.idlepcs()
|
||||
if idlepcs and idlepcs[0] != "0x0":
|
||||
dialog = IdlePCDialog(router, idlepcs, parent=self)
|
||||
dialog.show()
|
||||
dialog.exec_()
|
||||
if error:
|
||||
QtGui.QMessageBox.critical(self, "Auto Idle-PC", "Error: {}".format(result["message"]))
|
||||
else:
|
||||
QtGui.QMessageBox.critical(self, "Idle-PC", "Sorry no Idle-PC values could be computed, please check again with Cisco IOS in a different state")
|
||||
router = context["router"]
|
||||
idlepc = result["idlepc"]
|
||||
log.info("{} has received the auto idle-pc value: {}".format(router.name(), idlepc))
|
||||
router.setIdlepc(idlepc)
|
||||
# apply the idle-pc to templates with the same IOS image
|
||||
ios_image = os.path.basename(router.settings()["image"])
|
||||
router.module().updateImageIdlepc(ios_image, idlepc)
|
||||
QtGui.QMessageBox.information(self, "Auto Idle-PC", "Idle-PC value {} has been applied on {}".format(idlepc, router.name()))
|
||||
|
||||
def duplicateActionSlot(self):
|
||||
"""
|
||||
@@ -1066,6 +1263,32 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
text_edit_dialog.show()
|
||||
text_edit_dialog.exec_()
|
||||
|
||||
def horizontalAlignmentSlot(self):
|
||||
"""
|
||||
Slot to receive events from the horizontal align action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
horizontal_pos = None
|
||||
for item in self.scene().selectedItems():
|
||||
if item.parentItem() is None:
|
||||
if horizontal_pos is None:
|
||||
horizontal_pos = item.y()
|
||||
item.setPos(item.x(), horizontal_pos)
|
||||
|
||||
def verticalAlignmentSlot(self):
|
||||
"""
|
||||
Slot to receive events from the vertical align action in the
|
||||
contextual menu.
|
||||
"""
|
||||
|
||||
vertical_position = None
|
||||
for item in self.scene().selectedItems():
|
||||
if item.parentItem() is None:
|
||||
if vertical_position is None:
|
||||
vertical_position = item.x()
|
||||
item.setPos(vertical_position, item.y())
|
||||
|
||||
def raiseLayerActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the raise one layer action in the
|
||||
@@ -1089,6 +1312,8 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
current_zvalue = item.zValue()
|
||||
item.setZValue(current_zvalue - 1)
|
||||
item.update()
|
||||
if item.zValue() == -1:
|
||||
self._background_warning_msgbox.showMessage("Object moved to a background layer. You will now have to use the right-click action to select this object in the future and raise it to layer 0 to be able to move it")
|
||||
|
||||
def deleteActionSlot(self):
|
||||
"""
|
||||
@@ -1127,7 +1352,6 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
"""
|
||||
|
||||
try:
|
||||
log.debug('In createNode')
|
||||
node_module = None
|
||||
for module in MODULES:
|
||||
instance = module.instance()
|
||||
@@ -1139,7 +1363,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
if not node_module:
|
||||
raise ModuleError("Could not find any module for {}".format(node_class))
|
||||
|
||||
if not "server" in node_data:
|
||||
if "server" not in node_data:
|
||||
server = node_module.allocateServer(node_class)
|
||||
elif node_data["server"] == "local":
|
||||
server = Servers.instance().localServer()
|
||||
@@ -1151,10 +1375,11 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||
except ValueError:
|
||||
raise ModuleError("Wrong format for server: '{}', please recreate the node in preferences".format(node_data["server"]))
|
||||
server = Servers.instance().getRemoteServer(host, port)
|
||||
if not server.connected() and ConnectToServer(self, server) is False:
|
||||
|
||||
if server is None:
|
||||
return
|
||||
|
||||
node = node_module.createNode(node_class, server)
|
||||
node = node_module.createNode(node_class, server, self._main_window.project())
|
||||
node.error_signal.connect(self._main_window.uiConsoleTextEdit.writeError)
|
||||
node.warning_signal.connect(self._main_window.uiConsoleTextEdit.writeWarning)
|
||||
node.server_error_signal.connect(self._main_window.uiConsoleTextEdit.writeServerError)
|
||||
|
||||
407
gns3/http_client.py
Normal file
407
gns3/http_client.py
Normal file
@@ -0,0 +1,407 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import json
|
||||
import http
|
||||
import uuid
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from functools import partial
|
||||
|
||||
from .version import __version__, __version_info__
|
||||
from .qt import QtCore, QtNetwork
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HttpBadRequest(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class HTTPClient(QtCore.QObject):
|
||||
|
||||
"""
|
||||
HTTP client.
|
||||
|
||||
:param url: URL to connect to the server
|
||||
:param network_manager: A QT network manager
|
||||
"""
|
||||
|
||||
_instance_count = 1
|
||||
|
||||
# Callback class used for displaying progress
|
||||
_progress_callback = None
|
||||
|
||||
connected_signal = QtCore.Signal()
|
||||
connection_error_signal = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, url, network_manager):
|
||||
|
||||
super().__init__()
|
||||
self._url = url
|
||||
self._version = ""
|
||||
|
||||
url_settings = urllib.parse.urlparse(url)
|
||||
self.scheme = url_settings.scheme
|
||||
self.host = url_settings.netloc.split(":")[0]
|
||||
self.port = url_settings.port
|
||||
|
||||
self._connected = False
|
||||
self._local = True
|
||||
self._cloud = False
|
||||
|
||||
self._network_manager = network_manager
|
||||
|
||||
# create an unique ID
|
||||
self._id = HTTPClient._instance_count
|
||||
HTTPClient._instance_count += 1
|
||||
|
||||
def notify_progress_start_query(self, query_id):
|
||||
"""
|
||||
Called when a query start
|
||||
"""
|
||||
if HTTPClient._progress_callback:
|
||||
HTTPClient._progress_callback.add_query_signal.emit(query_id, "Waiting for {scheme}://{host}:{port}".format(scheme=self.scheme, host=self.host, port=self.port))
|
||||
|
||||
def notify_progress_end_query(cls, query_id):
|
||||
"""
|
||||
Called when a query is over
|
||||
"""
|
||||
|
||||
if HTTPClient._progress_callback:
|
||||
HTTPClient._progress_callback.remove_query_signal.emit(query_id)
|
||||
|
||||
@classmethod
|
||||
def setProgressCallback(cls, progress_callback):
|
||||
"""
|
||||
:param progress_callback: A progress callback instance
|
||||
"""
|
||||
|
||||
cls._progress_callback = progress_callback
|
||||
|
||||
@staticmethod
|
||||
def reset():
|
||||
"""Reset HTTP client internal variables"""
|
||||
|
||||
HTTPClient._instance_count = 0
|
||||
|
||||
def url(self):
|
||||
"""Returns current server url"""
|
||||
|
||||
return "{scheme}://{host}:{port}".format(scheme=self.scheme, host=self.host, port=self.port)
|
||||
|
||||
def id(self):
|
||||
"""
|
||||
Returns this HTTP Client identifier.
|
||||
:returns: HTTP client identifier (string)
|
||||
"""
|
||||
|
||||
return self._id
|
||||
|
||||
def setLocal(self, value):
|
||||
"""
|
||||
Sets either this is a connection to a local server or not.
|
||||
:param value: boolean
|
||||
"""
|
||||
|
||||
self._local = value
|
||||
|
||||
def isLocal(self):
|
||||
"""
|
||||
Returns either this is a connection to a local server or not.
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
return self._local
|
||||
|
||||
def connected(self):
|
||||
"""
|
||||
Returns if the client is connected.
|
||||
:returns: True or False
|
||||
"""
|
||||
|
||||
return self._connected
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Closes the connection with the server.
|
||||
"""
|
||||
|
||||
self._connected = False
|
||||
|
||||
def isServerRunning(self):
|
||||
"""
|
||||
Check if a server is already running on this host.
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
try:
|
||||
url = "{scheme}://{host}:{port}/v1/version".format(scheme=self.scheme, host=self.host, port=self.port)
|
||||
response = urllib.request.urlopen(url, timeout=2)
|
||||
content_type = response.getheader("CONTENT-TYPE")
|
||||
if response.status == 200 and content_type == "application/json":
|
||||
content = response.read()
|
||||
json_data = json.loads(content.decode("utf-8"))
|
||||
version = json_data.get("version")
|
||||
local_server = json_data.get("local", False)
|
||||
if version != __version__:
|
||||
log.debug("Client version {} differs with server version {}".format(__version__, version))
|
||||
return False
|
||||
if not local_server:
|
||||
log.debug("Running server is not a GNS3 local server (not started with --local)")
|
||||
return False
|
||||
return True
|
||||
except (OSError, urllib.error.HTTPError, http.client.BadStatusLine) as e:
|
||||
log.debug("No GNS3 server is already running on {}:{}: {}".format(self.host, self.port, e))
|
||||
return False
|
||||
|
||||
def get(self, path, callback, context={}):
|
||||
"""
|
||||
HTTP GET on the remote server
|
||||
|
||||
:param path: Remote path
|
||||
:param callback: callback method to call when the server replies
|
||||
:param context: Pass a context to the response callback
|
||||
"""
|
||||
|
||||
self.createHTTPQuery("GET", path, callback, context=context)
|
||||
|
||||
def put(self, path, callback, body={}, context={}):
|
||||
"""
|
||||
HTTP PUT on the remote server
|
||||
|
||||
:param path: Remote path
|
||||
:param callback: callback method to call when the server replies
|
||||
:param context: Pass a context to the response callback
|
||||
:param body: params to send (dictionary)
|
||||
"""
|
||||
|
||||
self.createHTTPQuery("PUT", path, callback, context=context, body=body)
|
||||
|
||||
def post(self, path, callback, body={}, context={}):
|
||||
"""
|
||||
HTTP POST on the remote server
|
||||
|
||||
:param path: Remote path
|
||||
:param callback: callback method to call when the server replies
|
||||
:param context: Pass a context to the response callback
|
||||
:param body: params to send (dictionary)
|
||||
"""
|
||||
|
||||
self.createHTTPQuery("POST", path, callback, context=context, body=body)
|
||||
|
||||
def delete(self, path, callback, context={}):
|
||||
"""
|
||||
HTTP DELETE on the remote server
|
||||
|
||||
:param path: Remote path
|
||||
:param callback: callback method to call when the server replies
|
||||
:param context: Pass a context to the response callback
|
||||
"""
|
||||
|
||||
self.createHTTPQuery("DELETE", path, callback, context=context)
|
||||
|
||||
def _request(self, url):
|
||||
"""
|
||||
Get a QNetworkRequest object. You can mock this
|
||||
if you want low level mocking.
|
||||
|
||||
:param url: Url of remote ressource (QtCore.QUrl)
|
||||
:returns: QT Network request (QtNetwork.QNetworkRequest)
|
||||
"""
|
||||
|
||||
return QtNetwork.QNetworkRequest(url)
|
||||
|
||||
def createHTTPQuery(self, method, path, callback, body={}, context={}):
|
||||
"""
|
||||
Call the remote server, if not connected, check connection before
|
||||
|
||||
:param method: HTTP method
|
||||
:param path: Remote path
|
||||
:param body: params to send (dictionary)
|
||||
:param callback: callback method to call when the server replies
|
||||
:param context: Pass a context to the response callback
|
||||
"""
|
||||
|
||||
if self._connected:
|
||||
self.executeHTTPQuery(method, path, callback, body, context=context)
|
||||
else:
|
||||
log.info("Connection to {}:{}".format(self.host, self.port))
|
||||
self.executeHTTPQuery("GET", "/version", partial(self._callbackConnect, method, path, callback, body, context), {})
|
||||
|
||||
def _callbackConnect(self, method, path, callback, body, original_context, params, error=False, **kwargs):
|
||||
"""
|
||||
Callback after /version response. Continue execution of query
|
||||
|
||||
:param method: HTTP method
|
||||
:param path: Remote path
|
||||
:param body: params to send (dictionary)
|
||||
:param original_context: Original context
|
||||
:param callback: callback method to call when the server replies
|
||||
"""
|
||||
|
||||
if error is not False:
|
||||
msg = "Can't connect to server {}://{}:{}".format(self.scheme, self.host, self.port)
|
||||
if callback is not None:
|
||||
callback({"message": msg}, error=True, server=self)
|
||||
return
|
||||
|
||||
if "version" not in params or "local" not in params:
|
||||
msg = "The remote server {}://{}:{} is not a GNS 3 server".format(self.scheme, self.host, self.port)
|
||||
log.error(msg)
|
||||
if callback is not None:
|
||||
callback({"message": msg}, error=True, server=self)
|
||||
return
|
||||
|
||||
if params["version"] != __version__:
|
||||
msg = "Client version {} differs with server version {}".format(__version__, params["version"])
|
||||
log.error(msg)
|
||||
# Official release
|
||||
if __version_info__[3] == 0:
|
||||
if callback is not None:
|
||||
callback({"message": msg}, error=True, server=self)
|
||||
return
|
||||
else:
|
||||
print(msg)
|
||||
print("WARNING: Use a different client and server version can create bugs. Use it at your own risk.")
|
||||
|
||||
if params["local"] != self.isLocal():
|
||||
msg = "Running server is not a GNS3 local server (not started with --local)"
|
||||
log.error(msg)
|
||||
if callback is not None:
|
||||
callback({"message": msg}, error=True, server=self)
|
||||
return
|
||||
|
||||
self.executeHTTPQuery(method, path, callback, body, context=original_context)
|
||||
self._connected = True
|
||||
self._version = params["version"]
|
||||
|
||||
def executeHTTPQuery(self, method, path, callback, body, context={}):
|
||||
"""
|
||||
Call the remote server
|
||||
|
||||
:param method: HTTP method
|
||||
:param path: Remote path
|
||||
:param body: params to send (dictionary)
|
||||
:param callback: callback method to call when the server replies
|
||||
:param context: Pass a context to the response callback
|
||||
"""
|
||||
|
||||
import copy
|
||||
context = copy.copy(context)
|
||||
context["query_id"] = str(uuid.uuid4())
|
||||
self.notify_progress_start_query(context["query_id"])
|
||||
log.debug("{method} {scheme}://{host}:{port}/v1{path} {body}".format(method=method, scheme=self.scheme, host=self.host, port=self.port, path=path, body=body))
|
||||
url = QtCore.QUrl("{scheme}://{host}:{port}/v1{path}".format(scheme=self.scheme, host=self.host, port=self.port, path=path))
|
||||
request = self._request(url)
|
||||
request.setRawHeader("Content-Type", "application/json")
|
||||
request.setRawHeader("Content-Length", str(len(body)))
|
||||
request.setRawHeader("User-Agent", "GNS3 QT Client v{version}".format(version=__version__))
|
||||
|
||||
if method == "GET":
|
||||
response = self._network_manager.get(request)
|
||||
|
||||
if method == "PUT":
|
||||
body = json.dumps(body)
|
||||
request.setRawHeader("Content-Type", "application/json")
|
||||
request.setRawHeader("Content-Length", str(len(body)))
|
||||
response = self._network_manager.put(request, body)
|
||||
|
||||
if method == "POST":
|
||||
body = json.dumps(body)
|
||||
request.setRawHeader("Content-Type", "application/json")
|
||||
request.setRawHeader("Content-Length", str(len(body)))
|
||||
response = self._network_manager.post(request, body)
|
||||
|
||||
if method == "DELETE":
|
||||
response = self._network_manager.deleteResource(request)
|
||||
|
||||
if HTTPClient._progress_callback and HTTPClient._progress_callback.progress_dialog():
|
||||
request_canceled = partial(self._requestCanceled, response, context)
|
||||
HTTPClient._progress_callback.progress_dialog().canceled.connect(request_canceled)
|
||||
response.finished.connect(partial(self._processResponse, response, callback, context))
|
||||
|
||||
def _requestCanceled(self, response, context):
|
||||
|
||||
if response.isRunning():
|
||||
response.abort()
|
||||
if "query_id" in context:
|
||||
self.notify_progress_end_query(context["query_id"])
|
||||
|
||||
def _processResponse(self, response, callback, context):
|
||||
|
||||
status = None
|
||||
body = None
|
||||
|
||||
if "query_id" in context:
|
||||
self.notify_progress_end_query(context["query_id"])
|
||||
if response.error() != QtNetwork.QNetworkReply.NoError:
|
||||
error_code = response.error()
|
||||
if error_code < 200:
|
||||
self._connected = False
|
||||
else:
|
||||
status = response.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
|
||||
error_message = response.errorString()
|
||||
log.info("Response error: {}".format(error_message))
|
||||
try:
|
||||
body = bytes(response.readAll()).decode("utf-8")
|
||||
# Some time anti-virus intercept our query and reply with garbage content
|
||||
except UnicodeError:
|
||||
body = None
|
||||
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
|
||||
if callback is not None:
|
||||
if not body or content_type != "application/json":
|
||||
callback({"message": error_message}, error=True, server=self, context=context)
|
||||
else:
|
||||
log.debug(body)
|
||||
callback(json.loads(body), error=True, server=self, context=context)
|
||||
else:
|
||||
status = response.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
|
||||
log.debug("Decoding response from {} response {}".format(response.url().toString(), status))
|
||||
body = bytes(response.readAll()).decode("utf-8")
|
||||
content_type = response.header(QtNetwork.QNetworkRequest.ContentTypeHeader)
|
||||
log.debug(body)
|
||||
if body and content_type == "application/json":
|
||||
params = json.loads(body)
|
||||
else:
|
||||
params = {}
|
||||
if callback is not None:
|
||||
if status >= 400:
|
||||
callback(params, error=True, server=self, context=context)
|
||||
else:
|
||||
callback(params, server=self, context=context)
|
||||
# response.deleteLater()
|
||||
if status == 400:
|
||||
raise HttpBadRequest(body)
|
||||
|
||||
def dump(self):
|
||||
"""
|
||||
Returns a representation of this server.
|
||||
:returns: dictionary
|
||||
"""
|
||||
|
||||
return {"id": self._id,
|
||||
"host": self.host,
|
||||
"port": self.port,
|
||||
"local": self._local,
|
||||
"cloud": self._cloud}
|
||||
|
||||
def isCloud(self):
|
||||
return False
|
||||
@@ -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.
|
||||
|
||||
@@ -235,6 +236,18 @@ class LinkItem(QtGui.QGraphicsPathItem):
|
||||
self._hovered = False
|
||||
self.adjust()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""
|
||||
Handles all key press events
|
||||
|
||||
:param event: QKeyEvent
|
||||
"""
|
||||
|
||||
#On pressing backspace or delete key, the selected link gets deleted
|
||||
if event.key() == QtCore.Qt.Key_Delete or event.key() == QtCore.Qt.Key_Backspace:
|
||||
self._deleteActionSlot()
|
||||
return
|
||||
|
||||
def _deleteActionSlot(self):
|
||||
"""
|
||||
Slot to receive events from the delete action in the
|
||||
|
||||
@@ -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):
|
||||
@@ -234,7 +235,7 @@ class NoteItem(QtGui.QGraphicsTextItem):
|
||||
if color:
|
||||
self.setDefaultTextColor(QtGui.QColor(color))
|
||||
if rotation is not None:
|
||||
self.setRotation(rotation)
|
||||
self.setRotation(float(rotation))
|
||||
if z is not None:
|
||||
self.setZValue(z)
|
||||
|
||||
|
||||
@@ -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
|
||||
16
gns3/link.py
16
gns3/link.py
@@ -28,6 +28,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Link(QtCore.QObject):
|
||||
|
||||
"""
|
||||
Link implementation.
|
||||
|
||||
@@ -136,10 +137,12 @@ class Link(QtCore.QObject):
|
||||
self._destination_port.name()))
|
||||
|
||||
# delete the NIOs on both source and destination nodes
|
||||
self._source_node.deleteNIO(self._source_port)
|
||||
if self._source_port.nio():
|
||||
self._source_node.deleteNIO(self._source_port)
|
||||
self._source_port.setFree()
|
||||
self._source_node.updated_signal.emit()
|
||||
self._destination_node.deleteNIO(self._destination_port)
|
||||
if self._destination_port.nio():
|
||||
self._destination_node.deleteNIO(self._destination_port)
|
||||
self._destination_port.setFree()
|
||||
self._destination_node.updated_signal.emit()
|
||||
|
||||
@@ -343,10 +346,6 @@ class Link(QtCore.QObject):
|
||||
# ignore TypeError: 'method' object is not connected
|
||||
pass
|
||||
|
||||
self._source_node.deleteNIO(self._source_port)
|
||||
self._source_port.setFree()
|
||||
self._source_node.updated_signal.emit()
|
||||
|
||||
elif self._destination_node.id() != node_id:
|
||||
try:
|
||||
# the source node has canceled its NIO allocation
|
||||
@@ -355,10 +354,6 @@ class Link(QtCore.QObject):
|
||||
# ignore TypeError: 'method' object is not connected
|
||||
pass
|
||||
|
||||
self._destination_node.deleteNIO(self._destination_port)
|
||||
self._destination_port.setFree()
|
||||
self._destination_node.updated_signal.emit()
|
||||
|
||||
self._source_node.nio_cancel_signal.disconnect(self.cancelNIOSlot)
|
||||
self._destination_node.nio_cancel_signal.disconnect(self.cancelNIOSlot)
|
||||
else:
|
||||
@@ -371,6 +366,7 @@ class Link(QtCore.QObject):
|
||||
|
||||
self._source_nio_active = False
|
||||
self._destination_nio_active = False
|
||||
self.deleteLink()
|
||||
|
||||
def dump(self):
|
||||
"""
|
||||
|
||||
196
gns3/local_config.py
Normal file
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", encoding="utf-8") as f:
|
||||
json.dump({"version": __version__, "type": "settings"}, f)
|
||||
except OSError as e:
|
||||
log.error("Could not create the config file {}: {}".format(self._config_file, e))
|
||||
|
||||
user_settings = self._readConfig(self._config_file)
|
||||
# overwrite system wide settings with user specific ones
|
||||
self._settings.update(user_settings)
|
||||
self._writeConfig()
|
||||
|
||||
def _readConfig(self, config_path):
|
||||
"""
|
||||
Read the configuration file.
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except (ValueError, OSError) as e:
|
||||
log.error("Could not read the config file {}: {}".format(self._config_file, e))
|
||||
|
||||
return dict()
|
||||
|
||||
def _writeConfig(self):
|
||||
"""
|
||||
Write the configuration file.
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(self._config_file, "w", encoding="utf-8") as f:
|
||||
json.dump(self._settings, f, sort_keys=True, indent=4)
|
||||
except (ValueError, OSError) as e:
|
||||
log.error("Could not write the config file {}: {}".format(self._config_file, e))
|
||||
|
||||
def configFilePath(self):
|
||||
"""
|
||||
Returns the config file path.
|
||||
|
||||
:returns: path to the config file.
|
||||
"""
|
||||
|
||||
return self._config_file
|
||||
|
||||
def setConfigFilePath(self, config_file):
|
||||
"""
|
||||
Set a new config file
|
||||
|
||||
:returns: path to the config file.
|
||||
"""
|
||||
|
||||
self._settings = self._readConfig(self._config_file)
|
||||
self._config_file = config_file
|
||||
|
||||
def settings(self):
|
||||
"""
|
||||
Get the settings.
|
||||
|
||||
:returns: settings (dict)
|
||||
"""
|
||||
|
||||
return self._readConfig(self._config_file)
|
||||
|
||||
def setSettings(self, settings):
|
||||
"""
|
||||
Save the settings.
|
||||
|
||||
:param settings: settings to save (dict)
|
||||
"""
|
||||
|
||||
self._settings.update(settings)
|
||||
self._writeConfig()
|
||||
|
||||
def loadSectionSettings(self, section, default_settings):
|
||||
"""
|
||||
Get all the settings from a given section.
|
||||
|
||||
:param default_settings: setting names and default values (dict)
|
||||
|
||||
:returns: settings (dict)
|
||||
"""
|
||||
|
||||
settings = self.settings().get(section, dict())
|
||||
|
||||
# use default values for missing settings
|
||||
for name, value in default_settings.items():
|
||||
if name not in settings:
|
||||
settings[name] = value
|
||||
|
||||
if section not in self._settings:
|
||||
self._settings[section] = {}
|
||||
self._settings[section].update(settings)
|
||||
return settings
|
||||
|
||||
def saveSectionSettings(self, section, settings):
|
||||
"""
|
||||
Save all the settings in a given section.
|
||||
|
||||
:param section: section name
|
||||
:param settings: settings to save (dict)
|
||||
"""
|
||||
|
||||
if section not in self._settings:
|
||||
self._settings[section] = {}
|
||||
self._settings[section].update(settings)
|
||||
self._writeConfig()
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
"""
|
||||
Singleton to return only on instance of LocalConfig.
|
||||
|
||||
:returns: instance of LocalConfig
|
||||
"""
|
||||
|
||||
if not hasattr(LocalConfig, "_instance"):
|
||||
LocalConfig._instance = LocalConfig()
|
||||
return LocalConfig._instance
|
||||
131
gns3/local_server_config.py
Normal file
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.RawConfigParser()
|
||||
if sys.platform.startswith("win"):
|
||||
filename = "gns3_server.ini"
|
||||
else:
|
||||
filename = "gns3_server.conf"
|
||||
self._config_file = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), filename)
|
||||
try:
|
||||
# create the config file if it doesn't exist
|
||||
open(self._config_file, "a").close()
|
||||
except OSError as e:
|
||||
log.error("Could not create the local server configuration {}: {}".format(self._config_file, e))
|
||||
self.readConfig()
|
||||
|
||||
def readConfig(self):
|
||||
"""
|
||||
Read the configuration file.
|
||||
"""
|
||||
|
||||
try:
|
||||
self._config.read(self._config_file)
|
||||
except (OSError, configparser.Error) as e:
|
||||
log.error("Could not read the local server configuration {}: {}".format(self._config_file, e))
|
||||
|
||||
def writeConfig(self):
|
||||
"""
|
||||
Write the configuration file.
|
||||
"""
|
||||
|
||||
try:
|
||||
log.debug("Write configuration file %s", self._config_file)
|
||||
with open(self._config_file, "w", encoding="utf-8") as fp:
|
||||
self._config.write(fp)
|
||||
except (OSError, configparser.Error) as e:
|
||||
log.error("Could not write the local server configuration {}: {}".format(self._config_file, e))
|
||||
|
||||
def loadSettings(self, section, default_settings, types):
|
||||
"""
|
||||
Get all the settings from a given section.
|
||||
|
||||
:param section: section name
|
||||
:param default_settings: setting names and default values (dict)
|
||||
:param types: setting types (dict)
|
||||
|
||||
:returns: settings (dict)
|
||||
"""
|
||||
|
||||
if section not in self._config:
|
||||
self._config[section] = {}
|
||||
|
||||
settings = {}
|
||||
for name, default in default_settings.items():
|
||||
if types[name] is int:
|
||||
settings[name] = self._config[section].getint(name, default)
|
||||
elif types[name] is bool:
|
||||
settings[name] = self._config[section].getboolean(name, default)
|
||||
elif types[name] is float:
|
||||
settings[name] = self._config[section].getfloat(name, default)
|
||||
else:
|
||||
settings[name] = self._config[section].get(name, default)
|
||||
|
||||
# sync with the config file
|
||||
self.saveSettings(section, settings)
|
||||
return settings
|
||||
|
||||
def saveSettings(self, section, settings):
|
||||
"""
|
||||
Save all the settings in a given section.
|
||||
|
||||
:param section: section name
|
||||
:param settings: settings to save (dict)
|
||||
"""
|
||||
|
||||
changed = False
|
||||
if section not in self._config:
|
||||
self._config[section] = {}
|
||||
changed = True
|
||||
|
||||
for name, value in settings.items():
|
||||
if name not in self._config[section] or self._config[section][name] != str(value):
|
||||
self._config[section][name] = str(value)
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
self.writeConfig()
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
"""
|
||||
Singleton to return only on instance of LocalServerConfig.
|
||||
|
||||
:returns: instance of Config
|
||||
"""
|
||||
|
||||
if not hasattr(LocalServerConfig, "_instance"):
|
||||
LocalServerConfig._instance = LocalServerConfig()
|
||||
return LocalServerConfig._instance
|
||||
93
gns3/logger.py
Normal file
93
gns3/logger.py
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Provide a pretty logging on console"""
|
||||
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
|
||||
class ColouredFormatter(logging.Formatter):
|
||||
RESET = '\x1B[0m'
|
||||
RED = '\x1B[31m'
|
||||
YELLOW = '\x1B[33m'
|
||||
GREEN = '\x1B[32m'
|
||||
PINK = '\x1b[35m'
|
||||
|
||||
def format(self, record, colour=False):
|
||||
|
||||
message = super().format(record)
|
||||
|
||||
if not colour:
|
||||
return message.replace("#RESET#", "")
|
||||
|
||||
level_no = record.levelno
|
||||
if level_no >= logging.CRITICAL:
|
||||
colour = self.RED
|
||||
elif level_no >= logging.ERROR:
|
||||
colour = self.RED
|
||||
elif level_no >= logging.WARNING:
|
||||
colour = self.YELLOW
|
||||
elif level_no >= logging.INFO:
|
||||
colour = self.GREEN
|
||||
elif level_no >= logging.DEBUG:
|
||||
colour = self.PINK
|
||||
else:
|
||||
colour = self.RESET
|
||||
|
||||
message = message.replace("#RESET#", self.RESET)
|
||||
message = '{colour}{message}{reset}'.format(colour=colour, message=message, reset=self.RESET)
|
||||
|
||||
return message
|
||||
|
||||
|
||||
class ColouredStreamHandler(logging.StreamHandler):
|
||||
|
||||
def format(self, record, colour=False):
|
||||
|
||||
if not isinstance(self.formatter, ColouredFormatter):
|
||||
self.formatter = ColouredFormatter()
|
||||
|
||||
return self.formatter.format(record, colour)
|
||||
|
||||
def emit(self, record):
|
||||
|
||||
stream = self.stream
|
||||
try:
|
||||
msg = self.format(record, stream.isatty())
|
||||
stream.write(msg)
|
||||
stream.write(self.terminator)
|
||||
self.flush()
|
||||
# On OSX when frozen flush raise a BrokenPipeError
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
except Exception:
|
||||
self.handleError(record)
|
||||
|
||||
|
||||
def init_logger(level, quiet=False):
|
||||
if sys.platform.startswith("win"):
|
||||
stream_handler = logging.StreamHandler(sys.stdout)
|
||||
stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {filename}:{lineno} {message}", "%Y-%m-%d %H:%M:%S", "{")
|
||||
else:
|
||||
stream_handler = ColouredStreamHandler(sys.stdout)
|
||||
stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {filename}:{lineno}#RESET# {message}", "%Y-%m-%d %H:%M:%S", "{")
|
||||
logging.basicConfig(level=level, handlers=[stream_handler])
|
||||
logging.getLogger().addHandler(stream_handler)
|
||||
return logging.getLogger()
|
||||
138
gns3/main.py
138
gns3/main.py
@@ -16,6 +16,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# WARNING
|
||||
# Due to buggy user machines we choose to put this as the first loading modules
|
||||
# otherwise the egg cache is initialized in his standard location and
|
||||
# if is not writetable the application crash. It's the user fault
|
||||
# because one day the user as used sudo to run an egg and break his
|
||||
# filesystem permissions, but it's a common mistake.
|
||||
from gns3.utils.get_resource import get_resource
|
||||
|
||||
|
||||
import datetime
|
||||
import sys
|
||||
import os
|
||||
@@ -24,6 +34,11 @@ import time
|
||||
import locale
|
||||
import argparse
|
||||
|
||||
|
||||
from gns3.logger import init_logger
|
||||
from gns3.crash_report import CrashReport
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -82,10 +97,14 @@ def main():
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--version', help="show the version", action='version', version=__version__)
|
||||
parser.add_argument('--debug', help="print out debug messages", action='store_true', default=False)
|
||||
parser.add_argument("project", help="load a GNS3 project (.gns3)", metavar="path", nargs="?")
|
||||
parser.add_argument("--version", help="show the version", action="version", version=__version__)
|
||||
parser.add_argument("--debug", help="print out debug messages", action="store_true", default=False)
|
||||
options = parser.parse_args()
|
||||
exception_file_path = "exception.log"
|
||||
exception_file_path = "exceptions.log"
|
||||
|
||||
if options.project and hasattr(sys, "frozen"):
|
||||
os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
|
||||
|
||||
def exceptionHook(exception, value, tb):
|
||||
|
||||
@@ -94,21 +113,22 @@ def main():
|
||||
|
||||
lines = traceback.format_exception(exception, value, tb)
|
||||
print("****** Exception detected, traceback information saved in {} ******".format(exception_file_path))
|
||||
print("\nPLEASE REPORT ON https://community.gns3.com/community/support/bug\n")
|
||||
print("\nPLEASE REPORT ON https://community.gns3.com/community/software/bug\n")
|
||||
print("".join(lines))
|
||||
try:
|
||||
curdate = time.strftime("%d %b %Y %H:%M:%S")
|
||||
logfile = open(exception_file_path, "a")
|
||||
logfile = open(exception_file_path, "a", encoding="utf-8")
|
||||
logfile.write("=== GNS3 {} traceback on {} ===\n".format(__version__, curdate))
|
||||
logfile.write("".join(lines))
|
||||
logfile.close()
|
||||
except OSError as e:
|
||||
print("Could not save traceback to {}: {}".format(exception_file_path, e))
|
||||
print("Could not save traceback to {}: {}".format(os.path.normpath(exception_file_path), e))
|
||||
|
||||
if not sys.stdout.isatty():
|
||||
# if stdout is not a tty (redirected to the console view),
|
||||
# then print the exception on stderr too.
|
||||
print("".join(lines), file=sys.stderr)
|
||||
CrashReport.instance().captureException(exception, value, tb)
|
||||
|
||||
# catch exceptions to write them in a file
|
||||
sys.excepthook = exceptionHook
|
||||
@@ -123,7 +143,8 @@ def main():
|
||||
elif sys.version_info[0] == 3 and sys.version_info < (3, 3):
|
||||
raise RuntimeError("Python 3.3 or higher is required")
|
||||
|
||||
version = lambda version_string: [int(i) for i in version_string.split('.')]
|
||||
def version(version_string):
|
||||
return [int(i) for i in version_string.split('.')]
|
||||
|
||||
if version(QtCore.QT_VERSION_STR) < version("4.6"):
|
||||
raise RuntimeError("Requirement is Qt version 4.6 or higher, got version {}".format(QtCore.QT_VERSION_STR))
|
||||
@@ -135,13 +156,6 @@ def main():
|
||||
if DEFAULT_BINDING == "PySide" and version(QtCore.BINDING_VERSION_STR) < version("1.0"):
|
||||
raise RuntimeError("Requirement is PySide version 1.0 or higher, got version {}".format(QtCore.BINDING_VERSION_STR))
|
||||
|
||||
try:
|
||||
# if tornado is present then enable pretty logging.
|
||||
import tornado.log
|
||||
tornado.log.enable_pretty_logging()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# check for the correct locale
|
||||
# (UNIX/Linux only)
|
||||
locale_check()
|
||||
@@ -156,7 +170,7 @@ def main():
|
||||
if sys.platform.startswith('win') or sys.platform.startswith('darwin'):
|
||||
QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
if sys.platform.startswith('win') and hasattr(sys, "frozen"):
|
||||
try:
|
||||
import win32console
|
||||
import win32con
|
||||
@@ -164,61 +178,53 @@ def main():
|
||||
except ImportError:
|
||||
raise RuntimeError("Python for Windows extensions must be installed.")
|
||||
|
||||
try:
|
||||
win32console.AllocConsole()
|
||||
console_window = win32console.GetConsoleWindow()
|
||||
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
|
||||
except win32console.error as e:
|
||||
print("warning: could not allocate console: {}".format(e))
|
||||
|
||||
exit_code = MainWindow.exit_code_reboot
|
||||
while exit_code == MainWindow.exit_code_reboot:
|
||||
|
||||
exit_code = 0
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
|
||||
# this info is necessary for QSettings
|
||||
app.setOrganizationName("GNS3")
|
||||
app.setOrganizationDomain("gns3.net")
|
||||
app.setApplicationName("GNS3")
|
||||
app.setApplicationVersion(__version__)
|
||||
|
||||
# save client logging info to a file
|
||||
logfile = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), "GNS3_client.log") # FIXME: does it work?
|
||||
try:
|
||||
if not options.debug:
|
||||
try:
|
||||
os.makedirs(os.path.dirname(QtCore.QSettings().fileName()))
|
||||
except FileExistsError:
|
||||
pass
|
||||
handler = logging.FileHandler(logfile, "w")
|
||||
if options.debug:
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.DEBUG)
|
||||
if len(root_logger.handlers) > 0:
|
||||
root_handler = root_logger.handlers[0]
|
||||
else:
|
||||
root_handler = logging.StreamHandler()
|
||||
root_logger.addHandler(root_handler)
|
||||
root_handler.setLevel(logging.DEBUG)
|
||||
else:
|
||||
handler.setLevel(logging.INFO)
|
||||
log.info('Log level: {}'.format(logging.getLevelName(log.getEffectiveLevel())))
|
||||
# hide the console
|
||||
console_window = win32console.GetConsoleWindow()
|
||||
win32gui.ShowWindow(console_window, win32con.SW_HIDE)
|
||||
except win32console.error as e:
|
||||
print("warning: could not allocate console: {}".format(e))
|
||||
|
||||
formatter = logging.Formatter("[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d] %(message)s",
|
||||
datefmt="%y%m%d %H:%M:%S")
|
||||
handler.setFormatter(formatter)
|
||||
log.addHandler(handler)
|
||||
except OSError as e:
|
||||
log.warn("could not log to {}: {}".format(logfile, e))
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
|
||||
# update the exception file path to have it in the same directory as the settings file.
|
||||
exception_file_path = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), exception_file_path)
|
||||
# this info is necessary for QSettings
|
||||
app.setOrganizationName("GNS3")
|
||||
app.setOrganizationDomain("gns3.net")
|
||||
app.setApplicationName("GNS3")
|
||||
app.setApplicationVersion(__version__)
|
||||
|
||||
mainwindow = MainWindow.instance()
|
||||
mainwindow.show()
|
||||
exit_code = app.exec_()
|
||||
delattr(MainWindow, "_instance")
|
||||
app.deleteLater()
|
||||
formatter = logging.Formatter("[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d] %(message)s",
|
||||
datefmt="%y%m%d %H:%M:%S")
|
||||
|
||||
# on debug enable logging to stdout
|
||||
if options.debug:
|
||||
root_logger = init_logger(logging.DEBUG)
|
||||
else:
|
||||
root_logger = init_logger(logging.INFO)
|
||||
|
||||
# save client logging info to a file
|
||||
logfile = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), "gns3_gui.log")
|
||||
try:
|
||||
try:
|
||||
os.makedirs(os.path.dirname(QtCore.QSettings().fileName()))
|
||||
except FileExistsError:
|
||||
pass
|
||||
handler = logging.FileHandler(logfile, "w")
|
||||
root_logger.addHandler(handler)
|
||||
except OSError as e:
|
||||
log.warn("could not log to {}: {}".format(logfile, e))
|
||||
|
||||
log.info('Log level: {}'.format(logging.getLevelName(log.getEffectiveLevel())))
|
||||
|
||||
# update the exception file path to have it in the same directory as the settings file.
|
||||
exception_file_path = os.path.join(os.path.dirname(QtCore.QSettings().fileName()), exception_file_path)
|
||||
|
||||
mainwindow = MainWindow(options.project)
|
||||
mainwindow.show()
|
||||
exit_code = app.exec_()
|
||||
delattr(MainWindow, "_instance")
|
||||
app.deleteLater()
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
1087
gns3/main_window.py
1087
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):
|
||||
|
||||
@@ -218,6 +161,8 @@ class Builtin(Module):
|
||||
available_interfaces, 0, False)
|
||||
if ok:
|
||||
return selection
|
||||
QtGui.QMessageBox.warning(mainwindow, "Cloud interface", "No alternative interface chosen to replace {} on this host, this may lead to issues".format(missing_interface))
|
||||
return None
|
||||
else:
|
||||
QtGui.QMessageBox.critical(mainwindow, "Cloud interface", "Could not find interface {} on this host".format(missing_interface))
|
||||
return missing_interface
|
||||
|
||||
@@ -25,6 +25,7 @@ from gns3.node import Node
|
||||
from gns3.ports.port import Port
|
||||
from gns3.nios.nio_generic_ethernet import NIOGenericEthernet
|
||||
from gns3.nios.nio_linux_ethernet import NIOLinuxEthernet
|
||||
from gns3.nios.nio_nat import NIONAT
|
||||
from gns3.nios.nio_udp import NIOUDP
|
||||
from gns3.nios.nio_tap import NIOTAP
|
||||
from gns3.nios.nio_unix import NIOUNIX
|
||||
@@ -36,17 +37,19 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Cloud(Node):
|
||||
|
||||
"""
|
||||
Dynamips cloud.
|
||||
|
||||
:param module: parent module for this node
|
||||
:param server: GNS3 server instance
|
||||
:param project: Project instance
|
||||
"""
|
||||
|
||||
_name_instance_count = 1
|
||||
|
||||
def __init__(self, module, server):
|
||||
Node.__init__(self, server)
|
||||
def __init__(self, module, server, project):
|
||||
Node.__init__(self, module, server, project)
|
||||
|
||||
log.info("cloud is being created")
|
||||
# create an unique id and name
|
||||
@@ -57,11 +60,10 @@ class Cloud(Node):
|
||||
self.setStatus(Node.started) # this is an always-on node
|
||||
self._defaults = {}
|
||||
self._ports = []
|
||||
self._module = module
|
||||
self._initial_settings = None
|
||||
self._settings = {"nios": [],
|
||||
self._settings = {"name": name,
|
||||
"interfaces": {},
|
||||
"name": name}
|
||||
"nios": []}
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
@@ -84,9 +86,10 @@ class Cloud(Node):
|
||||
|
||||
if initial_settings:
|
||||
self._initial_settings = initial_settings
|
||||
self._server.send_message("builtin.interfaces", None, self._setupCallback)
|
||||
|
||||
def _setupCallback(self, result, error=False):
|
||||
self._server.get("/interfaces", self._setupCallback)
|
||||
|
||||
def _setupCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for setup.
|
||||
|
||||
@@ -150,6 +153,19 @@ class Cloud(Node):
|
||||
return NIOLinuxEthernet(linux_device)
|
||||
return None
|
||||
|
||||
def _createNIONAT(self, nio):
|
||||
"""
|
||||
Creates a NIO NAT.
|
||||
|
||||
:param nio: nio string
|
||||
"""
|
||||
|
||||
match = re.search(r"""^nio_nat:(.+)$""", nio)
|
||||
if match:
|
||||
identifier = match.group(1)
|
||||
return NIONAT(identifier)
|
||||
return None
|
||||
|
||||
def _createNIOTAP(self, nio):
|
||||
"""
|
||||
Creates a NIO TAP.
|
||||
@@ -211,53 +227,57 @@ class Cloud(Node):
|
||||
:param new_settings: settings dictionary
|
||||
"""
|
||||
|
||||
nios = new_settings["nios"]
|
||||
|
||||
updated = False
|
||||
# add ports
|
||||
for nio in nios:
|
||||
if nio in self._settings["nios"]:
|
||||
# port already created for this NIO
|
||||
continue
|
||||
nio_object = None
|
||||
if nio.lower().startswith("nio_udp"):
|
||||
nio_object = self._createNIOUDP(nio)
|
||||
if nio.lower().startswith("nio_gen_eth"):
|
||||
nio_object = self._createNIOGenericEthernet(nio)
|
||||
if nio.lower().startswith("nio_gen_linux"):
|
||||
nio_object = self._createNIOLinuxEthernet(nio)
|
||||
if nio.lower().startswith("nio_tap"):
|
||||
nio_object = self._createNIOTAP(nio)
|
||||
if nio.lower().startswith("nio_unix"):
|
||||
nio_object = self._createNIOUNIX(nio)
|
||||
if nio.lower().startswith("nio_vde"):
|
||||
nio_object = self._createNIOVDE(nio)
|
||||
if nio.lower().startswith("nio_null"):
|
||||
nio_object = self._createNIONull(nio)
|
||||
if nio_object == None:
|
||||
log.error("Could not create NIO object from {}".format(nio))
|
||||
continue
|
||||
port = Port(nio, nio_object, stub=True)
|
||||
port.setStatus(Port.started)
|
||||
self._ports.append(port)
|
||||
updated = True
|
||||
log.debug("port {} has been added".format(nio))
|
||||
if "nios" in new_settings:
|
||||
nios = new_settings["nios"]
|
||||
|
||||
# delete ports
|
||||
for nio in self._settings["nios"]:
|
||||
if nio not in nios:
|
||||
for port in self._ports.copy():
|
||||
if port.name() == nio:
|
||||
self._ports.remove(port)
|
||||
updated = True
|
||||
log.debug("port {} has been deleted".format(nio))
|
||||
break
|
||||
# add ports
|
||||
for nio in nios:
|
||||
if nio in self._settings["nios"]:
|
||||
# port already created for this NIO
|
||||
continue
|
||||
nio_object = None
|
||||
if nio.lower().startswith("nio_udp"):
|
||||
nio_object = self._createNIOUDP(nio)
|
||||
if nio.lower().startswith("nio_gen_eth"):
|
||||
nio_object = self._createNIOGenericEthernet(nio)
|
||||
if nio.lower().startswith("nio_gen_linux"):
|
||||
nio_object = self._createNIOLinuxEthernet(nio)
|
||||
if nio.lower().startswith("nio_nat"):
|
||||
nio_object = self._createNIONAT(nio)
|
||||
if nio.lower().startswith("nio_tap"):
|
||||
nio_object = self._createNIOTAP(nio)
|
||||
if nio.lower().startswith("nio_unix"):
|
||||
nio_object = self._createNIOUNIX(nio)
|
||||
if nio.lower().startswith("nio_vde"):
|
||||
nio_object = self._createNIOVDE(nio)
|
||||
if nio.lower().startswith("nio_null"):
|
||||
nio_object = self._createNIONull(nio)
|
||||
if nio_object is None:
|
||||
log.error("Could not create NIO object from {}".format(nio))
|
||||
continue
|
||||
port = Port(nio, nio_object, stub=True)
|
||||
port.setStatus(Port.started)
|
||||
self._ports.append(port)
|
||||
updated = True
|
||||
log.debug("port {} has been added".format(nio))
|
||||
|
||||
# delete ports
|
||||
for nio in self._settings["nios"]:
|
||||
if nio not in nios:
|
||||
for port in self._ports.copy():
|
||||
if port.name() == nio:
|
||||
self._ports.remove(port)
|
||||
updated = True
|
||||
log.debug("port {} has been deleted".format(nio))
|
||||
break
|
||||
|
||||
self._settings["nios"] = new_settings["nios"].copy()
|
||||
|
||||
if "name" in new_settings and new_settings["name"] != self.name():
|
||||
self._settings["name"] = new_settings["name"]
|
||||
updated = True
|
||||
|
||||
self._settings["nios"] = new_settings["nios"].copy()
|
||||
if updated:
|
||||
log.info("cloud {} has been updated".format(self.name()))
|
||||
self.updated_signal.emit()
|
||||
@@ -309,7 +329,7 @@ This is a pseudo-device for external connections
|
||||
"properties": {"name": self.name(),
|
||||
"nios": self._settings["nios"]},
|
||||
"server_id": self._server.id(),
|
||||
}
|
||||
}
|
||||
|
||||
# add the ports
|
||||
if self._ports:
|
||||
@@ -357,11 +377,12 @@ This is a pseudo-device for external connections
|
||||
break
|
||||
if not available_interface:
|
||||
alternative_interface = self._module.findAlternativeInterface(self, topology_port_name)
|
||||
if topology_port["name"] in self._settings["nios"]:
|
||||
self._settings["nios"].remove(topology_port["name"])
|
||||
topology_port["name"] = topology_port["name"].replace(topology_port_name, alternative_interface)
|
||||
port.setName(topology_port["name"])
|
||||
self._settings["nios"].append(topology_port["name"])
|
||||
if alternative_interface:
|
||||
if topology_port["name"] in self._settings["nios"]:
|
||||
self._settings["nios"].remove(topology_port["name"])
|
||||
topology_port["name"] = topology_port["name"].replace(topology_port_name, alternative_interface)
|
||||
port.setName(topology_port["name"])
|
||||
self._settings["nios"].append(topology_port["name"])
|
||||
|
||||
log.info("cloud {} has been created".format(self.name()))
|
||||
self.setInitialized(True)
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -47,6 +48,12 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
|
||||
self.uiAddLinuxEthernetPushButton.clicked.connect(self._linuxEthernetAddSlot)
|
||||
self.uiDeleteLinuxEthernetPushButton.clicked.connect(self._linuxEthernetDeleteSlot)
|
||||
|
||||
# connect NIO NAT slots
|
||||
self.uiNIONATListWidget.currentRowChanged.connect(self._NIONATSelectedSlot)
|
||||
self.uiNIONATListWidget.itemSelectionChanged.connect(self._NIONATChangedSlot)
|
||||
self.uiAddNIONATPushButton.clicked.connect(self._NIONATAddSlot)
|
||||
self.uiDeleteNIONATPushButton.clicked.connect(self._NIONATDeleteSlot)
|
||||
|
||||
# connect NIO UDP slots
|
||||
self.uiNIOUDPListWidget.currentRowChanged.connect(self._NIOUDPSelectedSlot)
|
||||
self.uiNIOUDPListWidget.itemSelectionChanged.connect(self._NIOUDPChangedSlot)
|
||||
@@ -105,7 +112,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
|
||||
interface = self.uiGenericEthernetLineEdit.text()
|
||||
if interface:
|
||||
nio = "nio_gen_eth:{interface}".format(interface=interface)
|
||||
if not nio in self._nios:
|
||||
if nio not in self._nios:
|
||||
self.uiGenericEthernetListWidget.addItem(nio)
|
||||
self._nios.append(nio)
|
||||
|
||||
@@ -154,7 +161,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
|
||||
interface = self.uiLinuxEthernetLineEdit.text()
|
||||
if interface:
|
||||
nio = "nio_gen_linux:{interface}".format(interface=interface)
|
||||
if not nio in self._nios:
|
||||
if nio not in self._nios:
|
||||
self.uiLinuxEthernetListWidget.addItem(nio)
|
||||
self._nios.append(nio)
|
||||
|
||||
@@ -175,9 +182,65 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
|
||||
self._nios.remove(nio)
|
||||
self.uiLinuxEthernetListWidget.takeItem(self.uiLinuxEthernetListWidget.currentRow())
|
||||
|
||||
def _NIONATSelectedSlot(self, index):
|
||||
"""
|
||||
Loads a selected NAT NIO.
|
||||
|
||||
:param index: ignored
|
||||
"""
|
||||
|
||||
item = self.uiNIONATListWidget.currentItem()
|
||||
if item:
|
||||
nio = item.text()
|
||||
match = re.search(r"""^nio_nat:(.+)$""", nio)
|
||||
if match:
|
||||
self.uiNIONATIdentiferLineEdit.setText(match.group(1))
|
||||
|
||||
def _NIONATChangedSlot(self):
|
||||
"""
|
||||
Enables the use of the delete button.
|
||||
"""
|
||||
|
||||
item = self.uiNIONATListWidget.currentItem()
|
||||
if item:
|
||||
self.uiDeleteNIONATPushButton.setEnabled(True)
|
||||
else:
|
||||
self.uiDeleteNIONATPushButton.setEnabled(False)
|
||||
|
||||
def _NIONATAddSlot(self):
|
||||
"""
|
||||
Adds a new NAT NIO.
|
||||
"""
|
||||
|
||||
identifier = self.uiNIONATIdentiferLineEdit.text()
|
||||
if identifier:
|
||||
nio = "nio_nat:{}".format(identifier)
|
||||
if nio not in self._nios:
|
||||
self.uiNIONATListWidget.addItem(nio)
|
||||
self._nios.append(nio)
|
||||
|
||||
def _NIONATDeleteSlot(self):
|
||||
"""
|
||||
Deletes a NAT NIO.
|
||||
"""
|
||||
|
||||
item = self.uiNIONATListWidget.currentItem()
|
||||
if item:
|
||||
nio = item.text()
|
||||
# check we can delete that NIO
|
||||
node_ports = self._node.ports()
|
||||
for node_port in node_ports:
|
||||
if node_port.name() == nio and not node_port.isFree():
|
||||
QtGui.QMessageBox.critical(self, self._node.name(), "A link is connected to NIO {}, please remove it first".format(nio))
|
||||
return
|
||||
self._nios.remove(nio)
|
||||
self.uiNIONATListWidget.takeItem(self.uiNIONATListWidget.currentRow())
|
||||
|
||||
def _NIOUDPSelectedSlot(self, index):
|
||||
"""
|
||||
Loads a selected UDP.
|
||||
|
||||
:param index: ignored
|
||||
"""
|
||||
|
||||
item = self.uiNIOUDPListWidget.currentItem()
|
||||
@@ -212,7 +275,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
|
||||
nio = "nio_udp:{lport}:{rhost}:{rport}".format(lport=local_port,
|
||||
rhost=remote_host,
|
||||
rport=remote_port)
|
||||
if not nio in self._nios:
|
||||
if nio not in self._nios:
|
||||
self.uiNIOUDPListWidget.addItem(nio)
|
||||
self._nios.append(nio)
|
||||
self.uiLocalPortSpinBox.setValue(local_port + 1)
|
||||
@@ -268,7 +331,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
|
||||
tap_interface = self.uiNIOTAPLineEdit.text()
|
||||
if tap_interface:
|
||||
nio = "nio_tap:{}".format(tap_interface.lower())
|
||||
if not nio in self._nios:
|
||||
if nio not in self._nios:
|
||||
self.uiNIOTAPListWidget.addItem(nio)
|
||||
self._nios.append(nio)
|
||||
|
||||
@@ -325,7 +388,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
|
||||
if local_file and remote_file:
|
||||
nio = "nio_unix:{local}:{remote}".format(local=local_file,
|
||||
remote=remote_file)
|
||||
if not nio in self._nios:
|
||||
if nio not in self._nios:
|
||||
self.uiNIOUNIXListWidget.addItem(nio)
|
||||
self._nios.append(nio)
|
||||
|
||||
@@ -381,7 +444,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
|
||||
local_file = self.uiVDELocalFileLineEdit.text()
|
||||
if local_file and control_file:
|
||||
nio = "nio_vde:{control}:{local}".format(control=control_file, local=local_file)
|
||||
if not nio in self._nios:
|
||||
if nio not in self._nios:
|
||||
self.uiNIOVDEListWidget.addItem(nio)
|
||||
self._nios.append(nio)
|
||||
|
||||
@@ -405,6 +468,8 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
|
||||
def _NIONullSelectedSlot(self, index):
|
||||
"""
|
||||
Loads a selected NULL NIO.
|
||||
|
||||
:param index: ignored
|
||||
"""
|
||||
|
||||
item = self.uiNIONullListWidget.currentItem()
|
||||
@@ -433,7 +498,7 @@ class CloudConfigurationPage(QtGui.QWidget, Ui_cloudConfigPageWidget):
|
||||
identifier = self.uiNIONullIdentiferLineEdit.text()
|
||||
if identifier:
|
||||
nio = "nio_null:{}".format(identifier)
|
||||
if not nio in self._nios:
|
||||
if nio not in self._nios:
|
||||
self.uiNIONullListWidget.addItem(nio)
|
||||
self._nios.append(nio)
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>542</width>
|
||||
<height>500</height>
|
||||
<width>653</width>
|
||||
<height>478</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -15,13 +15,13 @@
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<widget class="QTabWidget" name="uiNIOsTabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<widget class="QWidget" name="NIOEthernetTab">
|
||||
<attribute name="title">
|
||||
<string>NIO Ethernet</string>
|
||||
<string>Ethernet</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
@@ -135,9 +135,104 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<widget class="QWidget" name="NIONATTab">
|
||||
<attribute name="title">
|
||||
<string>NIO UDP</string>
|
||||
<string>NAT</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="uiNIONATSettingsGroupBox">
|
||||
<property name="title">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="uiNIONATIdentifierLabel">
|
||||
<property name="text">
|
||||
<string>Local identifier:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="uiNIONATIdentiferLineEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2" rowspan="3">
|
||||
<widget class="QGroupBox" name="uiNIONATListGroupBox">
|
||||
<property name="title">
|
||||
<string>NIOs</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="_3">
|
||||
<item>
|
||||
<widget class="QListWidget" name="uiNIONATListWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="uiAddNIONATPushButton">
|
||||
<property name="text">
|
||||
<string>&Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="uiDeleteNIONATPushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" rowspan="2">
|
||||
<spacer name="spacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>294</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<spacer name="spacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>194</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="NIOUDPTab">
|
||||
<attribute name="title">
|
||||
<string>UDP</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
@@ -265,9 +360,9 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<widget class="QWidget" name="NIOTAPTab">
|
||||
<attribute name="title">
|
||||
<string>NIO TAP</string>
|
||||
<string>TAP</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
@@ -317,9 +412,9 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_4">
|
||||
<widget class="QWidget" name="NIOUnixTab">
|
||||
<attribute name="title">
|
||||
<string>NIO UNIX</string>
|
||||
<string>UNIX</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
@@ -440,9 +535,9 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_5">
|
||||
<widget class="QWidget" name="NIOVDETab">
|
||||
<attribute name="title">
|
||||
<string>NIO VDE</string>
|
||||
<string>VDE</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
@@ -563,9 +658,9 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_6">
|
||||
<widget class="QWidget" name="NIONullTab">
|
||||
<attribute name="title">
|
||||
<string>NIO NULL</string>
|
||||
<string>NULL</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
@@ -575,9 +670,9 @@
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<widget class="QLabel" name="uiNIONullIdentifierLabel">
|
||||
<property name="text">
|
||||
<string>Identifier:</string>
|
||||
<string>Local identifier:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -658,7 +753,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_7">
|
||||
<widget class="QWidget" name="MiscTab">
|
||||
<attribute name="title">
|
||||
<string>Misc.</string>
|
||||
</attribute>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/workspace/git/gns3-gui/gns3/modules/dynamips/ui/cloud_configuration_page.ui'
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/builtin/ui/cloud_configuration_page.ui'
|
||||
#
|
||||
# Created: Mon Mar 17 17:42:16 2014
|
||||
# by: PyQt4 UI code generator 4.10
|
||||
# Created: Tue May 5 21:08:19 2015
|
||||
# by: PyQt4 UI code generator 4.10.4
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -26,16 +26,16 @@ except AttributeError:
|
||||
class Ui_cloudConfigPageWidget(object):
|
||||
def setupUi(self, cloudConfigPageWidget):
|
||||
cloudConfigPageWidget.setObjectName(_fromUtf8("cloudConfigPageWidget"))
|
||||
cloudConfigPageWidget.resize(542, 500)
|
||||
cloudConfigPageWidget.resize(653, 478)
|
||||
self.vboxlayout = QtGui.QVBoxLayout(cloudConfigPageWidget)
|
||||
self.vboxlayout.setObjectName(_fromUtf8("vboxlayout"))
|
||||
self.tabWidget = QtGui.QTabWidget(cloudConfigPageWidget)
|
||||
self.tabWidget.setObjectName(_fromUtf8("tabWidget"))
|
||||
self.tab = QtGui.QWidget()
|
||||
self.tab.setObjectName(_fromUtf8("tab"))
|
||||
self.vboxlayout1 = QtGui.QVBoxLayout(self.tab)
|
||||
self.uiNIOsTabWidget = QtGui.QTabWidget(cloudConfigPageWidget)
|
||||
self.uiNIOsTabWidget.setObjectName(_fromUtf8("uiNIOsTabWidget"))
|
||||
self.NIOEthernetTab = QtGui.QWidget()
|
||||
self.NIOEthernetTab.setObjectName(_fromUtf8("NIOEthernetTab"))
|
||||
self.vboxlayout1 = QtGui.QVBoxLayout(self.NIOEthernetTab)
|
||||
self.vboxlayout1.setObjectName(_fromUtf8("vboxlayout1"))
|
||||
self.uiGenericEthernetGroupBox = QtGui.QGroupBox(self.tab)
|
||||
self.uiGenericEthernetGroupBox = QtGui.QGroupBox(self.NIOEthernetTab)
|
||||
self.uiGenericEthernetGroupBox.setObjectName(_fromUtf8("uiGenericEthernetGroupBox"))
|
||||
self.gridlayout = QtGui.QGridLayout(self.uiGenericEthernetGroupBox)
|
||||
self.gridlayout.setObjectName(_fromUtf8("gridlayout"))
|
||||
@@ -62,7 +62,7 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.uiGenericEthernetListWidget.setObjectName(_fromUtf8("uiGenericEthernetListWidget"))
|
||||
self.gridlayout.addWidget(self.uiGenericEthernetListWidget, 2, 0, 1, 3)
|
||||
self.vboxlayout1.addWidget(self.uiGenericEthernetGroupBox)
|
||||
self.uiLinuxEthernetGroupBox = QtGui.QGroupBox(self.tab)
|
||||
self.uiLinuxEthernetGroupBox = QtGui.QGroupBox(self.NIOEthernetTab)
|
||||
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -95,12 +95,57 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.vboxlayout1.addWidget(self.uiLinuxEthernetGroupBox)
|
||||
spacerItem = QtGui.QSpacerItem(21, 16, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
|
||||
self.vboxlayout1.addItem(spacerItem)
|
||||
self.tabWidget.addTab(self.tab, _fromUtf8(""))
|
||||
self.tab_2 = QtGui.QWidget()
|
||||
self.tab_2.setObjectName(_fromUtf8("tab_2"))
|
||||
self.gridlayout2 = QtGui.QGridLayout(self.tab_2)
|
||||
self.uiNIOsTabWidget.addTab(self.NIOEthernetTab, _fromUtf8(""))
|
||||
self.NIONATTab = QtGui.QWidget()
|
||||
self.NIONATTab.setObjectName(_fromUtf8("NIONATTab"))
|
||||
self.gridLayout_2 = QtGui.QGridLayout(self.NIONATTab)
|
||||
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
|
||||
self.uiNIONATSettingsGroupBox = QtGui.QGroupBox(self.NIONATTab)
|
||||
self.uiNIONATSettingsGroupBox.setObjectName(_fromUtf8("uiNIONATSettingsGroupBox"))
|
||||
self._2 = QtGui.QGridLayout(self.uiNIONATSettingsGroupBox)
|
||||
self._2.setObjectName(_fromUtf8("_2"))
|
||||
self.uiNIONATIdentifierLabel = QtGui.QLabel(self.uiNIONATSettingsGroupBox)
|
||||
self.uiNIONATIdentifierLabel.setObjectName(_fromUtf8("uiNIONATIdentifierLabel"))
|
||||
self._2.addWidget(self.uiNIONATIdentifierLabel, 0, 0, 1, 1)
|
||||
self.uiNIONATIdentiferLineEdit = QtGui.QLineEdit(self.uiNIONATSettingsGroupBox)
|
||||
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiNIONATIdentiferLineEdit.sizePolicy().hasHeightForWidth())
|
||||
self.uiNIONATIdentiferLineEdit.setSizePolicy(sizePolicy)
|
||||
self.uiNIONATIdentiferLineEdit.setObjectName(_fromUtf8("uiNIONATIdentiferLineEdit"))
|
||||
self._2.addWidget(self.uiNIONATIdentiferLineEdit, 1, 0, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.uiNIONATSettingsGroupBox, 0, 0, 1, 2)
|
||||
self.uiNIONATListGroupBox = QtGui.QGroupBox(self.NIONATTab)
|
||||
self.uiNIONATListGroupBox.setObjectName(_fromUtf8("uiNIONATListGroupBox"))
|
||||
self._3 = QtGui.QVBoxLayout(self.uiNIONATListGroupBox)
|
||||
self._3.setObjectName(_fromUtf8("_3"))
|
||||
self.uiNIONATListWidget = QtGui.QListWidget(self.uiNIONATListGroupBox)
|
||||
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiNIONATListWidget.sizePolicy().hasHeightForWidth())
|
||||
self.uiNIONATListWidget.setSizePolicy(sizePolicy)
|
||||
self.uiNIONATListWidget.setObjectName(_fromUtf8("uiNIONATListWidget"))
|
||||
self._3.addWidget(self.uiNIONATListWidget)
|
||||
self.gridLayout_2.addWidget(self.uiNIONATListGroupBox, 0, 2, 3, 1)
|
||||
self.uiAddNIONATPushButton = QtGui.QPushButton(self.NIONATTab)
|
||||
self.uiAddNIONATPushButton.setObjectName(_fromUtf8("uiAddNIONATPushButton"))
|
||||
self.gridLayout_2.addWidget(self.uiAddNIONATPushButton, 1, 0, 1, 1)
|
||||
self.uiDeleteNIONATPushButton = QtGui.QPushButton(self.NIONATTab)
|
||||
self.uiDeleteNIONATPushButton.setEnabled(False)
|
||||
self.uiDeleteNIONATPushButton.setObjectName(_fromUtf8("uiDeleteNIONATPushButton"))
|
||||
self.gridLayout_2.addWidget(self.uiDeleteNIONATPushButton, 1, 1, 1, 1)
|
||||
spacerItem1 = QtGui.QSpacerItem(20, 294, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridLayout_2.addItem(spacerItem1, 2, 0, 2, 1)
|
||||
spacerItem2 = QtGui.QSpacerItem(20, 194, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridLayout_2.addItem(spacerItem2, 3, 2, 1, 1)
|
||||
self.uiNIOsTabWidget.addTab(self.NIONATTab, _fromUtf8(""))
|
||||
self.NIOUDPTab = QtGui.QWidget()
|
||||
self.NIOUDPTab.setObjectName(_fromUtf8("NIOUDPTab"))
|
||||
self.gridlayout2 = QtGui.QGridLayout(self.NIOUDPTab)
|
||||
self.gridlayout2.setObjectName(_fromUtf8("gridlayout2"))
|
||||
self.uiNIOUDPSettingsGroupBox = QtGui.QGroupBox(self.tab_2)
|
||||
self.uiNIOUDPSettingsGroupBox = QtGui.QGroupBox(self.NIOUDPTab)
|
||||
self.uiNIOUDPSettingsGroupBox.setObjectName(_fromUtf8("uiNIOUDPSettingsGroupBox"))
|
||||
self.gridlayout3 = QtGui.QGridLayout(self.uiNIOUDPSettingsGroupBox)
|
||||
self.gridlayout3.setObjectName(_fromUtf8("gridlayout3"))
|
||||
@@ -143,7 +188,7 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.uiRemotePortSpinBox.setObjectName(_fromUtf8("uiRemotePortSpinBox"))
|
||||
self.gridlayout3.addWidget(self.uiRemotePortSpinBox, 2, 1, 1, 1)
|
||||
self.gridlayout2.addWidget(self.uiNIOUDPSettingsGroupBox, 0, 0, 1, 2)
|
||||
self.uiNIOUDPListGroupBox = QtGui.QGroupBox(self.tab_2)
|
||||
self.uiNIOUDPListGroupBox = QtGui.QGroupBox(self.NIOUDPTab)
|
||||
self.uiNIOUDPListGroupBox.setObjectName(_fromUtf8("uiNIOUDPListGroupBox"))
|
||||
self.vboxlayout2 = QtGui.QVBoxLayout(self.uiNIOUDPListGroupBox)
|
||||
self.vboxlayout2.setObjectName(_fromUtf8("vboxlayout2"))
|
||||
@@ -151,21 +196,21 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.uiNIOUDPListWidget.setObjectName(_fromUtf8("uiNIOUDPListWidget"))
|
||||
self.vboxlayout2.addWidget(self.uiNIOUDPListWidget)
|
||||
self.gridlayout2.addWidget(self.uiNIOUDPListGroupBox, 0, 2, 2, 1)
|
||||
self.uiAddNIOUDPPushButton = QtGui.QPushButton(self.tab_2)
|
||||
self.uiAddNIOUDPPushButton = QtGui.QPushButton(self.NIOUDPTab)
|
||||
self.uiAddNIOUDPPushButton.setObjectName(_fromUtf8("uiAddNIOUDPPushButton"))
|
||||
self.gridlayout2.addWidget(self.uiAddNIOUDPPushButton, 1, 0, 1, 1)
|
||||
self.uiDeleteNIOUDPPushButton = QtGui.QPushButton(self.tab_2)
|
||||
self.uiDeleteNIOUDPPushButton = QtGui.QPushButton(self.NIOUDPTab)
|
||||
self.uiDeleteNIOUDPPushButton.setEnabled(False)
|
||||
self.uiDeleteNIOUDPPushButton.setObjectName(_fromUtf8("uiDeleteNIOUDPPushButton"))
|
||||
self.gridlayout2.addWidget(self.uiDeleteNIOUDPPushButton, 1, 1, 1, 1)
|
||||
spacerItem1 = QtGui.QSpacerItem(20, 211, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridlayout2.addItem(spacerItem1, 2, 1, 1, 1)
|
||||
self.tabWidget.addTab(self.tab_2, _fromUtf8(""))
|
||||
self.tab_3 = QtGui.QWidget()
|
||||
self.tab_3.setObjectName(_fromUtf8("tab_3"))
|
||||
self.vboxlayout3 = QtGui.QVBoxLayout(self.tab_3)
|
||||
spacerItem3 = QtGui.QSpacerItem(20, 211, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridlayout2.addItem(spacerItem3, 2, 1, 1, 1)
|
||||
self.uiNIOsTabWidget.addTab(self.NIOUDPTab, _fromUtf8(""))
|
||||
self.NIOTAPTab = QtGui.QWidget()
|
||||
self.NIOTAPTab.setObjectName(_fromUtf8("NIOTAPTab"))
|
||||
self.vboxlayout3 = QtGui.QVBoxLayout(self.NIOTAPTab)
|
||||
self.vboxlayout3.setObjectName(_fromUtf8("vboxlayout3"))
|
||||
self.uiNIOTAPGroupBox = QtGui.QGroupBox(self.tab_3)
|
||||
self.uiNIOTAPGroupBox = QtGui.QGroupBox(self.NIOTAPTab)
|
||||
self.uiNIOTAPGroupBox.setObjectName(_fromUtf8("uiNIOTAPGroupBox"))
|
||||
self.gridlayout4 = QtGui.QGridLayout(self.uiNIOTAPGroupBox)
|
||||
self.gridlayout4.setObjectName(_fromUtf8("gridlayout4"))
|
||||
@@ -183,14 +228,14 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.uiNIOTAPListWidget.setObjectName(_fromUtf8("uiNIOTAPListWidget"))
|
||||
self.gridlayout4.addWidget(self.uiNIOTAPListWidget, 1, 0, 1, 3)
|
||||
self.vboxlayout3.addWidget(self.uiNIOTAPGroupBox)
|
||||
spacerItem2 = QtGui.QSpacerItem(20, 191, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.vboxlayout3.addItem(spacerItem2)
|
||||
self.tabWidget.addTab(self.tab_3, _fromUtf8(""))
|
||||
self.tab_4 = QtGui.QWidget()
|
||||
self.tab_4.setObjectName(_fromUtf8("tab_4"))
|
||||
self.gridlayout5 = QtGui.QGridLayout(self.tab_4)
|
||||
spacerItem4 = QtGui.QSpacerItem(20, 191, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.vboxlayout3.addItem(spacerItem4)
|
||||
self.uiNIOsTabWidget.addTab(self.NIOTAPTab, _fromUtf8(""))
|
||||
self.NIOUnixTab = QtGui.QWidget()
|
||||
self.NIOUnixTab.setObjectName(_fromUtf8("NIOUnixTab"))
|
||||
self.gridlayout5 = QtGui.QGridLayout(self.NIOUnixTab)
|
||||
self.gridlayout5.setObjectName(_fromUtf8("gridlayout5"))
|
||||
self.uiNIOUNIXSettingsGroupBox = QtGui.QGroupBox(self.tab_4)
|
||||
self.uiNIOUNIXSettingsGroupBox = QtGui.QGroupBox(self.NIOUnixTab)
|
||||
self.uiNIOUNIXSettingsGroupBox.setObjectName(_fromUtf8("uiNIOUNIXSettingsGroupBox"))
|
||||
self.gridlayout6 = QtGui.QGridLayout(self.uiNIOUNIXSettingsGroupBox)
|
||||
self.gridlayout6.setObjectName(_fromUtf8("gridlayout6"))
|
||||
@@ -223,7 +268,7 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.gridlayout8.addWidget(self.uiRemoteFileLineEdit, 1, 0, 1, 1)
|
||||
self.gridlayout6.addLayout(self.gridlayout8, 1, 0, 1, 1)
|
||||
self.gridlayout5.addWidget(self.uiNIOUNIXSettingsGroupBox, 0, 0, 1, 2)
|
||||
self.uiNIOUNIXListGroupBox = QtGui.QGroupBox(self.tab_4)
|
||||
self.uiNIOUNIXListGroupBox = QtGui.QGroupBox(self.NIOUnixTab)
|
||||
self.uiNIOUNIXListGroupBox.setObjectName(_fromUtf8("uiNIOUNIXListGroupBox"))
|
||||
self.vboxlayout4 = QtGui.QVBoxLayout(self.uiNIOUNIXListGroupBox)
|
||||
self.vboxlayout4.setObjectName(_fromUtf8("vboxlayout4"))
|
||||
@@ -236,23 +281,23 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.uiNIOUNIXListWidget.setObjectName(_fromUtf8("uiNIOUNIXListWidget"))
|
||||
self.vboxlayout4.addWidget(self.uiNIOUNIXListWidget)
|
||||
self.gridlayout5.addWidget(self.uiNIOUNIXListGroupBox, 0, 2, 3, 1)
|
||||
self.uiAddNIOUNIXPushButton = QtGui.QPushButton(self.tab_4)
|
||||
self.uiAddNIOUNIXPushButton = QtGui.QPushButton(self.NIOUnixTab)
|
||||
self.uiAddNIOUNIXPushButton.setObjectName(_fromUtf8("uiAddNIOUNIXPushButton"))
|
||||
self.gridlayout5.addWidget(self.uiAddNIOUNIXPushButton, 1, 0, 1, 1)
|
||||
self.uiDeleteNIOUNIXPushButton = QtGui.QPushButton(self.tab_4)
|
||||
self.uiDeleteNIOUNIXPushButton = QtGui.QPushButton(self.NIOUnixTab)
|
||||
self.uiDeleteNIOUNIXPushButton.setEnabled(False)
|
||||
self.uiDeleteNIOUNIXPushButton.setObjectName(_fromUtf8("uiDeleteNIOUNIXPushButton"))
|
||||
self.gridlayout5.addWidget(self.uiDeleteNIOUNIXPushButton, 1, 1, 1, 1)
|
||||
spacerItem3 = QtGui.QSpacerItem(160, 190, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
|
||||
self.gridlayout5.addItem(spacerItem3, 2, 0, 2, 2)
|
||||
spacerItem4 = QtGui.QSpacerItem(196, 132, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridlayout5.addItem(spacerItem4, 3, 2, 1, 1)
|
||||
self.tabWidget.addTab(self.tab_4, _fromUtf8(""))
|
||||
self.tab_5 = QtGui.QWidget()
|
||||
self.tab_5.setObjectName(_fromUtf8("tab_5"))
|
||||
self.gridlayout9 = QtGui.QGridLayout(self.tab_5)
|
||||
spacerItem5 = QtGui.QSpacerItem(160, 190, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
|
||||
self.gridlayout5.addItem(spacerItem5, 2, 0, 2, 2)
|
||||
spacerItem6 = QtGui.QSpacerItem(196, 132, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridlayout5.addItem(spacerItem6, 3, 2, 1, 1)
|
||||
self.uiNIOsTabWidget.addTab(self.NIOUnixTab, _fromUtf8(""))
|
||||
self.NIOVDETab = QtGui.QWidget()
|
||||
self.NIOVDETab.setObjectName(_fromUtf8("NIOVDETab"))
|
||||
self.gridlayout9 = QtGui.QGridLayout(self.NIOVDETab)
|
||||
self.gridlayout9.setObjectName(_fromUtf8("gridlayout9"))
|
||||
self.uiNIOVDESettingsGroupBox = QtGui.QGroupBox(self.tab_5)
|
||||
self.uiNIOVDESettingsGroupBox = QtGui.QGroupBox(self.NIOVDETab)
|
||||
self.uiNIOVDESettingsGroupBox.setObjectName(_fromUtf8("uiNIOVDESettingsGroupBox"))
|
||||
self.gridlayout10 = QtGui.QGridLayout(self.uiNIOVDESettingsGroupBox)
|
||||
self.gridlayout10.setObjectName(_fromUtf8("gridlayout10"))
|
||||
@@ -285,7 +330,7 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.gridlayout12.addWidget(self.uiVDELocalFileLineEdit, 1, 0, 1, 1)
|
||||
self.gridlayout10.addLayout(self.gridlayout12, 1, 0, 1, 1)
|
||||
self.gridlayout9.addWidget(self.uiNIOVDESettingsGroupBox, 0, 0, 1, 2)
|
||||
self.uiNIOVDEListGroupBox = QtGui.QGroupBox(self.tab_5)
|
||||
self.uiNIOVDEListGroupBox = QtGui.QGroupBox(self.NIOVDETab)
|
||||
self.uiNIOVDEListGroupBox.setObjectName(_fromUtf8("uiNIOVDEListGroupBox"))
|
||||
self.vboxlayout5 = QtGui.QVBoxLayout(self.uiNIOVDEListGroupBox)
|
||||
self.vboxlayout5.setObjectName(_fromUtf8("vboxlayout5"))
|
||||
@@ -298,29 +343,29 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.uiNIOVDEListWidget.setObjectName(_fromUtf8("uiNIOVDEListWidget"))
|
||||
self.vboxlayout5.addWidget(self.uiNIOVDEListWidget)
|
||||
self.gridlayout9.addWidget(self.uiNIOVDEListGroupBox, 0, 2, 3, 1)
|
||||
self.uiAddNIOVDEPushButton = QtGui.QPushButton(self.tab_5)
|
||||
self.uiAddNIOVDEPushButton = QtGui.QPushButton(self.NIOVDETab)
|
||||
self.uiAddNIOVDEPushButton.setObjectName(_fromUtf8("uiAddNIOVDEPushButton"))
|
||||
self.gridlayout9.addWidget(self.uiAddNIOVDEPushButton, 1, 0, 1, 1)
|
||||
self.uiDeleteNIOVDEPushButton = QtGui.QPushButton(self.tab_5)
|
||||
self.uiDeleteNIOVDEPushButton = QtGui.QPushButton(self.NIOVDETab)
|
||||
self.uiDeleteNIOVDEPushButton.setEnabled(False)
|
||||
self.uiDeleteNIOVDEPushButton.setObjectName(_fromUtf8("uiDeleteNIOVDEPushButton"))
|
||||
self.gridlayout9.addWidget(self.uiDeleteNIOVDEPushButton, 1, 1, 1, 1)
|
||||
spacerItem5 = QtGui.QSpacerItem(161, 201, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
|
||||
self.gridlayout9.addItem(spacerItem5, 2, 0, 2, 2)
|
||||
spacerItem6 = QtGui.QSpacerItem(196, 132, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridlayout9.addItem(spacerItem6, 3, 2, 1, 1)
|
||||
self.tabWidget.addTab(self.tab_5, _fromUtf8(""))
|
||||
self.tab_6 = QtGui.QWidget()
|
||||
self.tab_6.setObjectName(_fromUtf8("tab_6"))
|
||||
self.gridlayout13 = QtGui.QGridLayout(self.tab_6)
|
||||
spacerItem7 = QtGui.QSpacerItem(161, 201, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
|
||||
self.gridlayout9.addItem(spacerItem7, 2, 0, 2, 2)
|
||||
spacerItem8 = QtGui.QSpacerItem(196, 132, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridlayout9.addItem(spacerItem8, 3, 2, 1, 1)
|
||||
self.uiNIOsTabWidget.addTab(self.NIOVDETab, _fromUtf8(""))
|
||||
self.NIONullTab = QtGui.QWidget()
|
||||
self.NIONullTab.setObjectName(_fromUtf8("NIONullTab"))
|
||||
self.gridlayout13 = QtGui.QGridLayout(self.NIONullTab)
|
||||
self.gridlayout13.setObjectName(_fromUtf8("gridlayout13"))
|
||||
self.uiNIONullSettingsGroupBox = QtGui.QGroupBox(self.tab_6)
|
||||
self.uiNIONullSettingsGroupBox = QtGui.QGroupBox(self.NIONullTab)
|
||||
self.uiNIONullSettingsGroupBox.setObjectName(_fromUtf8("uiNIONullSettingsGroupBox"))
|
||||
self.gridlayout14 = QtGui.QGridLayout(self.uiNIONullSettingsGroupBox)
|
||||
self.gridlayout14.setObjectName(_fromUtf8("gridlayout14"))
|
||||
self.label_9 = QtGui.QLabel(self.uiNIONullSettingsGroupBox)
|
||||
self.label_9.setObjectName(_fromUtf8("label_9"))
|
||||
self.gridlayout14.addWidget(self.label_9, 0, 0, 1, 1)
|
||||
self.uiNIONullIdentifierLabel = QtGui.QLabel(self.uiNIONullSettingsGroupBox)
|
||||
self.uiNIONullIdentifierLabel.setObjectName(_fromUtf8("uiNIONullIdentifierLabel"))
|
||||
self.gridlayout14.addWidget(self.uiNIONullIdentifierLabel, 0, 0, 1, 1)
|
||||
self.uiNIONullIdentiferLineEdit = QtGui.QLineEdit(self.uiNIONullSettingsGroupBox)
|
||||
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
@@ -330,7 +375,7 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.uiNIONullIdentiferLineEdit.setObjectName(_fromUtf8("uiNIONullIdentiferLineEdit"))
|
||||
self.gridlayout14.addWidget(self.uiNIONullIdentiferLineEdit, 1, 0, 1, 1)
|
||||
self.gridlayout13.addWidget(self.uiNIONullSettingsGroupBox, 0, 0, 1, 2)
|
||||
self.uiNIONullListGroupBox = QtGui.QGroupBox(self.tab_6)
|
||||
self.uiNIONullListGroupBox = QtGui.QGroupBox(self.NIONullTab)
|
||||
self.uiNIONullListGroupBox.setObjectName(_fromUtf8("uiNIONullListGroupBox"))
|
||||
self.vboxlayout6 = QtGui.QVBoxLayout(self.uiNIONullListGroupBox)
|
||||
self.vboxlayout6.setObjectName(_fromUtf8("vboxlayout6"))
|
||||
@@ -343,35 +388,35 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.uiNIONullListWidget.setObjectName(_fromUtf8("uiNIONullListWidget"))
|
||||
self.vboxlayout6.addWidget(self.uiNIONullListWidget)
|
||||
self.gridlayout13.addWidget(self.uiNIONullListGroupBox, 0, 2, 3, 1)
|
||||
self.uiAddNIONullPushButton = QtGui.QPushButton(self.tab_6)
|
||||
self.uiAddNIONullPushButton = QtGui.QPushButton(self.NIONullTab)
|
||||
self.uiAddNIONullPushButton.setObjectName(_fromUtf8("uiAddNIONullPushButton"))
|
||||
self.gridlayout13.addWidget(self.uiAddNIONullPushButton, 1, 0, 1, 1)
|
||||
self.uiDeleteNIONullPushButton = QtGui.QPushButton(self.tab_6)
|
||||
self.uiDeleteNIONullPushButton = QtGui.QPushButton(self.NIONullTab)
|
||||
self.uiDeleteNIONullPushButton.setEnabled(False)
|
||||
self.uiDeleteNIONullPushButton.setObjectName(_fromUtf8("uiDeleteNIONullPushButton"))
|
||||
self.gridlayout13.addWidget(self.uiDeleteNIONullPushButton, 1, 1, 1, 1)
|
||||
spacerItem7 = QtGui.QSpacerItem(20, 261, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridlayout13.addItem(spacerItem7, 2, 0, 2, 2)
|
||||
spacerItem8 = QtGui.QSpacerItem(20, 181, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridlayout13.addItem(spacerItem8, 3, 2, 1, 1)
|
||||
self.tabWidget.addTab(self.tab_6, _fromUtf8(""))
|
||||
self.tab_7 = QtGui.QWidget()
|
||||
self.tab_7.setObjectName(_fromUtf8("tab_7"))
|
||||
self.gridLayout = QtGui.QGridLayout(self.tab_7)
|
||||
spacerItem9 = QtGui.QSpacerItem(20, 261, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridlayout13.addItem(spacerItem9, 2, 0, 2, 2)
|
||||
spacerItem10 = QtGui.QSpacerItem(20, 181, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridlayout13.addItem(spacerItem10, 3, 2, 1, 1)
|
||||
self.uiNIOsTabWidget.addTab(self.NIONullTab, _fromUtf8(""))
|
||||
self.MiscTab = QtGui.QWidget()
|
||||
self.MiscTab.setObjectName(_fromUtf8("MiscTab"))
|
||||
self.gridLayout = QtGui.QGridLayout(self.MiscTab)
|
||||
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
|
||||
self.uiNameLabel = QtGui.QLabel(self.tab_7)
|
||||
self.uiNameLabel = QtGui.QLabel(self.MiscTab)
|
||||
self.uiNameLabel.setObjectName(_fromUtf8("uiNameLabel"))
|
||||
self.gridLayout.addWidget(self.uiNameLabel, 0, 0, 1, 1)
|
||||
self.uiNameLineEdit = QtGui.QLineEdit(self.tab_7)
|
||||
self.uiNameLineEdit = QtGui.QLineEdit(self.MiscTab)
|
||||
self.uiNameLineEdit.setObjectName(_fromUtf8("uiNameLineEdit"))
|
||||
self.gridLayout.addWidget(self.uiNameLineEdit, 0, 1, 1, 1)
|
||||
spacerItem9 = QtGui.QSpacerItem(20, 399, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridLayout.addItem(spacerItem9, 1, 1, 1, 1)
|
||||
self.tabWidget.addTab(self.tab_7, _fromUtf8(""))
|
||||
self.vboxlayout.addWidget(self.tabWidget)
|
||||
spacerItem11 = QtGui.QSpacerItem(20, 399, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
self.gridLayout.addItem(spacerItem11, 1, 1, 1, 1)
|
||||
self.uiNIOsTabWidget.addTab(self.MiscTab, _fromUtf8(""))
|
||||
self.vboxlayout.addWidget(self.uiNIOsTabWidget)
|
||||
|
||||
self.retranslateUi(cloudConfigPageWidget)
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
self.uiNIOsTabWidget.setCurrentIndex(0)
|
||||
QtCore.QMetaObject.connectSlotsByName(cloudConfigPageWidget)
|
||||
|
||||
def retranslateUi(self, cloudConfigPageWidget):
|
||||
@@ -382,7 +427,13 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.uiLinuxEthernetGroupBox.setTitle(_translate("cloudConfigPageWidget", "Linux Ethernet NIO (Linux only, root access required)", None))
|
||||
self.uiAddLinuxEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
|
||||
self.uiDeleteLinuxEthernetPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("cloudConfigPageWidget", "NIO Ethernet", None))
|
||||
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOEthernetTab), _translate("cloudConfigPageWidget", "Ethernet", None))
|
||||
self.uiNIONATSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
|
||||
self.uiNIONATIdentifierLabel.setText(_translate("cloudConfigPageWidget", "Identifier:", None))
|
||||
self.uiNIONATListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
|
||||
self.uiAddNIONATPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
|
||||
self.uiDeleteNIONATPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
|
||||
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIONATTab), _translate("cloudConfigPageWidget", "NAT", None))
|
||||
self.uiNIOUDPSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
|
||||
self.uiLocalPortLabel.setText(_translate("cloudConfigPageWidget", "Local port:", None))
|
||||
self.uiRemoteHostLabel.setText(_translate("cloudConfigPageWidget", "Remote host:", None))
|
||||
@@ -391,31 +442,31 @@ class Ui_cloudConfigPageWidget(object):
|
||||
self.uiNIOUDPListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
|
||||
self.uiAddNIOUDPPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
|
||||
self.uiDeleteNIOUDPPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("cloudConfigPageWidget", "NIO UDP", None))
|
||||
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOUDPTab), _translate("cloudConfigPageWidget", "UDP", None))
|
||||
self.uiNIOTAPGroupBox.setTitle(_translate("cloudConfigPageWidget", "TAP interface (require root access)", None))
|
||||
self.uiAddNIOTAPPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
|
||||
self.uiDeleteNIOTAPPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("cloudConfigPageWidget", "NIO TAP", None))
|
||||
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOTAPTab), _translate("cloudConfigPageWidget", "TAP", None))
|
||||
self.uiNIOUNIXSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
|
||||
self.uiLocalFileLabel.setText(_translate("cloudConfigPageWidget", "Local file:", None))
|
||||
self.uiRemoteFileLabel.setText(_translate("cloudConfigPageWidget", "Remote file:", None))
|
||||
self.uiNIOUNIXListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
|
||||
self.uiAddNIOUNIXPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
|
||||
self.uiDeleteNIOUNIXPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), _translate("cloudConfigPageWidget", "NIO UNIX", None))
|
||||
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOUnixTab), _translate("cloudConfigPageWidget", "UNIX", None))
|
||||
self.uiNIOVDESettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
|
||||
self.uiVDEControlFileLabel.setText(_translate("cloudConfigPageWidget", "Control file:", None))
|
||||
self.uiVDELocalFileLabel.setText(_translate("cloudConfigPageWidget", "Local file:", None))
|
||||
self.uiNIOVDEListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
|
||||
self.uiAddNIOVDEPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
|
||||
self.uiDeleteNIOVDEPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_5), _translate("cloudConfigPageWidget", "NIO VDE", None))
|
||||
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIOVDETab), _translate("cloudConfigPageWidget", "VDE", None))
|
||||
self.uiNIONullSettingsGroupBox.setTitle(_translate("cloudConfigPageWidget", "Settings", None))
|
||||
self.label_9.setText(_translate("cloudConfigPageWidget", "Identifier:", None))
|
||||
self.uiNIONullIdentifierLabel.setText(_translate("cloudConfigPageWidget", "Identifier:", None))
|
||||
self.uiNIONullListGroupBox.setTitle(_translate("cloudConfigPageWidget", "NIOs", None))
|
||||
self.uiAddNIONullPushButton.setText(_translate("cloudConfigPageWidget", "&Add", None))
|
||||
self.uiDeleteNIONullPushButton.setText(_translate("cloudConfigPageWidget", "&Delete", None))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_6), _translate("cloudConfigPageWidget", "NIO NULL", None))
|
||||
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.NIONullTab), _translate("cloudConfigPageWidget", "NULL", None))
|
||||
self.uiNameLabel.setText(_translate("cloudConfigPageWidget", "Name:", None))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_7), _translate("cloudConfigPageWidget", "Misc.", None))
|
||||
self.uiNIOsTabWidget.setTabText(self.uiNIOsTabWidget.indexOf(self.MiscTab), _translate("cloudConfigPageWidget", "Misc.", None))
|
||||
|
||||
|
||||
@@ -19,12 +19,14 @@
|
||||
Dynamips module implementation.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import glob
|
||||
import shutil
|
||||
|
||||
from gns3.qt import QtCore, QtGui
|
||||
from gns3.servers import Servers
|
||||
from gns3.node import Node
|
||||
from gns3.local_config import LocalConfig
|
||||
from gns3.local_server_config import LocalServerConfig
|
||||
|
||||
from ..module import Module
|
||||
from ..module_error import ModuleError
|
||||
@@ -55,12 +57,12 @@ PLATFORM_TO_CLASS = {
|
||||
"c7200": C7200
|
||||
}
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Dynamips(Module):
|
||||
|
||||
"""
|
||||
Dynamips module.
|
||||
"""
|
||||
@@ -70,204 +72,166 @@ class Dynamips(Module):
|
||||
|
||||
self._settings = {}
|
||||
self._ios_routers = {}
|
||||
self._servers = []
|
||||
self._nodes = []
|
||||
self._working_dir = ""
|
||||
self._images_dir = ""
|
||||
self._ios_images_cache = {}
|
||||
|
||||
# load the settings and IOS images.
|
||||
self._loadSettings()
|
||||
self._loadIOSRouters()
|
||||
|
||||
@staticmethod
|
||||
def _findDynamips(self):
|
||||
"""
|
||||
Finds the Dynamips path.
|
||||
|
||||
:return: path to Dynamips
|
||||
"""
|
||||
|
||||
if sys.platform.startswith("win") and hasattr(sys, "frozen"):
|
||||
dynamips_path = os.path.join(os.getcwd(), "dynamips", "dynamips.exe")
|
||||
elif sys.platform.startswith("darwin") and hasattr(sys, "frozen"):
|
||||
dynamips_path = os.path.join(os.getcwd(), "dynamips")
|
||||
else:
|
||||
dynamips_path = shutil.which("dynamips")
|
||||
|
||||
if dynamips_path is None:
|
||||
return ""
|
||||
return dynamips_path
|
||||
|
||||
def _loadSettings(self):
|
||||
"""
|
||||
Loads the settings from the persistent settings file.
|
||||
"""
|
||||
|
||||
# load the settings
|
||||
local_config = LocalConfig.instance()
|
||||
# restore the Dynamips settings from QSettings (for backward compatibility)
|
||||
legacy_settings = {}
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup(self.__class__.__name__)
|
||||
for name, value in DYNAMIPS_SETTINGS.items():
|
||||
self._settings[name] = settings.value(name, value, type=DYNAMIPS_SETTING_TYPES[name])
|
||||
for name in DYNAMIPS_SETTINGS.keys():
|
||||
if settings.contains(name):
|
||||
legacy_settings[name] = settings.value(name, type=DYNAMIPS_SETTING_TYPES[name])
|
||||
settings.remove("")
|
||||
settings.endGroup()
|
||||
|
||||
if legacy_settings:
|
||||
local_config.saveSectionSettings(self.__class__.__name__, legacy_settings)
|
||||
self._settings = local_config.loadSectionSettings(self.__class__.__name__, DYNAMIPS_SETTINGS)
|
||||
|
||||
if not os.path.exists(self._settings["dynamips_path"]):
|
||||
self._settings["dynamips_path"] = self._findDynamips(self)
|
||||
|
||||
# keep the config file sync
|
||||
self._saveSettings()
|
||||
|
||||
def _saveSettings(self):
|
||||
"""
|
||||
Saves the settings to the persistent settings file.
|
||||
"""
|
||||
|
||||
# save the settings
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup(self.__class__.__name__)
|
||||
for name, value in self._settings.items():
|
||||
settings.setValue(name, value)
|
||||
settings.endGroup()
|
||||
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, self._settings)
|
||||
|
||||
# save some settings to the local server config file
|
||||
server_settings = {
|
||||
"allocate_aux_console_ports": self._settings["allocate_aux_console_ports"],
|
||||
"ghost_ios_support": self._settings["ghost_ios_support"],
|
||||
"sparse_memory_support": self._settings["sparse_memory_support"],
|
||||
"mmap_support": self._settings["mmap_support"],
|
||||
}
|
||||
|
||||
if self._settings["dynamips_path"]:
|
||||
server_settings["dynamips_path"] = os.path.normpath(self._settings["dynamips_path"])
|
||||
config = LocalServerConfig.instance()
|
||||
config.saveSettings(self.__class__.__name__, server_settings)
|
||||
|
||||
def _loadIOSRouters(self):
|
||||
"""
|
||||
Load the IOS routers from the persistent settings file.
|
||||
"""
|
||||
|
||||
local_config = LocalConfig.instance()
|
||||
|
||||
# restore the Dynamips VM settings from QSettings (for backward compatibility)
|
||||
ios_routers = []
|
||||
# load the settings
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup("IOSRouters")
|
||||
|
||||
# load the IOS images
|
||||
# load the VMs
|
||||
size = settings.beginReadArray("ios_router")
|
||||
for index in range(0, size):
|
||||
settings.setArrayIndex(index)
|
||||
name = settings.value("name")
|
||||
server = settings.value("server")
|
||||
key = "{server}:{name}".format(server=server, name=name)
|
||||
if key in self._ios_routers or not name or not server:
|
||||
continue
|
||||
self._ios_routers[key] = {}
|
||||
router = {}
|
||||
for setting_name, default_value in IOS_ROUTER_SETTINGS.items():
|
||||
self._ios_routers[key][setting_name] = settings.value(setting_name, default_value, IOS_ROUTER_SETTING_TYPES[setting_name])
|
||||
router[setting_name] = settings.value(setting_name, default_value, IOS_ROUTER_SETTING_TYPES[setting_name])
|
||||
|
||||
for slot_id in range(0, 7):
|
||||
slot = "slot{}".format(slot_id)
|
||||
if settings.contains(slot):
|
||||
self._ios_routers[key][slot] = settings.value(slot, "")
|
||||
router[slot] = settings.value(slot, "")
|
||||
|
||||
for wic_id in range(0, 3):
|
||||
wic = "wic{}".format(wic_id)
|
||||
if settings.contains(wic):
|
||||
self._ios_routers[key][wic] = settings.value(wic, "")
|
||||
router[wic] = settings.value(wic, "")
|
||||
|
||||
platform = self._ios_routers[key]["platform"]
|
||||
chassis = self._ios_routers[key]["chassis"]
|
||||
platform = router["platform"]
|
||||
chassis = router["chassis"]
|
||||
|
||||
if platform == "c7200":
|
||||
self._ios_routers[key]["midplane"] = settings.value("midplane", "vxr")
|
||||
self._ios_routers[key]["npe"] = settings.value("npe", "npe-400")
|
||||
self._ios_routers[key]["slot0"] = settings.value("slot0", "C7200-IO-FE")
|
||||
router["midplane"] = settings.value("midplane", "vxr")
|
||||
router["npe"] = settings.value("npe", "npe-400")
|
||||
router["slot0"] = settings.value("slot0", "C7200-IO-FE")
|
||||
else:
|
||||
self._ios_routers[key]["iomem"] = 5
|
||||
router["iomem"] = 5
|
||||
|
||||
if platform in ("c3725", "c3725", "c2691"):
|
||||
self._ios_routers[key]["slot0"] = settings.value("slot0", "GT96100-FE")
|
||||
router["slot0"] = settings.value("slot0", "GT96100-FE")
|
||||
elif platform == "c3600" and chassis == "3660":
|
||||
self._ios_routers[key]["slot0"] = settings.value("slot0", "Leopard-2FE")
|
||||
router["slot0"] = settings.value("slot0", "Leopard-2FE")
|
||||
elif platform == "c2600" and chassis == "2610":
|
||||
self._ios_routers[key]["slot0"] = settings.value("slot0", "C2600-MB-1E")
|
||||
router["slot0"] = settings.value("slot0", "C2600-MB-1E")
|
||||
elif platform == "c2600" and chassis == "2611":
|
||||
self._ios_routers[key]["slot0"] = settings.value("slot0", "C2600-MB-2E")
|
||||
router["slot0"] = settings.value("slot0", "C2600-MB-2E")
|
||||
elif platform == "c2600" and chassis in ("2620", "2610XM", "2620XM", "2650XM"):
|
||||
self._ios_routers[key]["slot0"] = settings.value("slot0", "C2600-MB-1FE")
|
||||
router["slot0"] = settings.value("slot0", "C2600-MB-1FE")
|
||||
elif platform == "c2600" and chassis in ("2621", "2611XM", "2621XM", "2651XM"):
|
||||
self._ios_routers[key]["slot0"] = settings.value("slot0", "C2600-MB-2FE")
|
||||
router["slot0"] = settings.value("slot0", "C2600-MB-2FE")
|
||||
elif platform == "c1700" and chassis in ("1720", "1721", "1750", "1751", "1760"):
|
||||
self._ios_routers[key]["slot0"] = settings.value("slot0", "C1700-MB-1FE")
|
||||
router["slot0"] = settings.value("slot0", "C1700-MB-1FE")
|
||||
elif platform == "c1700" and chassis in ("1751", "1760"):
|
||||
self._ios_routers[key]["slot0"] = settings.value("slot0", "C1700-MB-WIC1")
|
||||
router["slot0"] = settings.value("slot0", "C1700-MB-WIC1")
|
||||
|
||||
ios_routers.append(router)
|
||||
|
||||
settings.endArray()
|
||||
settings.remove("")
|
||||
settings.endGroup()
|
||||
|
||||
if ios_routers:
|
||||
local_config.saveSectionSettings(self.__class__.__name__, {"routers": ios_routers})
|
||||
|
||||
settings = local_config.settings()
|
||||
if "routers" in settings.get(self.__class__.__name__, {}):
|
||||
for router in settings[self.__class__.__name__]["routers"]:
|
||||
name = router.get("name")
|
||||
server = router.get("server")
|
||||
router["image"] = router.get("path", router["image"]) # for compatibility before version 1.3
|
||||
key = "{server}:{name}".format(server=server, name=name)
|
||||
if key in self._ios_routers or not name or not server:
|
||||
continue
|
||||
self._ios_routers[key] = router
|
||||
|
||||
# keep things sync
|
||||
self._saveIOSRouters()
|
||||
|
||||
def _saveIOSRouters(self):
|
||||
"""
|
||||
Saves the IOS routers to the persistent settings file.
|
||||
"""
|
||||
|
||||
# save the settings
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup("IOSRouters")
|
||||
settings.remove("")
|
||||
|
||||
# save the IOS images
|
||||
settings.beginWriteArray("ios_router", len(self._ios_routers))
|
||||
index = 0
|
||||
for ios_router in self._ios_routers.values():
|
||||
settings.setArrayIndex(index)
|
||||
for name, value in ios_router.items():
|
||||
settings.setValue(name, value)
|
||||
index += 1
|
||||
settings.endArray()
|
||||
settings.endGroup()
|
||||
|
||||
def _delete_dynamips_files(self):
|
||||
"""
|
||||
Deletes useless local Dynamips files from the working directory
|
||||
"""
|
||||
|
||||
files = glob.glob(os.path.join(self._working_dir, "dynamips", "*.ghost"))
|
||||
files += glob.glob(os.path.join(self._working_dir, "dynamips", "*_lock"))
|
||||
files += glob.glob(os.path.join(self._working_dir, "dynamips", "ilt_*"))
|
||||
files += glob.glob(os.path.join(self._working_dir, "dynamips", "c[0-9][0-9][0-9][0-9]_*_rommon_vars"))
|
||||
files += glob.glob(os.path.join(self._working_dir, "dynamips", "c[0-9][0-9][0-9][0-9]_*_ssa"))
|
||||
for file in files:
|
||||
try:
|
||||
log.debug("deleting file {}".format(file))
|
||||
os.remove(file)
|
||||
except OSError as e:
|
||||
log.warn("could not delete file {}: {}".format(file, e))
|
||||
continue
|
||||
|
||||
def setProjectFilesDir(self, path):
|
||||
"""
|
||||
Sets the project files directory path this module.
|
||||
|
||||
:param path: path to the local project files directory
|
||||
"""
|
||||
|
||||
#self._delete_dynamips_files() #FIXME: cause issues
|
||||
self._working_dir = path
|
||||
log.info("local working directory for Dynamips module: {}".format(self._working_dir))
|
||||
|
||||
# update the server with the new working directory / project name
|
||||
for server in self._servers:
|
||||
if server.connected():
|
||||
self._sendSettings(server)
|
||||
|
||||
def setImageFilesDir(self, path):
|
||||
"""
|
||||
Sets the image files directory path this module.
|
||||
|
||||
:param path: path to the local image files directory
|
||||
"""
|
||||
|
||||
self._images_dir = os.path.join(path, "IOS")
|
||||
|
||||
def imageFilesDir(self):
|
||||
"""
|
||||
Returns the files directory path this module.
|
||||
|
||||
:returns: path to the local image files directory
|
||||
"""
|
||||
|
||||
return self._images_dir
|
||||
|
||||
def addServer(self, server):
|
||||
"""
|
||||
Adds a server to be used by this module.
|
||||
|
||||
:param server: WebSocketClient instance
|
||||
"""
|
||||
|
||||
log.info("adding server {}:{} to Dynamips module".format(server.host, server.port))
|
||||
self._servers.append(server)
|
||||
self._sendSettings(server)
|
||||
|
||||
def removeServer(self, server):
|
||||
"""
|
||||
Removes a server from being used by this module.
|
||||
|
||||
:param server: WebSocketClient instance
|
||||
"""
|
||||
|
||||
log.info("removing server {}:{} from Dynamips module".format(server.host, server.port))
|
||||
self._servers.remove(server)
|
||||
|
||||
def servers(self):
|
||||
"""
|
||||
Returns all the servers used by this module.
|
||||
|
||||
:returns: list of WebSocketClient instances
|
||||
"""
|
||||
|
||||
return self._servers
|
||||
LocalConfig.instance().saveSectionSettings(self.__class__.__name__, {"routers": list(self._ios_routers.values())})
|
||||
|
||||
def addNode(self, node):
|
||||
"""
|
||||
@@ -323,55 +287,16 @@ class Dynamips(Module):
|
||||
:param settings: module settings (dictionary)
|
||||
"""
|
||||
|
||||
params = {}
|
||||
for name, value in settings.items():
|
||||
if name in self._settings and self._settings[name] != value:
|
||||
params[name] = value
|
||||
|
||||
if params:
|
||||
for server in self._servers:
|
||||
if server.isLocal():
|
||||
params.update({"working_dir": self._working_dir})
|
||||
else:
|
||||
if "path" in params:
|
||||
del params["path"] # do not send Dynamips path to remote servers
|
||||
project_name = os.path.basename(self._working_dir)
|
||||
if project_name.endswith("-files"):
|
||||
project_name = project_name[:-6]
|
||||
params.update({"project_name": project_name})
|
||||
server.send_notification("dynamips.settings", params)
|
||||
|
||||
self._settings.update(settings)
|
||||
self._saveSettings()
|
||||
|
||||
def _sendSettings(self, server):
|
||||
"""
|
||||
Sends the module settings to the server.
|
||||
|
||||
:param server: WebSocketClient instance
|
||||
"""
|
||||
|
||||
log.info("sending Dynamips settings to server {}:{}".format(server.host, server.port))
|
||||
params = self._settings.copy()
|
||||
# send the local working directory only if this is a local server
|
||||
if server.isLocal():
|
||||
params.update({"working_dir": self._working_dir})
|
||||
else:
|
||||
if "path" in params:
|
||||
del params["path"] # do not send Dynamips path to remote servers
|
||||
project_name = os.path.basename(self._working_dir)
|
||||
if project_name.endswith("-files"):
|
||||
project_name = project_name[:-6]
|
||||
params.update({"project_name": project_name})
|
||||
server.send_notification("dynamips.settings", params)
|
||||
|
||||
def allocateServer(self, node_class, use_cloud=False):
|
||||
"""
|
||||
Allocates a server.
|
||||
|
||||
:param node_class: Node object
|
||||
|
||||
:returns: allocated server (WebSocketClient instance)
|
||||
:returns: allocated server (HTTPClient instance)
|
||||
"""
|
||||
|
||||
# allocate a server for the node
|
||||
@@ -393,29 +318,19 @@ class Dynamips(Module):
|
||||
raise ModuleError("No remote server is configured")
|
||||
return server
|
||||
|
||||
def createNode(self, node_class, server):
|
||||
def createNode(self, node_class, server, project):
|
||||
"""
|
||||
Creates a new node.
|
||||
|
||||
:param node_class: Node object
|
||||
:param server: WebSocketClient instance
|
||||
:param server: HTTPClient instance
|
||||
:param project: Project instance
|
||||
"""
|
||||
|
||||
log.info("creating node {}".format(node_class))
|
||||
|
||||
if not server.connected():
|
||||
try:
|
||||
log.info("reconnecting to server {}:{}".format(server.host, server.port))
|
||||
server.reconnect()
|
||||
except OSError as e:
|
||||
raise ModuleError("Could not connect to server {}:{}: {}".format(server.host,
|
||||
server.port,
|
||||
e))
|
||||
if server not in self._servers:
|
||||
self.addServer(server)
|
||||
|
||||
# create an instance of the node class
|
||||
return node_class(self, server)
|
||||
return node_class(self, server, project)
|
||||
|
||||
def setupNode(self, node, node_name):
|
||||
"""
|
||||
@@ -436,63 +351,26 @@ class Dynamips(Module):
|
||||
ios_router = self._ios_routers[ios_key]
|
||||
break
|
||||
|
||||
# hack for EtherSwitch router
|
||||
if isinstance(node, EtherSwitchRouter) and node.server() == Servers.instance().localServer():
|
||||
for info in self._ios_routers.values():
|
||||
if info["platform"] == "c3725" and info["server"] == "local":
|
||||
ios_router = {
|
||||
"platform": "c3725",
|
||||
"path": info["path"],
|
||||
"ram": info["ram"],
|
||||
"startup_config": info["startup_config"],
|
||||
}
|
||||
break
|
||||
if not ios_router:
|
||||
raise ModuleError("Please create an c3725 IOS router in order to use an EtherSwitch router")
|
||||
|
||||
if not ios_router:
|
||||
raise ModuleError("No IOS router for platform {}".format(node.settings()["platform"]))
|
||||
|
||||
settings = {}
|
||||
# set initial settings like the chassis or an Idle-PC value etc.
|
||||
if "chassis" in ios_router and ios_router["chassis"]:
|
||||
settings["chassis"] = ios_router["chassis"]
|
||||
if "idlepc" in ios_router and ios_router["idlepc"]:
|
||||
settings["idlepc"] = ios_router["idlepc"]
|
||||
if ios_router["startup_config"]:
|
||||
settings["startup_config"] = ios_router["startup_config"]
|
||||
if "private_config" in ios_router and ios_router["private_config"]:
|
||||
settings["private_config"] = ios_router["private_config"]
|
||||
vm_settings = {}
|
||||
for setting_name, value in ios_router.items():
|
||||
if setting_name in node.settings() and setting_name != "name" and value != "" and value is not None:
|
||||
vm_settings[setting_name] = value
|
||||
|
||||
if ios_router["platform"] == "c7200":
|
||||
settings["midplane"] = ios_router["midplane"]
|
||||
settings["npe"] = ios_router["npe"]
|
||||
elif "iomem" in ios_router:
|
||||
settings["iomem"] = ios_router["iomem"]
|
||||
base_name = "R"
|
||||
if "slot1" in vm_settings and vm_settings["slot1"] == "NM-16ESW":
|
||||
# must be an EtherSwitch router
|
||||
base_name = "ESW"
|
||||
|
||||
if "nvram" in ios_router and ios_router["nvram"]:
|
||||
settings["nvram"] = ios_router["nvram"]
|
||||
if "console" in vm_settings:
|
||||
# Older GNS3 versions may have a console setting in the VM template
|
||||
del vm_settings["console"]
|
||||
|
||||
if "disk0" in ios_router and ios_router["disk0"]:
|
||||
settings["disk0"] = ios_router["disk0"]
|
||||
|
||||
if "disk1" in ios_router and ios_router["disk1"]:
|
||||
settings["disk1"] = ios_router["disk1"]
|
||||
|
||||
for slot_id in range(0, 7):
|
||||
slot = "slot{}".format(slot_id)
|
||||
if slot in ios_router:
|
||||
settings[slot] = ios_router[slot]
|
||||
for wic_id in range(0, 3):
|
||||
wic = "wic{}".format(wic_id)
|
||||
if wic in ios_router:
|
||||
settings[wic] = ios_router[wic]
|
||||
|
||||
if node.server().isCloud():
|
||||
settings["cloud_path"] = "images/IOS"
|
||||
node.setup(ios_router["image"], ios_router["ram"], initial_settings=settings)
|
||||
else:
|
||||
node.setup(ios_router["path"], ios_router["ram"], initial_settings=settings)
|
||||
ram = vm_settings.pop("ram")
|
||||
image = vm_settings.pop("image")
|
||||
node.setup(image, ram, additional_settings=vm_settings, base_name=base_name)
|
||||
else:
|
||||
node.setup()
|
||||
|
||||
@@ -505,7 +383,7 @@ class Dynamips(Module):
|
||||
"""
|
||||
|
||||
for ios_router in self._ios_routers.values():
|
||||
if ios_router["path"] == image_path:
|
||||
if os.path.basename(ios_router["image"]) == image_path:
|
||||
if ios_router["idlepc"] != idlepc:
|
||||
ios_router["idlepc"] = idlepc
|
||||
self._saveIOSRouters()
|
||||
@@ -513,36 +391,12 @@ class Dynamips(Module):
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Resets the servers and nodes.
|
||||
Resets the module.
|
||||
"""
|
||||
|
||||
log.info("Dynamips module reset")
|
||||
for server in self._servers:
|
||||
if server.connected():
|
||||
server.send_notification("dynamips.reset")
|
||||
self._servers.clear()
|
||||
|
||||
for node in self._nodes:
|
||||
node.reset()
|
||||
self._nodes.clear()
|
||||
|
||||
def notification(self, destination, params):
|
||||
"""
|
||||
To received notifications from the server.
|
||||
|
||||
:param destination: JSON-RPC method
|
||||
:param params: JSON-RPC params
|
||||
"""
|
||||
|
||||
if "devices" in params:
|
||||
for node in self._nodes:
|
||||
for device in params["devices"]:
|
||||
if node.name() == device:
|
||||
message = "node {}: {}".format(node.name(), params["message"])
|
||||
self.notification_signal.emit(message, params["details"])
|
||||
if hasattr(node, "stop"):
|
||||
node.stop()
|
||||
|
||||
def exportConfigs(self, directory):
|
||||
"""
|
||||
Exports all configs for all nodes to a directory.
|
||||
@@ -551,8 +405,8 @@ class Dynamips(Module):
|
||||
"""
|
||||
|
||||
for node in self._nodes:
|
||||
if hasattr(node, "exportConfigs") and node.initialized():
|
||||
node.exportConfigs(directory)
|
||||
if isinstance(node, Router) and node.initialized():
|
||||
node.exportConfigToDirectory(directory)
|
||||
|
||||
def importConfigs(self, directory):
|
||||
"""
|
||||
@@ -562,8 +416,8 @@ class Dynamips(Module):
|
||||
"""
|
||||
|
||||
for node in self._nodes:
|
||||
if hasattr(node, "importConfigs") and node.initialized():
|
||||
node.importConfigs(directory)
|
||||
if isinstance(node, Router) and node.initialized():
|
||||
node.importConfigFromDirectory(directory)
|
||||
|
||||
def findAlternativeIOSImage(self, image, node):
|
||||
"""
|
||||
@@ -582,7 +436,7 @@ class Dynamips(Module):
|
||||
mainwindow = MainWindow.instance()
|
||||
ios_routers = self.iosRouters()
|
||||
candidate_ios_images = {}
|
||||
alternative_image = {"path": image,
|
||||
alternative_image = {"image": image,
|
||||
"ram": None,
|
||||
"idlepc": None}
|
||||
|
||||
@@ -596,8 +450,8 @@ class Dynamips(Module):
|
||||
"IOS image", "IOS image {} could not be found\nPlease select an alternative from your existing images:".format(image),
|
||||
list(candidate_ios_images.keys()), 0, False)
|
||||
if ok:
|
||||
ios_image = candidate_ios_images[selection]
|
||||
alternative_image["path"] = ios_router["path"]
|
||||
ios_image = candidate_ios_images[selection] # FIXME
|
||||
alternative_image["image"] = ios_router["image"]
|
||||
alternative_image["ram"] = ios_router["ram"]
|
||||
alternative_image["idlepc"] = ios_router["idlepc"]
|
||||
self._ios_images_cache[image] = alternative_image
|
||||
@@ -606,9 +460,9 @@ class Dynamips(Module):
|
||||
# no registered IOS image is used, let's just ask for an IOS image path
|
||||
QtGui.QMessageBox.critical(mainwindow, "IOS image", "Could not find the {} IOS image \nPlease select a similar IOS image!".format(image))
|
||||
from .pages.ios_router_preferences_page import IOSRouterPreferencesPage
|
||||
path = IOSRouterPreferencesPage.getIOSImage(mainwindow)
|
||||
if path:
|
||||
alternative_image["path"] = path
|
||||
image_path = IOSRouterPreferencesPage.getIOSImage(mainwindow)
|
||||
if image_path:
|
||||
alternative_image["image"] = image_path
|
||||
self._ios_images_cache[image] = alternative_image
|
||||
return alternative_image
|
||||
|
||||
@@ -648,7 +502,7 @@ class Dynamips(Module):
|
||||
server = "{}:{}".format(remote_server.host, remote_server.port)
|
||||
|
||||
nodes = []
|
||||
for node_class in [EtherSwitchRouter, EthernetSwitch, EthernetHub, FrameRelaySwitch, ATMSwitch]:
|
||||
for node_class in [EthernetSwitch, EthernetHub, FrameRelaySwitch, ATMSwitch]:
|
||||
nodes.append(
|
||||
{"class": node_class.__name__,
|
||||
"name": node_class.symbolName(),
|
||||
|
||||
@@ -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,18 @@ Wizard for IOS routers.
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import hashlib
|
||||
|
||||
from gns3.qt import QtCore, QtGui
|
||||
from gns3.servers import Servers
|
||||
from gns3.utils.message_box import MessageBox
|
||||
from gns3.node import Node
|
||||
from gns3.utils.run_in_terminal import RunInTerminal
|
||||
from gns3.utils.get_resource import get_resource
|
||||
from gns3.utils.get_default_base_config import get_default_base_config
|
||||
|
||||
from ....settings import ENABLE_CLOUD
|
||||
from ..ui.ios_router_wizard_ui import Ui_IOSRouterWizard
|
||||
from ..settings import PLATFORMS_DEFAULT_RAM, CHASSIS, ADAPTER_MATRIX, WIC_MATRIX
|
||||
from ..settings import PLATFORMS_DEFAULT_RAM, PLATFORMS_DEFAULT_NVRAM, DEFAULT_IDLEPC, CHASSIS, ADAPTER_MATRIX, WIC_MATRIX
|
||||
from .. import Dynamips
|
||||
from ..nodes.c1700 import C1700
|
||||
from ..nodes.c2600 import C2600
|
||||
@@ -50,8 +53,12 @@ PLATFORM_TO_CLASS = {
|
||||
"c7200": C7200
|
||||
}
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
|
||||
"""
|
||||
Wizard to create an IOS router.
|
||||
|
||||
@@ -74,10 +81,25 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
self.uiIOSImageToolButton.clicked.connect(self._iosImageBrowserSlot)
|
||||
self.uiTestIOSImagePushButton.clicked.connect(self._testIOSImageSlot)
|
||||
self.uiIdlePCFinderPushButton.clicked.connect(self._idlePCFinderSlot)
|
||||
self.uiEtherSwitchCheckBox.stateChanged.connect(self._etherSwitchSlot)
|
||||
self.uiPlatformComboBox.currentIndexChanged[str].connect(self._platformChangedSlot)
|
||||
self.uiPlatformComboBox.addItems(list(PLATFORMS_DEFAULT_RAM.keys()))
|
||||
|
||||
#FIXME: hide because of issue on Windows.
|
||||
self._router = None
|
||||
# Validate the Idle PC value
|
||||
self._idle_valid = False
|
||||
idle_pc_rgx = QtCore.QRegExp("^(0x[0-9a-fA-F]{8})?$")
|
||||
validator = QtGui.QRegExpValidator(idle_pc_rgx, self)
|
||||
self.uiIdlepcLineEdit.setValidator(validator)
|
||||
self.uiIdlepcLineEdit.textChanged.connect(self._idlePCValidateSlot)
|
||||
self.uiIdlepcLineEdit.textChanged.emit(self.uiIdlepcLineEdit.text())
|
||||
|
||||
# location of the base config templates
|
||||
self._base_startup_config_template = get_resource(os.path.join("configs", "ios_base_startup-config.txt"))
|
||||
self._base_private_config_template = get_resource(os.path.join("configs", "ios_base_private-config.txt"))
|
||||
self._base_etherswitch_startup_config_template = get_resource(os.path.join("configs", "ios_etherswitch_startup-config.txt"))
|
||||
|
||||
# FIXME: hide because of issue on Windows.
|
||||
self.uiTestIOSImagePushButton.hide()
|
||||
|
||||
# Mandatory fields
|
||||
@@ -105,7 +127,6 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
if not ENABLE_CLOUD:
|
||||
self.uiCloudRadioButton.hide()
|
||||
|
||||
|
||||
def _remoteServerToggledSlot(self, checked):
|
||||
"""
|
||||
Slot for when the remote server radio button is toggled.
|
||||
@@ -140,6 +161,11 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
self.uiChassisComboBox.clear()
|
||||
if platform in CHASSIS:
|
||||
self.uiChassisComboBox.addItems(CHASSIS[platform])
|
||||
if platform not in ("c2600", "c3600", "c2691", "c3725", "c3745"):
|
||||
self.uiEtherSwitchCheckBox.setChecked(False)
|
||||
self.uiEtherSwitchCheckBox.hide()
|
||||
else:
|
||||
self.uiEtherSwitchCheckBox.show()
|
||||
|
||||
def _testIOSImageSlot(self):
|
||||
"""
|
||||
@@ -149,7 +175,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
platform = self.uiPlatformComboBox.currentText()
|
||||
ram = self.uiRamSpinBox.value()
|
||||
ios_image = self.uiIOSImageLineEdit.text()
|
||||
dynamips = os.path.realpath(Dynamips.instance().settings()["path"])
|
||||
dynamips = os.path.realpath(Dynamips.instance().settings()["image"])
|
||||
if not os.path.exists(dynamips):
|
||||
QtGui.QMessageBox.critical(self, "IOS image", "Could not find Dynamips executable: {}".format(dynamips))
|
||||
return
|
||||
@@ -162,22 +188,58 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
except OSError as e:
|
||||
QtGui.QMessageBox.critical(self, "IOS image", "Could not test the IOS image: {}".format(e))
|
||||
|
||||
def _idlePCValidateSlot(self):
|
||||
"""
|
||||
Slot to validate the entered Idle-PC Value
|
||||
"""
|
||||
|
||||
validator = self.uiIdlepcLineEdit.validator()
|
||||
text_input = self.uiIdlepcLineEdit.text()
|
||||
state = validator.validate(text_input, len(text_input))[0]
|
||||
if state == QtGui.QValidator.Acceptable:
|
||||
color = '#A2C964' # green
|
||||
self._idle_valid = True
|
||||
elif state == QtGui.QValidator.Intermediate:
|
||||
color = '#fff79a' # yellow
|
||||
self._idle_valid = False
|
||||
else:
|
||||
color = '#f6989d' # red
|
||||
self._idle_valid = False
|
||||
self.uiIdlepcLineEdit.setStyleSheet('QLineEdit { background-color: %s }' % color)
|
||||
|
||||
def _idlePCFinderSlot(self):
|
||||
"""
|
||||
Slot for the idle-PC finder.
|
||||
"""
|
||||
|
||||
server = Servers.instance().localServer()
|
||||
from gns3.main_window import MainWindow
|
||||
main_window = MainWindow.instance()
|
||||
server = Servers.instance().getServerFromString(self.getSettings()["server"])
|
||||
module = Dynamips.instance()
|
||||
platform = self.uiPlatformComboBox.currentText()
|
||||
ios_image = self.uiIOSImageLineEdit.text()
|
||||
ram = self.uiRamSpinBox.value()
|
||||
router_class = PLATFORM_TO_CLASS[platform]
|
||||
self._router = router_class(module, server)
|
||||
self._router = router_class(module, server, main_window.project())
|
||||
self._router.setup(ios_image, ram, name="AUTOIDLEPC")
|
||||
self._router.created_signal.connect(self.createdSlot)
|
||||
self._router.server_error_signal.connect(self.serverErrorSlot)
|
||||
self.uiIdlePCFinderPushButton.setEnabled(False)
|
||||
|
||||
def _etherSwitchSlot(self, state):
|
||||
"""
|
||||
Slot if the EtherSwitch option is chosen or not.
|
||||
:param state: boolean
|
||||
"""
|
||||
|
||||
if state:
|
||||
# forces the name to EtherSwitch
|
||||
self.uiNameLineEdit.setText("EtherSwitch router")
|
||||
# self.uiNameLineEdit.setEnabled(False)
|
||||
else:
|
||||
self.uiNameLineEdit.setText(self.uiPlatformComboBox.currentText())
|
||||
# self.uiNameLineEdit.setEnabled(True)
|
||||
|
||||
def createdSlot(self, node_id):
|
||||
"""
|
||||
The node for the auto Idle-PC has been created.
|
||||
@@ -186,12 +248,18 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
"""
|
||||
|
||||
self._router.computeAutoIdlepc(self._computeAutoIdlepcCallback)
|
||||
self._auto_idlepc_progress_dialog = QtGui.QProgressDialog("Searching for an Idle-PC value...", "Cancel", 0, 0, parent=self)
|
||||
self._auto_idlepc_progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
self._auto_idlepc_progress_dialog.setWindowTitle("Idle-PC finder")
|
||||
self._auto_idlepc_progress_dialog.show()
|
||||
|
||||
def _computeAutoIdlepcCallback(self, result, error=False):
|
||||
def serverErrorSlot(self, node_id, message):
|
||||
"""
|
||||
The auto idle-pc node could not be created.
|
||||
|
||||
:param node_id: not used
|
||||
:param message: error message from the server.
|
||||
"""
|
||||
|
||||
QtGui.QMessageBox.critical(self, "Idle-PC finder", "Could not create IOS router: {}".format(message))
|
||||
|
||||
def _computeAutoIdlepcCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for computeAutoIdlepc.
|
||||
|
||||
@@ -199,19 +267,15 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
self._router.delete()
|
||||
if self._auto_idlepc_progress_dialog.wasCanceled():
|
||||
return
|
||||
self._auto_idlepc_progress_dialog.accept()
|
||||
|
||||
if self._router:
|
||||
self._router.delete()
|
||||
self._router = None
|
||||
if error:
|
||||
QtGui.QMessageBox.critical(self, "Idle-PC finder", "Error: ".format(result["message"]))
|
||||
QtGui.QMessageBox.critical(self, "Idle-PC finder", "Error: {}".format(result["message"]))
|
||||
else:
|
||||
if result["idlepc"] and result["idlepc"] != "0x0":
|
||||
self.uiIdlepcLineEdit.setText(result["idlepc"])
|
||||
else:
|
||||
logs = "\n".join(result["logs"])
|
||||
MessageBox(self, "Idle-PC finder", "Could not find an Idle-PC value", details=logs)
|
||||
idlepc = result["idlepc"]
|
||||
self.uiIdlepcLineEdit.setText(idlepc)
|
||||
QtGui.QMessageBox.information(self, "Idle-PC finder", "Idle-PC value {} has been found suitable for your IOS image".format(idlepc))
|
||||
|
||||
def _iosImageBrowserSlot(self):
|
||||
"""
|
||||
@@ -227,7 +291,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
|
||||
# try to guess the platform
|
||||
image = os.path.basename(path)
|
||||
match = re.match("^(c[0-9]+)\\-\w+", image.lower())
|
||||
match = re.match("^(c[0-9]+)p?\\-\w+", image.lower())
|
||||
if not match:
|
||||
QtGui.QMessageBox.warning(self, "IOS image", "Could not detect the platform, make sure this is a valid IOS image!")
|
||||
return
|
||||
@@ -245,6 +309,9 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
QtGui.QMessageBox.warning(self, "IOS image", "This IOS image is for the {} platform/chassis and is not supported by this application!".format(detected_platform))
|
||||
return
|
||||
|
||||
if image.lower().startswith("c7200p"):
|
||||
QtGui.QMessageBox.warning(self, "IOS image", "This IOS image is for c7200 PowerPC routers and is not recommended. Please use an IOS image that do not start with c7200p.")
|
||||
|
||||
index = self.uiPlatformComboBox.findText(detected_platform)
|
||||
if index != -1:
|
||||
self.uiPlatformComboBox.setCurrentIndex(index)
|
||||
@@ -253,6 +320,12 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
if index != -1:
|
||||
self.uiChassisComboBox.setCurrentIndex(index)
|
||||
|
||||
def done(self, result):
|
||||
|
||||
if self._router:
|
||||
self._router.delete()
|
||||
QtGui.QWizard.done(self, result)
|
||||
|
||||
def _populateAdapters(self, platform, chassis):
|
||||
"""
|
||||
Loads the adapter and WIC configuration.
|
||||
@@ -270,7 +343,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
for slot_number, slot_adapters in ADAPTER_MATRIX[platform][chassis].items():
|
||||
self._widget_slots[slot_number].setEnabled(True)
|
||||
|
||||
if type(slot_adapters) == str:
|
||||
if isinstance(slot_adapters, str):
|
||||
# only one default adapter for this slot.
|
||||
self._widget_slots[slot_number].addItem(slot_adapters)
|
||||
else:
|
||||
@@ -290,6 +363,17 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
wic_list = list(wics)
|
||||
self._widget_wics[wic_number].addItems([""] + wic_list)
|
||||
|
||||
def _md5sum(self, filename):
|
||||
|
||||
with open(filename, "rb") as fd:
|
||||
m = hashlib.md5()
|
||||
while True:
|
||||
data = fd.read(8192)
|
||||
if not data:
|
||||
break
|
||||
m.update(data)
|
||||
return m.hexdigest()
|
||||
|
||||
def initializePage(self, page_id):
|
||||
|
||||
if self.page(page_id) == self.uiServerWizardPage:
|
||||
@@ -308,7 +392,7 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
path = self.uiIOSImageLineEdit.text()
|
||||
if os.path.isfile(path):
|
||||
minimum_required_ram = IOSRouterPreferencesPage.getMinimumRequiredRAM(path)
|
||||
if minimum_required_ram > PLATFORMS_DEFAULT_RAM[platform]:
|
||||
if minimum_required_ram >= PLATFORMS_DEFAULT_RAM[platform]:
|
||||
self.uiRamSpinBox.setValue(minimum_required_ram)
|
||||
else:
|
||||
self.uiRamSpinBox.setValue(PLATFORMS_DEFAULT_RAM[platform])
|
||||
@@ -323,10 +407,22 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
self._populateAdapters(platform, chassis)
|
||||
if platform == "c7200":
|
||||
self.uiSlot0comboBox.setCurrentIndex(self.uiSlot0comboBox.findText("C7200-IO-FE"))
|
||||
if self.uiEtherSwitchCheckBox.isChecked():
|
||||
self.uiSlot1comboBox.setCurrentIndex(self.uiSlot1comboBox.findText("NM-16ESW"))
|
||||
|
||||
elif self.page(page_id) == self.uiIdlePCWizardPage:
|
||||
path = self.uiIOSImageLineEdit.text()
|
||||
if os.path.isfile(path):
|
||||
try:
|
||||
md5sum = self._md5sum(path)
|
||||
if md5sum in DEFAULT_IDLEPC:
|
||||
self.uiIdlepcLineEdit.setText(DEFAULT_IDLEPC[md5sum])
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def validateCurrentPage(self):
|
||||
"""
|
||||
Validates the IOS name.
|
||||
Validates the IOS name and checks validation state for Idle-PC value
|
||||
"""
|
||||
|
||||
if self.currentPage() == self.uiNamePlatformWizardPage:
|
||||
@@ -335,23 +431,21 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
if ios_router["name"] == name:
|
||||
QtGui.QMessageBox.critical(self, "Name", "{} is already used, please choose another name".format(name))
|
||||
return False
|
||||
if self.currentPage() == self.uiMemoryWizardPage and self.uiPlatformComboBox.currentText() == "c7200":
|
||||
if self.uiRamSpinBox.value() > 512:
|
||||
QtGui.QMessageBox.critical(self, "c7200 RAM requirement", "c7200 routers with NPE-400 are limited to 512MB of RAM")
|
||||
return False
|
||||
if self.currentPage() == self.uiIdlePCWizardPage:
|
||||
if not self._idle_valid:
|
||||
idle_pc = self.uiIdlepcLineEdit.text()
|
||||
QtGui.QMessageBox.critical(self, "Idle-PC", "{} is not a valid Idle-PC value ".format(idle_pc))
|
||||
return False
|
||||
if self.currentPage() == self.uiServerWizardPage and self.uiRemoteRadioButton.isChecked():
|
||||
if not Servers.instance().remoteServers():
|
||||
QtGui.QMessageBox.critical(self, "Remote server", "There is no remote server registered in Dynamips preferences")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
# minimum_required_ram = self._getMinimumRequiredRAM(path)
|
||||
# if minimum_required_ram > ram:
|
||||
# QtGui.QMessageBox.warning(self, "IOS image", "There is not sufficient RAM allocated to this IOS image, recommended RAM is {} MB".format(minimum_required_ram))
|
||||
#
|
||||
# # basename doesn't work on Unix with Windows paths
|
||||
# if not sys.platform.startswith('win') and len(path) > 2 and path[1] == ":":
|
||||
# import ntpath
|
||||
# image = ntpath.basename(path)
|
||||
# else:
|
||||
# image = os.path.basename(path)
|
||||
#
|
||||
# if image.startswith("c7200p"):
|
||||
# QtGui.QMessageBox.warning(self, "IOS image", "This IOS image is for the c7200 platform with NPE-G2 and using it is not recommended.\nPlease use an IOS image that do not start with c7200p.")
|
||||
|
||||
def getSettings(self):
|
||||
"""
|
||||
Returns the settings set in this Wizard.
|
||||
@@ -359,32 +453,42 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
:return: settings dict
|
||||
"""
|
||||
|
||||
path = self.uiIOSImageLineEdit.text()
|
||||
image = self.uiIOSImageLineEdit.text()
|
||||
if Dynamips.instance().settings()["use_local_server"] or self.uiLocalRadioButton.isChecked():
|
||||
server = "local"
|
||||
elif self.uiRemoteRadioButton.isChecked():
|
||||
if self.uiLoadBalanceCheckBox.isChecked():
|
||||
server = next(iter(Servers.instance()))
|
||||
if not server:
|
||||
QtGui.QMessageBox.critical(self, "IOS router", "No remote server available!")
|
||||
return
|
||||
server = "{}:{}".format(server.host, server.port)
|
||||
else:
|
||||
server = self.uiRemoteServersComboBox.currentText()
|
||||
else: # Cloud is selected
|
||||
else: # Cloud is selected
|
||||
server = "cloud"
|
||||
|
||||
platform = self.uiPlatformComboBox.currentText()
|
||||
settings = {
|
||||
"name": self.uiNameLineEdit.text(),
|
||||
"path": path,
|
||||
"image": image,
|
||||
"startup_config": get_default_base_config(self._base_startup_config_template),
|
||||
"ram": self.uiRamSpinBox.value(),
|
||||
"nvram": PLATFORMS_DEFAULT_NVRAM[platform],
|
||||
"idlepc": self.uiIdlepcLineEdit.text(),
|
||||
"image": os.path.basename(path),
|
||||
"platform": self.uiPlatformComboBox.currentText(),
|
||||
"platform": platform,
|
||||
"chassis": self.uiChassisComboBox.currentText(),
|
||||
"server": server,
|
||||
}
|
||||
|
||||
if self.uiEtherSwitchCheckBox.isChecked():
|
||||
settings["startup_config"] = get_default_base_config(self._base_etherswitch_startup_config_template)
|
||||
settings["default_symbol"] = ":/symbols/multilayer_switch.normal.svg"
|
||||
settings["hover_symbol"] = ":/symbols/multilayer_switch.selected.svg"
|
||||
settings["disk0"] = 1 # adds 1MB disk to store vlan.dat
|
||||
settings["category"] = Node.switches
|
||||
|
||||
image_file = os.path.basename(image)
|
||||
if image_file.lower().startswith("c7200p"):
|
||||
settings["npe"] = "npe-g2"
|
||||
|
||||
for slot_id, widget in self._widget_slots.items():
|
||||
if widget.isEnabled():
|
||||
settings["slot{}".format(slot_id)] = widget.currentText()
|
||||
@@ -406,4 +510,4 @@ class IOSRouterWizard(QtGui.QWizard, Ui_IOSRouterWizard):
|
||||
if platform not in WIC_MATRIX:
|
||||
# skip the WIC modules page if the platform doesn't support any.
|
||||
return self.uiNetworkAdaptersWizardPage.nextId() + 1
|
||||
return QtGui.QWizard.nextId(self)
|
||||
return QtGui.QWizard.nextId(self)
|
||||
|
||||
@@ -23,35 +23,36 @@ Asynchronously sends JSON messages to the GNS3 server and receives responses wit
|
||||
import re
|
||||
from gns3.node import Node
|
||||
from gns3.ports.atm_port import ATMPort
|
||||
from .device import Device
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ATMSwitch(Node):
|
||||
class ATMSwitch(Device):
|
||||
|
||||
"""
|
||||
Dynamips ATM switch.
|
||||
|
||||
:param module: parent module for this node
|
||||
:param server: GNS3 server instance
|
||||
:param project: Project instance
|
||||
"""
|
||||
|
||||
def __init__(self, module, server):
|
||||
Node.__init__(self, server)
|
||||
def __init__(self, module, server, project):
|
||||
|
||||
log.info("ATM switch is being created")
|
||||
Device.__init__(self, module, server, project)
|
||||
self.setStatus(Node.started) # this is an always-on node
|
||||
self._atmsw_id = None
|
||||
self._ports = []
|
||||
self._module = module
|
||||
self._settings = {"name": "",
|
||||
"mappings": {}}
|
||||
|
||||
def setup(self, name=None, initial_ports=[], initial_mappings={}):
|
||||
def setup(self, name=None, device_id=None, initial_ports=[], initial_mappings={}):
|
||||
"""
|
||||
Setups this ATM switch.
|
||||
|
||||
:param name: optional name for this switch.
|
||||
:param device_id: device identifier on the server
|
||||
:param initial_ports: ports to be automatically added when creating this ATM switch
|
||||
:param initial_mappings: mappings to be automatically added when creating this ATM switch
|
||||
"""
|
||||
@@ -78,61 +79,11 @@ class ATMSwitch(Node):
|
||||
port.setPacketCaptureSupported(True)
|
||||
self._ports.append(port)
|
||||
|
||||
params = {"name": name}
|
||||
self._server.send_message("dynamips.atmsw.create", params, self._setupCallback)
|
||||
|
||||
def _setupCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for setup.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while setting up {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
return
|
||||
|
||||
self._atmsw_id = result["id"]
|
||||
if not self._atmsw_id:
|
||||
self.error_signal.emit(self.id(), "returned ID from server is null")
|
||||
return
|
||||
|
||||
self._settings["name"] = result["name"]
|
||||
log.info("ATM switch {} has been created".format(self.name()))
|
||||
self.setInitialized(True)
|
||||
self.created_signal.emit(self.id())
|
||||
self._module.addNode(self)
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Deletes this ATM switch.
|
||||
"""
|
||||
|
||||
log.debug("ATM switch {} is being deleted".format(self.name()))
|
||||
# first delete all the links attached to this node
|
||||
self.delete_links_signal.emit()
|
||||
if self._atmsw_id:
|
||||
self._server.send_message("dynamips.atmsw.delete", {"id": self._atmsw_id}, self._deleteCallback)
|
||||
else:
|
||||
self.deleted_signal.emit()
|
||||
self._module.removeNode(self)
|
||||
|
||||
def _deleteCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for delete.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
log.info("ATM switch {} has been deleted".format(self.name()))
|
||||
self.deleted_signal.emit()
|
||||
self._module.removeNode(self)
|
||||
params = {"name": name,
|
||||
"device_type": "atm_switch"}
|
||||
if device_id:
|
||||
params["device_id"] = device_id
|
||||
self.httpPost("/dynamips/devices", self._setupCallback, body=params)
|
||||
|
||||
def update(self, new_settings):
|
||||
"""
|
||||
@@ -141,53 +92,54 @@ class ATMSwitch(Node):
|
||||
:param new_settings: settings dictionary
|
||||
"""
|
||||
|
||||
ports_to_create = []
|
||||
mapping = new_settings["mappings"]
|
||||
|
||||
updated = False
|
||||
for source, destination in mapping.items():
|
||||
source_port = source.split(":")[0]
|
||||
destination_port = destination.split(":")[0]
|
||||
if source_port not in ports_to_create:
|
||||
ports_to_create.append(source_port)
|
||||
if destination_port not in ports_to_create:
|
||||
ports_to_create.append(destination_port)
|
||||
if "mappings" in new_settings:
|
||||
ports_to_create = []
|
||||
mapping = new_settings["mappings"]
|
||||
|
||||
for port in self._ports.copy():
|
||||
if port.isFree():
|
||||
for source, destination in mapping.items():
|
||||
source_port = source.split(":")[0]
|
||||
destination_port = destination.split(":")[0]
|
||||
if source_port not in ports_to_create:
|
||||
ports_to_create.append(source_port)
|
||||
if destination_port not in ports_to_create:
|
||||
ports_to_create.append(destination_port)
|
||||
|
||||
for port in self._ports.copy():
|
||||
if port.isFree():
|
||||
updated = True
|
||||
self._ports.remove(port)
|
||||
else:
|
||||
ports_to_create.remove(port.name())
|
||||
|
||||
for port_name in ports_to_create:
|
||||
port = ATMPort(port_name)
|
||||
port.setPortNumber(int(port_name))
|
||||
port.setStatus(ATMPort.started)
|
||||
port.setPacketCaptureSupported(True)
|
||||
self._ports.append(port)
|
||||
updated = True
|
||||
self._ports.remove(port)
|
||||
else:
|
||||
ports_to_create.remove(port.name())
|
||||
log.debug("port {} has been added".format(port_name))
|
||||
|
||||
for port_name in ports_to_create:
|
||||
port = ATMPort(port_name)
|
||||
port.setPortNumber(int(port_name))
|
||||
port.setStatus(ATMPort.started)
|
||||
port.setPacketCaptureSupported(True)
|
||||
self._ports.append(port)
|
||||
updated = True
|
||||
log.debug("port {} has been added".format(port_name))
|
||||
self._settings["mappings"] = new_settings["mappings"].copy()
|
||||
|
||||
params = {}
|
||||
if "name" in new_settings and new_settings["name"] != self.name():
|
||||
if self.hasAllocatedName(new_settings["name"]):
|
||||
self.error_signal.emit(self.id(), 'Name "{}" is already used by another node'.format(new_settings["name"]))
|
||||
return
|
||||
params = {"id": self._atmsw_id,
|
||||
"name": new_settings["name"]}
|
||||
params["name"] = new_settings["name"]
|
||||
updated = True
|
||||
|
||||
self._settings["mappings"] = new_settings["mappings"].copy()
|
||||
if updated:
|
||||
if params:
|
||||
log.debug("{} is being updated: {}".format(self.name(), params))
|
||||
self._server.send_message("dynamips.atmsw.update", params, self._updateCallback)
|
||||
self.httpPut("/dynamips/devices/{device_id}".format(device_id=self._device_id), self._updateCallback, body=params)
|
||||
else:
|
||||
log.info("ATM switch {} has been updated".format(self.name()))
|
||||
self.updated_signal.emit()
|
||||
|
||||
def _updateCallback(self, result, error=False):
|
||||
def _updateCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for update.
|
||||
|
||||
@@ -197,7 +149,7 @@ class ATMSwitch(Node):
|
||||
|
||||
if error:
|
||||
log.error("error while updating {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
self.server_error_signal.emit(self.id(), result["message"])
|
||||
else:
|
||||
if "name" in result:
|
||||
self._settings["name"] = result["name"]
|
||||
@@ -205,33 +157,6 @@ class ATMSwitch(Node):
|
||||
log.info("{} has been updated".format(self.name()))
|
||||
self.updated_signal.emit()
|
||||
|
||||
def allocateUDPPort(self, port_id):
|
||||
"""
|
||||
Requests an UDP port allocation.
|
||||
|
||||
:param port_id: port identifier
|
||||
"""
|
||||
|
||||
log.debug("{} is requesting an UDP port allocation".format(self.name()))
|
||||
self._server.send_message("dynamips.atmsw.allocate_udp_port", {"id": self._atmsw_id, "port_id": port_id}, self._allocateUDPPortCallback)
|
||||
|
||||
def _allocateUDPPortCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for allocateUDPPort.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while allocating an UDP port for {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
else:
|
||||
port_id = result["port_id"]
|
||||
lport = result["lport"]
|
||||
log.debug("{} has allocated UDP port {}".format(self.name(), lport))
|
||||
self.allocate_udp_nio_signal.emit(self.id(), port_id, lport)
|
||||
|
||||
def addNIO(self, port, nio):
|
||||
"""
|
||||
Adds a new NIO on the specified port for this ATM switch.
|
||||
@@ -240,11 +165,7 @@ class ATMSwitch(Node):
|
||||
:param nio: NIO instance
|
||||
"""
|
||||
|
||||
params = {"id": self._atmsw_id,
|
||||
"port": port.portNumber(),
|
||||
"port_id": port.id()}
|
||||
|
||||
params["nio"] = self.getNIOInfo(nio)
|
||||
params = {"nio": self.getNIOInfo(nio)}
|
||||
params["mappings"] = {}
|
||||
for source, destination in self._settings["mappings"].items():
|
||||
source_port = source.split(":")[0]
|
||||
@@ -255,124 +176,13 @@ class ATMSwitch(Node):
|
||||
params["mappings"][destination] = source
|
||||
|
||||
log.debug("{} is adding an {}: {}".format(self.name(), nio, params))
|
||||
self._server.send_message("dynamips.atmsw.add_nio", params, self._addNIOCallback)
|
||||
|
||||
def _addNIOCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for addNIO.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while adding an UDP NIO for {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
self.nio_cancel_signal.emit(self.id())
|
||||
else:
|
||||
log.debug("{} has added a new NIO: {}".format(self.name(), result))
|
||||
self.nio_signal.emit(self.id(), result["port_id"])
|
||||
|
||||
def deleteNIO(self, port):
|
||||
"""
|
||||
Deletes an NIO from the specified port on this switch.
|
||||
|
||||
:param port: Port instance
|
||||
"""
|
||||
|
||||
params = {"id": self._atmsw_id,
|
||||
"port": port.portNumber()}
|
||||
|
||||
log.debug("{} is deleting an NIO: {}".format(self.name(), params))
|
||||
self._server.send_message("dynamips.atmsw.delete_nio", params, self.deleteNIOCallback)
|
||||
|
||||
def deleteNIOCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for deleteNIO.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while deleting NIO {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
return
|
||||
|
||||
log.debug("{} has deleted a NIO: {}".format(self.name(), result))
|
||||
|
||||
def startPacketCapture(self, port, capture_file_name, data_link_type):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
|
||||
:param port: Port instance
|
||||
:param capture_file_name: PCAP capture file path
|
||||
:param data_link_type: PCAP data link type
|
||||
"""
|
||||
|
||||
params = {"id": self._atmsw_id,
|
||||
"port_id": port.id(),
|
||||
"port": port.portNumber(),
|
||||
"capture_file_name": capture_file_name,
|
||||
"data_link_type": data_link_type}
|
||||
|
||||
log.debug("{} is starting a packet capture on {}: {}".format(self.name(), port.name(), params))
|
||||
self._server.send_message("dynamips.atmsw.start_capture", params, self._startPacketCaptureCallback)
|
||||
|
||||
def _startPacketCaptureCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for starting a packet capture.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while starting capture {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
else:
|
||||
for port in self._ports:
|
||||
if port.id() == result["port_id"]:
|
||||
log.info("{} has successfully started capturing packets on {}".format(self.name(), port.name()))
|
||||
try:
|
||||
port.startPacketCapture(result["capture_file_path"])
|
||||
except OSError as e:
|
||||
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
|
||||
self.updated_signal.emit()
|
||||
break
|
||||
|
||||
def stopPacketCapture(self, port):
|
||||
"""
|
||||
Stops a packet capture.
|
||||
|
||||
:param port: Port instance
|
||||
"""
|
||||
|
||||
params = {"id": self._atmsw_id,
|
||||
"port_id": port.id(),
|
||||
"port": port.portNumber()}
|
||||
|
||||
log.debug("{} is stopping a packet capture on {}: {}".format(self.name(), port.name(), params))
|
||||
self._server.send_message("dynamips.atmsw.stop_capture", params, self._stopPacketCaptureCallback)
|
||||
|
||||
def _stopPacketCaptureCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for stopping a packet capture.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while stopping capture {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
else:
|
||||
for port in self._ports:
|
||||
if port.id() == result["port_id"]:
|
||||
log.info("{} has successfully stopped capturing packets on {}".format(self.name(), port.name()))
|
||||
port.stopPacketCapture()
|
||||
self.updated_signal.emit()
|
||||
break
|
||||
self.httpPost("/{prefix}/devices/{device_id}/ports/{port}/nio".format(
|
||||
port=port.portNumber(),
|
||||
prefix=self.URL_PREFIX,
|
||||
device_id=self._device_id),
|
||||
self._addNIOCallback,
|
||||
context={"port_id": port.id()},
|
||||
body=params)
|
||||
|
||||
def info(self):
|
||||
"""
|
||||
@@ -382,12 +192,13 @@ class ATMSwitch(Node):
|
||||
"""
|
||||
|
||||
info = """ATM switch {name} is always-on
|
||||
Node ID is {id}, server's ATM switch ID is {atmsw_id}
|
||||
Local node ID is {id}
|
||||
Server's Device ID is {device_id}
|
||||
Hardware is Dynamips emulated simple ATM switch
|
||||
Switch's server runs on {host}:{port}
|
||||
""".format(name=self.name(),
|
||||
id=self.id(),
|
||||
atmsw_id=self._atmsw_id,
|
||||
device_id=self._device_id,
|
||||
host=self._server.host,
|
||||
port=self._server.port)
|
||||
|
||||
@@ -449,11 +260,11 @@ class ATMSwitch(Node):
|
||||
"""
|
||||
|
||||
atmsw = {"id": self.id(),
|
||||
"device_id": self._device_id,
|
||||
"type": self.__class__.__name__,
|
||||
"description": str(self),
|
||||
"properties": {"name": self.name()},
|
||||
"server_id": self._server.id(),
|
||||
}
|
||||
"server_id": self._server.id()}
|
||||
|
||||
if self._settings["mappings"]:
|
||||
atmsw["properties"]["mappings"] = self._settings["mappings"]
|
||||
@@ -477,6 +288,10 @@ class ATMSwitch(Node):
|
||||
settings = node_info["properties"]
|
||||
name = settings.pop("name")
|
||||
|
||||
# pre-1.3 projects have no device id, set to 1 to have
|
||||
# a proper project conversion on the server side
|
||||
device_id = node_info.get("device_id", 1)
|
||||
|
||||
mappings = {}
|
||||
if "mappings" in settings:
|
||||
mappings = settings["mappings"]
|
||||
@@ -487,7 +302,7 @@ class ATMSwitch(Node):
|
||||
|
||||
log.info("ATM switch {} is loading".format(name))
|
||||
self.setName(name)
|
||||
self.setup(name, ports, mappings)
|
||||
self.setup(name, device_id, ports, mappings)
|
||||
|
||||
def name(self):
|
||||
"""
|
||||
|
||||
@@ -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()))
|
||||
elif port.name() in ports_to_create:
|
||||
ports_to_create.remove(port.name())
|
||||
|
||||
for port_name in ports_to_create:
|
||||
port = EthernetPort(port_name)
|
||||
port.setPortNumber(int(port_name))
|
||||
port.setStatus(EthernetPort.started)
|
||||
port.setPacketCaptureSupported(True)
|
||||
self._ports.append(port)
|
||||
updated = True
|
||||
log.debug("port {} has been removed".format(port.name()))
|
||||
else:
|
||||
ports_to_create.remove(port.name())
|
||||
log.debug("port {} has been added".format(port_name))
|
||||
|
||||
for port_name in ports_to_create:
|
||||
port = EthernetPort(port_name)
|
||||
port.setPortNumber(int(port_name))
|
||||
port.setStatus(EthernetPort.started)
|
||||
port.setPacketCaptureSupported(True)
|
||||
self._ports.append(port)
|
||||
updated = True
|
||||
log.debug("port {} has been added".format(port_name))
|
||||
self._settings["ports"] = new_settings["ports"].copy()
|
||||
|
||||
params = {}
|
||||
if "name" in new_settings and new_settings["name"] != self.name():
|
||||
if self.hasAllocatedName(new_settings["name"]):
|
||||
self.error_signal.emit(self.id(), 'Name "{}" is already used by another node'.format(new_settings["name"]))
|
||||
return
|
||||
params = {"id": self._ethhub_id,
|
||||
"name": new_settings["name"]}
|
||||
params["name"] = new_settings["name"]
|
||||
updated = True
|
||||
|
||||
self._settings["ports"] = new_settings["ports"].copy()
|
||||
if updated:
|
||||
if params:
|
||||
log.debug("{} is being updated: {}".format(self.name(), params))
|
||||
self._server.send_message("dynamips.ethhub.update", params, self._updateCallback)
|
||||
self.httpPut("/dynamips/devices/{device_id}".format(device_id=self._device_id), self._updateCallback, body=params)
|
||||
else:
|
||||
log.info("{} has been updated".format(self.name()))
|
||||
self.updated_signal.emit()
|
||||
|
||||
def _updateCallback(self, result, error=False):
|
||||
def _updateCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for update.
|
||||
|
||||
@@ -196,7 +148,7 @@ class EthernetHub(Node):
|
||||
|
||||
if error:
|
||||
log.error("error while updating {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
self.server_error_signal.emit(self.id(), result["message"])
|
||||
else:
|
||||
if "name" in result:
|
||||
self._settings["name"] = result["name"]
|
||||
@@ -204,164 +156,24 @@ class EthernetHub(Node):
|
||||
log.info("{} has been updated".format(self.name()))
|
||||
self.updated_signal.emit()
|
||||
|
||||
def allocateUDPPort(self, port_id):
|
||||
"""
|
||||
Requests an UDP port allocation.
|
||||
|
||||
:param port_id: port identifier
|
||||
"""
|
||||
|
||||
log.debug("{} is requesting an UDP port allocation".format(self.name()))
|
||||
self._server.send_message("dynamips.ethhub.allocate_udp_port", {"id": self._ethhub_id, "port_id": port_id}, self._allocateUDPPortCallback)
|
||||
|
||||
def _allocateUDPPortCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for allocateUDPPort.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while allocating an UDP port for {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
else:
|
||||
port_id = result["port_id"]
|
||||
lport = result["lport"]
|
||||
log.debug("{} has allocated UDP port {}".format(self.name(), port_id, lport))
|
||||
self.allocate_udp_nio_signal.emit(self.id(), port_id, lport)
|
||||
|
||||
def addNIO(self, port, nio):
|
||||
"""
|
||||
Adds a new NIO on the specified port for this hub.
|
||||
Adds a new NIO on the specified port for this Ethernet hub.
|
||||
|
||||
:param port: Port instance
|
||||
:param nio: NIO instance
|
||||
"""
|
||||
|
||||
params = {"id": self._ethhub_id,
|
||||
"port": port.portNumber(),
|
||||
"port_id": port.id()}
|
||||
|
||||
params = {}
|
||||
params["nio"] = self.getNIOInfo(nio)
|
||||
log.debug("{} is adding an {}: {}".format(self.name(), nio, params))
|
||||
self._server.send_message("dynamips.ethhub.add_nio", params, self._addNIOCallback)
|
||||
|
||||
def _addNIOCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for addNIO.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while adding an UDP NIO for {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
self.nio_cancel_signal.emit(self.id())
|
||||
else:
|
||||
self.nio_signal.emit(self.id(), result["port_id"])
|
||||
|
||||
def deleteNIO(self, port):
|
||||
"""
|
||||
Deletes an NIO from the specified port on this hub.
|
||||
|
||||
:param port: Port instance
|
||||
"""
|
||||
|
||||
params = {"id": self._ethhub_id,
|
||||
"port": port.portNumber()}
|
||||
|
||||
log.debug("{} is deleting an NIO: {}".format(self.name(), params))
|
||||
self._server.send_message("dynamips.ethhub.delete_nio", params, self._deleteNIOCallback)
|
||||
|
||||
def _deleteNIOCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for deleteNIO.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while deleting NIO {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
return
|
||||
|
||||
log.debug("{} has deleted a NIO: {}".format(self.name(), result))
|
||||
|
||||
def startPacketCapture(self, port, capture_file_name, data_link_type):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
|
||||
:param port: Port instance
|
||||
:param capture_file_name: PCAP capture file path
|
||||
:param data_link_type: PCAP data link type
|
||||
"""
|
||||
|
||||
params = {"id": self._ethhub_id,
|
||||
"port_id": port.id(),
|
||||
"port": port.portNumber(),
|
||||
"capture_file_name": capture_file_name,
|
||||
"data_link_type": data_link_type}
|
||||
|
||||
log.debug("{} is starting a packet capture on {}: {}".format(self.name(), port.name(), params))
|
||||
self._server.send_message("dynamips.ethhub.start_capture", params, self._startPacketCaptureCallback)
|
||||
|
||||
def _startPacketCaptureCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for starting a packet capture.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while starting capture {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
else:
|
||||
for port in self._ports:
|
||||
if port.id() == result["port_id"]:
|
||||
log.info("{} has successfully started capturing packets on {}".format(self.name(), port.name()))
|
||||
try:
|
||||
port.startPacketCapture(result["capture_file_path"])
|
||||
except OSError as e:
|
||||
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
|
||||
self.updated_signal.emit()
|
||||
break
|
||||
|
||||
def stopPacketCapture(self, port):
|
||||
"""
|
||||
Stops a packet capture.
|
||||
|
||||
:param port: Port instance
|
||||
"""
|
||||
|
||||
params = {"id": self._ethhub_id,
|
||||
"port_id": port.id(),
|
||||
"port": port.portNumber()}
|
||||
|
||||
log.debug("{} is stopping a packet capture on {}: {}".format(self.name(), port.name(), params))
|
||||
self._server.send_message("dynamips.ethhub.stop_capture", params, self._stopPacketCaptureCallback)
|
||||
|
||||
def _stopPacketCaptureCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for stopping a packet capture.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while stopping capture {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
else:
|
||||
for port in self._ports:
|
||||
if port.id() == result["port_id"]:
|
||||
log.info("{} has successfully stopped capturing packets on {}".format(self.name(), port.name()))
|
||||
port.stopPacketCapture()
|
||||
self.updated_signal.emit()
|
||||
break
|
||||
self.httpPost("/{prefix}/devices/{device_id}/ports/{port}/nio".format(
|
||||
port=port.portNumber(),
|
||||
prefix=self.URL_PREFIX,
|
||||
device_id=self._device_id),
|
||||
self._addNIOCallback,
|
||||
context={"port_id": port.id()},
|
||||
body=params)
|
||||
|
||||
def info(self):
|
||||
"""
|
||||
@@ -371,12 +183,13 @@ class EthernetHub(Node):
|
||||
"""
|
||||
|
||||
info = """Ethernet hub {name} is always-on
|
||||
Node ID is {id}, server's Ethernet hub ID is {ethhub_id}
|
||||
Local node ID is {id}
|
||||
Server's Device ID is {device_id}
|
||||
Hardware is Dynamips emulated simple Ethernet hub
|
||||
Hub's server runs on {host}:{port}
|
||||
""".format(name=self.name(),
|
||||
id=self.id(),
|
||||
ethhub_id=self._ethhub_id,
|
||||
device_id=self._device_id,
|
||||
host=self._server.host,
|
||||
port=self._server.port)
|
||||
|
||||
@@ -399,11 +212,11 @@ class EthernetHub(Node):
|
||||
"""
|
||||
|
||||
hub = {"id": self.id(),
|
||||
"device_id": self._device_id,
|
||||
"type": self.__class__.__name__,
|
||||
"description": str(self),
|
||||
"properties": {"name": self.name()},
|
||||
"server_id": self._server.id(),
|
||||
}
|
||||
"server_id": self._server.id()}
|
||||
|
||||
# add the ports
|
||||
if self._ports:
|
||||
@@ -424,6 +237,10 @@ class EthernetHub(Node):
|
||||
settings = node_info["properties"]
|
||||
name = settings.pop("name")
|
||||
|
||||
# pre-1.3 projects have no device id, set to 1 to have
|
||||
# a proper project conversion on the server side
|
||||
device_id = node_info.get("device_id", 1)
|
||||
|
||||
# create the ports with the correct port numbers and IDs
|
||||
ports = []
|
||||
if "ports" in node_info:
|
||||
@@ -431,7 +248,7 @@ class EthernetHub(Node):
|
||||
|
||||
log.info("Ethernet hub {} is loading".format(name))
|
||||
self.setName(name)
|
||||
self.setup(name, ports)
|
||||
self.setup(name, device_id, ports)
|
||||
|
||||
def name(self):
|
||||
"""
|
||||
|
||||
@@ -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,63 +17,36 @@
|
||||
|
||||
"""
|
||||
EtherSwitch router implementation (based on Dynamips c3745).
|
||||
This is legacy code, kept only to support topologies made with GNS3 < 1.2.2
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import pkg_resources
|
||||
from .router import Router
|
||||
from gns3.node import Node
|
||||
|
||||
|
||||
class EtherSwitchRouter(Router):
|
||||
|
||||
"""
|
||||
EtherSwitch router.
|
||||
|
||||
:param module: parent module for this node
|
||||
:param server: GNS3 server instance
|
||||
:param project: Project instance
|
||||
"""
|
||||
|
||||
def __init__(self, module, server):
|
||||
Router.__init__(self, module, server, platform="c3725")
|
||||
def __init__(self, module, server, project):
|
||||
Router.__init__(self, module, server, project, platform="c3725")
|
||||
|
||||
self._platform_settings = {"ram": 128,
|
||||
"nvram": 304,
|
||||
"disk0": 16,
|
||||
"disk1": 0,
|
||||
"iomem": 5,
|
||||
"clock_divisor": 8,
|
||||
"slot0": "GT96100-FE"}
|
||||
self._etherswitch_settings = {"ram": 128,
|
||||
"nvram": 304,
|
||||
"disk0": 16,
|
||||
"disk1": 0,
|
||||
"iomem": 5,
|
||||
"clock_divisor": 8,
|
||||
"slot0": "GT96100-FE"}
|
||||
|
||||
# merge platform settings with the generic ones
|
||||
self._settings.update(self._platform_settings)
|
||||
|
||||
# save the default settings
|
||||
self._defaults = self._settings.copy()
|
||||
|
||||
def setup(self, image, ram, name=None, router_id=None, initial_settings={}):
|
||||
"""
|
||||
Setups this router.
|
||||
|
||||
:param image: IOS image path
|
||||
:param ram: amount of RAM
|
||||
:param name: optional name for this router
|
||||
:param initial_settings: other additional and not mandatory settings
|
||||
"""
|
||||
# let's create a unique name if none has been chosen
|
||||
if not name:
|
||||
name = self.allocateName("ESW")
|
||||
|
||||
resource_name = "configs/ios_etherswitch_startup-config.txt"
|
||||
if hasattr(sys, "frozen") and os.path.isfile(resource_name):
|
||||
startup_config = os.path.normpath(resource_name)
|
||||
elif pkg_resources.resource_exists("gns3", resource_name):
|
||||
ios_etherswitch_config_path = pkg_resources.resource_filename("gns3", resource_name)
|
||||
startup_config = os.path.normpath(ios_etherswitch_config_path)
|
||||
|
||||
initial_settings.update({"slot1": "NM-16ESW", # add the EtherSwitch module
|
||||
"startup_config": startup_config}) # add the EtherSwitch startup-config
|
||||
Router.setup(self, image, ram, name, router_id, initial_settings)
|
||||
self._settings.update(self._etherswitch_settings)
|
||||
|
||||
@staticmethod
|
||||
def defaultSymbol():
|
||||
|
||||
@@ -22,35 +22,36 @@ Asynchronously sends JSON messages to the GNS3 server and receives responses wit
|
||||
|
||||
from gns3.node import Node
|
||||
from gns3.ports.frame_relay_port import FrameRelayPort
|
||||
from .device import Device
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FrameRelaySwitch(Node):
|
||||
class FrameRelaySwitch(Device):
|
||||
|
||||
"""
|
||||
Dynamips Frame-Relay switch.
|
||||
|
||||
:param module: parent module for this node
|
||||
:param server: GNS3 server instance
|
||||
:param project: Project instance
|
||||
"""
|
||||
|
||||
def __init__(self, module, server):
|
||||
Node.__init__(self, server)
|
||||
def __init__(self, module, server, project):
|
||||
|
||||
log.info("Frame-Relay switch is being created")
|
||||
Device.__init__(self, module, server, project)
|
||||
self.setStatus(Node.started) # this is an always-on node
|
||||
self._frsw_id = None
|
||||
self._ports = []
|
||||
self._module = module
|
||||
self._settings = {"name": "",
|
||||
"mappings": {}}
|
||||
|
||||
def setup(self, name=None, initial_ports=[], initial_mappings={}):
|
||||
def setup(self, name=None, device_id=None, initial_ports=[], initial_mappings={}):
|
||||
"""
|
||||
Setups this Frame Relay switch.
|
||||
|
||||
:param name: name for this switch.
|
||||
:param device_id: device identifier on the server
|
||||
:param initial_ports: ports to be automatically added when creating this Frame relay switch
|
||||
:param initial_mappings: mappings to be automatically added when creating this Frame relay switch
|
||||
"""
|
||||
@@ -63,6 +64,7 @@ class FrameRelaySwitch(Node):
|
||||
self.error_signal.emit(self.id(), "could not allocate a name for this Frame Relay switch")
|
||||
return
|
||||
|
||||
self._settings["name"] = name
|
||||
if initial_mappings:
|
||||
# add initial mappings
|
||||
self._settings["mappings"] = initial_mappings.copy()
|
||||
@@ -77,61 +79,11 @@ class FrameRelaySwitch(Node):
|
||||
port.setPacketCaptureSupported(True)
|
||||
self._ports.append(port)
|
||||
|
||||
params = {"name": name}
|
||||
self._server.send_message("dynamips.frsw.create", params, self._setupCallback)
|
||||
|
||||
def _setupCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for setup.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while setting up {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
return
|
||||
|
||||
self._frsw_id = result["id"]
|
||||
if not self._frsw_id:
|
||||
self.error_signal.emit(self.id(), "returned ID from server is null")
|
||||
return
|
||||
|
||||
self._settings["name"] = result["name"]
|
||||
log.info("Frame Relay switch {} has been created".format(self.name()))
|
||||
self.setInitialized(True)
|
||||
self.created_signal.emit(self.id())
|
||||
self._module.addNode(self)
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Deletes this Frame Relay switch.
|
||||
"""
|
||||
|
||||
log.debug("Frame Relay switch {} is being deleted".format(self.name()))
|
||||
# first delete all the links attached to this node
|
||||
self.delete_links_signal.emit()
|
||||
if self._frsw_id:
|
||||
self._server.send_message("dynamips.frsw.delete", {"id": self._frsw_id}, self._deleteCallback)
|
||||
else:
|
||||
self.deleted_signal.emit()
|
||||
self._module.removeNode(self)
|
||||
|
||||
def _deleteCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for the delete method.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while deleting {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
log.info("{} has been deleted".format(self.name()))
|
||||
self.deleted_signal.emit()
|
||||
self._module.removeNode(self)
|
||||
params = {"name": name,
|
||||
"device_type": "frame_relay_switch"}
|
||||
if device_id:
|
||||
params["device_id"] = device_id
|
||||
self.httpPost("/dynamips/devices", self._setupCallback, body=params)
|
||||
|
||||
def update(self, new_settings):
|
||||
"""
|
||||
@@ -140,54 +92,55 @@ class FrameRelaySwitch(Node):
|
||||
:param new_settings: settings dictionary
|
||||
"""
|
||||
|
||||
ports_to_create = []
|
||||
mapping = new_settings["mappings"]
|
||||
|
||||
updated = False
|
||||
for source, destination in mapping.items():
|
||||
source_port = source.split(":")[0]
|
||||
destination_port = destination.split(":")[0]
|
||||
if source_port not in ports_to_create:
|
||||
ports_to_create.append(source_port)
|
||||
if destination_port not in ports_to_create:
|
||||
ports_to_create.append(destination_port)
|
||||
if "mappings" in new_settings:
|
||||
ports_to_create = []
|
||||
mapping = new_settings["mappings"]
|
||||
|
||||
for port in self._ports.copy():
|
||||
if port.isFree():
|
||||
self._ports.remove(port)
|
||||
for source, destination in mapping.items():
|
||||
source_port = source.split(":")[0]
|
||||
destination_port = destination.split(":")[0]
|
||||
if source_port not in ports_to_create:
|
||||
ports_to_create.append(source_port)
|
||||
if destination_port not in ports_to_create:
|
||||
ports_to_create.append(destination_port)
|
||||
|
||||
for port in self._ports.copy():
|
||||
if port.isFree():
|
||||
self._ports.remove(port)
|
||||
updated = True
|
||||
log.debug("port {} has been removed".format(port.name()))
|
||||
else:
|
||||
ports_to_create.remove(port.name())
|
||||
|
||||
for port_name in ports_to_create:
|
||||
port = FrameRelayPort(port_name)
|
||||
port.setPortNumber(int(port_name))
|
||||
port.setStatus(FrameRelayPort.started)
|
||||
port.setPacketCaptureSupported(True)
|
||||
self._ports.append(port)
|
||||
updated = True
|
||||
log.debug("port {} has been removed".format(port.name()))
|
||||
else:
|
||||
ports_to_create.remove(port.name())
|
||||
log.debug("port {} has been added".format(port_name))
|
||||
|
||||
for port_name in ports_to_create:
|
||||
port = FrameRelayPort(port_name)
|
||||
port.setPortNumber(int(port_name))
|
||||
port.setStatus(FrameRelayPort.started)
|
||||
port.setPacketCaptureSupported(True)
|
||||
self._ports.append(port)
|
||||
updated = True
|
||||
log.debug("port {} has been added".format(port_name))
|
||||
self._settings["mappings"] = new_settings["mappings"].copy()
|
||||
|
||||
params = {}
|
||||
if "name" in new_settings and new_settings["name"] != self.name():
|
||||
if self.hasAllocatedName(new_settings["name"]):
|
||||
self.error_signal.emit(self.id(), 'Name "{}" is already used by another node'.format(new_settings["name"]))
|
||||
return
|
||||
params = {"id": self._frsw_id,
|
||||
"name": new_settings["name"]}
|
||||
params["name"] = new_settings["name"]
|
||||
updated = True
|
||||
|
||||
self._settings["mappings"] = new_settings["mappings"].copy()
|
||||
if updated:
|
||||
if params:
|
||||
log.debug("{} is being updated: {}".format(self.name(), params))
|
||||
self._server.send_message("dynamips.frsw.update", params, self._updateCallback)
|
||||
self.httpPut("/dynamips/devices/{device_id}".format(device_id=self._device_id), self._updateCallback, body=params)
|
||||
else:
|
||||
log.info("{} has been updated".format(self.name()))
|
||||
self.updated_signal.emit()
|
||||
|
||||
def _updateCallback(self, result, error=False):
|
||||
def _updateCallback(self, result, error=False, **kwargs):
|
||||
"""
|
||||
Callback for update.
|
||||
|
||||
@@ -197,7 +150,7 @@ class FrameRelaySwitch(Node):
|
||||
|
||||
if error:
|
||||
log.error("error while updating {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
self.server_error_signal.emit(self.id(), result["message"])
|
||||
else:
|
||||
if "name" in result:
|
||||
self._settings["name"] = result["name"]
|
||||
@@ -205,31 +158,6 @@ class FrameRelaySwitch(Node):
|
||||
log.info("{} has been updated".format(self.name()))
|
||||
self.updated_signal.emit()
|
||||
|
||||
def allocateUDPPort(self, port_id):
|
||||
"""
|
||||
Requests an UDP port allocation.
|
||||
"""
|
||||
|
||||
log.debug("{} is requesting an UDP port allocation".format(self.name()))
|
||||
self._server.send_message("dynamips.frsw.allocate_udp_port", {"id": self._frsw_id, "port_id": port_id}, self._allocateUDPPortCallback)
|
||||
|
||||
def _allocateUDPPortCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for allocateUDPPort.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while allocating an UDP port for {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
else:
|
||||
port_id = result["port_id"]
|
||||
lport = result["lport"]
|
||||
log.debug("{} has allocated UDP port {}".format(self.name(), lport))
|
||||
self.allocate_udp_nio_signal.emit(self.id(), port_id, lport)
|
||||
|
||||
def addNIO(self, port, nio):
|
||||
"""
|
||||
Adds a new NIO on the specified port for this Frame Relay switch.
|
||||
@@ -238,11 +166,7 @@ class FrameRelaySwitch(Node):
|
||||
:param nio: NIO instance
|
||||
"""
|
||||
|
||||
params = {"id": self._frsw_id,
|
||||
"port": port.portNumber(),
|
||||
"port_id": port.id()}
|
||||
|
||||
params["nio"] = self.getNIOInfo(nio)
|
||||
params = {"nio": self.getNIOInfo(nio)}
|
||||
params["mappings"] = {}
|
||||
for source, destination in self._settings["mappings"].items():
|
||||
source_port = source.split(":")[0]
|
||||
@@ -254,124 +178,13 @@ class FrameRelaySwitch(Node):
|
||||
log.debug("{} is adding an UDP NIO: {}".format(self.name(), params))
|
||||
|
||||
log.debug("{} is adding an {}: {}".format(self.name(), nio, params))
|
||||
self._server.send_message("dynamips.frsw.add_nio", params, self._addNIOCallback)
|
||||
|
||||
def _addNIOCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for addNIO.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while adding an UDP NIO for {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
self.nio_cancel_signal.emit(self.id())
|
||||
else:
|
||||
log.debug("{} has added a new NIO: {}".format(self.name(), result))
|
||||
self.nio_signal.emit(self.id(), result["port_id"])
|
||||
|
||||
def deleteNIO(self, port):
|
||||
"""
|
||||
Deletes an NIO from the specified port on this switch.
|
||||
|
||||
:param port: Port instance
|
||||
"""
|
||||
|
||||
params = {"id": self._frsw_id,
|
||||
"port": port.portNumber()}
|
||||
|
||||
log.debug("{} is deleting an NIO: {}".format(self.name(), params))
|
||||
self._server.send_message("dynamips.frsw.delete_nio", params, self._deleteNIOCallback)
|
||||
|
||||
def _deleteNIOCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for deleteNIO.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while deleting NIO {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
return
|
||||
|
||||
log.debug("{} has deleted a NIO: {}".format(self.name(), result))
|
||||
|
||||
def startPacketCapture(self, port, capture_file_name, data_link_type):
|
||||
"""
|
||||
Starts a packet capture.
|
||||
|
||||
:param port: Port instance
|
||||
:param capture_file_name: PCAP capture file path
|
||||
:param data_link_type: PCAP data link type
|
||||
"""
|
||||
|
||||
params = {"id": self._frsw_id,
|
||||
"port_id": port.id(),
|
||||
"port": port.portNumber(),
|
||||
"capture_file_name": capture_file_name,
|
||||
"data_link_type": data_link_type}
|
||||
|
||||
log.debug("{} is starting a packet capture on {}: {}".format(self.name(), port.name(), params))
|
||||
self._server.send_message("dynamips.frsw.start_capture", params, self._startPacketCaptureCallback)
|
||||
|
||||
def _startPacketCaptureCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for starting a packet capture.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while starting capture {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
else:
|
||||
for port in self._ports:
|
||||
if port.id() == result["port_id"]:
|
||||
log.info("{} has successfully started capturing packets on {}".format(self.name(), port.name()))
|
||||
try:
|
||||
port.startPacketCapture(result["capture_file_path"])
|
||||
except OSError as e:
|
||||
self.error_signal.emit(self.id(), "could not start the packet capture reader: {}: {}".format(e, e.filename))
|
||||
self.updated_signal.emit()
|
||||
break
|
||||
|
||||
def stopPacketCapture(self, port):
|
||||
"""
|
||||
Stops a packet capture.
|
||||
|
||||
:param port: Port instance
|
||||
"""
|
||||
|
||||
params = {"id": self._frsw_id,
|
||||
"port_id": port.id(),
|
||||
"port": port.portNumber()}
|
||||
|
||||
log.debug("{} is stopping a packet capture on {}: {}".format(self.name(), port.name(), params))
|
||||
self._server.send_message("dynamips.frsw.stop_capture", params, self._stopPacketCaptureCallback)
|
||||
|
||||
def _stopPacketCaptureCallback(self, result, error=False):
|
||||
"""
|
||||
Callback for stopping a packet capture.
|
||||
|
||||
:param result: server response
|
||||
:param error: indicates an error (boolean)
|
||||
"""
|
||||
|
||||
if error:
|
||||
log.error("error while stopping capture {}: {}".format(self.name(), result["message"]))
|
||||
self.server_error_signal.emit(self.id(), result["code"], result["message"])
|
||||
else:
|
||||
for port in self._ports:
|
||||
if port.id() == result["port_id"]:
|
||||
log.info("{} has successfully stopped capturing packets on {}".format(self.name(), port.name()))
|
||||
port.stopPacketCapture()
|
||||
self.updated_signal.emit()
|
||||
break
|
||||
self.httpPost("/{prefix}/devices/{device_id}/ports/{port}/nio".format(
|
||||
port=port.portNumber(),
|
||||
prefix=self.URL_PREFIX,
|
||||
device_id=self._device_id),
|
||||
self._addNIOCallback,
|
||||
context={"port_id": port.id()},
|
||||
body=params)
|
||||
|
||||
def info(self):
|
||||
"""
|
||||
@@ -381,12 +194,13 @@ class FrameRelaySwitch(Node):
|
||||
"""
|
||||
|
||||
info = """Frame relay switch {name} is always-on
|
||||
Node ID is {id}, server's frame relay switch ID is {frsw_id}
|
||||
Local node ID is {id}
|
||||
Server's Device ID is {device_id}
|
||||
Hardware is Dynamips emulated simple Frame relay switch
|
||||
Switch's server runs on {host}:{port}
|
||||
""".format(name=self.name(),
|
||||
id=self.id(),
|
||||
frsw_id=self._frsw_id,
|
||||
device_id=self._device_id,
|
||||
host=self._server.host,
|
||||
port=self._server.port)
|
||||
|
||||
@@ -396,7 +210,7 @@ class FrameRelaySwitch(Node):
|
||||
port_info += " Port {} is empty\n".format(port.name())
|
||||
else:
|
||||
port_info += " Port {name} {description}\n".format(name=port.name(),
|
||||
description=port.description())
|
||||
description=port.description())
|
||||
|
||||
for source, destination in self._settings["mappings"].items():
|
||||
source_port, source_dlci = source.split(":")
|
||||
@@ -427,6 +241,7 @@ class FrameRelaySwitch(Node):
|
||||
"""
|
||||
|
||||
frsw = {"id": self.id(),
|
||||
"device_id": self._device_id,
|
||||
"type": self.__class__.__name__,
|
||||
"description": str(self),
|
||||
"properties": {"name": self.name()},
|
||||
@@ -455,6 +270,10 @@ class FrameRelaySwitch(Node):
|
||||
settings = node_info["properties"]
|
||||
name = settings.pop("name")
|
||||
|
||||
# pre-1.3 projects have no device id, set to 1 to have
|
||||
# a proper project conversion on the server side
|
||||
device_id = node_info.get("device_id", 1)
|
||||
|
||||
mappings = {}
|
||||
if "mappings" in settings:
|
||||
mappings = settings["mappings"]
|
||||
@@ -465,7 +284,7 @@ class FrameRelaySwitch(Node):
|
||||
|
||||
log.info("Frame-Relay switch {} is loading".format(name))
|
||||
self.setName(name)
|
||||
self.setup(name, ports, mappings)
|
||||
self.setup(name, device_id, ports, mappings)
|
||||
|
||||
def name(self):
|
||||
"""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
@@ -323,9 +346,6 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
|
||||
# load the system ID (processor board ID in IOS) setting
|
||||
self.uiSystemIdLineEdit.setText(settings["system_id"])
|
||||
|
||||
# load the configuration register setting
|
||||
self.uiConfregLineEdit.setText(settings["confreg"])
|
||||
|
||||
if "exec_area" in settings:
|
||||
# load the exec area setting
|
||||
self.uiExecAreaSpinBox.setValue(settings["exec_area"])
|
||||
@@ -374,13 +394,13 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
|
||||
node_ports = node.ports()
|
||||
for node_port in node_ports:
|
||||
# ports > 15 are WICs ones.
|
||||
if node_port.slotNumber() == slot_number and node_port.portNumber() <= 15 and not node_port.isFree():
|
||||
if node_port.adapterNumber() == slot_number and node_port.portNumber() <= 15 and not node_port.isFree():
|
||||
adapter = settings["slot" + str(slot_number)]
|
||||
index = self._widget_slots[slot_number].findText(adapter)
|
||||
if index != -1:
|
||||
self._widget_slots[slot_number].setCurrentIndex(index)
|
||||
QtGui.QMessageBox.critical(self, node.name(), "A link is connected to port {} on adapter {}, please remove it first".format(node_port.name(),
|
||||
adapter))
|
||||
adapter))
|
||||
raise ConfigurationError()
|
||||
|
||||
def _checkForLinkConnectedToWIC(self, wic_number, settings, node):
|
||||
@@ -395,7 +415,7 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
|
||||
node_ports = node.ports()
|
||||
for node_port in node_ports:
|
||||
# ports > 15 are WICs ones.
|
||||
if node_port.slotNumber() == wic_number and node_port.portNumber() > 15 and not node_port.isFree():
|
||||
if node_port.adapterNumber() == wic_number and node_port.portNumber() > 15 and not node_port.isFree():
|
||||
wic = settings["wic" + str(wic_number)]
|
||||
index = self._widget_wics[wic_number].findText(wic)
|
||||
if index != -1:
|
||||
@@ -413,63 +433,69 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
|
||||
:param group: indicates the settings apply to a group of routers
|
||||
"""
|
||||
|
||||
#print("saving {}".format(group))
|
||||
|
||||
# these settings cannot be shared by nodes and updated
|
||||
# in the node configurator.
|
||||
|
||||
if not group:
|
||||
|
||||
# Check if the Idle-PC value has been validated okay
|
||||
if not self._idle_valid:
|
||||
idle_pc = self.uiIdlepcLineEdit.text()
|
||||
QtGui.QMessageBox.critical(self, "Idle-PC", "{} is not a valid Idle-PC value ".format(idle_pc))
|
||||
raise ConfigurationError()
|
||||
|
||||
# set the device name
|
||||
name = self.uiNameLineEdit.text()
|
||||
if not name:
|
||||
QtGui.QMessageBox.critical(self, "Name", "IOS router name cannot be empty!")
|
||||
elif node and not re.search(r"""^[\-\w]+$""", name):
|
||||
# IOS names must start with a letter, end with a letter or digit, and
|
||||
# have as interior characters only letters, digits, and hyphens.
|
||||
# They must be 63 characters or fewer.
|
||||
elif node and not node.validateHostname(name):
|
||||
QtGui.QMessageBox.critical(self, "Name", "Invalid name detected for IOS router: {}".format(name))
|
||||
else:
|
||||
settings["name"] = name
|
||||
|
||||
settings["console"] = self.uiConsolePortSpinBox.value()
|
||||
settings["aux"] = self.uiAuxPortSpinBox.value()
|
||||
|
||||
startup_config = self.uiStartupConfigLineEdit.text()
|
||||
if startup_config != settings["startup_config"]:
|
||||
if os.access(startup_config, os.R_OK):
|
||||
settings["startup_config"] = startup_config
|
||||
else:
|
||||
QtGui.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
|
||||
|
||||
private_config = self.uiPrivateConfigLineEdit.text()
|
||||
if private_config != settings["private_config"]:
|
||||
if os.access(private_config, os.R_OK):
|
||||
settings["private_config"] = private_config
|
||||
else:
|
||||
QtGui.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
|
||||
if "console" in settings:
|
||||
settings["console"] = self.uiConsolePortSpinBox.value()
|
||||
aux = self.uiAuxPortSpinBox.value()
|
||||
if aux:
|
||||
settings["aux"] = aux
|
||||
|
||||
# check and save the base MAC address
|
||||
#mac = self.uiBaseMACLineEdit.text()
|
||||
#if mac and not re.search(r"""^([0-9a-fA-F]{4}\.){2}[0-9a-fA-F]{4}$""", mac):
|
||||
# mac = self.uiBaseMACLineEdit.text()
|
||||
# if mac and not re.search(r"""^([0-9a-fA-F]{4}\.){2}[0-9a-fA-F]{4}$""", mac):
|
||||
# QtGui.QMessageBox.critical(self, "MAC address", "Invalid MAC address (format required: hhhh.hhhh.hhhh)")
|
||||
#elif mac != "":
|
||||
# elif mac != "":
|
||||
# settings["mac_addr"] = mac
|
||||
|
||||
# save the IOS image path
|
||||
path = self.uiIOSImageLineEdit.text()
|
||||
#settings["path"] = path
|
||||
settings["image"] = path#os.path.basename(path)
|
||||
settings["image"] = self.uiIOSImageLineEdit.text()
|
||||
|
||||
else:
|
||||
del settings["name"]
|
||||
del settings["console"]
|
||||
del settings["aux"]
|
||||
del settings["mac_addr"]
|
||||
del settings["startup_config"]
|
||||
del settings["private_config"]
|
||||
if "startup_config" in settings:
|
||||
del settings["startup_config"]
|
||||
if "private_config" in settings:
|
||||
del settings["private_config"]
|
||||
del settings["image"]
|
||||
|
||||
if not node:
|
||||
startup_config = self.uiStartupConfigLineEdit.text().strip()
|
||||
if not startup_config:
|
||||
settings["startup_config"] = ""
|
||||
elif startup_config != settings["startup_config"]:
|
||||
if os.access(startup_config, os.R_OK):
|
||||
settings["startup_config"] = startup_config
|
||||
else:
|
||||
QtGui.QMessageBox.critical(self, "Startup-config", "Cannot read the startup-config file")
|
||||
|
||||
private_config = self.uiPrivateConfigLineEdit.text().strip()
|
||||
if not private_config:
|
||||
settings["private_config"] = ""
|
||||
elif private_config != settings["private_config"]:
|
||||
if os.access(private_config, os.R_OK):
|
||||
settings["private_config"] = private_config
|
||||
else:
|
||||
QtGui.QMessageBox.critical(self, "Private-config", "Cannot read the private-config file")
|
||||
|
||||
# get the platform and chassis if applicable
|
||||
platform = settings["platform"]
|
||||
if "chassis" in settings:
|
||||
@@ -511,10 +537,6 @@ class IOSRouterConfigurationPage(QtGui.QWidget, Ui_iosRouterConfigPageWidget):
|
||||
# save the system ID (processor board ID in IOS) setting
|
||||
settings["system_id"] = self.uiSystemIdLineEdit.text()
|
||||
|
||||
# save the configuration register setting
|
||||
# TODO: check the format? 0xnnnn
|
||||
settings["confreg"] = self.uiConfregLineEdit.text()
|
||||
|
||||
# save the exec area setting
|
||||
settings["exec_area"] = self.uiExecAreaSpinBox.value()
|
||||
|
||||
|
||||
@@ -22,17 +22,17 @@ Configuration page for IOS router preferences.
|
||||
import os
|
||||
import copy
|
||||
import sys
|
||||
import pkg_resources
|
||||
import shutil
|
||||
import math
|
||||
import zipfile
|
||||
import logging
|
||||
|
||||
from gns3.qt import QtCore, QtGui
|
||||
from gns3.main_window import MainWindow
|
||||
from gns3.utils.progress_dialog import ProgressDialog
|
||||
from gns3.dialogs.symbol_selection_dialog import SymbolSelectionDialog
|
||||
from gns3.dialogs.configuration_dialog import ConfigurationDialog
|
||||
from gns3.cloud.utils import UploadFilesThread
|
||||
from gns3.utils.progress_dialog import ProgressDialog
|
||||
from gns3.utils.file_copy_worker import FileCopyWorker
|
||||
|
||||
from .. import Dynamips
|
||||
from ..settings import IOS_ROUTER_SETTINGS
|
||||
@@ -43,7 +43,11 @@ from ..pages.ios_router_configuration_page import IOSRouterConfigurationPage
|
||||
from ..dialogs.ios_router_wizard import IOSRouterWizard
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget):
|
||||
|
||||
"""
|
||||
QWidget preference page for IOS routers.
|
||||
"""
|
||||
@@ -94,25 +98,6 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
ios_settings = wizard.getSettings()
|
||||
key = "{server}:{name}".format(server=ios_settings["server"], name=ios_settings["name"])
|
||||
|
||||
# set the default base startup-config
|
||||
resource_name = "configs/ios_base_startup-config.txt"
|
||||
if hasattr(sys, "frozen") and os.path.isfile(resource_name):
|
||||
startup_config = os.path.normpath(resource_name)
|
||||
elif pkg_resources.resource_exists("gns3", resource_name):
|
||||
ios_base_config_path = pkg_resources.resource_filename("gns3", resource_name)
|
||||
startup_config = os.path.normpath(ios_base_config_path)
|
||||
|
||||
# set the default base private-config
|
||||
resource_name = "configs/ios_base_private-config.txt"
|
||||
if hasattr(sys, "frozen") and os.path.isfile(resource_name):
|
||||
private_config = os.path.normpath(resource_name)
|
||||
elif pkg_resources.resource_exists("gns3", resource_name):
|
||||
ios_base_config_path = pkg_resources.resource_filename("gns3", resource_name)
|
||||
private_config = os.path.normpath(ios_base_config_path)
|
||||
|
||||
ios_settings["startup_config"] = startup_config
|
||||
ios_settings["private_config"] = private_config
|
||||
|
||||
self._ios_routers[key] = IOS_ROUTER_SETTINGS.copy()
|
||||
self._ios_routers[key].update(ios_settings)
|
||||
|
||||
@@ -128,10 +113,15 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
self._upload_image_progress_dialog.setWindowTitle("IOS image upload")
|
||||
self._upload_image_progress_dialog.show()
|
||||
try:
|
||||
src = self._ios_routers[key]['path']
|
||||
# Eg: images/IOS/c3745.img
|
||||
dst = 'images/IOS/{}'.format(self._ios_routers[key]['image'])
|
||||
upload_thread = UploadFilesThread(self, MainWindow.instance().cloudSettings(), [(src, dst)])
|
||||
upload_thread = UploadFilesThread(
|
||||
self,
|
||||
cloud_settings=MainWindow.instance().cloudSettings(),
|
||||
files_to_upload=[(
|
||||
self._ios_routers[key]["image"],
|
||||
'images/' + os.path.relpath(self._ios_routers[key]["image"],
|
||||
self._main_window.settings().imagesDirPath())
|
||||
)]
|
||||
)
|
||||
upload_thread.completed.connect(self._imageUploadComplete)
|
||||
upload_thread.start()
|
||||
except Exception as e:
|
||||
@@ -181,6 +171,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
dialog.show()
|
||||
if dialog.exec_():
|
||||
if ios_router["name"] != item.text(0):
|
||||
# rename the IOS router
|
||||
new_key = "{server}:{name}".format(server=ios_router["server"], name=ios_router["name"])
|
||||
if new_key in self._ios_routers:
|
||||
QtGui.QMessageBox.critical(self, "IOS router", "IOS router name {} already exists for server {}".format(ios_router["name"],
|
||||
@@ -191,6 +182,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
del self._ios_routers[key]
|
||||
item.setText(0, ios_router["name"])
|
||||
item.setData(0, QtCore.Qt.UserRole, new_key)
|
||||
|
||||
self._refreshInfo(ios_router)
|
||||
|
||||
def _iosRouterDeleteSlot(self):
|
||||
@@ -201,6 +193,8 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
item = self.uiIOSRoutersTreeWidget.currentItem()
|
||||
if item:
|
||||
key = item.data(0, QtCore.Qt.UserRole)
|
||||
ios_router = self._ios_routers[key]
|
||||
|
||||
del self._ios_routers[key]
|
||||
self.uiIOSRoutersTreeWidget.takeTopLevelItem(self.uiIOSRoutersTreeWidget.indexOfTopLevelItem(item))
|
||||
if self._ios_routers == {}:
|
||||
@@ -217,7 +211,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
:return: path to the IOS image or None
|
||||
"""
|
||||
|
||||
destination_directory = os.path.join(MainWindow.instance().settings()["images_path"], "IOS")
|
||||
destination_directory = os.path.join(MainWindow.instance().imagesDirPath(), "IOS")
|
||||
path, _ = QtGui.QFileDialog.getOpenFileNameAndFilter(parent,
|
||||
"Select an IOS image",
|
||||
destination_directory,
|
||||
@@ -255,10 +249,15 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
except FileExistsError:
|
||||
pass
|
||||
except OSError as e:
|
||||
QtGui.QMessageBox.critical(parent, "IOS images directory", "Could not create the IOS images directory {}: {}".format(destination_directory, str(e)))
|
||||
QtGui.QMessageBox.critical(parent, "IOS images directory", "Could not create the IOS images directory {}: {}".format(destination_directory, e))
|
||||
return
|
||||
|
||||
if isIOSCompressed(path):
|
||||
compressed = False
|
||||
try:
|
||||
compressed = isIOSCompressed(path)
|
||||
except (OSError, ValueError):
|
||||
pass # ignore errors if we cannot find out the IOS image is compressed.
|
||||
if compressed:
|
||||
reply = QtGui.QMessageBox.question(parent, "IOS image", "Would you like to decompress this IOS image?",
|
||||
QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
|
||||
if reply == QtGui.QMessageBox.Yes:
|
||||
@@ -273,21 +272,24 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
path = decompressed_image_path
|
||||
thread.wait()
|
||||
|
||||
if os.path.dirname(path) != destination_directory:
|
||||
if os.path.normpath(os.path.dirname(path)) != destination_directory:
|
||||
# the IOS image is not in the default images directory
|
||||
new_destination_path = os.path.join(destination_directory, os.path.basename(path))
|
||||
try:
|
||||
# try to create a symbolic link to it
|
||||
symlink_path = new_destination_path
|
||||
os.symlink(path, symlink_path)
|
||||
path = symlink_path
|
||||
except (OSError, NotImplementedError):
|
||||
# if unsuccessful, then copy the IOS image itself
|
||||
try:
|
||||
shutil.copyfile(path, new_destination_path)
|
||||
path = new_destination_path
|
||||
except OSError:
|
||||
pass
|
||||
reply = QtGui.QMessageBox.question(parent,
|
||||
"IOS image",
|
||||
"Would you like to copy {} to the default images directory".format(os.path.basename(path)),
|
||||
QtGui.QMessageBox.Yes,
|
||||
QtGui.QMessageBox.No)
|
||||
if reply == QtGui.QMessageBox.Yes:
|
||||
destination_path = os.path.join(destination_directory, os.path.basename(path))
|
||||
worker = FileCopyWorker(path, destination_path)
|
||||
progress_dialog = ProgressDialog(worker, "IOS image", "Copying {}".format(os.path.basename(path)), "Cancel", busy=True, parent=parent)
|
||||
progress_dialog.show()
|
||||
progress_dialog.exec_()
|
||||
errors = progress_dialog.errors()
|
||||
if errors:
|
||||
QtGui.QMessageBox.critical(parent, "IOS image", "{}".format("".join(errors)))
|
||||
else:
|
||||
path = destination_path
|
||||
|
||||
return path
|
||||
|
||||
@@ -309,7 +311,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
decompressed_size += zip_info.file_size
|
||||
else:
|
||||
decompressed_size = os.path.getsize(path)
|
||||
except OSError:
|
||||
except (zipfile.BadZipFile, OSError):
|
||||
return 0
|
||||
|
||||
# get the size in MB
|
||||
@@ -317,46 +319,6 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
# round up to the closest multiple of 32 (step of the RAM SpinBox)
|
||||
return math.ceil(decompressed_size / 32) * 32
|
||||
|
||||
def _startupConfigBrowserSlot(self):
|
||||
"""
|
||||
Slot to open a file browser and select a startup-config file.
|
||||
"""
|
||||
|
||||
if hasattr(sys, "frozen"):
|
||||
config_dir = "configs"
|
||||
else:
|
||||
config_dir = pkg_resources.resource_filename("gns3", "configs")
|
||||
path = QtGui.QFileDialog.getOpenFileName(self, "Select a startup configuration", config_dir)
|
||||
if not path:
|
||||
return
|
||||
|
||||
if not os.access(path, os.R_OK):
|
||||
QtGui.QMessageBox.critical(self, "Startup configuration", "Cannot read {}".format(path))
|
||||
return
|
||||
|
||||
self.uiStartupConfigLineEdit.clear()
|
||||
self.uiStartupConfigLineEdit.setText(path)
|
||||
|
||||
def _privateConfigBrowserSlot(self):
|
||||
"""
|
||||
Slot to open a file browser and select a private-config file.
|
||||
"""
|
||||
|
||||
if hasattr(sys, "frozen"):
|
||||
config_dir = "configs"
|
||||
else:
|
||||
config_dir = pkg_resources.resource_filename("gns3", "configs")
|
||||
path = QtGui.QFileDialog.getOpenFileName(self, "Select a private configuration", config_dir)
|
||||
if not path:
|
||||
return
|
||||
|
||||
if not os.access(path, os.R_OK):
|
||||
QtGui.QMessageBox.critical(self, "Private configuration", "Cannot read {}".format(path))
|
||||
return
|
||||
|
||||
self.uiPrivateConfigLineEdit.clear()
|
||||
self.uiPrivateConfigLineEdit.setText(path)
|
||||
|
||||
def _decompressIOSSlot(self):
|
||||
"""
|
||||
Slot to decompress an IOS image.
|
||||
@@ -366,12 +328,20 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
if item:
|
||||
key = item.data(0, QtCore.Qt.UserRole)
|
||||
ios_router = self._ios_routers[key]
|
||||
path = ios_router["path"]
|
||||
path = ios_router["image"]
|
||||
if not os.path.isfile(path):
|
||||
QtGui.QMessageBox.critical(self, "IOS image", "IOS image file {} is does not exist".format(path))
|
||||
return
|
||||
if not isIOSCompressed(path):
|
||||
QtGui.QMessageBox.critical(self, "IOS image", "IOS image {} is not compressed".format(os.path.basename(path)))
|
||||
try:
|
||||
if not isIOSCompressed(path):
|
||||
QtGui.QMessageBox.critical(self, "IOS image", "IOS image {} is not compressed".format(os.path.basename(path)))
|
||||
return
|
||||
except (OSError, ValueError) as e:
|
||||
# errno 22, invalid argument means the file system where the IOS image is located doesn't support mmap
|
||||
if e.errno == 22:
|
||||
QtGui.QMessageBox.critical(self, "IOS image", "IOS image {} cannot be memory mapped, most likely because the file system doesn't support it".format(os.path.basename(path)))
|
||||
else:
|
||||
QtGui.QMessageBox.critical(self, "IOS image", "Could not determine if the IOS image is compressed: {}".format(e))
|
||||
return
|
||||
|
||||
decompressed_image_path = os.path.splitext(path)[0] + ".image"
|
||||
@@ -386,8 +356,7 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
"Cancel", busy=True, parent=self)
|
||||
progress_dialog.show()
|
||||
if progress_dialog.exec_() is not False:
|
||||
ios_router["path"] = decompressed_image_path
|
||||
ios_router["image"] = os.path.basename(decompressed_image_path)
|
||||
ios_router["image"] = decompressed_image_path
|
||||
self._refreshInfo(ios_router)
|
||||
thread.wait()
|
||||
|
||||
@@ -481,16 +450,16 @@ class IOSRouterPreferencesPage(QtGui.QWidget, Ui_IOSRouterPreferencesPageWidget)
|
||||
Change a symbol for an IOS router.
|
||||
"""
|
||||
|
||||
dialog = SymbolSelectionDialog(self)
|
||||
dialog.show()
|
||||
if dialog.exec_():
|
||||
normal_symbol, selected_symbol = dialog.getSymbols()
|
||||
category = dialog.getCategory()
|
||||
item = self.uiIOSRoutersTreeWidget.currentItem()
|
||||
if item:
|
||||
item = self.uiIOSRoutersTreeWidget.currentItem()
|
||||
if item:
|
||||
key = item.data(0, QtCore.Qt.UserRole)
|
||||
ios_router = self._ios_routers[key]
|
||||
dialog = SymbolSelectionDialog(self, symbol=ios_router["default_symbol"], category=ios_router["category"])
|
||||
dialog.show()
|
||||
if dialog.exec_():
|
||||
normal_symbol, selected_symbol = dialog.getSymbols()
|
||||
category = dialog.getCategory()
|
||||
item.setIcon(0, QtGui.QIcon(normal_symbol))
|
||||
key = item.data(0, QtCore.Qt.UserRole)
|
||||
ios_router = self._ios_routers[key]
|
||||
ios_router["default_symbol"] = normal_symbol
|
||||
ios_router["hover_symbol"] = selected_symbol
|
||||
ios_router["category"] = category
|
||||
|
||||
@@ -21,70 +21,26 @@ Default Dynamips settings.
|
||||
|
||||
from gns3.node import Node
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
# default path to Dynamips executable
|
||||
if sys.platform.startswith("win"):
|
||||
DEFAULT_DYNAMIPS_PATH = r"dynamips\dynamips.exe"
|
||||
elif sys.platform.startswith("darwin") and hasattr(sys, "frozen"):
|
||||
DEFAULT_DYNAMIPS_PATH = os.path.join(os.getcwd(), "dynamips")
|
||||
else:
|
||||
paths = [os.getcwd()] + os.environ["PATH"].split(os.pathsep)
|
||||
# look for dynamips in the current working directory and $PATH
|
||||
DEFAULT_DYNAMIPS_PATH = "dynamips"
|
||||
for path in paths:
|
||||
try:
|
||||
if "dynamips" in os.listdir(path) and os.access(os.path.join(path, "dynamips"), os.X_OK):
|
||||
DEFAULT_DYNAMIPS_PATH = os.path.join(path, "dynamips")
|
||||
break
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
DYNAMIPS_SETTINGS = {
|
||||
"path": DEFAULT_DYNAMIPS_PATH,
|
||||
"hypervisor_start_port_range": 7200,
|
||||
"hypervisor_end_port_range": 7700,
|
||||
"console_start_port_range": 2001,
|
||||
"console_end_port_range": 2500,
|
||||
"aux_start_port_range": 2501,
|
||||
"aux_end_port_range": 3000,
|
||||
"udp_start_port_range": 10001,
|
||||
"udp_end_port_range": 20000,
|
||||
"dynamips_path": "",
|
||||
"allocate_aux_console_ports": False,
|
||||
"use_local_server": True,
|
||||
"allocate_hypervisor_per_device": True,
|
||||
"memory_usage_limit_per_hypervisor": 1024,
|
||||
"allocate_hypervisor_per_ios_image": True,
|
||||
"ghost_ios_support": True,
|
||||
"jit_sharing_support": False,
|
||||
"sparse_memory_support": True,
|
||||
"mmap_support": True,
|
||||
}
|
||||
|
||||
DYNAMIPS_SETTING_TYPES = {
|
||||
"path": str,
|
||||
"hypervisor_start_port_range": int,
|
||||
"hypervisor_end_port_range": int,
|
||||
"console_start_port_range": int,
|
||||
"console_end_port_range": int,
|
||||
"aux_start_port_range": int,
|
||||
"aux_end_port_range": int,
|
||||
"udp_start_port_range": int,
|
||||
"udp_end_port_range": int,
|
||||
"dynamips_path": str,
|
||||
"allocate_aux_console_ports": bool,
|
||||
"use_local_server": bool,
|
||||
"allocate_hypervisor_per_device": bool,
|
||||
"memory_usage_limit_per_hypervisor": int,
|
||||
"allocate_hypervisor_per_ios_image": bool,
|
||||
"ghost_ios_support": bool,
|
||||
"jit_sharing_support": bool,
|
||||
"sparse_memory_support": bool,
|
||||
"mmap_support": bool,
|
||||
}
|
||||
|
||||
IOS_ROUTER_SETTINGS = {
|
||||
"name": "",
|
||||
"path": "",
|
||||
"image": "",
|
||||
"default_symbol": ":/symbols/router.normal.svg",
|
||||
"hover_symbol": ":/symbols/router.selected.svg",
|
||||
@@ -100,18 +56,16 @@ IOS_ROUTER_SETTINGS = {
|
||||
"mmap": True,
|
||||
"sparsemem": True,
|
||||
"ram": 128,
|
||||
"nvram": 256,
|
||||
"nvram": 128,
|
||||
"mac_addr": "",
|
||||
"disk0": 1,
|
||||
"disk0": 0,
|
||||
"disk1": 0,
|
||||
"confreg": "0x2102",
|
||||
"system_id": "FTX0945W0MY",
|
||||
"server": "local"
|
||||
}
|
||||
|
||||
IOS_ROUTER_SETTING_TYPES = {
|
||||
"name": str,
|
||||
"path": str,
|
||||
"image": str,
|
||||
"default_symbol": str,
|
||||
"hover_symbol": str,
|
||||
@@ -131,20 +85,44 @@ IOS_ROUTER_SETTING_TYPES = {
|
||||
"mac_addr": str,
|
||||
"disk0": int,
|
||||
"disk1": int,
|
||||
"confreg": str,
|
||||
"system_id": str,
|
||||
"server": str
|
||||
}
|
||||
|
||||
# supported platforms with the default RAM value
|
||||
PLATFORMS_DEFAULT_RAM = {"c1700": 64,
|
||||
"c2600": 64,
|
||||
"c2691": 128,
|
||||
"c3600": 128,
|
||||
PLATFORMS_DEFAULT_RAM = {"c1700": 160,
|
||||
"c2600": 160,
|
||||
"c2691": 192,
|
||||
"c3600": 192,
|
||||
"c3725": 128,
|
||||
"c3745": 128,
|
||||
"c3745": 256,
|
||||
"c7200": 512}
|
||||
|
||||
# supported platforms with the default NVRAM value
|
||||
PLATFORMS_DEFAULT_NVRAM = {"c1700": 128,
|
||||
"c2600": 128,
|
||||
"c2691": 256,
|
||||
"c3600": 256,
|
||||
"c3725": 256,
|
||||
"c3745": 256,
|
||||
"c7200": 512}
|
||||
|
||||
DEFAULT_IDLEPC = {"7f4ae12a098391bc0edcaf4f44caaf9d": "0x80358a60", # c1700-adventerprisek9-mz.124-25d
|
||||
"3aaecd2222e812c16c211bc9f7c77512": "0x824a4dc4", # c1700-adventerprisek9-mz.124-15.T14
|
||||
"062a32e9e3f59aeec930ea5694fda9c9": "0x80519c48", # c2600-adventerprisek9-mz.124-25d
|
||||
"483e3a579a5144ec23f2f160d4b0c0e2": "0x8027ec88", # c2600-adventerprisek9-mz.124-15.T14
|
||||
"37b444b29191630e5b688f002de2171c": "0x603a8bac", # c3620-a3jk8s-mz.122-26c
|
||||
"493c4ef6578801d74d715e7d11596964": "0x6050b114", # c3640-a3js-mz.124-25d
|
||||
"b88ee1b2ed182737395db2df27f34a33": "0x606071f8", # c3660-a3jk9s-mz.124-25d
|
||||
"daed99f508fd42dbaacf711e560643ed": "0x6076e0b4", # c3660-a3jk9s-mz.124-15.T14
|
||||
"8dc8486065de63883f29c85825a2f18c": "0x60a48cb8", # c2691-adventerprisek9-mz.124-25d
|
||||
"e7ee5a4a57ed1433e5f73ba6e7695c90": "0x60bcf9f8", # c2691-adventerprisek9-mz.124-15.T14
|
||||
"606484061b9e52e71d4f4ddab9af19e7": "0x602467a4", # c3725-adventerprisek9-mz.124-25d
|
||||
"64f8c427ed48fd21bd02cf1ff254c4eb": "0x60c09aa0", # c3725-adventerprisek9-mz.124-15.T14
|
||||
"ddbaf74274822b50fa9670e10c75b08f": "0x60aa1da0", # c3745-adventerprisek9-mz.124-25d
|
||||
"4af2e752220ed1397924150ff7bbe4ce": "0x602701e4", # c3745-adventerprisek9-mz.124-15.T14
|
||||
"6b89d0d804e1f2bb5b8bda66b5692047": "0x606df838"} # c7200-adventerprisek9-mz.124-24.T5
|
||||
|
||||
# platforms with supported chassis
|
||||
CHASSIS = {"c1700": ("1720", "1721", "1750", "1751", "1760"),
|
||||
"c2600": ("2610", "2611", "2620", "2621", "2610XM", "2611XM", "2620XM", "2621XM", "2650XM", "2651XM"),
|
||||
@@ -191,7 +169,7 @@ C7200_PAS = (
|
||||
IO_C7200 = ("C7200-IO-FE",
|
||||
"C7200-IO-2FE",
|
||||
"C7200-IO-GE-E"
|
||||
)
|
||||
)
|
||||
|
||||
"""
|
||||
Build the adapter compatibility matrix:
|
||||
|
||||
@@ -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>449</width>
|
||||
<height>491</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -65,7 +65,7 @@
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="uiIOSImageLabel">
|
||||
<property name="text">
|
||||
<string>IOS image:</string>
|
||||
<string>IOS image path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -89,7 +89,7 @@
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="uiStartupConfigLabel">
|
||||
<property name="text">
|
||||
<string>Startup-config:</string>
|
||||
<string>Initial startup-config:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -113,7 +113,7 @@
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="uiPrivateConfigLabel">
|
||||
<property name="text">
|
||||
<string>Private-config:</string>
|
||||
<string>Initial private-config:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -615,13 +615,6 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="uiSystemIdLineEdit"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiConfregLabel">
|
||||
<property name="text">
|
||||
<string>Confreg:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@@ -629,14 +622,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="uiBaseMacLabel">
|
||||
<property name="text">
|
||||
<string>Base MAC:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="uiBaseMACLineEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
@@ -649,19 +642,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="uiConfregLineEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0x2102</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -1008,7 +988,6 @@
|
||||
<tabstop>uiWic0comboBox</tabstop>
|
||||
<tabstop>uiWic1comboBox</tabstop>
|
||||
<tabstop>uiWic2comboBox</tabstop>
|
||||
<tabstop>uiConfregLineEdit</tabstop>
|
||||
<tabstop>uiBaseMACLineEdit</tabstop>
|
||||
<tabstop>uiExecAreaSpinBox</tabstop>
|
||||
</tabstops>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/ios_router_configuration_page.ui'
|
||||
#
|
||||
# Created: Fri Oct 10 10:43:48 2014
|
||||
# Created: Sat Mar 14 16:29:27 2015
|
||||
# by: PyQt4 UI code generator 4.10.4
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
@@ -26,7 +26,7 @@ except AttributeError:
|
||||
class Ui_iosRouterConfigPageWidget(object):
|
||||
def setupUi(self, iosRouterConfigPageWidget):
|
||||
iosRouterConfigPageWidget.setObjectName(_fromUtf8("iosRouterConfigPageWidget"))
|
||||
iosRouterConfigPageWidget.resize(419, 522)
|
||||
iosRouterConfigPageWidget.resize(449, 491)
|
||||
self.vboxlayout = QtGui.QVBoxLayout(iosRouterConfigPageWidget)
|
||||
self.vboxlayout.setObjectName(_fromUtf8("vboxlayout"))
|
||||
self.uiTabWidget = QtGui.QTabWidget(iosRouterConfigPageWidget)
|
||||
@@ -361,15 +361,12 @@ class Ui_iosRouterConfigPageWidget(object):
|
||||
self.uiSystemIdLineEdit = QtGui.QLineEdit(self.uiSystemGroupBox)
|
||||
self.uiSystemIdLineEdit.setObjectName(_fromUtf8("uiSystemIdLineEdit"))
|
||||
self.gridLayout_6.addWidget(self.uiSystemIdLineEdit, 0, 1, 1, 1)
|
||||
self.uiConfregLabel = QtGui.QLabel(self.uiSystemGroupBox)
|
||||
self.uiConfregLabel.setObjectName(_fromUtf8("uiConfregLabel"))
|
||||
self.gridLayout_6.addWidget(self.uiConfregLabel, 1, 0, 1, 1)
|
||||
self.label = QtGui.QLabel(self.uiSystemGroupBox)
|
||||
self.label.setObjectName(_fromUtf8("label"))
|
||||
self.gridLayout_6.addWidget(self.label, 0, 0, 1, 1)
|
||||
self.uiBaseMacLabel = QtGui.QLabel(self.uiSystemGroupBox)
|
||||
self.uiBaseMacLabel.setObjectName(_fromUtf8("uiBaseMacLabel"))
|
||||
self.gridLayout_6.addWidget(self.uiBaseMacLabel, 2, 0, 1, 1)
|
||||
self.gridLayout_6.addWidget(self.uiBaseMacLabel, 1, 0, 1, 1)
|
||||
self.uiBaseMACLineEdit = QtGui.QLineEdit(self.uiSystemGroupBox)
|
||||
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
@@ -378,15 +375,7 @@ class Ui_iosRouterConfigPageWidget(object):
|
||||
self.uiBaseMACLineEdit.setSizePolicy(sizePolicy)
|
||||
self.uiBaseMACLineEdit.setText(_fromUtf8(""))
|
||||
self.uiBaseMACLineEdit.setObjectName(_fromUtf8("uiBaseMACLineEdit"))
|
||||
self.gridLayout_6.addWidget(self.uiBaseMACLineEdit, 2, 1, 1, 1)
|
||||
self.uiConfregLineEdit = QtGui.QLineEdit(self.uiSystemGroupBox)
|
||||
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.uiConfregLineEdit.sizePolicy().hasHeightForWidth())
|
||||
self.uiConfregLineEdit.setSizePolicy(sizePolicy)
|
||||
self.uiConfregLineEdit.setObjectName(_fromUtf8("uiConfregLineEdit"))
|
||||
self.gridLayout_6.addWidget(self.uiConfregLineEdit, 1, 1, 1, 1)
|
||||
self.gridLayout_6.addWidget(self.uiBaseMACLineEdit, 1, 1, 1, 1)
|
||||
self.verticalLayout_4.addWidget(self.uiSystemGroupBox)
|
||||
self.uiOptimizationsGroupBox = QtGui.QGroupBox(self.uiAdvancedPageWidget)
|
||||
self.uiOptimizationsGroupBox.setObjectName(_fromUtf8("uiOptimizationsGroupBox"))
|
||||
@@ -557,8 +546,7 @@ class Ui_iosRouterConfigPageWidget(object):
|
||||
iosRouterConfigPageWidget.setTabOrder(self.uiSlot6comboBox, self.uiWic0comboBox)
|
||||
iosRouterConfigPageWidget.setTabOrder(self.uiWic0comboBox, self.uiWic1comboBox)
|
||||
iosRouterConfigPageWidget.setTabOrder(self.uiWic1comboBox, self.uiWic2comboBox)
|
||||
iosRouterConfigPageWidget.setTabOrder(self.uiWic2comboBox, self.uiConfregLineEdit)
|
||||
iosRouterConfigPageWidget.setTabOrder(self.uiConfregLineEdit, self.uiBaseMACLineEdit)
|
||||
iosRouterConfigPageWidget.setTabOrder(self.uiWic2comboBox, self.uiBaseMACLineEdit)
|
||||
iosRouterConfigPageWidget.setTabOrder(self.uiBaseMACLineEdit, self.uiExecAreaSpinBox)
|
||||
|
||||
def retranslateUi(self, iosRouterConfigPageWidget):
|
||||
@@ -566,11 +554,11 @@ class Ui_iosRouterConfigPageWidget(object):
|
||||
self.uiNameLabel.setText(_translate("iosRouterConfigPageWidget", "Name:", None))
|
||||
self.uiPlatformLabel.setText(_translate("iosRouterConfigPageWidget", "Platform:", None))
|
||||
self.uiChassisLabel.setText(_translate("iosRouterConfigPageWidget", "Chassis:", None))
|
||||
self.uiIOSImageLabel.setText(_translate("iosRouterConfigPageWidget", "IOS image:", None))
|
||||
self.uiIOSImageLabel.setText(_translate("iosRouterConfigPageWidget", "IOS image path:", None))
|
||||
self.uiIOSImageToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse...", None))
|
||||
self.uiStartupConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Startup-config:", None))
|
||||
self.uiStartupConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Initial startup-config:", None))
|
||||
self.uiStartupConfigToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse...", None))
|
||||
self.uiPrivateConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Private-config:", None))
|
||||
self.uiPrivateConfigLabel.setText(_translate("iosRouterConfigPageWidget", "Initial private-config:", None))
|
||||
self.uiPrivateConfigToolButton.setText(_translate("iosRouterConfigPageWidget", "&Browse...", None))
|
||||
self.uiConsolePortLabel.setText(_translate("iosRouterConfigPageWidget", "Console port:", None))
|
||||
self.uiAuxPortLabel.setText(_translate("iosRouterConfigPageWidget", "Aux port:", None))
|
||||
@@ -604,10 +592,8 @@ class Ui_iosRouterConfigPageWidget(object):
|
||||
self.uiWic2Label.setText(_translate("iosRouterConfigPageWidget", "wic 2:", None))
|
||||
self.uiTabWidget.setTabText(self.uiTabWidget.indexOf(self.uiSlotsPageWidget), _translate("iosRouterConfigPageWidget", "Slots", None))
|
||||
self.uiSystemGroupBox.setTitle(_translate("iosRouterConfigPageWidget", "System", None))
|
||||
self.uiConfregLabel.setText(_translate("iosRouterConfigPageWidget", "Confreg:", None))
|
||||
self.label.setText(_translate("iosRouterConfigPageWidget", "System ID:", None))
|
||||
self.uiBaseMacLabel.setText(_translate("iosRouterConfigPageWidget", "Base MAC:", None))
|
||||
self.uiConfregLineEdit.setText(_translate("iosRouterConfigPageWidget", "0x2102", None))
|
||||
self.uiOptimizationsGroupBox.setTitle(_translate("iosRouterConfigPageWidget", "Optimisations", None))
|
||||
self.uiExecAreaLabel.setText(_translate("iosRouterConfigPageWidget", "Exec area:", None))
|
||||
self.uiExecAreaSpinBox.setSuffix(_translate("iosRouterConfigPageWidget", " MiB", None))
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>517</width>
|
||||
<width>585</width>
|
||||
<height>398</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>New IOS router</string>
|
||||
<string>New IOS router template</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
@@ -183,6 +183,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="uiEtherSwitchCheckBox">
|
||||
<property name="text">
|
||||
<string>This is an EtherSwitch router</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWizardPage" name="uiMemoryWizardPage">
|
||||
@@ -190,7 +197,7 @@
|
||||
<string>Memory</string>
|
||||
</property>
|
||||
<property name="subTitle">
|
||||
<string>Please check the amount of memory (RAM) that you allocate to IOS. Not enough RAM could prevent IOS to start.</string>
|
||||
<string>Please check the amount of memory (RAM) that you allocate to IOS. Too much or not enough RAM could prevent IOS to start.</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
@@ -232,10 +239,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<item row="1" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><a href="http://tools.cisco.com/ITDIT/CFN/jsp/SearchBySoftware.jsp"><span style=" text-decoration: underline; color:#0000ff;">Check for minimum RAM requirement</span></a></p></body></html></string>
|
||||
<string><html><head/><body><p><a href="http://tools.cisco.com/ITDIT/CFN/jsp/SearchBySoftware.jsp"><span style=" text-decoration: underline; color:#0000ff;">Check for minimum and maximum RAM requirement</span></a></p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/modules/dynamips/ui/ios_router_wizard.ui'
|
||||
# Form implementation generated from reading ui file '/Users/noplay/code/gns3/gns3-gui/gns3/modules/dynamips/ui/ios_router_wizard.ui'
|
||||
#
|
||||
# Created: Wed Oct 22 16:46:37 2014
|
||||
# by: PyQt4 UI code generator 4.10.4
|
||||
# Created: Wed Apr 1 15:15:16 2015
|
||||
# by: PyQt4 UI code generator 4.11.3
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -26,7 +26,7 @@ except AttributeError:
|
||||
class Ui_IOSRouterWizard(object):
|
||||
def setupUi(self, IOSRouterWizard):
|
||||
IOSRouterWizard.setObjectName(_fromUtf8("IOSRouterWizard"))
|
||||
IOSRouterWizard.resize(517, 398)
|
||||
IOSRouterWizard.resize(585, 398)
|
||||
IOSRouterWizard.setModal(True)
|
||||
self.uiServerWizardPage = QtGui.QWizardPage()
|
||||
self.uiServerWizardPage.setObjectName(_fromUtf8("uiServerWizardPage"))
|
||||
@@ -111,6 +111,9 @@ class Ui_IOSRouterWizard(object):
|
||||
self.uiChassisLabel = QtGui.QLabel(self.uiNamePlatformWizardPage)
|
||||
self.uiChassisLabel.setObjectName(_fromUtf8("uiChassisLabel"))
|
||||
self.gridLayout.addWidget(self.uiChassisLabel, 2, 0, 1, 1)
|
||||
self.uiEtherSwitchCheckBox = QtGui.QCheckBox(self.uiNamePlatformWizardPage)
|
||||
self.uiEtherSwitchCheckBox.setObjectName(_fromUtf8("uiEtherSwitchCheckBox"))
|
||||
self.gridLayout.addWidget(self.uiEtherSwitchCheckBox, 3, 0, 1, 2)
|
||||
IOSRouterWizard.addPage(self.uiNamePlatformWizardPage)
|
||||
self.uiMemoryWizardPage = QtGui.QWizardPage()
|
||||
self.uiMemoryWizardPage.setObjectName(_fromUtf8("uiMemoryWizardPage"))
|
||||
@@ -138,7 +141,7 @@ class Ui_IOSRouterWizard(object):
|
||||
self.label.setWordWrap(False)
|
||||
self.label.setOpenExternalLinks(True)
|
||||
self.label.setObjectName(_fromUtf8("label"))
|
||||
self.gridLayout_2.addWidget(self.label, 1, 0, 1, 2)
|
||||
self.gridLayout_2.addWidget(self.label, 1, 0, 1, 3)
|
||||
IOSRouterWizard.addPage(self.uiMemoryWizardPage)
|
||||
self.uiNetworkAdaptersWizardPage = QtGui.QWizardPage()
|
||||
self.uiNetworkAdaptersWizardPage.setObjectName(_fromUtf8("uiNetworkAdaptersWizardPage"))
|
||||
@@ -280,7 +283,7 @@ class Ui_IOSRouterWizard(object):
|
||||
IOSRouterWizard.setTabOrder(self.uiNameLineEdit, self.uiPlatformComboBox)
|
||||
|
||||
def retranslateUi(self, IOSRouterWizard):
|
||||
IOSRouterWizard.setWindowTitle(_translate("IOSRouterWizard", "New IOS router", None))
|
||||
IOSRouterWizard.setWindowTitle(_translate("IOSRouterWizard", "New IOS router template", None))
|
||||
self.uiServerWizardPage.setTitle(_translate("IOSRouterWizard", "Server", None))
|
||||
self.uiServerWizardPage.setSubTitle(_translate("IOSRouterWizard", "Please choose a server type to run your new IOS router.", None))
|
||||
self.uiServerTypeGroupBox.setTitle(_translate("IOSRouterWizard", "Server type", None))
|
||||
@@ -299,12 +302,13 @@ class Ui_IOSRouterWizard(object):
|
||||
self.uiTypeLabel.setText(_translate("IOSRouterWizard", "Platform:", None))
|
||||
self.uiNameLabel.setText(_translate("IOSRouterWizard", "Name:", None))
|
||||
self.uiChassisLabel.setText(_translate("IOSRouterWizard", "Chassis:", None))
|
||||
self.uiEtherSwitchCheckBox.setText(_translate("IOSRouterWizard", "This is an EtherSwitch router", None))
|
||||
self.uiMemoryWizardPage.setTitle(_translate("IOSRouterWizard", "Memory", None))
|
||||
self.uiMemoryWizardPage.setSubTitle(_translate("IOSRouterWizard", "Please check the amount of memory (RAM) that you allocate to IOS. Not enough RAM could prevent IOS to start.", None))
|
||||
self.uiMemoryWizardPage.setSubTitle(_translate("IOSRouterWizard", "Please check the amount of memory (RAM) that you allocate to IOS. Too much or not enough RAM could prevent IOS to start.", None))
|
||||
self.uiRamLabel.setText(_translate("IOSRouterWizard", "Default RAM:", None))
|
||||
self.uiRamSpinBox.setSuffix(_translate("IOSRouterWizard", " MiB", None))
|
||||
self.uiTestIOSImagePushButton.setText(_translate("IOSRouterWizard", "&Test IOS image", None))
|
||||
self.label.setText(_translate("IOSRouterWizard", "<html><head/><body><p><a href=\"http://tools.cisco.com/ITDIT/CFN/jsp/SearchBySoftware.jsp\"><span style=\" text-decoration: underline; color:#0000ff;\">Check for minimum RAM requirement</span></a></p></body></html>", None))
|
||||
self.label.setText(_translate("IOSRouterWizard", "<html><head/><body><p><a href=\"http://tools.cisco.com/ITDIT/CFN/jsp/SearchBySoftware.jsp\"><span style=\" text-decoration: underline; color:#0000ff;\">Check for minimum and maximum RAM requirement</span></a></p></body></html>", None))
|
||||
self.uiNetworkAdaptersWizardPage.setTitle(_translate("IOSRouterWizard", "Network adapters", None))
|
||||
self.uiNetworkAdaptersWizardPage.setSubTitle(_translate("IOSRouterWizard", "Please choose the default network adapters that should be inserted into every new instance of this router.", None))
|
||||
self.uiSlot0Label.setText(_translate("IOSRouterWizard", "slot 0:", None))
|
||||
@@ -320,7 +324,7 @@ class Ui_IOSRouterWizard(object):
|
||||
self.uiWic1Label.setText(_translate("IOSRouterWizard", "wic 1:", None))
|
||||
self.uiWic2Label.setText(_translate("IOSRouterWizard", "wic 2:", None))
|
||||
self.uiIdlePCWizardPage.setTitle(_translate("IOSRouterWizard", "Idle-PC", None))
|
||||
self.uiIdlePCWizardPage.setSubTitle(_translate("IOSRouterWizard", "An Idle-PC value is necessary to prevent IOS to use 100% of your processor or one of its core.", None))
|
||||
self.uiIdlePCWizardPage.setSubTitle(_translate("IOSRouterWizard", "An idle-pc value is necessary to prevent IOS to use 100% of your processor or one of its core.", None))
|
||||
self.uiIdlepcLabel.setText(_translate("IOSRouterWizard", "Idle-PC:", None))
|
||||
self.uiIdlePCFinderPushButton.setText(_translate("IOSRouterWizard", "Idle-PC finder", None))
|
||||
|
||||
|
||||
@@ -32,10 +32,7 @@ def isIOSCompressed(ios_image):
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
try:
|
||||
fd = open(ios_image, "r+b")
|
||||
except OSError:
|
||||
return False
|
||||
fd = open(ios_image, "rb")
|
||||
mapped_file = mmap.mmap(fd.fileno(), 0)
|
||||
|
||||
# look for ZIP 'end of central directory' signature
|
||||
|
||||
@@ -19,11 +19,14 @@
|
||||
Thread to wait for an IOS image to be decompressed.
|
||||
"""
|
||||
|
||||
import zipfile
|
||||
|
||||
from gns3.qt import QtCore
|
||||
from .decompress_ios import decompressIOS
|
||||
|
||||
|
||||
class DecompressIOSThread(QtCore.QThread):
|
||||
|
||||
"""
|
||||
Thread to decompress an IOS image.
|
||||
|
||||
@@ -51,7 +54,10 @@ class DecompressIOSThread(QtCore.QThread):
|
||||
self._is_running = True
|
||||
try:
|
||||
decompressIOS(self._ios_image, self._destination_file)
|
||||
except OSError as e:
|
||||
except zipfile.BadZipFile as e:
|
||||
self.error.emit("File {} is corrupted {}".format(self._ios_image, e), True)
|
||||
return
|
||||
except (OSError, MemoryError) as e:
|
||||
self.error.emit("Could not decompress {}: {}".format(self._ios_image, e), True)
|
||||
return
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ from gns3.qt import QtGui
|
||||
|
||||
|
||||
class TreeWidgetItem(QtGui.QTreeWidgetItem):
|
||||
|
||||
"""
|
||||
QTreeWidgetItem reimplementation to allow numeric sort.
|
||||
"""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user