mirror of
				https://github.com/KevinMidboe/mktxp-no-cli.git
				synced 2025-10-29 17:50:23 +00:00 
			
		
		
		
	Initial commit
This commit is contained in:
		
							
								
								
									
										685
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										685
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,674 +1,11 @@ | |||||||
|                     GNU GENERAL PUBLIC LICENSE | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|                        Version 3, 29 June 2007 | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> | ## modify it under the terms of the GNU General Public License | ||||||
|  Everyone is permitted to copy and distribute verbatim copies | ## as published by the Free Software Foundation; either version 2 | ||||||
|  of this license document, but changing it is not allowed. | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|                             Preamble | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|   The GNU General Public License is a free, copyleft license for | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
| software and other kinds of works. | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|   The licenses for most software and other practical works are designed |  | ||||||
| to take away your freedom to share and change the works.  By contrast, |  | ||||||
| the GNU General Public License is intended to guarantee your freedom to |  | ||||||
| share and change all versions of a program--to make sure it remains free |  | ||||||
| software for all its users.  We, the Free Software Foundation, use the |  | ||||||
| GNU General Public License for most of our software; it applies also to |  | ||||||
| any other work released this way by its authors.  You can apply it to |  | ||||||
| your programs, too. |  | ||||||
|  |  | ||||||
|   When we speak of free software, we are referring to freedom, not |  | ||||||
| price.  Our General Public Licenses are designed to make sure that you |  | ||||||
| have the freedom to distribute copies of free software (and charge for |  | ||||||
| them if you wish), that you receive source code or can get it if you |  | ||||||
| want it, that you can change the software or use pieces of it in new |  | ||||||
| free programs, and that you know you can do these things. |  | ||||||
|  |  | ||||||
|   To protect your rights, we need to prevent others from denying you |  | ||||||
| these rights or asking you to surrender the rights.  Therefore, you have |  | ||||||
| certain responsibilities if you distribute copies of the software, or if |  | ||||||
| you modify it: responsibilities to respect the freedom of others. |  | ||||||
|  |  | ||||||
|   For example, if you distribute copies of such a program, whether |  | ||||||
| gratis or for a fee, you must pass on to the recipients the same |  | ||||||
| freedoms that you received.  You must make sure that they, too, receive |  | ||||||
| or can get the source code.  And you must show them these terms so they |  | ||||||
| know their rights. |  | ||||||
|  |  | ||||||
|   Developers that use the GNU GPL protect your rights with two steps: |  | ||||||
| (1) assert copyright on the software, and (2) offer you this License |  | ||||||
| giving you legal permission to copy, distribute and/or modify it. |  | ||||||
|  |  | ||||||
|   For the developers' and authors' protection, the GPL clearly explains |  | ||||||
| that there is no warranty for this free software.  For both users' and |  | ||||||
| authors' sake, the GPL requires that modified versions be marked as |  | ||||||
| changed, so that their problems will not be attributed erroneously to |  | ||||||
| authors of previous versions. |  | ||||||
|  |  | ||||||
|   Some devices are designed to deny users access to install or run |  | ||||||
| modified versions of the software inside them, although the manufacturer |  | ||||||
| can do so.  This is fundamentally incompatible with the aim of |  | ||||||
| protecting users' freedom to change the software.  The systematic |  | ||||||
| pattern of such abuse occurs in the area of products for individuals to |  | ||||||
| use, which is precisely where it is most unacceptable.  Therefore, we |  | ||||||
| have designed this version of the GPL to prohibit the practice for those |  | ||||||
| products.  If such problems arise substantially in other domains, we |  | ||||||
| stand ready to extend this provision to those domains in future versions |  | ||||||
| of the GPL, as needed to protect the freedom of users. |  | ||||||
|  |  | ||||||
|   Finally, every program is threatened constantly by software patents. |  | ||||||
| States should not allow patents to restrict development and use of |  | ||||||
| software on general-purpose computers, but in those that do, we wish to |  | ||||||
| avoid the special danger that patents applied to a free program could |  | ||||||
| make it effectively proprietary.  To prevent this, the GPL assures that |  | ||||||
| patents cannot be used to render the program non-free. |  | ||||||
|  |  | ||||||
|   The precise terms and conditions for copying, distribution and |  | ||||||
| modification follow. |  | ||||||
|  |  | ||||||
|                        TERMS AND CONDITIONS |  | ||||||
|  |  | ||||||
|   0. Definitions. |  | ||||||
|  |  | ||||||
|   "This License" refers to version 3 of the GNU General Public License. |  | ||||||
|  |  | ||||||
|   "Copyright" also means copyright-like laws that apply to other kinds of |  | ||||||
| works, such as semiconductor masks. |  | ||||||
|  |  | ||||||
|   "The Program" refers to any copyrightable work licensed under this |  | ||||||
| License.  Each licensee is addressed as "you".  "Licensees" and |  | ||||||
| "recipients" may be individuals or organizations. |  | ||||||
|  |  | ||||||
|   To "modify" a work means to copy from or adapt all or part of the work |  | ||||||
| in a fashion requiring copyright permission, other than the making of an |  | ||||||
| exact copy.  The resulting work is called a "modified version" of the |  | ||||||
| earlier work or a work "based on" the earlier work. |  | ||||||
|  |  | ||||||
|   A "covered work" means either the unmodified Program or a work based |  | ||||||
| on the Program. |  | ||||||
|  |  | ||||||
|   To "propagate" a work means to do anything with it that, without |  | ||||||
| permission, would make you directly or secondarily liable for |  | ||||||
| infringement under applicable copyright law, except executing it on a |  | ||||||
| computer or modifying a private copy.  Propagation includes copying, |  | ||||||
| distribution (with or without modification), making available to the |  | ||||||
| public, and in some countries other activities as well. |  | ||||||
|  |  | ||||||
|   To "convey" a work means any kind of propagation that enables other |  | ||||||
| parties to make or receive copies.  Mere interaction with a user through |  | ||||||
| a computer network, with no transfer of a copy, is not conveying. |  | ||||||
|  |  | ||||||
|   An interactive user interface displays "Appropriate Legal Notices" |  | ||||||
| to the extent that it includes a convenient and prominently visible |  | ||||||
| feature that (1) displays an appropriate copyright notice, and (2) |  | ||||||
| tells the user that there is no warranty for the work (except to the |  | ||||||
| extent that warranties are provided), that licensees may convey the |  | ||||||
| work under this License, and how to view a copy of this License.  If |  | ||||||
| the interface presents a list of user commands or options, such as a |  | ||||||
| menu, a prominent item in the list meets this criterion. |  | ||||||
|  |  | ||||||
|   1. Source Code. |  | ||||||
|  |  | ||||||
|   The "source code" for a work means the preferred form of the work |  | ||||||
| for making modifications to it.  "Object code" means any non-source |  | ||||||
| form of a work. |  | ||||||
|  |  | ||||||
|   A "Standard Interface" means an interface that either is an official |  | ||||||
| standard defined by a recognized standards body, or, in the case of |  | ||||||
| interfaces specified for a particular programming language, one that |  | ||||||
| is widely used among developers working in that language. |  | ||||||
|  |  | ||||||
|   The "System Libraries" of an executable work include anything, other |  | ||||||
| than the work as a whole, that (a) is included in the normal form of |  | ||||||
| packaging a Major Component, but which is not part of that Major |  | ||||||
| Component, and (b) serves only to enable use of the work with that |  | ||||||
| Major Component, or to implement a Standard Interface for which an |  | ||||||
| implementation is available to the public in source code form.  A |  | ||||||
| "Major Component", in this context, means a major essential component |  | ||||||
| (kernel, window system, and so on) of the specific operating system |  | ||||||
| (if any) on which the executable work runs, or a compiler used to |  | ||||||
| produce the work, or an object code interpreter used to run it. |  | ||||||
|  |  | ||||||
|   The "Corresponding Source" for a work in object code form means all |  | ||||||
| the source code needed to generate, install, and (for an executable |  | ||||||
| work) run the object code and to modify the work, including scripts to |  | ||||||
| control those activities.  However, it does not include the work's |  | ||||||
| System Libraries, or general-purpose tools or generally available free |  | ||||||
| programs which are used unmodified in performing those activities but |  | ||||||
| which are not part of the work.  For example, Corresponding Source |  | ||||||
| includes interface definition files associated with source files for |  | ||||||
| the work, and the source code for shared libraries and dynamically |  | ||||||
| linked subprograms that the work is specifically designed to require, |  | ||||||
| such as by intimate data communication or control flow between those |  | ||||||
| subprograms and other parts of the work. |  | ||||||
|  |  | ||||||
|   The Corresponding Source need not include anything that users |  | ||||||
| can regenerate automatically from other parts of the Corresponding |  | ||||||
| Source. |  | ||||||
|  |  | ||||||
|   The Corresponding Source for a work in source code form is that |  | ||||||
| same work. |  | ||||||
|  |  | ||||||
|   2. Basic Permissions. |  | ||||||
|  |  | ||||||
|   All rights granted under this License are granted for the term of |  | ||||||
| copyright on the Program, and are irrevocable provided the stated |  | ||||||
| conditions are met.  This License explicitly affirms your unlimited |  | ||||||
| permission to run the unmodified Program.  The output from running a |  | ||||||
| covered work is covered by this License only if the output, given its |  | ||||||
| content, constitutes a covered work.  This License acknowledges your |  | ||||||
| rights of fair use or other equivalent, as provided by copyright law. |  | ||||||
|  |  | ||||||
|   You may make, run and propagate covered works that you do not |  | ||||||
| convey, without conditions so long as your license otherwise remains |  | ||||||
| in force.  You may convey covered works to others for the sole purpose |  | ||||||
| of having them make modifications exclusively for you, or provide you |  | ||||||
| with facilities for running those works, provided that you comply with |  | ||||||
| the terms of this License in conveying all material for which you do |  | ||||||
| not control copyright.  Those thus making or running the covered works |  | ||||||
| for you must do so exclusively on your behalf, under your direction |  | ||||||
| and control, on terms that prohibit them from making any copies of |  | ||||||
| your copyrighted material outside their relationship with you. |  | ||||||
|  |  | ||||||
|   Conveying under any other circumstances is permitted solely under |  | ||||||
| the conditions stated below.  Sublicensing is not allowed; section 10 |  | ||||||
| makes it unnecessary. |  | ||||||
|  |  | ||||||
|   3. Protecting Users' Legal Rights From Anti-Circumvention Law. |  | ||||||
|  |  | ||||||
|   No covered work shall be deemed part of an effective technological |  | ||||||
| measure under any applicable law fulfilling obligations under article |  | ||||||
| 11 of the WIPO copyright treaty adopted on 20 December 1996, or |  | ||||||
| similar laws prohibiting or restricting circumvention of such |  | ||||||
| measures. |  | ||||||
|  |  | ||||||
|   When you convey a covered work, you waive any legal power to forbid |  | ||||||
| circumvention of technological measures to the extent such circumvention |  | ||||||
| is effected by exercising rights under this License with respect to |  | ||||||
| the covered work, and you disclaim any intention to limit operation or |  | ||||||
| modification of the work as a means of enforcing, against the work's |  | ||||||
| users, your or third parties' legal rights to forbid circumvention of |  | ||||||
| technological measures. |  | ||||||
|  |  | ||||||
|   4. Conveying Verbatim Copies. |  | ||||||
|  |  | ||||||
|   You may convey verbatim copies of the Program's source code as you |  | ||||||
| receive it, in any medium, provided that you conspicuously and |  | ||||||
| appropriately publish on each copy an appropriate copyright notice; |  | ||||||
| keep intact all notices stating that this License and any |  | ||||||
| non-permissive terms added in accord with section 7 apply to the code; |  | ||||||
| keep intact all notices of the absence of any warranty; and give all |  | ||||||
| recipients a copy of this License along with the Program. |  | ||||||
|  |  | ||||||
|   You may charge any price or no price for each copy that you convey, |  | ||||||
| and you may offer support or warranty protection for a fee. |  | ||||||
|  |  | ||||||
|   5. Conveying Modified Source Versions. |  | ||||||
|  |  | ||||||
|   You may convey a work based on the Program, or the modifications to |  | ||||||
| produce it from the Program, in the form of source code under the |  | ||||||
| terms of section 4, provided that you also meet all of these conditions: |  | ||||||
|  |  | ||||||
|     a) The work must carry prominent notices stating that you modified |  | ||||||
|     it, and giving a relevant date. |  | ||||||
|  |  | ||||||
|     b) The work must carry prominent notices stating that it is |  | ||||||
|     released under this License and any conditions added under section |  | ||||||
|     7.  This requirement modifies the requirement in section 4 to |  | ||||||
|     "keep intact all notices". |  | ||||||
|  |  | ||||||
|     c) You must license the entire work, as a whole, under this |  | ||||||
|     License to anyone who comes into possession of a copy.  This |  | ||||||
|     License will therefore apply, along with any applicable section 7 |  | ||||||
|     additional terms, to the whole of the work, and all its parts, |  | ||||||
|     regardless of how they are packaged.  This License gives no |  | ||||||
|     permission to license the work in any other way, but it does not |  | ||||||
|     invalidate such permission if you have separately received it. |  | ||||||
|  |  | ||||||
|     d) If the work has interactive user interfaces, each must display |  | ||||||
|     Appropriate Legal Notices; however, if the Program has interactive |  | ||||||
|     interfaces that do not display Appropriate Legal Notices, your |  | ||||||
|     work need not make them do so. |  | ||||||
|  |  | ||||||
|   A compilation of a covered work with other separate and independent |  | ||||||
| works, which are not by their nature extensions of the covered work, |  | ||||||
| and which are not combined with it such as to form a larger program, |  | ||||||
| in or on a volume of a storage or distribution medium, is called an |  | ||||||
| "aggregate" if the compilation and its resulting copyright are not |  | ||||||
| used to limit the access or legal rights of the compilation's users |  | ||||||
| beyond what the individual works permit.  Inclusion of a covered work |  | ||||||
| in an aggregate does not cause this License to apply to the other |  | ||||||
| parts of the aggregate. |  | ||||||
|  |  | ||||||
|   6. Conveying Non-Source Forms. |  | ||||||
|  |  | ||||||
|   You may convey a covered work in object code form under the terms |  | ||||||
| of sections 4 and 5, provided that you also convey the |  | ||||||
| machine-readable Corresponding Source under the terms of this License, |  | ||||||
| in one of these ways: |  | ||||||
|  |  | ||||||
|     a) Convey the object code in, or embodied in, a physical product |  | ||||||
|     (including a physical distribution medium), accompanied by the |  | ||||||
|     Corresponding Source fixed on a durable physical medium |  | ||||||
|     customarily used for software interchange. |  | ||||||
|  |  | ||||||
|     b) Convey the object code in, or embodied in, a physical product |  | ||||||
|     (including a physical distribution medium), accompanied by a |  | ||||||
|     written offer, valid for at least three years and valid for as |  | ||||||
|     long as you offer spare parts or customer support for that product |  | ||||||
|     model, to give anyone who possesses the object code either (1) a |  | ||||||
|     copy of the Corresponding Source for all the software in the |  | ||||||
|     product that is covered by this License, on a durable physical |  | ||||||
|     medium customarily used for software interchange, for a price no |  | ||||||
|     more than your reasonable cost of physically performing this |  | ||||||
|     conveying of source, or (2) access to copy the |  | ||||||
|     Corresponding Source from a network server at no charge. |  | ||||||
|  |  | ||||||
|     c) Convey individual copies of the object code with a copy of the |  | ||||||
|     written offer to provide the Corresponding Source.  This |  | ||||||
|     alternative is allowed only occasionally and noncommercially, and |  | ||||||
|     only if you received the object code with such an offer, in accord |  | ||||||
|     with subsection 6b. |  | ||||||
|  |  | ||||||
|     d) Convey the object code by offering access from a designated |  | ||||||
|     place (gratis or for a charge), and offer equivalent access to the |  | ||||||
|     Corresponding Source in the same way through the same place at no |  | ||||||
|     further charge.  You need not require recipients to copy the |  | ||||||
|     Corresponding Source along with the object code.  If the place to |  | ||||||
|     copy the object code is a network server, the Corresponding Source |  | ||||||
|     may be on a different server (operated by you or a third party) |  | ||||||
|     that supports equivalent copying facilities, provided you maintain |  | ||||||
|     clear directions next to the object code saying where to find the |  | ||||||
|     Corresponding Source.  Regardless of what server hosts the |  | ||||||
|     Corresponding Source, you remain obligated to ensure that it is |  | ||||||
|     available for as long as needed to satisfy these requirements. |  | ||||||
|  |  | ||||||
|     e) Convey the object code using peer-to-peer transmission, provided |  | ||||||
|     you inform other peers where the object code and Corresponding |  | ||||||
|     Source of the work are being offered to the general public at no |  | ||||||
|     charge under subsection 6d. |  | ||||||
|  |  | ||||||
|   A separable portion of the object code, whose source code is excluded |  | ||||||
| from the Corresponding Source as a System Library, need not be |  | ||||||
| included in conveying the object code work. |  | ||||||
|  |  | ||||||
|   A "User Product" is either (1) a "consumer product", which means any |  | ||||||
| tangible personal property which is normally used for personal, family, |  | ||||||
| or household purposes, or (2) anything designed or sold for incorporation |  | ||||||
| into a dwelling.  In determining whether a product is a consumer product, |  | ||||||
| doubtful cases shall be resolved in favor of coverage.  For a particular |  | ||||||
| product received by a particular user, "normally used" refers to a |  | ||||||
| typical or common use of that class of product, regardless of the status |  | ||||||
| of the particular user or of the way in which the particular user |  | ||||||
| actually uses, or expects or is expected to use, the product.  A product |  | ||||||
| is a consumer product regardless of whether the product has substantial |  | ||||||
| commercial, industrial or non-consumer uses, unless such uses represent |  | ||||||
| the only significant mode of use of the product. |  | ||||||
|  |  | ||||||
|   "Installation Information" for a User Product means any methods, |  | ||||||
| procedures, authorization keys, or other information required to install |  | ||||||
| and execute modified versions of a covered work in that User Product from |  | ||||||
| a modified version of its Corresponding Source.  The information must |  | ||||||
| suffice to ensure that the continued functioning of the modified object |  | ||||||
| code is in no case prevented or interfered with solely because |  | ||||||
| modification has been made. |  | ||||||
|  |  | ||||||
|   If you convey an object code work under this section in, or with, or |  | ||||||
| specifically for use in, a User Product, and the conveying occurs as |  | ||||||
| part of a transaction in which the right of possession and use of the |  | ||||||
| User Product is transferred to the recipient in perpetuity or for a |  | ||||||
| fixed term (regardless of how the transaction is characterized), the |  | ||||||
| Corresponding Source conveyed under this section must be accompanied |  | ||||||
| by the Installation Information.  But this requirement does not apply |  | ||||||
| if neither you nor any third party retains the ability to install |  | ||||||
| modified object code on the User Product (for example, the work has |  | ||||||
| been installed in ROM). |  | ||||||
|  |  | ||||||
|   The requirement to provide Installation Information does not include a |  | ||||||
| requirement to continue to provide support service, warranty, or updates |  | ||||||
| for a work that has been modified or installed by the recipient, or for |  | ||||||
| the User Product in which it has been modified or installed.  Access to a |  | ||||||
| network may be denied when the modification itself materially and |  | ||||||
| adversely affects the operation of the network or violates the rules and |  | ||||||
| protocols for communication across the network. |  | ||||||
|  |  | ||||||
|   Corresponding Source conveyed, and Installation Information provided, |  | ||||||
| in accord with this section must be in a format that is publicly |  | ||||||
| documented (and with an implementation available to the public in |  | ||||||
| source code form), and must require no special password or key for |  | ||||||
| unpacking, reading or copying. |  | ||||||
|  |  | ||||||
|   7. Additional Terms. |  | ||||||
|  |  | ||||||
|   "Additional permissions" are terms that supplement the terms of this |  | ||||||
| License by making exceptions from one or more of its conditions. |  | ||||||
| Additional permissions that are applicable to the entire Program shall |  | ||||||
| be treated as though they were included in this License, to the extent |  | ||||||
| that they are valid under applicable law.  If additional permissions |  | ||||||
| apply only to part of the Program, that part may be used separately |  | ||||||
| under those permissions, but the entire Program remains governed by |  | ||||||
| this License without regard to the additional permissions. |  | ||||||
|  |  | ||||||
|   When you convey a copy of a covered work, you may at your option |  | ||||||
| remove any additional permissions from that copy, or from any part of |  | ||||||
| it.  (Additional permissions may be written to require their own |  | ||||||
| removal in certain cases when you modify the work.)  You may place |  | ||||||
| additional permissions on material, added by you to a covered work, |  | ||||||
| for which you have or can give appropriate copyright permission. |  | ||||||
|  |  | ||||||
|   Notwithstanding any other provision of this License, for material you |  | ||||||
| add to a covered work, you may (if authorized by the copyright holders of |  | ||||||
| that material) supplement the terms of this License with terms: |  | ||||||
|  |  | ||||||
|     a) Disclaiming warranty or limiting liability differently from the |  | ||||||
|     terms of sections 15 and 16 of this License; or |  | ||||||
|  |  | ||||||
|     b) Requiring preservation of specified reasonable legal notices or |  | ||||||
|     author attributions in that material or in the Appropriate Legal |  | ||||||
|     Notices displayed by works containing it; or |  | ||||||
|  |  | ||||||
|     c) Prohibiting misrepresentation of the origin of that material, or |  | ||||||
|     requiring that modified versions of such material be marked in |  | ||||||
|     reasonable ways as different from the original version; or |  | ||||||
|  |  | ||||||
|     d) Limiting the use for publicity purposes of names of licensors or |  | ||||||
|     authors of the material; or |  | ||||||
|  |  | ||||||
|     e) Declining to grant rights under trademark law for use of some |  | ||||||
|     trade names, trademarks, or service marks; or |  | ||||||
|  |  | ||||||
|     f) Requiring indemnification of licensors and authors of that |  | ||||||
|     material by anyone who conveys the material (or modified versions of |  | ||||||
|     it) with contractual assumptions of liability to the recipient, for |  | ||||||
|     any liability that these contractual assumptions directly impose on |  | ||||||
|     those licensors and authors. |  | ||||||
|  |  | ||||||
|   All other non-permissive additional terms are considered "further |  | ||||||
| restrictions" within the meaning of section 10.  If the Program as you |  | ||||||
| received it, or any part of it, contains a notice stating that it is |  | ||||||
| governed by this License along with a term that is a further |  | ||||||
| restriction, you may remove that term.  If a license document contains |  | ||||||
| a further restriction but permits relicensing or conveying under this |  | ||||||
| License, you may add to a covered work material governed by the terms |  | ||||||
| of that license document, provided that the further restriction does |  | ||||||
| not survive such relicensing or conveying. |  | ||||||
|  |  | ||||||
|   If you add terms to a covered work in accord with this section, you |  | ||||||
| must place, in the relevant source files, a statement of the |  | ||||||
| additional terms that apply to those files, or a notice indicating |  | ||||||
| where to find the applicable terms. |  | ||||||
|  |  | ||||||
|   Additional terms, permissive or non-permissive, may be stated in the |  | ||||||
| form of a separately written license, or stated as exceptions; |  | ||||||
| the above requirements apply either way. |  | ||||||
|  |  | ||||||
|   8. Termination. |  | ||||||
|  |  | ||||||
|   You may not propagate or modify a covered work except as expressly |  | ||||||
| provided under this License.  Any attempt otherwise to propagate or |  | ||||||
| modify it is void, and will automatically terminate your rights under |  | ||||||
| this License (including any patent licenses granted under the third |  | ||||||
| paragraph of section 11). |  | ||||||
|  |  | ||||||
|   However, if you cease all violation of this License, then your |  | ||||||
| license from a particular copyright holder is reinstated (a) |  | ||||||
| provisionally, unless and until the copyright holder explicitly and |  | ||||||
| finally terminates your license, and (b) permanently, if the copyright |  | ||||||
| holder fails to notify you of the violation by some reasonable means |  | ||||||
| prior to 60 days after the cessation. |  | ||||||
|  |  | ||||||
|   Moreover, your license from a particular copyright holder is |  | ||||||
| reinstated permanently if the copyright holder notifies you of the |  | ||||||
| violation by some reasonable means, this is the first time you have |  | ||||||
| received notice of violation of this License (for any work) from that |  | ||||||
| copyright holder, and you cure the violation prior to 30 days after |  | ||||||
| your receipt of the notice. |  | ||||||
|  |  | ||||||
|   Termination of your rights under this section does not terminate the |  | ||||||
| licenses of parties who have received copies or rights from you under |  | ||||||
| this License.  If your rights have been terminated and not permanently |  | ||||||
| reinstated, you do not qualify to receive new licenses for the same |  | ||||||
| material under section 10. |  | ||||||
|  |  | ||||||
|   9. Acceptance Not Required for Having Copies. |  | ||||||
|  |  | ||||||
|   You are not required to accept this License in order to receive or |  | ||||||
| run a copy of the Program.  Ancillary propagation of a covered work |  | ||||||
| occurring solely as a consequence of using peer-to-peer transmission |  | ||||||
| to receive a copy likewise does not require acceptance.  However, |  | ||||||
| nothing other than this License grants you permission to propagate or |  | ||||||
| modify any covered work.  These actions infringe copyright if you do |  | ||||||
| not accept this License.  Therefore, by modifying or propagating a |  | ||||||
| covered work, you indicate your acceptance of this License to do so. |  | ||||||
|  |  | ||||||
|   10. Automatic Licensing of Downstream Recipients. |  | ||||||
|  |  | ||||||
|   Each time you convey a covered work, the recipient automatically |  | ||||||
| receives a license from the original licensors, to run, modify and |  | ||||||
| propagate that work, subject to this License.  You are not responsible |  | ||||||
| for enforcing compliance by third parties with this License. |  | ||||||
|  |  | ||||||
|   An "entity transaction" is a transaction transferring control of an |  | ||||||
| organization, or substantially all assets of one, or subdividing an |  | ||||||
| organization, or merging organizations.  If propagation of a covered |  | ||||||
| work results from an entity transaction, each party to that |  | ||||||
| transaction who receives a copy of the work also receives whatever |  | ||||||
| licenses to the work the party's predecessor in interest had or could |  | ||||||
| give under the previous paragraph, plus a right to possession of the |  | ||||||
| Corresponding Source of the work from the predecessor in interest, if |  | ||||||
| the predecessor has it or can get it with reasonable efforts. |  | ||||||
|  |  | ||||||
|   You may not impose any further restrictions on the exercise of the |  | ||||||
| rights granted or affirmed under this License.  For example, you may |  | ||||||
| not impose a license fee, royalty, or other charge for exercise of |  | ||||||
| rights granted under this License, and you may not initiate litigation |  | ||||||
| (including a cross-claim or counterclaim in a lawsuit) alleging that |  | ||||||
| any patent claim is infringed by making, using, selling, offering for |  | ||||||
| sale, or importing the Program or any portion of it. |  | ||||||
|  |  | ||||||
|   11. Patents. |  | ||||||
|  |  | ||||||
|   A "contributor" is a copyright holder who authorizes use under this |  | ||||||
| License of the Program or a work on which the Program is based.  The |  | ||||||
| work thus licensed is called the contributor's "contributor version". |  | ||||||
|  |  | ||||||
|   A contributor's "essential patent claims" are all patent claims |  | ||||||
| owned or controlled by the contributor, whether already acquired or |  | ||||||
| hereafter acquired, that would be infringed by some manner, permitted |  | ||||||
| by this License, of making, using, or selling its contributor version, |  | ||||||
| but do not include claims that would be infringed only as a |  | ||||||
| consequence of further modification of the contributor version.  For |  | ||||||
| purposes of this definition, "control" includes the right to grant |  | ||||||
| patent sublicenses in a manner consistent with the requirements of |  | ||||||
| this License. |  | ||||||
|  |  | ||||||
|   Each contributor grants you a non-exclusive, worldwide, royalty-free |  | ||||||
| patent license under the contributor's essential patent claims, to |  | ||||||
| make, use, sell, offer for sale, import and otherwise run, modify and |  | ||||||
| propagate the contents of its contributor version. |  | ||||||
|  |  | ||||||
|   In the following three paragraphs, a "patent license" is any express |  | ||||||
| agreement or commitment, however denominated, not to enforce a patent |  | ||||||
| (such as an express permission to practice a patent or covenant not to |  | ||||||
| sue for patent infringement).  To "grant" such a patent license to a |  | ||||||
| party means to make such an agreement or commitment not to enforce a |  | ||||||
| patent against the party. |  | ||||||
|  |  | ||||||
|   If you convey a covered work, knowingly relying on a patent license, |  | ||||||
| and the Corresponding Source of the work is not available for anyone |  | ||||||
| to copy, free of charge and under the terms of this License, through a |  | ||||||
| publicly available network server or other readily accessible means, |  | ||||||
| then you must either (1) cause the Corresponding Source to be so |  | ||||||
| available, or (2) arrange to deprive yourself of the benefit of the |  | ||||||
| patent license for this particular work, or (3) arrange, in a manner |  | ||||||
| consistent with the requirements of this License, to extend the patent |  | ||||||
| license to downstream recipients.  "Knowingly relying" means you have |  | ||||||
| actual knowledge that, but for the patent license, your conveying the |  | ||||||
| covered work in a country, or your recipient's use of the covered work |  | ||||||
| in a country, would infringe one or more identifiable patents in that |  | ||||||
| country that you have reason to believe are valid. |  | ||||||
|  |  | ||||||
|   If, pursuant to or in connection with a single transaction or |  | ||||||
| arrangement, you convey, or propagate by procuring conveyance of, a |  | ||||||
| covered work, and grant a patent license to some of the parties |  | ||||||
| receiving the covered work authorizing them to use, propagate, modify |  | ||||||
| or convey a specific copy of the covered work, then the patent license |  | ||||||
| you grant is automatically extended to all recipients of the covered |  | ||||||
| work and works based on it. |  | ||||||
|  |  | ||||||
|   A patent license is "discriminatory" if it does not include within |  | ||||||
| the scope of its coverage, prohibits the exercise of, or is |  | ||||||
| conditioned on the non-exercise of one or more of the rights that are |  | ||||||
| specifically granted under this License.  You may not convey a covered |  | ||||||
| work if you are a party to an arrangement with a third party that is |  | ||||||
| in the business of distributing software, under which you make payment |  | ||||||
| to the third party based on the extent of your activity of conveying |  | ||||||
| the work, and under which the third party grants, to any of the |  | ||||||
| parties who would receive the covered work from you, a discriminatory |  | ||||||
| patent license (a) in connection with copies of the covered work |  | ||||||
| conveyed by you (or copies made from those copies), or (b) primarily |  | ||||||
| for and in connection with specific products or compilations that |  | ||||||
| contain the covered work, unless you entered into that arrangement, |  | ||||||
| or that patent license was granted, prior to 28 March 2007. |  | ||||||
|  |  | ||||||
|   Nothing in this License shall be construed as excluding or limiting |  | ||||||
| any implied license or other defenses to infringement that may |  | ||||||
| otherwise be available to you under applicable patent law. |  | ||||||
|  |  | ||||||
|   12. No Surrender of Others' Freedom. |  | ||||||
|  |  | ||||||
|   If conditions are imposed on you (whether by court order, agreement or |  | ||||||
| otherwise) that contradict the conditions of this License, they do not |  | ||||||
| excuse you from the conditions of this License.  If you cannot convey a |  | ||||||
| covered work so as to satisfy simultaneously your obligations under this |  | ||||||
| License and any other pertinent obligations, then as a consequence you may |  | ||||||
| not convey it at all.  For example, if you agree to terms that obligate you |  | ||||||
| to collect a royalty for further conveying from those to whom you convey |  | ||||||
| the Program, the only way you could satisfy both those terms and this |  | ||||||
| License would be to refrain entirely from conveying the Program. |  | ||||||
|  |  | ||||||
|   13. Use with the GNU Affero General Public License. |  | ||||||
|  |  | ||||||
|   Notwithstanding any other provision of this License, you have |  | ||||||
| permission to link or combine any covered work with a work licensed |  | ||||||
| under version 3 of the GNU Affero General Public License into a single |  | ||||||
| combined work, and to convey the resulting work.  The terms of this |  | ||||||
| License will continue to apply to the part which is the covered work, |  | ||||||
| but the special requirements of the GNU Affero General Public License, |  | ||||||
| section 13, concerning interaction through a network will apply to the |  | ||||||
| combination as such. |  | ||||||
|  |  | ||||||
|   14. Revised Versions of this License. |  | ||||||
|  |  | ||||||
|   The Free Software Foundation may publish revised and/or new versions of |  | ||||||
| the GNU General Public License from time to time.  Such new versions will |  | ||||||
| be similar in spirit to the present version, but may differ in detail to |  | ||||||
| address new problems or concerns. |  | ||||||
|  |  | ||||||
|   Each version is given a distinguishing version number.  If the |  | ||||||
| Program specifies that a certain numbered version of the GNU General |  | ||||||
| Public License "or any later version" applies to it, you have the |  | ||||||
| option of following the terms and conditions either of that numbered |  | ||||||
| version or of any later version published by the Free Software |  | ||||||
| Foundation.  If the Program does not specify a version number of the |  | ||||||
| GNU General Public License, you may choose any version ever published |  | ||||||
| by the Free Software Foundation. |  | ||||||
|  |  | ||||||
|   If the Program specifies that a proxy can decide which future |  | ||||||
| versions of the GNU General Public License can be used, that proxy's |  | ||||||
| public statement of acceptance of a version permanently authorizes you |  | ||||||
| to choose that version for the Program. |  | ||||||
|  |  | ||||||
|   Later license versions may give you additional or different |  | ||||||
| permissions.  However, no additional obligations are imposed on any |  | ||||||
| author or copyright holder as a result of your choosing to follow a |  | ||||||
| later version. |  | ||||||
|  |  | ||||||
|   15. Disclaimer of Warranty. |  | ||||||
|  |  | ||||||
|   THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |  | ||||||
| APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |  | ||||||
| HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |  | ||||||
| OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |  | ||||||
| THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |  | ||||||
| PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |  | ||||||
| IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |  | ||||||
| ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |  | ||||||
|  |  | ||||||
|   16. Limitation of Liability. |  | ||||||
|  |  | ||||||
|   IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |  | ||||||
| WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |  | ||||||
| THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |  | ||||||
| GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |  | ||||||
| USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |  | ||||||
| DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |  | ||||||
| PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |  | ||||||
| EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |  | ||||||
| SUCH DAMAGES. |  | ||||||
|  |  | ||||||
|   17. Interpretation of Sections 15 and 16. |  | ||||||
|  |  | ||||||
|   If the disclaimer of warranty and limitation of liability provided |  | ||||||
| above cannot be given local legal effect according to their terms, |  | ||||||
| reviewing courts shall apply local law that most closely approximates |  | ||||||
| an absolute waiver of all civil liability in connection with the |  | ||||||
| Program, unless a warranty or assumption of liability accompanies a |  | ||||||
| copy of the Program in return for a fee. |  | ||||||
|  |  | ||||||
|                      END OF TERMS AND CONDITIONS |  | ||||||
|  |  | ||||||
|             How to Apply These Terms to Your New Programs |  | ||||||
|  |  | ||||||
|   If you develop a new program, and you want it to be of the greatest |  | ||||||
| possible use to the public, the best way to achieve this is to make it |  | ||||||
| free software which everyone can redistribute and change under these terms. |  | ||||||
|  |  | ||||||
|   To do so, attach the following notices to the program.  It is safest |  | ||||||
| to attach them to the start of each source file to most effectively |  | ||||||
| state the exclusion of warranty; and each file should have at least |  | ||||||
| the "copyright" line and a pointer to where the full notice is found. |  | ||||||
|  |  | ||||||
|     <one line to give the program's name and a brief idea of what it does.> |  | ||||||
|     Copyright (C) <year>  <name of author> |  | ||||||
|  |  | ||||||
|     This program is free software: you can redistribute it and/or modify |  | ||||||
|     it under the terms of the GNU General Public License as published by |  | ||||||
|     the Free Software Foundation, either version 3 of the License, or |  | ||||||
|     (at your option) any later version. |  | ||||||
|  |  | ||||||
|     This program is distributed in the hope that it will be useful, |  | ||||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
|     GNU General Public License for more details. |  | ||||||
|  |  | ||||||
|     You should have received a copy of the GNU General Public License |  | ||||||
|     along with this program.  If not, see <https://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
| Also add information on how to contact you by electronic and paper mail. |  | ||||||
|  |  | ||||||
|   If the program does terminal interaction, make it output a short |  | ||||||
| notice like this when it starts in an interactive mode: |  | ||||||
|  |  | ||||||
|     <program>  Copyright (C) <year>  <name of author> |  | ||||||
|     This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |  | ||||||
|     This is free software, and you are welcome to redistribute it |  | ||||||
|     under certain conditions; type `show c' for details. |  | ||||||
|  |  | ||||||
| The hypothetical commands `show w' and `show c' should show the appropriate |  | ||||||
| parts of the General Public License.  Of course, your program's commands |  | ||||||
| might be different; for a GUI interface, you would use an "about box". |  | ||||||
|  |  | ||||||
|   You should also get your employer (if you work as a programmer) or school, |  | ||||||
| if any, to sign a "copyright disclaimer" for the program, if necessary. |  | ||||||
| For more information on this, and how to apply and follow the GNU GPL, see |  | ||||||
| <https://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
|   The GNU General Public License does not permit incorporating your program |  | ||||||
| into proprietary programs.  If your program is a subroutine library, you |  | ||||||
| may consider it more useful to permit linking proprietary applications with |  | ||||||
| the library.  If this is what you want to do, use the GNU Lesser General |  | ||||||
| Public License instead of this License.  But first, please read |  | ||||||
| <https://www.gnu.org/licenses/why-not-lgpl.html>. |  | ||||||
							
								
								
									
										0
									
								
								mktxp/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								mktxp/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										35
									
								
								mktxp/basep.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								mktxp/basep.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | from http.server import HTTPServer | ||||||
|  | from prometheus_client.core import REGISTRY | ||||||
|  | from prometheus_client import MetricsHandler, start_http_server | ||||||
|  | from mktxp.collectors_handler import CollectorsHandler | ||||||
|  | from mktxp.metrics_handler import RouterMetricsHandler | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MKTXPProcessor: | ||||||
|  |     ''' Base Export Processing | ||||||
|  |     '''     | ||||||
|  |     @staticmethod | ||||||
|  |     def start(): | ||||||
|  |         router_metrics_handler = RouterMetricsHandler() | ||||||
|  |         REGISTRY.register(CollectorsHandler(router_metrics_handler)) | ||||||
|  |         MKTXPProcessor.run() | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def run(server_class=HTTPServer, handler_class=MetricsHandler, port=8000): | ||||||
|  |         server_address = ('', port) | ||||||
|  |         httpd = server_class(server_address, handler_class) | ||||||
|  |         httpd.serve_forever() | ||||||
							
								
								
									
										0
									
								
								mktxp/cli/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								mktxp/cli/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								mktxp/cli/checks/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								mktxp/cli/checks/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										48
									
								
								mktxp/cli/checks/chk_pv.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										48
									
								
								mktxp/cli/checks/chk_pv.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | ''' Python version check | ||||||
|  | ''' | ||||||
|  |  | ||||||
|  | from __future__ import print_function | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | def check_version(): | ||||||
|  |     if sys.version_info.major < 3: | ||||||
|  |         print(\ | ||||||
|  |         ''' | ||||||
|  |         Mikrotik Prometheus Exporter requires | ||||||
|  |                            Python version 3.6 or later. | ||||||
|  |  | ||||||
|  |         You can create an isolated Python 3.6 environment | ||||||
|  |         with the virtualenv tool: | ||||||
|  |           http://docs.python-guide.org/en/latest/dev/virtualenvs | ||||||
|  |  | ||||||
|  |         ''') | ||||||
|  |         sys.exit(0) | ||||||
|  |     elif sys.version_info.major == 3 and sys.version_info.minor < 6: | ||||||
|  |         print(\ | ||||||
|  |         ''' | ||||||
|  |  | ||||||
|  |         Mikrotik Prometheus Exporter requires | ||||||
|  |                             Python version 3.6 or later. | ||||||
|  |  | ||||||
|  |         Please upgrade to the latest Python 3.x version. | ||||||
|  |  | ||||||
|  |         ''') | ||||||
|  |         sys.exit(0) | ||||||
|  |  | ||||||
|  | # check | ||||||
|  | check_version() | ||||||
							
								
								
									
										0
									
								
								mktxp/cli/config/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								mktxp/cli/config/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										209
									
								
								mktxp/cli/config/config.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										209
									
								
								mktxp/cli/config/config.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,209 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | import os, sys, shutil | ||||||
|  | from collections import namedtuple | ||||||
|  | from configobj import ConfigObj | ||||||
|  | from abc import ABCMeta, abstractmethod | ||||||
|  | from pkg_resources import Requirement, resource_filename | ||||||
|  | from mktxp.utils.utils import FSHelper | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ''' MKTXP conf file handling | ||||||
|  | ''' | ||||||
|  |  | ||||||
|  | class MKTXPConfigKeys: | ||||||
|  |     ''' MKTXP config file keys | ||||||
|  |     ''' | ||||||
|  |     # Section Keys | ||||||
|  |     ENABLED_KEY = 'enabled' | ||||||
|  |     HOST_KEY = 'hostname' | ||||||
|  |     PORT_KEY = 'port' | ||||||
|  |     USER_KEY = 'username' | ||||||
|  |     PASSWD_KEY = 'password' | ||||||
|  |  | ||||||
|  |     SSL_KEY = 'use_ssl' | ||||||
|  |     SSL_CERTIFICATE = 'ssl_certificate' | ||||||
|  |  | ||||||
|  |     FE_DHCP_KEY = 'dhcp' | ||||||
|  |     FE_DHCP_LEASE_KEY = 'dhcp_lease'     | ||||||
|  |     FE_DHCP_POOL_KEY = 'pool'     | ||||||
|  |     FE_INTERFACE_KEY = 'interface' | ||||||
|  |     FE_MONITOR_KEY = 'monitor' | ||||||
|  |     FE_ROUTE_KEY = 'route' | ||||||
|  |     FE_WIRELESS_KEY = 'wireless' | ||||||
|  |     FE_CAPSMAN_KEY = 'capsman' | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     # UnRegistered entries placeholder | ||||||
|  |     NO_ENTRIES_REGISTERED = 'NoEntriesRegistered' | ||||||
|  |  | ||||||
|  |     # Base router id labels | ||||||
|  |     ROUTERBOARD_NAME = 'routerboard_name' | ||||||
|  |     ROUTERBOARD_ADDRESS = 'routerboard_address' | ||||||
|  |  | ||||||
|  |     # Default ports | ||||||
|  |     DEFAULT_API_PORT = 8728 | ||||||
|  |     DEFAULT_API_SSL_PORT = 8729 | ||||||
|  |  | ||||||
|  |     BOOLEAN_KEYS = [ENABLED_KEY, SSL_KEY, SSL_CERTIFICATE,   | ||||||
|  |                       FE_DHCP_KEY, FE_DHCP_LEASE_KEY, FE_DHCP_POOL_KEY, FE_INTERFACE_KEY,  | ||||||
|  |                       FE_MONITOR_KEY, FE_ROUTE_KEY, FE_WIRELESS_KEY, FE_CAPSMAN_KEY] | ||||||
|  |     STR_KEYS = [HOST_KEY, USER_KEY, PASSWD_KEY] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ConfigEntry: | ||||||
|  |     MKTXPEntry = namedtuple('MKTXPEntry', [MKTXPConfigKeys.ENABLED_KEY, MKTXPConfigKeys.HOST_KEY, MKTXPConfigKeys.PORT_KEY,  | ||||||
|  |                          MKTXPConfigKeys.USER_KEY, MKTXPConfigKeys.PASSWD_KEY,  | ||||||
|  |                          MKTXPConfigKeys.SSL_KEY, MKTXPConfigKeys.SSL_CERTIFICATE,  | ||||||
|  |                           | ||||||
|  |                          MKTXPConfigKeys.FE_DHCP_KEY, MKTXPConfigKeys.FE_DHCP_LEASE_KEY, MKTXPConfigKeys.FE_DHCP_POOL_KEY, MKTXPConfigKeys.FE_INTERFACE_KEY,  | ||||||
|  |                          MKTXPConfigKeys.FE_MONITOR_KEY, MKTXPConfigKeys.FE_ROUTE_KEY, MKTXPConfigKeys.FE_WIRELESS_KEY, MKTXPConfigKeys.FE_CAPSMAN_KEY | ||||||
|  |                          ]) | ||||||
|  |  | ||||||
|  | class OSConfig(metaclass = ABCMeta): | ||||||
|  |     ''' OS-related config | ||||||
|  |     ''' | ||||||
|  |     @staticmethod | ||||||
|  |     def os_config(quiet = False): | ||||||
|  |         ''' Factory method | ||||||
|  |         ''' | ||||||
|  |         if sys.platform == 'linux': | ||||||
|  |             return LinuxConfig() | ||||||
|  |         elif sys.platform == 'darwin': | ||||||
|  |             return OSXConfig() | ||||||
|  |         else: | ||||||
|  |             if not quiet: | ||||||
|  |                 print('Non-supported platform: {}'.format(sys.platform)) | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     @abstractmethod | ||||||
|  |     def mktxp_user_dir_path(self): | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OSXConfig(OSConfig): | ||||||
|  |     ''' OSX-related config | ||||||
|  |     ''' | ||||||
|  |     @property | ||||||
|  |     def mktxp_user_dir_path(self): | ||||||
|  |         return FSHelper.full_path('~/mktxp') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LinuxConfig(OSConfig): | ||||||
|  |     ''' Linux-related config | ||||||
|  |     ''' | ||||||
|  |     @property | ||||||
|  |     def mktxp_user_dir_path(self): | ||||||
|  |         return FSHelper.full_path('/etc/mktxp') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MKTXPConfigHandler: | ||||||
|  |     def __init__(self): | ||||||
|  |         self.os_config = OSConfig.os_config() | ||||||
|  |         if not self.os_config: | ||||||
|  |             sys.exit(1) | ||||||
|  |  | ||||||
|  |         # mktxp user config folder | ||||||
|  |         if not os.path.exists(self.os_config.mktxp_user_dir_path): | ||||||
|  |             os.makedirs(self.os_config.mktxp_user_dir_path) | ||||||
|  |  | ||||||
|  |         # if needed, stage the user config data | ||||||
|  |         self.usr_conf_data_path = os.path.join(self.os_config.mktxp_user_dir_path, 'mktxp.conf') | ||||||
|  |         if not os.path.exists(self.usr_conf_data_path): | ||||||
|  |             # stage from the mktxp conf template | ||||||
|  |             lookup_path = resource_filename(Requirement.parse("mktxp"), "mktxp/cli/config/mktxp.conf") | ||||||
|  |             shutil.copy(lookup_path, self.usr_conf_data_path) | ||||||
|  |  | ||||||
|  |         self.read_from_disk() | ||||||
|  |  | ||||||
|  |     def read_from_disk(self): | ||||||
|  |         ''' (Force-)Read conf data from disk | ||||||
|  |         ''' | ||||||
|  |         self.config = ConfigObj(self.usr_conf_data_path) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     # MKTXP entries | ||||||
|  |     ############## | ||||||
|  |     def register_entry(self, entry_name, entry_info, quiet = False): | ||||||
|  |         ''' Registers MKTXP conf entry | ||||||
|  |         ''' | ||||||
|  |         if entry_name in self.registered_entries(): | ||||||
|  |             if not quiet: | ||||||
|  |                 print('"{0}": entry name already registered'.format(entry_name)) | ||||||
|  |             return False | ||||||
|  |         else: | ||||||
|  |             self.config[entry_name] = dict(entry_info._asdict()) | ||||||
|  |             print(f'adding entry: {self.config[entry_name]}') | ||||||
|  |             self.config.write() | ||||||
|  |             if not quiet: | ||||||
|  |                 print('Entry registered: {0}'.format(entry_name)) | ||||||
|  |             return True | ||||||
|  |  | ||||||
|  |     def unregister_entry(self, entry_name, quiet = False): | ||||||
|  |         ''' Un-registers MKTXP conf entry | ||||||
|  |         ''' | ||||||
|  |         if self.config[entry_name]: | ||||||
|  |             del(self.config[entry_name]) | ||||||
|  |             self.config.write() | ||||||
|  |             if not quiet: | ||||||
|  |                 print('Unregistered entry: {}'.format(entry_name)) | ||||||
|  |             return True | ||||||
|  |         else: | ||||||
|  |             if not quiet: | ||||||
|  |                 print('Entry is not registered: {}'.format(entry_name)) | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |     def registered_entries(self): | ||||||
|  |         ''' All MKTXP registered entries | ||||||
|  |         ''' | ||||||
|  |         registered_entries = [entry_name for entry_name in self.config.keys()] | ||||||
|  |         if not registered_entries: | ||||||
|  |             registered_entries = [MKTXPConfigKeys.NO_ENTRIES_REGISTERED] | ||||||
|  |  | ||||||
|  |         return registered_entries | ||||||
|  |  | ||||||
|  |     def entry(self, entry_name): | ||||||
|  |         ''' Given an entry name, reads and returns the entry info | ||||||
|  |         ''' | ||||||
|  |         entry_reader = self._entry_reader(entry_name) | ||||||
|  |         return ConfigEntry.MKTXPEntry(**entry_reader) | ||||||
|  |  | ||||||
|  |     # Helpers | ||||||
|  |     def _entry_reader(self, entry_name): | ||||||
|  |         entry = {} | ||||||
|  |         for key in MKTXPConfigKeys.BOOLEAN_KEYS: | ||||||
|  |             if self.config[entry_name].get(key): | ||||||
|  |                 entry[key] = self.config[entry_name].as_bool(key) | ||||||
|  |             else: | ||||||
|  |                 entry[key] = False | ||||||
|  |  | ||||||
|  |         for key in MKTXPConfigKeys.STR_KEYS: | ||||||
|  |             entry[key] = self.config[entry_name][key] | ||||||
|  |  | ||||||
|  |         # port | ||||||
|  |         if self.config[entry_name].get(MKTXPConfigKeys.PORT_KEY): | ||||||
|  |             entry[MKTXPConfigKeys.PORT_KEY] = self.config[entry_name].as_int(MKTXPConfigKeys.PORT_KEY) | ||||||
|  |         else: | ||||||
|  |             if entry[MKTXPConfigKeys.SSL_KEY]: | ||||||
|  |                 entry[MKTXPConfigKeys.PORT_KEY] = MKTXPConfigKeys.DEFAULT_API_SSL_PORT | ||||||
|  |             else: | ||||||
|  |                 entry[MKTXPConfigKeys.PORT_KEY] = MKTXPConfigKeys.DEFAULT_API_PORT | ||||||
|  |  | ||||||
|  |         return entry | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Simplest possible Singleton impl | ||||||
|  | config_handler = MKTXPConfigHandler() | ||||||
|  |  | ||||||
							
								
								
									
										36
									
								
								mktxp/cli/config/mktxp.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								mktxp/cli/config/mktxp.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | [Sample_RouterBoard] | ||||||
|  |     enabled = False | ||||||
|  |  | ||||||
|  |     hostname = localhost | ||||||
|  |     port = 8728 | ||||||
|  |  | ||||||
|  |     username = user | ||||||
|  |     password = password | ||||||
|  |  | ||||||
|  |     use_ssl = False | ||||||
|  |     ssl_certificate = False | ||||||
|  |  | ||||||
|  |     identity = True | ||||||
|  |     resource = True | ||||||
|  |     health = True | ||||||
|  |     dhcp = True | ||||||
|  |     dhcp_lease = True | ||||||
|  |     pool = True | ||||||
|  |     interface = True | ||||||
|  |     monitor = True | ||||||
|  |     route = True | ||||||
|  |     wireless = True | ||||||
|  |     caps_man = True | ||||||
							
								
								
									
										107
									
								
								mktxp/cli/dispatch.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										107
									
								
								mktxp/cli/dispatch.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  | import pkg_resources | ||||||
|  | import mktxp.cli.checks.chk_pv | ||||||
|  | from mktxp.cli.options import MKTXPOptionsParser, MKTXPCommands | ||||||
|  | from mktxp.cli.config.config import config_handler, ConfigEntry | ||||||
|  | from mktxp.basep import MKTXPProcessor | ||||||
|  |  | ||||||
|  | class MKTXPDispatcher: | ||||||
|  |     ''' Base MKTXP Commands Dispatcher | ||||||
|  |     ''' | ||||||
|  |     def __init__(self): | ||||||
|  |         self.option_parser = MKTXPOptionsParser() | ||||||
|  |  | ||||||
|  |     # Dispatcher | ||||||
|  |     def dispatch(self): | ||||||
|  |         args = self.option_parser.parse_options() | ||||||
|  |  | ||||||
|  |         if args['sub_cmd'] == MKTXPCommands.VERSION: | ||||||
|  |             self.print_version() | ||||||
|  |  | ||||||
|  |         elif args['sub_cmd'] == MKTXPCommands.INFO: | ||||||
|  |             self.print_info() | ||||||
|  |  | ||||||
|  |         elif args['sub_cmd'] == MKTXPCommands.SHOW: | ||||||
|  |             self.show_entries() | ||||||
|  |  | ||||||
|  |         elif args['sub_cmd'] == MKTXPCommands.ADD: | ||||||
|  |             self.add_entry(args) | ||||||
|  |  | ||||||
|  |         elif args['sub_cmd'] == MKTXPCommands.EDIT: | ||||||
|  |             self.edit_entry(args) | ||||||
|  |  | ||||||
|  |         elif args['sub_cmd'] == MKTXPCommands.DELETE: | ||||||
|  |             self.delete_entry(args) | ||||||
|  |  | ||||||
|  |         elif args['sub_cmd'] == MKTXPCommands.START: | ||||||
|  |             self.start_export(args) | ||||||
|  |  | ||||||
|  |         else: | ||||||
|  |             # nothing to dispatch | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |         return True | ||||||
|  |  | ||||||
|  |     # Dispatched methods | ||||||
|  |     def print_version(self): | ||||||
|  |         ''' Prints MKTXP version info | ||||||
|  |         ''' | ||||||
|  |         version = pkg_resources.require("mktxp")[0].version | ||||||
|  |         print('Mikrotik RouterOS Prometheus Exporter version {}'.format(version)) | ||||||
|  |  | ||||||
|  |     def print_info(self): | ||||||
|  |         ''' Prints MKTXP general info | ||||||
|  |         ''' | ||||||
|  |         print('Mikrotik RouterOS Prometheus Exporter: {}'.format(self.option_parser.script_name)) | ||||||
|  |         print(self.option_parser.description) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def show_entries(self): | ||||||
|  |         for entryname in config_handler.registered_entries(): | ||||||
|  |             entry = config_handler.entry(entryname) | ||||||
|  |  | ||||||
|  |             print('[{}]'.format(entryname)) | ||||||
|  |             for field in entry._fields: | ||||||
|  |                 print('    {}: {}'.format(field, getattr(entry, field))) | ||||||
|  |             print() | ||||||
|  |  | ||||||
|  |     def add_entry(self, args): | ||||||
|  |         args.pop('sub_cmd', None) | ||||||
|  |         entry_name = args['entry_name'] | ||||||
|  |         args.pop('entry_name', None) | ||||||
|  |  | ||||||
|  |         entry_info = ConfigEntry.MKTXPEntry(**args) | ||||||
|  |         config_handler.register_entry(entry_name = entry_name, entry_info = entry_info) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def edit_entry(self, args): | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     def delete_entry(self, args): | ||||||
|  |         config_handler.unregister_entry(entry_name = args['entry_name']) | ||||||
|  |          | ||||||
|  |  | ||||||
|  |     def start_export(self, args): | ||||||
|  |         MKTXPProcessor.start() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def main(): | ||||||
|  |     MKTXPDispatcher().dispatch() | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     main() | ||||||
|  |  | ||||||
							
								
								
									
										287
									
								
								mktxp/cli/options.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										287
									
								
								mktxp/cli/options.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,287 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | import os | ||||||
|  | from argparse import ArgumentParser, HelpFormatter | ||||||
|  | from mktxp.cli.config.config import config_handler, MKTXPConfigKeys | ||||||
|  | from mktxp.utils.utils import FSHelper, UniquePartialMatchList | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MKTXPCommands: | ||||||
|  |     INFO = 'info' | ||||||
|  |     VERSION = 'version' | ||||||
|  |     SHOW = 'show' | ||||||
|  |     ADD = 'add' | ||||||
|  |     EDIT = 'edit' | ||||||
|  |     DELETE = 'delete'  | ||||||
|  |     START = 'start' | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def commands_meta(cls): | ||||||
|  |         return ''.join(('{', | ||||||
|  |                         '{}, '.format(cls.INFO), | ||||||
|  |                         '{}'.format(cls.VERSION), | ||||||
|  |                         '{}'.format(cls.SHOW), | ||||||
|  |                         '{}'.format(cls.ADD), | ||||||
|  |                         '{}'.format(cls.EDIT), | ||||||
|  |                         '{}'.format(cls.DELETE), | ||||||
|  |                         '{}'.format(cls.START),                         | ||||||
|  |                         '}')) | ||||||
|  |  | ||||||
|  | class MKTXPOptionsParser: | ||||||
|  |     ''' Base MKTXP Options Parser | ||||||
|  |     ''' | ||||||
|  |     def __init__(self): | ||||||
|  |         self._script_name = 'MKTXP' | ||||||
|  |         self._description = \ | ||||||
|  |     ''' | ||||||
|  |     Prometheus Exporter for Mikrotik RouterOS Devices | ||||||
|  |     ''' | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def description(self): | ||||||
|  |         return self._description | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def script_name(self): | ||||||
|  |         return self._script_name | ||||||
|  |  | ||||||
|  |     # Options Parsing Workflow | ||||||
|  |     def parse_options(self): | ||||||
|  |         ''' General Options parsing workflow | ||||||
|  |         ''' | ||||||
|  |         parser = ArgumentParser(prog = self._script_name, | ||||||
|  |                                 description = self._description, | ||||||
|  |                                 formatter_class=MKTXPHelpFormatter) | ||||||
|  |  | ||||||
|  |         self.parse_global_options(parser) | ||||||
|  |         self.parse_commands(parser) | ||||||
|  |         args = vars(parser.parse_args()) | ||||||
|  |  | ||||||
|  |         self._check_args(args, parser) | ||||||
|  |  | ||||||
|  |         return args | ||||||
|  |  | ||||||
|  |     def parse_global_options(self, parser): | ||||||
|  |         ''' Parses global options | ||||||
|  |         ''' | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     def parse_commands(self, parser): | ||||||
|  |         ''' Commands parsing | ||||||
|  |         ''' | ||||||
|  |         subparsers = parser.add_subparsers(dest = 'sub_cmd', | ||||||
|  |                                            title = 'MKTXP commands', | ||||||
|  |                                            metavar = MKTXPCommands.commands_meta()) | ||||||
|  |          | ||||||
|  |         # Info command | ||||||
|  |         subparsers.add_parser(MKTXPCommands.INFO, | ||||||
|  |                                         description = 'Displays MKTXP info', | ||||||
|  |                                         formatter_class=MKTXPHelpFormatter) | ||||||
|  |         # Version command | ||||||
|  |         subparsers.add_parser(MKTXPCommands.VERSION, | ||||||
|  |                                         description = 'Displays MKTXP version info', | ||||||
|  |                                         formatter_class=MKTXPHelpFormatter) | ||||||
|  |  | ||||||
|  |         # Show command | ||||||
|  |         subparsers.add_parser(MKTXPCommands.SHOW, | ||||||
|  |                                         description = 'Displays MKTXP router entries', | ||||||
|  |                                         formatter_class=MKTXPHelpFormatter) | ||||||
|  |  | ||||||
|  |         # Add command | ||||||
|  |         add_parser = subparsers.add_parser(MKTXPCommands.ADD, | ||||||
|  |                                         description = 'Adds a new MKTXP router entry', | ||||||
|  |                                         formatter_class=MKTXPHelpFormatter) | ||||||
|  |         required_args_group = add_parser.add_argument_group('Required Arguments') | ||||||
|  |         self._add_entry_name(required_args_group, registered_only = False, help = "Config entry name") | ||||||
|  |         required_args_group.add_argument('-host', '--hostname', dest='hostname', | ||||||
|  |                 help = "IP address of RouterOS device to export metrics from", | ||||||
|  |                 type = str, | ||||||
|  |                 required=True) | ||||||
|  |         required_args_group.add_argument('-usr', '--username', dest='username', | ||||||
|  |                 help = "username", | ||||||
|  |                 type = str, | ||||||
|  |                 required=True) | ||||||
|  |         required_args_group.add_argument('-pwd', '--password', dest='password', | ||||||
|  |                 help = "password", | ||||||
|  |                 type = str, | ||||||
|  |                 required=True) | ||||||
|  |  | ||||||
|  |         optional_args_group = add_parser.add_argument_group('Optional Arguments') | ||||||
|  |         optional_args_group.add_argument('-e', dest='enabled', | ||||||
|  |                 help = "Enables entry for metrics processing", | ||||||
|  |                 action = 'store_false') | ||||||
|  |  | ||||||
|  |         optional_args_group.add_argument('-port', dest='port', | ||||||
|  |                 help = "port", | ||||||
|  |                 default = MKTXPConfigKeys.DEFAULT_API_PORT, | ||||||
|  |                 type = int) | ||||||
|  |  | ||||||
|  |         optional_args_group.add_argument('-ssl', '--use-ssl', dest='use_ssl', | ||||||
|  |                 help = "Connect via RouterOS api-ssl service", | ||||||
|  |                 action = 'store_true') | ||||||
|  |         optional_args_group.add_argument('-ssl-cert', '--use-ssl-certificate', dest='ssl_certificate', | ||||||
|  |                 help = "Connect with configured RouterOS SSL ceritficate", | ||||||
|  |                 action = 'store_true') | ||||||
|  |  | ||||||
|  |         optional_args_group.add_argument('-dhcp', '--export_dhcp', dest='dhcp', | ||||||
|  |                 help = "Export DHCP metrics", | ||||||
|  |                 action = 'store_true') | ||||||
|  |         optional_args_group.add_argument('-dhcp_lease', '--export_dhcp_lease', dest='dhcp_lease', | ||||||
|  |                 help = "Export DHCP Lease metrics", | ||||||
|  |                 action = 'store_true') | ||||||
|  |         optional_args_group.add_argument('-pool', '--export_pool', dest='pool', | ||||||
|  |                 help = "Export IP Pool metrics", | ||||||
|  |                 action = 'store_true') | ||||||
|  |         optional_args_group.add_argument('-interface', '--export_interface', dest='interface', | ||||||
|  |                 help = "Export Interface metrics", | ||||||
|  |                 action = 'store_true') | ||||||
|  |         optional_args_group.add_argument('-monitor', '--export_monitor', dest='monitor', | ||||||
|  |                 help = "Export Interface Monitor metrics", | ||||||
|  |                 action = 'store_true') | ||||||
|  |         optional_args_group.add_argument('-route', '--export_route', dest='route', | ||||||
|  |                 help = "Export IP Route metrics", | ||||||
|  |                 action = 'store_true') | ||||||
|  |         optional_args_group.add_argument('-wireless', '--export_wireless', dest='wireless', | ||||||
|  |                 help = "Export Wireless metrics", | ||||||
|  |                 action = 'store_true') | ||||||
|  |         optional_args_group.add_argument('-capsman', '--export_capsman', dest='capsman', | ||||||
|  |                 help = "Export CAPsMAN metrics", | ||||||
|  |                 action = 'store_true') | ||||||
|  |  | ||||||
|  | #'hostname', 'port', 'username', 'password', 'use_ssl', 'ssl_certificate', 'dhcp', 'dhcp_lease', 'pool', 'interface', 'monitor', 'route', 'wireless', and 'capsman' | ||||||
|  |  | ||||||
|  |         # Edit command | ||||||
|  |         edit_parser = subparsers.add_parser(MKTXPCommands.EDIT, | ||||||
|  |                                         description = 'Edits an existing MKTXP router entry', | ||||||
|  |                                         formatter_class=MKTXPHelpFormatter) | ||||||
|  |         required_args_group = edit_parser.add_argument_group('Required Arguments') | ||||||
|  |         self._add_entry_name(required_args_group, registered_only = True, help = "Name of entry to edit")   | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         # Delete command | ||||||
|  |         delete_parser = subparsers.add_parser(MKTXPCommands.DELETE, | ||||||
|  |                                         description = 'Deletes an existing MKTXP router entry', | ||||||
|  |                                         formatter_class=MKTXPHelpFormatter) | ||||||
|  |         required_args_group = delete_parser.add_argument_group('Required Arguments') | ||||||
|  |         self._add_entry_name(required_args_group, registered_only = True, help = "Name of entry to delete") | ||||||
|  |  | ||||||
|  |         # Start command | ||||||
|  |         start_parser = subparsers.add_parser(MKTXPCommands.START, | ||||||
|  |                                         description = 'Starts exporting Miktorik Router Metrics', | ||||||
|  |                                         formatter_class=MKTXPHelpFormatter) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     # Options checking | ||||||
|  |     def _check_args(self, args, parser): | ||||||
|  |         ''' Validation of supplied CLI arguments | ||||||
|  |         ''' | ||||||
|  |         # check if there is a cmd to execute | ||||||
|  |         self._check_cmd_args(args, parser) | ||||||
|  |  | ||||||
|  |         if args['sub_cmd'] in (MKTXPCommands.EDIT, MKTXPCommands.DELETE): | ||||||
|  |             # Registered Entry name could be a partial match, need to expand | ||||||
|  |             args['entry_name'] = UniquePartialMatchList(config_handler.registered_entries()).find(args['entry_name']) | ||||||
|  |  | ||||||
|  |         if args['sub_cmd'] == MKTXPCommands.ADD: | ||||||
|  |             if args['entry_name'] in (config_handler.registered_entries()): | ||||||
|  |                 print('"{0}": entry name already exists'.format(args['entry_name'])) | ||||||
|  |                 parser.exit() | ||||||
|  |  | ||||||
|  |     def _check_cmd_args(self, args, parser): | ||||||
|  |         ''' Validation of supplied CLI commands | ||||||
|  |         ''' | ||||||
|  |         # base command check | ||||||
|  |         if 'sub_cmd' not in args or not args['sub_cmd']: | ||||||
|  |             # If no command was specified, check for the default one | ||||||
|  |             cmd = self._default_command | ||||||
|  |             if cmd: | ||||||
|  |                 args['sub_cmd'] = cmd | ||||||
|  |             else: | ||||||
|  |                 # no appropriate default either | ||||||
|  |                 parser.print_help() | ||||||
|  |                 parser.exit() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def _default_command(self): | ||||||
|  |         ''' If no command was specified, print INFO by default | ||||||
|  |         ''' | ||||||
|  |         return MKTXPCommands.START | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     # Internal helpers | ||||||
|  |     @staticmethod | ||||||
|  |     def _is_valid_dir_path(parser, path_arg): | ||||||
|  |         ''' Checks if path_arg is a valid dir path | ||||||
|  |         ''' | ||||||
|  |         path_arg = FSHelper.full_path(path_arg) | ||||||
|  |         if not (os.path.exists(path_arg) and os.path.isdir(path_arg)): | ||||||
|  |             parser.error('"{}" does not seem to be an existing directory path'.format(path_arg)) | ||||||
|  |         else: | ||||||
|  |             return path_arg | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _is_valid_file_path(parser, path_arg): | ||||||
|  |         ''' Checks if path_arg is a valid file path | ||||||
|  |         ''' | ||||||
|  |         path_arg = FSHelper.full_path(path_arg) | ||||||
|  |         if not (os.path.exists(path_arg) and os.path.isfile(path_arg)): | ||||||
|  |             parser.error('"{}" does not seem to be an existing file path'.format(path_arg)) | ||||||
|  |         else: | ||||||
|  |             return path_arg | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _add_entry_name(parser, registered_only = False, help = 'MKTXP Entry name'): | ||||||
|  |         parser.add_argument('-en', '--entry-name', dest = 'entry_name', | ||||||
|  |             type = str, | ||||||
|  |             metavar = config_handler.registered_entries() if registered_only else None, | ||||||
|  |             required = True, | ||||||
|  |             choices = UniquePartialMatchList(config_handler.registered_entries())if registered_only else None, | ||||||
|  |             help = help) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _add_entry_groups(parser): | ||||||
|  |         required_args_group = parser.add_argument_group('Required Arguments') | ||||||
|  |         MKTXPOptionsParser._add_entry_name(required_args_group) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MKTXPHelpFormatter(HelpFormatter): | ||||||
|  |     ''' Custom formatter for ArgumentParser | ||||||
|  |         Disables double metavar display, showing only for long-named options | ||||||
|  |     ''' | ||||||
|  |     def _format_action_invocation(self, action): | ||||||
|  |         if not action.option_strings: | ||||||
|  |             metavar, = self._metavar_formatter(action, action.dest)(1) | ||||||
|  |             return metavar | ||||||
|  |         else: | ||||||
|  |             parts = [] | ||||||
|  |             # if the Optional doesn't take a value, format is: | ||||||
|  |             #    -s, --long | ||||||
|  |             if action.nargs == 0: | ||||||
|  |                 parts.extend(action.option_strings) | ||||||
|  |  | ||||||
|  |             # if the Optional takes a value, format is: | ||||||
|  |             #    -s ARGS, --long ARGS | ||||||
|  |             # change to | ||||||
|  |             #    -s, --long ARGS | ||||||
|  |             else: | ||||||
|  |                 default = action.dest.upper() | ||||||
|  |                 args_string = self._format_args(action, default) | ||||||
|  |                 for option_string in action.option_strings: | ||||||
|  |                     #parts.append('%s %s' % (option_string, args_string)) | ||||||
|  |                     parts.append('%s' % option_string) | ||||||
|  |                 parts[-1] += ' %s'%args_string | ||||||
|  |             return ', '.join(parts) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										0
									
								
								mktxp/collectors/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								mktxp/collectors/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										58
									
								
								mktxp/collectors/base_collector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								mktxp/collectors/base_collector.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, InfoMetricFamily | ||||||
|  | from mktxp.cli.config.config import MKTXPConfigKeys | ||||||
|  |  | ||||||
|  |       | ||||||
|  | class BaseCollector: | ||||||
|  |     ''' Base Collector methods | ||||||
|  |         For use by custom collectors | ||||||
|  |     ''' | ||||||
|  |     @staticmethod | ||||||
|  |     def info_collector(name, decription, router_records, metric_labels=[]): | ||||||
|  |         BaseCollector._add_id_labels(metric_labels) | ||||||
|  |         collector = InfoMetricFamily(f'mktxp_{name}', decription) | ||||||
|  |  | ||||||
|  |         for router_record in router_records: | ||||||
|  |             label_values = {label: router_record.get(label) if router_record.get(label) else '' for label in metric_labels} | ||||||
|  |             collector.add_metric(metric_labels, label_values) | ||||||
|  |         return collector | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def counter_collector(name, decription, router_records, metric_key, metric_labels=[]): | ||||||
|  |         BaseCollector._add_id_labels(metric_labels) | ||||||
|  |         collector = CounterMetricFamily(f'mktxp_{name}', decription, labels=metric_labels) | ||||||
|  |  | ||||||
|  |         for router_record in router_records:            | ||||||
|  |             label_values = [router_record.get(label) for label in metric_labels] | ||||||
|  |             collector.add_metric(label_values, router_record.get(metric_key, 0)) | ||||||
|  |         return collector | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def gauge_collector(name, decription, router_records, metric_key, metric_labels=[]): | ||||||
|  |         BaseCollector._add_id_labels(metric_labels) | ||||||
|  |         collector = GaugeMetricFamily(f'mktxp_{name}', decription, labels=metric_labels) | ||||||
|  |  | ||||||
|  |         for router_record in router_records:        | ||||||
|  |             label_values = [router_record.get(label) for label in metric_labels] | ||||||
|  |             collector.add_metric(label_values, router_record.get(metric_key, 0)) | ||||||
|  |         return collector | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     # Helpers | ||||||
|  |     @staticmethod | ||||||
|  |     def _add_id_labels(metric_labels): | ||||||
|  |         metric_labels.append(MKTXPConfigKeys.ROUTERBOARD_NAME) | ||||||
|  |         metric_labels.append(MKTXPConfigKeys.ROUTERBOARD_ADDRESS) | ||||||
							
								
								
									
										33
									
								
								mktxp/collectors/capsman_collector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								mktxp/collectors/capsman_collector.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | from mktxp.collectors.base_collector import BaseCollector | ||||||
|  | from mktxp.router_metric import RouterMetric | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CapsmanCollector(BaseCollector): | ||||||
|  |     ''' CAPsMAN Metrics collector | ||||||
|  |     '''     | ||||||
|  |     @staticmethod | ||||||
|  |     def collect(router_metric): | ||||||
|  |         resource_labels = ['uptime', 'version', 'free-memory', 'total-memory',  | ||||||
|  |                            'cpu', 'cpu-count', 'cpu-frequency', 'cpu-load',  | ||||||
|  |                            'free-hdd-space', 'total-hdd-space',  | ||||||
|  |                            'architecture-name', 'board-name'] | ||||||
|  |         resource_records = router_metric.resource_records(resource_labels) | ||||||
|  |         if not resource_records: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         resource_metrics = BaseCollector.info_collector('resource', 'resource', resource_records, resource_labels) | ||||||
|  |         yield resource_metrics | ||||||
|  |  | ||||||
							
								
								
									
										46
									
								
								mktxp/collectors/dhcp_collector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								mktxp/collectors/dhcp_collector.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | from mktxp.cli.config.config import MKTXPConfigKeys | ||||||
|  | from mktxp.collectors.base_collector import BaseCollector | ||||||
|  | from mktxp.router_metric import RouterMetric | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class DHCPCollector(BaseCollector): | ||||||
|  |     ''' DHCP Metrics collector | ||||||
|  |     '''     | ||||||
|  |     @staticmethod | ||||||
|  |     def collect(router_metric): | ||||||
|  |         dhcp_lease_labels = ['active_address', 'mac_address', 'host_name', 'comment', 'server', 'expires_after'] | ||||||
|  |         dhcp_lease_records = router_metric.dhcp_lease_records(dhcp_lease_labels) | ||||||
|  |         if not dhcp_lease_records: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         # calculate number of leases per DHCP server | ||||||
|  |         dhcp_lease_servers = {} | ||||||
|  |         for dhcp_lease_record in dhcp_lease_records: | ||||||
|  |             dhcp_lease_servers[dhcp_lease_record['server']] = dhcp_lease_servers.get(dhcp_lease_record['server'], 0) + 1 | ||||||
|  |  | ||||||
|  |         # compile leases-per-server records | ||||||
|  |         dhcp_lease_servers_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME], | ||||||
|  |                                         MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS], | ||||||
|  |                                         'server': key, 'count': value} for key, value in dhcp_lease_servers.items()] | ||||||
|  |          | ||||||
|  |         # yield lease-per-server metrics | ||||||
|  |         dhcp_lease_server_metrics = BaseCollector.gauge_collector('dhcp_lease_active_count', 'Number of active leases per DHCP server', dhcp_lease_servers_records, 'count', ['server']) | ||||||
|  |         yield dhcp_lease_server_metrics | ||||||
|  |  | ||||||
|  |         # active lease metrics | ||||||
|  |         if router_metric.router_entry.dhcp_lease: | ||||||
|  |             dhcp_lease_metrics = BaseCollector.info_collector('dhcp_lease', 'DHCP Active Leases', dhcp_lease_records, dhcp_lease_labels) | ||||||
|  |             yield dhcp_lease_metrics | ||||||
							
								
								
									
										33
									
								
								mktxp/collectors/health_collector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								mktxp/collectors/health_collector.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | from mktxp.collectors.base_collector import BaseCollector | ||||||
|  | from mktxp.router_metric import RouterMetric | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class HealthCollector(BaseCollector): | ||||||
|  |     ''' System Health Metrics collector | ||||||
|  |     '''     | ||||||
|  |     @staticmethod | ||||||
|  |     def collect(router_metric): | ||||||
|  |         health_labels = ['voltage', 'temperature'] | ||||||
|  |         health_records = router_metric.health_records(health_labels) | ||||||
|  |         if not health_records: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         voltage_metrics = BaseCollector.gauge_collector('routerboard_voltage', 'Supplied routerboard voltage', health_records, 'voltage') | ||||||
|  |         yield voltage_metrics | ||||||
|  |  | ||||||
|  |         temperature_metrics = BaseCollector.gauge_collector('routerboard_temperature', ' Routerboard current temperature', health_records, 'temperature') | ||||||
|  |         yield temperature_metrics | ||||||
|  |  | ||||||
							
								
								
									
										30
									
								
								mktxp/collectors/identity_collector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								mktxp/collectors/identity_collector.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | from mktxp.collectors.base_collector import BaseCollector | ||||||
|  | from mktxp.router_metric import RouterMetric | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class IdentityCollector(BaseCollector): | ||||||
|  |     ''' System Identity Metrics collector | ||||||
|  |     '''      | ||||||
|  |     @staticmethod | ||||||
|  |     def collect(router_metric): | ||||||
|  |         identity_labels = ['name'] | ||||||
|  |         identity_records = router_metric.identity_records(identity_labels) | ||||||
|  |         if not identity_records: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         identity_metrics = BaseCollector.info_collector('identity', 'System identity', identity_records, identity_labels) | ||||||
|  |         yield identity_metrics | ||||||
|  |  | ||||||
							
								
								
									
										52
									
								
								mktxp/collectors/interface_collector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								mktxp/collectors/interface_collector.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | from mktxp.collectors.base_collector import BaseCollector | ||||||
|  | from mktxp.router_metric import RouterMetric | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class InterfaceCollector(BaseCollector): | ||||||
|  |     ''' Router Interface Metrics collector | ||||||
|  |     '''         | ||||||
|  |     @staticmethod | ||||||
|  |     def collect(router_metric): | ||||||
|  |         interface_traffic_labels = ['name', 'comment', 'rx_byte', 'tx_byte', 'rx_packet', 'tx_packet', 'rx_error', 'tx_error', 'rx_drop', 'tx_drop'] | ||||||
|  |         interface_traffic_records = router_metric.interface_traffic_records(interface_traffic_labels) | ||||||
|  |         if not interface_traffic_records: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         rx_byte_metric = BaseCollector.counter_collector('rx_byte', 'Number of received bytes', interface_traffic_records, 'rx_byte', ['name']) | ||||||
|  |         yield rx_byte_metric | ||||||
|  |  | ||||||
|  |         tx_byte_metric = BaseCollector.counter_collector('tx_byte', 'Number of transmitted bytes', interface_traffic_records, 'tx_byte', ['name']) | ||||||
|  |         yield tx_byte_metric | ||||||
|  |  | ||||||
|  |         rx_packet_metric = BaseCollector.counter_collector('rx_packet', 'Number of packets received', interface_traffic_records, 'rx_packet', ['name']) | ||||||
|  |         yield rx_packet_metric | ||||||
|  |  | ||||||
|  |         tx_packet_metric = BaseCollector.counter_collector('tx_packet', 'Number of transmitted packets', interface_traffic_records, 'tx_packet', ['name']) | ||||||
|  |         yield tx_packet_metric | ||||||
|  |  | ||||||
|  |         rx_error_metric = BaseCollector.counter_collector('rx_error', 'Number of packets received with an error', interface_traffic_records, 'rx_error', ['name']) | ||||||
|  |         yield rx_error_metric | ||||||
|  |  | ||||||
|  |         tx_error_metric = BaseCollector.counter_collector('tx_error', 'Number of packets transmitted with an error', interface_traffic_records, 'tx_error', ['name']) | ||||||
|  |         yield tx_error_metric | ||||||
|  |  | ||||||
|  |         rx_drop_metric = BaseCollector.counter_collector('rx_drop', 'Number of received packets being dropped', interface_traffic_records, 'rx_drop', ['name']) | ||||||
|  |         yield rx_drop_metric | ||||||
|  |  | ||||||
|  |         tx_drop_metric = BaseCollector.counter_collector('tx_drop', 'Number of transmitted packets being dropped', interface_traffic_records, 'tx_drop', ['name']) | ||||||
|  |         yield tx_drop_metric | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										77
									
								
								mktxp/collectors/monitor_collector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								mktxp/collectors/monitor_collector.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | import re | ||||||
|  | from mktxp.collectors.base_collector import BaseCollector | ||||||
|  | from mktxp.router_metric import RouterMetric | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MonitorCollector(BaseCollector): | ||||||
|  |     ''' Ethernet Interface Monitor Metrics collector | ||||||
|  |     '''     | ||||||
|  |     @staticmethod | ||||||
|  |     def collect(router_metric): | ||||||
|  |         monitor_labels = ['status', 'rate', 'full_duplex', 'name'] | ||||||
|  |         monitor_records = router_metric.interface_monitor_records(monitor_labels) | ||||||
|  |         if not monitor_records: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         # translate records to appropriate values | ||||||
|  |         for monitor_record in monitor_records: | ||||||
|  |             for monitor_label in monitor_labels: | ||||||
|  |                 value = monitor_record.get(monitor_label, None)     | ||||||
|  |                 if value:             | ||||||
|  |                     monitor_record[monitor_label] = MonitorCollector._translated_values(monitor_label, value) | ||||||
|  |  | ||||||
|  |         monitor_status_metrics = BaseCollector.gauge_collector('status', 'Current interface link status', monitor_records, 'status', ['name']) | ||||||
|  |         yield monitor_status_metrics | ||||||
|  |  | ||||||
|  |         # limit records according to the relevant metrics | ||||||
|  |         rate_records = [monitor_record for monitor_record in monitor_records if monitor_record.get('rate', None)] | ||||||
|  |         monitor_rates_metrics = BaseCollector.gauge_collector('rate', 'Actual interface connection data rate', rate_records, 'rate', ['name']) | ||||||
|  |         yield monitor_rates_metrics | ||||||
|  |  | ||||||
|  |         full_duplex_records = [monitor_record for monitor_record in monitor_records if monitor_record.get('full_duplex', None)] | ||||||
|  |         monitor_rates_metrics = BaseCollector.gauge_collector('full_duplex', 'Full duplex data transmission', full_duplex_records, 'full_duplex', ['name']) | ||||||
|  |         yield monitor_rates_metrics | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     # Helpers | ||||||
|  |     @staticmethod | ||||||
|  |     def _translated_values(monitor_label, value): | ||||||
|  |         return { | ||||||
|  |                 'status': lambda value: '1' if value=='link-ok' else '0', | ||||||
|  |                 'rate': lambda value: MonitorCollector._rates(value), | ||||||
|  |                 'full_duplex': lambda value: '1' if value=='true' else '0', | ||||||
|  |                 'name': lambda value: value | ||||||
|  |                 }[monitor_label](value) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _rates(rate_option): | ||||||
|  |         # according mikrotik docs, an interface rate should be one of these | ||||||
|  |         rate_value =  { | ||||||
|  |                 '10Mbps': '10', | ||||||
|  |                 '100Mbps': '100', | ||||||
|  |                 '1Gbps': '1000', | ||||||
|  |                 '2.5Gbps': '2500', | ||||||
|  |                 '5Gbps': '5000', | ||||||
|  |                 '10Gbps': '10000', | ||||||
|  |                 '40Gbps': '40000' | ||||||
|  |                 }.get(rate_option, None) | ||||||
|  |         if rate_value: | ||||||
|  |             return rate_value | ||||||
|  |          | ||||||
|  |         # ...or just calculate if it's not | ||||||
|  |         rate = lambda rate_option: 1000 if rate_option.find('Mbps') < 0 else 1 | ||||||
|  |         return(int(float(re.sub('[^.\-\d]', '', rate_option)) * rate(rate_option))) | ||||||
|  |  | ||||||
							
								
								
									
										45
									
								
								mktxp/collectors/pool_collector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								mktxp/collectors/pool_collector.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | from mktxp.cli.config.config import MKTXPConfigKeys | ||||||
|  | from mktxp.collectors.base_collector import BaseCollector | ||||||
|  | from mktxp.router_metric import RouterMetric | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PoolCollector(BaseCollector): | ||||||
|  |     ''' IP Pool Metrics collector | ||||||
|  |     '''     | ||||||
|  |     @staticmethod | ||||||
|  |     def collect(router_metric): | ||||||
|  |  | ||||||
|  |         # initialize all pool counts, including those currently not used | ||||||
|  |         pool_records = router_metric.pool_records(['name']) | ||||||
|  |         if not pool_records: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         pool_used_labels = ['pool'] | ||||||
|  |         pool_used_counts = {pool_record['name']: 0 for pool_record in pool_records} | ||||||
|  |  | ||||||
|  |         # for pools in usage, calculate the current numbers | ||||||
|  |         pool_used_records = router_metric.pool_used_records(pool_used_labels) | ||||||
|  |         for pool_used_record in pool_used_records: | ||||||
|  |             pool_used_counts[pool_used_record['pool']] = pool_used_counts.get(pool_used_record['pool'], 0) + 1 | ||||||
|  |  | ||||||
|  |        # compile used-per-pool records | ||||||
|  |         used_per_pool_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME], | ||||||
|  |                                    MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS], | ||||||
|  |                                    'pool': key, 'count': value} for key, value in pool_used_counts.items()] | ||||||
|  |          | ||||||
|  |         # yield used-per-pool metrics | ||||||
|  |         used_per_pool_metrics = BaseCollector.gauge_collector('ip_pool_used', 'Number of used addresses per IP pool', used_per_pool_records, 'count', ['pool']) | ||||||
|  |         yield used_per_pool_metrics | ||||||
							
								
								
									
										77
									
								
								mktxp/collectors/resource_collector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								mktxp/collectors/resource_collector.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | import re | ||||||
|  | from datetime import timedelta | ||||||
|  | from mktxp.collectors.base_collector import BaseCollector | ||||||
|  | from mktxp.router_metric import RouterMetric | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SystemResourceCollector(BaseCollector): | ||||||
|  |     ''' System Resource Metrics collector | ||||||
|  |     '''         | ||||||
|  |     @staticmethod | ||||||
|  |     def collect(router_metric): | ||||||
|  |         resource_labels = ['uptime', 'version', 'free_memory', 'total_memory',  | ||||||
|  |                            'cpu', 'cpu_count', 'cpu_frequency', 'cpu_load',  | ||||||
|  |                            'free_hdd_space', 'total_hdd_space',  | ||||||
|  |                            'architecture_name', 'board_name'] | ||||||
|  |         resource_records = router_metric.system_resource_records(resource_labels) | ||||||
|  |         if not resource_records: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         # translate records to appropriate values | ||||||
|  |         translated_fields = ['uptime']         | ||||||
|  |         for resource_record in resource_records: | ||||||
|  |             for translated_field in translated_fields: | ||||||
|  |                 value = resource_record.get(translated_field, None)     | ||||||
|  |                 if value:             | ||||||
|  |                     resource_record[translated_field] = SystemResourceCollector._translated_values(translated_field, value) | ||||||
|  |  | ||||||
|  |         uptime_metrics = BaseCollector.gauge_collector('uptime', 'Time interval since boot-up', resource_records, 'uptime', ['version', 'board_name', 'cpu', 'architecture_name']) | ||||||
|  |         yield uptime_metrics | ||||||
|  |  | ||||||
|  |         free_memory_metrics = BaseCollector.gauge_collector('free_memory', 'Unused amount of RAM', resource_records, 'free_memory', ['version', 'board_name', 'cpu', 'architecture_name']) | ||||||
|  |         yield free_memory_metrics | ||||||
|  |  | ||||||
|  |         total_memory_metrics = BaseCollector.gauge_collector('total_memory', 'Amount of installed RAM', resource_records, 'total_memory', ['version', 'board_name', 'cpu', 'architecture_name']) | ||||||
|  |         yield total_memory_metrics | ||||||
|  |  | ||||||
|  |         free_hdd_metrics = BaseCollector.gauge_collector('free_hdd_space', 'Free space on hard drive or NAND', resource_records, 'free_hdd_space', ['version', 'board_name', 'cpu', 'architecture_name']) | ||||||
|  |         yield free_hdd_metrics | ||||||
|  |  | ||||||
|  |         total_hdd_metrics = BaseCollector.gauge_collector('total_hdd_space', 'Size of the hard drive or NAND', resource_records, 'total_hdd_space', ['version', 'board_name', 'cpu', 'architecture_name']) | ||||||
|  |         yield total_hdd_metrics | ||||||
|  |  | ||||||
|  |         cpu_load_metrics = BaseCollector.gauge_collector('cpu_load', 'Percentage of used CPU resources', resource_records, 'cpu_load', ['version', 'board_name', 'cpu', 'architecture_name']) | ||||||
|  |         yield cpu_load_metrics | ||||||
|  |  | ||||||
|  |         cpu_count_metrics = BaseCollector.gauge_collector('cpu_count', 'Number of CPUs present on the system', resource_records, 'cpu_count', ['version', 'board_name', 'cpu', 'architecture_name']) | ||||||
|  |         yield cpu_count_metrics | ||||||
|  |  | ||||||
|  |         cpu_frequency_metrics = BaseCollector.gauge_collector('cpu_frequency', 'Current CPU frequency', resource_records, 'cpu_frequency', ['version', 'board_name', 'cpu', 'architecture_name']) | ||||||
|  |         yield cpu_frequency_metrics | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     # Helpers | ||||||
|  |     @staticmethod | ||||||
|  |     def _translated_values(translated_field, value): | ||||||
|  |         return { | ||||||
|  |                 'uptime': lambda value: SystemResourceCollector._parse_uptime(value) | ||||||
|  |                 }[translated_field](value) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _parse_uptime(time): | ||||||
|  |         time_dict = re.match(r'((?P<weeks>\d+)w)?((?P<days>\d+)d)?((?P<hours>\d+)h)?((?P<minutes>\d+)m)?((?P<seconds>\d+)s)?', time).groupdict() | ||||||
|  |         return timedelta(**{key: int(value) for key, value in time_dict.items() if value}).total_seconds() | ||||||
|  |  | ||||||
							
								
								
									
										54
									
								
								mktxp/collectors/route_collector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								mktxp/collectors/route_collector.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | from mktxp.cli.config.config import MKTXPConfigKeys | ||||||
|  | from mktxp.collectors.base_collector import BaseCollector | ||||||
|  | from mktxp.router_metric import RouterMetric | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class RouteCollector(BaseCollector): | ||||||
|  |     ''' IP Route Metrics collector | ||||||
|  |     '''         | ||||||
|  |     @staticmethod | ||||||
|  |     def collect(router_metric): | ||||||
|  |         route_labels = ['connect', 'dynamic', 'static', 'bgp', 'ospf'] | ||||||
|  |         route_records = router_metric.route_records(route_labels) | ||||||
|  |         if not route_records: | ||||||
|  |             return | ||||||
|  |          | ||||||
|  |         # compile total routes records | ||||||
|  |         total_routes = len(route_records) | ||||||
|  |         total_routes_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME], | ||||||
|  |                                   MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS], | ||||||
|  |                                   'count': total_routes | ||||||
|  |                                 }] | ||||||
|  |         total_routes_metrics = BaseCollector.gauge_collector('total_routes', 'Overall number of routes in RIB', total_routes_records, 'count') | ||||||
|  |         yield total_routes_metrics | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         # init routes per protocol (with 0) | ||||||
|  |         routes_per_protocol = {route_label: 0 for route_label in route_labels} | ||||||
|  |         for route_record in route_records: | ||||||
|  |             for route_label in route_labels: | ||||||
|  |                 if route_record.get(route_label): | ||||||
|  |                     routes_per_protocol[route_label] += 1  | ||||||
|  |  | ||||||
|  |         # compile route-per-protocol records | ||||||
|  |         route_per_protocol_records = [{ MKTXPConfigKeys.ROUTERBOARD_NAME: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_NAME], | ||||||
|  |                                         MKTXPConfigKeys.ROUTERBOARD_ADDRESS: router_metric.router_id[MKTXPConfigKeys.ROUTERBOARD_ADDRESS], | ||||||
|  |                                         'protocol': key, 'count': value} for key, value in routes_per_protocol.items()] | ||||||
|  |          | ||||||
|  |         # yield route-per-protocol metrics | ||||||
|  |         route_per_protocol_metrics = BaseCollector.gauge_collector('routes_protocol_count', 'Number of routes per protocol in RIB', route_per_protocol_records, 'count', ['protocol']) | ||||||
|  |         yield route_per_protocol_metrics | ||||||
|  |  | ||||||
							
								
								
									
										38
									
								
								mktxp/collectors/wlan_collector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								mktxp/collectors/wlan_collector.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | from mktxp.collectors.base_collector import BaseCollector | ||||||
|  | from mktxp.router_metric import RouterMetric | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class WLANCollector(BaseCollector): | ||||||
|  |     ''' Wireless Metrics collector | ||||||
|  |     '''     | ||||||
|  |     @staticmethod | ||||||
|  |     def collect(router_metric): | ||||||
|  |         monitor_labels = ['channel', 'noise_floor', 'overall_tx_ccq'] | ||||||
|  |         monitor_records = router_metric.interface_monitor_records(monitor_labels, 'wireless') | ||||||
|  |         if not monitor_records: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         # sanitize records for relevant labels | ||||||
|  |         noise_floor_records = [monitor_record for monitor_record in monitor_records if monitor_record.get('noise_floor')] | ||||||
|  |         tx_ccq_records = [monitor_record for monitor_record in monitor_records if monitor_record.get('overall_tx_ccq')] | ||||||
|  |  | ||||||
|  |         if noise_floor_records: | ||||||
|  |             noise_floor_metrics = BaseCollector.gauge_collector('noise_floor', 'Noise floor threshold', noise_floor_records, 'noise_floor', ['channel']) | ||||||
|  |             yield noise_floor_metrics | ||||||
|  |  | ||||||
|  |         if tx_ccq_records: | ||||||
|  |             overall_tx_ccq_metrics = BaseCollector.gauge_collector('overall_tx_ccq', ' Client Connection Quality for transmitting', tx_ccq_records, 'overall_tx_ccq', ['channel']) | ||||||
|  |             yield overall_tx_ccq_metrics | ||||||
							
								
								
									
										57
									
								
								mktxp/collectors_handler.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								mktxp/collectors_handler.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | from mktxp.collectors.dhcp_collector import DHCPCollector | ||||||
|  | from mktxp.collectors.interface_collector import InterfaceCollector | ||||||
|  | from mktxp.collectors.health_collector import HealthCollector | ||||||
|  | from mktxp.collectors.identity_collector import IdentityCollector | ||||||
|  | from mktxp.collectors.monitor_collector import MonitorCollector | ||||||
|  | from mktxp.collectors.pool_collector import PoolCollector | ||||||
|  | from mktxp.collectors.resource_collector import SystemResourceCollector | ||||||
|  | from mktxp.collectors.route_collector import RouteCollector | ||||||
|  | from mktxp.collectors.wlan_collector import WLANCollector | ||||||
|  |  | ||||||
|  | class CollectorsHandler: | ||||||
|  |     ''' MKTXP Collectors Handler | ||||||
|  |     ''' | ||||||
|  |     def __init__(self, metrics_handler): | ||||||
|  |         self.metrics_handler = metrics_handler | ||||||
|  |  | ||||||
|  |     def collect(self): | ||||||
|  |         for router_metric in self.metrics_handler.router_metrics: | ||||||
|  |             yield from IdentityCollector.collect(router_metric) | ||||||
|  |             yield from SystemResourceCollector.collect(router_metric) | ||||||
|  |             yield from HealthCollector.collect(router_metric) | ||||||
|  |  | ||||||
|  |             if router_metric.router_entry.dhcp: | ||||||
|  |                 yield from DHCPCollector.collect(router_metric) | ||||||
|  |  | ||||||
|  |             if router_metric.router_entry.pool: | ||||||
|  |                 yield from PoolCollector.collect(router_metric) | ||||||
|  |              | ||||||
|  |             if router_metric.router_entry.interface: | ||||||
|  |                 yield from InterfaceCollector.collect(router_metric) | ||||||
|  |              | ||||||
|  |             if router_metric.router_entry.monitor: | ||||||
|  |                 yield from MonitorCollector.collect(router_metric) | ||||||
|  |              | ||||||
|  |             if router_metric.router_entry.route: | ||||||
|  |                 yield from RouteCollector.collect(router_metric) | ||||||
|  |          | ||||||
|  |             if router_metric.router_entry.wireless: | ||||||
|  |                 yield from WLANCollector.collect(router_metric) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										29
									
								
								mktxp/metrics_handler.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								mktxp/metrics_handler.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | from mktxp.cli.config.config import config_handler | ||||||
|  | from mktxp.router_metric import RouterMetric | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class RouterMetricsHandler: | ||||||
|  |     ''' Handles RouterOS entries defined in MKTXP config  | ||||||
|  |     '''          | ||||||
|  |     def __init__(self): | ||||||
|  |         self.router_metrics = [] | ||||||
|  |         for router_name in config_handler.registered_entries(): | ||||||
|  |             entry = config_handler.entry(router_name) | ||||||
|  |             if entry.enabled: | ||||||
|  |                 self.router_metrics.append(RouterMetric(router_name)) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										72
									
								
								mktxp/router_connection.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								mktxp/router_connection.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | import ssl | ||||||
|  | import socket | ||||||
|  | from routeros_api import RouterOsApiPool | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class RouterAPIConnectionError(Exception): | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class RouterAPIConnection: | ||||||
|  |     ''' Base wrapper interface for the routeros_api library | ||||||
|  |     '''             | ||||||
|  |     def __init__(self, router_name, router_entry): | ||||||
|  |         self.router_name = router_name | ||||||
|  |         self.router_entry  = router_entry | ||||||
|  |          | ||||||
|  |         ctx = None | ||||||
|  |         if not self.router_entry.ssl_certificate: | ||||||
|  |             ctx = ssl.create_default_context() | ||||||
|  |             ctx.set_ciphers('ADH:@SECLEVEL=0')        | ||||||
|  |  | ||||||
|  |         self.connection = RouterOsApiPool( | ||||||
|  |                 host = self.router_entry.hostname, | ||||||
|  |                 username = self.router_entry.username, | ||||||
|  |                 password = self.router_entry.password, | ||||||
|  |                 port = self.router_entry.port, | ||||||
|  |                 plaintext_login = True, | ||||||
|  |                 use_ssl = True, | ||||||
|  |                 ssl_context = ctx) | ||||||
|  |          | ||||||
|  |         self.connection.socket_timeout = 2 | ||||||
|  |         self.api = None | ||||||
|  |  | ||||||
|  |     def is_connected(self): | ||||||
|  |         if not (self.connection and self.connection.connected and self.api): | ||||||
|  |             return False | ||||||
|  |         try: | ||||||
|  |             self.api.get_resource('/system/identity').get() | ||||||
|  |             return True | ||||||
|  |         except (socket.error, socket.timeout, Exception) as ex: | ||||||
|  |             print(f'Connection to router {self.router_name}@{self.router_entry.hostname} has been lost: {ex}') | ||||||
|  |             self.api = None | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |     def connect(self): | ||||||
|  |         if self.is_connected(): | ||||||
|  |             return | ||||||
|  |         try: | ||||||
|  |             print('Connecting to router {0}@{1}'.format(self.router_name, self.router_entry.hostname)) | ||||||
|  |             self.api = self.connection.get_api() | ||||||
|  |             print('Connection to router {0}@{1} has been established'.format(self.router_name, self.router_entry.hostname)) | ||||||
|  |         except (socket.error, socket.timeout, Exception) as ex: | ||||||
|  |             print('Connection to router {0}@{1} has failed: {2}'.format(self.router_name, self.router_entry.hostname, ex)) | ||||||
|  |             raise  | ||||||
|  |  | ||||||
|  |     def router_api(self): | ||||||
|  |         if not self.is_connected(): | ||||||
|  |             self.connect() | ||||||
|  |         return self.api | ||||||
							
								
								
									
										133
									
								
								mktxp/router_metric.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								mktxp/router_metric.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | from mktxp.cli.config.config import config_handler, MKTXPConfigKeys | ||||||
|  | from mktxp.router_connection import RouterAPIConnection | ||||||
|  |  | ||||||
|  | class RouterMetric: | ||||||
|  |     ''' RouterOS Metrics data provider | ||||||
|  |     '''              | ||||||
|  |     def __init__(self, router_name): | ||||||
|  |         self.router_name = router_name | ||||||
|  |         self.router_entry  = config_handler.entry(router_name) | ||||||
|  |         self.api_connection = RouterAPIConnection(router_name, self.router_entry) | ||||||
|  |         self.router_id = { | ||||||
|  |             MKTXPConfigKeys.ROUTERBOARD_NAME: self.router_name, | ||||||
|  |             MKTXPConfigKeys.ROUTERBOARD_ADDRESS: self.router_entry.hostname | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |     def identity_records(self, identity_labels = []): | ||||||
|  |         try: | ||||||
|  |             identity_records = self.api_connection.router_api().get_resource('/system/identity').get() | ||||||
|  |             return self._trimmed_records(identity_records, identity_labels) | ||||||
|  |         except Exception as exc: | ||||||
|  |             print(f'Error getting system identity info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |     def routerboard_records(self, routerboard_labels = []): | ||||||
|  |         try: | ||||||
|  |             routerboard_records = self.api_connection.router_api().get_resource('/system/routerboard').get() | ||||||
|  |             return self._trimmed_records(routerboard_records, routerboard_labels) | ||||||
|  |         except Exception as exc: | ||||||
|  |             print(f'Error getting system routerboard info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |     def health_records(self, health_labels = []): | ||||||
|  |         try: | ||||||
|  |             health_records = self.api_connection.router_api().get_resource('/system/health').get() | ||||||
|  |             return self._trimmed_records(health_records, health_labels) | ||||||
|  |         except Exception as exc: | ||||||
|  |             print(f'Error getting system health info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |     def system_resource_records(self, resource_labels = []): | ||||||
|  |         try: | ||||||
|  |             system_resource_records = self.api_connection.router_api().get_resource('/system/resource').get() | ||||||
|  |             return self._trimmed_records(system_resource_records, resource_labels) | ||||||
|  |         except Exception as exc: | ||||||
|  |             print(f'Error getting system resource info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |     def dhcp_lease_records(self, dhcp_lease_labels = []): | ||||||
|  |         try: | ||||||
|  |             #dhcp_lease_records = self.api_connection.router_api().get_resource('/ip/dhcp-server/lease').get(status='bound') | ||||||
|  |             dhcp_lease_records = self.api_connection.router_api().get_resource('/ip/dhcp-server/lease').call('print', {'active':''}) | ||||||
|  |             return self._trimmed_records(dhcp_lease_records, dhcp_lease_labels) | ||||||
|  |         except Exception as exc: | ||||||
|  |             print(f'Error getting dhcp info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |     def interface_traffic_records(self, interface_traffic_labels = []): | ||||||
|  |         try: | ||||||
|  |             traffic_records = self.api_connection.router_api().get_resource('/interface').get(running='yes', disabled='no') | ||||||
|  |             return self._trimmed_records(traffic_records, interface_traffic_labels) | ||||||
|  |         except Exception as exc: | ||||||
|  |             print(f'Error getting interface traffic info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |     def interface_monitor_records(self, interface_monitor_labels = [], kind = 'ethernet'): | ||||||
|  |         try: | ||||||
|  |             interfaces = self.api_connection.router_api().get_resource(f'/interface/{kind}').get() | ||||||
|  |             interface_names = [interface['name'] for interface in interfaces] | ||||||
|  |  | ||||||
|  |             interface_monitor = lambda int_num : self.api_connection.router_api().get_resource(f'/interface/{kind}').call('monitor', {'once':'', 'numbers':f'{int_num}'}) | ||||||
|  |             interface_monitor_records = [interface_monitor(int_num)[0] for int_num in range(len(interface_names))] | ||||||
|  |             return self._trimmed_records(interface_monitor_records, interface_monitor_labels) | ||||||
|  |         except Exception as exc: | ||||||
|  |             print(f'Error getting {kind} interface monitor info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |     def pool_records(self, pool_labels = []): | ||||||
|  |         try: | ||||||
|  |             pool_records = self.api_connection.router_api().get_resource('/ip/pool').get() | ||||||
|  |             return self._trimmed_records(pool_records, pool_labels) | ||||||
|  |         except Exception as exc: | ||||||
|  |             print(f'Error getting pool info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |     def pool_used_records(self, pool_used_labels = []): | ||||||
|  |         try: | ||||||
|  |             pool_used_records = self.api_connection.router_api().get_resource('/ip/pool/used').get() | ||||||
|  |             return self._trimmed_records(pool_used_records, pool_used_labels) | ||||||
|  |         except Exception as exc: | ||||||
|  |             print(f'Error getting pool used info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |     def route_records(self, route_labels = []): | ||||||
|  |         try: | ||||||
|  |             route_records = self.api_connection.router_api().get_resource('/ip/route').get(active='yes') | ||||||
|  |             return self._trimmed_records(route_records, route_labels) | ||||||
|  |         except Exception as exc: | ||||||
|  |             print(f'Error getting pool active routes info from router{self.router_name}@{self.router_entry.hostname}: {exc}') | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     # Helpers | ||||||
|  |     def _trimmed_records(self, router_records, metric_labels): | ||||||
|  |         if len(metric_labels) == 0 and len(router_records) > 0: | ||||||
|  |             metric_labels = router_records[0].keys() | ||||||
|  |         metric_labels = set(metric_labels)       | ||||||
|  |  | ||||||
|  |         labeled_records = [] | ||||||
|  |         dash2_ = lambda x : x.replace('-', '_') | ||||||
|  |         for router_record in router_records: | ||||||
|  |             translated_record = {dash2_(key): value for (key, value) in router_record.items() if dash2_(key) in metric_labels} | ||||||
|  |             for key, value in self.router_id.items(): | ||||||
|  |                 translated_record[key] = value | ||||||
|  |             labeled_records.append(translated_record) | ||||||
|  |         return labeled_records | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										0
									
								
								mktxp/utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								mktxp/utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										273
									
								
								mktxp/utils/utils.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										273
									
								
								mktxp/utils/utils.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,273 @@ | |||||||
|  | # coding=utf8 | ||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | import os, sys, shlex, tempfile, shutil, re | ||||||
|  | import subprocess, hashlib | ||||||
|  | import keyring, getpass | ||||||
|  | from collections import Iterable | ||||||
|  | from contextlib import contextmanager | ||||||
|  |  | ||||||
|  | ''' Utilities / Helpers | ||||||
|  | ''' | ||||||
|  |  | ||||||
|  | @contextmanager | ||||||
|  | def temp_dir(quiet = True): | ||||||
|  |     ''' Temp dir context manager | ||||||
|  |     ''' | ||||||
|  |     tmp_dir = tempfile.mkdtemp() | ||||||
|  |     try: | ||||||
|  |         yield tmp_dir | ||||||
|  |     finally: | ||||||
|  |         # remove tmp dir | ||||||
|  |         try: | ||||||
|  |             shutil.rmtree(tmp_dir) | ||||||
|  |         except OSError as e: | ||||||
|  |             if not quiet: | ||||||
|  |                 print ('Error while removing a tmp dir: {}'.format(e.args[0])) | ||||||
|  |  | ||||||
|  | class CmdProcessingError(Exception): | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  | def run_cmd(cmd, shell = False): | ||||||
|  |     ''' Runs shell commands in a separate process | ||||||
|  |     ''' | ||||||
|  |     if not shell: | ||||||
|  |         cmd = shlex.split(cmd) | ||||||
|  |     proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell = shell) | ||||||
|  |     output = proc.communicate()[0].decode('utf-8') | ||||||
|  |     if proc.returncode != 0: | ||||||
|  |         raise CmdProcessingError(output) | ||||||
|  |     return output | ||||||
|  |  | ||||||
|  | def get_last_digit_from_shell_cmd(cmd): | ||||||
|  |     try: | ||||||
|  |         cmd_output = run_cmd(cmd, shell = True) | ||||||
|  |     except CmdProcessingError as e: | ||||||
|  |         if not quiet: | ||||||
|  |             print ('Error while running cmd: {}'.format(e.args[0])) | ||||||
|  |         return -1 | ||||||
|  |     else: | ||||||
|  |         return get_last_digit(cmd_output) | ||||||
|  |  | ||||||
|  | def get_last_digit(str_to_search): | ||||||
|  |     p = re.compile('(\d*\.?\d+)') | ||||||
|  |     match = p.search(str_to_search) | ||||||
|  |     if match: | ||||||
|  |         return float(match.group()) | ||||||
|  |     else: | ||||||
|  |         return -1 | ||||||
|  |  | ||||||
|  | def encfs_version(): | ||||||
|  |     cmd = 'encfs --version' | ||||||
|  |     return get_last_digit_from_shell_cmd(cmd) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FSHelper: | ||||||
|  |     ''' File System ops helper | ||||||
|  |     ''' | ||||||
|  |     @staticmethod | ||||||
|  |     def full_path(path, check_parent_path = False): | ||||||
|  |         ''' Full path | ||||||
|  |         ''' | ||||||
|  |         if path: | ||||||
|  |             path = os.path.expanduser(path) | ||||||
|  |             path = os.path.expandvars(path) | ||||||
|  |             path = os.path.abspath(path) | ||||||
|  |             path = os.path.realpath(path) | ||||||
|  |  | ||||||
|  |         # for files, check that the parent dir exists | ||||||
|  |         if check_parent_path: | ||||||
|  |             if not os.access(os.path.dirname(path), os.W_OK): | ||||||
|  |                 print('Non-valid folder path:\n\t "{}"'.format(os.path.dirname(path))) | ||||||
|  |                 sys.exit(1) | ||||||
|  |  | ||||||
|  |         return path if path else None | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def mountpoint(path): | ||||||
|  |         ''' The mount point portion of a path | ||||||
|  |         ''' | ||||||
|  |         path = FSHelper.full_path(path) | ||||||
|  |         while path != os.path.sep: | ||||||
|  |             if os.path.ismount(path): | ||||||
|  |                 return path | ||||||
|  |             path = os.path.realpath(os.path.join(path, os.pardir)) | ||||||
|  |         return path if path != os.path.sep else None | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def move_FS_entry(orig_path, target_path, | ||||||
|  |                       check_unique = True, | ||||||
|  |                       quiet = False, stop = False): | ||||||
|  |         ''' Moves FS entry | ||||||
|  |         ''' | ||||||
|  |         succeeded = False | ||||||
|  |         try: | ||||||
|  |             if check_unique and os.path.exists(target_path): | ||||||
|  |                 raise OSError('\nTarget path entry already exists') | ||||||
|  |             shutil.move(orig_path, target_path) | ||||||
|  |             succeeded = True | ||||||
|  |         except OSError as e: | ||||||
|  |             if not quiet: | ||||||
|  |                 print(str(e)) | ||||||
|  |                 print('Failed to move entry:\n\t{0}\n\t{1}'.format(orig_path, target_path)) | ||||||
|  |                 print('Exiting...') if stop else print('Skipping...') | ||||||
|  |             if stop: | ||||||
|  |                 sys.exit(1) | ||||||
|  |         return succeeded | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def file_md5(fpath, block_size=0, hex=False): | ||||||
|  |         ''' Calculates MD5 hash for a file at fpath | ||||||
|  |         ''' | ||||||
|  |         md5 = hashlib.md5() | ||||||
|  |         if block_size == 0: | ||||||
|  |             block_size = 128 * md5.block_size | ||||||
|  |         with open(fpath,'rb') as f: | ||||||
|  |             for chunk in iter(lambda: f.read(block_size), b''): | ||||||
|  |                 md5.update(chunk) | ||||||
|  |         return md5.hexdigest() if hex else md5.digest() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UniqueDirNamesChecker: | ||||||
|  |     ''' Unique file names Helper | ||||||
|  |     ''' | ||||||
|  |     def __init__(self, src_dir, unique_fnames = None): | ||||||
|  |         self._uname_gen = unique_fnames() if unique_fnames else self.unique_fnames() | ||||||
|  |  | ||||||
|  |         # init the generator function with file names from given source directory | ||||||
|  |         src_dir = FSHelper.full_path(src_dir) | ||||||
|  |         fnames = [fname for fname in os.listdir(src_dir)] | ||||||
|  |  | ||||||
|  |         for fname in fnames: | ||||||
|  |             next(self._uname_gen) | ||||||
|  |             self._uname_gen.send(fname) | ||||||
|  |  | ||||||
|  |     def unique_name(self, fname): | ||||||
|  |         ''' Returns unique file name | ||||||
|  |         ''' | ||||||
|  |         next(self._uname_gen) | ||||||
|  |         return self._uname_gen.send(fname) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def unique_fnames(): | ||||||
|  |         ''' default unique file names generator method, | ||||||
|  |             via appending a simple numbering pattern | ||||||
|  |         ''' | ||||||
|  |         unique_names = {} | ||||||
|  |         while True: | ||||||
|  |             fname = yield | ||||||
|  |             while True: | ||||||
|  |                 if fname in unique_names: | ||||||
|  |                     unique_names[fname] += 1 | ||||||
|  |                     name_base, name_ext = os.path.splitext(fname) | ||||||
|  |                     fname = '{0}_{1}{2}'.format(name_base, unique_names[fname], name_ext) | ||||||
|  |                 else: | ||||||
|  |                     unique_names[fname] = 0 | ||||||
|  |                     yield fname | ||||||
|  |                     break | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PasswordHandler: | ||||||
|  |     ''' Password Helper | ||||||
|  |     ''' | ||||||
|  |     @staticmethod | ||||||
|  |     def get_pwd_input(confirm = False): | ||||||
|  |         ''' Gets password from command line | ||||||
|  |         ''' | ||||||
|  |         pwd = getpass.getpass('Enter password:') | ||||||
|  |         if pwd and confirm: | ||||||
|  |             pwd_confirm = getpass.getpass('Confirm password:') | ||||||
|  |             if pwd != pwd_confirm: | ||||||
|  |                 print ("Passwords do not match") | ||||||
|  |                 return None | ||||||
|  |         return pwd | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def get_pwd(cls, pwd_entry_name = None, confirm = False): | ||||||
|  |         ''' Gets password from an OS-specific keyring or command line | ||||||
|  |         ''' | ||||||
|  |         pwd = None | ||||||
|  |         new_pwd = False | ||||||
|  |  | ||||||
|  |         if pwd_entry_name: | ||||||
|  |             pwd = keyring.get_password(pwd_entry_name, getpass.getuser()) | ||||||
|  |  | ||||||
|  |         if not pwd: | ||||||
|  |             pwd = cls.get_pwd_input(confirm = confirm) | ||||||
|  |             new_pwd = True | ||||||
|  |  | ||||||
|  |         return pwd, new_pwd | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def store_pwd(pwd, pwd_entry_name): | ||||||
|  |         ''' Store password into an OS-specific keyring | ||||||
|  |         ''' | ||||||
|  |         if not (pwd and pwd_entry_name): | ||||||
|  |             return | ||||||
|  |         keyring.set_password(pwd_entry_name, getpass.getuser(), pwd) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def delete_pwd(pwd_entry_name): | ||||||
|  |         ''' Deletes password from an OS-specific keyring | ||||||
|  |         ''' | ||||||
|  |         if pwd_entry_name: | ||||||
|  |             pwd = keyring.get_password(pwd_entry_name, getpass.getuser()) | ||||||
|  |             if pwd: | ||||||
|  |                 keyring.delete_password(pwd_entry_name, getpass.getuser()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UniquePartialMatchList(list): | ||||||
|  |     ''' Enables matching elements by unique "shortcuts" | ||||||
|  |         e.g: | ||||||
|  |             >> 'Another' in UniquePartialMatchList(['A long string', 'Another longs string']) | ||||||
|  |             >> True | ||||||
|  |             >>'long' in UniquePartialMatchList(['A long string', 'Another longs string']) | ||||||
|  |             >> False | ||||||
|  |             >> l.find('Another') | ||||||
|  |             >> 'Another longs string' | ||||||
|  |     ''' | ||||||
|  |     def _matched_items(self, partialMatch): | ||||||
|  |         ''' Generator expression of <matched items>, where <matched item> is | ||||||
|  |             a tuple of (<matched_element>, <is_exact_match>) | ||||||
|  |         ''' | ||||||
|  |         def _contains_or_equal(item): | ||||||
|  |             if isinstance(item, Iterable): | ||||||
|  |                 return (partialMatch in item) | ||||||
|  |             else: | ||||||
|  |                 return (partialMatch == item) | ||||||
|  |         return ((item, (partialMatch == item)) for item in self if _contains_or_equal(item)) | ||||||
|  |  | ||||||
|  |     def find(self, partialMatch): | ||||||
|  |         ''' Returns the element in which <partialMatch> can be found | ||||||
|  |             <partialMatch> is found if it either: | ||||||
|  |                 equals to an element or is contained by exactly one element | ||||||
|  |         ''' | ||||||
|  |         matched_cnt, unique_match = 0, None | ||||||
|  |         matched_items = self._matched_items(partialMatch) | ||||||
|  |         for match, exact_match in matched_items: | ||||||
|  |             if exact_match: | ||||||
|  |                 # found exact match | ||||||
|  |                 return match | ||||||
|  |             else: | ||||||
|  |                 # found a partial match | ||||||
|  |                 if not unique_match: | ||||||
|  |                     unique_match = match | ||||||
|  |                 matched_cnt += 1 | ||||||
|  |         return unique_match if matched_cnt == 1 else None | ||||||
|  |  | ||||||
|  |     def __contains__(self, partialMatch): | ||||||
|  |         ''' Check if <partialMatch> is contained by an element in the list, | ||||||
|  |             where <contained> is defined either as: | ||||||
|  |                 either "equals to element" or "contained by exactly one element" | ||||||
|  |         ''' | ||||||
|  |         return True if self.find(partialMatch) else False | ||||||
							
								
								
									
										79
									
								
								setup.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										79
									
								
								setup.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | ## Copyright (c) 2020 Arseniy Kuznetsov | ||||||
|  | ## | ||||||
|  | ## This program is free software; you can redistribute it and/or | ||||||
|  | ## modify it under the terms of the GNU General Public License | ||||||
|  | ## as published by the Free Software Foundation; either version 2 | ||||||
|  | ## of the License, or (at your option) any later version. | ||||||
|  | ## | ||||||
|  | ## This program is distributed in the hope that it will be useful, | ||||||
|  | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | ## GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | from setuptools import setup, find_packages | ||||||
|  | from os import path | ||||||
|  |  | ||||||
|  | # read the README.md contents | ||||||
|  | pkg_dir = path.abspath(path.dirname(__file__)) | ||||||
|  | with open(path.join(pkg_dir, 'README.md'), encoding='utf-8') as f: | ||||||
|  |     long_description = f.read() | ||||||
|  |  | ||||||
|  | setup( | ||||||
|  |     name='mktxp', | ||||||
|  |     version='0.14', | ||||||
|  |  | ||||||
|  |     url='https://github.com/akpw/mktxp', | ||||||
|  |  | ||||||
|  |     author='Arseniy Kuznetsov', | ||||||
|  |     author_email='k.arseniy@gmail.com', | ||||||
|  |  | ||||||
|  |     long_description=long_description, | ||||||
|  |     long_description_content_type='text/markdown', | ||||||
|  |  | ||||||
|  |     description=(''' | ||||||
|  |                     Prometheus Exporter for Mikrotik RouterOS devices | ||||||
|  |                 '''), | ||||||
|  |     license='GNU General Public License v2 (GPLv2)', | ||||||
|  |  | ||||||
|  |     packages=find_packages(exclude=['test*']), | ||||||
|  |  | ||||||
|  |     package_data = { | ||||||
|  |         '': ['config/*.conf'], | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     keywords = 'Mikrotik RouterOS Prometheus Exporter', | ||||||
|  |  | ||||||
|  |     install_requires = ['prometheus-client>=0.9.0', 'librouteros>=3.0.2'], | ||||||
|  |  | ||||||
|  |     test_suite = 'tests.mktxp_test_suite', | ||||||
|  |  | ||||||
|  |     entry_points={'console_scripts': [ | ||||||
|  |         'mktxp = mktxp.cli.dispatch:main', | ||||||
|  |     ]}, | ||||||
|  |  | ||||||
|  |     zip_safe=True, | ||||||
|  |  | ||||||
|  |     classifiers=[ | ||||||
|  |         'Development Status :: 4 - Beta', | ||||||
|  |         'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', | ||||||
|  |         'Programming Language :: Python', | ||||||
|  |         'Programming Language :: Python :: 3.6', | ||||||
|  |         'Programming Language :: Python :: 3 :: Only', | ||||||
|  |         'Intended Audience :: End Users.Desktop', | ||||||
|  |         'Intended Audience :: Developers', | ||||||
|  |         'Intended Audience :: System Administrators', | ||||||
|  |         'Intended Audience :: Information Technology', | ||||||
|  |         'Operating System :: MacOS', | ||||||
|  |         'Operating System :: POSIX :: Linux', | ||||||
|  |         'Topic :: System', | ||||||
|  |         'Topic :: System :: Systems Administration', | ||||||
|  |         'Topic :: Utilities', | ||||||
|  |         'Topic :: Mikrotik', | ||||||
|  |         'Topic :: Mikrotik :: RouterOS', | ||||||
|  |         'Topic :: Prometheus', | ||||||
|  |         'Topic :: Prometheus :: Exporter' | ||||||
|  |     ] | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										27
									
								
								tests/__init__.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										27
									
								
								tests/__init__.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | import unittest | ||||||
|  | import tests.efsc.test_efsc_tools | ||||||
|  | import tests.efsm.test_efsm_tools | ||||||
|  | import tests.efsb.test_efsb_tools | ||||||
|  |  | ||||||
|  | def efst_test_suite(): | ||||||
|  |  | ||||||
|  |     loader = unittest.TestLoader() | ||||||
|  |  | ||||||
|  |     # load tests from the efsc package | ||||||
|  |     efsc_tools_suite = loader.loadTestsFromModule(tests.efsc.test_efsc_tools) | ||||||
|  |  | ||||||
|  |     # load tests from the efsm package | ||||||
|  |     efsm_tools_suite = loader.loadTestsFromModule(tests.efsm.test_efsm_tools) | ||||||
|  |  | ||||||
|  |     # load tests from the efsm package | ||||||
|  |     efsb_tools_suite = loader.loadTestsFromModule(tests.efsb.test_efsb_tools) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     # add all tests to EFST suite | ||||||
|  |     efst_test_suite = unittest.TestSuite() | ||||||
|  |     efst_test_suite.addTests(efsc_tools_suite) | ||||||
|  |     efst_test_suite.addTests(efsm_tools_suite) | ||||||
|  |     efst_test_suite.addTests(efsb_tools_suite) | ||||||
|  |  | ||||||
|  |     return efst_test_suite | ||||||
		Reference in New Issue
	
	Block a user