├── .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 |
--------------------------------------------------------------------------------