├── .codeclimate.yml ├── .gitignore ├── .travis.yml ├── COPYING ├── ChangeLog ├── MANIFEST.in ├── README.rst ├── converter ├── dev-requirements.txt ├── docs ├── Makefile ├── conf.py ├── gns3converter.adapters.rst ├── gns3converter.converter.rst ├── gns3converter.interfaces.rst ├── gns3converter.main.rst ├── gns3converter.models.rst ├── gns3converter.node.rst ├── gns3converter.rst ├── gns3converter.topology.rst ├── gns3converter.utils.rst ├── index.rst ├── installation.rst ├── requirements.txt └── usage.rst ├── gns3-converter.py ├── gns3converter ├── __init__.py ├── adapters.py ├── configspec ├── converter.py ├── converterror.py ├── interfaces.py ├── main.py ├── models.py ├── node.py ├── topology.py └── utils.py ├── requirements.txt ├── setup.py └── tests ├── __init__.py ├── configs ├── R1.cfg └── R2.cfg ├── data.py ├── test_converter.py ├── test_converterror.py ├── test_main.py ├── test_node.py ├── test_topology.py ├── test_utils.py ├── test_version.py └── topology.net /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | fixme: 4 | enabled: true 5 | radon: 6 | enabled: true 7 | ratings: 8 | paths: 9 | - "**.py" 10 | exclude_paths: 11 | - tests/**/* 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # PyCharm 56 | .idea 57 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: python 4 | 5 | python: 6 | - "3.3" 7 | - "3.4" 8 | 9 | install: 10 | - "pip install -r requirements.txt --use-mirrors" 11 | - "pip install -r dev-requirements.txt --use-mirrors" 12 | 13 | script: coverage run --source=gns3converter setup.py test 14 | 15 | after_success: coveralls 16 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 1.3.0 Julien Duponchelle 2 | 3 | * Fix error 1700 4 | * Warn when using ASA8 5 | * Support VLAN for dyanmips 6 | 7 | 8 | 1.2.4 Daniel Lintott 9 | 10 | * Fix error with adding slot0 on a C7200 11 | * Make cx_Freeze option (thanks to boenrobot) 12 | * Support border width (thanks to noplay) 13 | * Add missing C7200-io-FE (thanks to noplay) 14 | 15 | 1.2.3 Daniel Lintott 16 | 17 | * Ensure the rotation of a shape is defined as a float, 18 | rather than string (Fixes #23) 19 | 20 | 1.2.2 Daniel Lintott 21 | 22 | * Correct the path used for multi-host VPCS configs 23 | Thanks to rednectar for spotting this. 24 | * Fix running tests on Windows 25 | 26 | 1.2.1 Daniel Lintott 27 | 28 | * Fix copying of images when path in topology is relative (Fixes #22) 29 | * Copy *.png to the base directory of the new project if exists (Fixes #20) 30 | * Copy VPCS configs to the new multi-host vpcs directory (Fixes #21) 31 | * Fix shapes and text losing their rotation (Fixes #19) 32 | * Add default value for Qemu RAM 33 | 34 | 1.2.0 Daniel Lintott 35 | 36 | * Fix converting shapes that don't have fill_color specified, make them 37 | transparent (Fixes #18) 38 | * Copy instructions to new topology if they are present 39 | 40 | 1.1.1 Daniel Lintott 41 | 42 | * Fix error numbering of multiple serial ports (Fixes #15) 43 | 44 | 1.1.0 Daniel Lintott 45 | 46 | * Add new custom exception ConvertError 47 | * Raise a ConvertError if we can't get a snapshot name 48 | * Add quiet mode to prevent console output 49 | * Include mscvr100.dll when compiling for Windows 50 | 51 | 1.0.0 Daniel Lintott 52 | 53 | * Stable release version 1.0! 54 | * Split out parts of the save function to improve readability 55 | * Rework the passing of arguments to simplify integration into GNS3 56 | * Define ellipse and rectangle in shapes dict during init 57 | * Major improvements to the test-suite (still lots to be done) 58 | 59 | 0.5.0 Daniel Lintott 60 | 61 | * Correctly handle a cloud being in a topology but not connected 62 | * Correct WIC card numbering for routers. If multiple WICs were installed 63 | it would create duplicate port names. 64 | * Update the snapshot system based upon GNS3v1 snapshot system 65 | 66 | 0.4.0 Daniel Lintott 67 | 68 | * Implement support for converting QEMU Based Devices (Qemu VM, ASA, PIX, 69 | JUNOS, IDS) 70 | 71 | 0.3.5 Daniel Lintott 72 | 73 | * Convert Host symbol to computer symbol (Fixes #11) 74 | 75 | 0.3.4 Daniel Lintott 76 | 77 | * Force NIO connections to be lowercase (Fixes #10 again) 78 | 79 | 0.3.3 Daniel Lintott 80 | 81 | * Fix issue with cloud connections not connecting when more than one cloud 82 | is used in a topology (Fixes #10) 83 | 84 | 0.3.2 Daniel Lintott 85 | 86 | * Fix incorrect topology path when running on Windows (Fixes #9) 87 | 88 | 0.3.1 Daniel Lintott 89 | 90 | * Fix error when there are no VirtualBox nodes (Fixes #8) 91 | 92 | 0.3.0 Daniel Lintott 93 | 94 | * Implement snapshot support based upon legacy snapshot system. 95 | This will not currently work until we can confirm how snapshots will work 96 | in GNS3 v1.0 97 | * Change how we retrieve the version number for use in setup.py 98 | * Tidy up Legacy and JsonTopology classes 99 | * Add support for converting VirtualBox nodes 100 | * Ensure node labels are placed in the correct place 101 | 102 | 0.2.0 Daniel Lintott 103 | 104 | * Implement new JSONTopology class 105 | * Ignore NOTES that have an interface property (old interface labels) 106 | * Don't create the project in a subdirectory, get the topology name from 107 | the project directory and make the input file optional (Fixes #3) 108 | * Correct the path for the config files in the converted topology (Fixes #7) 109 | * Process frame relay interfaces and mappings (Fixes #6) 110 | * Improve error and debug printing 111 | * Convert hx and hy values for label positions 112 | 113 | 0.1.3 Daniel Lintott 114 | 115 | * Fix handling of NOTE text and color (Fixes #4) 116 | * Correct the logic for handling ConfigObj validation errors 117 | * Streamline the process of getting the topology sections 118 | 119 | 0.1.2 Daniel Lintott 120 | 121 | * Correctly handle Windows paths when working on Unix (Fixes #2) 122 | 123 | 0.1.1 Daniel Lintott 124 | 125 | * Correctly handle NOTE, SHAPE and PIXMAP objects (Fixes #1) 126 | 127 | 0.1.0 Daniel Lintott 128 | 129 | * Initial release of gns3-converter 130 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include gns3-converter.py COPYING README.rst ChangeLog -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | gns3-converter: GNS3 Topology Converter 2 | *************************************** 3 | 4 | The original code is from Daniel Lintott: 5 | https://github.com/dlintott/gns3-converter 6 | 7 | The GNS3 team forked it in order to update it for the last versions of GNS3. 8 | 9 | .. image:: https://img.shields.io/pypi/v/gns3-net-converter.svg 10 | :target: https://pypi.python.org/pypi/gns3-net-converter 11 | 12 | .. image:: https://img.shields.io/pypi/l/gns3-net-converter.svg 13 | :target: https://pypi.python.org/pypi/gns3-net-converter 14 | 15 | GNS3 Converter is designed to convert old ini-style GNS3 topologies (<=0.8.7) 16 | to the newer version v1+ JSON format for use in GNS3 v1+ 17 | 18 | The converter will convert all IOS, Cloud and VirtualBox devices to the new 19 | format. It will also convert all QEMU based devices (QEMU VM, ASA, PIX, JUNOS & 20 | IDS). VPCS nodes will be converted to cloud devices due to lack of information 21 | the 0.8.7 topology files. 22 | 23 | For topologies containing snapshots, the snapshots will also be converted to 24 | the new format automatically. 25 | 26 | Documentation 27 | ============= 28 | Current documentation for gns3-converter can be found here: 29 | http://gns3-converter.readthedocs.org/en/latest/ 30 | -------------------------------------------------------------------------------- /converter: -------------------------------------------------------------------------------- 1 | gns3converter/main.py -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | coveralls 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/gns3-converter.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/gns3-converter.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/gns3-converter" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/gns3-converter" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # gns3-converter documentation build configuration file, created by 5 | # sphinx-quickstart on Sat Aug 2 21:01:08 2014. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinxarg.ext', 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix of source filenames. 41 | source_suffix = '.rst' 42 | 43 | # The encoding of source files. 44 | #source_encoding = 'utf-8-sig' 45 | 46 | # The master toctree document. 47 | master_doc = 'index' 48 | 49 | # General information about the project. 50 | project = 'gns3-converter' 51 | copyright = '2014-2015, Daniel Lintott' 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = '1.3' 59 | # The full version, including alpha/beta/rc tags. 60 | release = '1.3.0' 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | #language = None 65 | 66 | # There are two options for replacing |today|: either, you set today to some 67 | # non-false value, then it is used: 68 | #today = '' 69 | # Else, today_fmt is used as the format for a strftime call. 70 | #today_fmt = '%B %d, %Y' 71 | 72 | # List of patterns, relative to source directory, that match files and 73 | # directories to ignore when looking for source files. 74 | exclude_patterns = ['_build'] 75 | 76 | # The reST default role (used for this markup: `text`) to use for all 77 | # documents. 78 | #default_role = None 79 | 80 | # If true, '()' will be appended to :func: etc. cross-reference text. 81 | #add_function_parentheses = True 82 | 83 | # If true, the current module name will be prepended to all description 84 | # unit titles (such as .. function::). 85 | #add_module_names = True 86 | 87 | # If true, sectionauthor and moduleauthor directives will be shown in the 88 | # output. They are ignored by default. 89 | #show_authors = False 90 | 91 | # The name of the Pygments (syntax highlighting) style to use. 92 | pygments_style = 'sphinx' 93 | 94 | # A list of ignored prefixes for module index sorting. 95 | #modindex_common_prefix = [] 96 | 97 | # If true, keep warnings as "system message" paragraphs in the built documents. 98 | #keep_warnings = False 99 | 100 | 101 | # -- Options for HTML output ---------------------------------------------- 102 | 103 | # The theme to use for HTML and HTML Help pages. See the documentation for 104 | # a list of builtin themes. 105 | #html_theme = 'nature' 106 | # on_rtd is whether we are on readthedocs.org 107 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 108 | 109 | if not on_rtd: # only import and set the theme if we're building docs locally 110 | import sphinx_rtd_theme 111 | html_theme = 'sphinx_rtd_theme' 112 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 113 | 114 | # otherwise, readthedocs.org uses their theme by default, so no need to 115 | # specify it 116 | 117 | # Theme options are theme-specific and customize the look and feel of a theme 118 | # further. For a list of options available for each theme, see the 119 | # documentation. 120 | #html_theme_options = {} 121 | 122 | # Add any paths that contain custom themes here, relative to this directory. 123 | #html_theme_path = [] 124 | 125 | # The name for this set of Sphinx documents. If None, it defaults to 126 | # " v documentation". 127 | #html_title = None 128 | 129 | # A shorter title for the navigation bar. Default is the same as html_title. 130 | #html_short_title = None 131 | 132 | # The name of an image file (relative to this directory) to place at the top 133 | # of the sidebar. 134 | #html_logo = None 135 | 136 | # The name of an image file (within the static path) to use as favicon of the 137 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 138 | # pixels large. 139 | #html_favicon = None 140 | 141 | # Add any paths that contain custom static files (such as style sheets) here, 142 | # relative to this directory. They are copied after the builtin static files, 143 | # so a file named "default.css" will overwrite the builtin "default.css". 144 | html_static_path = ['_static'] 145 | 146 | # Add any extra paths that contain custom files (such as robots.txt or 147 | # .htaccess) here, relative to this directory. These files are copied 148 | # directly to the root of the documentation. 149 | #html_extra_path = [] 150 | 151 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 152 | # using the given strftime format. 153 | #html_last_updated_fmt = '%b %d, %Y' 154 | 155 | # If true, SmartyPants will be used to convert quotes and dashes to 156 | # typographically correct entities. 157 | #html_use_smartypants = True 158 | 159 | # Custom sidebar templates, maps document names to template names. 160 | #html_sidebars = {} 161 | 162 | # Additional templates that should be rendered to pages, maps page names to 163 | # template names. 164 | #html_additional_pages = {} 165 | 166 | # If false, no module index is generated. 167 | #html_domain_indices = True 168 | 169 | # If false, no index is generated. 170 | html_use_index = False 171 | 172 | # If true, the index is split into individual pages for each letter. 173 | #html_split_index = False 174 | 175 | # If true, links to the reST sources are added to the pages. 176 | #html_show_sourcelink = True 177 | 178 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 179 | #html_show_sphinx = True 180 | 181 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 182 | #html_show_copyright = True 183 | 184 | # If true, an OpenSearch description file will be output, and all pages will 185 | # contain a tag referring to it. The value of this option must be the 186 | # base URL from which the finished HTML is served. 187 | #html_use_opensearch = '' 188 | 189 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 190 | #html_file_suffix = None 191 | 192 | # Output file base name for HTML help builder. 193 | htmlhelp_basename = 'gns3-converterdoc' 194 | 195 | 196 | # -- Options for LaTeX output --------------------------------------------- 197 | 198 | latex_elements = { 199 | # The paper size ('letterpaper' or 'a4paper'). 200 | #'papersize': 'letterpaper', 201 | 202 | # The font size ('10pt', '11pt' or '12pt'). 203 | #'pointsize': '10pt', 204 | 205 | # Additional stuff for the LaTeX preamble. 206 | #'preamble': '', 207 | } 208 | 209 | # Grouping the document tree into LaTeX files. List of tuples 210 | # (source start file, target name, title, 211 | # author, documentclass [howto, manual, or own class]). 212 | latex_documents = [ 213 | ('index', 'gns3-converter.tex', 'gns3-converter Documentation', 214 | 'Daniel Lintott', 'manual'), 215 | ] 216 | 217 | # The name of an image file (relative to this directory) to place at the top of 218 | # the title page. 219 | #latex_logo = None 220 | 221 | # For "manual" documents, if this is true, then toplevel headings are parts, 222 | # not chapters. 223 | #latex_use_parts = False 224 | 225 | # If true, show page references after internal links. 226 | #latex_show_pagerefs = False 227 | 228 | # If true, show URL addresses after external links. 229 | #latex_show_urls = False 230 | 231 | # Documents to append as an appendix to all manuals. 232 | #latex_appendices = [] 233 | 234 | # If false, no module index is generated. 235 | #latex_domain_indices = True 236 | 237 | 238 | # -- Options for manual page output --------------------------------------- 239 | 240 | # One entry per manual page. List of tuples 241 | # (source start file, name, description, authors, manual section). 242 | man_pages = [ 243 | ('index', 'gns3-converter', 'gns3-converter Documentation', 244 | ['Daniel Lintott'], 1) 245 | ] 246 | 247 | # If true, show URL addresses after external links. 248 | #man_show_urls = False 249 | 250 | 251 | # -- Options for Texinfo output ------------------------------------------- 252 | 253 | # Grouping the document tree into Texinfo files. List of tuples 254 | # (source start file, target name, title, author, 255 | # dir menu entry, description, category) 256 | texinfo_documents = [ 257 | ('index', 'gns3-converter', 'gns3-converter Documentation', 258 | 'Daniel Lintott', 'gns3-converter', 'One line description of project.', 259 | 'Miscellaneous'), 260 | ] 261 | 262 | # Documents to append as an appendix to all manuals. 263 | #texinfo_appendices = [] 264 | 265 | # If false, no module index is generated. 266 | #texinfo_domain_indices = True 267 | 268 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 269 | #texinfo_show_urls = 'footnote' 270 | 271 | # If true, do not generate a @detailmenu in the "Top" node's menu. 272 | #texinfo_no_detailmenu = False 273 | -------------------------------------------------------------------------------- /docs/gns3converter.adapters.rst: -------------------------------------------------------------------------------- 1 | gns3converter.adapters 2 | ====================== 3 | 4 | .. automodule:: gns3converter.adapters 5 | :members: 6 | :show-inheritance: -------------------------------------------------------------------------------- /docs/gns3converter.converter.rst: -------------------------------------------------------------------------------- 1 | gns3converter.converter 2 | ======================= 3 | 4 | .. automodule:: gns3converter.converter 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/gns3converter.interfaces.rst: -------------------------------------------------------------------------------- 1 | gns3converter.interfaces 2 | ======================== 3 | 4 | .. automodule:: gns3converter.interfaces 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/gns3converter.main.rst: -------------------------------------------------------------------------------- 1 | gns3converter.main 2 | ================== 3 | 4 | .. automodule:: gns3converter.main 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/gns3converter.models.rst: -------------------------------------------------------------------------------- 1 | gns3converter.models 2 | ==================== 3 | 4 | .. automodule:: gns3converter.models 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/gns3converter.node.rst: -------------------------------------------------------------------------------- 1 | gns3converter.node 2 | ================== 3 | 4 | .. automodule:: gns3converter.node 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/gns3converter.rst: -------------------------------------------------------------------------------- 1 | gns3converter modules 2 | ===================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | gns3converter.adapters 8 | gns3converter.converter 9 | gns3converter.interfaces 10 | gns3converter.main 11 | gns3converter.models 12 | gns3converter.node 13 | gns3converter.topology 14 | gns3converter.utils -------------------------------------------------------------------------------- /docs/gns3converter.topology.rst: -------------------------------------------------------------------------------- 1 | gns3converter.topology 2 | ====================== 3 | 4 | .. automodule:: gns3converter.topology 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/gns3converter.utils.rst: -------------------------------------------------------------------------------- 1 | gns3converter.utils 2 | =================== 3 | 4 | .. automodule:: gns3converter.utils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to gns3-converter's documentation! 2 | ========================================== 3 | 4 | GNS3 Converter is designed to convert old ini-style GNS3 topologies (<=0.8.7) 5 | to the newer version v1+ JSON format for use in GNS3 v1+ 6 | 7 | The converter will convert all IOS, Cloud and VirtualBox devices to the new 8 | format. It will also convert all QEMU based devices (QEMU VM, ASA, PIX, JUNOS & 9 | IDS). VPCS nodes will be converted to cloud devices due to lack of information 10 | the 0.8.7 topology files. 11 | 12 | For topologies containing snapshots, the snapshots will also be converted to 13 | the new format automatically. 14 | 15 | Contents: 16 | 17 | .. toctree:: 18 | :maxdepth: 2 19 | 20 | installation 21 | usage 22 | gns3converter 23 | 24 | Development 25 | =========== 26 | If you find a bug in gns3-converter please feel free to report it to the issue 27 | tracker listed below. If the problem occurs with a particular topology, please 28 | include the topology with the issue report. 29 | 30 | * Public Repository: https://github.com/dlintott/gns3-converter 31 | * Issue Tracker: https://github.com/dlintott/gns3-converter/issues 32 | * License: GPL-3+ 33 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ************ 3 | 4 | Linux 5 | ===== 6 | Requirements 7 | ------------ 8 | 9 | - Python 3.3+ 10 | - ConfigObj 11 | 12 | Instructions 13 | ------------ 14 | On linux gns3-converter can be installed using pip. Simply type: 15 | 16 | :: 17 | 18 | pip install gns3-converter 19 | 20 | or easy_install: 21 | 22 | :: 23 | 24 | easy_install gns3-converter 25 | 26 | alternatively you can manually install gns3-converter, by downloading the 27 | source from http://pypi.python.org/pypi/gns3-converter (you'll need to also 28 | install ConfigObj): 29 | 30 | :: 31 | 32 | python setup.py install 33 | 34 | Windows 35 | ======= 36 | Instructions 37 | ------------ 38 | On windows you can install gns3-converter using the installer provided at: 39 | https://github.com/dlintott/gns3-converter/releases 40 | 41 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | configobj 2 | sphinx-argparse -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | Using gns3-converter 2 | ******************** 3 | 4 | .. argparse:: 5 | :module: gns3converter.main 6 | :func: setup_argparse 7 | :prog: gns3-converter 8 | 9 | Example 10 | ======= 11 | By default the converted topology will be output to the current working 12 | directory. 13 | 14 | To convert a topology from the folder containing the topology.net file just 15 | type: 16 | 17 | :: 18 | 19 | gns3-converter 20 | 21 | Alternatively you can specify a topology file to convert on the command line: 22 | 23 | :: 24 | 25 | gns3-converter ~/GNS3/Projects/CCNA_1/topology.net 26 | 27 | If the relevant configs are also present alongside the topology file these will 28 | be copied to the new topology and renamed accordingly. 29 | 30 | If you wish to output the converted topology to a different destination this 31 | can be done using the -o or --output argument like this: 32 | 33 | :: 34 | 35 | gns3-converter -o ../output 36 | 37 | or 38 | 39 | :: 40 | 41 | gns3-converter --output ../output 42 | 43 | The name of the converted topology is taken from the folder containing the 44 | topology file. For example a topology in ~/GNS3/Projects/CCNA_1/topology.net 45 | will be named CCNA_1. 46 | 47 | It is also possible to specify a name for the new topology using the -n or 48 | --name in the same way as specifying the output directory. -------------------------------------------------------------------------------- /gns3-converter.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | from gns3converter.main import main 16 | 17 | main() 18 | -------------------------------------------------------------------------------- /gns3converter/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | __version__ = '1.3.0' 16 | -------------------------------------------------------------------------------- /gns3converter/adapters.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | """ 16 | Convenience module for adapters containing: 17 | * Adapter and port number/type matrix 18 | * Port type conversions (short to long) 19 | """ 20 | 21 | # Adapter Cards Matrix 22 | ADAPTER_MATRIX = {'NM-16ESW': {'ports': 16, 'type': 'F'}, 23 | 'NM-1E': {'ports': 1, 'type': 'E'}, 24 | 'NM-1FE-TX': {'ports': 1, 'type': 'F'}, 25 | 'NM-4E': {'ports': 4, 'type': 'E'}, 26 | 'NM-4T': {'ports': 4, 'type': 'S'}, 27 | 'PA-2FE-TX': {'ports': 2, 'type': 'F'}, 28 | 'PA-4E': {'ports': 4, 'type': 'E'}, 29 | 'PA-4T+': {'ports': 4, 'type': 'S'}, 30 | 'PA-8E': {'ports': 8, 'type': 'E'}, 31 | 'PA-8T': {'ports': 8, 'type': 'S'}, 32 | 'PA-A1': {'ports': 1, 'type': 'A'}, 33 | 'PA-FE-TX': {'ports': 1, 'type': 'F'}, 34 | 'PA-GE': {'ports': 1, 'type': 'G'}, 35 | 'PA-POS-OC3': {'ports': 1, 'type': 'P'}, 36 | 'C7200-IO-2FE': {'ports': 2, 'type': 'F'}, 37 | 'C7200-IO-FE': {'ports': 1, 'type': 'F'}, 38 | 'C7200-IO-GE-E': {'ports': 1, 'type': 'G'}, 39 | 'WIC-1ENET': {'ports': 1, 'type': 'E'}, 40 | 'WIC-1T': {'ports': 1, 'type': 'S'}, 41 | 'WIC-2T': {'ports': 2, 'type': 'S'}} 42 | 43 | # Port Type Matrix 44 | PORT_TYPES = {'G': 'GigabitEthernet', 45 | 'F': 'FastEthernet', 46 | 'E': 'Ethernet', 47 | 'S': 'Serial', 48 | 'A': 'ATM', 49 | 'P': 'POS'} 50 | -------------------------------------------------------------------------------- /gns3converter/configspec: -------------------------------------------------------------------------------- 1 | autostart = boolean(default=True) 2 | model = option('1710', '1720', '1721', '1750', '1751', '1760', '2610', '2611', '2620', '2621', '2610XM', '2611XM', '2620XM', '2621XM', '2650XM', '2651XM', '2691', '3725', '3745', '3620', '3640', '3660', '7200', default='7200') 3 | ghostios = boolean(default=False) 4 | ghostsize = integer(min=1, default=None) 5 | jitsharing = boolean(default=False) 6 | sparsemem = boolean(default=False) 7 | idlemax = integer(min=1, default=None) 8 | idlesleep = integer(min=1, default=None) 9 | oldidle = boolean(default=False) 10 | debug = integer(min=0, max=9, default=0) 11 | version = string(default=None) 12 | 13 | [__many__] 14 | port = integer(min=1, max=65535, default=None) 15 | workingdir = string(default=None) 16 | qemupath = string(default=None) 17 | qemuimgpath = string(default=None) 18 | console = integer(min=1, max=65535, default=None) 19 | aux = integer(min=1, max=65535, default=None) 20 | udp = integer(min=1, max=65535, default=None) 21 | 22 | [[__many__]] 23 | model = option('1710', '1720', '1721', '1750', '1751', '1760', '2610', '2611', '2620', '2621', '2610XM', '2611XM', '2620XM', '2621XM', '2650XM', '2651XM', '2691', '3725', '3745', '3620', '3640', '3660', '7200', default=None) 24 | console = integer(min=1, max=65535, default=None) 25 | aux = integer(min=1, max=65535, default=None) 26 | mac = string(default=None) 27 | image = string(default=None) 28 | image1 = string(default=None) 29 | image2 = string(default=None) 30 | ram = integer(min=0, default=None) 31 | nvram = integer(min=0, default=None) 32 | cnfg = string(default=None) 33 | confreg = string(default=None) 34 | idlepc = string(default=None) 35 | exec_area = string(default=None) 36 | clock = integer(min=0, default=None) 37 | npe = option('npe-100', 'npe-150', 'npe-175', 'npe-200', 'npe-225', 'npe-300', 'npe-400', 'npe-g1', 'npe-g2', default=None) 38 | midplane = option('std', 'vxr', default=None) 39 | disk0 = integer(min=0, default=None) 40 | disk1 = integer(min=0, default=None) 41 | mmap = boolean(default=None) 42 | ghostios = boolean(default=None) 43 | ghostsize = integer(min=1, default=None) 44 | jitsharing = boolean(default=None) 45 | sparsemem = boolean(default=None) 46 | autostart = boolean(default=None) 47 | configuration = string(default=None) 48 | idlemax = integer(min=1, default=None) 49 | idlesleep = integer(min=1, default=None) 50 | oldidle = boolean(default=None) 51 | rom = integer(min=0, default=None) 52 | x = float(default=None) 53 | y = float(default=None) 54 | z = float(default=None) 55 | hx = float(default=None) 56 | hy = float(default=None) 57 | key = string(default=None) 58 | serial = string(default=None) 59 | initrd = string(default=None) 60 | kernel = string(default=None) 61 | kernel_cmdline = string(default=None) 62 | options = string(default=None) 63 | nics = integer(min=0, default=None) 64 | netcard = string(default=None) 65 | kvm = boolean(default=None) 66 | usermod = boolean(default=None) 67 | monitor = boolean(default=None) 68 | guestcontrol_user = string(default=None) 69 | guestcontrol_password = string(default=None) 70 | first_nic_managed = boolean(default=None) 71 | headless_mode = boolean(default=None) 72 | console_support = boolean(default=None) 73 | console_telnet_server = boolean(default=None) 74 | symbol = string(default=None) 75 | width = float(default=None) 76 | height = float(default=None) 77 | border_style = integer(min=0, default=None) 78 | border_width = integer(min=0, default=None) 79 | color = string(default=None) 80 | text = string(default=None) 81 | -------------------------------------------------------------------------------- /gns3converter/converter.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | """ 16 | This class is the main gns3-converter class 17 | """ 18 | from configobj import ConfigObj, flatten_errors 19 | from validate import Validator 20 | import sys 21 | import os.path 22 | import logging 23 | from pkg_resources import resource_stream 24 | from gns3converter.adapters import PORT_TYPES 25 | from gns3converter.models import MODEL_TRANSFORM, EXTRA_CONF 26 | from gns3converter.node import Node 27 | from gns3converter.interfaces import INTERFACE_RE, VBQ_INT_RE 28 | from gns3converter.topology import LegacyTopology 29 | from gns3converter.utils import fix_path 30 | 31 | 32 | class Converter(object): 33 | """ 34 | GNS3 Topology Converter Class 35 | 36 | :param str topology: Filename of the ini-style topology 37 | :param bool debug: enable debugging (Default: False) 38 | """ 39 | def __init__(self, topology, debug=False): 40 | self._topology = topology 41 | self._debug = debug 42 | 43 | self.port_id = 1 44 | self.links = [] 45 | self.configs = [] 46 | self.datas = [] 47 | self.images = [] 48 | 49 | logging.getLogger(__name__) 50 | logging.debug('Topology file: {}'.format(self._topology)) 51 | 52 | @property 53 | def topology(self): 54 | """ 55 | Return the topology filename the converter is working on 56 | 57 | :return: topology filename 58 | :rtype: str 59 | """ 60 | return self._topology 61 | 62 | def read_topology(self): 63 | """ 64 | Read the ini-style topology file using ConfigObj 65 | 66 | :return config: Topology parsed by :py:mod:`ConfigObj` 67 | :rtype: ConfigObj 68 | """ 69 | configspec = resource_stream(__name__, 'configspec') 70 | try: 71 | handle = open(self._topology) 72 | handle.close() 73 | try: 74 | config = ConfigObj(self._topology, 75 | configspec=configspec, 76 | raise_errors=True, 77 | list_values=False, 78 | encoding='utf-8') 79 | except SyntaxError: 80 | logging.error('Error loading .net file') 81 | sys.exit(1) 82 | except IOError: 83 | logging.error('Cannot open topology file') 84 | sys.exit(1) 85 | 86 | vtor = Validator() 87 | res = config.validate(vtor, preserve_errors=True) 88 | if res: 89 | logging.debug('Validation passed') 90 | elif not res: 91 | for entry in flatten_errors(config, res): 92 | # each entry is a tuple 93 | (section_list, key, error) = entry 94 | if key is not None: 95 | section_list.append(key) 96 | else: 97 | section_list.append('[missing section]') 98 | section_string = ', '.join(section_list) 99 | 100 | if error is False: 101 | error = 'Missing value or section' 102 | print(section_string, ' = ', error) 103 | input('Press ENTER to continue') 104 | sys.exit(1) 105 | 106 | configspec.close() 107 | return config 108 | 109 | def process_topology(self, old_top): 110 | """ 111 | Processes the sections returned by get_instances 112 | 113 | :param ConfigObj old_top: old topology as processed by 114 | :py:meth:`read_topology` 115 | :returns: tuple of dicts containing hypervisors, devices and artwork 116 | :rtype: tuple 117 | """ 118 | sections = self.get_sections(old_top) 119 | topo = LegacyTopology(sections, old_top) 120 | 121 | for instance in sorted(sections): 122 | if instance.startswith('vbox') or instance.startswith('qemu'): 123 | 124 | if instance.startswith('qemu') and \ 125 | 'qemupath' in old_top[instance]: 126 | topo.add_qemu_path(instance) 127 | 128 | for device in EXTRA_CONF: 129 | try: 130 | if isinstance(old_top[instance][device], dict): 131 | topo.add_conf_item(instance, device) 132 | old_top[instance].pop(device) 133 | except KeyError: 134 | pass 135 | 136 | for item in sorted(old_top[instance]): 137 | if isinstance(old_top[instance][item], dict): 138 | if item in MODEL_TRANSFORM: 139 | # A configuration item (topo.conf) 140 | topo.add_conf_item(instance, item) 141 | elif instance == 'GNS3-DATA' and \ 142 | (item.startswith('SHAPE') 143 | or item.startswith('NOTE') 144 | or item.startswith('PIXMAP')): 145 | # Item is an artwork item e.g. shapes and notes from 146 | # GNS3-DATA 147 | topo.add_artwork_item(instance, item) 148 | else: 149 | # It must be a physical item (topo.devices) 150 | topo.add_physical_item(instance, item) 151 | return topo.topology 152 | 153 | @staticmethod 154 | def get_sections(config): 155 | """ 156 | Get a list of Hypervisor instances 157 | 158 | :param ConfigObj config: Configuration from :py:meth:`read_topology` 159 | :return: configuration sections 160 | :rtype: list 161 | """ 162 | return config.sections 163 | 164 | def generate_nodes(self, topology): 165 | """ 166 | Generate a list of nodes for the new topology 167 | 168 | :param dict topology: processed topology from 169 | :py:meth:`process_topology` 170 | :return: a list of dicts on nodes 171 | :rtype: list 172 | """ 173 | nodes = [] 174 | 175 | devices = topology['devices'] 176 | hypervisors = topology['conf'] 177 | 178 | for device in sorted(devices): 179 | hv_id = devices[device]['hv_id'] 180 | try: 181 | tmp_node = Node(hypervisors[hv_id], self.port_id) 182 | except IndexError: 183 | tmp_node = Node({}, self.port_id) 184 | # Start building the structure 185 | tmp_node.node['properties']['name'] = device 186 | tmp_node.node['id'] = devices[device]['node_id'] 187 | tmp_node.node['x'] = devices[device]['x'] 188 | tmp_node.node['y'] = devices[device]['y'] 189 | tmp_node.device_info['from'] = devices[device]['from'] 190 | tmp_node.device_info['type'] = devices[device]['type'] 191 | tmp_node.device_info['desc'] = devices[device]['desc'] 192 | 193 | if 'ext_conf' in devices[device]: 194 | tmp_node.device_info['ext_conf'] = devices[device]['ext_conf'] 195 | 196 | # Node Label 197 | tmp_node.node['label']['text'] = device 198 | if 'hx' in devices[device] and 'hy' in devices[device]: 199 | tmp_node.node['label']['x'] = devices[device]['hx'] 200 | tmp_node.node['label']['y'] = devices[device]['hy'] 201 | 202 | if 'model' in devices[device]: 203 | tmp_node.device_info['model'] = devices[device]['model'] 204 | else: 205 | tmp_node.device_info['model'] = '' 206 | 207 | tmp_node.set_description() 208 | tmp_node.set_type() 209 | 210 | # Now lets process the rest 211 | for item in sorted(devices[device]): 212 | tmp_node.add_device_items(item, devices[device]) 213 | 214 | if tmp_node.device_info['type'] == 'Router': 215 | tmp_node.add_info_from_hv() 216 | tmp_node.node['router_id'] = devices[device]['node_id'] 217 | tmp_node.calc_mb_ports() 218 | 219 | for item in sorted(tmp_node.node['properties']): 220 | if item.startswith('slot'): 221 | tmp_node.add_slot_ports(item) 222 | elif item.startswith('wic'): 223 | tmp_node.add_wic_ports(item) 224 | 225 | # Add default ports to 7200 and 3660 226 | if tmp_node.device_info['model'] == 'c7200': 227 | # tmp_node.add_slot_ports('slot0') 228 | # C7200 doesnt have any ports by default 229 | pass 230 | elif tmp_node.device_info['model'] == 'c3600' \ 231 | and tmp_node.device_info['chassis'] == '3660': 232 | tmp_node.node['properties']['slot0'] = 'Leopard-2FE' 233 | 234 | for name in ['rom', 'nvram', 'bootflash', 'disk0', 'disk1', 'slot0', 'slot1']: 235 | self.datas.append({ 236 | 'old': os.path.join('working', tmp_node.device_info['model'] + '_' + device + '_' + name), 237 | 'new': tmp_node.device_info['model'] + '_i' + str(tmp_node.node['router_id']) + '_' + name 238 | }) 239 | 240 | # Calculate the router links 241 | tmp_node.calc_device_links() 242 | 243 | elif tmp_node.device_info['type'] == 'Cloud': 244 | try: 245 | tmp_node.calc_cloud_connection() 246 | except RuntimeError as err: 247 | print(err) 248 | 249 | elif tmp_node.device_info['type'] == 'FrameRelaySwitch': 250 | tmp_node.process_mappings() 251 | 252 | elif tmp_node.device_info['type'] == 'VirtualBoxVM': 253 | tmp_node.add_to_virtualbox() 254 | tmp_node.add_vm_ethernet_ports() 255 | tmp_node.calc_device_links() 256 | 257 | elif tmp_node.device_info['type'] == 'QemuVM': 258 | tmp_node.add_to_qemu() 259 | tmp_node.set_qemu_symbol() 260 | tmp_node.add_vm_ethernet_ports() 261 | tmp_node.calc_device_links() 262 | 263 | # Get the data we need back from the node instance 264 | self.links.extend(tmp_node.links) 265 | self.configs.extend(tmp_node.config) 266 | self.port_id += tmp_node.get_nb_added_ports(self.port_id) 267 | 268 | nodes.append(tmp_node.node) 269 | 270 | return nodes 271 | 272 | def generate_links(self, nodes): 273 | """ 274 | Generate a list of links 275 | 276 | :param list nodes: A list of nodes from :py:meth:`generate_nodes` 277 | :return: list of links 278 | :rtype: list 279 | """ 280 | new_links = [] 281 | 282 | for link in self.links: 283 | # Expand port name if required 284 | if INTERFACE_RE.search(link['dest_port'])\ 285 | or VBQ_INT_RE.search(link['dest_port']): 286 | int_type = link['dest_port'][0] 287 | dest_port = link['dest_port'].replace( 288 | int_type, PORT_TYPES[int_type.upper()]) 289 | else: 290 | dest_port = link['dest_port'] 291 | 292 | # Convert dest_dev and port to id's 293 | dest_details = self.convert_destination_to_id( 294 | link['dest_dev'], dest_port, nodes) 295 | 296 | desc = 'Link from %s port %s to %s port %s' % \ 297 | (link['source_dev'], link['source_port_name'], 298 | dest_details['name'], dest_port) 299 | 300 | new_links.append({'description': desc, 301 | 'destination_node_id': dest_details['id'], 302 | 'destination_port_id': dest_details['pid'], 303 | 'source_port_id': link['source_port_id'], 304 | 'source_node_id': link['source_node_id']}) 305 | 306 | # Remove duplicate links and add link_id 307 | link_id = 1 308 | for link in new_links: 309 | t_link = str(link['source_node_id']) + ':' + \ 310 | str(link['source_port_id']) 311 | for link2 in new_links: 312 | d_link = str(link2['destination_node_id']) + ':' + \ 313 | str(link2['destination_port_id']) 314 | if t_link == d_link: 315 | new_links.remove(link2) 316 | break 317 | link['id'] = link_id 318 | link_id += 1 319 | 320 | self.add_node_connection(link, nodes) 321 | 322 | return new_links 323 | 324 | @staticmethod 325 | def device_id_from_name(device_name, nodes): 326 | """ 327 | Get the device ID when given a device name 328 | 329 | :param str device_name: device name 330 | :param list nodes: list of nodes from :py:meth:`generate_nodes` 331 | :return: device ID 332 | :rtype: int 333 | """ 334 | device_id = None 335 | for node in nodes: 336 | if device_name == node['properties']['name']: 337 | device_id = node['id'] 338 | break 339 | return device_id 340 | 341 | @staticmethod 342 | def port_id_from_name(port_name, device_id, nodes): 343 | """ 344 | Get the port ID when given a port name 345 | 346 | :param str port_name: port name 347 | :param str device_id: device ID 348 | :param list nodes: list of nodes from :py:meth:`generate_nodes` 349 | :return: port ID 350 | :rtype: int 351 | """ 352 | port_id = None 353 | for node in nodes: 354 | if device_id == node['id']: 355 | for port in node['ports']: 356 | if port_name == port['name']: 357 | port_id = port['id'] 358 | break 359 | break 360 | return port_id 361 | 362 | @staticmethod 363 | def convert_destination_to_id(destination_node, destination_port, nodes): 364 | """ 365 | Convert a destination to device and port ID 366 | 367 | :param str destination_node: Destination node name 368 | :param str destination_port: Destination port name 369 | :param list nodes: list of nodes from :py:meth:`generate_nodes` 370 | :return: dict containing device ID, device name and port ID 371 | :rtype: dict 372 | """ 373 | device_id = None 374 | device_name = None 375 | port_id = None 376 | if destination_node != 'NIO': 377 | for node in nodes: 378 | if destination_node == node['properties']['name']: 379 | device_id = node['id'] 380 | device_name = destination_node 381 | for port in node['ports']: 382 | if destination_port == port['name']: 383 | port_id = port['id'] 384 | break 385 | break 386 | else: 387 | for node in nodes: 388 | if node['type'] == 'Cloud': 389 | for port in node['ports']: 390 | if destination_port.lower() == port['name'].lower(): 391 | device_id = node['id'] 392 | device_name = node['properties']['name'] 393 | port_id = port['id'] 394 | break 395 | 396 | info = {'id': device_id, 397 | 'name': device_name, 398 | 'pid': port_id} 399 | return info 400 | 401 | @staticmethod 402 | def get_node_name_from_id(node_id, nodes): 403 | """ 404 | Get the name of a node when given the node_id 405 | 406 | :param int node_id: The ID of a node 407 | :param list nodes: list of nodes from :py:meth:`generate_nodes` 408 | :return: node name 409 | :rtype: str 410 | """ 411 | node_name = '' 412 | for node in nodes: 413 | if node['id'] == node_id: 414 | node_name = node['properties']['name'] 415 | break 416 | return node_name 417 | 418 | @staticmethod 419 | def get_port_name_from_id(node_id, port_id, nodes): 420 | """ 421 | Get the name of a port for a given node and port ID 422 | 423 | :param int node_id: node ID 424 | :param int port_id: port ID 425 | :param list nodes: list of nodes from :py:meth:`generate_nodes` 426 | :return: port name 427 | :rtype: str 428 | """ 429 | port_name = '' 430 | for node in nodes: 431 | if node['id'] == node_id: 432 | for port in node['ports']: 433 | if port['id'] == port_id: 434 | port_name = port['name'] 435 | break 436 | return port_name 437 | 438 | def add_node_connection(self, link, nodes): 439 | """ 440 | Add a connection to a node 441 | 442 | :param dict link: link definition 443 | :param list nodes: list of nodes from :py:meth:`generate_nodes` 444 | """ 445 | # Description 446 | src_desc = 'connected to %s on port %s' % \ 447 | (self.get_node_name_from_id(link['destination_node_id'], 448 | nodes), 449 | self.get_port_name_from_id(link['destination_node_id'], 450 | link['destination_port_id'], 451 | nodes)) 452 | dest_desc = 'connected to %s on port %s' % \ 453 | (self.get_node_name_from_id(link['source_node_id'], 454 | nodes), 455 | self.get_port_name_from_id(link['source_node_id'], 456 | link['source_port_id'], 457 | nodes)) 458 | # Add source connections 459 | for node in nodes: 460 | if node['id'] == link['source_node_id']: 461 | for port in node['ports']: 462 | if port['id'] == link['source_port_id']: 463 | port['link_id'] = link['id'] 464 | port['description'] = src_desc 465 | break 466 | elif node['id'] == link['destination_node_id']: 467 | for port in node['ports']: 468 | if port['id'] == link['destination_port_id']: 469 | port['link_id'] = link['id'] 470 | port['description'] = dest_desc 471 | break 472 | 473 | @staticmethod 474 | def generate_shapes(shapes): 475 | """ 476 | Generate the shapes for the topology 477 | 478 | :param dict shapes: A dict of converted shapes from the old topology 479 | :return: dict containing two lists (ellipse, rectangle) 480 | :rtype: dict 481 | """ 482 | new_shapes = {'ellipse': [], 'rectangle': []} 483 | 484 | for shape in shapes: 485 | tmp_shape = {} 486 | for shape_item in shapes[shape]: 487 | if shape_item != 'type': 488 | tmp_shape[shape_item] = shapes[shape][shape_item] 489 | 490 | new_shapes[shapes[shape]['type']].append(tmp_shape) 491 | 492 | return new_shapes 493 | 494 | @staticmethod 495 | def generate_notes(notes): 496 | """ 497 | Generate the notes list 498 | 499 | :param dict notes: A dict of converted notes from the old topology 500 | :return: List of notes for the the topology 501 | :rtype: list 502 | """ 503 | new_notes = [] 504 | 505 | for note in notes: 506 | tmp_note = {} 507 | for note_item in notes[note]: 508 | tmp_note[note_item] = notes[note][note_item] 509 | 510 | new_notes.append(tmp_note) 511 | 512 | return new_notes 513 | 514 | def generate_images(self, pixmaps): 515 | """ 516 | Generate the images list and store the images to copy 517 | 518 | :param dict pixmaps: A dict of converted pixmaps from the old topology 519 | :return: A list of images 520 | :rtype: list 521 | """ 522 | new_images = [] 523 | 524 | for image in pixmaps: 525 | tmp_image = {} 526 | for img_item in pixmaps[image]: 527 | if img_item == 'path': 528 | path = os.path.join('images', 529 | os.path.basename( 530 | pixmaps[image][img_item])) 531 | tmp_image['path'] = fix_path(path) 532 | self.images.append(pixmaps[image][img_item]) 533 | else: 534 | tmp_image[img_item] = pixmaps[image][img_item] 535 | 536 | new_images.append(tmp_image) 537 | 538 | return new_images 539 | -------------------------------------------------------------------------------- /gns3converter/converterror.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | """ 16 | Custom exception for gns3-converter. 17 | """ 18 | 19 | 20 | class ConvertError(Exception): 21 | def __init__(self, message, original_exception=None): 22 | Exception.__init__(self, message) 23 | self._message = message 24 | self._original_exception = original_exception 25 | 26 | def __repr__(self): 27 | return self._message 28 | 29 | def __str__(self): 30 | return self._message 31 | -------------------------------------------------------------------------------- /gns3converter/interfaces.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | """ 16 | Anything to do with interfaces, also contains: 17 | * INTERFACE_RE for matching interfaces in a .net topology 18 | * ETHSWINT_RE for matching Ethernet switch port in a .net topology 19 | """ 20 | import re 21 | 22 | # Regex matching interfaces (e.g. "f0/0") 23 | INTERFACE_RE = re.compile(r"""^(g|gi|f|fa|a|at|s|se|e|et|p|po|i|id|IDS-Sensor 24 | |an|Analysis-Module)([0-9]+)/([0-9]+)""", re.IGNORECASE) 25 | # Regex matching a number 26 | NUMBER_RE = re.compile(r"""^[0-9]+$""") 27 | # Regex matching a frame relay mapping 28 | MAPINT_RE = re.compile(r"""^[0-9]+:[0-9]+$""") 29 | # Regex matching VirtualBox or QEMU interfaces 30 | VBQ_INT_RE = re.compile(r"""^(e|et|eth)([0-9])""", re.IGNORECASE) 31 | 32 | 33 | class Interfaces(object): 34 | """ 35 | Base Interface Class 36 | 37 | :param int port_id: starting port ID 38 | """ 39 | def __init__(self, port_id): 40 | self.interfaces = [] 41 | self.links = [] 42 | self.port_id = port_id 43 | self.connections = None 44 | self.mappings = [] 45 | self.port_numbering = {'G': 0, # GigabitEthernet 46 | 'F': 0, # FastEthernet 47 | 'E': 0, # Ethernet 48 | 'S': 0, # Serial 49 | 'A': 0, # ATM 50 | 'P': 0} # POS 51 | -------------------------------------------------------------------------------- /gns3converter/main.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | import json 16 | import os 17 | import shutil 18 | import argparse 19 | import logging 20 | import re 21 | import glob 22 | from gns3converter import __version__ 23 | from gns3converter.converter import Converter 24 | from gns3converter.converterror import ConvertError 25 | from gns3converter.topology import JSONTopology 26 | 27 | LOG_MSG_FMT = '[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d] ' \ 28 | '%(message)s' 29 | LOG_DATE_FMT = '%y%m%d %H:%M:%S' 30 | 31 | 32 | def main(): 33 | """ 34 | Entry point for gns3-converter 35 | """ 36 | arg_parse = setup_argparse() 37 | args = arg_parse.parse_args() 38 | 39 | if not args.quiet: 40 | print('GNS3 Topology Converter') 41 | 42 | if args.debug: 43 | logging_level = logging.DEBUG 44 | else: 45 | logging_level = logging.WARNING 46 | 47 | logging.basicConfig(level=logging_level, 48 | format=LOG_MSG_FMT, datefmt=LOG_DATE_FMT) 49 | 50 | logging.getLogger(__name__) 51 | 52 | # Add the main topology to the list of files to convert 53 | if args.topology == 'topology.net': 54 | args.topology = os.path.join(os.getcwd(), 'topology.net') 55 | 56 | topology_files = [{'file': topology_abspath(args.topology), 57 | 'snapshot': False}] 58 | 59 | # Add any snapshot topologies to be converted 60 | topology_files.extend(get_snapshots(args.topology)) 61 | 62 | topology_name = name(args.topology, args.name) 63 | 64 | # Do the conversion 65 | for topology in topology_files: 66 | do_conversion(topology, topology_name, args.output, args.debug) 67 | 68 | 69 | def setup_argparse(): 70 | """ 71 | Setup the argparse argument parser 72 | 73 | :return: instance of argparse 74 | :rtype: ArgumentParser 75 | """ 76 | parser = argparse.ArgumentParser( 77 | description='Convert old ini-style GNS3 topologies (<=0.8.7) to ' 78 | 'the newer version 1+ JSON format') 79 | parser.add_argument('--version', 80 | action='version', 81 | version='%(prog)s ' + __version__) 82 | parser.add_argument('-n', '--name', help='Topology name (default uses the ' 83 | 'name of the old project ' 84 | 'directory)') 85 | parser.add_argument('-o', '--output', help='Output directory') 86 | parser.add_argument('topology', nargs='?', default='topology.net', 87 | help='GNS3 .net topology file (default: topology.net)') 88 | parser.add_argument('--debug', 89 | help='Enable debugging output', 90 | action='store_true') 91 | parser.add_argument('-q', '--quiet', 92 | help='Quiet-mode (no output to console)', 93 | action='store_true') 94 | return parser 95 | 96 | 97 | def do_conversion(topology_def, topology_name, output_dir=None, debug=False, 98 | quiet=False): 99 | """ 100 | Convert the topology 101 | 102 | :param dict topology_def: Dict containing topology file and snapshot bool. 103 | For example: 104 | ``{'file': filename, 'snapshot': False}`` 105 | :param str topology_name: The name of the topology 106 | :param str output_dir: The directory in which to output the topology. 107 | (Default: None) 108 | :param bool debug: Enable debugging (Default: False) 109 | """ 110 | # Create a new instance of the the Converter 111 | gns3_conv = Converter(topology_def['file'], debug) 112 | # Read the old topology 113 | old_top = gns3_conv.read_topology() 114 | new_top = JSONTopology() 115 | 116 | # Process the sections 117 | (topology) = gns3_conv.process_topology(old_top) 118 | 119 | # Generate the nodes 120 | new_top.nodes = gns3_conv.generate_nodes(topology) 121 | # Generate the links 122 | new_top.links = gns3_conv.generate_links(new_top.nodes) 123 | 124 | new_top.notes = gns3_conv.generate_notes(topology['artwork']['NOTE']) 125 | new_top.shapes = gns3_conv.generate_shapes(topology['artwork']['SHAPE']) 126 | new_top.images = gns3_conv.generate_images(topology['artwork']['PIXMAP']) 127 | 128 | # Enter topology name 129 | new_top.name = topology_name 130 | 131 | # Save the new topology 132 | save(output_dir, gns3_conv, new_top, topology_def['snapshot'], quiet) 133 | 134 | 135 | def topology_abspath(topology): 136 | """ 137 | Get the absolute path of the topology file 138 | 139 | :param str topology: Topology file 140 | :return: Absolute path of topology file 141 | :rtype: str 142 | """ 143 | return os.path.abspath(topology) 144 | 145 | 146 | def topology_dirname(topology): 147 | """ 148 | Get the directory containing the topology file 149 | 150 | :param str topology: topology file 151 | :return: directory which contains the topology file 152 | :rtype: str 153 | """ 154 | return os.path.dirname(topology_abspath(topology)) 155 | 156 | 157 | def get_snapshots(topology): 158 | """ 159 | Return the paths of any snapshot topologies 160 | 161 | :param str topology: topology file 162 | :return: list of dicts containing snapshot topologies 163 | :rtype: list 164 | """ 165 | snapshots = [] 166 | snap_dir = os.path.join(topology_dirname(topology), 'snapshots') 167 | if os.path.exists(snap_dir): 168 | snaps = os.listdir(snap_dir) 169 | for directory in snaps: 170 | snap_top = os.path.join(snap_dir, directory, 'topology.net') 171 | if os.path.exists(snap_top): 172 | snapshots.append({'file': snap_top, 173 | 'snapshot': True}) 174 | return snapshots 175 | 176 | 177 | def name(topology_file, topology_name=None): 178 | """ 179 | Calculate the name to save the converted topology as using either either 180 | a specified name or the directory name of the current project 181 | 182 | :param str topology_file: Topology filename 183 | :param topology_name: Optional topology name (Default: None) 184 | :type topology_name: str or None 185 | :return: new topology name 186 | :rtype: str 187 | """ 188 | if topology_name is not None: 189 | logging.debug('topology name supplied') 190 | topo_name = topology_name 191 | else: 192 | logging.debug('topology name not supplied') 193 | topo_name = os.path.basename(topology_dirname(topology_file)) 194 | return topo_name 195 | 196 | 197 | def snapshot_name(topo_name): 198 | """ 199 | Get the snapshot name 200 | 201 | :param str topo_name: topology file location. The name is taken from the 202 | directory containing the topology file using the 203 | following format: topology_NAME_snapshot_DATE_TIME 204 | :return: snapshot name 205 | :raises ConvertError: when unable to determine the snapshot name 206 | """ 207 | topo_name = os.path.basename(topology_dirname(topo_name)) 208 | snap_re = re.compile('^topology_(.+)(_snapshot_)(\d{6}_\d{6})$') 209 | result = snap_re.search(topo_name) 210 | 211 | if result is not None: 212 | snap_name = result.group(1) + '_' + result.group(3) 213 | else: 214 | raise ConvertError('Unable to get snapshot name') 215 | 216 | return snap_name 217 | 218 | 219 | def save(output_dir, converter, json_topology, snapshot, quiet): 220 | """ 221 | Save the converted topology 222 | 223 | :param str output_dir: Output Directory 224 | :param Converter converter: Converter instance 225 | :param JSONTopology json_topology: JSON topology layout 226 | :param bool snapshot: Is this a snapshot? 227 | :param bool quiet: No console printing 228 | """ 229 | try: 230 | old_topology_dir = topology_dirname(converter.topology) 231 | 232 | if output_dir: 233 | output_dir = os.path.abspath(output_dir) 234 | else: 235 | output_dir = os.getcwd() 236 | 237 | topology_name = json_topology.name 238 | topology_files_dir = os.path.join(output_dir, topology_name + '-files') 239 | 240 | if snapshot: 241 | snap_name = snapshot_name(converter.topology) 242 | output_dir = os.path.join(topology_files_dir, 'snapshots', 243 | snap_name) 244 | topology_files_dir = os.path.join(output_dir, topology_name + 245 | '-files') 246 | 247 | # Prepare the directory structure 248 | if not os.path.exists(output_dir): 249 | os.makedirs(output_dir) 250 | 251 | # Move the dynamips config files to the new topology folder 252 | config_err = copy_configs(converter.configs, old_topology_dir, 253 | topology_files_dir) 254 | 255 | copy_datas(converter.datas, old_topology_dir, 256 | topology_files_dir) 257 | 258 | # Copy any VPCS configurations to the the new topology 259 | copy_vpcs_configs(old_topology_dir, topology_files_dir) 260 | 261 | # Copy the topology images to the new topology 262 | copy_topology_image(old_topology_dir, output_dir) 263 | 264 | # Copy the instructions to the new topology folder 265 | if not snapshot: 266 | copy_instructions(old_topology_dir, output_dir) 267 | 268 | # Move the image files to the new topology folder 269 | image_err = copy_images(converter.images, old_topology_dir, 270 | topology_files_dir) 271 | 272 | # Create the vbox working directories 273 | make_vbox_dirs(json_topology.get_vboxes(), output_dir, topology_name) 274 | 275 | # Create the qemu working directories 276 | make_qemu_dirs(json_topology.get_qemus(), output_dir, topology_name) 277 | 278 | if config_err: 279 | logging.warning('Some router startup configurations could not be ' 280 | 'found to be copied to the new topology') 281 | 282 | if image_err: 283 | logging.warning('Some images could not be found to be copied to ' 284 | 'the new topology') 285 | 286 | filename = '%s.gns3' % topology_name 287 | file_path = os.path.join(output_dir, filename) 288 | with open(file_path, 'w') as file: 289 | json.dump(json_topology.get_topology(), file, indent=4, 290 | sort_keys=True) 291 | if not snapshot and not quiet: 292 | print('Your topology has been converted and can found in:\n' 293 | ' %s' % output_dir) 294 | except OSError as error: 295 | logging.error(error) 296 | 297 | 298 | def copy_datas(datas, source, target): 299 | """ 300 | Copy dynamips data to converted topology 301 | 302 | :param datas: Configs to copy 303 | :param str source: Source topology directory 304 | :param str target: Target topology files directory 305 | :return: True when a data cannot be found, otherwise false 306 | :rtype: bool 307 | """ 308 | data_err = False 309 | if len(datas) > 0: 310 | data_dir = os.path.join(target, 'dynamips') 311 | os.makedirs(data_dir, exist_ok=True) 312 | for data in datas: 313 | old_data_file = os.path.join(source, data['old']) 314 | new_data_file = os.path.join(data_dir, data['new']) 315 | if os.path.isfile(old_data_file): 316 | # Copy and rename the data 317 | shutil.copy(old_data_file, new_data_file) 318 | return data_err 319 | 320 | 321 | 322 | def copy_configs(configs, source, target): 323 | """ 324 | Copy dynamips configs to converted topology 325 | 326 | :param configs: Configs to copy 327 | :param str source: Source topology directory 328 | :param str target: Target topology files directory 329 | :return: True when a config cannot be found, otherwise false 330 | :rtype: bool 331 | """ 332 | config_err = False 333 | if len(configs) > 0: 334 | config_dir = os.path.join(target, 'dynamips', 'configs') 335 | os.makedirs(config_dir) 336 | for config in configs: 337 | old_config_file = os.path.join(source, config['old']) 338 | new_config_file = os.path.join(config_dir, 339 | os.path.basename(config['new'])) 340 | if os.path.isfile(old_config_file): 341 | # Copy and rename the config 342 | shutil.copy(old_config_file, new_config_file) 343 | else: 344 | config_err = True 345 | logging.error('Unable to find %s' % config['old']) 346 | return config_err 347 | 348 | 349 | def copy_vpcs_configs(source, target): 350 | """ 351 | Copy any VPCS configs to the converted topology 352 | 353 | :param str source: Source topology directory 354 | :param str target: Target topology files directory 355 | """ 356 | # Prepare a list of files to copy 357 | vpcs_files = glob.glob(os.path.join(source, 'configs', '*.vpc')) 358 | vpcs_hist = os.path.join(source, 'configs', 'vpcs.hist') 359 | vpcs_config_path = os.path.join(target, 'vpcs', 'multi-host') 360 | if os.path.isfile(vpcs_hist): 361 | vpcs_files.append(vpcs_hist) 362 | # Create the directory tree 363 | if len(vpcs_files) > 0: 364 | os.makedirs(vpcs_config_path) 365 | # Copy the files 366 | for old_file in vpcs_files: 367 | new_file = os.path.join(vpcs_config_path, os.path.basename(old_file)) 368 | shutil.copy(old_file, new_file) 369 | 370 | 371 | def copy_topology_image(source, target): 372 | """ 373 | Copy any images of the topology to the converted topology 374 | 375 | :param str source: Source topology directory 376 | :param str target: Target Directory 377 | """ 378 | files = glob.glob(os.path.join(source, '*.png')) 379 | 380 | for file in files: 381 | shutil.copy(file, target) 382 | 383 | 384 | def copy_images(images, source, target): 385 | """ 386 | Copy images to converted topology 387 | 388 | :param images: Images to copy 389 | :param source: Old Topology Directory 390 | :param target: Target topology files directory 391 | :return: True when an image cannot be found, otherwise false 392 | :rtype: bool 393 | """ 394 | image_err = False 395 | if len(images) > 0: 396 | images_dir = os.path.join(target, 'images') 397 | os.makedirs(images_dir) 398 | for image in images: 399 | if os.path.isabs(image): 400 | old_image_file = image 401 | else: 402 | old_image_file = os.path.join(source, image) 403 | 404 | new_image_file = os.path.join(images_dir, 405 | os.path.basename(image)) 406 | if os.path.isfile(os.path.abspath(old_image_file)): 407 | shutil.copy(old_image_file, new_image_file) 408 | else: 409 | image_err = True 410 | logging.error('Unable to find %s' % old_image_file) 411 | return image_err 412 | 413 | 414 | def copy_instructions(source_project, dest_project): 415 | old_instructions = os.path.join(source_project, 'instructions') 416 | new_instructions = os.path.join(dest_project, 'instructions') 417 | 418 | if os.path.exists(old_instructions): 419 | try: 420 | shutil.copytree(old_instructions, new_instructions) 421 | except shutil.Error as error: 422 | raise ConvertError('Error copying instructions', error) 423 | 424 | 425 | def make_vbox_dirs(max_vbox_id, output_dir, topology_name): 426 | """ 427 | Create VirtualBox working directories if required 428 | 429 | :param int max_vbox_id: Number of directories to create 430 | :param str output_dir: Output directory 431 | :param str topology_name: Topology name 432 | """ 433 | if max_vbox_id is not None: 434 | for i in range(1, max_vbox_id + 1): 435 | vbox_dir = os.path.join(output_dir, topology_name + '-files', 436 | 'vbox', 'vm-%s' % i) 437 | os.makedirs(vbox_dir) 438 | 439 | 440 | def make_qemu_dirs(max_qemu_id, output_dir, topology_name): 441 | """ 442 | Create Qemu VM working directories if required 443 | 444 | :param int max_qemu_id: Number of directories to create 445 | :param str output_dir: Output directory 446 | :param str topology_name: Topology name 447 | """ 448 | if max_qemu_id is not None: 449 | for i in range(1, max_qemu_id + 1): 450 | qemu_dir = os.path.join(output_dir, topology_name + '-files', 451 | 'qemu', 'vm-%s' % i) 452 | os.makedirs(qemu_dir) 453 | 454 | 455 | if __name__ == '__main__': 456 | main() 457 | -------------------------------------------------------------------------------- /gns3converter/models.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | """ 16 | Convenience module for building a model matrix arranged by: 17 | * Model 18 | * Chassis (if applicable) 19 | and containing: 20 | * 'ports' = number of ports 21 | * 'type' = type of ports 22 | """ 23 | MODEL_MATRIX = {} 24 | 25 | for platform in ('c1700', 'c2600', 'c2691', 'c3725', 'c3745', 26 | 'c3600', 'c7200'): 27 | MODEL_MATRIX[platform] = {} 28 | 29 | # 1700s have one FE on the motherboard 30 | for chassis in ('1710', '1720', '1721', '1750', '1751', '1760'): 31 | MODEL_MATRIX['c1700'][chassis] = {'ports': 1, 'type': 'F'} 32 | 33 | # 2600s have one or more interfaces on the motherboard 34 | for chassis in ('2620', '2610XM', '2620XM', '2650XM'): 35 | MODEL_MATRIX['c2600'][chassis] = {'ports': 1, 'type': 'F'} 36 | 37 | for chassis in ('2621', '2611XM', '2621XM', '2651XM'): 38 | MODEL_MATRIX['c2600'][chassis] = {'ports': 2, 'type': 'F'} 39 | 40 | MODEL_MATRIX['c2600']['2610'] = {'ports': 1, 'type': 'E'} 41 | MODEL_MATRIX['c2600']['2611'] = {'ports': 2, 'type': 'E'} 42 | 43 | # 2691s have two FEs on the motherboard 44 | MODEL_MATRIX['c2691'][''] = {'ports': 2, 'type': 'F'} 45 | 46 | # 3620s and 3640s have no ports on the motherboard 47 | for chassis in ('3620', '3640'): 48 | MODEL_MATRIX['c3600'][chassis] = {'ports': 0} 49 | 50 | # 3660s have 2 FEs on the motherboard 51 | MODEL_MATRIX['c3600']['3660'] = {'ports': 2, 'type': 'F'} 52 | 53 | # 3700s have 2 FEs on the motherboard 54 | for platform in ('c3725', 'c3745'): 55 | MODEL_MATRIX[platform][''] = {'ports': 2, 'type': 'F'} 56 | 57 | # 7206s have no ports on the motherboard 58 | MODEL_MATRIX['c7200'][''] = {'ports': 0} 59 | 60 | MODEL_TRANSFORM = {'2691': 'c2691', 61 | '3725': 'c3725', 62 | '3745': 'c3745', 63 | '7200': 'c7200'} 64 | for chassis in ('1710', '1720', '1721', '1750', '1751', '1760'): 65 | MODEL_TRANSFORM[chassis] = 'c1700' 66 | for chassis in ('2620', '2621', '2610XM', '2611XM', '2620XM', 67 | '2621XM', '2650XM', '2651XM'): 68 | MODEL_TRANSFORM[chassis] = 'c2600' 69 | for chassis in ('3620', '3640', '3660'): 70 | MODEL_TRANSFORM[chassis] = 'c3600' 71 | 72 | EXTRA_CONF = ('VBoxDevice', 'QemuDevice', '5520', '525', 'O-series', 73 | 'IDS-4215') 74 | -------------------------------------------------------------------------------- /gns3converter/node.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | """ 16 | This module is used for building Nodes 17 | """ 18 | import os 19 | import re 20 | from gns3converter.adapters import ADAPTER_MATRIX, PORT_TYPES 21 | from gns3converter.models import MODEL_MATRIX 22 | from gns3converter.interfaces import INTERFACE_RE, NUMBER_RE, MAPINT_RE, \ 23 | VBQ_INT_RE, Interfaces 24 | from gns3converter.utils import fix_path 25 | from gns3converter.converterror import ConvertError 26 | 27 | 28 | class Node(Interfaces): 29 | """ 30 | This class defines a node used for building the Nodes configuration 31 | 32 | :param hypervisor: Hypervisor 33 | :param int port_id: starting port ID for this node 34 | """ 35 | 36 | def __init__(self, hypervisor, port_id): 37 | super().__init__(port_id) 38 | self.node = {'ports': [], 39 | 'server_id': 1, 40 | 'label': {'x': 15, 'y': -25}, 41 | 'properties': {}} 42 | self.device_info = {'chassis': '', 43 | 'model': '', 44 | 'npe': None} 45 | self.hypervisor = hypervisor 46 | self.config = [] 47 | self.base_ports = {'vbox_console': 3501, 48 | 'qemu_console': 5001} 49 | 50 | def add_wic(self, old_wic, wic): 51 | """ 52 | Convert the old style WIC slot to a new style WIC slot and add the WIC 53 | to the node properties 54 | 55 | :param str old_wic: Old WIC slot 56 | :param str wic: WIC name 57 | """ 58 | new_wic = 'wic' + old_wic[-1] 59 | self.node['properties'][new_wic] = wic 60 | 61 | def add_wic_ports(self, wic_slot): 62 | """ 63 | Add the ports for a specific WIC to the node['ports'] dictionary 64 | 65 | :param str wic_slot: WIC Slot (wic0) 66 | """ 67 | wic_slot_number = int(wic_slot[3]) 68 | wic_adapter = self.node['properties'][wic_slot] 69 | 70 | num_ports = ADAPTER_MATRIX[wic_adapter]['ports'] 71 | port_type = ADAPTER_MATRIX[wic_adapter]['type'] 72 | ports = [] 73 | 74 | # Dynamips WICs port number start on a multiple of 16. 75 | base = 16 * (wic_slot_number + 1) 76 | # WICs are always in adapter slot 0. 77 | slot = 0 78 | 79 | for port_number in range(num_ports): 80 | phy_port_number = port_number + self.port_numbering[port_type] 81 | port_name = PORT_TYPES[port_type] + '%s/%s' % (slot, 82 | phy_port_number) 83 | port_temp = {'name': port_name, 84 | 'id': self.port_id, 85 | 'port_number': base + port_number, 86 | 'slot_number': slot} 87 | ports.append(port_temp) 88 | self.port_id += 1 89 | self.port_numbering[port_type] += num_ports 90 | self.node['ports'].extend(ports) 91 | 92 | def add_slot_ports(self, slot): 93 | """ 94 | Add the ports to be added for a adapter card 95 | 96 | :param str slot: Slot name 97 | """ 98 | slot_nb = int(slot[4]) 99 | # slot_adapter = None 100 | # if slot in self.node['properties']: 101 | # slot_adapter = self.node['properties'][slot] 102 | # elif self.device_info['model'] == 'c7200': 103 | # if self.device_info['npe'] == 'npe-g2': 104 | # slot_adapter = 'C7200-IO-GE-E' 105 | # else: 106 | # slot_adapter = 'C7200-IO-2FE' 107 | 108 | slot_adapter = self.node['properties'][slot] 109 | 110 | num_ports = ADAPTER_MATRIX[slot_adapter]['ports'] 111 | port_type = ADAPTER_MATRIX[slot_adapter]['type'] 112 | ports = [] 113 | 114 | for i in range(num_ports): 115 | port_name = PORT_TYPES[port_type] + '%s/%s' % (slot_nb, i) 116 | port_temp = {'name': port_name, 117 | 'id': self.port_id, 118 | 'port_number': i, 119 | 'slot_number': slot_nb} 120 | ports.append(port_temp) 121 | self.port_id += 1 122 | self.node['ports'].extend(ports) 123 | 124 | def add_info_from_hv(self): 125 | """ 126 | Add the information we need from the old hypervisor section 127 | """ 128 | # Router Image 129 | if 'image' in self.hypervisor: 130 | self.node['properties']['image'] = \ 131 | os.path.basename(self.hypervisor['image']) 132 | # IDLE-PC 133 | if 'idlepc' in self.hypervisor: 134 | self.node['properties']['idlepc'] = self.hypervisor['idlepc'] 135 | # Router RAM 136 | if 'ram' in self.hypervisor: 137 | self.node['properties']['ram'] = self.hypervisor['ram'] 138 | # 7200 NPE 139 | if 'npe' in self.hypervisor: 140 | self.device_info['npe'] = self.hypervisor['npe'] 141 | # Device Chassis 142 | if 'chassis' in self.hypervisor: 143 | self.device_info['chassis'] = self.hypervisor['chassis'] 144 | if self.device_info['model'] == 'c3600': 145 | self.node['properties']['chassis'] = \ 146 | self.device_info['chassis'] 147 | 148 | def add_device_items(self, item, device): 149 | """ 150 | Add the various items from the device to the node 151 | 152 | :param str item: item key 153 | :param dict device: dictionary containing items 154 | """ 155 | if item in ('aux', 'console'): 156 | self.node['properties'][item] = device[item] 157 | elif item.startswith('slot'): 158 | # if self.device_info['model'] == 'c7200': 159 | # if item != 'slot0': 160 | # self.node['properties'][item] = device[item] 161 | # else: 162 | self.node['properties'][item] = device[item] 163 | elif item == 'connections': 164 | self.connections = device[item] 165 | elif INTERFACE_RE.search(item) or VBQ_INT_RE.search(item): 166 | self.interfaces.append({'from': item, 167 | 'to': device[item]}) 168 | elif NUMBER_RE.search(item): 169 | if self.device_info['type'] == 'EthernetSwitch': 170 | self.calc_ethsw_port(item, device[item]) 171 | elif self.device_info['type'] == 'FrameRelaySwitch': 172 | self.calc_frsw_port(item, device[item]) 173 | elif MAPINT_RE.search(item): 174 | self.add_mapping((item, device[item])) 175 | elif item == 'cnfg': 176 | new_config = os.path.join('configs', 'i%s_startup-config.cfg' % 177 | self.node['id']) 178 | self.node['properties']['startup_config'] = new_config 179 | 180 | self.config.append({'old': fix_path(device[item]), 181 | 'new': new_config}) 182 | elif item.startswith('wic'): 183 | self.add_wic(item, device[item]) 184 | elif item == 'symbol': 185 | self.set_symbol(device[item]) 186 | elif item == 'nics': 187 | self.node['properties']['adapters'] = device[item] 188 | elif item == 'image': 189 | self.node['properties']['vmname'] = device[item] 190 | elif item == 'vbox_id' or item == 'qemu_id': 191 | self.node[item] = device[item] 192 | 193 | def add_to_virtualbox(self): 194 | """ 195 | Add additional parameters that were in the VBoxDevice section or not 196 | present 197 | """ 198 | # VirtualBox Image 199 | if 'vmname' not in self.node['properties']: 200 | self.node['properties']['vmname'] = \ 201 | self.hypervisor['VBoxDevice']['image'] 202 | # Number of adapters 203 | if 'adapters' not in self.node['properties']: 204 | self.node['properties']['adapters'] = \ 205 | self.hypervisor['VBoxDevice']['nics'] 206 | # Console Port 207 | if 'console' not in self.node['properties']: 208 | self.node['properties']['console'] = \ 209 | self.base_ports['vbox_console'] + self.node['vbox_id'] - 1 210 | 211 | def add_to_qemu(self): 212 | """ 213 | Add additional parameters to a QemuVM Device that were present in its 214 | global conf section 215 | """ 216 | device = self.device_info['ext_conf'] 217 | 218 | if device == "5520": 219 | raise ConvertError("ASA 8 is not supported by GNS3 1.4. You should switch to ASAv. This topology can not be converted.") 220 | 221 | node_prop = self.node['properties'] 222 | hv_device = self.hypervisor[device] 223 | # QEMU HDD Images 224 | if 'hda_disk_image' not in node_prop: 225 | if 'image' in hv_device: 226 | node_prop['hda_disk_image'] = hv_device['image'] 227 | elif 'image1' in hv_device: 228 | node_prop['hda_disk_image'] = hv_device['image1'] 229 | if 'hdb_disk_image' not in node_prop and 'image2' in hv_device: 230 | node_prop['hdb_disk_image'] = hv_device['image2'] 231 | # RAM 232 | if 'ram' not in node_prop and 'ram' in hv_device: 233 | node_prop['ram'] = hv_device['ram'] 234 | else: 235 | node_prop['ram'] = 256 236 | # QEMU Options 237 | if 'options' not in node_prop and 'options' in hv_device: 238 | node_prop['options'] = hv_device['options'] 239 | # Kernel Image 240 | if 'kernel_image' not in node_prop and 'kernel' in hv_device: 241 | node_prop['kernel_image'] = hv_device['kernel'] 242 | # Kernel Command Line 243 | if 'kernel_command_line' not in node_prop and \ 244 | 'kernel_cmdline' in hv_device: 245 | node_prop['kernel_command_line'] = hv_device['kernel_cmdline'] 246 | # initrd 247 | if 'initrd' not in node_prop and 'initrd' in hv_device: 248 | node_prop['initrd'] = hv_device['initrd'] 249 | # Number of adapters 250 | if 'adapters' not in node_prop and 'nics' in hv_device: 251 | node_prop['adapters'] = hv_device['nics'] 252 | elif 'adapters' not in node_prop and 'nics' not in hv_device: 253 | node_prop['adapters'] = 6 254 | # Adapter type 255 | if 'adapter_type' not in node_prop and 'netcard' in hv_device: 256 | node_prop['adapter_type'] = hv_device['netcard'] 257 | # Console Port 258 | if 'console' not in node_prop: 259 | node_prop['console'] = self.base_ports['qemu_console'] + \ 260 | self.node['qemu_id'] - 1 261 | # Qemu Path 262 | if 'qemu_path' not in node_prop: 263 | qemu_path = self.hypervisor['qemu_path'] 264 | # Modify QEMU Path if flavor is specified 265 | if 'flavor' in hv_device: 266 | qemu_path = re.sub(r'qemu-system-.*', 267 | 'qemu-system' + hv_device['flavor'], 268 | qemu_path) 269 | node_prop['qemu_path'] = qemu_path 270 | 271 | def add_vm_ethernet_ports(self): 272 | """ 273 | Add ethernet ports to Virtualbox and Qemu nodes 274 | """ 275 | for i in range(self.node['properties']['adapters']): 276 | port = {'id': self.port_id, 277 | 'name': 'Ethernet%s' % i, 278 | 'port_number': i} 279 | self.node['ports'].append(port) 280 | self.port_id += 1 281 | 282 | def set_qemu_symbol(self): 283 | """ 284 | Set the appropriate symbol for QEMU Devices 285 | """ 286 | valid_devices = {'ASA': 'asa', 'PIX': 'PIX_firewall', 287 | 'JUNOS': 'router', 'IDS': 'ids'} 288 | if self.device_info['from'] in valid_devices \ 289 | and 'default_symbol' not in self.node \ 290 | and 'hover_symbol' not in self.node: 291 | self.set_symbol(valid_devices[self.device_info['from']]) 292 | 293 | def set_symbol(self, symbol): 294 | """ 295 | Set a symbol for a device 296 | 297 | :param str symbol: Symbol to use 298 | """ 299 | if symbol == 'EtherSwitch router': 300 | symbol = 'multilayer_switch' 301 | elif symbol == 'Host': 302 | symbol = 'computer' 303 | 304 | normal = ':/symbols/%s.normal.svg' % symbol 305 | selected = ':/symbols/%s.selected.svg' % symbol 306 | 307 | self.node['default_symbol'] = normal 308 | self.node['hover_symbol'] = selected 309 | 310 | def calc_ethsw_port(self, port_num, port_def): 311 | """ 312 | Split and create the port entry for an Ethernet Switch 313 | 314 | :param port_num: port number 315 | :type port_num: str or int 316 | :param str port_def: port definition 317 | """ 318 | # Port String - access 1 SW2 1 319 | # 0: type 1: vlan 2: destination device 3: destination port 320 | port_def = port_def.split(' ') 321 | if len(port_def) == 4: 322 | destination = {'device': port_def[2], 323 | 'port': port_def[3]} 324 | else: 325 | destination = {'device': 'NIO', 326 | 'port': port_def[2]} 327 | # port entry 328 | port = {'id': self.port_id, 329 | 'name': str(port_num), 330 | 'port_number': int(port_num), 331 | 'type': port_def[0], 332 | 'vlan': int(port_def[1])} 333 | self.node['ports'].append(port) 334 | self.calc_link(self.node['id'], self.port_id, port['name'], 335 | destination) 336 | self.port_id += 1 337 | 338 | def calc_frsw_port(self, port_num, port_def): 339 | """ 340 | Split and create the port entry for a Frame Relay Switch 341 | 342 | :param port_num: port number 343 | :type port_num: str or int 344 | :param str port_def: port definition 345 | """ 346 | port_def = port_def.split(' ') 347 | destination = {'device': port_def[0], 348 | 'port': port_def[1]} 349 | # port entry 350 | port = {'id': self.port_id, 351 | 'name': str(port_num), 352 | 'port_number': int(port_num)} 353 | self.node['ports'].append(port) 354 | self.calc_link(self.node['id'], self.port_id, port['name'], 355 | destination) 356 | 357 | self.port_id += 1 358 | 359 | def calc_mb_ports(self): 360 | """ 361 | Add the default ports to add to a router 362 | """ 363 | model = self.device_info['model'] 364 | chassis = self.device_info['chassis'] 365 | num_ports = MODEL_MATRIX[model][chassis]['ports'] 366 | ports = [] 367 | 368 | if num_ports > 0: 369 | port_type = MODEL_MATRIX[model][chassis]['type'] 370 | 371 | # Create the ports dict 372 | for i in range(num_ports): 373 | port_temp = {'name': PORT_TYPES[port_type] + '0/' + str(i), 374 | 'id': self.port_id, 375 | 'port_number': i, 376 | 'slot_number': 0} 377 | ports.append(port_temp) 378 | self.port_id += 1 379 | self.node['ports'].extend(ports) 380 | 381 | def calc_link(self, src_id, src_port, src_port_name, destination): 382 | """ 383 | Add a link item for processing later 384 | 385 | :param int src_id: Source node ID 386 | :param int src_port: Source port ID 387 | :param str src_port_name: Source port name 388 | :param dict destination: Destination 389 | """ 390 | if destination['device'] == 'NIO': 391 | destination['port'] = destination['port'].lower() 392 | 393 | link = {'source_node_id': src_id, 394 | 'source_port_id': src_port, 395 | 'source_port_name': src_port_name, 396 | 'source_dev': self.node['properties']['name'], 397 | 'dest_dev': destination['device'], 398 | 'dest_port': destination['port']} 399 | 400 | self.links.append(link) 401 | 402 | def add_mapping(self, mapping): 403 | mapping = {'source': mapping[0], 404 | 'dest': mapping[1]} 405 | self.mappings.append(mapping) 406 | 407 | def set_description(self): 408 | """ 409 | Set the node description 410 | """ 411 | if self.device_info['type'] == 'Router': 412 | self.node['description'] = '%s %s' % (self.device_info['type'], 413 | self.device_info['model']) 414 | else: 415 | self.node['description'] = self.device_info['desc'] 416 | 417 | def set_type(self): 418 | """ 419 | Set the node type 420 | """ 421 | if self.device_info['type'] == 'Router': 422 | self.node['type'] = self.device_info['model'].upper() 423 | else: 424 | self.node['type'] = self.device_info['type'] 425 | 426 | def get_nb_added_ports(self, old_port_id): 427 | """ 428 | Get the number of ports add to the node 429 | 430 | :param int old_port_id: starting port_id 431 | :return: number of ports added 432 | :rtype: int 433 | """ 434 | return self.port_id - old_port_id 435 | 436 | def calc_device_links(self): 437 | """ 438 | Calculate a router or VirtualBox link 439 | """ 440 | for connection in self.interfaces: 441 | int_type = connection['from'][0] 442 | int_name = connection['from'].replace(int_type, 443 | PORT_TYPES[int_type.upper()]) 444 | # Get the source port id 445 | src_port = None 446 | for port in self.node['ports']: 447 | if int_name == port['name']: 448 | src_port = port['id'] 449 | break 450 | dest_temp = connection['to'].split(' ') 451 | 452 | if len(dest_temp) == 2: 453 | conn_to = {'device': dest_temp[0], 454 | 'port': dest_temp[1]} 455 | else: 456 | conn_to = {'device': 'NIO', 457 | 'port': dest_temp[0]} 458 | 459 | self.calc_link(self.node['id'], src_port, int_name, conn_to) 460 | 461 | def calc_cloud_connection(self): 462 | """ 463 | Add the ports and nios for a cloud connection 464 | 465 | :return: None on success or RuntimeError on error 466 | """ 467 | # Connection String - SW1:1:nio_gen_eth:eth0 468 | # 0: Destination device 1: Destination port 469 | # 2: NIO 3: NIO Destination 470 | self.node['properties']['nios'] = [] 471 | if self.connections is None: 472 | return None 473 | else: 474 | self.connections = self.connections.split(' ') 475 | 476 | for connection in sorted(self.connections): 477 | connection = connection.split(':') 478 | connection_len = len(connection) 479 | if connection_len == 4: 480 | nio = '%s:%s' % (connection[2], connection[3]) 481 | elif connection_len == 6: 482 | nio = '%s:%s:%s:%s' % (connection[2].lower(), connection[3], 483 | connection[4], connection[5]) 484 | else: 485 | return RuntimeError('Error: Unknown connection string length ' 486 | '(Length: %s)' % connection_len) 487 | self.node['properties']['nios'].append(nio) 488 | # port entry 489 | self.node['ports'].append({'id': self.port_id, 490 | 'name': nio, 491 | 'stub': True}) 492 | self.port_id += 1 493 | return None 494 | 495 | def process_mappings(self): 496 | """ 497 | Process the mappings for a Frame Relay switch. Removes duplicates and 498 | adds the mappings to the node properties 499 | """ 500 | for mapping_a in self.mappings: 501 | for mapping_b in self.mappings: 502 | if mapping_a['source'] == mapping_b['dest']: 503 | self.mappings.remove(mapping_b) 504 | break 505 | 506 | self.node['properties']['mappings'] = {} 507 | mappings = self.node['properties']['mappings'] 508 | for mapping in self.mappings: 509 | mappings[mapping['source']] = mapping['dest'] 510 | -------------------------------------------------------------------------------- /gns3converter/topology.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | """ 16 | This module is for processing a topology 17 | """ 18 | from gns3converter.models import MODEL_TRANSFORM, EXTRA_CONF 19 | 20 | 21 | class LegacyTopology(): 22 | """ 23 | Legacy Topology (pre-1.0) 24 | 25 | :param list sections: list of sections from 26 | :py:meth:`gns3converter.converter.Converter.get_instances` 27 | :param ConfigObj old_top: Old topology as returned by 28 | :py:meth:`gns3converter.converter.Converter.read_topology` 29 | """ 30 | def __init__(self, sections, old_top): 31 | self.topology = {'devices': {}, 32 | 'conf': [], 33 | 'artwork': {'SHAPE': {}, 'NOTE': {}, 'PIXMAP': {}}} 34 | self.sections = sections 35 | self.old_top = old_top 36 | self._id = {'hv_id': 0, 37 | 'nid': 1, 38 | 'vbox_id': 1, 39 | 'qemu_id': 1} 40 | 41 | @property 42 | def artwork(self): 43 | """ 44 | Return the Artwork dict 45 | 46 | :return: artwork dict 47 | :rtype: dict 48 | """ 49 | return self.topology['artwork'] 50 | 51 | @property 52 | def hv_id(self): 53 | """ 54 | Return the Hypervisor ID 55 | 56 | :return: Hypervisor ID 57 | :rtype: int 58 | """ 59 | return self._id['hv_id'] 60 | 61 | @hv_id.setter 62 | def hv_id(self, value): 63 | """ 64 | Set the Hypervisor ID 65 | 66 | :param int value: Hypervisor ID 67 | """ 68 | self._id['hv_id'] = value 69 | 70 | @property 71 | def nid(self): 72 | """ 73 | Return the node ID 74 | 75 | :return: Node ID 76 | :rtype: int 77 | """ 78 | return self._id['nid'] 79 | 80 | @nid.setter 81 | def nid(self, value): 82 | """ 83 | Set the node ID 84 | :param int value: Node ID 85 | """ 86 | self._id['nid'] = value 87 | 88 | @property 89 | def vbox_id(self): 90 | """ 91 | Return the VBox ID 92 | :return: VBox ID 93 | :rtype: int 94 | """ 95 | return self._id['vbox_id'] 96 | 97 | @vbox_id.setter 98 | def vbox_id(self, value): 99 | """ 100 | Set the VBox ID 101 | 102 | :param int value: VBox ID 103 | """ 104 | self._id['vbox_id'] = value 105 | 106 | @property 107 | def qemu_id(self): 108 | """ 109 | Return the Qemu VM ID 110 | :return: Qemu VM ID 111 | :rtype: int 112 | """ 113 | return self._id['qemu_id'] 114 | 115 | @qemu_id.setter 116 | def qemu_id(self, value): 117 | """ 118 | Set the Qemu VM ID 119 | 120 | :param int value: Qemu VM ID 121 | """ 122 | self._id['qemu_id'] = value 123 | 124 | def add_artwork_item(self, instance, item): 125 | """ 126 | Add an artwork item e.g. Shapes, Notes and Pixmaps 127 | 128 | :param instance: Hypervisor instance 129 | :param item: Item to add 130 | """ 131 | if 'interface' in self.old_top[instance][item]: 132 | pass 133 | else: 134 | (item_type, item_id) = item.split(' ') 135 | self.artwork[item_type][item_id] = {} 136 | for s_item in sorted(self.old_top[instance][item]): 137 | if self.old_top[instance][item][s_item] is not None: 138 | s_detail = self.old_top[instance][item][s_item] 139 | s_type = type(s_detail) 140 | 141 | if item_type == 'NOTE' and s_type == str: 142 | # Fix any escaped newline characters 143 | s_detail = s_detail.replace('\\n', '\n') 144 | 145 | if s_type == str and len(s_detail) > 1 \ 146 | and s_detail[0] == '"' and s_detail[-1] == '"': 147 | s_detail = s_detail[1:-1] 148 | 149 | if item_type == 'SHAPE' and s_item == 'fill_color': 150 | s_item = 'color' 151 | elif s_item == 'rotate': 152 | s_item = 'rotation' 153 | s_detail = float(s_detail) 154 | 155 | self.artwork[item_type][item_id][s_item] = s_detail 156 | 157 | if item_type == 'SHAPE' and \ 158 | 'color' not in self.artwork[item_type][item_id]: 159 | self.artwork[item_type][item_id]['color'] = '#ffffff' 160 | self.artwork[item_type][item_id]['transparency'] = 0 161 | 162 | def add_qemu_path(self, instance): 163 | """ 164 | Add the qemu path to the hypervisor conf data 165 | 166 | :param instance: Hypervisor instance 167 | """ 168 | tmp_conf = {'qemu_path': self.old_top[instance]['qemupath']} 169 | if len(self.topology['conf']) == 0: 170 | self.topology['conf'].append(tmp_conf) 171 | else: 172 | self.topology['conf'][self.hv_id].update(tmp_conf) 173 | 174 | def add_conf_item(self, instance, item): 175 | """ 176 | Add a hypervisor configuration item 177 | 178 | :param instance: Hypervisor instance 179 | :param item: Item to add 180 | """ 181 | tmp_conf = {} 182 | 183 | if item not in EXTRA_CONF: 184 | tmp_conf['model'] = MODEL_TRANSFORM[item] 185 | 186 | for s_item in sorted(self.old_top[instance][item]): 187 | if self.old_top[instance][item][s_item] is not None: 188 | tmp_conf[s_item] = self.old_top[instance][item][s_item] 189 | 190 | if item in EXTRA_CONF: 191 | tmp_conf = {item: tmp_conf} 192 | if len(self.topology['conf']) == 0: 193 | self.topology['conf'].append(tmp_conf) 194 | else: 195 | self.topology['conf'][self.hv_id].update(tmp_conf) 196 | else: 197 | self.topology['conf'].append(tmp_conf) 198 | self.hv_id = len(self.topology['conf']) - 1 199 | 200 | def add_physical_item(self, instance, item): 201 | """ 202 | Add a physical item e.g router, cloud etc 203 | 204 | :param instance: Hypervisor instance 205 | :param item: Item to add 206 | """ 207 | (name, dev_type) = self.device_typename(item) 208 | self.topology['devices'][name] = {} 209 | self.topology['devices'][name]['hv_id'] = self.hv_id 210 | self.topology['devices'][name]['node_id'] = self.nid 211 | self.topology['devices'][name]['from'] = dev_type['from'] 212 | self.topology['devices'][name]['type'] = dev_type['type'] 213 | self.topology['devices'][name]['desc'] = dev_type['desc'] 214 | 215 | if 'ext_conf' in dev_type: 216 | self.topology['devices'][name]['ext_conf'] = dev_type['ext_conf'] 217 | 218 | for s_item in sorted(self.old_top[instance][item]): 219 | if self.old_top[instance][item][s_item] is not None: 220 | self.topology['devices'][name][s_item] = \ 221 | self.old_top[instance][item][s_item] 222 | 223 | if instance != 'GNS3-DATA' and \ 224 | self.topology['devices'][name]['type'] == 'Router': 225 | if 'model' not in self.topology['devices'][name]: 226 | self.topology['devices'][name]['model'] = \ 227 | self.topology['conf'][self.hv_id]['model'] 228 | else: 229 | self.topology['devices'][name]['model'] = MODEL_TRANSFORM[ 230 | self.topology['devices'][name]['model']] 231 | elif dev_type['type'] == 'VirtualBoxVM': 232 | self.topology['devices'][name]['vbox_id'] = self.vbox_id 233 | self.vbox_id += 1 234 | elif dev_type['type'] == 'QemuVM': 235 | self.topology['devices'][name]['qemu_id'] = self.qemu_id 236 | self.qemu_id += 1 237 | 238 | if instance != 'GNS3-DATA' \ 239 | and 'hx' not in self.topology['devices'][name] \ 240 | and 'hy' not in self.topology['devices'][name]: 241 | self.topology['devices'][name]['hx'] = dev_type['label_x'] 242 | self.topology['devices'][name]['hy'] = -25.0 243 | self.nid += 1 244 | 245 | @staticmethod 246 | def device_typename(item): 247 | """ 248 | Convert the old names to new-style names and types 249 | 250 | :param str item: A device in the form of 'TYPE NAME' 251 | :return: tuple containing device name and type details 252 | """ 253 | 254 | dev_type = {'ROUTER': {'from': 'ROUTER', 255 | 'desc': 'Router', 256 | 'type': 'Router', 257 | 'label_x': 19.5}, 258 | 'QEMU': {'from': 'QEMU', 259 | 'desc': 'QEMU VM', 260 | 'type': 'QemuVM', 261 | 'ext_conf': 'QemuDevice', 262 | 'label_x': -12}, 263 | 'ASA': {'from': 'ASA', 264 | 'desc': 'QEMU VM', 265 | 'type': 'QemuVM', 266 | 'ext_conf': '5520', 267 | 'label_x': 2.5}, 268 | 'PIX': {'from': 'PIX', 269 | 'desc': 'QEMU VM', 270 | 'type': 'QemuVM', 271 | 'ext_conf': '525', 272 | 'label_x': -12}, 273 | 'JUNOS': {'from': 'JUNOS', 274 | 'desc': 'QEMU VM', 275 | 'type': 'QemuVM', 276 | 'ext_conf': 'O-series', 277 | 'label_x': -12}, 278 | 'IDS': {'from': 'IDS', 279 | 'desc': 'QEMU VM', 280 | 'type': 'QemuVM', 281 | 'ext_conf': 'IDS-4215', 282 | 'label_x': -12}, 283 | 'VBOX': {'from': 'VBOX', 284 | 'desc': 'VirtualBox VM', 285 | 'type': 'VirtualBoxVM', 286 | 'ext_conf': 'VBoxDevice', 287 | 'label_x': -4.5}, 288 | 'FRSW': {'from': 'FRSW', 289 | 'desc': 'Frame Relay switch', 290 | 'type': 'FrameRelaySwitch', 291 | 'label_x': 7.5}, 292 | 'ETHSW': {'from': 'ETHSW', 293 | 'desc': 'Ethernet switch', 294 | 'type': 'EthernetSwitch', 295 | 'label_x': 15.5}, 296 | 'Hub': {'from': 'Hub', 297 | 'desc': 'Ethernet hub', 298 | 'type': 'EthernetHub', 299 | 'label_x': 12.0}, 300 | 'ATMSW': {'from': 'ATMSW', 301 | 'desc': 'ATM switch', 302 | 'type': 'ATMSwitch', 303 | 'label_x': 2.0}, 304 | 'ATMBR': {'from': 'ATMBR', # TODO: Investigate ATM Bridge 305 | 'desc': 'ATMBR', 306 | 'type': 'ATMBR'}, 307 | 'Cloud': {'from': 'Cloud', 308 | 'desc': 'Cloud', 309 | 'type': 'Cloud', 310 | 'label_x': 47.5}} 311 | 312 | item_type = item.split(' ')[0] 313 | name = item.replace('%s ' % dev_type[item_type]['from'], '') 314 | return name, dev_type[item_type] 315 | 316 | 317 | class JSONTopology(): 318 | """ 319 | v1.0 JSON Topology 320 | """ 321 | def __init__(self): 322 | self._nodes = [] 323 | self._links = [] 324 | self._notes = [] 325 | self._shapes = {'ellipse': None, 326 | 'rectangle': None} 327 | self._images = [] 328 | self._servers = [{'host': '127.0.0.1', 'id': 1, 'local': True, 329 | 'port': 8000}] 330 | self._name = None 331 | 332 | @property 333 | def nodes(self): 334 | """ 335 | Returns the nodes 336 | 337 | :return: topology nodes 338 | :rtype: list 339 | """ 340 | return self._nodes 341 | 342 | @nodes.setter 343 | def nodes(self, nodes): 344 | """ 345 | Sets the nodes 346 | 347 | :param list nodes: List of nodes from 348 | :py:meth:`gns3converter.converter.Converter.generate_nodes` 349 | """ 350 | self._nodes = nodes 351 | 352 | @property 353 | def links(self): 354 | """ 355 | Returns the links 356 | 357 | :return: Topology links 358 | :rtype: list 359 | """ 360 | return self._links 361 | 362 | @links.setter 363 | def links(self, links): 364 | """ 365 | Sets the links 366 | 367 | :param list links: List of links from 368 | :py:meth:`gns3converter.converter.Converter.generate_links` 369 | """ 370 | self._links = links 371 | 372 | @property 373 | def notes(self): 374 | """ 375 | Returns the notes 376 | 377 | :return: Topology notes 378 | :rtype: list 379 | """ 380 | return self._notes 381 | 382 | @notes.setter 383 | def notes(self, notes): 384 | """ 385 | Sets the notes 386 | 387 | :param list notes: List of notes from 388 | :py:meth:`gns3converter.converter.Converter.generate_notes` 389 | """ 390 | self._notes = notes 391 | 392 | @property 393 | def shapes(self): 394 | """ 395 | Returns the shapes 396 | 397 | :return: Topology shapes 398 | :rtype: dict 399 | """ 400 | return self._shapes 401 | 402 | @shapes.setter 403 | def shapes(self, shapes): 404 | """ 405 | Sets the shapes 406 | 407 | :param dict shapes: List of shapes from 408 | :py:meth:`gns3converter.converter.Converter.generate_shapes` 409 | """ 410 | self._shapes = shapes 411 | 412 | @property 413 | def images(self): 414 | """ 415 | Returns the images 416 | 417 | :return: Topology images 418 | :rtype: list 419 | """ 420 | return self._images 421 | 422 | @images.setter 423 | def images(self, images): 424 | """ 425 | Sets the images 426 | 427 | :param list images: List of images from 428 | :py:meth:`gns3converter.converter.Converter.generate_images` 429 | """ 430 | self._images = images 431 | 432 | @property 433 | def servers(self): 434 | """ 435 | Returns the servers 436 | 437 | :return: Topology servers 438 | :rtype: list 439 | """ 440 | return self._servers 441 | 442 | @servers.setter 443 | def servers(self, servers): 444 | """ 445 | Sets the servers 446 | 447 | :param list servers: List of servers 448 | """ 449 | self._servers = servers 450 | 451 | @property 452 | def name(self): 453 | """ 454 | Returns the topology name 455 | 456 | :return: Topology name 457 | :rtype: None or str 458 | """ 459 | return self._name 460 | 461 | @name.setter 462 | def name(self, name): 463 | """ 464 | Sets the topology name 465 | :param str name: Topology name 466 | """ 467 | self._name = name 468 | 469 | def get_topology(self): 470 | """ 471 | Get the converted topology ready for JSON encoding 472 | 473 | :return: converted topology assembled into a single dict 474 | :rtype: dict 475 | """ 476 | topology = {'name': self._name, 477 | 'resources_type': 'local', 478 | 'topology': {}, 479 | 'type': 'topology', 480 | 'version': '1.0'} 481 | 482 | if self._links: 483 | topology['topology']['links'] = self._links 484 | if self._nodes: 485 | topology['topology']['nodes'] = self._nodes 486 | if self._servers: 487 | topology['topology']['servers'] = self._servers 488 | if self._notes: 489 | topology['topology']['notes'] = self._notes 490 | if self._shapes['ellipse']: 491 | topology['topology']['ellipses'] = self._shapes['ellipse'] 492 | if self._shapes['rectangle']: 493 | topology['topology']['rectangles'] = \ 494 | self._shapes['rectangle'] 495 | if self._images: 496 | topology['topology']['images'] = self._images 497 | 498 | return topology 499 | 500 | def get_vboxes(self): 501 | """ 502 | Get the maximum ID of the VBoxes 503 | 504 | :return: Maximum VBox ID 505 | :rtype: int 506 | """ 507 | vbox_list = [] 508 | vbox_max = None 509 | for node in self.nodes: 510 | if node['type'] == 'VirtualBoxVM': 511 | vbox_list.append(node['vbox_id']) 512 | 513 | if len(vbox_list) > 0: 514 | vbox_max = max(vbox_list) 515 | return vbox_max 516 | 517 | def get_qemus(self): 518 | """ 519 | Get the maximum ID of the Qemu VMs 520 | 521 | :return: Maximum Qemu VM ID 522 | :rtype: int 523 | """ 524 | qemu_vm_list = [] 525 | qemu_vm_max = None 526 | for node in self.nodes: 527 | if node['type'] == 'QemuVM': 528 | qemu_vm_list.append(node['qemu_id']) 529 | 530 | if len(qemu_vm_list) > 0: 531 | qemu_vm_max = max(qemu_vm_list) 532 | return qemu_vm_max 533 | -------------------------------------------------------------------------------- /gns3converter/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | import os.path 16 | 17 | 18 | def fix_path(path): 19 | """ 20 | Fix windows path's. Linux path's will remain unaltered 21 | 22 | :param str path: The path to be fixed 23 | :return: The fixed path 24 | :rtype: str 25 | """ 26 | if '\\' in path: 27 | path = path.replace('\\', '/') 28 | 29 | path = os.path.normpath(path) 30 | 31 | return path 32 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | configobj>=5.0.6 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sys 3 | import warnings 4 | from setuptools import setup 5 | 6 | executables = [] 7 | setup_options = {} 8 | if sys.platform == 'win32': 9 | try: 10 | from cx_Freeze import setup, Executable 11 | 12 | executables.append(Executable("gns3-converter.py")) 13 | except ImportError: 14 | warnings.warn('Module "cx_Freeze" not found. Skipping exe file creation.') 15 | 16 | setup_options = { 17 | 'build_exe': { 18 | 'namespace_packages': 'gns3converter', 19 | 'packages': 'gns3converter', 20 | 'zip_includes': [ 21 | ( 22 | 'gns3converter/configspec', 23 | os.path.join('gns3converter', 'configspec') 24 | ) 25 | ], 26 | 'include_msvcr': True 27 | } 28 | } 29 | 30 | setup( 31 | name='gns3-net-converter', 32 | version=__import__('gns3converter').__version__, 33 | packages=['gns3converter'], 34 | url='https://github.com/gns3/gns3-converter', 35 | license='GPLv3+', 36 | author='GNS3 Team', 37 | author_email='developers@gns3.net', 38 | description='Official fork by GNS3 team of the gns3 converter.' 39 | 'Convert old ini-style GNS3 topologies (<=0.8.7) to the ' 40 | 'newer version 1+ JSON format', 41 | long_description=open("README.rst", "r").read(), 42 | test_suite='tests', 43 | install_requires=['configobj'], 44 | package_data={'gns3converter': ['configspec']}, 45 | entry_points={ 46 | 'console_scripts': ['gns3-converter = gns3converter.main:main'] 47 | }, 48 | platforms='any', 49 | classifiers=[ 50 | 'Development Status :: 5 - Production/Stable', 51 | 'Environment :: Console', 52 | 'Intended Audience :: Education', 53 | 'Intended Audience :: Information Technology', 54 | 'License :: OSI Approved :: GNU General Public License ' 55 | 'v3 or later (GPLv3+)', 56 | 'Natural Language :: English', 57 | 'Operating System :: OS Independent', 58 | 'Programming Language :: Python :: 3', 59 | 'Programming Language :: Python :: 3.3', 60 | 'Programming Language :: Python :: 3.4', 61 | 'Topic :: Education', 62 | 'Topic :: Utilities' 63 | ] 64 | ) 65 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class BaseTest(TestCase): 5 | pass 6 | -------------------------------------------------------------------------------- /tests/configs/R1.cfg: -------------------------------------------------------------------------------- 1 | ! 2 | ! 3 | ! 4 | hostname R1 5 | ! 6 | no ip domain lookup 7 | no ip icmp rate-limit unreachable 8 | ip tcp synwait 5 9 | ! 10 | line con 0 11 | exec-timeout 0 0 12 | logging synchronous 13 | privilege level 15 14 | no login 15 | line aux 0 16 | exec-timeout 0 0 17 | logging synchronous 18 | privilege level 15 19 | no login 20 | ! 21 | ! 22 | end 23 | -------------------------------------------------------------------------------- /tests/configs/R2.cfg: -------------------------------------------------------------------------------- 1 | ! 2 | ! 3 | ! 4 | hostname R2 5 | ! 6 | no ip domain lookup 7 | no ip icmp rate-limit unreachable 8 | ip tcp synwait 5 9 | ! 10 | line con 0 11 | exec-timeout 0 0 12 | logging synchronous 13 | privilege level 15 14 | no login 15 | line aux 0 16 | exec-timeout 0 0 17 | logging synchronous 18 | privilege level 15 19 | no login 20 | ! 21 | ! 22 | end 23 | -------------------------------------------------------------------------------- /tests/data.py: -------------------------------------------------------------------------------- 1 | old_top = { 2 | 'autostart': False, 'version': '0.8.6', 'model': '7200', 'ghostios': False, 3 | 'ghostsize': None, 'jitsharing': False, 'sparsemem': False, 4 | 'idlemax': None, 'idlesleep': None, 'oldidle': False, 'debug': 0, 5 | '127.0.0.1:7200': {'workingdir': '/tmp', 'udp': 10001, 'port': None, 6 | 'qemupath': None, 'qemuimgpath': None, 'console': None, 7 | 'aux': None, 8 | '3660': { 9 | 'image': '/home/daniel/GNS3/Images/3660/c3660-jk9o3' 10 | 's-mz.124-19.image', 11 | 'idlepc': '0x6056c1ec', 'sparsemem': True, 12 | 'chassis': '3660', 'model': None, 'console': None, 13 | 'aux': None, 'mac': None, 'image1': None, 14 | 'image2': None, 'ram': None, 'nvram': None, 15 | 'cnfg': None, 'confreg': None, 'exec_area': None, 16 | 'clock': None, 'npe': None, 'midplane': None, 17 | 'disk0': None, 'disk1': None, 'mmap': None, 18 | 'ghostios': None, 'ghostsize': None, 19 | 'jitsharing': None, 'autostart': None, 20 | 'configuration': None, 'idlemax': None, 21 | 'idlesleep': None, 'oldidle': None, 'rom': None, 22 | 'x': None, 'y': None, 'z': None, 'hx': None, 23 | 'hy': None, 'key': None, 'serial': None, 24 | 'initrd': None, 'kernel': None, 25 | 'kernel_cmdline': None, 'options': None, 26 | 'nics': None, 'netcard': None, 'kvm': None, 27 | 'usermod': None, 'monitor': None, 28 | 'guestcontrol_user': None, 29 | 'guestcontrol_password': None, 30 | 'first_nic_managed': None, 'headless_mode': None, 31 | 'console_support': None, 32 | 'console_telnet_server': None, 'symbol': None, 33 | 'width': None, 'height': None, 'border_style': None, 34 | 'color': None, 'text': None, 'border_width': None}, 35 | 'ROUTER R1': {'model': '3660', 'console': 2103, 36 | 'aux': 2503, 'cnfg': 'configs/R1.cfg', 37 | 'f0/0': 'SW1 2', 'x': -20.0, 'y': -12.0, 38 | 'z': 1.0, 'mac': None, 'image': None, 39 | 'image1': None, 'image2': None, 40 | 'ram': None, 'nvram': None, 41 | 'confreg': None, 'idlepc': None, 42 | 'exec_area': None, 'clock': None, 43 | 'npe': None, 'midplane': None, 44 | 'disk0': None, 'disk1': None, 45 | 'mmap': None, 'ghostios': None, 46 | 'ghostsize': None, 'jitsharing': None, 47 | 'sparsemem': None, 'autostart': None, 48 | 'configuration': None, 'idlemax': None, 49 | 'idlesleep': None, 'oldidle': None, 50 | 'rom': None, 'hx': None, 'hy': None, 51 | 'key': None, 'serial': None, 52 | 'initrd': None, 'kernel': None, 53 | 'kernel_cmdline': None, 'options': None, 54 | 'nics': None, 'netcard': None, 55 | 'kvm': None, 'usermod': None, 56 | 'monitor': None, 57 | 'guestcontrol_user': None, 58 | 'guestcontrol_password': None, 59 | 'first_nic_managed': None, 60 | 'headless_mode': None, 61 | 'console_support': None, 62 | 'console_telnet_server': None, 63 | 'symbol': None, 'width': None, 64 | 'height': None, 'border_style': None, 65 | 'color': None, 'text': None, 66 | 'border_width': None}}, 67 | 'GNS3-DATA': {'configs': 'configs', 'port': None, 'workingdir': None, 68 | 'qemupath': None, 'qemuimgpath': None, 'console': None, 69 | 'aux': None, 'udp': None, 70 | 'NOTE 1': {'model': None, 'console': None, 'aux': None, 71 | 'cnfg': None, 'x': 48.0, 'y': -120.5, 'z': None, 72 | 'mac': None, 'image': None, 'image1': None, 73 | 'image2': None, 'ram': None, 'nvram': None, 74 | 'confreg': None, 'idlepc': None, 75 | 'exec_area': None, 'clock': None, 'npe': None, 76 | 'midplane': None, 'disk0': None, 'disk1': None, 77 | 'mmap': None, 'ghostios': None, 'ghostsize': None, 78 | 'jitsharing': None, 'sparsemem': None, 79 | 'autostart': None, 'configuration': None, 80 | 'idlemax': None, 'idlesleep': None, 81 | 'oldidle': None, 'rom': None, 'hx': None, 82 | 'hy': None, 'key': None, 'serial': None, 83 | 'initrd': None, 'kernel': None, 84 | 'kernel_cmdline': None, 'options': None, 85 | 'nics': None, 'netcard': None, 'kvm': None, 86 | 'usermod': None, 'monitor': None, 87 | 'guestcontrol_user': None, 88 | 'guestcontrol_password': None, 89 | 'first_nic_managed': None, 'headless_mode': None, 90 | 'console_support': None, 91 | 'console_telnet_server': None, 'symbol': None, 92 | 'width': None, 'height': None, 93 | 'border_style': None, 'color': '"#ff5500"', 94 | 'text': '"Sales VLAN\\n300 Users"', 95 | 'border_width': None}, 96 | 'NOTE 2': {'model': None, 'console': None, 'aux': None, 97 | 'cnfg': None, 'x': -220.0, 'y': -121.5, 'z': None, 98 | 'mac': None, 'image': None, 'image1': None, 99 | 'image2': None, 'ram': None, 'nvram': None, 100 | 'confreg': None, 'idlepc': None, 101 | 'exec_area': None, 'clock': None, 'npe': None, 102 | 'midplane': None, 'disk0': None, 'disk1': None, 103 | 'mmap': None, 'ghostios': None, 'ghostsize': None, 104 | 'jitsharing': None, 'sparsemem': None, 105 | 'autostart': None, 'configuration': None, 106 | 'idlemax': None, 'idlesleep': None, 107 | 'oldidle': None, 'rom': None, 'hx': None, 108 | 'hy': None, 'key': None, 'serial': None, 109 | 'initrd': None, 'kernel': None, 110 | 'kernel_cmdline': None, 'options': None, 111 | 'nics': None, 'netcard': None, 'kvm': None, 112 | 'usermod': None, 'monitor': None, 113 | 'guestcontrol_user': None, 114 | 'guestcontrol_password': None, 115 | 'first_nic_managed': None, 'headless_mode': None, 116 | 'console_support': None, 117 | 'console_telnet_server': None, 'symbol': None, 118 | 'width': None, 'height': None, 119 | 'border_style': None, 'color': '"#1a1a1a"', 120 | 'text': '"Servers VLAN\\n20 Servers"', 121 | 'border_width': None}}} 122 | 123 | devices = {'R1': {'aux': 2503, 'model': 'c3600', 'console': 2103, 124 | 'f0/0': 'SW1 2', 'node_id': 1, 'type': 'Router', 125 | 'cnfg': 'configs/R1.cfg', 'x': -20.0, 'y': -12.0, 126 | 'hv_id': 0, 'z': 1.0, 'desc': 'Router', 'from': 'ROUTER', 127 | 'hx': 19.5, 'hy': -25.0}} 128 | 129 | conf = [{'idlepc': '0x6056c1ec', 'model': 'c3600', 130 | 'image': '/home/daniel/GNS3/Images/3660/c3660-jk9o3s-mz.124-19.image', 131 | 'chassis': '3660', 'sparsemem': True}] 132 | 133 | artwork = {'SHAPE': {}, 134 | 'NOTE': {'1': {'text': 'Sales VLAN\n300 Users', 135 | 'x': 48.0, 'y': -120.5, 136 | 'color': '#ff5500'}, 137 | '2': {'text': 'Servers VLAN\n20 Servers', 138 | 'x': -220.0, 'y': -121.5, 139 | 'color': '#1a1a1a'}}, 140 | 'PIXMAP': {}} 141 | -------------------------------------------------------------------------------- /tests/test_converter.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | import unittest 16 | from configobj import ConfigObj 17 | import os.path 18 | from gns3converter.converter import Converter 19 | import tests.data 20 | 21 | 22 | class TestConverter(unittest.TestCase): 23 | def setUp(self): 24 | if os.path.isfile(os.path.abspath('./tests/topology.net')): 25 | self._topology = os.path.abspath('./tests/topology.net') 26 | else: 27 | self._topology = os.path.abspath('./topology.net') 28 | self.app = Converter(self._topology) 29 | 30 | def test_get_topology(self): 31 | topo_file = self.app.topology 32 | exp_res = (os.path.abspath('./tests/topology.net'), 33 | os.path.abspath('./topology.net')) 34 | 35 | self.assertIn(topo_file, exp_res) 36 | 37 | def test_read_topology(self): 38 | self.maxDiff = None 39 | topology = self.app.read_topology() 40 | self.assertIsInstance(topology, ConfigObj) 41 | self.assertDictEqual(tests.data.old_top, topology) 42 | 43 | def test_get_sections(self): 44 | topology = self.app.read_topology() 45 | sections = self.app.get_sections(topology) 46 | self.assertEqual(['127.0.0.1:7200', 'GNS3-DATA'], sections) 47 | 48 | def test_process_topology(self): 49 | topology = self.app.read_topology() 50 | (processed) = self.app.process_topology(topology) 51 | self.assertDictEqual(tests.data.devices, processed['devices']) 52 | self.assertListEqual(tests.data.conf, processed['conf']) 53 | self.assertDictEqual(tests.data.artwork, processed['artwork']) 54 | 55 | def test_generate_shapes(self): 56 | shapes = {'1': {'type': 'ellipse', 'x': 20, 'y': 25, 'width': 500, 57 | 'height': 250, 'border_style': 2}, 58 | '2': {'type': 'rectangle', 'x': 40, 'y': 250, 'width': 250, 59 | 'height': 275, 'border_style': 2}} 60 | exp_res = {'ellipse': [{'x': 20, 'y': 25, 'width': 500, 61 | 'height': 250, 'border_style': 2}], 62 | 'rectangle': [{'x': 40, 'y': 250, 'width': 250, 63 | 'height': 275, 'border_style': 2}]} 64 | res = self.app.generate_shapes(shapes) 65 | self.assertDictEqual(res, exp_res) 66 | 67 | def test_generate_notes(self): 68 | notes = {'1': {'text': 'SomeText', 'x': 20, 'y': 25, 69 | 'color': '#1a1a1a'}} 70 | exp_res = [{'text': 'SomeText', 'x': 20, 'y': 25, 'color': '#1a1a1a'}] 71 | 72 | res = self.app.generate_notes(notes) 73 | self.assertListEqual(res, exp_res) 74 | 75 | def test_generate_nodes(self): 76 | topology = {} 77 | topology['conf'] = [ 78 | {'sparsemem': True, 79 | 'ghostios': True, 80 | 'idlepc': '0x60bec828', 81 | 'ram': 128, 82 | 'model': 'c3725', 83 | 'image': 'c3725-adventerprisek9-mz.124-15.T5.image' 84 | } 85 | ] 86 | topology['devices'] = { 87 | 'GooglISP': { 88 | 'model': 'c7200', 89 | 'aux': 2512, 90 | 'hx': 19.5, 91 | 'z': 1.0, 92 | 'type': 'Router', 93 | 'node_id': 11, 94 | 'p1/0': 'VerISPon p1/0', 95 | 'hv_id': 3, 96 | 'x': -261.643648086, 97 | 'cnfg': 'configs\\GooglISP.cfg', 98 | 'f0/0': 'SW1 f0/0', 99 | 'y': -419.773080371, 100 | 'console': 2012, 101 | 'from': 'ROUTER', 102 | 'hy': -25.0, 103 | 'slot0': 'C7200-IO-FE', 104 | 'desc': 'Router', 105 | 'slot1': 'PA-POS-OC3' 106 | } 107 | } 108 | 109 | config = self.app.generate_nodes(topology) 110 | self.assertEqual(self.app.datas, [ 111 | {'new': 'c7200_i11_rom', 'old': 'working/c7200_GooglISP_rom'}, 112 | {'new': 'c7200_i11_nvram', 'old': 'working/c7200_GooglISP_nvram'}, 113 | {'new': 'c7200_i11_bootflash', 'old': 'working/c7200_GooglISP_bootflash'}, 114 | {'new': 'c7200_i11_disk0', 'old': 'working/c7200_GooglISP_disk0'} 115 | ]) 116 | 117 | if __name__ == '__main__': 118 | unittest.main() 119 | -------------------------------------------------------------------------------- /tests/test_converterror.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | import unittest 16 | from gns3converter.converterror import ConvertError 17 | 18 | 19 | class TestConvertError(unittest.TestCase): 20 | def test_raise_error(self): 21 | try: 22 | raise ConvertError('TestError') 23 | except ConvertError: 24 | self.assertRaises(ConvertError) 25 | -------------------------------------------------------------------------------- /tests/test_main.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from gns3converter.main import snapshot_name 3 | from gns3converter.converterror import ConvertError 4 | 5 | 6 | class TestMain(unittest.TestCase): 7 | def test_snapshot_name(self): 8 | res = snapshot_name('/home/daniel/GNS3/Projects/snapshot_test/' 9 | 'snapshots/topology_Begin_snapshot_250814_140731/' 10 | 'topology.net') 11 | 12 | self.assertEqual(res, 'Begin_250814_140731') 13 | # assertRaises(excClass, callableObj, args) 14 | self.assertRaises(ConvertError, snapshot_name, '') 15 | -------------------------------------------------------------------------------- /tests/test_node.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | import unittest 16 | from gns3converter.node import Node 17 | from gns3converter.converterror import ConvertError 18 | 19 | class TestNode(unittest.TestCase): 20 | def setUp(self): 21 | hv_input = {'image': '/home/test/GNS3/Images/c3725.image', 22 | 'idlepc': '0x61616161', 23 | 'ram': '256', 24 | 'npe': 'npe-400', 25 | 'chassis': '3640',} 26 | 27 | self.app = Node(hv_input, 1) 28 | 29 | def test_add_wic(self): 30 | exp_res = {'wic0': 'WIC-1T'} 31 | 32 | self.app.add_wic('wic0/0', 'WIC-1T') 33 | self.assertDictEqual(exp_res, self.app.node['properties']) 34 | 35 | def test_add_wic_ports_wic1t(self): 36 | exp_res = [{'name': 'Serial0/0', 37 | 'id': 1, 38 | 'port_number': 16, 39 | 'slot_number': 0}] 40 | 41 | self.app.node['properties']['wic0'] = 'WIC-1T' 42 | 43 | self.app.add_wic_ports('wic0') 44 | self.assertListEqual(exp_res, self.app.node['ports']) 45 | self.assertEqual(self.app.port_id, 2) 46 | 47 | def test_add_wic_ports_wic2t(self): 48 | exp_res = [{'name': 'Serial0/0', 49 | 'id': 1, 50 | 'port_number': 16, 51 | 'slot_number': 0}, 52 | {'name': 'Serial0/1', 53 | 'id': 2, 54 | 'port_number': 17, 55 | 'slot_number': 0}] 56 | 57 | self.app.node['properties']['wic0'] = 'WIC-2T' 58 | 59 | self.app.add_wic_ports('wic0') 60 | self.assertListEqual(exp_res, self.app.node['ports']) 61 | self.assertEqual(self.app.port_id, 3) 62 | 63 | def test_add_wic_ports_wic2t_and_wic1t(self): 64 | exp_res = [{'name': 'Serial0/0', 65 | 'id': 1, 66 | 'port_number': 16, 67 | 'slot_number': 0}, 68 | {'name': 'Serial0/1', 69 | 'id': 2, 70 | 'port_number': 17, 71 | 'slot_number': 0}, 72 | {'name': 'Serial0/2', 73 | 'id': 3, 74 | 'port_number': 32, 75 | 'slot_number': 0}] 76 | 77 | self.app.node['properties']['wic0'] = 'WIC-2T' 78 | self.app.node['properties']['wic1'] = 'WIC-1T' 79 | 80 | self.app.add_wic_ports('wic0') 81 | self.app.add_wic_ports('wic1') 82 | self.assertListEqual(exp_res, self.app.node['ports']) 83 | self.assertEqual(self.app.port_id, 4) 84 | 85 | def test_add_info_from_hv(self): 86 | exp_res_node_prop = {'image': 'c3725.image', 87 | 'idlepc': '0x61616161', 88 | 'ram': '256', 89 | 'chassis': '3640'} 90 | exp_res_device_info = {'model': 'c3600', 91 | 'chassis': '3640', 92 | 'npe': 'npe-400'} 93 | 94 | self.app.device_info['model'] = 'c3600' 95 | 96 | self.app.add_info_from_hv() 97 | self.assertDictEqual(exp_res_node_prop, self.app.node['properties']) 98 | self.assertDictEqual(exp_res_device_info, self.app.device_info) 99 | 100 | def test_calc_mb_ports_c3725(self): 101 | exp_res = [{'name': 'FastEthernet0/0', 'id': 1, 'port_number': 0, 102 | 'slot_number': 0}, 103 | {'name': 'FastEthernet0/1', 'id': 2, 'port_number': 1, 104 | 'slot_number': 0}] 105 | 106 | self.app.device_info['model'] = 'c3725' 107 | 108 | self.app.calc_mb_ports() 109 | self.assertDictEqual(self.app.node['ports'][0], exp_res[0]) 110 | self.assertDictEqual(self.app.node['ports'][1], exp_res[1]) 111 | self.assertEqual(self.app.port_id, 3) 112 | 113 | def test_calc_mb_ports_c2600(self): 114 | exp_res = [{'name': 'Ethernet0/0', 'id': 1, 'port_number': 0, 115 | 'slot_number': 0}] 116 | 117 | self.app.device_info['model'] = 'c2600' 118 | self.app.device_info['chassis'] = '2610' 119 | 120 | self.app.calc_mb_ports() 121 | self.assertDictEqual(self.app.node['ports'][0], exp_res[0]) 122 | self.assertEqual(self.app.port_id, 2) 123 | 124 | def test_calc_cloud_connection_4(self): 125 | exp_result = {'id': 1, 126 | 'name': 'nio_gen_eth:eth0', 127 | 'stub': True} 128 | self.app.connections = 'SW1:1:nio_gen_eth:eth0' 129 | self.app.calc_cloud_connection() 130 | #Check NIO String 131 | self.assertIsInstance(self.app.node['properties']['nios'], list) 132 | self.assertIsInstance(self.app.node['properties']['nios'][0], str) 133 | self.assertEqual(self.app.node['properties']['nios'][0], 134 | 'nio_gen_eth:eth0') 135 | #Check Port dictionary 136 | self.assertIsInstance(self.app.node['ports'][0], dict) 137 | self.assertDictEqual(self.app.node['ports'][0], exp_result) 138 | self.assertEqual(self.app.port_id, 2) 139 | 140 | def test_calc_cloud_connection_5(self): 141 | self.app.connections = 'SW1:1:nio_udp:30000:127.0.0.1' 142 | self.app.calc_cloud_connection() 143 | 144 | self.assertRaises(RuntimeError) 145 | 146 | def test_calc_cloud_connection_6(self): 147 | exp_result = {'id': 1, 148 | 'name': 'nio_udp:30000:127.0.0.1:20000', 149 | 'stub': True} 150 | self.app.connections = 'SW1:1:nio_udp:30000:127.0.0.1:20000' 151 | self.app.calc_cloud_connection() 152 | #Check NIO String 153 | self.assertIsInstance(self.app.node['properties']['nios'], list) 154 | self.assertIsInstance(self.app.node['properties']['nios'][0], str) 155 | self.assertEqual(self.app.node['properties']['nios'][0], 156 | 'nio_udp:30000:127.0.0.1:20000') 157 | #Check Port dictionary 158 | self.assertIsInstance(self.app.node['ports'][0], dict) 159 | self.assertDictEqual(self.app.node['ports'][0], exp_result) 160 | self.assertEqual(self.app.port_id, 2) 161 | 162 | def test_calc_cloud_connection_none(self): 163 | self.app.connections = None 164 | ret = self.app.calc_cloud_connection() 165 | self.assertIsNone(ret) 166 | 167 | def test_calc_ethsw_port_device(self): 168 | self.app.node['id'] = 1 169 | self.app.node['properties']['name'] = 'SW1' 170 | exp_port = {'id': 1, 'name': '1', 'port_number': 1, 171 | 'type': 'access', 'vlan': 1} 172 | exp_link = {'source_port_id': 1, 173 | 'source_node_id': 1, 174 | 'source_port_name': '1', 175 | 'dest_dev': 'SW2', 176 | 'source_dev': 'SW1', 177 | 'dest_port': '1'} 178 | 179 | self.app.calc_ethsw_port(1, 'access 1 SW2 1') 180 | self.assertIsInstance(self.app.node['ports'][0], dict) 181 | self.assertIsInstance(self.app.links[0], dict) 182 | 183 | self.assertDictEqual(self.app.node['ports'][0], exp_port) 184 | self.assertDictEqual(self.app.links[0], exp_link) 185 | 186 | def test_calc_ethsw_port_nio(self): 187 | self.app.node['id'] = 1 188 | self.app.node['properties']['name'] = 'SW1' 189 | exp_port = {'id': 1, 'name': '1', 'port_number': 1, 190 | 'type': 'access', 'vlan': 1} 191 | exp_link = {'source_port_id': 1, 192 | 'source_node_id': 1, 193 | 'source_port_name': '1', 194 | 'dest_dev': 'NIO', 195 | 'source_dev': 'SW1', 196 | 'dest_port': 'nio_gen_eth:eth0'} 197 | 198 | self.app.calc_ethsw_port(1, 'access 1 nio_gen_eth:eth0') 199 | self.assertIsInstance(self.app.node['ports'][0], dict) 200 | self.assertIsInstance(self.app.links[0], dict) 201 | 202 | self.assertDictEqual(self.app.node['ports'][0], exp_port) 203 | self.assertDictEqual(self.app.links[0], exp_link) 204 | 205 | def test_calc_link(self): 206 | self.app.node['properties']['name'] = 'R1' 207 | exp_res = {'source_node_id': 1, 208 | 'source_port_id': 2, 209 | 'source_port_name': 'FastEthernet0/0', 210 | 'source_dev': 'R1', 211 | 'dest_dev': 'SiteA', 212 | 'dest_port': 'f0/0'} 213 | 214 | self.app.calc_link(1, 2, 'FastEthernet0/0', 215 | {'device': 'SiteA', 'port': 'f0/0'}) 216 | self.assertIsInstance(self.app.links[0], dict) 217 | self.assertDictEqual(self.app.links[0], exp_res) 218 | 219 | def test_add_slot_ports(self): 220 | self.app.node['properties']['slot1'] = 'NM-4T' 221 | exp_res = [{'name': 'Serial1/0', 'id': 1, 'port_number': 0, 222 | 'slot_number': 1}, 223 | {'name': 'Serial1/1', 'id': 2, 'port_number': 1, 224 | 'slot_number': 1}, 225 | {'name': 'Serial1/2', 'id': 3, 'port_number': 2, 226 | 'slot_number': 1}, 227 | {'name': 'Serial1/3', 'id': 4, 'port_number': 3, 228 | 'slot_number': 1}] 229 | 230 | self.app.add_slot_ports('slot1') 231 | self.assertListEqual(self.app.node['ports'], exp_res) 232 | self.assertDictEqual(self.app.node['ports'][0], exp_res[0]) 233 | self.assertDictEqual(self.app.node['ports'][1], exp_res[1]) 234 | self.assertEqual(self.app.port_id, 5) 235 | 236 | def test_add_slot_ports_c7200(self): 237 | self.app.device_info['model'] = 'c7200' 238 | self.app.node['properties']['slot0'] = 'C7200-IO-2FE' 239 | exp_res = [{'name': 'FastEthernet0/0', 'id': 1, 'port_number': 0, 240 | 'slot_number': 0}, 241 | {'name': 'FastEthernet0/1', 'id': 2, 'port_number': 1, 242 | 'slot_number': 0}] 243 | 244 | self.app.add_slot_ports('slot0') 245 | self.assertListEqual(self.app.node['ports'], exp_res) 246 | self.assertDictEqual(self.app.node['ports'][0], exp_res[0]) 247 | self.assertDictEqual(self.app.node['ports'][1], exp_res[1]) 248 | self.assertEqual(self.app.port_id, 3) 249 | 250 | def test_set_description_router(self): 251 | self.app.device_info['type'] = 'Router' 252 | self.app.device_info['model'] = 'c3725' 253 | 254 | self.app.set_description() 255 | self.assertEqual(self.app.node['description'], 'Router c3725') 256 | 257 | def test_set_description_cloud(self): 258 | self.app.device_info['type'] = 'Cloud' 259 | self.app.device_info['desc'] = 'Cloud' 260 | 261 | self.app.set_description() 262 | self.assertEqual(self.app.node['description'], 'Cloud') 263 | 264 | def test_set_type_router(self): 265 | self.app.device_info['type'] = 'Router' 266 | self.app.device_info['model'] = 'c3725' 267 | 268 | self.app.set_type() 269 | self.assertEqual(self.app.node['type'], 'C3725') 270 | 271 | def test_set_type_cloud(self): 272 | self.app.device_info['type'] = 'Cloud' 273 | 274 | self.app.set_type() 275 | self.assertEqual(self.app.node['type'], 'Cloud') 276 | 277 | def test_get_nb_added_ports(self): 278 | self.app.node['properties']['slot1'] = 'NM-4T' 279 | self.app.add_slot_ports('slot1') 280 | 281 | nb_added = self.app.get_nb_added_ports(0) 282 | self.assertIsInstance(nb_added, int) 283 | self.assertEqual(nb_added, 5) 284 | 285 | def test_set_symbol_access_point(self): 286 | self.app.set_symbol('access_point') 287 | 288 | self.assertEqual(self.app.node['default_symbol'], 289 | ':/symbols/access_point.normal.svg') 290 | self.assertEqual(self.app.node['hover_symbol'], 291 | ':/symbols/access_point.selected.svg') 292 | 293 | def test_set_symbol_etherswitch_router(self): 294 | self.app.set_symbol('EtherSwitch router') 295 | 296 | self.assertEqual(self.app.node['default_symbol'], 297 | ':/symbols/multilayer_switch.normal.svg') 298 | self.assertEqual(self.app.node['hover_symbol'], 299 | ':/symbols/multilayer_switch.selected.svg') 300 | 301 | def test_set_symbol_host(self): 302 | self.app.set_symbol('Host') 303 | 304 | self.assertEqual(self.app.node['default_symbol'], 305 | ':/symbols/computer.normal.svg') 306 | self.assertEqual(self.app.node['hover_symbol'], 307 | ':/symbols/computer.selected.svg') 308 | 309 | def test_calc_device_links(self): 310 | self.app.interfaces.append({'to': 'R2 f0/0', 311 | 'from': 'f0/0'}) 312 | self.app.node['id'] = 1 313 | self.app.node['properties']['name'] = 'R1' 314 | self.app.device_info['model'] = 'c3725' 315 | self.app.calc_mb_ports() 316 | 317 | exp_res = {'source_node_id': 1, 318 | 'source_port_id': 1, 319 | 'source_port_name': 'FastEthernet0/0', 320 | 'source_dev': 'R1', 321 | 'dest_dev': 'R2', 322 | 'dest_port': 'f0/0'} 323 | 324 | self.app.calc_device_links() 325 | self.assertDictEqual(self.app.links[0], exp_res) 326 | 327 | def test_calc_device_links_nio(self): 328 | self.app.interfaces.append({'to': 'nio_gen_eth:eth0', 329 | 'from': 'f0/0'}) 330 | self.app.node['id'] = 1 331 | self.app.node['properties']['name'] = 'R1' 332 | self.app.device_info['model'] = 'c3725' 333 | self.app.calc_mb_ports() 334 | 335 | exp_res = {'source_node_id': 1, 336 | 'source_port_id': 1, 337 | 'source_port_name': 'FastEthernet0/0', 338 | 'source_dev': 'R1', 339 | 'dest_dev': 'NIO', 340 | 'dest_port': 'nio_gen_eth:eth0'} 341 | 342 | self.app.calc_device_links() 343 | self.assertDictEqual(self.app.links[0], exp_res) 344 | 345 | def test_add_mapping(self): 346 | self.app.add_mapping(('1:122', '2:221')) 347 | self.assertListEqual(self.app.mappings, [{'source': '1:122', 348 | 'dest': '2:221'}]) 349 | 350 | def test_process_mappings(self): 351 | self.app.add_mapping(('1:122', '2:221')) 352 | self.app.add_mapping(('2:221', '1:122')) 353 | self.app.add_mapping(('3:321', '1:123')) 354 | 355 | self.app.process_mappings() 356 | 357 | exp_res = {'1:122': '2:221', '3:321': '1:123'} 358 | self.assertDictEqual(self.app.node['properties']['mappings'], 359 | exp_res) 360 | 361 | def test_calc_frsw_port(self): 362 | self.app.node['id'] = 1 363 | self.app.node['properties']['name'] = 'FRSW1' 364 | exp_link = [{'source_port_id': 1, 365 | 'source_node_id': 1, 366 | 'source_port_name': '1', 367 | 'dest_dev': 'R1', 368 | 'source_dev': 'FRSW1', 369 | 'dest_port': 's0/0'}] 370 | self.app.calc_frsw_port('1', 'R1 s0/0') 371 | self.assertListEqual(self.app.node['ports'], 372 | [{'id': 1, 'name': '1', 'port_number': 1}]) 373 | self.assertListEqual(self.app.links, exp_link) 374 | self.assertEqual(self.app.port_id, 2) 375 | 376 | def test_set_qemu_symbol(self): 377 | self.app.device_info['from'] = 'ASA' 378 | self.app.set_qemu_symbol() 379 | 380 | self.assertEqual(self.app.node['default_symbol'], 381 | ':/symbols/asa.normal.svg') 382 | self.assertEqual(self.app.node['hover_symbol'], 383 | ':/symbols/asa.selected.svg') 384 | 385 | def test_add_vm_ethernet_ports(self): 386 | exp_res = [{'id': 1, 'name': 'Ethernet0', 'port_number': 0}, 387 | {'id': 2, 'name': 'Ethernet1', 'port_number': 1}] 388 | self.app.node['properties']['adapters'] = 2 389 | self.app.add_vm_ethernet_ports() 390 | 391 | self.assertListEqual(self.app.node['ports'], exp_res) 392 | self.assertEqual(self.app.port_id, 3) 393 | 394 | def test_add_to_qemu(self): 395 | self.app.node['qemu_id'] = 1 396 | self.app.device_info['ext_conf'] = 'QemuDevice' 397 | self.app.hypervisor['QemuDevice'] = {} 398 | self.app.hypervisor['qemu_path'] = '/bin/qemu' 399 | 400 | self.app.add_to_qemu() 401 | 402 | self.assertEqual(self.app.node['properties']['adapters'], 6) 403 | self.assertEqual(self.app.node['properties']['console'], 5001) 404 | self.assertEqual(self.app.node['properties']['qemu_path'], '/bin/qemu') 405 | self.assertEqual(self.app.node['properties']['ram'], 256) 406 | 407 | def test_add_to_qemu_asa(self): 408 | self.app.node['qemu_id'] = 1 409 | self.app.device_info['ext_conf'] = '5520' 410 | self.app.hypervisor['QemuDevice'] = {} 411 | self.app.hypervisor['qemu_path'] = '/bin/qemu' 412 | 413 | try: 414 | self.app.add_to_qemu() 415 | except ConvertError: 416 | self.assertRaises(ConvertError) 417 | 418 | def test_add_to_virtualbox(self): 419 | self.app.node['vbox_id'] = 1 420 | self.app.hypervisor['VBoxDevice'] = {} 421 | self.app.hypervisor['VBoxDevice']['image'] = 'image_name' 422 | self.app.hypervisor['VBoxDevice']['nics'] = 2 423 | 424 | self.app.add_to_virtualbox() 425 | 426 | self.assertEqual(self.app.node['properties']['vmname'], 'image_name') 427 | self.assertEqual(self.app.node['properties']['adapters'], 2) 428 | self.assertEqual(self.app.node['properties']['console'], 3501) 429 | 430 | @unittest.skip 431 | def test_add_device_items(self): 432 | # TODO 433 | self.fail() 434 | 435 | 436 | if __name__ == '__main__': 437 | unittest.main() 438 | -------------------------------------------------------------------------------- /tests/test_topology.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | import unittest 16 | from configobj import ConfigObj 17 | from gns3converter.topology import LegacyTopology, JSONTopology 18 | 19 | 20 | class TestLegacyTopology(unittest.TestCase): 21 | def setUp(self): 22 | conf = ConfigObj() 23 | conf['127.0.0.1:7200'] = {'3725': {'image': 'c3725.image', 24 | 'ram': 128, 25 | 'x': None, 26 | 'y': None}, 27 | 'ROUTER R1': {'cnfg': 'configs/R1.cfg', 28 | 'console': 2101, 29 | 'aux': 2501, 30 | 'model': None}} 31 | self.app = LegacyTopology([], conf) 32 | 33 | def add_hv_details(self): 34 | instance = '127.0.0.1:7200' 35 | item = '3725' 36 | 37 | self.app.add_conf_item(instance, item) 38 | 39 | def test_add_artwork_item(self): 40 | self.app.old_top['GNS3-DATA'] = { 41 | 'NOTE 1': {'text': 'SomeText', 'x': 20, 'y': 25, 42 | 'color': '#1a1a1a'}, 43 | 'NOTE 2': {'text': 'f0/0', 'x': 20, 'y': 25, 44 | 'color': '#1a1a1a', 'interface': 'f0/0'}, 45 | 'SHAPE 1': {'type': 'ellipse', 'x': 20, 'y': 25, 'width': 500, 46 | 'border_width': 4, 47 | 'height': 250, 'border_style': 2, 'rotate': "45"} 48 | } 49 | 50 | exp_res = {'SHAPE': {'1': {'type': 'ellipse', 51 | 'x': 20, 'y': 25, 52 | 'color': '#ffffff', 53 | 'transparency': 0, 54 | 'width': 500, 55 | 'height': 250, 56 | 'border_style': 2, 57 | 'border_width': 4, 58 | 'rotation': 45.0}}, 59 | 'PIXMAP': {}, 60 | 'NOTE': {'1': {'text': 'SomeText', 61 | 'x': 20, 'y': 25, 62 | 'color': '#1a1a1a'}} 63 | } 64 | 65 | self.app.add_artwork_item('GNS3-DATA', 'SHAPE 1') 66 | self.app.add_artwork_item('GNS3-DATA', 'NOTE 1') 67 | self.app.add_artwork_item('GNS3-DATA', 'NOTE 2') 68 | 69 | self.assertDictEqual(self.app.topology['artwork'], exp_res) 70 | 71 | def test_add_conf_item(self): 72 | instance = '127.0.0.1:7200' 73 | item = '3725' 74 | 75 | exp_res = [{'image': 'c3725.image', 'model': 'c3725', 'ram': 128}] 76 | 77 | self.app.add_conf_item(instance, item) 78 | self.assertListEqual(self.app.topology['conf'], exp_res) 79 | 80 | def test_add_physical_item_no_model(self): 81 | self.add_hv_details() 82 | 83 | instance = '127.0.0.1:7200' 84 | item = 'ROUTER R1' 85 | 86 | exp_res = {'R1': {'hv_id': 0, 87 | 'node_id': 1, 88 | 'type': 'Router', 89 | 'desc': 'Router', 90 | 'from': 'ROUTER', 91 | 'cnfg': 'configs/R1.cfg', 92 | 'console': 2101, 93 | 'aux': 2501, 94 | 'model': 'c3725', 95 | 'hx': 19.5, 'hy': -25}} 96 | 97 | self.app.add_physical_item(instance, item) 98 | self.assertDictEqual(self.app.topology['devices'], exp_res) 99 | 100 | def test_add_physical_item_with_model(self): 101 | self.add_hv_details() 102 | 103 | instance = '127.0.0.1:7200' 104 | item = 'ROUTER R1' 105 | 106 | exp_res = {'R1': {'hv_id': 0, 107 | 'node_id': 1, 108 | 'type': 'Router', 109 | 'desc': 'Router', 110 | 'from': 'ROUTER', 111 | 'cnfg': 'configs/R1.cfg', 112 | 'console': 2101, 113 | 'aux': 2501, 114 | 'model': 'c7200', 115 | 'hx': 19.5, 'hy': -25}} 116 | 117 | self.app.old_top['127.0.0.1:7200']['ROUTER R1']['model'] = '7200' 118 | 119 | self.app.add_physical_item(instance, item) 120 | self.assertDictEqual(self.app.topology['devices'], exp_res) 121 | 122 | def test_device_typename(self): 123 | exp_result = {'ROUTER R1': {'name': 'R1', 'type': 'Router'}, 124 | 'QEMU Q1': {'name': 'Q1', 'type': 'QemuVM'}, 125 | 'ASA ASA1': {'name': 'ASA1', 'type': 'QemuVM'}, 126 | 'PIX PIX1': {'name': 'PIX1', 'type': 'QemuVM'}, 127 | 'JUNOS JUNOS1': {'name': 'JUNOS1', 'type': 'QemuVM'}, 128 | 'IDS IDS1': {'name': 'IDS1', 'type': 'QemuVM'}, 129 | 'VBOX V1': {'name': 'V1', 'type': 'VirtualBoxVM'}, 130 | 'FRSW FR1': {'name': 'FR1', 'type': 'FrameRelaySwitch'}, 131 | 'ETHSW SW1': {'name': 'SW1', 'type': 'EthernetSwitch'}, 132 | 'Hub Hub1': {'name': 'Hub1', 'type': 'EthernetHub'}, 133 | 'ATMSW SW1': {'name': 'SW1', 'type': 'ATMSwitch'}, 134 | 'ATMBR BR1': {'name': 'BR1', 'type': 'ATMBR'}, 135 | 'Cloud C1': {'name': 'C1', 'type': 'Cloud'}} 136 | 137 | for device in exp_result: 138 | (name, dev_type) = self.app.device_typename(device) 139 | self.assertEqual(exp_result[device]['name'], name) 140 | self.assertEqual(exp_result[device]['type'], dev_type['type']) 141 | 142 | def test_vbox_id(self): 143 | self.assertEqual(self.app.vbox_id, 1) 144 | self.app.vbox_id = 5 145 | self.assertEqual(self.app.vbox_id, 5) 146 | 147 | def test_qemu_id(self): 148 | self.assertEqual(self.app.qemu_id, 1) 149 | self.app.qemu_id = 5 150 | self.assertEqual(self.app.qemu_id, 5) 151 | 152 | 153 | class TestJSONTopology(unittest.TestCase): 154 | def setUp(self): 155 | self.app = JSONTopology() 156 | 157 | def test_nodes(self): 158 | self.assertListEqual(self.app.nodes, []) 159 | self.app.nodes = [{'node_id': 1}] 160 | self.assertListEqual(self.app.nodes, [{'node_id': 1}]) 161 | 162 | def test_links(self): 163 | self.assertListEqual(self.app.links, []) 164 | self.app.links = [{'id': 1}] 165 | self.assertListEqual(self.app.links, [{'id': 1}]) 166 | 167 | def test_notes(self): 168 | self.assertListEqual(self.app.notes, []) 169 | self.app.notes = [{'id': 1}] 170 | self.assertListEqual(self.app.notes, [{'id': 1}]) 171 | 172 | def test_shapes(self): 173 | self.assertDictEqual(self.app.shapes, 174 | {'ellipse': None, 'rectangle': None}) 175 | self.app.shapes = {'ellipse': {'id': 1}, 176 | 'rectangle': {'id': 2}} 177 | self.assertDictEqual(self.app.shapes, {'ellipse': {'id': 1}, 178 | 'rectangle': {'id': 2}}) 179 | 180 | def test_images(self): 181 | self.assertListEqual(self.app.images, []) 182 | self.app.images = [{'id': 1}] 183 | self.assertListEqual(self.app.images, [{'id': 1}]) 184 | 185 | def test_servers(self): 186 | exp_res = [{'host': '127.0.0.1', 'id': 1, 'local': True, 'port': 8000}] 187 | self.assertListEqual(self.app.servers, exp_res) 188 | self.app.servers = [{'host': '127.0.0.1', 'id': 2, 'local': True, 189 | 'port': 8001}] 190 | exp_res = [{'host': '127.0.0.1', 'id': 2, 'local': True, 191 | 'port': 8001}] 192 | self.assertListEqual(self.app.servers, exp_res) 193 | 194 | def test_name(self): 195 | self.assertIsNone(self.app.name) 196 | self.app.name = 'Super Topology' 197 | self.assertEqual(self.app.name, 'Super Topology') 198 | 199 | def test_get_topology(self): 200 | exp_res = {'name': None, 201 | 'resources_type': 'local', 202 | 'topology': {'servers': [{'host': '127.0.0.1', 'id': 1, 203 | 'local': True, 'port': 8000}]}, 204 | 'type': 'topology', 205 | 'version': '1.0'} 206 | 207 | result = self.app.get_topology() 208 | self.assertDictEqual(result, exp_res) 209 | 210 | def test_get_vboxes(self): 211 | # TODO 212 | pass 213 | 214 | def test_get_qemus(self): 215 | # TODO 216 | pass 217 | 218 | if __name__ == '__main__': 219 | unittest.main() 220 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lintott. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | import unittest 16 | import sys 17 | from gns3converter import utils 18 | 19 | 20 | class TestUtils(unittest.TestCase): 21 | def test_fix_path_win(self): 22 | res = utils.fix_path('configs\R1.cfg') 23 | 24 | if sys.platform == 'win32': 25 | exp_res = 'configs\\R1.cfg' 26 | else: 27 | exp_res = 'configs/R1.cfg' 28 | 29 | self.assertEqual(res, exp_res) 30 | 31 | def test_fix_path_unix(self): 32 | res = utils.fix_path('configs/R1.cfg') 33 | 34 | if sys.platform == 'win32': 35 | exp_res = 'configs\\R1.cfg' 36 | else: 37 | exp_res = 'configs/R1.cfg' 38 | 39 | self.assertEqual(res, exp_res) 40 | -------------------------------------------------------------------------------- /tests/test_version.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import gns3converter 3 | 4 | 5 | class TestVersion(unittest.TestCase): 6 | def test_version(self): 7 | self.assertEqual('1.3.0', gns3converter.__version__) 8 | 9 | if __name__ == '__main__': 10 | unittest.main() 11 | -------------------------------------------------------------------------------- /tests/topology.net: -------------------------------------------------------------------------------- 1 | autostart = False 2 | version = 0.8.6 3 | [127.0.0.1:7200] 4 | workingdir = /tmp 5 | udp = 10001 6 | [[3660]] 7 | image = /home/daniel/GNS3/Images/3660/c3660-jk9o3s-mz.124-19.image 8 | idlepc = 0x6056c1ec 9 | sparsemem = True 10 | chassis = 3660 11 | [[ROUTER R1]] 12 | model = 3660 13 | console = 2103 14 | aux = 2503 15 | cnfg = configs/R1.cfg 16 | f0/0 = SW1 2 17 | x = -20.0 18 | y = -12.0 19 | z = 1.0 20 | [GNS3-DATA] 21 | configs = configs 22 | [[NOTE 1]] 23 | text = "Sales VLAN\n300 Users" 24 | x = 48.0 25 | y = -120.5 26 | color = "#ff5500" 27 | [[NOTE 2]] 28 | text = "Servers VLAN\n20 Servers" 29 | x = -220.0 30 | y = -121.5 31 | color = "#1a1a1a" 32 | --------------------------------------------------------------------------------