pax_global_header00006660000000000000000000000064133165104410014510gustar00rootroot0000000000000052 comment=434b03ba3cf66bd5487d7ce2f0f91cc02d623c54 ncclient-0.6.0/000077500000000000000000000000001331651044100133125ustar00rootroot00000000000000ncclient-0.6.0/.gitignore000066400000000000000000000001171331651044100153010ustar00rootroot00000000000000*.egg *.egg-info *.EGG *.EGG-INFO *.mo *.pyc *.pyo *.swp bin build dist *~ v/ ncclient-0.6.0/.travis.yml000066400000000000000000000004221331651044100154210ustar00rootroot00000000000000language: python python: - '2.7' - '3.4' - '3.6' - '3.7-dev' install: - pip install . - pip install -r requirements.txt - pip install rednose coverage coveralls script: nosetests test --rednose --verbosity=3 --with-coverage --cover-package ncclient after_success: coveralls ncclient-0.6.0/Changelog000066400000000000000000000046431331651044100151330ustar00rootroot00000000000000version 0.6.0 ------------- * Re-enabled Python 3.7 by changing "async" to "async_mode" in several locations version 0.5.4 ------------- * No major functionality, accumulation of small fixes since 0.5.3 (see commit history) * Disable Python 3.7 due to use of "async" (a new Python keyword) in some functions version 0.5.3 ------------- Fixes/Enhancements ================== * Add notifications support * Add support for ecdsa keys * Various bug fixes version 0.5.2 ------------- Fixes/Enhancements ================== * Add support for Python 3 * Improve Junos ioproc performance * Performance improvements * Updated test cases * Many bug and performance fixes version 0.4.7 ------------- Fixes/Enhancements ================== * Add support for netconf 1.1 version 0.4.6 ------------- Fixes/Enhancements ================== * Fix multiple RPC error handling * Add support for cancel-commit and persist param * Add more examples version 0.4.5 ------------- Device Support ============== * Add Huawei device support * Add cli command support for hpcomware v7 devices * Add H3C support, Support H3C CLI,Action,Get_bulk,Save,Rollback,etc. * Add alcatel lucent support Fixes ===== * Rewrite multiple error handling * Add coveralls support, with shield in README.md * Set severity level to higher when multiple * Simplify logging and multi-error reporting * Keep stacktrace of errors * Check for known hosts on hostkey_verify only * Add check for device sending back null error_text * Fix RPC.raise_mode * Specifying hostkey_verify=False should not load_known_hosts * Check the correct field on rpc-error element version 0.4.3 ------------- Features ======== * Nexus exec_command operation * Allow specifying multiple cmd elements in Cisco Nexus * Update rpc for nested rpc-errors Fixes ===== * Prevent race condition in threading * Prevent hanging in session close version 0.4.2 ------------- Features ======== * Support for paramiko ProxyCommand via ~/.ssh/config parsing * Add Juniper-specific commit operations * Add Huawei devices support * Tests/Travis support * ioproc transport support for Juniper devices * Update Cisco CSR device handler Fixes ===== * Fix issue with locked method missing device handler * Fix for namespace definition with lxml * Add missing SessionError exception * Update docs for Nexus device handler * Docstring fixes * Typos * Minor bugfixes Other ===== * Add an example for Cisco CSR1000v ncclient-0.6.0/LICENSE000066400000000000000000000261361331651044100143270ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ncclient-0.6.0/MANIFEST.in000066400000000000000000000001011331651044100150400ustar00rootroot00000000000000include *.rst requirements.txt LICENSE graft docs graft examples ncclient-0.6.0/README000066400000000000000000000111051331651044100141700ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/einarnn/ncclient.svg?branch=master)](https://travis-ci.org/einarnn/ncclient) ncclient: Python library for NETCONF clients -------------------------------------------- ncclient is a Python library that facilitates client-side scripting and application development around the NETCONF protocol. `ncclient` was developed by [Shikar Bhushan](http://schmizz.net). It is now maintained by [Leonidas Poulopoulos (@leopoul)](http://ncclient.org/ncclient/) **Docs**: [http://ncclient.readthedocs.org](http://ncclient.readthedocs.org) **PyPI**: [https://pypi.python.org/pypi/ncclient](https://pypi.python.org/pypi/ncclient) #### Requirements: * Python 2.7 or Python 3.4+ * setuptools 0.6+ * Paramiko 1.7+ * lxml 3.3.0+ * libxml2 * libxslt If you are on Debian/Ubuntu install the following libs (via aptitude or apt-get): * libxml2-dev * libxslt1-dev #### Installation: [ncclient] $ sudo python setup.py install or via pip: pip install ncclient #### Examples: [ncclient] $ python examples/juniper/*.py ### Usage ####Get device running config Use either an interactive Python console (ipython) or integrate the following in your code: from ncclient import manager with manager.connect(host=host, port=830, username=user, hostkey_verify=False) as m: c = m.get_config(source='running').data_xml with open("%s.xml" % host, 'w') as f: f.write(c) As of 0.4.1 ncclient integrates Juniper's and Cisco's forks, lots of new concepts have been introduced that ease management of Juniper and Cisco devices respectively. The biggest change is the introduction of device handlers in connection paramms. For example to invoke Juniper's functions annd params one has to re-write the above with ***device_params={'name':'junos'}***: from ncclient import manager with manager.connect(host=host, port=830, username=user, hostkey_verify=False, device_params={'name':'junos'}) as m: c = m.get_config(source='running').data_xml with open("%s.xml" % host, 'w') as f: f.write(c) Device handlers are easy to implement and prove to be futureproof. ####Supported device handlers * Juniper: device_params={'name':'junos'} * Cisco CSR: device_params={'name':'csr'} * Cisco Nexus: device_params={'name':'nexus'} * Huawei: device_params={'name':'huawei'} * Alcatel Lucent: device_params={'name':'alu'} * H3C: device_params={'name':'h3c'} * HP Comware: device_params={'name':'hpcomware'} ### Changes | brief - v0.5.3 * Add notifications support * Add support for ecdsa keys * Various bug fixes ### Contributors * v0.5.3: [Justin Wilcox](https://github.com/jwwilcox), [Stacy W. Smith](https://github.com/stacywsmith), [Mircea Ulinic](https://github.com/mirceaulinic), [Ebben Aries](https://github.com/earies), [Einar Nilsen-Nygaard](https://github.com/einarnn), [QijunPan](https://github.com/QijunPan) * v0.5.2: [Nitin Kumar](https://github.com/vnitinv), [Kristian Larsson](https://github.com/plajjan), [palashgupta](https://github.com/palashgupta), [Jonathan Provost](https://github.com/JoProvost), [Jainpriyal](https://github.com/Jainpriyal), [sharang](https://github.com/sharang), [pseguel](https://github.com/pseguel), [nnakamot](https://github.com/nnakamot), [Алексей Пастухов](https://github.com/p-alik), [Christian Giese](https://github.com/GIC-de), [Peipei Guo](https://github.com/peipeiguo), [Time Warner Cable Openstack Team](https://github.com/twc-openstack) * v0.4.7: [Einar Nilsen-Nygaard](https://github.com/einarnn), [Vaibhav Bajpai](https://github.com/vbajpai), Norio Nakamoto * v0.4.6: [Nitin Kumar](https://github.com/vnitinv), [Carl Moberg](https://github.com/cmoberg), [Stavros Kroustouris](https://github.com/kroustou) * v0.4.5: [Sebastian Wiesinger](https://github.com/sebastianw), [Vincent Bernat](https://github.com/vincentbernat), [Matthew Stone](https://github.com/bigmstone), [Nitin Kumar](https://github.com/vnitinv) * v0.4.3: [Jeremy Schulman](https://github.com/jeremyschulman), [Ray Solomon](https://github.com/rsolomo), [Rick Sherman](https://github.com/shermdog), [subhak186](https://github.com/subhak186) * v0.4.2: [katharh](https://github.com/katharh), [Francis Luong (Franco)](https://github.com/francisluong), [Vincent Bernat](https://github.com/vincentbernat), [Juergen Brendel](https://github.com/juergenbrendel), [Quentin Loos](https://github.com/Kent1), [Ray Solomon](https://github.com/rsolomo), [Sebastian Wiesinger](https://github.com/sebastianw), [Ebben Aries](https://github.com/earies) * v0.4.1: [Jeremy Schulman](https://github.com/jeremyschulman), [Ebben Aries](https://github.com/earies), Juergen Brendel ncclient-0.6.0/README.md000066400000000000000000000134721331651044100146000ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/ncclient/ncclient.svg?branch=master)](https://travis-ci.org/ncclient/ncclient) [![Coverage Status](https://coveralls.io/repos/github/ncclient/ncclient/badge.svg?branch=master)](https://coveralls.io/github/ncclient/ncclient?branch=master) [![Documentation Status](https://readthedocs.org/projects/ncclient/badge/?version=latest)](https://readthedocs.org/projects/ncclient/?badge=latest) # ncclient: Python library for NETCONF clients -------------------------------------------- ncclient is a Python library that facilitates client-side scripting and application development around the NETCONF protocol. `ncclient` was developed by [Shikar Bhushan](http://schmizz.net). It is now maintained by [Leonidas Poulopoulos (@leopoul)](http://ncclient.org) and Einar Nilsen-Nygaard (@einarnn) **Docs**: [http://ncclient.readthedocs.org](http://ncclient.readthedocs.org) **PyPI**: [https://pypi.python.org/pypi/ncclient](https://pypi.python.org/pypi/ncclient) ## Recent Highlights | Date | Release | Description | | :----: | :-----: | :---------- | | 07/02/18 | `0.6.0` | Minor release reinstating Python 3.7 and greater compatibility, but necessitating a change to client code that uses `async_mode`. | | 07/02/18 | `0.5.4` | New release rolling up myriad of small commits since `0.5.3`. Please note that this release is **incompatible wth Python 3.7** due to the use of a new Python 3.7 keyword, `async`, in function signatures. This will be resolved in 0.6.0| ## Requirements * Python 2.7 or Python 3.4+ * setuptools 0.6+ * Paramiko 1.7+ * lxml 3.3.0+ * libxml2 * libxslt If you are on Debian/Ubuntu install the following libs (via aptitude or apt-get): * libxml2-dev * libxslt1-dev ## Installation [ncclient] $ sudo python setup.py install or via pip: pip install ncclient ## Examples: [ncclient] $ python examples/juniper/*.py ## Usage ### Get device running config Use either an interactive Python console (ipython) or integrate the following in your code: from ncclient import manager with manager.connect(host=host, port=830, username=user, hostkey_verify=False) as m: c = m.get_config(source='running').data_xml with open("%s.xml" % host, 'w') as f: f.write(c) As of 0.4.1 ncclient integrates Juniper's and Cisco's forks, lots of new concepts have been introduced that ease management of Juniper and Cisco devices respectively. The biggest change is the introduction of device handlers in connection paramms. For example to invoke Juniper's functions annd params one has to re-write the above with ***device_params={'name':'junos'}***: from ncclient import manager with manager.connect(host=host, port=830, username=user, hostkey_verify=False, device_params={'name':'junos'}) as m: c = m.get_config(source='running').data_xml with open("%s.xml" % host, 'w') as f: f.write(c) Device handlers are easy to implement and prove to be futureproof. ## Supported device handlers * Juniper: device_params={'name':'junos'} * Cisco CSR: device_params={'name':'csr'} * Cisco Nexus: device_params={'name':'nexus'} * Cisco IOS XR: device_params={'name':'iosxr'} * Cisco IOS XE: device_params={'name':'iosxe'} * Huawei: device_params={'name':'huawei'} * Alcatel Lucent: device_params={'name':'alu'} * H3C: device_params={'name':'h3c'} * HP Comware: device_params={'name':'hpcomware'} * Server or anything not in above: device_params={'name':'default'} ## Changes | brief - v0.5.3 * Add notifications support * Add support for ecdsa keys * Various bug fixes ## Contributors * v0.6.0: @einarnn * v0.5.4: @adamcubel, Joel Teichroeb, @leopoul, Chase Garner, @budhadityabanerjee, @earies, @ganeshrn, @vnitinv, Siming Yuan, @mirceaaulinic, @stacywsmith, Xavier Hardy, @jwwilcox, @QijunPan, @avangel, @marekgr, @hugovk, @felixonmars, @dexteradeus * v0.5.3: [Justin Wilcox](https://github.com/jwwilcox), [Stacy W. Smith](https://github.com/stacywsmith), [Mircea Ulinic](https://github.com/mirceaulinic), [Ebben Aries](https://github.com/earies), [Einar Nilsen-Nygaard](https://github.com/einarnn), [QijunPan](https://github.com/QijunPan) * v0.5.2: [Nitin Kumar](https://github.com/vnitinv), [Kristian Larsson](https://github.com/plajjan), [palashgupta](https://github.com/palashgupta), [Jonathan Provost](https://github.com/JoProvost), [Jainpriyal](https://github.com/Jainpriyal), [sharang](https://github.com/sharang), [pseguel](https://github.com/pseguel), [nnakamot](https://github.com/nnakamot), [Алексей Пастухов](https://github.com/p-alik), [Christian Giese](https://github.com/GIC-de), [Peipei Guo](https://github.com/peipeiguo), [Time Warner Cable Openstack Team](https://github.com/twc-openstack) * v0.4.7: [Einar Nilsen-Nygaard](https://github.com/einarnn), [Vaibhav Bajpai](https://github.com/vbajpai), Norio Nakamoto * v0.4.6: [Nitin Kumar](https://github.com/vnitinv), [Carl Moberg](https://github.com/cmoberg), [Stavros Kroustouris](https://github.com/kroustou) * v0.4.5: [Sebastian Wiesinger](https://github.com/sebastianw), [Vincent Bernat](https://github.com/vincentbernat), [Matthew Stone](https://github.com/bigmstone), [Nitin Kumar](https://github.com/vnitinv) * v0.4.3: [Jeremy Schulman](https://github.com/jeremyschulman), [Ray Solomon](https://github.com/rsolomo), [Rick Sherman](https://github.com/shermdog), [subhak186](https://github.com/subhak186) * v0.4.2: [katharh](https://github.com/katharh), [Francis Luong (Franco)](https://github.com/francisluong), [Vincent Bernat](https://github.com/vincentbernat), [Juergen Brendel](https://github.com/juergenbrendel), [Quentin Loos](https://github.com/Kent1), [Ray Solomon](https://github.com/rsolomo), [Sebastian Wiesinger](https://github.com/sebastianw), [Ebben Aries](https://github.com/earies) * v0.4.1: [Jeremy Schulman](https://github.com/jeremyschulman), [Ebben Aries](https://github.com/earies), Juergen Brendel ncclient-0.6.0/README.rst000066400000000000000000000155201331651044100150040ustar00rootroot00000000000000ncclient: Python library for NETCONF clients -------------------------------------------- ncclient is a Python library that facilitates client-side scripting and application development around the NETCONF protocol. ``ncclient`` was developed by `Shikar Bhushan `_. It is now maintained by `Leonidas Poulopoulos (@leopoul) `_ Docs: `http://ncclient.readthedocs.org `_ Github: `https://github.com/ncclient/ncclient `_ Requirements: ^^^^^^^^^^^^^ - Python 2.7 or Python 3.4+ - setuptools 0.6+ - Paramiko 1.7+ - lxml 3.3.0+ - libxml2 - libxslt If you are on Debian/Ubuntu install the following libs (via aptitude or apt-get): - libxml2-dev - libxslt1-dev Installation: ^^^^^^^^^^^^^ :: [ncclient] $ sudo python setup.py install or via pip: :: pip install ncclient Examples: ^^^^^^^^^ :: [ncclient] $ python examples/juniper/*.py Usage ~~~~~ Get device running config ''''''''''''''''''''''''' Use either an interactive Python console (ipython) or integrate the following in your code: :: from ncclient import manager with manager.connect(host=host, port=830, username=user, hostkey_verify=False) as m: c = m.get_config(source='running').data_xml with open("%s.xml" % host, 'w') as f: f.write(c) As of 0.4.1 ncclient integrates Juniper's and Cisco's forks, lots of new concepts have been introduced that ease management of Juniper and Cisco devices respectively. The biggest change is the introduction of device handlers in connection paramms. For example to invoke Juniper's functions annd params one has to re-write the above with **device\_params={'name':'junos'}**: :: from ncclient import manager with manager.connect(host=host, port=830, username=user, hostkey_verify=False, device_params={'name':'junos'}) as m: c = m.get_config(source='running').data_xml with open("%s.xml" % host, 'w') as f: f.write(c) Device handlers are easy to implement and prove to be futureproof. Supported device handlers ''''''''''''''''''''''''' * Juniper: device_params={'name':'junos'} * Cisco CSR: device_params={'name':'csr'} * Cisco Nexus: device_params={'name':'nexus'} * Huawei: device_params={'name':'huawei'} * Alcatel Lucent: device_params={'name':'alu'} * H3C: device_params={'name':'h3c'} * HP Comware: device_params={'name':'hpcomware'} Changes \| brief ~~~~~~~~~~~~~~~~ **v0.6.0** - Fix use of new Python 3.7 keyword, async - Re-enable Python 3.7 **v0.5.4** - Rollup of minor changes since 0.5.3 - Disablement of Python 3.7 due to async keyword issue **v0.5.3** - Add notifications support - Add support for ecdsa keys - Various bug fixes **v0.5.2** - Add support for Python 3 - Improve Junos ioproc performance - Performance improvements - Updated test cases - Many bug and performance fixes **v0.4.7** - Add support for netconf 1.1 **v0.4.6** - Fix multiple RPC error generation - Add support for cancel-commit and persist param - Add more examples **v0.4.5** - Add Huawei device support - Add cli command support for hpcomware v7 devices - Add H3C support, Support H3C CLI,Action,Get_bulk,Save,Rollback,etc. - Add alcatel lucent support - Rewrite multiple error handling - Add coveralls support, with shield in README.md - Set severity level to higher when multiple - Simplify logging and multi-error reporting - Keep stacktrace of errors - Check for known hosts on hostkey_verify only - Add check for device sending back null error_text - Fix RPC.raise_mode - Specifying hostkey_verify=False should not load_known_hosts - Check the correct field on rpc-error element **v0.4.3** - Nexus exec_command operation - Allow specifying multiple cmd elements in Cisco Nexus - Update rpc for nested rpc-errors - Prevent race condition in threading - Prevent hanging in session close **v0.4.2** - Support for paramiko ProxyCommand via ~/.ssh/config parsing - Add Juniper-specific commit operations - Add Huawei devices support - Tests/Travis support - ioproc transport support for Juniper devices - Update Cisco CSR device handler - Many minor and major fixes **v0.4.1** - Switch between replies if custom handler is found - Add Juniper, Cisco and default device handlers - Allow preferred SSH subsystem name in device params - Allow iteration over multiple SSH subsystem names. Acknowledgements ~~~~~~~~~~~~~~~~ - v0.6.0: `Einar Nilsen-Nygaard`_ - v0.5.4: Various - v0.5.3: `Justin Wilcox`_, `Stacy W. Smith`_, `Mircea Ulinic`_, `Ebben Aries`_, `Einar Nilsen-Nygaard`_, `QijunPan`_ - v0.5.2: `Nitin Kumar`_, `Kristian Larsson`_, `palashgupta`_, `Jonathan Provost`_, `Jainpriyal`_, `sharang`_, `pseguel`_, `nnakamot`_, `Алексей Пастухов`_, `Christian Giese`_, `Peipei Guo`_, `Time Warner Cable Openstack Team`_ - v0.4.7: `Einar Nilsen-Nygaard`_, `Vaibhav Bajpai`_, Norio Nakamoto - v0.4.6: `Nitin Kumar`_, `Carl Moberg`_, `Stavros Kroustouris`_ - v0.4.5: `Sebastian Wiesinger`_, `Vincent Bernat`_, `Matthew Stone`_, `Nitin Kumar`_ - v0.4.3: `Jeremy Schulman`_, `Ray Solomon`_, `Rick Sherman`_, `subhak186`_ - v0.4.2: `katharh`_, `Francis Luong (Franco)`_, `Vincent Bernat`_, `Juergen Brendel`_, `Quentin Loos`_, `Ray Solomon`_, `Sebastian Wiesinger`_, `Ebben Aries`_ - v0.4.1: `Jeremy Schulman`_, `Ebben Aries`_, Juergen Brendel .. _Nitin Kumar: https://github.com/vnitinv .. _Kristian Larsson: https://github.com/plajjan .. _palashgupta: https://github.com/palashgupta .. _Jonathan Provost: https://github.com/JoProvost .. _Jainpriyal: https://github.com/Jainpriyal .. _sharang: https://github.com/sharang .. _pseguel: https://github.com/pseguel .. _nnakamot: https://github.com/nnakamot .. _Алексей Пастухов: https://github.com/p-alik .. _Christian Giese: https://github.com/GIC-de .. _Peipei Guo: https://github.com/peipeiguo .. _Time Warner Cable Openstack Team: https://github.com/twc-openstack .. _Einar Nilsen-Nygaard: https://github.com/einarnn .. _Vaibhav Bajpai: https://github.com/vbajpai .. _Carl Moberg: https://github.com/cmoberg .. _Stavros Kroustouris: https://github.com/kroustou .. _Sebastian Wiesinger: https://github.com/sebastianw .. _Vincent Bernat: https://github.com/vincentbernat .. _Matthew Stone: https://github.com/bigmstone .. _Jeremy Schulman: https://github.com/jeremyschulman .. _Ray Solomon: https://github.com/rsolomo .. _Rick Sherman: https://github.com/shermdog .. _subhak186: https://github.com/subhak186 .. _katharh: https://github.com/katharh .. _Francis Luong (Franco): https://github.com/francisluong .. _Juergen Brendel: https://github.com/juergenbrendel .. _Quentin Loos: https://github.com/Kent1 .. _Ebben Aries: https://github.com/earies .. _Justin Wilcox: https://github.com/jwwilcox .. _Stacy W. Smith: https://github.com/stacywsmith .. _Mircea Ulinic: https://github.com/mirceaulinic .. _QijunPan: https://github.com/QijunPan ncclient-0.6.0/docs/000077500000000000000000000000001331651044100142425ustar00rootroot00000000000000ncclient-0.6.0/docs/Makefile000066400000000000000000000060761331651044100157130ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ncclient.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ncclient.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." ncclient-0.6.0/docs/source/000077500000000000000000000000001331651044100155425ustar00rootroot00000000000000ncclient-0.6.0/docs/source/_static/000077500000000000000000000000001331651044100171705ustar00rootroot00000000000000ncclient-0.6.0/docs/source/_static/logo.png000066400000000000000000000120761331651044100206440ustar00rootroot00000000000000PNG  IHDR<q pHYs+IDATxyxT?3Y$$B;E+uCbj[l-R\ZV-Uq}R?˯ZY ( Idf~2 =]f>sL=;|_ \ v/)@j- siiM:IE@! 8ja̢rÐiY싴c*P״r`.0 R% (kmU#@Vece?W?>pzk;[[1_ ]_#=h$63pW;;a+2cI>=_MhK${t +b!1 ,!s"z Mc5yDc<0Fz`p)fRk pg ܒB-OpSmH$'G0o 5d˵\Ʃ" uk5ҋtUeHͶO~fBd I4K(XA^#-:"HJ>D s?;Si/?pWo4RL}^]Yzt{r&? %s ly)&"0=YěWdmo?DA#9Z|`LD9v~2&huq(se|[77ñ*YFsydk,1oy |g5Ǎ['ucXKT~7L( E-U9=7>,vt]lduX?lf0PIK ~_E]{0 EI vW»eT8mHGYTxs߳Vw_7Ղ*lbKÜu||~h${3^B)sKqæig,>5"[liU ;n޿i4ɯ7zL/D'Ѧj,YSH(Zs)N#5 ZM#A)3ϞD~,պz?o[Ǘif#鿎$\+xL1jAש@n#źVqNj;"7Ku;H8ة cDj_5krD."բoK u?.q #k4j-+&+Qk*)2y荰l?eU"܁<;:/9uz?AoKd9D@k @'hy8#_Y:ىi*w$%Fi>D wVW /܎KXB{IHJ G%@,Nd(y DCu,"=!٭JF]{{RQʝvm :`ovbp~T׋VNE6t5غMy {4c=5q5AR#G )ms_>k^UǸh?"3Y϶RQO1j BMWxUOsLVEKɎ,By>OՀS2&Λvf^9T 8T`e!-C3 _~{XU±N 4#VyWE=˔z,mNZZ$V ;V}c]J2cYoTvV԰XNZkRPu,թ՚#qb&e)r[2`t~\ভd!Jqx\mcjH0cm q'XrƵvGձVwDձT2$<KBh=A5_GB#HKUyE)rDžuHBUU?UJ4JE=xq^J: k]MNJ ?' vnB6cņj tQnMI:Vl3VODIG,ۑcFZhr!"-Hp*KOGuZLMeYS0H]LұSܦ^1AzFt,5^CG.8nI::7"!tr1R`a R0q$K-&Gw^p,1"f"Z?2~_દ $ԱY~W<83ڌyxy6r܊EF6i_s hwz1B}.uo4xUIK)\J"o< I2sA<ޤ Εt,X A=ىI:ylNCQưcK)"z Vi5Tkddם/j׉ >A`! t1]+DFMm3Y[r;[sbbLytnz 8 Tߊ\a@72dG*'iG=.}$e[l;Z;i{Rzpn̖"śj mRMmθUVW@k"}MnDn#Ѯo!

{% endblock %} ncclient-0.6.0/docs/source/api.rst000066400000000000000000000002001331651044100170350ustar00rootroot00000000000000Complete API documentation ========================== .. toctree:: capabilities xml_ transport operations ncclient-0.6.0/docs/source/capabilities.rst000066400000000000000000000013201331651044100207210ustar00rootroot00000000000000:mod:`~ncclient.capabilities` -- NETCONF Capabilities ===================================================== .. module:: ncclient.capabilities .. autofunction:: schemes .. autoclass:: Capabilities :members: .. describe:: ":cap" in caps Check for the presence of capability. In addition to the URI, for capabilities of the form `urn:ietf:params:netconf:capability:$name:$version` their shorthand can be used as a key. For example, for `urn:ietf:params:netconf:capability:candidate:1.0` the shorthand would be `:candidate`. If version is significant, use `:candidate:1.0` as key. .. describe:: iter(caps) Return an iterator over the full URI's of capabilities represented by this object.ncclient-0.6.0/docs/source/conf.py000066400000000000000000000150711331651044100170450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # ncclient documentation build configuration file, created by # sphinx-quickstart on Fri Sep 18 17:32:15 2009. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath("../..")) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = u'ncclient' copyright = u'2009, Shikhar Bhushan; 2011-2014, Leonidas Poulopoulos' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.5' # The full version, including alpha/beta/rc tags. release = '0.5.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = 'obj' # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ["ncclient."] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = "_static/logo.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'ncclientdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). latex_paper_size = 'a4' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'ncclient.tex', u'ncclient Documentation', u'Shikhar Bhushan \and Leonidas Poulopoulos', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. latex_logo = "_static/logo.png" # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} autoclass_content = 'both' ncclient-0.6.0/docs/source/extending.rst000066400000000000000000000002031331651044100202540ustar00rootroot00000000000000Extending ncclient ================== **TODO** Here it is discussed how new transport mappings and new operations can be added. ncclient-0.6.0/docs/source/index.rst000066400000000000000000000043411331651044100174050ustar00rootroot00000000000000Welcome ======= `ncclient` is a Python library for NETCONF clients. It aims to offer an intuitive API that sensibly maps the XML-encoded nature of NETCONF to Python constructs and idioms, and make writing network-management scripts easier. Other key features are: * Supports all operations and capabilities defined in :rfc:`4741`. * Request pipelining. * Asynchronous RPC requests. * Keeping XML out of the way unless really needed. * Extensible. New transport mappings and capabilities/operations can be easily added. The best way to introduce is through a simple code example:: from ncclient import manager # use unencrypted keys from ssh-agent or ~/.ssh keys, and rely on known_hosts with manager.connect_ssh("host", username="user") as m: assert(":url" in m.server_capabilities) with m.locked("running"): m.copy_config(source="running", target="file:///new_checkpoint.conf") m.copy_config(source="file:///old_checkpoint.conf", target="running") As of version 0.4 there has been an integration of Juniper's and Cisco's forks. Thus, lots of new concepts have been introduced that ease management of Juniper and Cisco devices respectively. The biggest change is the introduction of device handlers in connection params. For example to invoke Juniper's functions and params one has to re-write the above with **device_params={'name':'junos'}**:: from ncclient import manager with manager.connect(host=host, port=830, username=user, hostkey_verify=False, device_params={'name':'junos'}) as m: c = m.get_config(source='running').data_xml with open("%s.xml" % host, 'w') as f: f.write(c) Respectively, for Cisco Nexus, the name is **nexus**. Device handlers are easy to implement and prove to be futureproof. The latest pull request merge includes support for Huawei devices with name **huawei** in device_params. Supported device handlers ------------------------- * Juniper: device_params={'name':'junos'} * Cisco CSR: device_params={'name':'csr'} * Cisco Nexus: device_params={'name':'nexus'} * Huawei: device_params={'name':'huawei'} Contents: .. toctree:: manager api Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` ncclient-0.6.0/docs/source/manager.rst000066400000000000000000000104361331651044100177120ustar00rootroot00000000000000:mod:`~ncclient.manager` -- High-level API ========================================== .. automodule:: ncclient.manager :synopsis: High-level API Customizing ------------ These attributes control what capabilties are exchanged with the NETCONF server and what operations are available through the :class:`Manager` API. .. autodata:: OPERATIONS Factory functions ----------------- A :class:`Manager` instance is created using a factory function. .. autofunction:: connect_ssh .. autodata:: connect Manager ------- Exposes an API for RPC operations as method calls. The return type of these methods depends on whether we are in :attr:`asynchronous or synchronous mode `. In synchronous mode replies are awaited and the corresponding :class:`~ncclient.operations.RPCReply` object is returned. Depending on the :attr:`exception raising mode `, an `rpc-error` in the reply may be raised as an :exc:`~ncclient.operations.RPCError` exception. However in asynchronous mode, operations return immediately with the corresponding :class:`~ncclient.operations.RPC` object. Error handling and checking for whether a reply has been received must be dealt with manually. See the :class:`~ncclient.operations.RPC` documentation for details. Note that in case of the :meth:`~Manager.get` and :meth:`~Manager.get_config` operations, the reply is an instance of :class:`~ncclient.operations.GetReply` which exposes the additional attributes :attr:`~ncclient.operations.GetReply.data` (as :class:`~xml.etree.ElementTree.Element`) and :attr:`~ncclient.operations.GetReply.data_xml` (as a string), which are of primary interest in case of these operations. Presence of capabilities is verified to the extent possible, and you can expect a :exc:`~ncclient.operations.MissingCapabilityError` if something is amiss. In case of transport-layer errors, e.g. unexpected session close, :exc:`~ncclient.transport.TransportError` will be raised. .. autoclass:: Manager .. automethod:: get_config(source, filter=None) .. automethod:: edit_config(target, config, default_operation=None, test_option=None, error_option=None) .. automethod:: copy_config(source, target) .. automethod:: delete_config(target) .. automethod:: dispatch(rpc_command, source=None, filter=None) .. automethod:: lock(target) .. automethod:: unlock(target) .. automethod:: locked(target) .. automethod:: get() .. automethod:: close_session() .. automethod:: kill_session(session_id) .. automethod:: commit(confirmed=False, timeout=None, persist=None) .. automethod:: cancel_commit(persist_id=None) .. automethod:: discard_changes() .. automethod:: validate(source) .. automethod:: create_subscription() .. automethod:: take_notification(block=True, timeout=None) .. autoattribute:: async_mode .. autoattribute:: timeout .. autoattribute:: raise_mode .. autoattribute:: client_capabilities .. autoattribute:: server_capabilities .. autoattribute:: session_id .. autoattribute:: connected Special kinds of parameters --------------------------- Some parameters can take on different types to keep the interface simple. .. _srctarget_params: Source and target parameters ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Where an method takes a *source* or *target* argument, usually a datastore name or URL is expected. The latter depends on the `:url` capability and on whether the specific URL scheme is supported. Either must be specified as a string. For example, `"running"`, `"ftp://user:pass@host/config"`. If the source may be a `config` element, e.g. as allowed for the `validate` RPC, it can also be specified as an XML string or an :class:`~xml.etree.ElementTree.Element` object. .. _filter_params: Filter parameters ^^^^^^^^^^^^^^^^^ Where a method takes a *filter* argument, it can take on the following types: * A tuple of *(type, criteria)*. Here *type* has to be one of `"xpath"` or `"subtree"`. * For `"xpath"` the *criteria* should be a string containing the XPath expression. * For `"subtree"` the *criteria* should be an XML string or an :class:`~xml.etree.ElementTree.Element` object containing the criteria. * A `` element as an XML string or an :class:`~xml.etree.ElementTree.Element` object. ncclient-0.6.0/docs/source/operations.rst000066400000000000000000000040011331651044100204520ustar00rootroot00000000000000:mod:`~ncclient.operations` -- Everything RPC ============================================= .. module:: ncclient.operations :synopsis: Everything RPC .. autoclass:: RaiseMode :members: NONE, ERRORS, ALL Base classes ------------ .. autoclass:: RPC :members: DEPENDS, REPLY_CLS, _assert, _request, request, event, error, reply, raise_mode, is_async, timeout .. autoclass:: RPCReply :members: xml, ok, error, errors, _parsing_hook .. autoexception:: RPCError :show-inheritance: :members: type, severity, tag, path, message, info Operations ---------- Retrieval .......... .. autoclass:: Get :members: request :show-inheritance: .. autoattribute:: REPLY_CLS .. autoclass:: GetConfig :members: request :show-inheritance: .. autoattribute:: REPLY_CLS .. autoclass:: GetReply :show-inheritance: :members: data, data_ele, data_xml .. autoclass:: Dispatch :members: request :show-inheritance: .. autoattribute:: REPLY_CLS Editing ........ .. autoclass:: EditConfig :members: request :show-inheritance: .. autoclass:: DeleteConfig :members: request :show-inheritance: .. autoclass:: CopyConfig :members: request :show-inheritance: .. autoclass:: Validate :members: request :show-inheritance: .. autoclass:: Commit :members: request :show-inheritance: .. autoclass:: DiscardChanges :members: request :show-inheritance: .. autoclass:: CancelCommit :members: request :show-inheritance: Locking ........ .. autoclass:: Lock :members: request :show-inheritance: .. autoclass:: Unlock :members: request :show-inheritance: Session ........ .. autoclass:: CloseSession :members: request :show-inheritance: .. autoclass:: KillSession :members: request :show-inheritance: Exceptions ---------- .. autoexception:: OperationError :show-inheritance: .. autoexception:: MissingCapabilityError :show-inheritance: .. autoexception:: TimeoutExpiredError :show-inheritance: ncclient-0.6.0/docs/source/transport.rst000066400000000000000000000022071331651044100203310ustar00rootroot00000000000000:mod:`~ncclient.transport` -- Transport / Session layer ======================================================= .. module:: ncclient.transport :synopsis: Transport / Session layer Base types ----------- .. autoclass:: Session :members: add_listener, remove_listener, get_listener_instance, client_capabilities, server_capabilities, connected, id .. autoclass:: SessionListener :members: callback, errback SSH session implementation -------------------------- .. automethod:: ssh.default_unknown_host_cb .. autoclass:: SSHSession :show-inheritance: :members: load_known_hosts, close, transport .. automethod:: connect(host[, port=830, timeout=None, unknown_host_cb=default_unknown_host_cb, username=None, password=None, key_filename=None, allow_agent=True, hostkey_verify=True, look_for_keys=True, ssh_config=None]) Errors ------ .. autoexception:: TransportError :show-inheritance: .. autoexception:: SessionCloseError :show-inheritance: .. autoexception:: SSHError :show-inheritance: .. autoexception:: AuthenticationError :show-inheritance: .. autoexception:: SSHUnknownHostError :show-inheritance: ncclient-0.6.0/docs/source/xml_.rst000066400000000000000000000011331331651044100172310ustar00rootroot00000000000000:mod:`~ncclient.xml_` -- XML handling ===================================== .. automodule:: ncclient.xml_ :synopsis: XML handling .. autoexception:: XMLError :show-inheritance: Namespaces ----------- .. autodata:: BASE_NS_1_0 .. autodata:: TAILF_AAA_1_1 .. autodata:: TAILF_EXECD_1_1 .. autodata:: CISCO_CPI_1_0 .. autodata:: JUNIPER_1_1 .. autodata:: FLOWMON_1_0 .. autofunction:: register_namespace(prefix, uri) .. autofunction:: qualify Conversion ----------- .. autofunction:: to_xml .. autofunction:: to_ele .. autofunction:: parse_root .. autofunction:: validated_element ncclient-0.6.0/examples/000077500000000000000000000000001331651044100151305ustar00rootroot00000000000000ncclient-0.6.0/examples/csr1000v_example.py000066400000000000000000000077641331651044100205110ustar00rootroot00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2013 Cisco Systems, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # @author: Hareesh Puthalath, Cisco Systems, Inc. import sys import logging from ncclient import manager log = logging.getLogger(__name__) # Various IOS Snippets CREATE_VRF = """ ip routing ip vrf %s """ REMOVE_VRF = """ ip routing no ip vrf %s """ CREATE_SUBINTERFACE = """ interface %s encapsulation dot1Q %s ip vrf forwarding %s ip address %s %s """ REMOVE_SUBINTERFACE = """ no interface %s """ def csr_connect(host, port, user, password): return manager.connect(host=host, port=port, username=user, password=password, device_params={'name': "csr"}, timeout=30 ) def create_vrf(conn, vrf_name): try: confstr = CREATE_VRF % vrf_name rpc_obj = conn.edit_config(target='running', config=confstr) _check_response(rpc_obj, "CREATE_VRF") except Exception: log.exception("Exception in creating VRF %s" % vrf_name) def create_subinterface(conn, subinterface, vlan_id, vrf_name, ip, mask): try: confstr = CREATE_SUBINTERFACE % (subinterface, vlan_id, vrf_name, ip, mask) rpc_obj = conn.edit_config(target='running', config=confstr) _check_response(rpc_obj, 'CREATE_SUBINTERFACE') except Exception: log.exception("Exception in creating subinterface %s" % subinterface) def remove_vrf(conn, vrf_name): try: confstr = REMOVE_VRF % vrf_name rpc_obj = conn.edit_config(target='running', config=confstr) _check_response(rpc_obj, "REMOVE_VRF") except Exception: log.exception("Exception in removing VRF %s" % vrf_name) def remove_subinterface(conn, subinterface): try: confstr = REMOVE_SUBINTERFACE % (subinterface) rpc_obj = conn.edit_config(target='running', config=confstr) _check_response(rpc_obj, 'REMOVE_SUBINTERFACE') except Exception: log.exception("Exception in removing subinterface %s" % subinterface) def _check_response(rpc_obj, snippet_name): log.debug("RPCReply for %s is %s" % (snippet_name, rpc_obj.xml)) xml_str = rpc_obj.xml if "" in xml_str: log.info("%s successful" % snippet_name) else: log.error("Cannot successfully execute: %s" % snippet_name) def test_csr(host, user, password): with csr_connect(host, port=22, user=user, password=password) as m: create_vrf(m, "test_vrf") create_subinterface(m, "GigabitEthernet 1.500", '500', 'test_vrf', '192.168.0.1', '255.255.255.0') #Optional remove_vrf(m, "test_vrf") remove_subinterface(m, "GigabitEthernet 1.500") if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) test_csr(sys.argv[1], sys.argv[2], sys.argv[3])ncclient-0.6.0/examples/juniper/000077500000000000000000000000001331651044100166045ustar00rootroot00000000000000ncclient-0.6.0/examples/juniper/command-jnpr.py000077500000000000000000000021601331651044100215450ustar00rootroot00000000000000#!/usr/bin/env python import logging import sys from ncclient import manager def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) logging.info('show system users') logging.info('*' * 30) result = conn.command(command='show system users', format='text') logging.info(result) logging.info('show version') logging.info('*' * 30) result = conn.command('show version', format='text') logging.info(result.xpath('output')[0].text) logging.info('bgp summary') logging.info('*' * 30) result = conn.command('show bgp summary') logging.info(result) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', '22', 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/compare-config-jnpr.py000077500000000000000000000014411331651044100230210ustar00rootroot00000000000000#!/usr/bin/env python import logging from ncclient import manager from ncclient.xml_ import * def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) compare_config_result = conn.compare_configuration(rollback=3) logging.info(compare_config_result) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/delete-config-jnpr.py000077500000000000000000000022631331651044100226400ustar00rootroot00000000000000#!/usr/bin/env python import logging from ncclient import manager from ncclient.xml_ import * def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) template = """ test.slax """ conn.lock() config = to_ele(template) load_config_result = conn.load_configuration(config=config) logging.info(load_config_result) validate_result = conn.validate() logging.info(validate_result) compare_config_result = conn.compare_configuration() logging.info(compare_config_result) conn.commit() conn.unlock() conn.close_session() if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/edit-config-bgp-peer.py000077500000000000000000000041051331651044100230500ustar00rootroot00000000000000#!/usr/bin/env python import logging from ncclient import manager from ncclient.xml_ import * def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) logging.info('locking configuration') lock_result = conn.lock() logging.info(lock_result) peers = { '10.1.1.1': '65001', '10.2.1.1': '65002', '10.3.1.1': '65003', '10.4.1.1': '65004', '10.5.1.1': '65005' } # build configuration element config = new_ele('protocols') config_bgp = sub_ele(config, 'bgp') config_group = sub_ele(config_bgp, 'group') # TODO: unused variable! Is this example broken? config_group_name = sub_ele(config_group, 'name').text = 'NETCONF_GROUP' sub_ele(config_group, 'multipath') sub_ele(config_group, 'local-address').text = '10.0.0.1' for peer in peers: config_neighbor = sub_ele(config_group, 'neighbor') sub_ele(config_neighbor, 'name').text = peer sub_ele(config_neighbor, 'peer-as').text = peers[peer] load_config_result = conn.load_configuration(config=config) logging.info(load_config_result) validate_result = conn.validate() logging.info(validate_result) compare_config_result = conn.compare_configuration() logging.info(compare_config_result) conn.commit() logging.info('committed configuration') discard_changes_result = conn.discard_changes() logging.info(discard_changes_result) logging.info('unlocking configuration') unlock_result = conn.unlock() logging.info(unlock_result) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/edit-config-jnpr-json.py000077500000000000000000000034241331651044100232720ustar00rootroot00000000000000#!/usr/bin/env python import json import logging import sys from ncclient import manager def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) conn.lock() # configuration as a json encoded string # TODO: this example (and possibly the ncclient library implementation?) is broken! # class LoadConfiguration in operations/third_party/juniper/rpc.py would expect 'configuration-json' element to be # used. Changing "configuration" to "configuration-json" in the 2 occasions below: still does not work! location = """ { "configuration": { "system": { "location": { "building": "Main Campus, E", "floor": "15" } } } } """ config_json = json.loads(location) config_json['configuration']['system']['location']['rack'] = "1117" config = json.dumps(config_json) load_config_result = conn.load_configuration(format='json', config=config) logging.info(load_config_result) validate_result = conn.validate() logging.info(validate_result) compare_config_result = conn.compare_configuration() logging.info(compare_config_result) conn.commit() conn.unlock() conn.close_session() if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', '22', 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/edit-config-jnpr-set.py000077500000000000000000000026351331651044100231170ustar00rootroot00000000000000#!/usr/bin/env python import sys import logging from ncclient import manager def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) conn.lock() # configuration as a string load_config_result = conn.load_configuration(action='set', config='set system host-name foo') logging.info(load_config_result) # configuration as a list location = [] location.append('set system location building "Main Campus, C"') location.append('set system location floor 15') location.append('set system location rack 1117') load_config_result = conn.load_configuration(action='set', config=location) logging.info(load_config_result) validate_result = conn.validate() logging.info(validate_result) compare_config_result = conn.compare_configuration() logging.info(compare_config_result) conn.commit() conn.unlock() conn.close_session() if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', '22', 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/edit-config-jnpr-text.py000077500000000000000000000027201331651044100233030ustar00rootroot00000000000000#!/usr/bin/env python import logging import sys from ncclient import manager def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) conn.lock() new_host_name = 'foo-bar' # configuration as a text string location = """ system { location { building "Main Campus, E"; floor 15; rack 1117; } } """ load_config_result = conn.load_configuration(format='text', config=location) logging.info(load_config_result) # configuration as an argument load_config_result = conn.load_configuration(format='text', config=""" system { host-name %s; } """ % new_host_name) logging.info(load_config_result) validate_result = conn.validate() logging.info(validate_result) compare_config_result = conn.compare_configuration() logging.info(compare_config_result) conn.commit() conn.unlock() conn.close_session() if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', '22', 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/edit-config-jnpr.py000077500000000000000000000031751331651044100223260ustar00rootroot00000000000000#!/usr/bin/env python import logging import time from ncclient import manager from ncclient.xml_ import * def connect(host, port, user, password, source): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) logging.info('locking configuration') lock_result = conn.lock() logging.info(lock_result) # build configuration element config = new_ele('system') sub_ele(config, 'host-name').text = 'foo' sub_ele(config, 'domain-name').text = 'bar' load_config_result = conn.load_configuration(config=config) logging.info(load_config_result) validate_result = conn.validate() logging.info(validate_result) compare_config_result = conn.compare_configuration() logging.info(compare_config_result) logging.info('commit confirmed') commit_config = conn.commit(confirmed=True, timeout='300') logging.info(commit_config) logging.info('sleeping for 5 sec...') time.sleep(5) discard_changes_result = conn.discard_changes() logging.info(discard_changes_result) logging.info('unlocking configuration') unlock_result = conn.unlock() logging.info(unlock_result) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!', 'candidate') ncclient-0.6.0/examples/juniper/edit-config-std.py000077500000000000000000000025371331651044100221500ustar00rootroot00000000000000#!/usr/bin/env python import logging import sys from ncclient import manager from ncclient.xml_ import new_ele, sub_ele def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) conn.lock() root = new_ele('config') configuration = sub_ele(root, 'configuration') system = sub_ele(configuration, 'system') location = sub_ele(system, 'location') sub_ele(location, 'building').text = "Main Campus, A" sub_ele(location, 'floor').text = "5" sub_ele(location, 'rack').text = "27" edit_config_result = conn.edit_config(config=root) logging.info(edit_config_result) validate_result = conn.validate() logging.info(validate_result) compare_config_result = conn.compare_configuration() logging.info(compare_config_result) conn.commit() conn.unlock() conn.close_session() if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', '22', 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/edit-config-text-std.py000077500000000000000000000022011331651044100231160ustar00rootroot00000000000000#!/usr/bin/env python import logging import sys from ncclient import manager def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) conn.lock() # configuration as a text string host_name = """ system { host-name foo-bar; } """ edit_config_result = conn.edit_config(format='text', config=host_name) logging.info(edit_config_result) validate_result = conn.validate() logging.info(validate_result) compare_config_result = conn.compare_configuration() logging.info(compare_config_result) conn.commit() conn.unlock() conn.close_session() if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', '22', 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/execute-async-rpc.py000066400000000000000000000024731331651044100225230ustar00rootroot00000000000000import logging from ncclient import manager from ncclient.xml_ import * import time from ncclient.devices.junos import JunosDeviceHandler def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) junos_dev_handler = JunosDeviceHandler( device_params={'name': 'junos', 'local': False}) conn.async_mode = True rpc = new_ele('get-software-information') obj = conn.rpc(rpc) # for demo purposes, we just wait for the result while not obj.event.is_set(): logging.info('waiting for answer ...') time.sleep(.3) result = NCElement(obj.reply, junos_dev_handler.transform_reply() ).remove_namespaces(obj.reply.xml) logging.info('Hostname: %s', result.findtext('.//host-name')) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/execute-rpc-string.py000077500000000000000000000016371331651044100227200ustar00rootroot00000000000000#!/usr/bin/env python import logging from ncclient import manager from ncclient.xml_ import * def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) rpc = """ """ result = conn.rpc(rpc) logging.info('Chassis serial-number: %s', result.xpath('//chassis-inventory/chassis/serial-number')[0].text) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/execute-rpc.py000077500000000000000000000015331331651044100214070ustar00rootroot00000000000000#!/usr/bin/env python import logging from ncclient import manager from ncclient.xml_ import * def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) rpc = new_ele('get-software-information') result = conn.rpc(rpc) logging.info('Hostname: %s', result.xpath('//software-information/host-name')[0].text) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/get-config-jnpr.py000077500000000000000000000043231331651044100221540ustar00rootroot00000000000000#!/usr/bin/env python import logging from ncclient import manager from ncclient.xml_ import * def connect(host, port, user, password, source): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) logging.info('Retrieving full config, please wait ...') result = conn.get_config(source) logging.info('Showing \'system\' hierarchy ...') output = result.xpath('data/configuration/system')[0] logging.info(to_xml(output)) # specify filter to pass to get_config root_filter = new_ele('filter') config_filter = sub_ele(root_filter, 'configuration') system_filter = sub_ele(config_filter, 'system') sub_ele(system_filter, 'services') filtered_result = conn.get_config(source, filter=root_filter) logging.info('Configured Services...') for i in filtered_result.xpath('data/configuration/system/services/*'): logging.info(' %s', i.tag) logging.info('Configured Interfaces...') logging.info('%-15s %-30s' % ('Name', 'Description')) logging.info('-' * 40) interfaces = result.xpath('data/configuration/interfaces/interface') for i in interfaces: if i.tag == 'interface': interface = i.xpath('name')[0].text try: description = i.xpath('description')[0].text except IndexError: description = None logging.info('%-15s %-30s' % (interface, description)) units = i.xpath('unit') for u in units: unit = u.xpath('name')[0].text try: u_desc = u.xpath('description')[0].text except IndexError: u_desc = None logging.info(' %-12s %-30s' % (unit, u_desc)) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!', 'candidate') ncclient-0.6.0/examples/juniper/get-configuration-jnpr.py000077500000000000000000000034511331651044100235570ustar00rootroot00000000000000#!/usr/bin/env python import json import logging from ncclient import manager from ncclient.xml_ import * def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) result_xml = conn.get_configuration(format='xml') logging.info(result_xml) result_json = conn.get_configuration(format='json') payload = json.loads(result_json.xpath('.')[0].text) logging.info(payload) logging.info(payload['configuration'][0]['system'][0]['services']) result_text = conn.get_configuration(format='text') logging.info(result_text.xpath('configuration-text')[0].text) logging.info('Version') logging.info('*' * 30) logging.info(result_xml.xpath('configuration/version')[0].text) config_filter = new_ele('configuration') system_ele = sub_ele(config_filter, 'system') sub_ele(system_ele, 'login') result_filter = conn.get_configuration(format='xml', filter=config_filter) logging.info(result_filter) logging.info('Configured Interfaces...') interfaces = result_xml.xpath('configuration/interfaces/interface') for i in interfaces: interface = i.xpath('name')[0].text ip = [] for name in i.xpath('unit/family/inet/address/name'): ip.append(name.text) logging.info(' %s %s', interface, ip) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/get-configuration-matching.py000077500000000000000000000035061331651044100244010ustar00rootroot00000000000000#!/usr/bin/env python import logging import sys from ncclient import manager def connect(host, port, user, password, source): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) result_xml = conn.get_configuration(format='xml') logging.info("xpath starts-with") ge_configs = result_xml.xpath('configuration/interfaces/interface/name[starts-with(text(), "ge-")]') for i in ge_configs: logging.info("%s %s", i.tag, i.text) logging.info("xpath re:match") ge_configs = result_xml.xpath('configuration/interfaces/interface/name[re:match(text(), "ge")]') for i in ge_configs: logging.info("%s %s", i.tag, i.text) logging.info("xpath contains") ge_configs = result_xml.xpath('configuration/interfaces/interface/name[contains(text(), "ge-")]') for i in ge_configs: logging.info("%s %s", i.tag, i.text) logging.info("xpath match on text") ge_configs = result_xml.xpath('configuration/interfaces/interface/name[text()="ge-0/0/0"]') for i in ge_configs: logging.info("%s %s", i.tag, i.text) logging.info("xpath match on text - alternative (wildcards not permitted)") ge_configs = result_xml.xpath('configuration/interfaces/interface[name="ge-0/0/0"]') for i in ge_configs: logging.info("%s %s", i.xpath('name')[0].tag, i.xpath('name')[0].text) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!', 'candidate') ncclient-0.6.0/examples/juniper/get-interface-status.py000066400000000000000000000022461331651044100232200ustar00rootroot00000000000000#!/usr/bin/env python # Python script to fetch interface name and their operation status import sys import logging from ncclient import manager def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) rpc = "" response = conn.rpc(rpc) interface_name = response.xpath('//physical-interface/name') interface_status = response.xpath('//physical-interface/oper-status') for name, status in zip(interface_name, interface_status): name = name.text.split('\n')[1] status = status.text.split('\n')[1] logging.info("{} - {}".format(name, status)) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/get-inventory.py000077500000000000000000000015751331651044100220030ustar00rootroot00000000000000#!/usr/bin/env python import logging import sys from ncclient import manager def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) result = conn.get_chassis_inventory() logging.info("Chassis: %s", result.xpath('//chassis/description')[0].text) logging.info("Chassis Serial-Number: %s", result.xpath('//chassis/serial-number')[0].text) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/get-session-params.py000077500000000000000000000025511331651044100227050ustar00rootroot00000000000000#!/usr/bin/env python import logging import sys from ncclient import manager from ncclient.transport import errors def connect(host, port, user, password): try: conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) logging.info('connected: %s ... to host %s on port %s', conn.connected, host, port) logging.info('session-id %s:', conn.session_id) logging.info('client capabilities:') for i in conn.client_capabilities: logging.info(' %s', i) logging.info('server capabilities:') for i in conn.server_capabilities: logging.info(' %s', i) conn.close_session() except errors.SSHError: logging.exception('Unable to connect to host: %s on port %s', host, port) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') connect('router', 831, 'netconf', 'juniper!') connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/get-xnm-information.py000077500000000000000000000025611331651044100230670ustar00rootroot00000000000000#!/usr/bin/env python import logging import os from ncclient import manager from ncclient.xml_ import * def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=1800, device_params={'name': 'junos'}, hostkey_verify=False) # https://www.juniper.net/documentation/en_US/junos/topics/task/operational/junos-xml-protocol-requesting-xml-schema.html logging.info("Requesting an XML Schema for the Configuration Hierarchy") logging.info("This may take several (even 10+) minutes") rpc = """ xml-schema junos-configuration """ result = conn.rpc(rpc) with open('schema.txt', 'w') as fh: # Note: using NCElement's __str__() for python version independent conversion to string fh.write(str(result)) logging.info('schema.txt is written to directory %s', os.getcwd()) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/get_l2vpns.py000077500000000000000000000036731331651044100212550ustar00rootroot00000000000000#!/usr/bin/env python import logging from ncclient import manager from ncclient.xml_ import * def connect(host, port, user, password): with manager.connect( host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False ) as conn: vpns = conn.command( 'show l2circuit connections' ).xpath( 'l2circuit-connection-information/l2circuit-neighbor' ) connection_dict = {} for vpn in vpns: neighbor = '' for tag in vpn.getchildren(): connection_details_dict = {} if tag.tag == 'neighbor-address': neighbor = tag.text if tag.tag == 'connection': for child in tag.getchildren(): if child.tag == 'local-interface': ifce = child.find('interface-name').text connection_details_dict.update({'interface': ifce}) else: if child.tag == 'connection-id': cid = child.text else: connection_details_dict.update({child.tag: child.text}) connection_details_dict.update({'neighbor': neighbor}) if connection_dict.get(cid): connection_dict.get(cid).append(connection_details_dict) else: connection_dict.update({cid: [connection_details_dict]}) return connection_dict if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) response = connect('router', 22, 'netconf', 'juniper!') logging.info("Response: %s", response) ncclient-0.6.0/examples/juniper/outbound-ssh-ncclient.py000077500000000000000000000052511331651044100234130ustar00rootroot00000000000000#!/usr/bin/env python """ Listen on TCP port 2200 for incoming SSH session from Junos devices with the following ssh outbound configuration and collect host-name and junos-version upon connect, then terminate lab@router> show configuration system services outbound-ssh client outbound-ssh-ncclient { device-id vRR; services netconf; 10.0.2.2 port 2200; } Example: $ ./outbound-ssh-ncclient.py Listening on port 2200 for incoming sessions ... Got a connection from 172.17.0.1:48038! MSG DEVICE-CONN-INFO V1 vRR Logging in ... requesting info... Hostname: vRR Version: 16.1R3.10 $ """ import logging import socket import sys import time from ncclient import manager from ncclient.xml_ import * def listener(port, user, password): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(('', port)) s.listen(5) logging.info('Listening on port %d for incoming sessions ...', port) while True: client, addr = s.accept() logging.info('Got a connection from %s:%d!', addr[0], addr[1]) launch_junos_proxy(client, addr, user, password) def launch_junos_proxy(client, addr, user, password): val = { 'MSG-ID': None, 'MSG-VER': None, 'DEVICE-ID': None } msg = '' count = 3 while len(msg) < 100 and count > 0: c = client.recv(1) c = c.decode() if c == '\r': continue if c == '\n': count -= 1 if msg.find(':'): (key, value) = msg.split(': ') val[key] = value msg = '' else: msg += c logging.info('MSG %s %s %s', val['MSG-ID'], val['MSG-VER'], val['DEVICE-ID']) logging.info('Logging in ...') sock_fd = client.fileno() conn = manager.connect(host=None, sock_fd=sock_fd, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) rpc = new_ele('get-software-information') logging.info('requesting info...') result = conn.rpc(rpc) logging.info(' Hostname: ' + result.xpath('//software-information/host-name')[0].text) logging.info(' Version: ' + result.xpath('//software-information/junos-version')[0].text) sys.exit(0) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) listener(2200, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/pubkey-auth.py000077500000000000000000000013671331651044100214260ustar00rootroot00000000000000#!/usr/bin/env python import logging import sys from ncclient import manager def connect(host, port, user): conn = manager.connect(host=host, port=port, username=user, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) result = conn.get_software_information('brief', test='me') logging.info('Hostname: %s', result.xpath('software-information/host-name')[0].text) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 22, 'earies') ncclient-0.6.0/examples/juniper/request-reboot.py000077500000000000000000000020521331651044100221400ustar00rootroot00000000000000#!/usr/bin/env python import logging import sys import time from ncclient import manager def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) result = conn.reboot() logging.info(result) reboot_nodes = result.xpath('request-reboot-results/request-reboot-status') if reboot_nodes: reboot_time = result.xpath('request-reboot-results/request-reboot-status/@reboot-time')[0] if 'Shutdown NOW' in reboot_nodes[0].text: logging.info('Rebooted at: %s', time.ctime(int(reboot_time))) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/rollback.py000066400000000000000000000013671331651044100207560ustar00rootroot00000000000000#!/usr/bin/env python import logging import sys from ncclient import manager def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) rollback_config = conn.rollback(rollback=1) logging.info(rollback_config) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/juniper/set-description.py000077500000000000000000000015461331651044100223030ustar00rootroot00000000000000#!/usr/bin/env python import logging import sys from ncclient import manager def connect(host, port, user, password, command): with manager.connect( host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False ) as m: with m.locked('candidate'): result = m.load_configuration(action='set', config=command) logging.info(result) result = m.commit() logging.info(result) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) interface = 'em0' connect('router', 830, 'netconf', 'juniper!', 'set interfaces %s description example' % interface) ncclient-0.6.0/examples/juniper/unknown-rpc.py000077500000000000000000000015351331651044100214460ustar00rootroot00000000000000#!/usr/bin/env python import logging from ncclient import manager from ncclient.xml_ import * def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=60, device_params={'name': 'junos'}, hostkey_verify=False) result = conn.get_software_information('brief', test='me') logging.info(result) result = conn.get_chassis_inventory('extensive') logging.info(result) if __name__ == '__main__': LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) connect('router', 830, 'netconf', 'juniper!') ncclient-0.6.0/examples/nc01.py000066400000000000000000000012701331651044100162430ustar00rootroot00000000000000#! /usr/bin/env python # # Connect to the NETCONF server passed on the command line and # display their capabilities. This script and the following scripts # all assume that the user calling the script is known by the server # and that suitable SSH keys are in place. For brevity and clarity # of the examples, we omit proper exception handling. # # $ ./nc01.py broccoli import sys, os, warnings warnings.simplefilter("ignore", DeprecationWarning) from ncclient import manager def demo(host, user): with manager.connect(host=host, port=22, username=user) as m: for c in m.server_capabilities: print c if __name__ == '__main__': demo(sys.argv[1], os.getenv("USER")) ncclient-0.6.0/examples/nc02.py000066400000000000000000000011221331651044100162400ustar00rootroot00000000000000#! /usr/bin/env python # # Retrieve the running config from the NETCONF server passed on the # command line using get-config and write the XML configs to files. # # $ ./nc02.py broccoli import sys, os, warnings warnings.simplefilter("ignore", DeprecationWarning) from ncclient import manager def demo(host, user): with manager.connect(host=host, port=22, username=user, hostkey_verify=False) as m: c = m.get_config(source='running').data_xml with open("%s.xml" % host, 'w') as f: f.write(c) if __name__ == '__main__': demo(sys.argv[1], os.getenv("USER")) ncclient-0.6.0/examples/nc03.py000066400000000000000000000013721331651044100162500ustar00rootroot00000000000000#! /usr/bin/env python # # Retrieve a portion selected by an XPATH expression from the running # config from the NETCONF server passed on the command line using # get-config and write the XML configs to files. # # $ ./nc03.py broccoli "aaa/authentication/users/user[name='schoenw']" import sys, os, warnings warnings.simplefilter("ignore", DeprecationWarning) from ncclient import manager def demo(host, user, expr): with manager.connect(host=host, port=22, username=user) as m: assert(":xpath" in m.server_capabilities) c = m.get_config(source='running', filter=('xpath', expr)).data_xml with open("%s.xml" % host, 'w') as f: f.write(c) if __name__ == '__main__': demo(sys.argv[1], os.getenv("USER"), sys.argv[2]) ncclient-0.6.0/examples/nc04.py000066400000000000000000000020201331651044100162400ustar00rootroot00000000000000#! /usr/bin/env python # # Create a new user to the running configuration using edit-config # and the test-option provided by the :validate capability. # # $ ./nc04.py broccoli bob 42 42 import sys, os, warnings warnings.simplefilter("ignore", DeprecationWarning) from ncclient import manager def demo(host, user, name, uid, gid): snippet = """ %s %s %s * """ % (name, uid, gid) with manager.connect(host=host, port=22, username=user) as m: assert(":validate" in m.server_capabilities) m.edit_config(target='running', config=snippet, test_option='test-then-set') if __name__ == '__main__': demo(sys.argv[1], os.getenv("USER"), sys.argv[2], sys.argv[3], sys.argv[4]) ncclient-0.6.0/examples/nc05.py000066400000000000000000000016201331651044100162460ustar00rootroot00000000000000#! /usr/bin/env python # # Delete an existing user from the running configuration using # edit-config and the test-option provided by the :validate # capability. # # $ ./nc05.py broccoli bob import sys, os, warnings warnings.simplefilter("ignore", DeprecationWarning) from ncclient import manager def demo(host, user, name): snippet = """ %s """ % name with manager.connect(host=host, port=22, username=user) as m: assert(":validate" in m.server_capabilities) m.edit_config(target='running', config=snippet, test_option='test-then-set') if __name__ == '__main__': demo(sys.argv[1], os.getenv("USER"), sys.argv[2]) ncclient-0.6.0/examples/nc06.py000066400000000000000000000015251331651044100162530ustar00rootroot00000000000000#! /usr/bin/env python # # Delete a list of existing users from the running configuration using # edit-config; protect the transaction using a lock. # # $ ./nc06.py broccoli bob alice import sys, os, warnings warnings.simplefilter("ignore", DeprecationWarning) from ncclient import manager template = """ %s """ def demo(host, user, names): with manager.connect(host=host, port=22, username=user) as m: with m.locked(target='running'): for n in names: m.edit_config(target='running', config=template % n) if __name__ == '__main__': demo(sys.argv[1], os.getenv("USER"), sys.argv[2:]) ncclient-0.6.0/examples/nc07.py000066400000000000000000000017201331651044100162510ustar00rootroot00000000000000#! /usr/bin/env python # # Delete a list of existing users from the running configuration using # edit-config and the candidate datastore protected by a lock. # # $ ./nc07.py broccoli bob alice import sys, os, warnings warnings.simplefilter("ignore", DeprecationWarning) from ncclient import manager template = """ %s """ def demo(host, user, names): with manager.connect(host=host, port=22, username=user) as m: assert(":candidate" in m.server_capabilities) with m.locked(target='candidate'): m.discard_changes() for n in names: m.edit_config(target='candidate', config=template % n) m.commit() if __name__ == '__main__': demo(sys.argv[1], os.getenv("USER"), sys.argv[2:]) ncclient-0.6.0/examples/nc08.py000066400000000000000000000026331331651044100162560ustar00rootroot00000000000000#! /usr/bin/env python # # Configure an Interface: its description and make it active. # XML payload created with lxml/etree instead of a template # # $ ./nc08.py Paulo Seguel import sys, os, warnings warnings.simplefilter("ignore", DeprecationWarning) import datetime from ncclient import manager from lxml import etree current_time = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S') # build xml config_e = etree.Element("config") configuration = etree.SubElement(config_e, "interface-configurations", nsmap = {None: 'http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg'}) interface_cfg = etree.SubElement(configuration, "interface-configuration") active = etree.SubElement(interface_cfg, "active").text = 'act' interface_name = etree.SubElement(interface_cfg, "interface-name").text = 'GigabitEthernet0/0/0/0' description = etree.SubElement(interface_cfg, "description").text = 'NETCONF configured - ' + current_time def demo(host, user, password): with manager.connect(host=host, port=830, username=user, password=password, hostkey_verify=False, device_params={'name':'default'}, look_for_keys=False, allow_agent=False) as m: with m.locked(target="candidate"): m.edit_config(config=config_e, default_operation="merge", target="candidate") m.commit() if __name__ == '__main__': demo(sys.argv[1], sys.argv[2], sys.argv[3]) ncclient-0.6.0/examples/nxosapi.py000066400000000000000000000107601331651044100171670ustar00rootroot00000000000000#! /usr/bin/env python # # Connect to the NETCONF server passed on the command line and # display their capabilities. This script and the following scripts # all assume that the user calling the script is known by the server # and that suitable SSH keys are in place. For brevity and clarity # of the examples, we omit proper exception handling. # # $ ./nc01.py broccoli import sys, os, warnings warnings.simplefilter("ignore", DeprecationWarning) from ncclient import manager exec_conf_prefix = """ <__XML__MODE__exec_configure> """ exec_conf_postfix = """ """ cmd_vlan_conf_snippet= """ <__XML__PARAM_value>%s <__XML__MODE_vlan> %s active """ cmd_vlan_int_snippet = """ %s <__XML__MODE_if-ethernet-switch> <__XML__BLK_Cmd_switchport_trunk_allowed_allow-vlans> %s """ cmd_no_vlan_int_snippet = """ <__XML__MODE__exec_configure> %s <__XML__MODE_if-ethernet-switch> <__XML__BLK_Cmd_switchport_trunk_allowed_allow-vlans> %s """ filter_show_vlan_brief_snippet = """ """ def nxos_connect(host, port, user, password): return manager.connect(host=host, port=port, username=user, password=password, device_params={'name': 'nexus'}) def enable_vlan(mgr, vlanid, vlanname): confstr = cmd_vlan_conf_snippet % (vlanid, vlanname) confstr = exec_conf_prefix + confstr + exec_conf_postfix mgr.edit_config(target='running', config=confstr) def enable_vlan_on_trunk_int(mgr, interface, vlanid): confstr = cmd_vlan_int_snippet % (interface, vlanid) confstr = exec_conf_prefix + confstr + exec_conf_postfix print confstr mgr.edit_config(target='running', config=confstr) def disable_vlan_on_trunk_int(mgr, interface, vlanid): confstr = cmd_no_vlan_int_snippet % (interface, vlanid) print confstr mgr.edit_config(target='running', config=confstr) def test_nxos_api(host, user, password): with nxos_connect(host, port=22, user=user, password=password) as m: enable_vlan(m, '100', 'customer') enable_vlan_on_trunk_int(m, '2/1', '100') disable_vlan_on_trunk_int(m, '2/1', '100') result = m.get(("subtree", filter_show_vlan_brief_snippet)) # print result if __name__ == '__main__': test_nxos_api(sys.argv[1], sys.argv[2], sys.argv[3]) ncclient-0.6.0/ncclient/000077500000000000000000000000001331651044100151115ustar00rootroot00000000000000ncclient-0.6.0/ncclient/__init__.py000066400000000000000000000014261331651044100172250ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. __version__ = (0,5,3) import sys if sys.version_info < (2, 7): raise RuntimeError('You need Python 2.7+ for this module.') class NCClientError(Exception): "Base type for all NCClient errors" pass ncclient-0.6.0/ncclient/capabilities.py000066400000000000000000000046001331651044100201140ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys import six def _abbreviate(uri): if uri.startswith("urn:ietf:params") and ":netconf:" in uri: splitted = uri.split(":") if ":capability:" in uri: if uri.startswith("urn:ietf:params:xml:ns:netconf"): name, version = splitted[7], splitted[8] else: name, version = splitted[5], splitted[6] return [ ":" + name, ":" + name + ":" + version ] elif ":base:" in uri: if uri.startswith("urn:ietf:params:xml:ns:netconf"): return [ ":base", ":base" + ":" + splitted[7] ] else: return [ ":base", ":base" + ":" + splitted[5] ] return [] def schemes(url_uri): "Given a URI that has a *scheme* query string (i.e. `:url` capability URI), will return a list of supported schemes." return url_uri.partition("?scheme=")[2].split(",") class Capabilities(object): "Represents the set of capabilities available to a NETCONF client or server. It is initialized with a list of capability URI's." def __init__(self, capabilities): self._dict = {} for uri in capabilities: self._dict[uri] = _abbreviate(uri) def __contains__(self, key): if key in self._dict: return True for abbrs in six.itervalues(self._dict): if key in abbrs: return True return False def __len__(self): return len(self._dict) # python 2 and 3 compatible def __iter__(self): return six.iterkeys(self._dict) def __repr__(self): return repr(six.iterkeys(self._dict)) def add(self, uri): "Add a capability." self._dict[uri] = _abbreviate(uri) def remove(self, uri): "Remove a capability." if uri in self._dict: del self._dict[uri] ncclient-0.6.0/ncclient/debug.py000066400000000000000000000015771331651044100165630ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from ncclient.transport import SessionListener class PrintListener(SessionListener): def callback(self, root, raw): print('\n# RECEIVED MESSAGE with root=[tag=%r, attrs=%r] #\n%r\n' % (root[0], root[1], raw)) def errback(self, err): print('\n# RECEIVED ERROR #\n%r\n' % err) ncclient-0.6.0/ncclient/devices/000077500000000000000000000000001331651044100165335ustar00rootroot00000000000000ncclient-0.6.0/ncclient/devices/__init__.py000066400000000000000000000000001331651044100206320ustar00rootroot00000000000000ncclient-0.6.0/ncclient/devices/alu.py000066400000000000000000000006001331651044100176620ustar00rootroot00000000000000from .default import DefaultDeviceHandler class AluDeviceHandler(DefaultDeviceHandler): """ Alcatel-Lucent 7x50 handler for device specific information. """ def __init__(self, device_params): super(AluDeviceHandler, self).__init__(device_params) def get_capabilities(self): return [ "urn:ietf:params:netconf:base:1.0", ] ncclient-0.6.0/ncclient/devices/csr.py000066400000000000000000000017141331651044100176770ustar00rootroot00000000000000""" Handler for Cisco CSR device specific information. Note that for proper import, the classname has to be: "DeviceHandler" ...where is something like "Default", "Nexus", etc. All device-specific handlers derive from the DefaultDeviceHandler, which implements the generic information needed for interaction with a Netconf server. """ from .default import DefaultDeviceHandler def csr_unknown_host_cb(host, fingerprint): #This will ignore the unknown host check when connecting to CSR devices return True class CsrDeviceHandler(DefaultDeviceHandler): """ Cisco CSR handler for device specific information. """ def __init__(self, device_params): super(CsrDeviceHandler, self).__init__(device_params) def add_additional_ssh_connect_params(self, kwargs): kwargs['allow_agent'] = False kwargs['look_for_keys'] = False kwargs['unknown_host_cb'] = csr_unknown_host_cb ncclient-0.6.0/ncclient/devices/default.py000066400000000000000000000176251331651044100205440ustar00rootroot00000000000000""" Handler for default device information. Some devices require very specific information and action during client interaction. The "device handlers" provide a number of callbacks that return the necessary information. This allows the ncclient code to merely call upon this device handler - once configured - instead of cluttering its code with if-statements. Initially, not much is dealt with by the handler. However, in the future, as more devices with specific handling are added, more handlers and more functions should be implememted here, so that the ncclient code can use these callbacks to fill in the device specific information. Note that for proper import, the classname has to be: "DeviceHandler" ...where is something like "Default", "Nexus", etc. All device-specific handlers derive from the DefaultDeviceHandler, which implements the generic information needed for interaction with a Netconf server. """ import sys if sys.version >= '3': xrange = range class DefaultDeviceHandler(object): """ Default handler for device specific information. """ # Define the exempt error messages (those that shouldn't cause an exception). # Wild cards are possible: Start and/or end with a '*' to indicate that the text # can appear at the start, the end or the middle of the error message to still # match. All comparisons are case insensitive. _EXEMPT_ERRORS = [] def __init__(self, device_params=None): self.device_params = device_params # Turn all exempt errors into lower case, since we don't want those comparisons # to be case sensitive later on. Sort them into exact match, wildcard start, # wildcard end, and full wildcard categories, depending on whether they start # and/or end with a '*'. self._exempt_errors_exact_match = [] self._exempt_errors_startwith_wildcard_match = [] self._exempt_errors_endwith_wildcard_match = [] self._exempt_errors_full_wildcard_match = [] for i in xrange(len(self._EXEMPT_ERRORS)): e = self._EXEMPT_ERRORS[i].lower() if e.startswith("*"): if e.endswith("*"): self._exempt_errors_full_wildcard_match.append(e[1:-1]) else: self._exempt_errors_startwith_wildcard_match.append(e[1:]) elif e.endswith("*"): self._exempt_errors_endwith_wildcard_match.append(e[:-1]) else: self._exempt_errors_exact_match.append(e) def add_additional_ssh_connect_params(self, kwargs): """ Add device specific parameters for the SSH connect. Pass in the keyword-argument dictionary for the SSH connect call. The dictionary will be modified (!) with the additional device-specific parameters. """ pass def get_capabilities(self): """ Return the capability list. A list of URI's representing the client's capabilities. This is used during the initial capability exchange. Modify (in a new device-handler subclass) as needed. """ return [ "urn:ietf:params:netconf:base:1.0", "urn:ietf:params:netconf:base:1.1", "urn:ietf:params:netconf:capability:writable-running:1.0", "urn:ietf:params:netconf:capability:candidate:1.0", "urn:ietf:params:netconf:capability:confirmed-commit:1.0", "urn:ietf:params:netconf:capability:rollback-on-error:1.0", "urn:ietf:params:netconf:capability:startup:1.0", "urn:ietf:params:netconf:capability:url:1.0?scheme=http,ftp,file,https,sftp", "urn:ietf:params:netconf:capability:validate:1.0", "urn:ietf:params:netconf:capability:xpath:1.0", "urn:ietf:params:netconf:capability:notification:1.0", "urn:liberouter:params:netconf:capability:power-control:1.0", "urn:ietf:params:netconf:capability:interleave:1.0", "urn:ietf:params:netconf:capability:with-defaults:1.0" ] def get_xml_base_namespace_dict(self): """ A dictionary containing the base namespace. For lxml's nsmap, the base namespace should have a 'None' key. { None: "... base namespace... " } If no base namespace is needed, an empty dictionary should be returned. """ return {} def get_xml_extra_prefix_kwargs(self): """ Return any extra prefix that should be sent with each RPC request. Since these are used as kwargs, the function should return either an empty dictionary if there are no additional arguments, or a dictionary with keyword parameters suitable fo the Element() function. Mostly, this is the "nsmap" argument. { "nsmap" : { ... namespace definitions ... } } """ return {} def get_ssh_subsystem_names(self): """ Return a list of names to try for the SSH subsystems. This always returns a list, even if only a single subsystem name is used. If the returned list contains multiple names then the various subsystems are tried in order, until one of them can successfully connect. """ return [ "netconf" ] def is_rpc_error_exempt(self, error_text): """ Check whether an RPC error message is excempt, thus NOT causing an exception. On some devices the RPC operations may indicate an error response, even though the operation actually succeeded. This may be in cases where a warning would be more appropriate. In that case, the client may be better advised to simply ignore that error and not raise an exception. Note that there is also the "raise_mode", set on session and manager, which controls the exception-raising behaviour in case of returned errors. This error filter here is independent of that: No matter what the raise_mode says, if the error message matches one of the exempt errors returned here, an exception will not be raised. The exempt error messages are defined in the _EXEMPT_ERRORS field of the device handler object and can be overwritten by child classes. Wild cards are possible: Start and/or end with a '*' to indicate that the text can appear at the start, the end or the middle of the error message to still match. All comparisons are case insensitive. Return True/False depending on found match. """ if error_text is not None: error_text = error_text.lower().strip() else: error_text = 'no error given' # Compare the error text against all the exempt errors. for ex in self._exempt_errors_exact_match: if error_text == ex: return True for ex in self._exempt_errors_startwith_wildcard_match: if error_text.endswith(ex): return True for ex in self._exempt_errors_endwith_wildcard_match: if error_text.startswith(ex): return True for ex in self._exempt_errors_full_wildcard_match: if ex in error_text: return True return False def perform_qualify_check(self): """ During RPC operations, we perform some initial sanity checks on the responses. This check will fail for some devices, in which case this function here should return False in order to skip the test. """ return True def add_additional_operations(self): """ Add device/vendor specific operations. """ return {} def handle_raw_dispatch(self, raw): return False def handle_connection_exceptions(self, sshsession): return False def transform_reply(self): return False ncclient-0.6.0/ncclient/devices/h3c.py000066400000000000000000000036201331651044100175630ustar00rootroot00000000000000""" Handler for Huawei device specific information. Note that for proper import, the classname has to be: "DeviceHandler" ...where is something like "Default", "Huawei", etc. All device-specific handlers derive from the DefaultDeviceHandler, which implements the generic information needed for interaction with a Netconf server. """ from ncclient.xml_ import BASE_NS_1_0 from .default import DefaultDeviceHandler from ncclient.operations.third_party.h3c.rpc import * class H3cDeviceHandler(DefaultDeviceHandler): """ H3C handler for device specific information. In the device_params dictionary, which is passed to __init__, you can specify the parameter "ssh_subsystem_name". That allows you to configure the preferred SSH subsystem name that should be tried on your H3C switch. If connecting with that name fails, or you didn't specify that name, the other known subsystem names will be tried. However, if you specify it then this name will be tried first. """ _EXEMPT_ERRORS = [] def __init__(self, device_params): super(H3cDeviceHandler, self).__init__(device_params) def add_additional_operations(self): dict = {} dict["get_bulk"] = GetBulk dict["get_bulk_config"] = GetBulkConfig dict["cli"] = CLI dict["action"] = Action dict["save"] = Save dict["load"] = Load dict["rollback"] = Rollback return dict def get_capabilities(self): # Just need to replace a single value in the default capabilities c = super(H3cDeviceHandler, self).get_capabilities() return c def get_xml_base_namespace_dict(self): return {None: BASE_NS_1_0} def get_xml_extra_prefix_kwargs(self): d = {} d.update(self.get_xml_base_namespace_dict()) return {"nsmap": d} def perform_qualify_check(self): return False ncclient-0.6.0/ncclient/devices/hpcomware.py000066400000000000000000000024531331651044100210760ustar00rootroot00000000000000from ncclient.xml_ import BASE_NS_1_0 from .default import DefaultDeviceHandler from ncclient.operations.third_party.hpcomware.rpc import DisplayCommand, ConfigCommand, Action, Rollback, Save class HpcomwareDeviceHandler(DefaultDeviceHandler): def __init__(self, device_params): super(HpcomwareDeviceHandler, self).__init__(device_params) def get_xml_base_namespace_dict(self): """ Base namespace needs a None key. See 'nsmap' argument for lxml's Element(). """ return {None: BASE_NS_1_0} def get_xml_extra_prefix_kwargs(self): """ Return keyword arguments per request, which are applied to Element(). Mostly, this is a dictionary containing the "nsmap" key. See 'nsmap' argument for lxml's Element(). """ d = { "data": "http://www.hp.com/netconf/data:1.0", "config": "http://www.hp.com/netconf/config:1.0", } d.update(self.get_xml_base_namespace_dict()) return {"nsmap": d} def add_additional_operations(self): addtl = {} addtl['cli_display'] = DisplayCommand addtl['cli_config'] = ConfigCommand addtl['action'] = Action addtl['rollback'] = Rollback addtl['save'] = Save return addtl ncclient-0.6.0/ncclient/devices/huawei.py000066400000000000000000000042731331651044100203750ustar00rootroot00000000000000""" Handler for Huawei device specific information. Note that for proper import, the classname has to be: "DeviceHandler" ...where is something like "Default", "Huawei", etc. All device-specific handlers derive from the DefaultDeviceHandler, which implements the generic information needed for interaction with a Netconf server. """ from ncclient.operations.third_party.huawei.rpc import * from ncclient.xml_ import BASE_NS_1_0 from .default import DefaultDeviceHandler class HuaweiDeviceHandler(DefaultDeviceHandler): """ Huawei handler for device specific information. In the device_params dictionary, which is passed to __init__, you can specify the parameter "ssh_subsystem_name". That allows you to configure the preferred SSH subsystem name that should be tried on your Huawei switch. If connecting with that name fails, or you didn't specify that name, the other known subsystem names will be tried. However, if you specify it then this name will be tried first. """ _EXEMPT_ERRORS = [] def __init__(self, device_params): super(HuaweiDeviceHandler, self).__init__(device_params) def add_additional_operations(self): dict = {} dict["cli"] = CLI dict["action"] = Action return dict def handle_raw_dispatch(self, raw): return raw.strip('\0') def get_capabilities(self): # Just need to replace a single value in the default capabilities c = super(HuaweiDeviceHandler, self).get_capabilities() c.append('http://www.huawei.com/netconf/capability/execute-cli/1.0') c.append('http://www.huawei.com/netconf/capability/action/1.0') c.append('http://www.huawei.com/netconf/capability/active/1.0') c.append('http://www.huawei.com/netconf/capability/discard-commit/1.0') c.append('http://www.huawei.com/netconf/capability/exchange/1.0') return c def get_xml_base_namespace_dict(self): return {None: BASE_NS_1_0} def get_xml_extra_prefix_kwargs(self): d = {} d.update(self.get_xml_base_namespace_dict()) return {"nsmap": d} def perform_qualify_check(self): return False ncclient-0.6.0/ncclient/devices/iosxe.py000066400000000000000000000022351331651044100202360ustar00rootroot00000000000000""" Handler for Cisco IOS-XE device specific information. Note that for proper import, the classname has to be: "DeviceHandler" ...where is something like "Default", "Nexus", etc. All device-specific handlers derive from the DefaultDeviceHandler, which implements the generic information needed for interaction with a Netconf server. """ from .default import DefaultDeviceHandler from ncclient.operations.third_party.iosxe.rpc import SaveConfig def iosxe_unknown_host_cb(host, fingerprint): #This will ignore the unknown host check when connecting to CSR devices return True class IosxeDeviceHandler(DefaultDeviceHandler): """ Cisco IOS-XE handler for device specific information. """ def __init__(self, device_params): super(IosxeDeviceHandler, self).__init__(device_params) def add_additional_operations(self): dict = {} dict["save_config"] = SaveConfig return dict def add_additional_ssh_connect_params(self, kwargs): kwargs['allow_agent'] = False kwargs['look_for_keys'] = False kwargs['unknown_host_cb'] = iosxe_unknown_host_cb ncclient-0.6.0/ncclient/devices/iosxr.py000066400000000000000000000021011331651044100202430ustar00rootroot00000000000000""" Handler for Cisco IOS-XR device specific information. Note that for proper import, the classname has to be: "DeviceHandler" ...where is something like "Default", "Nexus", etc. All device-specific handlers derive from the DefaultDeviceHandler, which implements the generic information needed for interaction with a Netconf server. """ from .default import DefaultDeviceHandler def iosxr_unknown_host_cb(host, fingerprint): #This will ignore the unknown host check when connecting to IOS-XR devices return True class IosxrDeviceHandler(DefaultDeviceHandler): """ Cisco IOS-XR handler for device specific information. """ def __init__(self, device_params): super(IosxrDeviceHandler, self).__init__(device_params) def add_additional_ssh_connect_params(self, kwargs): kwargs['allow_agent'] = False kwargs['look_for_keys'] = False kwargs['hostkey_verify'] = False kwargs['unknown_host_cb'] = iosxr_unknown_host_cb def perform_qualify_check(self): return False ncclient-0.6.0/ncclient/devices/junos.py000066400000000000000000000100011331651044100202330ustar00rootroot00000000000000""" Handler for Juniper device specific information. Note that for proper import, the classname has to be: "DeviceHandler" ...where is something like "Default", "Junos", etc. All device-specific handlers derive from the DefaultDeviceHandler, which implements the generic information needed for interaction with a Netconf server. """ import re from lxml import etree from .default import DefaultDeviceHandler from ncclient.operations.third_party.juniper.rpc import GetConfiguration, LoadConfiguration, CompareConfiguration from ncclient.operations.third_party.juniper.rpc import ExecuteRpc, Command, Reboot, Halt, Commit, Rollback from ncclient.operations.rpc import RPCError from ncclient.xml_ import to_ele class JunosDeviceHandler(DefaultDeviceHandler): """ Juniper handler for device specific information. """ def __init__(self, device_params): super(JunosDeviceHandler, self).__init__(device_params) def add_additional_operations(self): dict = {} dict["rpc"] = ExecuteRpc dict["get_configuration"] = GetConfiguration dict["load_configuration"] = LoadConfiguration dict["compare_configuration"] = CompareConfiguration dict["command"] = Command dict["reboot"] = Reboot dict["halt"] = Halt dict["commit"] = Commit dict["rollback"] = Rollback return dict def perform_qualify_check(self): return False def handle_raw_dispatch(self, raw): if 'routing-engine' in raw: raw = re.sub(r'', '\n', raw) return raw # check if error is during capabilites exchange itself elif re.search('\.*?\.*\?', raw, re.M|re.S): errs = re.findall('\.*?\', raw, re.M|re.S) err_list = [] if errs: add_ns = """ """ for err in errs: doc = etree.ElementTree(etree.XML(err)) # Adding namespace using xslt xslt = etree.XSLT(etree.XML(add_ns)) transformed_xml = etree.XML(etree.tostring(xslt(doc))) err_list.append(RPCError(transformed_xml)) return RPCError(to_ele(''.join(errs)), err_list) else: return False def handle_connection_exceptions(self, sshsession): c = sshsession._channel = sshsession._transport.open_channel(kind="session") c.set_name("netconf-command-" + str(sshsession._channel_id)) c.exec_command("xml-mode netconf need-trailer") return True def transform_reply(self): reply = ''' ''' import sys if sys.version < '3': return reply else: return reply.encode('UTF-8') ncclient-0.6.0/ncclient/devices/nexus.py000066400000000000000000000064621331651044100202570ustar00rootroot00000000000000""" Handler for Cisco Nexus device specific information. Note that for proper import, the classname has to be: "DeviceHandler" ...where is something like "Default", "Nexus", etc. All device-specific handlers derive from the DefaultDeviceHandler, which implements the generic information needed for interaction with a Netconf server. """ from ncclient.xml_ import BASE_NS_1_0 from ncclient.operations.third_party.nexus.rpc import ExecCommand from .default import DefaultDeviceHandler class NexusDeviceHandler(DefaultDeviceHandler): """ Cisco Nexus handler for device specific information. In the device_params dictionary, which is passed to __init__, you can specify the parameter "ssh_subsystem_name". That allows you to configure the preferred SSH subsystem name that should be tried on your Nexus switch. If connecting with that name fails, or you didn't specify that name, the other known subsystem names will be tried. However, if you specify it then this name will be tried first. """ _EXEMPT_ERRORS = [ "*VLAN with the same name exists*", # returned even if VLAN was created, but # name was already in use (switch will # automatically choose different, unique # name for VLAN) ] def __init__(self, device_params): super(NexusDeviceHandler, self).__init__(device_params) def add_additional_operations(self): dict = {} dict['exec_command'] = ExecCommand return dict def get_capabilities(self): # Just need to replace a single value in the default capabilities c = super(NexusDeviceHandler, self).get_capabilities() c[0] = "urn:ietf:params:xml:ns:netconf:base:1.0" return c def get_xml_base_namespace_dict(self): """ Base namespace needs a None key. See 'nsmap' argument for lxml's Element(). """ return { None : BASE_NS_1_0 } def get_xml_extra_prefix_kwargs(self): """ Return keyword arguments per request, which are applied to Element(). Mostly, this is a dictionary containing the "nsmap" key. See 'nsmap' argument for lxml's Element(). """ d = { "nxos":"http://www.cisco.com/nxos:1.0", "if":"http://www.cisco.com/nxos:1.0:if_manager", "nfcli": "http://www.cisco.com/nxos:1.0:nfcli", "vlan_mgr_cli": "http://www.cisco.com/nxos:1.0:vlan_mgr_cli" } d.update(self.get_xml_base_namespace_dict()) return { "nsmap" : d } def get_ssh_subsystem_names(self): """ Return a list of possible SSH subsystem names. Different NXOS versions use different SSH subsystem names for netconf. Therefore, we return a list so that several can be tried, if necessary. The Nexus device handler also accepts """ preferred_ssh_subsystem = self.device_params.get("ssh_subsystem_name") name_list = [ "netconf", "xmlagent" ] if preferred_ssh_subsystem: return [ preferred_ssh_subsystem ] + \ [ n for n in name_list if n != preferred_ssh_subsystem ] else: return name_list ncclient-0.6.0/ncclient/manager.py000066400000000000000000000270361331651044100171050ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # Copyright 2011 Leonidas Poulopoulos # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ This module is a thin layer of abstraction around the library. It exposes all core functionality. """ from ncclient import capabilities from ncclient import operations from ncclient import transport import six import logging from ncclient.xml_ import * logger = logging.getLogger('ncclient.manager') OPERATIONS = { "get": operations.Get, "get_config": operations.GetConfig, "get_schema": operations.GetSchema, "dispatch": operations.Dispatch, "edit_config": operations.EditConfig, "copy_config": operations.CopyConfig, "validate": operations.Validate, "commit": operations.Commit, "discard_changes": operations.DiscardChanges, "cancel_commit": operations.CancelCommit, "delete_config": operations.DeleteConfig, "lock": operations.Lock, "unlock": operations.Unlock, "create_subscription": operations.CreateSubscription, "close_session": operations.CloseSession, "kill_session": operations.KillSession, "poweroff_machine": operations.PoweroffMachine, "reboot_machine": operations.RebootMachine, } """ Dictionary of base method names and corresponding :class:`~ncclient.operations.RPC` subclasses. It is used to lookup operations, e.g. `get_config` is mapped to :class:`~ncclient.operations.GetConfig`. It is thus possible to add additional operations to the :class:`Manager` API. """ VENDOR_OPERATIONS = {} def make_device_handler(device_params): """ Create a device handler object that provides device specific parameters and functions, which are called in various places throughout our code. If no device_params are defined or the "name" in the parameter dict is not known then a default handler will be returned. """ if device_params is None: device_params = {} handler = device_params.get('handler', None) if handler: return handler(device_params) device_name = device_params.get("name", "default") # Attempt to import device handler class. All device handlers are # in a module called "ncclient.devices." and in a class named # "DeviceHandler", with the first letter capitalized. class_name = "%sDeviceHandler" % device_name.capitalize() devices_module_name = "ncclient.devices.%s" % device_name dev_module_obj = __import__(devices_module_name) handler_module_obj = getattr(getattr(dev_module_obj, "devices"), device_name) class_obj = getattr(handler_module_obj, class_name) handler_obj = class_obj(device_params) return handler_obj def connect_ssh(*args, **kwds): """ Initialize a :class:`Manager` over the SSH transport. For documentation of arguments see :meth:`ncclient.transport.SSHSession.connect`. The underlying :class:`ncclient.transport.SSHSession` is created with :data:`CAPABILITIES`. It is first instructed to :meth:`~ncclient.transport.SSHSession.load_known_hosts` and then all the provided arguments are passed directly to its implementation of :meth:`~ncclient.transport.SSHSession.connect`. To invoke advanced vendor related operation add device_params = {'name':''} in connection paramerers. For the time, 'junos' and 'nexus' are supported for Juniper and Cisco Nexus respectively. A custom device handler can be provided with device_params = {'handler':} in connection paramerers. """ # Extract device parameter dict, if it was passed into this function. Need to # remove it from kwds, since the session.connect() doesn't like extra stuff in # there. if "device_params" in kwds: device_params = kwds["device_params"] del kwds["device_params"] else: device_params = None device_handler = make_device_handler(device_params) device_handler.add_additional_ssh_connect_params(kwds) global VENDOR_OPERATIONS VENDOR_OPERATIONS.update(device_handler.add_additional_operations()) session = transport.SSHSession(device_handler) if "hostkey_verify" not in kwds or kwds["hostkey_verify"]: session.load_known_hosts() try: session.connect(*args, **kwds) except Exception as ex: if session.transport: session.close() raise return Manager(session, device_handler, **kwds) def connect_ioproc(*args, **kwds): if "device_params" in kwds: device_params = kwds["device_params"] del kwds["device_params"] import_string = 'ncclient.transport.third_party.' import_string += device_params['name'] + '.ioproc' third_party_import = __import__(import_string, fromlist=['IOProc']) else: device_params = None device_handler = make_device_handler(device_params) global VENDOR_OPERATIONS VENDOR_OPERATIONS.update(device_handler.add_additional_operations()) session = third_party_import.IOProc(device_handler) session.connect() return Manager(session, device_handler, **kwds) def connect(*args, **kwds): if "host" in kwds: host = kwds["host"] device_params = kwds.get('device_params', {}) if host == 'localhost' and device_params.get('name') == 'junos' \ and device_params.get('local'): return connect_ioproc(*args, **kwds) else: return connect_ssh(*args, **kwds) class OpExecutor(type): def __new__(cls, name, bases, attrs): def make_wrapper(op_cls): def wrapper(self, *args, **kwds): return self.execute(op_cls, *args, **kwds) wrapper.__doc__ = op_cls.request.__doc__ return wrapper for op_name, op_cls in six.iteritems(OPERATIONS): attrs[op_name] = make_wrapper(op_cls) return super(OpExecutor, cls).__new__(cls, name, bases, attrs) def __call__(cls, *args, **kwargs): def make_wrapper(op_cls): def wrapper(self, *args, **kwds): return self.execute(op_cls, *args, **kwds) wrapper.__doc__ = op_cls.request.__doc__ return wrapper if VENDOR_OPERATIONS: for op_name, op_cls in six.iteritems(VENDOR_OPERATIONS): setattr(cls, op_name, make_wrapper(op_cls)) return super(OpExecutor, cls).__call__(*args, **kwargs) class Manager(six.with_metaclass(OpExecutor, object)): """ For details on the expected behavior of the operations and their parameters refer to :rfc:`4741`. Manager instances are also context managers so you can use it like this:: with manager.connect("host") as m: # do your stuff ... or like this:: m = manager.connect("host") try: # do your stuff finally: m.close_session() """ # __metaclass__ = OpExecutor def __init__(self, session, device_handler, timeout=30, *args, **kwargs): self._session = session self._async_mode = False self._timeout = timeout self._raise_mode = operations.RaiseMode.ALL self._device_handler = device_handler def __enter__(self): return self def __exit__(self, *args): self.close_session() return False def __set_timeout(self, timeout): self._timeout = timeout def __set_async_mode(self, mode): self._async_mode = mode def __set_raise_mode(self, mode): assert(mode in (operations.RaiseMode.NONE, operations.RaiseMode.ERRORS, operations.RaiseMode.ALL)) self._raise_mode = mode def execute(self, cls, *args, **kwds): return cls(self._session, device_handler=self._device_handler, async_mode=self._async_mode, timeout=self._timeout, raise_mode=self._raise_mode).request(*args, **kwds) def locked(self, target): """Returns a context manager for a lock on a datastore, where *target* is the name of the configuration datastore to lock, e.g.:: with m.locked("running"): # do your stuff ... instead of:: m.lock("running") try: # do your stuff finally: m.unlock("running") """ return operations.LockContext(self._session, self._device_handler, target) def scp(self): return self._session.scp() def session(self): raise NotImplementedError def __getattr__(self, method): """Parse args/kwargs correctly in order to build XML element""" def _missing(*args, **kwargs): m = method.replace('_', '-') root = new_ele(m) if args: for arg in args: sub_ele(root, arg) r = self.rpc(root) return r return _missing def take_notification(self, block=True, timeout=None): """Attempt to retrieve one notification from the queue of received notifications. If block is True, the call will wait until a notification is received. If timeout is a number greater than 0, the call will wait that many seconds to receive a notification before timing out. If there is no notification available when block is False or when the timeout has elapse, None will be returned. Otherwise a :class:`~ncclient.operations.notify.Notification` object will be returned. """ return self._session.take_notification(block, timeout) @property def client_capabilities(self): """:class:`~ncclient.capabilities.Capabilities` object representing the client's capabilities.""" return self._session._client_capabilities @property def server_capabilities(self): """:class:`~ncclient.capabilities.Capabilities` object representing the server's capabilities.""" return self._session._server_capabilities @property def channel_id(self): return self._session._channel_id @property def channel_name(self): return self._session._channel_name @property def session_id(self): """`session-id` assigned by the NETCONF server.""" return self._session.id @property def connected(self): """Whether currently connected to the NETCONF server.""" return self._session.connected async_mode = property(fget=lambda self: self._async_mode, fset=__set_async_mode) """Specify whether operations are executed asynchronously (`True`) or synchronously (`False`) (the default).""" timeout = property(fget=lambda self: self._timeout, fset=__set_timeout) """Specify the timeout for synchronous RPC requests.""" raise_mode = property(fget=lambda self: self._raise_mode, fset=__set_raise_mode) """Specify which errors are raised as :exc:`~ncclient.operations.RPCError` exceptions. Valid values are the constants defined in :class:`~ncclient.operations.RaiseMode`. The default value is :attr:`~ncclient.operations.RaiseMode.ALL`.""" ncclient-0.6.0/ncclient/operations/000077500000000000000000000000001331651044100172745ustar00rootroot00000000000000ncclient-0.6.0/ncclient/operations/__init__.py000066400000000000000000000031301331651044100214020ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from ncclient.operations.errors import OperationError, TimeoutExpiredError, MissingCapabilityError from ncclient.operations.rpc import RPC, RPCReply, RPCError, RaiseMode # rfc4741 ops from ncclient.operations.retrieve import Get, GetConfig, GetSchema, GetReply, Dispatch from ncclient.operations.edit import EditConfig, CopyConfig, DeleteConfig, Validate, Commit, DiscardChanges, CancelCommit from ncclient.operations.session import CloseSession, KillSession from ncclient.operations.lock import Lock, Unlock, LockContext from ncclient.operations.subscribe import CreateSubscription # others... from ncclient.operations.flowmon import PoweroffMachine, RebootMachine __all__ = [ 'RPC', 'RPCReply', 'RPCError', 'RaiseMode', 'Get', 'GetConfig', 'GetSchema', 'Dispatch', 'GetReply', 'EditConfig', 'CopyConfig', 'Validate', 'Commit', 'DiscardChanges', 'CancelCommit', 'DeleteConfig', 'Lock', 'Unlock', 'CreateSubscription', 'PoweroffMachine', 'RebootMachine', ] ncclient-0.6.0/ncclient/operations/edit.py000066400000000000000000000161371331651044100206030ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from ncclient.xml_ import * from ncclient.operations.rpc import RPC from ncclient.operations import util import logging logger = logging.getLogger("ncclient.operations.edit") "Operations related to changing device configuration" class EditConfig(RPC): "`edit-config` RPC" def request(self, config, format='xml', target='candidate', default_operation=None, test_option=None, error_option=None): """Loads all or part of the specified *config* to the *target* configuration datastore. *target* is the name of the configuration datastore being edited *config* is the configuration, which must be rooted in the `config` element. It can be specified either as a string or an :class:`~xml.etree.ElementTree.Element`. *default_operation* if specified must be one of { `"merge"`, `"replace"`, or `"none"` } *test_option* if specified must be one of { `"test_then_set"`, `"set"` } *error_option* if specified must be one of { `"stop-on-error"`, `"continue-on-error"`, `"rollback-on-error"` } The `"rollback-on-error"` *error_option* depends on the `:rollback-on-error` capability. """ node = new_ele("edit-config") node.append(util.datastore_or_url("target", target, self._assert)) if error_option is not None: if error_option == "rollback-on-error": self._assert(":rollback-on-error") sub_ele(node, "error-option").text = error_option if test_option is not None: self._assert(':validate') sub_ele(node, "test-option").text = test_option if default_operation is not None: # TODO: check if it is a valid default-operation sub_ele(node, "default-operation").text = default_operation # <<<<<<< HEAD # node.append(validated_element(config, ("config", qualify("config")))) # ======= if format == 'xml': node.append(validated_element(config, ("config", qualify("config")))) if format == 'text': config_text = sub_ele(node, "config-text") sub_ele(config_text, "configuration-text").text = config # >>>>>>> juniper return self._request(node) class DeleteConfig(RPC): "`delete-config` RPC" def request(self, target): """Delete a configuration datastore. *target* specifies the name or URL of configuration datastore to delete :seealso: :ref:`srctarget_params`""" node = new_ele("delete-config") node.append(util.datastore_or_url("target", target, self._assert)) return self._request(node) class CopyConfig(RPC): "`copy-config` RPC" def request(self, source, target): """Create or replace an entire configuration datastore with the contents of another complete configuration datastore. *source* is the name of the configuration datastore to use as the source of the copy operation or `config` element containing the configuration subtree to copy *target* is the name of the configuration datastore to use as the destination of the copy operation :seealso: :ref:`srctarget_params`""" node = new_ele("copy-config") node.append(util.datastore_or_url("target", target, self._assert)) try: # datastore name or URL node.append(util.datastore_or_url("source", source, self._assert)) except Exception: # `source` with `config` element containing the configuration subtree to copy node.append(validated_element(source, ("source", qualify("source")))) return self._request(node) class Validate(RPC): "`validate` RPC. Depends on the `:validate` capability." DEPENDS = [':validate'] def request(self, source="candidate"): """Validate the contents of the specified configuration. *source* is the name of the configuration datastore being validated or `config` element containing the configuration subtree to be validated :seealso: :ref:`srctarget_params`""" node = new_ele("validate") if type(source) is str: src = util.datastore_or_url("source", source, self._assert) else: validated_element(source, ("config", qualify("config"))) src = new_ele("source") src.append(source) node.append(src) return self._request(node) class Commit(RPC): "`commit` RPC. Depends on the `:candidate` capability, and the `:confirmed-commit`." DEPENDS = [':candidate'] def request(self, confirmed=False, timeout=None, persist=None): """Commit the candidate configuration as the device's new current configuration. Depends on the `:candidate` capability. A confirmed commit (i.e. if *confirmed* is `True`) is reverted if there is no followup commit within the *timeout* interval. If no timeout is specified the confirm timeout defaults to 600 seconds (10 minutes). A confirming commit may have the *confirmed* parameter but this is not required. Depends on the `:confirmed-commit` capability. *confirmed* whether this is a confirmed commit *timeout* specifies the confirm timeout in seconds *persist* make the confirmed commit survive a session termination, and set a token on the ongoing confirmed commit """ node = new_ele("commit") if confirmed: self._assert(":confirmed-commit") sub_ele(node, "confirmed") if timeout is not None: sub_ele(node, "confirm-timeout").text = timeout if persist is not None: sub_ele(node, "persist").text = persist return self._request(node) class CancelCommit(RPC): "`cancel-commit` RPC. Depends on the `:candidate` and `:confirmed-commit` capabilities." DEPENDS = [':candidate', ':confirmed-commit'] def request(self, persist_id=None): """Cancel an ongoing confirmed commit. Depends on the `:candidate` and `:confirmed-commit` capabilities. *persist-id* value must be equal to the value given in the parameter to the previous operation. """ node = new_ele("cancel-commit") if persist_id is not None: sub_ele(node, "persist-id").text = persist_id return self._request(node) class DiscardChanges(RPC): "`discard-changes` RPC. Depends on the `:candidate` capability." DEPENDS = [":candidate"] def request(self): """Revert the candidate configuration to the currently running configuration. Any uncommitted changes are discarded.""" return self._request(new_ele("discard-changes")) ncclient-0.6.0/ncclient/operations/errors.py000066400000000000000000000014001331651044100211550ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from ncclient import NCClientError class OperationError(NCClientError): pass class TimeoutExpiredError(NCClientError): pass class MissingCapabilityError(NCClientError): pass ncclient-0.6.0/ncclient/operations/flowmon.py000066400000000000000000000023211331651044100213250ustar00rootroot00000000000000# Copyright 2h009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. 'Power-control operations' from ncclient.xml_ import * from ncclient.operations.rpc import RPC PC_URN = "urn:liberouter:params:xml:ns:netconf:power-control:1.0" class PoweroffMachine(RPC): "*poweroff-machine* RPC (flowmon)" DEPENDS = ["urn:liberouter:param:netconf:capability:power-control:1.0"] def request(self): return self._request(new_ele(qualify("poweroff-machine", PC_URN))) class RebootMachine(RPC): "*reboot-machine* RPC (flowmon)" DEPENDS = ["urn:liberouter:params:netconf:capability:power-control:1.0"] def request(self): return self._request(new_ele(qualify("reboot-machine", PC_URN))) ncclient-0.6.0/ncclient/operations/lock.py000066400000000000000000000042671331651044100206070ustar00rootroot00000000000000# Copyright 2h009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. "Locking-related NETCONF operations" from ncclient.xml_ import * from ncclient.operations.rpc import RaiseMode, RPC # TODO: parse session-id from a lock-denied error, and raise a tailored exception? class Lock(RPC): "`lock` RPC" def request(self, target="candidate"): """Allows the client to lock the configuration system of a device. *target* is the name of the configuration datastore to lock """ node = new_ele("lock") sub_ele(sub_ele(node, "target"), target) return self._request(node) class Unlock(RPC): "`unlock` RPC" def request(self, target="candidate"): """Release a configuration lock, previously obtained with the lock operation. *target* is the name of the configuration datastore to unlock """ node = new_ele("unlock") sub_ele(sub_ele(node, "target"), target) return self._request(node) class LockContext(object): """A context manager for the :class:`Lock` / :class:`Unlock` pair of RPC's. Any `rpc-error` will be raised as an exception. Initialise with (:class:`Session `) instance and lock target. """ def __init__(self, session, device_handler, target): self.session = session self.target = target self.device_handler = device_handler def __enter__(self): Lock(self.session, self.device_handler, raise_mode=RaiseMode.ERRORS).request(self.target) return self def __exit__(self, *args): Unlock(self.session, self.device_handler, raise_mode=RaiseMode.ERRORS).request(self.target) return False ncclient-0.6.0/ncclient/operations/retrieve.py000066400000000000000000000147541331651044100215060ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from ncclient.operations.errors import OperationError from ncclient.operations.rpc import RPC, RPCReply from ncclient.xml_ import * from lxml import etree from ncclient.operations import util _VALID_WITH_DEFAULTS = [ "explicit", "report-all", "report-all-tagged", "trim" ] class WithDefaultsError(OperationError): """Raised when an invalid "with-defaults" option is specified in a Get or GetConfig request""" def __init__(self, invalid_mode): super(WithDefaultsError, self).__init__( "Invalid 'with-defaults' mode '{provided}', must be one of the " "following: {options}".format( provided=invalid_mode, options=', '.join(_VALID_WITH_DEFAULTS) )) class GetReply(RPCReply): """Adds attributes for the *data* element to `RPCReply`.""" def _parsing_hook(self, root): self._data = None if not self._errors: self._data = root.find(qualify("data")) @property def data_ele(self): "*data* element as an :class:`~xml.etree.ElementTree.Element`" if not self._parsed: self.parse() return self._data @property def data_xml(self): "*data* element as an XML string" if not self._parsed: self.parse() return to_xml(self._data) data = data_ele "Same as :attr:`data_ele`" class GetSchemaReply(GetReply): """Reply for GetSchema called with specific parsing hook.""" def _parsing_hook(self, root): self._data = None if not self._errors: self._data = root.find(qualify("data", NETCONF_MONITORING_NS)).text class Get(RPC): "The *get* RPC." REPLY_CLS = GetReply "See :class:`GetReply`." def request(self, filter=None, with_defaults=None): """Retrieve running configuration and device state information. *filter* specifies the portion of the configuration to retrieve (by default entire configuration is retrieved) *with_defaults* defines an explicit method of retrieving default values from the configuration (see RFC 6243) :seealso: :ref:`filter_params` """ node = new_ele("get") if filter is not None: node.append(util.build_filter(filter)) if with_defaults is not None: self._assert(":with-defaults") _append_with_defaults(node, with_defaults) return self._request(node) def _append_with_defaults(node, with_defaults): if with_defaults.strip().lower() not in _VALID_WITH_DEFAULTS: raise WithDefaultsError(with_defaults) with_defaults_ele = sub_ele_ns(node, "with-defaults", NETCONF_WITH_DEFAULTS_NS) with_defaults_ele.text = with_defaults class GetConfig(RPC): """The *get-config* RPC.""" REPLY_CLS = GetReply """See :class:`GetReply`.""" def request(self, source, filter=None, with_defaults=None): """Retrieve all or part of a specified configuration. *source* name of the configuration datastore being queried *filter* specifies the portion of the configuration to retrieve (by default entire configuration is retrieved) *with_defaults* defines an explicit method of retrieving default values from the configuration (see RFC 6243) :seealso: :ref:`filter_params`""" node = new_ele("get-config") node.append(util.datastore_or_url("source", source, self._assert)) if filter is not None: node.append(util.build_filter(filter)) if with_defaults is not None: self._assert(":with-defaults") _append_with_defaults(node, with_defaults) return self._request(node) class GetSchema(RPC): """The *get-schema* RPC.""" REPLY_CLS = GetSchemaReply """See :class:`GetReply`.""" def request(self, identifier, version=None, format=None): """Retrieve a named schema, with optional revision and type. *identifier* name of the schema to be retrieved *version* version of schema to get *format* format of the schema to be retrieved, yang is the default :seealso: :ref:`filter_params`""" node = etree.Element(qualify("get-schema",NETCONF_MONITORING_NS)) if identifier is not None: elem = etree.Element(qualify("identifier",NETCONF_MONITORING_NS)) elem.text = identifier node.append(elem) if version is not None: elem = etree.Element(qualify("version",NETCONF_MONITORING_NS)) elem.text = version node.append(elem) if format is not None: elem = etree.Element(qualify("format",NETCONF_MONITORING_NS)) elem.text = format node.append(elem) return self._request(node) class Dispatch(RPC): """Generic retrieving wrapper""" REPLY_CLS = GetReply """See :class:`GetReply`.""" def request(self, rpc_command, source=None, filter=None): """ *rpc_command* specifies rpc command to be dispatched either in plain text or in xml element format (depending on command) *source* name of the configuration datastore being queried *filter* specifies the portion of the configuration to retrieve (by default entire configuration is retrieved) :seealso: :ref:`filter_params` Examples of usage:: dispatch('clear-arp-table') or dispatch element like :: xsd_fetch = new_ele('get-xnm-information') sub_ele(xsd_fetch, 'type').text="xml-schema" sub_ele(xsd_fetch, 'namespace').text="junos-configuration" dispatch(xsd_fetch) """ if etree.iselement(rpc_command): node = rpc_command else: node = new_ele(rpc_command) if source is not None: node.append(util.datastore_or_url("source", source, self._assert)) if filter is not None: node.append(util.build_filter(filter)) return self._request(node) ncclient-0.6.0/ncclient/operations/rpc.py000066400000000000000000000366331331651044100204450ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from threading import Event, Lock from uuid import uuid4 import six from ncclient.xml_ import * from ncclient.transport import SessionListener from ncclient.operations.errors import OperationError, TimeoutExpiredError, MissingCapabilityError import logging logger = logging.getLogger("ncclient.operations.rpc") class RPCError(OperationError): "Represents an `rpc-error`. It is a type of :exc:`OperationError` and can be raised as such." tag_to_attr = { qualify("error-type"): "_type", qualify("error-tag"): "_tag", qualify("error-severity"): "_severity", qualify("error-info"): "_info", qualify("error-path"): "_path", qualify("error-message"): "_message" } def __init__(self, raw, errs=None): self._raw = raw if errs is None: # Single RPCError for attr in six.itervalues(RPCError.tag_to_attr): setattr(self, attr, None) for subele in raw: attr = RPCError.tag_to_attr.get(subele.tag, None) if attr is not None: setattr(self, attr, subele.text if attr != "_info" else to_xml(subele) ) if self.message is not None: OperationError.__init__(self, self.message) else: OperationError.__init__(self, self.to_dict()) else: # Multiple errors returned. Errors is a list of RPCError objs errlist = [] for err in errs: if err.severity: errsev = err.severity else: errsev = 'undefined' if err.message: errmsg = err.message else: errmsg = 'not an error message in the reply. Enable debug' errordict = {"severity": errsev, "message":errmsg} errlist.append(errordict) # We are interested in the severity and the message self._severity = 'warning' self._message = "\n".join(["%s: %s" %(err['severity'].strip(), err['message'].strip()) for err in errlist]) self.errors = errs has_error = filter(lambda higherr: higherr['severity'] == 'error', errlist) if has_error: self._severity = 'error' OperationError.__init__(self, self.message) def to_dict(self): return dict([ (attr[1:], getattr(self, attr)) for attr in six.itervalues(RPCError.tag_to_attr) ]) @property def xml(self): "The `rpc-error` element as returned in XML. \ Multiple errors are returned as list of RPC errors" return self._raw @property def type(self): "The contents of the `error-type` element." return self._type @property def tag(self): "The contents of the `error-tag` element." return self._tag @property def severity(self): "The contents of the `error-severity` element." return self._severity @property def path(self): "The contents of the `error-path` element if present or `None`." return self._path @property def message(self): "The contents of the `error-message` element if present or `None`." return self._message @property def info(self): "XML string or `None`; representing the `error-info` element." return self._info class RPCReply(object): """Represents an *rpc-reply*. Only concerns itself with whether the operation was successful. .. note:: If the reply has not yet been parsed there is an implicit, one-time parsing overhead to accessing some of the attributes defined by this class. """ ERROR_CLS = RPCError "Subclasses can specify a different error class, but it should be a subclass of `RPCError`." def __init__(self, raw): self._raw = raw self._parsed = False self._root = None self._errors = [] def __repr__(self): return self._raw def parse(self): "Parses the *rpc-reply*." if self._parsed: return root = self._root = to_ele(self._raw) # The element # Per RFC 4741 an tag is sent when there are no errors or warnings ok = root.find(qualify("ok")) if ok is None: # Create RPCError objects from elements error = root.find('.//'+qualify('rpc-error')) if error is not None: for err in root.getiterator(error.tag): # Process a particular self._errors.append(self.ERROR_CLS(err)) self._parsing_hook(root) self._parsed = True def _parsing_hook(self, root): "No-op by default. Gets passed the *root* element for the reply." pass @property def xml(self): "*rpc-reply* element as returned." return self._raw @property def ok(self): "Boolean value indicating if there were no errors." return not self.errors # empty list => false @property def error(self): "Returns the first :class:`RPCError` and `None` if there were no errors." self.parse() if self._errors: return self._errors[0] else: return None @property def errors(self): "List of `RPCError` objects. Will be empty if there were no *rpc-error* elements in reply." self.parse() return self._errors class RPCReplyListener(SessionListener): # internal use creation_lock = Lock() # one instance per session -- maybe there is a better way?? def __new__(cls, session, device_handler): with RPCReplyListener.creation_lock: instance = session.get_listener_instance(cls) if instance is None: instance = object.__new__(cls) instance._lock = Lock() instance._id2rpc = {} instance._device_handler = device_handler #instance._pipelined = session.can_pipeline session.add_listener(instance) return instance def register(self, id, rpc): with self._lock: self._id2rpc[id] = rpc def callback(self, root, raw): tag, attrs = root if self._device_handler.perform_qualify_check(): if tag != qualify("rpc-reply"): return if "message-id" not in attrs: # required attribute so raise OperationError raise OperationError("Could not find 'message-id' attribute in ") else: id = attrs["message-id"] # get the msgid with self._lock: try: rpc = self._id2rpc[id] # the corresponding rpc logger.debug("Delivering to %r" % rpc) rpc.deliver_reply(raw) except KeyError: raise OperationError("Unknown 'message-id': %s" % id) # no catching other exceptions, fail loudly if must else: # if no error delivering, can del the reference to the RPC del self._id2rpc[id] def errback(self, err): try: for rpc in six.itervalues(self._id2rpc): rpc.deliver_error(err) finally: self._id2rpc.clear() class RaiseMode(object): """ Define how errors indicated by RPC should be handled. Note that any error_filters defined in the device handler will still be applied, even if ERRORS or ALL is defined: If the filter matches, an exception will NOT be raised. """ NONE = 0 "Don't attempt to raise any type of `rpc-error` as :exc:`RPCError`." ERRORS = 1 "Raise only when the `error-type` indicates it is an honest-to-god error." ALL = 2 "Don't look at the `error-type`, always raise." class RPC(object): """Base class for all operations, directly corresponding to *rpc* requests. Handles making the request, and taking delivery of the reply.""" DEPENDS = [] """Subclasses can specify their dependencies on capabilities as a list of URI's or abbreviated names, e.g. ':writable-running'. These are verified at the time of instantiation. If the capability is not available, :exc:`MissingCapabilityError` is raised.""" REPLY_CLS = RPCReply "By default :class:`RPCReply`. Subclasses can specify a :class:`RPCReply` subclass." def __init__(self, session, device_handler, async_mode=False, timeout=30, raise_mode=RaiseMode.NONE): """ *session* is the :class:`~ncclient.transport.Session` instance *device_handler" is the :class:`~ncclient.devices.*.*DeviceHandler` instance *async* specifies whether the request is to be made asynchronously, see :attr:`is_async` *timeout* is the timeout for a synchronous request, see :attr:`timeout` *raise_mode* specifies the exception raising mode, see :attr:`raise_mode` """ self._session = session try: for cap in self.DEPENDS: self._assert(cap) except AttributeError: pass self._async = async_mode self._timeout = timeout self._raise_mode = raise_mode self._id = uuid4().urn # Keeps things simple instead of having a class attr with running ID that has to be locked self._listener = RPCReplyListener(session, device_handler) self._listener.register(self._id, self) self._reply = None self._error = None self._event = Event() self._device_handler = device_handler def _wrap(self, subele): # internal use ele = new_ele("rpc", {"message-id": self._id}, **self._device_handler.get_xml_extra_prefix_kwargs()) ele.append(subele) #print to_xml(ele) return to_xml(ele) def _request(self, op): """Implementations of :meth:`request` call this method to send the request and process the reply. In synchronous mode, blocks until the reply is received and returns :class:`RPCReply`. Depending on the :attr:`raise_mode` a `rpc-error` element in the reply may lead to an :exc:`RPCError` exception. In asynchronous mode, returns immediately, returning `self`. The :attr:`event` attribute will be set when the reply has been received (see :attr:`reply`) or an error occured (see :attr:`error`). *op* is the operation to be requested as an :class:`~xml.etree.ElementTree.Element` """ logger.info('Requesting %r' % self.__class__.__name__) req = self._wrap(op) self._session.send(req) if self._async: logger.debug('Async request, returning %r', self) return self else: logger.debug('Sync request, will wait for timeout=%r' % self._timeout) self._event.wait(self._timeout) if self._event.isSet(): if self._error: # Error that prevented reply delivery raise self._error self._reply.parse() if self._reply.error is not None and not self._device_handler.is_rpc_error_exempt(self._reply.error.message): # 's [ RPCError ] if self._raise_mode == RaiseMode.ALL or (self._raise_mode == RaiseMode.ERRORS and self._reply.error.severity == "error"): errlist = [] errors = self._reply.errors if len(errors) > 1: raise RPCError(to_ele(self._reply._raw), errs=errors) else: raise self._reply.error if self._device_handler.transform_reply(): return NCElement(self._reply, self._device_handler.transform_reply()) else: return self._reply else: raise TimeoutExpiredError('ncclient timed out while waiting for an rpc reply.') def request(self): """Subclasses must implement this method. Typically only the request needs to be built as an :class:`~xml.etree.ElementTree.Element` and everything else can be handed off to :meth:`_request`.""" pass def _assert(self, capability): """Subclasses can use this method to verify that a capability is available with the NETCONF server, before making a request that requires it. A :exc:`MissingCapabilityError` will be raised if the capability is not available.""" if capability not in self._session.server_capabilities: raise MissingCapabilityError('Server does not support [%s]' % capability) def deliver_reply(self, raw): # internal use self._reply = self.REPLY_CLS(raw) self._event.set() def deliver_error(self, err): # internal use self._error = err self._event.set() @property def reply(self): ":class:`RPCReply` element if reply has been received or `None`" return self._reply @property def error(self): """:exc:`Exception` type if an error occured or `None`. .. note:: This represents an error which prevented a reply from being received. An *rpc-error* does not fall in that category -- see `RPCReply` for that. """ return self._error @property def id(self): "The *message-id* for this RPC." return self._id @property def session(self): "The `~ncclient.transport.Session` object associated with this RPC." return self._session @property def event(self): """:class:`~threading.Event` that is set when reply has been received or when an error preventing delivery of the reply occurs. """ return self._event def __set_async(self, async_mode=True): self._async = async_mode if async_mode and not self._session.can_pipeline: raise UserWarning('Asynchronous mode not supported for this device/session') def __set_raise_mode(self, mode): assert(mode in (RaiseMode.NONE, RaiseMode.ERRORS, RaiseMode.ALL)) self._raise_mode = mode def __set_timeout(self, timeout): self._timeout = timeout raise_mode = property(fget=lambda self: self._raise_mode, fset=__set_raise_mode) """Depending on this exception raising mode, an `rpc-error` in the reply may be raised as an :exc:`RPCError` exception. Valid values are the constants defined in :class:`RaiseMode`. """ is_async = property(fget=lambda self: self._async, fset=__set_async) """Specifies whether this RPC will be / was requested asynchronously. By default RPC's are synchronous.""" timeout = property(fget=lambda self: self._timeout, fset=__set_timeout) """Timeout in seconds for synchronous waiting defining how long the RPC request will block on a reply before raising :exc:`TimeoutExpiredError`. Irrelevant for asynchronous usage. """ ncclient-0.6.0/ncclient/operations/session.py000066400000000000000000000026321331651044100213340ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. "Session-related NETCONF operations" from ncclient.xml_ import * from ncclient.operations.rpc import RPC class CloseSession(RPC): "`close-session` RPC. The connection to NETCONF server is also closed." def request(self): "Request graceful termination of the NETCONF session, and also close the transport." try: return self._request(new_ele("close-session")) finally: self.session.close() class KillSession(RPC): "`kill-session` RPC." def request(self, session_id): """Force the termination of a NETCONF session (not the current one!) *session_id* is the session identifier of the NETCONF session to be terminated as a string """ node = new_ele("kill-session") sub_ele(node, "session-id").text = session_id return self._request(node) ncclient-0.6.0/ncclient/operations/subscribe.py000066400000000000000000000046151331651044100216350ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from ncclient.operations.rpc import RPC from ncclient.xml_ import * from ncclient.operations import util class CreateSubscription(RPC): "`create-subscription` RPC. Depends on the `:notification` capability." DEPENDS = [':notification'] def request(self, filter=None, stream_name=None, start_time=None, stop_time=None): """Creates a subscription for notifications from the server. *filter* specifies the subset of notifications to receive (by default all notificaitons are received) :seealso: :ref:`filter_params` *stream_name* specifies the notification stream name. The default is None meaning all streams. *start_time* triggers the notification replay feature to replay notifications from the given time. The default is None, meaning that this is not a replay subscription. The format is an RFC 3339/ISO 8601 date and time. *stop_time* indicates the end of the notifications of interest. This parameter must be used with *start_time*. The default is None, meaning that (if *start_time* is present) the notifications will continue until the subscription is terminated. The format is an RFC 3339/ISO 8601 date and time. """ node = new_ele_ns("create-subscription", NETCONF_NOTIFICATION_NS) if filter is not None: node.append(util.build_filter(filter)) if stream_name is not None: sub_ele(node, "stream").text = stream_name if start_time is not None: sub_ele(node, "startTime").text = start_time if stop_time is not None: if start_time is None: raise ValueError("You must provide start_time if you provide stop_time") sub_ele(node, "stopTime").text = stop_time return self._request(node) ncclient-0.6.0/ncclient/operations/third_party/000077500000000000000000000000001331651044100216255ustar00rootroot00000000000000ncclient-0.6.0/ncclient/operations/third_party/__init__.py000066400000000000000000000000001331651044100237240ustar00rootroot00000000000000ncclient-0.6.0/ncclient/operations/third_party/h3c/000077500000000000000000000000001331651044100223025ustar00rootroot00000000000000ncclient-0.6.0/ncclient/operations/third_party/h3c/__init__.py000066400000000000000000000001031331651044100244050ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # by yangxufeng.zhao ncclient-0.6.0/ncclient/operations/third_party/h3c/rpc.py000066400000000000000000000043561331651044100234500ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # by yangxufeng.zhao from ncclient.xml_ import * from ncclient.operations import util from ncclient.operations.rpc import RPC class GetBulk(RPC): "The *get-bulk* RPC." def request(self, filter=None): """Retrieve running configuration and device state information. *filter* specifies the portion of the configuration to retrieve (by default entire configuration is retrieved) :seealso: :ref:`filter_params` """ node = new_ele("get-bulk") if filter is not None: node.append(util.build_filter(filter)) return self._request(node) class GetBulkConfig(RPC): """The *get-bulk-config* RPC.""" def request(self, source, filter=None): """Retrieve all or part of a specified configuration. *source* name of the configuration datastore being queried *filter* specifies the portion of the configuration to retrieve (by default entire configuration is retrieved) :seealso: :ref:`filter_params`""" node = new_ele("get-bulk-config") node.append(util.datastore_or_url("source", source, self._assert)) if filter is not None: node.append(util.build_filter(filter)) return self._request(node) class CLI(RPC): def request(self, command=None): """command text view: Execution user view exec Configuration system view exec """ node = new_ele("CLI") node.append(validated_element(command)) # sub_ele(node, view).text = command return self._request(node) class Action(RPC): def request(self, action=None): node = new_ele("action") node.append(validated_element(action)) return self._request(node) class Save(RPC): def request(self, file=None): node = new_ele('save') sub_ele(node, 'file').text = file return self._request(node) class Load(RPC): def request(self, file=None): node = new_ele('load') sub_ele(node, 'file').text = file return self._request(node) class Rollback(RPC): def request(self, file=None): node = new_ele('rollback') sub_ele(node, 'file').text = file return self._request(node)ncclient-0.6.0/ncclient/operations/third_party/hpcomware/000077500000000000000000000000001331651044100236125ustar00rootroot00000000000000ncclient-0.6.0/ncclient/operations/third_party/hpcomware/__init__.py000066400000000000000000000000001331651044100257110ustar00rootroot00000000000000ncclient-0.6.0/ncclient/operations/third_party/hpcomware/rpc.py000066400000000000000000000033401331651044100247500ustar00rootroot00000000000000from lxml import etree from ncclient.xml_ import * from ncclient.operations.rpc import RPC class DisplayCommand(RPC): def request(self, cmds): """ Single Execution element is permitted. cmds can be a list or single command """ if isinstance(cmds, list): cmd = '\n'.join(cmds) elif isinstance(cmds, str) or isinstance(cmds, unicode): cmd = cmds node = etree.Element(qualify('CLI', BASE_NS_1_0)) etree.SubElement(node, qualify('Execution', BASE_NS_1_0)).text = cmd return self._request(node) class ConfigCommand(RPC): def request(self, cmds): """ Single Configuration element is permitted. cmds can be a list or single command commands are pushed to the switch in this method """ if isinstance(cmds, list): cmd = '\n'.join(cmds) elif isinstance(cmds, str) or isinstance(cmds, unicode): cmd = cmds node = etree.Element(qualify('CLI', BASE_NS_1_0)) etree.SubElement(node, qualify('Configuration', BASE_NS_1_0)).text = cmd return self._request(node) class Action(RPC): def request(self, action=None): node = new_ele("action") node.append(validated_element(action)) return self._request(node) class Save(RPC): def request(self, filename=None): node = new_ele('save') sub_ele(node, 'file').text = filename return self._request(node) class Rollback(RPC): def request(self, filename=None): node = new_ele('rollback') sub_ele(node, 'file').text = filename return self._request(node) ncclient-0.6.0/ncclient/operations/third_party/huawei/000077500000000000000000000000001331651044100231075ustar00rootroot00000000000000ncclient-0.6.0/ncclient/operations/third_party/huawei/__init__.py000066400000000000000000000001021331651044100252110ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # by yangxufeng.zhaoncclient-0.6.0/ncclient/operations/third_party/huawei/rpc.py000066400000000000000000000013461331651044100242510ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # by yangxufeng.zhao from ncclient.xml_ import * from ncclient.operations.rpc import RPC class CLI(RPC): def request(self, command=None): """command text view: Execution user view exec Configuration system view exec """ # node = new_ele("execute-cli") node = new_ele("execute-cli", attrs={"xmlns":HW_PRIVATE_NS}) node.append(validated_element(command)) return self._request(node) class Action(RPC): "`execute-action` RPC" def request(self, action=None): node = new_ele("execute-action", attrs={"xmlns":HW_PRIVATE_NS}) node.append(validated_element(action)) return self._request(node) ncclient-0.6.0/ncclient/operations/third_party/iosxe/000077500000000000000000000000001331651044100227545ustar00rootroot00000000000000ncclient-0.6.0/ncclient/operations/third_party/iosxe/__init__.py000066400000000000000000000000001331651044100250530ustar00rootroot00000000000000ncclient-0.6.0/ncclient/operations/third_party/iosxe/rpc.py000066400000000000000000000004051331651044100241110ustar00rootroot00000000000000from lxml import etree from ncclient.xml_ import * from ncclient.operations.rpc import RPC class SaveConfig(RPC): def request(self): node = etree.Element(qualify('save-config', "http://cisco.com/yang/cisco-ia")) return self._request(node) ncclient-0.6.0/ncclient/operations/third_party/juniper/000077500000000000000000000000001331651044100233015ustar00rootroot00000000000000ncclient-0.6.0/ncclient/operations/third_party/juniper/__init__.py000066400000000000000000000000001331651044100254000ustar00rootroot00000000000000ncclient-0.6.0/ncclient/operations/third_party/juniper/rpc.py000066400000000000000000000125011331651044100244360ustar00rootroot00000000000000from ncclient.xml_ import * from ncclient.operations.rpc import RPC from ncclient.operations.rpc import RPCReply from ncclient.operations.rpc import RPCError from ncclient import NCClientError import math class GetConfiguration(RPC): def request(self, format='xml', filter=None): node = new_ele('get-configuration', {'format':format}) if filter is not None: node.append(filter) return self._request(node) class LoadConfiguration(RPC): def request(self, format='xml', action='merge', target='candidate', config=None): if config is not None: if type(config) == list: config = '\n'.join(config) if action == 'set': format = 'text' node = new_ele('load-configuration', {'action':action, 'format':format}) if format == 'xml': config_node = sub_ele(node, 'configuration') config_node.append(config) if format == 'json': config_node = sub_ele(node, 'configuration-json').text = config if format == 'text' and not action == 'set': config_node = sub_ele(node, 'configuration-text').text = config if action == 'set' and format == 'text': config_node = sub_ele(node, 'configuration-set').text = config return self._request(node) class CompareConfiguration(RPC): def request(self, rollback=0): node = new_ele('get-configuration', {'compare':'rollback', 'rollback':str(rollback)}) return self._request(node) class ExecuteRpc(RPC): def request(self, rpc): if isinstance(rpc, str): rpc = to_ele(rpc) return self._request(rpc) class Command(RPC): def request(self, command=None, format='xml'): node = new_ele('command', {'format':format}) node.text = command return self._request(node) class Reboot(RPC): def request(self): node = new_ele('request-reboot') return self._request(node) class Halt(RPC): def request(self): node = new_ele('request-halt') return self._request(node) class Commit(RPC): "`commit` RPC. Depends on the `:candidate` capability, and the `:confirmed-commit`." DEPENDS = [':candidate'] def request(self, confirmed=False, timeout=None, comment=None, synchronize=False, at_time=None, check=False): """Commit the candidate configuration as the device's new current configuration. Depends on the `:candidate` capability. A confirmed commit (i.e. if *confirmed* is `True`) is reverted if there is no followup commit within the *timeout* interval. If no timeout is specified the confirm timeout defaults to 600 seconds (10 minutes). A confirming commit may have the *confirmed* parameter but this is not required. Depends on the `:confirmed-commit` capability. *confirmed* whether this is a confirmed commit. Mutually exclusive with at_time. *timeout* specifies the confirm timeout in seconds *comment* a string to comment the commit with. Review on the device using 'show system commit' *synchronize* Whether we should synch this commit across both Routing Engines *at_time* Mutually exclusive with confirmed. The time at which the commit should happen. Junos expects either of these two formats: A time value of the form hh:mm[:ss] (hours, minutes, and, optionally, seconds) A date and time value of the form yyyy-mm-dd hh:mm[:ss] (year, month, date, hours, minutes, and, optionally, seconds) *check* Verify the syntactic correctness of the candidate configuration""" # NOTE: non netconf standard, Junos specific commit-configuration element, see # https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-commit-configuration.html node = new_ele_ns("commit-configuration", "") if confirmed and at_time is not None: raise NCClientError("'Commit confirmed' and 'commit at' are mutually exclusive.") if confirmed: self._assert(":confirmed-commit") sub_ele(node, "confirmed") if timeout is not None: # NOTE: For Junos, confirm-timeout has to be given in minutes: # https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-commit-configuration.html # but the netconf standard and ncclient library uses seconds: # https://tools.ietf.org/html/rfc6241#section-8.4.5 # http://ncclient.readthedocs.io/en/latest/manager.html#ncclient.manager.Manager.commit # so need to convert the value in seconds to minutes. timeout_int = int(timeout) if isinstance(timeout, str) else timeout sub_ele(node, "confirm-timeout").text = str(int(math.ceil(timeout_int/60.0))) elif at_time is not None: sub_ele(node, "at-time").text = at_time if comment is not None: sub_ele(node, "log").text = comment if synchronize: sub_ele(node, "synchronize") if check: sub_ele(node, "check") return self._request(node) class Rollback(RPC): def request(self, rollback=0): node = new_ele('load-configuration', {'rollback':str(rollback)}) return self._request(node) ncclient-0.6.0/ncclient/operations/third_party/nexus/000077500000000000000000000000001331651044100227675ustar00rootroot00000000000000ncclient-0.6.0/ncclient/operations/third_party/nexus/__init__.py000066400000000000000000000000001331651044100250660ustar00rootroot00000000000000ncclient-0.6.0/ncclient/operations/third_party/nexus/rpc.py000066400000000000000000000005301331651044100241230ustar00rootroot00000000000000from lxml import etree from ncclient.xml_ import * from ncclient.operations.rpc import RPC class ExecCommand(RPC): def request(self, cmds): node = etree.Element(qualify('exec-command', NXOS_1_0)) for cmd in cmds: etree.SubElement(node, qualify('cmd', NXOS_1_0)).text = cmd return self._request(node) ncclient-0.6.0/ncclient/operations/util.py000066400000000000000000000046521331651044100206320ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. 'Boilerplate ugliness' from ncclient.xml_ import * from ncclient.operations.errors import OperationError, MissingCapabilityError def one_of(*args): "Verifies that only one of the arguments is not None" for i, arg in enumerate(args): if arg is not None: for argh in args[i+1:]: if argh is not None: raise OperationError("Too many parameters") else: return raise OperationError("Insufficient parameters") def datastore_or_url(wha, loc, capcheck=None): node = new_ele(wha) if "://" in loc: # e.g. http://, file://, ftp:// if capcheck is not None: capcheck(":url") # url schema check at some point! sub_ele(node, "url").text = loc else: #if loc == 'candidate': # capcheck(':candidate') #elif loc == 'startup': # capcheck(':startup') #elif loc == 'running' and wha == 'target': # capcheck(':writable-running') sub_ele(node, loc) return node def build_filter(spec, capcheck=None): type = None if isinstance(spec, tuple): type, criteria = spec rep = new_ele("filter", type=type) if type == "xpath": rep.attrib["select"] = criteria elif type == "subtree": rep.append(to_ele(criteria)) else: raise OperationError("Invalid filter type") else: rep = validated_element(spec, ("filter", qualify("filter"))) # results in XMLError: line 105 ncclient/xml_.py - commented by earies - 5/10/13 #rep = validated_element(spec, ("filter", qualify("filter")), # attrs=("type",)) # TODO set type var here, check if select attr present in case of xpath.. if type == "xpath" and capcheck is not None: capcheck(":xpath") return rep ncclient-0.6.0/ncclient/transport/000077500000000000000000000000001331651044100171455ustar00rootroot00000000000000ncclient-0.6.0/ncclient/transport/__init__.py000066400000000000000000000016501331651044100212600ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. "Transport layer" from ncclient.transport.session import Session, SessionListener from ncclient.transport.ssh import SSHSession from ncclient.transport.errors import * __all__ = [ 'Session', 'SessionListener', 'SSHSession', 'TransportError', 'AuthenticationError', 'SessionCloseError', 'SSHError', 'SSHUnknownHostError' ] ncclient-0.6.0/ncclient/transport/errors.py000066400000000000000000000026211331651044100210340ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # Copyright 2014 Leonidas Poulopoulos # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from ncclient import NCClientError class TransportError(NCClientError): pass class SessionError(NCClientError): pass class AuthenticationError(TransportError): pass class PermissionError(TransportError): pass class SessionCloseError(TransportError): def __init__(self, in_buf, out_buf=None): msg = 'Unexpected session close' if in_buf: msg += '\nIN_BUFFER: `%s`' % in_buf if out_buf: msg += ' OUT_BUFFER: `%s`' % out_buf SSHError.__init__(self, msg) class SSHError(TransportError): pass class SSHUnknownHostError(SSHError): def __init__(self, host, fingerprint): SSHError.__init__(self, 'Unknown host key [%s] for [%s]' % (fingerprint, host)) self.host = host self.fingerprint = fingerprint ncclient-0.6.0/ncclient/transport/notify.py000066400000000000000000000015601331651044100210310ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from ncclient.xml_ import to_ele class Notification(object): def __init__(self, raw): self._raw = raw self._root_ele = to_ele(raw) @property def notification_ele(self): return self._root_ele @property def notification_xml(self): return self._raw ncclient-0.6.0/ncclient/transport/session.py000066400000000000000000000232341331651044100212060ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # Copyright 2014 Leonidas Poulopoulos # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re import sys import logging from threading import Thread, Lock, Event try: from Queue import Queue, Empty except ImportError: from queue import Queue, Empty from ncclient.xml_ import * from ncclient.capabilities import Capabilities from ncclient.transport.errors import TransportError, SessionError from ncclient.transport.notify import Notification logger = logging.getLogger('ncclient.transport.session') class Session(Thread): "Base class for use by transport protocol implementations." def __init__(self, capabilities): Thread.__init__(self) self.setDaemon(True) self._listeners = set() self._lock = Lock() self.setName('session') self._q = Queue() self._notification_q = Queue() self._client_capabilities = capabilities self._server_capabilities = None # yet self._id = None # session-id self._connected = False # to be set/cleared by subclass implementation logger.debug('%r created: client_capabilities=%r' % (self, self._client_capabilities)) self._device_handler = None # Should be set by child class def _dispatch_message(self, raw): try: root = parse_root(raw) except Exception as e: device_handled_raw=self._device_handler.handle_raw_dispatch(raw) if isinstance(device_handled_raw, str): root = parse_root(device_handled_raw) elif isinstance(device_handled_raw, Exception): self._dispatch_error(device_handled_raw) return else: logger.error('error parsing dispatch message: %s' % e) return with self._lock: listeners = list(self._listeners) for l in listeners: logger.debug('dispatching message to %r: %s' % (l, raw)) l.callback(root, raw) # no try-except; fail loudly if you must! def _dispatch_error(self, err): with self._lock: listeners = list(self._listeners) for l in listeners: logger.debug('dispatching error to %r' % l) try: # here we can be more considerate with catching exceptions l.errback(err) except Exception as e: logger.warning('error dispatching to %r: %r' % (l, e)) def _post_connect(self): "Greeting stuff" init_event = Event() error = [None] # so that err_cb can bind error[0]. just how it is. # callbacks def ok_cb(id, capabilities): self._id = id self._server_capabilities = capabilities init_event.set() def err_cb(err): error[0] = err init_event.set() self.add_listener(NotificationHandler(self._notification_q)) listener = HelloHandler(ok_cb, err_cb) self.add_listener(listener) self.send(HelloHandler.build(self._client_capabilities, self._device_handler)) logger.debug('starting main loop') self.start() # we expect server's hello message, if server doesn't responds in 60 seconds raise exception init_event.wait(60) if not init_event.is_set(): raise SessionError("Capability exchange timed out") # received hello message or an error happened self.remove_listener(listener) if error[0]: raise error[0] #if ':base:1.0' not in self.server_capabilities: # raise MissingCapabilityError(':base:1.0') logger.info('initialized: session-id=%s | server_capabilities=%s' % (self._id, self._server_capabilities)) def add_listener(self, listener): """Register a listener that will be notified of incoming messages and errors. :type listener: :class:`SessionListener` """ logger.debug('installing listener %r' % listener) if not isinstance(listener, SessionListener): raise SessionError("Listener must be a SessionListener type") with self._lock: self._listeners.add(listener) def remove_listener(self, listener): """Unregister some listener; ignore if the listener was never registered. :type listener: :class:`SessionListener` """ logger.debug('discarding listener %r' % listener) with self._lock: self._listeners.discard(listener) def get_listener_instance(self, cls): """If a listener of the specified type is registered, returns the instance. :type cls: :class:`SessionListener` """ with self._lock: for listener in self._listeners: if isinstance(listener, cls): return listener def connect(self, *args, **kwds): # subclass implements raise NotImplementedError def run(self): # subclass implements raise NotImplementedError def send(self, message): """Send the supplied *message* (xml string) to NETCONF server.""" if not self.connected: raise TransportError('Not connected to NETCONF server') logger.debug('queueing %s' % message) self._q.put(message) def scp(self): raise NotImplementedError ### Properties def take_notification(self, block, timeout): try: return self._notification_q.get(block, timeout) except Empty: return None @property def connected(self): "Connection status of the session." return self._connected @property def client_capabilities(self): "Client's :class:`Capabilities`" return self._client_capabilities @property def server_capabilities(self): "Server's :class:`Capabilities`" return self._server_capabilities @property def id(self): """A string representing the `session-id`. If the session has not been initialized it will be `None`""" return self._id class SessionListener(object): """Base class for :class:`Session` listeners, which are notified when a new NETCONF message is received or an error occurs. .. note:: Avoid time-intensive tasks in a callback's context. """ def callback(self, root, raw): """Called when a new XML document is received. The *root* argument allows the callback to determine whether it wants to further process the document. Here, *root* is a tuple of *(tag, attributes)* where *tag* is the qualified name of the root element and *attributes* is a dictionary of its attributes (also qualified names). *raw* will contain the XML document as a string. """ raise NotImplementedError def errback(self, ex): """Called when an error occurs. :type ex: :exc:`Exception` """ raise NotImplementedError class HelloHandler(SessionListener): def __init__(self, init_cb, error_cb): self._init_cb = init_cb self._error_cb = error_cb def callback(self, root, raw): tag, attrs = root if (tag == qualify("hello")) or (tag == "hello"): try: id, capabilities = HelloHandler.parse(raw) except Exception as e: self._error_cb(e) else: self._init_cb(id, capabilities) def errback(self, err): self._error_cb(err) @staticmethod def build(capabilities, device_handler): "Given a list of capability URI's returns message XML string" if device_handler: # This is used as kwargs dictionary for lxml's Element() function. # Therefore the arg-name ("nsmap") is used as key here. xml_namespace_kwargs = { "nsmap" : device_handler.get_xml_base_namespace_dict() } else: xml_namespace_kwargs = {} hello = new_ele("hello", **xml_namespace_kwargs) caps = sub_ele(hello, "capabilities") def fun(uri): sub_ele(caps, "capability").text = uri #python3 changes if sys.version < '3': map(fun, capabilities) else: list(map(fun, capabilities)) return to_xml(hello) @staticmethod def parse(raw): "Returns tuple of (session-id (str), capabilities (Capabilities)" sid, capabilities = 0, [] root = to_ele(raw) for child in root.getchildren(): if child.tag == qualify("session-id") or child.tag == "session-id": sid = child.text elif child.tag == qualify("capabilities") or child.tag == "capabilities" : for cap in child.getchildren(): if cap.tag == qualify("capability") or cap.tag == "capability": capabilities.append(cap.text) return sid, Capabilities(capabilities) class NotificationHandler(SessionListener): def __init__(self, notification_q): self._notification_q = notification_q def callback(self, root, raw): tag, _ = root if tag == qualify('notification', NETCONF_NOTIFICATION_NS): self._notification_q.put(Notification(raw)) def errback(self, _): pass ncclient-0.6.0/ncclient/transport/ssh.py000066400000000000000000000614111331651044100203170ustar00rootroot00000000000000# Copyright 2012 Vaibhav Bajpai # Copyright 2009 Shikhar Bhushan # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys import socket import getpass import threading from binascii import hexlify from lxml import etree from select import select from ncclient.capabilities import Capabilities import paramiko from ncclient.transport.errors import AuthenticationError, SessionCloseError, SSHError, SSHUnknownHostError from ncclient.transport.session import Session from ncclient.xml_ import * import logging logger = logging.getLogger("ncclient.transport.ssh") BUF_SIZE = 4096 # v1.0: RFC 4742 MSG_DELIM = "]]>]]>" MSG_DELIM_LEN = len(MSG_DELIM) # v1.1: RFC 6242 END_DELIM = '\n##\n' TICK = 0.1 def default_unknown_host_cb(host, fingerprint): """An unknown host callback returns `True` if it finds the key acceptable, and `False` if not. This default callback always returns `False`, which would lead to :meth:`connect` raising a :exc:`SSHUnknownHost` exception. Supply another valid callback if you need to verify the host key programmatically. *host* is the hostname that needs to be verified *fingerprint* is a hex string representing the host key fingerprint, colon-delimited e.g. `"4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21"` """ return False def _colonify(fp): fp = fp.decode('UTF-8') finga = fp[:2] for idx in range(2, len(fp), 2): finga += ":" + fp[idx:idx+2] return finga if sys.version < '3': def textify(buf): return buf else: def textify(buf): return buf.decode('UTF-8') if sys.version < '3': from six import StringIO else: from io import BytesIO as StringIO class SSHSession(Session): "Implements a :rfc:`4742` NETCONF session over SSH." def __init__(self, device_handler): capabilities = Capabilities(device_handler.get_capabilities()) Session.__init__(self, capabilities) self._host_keys = paramiko.HostKeys() self._transport = None self._connected = False self._channel = None self._channel_id = None self._channel_name = None self._buffer = StringIO() # parsing-related, see _parse() self._device_handler = device_handler self._parsing_state10 = 0 self._parsing_pos10 = 0 self._parsing_pos11 = 0 self._parsing_state11 = 0 self._expchunksize = 0 self._curchunksize = 0 self._inendpos = 0 self._size_num_list = [] self._message_list = [] def _parse(self): "Messages ae delimited by MSG_DELIM. The buffer could have grown by a maximum of BUF_SIZE bytes everytime this method is called. Retains state across method calls and if a byte has been read it will not be considered again." return self._parse10() def _parse10(self): """Messages are delimited by MSG_DELIM. The buffer could have grown by a maximum of BUF_SIZE bytes everytime this method is called. Retains state across method calls and if a chunk has been read it will not be considered again.""" logger.debug("parsing netconf v1.0") buf = self._buffer buf.seek(self._parsing_pos10) if MSG_DELIM in buf.read().decode('UTF-8'): buf.seek(0) msg, _, remaining = buf.read().decode('UTF-8').partition(MSG_DELIM) msg = msg.strip() if sys.version < '3': self._dispatch_message(msg.encode()) else: self._dispatch_message(msg) # create new buffer which contains remaining of old buffer self._buffer = StringIO() self._buffer.write(remaining.encode()) self._parsing_pos10 = 0 if len(remaining) > 0: # There could be another entire message in the # buffer, so we should try to parse again. logger.debug('Trying another round of parsing since there is still data') self._parse10() else: # handle case that MSG_DELIM is split over two chunks self._parsing_pos10 = buf.tell() - MSG_DELIM_LEN if self._parsing_pos10 < 0: self._parsing_pos10 = 0 def _parse11(self): logger.debug("parsing netconf v1.1") expchunksize = self._expchunksize curchunksize = self._curchunksize idle, instart, inmsg, inbetween, inend = range(5) state = self._parsing_state11 inendpos = self._inendpos num_list = self._size_num_list MAX_STARTCHUNK_SIZE = 12 # \#+4294967295+\n pre = 'invalid base:1:1 frame' buf = self._buffer buf.seek(self._parsing_pos11) message_list = self._message_list # a message is a list of chunks chunk_list = [] # a chunk is a list of characters should_recurse = False while True: x = buf.read(1) if not x: logger.debug('No more data to read') # Store the current chunk to the message list chunk = b''.join(chunk_list) message_list.append(textify(chunk)) break # done reading logger.debug('x: %s', x) if state == idle: if x == b'\n': state = instart inendpos = 1 else: logger.debug('%s (%s: expect newline)'%(pre, state)) raise Exception elif state == instart: if inendpos == 1: if x == b'#': inendpos += 1 else: logger.debug('%s (%s: expect "#")'%(pre, state)) raise Exception elif inendpos == 2: if x.isdigit(): inendpos += 1 # == 3 now # num_list.append(x) else: logger.debug('%s (%s: expect digit)'%(pre, state)) raise Exception else: if inendpos == MAX_STARTCHUNK_SIZE: logger.debug('%s (%s: no. too long)'%(pre, state)) raise Exception elif x == b'\n': num = b''.join(num_list) num_list = [] # Reset num_list try: num = int(num) except: logger.debug('%s (%s: invalid no.)'%(pre, state)) raise Exception else: state = inmsg expchunksize = num logger.debug('response length: %d'%expchunksize) curchunksize = 0 inendpos += 1 elif x.isdigit(): inendpos += 1 # > 3 now # num_list.append(x) else: logger.debug('%s (%s: expect digit)'%(pre, state)) raise Exception elif state == inmsg: chunk_list.append(x) curchunksize += 1 chunkleft = expchunksize - curchunksize if chunkleft == 0: inendpos = 0 state = inbetween chunk = b''.join(chunk_list) message_list.append(textify(chunk)) chunk_list = [] # Reset chunk_list logger.debug('parsed new chunk: %s'%(chunk)) elif state == inbetween: if inendpos == 0: if x == b'\n': inendpos += 1 else: logger.debug('%s (%s: expect newline)'%(pre, state)) raise Exception elif inendpos == 1: if x == b'#': inendpos += 1 else: logger.debug('%s (%s: expect "#")'%(pre, state)) raise Exception else: inendpos += 1 # == 3 now # if x == b'#': state = inend elif x.isdigit(): # More trunks state = instart num_list = [] num_list.append(x) else: logger.debug('%s (%s: expect "#")'%(pre, state)) raise Exception elif state == inend: if inendpos == 3: if x == b'\n': inendpos = 0 state = idle logger.debug('dispatching message') self._dispatch_message(''.join(message_list)) # reset rest = buf.read() buf = BytesIO() buf.write(rest) buf.seek(0) message_list = [] self._message_list = message_list chunk_list = [] expchunksize = chunksize = 0 parsing_state11 = idle inendpos = parsing_pos11 = 0 # There could be another entire message in the # buffer, so we should try to parse again. should_recurse = True break else: logger.debug('%s (%s: expect newline)'%(pre, state)) raise Exception else: logger.debug('%s (%s invalid state)'%(pre, state)) raise Exception self._expchunksize = expchunksize self._curchunksize = curchunksize self._parsing_state11 = state self._inendpos = inendpos self._size_num_list = num_list self._buffer = buf self._parsing_pos11 = self._buffer.tell() logger.debug('parse11 ending ...') if should_recurse: logger.debug('Trying another round of parsing since there is still data') self._parse11() def load_known_hosts(self, filename=None): """Load host keys from an openssh :file:`known_hosts`-style file. Can be called multiple times. If *filename* is not specified, looks in the default locations i.e. :file:`~/.ssh/known_hosts` and :file:`~/ssh/known_hosts` for Windows. """ if filename is None: filename = os.path.expanduser('~/.ssh/known_hosts') try: self._host_keys.load(filename) except IOError: # for windows filename = os.path.expanduser('~/ssh/known_hosts') try: self._host_keys.load(filename) except IOError: pass else: self._host_keys.load(filename) def close(self): if self._transport.is_active(): self._transport.close() # Wait for the transport thread to close. while self.is_alive() and (self is not threading.current_thread()): self.join(10) self._channel = None self._connected = False # REMEMBER to update transport.rst if sig. changes, since it is hardcoded there def connect(self, host, port=830, timeout=None, unknown_host_cb=default_unknown_host_cb, username=None, password=None, key_filename=None, allow_agent=True, hostkey_verify=True, look_for_keys=True, ssh_config=None, sock_fd=None): """Connect via SSH and initialize the NETCONF session. First attempts the publickey authentication method and then password authentication. To disable attempting publickey authentication altogether, call with *allow_agent* and *look_for_keys* as `False`. *host* is the hostname or IP address to connect to *port* is by default 830, but some devices use the default SSH port of 22 so this may need to be specified *timeout* is an optional timeout for socket connect *unknown_host_cb* is called when the server host key is not recognized. It takes two arguments, the hostname and the fingerprint (see the signature of :func:`default_unknown_host_cb`) *username* is the username to use for SSH authentication *password* is the password used if using password authentication, or the passphrase to use for unlocking keys that require it *key_filename* is a filename where a the private key to be used can be found *allow_agent* enables querying SSH agent (if found) for keys *hostkey_verify* enables hostkey verification from ~/.ssh/known_hosts *look_for_keys* enables looking in the usual locations for ssh keys (e.g. :file:`~/.ssh/id_*`) *ssh_config* enables parsing of an OpenSSH configuration file, if set to its path, e.g. :file:`~/.ssh/config` or to True (in this case, use :file:`~/.ssh/config`). *sock_fd* is an already open socket which shall be used for this connection. Useful for NETCONF outbound ssh. Use host=None together with a valid sock_fd number """ if not (host or sock_fd): raise SSHError("Missing host or socket fd") # Optionally, parse .ssh/config config = {} if ssh_config is True: ssh_config = "~/.ssh/config" if sys.platform != "win32" else "~/ssh/config" if ssh_config is not None: config = paramiko.SSHConfig() config.parse(open(os.path.expanduser(ssh_config))) config = config.lookup(host) host = config.get("hostname", host) if username is None: username = config.get("user") if key_filename is None: key_filename = config.get("identityfile") if username is None: username = getpass.getuser() if sock_fd is None: if config.get("proxycommand"): sock = paramiko.proxy.ProxyCommand(config.get("proxycommand")) else: for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res try: sock = socket.socket(af, socktype, proto) sock.settimeout(timeout) except socket.error: continue try: sock.connect(sa) except socket.error: sock.close() continue break else: raise SSHError("Could not open socket to %s:%s" % (host, port)) else: if sys.version_info[0] < 3: s = socket.fromfd(int(sock_fd), socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, _sock=s) else: sock = socket.fromfd(int(sock_fd), socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) t = self._transport = paramiko.Transport(sock) t.set_log_channel(logger.name) if config.get("compression") == 'yes': t.use_compression() try: t.start_client() except paramiko.SSHException: raise SSHError('Negotiation failed') # host key verification server_key = t.get_remote_server_key() fingerprint = _colonify(hexlify(server_key.get_fingerprint())) if hostkey_verify: known_host = self._host_keys.check(host, server_key) if not known_host and not unknown_host_cb(host, fingerprint): raise SSHUnknownHostError(host, fingerprint) if key_filename is None: key_filenames = [] elif isinstance(key_filename, (str, bytes)): key_filenames = [ key_filename ] else: key_filenames = key_filename self._auth(username, password, key_filenames, allow_agent, look_for_keys) self._connected = True # there was no error authenticating # TODO: leopoul: Review, test, and if needed rewrite this part subsystem_names = self._device_handler.get_ssh_subsystem_names() for subname in subsystem_names: c = self._channel = self._transport.open_session() self._channel_id = c.get_id() channel_name = "%s-subsystem-%s" % (subname, str(self._channel_id)) c.set_name(channel_name) try: c.invoke_subsystem(subname) except paramiko.SSHException as e: logger.info("%s (subsystem request rejected)", e) handle_exception = self._device_handler.handle_connection_exceptions(self) # Ignore the exception, since we continue to try the different # subsystem names until we find one that can connect. #have to handle exception for each vendor here if not handle_exception: continue self._channel_name = c.get_name() self._post_connect() return raise SSHError("Could not open connection, possibly due to unacceptable" " SSH subsystem name.") def _auth(self, username, password, key_filenames, allow_agent, look_for_keys): saved_exception = None for key_filename in key_filenames: for cls in (paramiko.RSAKey, paramiko.DSSKey, paramiko.ECDSAKey): try: key = cls.from_private_key_file(key_filename, password) logger.debug("Trying key %s from %s" % (hexlify(key.get_fingerprint()), key_filename)) self._transport.auth_publickey(username, key) return except Exception as e: saved_exception = e logger.debug(e) if allow_agent: for key in paramiko.Agent().get_keys(): try: logger.debug("Trying SSH agent key %s" % hexlify(key.get_fingerprint())) self._transport.auth_publickey(username, key) return except Exception as e: saved_exception = e logger.debug(e) keyfiles = [] if look_for_keys: rsa_key = os.path.expanduser("~/.ssh/id_rsa") dsa_key = os.path.expanduser("~/.ssh/id_dsa") ecdsa_key = os.path.expanduser("~/.ssh/id_ecdsa") if os.path.isfile(rsa_key): keyfiles.append((paramiko.RSAKey, rsa_key)) if os.path.isfile(dsa_key): keyfiles.append((paramiko.DSSKey, dsa_key)) if os.path.isfile(ecdsa_key): keyfiles.append((paramiko.ECDSAKey, ecdsa_key)) # look in ~/ssh/ for windows users: rsa_key = os.path.expanduser("~/ssh/id_rsa") dsa_key = os.path.expanduser("~/ssh/id_dsa") ecdsa_key = os.path.expanduser("~/ssh/id_ecdsa") if os.path.isfile(rsa_key): keyfiles.append((paramiko.RSAKey, rsa_key)) if os.path.isfile(dsa_key): keyfiles.append((paramiko.DSSKey, dsa_key)) if os.path.isfile(ecdsa_key): keyfiles.append((paramiko.ECDSAKey, ecdsa_key)) for cls, filename in keyfiles: try: key = cls.from_private_key_file(filename, password) logger.debug("Trying discovered key %s in %s" % (hexlify(key.get_fingerprint()), filename)) self._transport.auth_publickey(username, key) return except Exception as e: saved_exception = e logger.debug(e) if password is not None: try: self._transport.auth_password(username, password) return except Exception as e: saved_exception = e logger.debug(e) if saved_exception is not None: # need pep-3134 to do this right raise AuthenticationError(repr(saved_exception)) raise AuthenticationError("No authentication methods available") def run(self): chan = self._channel q = self._q def start_delim(data_len): return '\n#%s\n'%(data_len) try: while True: # select on a paramiko ssh channel object does not ever return it in the writable list, so channels don't exactly emulate the socket api r, w, e = select([chan], [], [], TICK) # will wakeup evey TICK seconds to check if something to send, more if something to read (due to select returning chan in readable list) if r: data = chan.recv(BUF_SIZE) if data: self._buffer.write(data) if self._server_capabilities: if 'urn:ietf:params:netconf:base:1.1' in self._server_capabilities and 'urn:ietf:params:netconf:base:1.1' in self._client_capabilities: logger.debug("Selecting netconf:base:1.1 for encoding") self._parse11() elif 'urn:ietf:params:netconf:base:1.0' in self._server_capabilities or 'urn:ietf:params:xml:ns:netconf:base:1.0' in self._server_capabilities or 'urn:ietf:params:netconf:base:1.0' in self._client_capabilities: logger.debug("Selecting netconf:base:1.0 for encoding") self._parse10() else: raise Exception else: self._parse10() # HELLO msg uses EOM markers. else: raise SessionCloseError(self._buffer.getvalue()) if not q.empty() and chan.send_ready(): logger.debug("Sending message") data = q.get() try: # send a HELLO msg using v1.0 EOM markers. validated_element(data, tags='{urn:ietf:params:xml:ns:netconf:base:1.0}hello') data = "%s%s"%(data, MSG_DELIM) except XMLError: # this is not a HELLO msg # we publish v1.1 support if 'urn:ietf:params:netconf:base:1.1' in self._client_capabilities: if self._server_capabilities: if 'urn:ietf:params:netconf:base:1.1' in self._server_capabilities: # send using v1.1 chunked framing data = "%s%s%s"%(start_delim(len(data)), data, END_DELIM) elif 'urn:ietf:params:netconf:base:1.0' in self._server_capabilities or 'urn:ietf:params:xml:ns:netconf:base:1.0' in self._server_capabilities: # send using v1.0 EOM markers data = "%s%s"%(data, MSG_DELIM) else: raise Exception else: logger.debug('HELLO msg was sent, but server capabilities are still not known') raise Exception # we publish only v1.0 support else: # send using v1.0 EOM markers data = "%s%s"%(data, MSG_DELIM) finally: logger.debug("Sending: %s", data) while data: n = chan.send(data) if n <= 0: raise SessionCloseError(self._buffer.getvalue(), data) data = data[n:] except Exception as e: logger.debug("Broke out of main loop, error=%r", e) self._dispatch_error(e) self.close() @property def transport(self): "Underlying `paramiko.Transport `_ object. This makes it possible to call methods like :meth:`~paramiko.Transport.set_keepalive` on it." return self._transport ncclient-0.6.0/ncclient/transport/third_party/000077500000000000000000000000001331651044100214765ustar00rootroot00000000000000ncclient-0.6.0/ncclient/transport/third_party/__init__.py000066400000000000000000000000271331651044100236060ustar00rootroot00000000000000__author__ = 'katharh' ncclient-0.6.0/ncclient/transport/third_party/junos/000077500000000000000000000000001331651044100226345ustar00rootroot00000000000000ncclient-0.6.0/ncclient/transport/third_party/junos/__init__.py000066400000000000000000000000271331651044100247440ustar00rootroot00000000000000__author__ = 'katharh' ncclient-0.6.0/ncclient/transport/third_party/junos/ioproc.py000066400000000000000000000055471331651044100245140ustar00rootroot00000000000000import os import sys import re if sys.version < '3': from cStringIO import StringIO else: from io import StringIO from select import select if sys.version>='2.7': from subprocess import Popen, check_output, PIPE, STDOUT else: from subprocess import Popen, PIPE, STDOUT from ncclient.transport.errors import SessionCloseError, TransportError, PermissionError from ncclient.transport.ssh import SSHSession MSG_DELIM = "]]>]]>" NETCONF_SHELL = 'xml-mode netconf need-trailer' class IOProc(SSHSession): def __init__(self, device_handler): SSHSession.__init__(self, device_handler) self._host_keys = None self._transport = None self._connected = False self._channel = None self._channel_id = None self._channel_name = None self._buffer = StringIO() # for incoming data # parsing-related, see _parse() self._parsing_state = 0 self._parsing_pos = 0 self._device_handler = device_handler def close(self): self._channel.wait() self._channel = None self._connected = False def connect(self): stdoutdata = check_output(NETCONF_SHELL, shell=True, stdin=PIPE, stderr=STDOUT) if 'error: Restricted user session' in stdoutdata: obj = re.search(r'\n?(.*)\n?', stdoutdata, re.M) if obj: raise PermissionError(obj.group(1)) else: raise PermissionError('Restricted user session') elif 'xml-mode: command not found' in stdoutdata: raise PermissionError('xml-mode: command not found') self._channel = Popen(NETCONF_SHELL, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT) self._connected = True self._channel_id = self._channel.pid self._channel_name = "netconf-shell" self._post_connect() return def run(self): chan = self._channel q = self._q try: while True: # write data = q.get() + MSG_DELIM chan.stdin.write(data) chan.stdin.flush() # read data = [] while True: line = chan.stdout.readline() data.append(line) if MSG_DELIM in line: break self._buffer.write(b''.join(data)) self._parse() except Exception as e: self._dispatch_error(e) self.close() @property def transport(self): "Underlying `paramiko.Transport `_ object. This makes it possible to call methods like :meth:`~paramiko.Transport.set_keepalive` on it." return self._transport ncclient-0.6.0/ncclient/xml_.py000066400000000000000000000176621331651044100164360ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # Copyright 2011 Leonidas Poulopoulos # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. "Methods for creating, parsing, and dealing with XML and ElementTree objects." import io import sys import six from six import StringIO from io import BytesIO from lxml import etree # In case issues come up with XML generation/parsing # make sure you have the ElementTree v1.2.7+ lib as # well as lxml v3.0+ from ncclient import NCClientError parser = etree.XMLParser(recover=False) class XMLError(NCClientError): pass ### Namespace-related #: Base NETCONF namespace BASE_NS_1_0 = "urn:ietf:params:xml:ns:netconf:base:1.0" # NXOS_1_0 NXOS_1_0 = "http://www.cisco.com/nxos:1.0" # NXOS_IF NXOS_IF = "http://www.cisco.com/nxos:1.0:if_manager" #: Namespace for Tail-f core data model TAILF_AAA_1_1 = "http://tail-f.com/ns/aaa/1.1" #: Namespace for Tail-f execd data model TAILF_EXECD_1_1 = "http://tail-f.com/ns/execd/1.1" #: Namespace for Cisco data model CISCO_CPI_1_0 = "http://www.cisco.com/cpi_10/schema" #: Namespace for Flowmon data model FLOWMON_1_0 = "http://www.liberouter.org/ns/netopeer/flowmon/1.0" #: Namespace for Juniper 9.6R4. Tested with Junos 9.6R4+ JUNIPER_1_1 = "http://xml.juniper.net/xnm/1.1/xnm" #: Namespace for Huawei data model HUAWEI_NS = "http://www.huawei.com/netconf/vrp" #: Namespace for Huawei private HW_PRIVATE_NS = "http://www.huawei.com/netconf/capability/base/1.0" #: Namespace for H3C data model H3C_DATA_1_0 = "http://www.h3c.com/netconf/data:1.0" #: Namespace for H3C config model H3C_CONFIG_1_0 = "http://www.h3c.com/netconf/config:1.0" #: Namespace for H3C action model H3C_ACTION_1_0 = "http://www.h3c.com/netconf/action:1.0" #: Namespace for netconf monitoring NETCONF_MONITORING_NS = "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring" #: Namespace for netconf notifications NETCONF_NOTIFICATION_NS = "urn:ietf:params:xml:ns:netconf:notification:1.0" #: Namespace for netconf with-defaults (RFC 6243) NETCONF_WITH_DEFAULTS_NS = "urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults" # try: register_namespace = etree.register_namespace except AttributeError: def register_namespace(prefix, uri): from xml.etree import ElementTree # cElementTree uses ElementTree's _namespace_map, so that's ok ElementTree._namespace_map[uri] = prefix for (ns, pre) in six.iteritems({ BASE_NS_1_0: 'nc', NETCONF_MONITORING_NS: 'ncm', NXOS_1_0: 'nxos', NXOS_IF: 'if', TAILF_AAA_1_1: 'aaa', TAILF_EXECD_1_1: 'execd', CISCO_CPI_1_0: 'cpi', FLOWMON_1_0: 'fm', JUNIPER_1_1: 'junos', }): register_namespace(pre, ns) qualify = lambda tag, ns=BASE_NS_1_0: tag if ns is None else "{%s}%s" % (ns, tag) """Qualify a *tag* name with a *namespace*, in :mod:`~xml.etree.ElementTree` fashion i.e. *{namespace}tagname*.""" def to_xml(ele, encoding="UTF-8", pretty_print=False): "Convert and return the XML for an *ele* (:class:`~xml.etree.ElementTree.Element`) with specified *encoding*." xml = etree.tostring(ele, encoding=encoding, pretty_print=pretty_print) if sys.version < '3': return xml if xml.startswith('%s' % (encoding, xml) else: return xml.decode('UTF-8') if xml.startswith(b'%s' % (encoding, xml.decode('UTF-8')) def to_ele(x): "Convert and return the :class:`~xml.etree.ElementTree.Element` for the XML document *x*. If *x* is already an :class:`~xml.etree.ElementTree.Element` simply returns that." if sys.version < '3': return x if etree.iselement(x) else etree.fromstring(x, parser=parser) else: return x if etree.iselement(x) else etree.fromstring(x.encode('UTF-8'), parser=parser) def parse_root(raw): "Efficiently parses the root element of a *raw* XML document, returning a tuple of its qualified name and attribute dictionary." if sys.version < '3': fp = StringIO(raw) else: fp = BytesIO(raw.encode('UTF-8')) for event, element in etree.iterparse(fp, events=('start',)): return (element.tag, element.attrib) def validated_element(x, tags=None, attrs=None): """Checks if the root element of an XML document or Element meets the supplied criteria. *tags* if specified is either a single allowable tag name or sequence of allowable alternatives *attrs* if specified is a sequence of required attributes, each of which may be a sequence of several allowable alternatives Raises :exc:`XMLError` if the requirements are not met. """ ele = to_ele(x) if tags: if isinstance(tags, (str, bytes)): tags = [tags] if ele.tag not in tags: raise XMLError("Element [%s] does not meet requirement" % ele.tag) if attrs: for req in attrs: if isinstance(req, (str, bytes)): req = [req] for alt in req: if alt in ele.attrib: break else: raise XMLError("Element [%s] does not have required attributes" % ele.tag) return ele XPATH_NAMESPACES = { 're':'http://exslt.org/regular-expressions' } class NCElement(object): def __init__(self, result, transform_reply): self.__result = result self.__transform_reply = transform_reply self.__doc = self.remove_namespaces(self.__result) def xpath(self, expression): """ return result for a call to lxml xpath() output will be a list """ self.__expression = expression self.__namespaces = XPATH_NAMESPACES return self.__doc.xpath(self.__expression, namespaces=self.__namespaces) def find(self, expression): """return result for a call to lxml ElementPath find()""" self.__expression = expression return self.__doc.find(self.__expression) def findtext(self, expression): """return result for a call to lxml ElementPath findtext()""" self.__expression = expression return self.__doc.findtext(self.__expression) def __str__(self): """syntactic sugar for str() - alias to tostring""" if sys.version<'3': return self.tostring else: return self.tostring.decode('UTF-8') @property def tostring(self): """return a pretty-printed string output for rpc reply""" parser = etree.XMLParser(remove_blank_text=True) outputtree = etree.XML(etree.tostring(self.__doc), parser) return etree.tostring(outputtree, pretty_print=True) @property def data_xml(self): """return an unmodified output for rpc reply""" return to_xml(self.__doc) def remove_namespaces(self, rpc_reply): """remove xmlns attributes from rpc reply""" self.__xslt=self.__transform_reply self.__parser = etree.XMLParser(remove_blank_text=True) self.__xslt_doc = etree.parse(io.BytesIO(self.__xslt), self.__parser) self.__transform = etree.XSLT(self.__xslt_doc) self.__root = etree.fromstring(str(self.__transform(etree.parse(StringIO(str(rpc_reply)))))) return self.__root new_ele = lambda tag, attrs={}, **extra: etree.Element(qualify(tag), attrs, **extra) new_ele_ns = lambda tag, ns, attrs={}, **extra: etree.Element(qualify(tag,ns), attrs, **extra) sub_ele = lambda parent, tag, attrs={}, **extra: etree.SubElement(parent, qualify(tag), attrs, **extra) sub_ele_ns = lambda parent, tag, ns, attrs={}, **extra: etree.SubElement(parent, qualify(tag, ns), attrs, **extra) ncclient-0.6.0/requirements.txt000066400000000000000000000000601331651044100165720ustar00rootroot00000000000000setuptools>0.6 paramiko>=1.15.0 lxml>=3.3.0 six ncclient-0.6.0/setup.py000066400000000000000000000045311331651044100150270ustar00rootroot00000000000000# Copyright 2009 Shikhar Bhushan # Copyright 201[2-5] Leonidas Poulopoulos (@leopoul) # Copyright 2013 Ebben Aries # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from setuptools import setup, find_packages from distutils.command.install import install as _install import sys import platform import codecs if sys.version_info.major == 2 and sys.version_info.minor < 7: print ("Sorry, Python < 2.7 is not supported") exit() #parse requirements req_lines = [line.strip() for line in open("requirements.txt").readlines()] install_reqs = list(filter(None, req_lines)) with codecs.open('README.rst', 'r', encoding='utf8') as file: long_description = file.read() setup(name='ncclient', version='0.6.0', description="Python library for NETCONF clients", long_description = long_description, author="Shikhar Bhushan, Leonidas Poulopoulos, Ebben Aries", author_email="shikhar@schmizz.net, lpoulopoulos@verisign.com, earies@juniper.net", url="http://ncclient.org", packages=find_packages('.'), install_requires=install_reqs, license="Apache License 2.0", platforms=["Posix; OS X; Windows"], keywords=('NETCONF', 'NETCONF Python client', 'Juniper Optimization', 'Cisco NXOS Optimization'), python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Topic :: System :: Networking', 'Intended Audience :: Developers', 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) ncclient-0.6.0/test/000077500000000000000000000000001331651044100142715ustar00rootroot00000000000000ncclient-0.6.0/test/unit/000077500000000000000000000000001331651044100152505ustar00rootroot00000000000000ncclient-0.6.0/test/unit/__init__.py000066400000000000000000000000261331651044100173570ustar00rootroot00000000000000__author__ = 'palash' ncclient-0.6.0/test/unit/devices/000077500000000000000000000000001331651044100166725ustar00rootroot00000000000000ncclient-0.6.0/test/unit/devices/__init__.py000066400000000000000000000000001331651044100207710ustar00rootroot00000000000000ncclient-0.6.0/test/unit/devices/test_default.py000066400000000000000000000033631331651044100217340ustar00rootroot00000000000000import unittest from ncclient.devices.default import DefaultDeviceHandler capabilities = ['urn:ietf:params:netconf:base:1.0', 'urn:ietf:params:netconf:base:1.1', 'urn:ietf:params:netconf:capability:writable-running:1.0', 'urn:ietf:params:netconf:capability:candidate:1.0', 'urn:ietf:params:netconf:capability:confirmed-commit:1.0', 'urn:ietf:params:netconf:capability:rollback-on-error:1.0', 'urn:ietf:params:netconf:capability:startup:1.0', 'urn:ietf:params:netconf:capability:url:1.0?scheme=http,ftp,file,https,sftp', 'urn:ietf:params:netconf:capability:validate:1.0', 'urn:ietf:params:netconf:capability:xpath:1.0', 'urn:ietf:params:netconf:capability:notification:1.0', 'urn:liberouter:params:netconf:capability:power-control:1.0', 'urn:ietf:params:netconf:capability:interleave:1.0', 'urn:ietf:params:netconf:capability:with-defaults:1.0'] class TestDefaultDevice(unittest.TestCase): def setUp(self): self.obj = DefaultDeviceHandler() def test_get_capabilties(self): self.assertEqual(self.obj.get_capabilities(), capabilities) def test_get_ssh_subsystem_names(self): self.assertEqual(self.obj.get_ssh_subsystem_names(), ["netconf"]) def test_perform_qualify_check(self): self.assertTrue(self.obj.perform_qualify_check()) def test_handle_raw_dispatch(self): self.assertFalse(self.obj.handle_raw_dispatch(None)) def test_handle_connection_exceptions(self): self.assertFalse(self.obj.handle_connection_exceptions(None)) suite = unittest.TestSuite() unittest.TextTestRunner().run(suite) ncclient-0.6.0/test/unit/devices/test_junos.py000066400000000000000000000054241331651044100214460ustar00rootroot00000000000000import unittest from ncclient.devices.junos import * import ncclient.transport from mock import patch import paramiko import sys xml = ''' ''' xml2 = """ reX """ xml3 = """ reX """ class TestJunosDevice(unittest.TestCase): def setUp(self): self.obj = JunosDeviceHandler({'name': 'junos'}) @patch('paramiko.Channel.exec_command') @patch('paramiko.Transport.__init__') @patch('paramiko.Transport.open_channel') def test_handle_connection_exceptions( self, mock_open, mock_init, mock_channel): session = ncclient.transport.SSHSession(self.obj) session._channel_id = 100 mock_init.return_value = None session._transport = paramiko.Transport() channel = paramiko.Channel(100) mock_open.return_value = channel self.obj.handle_connection_exceptions(session) self.assertEqual(channel._name, "netconf-command-100") self.assertEqual( mock_channel.call_args_list[0][0][0], "xml-mode netconf need-trailer") def test_additional_operations(self): dict = {} dict["rpc"] = ExecuteRpc dict["get_configuration"] = GetConfiguration dict["load_configuration"] = LoadConfiguration dict["compare_configuration"] = CompareConfiguration dict["command"] = Command dict["reboot"] = Reboot dict["halt"] = Halt dict["commit"] = Commit dict["rollback"] = Rollback self.assertEqual(dict, self.obj.add_additional_operations()) def test_transform_reply(self): if sys.version >= '3': reply = xml.encode('utf-8') else: reply = xml self.assertEqual(self.obj.transform_reply(), reply) def test_perform_quality_check(self): self.assertFalse(self.obj.perform_qualify_check()) ncclient-0.6.0/test/unit/operations/000077500000000000000000000000001331651044100174335ustar00rootroot00000000000000ncclient-0.6.0/test/unit/operations/__init__.py000066400000000000000000000000001331651044100215320ustar00rootroot00000000000000ncclient-0.6.0/test/unit/operations/test_edit.py000066400000000000000000000207001331651044100217700ustar00rootroot00000000000000from ncclient.operations.edit import * import unittest from mock import patch, MagicMock from ncclient import manager import ncclient.manager import ncclient.transport from ncclient.xml_ import * from ncclient.operations import RaiseMode from xml.etree import ElementTree from ncclient.operations.errors import MissingCapabilityError import copy class TestEdit(unittest.TestCase): def setUp(self): self.device_handler = manager.make_device_handler({'name': 'junos'}) @patch('ncclient.operations.edit.RPC._request') def test_edit_config(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) obj = EditConfig( session, self.device_handler, raise_mode=RaiseMode.ALL) root = new_ele('config') configuration = sub_ele(root, 'configuration') system = sub_ele(configuration, 'system') location = sub_ele(system, 'location') sub_ele(location, 'building').text = "Main Campus, A" sub_ele(location, 'floor').text = "5" sub_ele(location, 'rack').text = "27" obj.request(copy.deepcopy(root)) node = new_ele("edit-config") node.append(util.datastore_or_url("target", "candidate")) node.append(validated_element(root, ("config", qualify("config")))) xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.edit.RPC._request') def test_edit_config_2(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [":rollback-on-error", ":validate"] obj = EditConfig( session, self.device_handler, raise_mode=RaiseMode.ALL) root = """ system { host-name foo-bar; } """ obj.request(copy.deepcopy(root), format="text", target="running", error_option="rollback-on-error", default_operation="default", test_option="test") node = new_ele("edit-config") node.append(util.datastore_or_url("target", "running")) sub_ele(node, "error-option").text = "rollback-on-error" sub_ele(node, "test-option").text = "test" sub_ele(node, "default-operation").text = "default" config_text = sub_ele(node, "config-text") sub_ele(config_text, "configuration-text").text = root xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.RPC._request') def test_delete_config(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) obj = DeleteConfig( session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request("candidate") node = new_ele("delete-config") node.append(util.datastore_or_url("target", "candidate")) xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.RPC._request') def test_copy_config(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) obj = CopyConfig( session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request("candidate", "running") node = new_ele("copy-config") node.append(util.datastore_or_url("target", "running")) node.append(util.datastore_or_url("source", "candidate")) xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.RPC._request') def test_copy_config_2(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) obj = CopyConfig( session, self.device_handler, raise_mode=RaiseMode.ALL) source = new_ele('source') config = sub_ele(source, 'config') configuration = sub_ele(config, 'configuration') system = sub_ele(configuration, 'system') location = sub_ele(system, 'location') sub_ele(location, 'building').text = "Main Campus, A" sub_ele(location, 'floor').text = "5" sub_ele(location, 'rack').text = "27" obj.request(copy.deepcopy(source), "candidate") node = new_ele("copy-config") node.append(util.datastore_or_url("target", "candidate")) node.append(validated_element(source, ("source", qualify("source")))) xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.RPC._request') def test_validate_config(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [':validate'] obj = Validate(session, self.device_handler, raise_mode=RaiseMode.ALL) cfg_data = new_ele("config") sub_ele(cfg_data, "data") obj.request(cfg_data) node = new_ele("validate") src = sub_ele(node, "source") cfg = sub_ele(src, "config") sub_ele(cfg, "data") xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.RPC._request') def test_validate_datastore(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [':validate'] obj = Validate(session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request("candidate") node = new_ele("validate") src = sub_ele(node, "source") sub_ele(src, "candidate") xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.RPC._request') def test_commit(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [':candidate', ":confirmed-commit"] obj = Commit(session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request(confirmed=True, timeout="0") node = new_ele("commit") sub_ele(node, "confirmed") sub_ele(node, "confirm-timeout").text = "0" xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.RPC._request') def test_commit_exception(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [':candidate'] obj = Commit(session, self.device_handler, raise_mode=RaiseMode.ALL) self.assertRaises(MissingCapabilityError, obj.request, confirmed=True, timeout="0") @patch('ncclient.operations.RPC._request') def test_discard_changes(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [':candidate', ":confirmed-commit"] obj = DiscardChanges( session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request() node = new_ele("discard-changes") xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.RPC._request') def test_cancel_commit(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [':candidate', ":confirmed-commit"] obj = CancelCommit(session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request(persist_id="foo") node = new_ele("cancel-commit") sub_ele(node, "persist-id").text = "foo" xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) ncclient-0.6.0/test/unit/operations/test_lock.py000066400000000000000000000075241331651044100220040ustar00rootroot00000000000000from ncclient.operations.lock import * import unittest from mock import patch from ncclient import manager import ncclient.manager import ncclient.transport from ncclient.xml_ import * from ncclient.operations import RaiseMode from xml.etree import ElementTree class TestLock(unittest.TestCase): def setUp(self): self.device_handler = manager.make_device_handler({'name': 'junos'}) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.RPC._request') def test_lock_default_param(self, mock_request, mock_session): session = ncclient.transport.SSHSession(self.device_handler) obj = Lock(session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request() node = new_ele("lock") sub_ele(sub_ele(node, "target"), "candidate") xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.RPC._request') def test_lock(self, mock_request, mock_session): session = ncclient.transport.SSHSession(self.device_handler) obj = Lock(session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request(target="running") node = new_ele("lock") sub_ele(sub_ele(node, "target"), "running") xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.RPC._request') def test_unlock_default_param(self, mock_request, mock_session): session = ncclient.transport.SSHSession(self.device_handler) obj = Unlock(session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request() node = new_ele("unlock") sub_ele(sub_ele(node, "target"), "candidate") xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.RPC._request') def test_unlock(self, mock_request, mock_session): session = ncclient.transport.SSHSession(self.device_handler) obj = Unlock(session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request(target="running") node = new_ele("unlock") sub_ele(sub_ele(node, "target"), "running") xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.RPC._request') def test_lock_context_enter(self, mock_request, mock_session): session = ncclient.transport.SSHSession(self.device_handler) obj = LockContext(session, self.device_handler, "candidate") self.assertEqual(obj.__enter__(), obj) node = new_ele("lock") sub_ele(sub_ele(node, "target"), "candidate") xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.RPC._request') def test_lock_context_exit(self, mock_request, mock_session): session = ncclient.transport.SSHSession(self.device_handler) obj = LockContext(session, self.device_handler, "running") self.assertFalse(obj.__exit__()) node = new_ele("unlock") sub_ele(sub_ele(node, "target"), "running") xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) ncclient-0.6.0/test/unit/operations/test_retrieve.py000066400000000000000000000174451331651044100227040ustar00rootroot00000000000000from ncclient.operations.retrieve import * import unittest from mock import patch from ncclient import manager import ncclient.manager import ncclient.transport from ncclient.xml_ import * from ncclient.operations import RaiseMode from ncclient.operations.errors import MissingCapabilityError from xml.etree import ElementTree from lxml import etree import copy class TestRetrieve(unittest.TestCase): def setUp(self): self.device_handler = manager.make_device_handler({'name': 'junos'}) @patch('ncclient.operations.retrieve.RPC._request') def test_get(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) obj = Get(session, self.device_handler, raise_mode=RaiseMode.ALL) root_filter = new_ele('filter') config_filter = sub_ele(root_filter, 'configuration') system_filter = sub_ele(config_filter, 'system') sub_ele(system_filter, 'services') obj.request(copy.deepcopy(root_filter)) node = new_ele("get") node.append(util.build_filter(root_filter)) xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.retrieve.RPC._request') def test_get_with_defaults(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [':with-defaults'] obj = Get(session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request(with_defaults='report-all') expected_xml = etree.fromstring( '' 'report-all' ''.format( base=BASE_NS_1_0, defaults=NETCONF_WITH_DEFAULTS_NS ) ) call = mock_request.call_args_list[0][0][0] self.assertEqual(etree.tostring(call), etree.tostring(expected_xml)) @patch('ncclient.operations.retrieve.RPC._request') def test_get_with_defaults_not_supported(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [] obj = Get(session, self.device_handler, raise_mode=RaiseMode.ALL) self.assertRaises( MissingCapabilityError, obj.request, with_defaults='report-all' ) @patch('ncclient.operations.retrieve.RPC._request') def test_with_defaults_valid_options(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [':with-defaults'] obj = Get(session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request(with_defaults='explicit') obj.request(with_defaults='report-all') obj.request(with_defaults='report-all-tagged') obj.request(with_defaults='trim') self.assertRaises( WithDefaultsError, obj.request, with_defaults='invalid-option' ) @patch('ncclient.operations.retrieve.RPC._request') def test_get_config(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) obj = GetConfig(session, self.device_handler, raise_mode=RaiseMode.ALL) source = "candidate" obj.request(source) node = new_ele("get-config") node.append(util.datastore_or_url("source", source)) xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.retrieve.RPC._request') def test_get_config_with_defaults(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [':with-defaults'] obj = GetConfig(session, self.device_handler, raise_mode=RaiseMode.ALL) source = 'candidate' obj.request(source, with_defaults='explicit') expected_xml = etree.fromstring( '' '' 'explicit' ''.format( base=BASE_NS_1_0, defaults=NETCONF_WITH_DEFAULTS_NS ) ) call = mock_request.call_args_list[0][0][0] self.assertEqual(etree.tostring(call), etree.tostring(expected_xml)) @patch('ncclient.operations.retrieve.RPC._request') def test_get_config_with_defaults_not_supported(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [] obj = GetConfig(session, self.device_handler, raise_mode=RaiseMode.ALL) source = 'candidate' self.assertRaises( MissingCapabilityError, obj.request, source, with_defaults='explicit' ) @patch('ncclient.operations.retrieve.RPC._request') def test_get_schema(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) obj = GetSchema(session, self.device_handler, raise_mode=RaiseMode.ALL) identifier = "foo" version = "1.0" reqformat = "xsd" obj.request(identifier, version, reqformat) node = etree.Element(qualify("get-schema", NETCONF_MONITORING_NS)) id = etree.SubElement(node, qualify("identifier", NETCONF_MONITORING_NS)) id.text = identifier ver = etree.SubElement(node, qualify("version", NETCONF_MONITORING_NS)) ver.text = version formt = etree.SubElement(node, qualify("format", NETCONF_MONITORING_NS)) formt.text = reqformat xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.retrieve.RPC._request') def test_dispatch(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) obj = Dispatch(session, self.device_handler, raise_mode=RaiseMode.ALL) rpc = 'get-software-information' source = "candidate" root_filter = new_ele('filter') config_filter = sub_ele(root_filter, 'configuration') system_filter = sub_ele(config_filter, 'system') sub_ele(system_filter, 'services') a = copy.deepcopy(root_filter) obj.request(rpc, source=source, filter=a) node = new_ele(rpc) node.append(util.datastore_or_url("source", source)) node.append(util.build_filter(root_filter)) xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.retrieve.RPC._request') def test_dispatch_2(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) obj = Dispatch(session, self.device_handler, raise_mode=RaiseMode.ALL) node = new_ele('get-software-information') source = "candidate" root_filter = new_ele('filter') config_filter = sub_ele(root_filter, 'configuration') system_filter = sub_ele(config_filter, 'system') sub_ele(system_filter, 'services') a = copy.deepcopy(root_filter) obj.request(node, source=source, filter=a) node.append(util.datastore_or_url("source", source)) node.append(util.build_filter(root_filter)) xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) ncclient-0.6.0/test/unit/operations/test_rpc.py000066400000000000000000000145071331651044100216370ustar00rootroot00000000000000from ncclient.operations.rpc import * import unittest from mock import patch from ncclient import manager import ncclient.manager import ncclient.transport from ncclient.xml_ import * from ncclient.operations import RaiseMode from ncclient.capabilities import Capabilities import sys if sys.version >= '3': patch_str = 'ncclient.operations.rpc.Event.isSet' else: patch_str = 'threading._Event.isSet' xml1 = """ """ xml2 = """ error system1 syntax error error } error recovery ignores input until this point """ xml3 = """ R1 firefly-perimeter firefly-perimeter junos JUNOS Software Release [12.1X46-D10.2] """ xml4 = """ 200 214 allow """ class TestRPC(unittest.TestCase): def test_rpc_reply(self): obj = RPCReply(xml4) obj.parse() self.assertTrue(obj.ok) self.assertFalse(obj.error) self.assertEqual(xml4, obj.xml) self.assertTrue(obj._parsed) @patch('ncclient.transport.Session.send') @patch(patch_str) def test_rpc_send(self, mock_thread, mock_send): device_handler = manager.make_device_handler({'name': 'junos'}) capabilities = Capabilities(device_handler.get_capabilities()) session = ncclient.transport.Session(capabilities) obj = RPC(session, device_handler, raise_mode=RaiseMode.ALL, timeout=0) reply = RPCReply(xml1) obj._reply = reply node = new_ele("commit") sub_ele(node, "confirmed") sub_ele(node, "confirm-timeout").text = "50" sub_ele(node, "log").text = "message" result = obj._request(node) ele = new_ele("rpc", {"message-id": obj._id}, **device_handler.get_xml_extra_prefix_kwargs()) ele.append(node) node = to_xml(ele) mock_send.assert_called_once_with(node) self.assertEqual( result.data_xml, (NCElement( reply, device_handler.transform_reply())).data_xml) self.assertEqual(obj.session, session) self.assertEqual(reply, obj.reply) @patch('ncclient.transport.Session.send') @patch(patch_str) def test_rpc_async(self, mock_thread, mock_send): device_handler = manager.make_device_handler({'name': 'junos'}) capabilities = Capabilities(device_handler.get_capabilities()) session = ncclient.transport.Session(capabilities) obj = RPC( session, device_handler, raise_mode=RaiseMode.ALL, timeout=0, async_mode=True) reply = RPCReply(xml1) obj._reply = reply node = new_ele("commit") result = obj._request(node) self.assertEqual(result, obj) @patch('ncclient.transport.Session.send') @patch(patch_str) def test_rpc_timeout_error(self, mock_thread, mock_send): device_handler = manager.make_device_handler({'name': 'junos'}) capabilities = Capabilities(device_handler.get_capabilities()) session = ncclient.transport.Session(capabilities) obj = RPC(session, device_handler, raise_mode=RaiseMode.ALL, timeout=0) reply = RPCReply(xml1) obj.deliver_reply(reply) node = new_ele("commit") sub_ele(node, "confirmed") mock_thread.return_value = False self.assertRaises(TimeoutExpiredError, obj._request, node) @patch('ncclient.transport.Session.send') @patch(patch_str) def test_rpc_rpcerror(self, mock_thread, mock_send): device_handler = manager.make_device_handler({'name': 'junos'}) capabilities = Capabilities(device_handler.get_capabilities()) session = ncclient.transport.Session(capabilities) obj = RPC(session, device_handler, raise_mode=RaiseMode.ALL, timeout=0) reply = RPCReply(xml1) obj._reply = reply node = new_ele("commit") sub_ele(node, "confirmed") err = RPCError(to_ele(xml2)) obj.deliver_error(err) self.assertRaises(RPCError, obj._request, node) @patch('ncclient.transport.Session.send') @patch(patch_str) def test_rpc_capability_error(self, mock_thread, mock_send): device_handler = manager.make_device_handler({'name': 'junos'}) capabilities = Capabilities(device_handler.get_capabilities()) session = ncclient.transport.Session(capabilities) session._server_capabilities = [':running'] obj = RPC(session, device_handler, raise_mode=RaiseMode.ALL, timeout=0) obj._assert(':running') self.assertRaises(MissingCapabilityError, obj._assert, ':candidate') ncclient-0.6.0/test/unit/operations/test_session.py000066400000000000000000000031061331651044100225270ustar00rootroot00000000000000from ncclient.operations.session import * import unittest from mock import patch from ncclient import manager import ncclient.manager import ncclient.transport from ncclient.xml_ import * from ncclient.operations import RaiseMode from xml.etree import ElementTree class TestSession(unittest.TestCase): def setUp(self): self.device_handler = manager.make_device_handler({'name': 'junos'}) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.RPC._request') def test_close_session(self, mock_request, mock_session): session = ncclient.transport.SSHSession(self.device_handler) obj = CloseSession( session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request() node = new_ele("close-session") xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.RPC._request') def test_kill_session(self, mock_request, mock_session): session = ncclient.transport.SSHSession(self.device_handler) obj = KillSession( session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request("100") node = new_ele("kill-session") sub_ele(node, "session-id").text = "100" xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) ncclient-0.6.0/test/unit/operations/test_subscribe.py000066400000000000000000000052441331651044100230320ustar00rootroot00000000000000from ncclient.operations.edit import * from ncclient.operations.subscribe import * import unittest from mock import patch, MagicMock from ncclient import manager import ncclient.manager import ncclient.transport from ncclient.xml_ import * from ncclient.operations import RaiseMode from xml.etree import ElementTree from ncclient.operations.errors import MissingCapabilityError import copy start_time = "1990-12-31T23:59:60Z" stop_time = "1996-12-19T16:39:57-08:00" class TestSubscribe(unittest.TestCase): def setUp(self): self.device_handler = manager.make_device_handler({'name': 'junos'}) @patch('ncclient.operations.edit.RPC._request') def test_subscribe_all(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [":notification"] obj = CreateSubscription( session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request() node = new_ele_ns("create-subscription", NETCONF_NOTIFICATION_NS) xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.edit.RPC._request') def test_subscribe_stream(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [":notification"] obj = CreateSubscription( session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request(filter=None, stream_name="nameofstream") node = new_ele_ns("create-subscription", NETCONF_NOTIFICATION_NS) sub_ele(node, "stream").text = "nameofstream" xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.operations.edit.RPC._request') def test_subscribe_times(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [":notification"] obj = CreateSubscription( session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request(filter=None, start_time=start_time, stop_time=stop_time) node = new_ele_ns("create-subscription", NETCONF_NOTIFICATION_NS) sub_ele(node, "startTime").text = start_time sub_ele(node, "stopTime").text = stop_time xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) ncclient-0.6.0/test/unit/operations/test_utils.py000066400000000000000000000053541331651044100222130ustar00rootroot00000000000000import unittest from xml.etree import ElementTree from ncclient.operations.util import * from mock import MagicMock xml = """ """ class TestUtils(unittest.TestCase): def test_one_of_1(self): self.assertEqual(one_of(None, 10, None), None) def test_one_of_2(self): self.assertRaises(OperationError, one_of, None, 10, 10) def test_one_of_3(self): self.assertRaises(OperationError, one_of, None, None) def test_datastore_url(self): node = new_ele("target") sub_ele(node, "candidate") result = ElementTree.tostring( datastore_or_url( "target", "candidate")) self.assertEqual(result, ElementTree.tostring(node)) def test_datastore_url_2(self): node = new_ele("web") sub_ele(node, "url").text = "http://juniper.net" result = ElementTree.tostring( datastore_or_url( "web", "http://juniper.net", capcheck=MagicMock())) self.assertEqual(result, ElementTree.tostring(node)) def test_datastore_url_3(self): node = new_ele("web") result = ElementTree.tostring( datastore_or_url( "web", "http://juniper.net")) self.assertEqual(result, ElementTree.tostring(node)) def test_build_filter(self): reply = build_filter(xml) call = ElementTree.tostring(reply) self.assertEqual(call, ElementTree.tostring(to_ele(xml))) def test_build_filter_2(self): criteria = "configuration/system" filter = ("xpath", criteria) reply = build_filter(filter) call = ElementTree.tostring(reply) node = new_ele("filter", type="xpath") node.attrib["select"] = criteria self.assertEqual(call, ElementTree.tostring(node)) def test_build_filter_3(self): criteria = """ """ filter = ("subtree", criteria) reply = build_filter(filter, capcheck="cap") call = ElementTree.tostring(reply) node = new_ele("filter", type="subtree") node.append(to_ele(criteria)) self.assertEqual(call, ElementTree.tostring(node)) def test_build_filter_4(self): criteria = """ """ filter = ("text", criteria) self.assertRaises(OperationError, build_filter, filter, capcheck="cap") ncclient-0.6.0/test/unit/operations/third_party/000077500000000000000000000000001331651044100217645ustar00rootroot00000000000000ncclient-0.6.0/test/unit/operations/third_party/__init__.py000066400000000000000000000000001331651044100240630ustar00rootroot00000000000000ncclient-0.6.0/test/unit/operations/third_party/juniper/000077500000000000000000000000001331651044100234405ustar00rootroot00000000000000ncclient-0.6.0/test/unit/operations/third_party/juniper/__init__.py000066400000000000000000000000001331651044100255370ustar00rootroot00000000000000ncclient-0.6.0/test/unit/operations/third_party/juniper/test_rpc.py000066400000000000000000000307021331651044100256370ustar00rootroot00000000000000from ncclient.operations.third_party.juniper.rpc import * import json import unittest from mock import patch from ncclient import manager import ncclient.manager import ncclient.transport from ncclient.xml_ import * from ncclient.operations import RaiseMode from xml.etree import ElementTree class TestRPC(unittest.TestCase): @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') def test_command(self, mock_request, mock_session): device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = Command(session, device_handler, raise_mode=RaiseMode.ALL) command = 'show system users' format = 'text' obj.request(command=command, format=format) node = new_ele('command', {'format': format}) node.text = command call = mock_request.call_args_list[0][0][0] self.assertEqual(call.tag, node.tag) self.assertEqual(call.text, node.text) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') def test_getconf(self, mock_request, mock_session): device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = GetConfiguration( session, device_handler, raise_mode=RaiseMode.ALL) root_filter = new_ele('filter') config_filter = sub_ele(root_filter, 'configuration') system_filter = sub_ele(config_filter, 'system') obj.request(format='xml', filter=system_filter) node = new_ele('get-configuration', {'format': 'xml'}) node.append(system_filter) call = mock_request.call_args_list[0][0][0] self.assertEqual(call.tag, node.tag) self.assertEqual(call.attrib, node.attrib) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') def test_loadconf_xml(self, mock_request, mock_session): device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = LoadConfiguration( session, device_handler, raise_mode=RaiseMode.ALL) root_config = new_ele('configuration') system_config = sub_ele(root_config, 'system') location_config = sub_ele(system_config, 'location') floor_config = sub_ele(location_config, 'floor').text = "7" obj.request(format='xml', config=root_config) node = new_ele('load-configuration', {'format': 'xml', 'action': 'merge'}) node.append(root_config) call = mock_request.call_args_list[0][0][0] self.assertEqual(call.tag, node.tag) self.assertEqual(call.attrib, node.attrib) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') def test_loadconf_json(self, mock_request, mock_session): device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = LoadConfiguration( session, device_handler, raise_mode=RaiseMode.ALL) location = '{ "configuration": { "system": { "location": { "floor": "7" }}}}' config_json = json.loads(location) config = json.dumps(config_json) obj.request(format='json', action='merge', config=config) node = new_ele('load-configuration', {'format': 'json', 'action': 'merge'}) sub_ele(node, 'configuration-json').text = config call = mock_request.call_args_list[0][0][0] self.assertEqual(call.tag, node.tag) self.assertEqual(call.attrib, node.attrib) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') def test_loadconf_set(self, mock_request, mock_session): device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = LoadConfiguration( session, device_handler, raise_mode=RaiseMode.ALL) config = 'set system location floor 7' obj.request(format='text', action='set', config=config) node = new_ele('load-configuration', {'format': 'text', 'action': 'set'}) sub_ele(node, 'configuration-set').text = config call = mock_request.call_args_list[0][0][0] self.assertEqual(call.tag, node.tag) self.assertEqual(call.attrib, node.attrib) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') def test_loadconf_text(self, mock_request, mock_session): device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = LoadConfiguration( session, device_handler, raise_mode=RaiseMode.ALL) config = 'system { location floor 7; }' obj.request(format='text', action='merge', config=config) node = new_ele('load-configuration', {'format': 'text', 'action': 'merge'}) sub_ele(node, 'configuration-text').text = config call = mock_request.call_args_list[0][0][0] self.assertEqual(call.tag, node.tag) self.assertEqual(call.attrib, node.attrib) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') def test_loadconf_list(self, mock_request, mock_session): device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = LoadConfiguration( session, device_handler, raise_mode=RaiseMode.ALL) config = ['set system location floor 7', 'set system location rack 3'] obj.request(format='text', action='set', config=config) node = new_ele('load-configuration', {'format': 'text', 'action': 'set'}) sub_ele(node, 'configuration-set').text = '\n'.join(config) call = mock_request.call_args_list[0][0][0] self.assertEqual(call.tag, node.tag) self.assertEqual(call.attrib, node.attrib) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') def test_compare_conf(self, mock_request, mock_session): device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = CompareConfiguration( session, device_handler, raise_mode=RaiseMode.ALL) obj.request(rollback=2) node = new_ele( 'get-configuration', {'compare': 'rollback', 'rollback': str(2)}) call = mock_request.call_args_list[0][0][0] self.assertEqual(call.tag, node.tag) self.assertEqual(call.attrib, node.attrib) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') def test_execute_rpc(self, mock_request, mock_session): device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = ExecuteRpc(session, device_handler, raise_mode=RaiseMode.ALL) rpc = new_ele('get-software-information') obj.request(rpc) mock_request.assert_called_once_with(rpc) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') def test_execute_rpc_str(self, mock_request, mock_session): device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = ExecuteRpc(session, device_handler, raise_mode=RaiseMode.ALL) rpc = '' obj.request(rpc) self.assertEqual(True, isinstance(rpc, str)) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') def test_reboot(self, mock_request, mock_session): device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = Reboot(session, device_handler, raise_mode=RaiseMode.ALL) obj.request() node = new_ele('request-reboot') call = mock_request.call_args_list[0][0][0] self.assertEqual(call.tag, node.tag) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') def test_halt(self, mock_request, mock_session): device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = Halt(session, device_handler, raise_mode=RaiseMode.ALL) obj.request() node = new_ele('request-halt') call = mock_request.call_args_list[0][0][0] self.assertEqual(call.tag, node.tag) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') @patch('ncclient.operations.third_party.juniper.rpc.RPC._assert') def test_commit_confirmed(self, mock_assert, mock_request, mock_session): # mock_session.server_capabilities.return_value = [':candidate'] device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = Commit(session, device_handler, raise_mode=RaiseMode.ALL) obj.request(confirmed=True, comment="message", timeout="50") node = new_ele_ns("commit-configuration", "") sub_ele(node, "confirmed") # confirm-timeout need to be provided in minutes for this Junos specific rpc sub_ele(node, "confirm-timeout").text = "1" sub_ele(node, "log").text = "message" xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') @patch('ncclient.operations.third_party.juniper.rpc.RPC._assert') def test_commit(self, mock_assert, mock_request, mock_session): # mock_session.server_capabilities.return_value = [':candidate'] device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = Commit(session, device_handler, raise_mode=RaiseMode.ALL) obj.request() node = new_ele_ns("commit-configuration", "") xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') @patch('ncclient.operations.third_party.juniper.rpc.RPC._assert') def test_commit_at_time(self, mock_assert, mock_request, mock_session): # mock_session.server_capabilities.return_value = [':candidate'] device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = Commit(session, device_handler, raise_mode=RaiseMode.ALL) obj.request(at_time="1111-11-11 00:00:00", synchronize=True) node = new_ele_ns("commit-configuration", "") sub_ele(node, "at-time").text = "1111-11-11 00:00:00" sub_ele(node, "synchronize") xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') @patch('ncclient.operations.third_party.juniper.rpc.RPC._assert') def test_commit_confirmed_at_time( self, mock_assert, mock_request, mock_session): # mock_session.server_capabilities.return_value = [':candidate'] device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = Commit(session, device_handler, raise_mode=RaiseMode.ALL) self.assertRaises(NCClientError, obj.request, at_time="1111-11-11 00:00:00", synchronize=True, confirmed=True) ncclient-0.6.0/test/unit/reply1000066400000000000000000000007751331651044100164200ustar00rootroot00000000000000 R1 firefly-perimeter firefly-perimeter junos JUNOS Software Release [12.1X46-D10.2] ncclient-0.6.0/test/unit/test_manager.py000066400000000000000000000156501331651044100203020ustar00rootroot00000000000000import unittest from mock import patch, MagicMock from ncclient import manager from ncclient.devices.junos import JunosDeviceHandler class TestManager(unittest.TestCase): @patch('ncclient.transport.SSHSession') def test_ssh(self, mock_ssh): m = MagicMock() mock_ssh.return_value = m conn = self._mock_manager() self.assertEqual(conn._session, m) self.assertEqual(conn._timeout, 10) @patch('ncclient.manager.connect_ssh') def test_connect_ssh(self, mock_ssh): manager.connect(host='host') mock_ssh.assert_called_once_with(host='host') @patch('ncclient.manager.connect_ssh') def test_connect_outbound_ssh(self, mock_ssh): manager.connect(host=None, sock_fd=6) mock_ssh.assert_called_once_with(host=None, sock_fd=6) @patch('ncclient.manager.connect_ioproc') def test_connect_ioproc(self, mock_ssh): manager.connect(host='localhost', device_params={'name': 'junos', 'local': True}) mock_ssh.assert_called_once_with(host='localhost', device_params={'local': True, 'name': 'junos'}) @patch('socket.socket') @patch('paramiko.Transport') @patch('ncclient.transport.ssh.hexlify') @patch('ncclient.transport.ssh.Session._post_connect') def test_ssh2(self, mock_session, mock_hex, mock_trans, mock_socket): conn = self._mock_manager() self.assertEqual(mock_trans.called, 1) self.assertEqual(conn._timeout, 10) self.assertEqual(conn._device_handler.device_params, {'name': 'junos'}) self.assertEqual( conn._device_handler.__class__.__name__, "JunosDeviceHandler") @patch('ncclient.transport.ssh.Session._post_connect') @patch('ncclient.transport.third_party.junos.ioproc.IOProc.connect') def test_ioproc(self, mock_connect, mock_ioproc): conn = manager.connect(host='localhost', port=22, username='user', password='password', timeout=10, device_params={'local': True, 'name': 'junos'}, hostkey_verify=False) self.assertEqual(mock_connect.called, 1) self.assertEqual(conn._timeout, 10) self.assertEqual(conn._device_handler.device_params, {'local': True, 'name': 'junos'}) self.assertEqual( conn._device_handler.__class__.__name__, "JunosDeviceHandler") def test_make_device_handler(self): device_handler = manager.make_device_handler({'name': 'junos'}) self.assertEqual( device_handler.__class__.__name__, "JunosDeviceHandler") def test_make_device_handler_provided_handler(self): device_handler = manager.make_device_handler( {'handler': JunosDeviceHandler}) self.assertEqual( device_handler.__class__.__name__, "JunosDeviceHandler") @patch('ncclient.operations.LockContext') def test_manager_locked(self, mock_lock): conn = manager.Manager(None, None, timeout=20) conn.locked(None) mock_lock.assert_called_once_with(None, None, None) @patch('socket.socket') @patch('paramiko.Transport') @patch('ncclient.transport.ssh.hexlify') @patch('ncclient.transport.ssh.Session._post_connect') def test_manager_client_capability( self, mock_session, mock_hex, mock_trans, mock_socket): conn = self._mock_manager() self.assertEqual( conn.client_capabilities, conn._session.client_capabilities) @patch('socket.socket') @patch('paramiko.Transport') @patch('ncclient.transport.ssh.hexlify') @patch('ncclient.transport.ssh.Session._post_connect') def test_manager_server_capability( self, mock_session, mock_hex, mock_trans, mock_socket): conn = self._mock_manager() self.assertEqual( conn.server_capabilities, conn._session.server_capabilities) @patch('socket.socket') @patch('paramiko.Transport') @patch('ncclient.transport.ssh.hexlify') @patch('ncclient.transport.ssh.Session._post_connect') def test_manager_channel_id( self, mock_session, mock_hex, mock_trans, mock_socket): conn = self._mock_manager() self.assertEqual(conn.channel_id, conn._session._channel_id) @patch('socket.socket') @patch('paramiko.Transport') @patch('ncclient.transport.ssh.hexlify') @patch('ncclient.transport.ssh.Session._post_connect') def test_manager_channel_name( self, mock_session, mock_hex, mock_trans, mock_socket): conn = self._mock_manager() self.assertEqual(conn.channel_name, conn._session._channel_name) @patch('socket.socket') @patch('paramiko.Transport') @patch('ncclient.transport.ssh.hexlify') @patch('ncclient.transport.ssh.Session._post_connect') def test_manager_channel_session_id( self, mock_session, mock_hex, mock_trans, mock_socket): conn = self._mock_manager() self.assertEqual(conn.session_id, conn._session.id) @patch('socket.socket') @patch('paramiko.Transport') @patch('ncclient.transport.ssh.hexlify') @patch('ncclient.transport.ssh.Session._post_connect') def test_manager_connected( self, mock_session, mock_hex, mock_trans, mock_socket): conn = self._mock_manager() self.assertEqual(conn.connected, True) def _mock_manager(self): conn = manager.connect(host='10.10.10.10', port=22, username='user', password='password', timeout=10, device_params={'name': 'junos'}, hostkey_verify=False, allow_agent=False) return conn @patch('socket.fromfd') @patch('paramiko.Transport') @patch('ncclient.transport.ssh.hexlify') @patch('ncclient.transport.ssh.Session._post_connect') def test_outbound_manager_connected( self, mock_session, mock_hex, mock_trans, mock_fromfd): conn = self._mock_outbound_manager() self.assertEqual(conn.connected, True) def _mock_outbound_manager(self): conn = manager.connect(host=None, sock_fd=6, username='user', password='password', timeout=10, device_params={'name': 'junos'}, hostkey_verify=False, allow_agent=False) return conn if __name__ == "__main__": suite = unittest.TestLoader().loadTestsFromTestCase(TestManager) unittest.TextTestRunner(verbosity=2).run(suite) ncclient-0.6.0/test/unit/test_xml_.py000066400000000000000000000162701331651044100176260ustar00rootroot00000000000000from ncclient import manager from ncclient.xml_ import * import unittest from nose.tools import assert_equal from nose.tools import assert_not_equal import os import sys file_path = os.path.join(os.getcwd(), "test", "unit", "reply1") class Test_NCElement(object): def test_ncelement_reply_001(self): """test parse rpc_reply and string/data_xml""" # read in reply1 contents with open(file_path, 'r') as f: reply = f.read() device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() result = NCElement(reply, transform_reply) result_str = result.tostring if sys.version >= '3': result_str = result_str.decode('UTF-8') assert_equal(str(result), result_str) #data_xml != tostring assert_not_equal(result_str, result.data_xml) def test_ncelement_reply_002(self): """test parse rpc_reply and xpath""" # read in reply1 contents with open(file_path, 'r') as f: reply = f.read() device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() result = NCElement(reply, transform_reply) # XPATH checks work assert_equal(result.xpath("//host-name")[0].text, "R1") assert_equal( result.xpath("/rpc-reply/software-information/host-name")[0].text, "R1") assert_equal( result.xpath("software-information/host-name")[0].text, "R1") def test_ncelement_reply_003(self): """test parse rpc_reply and find""" # read in reply1 contents with open(file_path, 'r') as f: reply = f.read() device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() result = NCElement(reply, transform_reply) # find assert_equal(result.findtext(".//host-name"), "R1") assert_equal(result.find(".//host-name").tag, "host-name") class TestXML(unittest.TestCase): @classmethod def setUpClass(cls): cls.f = open(file_path, 'r') cls.reply = cls.f.read() def test_ncelement_reply_001(self): device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() result = NCElement(self.reply, transform_reply) self.assertEqual(result.xpath("//name")[0].text, "junos") self.assertEqual(result.xpath("//name")[0].tag, "name") self.assertEqual( result.xpath("//package-information")[0].tag, "package-information") def test_ncelement_find(self): device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() result = NCElement(self.reply, transform_reply) self.assertEqual(result.find(".//name").tag, "name") self.assertEqual(result.find(".//name").text, "junos") def test_ncelement_findtext(self): device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() result = NCElement(self.reply, transform_reply) self.assertEqual(result.findtext(".//name"), "junos") def test_ncelement_remove_namespace(self): device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() result = NCElement(self.reply, transform_reply) re = result.remove_namespaces(self.reply) self.assertEqual(re.tag, "rpc-reply") ele = to_ele((result.find(".//name"))) self.assertEqual(ele.tag, "name") self.assertEqual(ele.text, "junos") ele = to_ele(self.reply) self.assertEqual(ele.tag, "rpc-reply") def test_to_ele(self): device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() result = NCElement(self.reply, transform_reply) ele = to_ele((result.find(".//name"))) self.assertEqual(ele.tag, "name") self.assertEqual(ele.text, "junos") ele = to_ele(self.reply) self.assertEqual(ele.tag, "rpc-reply") def test_parse_root(self): device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() result = NCElement(self.reply, transform_reply) tag, attrib = parse_root(result.data_xml) self.assertEqual(tag, "rpc-reply") self.assertEqual(attrib, {'attrib1': 'test'}) def test_validated_element_pass(self): device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() result = NCElement(self.reply, transform_reply) result_xml = result.data_xml ele = validated_element( result_xml, tags=["rpc-reply", "rpc"], attrs=[["attrib1", "attrib2"]]) self.assertEqual(ele.tag, "rpc-reply") ele = validated_element( result_xml, attrs=[["attrib1", "attrib2"]]) self.assertEqual(ele.tag, "rpc-reply") ele = validated_element(result_xml) self.assertEqual(ele.tag, "rpc-reply") def test_validated_element_fail(self): device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() result = NCElement(self.reply, transform_reply) XMLError.message = "Element does not meet requirement" result_xml = result.data_xml self.assertRaises(XMLError, validated_element, result_xml, tags=["rpc"], attrs=[["attrib1", "attrib2"]]) def test_validated_element_fail_2(self): device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() result = NCElement(self.reply, transform_reply) XMLError.message = "Element does not meet requirement" result_xml = result.data_xml self.assertRaises(XMLError, validated_element, result_xml, tags=[ "rpc-reply", "rpc"], attrs=[ ["attrib1"], ["attrib2"]]) def test_validated_element_fail_3(self): device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() result = NCElement(self.reply, transform_reply) XMLError.message = "Element does not meet requirement" result_xml = result.data_xml self.assertRaises(XMLError, validated_element, result_xml, tags=["rpc"]) ncclient-0.6.0/test/unit/transport/000077500000000000000000000000001331651044100173045ustar00rootroot00000000000000ncclient-0.6.0/test/unit/transport/__init__.py000066400000000000000000000000001331651044100214030ustar00rootroot00000000000000ncclient-0.6.0/test/unit/transport/test_session.py000066400000000000000000000204001331651044100223740ustar00rootroot00000000000000import unittest from mock import patch from ncclient.transport.session import * from ncclient.devices.junos import JunosDeviceHandler try: from Queue import Queue, Empty except ImportError: from queue import Queue, Empty import logging rpc_reply = """ R1 firefly-perimeter firefly-perimeter junos JUNOS Software Release [12.1X46-D10.2] """ hello_rpc_reply = """ candidate validate s001 """ notification=""" 2007-07-08T00:01:00Z fault Ethernet0 major """ class TestSession(unittest.TestCase): @patch('ncclient.transport.session.HelloHandler.callback') def test_dispatch_message(self, mock_handler): cap = [':candidate'] obj = Session(cap) device_handler = JunosDeviceHandler({'name': 'junos'}) obj._device_handler = device_handler listener = HelloHandler(None, None) obj._listeners.add(listener) obj._dispatch_message(rpc_reply) mock_handler.assert_called_once_with(parse_root(rpc_reply), rpc_reply) @patch('ncclient.transport.session.parse_root') @patch('logging.Logger.error') def test_dispatch_message_error(self, mock_log, mock_parse_root): mock_parse_root.side_effect = Exception cap = [':candidate'] obj = Session(cap) device_handler = JunosDeviceHandler({'name': 'junos'}) obj._device_handler = device_handler listener = HelloHandler(None, None) obj._listeners.add(listener) obj._dispatch_message(rpc_reply) self.assertNotEqual( mock_log.call_args_list[0][0][0].find("error parsing dispatch message"), -1) @patch('ncclient.transport.session.HelloHandler.errback') def test_dispatch_error(self, mock_handler): cap = [':candidate'] obj = Session(cap) listener = HelloHandler(None, None) obj._listeners.add(listener) obj._dispatch_error("Error") mock_handler.assert_called_once_with("Error") @patch('logging.Logger.info') @patch('ncclient.transport.session.Thread.start') @patch('ncclient.transport.session.Event') def test_post_connect(self, mock_lock, mock_handler, mock_log): cap = [':candidate'] obj = Session(cap) device_handler = JunosDeviceHandler({'name': 'junos'}) obj._device_handler = device_handler obj._connected = True obj._id = 100 obj._server_capabilities = cap obj._post_connect() log_call = mock_log.call_args_list[0][0][0] self.assertNotEqual(log_call.find("initialized"), -1) self.assertNotEqual(log_call.find("session-id=100"), -1) self.assertNotEqual( log_call.find("server_capabilities=[':candidate']"), -1) def test_add_listener(self): cap = [':candidate'] obj = Session(cap) listener = HelloHandler(None, None) obj.add_listener(listener) self.assertTrue(listener in obj._listeners) def test_add_listener_exception(self): cap = [':candidate'] obj = Session(cap) listener = Session(None) self.assertRaises(SessionError, obj.add_listener, listener) def test_remove_listener(self): cap = [':candidate'] obj = Session(cap) listener = HelloHandler(None, None) obj._listeners.add(listener) obj.remove_listener(listener) self.assertEqual(len(obj._listeners), 0) def test_get_listener_instance(self): cap = [':candidate'] obj = Session(cap) listener = HelloHandler(None, None) obj._listeners.add(listener) ret = obj.get_listener_instance(HelloHandler) self.assertEqual(ret, listener) def test_send_connected(self): cap = [':candidate'] obj = Session(cap) obj._connected = True obj.send("Hello World") self.assertEqual("Hello World", obj._q.get()) def test_send_disconnected(self): cap = [':candidate'] obj = Session(cap) obj._connected = False self.assertRaises(TransportError, obj.send, "Hello World") def test_connected(self): cap = [':candidate'] obj = Session(cap) obj._connected = True self.assertTrue(obj.connected) def test_client_capability(self): cap = [':validate'] obj = Session(cap) self.assertEqual(obj.client_capabilities, cap) def test_server_capability(self): cap = [':validate'] obj = Session(cap) obj._server_capabilities = cap self.assertEqual(obj.server_capabilities, cap) def test_id(self): cap = [':validate'] obj = Session(cap) obj._id = "1000" self.assertEqual(obj.id, "1000") def test_hello_handler_callback_ok(self): def ok_cb(id, capabilities): self._id = id self._server_capabilities = capabilities def err_cb(err): self.error = err listener = HelloHandler(ok_cb, err_cb) listener.callback(parse_root(hello_rpc_reply), hello_rpc_reply) caps = ["candidate", "validate"] self.assertEqual(self._id, "s001") self.assertEqual( self._server_capabilities._dict, Capabilities(caps)._dict) @patch('ncclient.transport.session.HelloHandler.parse') def test_hello_handler_callback_error(self, mock_parse): def ok_cb(id, capabilities): self._id = id self._server_capabilities = capabilities def err_cb(err): self.error = "Error" mock_parse.side_effect = Exception listener = HelloHandler(ok_cb, err_cb) listener.callback(parse_root(hello_rpc_reply), hello_rpc_reply) self.assertEqual(self.error, "Error") def test_hello_handler_build(self): cap = [':candidate', ':validate'] listener = HelloHandler(None, None) result = listener.build(cap, None) node = new_ele("hello") caps = sub_ele(node, "capabilities") sub_ele(caps, "capability").text = ":candidate" sub_ele(caps, "capability").text = ":validate" node = to_xml(node) self.assertEqual(node, result) def test_hello_handler_parse(self): cap = [':candidate'] obj = Session(cap) listener = HelloHandler(None, None) obj._listeners.add(listener) id, capabilities = listener.parse(hello_rpc_reply) self.assertEqual(id, "s001") caps = ["candidate", "validate"] self.assertEqual(capabilities._dict, Capabilities(caps)._dict) def test_notification_handler_valid_notification(self): q = Queue() listener = NotificationHandler(q) listener.callback(parse_root(notification), notification) notif = q.get_nowait() self.assertEquals(notif.notification_xml, notification) self.assertRaises(Empty, q.get_nowait) def test_notification_handler_non_notification(self): q = Queue() listener = NotificationHandler(q) # This handler should ignore things that aren't notifications listener.callback(parse_root(rpc_reply), rpc_reply) self.assertRaises(Empty, q.get_nowait) def test_take_notification(self): cap = [':candidate'] obj = Session(cap) obj._notification_q.put('Test object') self.assertEqual(obj.take_notification(block=False, timeout=None), 'Test object') self.assertEqual(obj.take_notification(block=False, timeout=None), None) ncclient-0.6.0/test/unit/transport/test_ssh.py000066400000000000000000000264661331651044100215300ustar00rootroot00000000000000import unittest from mock import MagicMock, patch from ncclient.transport.ssh import SSHSession from ncclient.transport import AuthenticationError, SessionCloseError import paramiko from ncclient.devices.junos import JunosDeviceHandler import sys reply_data = """ R1 firefly-perimeter firefly-perimeter junos JUNOS Software Release [12.1X46-D10.2] """ reply_ok = """ """ # A buffer of data with two complete messages and an incomplete message rpc_reply = reply_data + "\n]]>]]>\n" + reply_ok + "\n]]>]]>\n" + reply_ok reply_ok_chunk = "\n#%d\n%s\n##\n" % (len(reply_ok), reply_ok) reply_ok_partial_chunk = "\n#%d\n%s\n" % (len(reply_ok), reply_ok) # A buffer of data with two complete messages and an incomplete message rpc_reply11 = "\n#%d\n%s\n#%d\n%s\n##\n%s%s" % ( 30, reply_data[:30], len(reply_data[30:]), reply_data[30:], reply_ok_chunk, reply_ok_partial_chunk) rpc_reply_part_1 = """ R1 firefly-perimeter firefly-perimeter junos JUNOS Software Release [12.1X46-D10.2] ]]>]]""" rpc_reply_part_2 = """> """ class TestSSH(unittest.TestCase): def _test_parsemethod(self, mock_dispatch, parsemethod, reply, ok_chunk, expected_messages): device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) if sys.version >= "3.0": obj._buffer.write(bytes(reply, "utf-8")) remainder = bytes(ok_chunk, "utf-8") else: obj._buffer.write(reply) remainder = ok_chunk parsemethod(obj) for i in range(0, len(expected_messages)): call = mock_dispatch.call_args_list[i][0][0] self.assertEqual(call, expected_messages[i]) self.assertEqual(obj._buffer.getvalue(), remainder) @patch('ncclient.transport.ssh.Session._dispatch_message') def test_parse(self, mock_dispatch): self._test_parsemethod(mock_dispatch, SSHSession._parse, rpc_reply, "\n" + reply_ok, [reply_data]) @patch('ncclient.transport.ssh.Session._dispatch_message') def test_parse11(self, mock_dispatch): self._test_parsemethod(mock_dispatch, SSHSession._parse11, rpc_reply11, reply_ok_partial_chunk, [reply_data, reply_ok]) @patch('ncclient.transport.ssh.Session._dispatch_message') def test_parse_incomplete_delimiter(self, mock_dispatch): device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) if sys.version >= "3.0": b = bytes(rpc_reply_part_1, "utf-8") obj._buffer.write(b) obj._parse() self.assertFalse(mock_dispatch.called) b = bytes(rpc_reply_part_2, "utf-8") obj._buffer.write(b) obj._parse() self.assertTrue(mock_dispatch.called) else: obj._buffer.write(rpc_reply_part_1) obj._parse() self.assertFalse(mock_dispatch.called) obj._buffer.write(rpc_reply_part_2) obj._parse() self.assertTrue(mock_dispatch.called) @patch('paramiko.transport.Transport.auth_publickey') @patch('paramiko.agent.AgentSSH.get_keys') def test_auth_agent(self, mock_get_key, mock_auth_public_key): key = paramiko.PKey(msg="hello") mock_get_key.return_value = [key] device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj._transport = paramiko.Transport(MagicMock()) obj._auth('user', 'password', [], True, True) self.assertEqual( (mock_auth_public_key.call_args_list[0][0][1]).__repr__(), key.__repr__()) @patch('paramiko.transport.Transport.auth_publickey') @patch('paramiko.agent.AgentSSH.get_keys') def test_auth_agent_exception(self, mock_get_key, mock_auth_public_key): key = paramiko.PKey() mock_get_key.return_value = [key] mock_auth_public_key.side_effect = paramiko.ssh_exception.AuthenticationException device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj._transport = paramiko.Transport(MagicMock()) self.assertRaises(AuthenticationError, obj._auth,'user', None, [], True, False) @patch('paramiko.transport.Transport.auth_publickey') @patch('paramiko.pkey.PKey.from_private_key_file') def test_auth_keyfiles(self, mock_get_key, mock_auth_public_key): key = paramiko.PKey() mock_get_key.return_value = key device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj._transport = paramiko.Transport(MagicMock()) obj._auth('user', 'password', ["key_file_name"], False, True) self.assertEqual( (mock_auth_public_key.call_args_list[0][0][1]).__repr__(), key.__repr__()) @patch('paramiko.transport.Transport.auth_publickey') @patch('paramiko.pkey.PKey.from_private_key_file') def test_auth_keyfiles_exception(self, mock_get_key, mock_auth_public_key): key = paramiko.PKey() mock_get_key.side_effect = paramiko.ssh_exception.PasswordRequiredException device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj._transport = paramiko.Transport(MagicMock()) self.assertRaises(AuthenticationError, obj._auth,'user', None, ["key_file_name"], False, True) @patch('os.path.isfile') @patch('paramiko.transport.Transport.auth_publickey') @patch('paramiko.pkey.PKey.from_private_key_file') def test_auth_default_keyfiles(self, mock_get_key, mock_auth_public_key, mock_is_file): key = paramiko.PKey() mock_get_key.return_value = key mock_is_file.return_value = True device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj._transport = paramiko.Transport(MagicMock()) obj._auth('user', 'password', [], False, True) self.assertEqual( (mock_auth_public_key.call_args_list[0][0][1]).__repr__(), key.__repr__()) @patch('os.path.isfile') @patch('paramiko.transport.Transport.auth_publickey') @patch('paramiko.pkey.PKey.from_private_key_file') def test_auth_default_keyfiles_exception(self, mock_get_key, mock_auth_public_key, mock_is_file): key = paramiko.PKey() mock_is_file.return_value = True mock_get_key.side_effect = paramiko.ssh_exception.PasswordRequiredException device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj._transport = paramiko.Transport(MagicMock()) self.assertRaises(AuthenticationError, obj._auth,'user', None, [], False, True) @patch('paramiko.transport.Transport.auth_password') def test_auth_password(self, mock_auth_password): device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj._transport = paramiko.Transport(MagicMock()) obj._auth('user', 'password', [], False, True) self.assertEqual( mock_auth_password.call_args_list[0][0], ('user', 'password')) @patch('paramiko.transport.Transport.auth_password') def test_auth_exception(self, mock_auth_password): mock_auth_password.side_effect = Exception device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj._transport = paramiko.Transport(MagicMock()) self.assertRaises(AuthenticationError, obj._auth, 'user', 'password', [], False, True) def test_auth_no_methods_exception(self): device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj._transport = paramiko.Transport(MagicMock()) self.assertRaises(AuthenticationError, obj._auth,'user', None, [], False, False) @patch('paramiko.transport.Transport.close') def test_close(self, mock_close): device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj._transport = paramiko.Transport(MagicMock()) obj._transport.active = True obj._connected = True obj.close() mock_close.assert_called_once_with() self.assertFalse(obj._connected) @patch('paramiko.hostkeys.HostKeys.load') def test_load_host_key(self, mock_load): device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj.load_known_hosts("file_name") mock_load.assert_called_once_with("file_name") @patch('os.path.expanduser') @patch('paramiko.hostkeys.HostKeys.load') def test_load_host_key_2(self, mock_load, mock_os): mock_os.return_value = "file_name" device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj.load_known_hosts() mock_load.assert_called_once_with("file_name") @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.recv') @patch('ncclient.transport.ssh.select') @patch('ncclient.transport.ssh.Session._dispatch_error') def test_run_recieve(self, mock_error, mock_select, mock_recv, mock_close): mock_select.return_value = True, None, None mock_recv.return_value = 0 device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj._channel = paramiko.Channel("c100") obj.run() self.assertTrue( isinstance( mock_error.call_args_list[0][0][0], SessionCloseError)) @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.send_ready') @patch('paramiko.channel.Channel.send') @patch('ncclient.transport.ssh.Session._dispatch_error') def test_run_send(self, mock_error, mock_send, mock_ready, mock_close): mock_ready.return_value = True mock_send.return_value = -1 device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj._channel = paramiko.Channel("c100") obj._q.put("rpc") obj.run() self.assertEqual(mock_send.call_args_list[0][0][0], "rpc") self.assertTrue( isinstance( mock_error.call_args_list[0][0][0], SessionCloseError))