pax_global_header00006660000000000000000000000064145132553050014515gustar00rootroot0000000000000052 comment=f0f4d95ed8613f256f0972491d6c47f53246da0b ncclient-0.6.15/000077500000000000000000000000001451325530500134055ustar00rootroot00000000000000ncclient-0.6.15/.gitattributes000066400000000000000000000000421451325530500162740ustar00rootroot00000000000000ncclient/_version.py export-subst ncclient-0.6.15/.github/000077500000000000000000000000001451325530500147455ustar00rootroot00000000000000ncclient-0.6.15/.github/workflows/000077500000000000000000000000001451325530500170025ustar00rootroot00000000000000ncclient-0.6.15/.github/workflows/check.yaml000066400000000000000000000053151451325530500207470ustar00rootroot00000000000000name: Run Tests and Check Coverage on: [push, pull_request] jobs: check_legacy: runs-on: ubuntu-20.04 strategy: matrix: python-version: ['3.6.15'] steps: - uses: actions/checkout@v3 with: submodules: 'recursive' - name: Setup Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install Dependencies run: | python -m pip install --upgrade pip pip install coverage coveralls pip install -r requirements.txt pip install -r test-requirements.txt - name: Run Check Scripts run: | pytest test --verbosity=3 --cov=ncclient # check_beta: # runs-on: ubuntu-22.04 # strategy: # matrix: # python-version: ['3.12.0-beta.3'] # steps: # - uses: actions/checkout@v3 # with: # submodules: 'recursive' # - name: Setup Python # uses: actions/setup-python@v4 # with: # python-version: ${{ matrix.python-version }} # - name: Install Dependencies # run: | # sudo apt install -y python3-dev libxml2-dev libxslt1-dev # python -m pip install --upgrade pip # pip install coverage coveralls # pip install -r requirements.txt # pip install -r test-requirements.txt # - name: Run Check Scripts # run: | # pytest test --verbosity=3 --cov=ncclient check: runs-on: ubuntu-22.04 strategy: matrix: python-version: ['3.7.17', '3.8.17', '3.9.17', '3.10.12', '3.11.4'] steps: - uses: actions/checkout@v3 with: submodules: 'recursive' - name: Setup Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install Dependencies run: | python -m pip install --upgrade pip pip install coverage coveralls pip install -r requirements.txt pip install -r test-requirements.txt - name: Run Check Scripts run: | pytest test --verbosity=3 --cov=ncclient - name: Upload Coverage env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_FLAG_NAME: ${{ matrix.python-version }} COVERALLS_PARALLEL: true run: | coveralls --service=github coveralls: name: Indicate completion to coveralls.io needs: check runs-on: ubuntu-20.04 container: python:3-slim steps: - name: Finished run: | pip3 install --upgrade coveralls coveralls --service=github --finish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ncclient-0.6.15/.gitignore000066400000000000000000000002441451325530500153750ustar00rootroot00000000000000*.egg *.egg-info *.EGG *.EGG-INFO *.mo *.pyc *.pyo *.swp bin build dist *~ v/ # dotenv .env # virtualenv .venv venv/ ENV/ .idea .coverage docs/html .tox cover/ ncclient-0.6.15/Changelog000066400000000000000000000113331451325530500152200ustar00rootroot00000000000000version 0.6.11 ------------- * Support for custom client capabilities * Restructuring/refactoring of example scripts * Minor bugfixes * Minor unit test refactoring version 0.6.10 ------------- * NETCONF call-home (RFC8071) support * YANG 1.1 `action` support * Nokia SR OS device handler support * Removal of old ALU base-r13 API documentation * Increased test coverage * Variety of bugfixes and minor enhancements from a variety of contributors since 0.6.9 (see commit history) * Thanks to all contributors! version 0.6.9 ------------- * Resiolved breaking API change version 0.6.8 ------------- * Variety of small updates and bugfixes, but of note: - Support for namespace prefixes for XPath queries - `edit-config` parameter validation - Support for multiple RPC errors - API to get supported device types - Support for subtree filters with multiple top-level tags * Thanks to all contributors! * Pulled due to avccidental breaking API change version 0.6.7 ------------- * Variety of bugfixes from a variety of contributors since 0.6.6 (see commit history) version 0.6.6 ------------- * Read ssh timeout from config file if not specified in method call * Tox support * Huge XML tree parser support * Adding optional bind address to connect version 0.6.5 ------------- * Updated README for 0.6.5 release version 0.6.4 ------------- * Pin selectors2 to Python versions <= 3.4 * Fix config examples to actually use the nc namespace * Fix: correctly set port for paramiko when using ssh_config file * Test: add test to check ProxyCommand uses correct port * Update commits for py3 * Enhance Alcatel-Lucent-support * Juniper RPC: allow specifying format in CompareConfiguration * Parsing of NETCONF 1.1 frames no longer decodes each chunk of bytes * Fix filter in create_subscription * Validate 'with-defaults' mode based on supported modes advertised in capability URI version 0.6.3 ------------- * Fix homepage link registered with PyPi * SSH Host Key checking * Updated junos.py to resolve RestrictedUser error * Close the channel when closing SSH session * Invoke self.parse() to ensure errors, if any, have been detected before check in ok() version 0.6.2 ------------- * Migration to user selectors instead of select, allowing higher scale operations * Improved netconf:base:1.1 parsing * Graceful exit on session close version 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.15/LICENSE000066400000000000000000000261361451325530500144220ustar00rootroot00000000000000 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.15/MANIFEST.in000066400000000000000000000002371451325530500151450ustar00rootroot00000000000000include *.rst requirements.txt LICENSE graft docs graft examples include versioneer.py include ncclient/_version.py include test include test-requirements.txt ncclient-0.6.15/NOTICE000066400000000000000000000002471451325530500143140ustar00rootroot00000000000000ncclient Portions Copyright (c) 2019 Cisco Systems, Inc. and/or its affiliates This project includes software developed at Cisco Systems, Inc. and/or its affiliates. ncclient-0.6.15/README.md000066400000000000000000000240021451325530500146620ustar00rootroot00000000000000![Build Status](https://github.com/ncclient/ncclient/actions/workflows/check.yaml/badge.svg) [![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 | | :----: | :-----: | :---------- | | 04/10/22 | `0.6.13` | See [release page](https://github.com/ncclient/ncclient/releases/tag/v0.6.13)| | 05/29/21 | `0.6.12` | See [release page](https://github.com/ncclient/ncclient/releases/tag/v0.6.12)| | 05/27/21 | `0.6.11` | See [release page](https://github.com/ncclient/ncclient/releases/tag/v0.6.11)| | 02/18/21 | `0.6.10` | See [release page](https://github.com/ncclient/ncclient/releases/tag/v0.6.10)| | 08/08/20 | `0.6.9` | See [release page](https://github.com/ncclient/ncclient/releases/tag/v0.6.9) | | 08/01/20 | `0.6.8` | Pulled due to accidental breaking API change | | 12/21/19 | `0.6.7` | See [release page](https://github.com/ncclient/ncclient/releases/tag/v0.6.7) | | 05/27/19 | `0.6.6` | See [release page](https://github.com/ncclient/ncclient/releases/tag/v0.6.6) | | 05/27/19 | `0.6.5` | Pulled due to bug in PyPi upload | | 04/07/19 | `0.6.4` | See [release page](https://github.com/ncclient/ncclient/releases/tag/v0.6.4) | | 09/26/18 | `0.6.3` | See [release page](https://github.com/ncclient/ncclient/releases/tag/v0.6.3) | | 08/20/18 | `0.6.2` | See [release page](https://github.com/ncclient/ncclient/releases/tag/v0.6.2) | | 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.5+ * 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 Also locally via pip from within local clone: pip install -U . ## 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 When instantiating a connection to a known type of NETCONF server: * Juniper: `device_params={'name':'junos'}` * Cisco: - CSR: `device_params={'name':'csr'}` - Nexus: `device_params={'name':'nexus'}` - IOS XR: `device_params={'name':'iosxr'}` - IOS XE: `device_params={'name':'iosxe'}` * Huawei: - `device_params={'name':'huawei'}` - `device_params={'name':'huaweiyang'}` * Nokia SR OS: `device_params={'name':'sros'}` * H3C: `device_params={'name':'h3c'}` * HP Comware: `device_params={'name':'hpcomware'}` * Server or anything not in above: `device_params={'name':'default'}` ## For Developers ### Running Unit Tests Locally To run the same tests locally as are run via GitHub's CI/CD integration with Travis, the following istructions can be followed: 1. Create a virtual environment, in this case using `virtualenvwrapper`: ``` mkvirtualenv ncclient-testing ``` 1. Install your local `ncclient` package (ensuring you are in your virtual environment): ``` pip install -U . ``` 1. Install testing dependencies: ``` pip install nose rednose coverage coveralls mock ``` 1. Finally, run the tests: ``` nosetests test --rednose --verbosity=3 ``` ### Making a Release As of `0.6.1`, `versioneer` has been integrated into the `ncclient` codebase. This simplifies the creation of a new release, by ensuring that version numbers are automatically generated from the git tag used for the release, which **must** be in the form `v0.1.2`. Versioneer also allows for the clean install of development versions locally using pip. For example: ``` $ pip install -U . Processing /opt/git-repos/versioneer-ncclient [...intermediate ouput elided...] Building wheels for collected packages: ncclient Running setup.py bdist_wheel for ncclient ... done Stored in directory: /Users/einarnn/Library/Caches/pip/wheels/fb/48/a8/5c781ebcfff7f091e18950e125c0ff638a5a2dc006610aa1e5 Successfully built ncclient Installing collected packages: ncclient Found existing installation: ncclient 0.6.1 Uninstalling ncclient-0.6.1: Successfully uninstalled ncclient-0.6.1 Successfully installed ncclient-0.6.0+23.g0d9ccd6.dirty ``` Thus, making a release becomes a simple process: 1. Ensure all tests run clean (ideally both locally and via Travis) and that `README.md` (yes, this file!!) has been updated appropriately. 2. Apply appropriate version tag, e.g. `git tag v0.6.1` 3. Build packages: ``` python setup.py bdist sdist ``` 4. After ensuring twine is installed, test twine upload: ``` twine upload \ --repository-url https://test.pypi.org/legacy/ \ -u ******* -p ******* \ dist/ncclient-0.6.1.tar.gz ```` 5. Push git tags back to origin, `git push --tags` 6. Do real twine upload: ``` twine upload \ -u ******* -p ******* \ dist/ncclient-0.6.1.tar.gz ``` ## Contributors * v0.6.12: @einarnn * v0.6.11: @musicinmybrain, @sstancu, @earies * v0.6.10: @vnitinv, @omaxx, @einarnn, @musicinmybrain, @tonynii, @sstancu, Martin Volf, @fredgan, @avisom, Viktor Velichkin, @ogenstad, @earies * v0.6.8: [Fred Gan](https://github.com/fredgan), @vnitinv, @kbijakowski, @iwanb, @badguy99, @liuyong, Andrew Mallory, William Lvory * v0.6.7: @vnitinv, @chaitu-tk, @sidhujasminder, @crutcha, @markgoddard, @ganeshrn, @songxl, @doesitblend, @psikala, @xuxiaowei0512, @muffizone * v0.6.6: @sstancu, @hemna, @ishayansheikh * v0.6.4: @davidhankins, @mzagozen, @knobix, @markafarrell, @psikala, @moepman, @apt-itude, @yuekyang * v0.6.3: @rdkls, @Anthony25, @rsmekala, @vnitinv, @siming85 * v0.6.2: @einarnn, @glennmatthews, @bryan-stripe, @nickylba * 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.15/README.rst000066400000000000000000000241151451325530500150770ustar00rootroot00000000000000ncclient: 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) ` and `Einar Nilsen-Nygaard (@einarnn)`. 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'}` - Nexus: `device_params={'name':'nexus'}` - IOS XR: `device_params={'name':'iosxr'}` - IOS XE: `device_params={'name':'iosxe'}` * Huawei: - `device_params={'name':'huawei'}` - `device_params={'name':'huaweiyang'}` * Nokia SR OS: `device_params={'name':'sros'}` * H3C: `device_params={'name':'h3c'}` * HP Comware: `device_params={'name':'hpcomware'}` * Server or anything not in above: `device_params={'name':'default'}` Changes \| brief ~~~~~~~~~~~~~~~~ **v0.6.12** * Fix for accidental breakage of Juniper ExecuteRPC support **v0.6.11** * Support for custom client capabilities * Restructuring/refactoring of example scripts * Minor bugfixes * Minor unit test refactoring **v0.6.10** * NETCONF call-home (RFC8071) support * YANG 1.1 `action` support * Nokia SR OS device handler support * Removal of old ALU base-r13 API documentation * Increased test coverage * Variety of bugfixes and minor enhancements from a variety of contributors since 0.6.9 (see commit history) * Thanks to all contributors! **v0.6.9** * Fix for breaking API change **v0.6.8** * Pulled due to accidental breaking API change * Variety of small updates and bugfixes, but of note: - Support for namespace prefixes for XPath queries - `edit-config` parameter validation - Support for multiple RPC errors - API to get supported device types - Support for subtree filters with multiple top-level tags * Thanks to all contributors! **v0.6.7** - Variety of bugfixes from a variety of contributors since 0.6.6 (see commit history) **v0.6.6** - Read ssh timeout from config file if not specified in method call - Tox support - Huge XML tree parser support - Adding optional bind address to connect **v0.6.5** - Updated README for 0.6.5 release **v0.6.4** - Pin selectors2 to Python versions <= 3.4 - Fix config examples to actually use the nc namespace - Fix: correctly set port for paramiko when using ssh_config file - Test: add test to check ProxyCommand uses correct port - Update commits for py3 - Enhance Alcatel-Lucent-support - Juniper RPC: allow specifying format in CompareConfiguration - Parsing of NETCONF 1.1 frames no longer decodes each chunk of bytes - Fix filter in create_subscription - Validate 'with-defaults' mode based on supported modes advertised in capability URI **v0.6.3** - Fix homepage link registered with PyPi - SSH Host Key checking - Updated junos.py to resolve RestrictedUser error - Close the channel when closing SSH session - Invoke self.parse() to ensure errors, if any, have been detected before check in ok() **v0.6.2** - Migration to user selectors instead of select, allowing higher scale operations - Improved netconf:base:1.1 parsing - Graceful exit on session close **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.11: @musicinmybrain, @sstancu, @earies - v0.6.10: @vnitinv, @omaxx, @einarnn, @musicinmybrain, @tonynii, @sstancu, Martin Volf, @fredgan, @avisom, Viktor Velichkin, @ogenstad, @earies - v0.6.9: [Fred Gan](https://github.com/fredgan) - v0.6.8: [Fred Gan](https://github.com/fredgan), @vnitinv, @kbijakowski, @iwanb, @badguy99, @liuyong, Andrew Mallory, William Lvory - v0.6.7: @vnitinv, @chaitu-tk, @sidhujasminder, @crutcha, @markgoddard, @ganeshrn, @songxl, @doesitblend, @psikala, @xuxiaowei0512, @muffizone - v0.6.6: @sstancu, @hemna, @ishayansheikh - v0.6.4: @davidhankins, @mzagozen, @knobix, @markafarrell, @psikala, @moepman, @apt-itude, @yuekyang - v0.6.3: @rdkls, @Anthony25, @rsmekala, @vnitinv, @siming85 - v0.6.2: @einarnn, @glennmatthews, @bryan-stripe, @nickylba - 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.15/docs/000077500000000000000000000000001451325530500143355ustar00rootroot00000000000000ncclient-0.6.15/docs/Makefile000066400000000000000000000060761451325530500160060ustar00rootroot00000000000000# 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.15/docs/requirements.txt000066400000000000000000000000461451325530500176210ustar00rootroot00000000000000Sphinx==3.2.0 -r ../requirements.txt ncclient-0.6.15/docs/source/000077500000000000000000000000001451325530500156355ustar00rootroot00000000000000ncclient-0.6.15/docs/source/_static/000077500000000000000000000000001451325530500172635ustar00rootroot00000000000000ncclient-0.6.15/docs/source/_static/logo.png000066400000000000000000000120761451325530500207370ustar00rootroot00000000000000PNG  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.15/docs/source/api.rst000066400000000000000000000002001451325530500171300ustar00rootroot00000000000000Complete API documentation ========================== .. toctree:: capabilities xml_ transport operations ncclient-0.6.15/docs/source/capabilities.rst000066400000000000000000000013201451325530500210140ustar00rootroot00000000000000: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.15/docs/source/conf.py000066400000000000000000000151171451325530500171410ustar00rootroot00000000000000# -*- 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 ----------------------------------------------------- needs_sphinx = '2.0' # 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.imgmath', '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.6' # The full version, including alpha/beta/rc tags. release = '0.6.12' # 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.15/docs/source/extending.rst000066400000000000000000000002031451325530500203470ustar00rootroot00000000000000Extending ncclient ================== **TODO** Here it is discussed how new transport mappings and new operations can be added. ncclient-0.6.15/docs/source/index.rst000066400000000000000000000051161451325530500175010ustar00rootroot00000000000000Welcome ======= `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:`6241`. * 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'}` - Nexus: `device_params={'name':'nexus'}` - IOS XR: `device_params={'name':'iosxr'}` - IOS XE: `device_params={'name':'iosxe'}` * Huawei: - `device_params={'name':'huawei'}` - `device_params={'name':'huaweiyang'}` * 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'}` Contents: .. toctree:: manager api Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` ncclient-0.6.15/docs/source/manager.rst000066400000000000000000000143041451325530500200030ustar00rootroot00000000000000: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:`~lxml.etree._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 .. autoattribute:: HUGE_TREE_DEFAULT .. method:: get_config(source, filter=None, with_defaults=None) `get_config` is mapped to :class:`~ncclient.operations.GetConfig` .. method:: get_schema(identifier, version=None, format=None) `get_schema` is mapped to :class:`~ncclient.operations.GetSchema` .. method:: edit_config(config, format='xml', target='candidate', default_operation=None, test_option=None, error_option=None) `edit_config` is mapped to :class:`~ncclient.operations.EditConfig` .. method:: copy_config(source, target) `copy_config` is mapped to :class:`~ncclient.operations.CopyConfig` .. method:: delete_config(target) `delete_config` is mapped to :class:`~ncclient.operations.DeleteConfig` .. method:: dispatch(rpc_command, source=None, filter=None) `dispatch` is mapped to :class:`~ncclient.operations.Dispatch` .. method:: lock(target="candidate") `lock` is mapped to :class:`~ncclient.operations.Lock` .. method:: unlock(target="candidate") `unlock` is mapped to :class:`~ncclient.operations.Unlock` .. method:: get(filter=None, with_defaults=None) `get` is mapped to :class:`~ncclient.operations.Get` .. method:: close_session() `close_session` is mapped to :class:`~ncclient.operations.CloseSession` .. method:: kill_session(session_id) `kill_session` is mapped to :class:`~ncclient.operations.KillSession` .. method:: commit(confirmed=False, timeout=None, persist=None, persist_id=None) `commit` is mapped to :class:`~ncclient.operations.Commit` .. method:: cancel_commit(persist_id=None) `cancel_commit` is mapped to :class:`~ncclient.operations.CancelCommit` .. method:: discard_changes() `discard_changes` is mapped to :class:`~ncclient.operations.DiscardChanges` .. method:: validate(source="candidate") `validate` is mapped to :class:`~ncclient.operations.Validate` .. method:: create_subscription(filter=None, stream_name=None, start_time=None, stop_time=None) `create_subscription` is mapped to :class:`~ncclient.operations.CreateSubscription` .. method:: reboot_machine() `reboot_machine` is mapped to :class:`~ncclient.operations.RebootMachine` .. method:: poweroff_machine() `poweroff_machine` is mapped to :class:`~ncclient.operations.PoweroffMachine` .. automethod:: locked(target) .. 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 .. autoattribute:: huge_tree 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 or a tuple containing a dict of namespace mapping and the XPath expression. * For `"subtree"` the *criteria* should be an XML string or an :class:`~xml.etree.ElementTree.Element` object containing the criteria. * A list of *spec* Here *type* has to be `"subtree"`. * the *spec* should be a list containing multiple XML string or multiple :class:`~xml.etree.ElementTree.Element` objects. * A `` element as an XML string or an :class:`~xml.etree.ElementTree.Element` object. ncclient-0.6.15/docs/source/operations.rst000066400000000000000000000046111451325530500205540ustar00rootroot00000000000000: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, huge_tree .. 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 .. autoclass:: GetSchema :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: Flowmon ........ .. autoclass:: PoweroffMachine :members: request :show-inheritance: .. autoclass:: RebootMachine :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: Subscribing ............ .. autoclass:: CreateSubscription :members: request :show-inheritance: Exceptions ---------- .. autoexception:: OperationError :show-inheritance: .. autoexception:: MissingCapabilityError :show-inheritance: .. autoexception:: TimeoutExpiredError :show-inheritance: ncclient-0.6.15/docs/source/transport.rst000066400000000000000000000022451451325530500204260ustar00rootroot00000000000000: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, hostkey=None, look_for_keys=True, ssh_config=None, bind_addr=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.15/docs/source/xml_.rst000066400000000000000000000011331451325530500173240ustar00rootroot00000000000000: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.15/examples/000077500000000000000000000000001451325530500152235ustar00rootroot00000000000000ncclient-0.6.15/examples/base/000077500000000000000000000000001451325530500161355ustar00rootroot00000000000000ncclient-0.6.15/examples/base/nc01.py000066400000000000000000000012711451325530500172510ustar00rootroot00000000000000#! /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.15/examples/base/nc02.py000066400000000000000000000011221451325530500172450ustar00rootroot00000000000000#! /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.15/examples/base/nc03.py000066400000000000000000000013721451325530500172550ustar00rootroot00000000000000#! /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.15/examples/base/nc04.py000066400000000000000000000020261451325530500172530ustar00rootroot00000000000000#! /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.15/examples/base/nc05.py000066400000000000000000000016261451325530500172610ustar00rootroot00000000000000#! /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.15/examples/base/nc06.py000066400000000000000000000015331451325530500172570ustar00rootroot00000000000000#! /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.15/examples/base/nc07.py000066400000000000000000000017261451325530500172640ustar00rootroot00000000000000#! /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.15/examples/base/nc08.py000066400000000000000000000026331451325530500172630ustar00rootroot00000000000000#! /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.15/examples/base/nc09.py000066400000000000000000000020731451325530500172620ustar00rootroot00000000000000#! /usr/bin/env python # # Connect to the NETCONF server passed on the command line while passing # custom capabilities on the default device handler and display the # client capabilities. For brevity and clarity of the examples, we omit # proper exception handling. # # $ ./nc09.py host username password import logging import os import sys import warnings warnings.simplefilter("ignore", DeprecationWarning) from ncclient import manager def demo(host, user, password): with manager.connect( host=host, port=830, username=user, password=password, nc_params={ 'capabilities': [ 'urn:custom:capability:1.0', 'urn:custom:capability:2.0' ]}, hostkey_verify=False) as m: for c in m.client_capabilities: print(c) 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) demo(sys.argv[1], sys.argv[2], sys.argv[3]) ncclient-0.6.15/examples/vendor/000077500000000000000000000000001451325530500165205ustar00rootroot00000000000000ncclient-0.6.15/examples/vendor/alu/000077500000000000000000000000001451325530500173015ustar00rootroot00000000000000ncclient-0.6.15/examples/vendor/alu/edit_config.py000066400000000000000000000020751451325530500221310ustar00rootroot00000000000000#!/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=10, device_params={'name': 'alu'}, hostkey_verify=False) print('Set CLI -config -block') config = """ configure port 1/1/1 description \"Loaded as CLI -block\" exit""" conn.load_configuration(format='cli', config=config) print('Load XML -config') config = new_ele('configure', attrs={'xmlns': ALU_CONFIG}) port = sub_ele(config, 'port') sub_ele(port, 'port-id').text = '1/1/1' desc = sub_ele(port, 'description') sub_ele(desc, 'long-description-string').text = "Loaded using XML" conn.load_configuration(config=config, format='xml') conn.close_session() if __name__ == '__main__': connect('router', 830, 'admin', 'admin') ncclient-0.6.15/examples/vendor/alu/get_config.py000066400000000000000000000032561451325530500217650ustar00rootroot00000000000000#!/usr/bin/env python import logging from lxml import etree 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=10, device_params={'name': 'alu'}, hostkey_verify=False) logging.info('Retrieving full config, please wait ...') result = conn.get_configuration() logging.info(result) logging.info('Here is the chassis configuration') output = result.xpath('data/configure/system/chassis')[0] logging.info(to_xml(output)) logging.info('Retrieving service config') # specify filter to pass to get_config filter = new_ele('configure', attrs={'xmlns': ALU_CONFIG}) sub_ele(filter, 'service') result = conn.get_configuration(filter=filter) epipes = result.xpath('data/configure/service/epipe') for i in epipes: logging.info(etree.tostring(i, pretty_print=True).decode('utf-8')) logging.info('Getting CLI -config') cli_cfg = conn.get_configuration(content='cli', filter=['port 1/1/11']) logging.info(cli_cfg) logging.info('Get detailed CLI -config') cli_cfg = conn.get_configuration(content='cli', filter=['port 1/1/11'], detail=True) logging.info(cli_cfg) 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('localhost', 830, 'admin', 'admin') ncclient-0.6.15/examples/vendor/cisco/000077500000000000000000000000001451325530500176205ustar00rootroot00000000000000ncclient-0.6.15/examples/vendor/cisco/csr1000v_example.py000066400000000000000000000077641451325530500232010ustar00rootroot00000000000000# 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.15/examples/vendor/cisco/nxosapi.py000066400000000000000000000107741451325530500216640ustar00rootroot00000000000000#! /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.15/examples/vendor/ericsson/000077500000000000000000000000001451325530500203455ustar00rootroot00000000000000ncclient-0.6.15/examples/vendor/ericsson/ericsson_nc_prefix_example.py000066400000000000000000000035701451325530500263210ustar00rootroot00000000000000#! /usr/bin/env python # # Connect to the NETCONF server passed on the command line and # set a device_params to turn on/off the namespace prefix "nc". # if you want to verify the result, you can print the request that # was sent. For brevity and clarity of the examples, we omit proper # exception handling. # # $ ./ericsson_nc_prefix_example.py host username password import sys, os, warnings warnings.simplefilter("ignore", DeprecationWarning) from ncclient import manager def ericsson_connect(host, port, user, password, device_params): return manager.connect(host=host, port=port, username=user, password=password, device_params=device_params, hostkey_verify-false) def enable_nc_prefix(host, user, password): # add a parameter 'with_ns' to turn on/off 'nc' device_params = {'name': 'ericsson', 'with_ns': True} with ericsson_connect(host, port=22, user=user, password=password, device_params=device_params) as m: ret = m.get_config(source="running").data_xml print(ret) def disable_nc_prefix(host, user, password): # add a parameter 'with_ns' to turn on/off 'nc' device_params = {'name': 'ericsson', 'with_ns': False} with ericsson_connect(host, port=22, user=user, password=password, device_params=device_params) as m: ret = m.get_config(source="running").data_xml print(ret) def demo(host, user, password): enable_nc_prefix(host, user, password) print("#"*50) disable_nc_prefix(host, user, password) if __name__ == '__main__': demo(sys.argv[1], sys.argv[2], sys.argv[3]) ncclient-0.6.15/examples/vendor/juniper/000077500000000000000000000000001451325530500201745ustar00rootroot00000000000000ncclient-0.6.15/examples/vendor/juniper/command-jnpr.py000066400000000000000000000021601451325530500231320ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/compare-config-jnpr.py000066400000000000000000000014411451325530500244060ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/delete-config-jnpr.py000066400000000000000000000022631451325530500242250ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/edit-config-bgp-peer.py000066400000000000000000000041051451325530500244350ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/edit-config-jnpr-json.py000066400000000000000000000034241451325530500246570ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/edit-config-jnpr-set.py000066400000000000000000000026351451325530500245040ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/edit-config-jnpr-text.py000066400000000000000000000027201451325530500246700ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/edit-config-jnpr.py000066400000000000000000000031751451325530500237130ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/edit-config-std.py000066400000000000000000000025371451325530500235350ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/edit-config-text-std.py000066400000000000000000000022011451325530500245030ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/execute-async-rpc.py000066400000000000000000000024731451325530500241130ustar00rootroot00000000000000import 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.15/examples/vendor/juniper/execute-rpc-string.py000066400000000000000000000016371451325530500243050ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/execute-rpc.py000066400000000000000000000015331451325530500227740ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/get-config-jnpr.py000066400000000000000000000043231451325530500235410ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/get-configuration-jnpr.py000066400000000000000000000034511451325530500251440ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/get-configuration-matching.py000066400000000000000000000035061451325530500257660ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/get-interface-status.py000066400000000000000000000022461451325530500246100ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/get-inventory.py000066400000000000000000000015751451325530500233700ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/get-session-params.py000066400000000000000000000025511451325530500242720ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/get-xnm-information.py000066400000000000000000000025611451325530500244540ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/get_l2vpns.py000066400000000000000000000036731451325530500226420ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/outbound-ssh-ncclient.py000066400000000000000000000052511451325530500250000ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/pubkey-auth.py000066400000000000000000000013671451325530500230130ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/request-reboot.py000066400000000000000000000020521451325530500235250ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/rollback.py000066400000000000000000000013671451325530500223460ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/set-description.py000066400000000000000000000015461451325530500236700ustar00rootroot00000000000000#!/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.15/examples/vendor/juniper/unknown-rpc.py000066400000000000000000000015351451325530500230330ustar00rootroot00000000000000#!/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.15/examples/vendor/nokia/000077500000000000000000000000001451325530500176215ustar00rootroot00000000000000ncclient-0.6.15/examples/vendor/nokia/sros/000077500000000000000000000000001451325530500206075ustar00rootroot00000000000000ncclient-0.6.15/examples/vendor/nokia/sros/commit.py000066400000000000000000000041751451325530500224600ustar00rootroot00000000000000import logging import sys from ncclient import manager # Global constants DEVICE_HOST = 'localhost' DEVICE_PORT = 830 DEVICE_USER = 'admin' DEVICE_PASS = 'admin' EDIT_CONFIG_PAYLOAD = """ Sample Community r v2c """ def create_session(host, port, username, password): """Creates and returns an ncclient manager session.""" return manager.connect( host=host, port=port, username=username, password=password, hostkey_verify=False, device_params={'name': 'sros'} ) def edit_config(m, config_payload): """Edits the configuration with the given payload.""" m.edit_config(target='candidate', config=config_payload) def commit_changes(m, comment=''): """Commits changes with an optional comment.""" response = m.commit(comment=comment) logging.info(response) def main(): LOG_FORMAT = '%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(stream=sys.stdout, level=logging.INFO, format=LOG_FORMAT) try: with create_session(DEVICE_HOST, DEVICE_PORT, DEVICE_USER, DEVICE_PASS) as m: # Lock the configuration m.lock(target='candidate') # Edit the configuration edit_config(m, EDIT_CONFIG_PAYLOAD) # Commit the configuration commit_changes(m, comment='A sample commit comment goes here') # Unlock the configuration m.unlock(target='candidate') except Exception as e: logging.error(f"Encountered an error: {e}") if __name__ == '__main__': main() ncclient-0.6.15/examples/vendor/nokia/sros/get_config.py000066400000000000000000000046641451325530500232770ustar00rootroot00000000000000#!/usr/bin/env python # import logging import sys from ncclient import manager from ncclient.xml_ import to_xml from ncclient.operations.rpc import RPCError _NS_MAP = { 'nc': 'urn:ietf:params:xml:ns:netconf:base:1.0', 'nokia-conf': 'urn:nokia.com:sros:ns:yang:sr:conf' } _NOKIA_MGMT_INT_FILTER = ''' ''' % (_NS_MAP['nokia-conf']) def connect(host, port, user, password): m = manager.connect(host=host, port=port, username=user, password=password, device_params={'name': 'sros'}, hostkey_verify=False) ## Retrieve full configuration from the running datastore running_xml = m.get_config(source='running') logging.info(running_xml) ## Retrieve full configuration from the running datastore and strip ## the rpc-reply + data elements running_xml = m.get_config(source='running').xpath( '/nc:rpc-reply/nc:data/nokia-conf:configure', namespaces=_NS_MAP)[0] logging.info(to_xml(running_xml, pretty_print=True)) ## Retrieve full configuration from the running datastore and strip ## out elements except for /configure/system/management-interface running_xml = m.get_config(source='running').xpath( '/nc:rpc-reply/nc:data/nokia-conf:configure' \ '/nokia-conf:system/nokia-conf:management-interface', namespaces=_NS_MAP)[0] logging.info(to_xml(running_xml, pretty_print=True)) ## Retrieve a specific filtered subtree from the running datastore ## and handle any rpc-error should a portion of the filter criteria ## be invalid try: running_xml = m.get_config(source='running', filter=('subtree', _NOKIA_MGMT_INT_FILTER), with_defaults='report-all').xpath( '/nc:rpc-reply/nc:data/nokia-conf:configure', namespaces=_NS_MAP)[0] logging.info(to_xml(running_xml, pretty_print=True)) except RPCError as err: logging.info('Error: %s' % err.message.strip()) m.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) try: connect(sys.argv[1], '830', 'admin', 'admin') except IndexError: logging.error('Must supply a valid hostname or IP address') ncclient-0.6.15/examples/vendor/nokia/sros/md_cli_raw_command.py000066400000000000000000000057611451325530500247700ustar00rootroot00000000000000#!/usr/bin/env python # # For Nokia SR OS > 20.10, support for issuing a subset of CLI commands # (e.g. 'show', 'admin', 'clear', 'ping', etc..) over NETCONF RPCs is # achieved by the use of the YANG 1.1 action import logging import sys from ncclient import manager from ncclient.operations.rpc import RPCError _NS_MAP = { 'nc': 'urn:ietf:params:xml:ns:netconf:base:1.0', 'nokia-oper': 'urn:nokia.com:sros:ns:yang:sr:oper-global' } def connect(host, port, user, password): m = manager.connect(host=host, port=port, username=user, password=password, device_params={'name': 'sros'}, hostkey_verify=False) ## Issue 'show card' and display the raw XML output result = m.md_cli_raw_command('show card') logging.info(result) ## Issue 'show port' from MD-CLI context and emit only ## the returned command contents result = m.md_cli_raw_command('show port') output = result.xpath( '/nc:rpc-reply/nokia-oper:results' \ '/nokia-oper:md-cli-output-block', namespaces=_NS_MAP)[0].text logging.info(output) ## Issue 'show version' from Classic CLI context and emit ## only the returned command contents result = m.md_cli_raw_command('//show version') output = result.xpath( '/nc:rpc-reply/nokia-oper:results' \ '/nokia-oper:md-cli-output-block', namespaces=_NS_MAP)[0].text logging.info(output) ## Issue 'ping' from MD-CLI context and emit only the ## returned command contents result = m.md_cli_raw_command( 'ping 127.0.0.1 router-instance "Base" count 3') output = result.xpath( '/nc:rpc-reply/nokia-oper:results' \ '/nokia-oper:md-cli-output-block', namespaces=_NS_MAP)[0].text logging.info(output) ## Issue an admin command that returns only NETCONF or ## result = m.md_cli_raw_command('admin save') try: if len(result.xpath('/nc:rpc-reply/nc:ok', namespaces=_NS_MAP)) > 0: logging.info('Admin save successful') else: logging.info('Admin save unsuccessful') except RPCError as err: logging.info('Error: %s' % err.message.strip()) ## Issue an unsupported command and handle the RPC error gracefully try: result = m.md_cli_raw_command('configure') if len(result.xpath('/nc:rpc-reply/nc:ok', namespaces=_NS_MAP)) > 0: logging.info('Command successful') else: logging.info('Command unsuccessful') except RPCError as err: logging.info('Error: %s' % err.message.strip()) m.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) try: connect(sys.argv[1], '830', 'admin', 'admin') except IndexError: logging.error('Must supply a valid hostname or IP address') ncclient-0.6.15/ncclient/000077500000000000000000000000001451325530500152045ustar00rootroot00000000000000ncclient-0.6.15/ncclient/__init__.py000066400000000000000000000015401451325530500173150ustar00rootroot00000000000000# 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,6,12) 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 from . import _version __version__ = _version.get_versions()['version'] ncclient-0.6.15/ncclient/_version.py000066400000000000000000000561641451325530500174160ustar00rootroot00000000000000 # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. # Generated by versioneer-0.28 # https://github.com/python-versioneer/python-versioneer """Git implementation of _version.py.""" import errno import os import re import subprocess import sys from typing import Callable, Dict import functools def get_keywords(): """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = " (tag: v0.6.15)" git_full = "f0f4d95ed8613f256f0972491d6c47f53246da0b" git_date = "2023-10-16 16:46:13 +0100" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_config(): """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "pep440" cfg.tag_prefix = "v" cfg.parentdir_prefix = "None" cfg.versionfile_source = "ncclient/_version.py" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY: Dict[str, str] = {} HANDLERS: Dict[str, Dict[str, Callable]] = {} def register_vcs_handler(vcs, method): # decorator """Create decorator to mark a method as the handler of a VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) process = None popen_kwargs = {} if sys.platform == "win32": # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW popen_kwargs["startupinfo"] = startupinfo for command in commands: try: dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git process = subprocess.Popen([command] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None), **popen_kwargs) break except OSError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None, None stdout = process.communicate()[0].strip().decode() if process.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) return None, process.returncode return stdout, process.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: with open(versionfile_abs, "r") as fobj: for line in fobj: if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) except OSError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if "refnames" not in keywords: raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] # Filter out refs that exactly match prefix or that don't start # with a number once the prefix is stripped (mostly a concern # when prefix is '') if not re.match(r'\d', r): continue if verbose: print("picking %s" % r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] # GIT_DIR can interfere with correct operation of Versioneer. # It may be intended to be passed to the Versioneer-versioned project, # but that should not change where we get our version from. env = os.environ.copy() env.pop("GIT_DIR", None) runner = functools.partial(runner, env=env) _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %s not under git control" % root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = runner(GITS, [ "describe", "--tags", "--dirty", "--always", "--long", "--match", f"{tag_prefix}[[:digit:]]*" ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root) # --abbrev-ref was added in git-1.6.3 if rc != 0 or branch_name is None: raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") branch_name = branch_name.strip() if branch_name == "HEAD": # If we aren't exactly on a branch, pick a branch which represents # the current commit. If all else fails, we are on a branchless # commit. branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) # --contains was added in git-1.5.4 if rc != 0 or branches is None: raise NotThisMethod("'git branch --contains' returned error") branches = branches.split("\n") # Remove the first line if we're running detached if "(" in branches[0]: branches.pop(0) # Strip off the leading "* " from the list of branches. branches = [branch[2:] for branch in branches] if "master" in branches: branch_name = "master" elif not branches: branch_name = None else: # Pick the first branch that is returned. Good or bad. branch_name = branches[0] pieces["branch"] = branch_name # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_branch(pieces): """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . The ".dev0" means not master branch. Note that .dev0 sorts backwards (a feature branch will appear "older" than the master branch). Exceptions: 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0" if pieces["branch"] != "master": rendered += ".dev0" rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def pep440_split_post(ver): """Split pep440 version string at the post-release segment. Returns the release segments before the post-release and the post-release version number (or -1 if no post-release segment is present). """ vc = str.split(ver, ".post") return vc[0], int(vc[1] or 0) if len(vc) == 2 else None def render_pep440_pre(pieces): """TAG[.postN.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post0.devDISTANCE """ if pieces["closest-tag"]: if pieces["distance"]: # update the post release segment tag_version, post_version = pep440_split_post(pieces["closest-tag"]) rendered = tag_version if post_version is not None: rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) else: rendered += ".post0.dev%d" % (pieces["distance"]) else: # no commits, use the tag as the version rendered = pieces["closest-tag"] else: # exception #1 rendered = "0.post0.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_post_branch(pieces): """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . The ".dev0" means not master branch. Exceptions: 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += "+g%s" % pieces["short"] if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-branch": rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-post-branch": rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} def get_versions(): """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. for _ in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to find root of source tree", "date": None} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} ncclient-0.6.15/ncclient/capabilities.py000066400000000000000000000105221451325530500202070ustar00rootroot00000000000000# 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 logging import six logger = logging.getLogger("ncclient.capabilities") 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.add(uri) def __contains__(self, key): try: self.__getitem__(key) except KeyError: return False else: return True def __getitem__(self, key): try: return self._dict[key] except KeyError: for capability in six.itervalues(self._dict): if key in capability.get_abbreviations(): return capability raise KeyError(key) 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] = Capability.from_uri(uri) def remove(self, uri): "Remove a capability." if uri in self._dict: del self._dict[uri] class Capability(object): """Represents a single capability""" def __init__(self, namespace_uri, parameters=None): self.namespace_uri = namespace_uri self.parameters = parameters or {} @classmethod def from_uri(cls, uri): split_uri = uri.split("?") namespace_uri = split_uri[0] capability = cls(namespace_uri) try: param_string = split_uri[1] except IndexError: return capability capability.parameters = { param.key: param.value for param in _parse_parameter_string(param_string, uri) } return capability def __eq__(self, other): return ( self.namespace_uri == other.namespace_uri and self.parameters == other.parameters ) def get_abbreviations(self): return _abbreviate(self.namespace_uri) def _parse_parameter_string(string, uri): for param_string in string.split("&"): try: yield _Parameter.from_string(param_string) except _InvalidParameter: logger.error( "Invalid parameter '{param}' in capability URI '{uri}'".format( param=param_string, uri=uri, ) ) class _Parameter(object): """Represents a parameter to a capability""" def __init__(self, key, value): self.key = key self.value = value @classmethod def from_string(cls, string): try: key, value = string.split("=") except ValueError: raise _InvalidParameter return cls(key, value) class _InvalidParameter(Exception): pass ncclient-0.6.15/ncclient/debug.py000066400000000000000000000015771451325530500166560ustar00rootroot00000000000000# 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.15/ncclient/devices/000077500000000000000000000000001451325530500166265ustar00rootroot00000000000000ncclient-0.6.15/ncclient/devices/__init__.py000066400000000000000000000014541451325530500207430ustar00rootroot00000000000000# supported devices config, add new device (eg: 'device name':'device label'). supported_devices_cfg = {'junos':'Juniper', 'csr':'Cisco CSR1000v', 'nexus':'Cisco Nexus', 'iosxr':'Cisco IOS XR', 'iosxe':'Cisco IOS XE', 'huawei':'Huawei', 'huaweiyang':'Huawei', 'alu':'Alcatel Lucent', 'h3c':'H3C', 'hpcomware':'HP Comware', 'sros':'Nokia SR OS', 'default':'Server or anything not in above'} def get_supported_devices(): return tuple(supported_devices_cfg.keys()) def get_supported_device_labels(): return supported_devices_cfg ncclient-0.6.15/ncclient/devices/alu.py000066400000000000000000000024371451325530500177670ustar00rootroot00000000000000from lxml import etree from .default import DefaultDeviceHandler from ncclient.operations.third_party.alu.rpc import GetConfiguration, LoadConfiguration, ShowCLI from ncclient.xml_ import BASE_NS_1_0 def remove_namespaces(xml): for elem in xml.getiterator(): if elem.tag is etree.Comment: continue i = elem.tag.find('}') if i > 0: elem.tag = elem.tag[i + 1:] etree.cleanup_namespaces(xml) return xml 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", ] 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 add_additional_operations(self): dict = {} dict["get_configuration"] = GetConfiguration dict["show_cli"] = ShowCLI dict["load_configuration"] = LoadConfiguration return dict def transform_reply(self): return remove_namespaces ncclient-0.6.15/ncclient/devices/csr.py000066400000000000000000000023001451325530500177620ustar00rootroot00000000000000""" 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 from warnings import warn 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): warn( 'CsrDeviceHandler is deprecated, please use IosxeDeviceHandler', DeprecationWarning, stacklevel=2) super(CsrDeviceHandler, self).__init__(device_params) def add_additional_ssh_connect_params(self, kwargs): warn( 'CsrDeviceHandler is deprecated, please use IosxeDeviceHandler', DeprecationWarning, stacklevel=2) kwargs['unknown_host_cb'] = csr_unknown_host_cb ncclient-0.6.15/ncclient/devices/default.py000066400000000000000000000235461451325530500206360ustar00rootroot00000000000000""" 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. """ from ncclient.transport.parser import DefaultXMLParser 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 = [] _BASE_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:ietf:params:netconf:capability:interleave:1.0", "urn:ietf:params:netconf:capability:with-defaults:1.0" ] def __init__(self, device_params=None): self.device_params = device_params self.capabilities = [] # 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 add_additional_netconf_params(self, kwargs): """Add additional NETCONF parameters Accept a keyword-argument dictionary to add additional NETCONF parameters that may be in addition to those specified by the default and device specific handlers. Currently, only additional client specified capabilities are supported and will be appended to default and device specific capabilities. Args: kwargs: A dictionary of specific NETCONF parameters to apply in addition to those derived by default and device specific handlers. """ self.capabilities = kwargs.pop("capabilities", []) 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 self._BASE_CAPABILITIES + self.capabilities 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 reply_parsing_error_transform(self, reply_cls): """ Hook for working around bugs in replies from devices (the root element can be "fixed") :param reply_cls: the RPCReply class that is parsing the reply 'root' xml element :return: transform function for the 'root' xml element of the RPC reply in case the normal parsing fails """ # No transformation by default return None def transform_reply(self): return False def transform_edit_config(self, node): """ Hook for working around bugs in devices that cannot deal with standard config payloads for edits. This will be called in EditConfig.request just before the request is submitted, meaning it will get an XML tree rooted at edit-config. :param node: the XML tree for edit-config :return: either the original XML tree if no changes made or a modified XML tree """ return node def get_xml_parser(self, session): """ vendor can chose which parser to use for RPC reply response. Default being DOM :param session: ssh session object :return: default DOM parser """ return DefaultXMLParser(session) ncclient-0.6.15/ncclient/devices/ericsson.py000066400000000000000000000024441451325530500210310ustar00rootroot00000000000000""" Handler for Ericsson device specific information. Note that for proper import, the classname has to be: "DeviceHandler" ...where is something like "Default", "Ericsson", 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.errors import OperationError from .default import DefaultDeviceHandler class EricssonDeviceHandler(DefaultDeviceHandler): """ Ericsson handler for device specific information. """ _EXEMPT_ERRORS = [] def __init__(self, device_params): super(EricssonDeviceHandler, self).__init__(device_params) def get_xml_base_namespace_dict(self): return {None: BASE_NS_1_0} def get_xml_extra_prefix_kwargs(self): d = {} if self.check_device_params() is False: d.update(self.get_xml_base_namespace_dict()) return {"nsmap": d} def check_device_params(self): value = self.device_params.get('with_ns') if value in [True, False]: return value elif value is None: return False else: raise OperationError('Invalid "with_ns" value: %s' % value) ncclient-0.6.15/ncclient/devices/h3c.py000066400000000000000000000035431451325530500176620ustar00rootroot00000000000000""" Handler for H3c device specific information. Note that for proper import, the classname has to be: "DeviceHandler" ...where is something like "Default", "H3c", 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.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.15/ncclient/devices/hpcomware.py000066400000000000000000000024531451325530500211710ustar00rootroot00000000000000from 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.15/ncclient/devices/huawei.py000066400000000000000000000042731451325530500204700ustar00rootroot00000000000000""" 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.15/ncclient/devices/huaweiyang.py000066400000000000000000000031131451325530500213370ustar00rootroot00000000000000""" Handler for Huawei device specific information through YANG. 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 class HuaweiyangDeviceHandler(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(HuaweiyangDeviceHandler, self).__init__(device_params) def get_capabilities(self): # Just need to replace a single value in the default capabilities c = [] c.append('urn:ietf:params:netconf:base:1.0') c.append('urn:ietf:params:netconf:base:1.1') 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} ncclient-0.6.15/ncclient/devices/iosxe.py000066400000000000000000000030271451325530500203310ustar00rootroot00000000000000""" 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 from ncclient.xml_ import BASE_NS_1_0 import logging logger = logging.getLogger("ncclient.devices.iosxe") 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['unknown_host_cb'] = iosxe_unknown_host_cb def transform_edit_config(self, node): # find the first node that has the tag "config" with no namespace nodes = node.findall("./config") if len(nodes) == 1: logger.debug('IOS XE handler: patching namespace of config element') nodes[0].tag = '{%s}%s' % (BASE_NS_1_0, 'config') return node ncclient-0.6.15/ncclient/devices/iosxr.py000066400000000000000000000017101451325530500203430ustar00rootroot00000000000000""" 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['unknown_host_cb'] = iosxr_unknown_host_cb def perform_qualify_check(self): return False ncclient-0.6.15/ncclient/devices/junos.py000066400000000000000000000145031451325530500203410ustar00rootroot00000000000000""" 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 logging import re from lxml import etree from lxml.etree import QName from ncclient.operations.retrieve import GetSchemaReply 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, replace_namespace, BASE_NS_1_0, NETCONF_MONITORING_NS from ncclient.transport.third_party.junos.parser import JunosXMLParser from ncclient.transport.parser import DefaultXMLParser from ncclient.transport.parser import SAXParserHandler logger = logging.getLogger(__name__) class JunosDeviceHandler(DefaultDeviceHandler): """ Juniper handler for device specific information. """ def __init__(self, device_params): super(JunosDeviceHandler, self).__init__(device_params) self.__reply_parsing_error_transform_by_cls = { GetSchemaReply: fix_get_schema_reply } 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 capabilities exchange itself elif re.search(r'.*?.*?', raw, re.M | re.S): errs = re.findall( r'.*?', 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 reply_parsing_error_transform(self, reply_cls): # return transform function if found, else None return self.__reply_parsing_error_transform_by_cls.get(reply_cls) def transform_reply(self): reply = ''' ''' import sys if sys.version < '3': return reply else: return reply.encode('UTF-8') def get_xml_parser(self, session): # use_filter in device_params can be used to enabled using SAX parsing if self.device_params.get('use_filter', False): l = session.get_listener_instance(SAXParserHandler) if l: session.remove_listener(l) del l session.add_listener(SAXParserHandler(session)) return JunosXMLParser(session) else: return DefaultXMLParser(session) def fix_get_schema_reply(root): # Workaround for wrong namespace of the data elem # (issue with some Junos versions, might be corrected by Juniper at some point) # get the data element, by local-name data_elems = root.xpath('/nc:rpc-reply/*[local-name()="data"]', namespaces={'nc': BASE_NS_1_0}) if len(data_elems) != 1: return # Will not alter unexpected content data_el = data_elems[0] namespace = QName(data_el).namespace if namespace == BASE_NS_1_0: # With the default netconf setting, we may get "{BASE_NS_1_0}data"; warn and fix it logger.warning("The device seems to run non-rfc compliant netconf. You may want to " "configure: 'set system services netconf rfc-compliant'") replace_namespace(data_el, old_ns=BASE_NS_1_0, new_ns=NETCONF_MONITORING_NS) elif namespace is None: # With 'set system services netconf rfc-compliant' we may get "data" (no namespace); fix it # There is no default xmlns and the data el is replace_namespace(data_el, old_ns=None, new_ns=NETCONF_MONITORING_NS) ncclient-0.6.15/ncclient/devices/nexus.py000066400000000000000000000064621451325530500203520ustar00rootroot00000000000000""" 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.15/ncclient/devices/sros.py000066400000000000000000000031131451325530500201640ustar00rootroot00000000000000from lxml import etree from .default import DefaultDeviceHandler from ncclient.operations.third_party.sros.rpc import MdCliRawCommand, Commit from ncclient.xml_ import BASE_NS_1_0 def passthrough(xml): return xml class SrosDeviceHandler(DefaultDeviceHandler): """ Nokia SR OS handler for device specific information. """ def __init__(self, device_params): super(SrosDeviceHandler, self).__init__(device_params) def get_capabilities(self): """Set SR OS device handler client capabilities Set additional capabilities beyond the default device handler. Returns: A list of strings representing NETCONF capabilities to be sent to the server. """ base = super(SrosDeviceHandler, self).get_capabilities() additional = [ 'urn:ietf:params:xml:ns:netconf:base:1.0', 'urn:ietf:params:xml:ns:yang:1', 'urn:ietf:params:netconf:capability:confirmed-commit:1.1', 'urn:ietf:params:netconf:capability:validate:1.1'] return base + additional 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 add_additional_operations(self): operations = { 'md_cli_raw_command': MdCliRawCommand, 'commit': Commit, } return operations def perform_qualify_check(self): return False def transform_reply(self): return passthrough ncclient-0.6.15/ncclient/logging_.py000066400000000000000000000016261451325530500173500ustar00rootroot00000000000000import logging class SessionLoggerAdapter(logging.LoggerAdapter): """Logger adapter that automatically adds session information to logs.""" def process(self, msg, kwargs): if 'session' not in self.extra or self.extra['session'] is None: return msg, kwargs session = self.extra['session'] prefix = "" # All Session instances have an id. SSHSessions have a host as well. if hasattr(session, 'host'): prefix += "host %s " % session.host if session.id is not None: prefix += "session-id %s" % session.id else: prefix += "session 0x%x" % id(session) # Pass the session information through to the LogRecord itself if 'extra' not in kwargs: kwargs['extra'] = self.extra else: kwargs['extra'].update(self.extra) return "[%s] %s" % (prefix, msg), kwargs ncclient-0.6.15/ncclient/manager.py000066400000000000000000000313641451325530500171770ustar00rootroot00000000000000# 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 operations from ncclient import transport import socket import logging import functools 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, "rpc": operations.GenericRPC, } """ 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. """ 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 _extract_device_params(kwds): device_params = kwds.pop("device_params", None) return device_params def _extract_manager_params(kwds): manager_params = kwds.pop("manager_params", {}) # To maintain backward compatibility if 'timeout' not in manager_params and 'timeout' in kwds: manager_params['timeout'] = kwds['timeout'] return manager_params def _extract_nc_params(kwds): nc_params = kwds.pop("nc_params", {}) return nc_params 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`. All the provided arguments are passed directly to its implementation of :meth:`~ncclient.transport.SSHSession.connect`. To customize the :class:`Manager`, add a `manager_params` dictionary in connection parameters (e.g. `manager_params={'timeout': 60}` for a bigger RPC timeout parameter) To invoke advanced vendor related operation add `device_params={'name': ''}` in connection parameters. 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 parameters. """ # Extract device/manager/netconf parameter dictionaries, if they were passed into this function. # Remove them from kwds (which should keep only session.connect() parameters). device_params = _extract_device_params(kwds) manager_params = _extract_manager_params(kwds) nc_params = _extract_nc_params(kwds) device_handler = make_device_handler(device_params) device_handler.add_additional_ssh_connect_params(kwds) device_handler.add_additional_netconf_params(nc_params) session = transport.SSHSession(device_handler) try: session.connect(*args, **kwds) except Exception as ex: if session.transport: session.close() raise return Manager(session, device_handler, **manager_params) def connect_tls(*args, **kwargs): """Initialize a :class:`Manager` over the TLS transport.""" device_params = _extract_device_params(kwargs) manager_params = _extract_manager_params(kwargs) nc_params = _extract_nc_params(kwargs) device_handler = make_device_handler(device_params) device_handler.add_additional_netconf_params(nc_params) session = transport.TLSSession(device_handler) session.connect(*args, **kwargs) return Manager(session, device_handler, **manager_params) def connect_ioproc(*args, **kwds): device_params = _extract_device_params(kwds) manager_params = _extract_manager_params(kwds) if device_params: import_string = 'ncclient.transport.third_party.' import_string += device_params['name'] + '.ioproc' third_party_import = __import__(import_string, fromlist=['IOProc']) device_handler = make_device_handler(device_params) session = third_party_import.IOProc(device_handler) session.connect() return Manager(session, device_handler, **manager_params) 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) def call_home(*args, **kwds): host = kwds["host"] port = kwds.get("port",4334) srv_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv_socket.bind((host, port)) srv_socket.settimeout(10) srv_socket.listen() sock, remote_host = srv_socket.accept() logger.info('Callhome connection initiated from remote host {0}'.format(remote_host)) kwds['sock'] = sock return connect_ssh(*args, **kwds) class Manager(object): """ For details on the expected behavior of the operations and their parameters refer to :rfc:`6241`. 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 HUGE_TREE_DEFAULT = False """Default for `huge_tree` support for XML parsing of RPC replies (defaults to False)""" def __init__(self, session, device_handler, timeout=30): self._session = session self._async_mode = False self._timeout = timeout self._raise_mode = operations.RaiseMode.ALL self._huge_tree = self.HUGE_TREE_DEFAULT self._device_handler = device_handler self._vendor_operations = {} if device_handler: self._vendor_operations.update(device_handler.add_additional_operations()) 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, huge_tree=self._huge_tree).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): if method in self._vendor_operations: return functools.partial(self.execute, self._vendor_operations[method]) elif method in OPERATIONS: return functools.partial(self.execute, OPERATIONS[method]) else: """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`.""" @property def huge_tree(self): """Whether `huge_tree` support for XML parsing of RPC replies is enabled (default=False) The default value is configurable through :attr:`~ncclient.manager.Manager.HUGE_TREE_DEFAULT`""" return self._huge_tree @huge_tree.setter def huge_tree(self, x): self._huge_tree = x ncclient-0.6.15/ncclient/operations/000077500000000000000000000000001451325530500173675ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/__init__.py000066400000000000000000000031661451325530500215060ustar00rootroot00000000000000# 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, GenericRPC # 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', 'GenericRPC', 'Get', 'GetConfig', 'GetSchema', 'Dispatch', 'GetReply', 'EditConfig', 'CopyConfig', 'Validate', 'Commit', 'DiscardChanges', 'CancelCommit', 'DeleteConfig', 'Lock', 'Unlock', 'CreateSubscription', 'PoweroffMachine', 'RebootMachine', ] ncclient-0.6.15/ncclient/operations/edit.py000066400000000000000000000200741451325530500206710ustar00rootroot00000000000000# 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 from .errors import OperationError 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"`, `"test-only"` } *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 (default_operation is not None and util.validate_args('default_operation', default_operation, ["merge", "replace", "none"]) is True): sub_ele(node, "default-operation").text = default_operation if (test_option is not None and util.validate_args('test_option', test_option, ["test-then-set", "set", "test-only"]) is True): self._assert(':validate') if test_option == 'test-only': self._assert(':validate:1.1') sub_ele(node, "test-option").text = test_option if (error_option is not None and util.validate_args('error_option', error_option, ["stop-on-error", "continue-on-error", "rollback-on-error"]) is True): if error_option == "rollback-on-error": self._assert(":rollback-on-error") sub_ele(node, "error-option").text = error_option if format == 'xml': node.append(validated_element(config, ("config", qualify("config")))) elif format == 'text': config_text = sub_ele(node, "config-text") sub_ele(config_text, "configuration-text").text = config elif format == 'url': if util.url_validator(config): self._assert(':url') sub_ele(node, "url").text = config else: raise OperationError("Invalid URL.") node = self._device_handler.transform_edit_config(node) 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, persist_id=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 *persist_id* value must be equal to the value given in the parameter to the original operation. """ node = new_ele("commit") if persist and persist_id: raise OperationError("Invalid operation as persist cannot be present with persist-id") 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 if persist_id: sub_ele(node, "persist-id").text = persist_id 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.15/ncclient/operations/errors.py000066400000000000000000000014001451325530500212500ustar00rootroot00000000000000# 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.15/ncclient/operations/flowmon.py000066400000000000000000000023211451325530500214200ustar00rootroot00000000000000# 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.15/ncclient/operations/lock.py000066400000000000000000000042671451325530500207020ustar00rootroot00000000000000# 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.15/ncclient/operations/retrieve.py000066400000000000000000000164401451325530500215730ustar00rootroot00000000000000# 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 class WithDefaultsError(OperationError): """Invalid 'with-defaults' mode or capability URI""" 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_mode( node, with_defaults, self._session.server_capabilities, ) return self._request(node) def _append_with_defaults_mode(node, mode, capabilities): _validate_with_defaults_mode(mode, capabilities) with_defaults_element = sub_ele_ns( node, "with-defaults", NETCONF_WITH_DEFAULTS_NS, ) with_defaults_element.text = mode def _validate_with_defaults_mode(mode, capabilities): valid_modes = _get_valid_with_defaults_modes(capabilities) if mode.strip().lower() not in valid_modes: raise WithDefaultsError( "Invalid 'with-defaults' mode '{provided}'; the server only " "supports the following: {options}".format( provided=mode, options=', '.join(valid_modes) ) ) def _get_valid_with_defaults_modes(capabilities): """Reference: https://tools.ietf.org/html/rfc6243#section-4.3""" capability = capabilities[":with-defaults"] try: valid_modes = [capability.parameters["basic-mode"]] except KeyError: raise WithDefaultsError( "Invalid 'with-defaults' capability URI advertised by the server; " "missing 'basic-mode' parameter" ) try: also_supported = capability.parameters["also-supported"] except KeyError: return valid_modes valid_modes.extend(also_supported.split(",")) return valid_modes 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_mode( node, with_defaults, self._session.server_capabilities, ) 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`""" self._huge_tree = True 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 = RPCReply """See :class:`RPCReply`.""" 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.15/ncclient/operations/rpc.py000066400000000000000000000464241451325530500205370ustar00rootroot00000000000000# 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 from ncclient.xml_ import * from ncclient.logging_ import SessionLoggerAdapter from ncclient.transport import SessionListener from ncclient.operations import util 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-app-tag"): "_app_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 self._errlist = None 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 self._errlist = errs 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 app_tag(self): "The contents of the `error-app-tag` element." return self._app_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 @property def errlist(self): "List of errors if this represents multiple errors, otherwise None." return self._errlist class RPCReply(object): """Represents an *rpc-reply*. Only concerns itself with whether the operation was successful. *raw*: the raw unparsed reply *huge_tree*: parse XML with very deep trees and very long text content .. 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, huge_tree=False, parsing_error_transform=None): self._raw = raw self._parsing_error_transform = parsing_error_transform self._parsed = False self._root = None self._errors = [] self._huge_tree = huge_tree def __repr__(self): return self._raw def parse(self): "Parses the *rpc-reply*." if self._parsed: return root = self._root = to_ele(self._raw, huge_tree=self._huge_tree) # 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)) try: self._parsing_hook(root) except Exception as e: if self._parsing_error_transform is None: # re-raise as we have no workaround exc_type, exc_value, exc_traceback = sys.exc_info() six.reraise(exc_type, exc_value, exc_traceback) # Apply device specific workaround and try again self._parsing_error_transform(root) 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 def set_parsing_error_transform(self, transform_function): self._parsing_error_transform = transform_function @property def xml(self): "*rpc-reply* element as returned." return self._raw @property def ok(self): "Boolean value indicating if there were no errors." self.parse() 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) instance.logger = SessionLoggerAdapter(logger, {'session': session}) 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 self.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, huge_tree=False): """ *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` *huge_tree* parse xml with huge_tree support (e.g. for large text config retrieval), see :attr:`huge_tree` """ 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._huge_tree = huge_tree 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 self.logger = SessionLoggerAdapter(logger, {'session': session}) 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) 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` """ self.logger.info('Requesting %r', self.__class__.__name__) req = self._wrap(op) self._session.send(req) if self._async: self.logger.debug('Async request, returning %r', self) return self else: self.logger.debug('Sync request, will wait for timeout=%r', self._timeout) self._event.wait(self._timeout) if self._event.is_set(): 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(), huge_tree=self._huge_tree) 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, huge_tree=self._huge_tree) # Set the reply_parsing_error transform outside the constructor, to keep compatibility for # third party reply classes outside of ncclient self._reply.set_parsing_error_transform( self._device_handler.reply_parsing_error_transform(self.REPLY_CLS) ) 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. """ @property def huge_tree(self): """Whether `huge_tree` support for XML parsing of RPC replies is enabled (default=False)""" return self._huge_tree @huge_tree.setter def huge_tree(self, x): self._huge_tree = x class GenericRPC(RPC): """Generic rpc commands wrapper""" REPLY_CLS = RPCReply """See :class:`RPCReply`.""" def request(self, rpc_command, source=None, filter=None, config=None, target=None, format=None): """ *rpc_command* specifies rpc command to be dispatched either in plain text or in xml element format (depending on command) *target* name of the configuration datastore being edited *source* name of the configuration datastore being queried *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`. *filter* specifies the portion of the configuration to retrieve (by default entire configuration is retrieved) :seealso: :ref:`filter_params` Examples of usage:: m.rpc('rpc_command') or dispatch element like :: rpc_command = new_ele('get-xnm-information') sub_ele(rpc_command, 'type').text = "xml-schema" m.rpc(rpc_command) """ if etree.iselement(rpc_command): node = rpc_command else: node = new_ele(rpc_command) if target is not None: node.append(util.datastore_or_url("target", target, self._assert)) 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)) if config is not None: node.append(validated_element(config, ("config", qualify("config")))) return self._request(node) ncclient-0.6.15/ncclient/operations/session.py000066400000000000000000000026061451325530500214300ustar00rootroot00000000000000# 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." ret = self._request(new_ele("close-session")) self.session.close() return ret 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.15/ncclient/operations/subscribe.py000066400000000000000000000047411451325530500217300ustar00rootroot00000000000000# 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_ns(node, "stream", NETCONF_NOTIFICATION_NS).text = stream_name if start_time is not None: sub_ele_ns(node, "startTime", NETCONF_NOTIFICATION_NS).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_ns(node, "stopTime", NETCONF_NOTIFICATION_NS).text = stop_time return self._request(node) ncclient-0.6.15/ncclient/operations/third_party/000077500000000000000000000000001451325530500217205ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/__init__.py000066400000000000000000000000001451325530500240170ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/alu/000077500000000000000000000000001451325530500225015ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/alu/__init__.py000066400000000000000000000000001451325530500246000ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/alu/rpc.py000066400000000000000000000052021451325530500236360ustar00rootroot00000000000000# by Pasi Sikala from ncclient.xml_ import * from ncclient.operations.rpc import RPC from ncclient.operations import util class ShowCLI(RPC): def request(self, command=None): """Run CLI -show commands *command* (show) command to run """ node = new_ele('get') filter = sub_ele(node, 'filter') block = sub_ele(filter, 'oper-data-format-cli-block') sub_ele(block, 'cli-show').text = command return self._request(node) class GetConfiguration(RPC): def request(self, content='xml', filter=None, detail=False): """Get config from Alu router *content* Content layer. cli or xml *filter* specifies the portion of the configuration to retrieve (by default entire configuration is retrieved) *detail* Show detailed config in CLI -layer""" node = new_ele('get-config') node.append(util.datastore_or_url('source', 'running', self._assert)) if filter is not None: if content == 'xml': node.append(util.build_filter(('subtree', filter))) elif content == 'cli': rep = new_ele('filter') sub_filter = sub_ele(rep, 'config-format-cli-block') if filter is not None: for item in filter: if detail: sub_ele(sub_filter, 'cli-info-detail').text = item else: sub_ele(sub_filter, 'cli-info').text = item else: if detail: sub_ele(sub_filter, 'cli-info-detail') else: sub_ele(sub_filter, 'cli-info') node.append(validated_element(rep)) return self._request(node) class LoadConfiguration(RPC): def request(self, format='xml', default_operation=None, target='running', config=None): node = new_ele('edit-config') config_node = new_ele('config') if default_operation is not None: # TODO: check if it is a valid default-operation sub_ele(node, "default-operation").text = default_operation if config is not None: if format == 'xml': node.append(util.datastore_or_url('target', target, self._assert)) config_node.append(config) if format == 'cli': node.append(util.datastore_or_url('target', 'running', self._assert)) sub_ele(config_node, 'config-format-cli-block').text = config node.append(config_node) return self._request(node) ncclient-0.6.15/ncclient/operations/third_party/h3c/000077500000000000000000000000001451325530500223755ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/h3c/__init__.py000066400000000000000000000000551451325530500245060ustar00rootroot00000000000000# -*- coding: utf-8 -*- # by yangxufeng.zhao ncclient-0.6.15/ncclient/operations/third_party/h3c/rpc.py000066400000000000000000000043301451325530500235330ustar00rootroot00000000000000# -*- 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.15/ncclient/operations/third_party/hpcomware/000077500000000000000000000000001451325530500237055ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/hpcomware/__init__.py000066400000000000000000000000001451325530500260040ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/hpcomware/rpc.py000066400000000000000000000033401451325530500250430ustar00rootroot00000000000000from 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.15/ncclient/operations/third_party/huawei/000077500000000000000000000000001451325530500232025ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/huawei/__init__.py000066400000000000000000000000541451325530500253120ustar00rootroot00000000000000# -*- coding: utf-8 -*- # by yangxufeng.zhaoncclient-0.6.15/ncclient/operations/third_party/huawei/rpc.py000066400000000000000000000013201451325530500243340ustar00rootroot00000000000000# -*- 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.15/ncclient/operations/third_party/iosxe/000077500000000000000000000000001451325530500230475ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/iosxe/__init__.py000066400000000000000000000000001451325530500251460ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/iosxe/rpc.py000066400000000000000000000004051451325530500242040ustar00rootroot00000000000000from 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.15/ncclient/operations/third_party/juniper/000077500000000000000000000000001451325530500233745ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/juniper/__init__.py000066400000000000000000000000001451325530500254730ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/juniper/rpc.py000066400000000000000000000131211451325530500245300ustar00rootroot00000000000000from 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) if format !='xml': # The entire config comes as a single text element and this requires huge_tree support for large configs self._huge_tree = True 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, format='text'): node = new_ele('get-configuration', {'compare':'rollback', 'format':format, 'rollback':str(rollback)}) return self._request(node) class ExecuteRpc(RPC): def request(self, rpc, filter_xml=None): if isinstance(rpc, str): rpc = to_ele(rpc) self._filter_xml = filter_xml 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.15/ncclient/operations/third_party/nexus/000077500000000000000000000000001451325530500230625ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/nexus/__init__.py000066400000000000000000000000001451325530500251610ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/nexus/rpc.py000066400000000000000000000005301451325530500242160ustar00rootroot00000000000000from 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.15/ncclient/operations/third_party/sros/000077500000000000000000000000001451325530500227065ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/sros/__init__.py000066400000000000000000000000001451325530500250050ustar00rootroot00000000000000ncclient-0.6.15/ncclient/operations/third_party/sros/rpc.py000066400000000000000000000056061451325530500240530ustar00rootroot00000000000000from ncclient.operations import OperationError from ncclient.xml_ import * from ncclient.operations.rpc import RPC def global_operations(node): """Instantiate an SR OS global operation action element Args: node: A string representing the top-level action for a given global operation. Returns: A tuple of 'lxml.etree._Element' values. The first value represents the top-level YANG action element and the second represents the caller supplied initial node. """ parent, child = yang_action('global-operations', attrs={'xmlns': SROS_GLOBAL_OPS_NS}) ele = sub_ele(child, node) return (parent, ele) class MdCliRawCommand(RPC): def request(self, command=None): node, raw_cmd_node = global_operations('md-cli-raw-command') sub_ele(raw_cmd_node, 'md-cli-input-line').text = command self._huge_tree = True 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, persist_id=None, comment=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 *persist_id* value must be equal to the value given in the parameter to the original operation. *comment* a descriptive text comment that will be associated with the commit action. This is useful for logging or audit purposes. """ node = new_ele("commit") if comment and comment.strip(): sub_ele(node, "comment", attrs={'xmlns': "urn:nokia.com:sros:ns:yang:sr:ietf-netconf-augments"}).text = comment if persist and persist_id: raise OperationError("Invalid operation as persist cannot be present with persist-id") 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 if persist_id: sub_ele(node, "persist-id").text = persist_id return self._request(node) ncclient-0.6.15/ncclient/operations/util.py000077500000000000000000000066101451325530500207240ustar00rootroot00000000000000# 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 try: from urlparse import urlparse except: from urllib.parse import urlparse 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 if type == "xpath": if isinstance(criteria, tuple): ns, select = criteria rep = new_ele_nsmap("filter", ns, type=type) rep.attrib["select"] = select else: rep = new_ele("filter", type=type) rep.attrib["select"]=criteria elif type == "subtree": rep = new_ele("filter", type=type) rep.append(to_ele(criteria)) else: raise OperationError("Invalid filter type") elif isinstance(spec, list): rep = new_ele("filter", type="subtree") for cri in spec: rep.append(to_ele(cri)) else: rep = validated_element(spec, ("filter", qualify("filter"), qualify("filter", ns=NETCONF_NOTIFICATION_NS))) # 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 def validate_args(arg_name, value, args_list): # this is a common method, which used to check whether a value is in args_list if value not in args_list: raise OperationError('Invalid value "%s" in "%s" element' % (value, arg_name)) return True def url_validator(url): try: result = urlparse(url) return all([result.scheme, result.netloc]) except: return False ncclient-0.6.15/ncclient/transport/000077500000000000000000000000001451325530500172405ustar00rootroot00000000000000ncclient-0.6.15/ncclient/transport/__init__.py000066400000000000000000000020101451325530500213420ustar00rootroot00000000000000# 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, NetconfBase from ncclient.transport.ssh import SSHSession from ncclient.transport.tls import TLSSession from ncclient.transport.errors import * __all__ = [ 'Session', 'SessionListener', 'SSHSession', 'TLSSession', 'TransportError', 'AuthenticationError', 'SessionCloseError', 'NetconfBase', 'SSHError', 'SSHUnknownHostError' ] ncclient-0.6.15/ncclient/transport/errors.py000066400000000000000000000027611451325530500211340ustar00rootroot00000000000000# 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 class NetconfFramingError(TransportError): pass class TLSError(TransportError): pass ncclient-0.6.15/ncclient/transport/notify.py000066400000000000000000000015601451325530500211240ustar00rootroot00000000000000# 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.15/ncclient/transport/parser.py000066400000000000000000000220101451325530500211010ustar00rootroot00000000000000# Copyright 2018 Nitin Kumar # 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 re try: import selectors except ImportError: import selectors2 as selectors from xml.sax.handler import ContentHandler from ncclient.transport.errors import NetconfFramingError from ncclient.transport.session import NetconfBase from ncclient.logging_ import SessionLoggerAdapter from ncclient.operations.errors import OperationError from ncclient.transport import SessionListener import logging logger = logging.getLogger("ncclient.transport.parser") if sys.version < '3': from six import StringIO else: from io import BytesIO as StringIO PORT_NETCONF_DEFAULT = 830 PORT_SSH_DEFAULT = 22 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 # # Define delimiters for chunks and messages for netconf 1.1 chunk enoding. # When matched: # # * result.group(0) will contain whole matched string # * result.group(1) will contain the digit string for a chunk # * result.group(2) will be defined if '##' found # RE_NC11_DELIM = re.compile(r'\n(?:#([0-9]+)|(##))\n') if sys.version < '3': def textify(buf): return buf else: def textify(buf): return buf.decode('UTF-8') class SAXParserHandler(SessionListener): def __init__(self, session): self._session = session def callback(self, root, raw): if type(self._session.parser) == DefaultXMLParser: self._session.parser = self._session._device_handler.get_xml_parser(self._session) def errback(self, _): pass class SAXFilterXMLNotFoundError(OperationError): def __init__(self, rpc_listener): self._listener = rpc_listener def __str__(self): return "SAX filter input xml not provided for listener: %s" % self._listener class DefaultXMLParser(object): def __init__(self, session): """ DOM Parser :param session: ssh session object """ self._session = session self._parsing_pos10 = 0 self.logger = SessionLoggerAdapter(logger, {'session': self._session}) def parse(self, data): """ parse incoming RPC response from networking device. :param data: incoming RPC data from device :return: None """ if data: self._session._buffer.seek(0, os.SEEK_END) self._session._buffer.write(data) if self._session._base == NetconfBase.BASE_11: self._parse11() else: 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.""" self.logger.debug("parsing netconf v1.0") buf = self._session._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._session._dispatch_message(msg.encode()) else: self._session._dispatch_message(msg) self._session._buffer = StringIO() self._parsing_pos10 = 0 if len(remaining.strip()) > 0: # There could be another entire message in the # buffer, so we should try to parse again. if type(self._session.parser) != DefaultXMLParser: self.logger.debug('send remaining data to SAX parser') self._session.parser.parse(remaining.encode()) else: self.logger.debug('Trying another round of parsing since there is still data') self._session._buffer.write(remaining.encode()) 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): """Messages are split into chunks. Chunks and messages are delimited by the regex #RE_NC11_DELIM defined earlier in this file. Each time we get called here either a chunk delimiter or an end-of-message delimiter should be found iff there is enough data. If there is not enough data, we will wait for more. If a delimiter is found in the wrong place, a #NetconfFramingError will be raised.""" self.logger.debug("_parse11: starting") # suck in whole string that we have (this is what we will work on in # this function) and initialize a couple of useful values self._session._buffer.seek(0, os.SEEK_SET) data = self._session._buffer.getvalue() data_len = len(data) start = 0 self.logger.debug('_parse11: working with buffer of %d bytes', data_len) while True and start < data_len: # match to see if we found at least some kind of delimiter self.logger.debug('_parse11: matching from %d bytes from start of buffer', start) re_result = RE_NC11_DELIM.match(data[start:].decode('utf-8', errors='ignore')) if not re_result: # not found any kind of delimiter just break; this should only # ever happen if we just have the first few characters of a # message such that we don't yet have a full delimiter self.logger.debug('_parse11: no delimiter found, buffer="%s"', data[start:].decode()) break # save useful variables for reuse re_start = re_result.start() re_end = re_result.end() self.logger.debug('_parse11: regular expression start=%d, end=%d', re_start, re_end) # If the regex doesn't start at the beginning of the buffer, # we're in trouble, so throw an error if re_start != 0: raise NetconfFramingError('_parse11: delimiter not at start of match buffer', data[start:]) if re_result.group(2): # we've found the end of the message, need to form up # whole message, save back remainder (if any) to buffer # and dispatch the message start += re_end message = ''.join(self._session._message_list) self._session._message_list = [] self.logger.debug('_parse11: found end of message delimiter') self._session._dispatch_message(message) break elif re_result.group(1): # we've found a chunk delimiter, and group(1) is the digit # string that will tell us how many bytes past the end of # where it was found that we need to have available to # save the next chunk off self.logger.debug('_parse11: found chunk delimiter') digits = int(re_result.group(1)) self.logger.debug('_parse11: chunk size %d bytes', digits) if (data_len-start) >= (re_end + digits): # we have enough data for the chunk fragment = textify(data[start+re_end:start+re_end+digits]) self._session._message_list.append(fragment) start += re_end + digits self.logger.debug('_parse11: appending %d bytes', digits) self.logger.debug('_parse11: fragment = "%s"', fragment) else: # we don't have enough bytes, just break out for now # after updating start pointer to start of new chunk start += re_start self.logger.debug('_parse11: not enough data for chunk yet') self.logger.debug('_parse11: setting start to %d', start) break # Now out of the loop, need to see if we need to save back any content if start > 0: self.logger.debug( '_parse11: saving back rest of message after %d bytes, original size %d', start, data_len) self._session._buffer = StringIO(data[start:]) if start < data_len: self.logger.debug('_parse11: still have data, may have another full message!') self._parse11() self.logger.debug('_parse11: ending') ncclient-0.6.15/ncclient/transport/session.py000066400000000000000000000330431451325530500213000ustar00rootroot00000000000000# 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 logging from threading import Thread, Lock, Event try: from Queue import Queue, Empty except ImportError: from queue import Queue, Empty try: import selectors except ImportError: import selectors2 as selectors import ncclient.transport from ncclient.xml_ import * from ncclient.capabilities import Capabilities from ncclient.logging_ import SessionLoggerAdapter from ncclient.transport.errors import TransportError, SessionError, SessionCloseError from ncclient.transport.notify import Notification logger = logging.getLogger('ncclient.transport.session') # v1.0: RFC 4742 MSG_DELIM = b"]]>]]>" # v1.1: RFC 6242 END_DELIM = b'\n##\n' TICK = 0.1 class NetconfBase(object): '''Netconf Base protocol version''' BASE_10 = 1 BASE_11 = 2 class Session(Thread): "Base class for use by transport protocol implementations." def __init__(self, capabilities): Thread.__init__(self, daemon=True, name='session') self._listeners = set() self._lock = Lock() self._q = Queue() self._notification_q = Queue() self._client_capabilities = capabilities self._server_capabilities = None # yet self._base = NetconfBase.BASE_10 self._id = None # session-id self._connected = False # to be set/cleared by subclass implementation self.logger = SessionLoggerAdapter(logger, {'session': self}) self.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: self.logger.error('error parsing dispatch message: %s', e) return self.logger.debug('dispatching message to different listeners: %s', raw) with self._lock: listeners = list(self._listeners) for l in listeners: self.logger.debug('dispatching message to listener: %r', l) 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: self.logger.debug('dispatching error to %r', l) try: # here we can be more considerate with catching exceptions l.errback(err) except Exception as e: self.logger.warning('error dispatching to %r: %r', l, e) def _post_connect(self, timeout=60): "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)) self.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(timeout) 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') if 'urn:ietf:params:netconf:base:1.1' in self._server_capabilities and 'urn:ietf:params:netconf:base:1.1' in self._client_capabilities: self.logger.debug("After 'hello' message selecting netconf:base:1.1 for encoding") self._base = NetconfBase.BASE_11 self.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` """ self.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` """ self.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 _transport_read(self): """ Read data from underlying Transport layer, either SSH or TLS, as implemented in subclass. :return: Byte string read from Transport, or None if nothing was read. """ raise NotImplementedError def _transport_write(self, data): """ Write data into underlying Transport layer, either SSH or TLS, as implemented in subclass. :param data: Byte string to write. :return: Number of bytes sent, or 0 if the stream is closed. """ raise NotImplementedError def _transport_register(self, selector, event): """ Register the channel/socket of Transport layer for selection. Implemented in a subclass. :param selector: Selector to register with. :param event: Type of event for selection. """ raise NotImplementedError def _send_ready(self): """ Check if Transport layer is ready to send the data. Implemented in a subclass. :return: True if the layer is ready, False otherwise. """ raise NotImplementedError def run(self): q = self._q def start_delim(data_len): return b'\n#%i\n' % data_len try: s = selectors.DefaultSelector() self._transport_register(s, selectors.EVENT_READ) self.logger.debug('selector type = %s', s.__class__.__name__) while True: events = s.select(timeout=TICK) if events: data = self._transport_read() if data: try: self.parser.parse(data) except ncclient.transport.parser.SAXFilterXMLNotFoundError: self.logger.debug('switching from sax to dom parsing') self.parser = ncclient.transport.parser.DefaultXMLParser(self) self.parser.parse(data) elif self._closing.is_set(): # End of session, expected break else: # End of session, unexpected raise SessionCloseError(self._buffer.getvalue()) if not q.empty() and self._send_ready(): self.logger.debug("Sending message") data = q.get().encode() if self._base == NetconfBase.BASE_11: data = b"%s%s%s" % (start_delim(len(data)), data, END_DELIM) else: data = b"%s%s" % (data, MSG_DELIM) self.logger.info("Sending:\n%s", data) while data: n = self._transport_write(data) if n <= 0: raise SessionCloseError(self._buffer.getvalue(), data) data = data[n:] except Exception as e: self.logger.debug("Broke out of main loop, error=%r", e) self._dispatch_error(e) self.close() def send(self, message): """Send the supplied *message* (xml string) to NETCONF server.""" if not self.connected: raise TransportError('Not connected to NETCONF server') self.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.15/ncclient/transport/ssh.py000066400000000000000000000525771451325530500204270ustar00rootroot00000000000000# 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 base64 import getpass import os import re import six import sys import socket import threading from binascii import hexlify try: import selectors except ImportError: import selectors2 as selectors from ncclient.capabilities import Capabilities from ncclient.logging_ import SessionLoggerAdapter import paramiko from ncclient.transport.errors import AuthenticationError, SSHError, SSHUnknownHostError from ncclient.transport.session import Session from ncclient.transport.parser import DefaultXMLParser import logging logger = logging.getLogger("ncclient.transport.ssh") PORT_NETCONF_DEFAULT = 830 BUF_SIZE = 4096 # # Define delimiters for chunks and messages for netconf 1.1 chunk enoding. # When matched: # # * result.group(0) will contain whole matched string # * result.group(1) will contain the digit string for a chunk # * result.group(2) will be defined if '##' found # RE_NC11_DELIM = re.compile(br'\n(?:#([0-9]+)|(##))\n') 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': 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 = None 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() self._device_handler = device_handler self._message_list = [] self._closing = threading.Event() self.parser = DefaultXMLParser(self) # SAX or DOM parser self.logger = SessionLoggerAdapter(logger, {'session': self}) def _dispatch_message(self, raw): # Provide basic response message self.logger.info("Received message from host") # Provide complete response from host at debug log level self.logger.debug("Received:\n%s", raw) return super(SSHSession, self)._dispatch_message(raw) 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.parser._parse10() 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): self._closing.set() 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) if self._channel: self._channel.close() self._channel = None self._connected = False # REMEMBER to update transport.rst if sig. changes, since it is hardcoded there def connect( self, host, port = PORT_NETCONF_DEFAULT, timeout = None, unknown_host_cb = default_unknown_host_cb, username = None, password = None, key_filename = None, allow_agent = True, hostkey_verify = True, hostkey_b64 = None, look_for_keys = True, ssh_config = None, sock_fd = None, bind_addr = None, sock = None, keepalive = None, environment = 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 (PORT_NETCONF_DEFAULT), 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 *hostkey_b64* only connect when server presents a public hostkey matching this (obtain from server /etc/ssh/ssh_host_*pub or ssh-keyscan) *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 *bind_addr* is a (local) source IP address to use, must be reachable from the remote device. *sock* is an already open Python socket to be used for this connection. *keepalive* Turn on/off keepalive packets (default is off). If this is set, after interval seconds without sending any data over the connection, a "keepalive" packet will be sent (and ignored by the remote host). This can be useful to keep connections alive over a NAT. *environment* a dictionary containing the name and respective values to set """ if not (host or sock_fd or sock): raise SSHError("Missing host, socket or socket fd") self._host = host # 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() with open(os.path.expanduser(ssh_config)) as ssh_config_file_obj: config.parse(ssh_config_file_obj) # Save default Paramiko SSH port so it can be reverted paramiko_default_ssh_port = paramiko.config.SSH_PORT # Change the default SSH port to the port specified by the user so expand_variables # replaces %p with the passed in port rather than 22 (the defauld paramiko.config.SSH_PORT) paramiko.config.SSH_PORT = port config = config.lookup(host) # paramiko.config.SSHconfig::expand_variables is called by lookup so we can set the SSH port # back to the default paramiko.config.SSH_PORT = paramiko_default_ssh_port host = config.get("hostname", host) if username is None: username = config.get("user") if key_filename is None: key_filename = config.get("identityfile") if timeout is None: timeout = config.get("connecttimeout") if timeout: timeout = int(timeout) if hostkey_verify: userknownhostsfile = config.get("userknownhostsfile") if userknownhostsfile: self.load_known_hosts(os.path.expanduser(userknownhostsfile)) else: self.load_known_hosts() if username is None: username = getpass.getuser() if sock_fd is None and sock is None: proxycommand = config.get("proxycommand") if proxycommand: self.logger.debug("Configuring Proxy. %s", proxycommand) if not isinstance(proxycommand, six.string_types): proxycommand = [os.path.expanduser(elem) for elem in proxycommand] else: proxycommand = os.path.expanduser(proxycommand) sock = paramiko.proxy.ProxyCommand(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: if bind_addr: sock.bind((bind_addr, 0)) sock.connect(sa) except socket.error: sock.close() continue break else: raise SSHError("Could not open socket to %s:%s" % (host, port)) elif sock is None: 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) self._transport = paramiko.Transport(sock) self._transport.set_log_channel(logger.name) if config.get("compression") == 'yes': self._transport.use_compression() if hostkey_b64: # If we need to connect with a specific hostkey, negotiate for only its type hostkey_obj = None for key_cls in [paramiko.DSSKey, paramiko.Ed25519Key, paramiko.RSAKey, paramiko.ECDSAKey]: try: hostkey_obj = key_cls(data=base64.b64decode(hostkey_b64)) except paramiko.SSHException: # Not a key of this type - try the next pass if not hostkey_obj: # We've tried all known host key types and haven't found a suitable one to use - bail raise SSHError("Couldn't find suitable paramiko key class for host key %s" % hostkey_b64) self._transport._preferred_keys = [hostkey_obj.get_name()] elif self._host_keys: # Else set preferred host keys to those we possess for the host # (avoids situation where known_hosts contains a valid key for the host, but that key type is not selected during negotiation) known_host_keys_for_this_host = self._host_keys.lookup(host) or {} host_port = '[%s]:%s' % (host, port) known_host_keys_for_this_host.update(self._host_keys.lookup(host_port) or {}) if known_host_keys_for_this_host: self._transport._preferred_keys = list(known_host_keys_for_this_host) # Connect try: self._transport.start_client() except paramiko.SSHException as e: raise SSHError('Negotiation failed: %s' % e) if hostkey_verify: server_key_obj = self._transport.get_remote_server_key() fingerprint = _colonify(hexlify(server_key_obj.get_fingerprint())) is_known_host = False # For looking up entries for nonstandard (22) ssh ports in known_hosts # we enclose host in brackets and append port number known_hosts_lookups = [host, '[%s]:%s' % (host, port)] if hostkey_b64: # If hostkey specified, remote host /must/ use that hostkey if(hostkey_obj.get_name() == server_key_obj.get_name() and hostkey_obj.asbytes() == server_key_obj.asbytes()): is_known_host = True else: # Check known_hosts is_known_host = any(self._host_keys.check(lookup, server_key_obj) for lookup in known_hosts_lookups) if not is_known_host and not unknown_host_cb(host, fingerprint): raise SSHUnknownHostError(known_hosts_lookups[0], fingerprint) # Authenticating with our private key/identity 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 self._closing.clear() if keepalive: self._transport.set_keepalive(keepalive) # TODO: leopoul: Review, test, and if needed rewrite this part subsystem_names = self._device_handler.get_ssh_subsystem_names() for subname in subsystem_names: self._channel = self._transport.open_session() self._channel_id = self._channel.get_id() channel_name = "%s-subsystem-%s" % (subname, str(self._channel_id)) self._channel.set_name(channel_name) if environment: try: self._channel.update_environment(environment) except paramiko.SSHException as e: self.logger.info("%s (environment update 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 try: self._channel.invoke_subsystem(subname) except paramiko.SSHException as e: self.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 = self._channel.get_name() self._post_connect(timeout) # for further upcoming RPC responses, vendor can chose their # choice of parser. Say DOM or SAX self.parser = self._device_handler.get_xml_parser(self) 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, paramiko.Ed25519Key): try: key = cls.from_private_key_file(key_filename, password) self.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 self.logger.debug(e) if allow_agent: # resequence keys from agent using private key names prepend_agent_keys=[] append_agent_keys=list(paramiko.Agent().get_keys()) for key_filename in key_filenames: pubkey_filename=key_filename.strip(".pub")+".pub" try: file_key=paramiko.PublicBlob.from_file(pubkey_filename).key_blob except (FileNotFoundError, ValueError): continue for idx, agent_key in enumerate(append_agent_keys): if agent_key.asbytes() == file_key: self.logger.debug("Prioritising SSH agent key found in %s",key_filename ) prepend_agent_keys.append(append_agent_keys.pop(idx)) break agent_keys=tuple(prepend_agent_keys+append_agent_keys) for key in agent_keys: try: self.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 self.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) self.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 self.logger.debug(e) if password is not None: try: self._transport.auth_password(username, password) return except Exception as e: saved_exception = e self.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 _transport_read(self): return self._channel.recv(BUF_SIZE) def _transport_write(self, data): return self._channel.send(data) def _transport_register(self, selector, event): selector.register(self._channel, event) def _send_ready(self): return self._channel.send_ready() @property def host(self): """Host this session is connected to, or None if not connected.""" if hasattr(self, '_host'): return self._host return None @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.15/ncclient/transport/third_party/000077500000000000000000000000001451325530500215715ustar00rootroot00000000000000ncclient-0.6.15/ncclient/transport/third_party/__init__.py000066400000000000000000000000271451325530500237010ustar00rootroot00000000000000__author__ = 'katharh' ncclient-0.6.15/ncclient/transport/third_party/junos/000077500000000000000000000000001451325530500227275ustar00rootroot00000000000000ncclient-0.6.15/ncclient/transport/third_party/junos/__init__.py000066400000000000000000000000271451325530500250370ustar00rootroot00000000000000__author__ = 'katharh' ncclient-0.6.15/ncclient/transport/third_party/junos/ioproc.py000066400000000000000000000055711451325530500246040ustar00rootroot00000000000000import sys import re import six if sys.version < '3': from cStringIO import StringIO else: from io import BytesIO as StringIO 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 = six.b("]]>]]>") NETCONF_SHELL = 'netconf' 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): stdout, stderr = self._channel.communicate() self._channel = None self._connected = False def connect(self): stdoutdata = check_output(NETCONF_SHELL, shell=True, stdin=PIPE, stderr=STDOUT).decode(encoding="utf8") 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], 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().encode() + 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.15/ncclient/transport/third_party/junos/parser.py000066400000000000000000000254071451325530500246050ustar00rootroot00000000000000# Copyright 2018 Nitin Kumar # 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 from threading import Lock import difflib from xml.sax.handler import ContentHandler from lxml import etree from lxml.builder import E from xml.sax._exceptions import SAXParseException from xml.sax import make_parser from ncclient.transport.parser import DefaultXMLParser from ncclient.operations import rpc from ncclient.transport.parser import SAXFilterXMLNotFoundError from ncclient.transport.parser import MSG_DELIM, MSG_DELIM_LEN from ncclient.operations.errors import OperationError import logging logger = logging.getLogger("ncclient.transport.third_party.junos.parser") from ncclient.xml_ import BASE_NS_1_0 RPC_REPLY_END_TAG = "" RFC_RPC_REPLY_END_TAG = "" class JunosXMLParser(DefaultXMLParser): def __init__(self, session): super(JunosXMLParser, self).__init__(session) self._session = session self.sax_parser = make_parser() self.sax_parser.setContentHandler(SAXParser(session)) def parse(self, data): try: self.sax_parser.feed(data) except SAXParseException: self._delimiter_check(data) except SAXFilterXMLNotFoundError: self.logger.debug('Missing SAX filter_xml. Switching from sax to dom parsing') self._session.parser = DefaultXMLParser(self._session) if not isinstance(data, bytes): data = str.encode(data) self._session._buffer.write(data) finally: self._parse10() def _delimiter_check(self, data): """ SAX parser throws SAXParseException exception, if there is extra data after MSG_DELIM :param data: content read by select loop :return: None """ data = data.decode('UTF-8') if MSG_DELIM in data: # need to feed extra data after MSG_DELIM msg, delim, remaining = data.partition(MSG_DELIM) self._session._buffer.seek(0, os.SEEK_END) self._session._buffer.write(delim.encode()) if remaining.strip() != '': self._session._buffer.write(remaining.encode()) # we need to renew parser, as old parser is gone. self.sax_parser = make_parser() self.sax_parser.setContentHandler(SAXParser(self._session)) elif RPC_REPLY_END_TAG in data or RFC_RPC_REPLY_END_TAG in data: tag = RPC_REPLY_END_TAG if RPC_REPLY_END_TAG in data else \ RFC_RPC_REPLY_END_TAG logger.warning("Check for rpc reply end tag within data received: %s" % data) msg, delim, remaining = data.partition(tag) self._session._buffer.seek(0, os.SEEK_END) self._session._buffer.write(remaining.encode()) else: logger.warning("Check if end delimiter is split within data received: %s" % data) # When data is "-reply/>]]>" or "]]>" # Data is not full MSG_DELIM, So check if last rpc reply is complete. # if then, wait for next iteration of data and do a recursive call to # _delimiter_check for MSG_DELIM check buf = self._session._buffer buf.seek(buf.tell() - len(RFC_RPC_REPLY_END_TAG) - MSG_DELIM_LEN) rpc_response_last_msg = buf.read().decode('UTF-8').replace('\n', '') if RPC_REPLY_END_TAG in rpc_response_last_msg or \ RFC_RPC_REPLY_END_TAG in rpc_response_last_msg: tag = RPC_REPLY_END_TAG if RPC_REPLY_END_TAG in \ rpc_response_last_msg else \ RFC_RPC_REPLY_END_TAG # rpc_response_last_msg and data can be overlapping match_obj = difflib.SequenceMatcher(None, rpc_response_last_msg, data).get_matching_blocks() if match_obj: # 0 means second string match start from beginning, hence # there is a overlap if match_obj[0].b == 0: # matching char are of match_obj[0].size self._delimiter_check((rpc_response_last_msg + data[match_obj[0].size:]).encode()) else: data = rpc_response_last_msg+data if MSG_DELIM in data: # there can be residual end delimiter chars in buffer. # as first if condition will add full delimiter, so clean # it off clean_up = len(rpc_response_last_msg) - ( rpc_response_last_msg.find(tag) + len(tag)) self._session._buffer.truncate(buf.tell() - clean_up) self._delimiter_check(data.encode()) else: self._delimiter_check((rpc_response_last_msg + data).encode()) def __dict_replace(s, d): """Replace substrings of a string using a dictionary.""" for key, value in d.items(): s = s.replace(key, value) return s def _get_sax_parser_root(xml): """ This function does some validation and rule check of xmlstring :param xml: string or object to be used in parsing reply :return: lxml object """ if isinstance(xml, etree._Element): root = xml else: root = etree.fromstring(xml) return root def escape(data, entities={}): """Escape &, <, and > in a string of data. You can escape other strings of data by passing a dictionary as the optional entities parameter. The keys and values must all be strings; each key will be replaced with its corresponding value. """ # must do ampersand first data = data.replace("&", "&") data = data.replace(">", ">") data = data.replace("<", "<") if entities: data = __dict_replace(data, entities) return data def quoteattr(data, entities={}): """Escape and quote an attribute value. Escape &, <, and > in a string of data, then quote it for use as an attribute value. The \" character will be escaped as well, if necessary. You can escape other strings of data by passing a dictionary as the optional entities parameter. The keys and values must all be strings; each key will be replaced with its corresponding value. """ # entities = entities.copy() entities.update({'\n': ' ', '\r': ' ', '\t':' '}) data = escape(data, entities) if '"' in data: if "'" in data: data = '"%s"' % data.replace('"', """) else: data = "'%s'" % data else: data = '"%s"' % data return data class SAXParser(ContentHandler): def __init__(self, session): ContentHandler.__init__(self) self._currenttag = None self._ignoretag = None self._defaulttags = [] self._session = session self._validate_reply_and_sax_tag = False self._lock = Lock() self.nc_namespace = None def startElement(self, tag, attributes): if tag in ['rpc-reply', 'nc:rpc-reply']: if tag == 'nc:rpc-reply': self.nc_namespace = BASE_NS_1_0 # in case last rpc called used sax parsing and error'd out # without resetting use_filer in endElement rpc-reply check with self._lock: listeners = list(self._session._listeners) rpc_reply_listener = [i for i in listeners if isinstance(i, rpc.RPCReplyListener)] rpc_msg_id = attributes._attrs['message-id'] if rpc_msg_id in rpc_reply_listener[0]._id2rpc: rpc_reply_handler = rpc_reply_listener[0]._id2rpc[rpc_msg_id] if hasattr(rpc_reply_handler, '_filter_xml') and \ rpc_reply_handler._filter_xml is not None: self._cur = self._root = _get_sax_parser_root( rpc_reply_handler._filter_xml) else: raise SAXFilterXMLNotFoundError(rpc_reply_handler) else: raise OperationError("Unknown 'message-id': %s" % rpc_msg_id) if self._ignoretag is not None: return if self._cur == self._root and self._cur.tag == tag: node = self._root else: node = self._cur.find(tag, namespaces={"nc": self.nc_namespace}) if self._validate_reply_and_sax_tag: if tag != self._root.tag: self._write_buffer(tag, format_str='<{}>\n') self._cur = E(tag, self._cur) else: self._write_buffer(tag, format_str='<{}{}>', **attributes) self._cur = node self._currenttag = tag self._validate_reply_and_sax_tag = False self._defaulttags.append(tag) elif node is not None: self._write_buffer(tag, format_str='<{}{}>', **attributes) self._cur = node self._currenttag = tag elif tag in ['rpc-reply', 'nc:rpc-reply']: self._write_buffer(tag, format_str='<{}{}>', **attributes) self._defaulttags.append(tag) self._validate_reply_and_sax_tag = True else: self._currenttag = None self._ignoretag = tag def endElement(self, tag): if self._ignoretag == tag: self._ignoretag = None if tag in self._defaulttags: self._write_buffer(tag, format_str='\n') elif self._cur.tag == tag: self._write_buffer(tag, format_str='\n') self._cur = self._cur.getparent() self._currenttag = None def characters(self, content): if self._currenttag is not None: self._write_buffer(content, format_str='{}') def _write_buffer(self, content, format_str, **kwargs): # print(content, format_str, kwargs) self._session._buffer.seek(0, os.SEEK_END) attrs = '' for (name, value) in kwargs.items(): attr = ' {}={}'.format(name, quoteattr(value)) attrs = attrs + attr data = format_str.format(escape(content), attrs) self._session._buffer.write(str.encode(data)) ncclient-0.6.15/ncclient/transport/tls.py000066400000000000000000000132341451325530500204170ustar00rootroot00000000000000# Copyright (c) Siemens AG, 2022 # # 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 logging import socket import sys import threading from socket import AF_INET, SOCK_STREAM from ssl import CERT_REQUIRED, SSLContext, SSLError if sys.version < '3': from six import StringIO else: from io import BytesIO as StringIO from ncclient.capabilities import Capabilities from ncclient.logging_ import SessionLoggerAdapter from ncclient.transport.errors import TLSError from ncclient.transport.session import Session from ncclient.transport.parser import DefaultXMLParser logger = logging.getLogger("ncclient.transport.tls") DEFAULT_TLS_NETCONF_PORT = 6513 DEFAULT_TLS_TIMEOUT = 120 BUF_SIZE = 4096 class TLSSession(Session): def __init__(self, device_handler): capabilities = Capabilities(device_handler.get_capabilities()) Session.__init__(self, capabilities) self._host = None self._connected = False self._socket = None self._buffer = StringIO() self._device_handler = device_handler self._message_list = [] self._closing = threading.Event() self.parser = DefaultXMLParser(self) self.logger = SessionLoggerAdapter(logger, {'session': self}) def _dispatch_message(self, raw): self.logger.info("Received message from host") self.logger.debug("Received:\n%s", raw) return super(TLSSession, self)._dispatch_message(raw) def close(self): self._closing.set() self._socket.close() self._connected = False def connect(self, host=None, port=DEFAULT_TLS_NETCONF_PORT, keyfile=None, certfile=None, ca_certs=None, protocol=None, check_hostname=True, server_hostname=None, timeout=DEFAULT_TLS_TIMEOUT): """Establish NETCONF session via TLS. :param host: Hostname or IP address to connect to. :param port: Port number which would be used for connection, the default value is 6513 (`DEFAULT_TLS_NETCONF_PORT`). :param certfile: Points to file in PEM format with client's certificate and optionally with client's private key. :param keyfile: Points to file with client's private key in PEM format, if the latter is not in certfile. :param ca_certs: Points to file containing a list of certificates, which should be used to verify server certificate. :param protocol: Protocol version to use. Should be either ssl.PROTOCOL_TLS_CLIENT for Python 3.6+, or ssl.PROTOCOL_TLS for older versions. :param check_hostname: If set to True, perform verification of the hostname during the handshake. :param server_hostname: If set, is used upon hostname checking instead of the `host` parameter. :param timeout: Specifies the connection timeout, defaults to 120 seconds (`DEFAULT_TLS_TIMEOUT`). :raise TLSError if the connection can not be established. """ if host is None: raise TLSError('Missing host') if certfile is None: raise TLSError('Missing client certificate file') if protocol is None: raise TLSError('Missing TLS protocol') ssl_context = SSLContext(protocol) ssl_context.verify_mode = CERT_REQUIRED ssl_context.check_hostname = check_hostname try: ssl_context.load_cert_chain(certfile=certfile, keyfile=keyfile) except SSLError: raise TLSError('Bad client private key / certificate pair') except IOError: raise TLSError('Private key / certificate pair not found') if ca_certs: try: ssl_context.load_verify_locations(cafile=ca_certs) except SSLError: raise TLSError('Bad Certification Authority file') except IOError: raise TLSError('CA certificate file not found') sock = socket.socket(AF_INET, SOCK_STREAM) ssl_sock = ssl_context.wrap_socket( sock, do_handshake_on_connect=False, server_hostname=server_hostname or host) ssl_sock.settimeout(timeout) try: ssl_sock.connect((host, port)) except Exception: raise TLSError("Could not connect to %s:%s" % (host, port)) try: ssl_sock.do_handshake() except Exception: raise TLSError("Unsuccessful TLS handshake with %s:%s" % (host, port)) self._host = host self._socket = ssl_sock self._connected = True self._post_connect() def _transport_read(self): return self._socket.recv(BUF_SIZE) def _transport_write(self, data): return self._socket.send(data) def _transport_register(self, selector, event): selector.register(self._socket, event) def _send_ready(self): # In contrast to Paramiko's `Channel`, pure python sockets do not # expose `send_ready()` function. return True @property def host(self): """Host this session is connected to, or None if not connected.""" if hasattr(self, '_host'): return self._host return None ncclient-0.6.15/ncclient/xml_.py000066400000000000000000000263021451325530500165200ustar00rootroot00000000000000# 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 import types 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) huge_parser = etree.XMLParser(recover=False, huge_tree=True) def _get_parser(huge_tree=False): return huge_parser if huge_tree else parser class XMLError(NCClientError): pass ### Namespace-related #: Base NETCONF namespace BASE_NS_1_0 = "urn:ietf:params:xml:ns:netconf:base:1.0" #: YANG (RFC 6020/RFC 7950) namespace YANG_NS_1_0 = "urn:ietf:params:xml:ns:yang:1" #: 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" #: Namespace for Alcatel-Lucent SR OS Base r13 YANG models ALU_CONFIG = "urn:alcatel-lucent.com:sros:ns:yang:conf-r13" #: Namespace for Nokia SR OS global operations SROS_GLOBAL_OPS_NS = "urn:nokia.com:sros:ns:yang:sr:oper-global" 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, huge_tree=False): """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. *huge_tree*: parse XML with very deep trees and very long text content """ if sys.version < '3': return x if etree.iselement(x) else etree.fromstring(x, parser=_get_parser(huge_tree)) else: return x if etree.iselement(x) else etree.fromstring(x.encode('UTF-8'), parser=_get_parser(huge_tree)) 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, huge_tree=False): self.__result = result self.__transform_reply = transform_reply self.__huge_tree = huge_tree if isinstance(transform_reply, types.FunctionType): self.__doc = self.__transform_reply(result._root) else: self.__doc = self.remove_namespaces(self.__result) def xpath(self, expression, namespaces={}): """Perform XPath navigation on an object Args: expression: A string representing a compliant XPath expression. namespaces: A dict of caller supplied prefix/xmlns to append to the static dict of XPath namespaces. Returns: A list of 'lxml.etree._Element' should a match on the expression be successful. Otherwise, an empty list will be returned to the caller. """ self.__expression = expression self.__namespaces = XPATH_NAMESPACES self.__namespaces.update(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 findall(self, expression): """return result for a call to lxml ElementPath findall()""" self.__expression = expression return self.__doc.findall(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, huge_tree=self.__huge_tree) 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, huge_tree=self.__huge_tree) 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)), parser=self.__parser))), parser=self.__parser) return self.__root def parent_ns(node): if node.prefix: return node.nsmap[node.prefix] return None def yang_action(name, attrs): """Instantiate a YANG action element Args: name: A string representing the first descendant name of the XML element for the YANG action. attrs: A dict of attributes to apply to the XML element (e.g. namespaces). Returns: A tuple of 'lxml.etree._Element' values. The first value represents the top-level YANG action element and the second represents the caller supplied initial node. """ node = new_ele('action', attrs={'xmlns': YANG_NS_1_0}) return (node, sub_ele(node, name, attrs)) def replace_namespace(root, old_ns, new_ns): """ Substitute old_ns with new_ns for all the xml elements including and below root :param root: top element (root for this change) :param old_ns: old namespace :param new_ns: new namespace :return: """ for elem in root.getiterator(): # Comments don't have a namespace if elem.tag is not etree.Comment: # handle tag qtag = etree.QName(elem) if qtag.namespace == old_ns: elem.tag = etree.QName(new_ns, qtag.localname) # handle attributes attribs_dict = elem.attrib for attr in attribs_dict.keys(): qattr = etree.QName(attr) if qattr.namespace == old_ns: attribs_dict[etree.QName(new_ns, qattr.localname)] = attribs_dict.pop(attr) new_ele_nsmap = lambda tag, nsmap, attrs={}, **extra: etree.Element(qualify(tag), attrs, nsmap, **extra) 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, parent_ns(parent)), attrs, **extra) sub_ele_ns = lambda parent, tag, ns, attrs={}, **extra: etree.SubElement(parent, qualify(tag, ns), attrs, **extra) ncclient-0.6.15/requirements.txt000066400000000000000000000001331451325530500166660ustar00rootroot00000000000000setuptools>0.6 paramiko>=1.15.0 lxml>=3.3.0 selectors2>=2.0.1; python_version <= '3.4' six ncclient-0.6.15/setup.cfg000066400000000000000000000006101451325530500152230ustar00rootroot00000000000000[aliases] test=pytest [tool:pytest] addopts = --verbose [metadata] description_file = README.md [bdist_wheel] universal=1 # See the docstring in versioneer.py for instructions. Note that you must # re-run 'versioneer.py setup' after changing this section, and commit the # resulting files. [versioneer] VCS = git style = pep440 versionfile_source = ncclient/_version.py tag_prefix = v ncclient-0.6.15/setup.py000066400000000000000000000052641451325530500151260ustar00rootroot00000000000000# 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 import versioneer __author__ = "Shikhar Bhushan, Leonidas Poulopoulos, Ebben Aries, Einar Nilsen-Nygaard" __author_email__ = "shikhar@schmizz.net, lpoulopoulos@verisign.com, exa@dscp.org, einarnn@gmail.com" __licence__ = "Apache 2.0" 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)) test_req_lines = [line.strip() for line in open("test-requirements.txt").readlines()] test_reqs = list(filter(None, test_req_lines)) with codecs.open('README.rst', 'r', encoding='utf8') as file: long_description = file.read() setup(name='ncclient', version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), description="Python library for NETCONF clients", long_description = long_description, author=__author__, author_email=__author_email__, url="https://github.com/ncclient/ncclient", packages=find_packages(exclude=['test', 'test.*']), install_requires=install_reqs, tests_require=test_reqs, license=__licence__, 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.*, !=3.4.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Programming Language :: Python :: 2.7', '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.15/test-requirements.txt000066400000000000000000000001041451325530500176410ustar00rootroot00000000000000pytest-runner pytest-cov sphinx flake8 mock; python_version < '3.6' ncclient-0.6.15/test/000077500000000000000000000000001451325530500143645ustar00rootroot00000000000000ncclient-0.6.15/test/__init__.py000066400000000000000000000000011451325530500164640ustar00rootroot00000000000000 ncclient-0.6.15/test/unit/000077500000000000000000000000001451325530500153435ustar00rootroot00000000000000ncclient-0.6.15/test/unit/__init__.py000066400000000000000000000000261451325530500174520ustar00rootroot00000000000000__author__ = 'palash' ncclient-0.6.15/test/unit/devices/000077500000000000000000000000001451325530500167655ustar00rootroot00000000000000ncclient-0.6.15/test/unit/devices/__init__.py000066400000000000000000000000001451325530500210640ustar00rootroot00000000000000ncclient-0.6.15/test/unit/devices/test_alu.py000066400000000000000000000035751451325530500211710ustar00rootroot00000000000000import unittest from ncclient.devices.alu import * from ncclient.xml_ import * import re xml = """ reX """ class TestAluDevice(unittest.TestCase): def setUp(self): self.obj = AluDeviceHandler({'name': 'alu'}) def test_remove_namespaces(self): xmlObj = to_ele(xml) expected = re.sub(r'', r'', xml) self.assertEqual(expected, to_xml(remove_namespaces(xmlObj))) def test_get_capabilities(self): expected = ["urn:ietf:params:netconf:base:1.0", ] self.assertListEqual(expected, self.obj.get_capabilities()) def test_get_xml_base_namespace_dict(self): expected = {None: BASE_NS_1_0} self.assertDictEqual(expected, self.obj.get_xml_base_namespace_dict()) def test_get_xml_extra_prefix_kwargs(self): expected = dict() expected["nsmap"] = self.obj.get_xml_base_namespace_dict() self.assertDictEqual(expected, self.obj.get_xml_extra_prefix_kwargs()) def test_add_additional_operations(self): expected=dict() expected["get_configuration"] = GetConfiguration expected["show_cli"] = ShowCLI expected["load_configuration"] = LoadConfiguration self.assertDictEqual(expected, self.obj.add_additional_operations()) def test_transform_reply(self): expected = re.sub(r'', r'', xml) actual = self.obj.transform_reply() xmlObj = to_ele(xml) self.assertEqual(expected, to_xml(actual(xmlObj))) ncclient-0.6.15/test/unit/devices/test_csr.py000066400000000000000000000010461451325530500211660ustar00rootroot00000000000000import unittest from ncclient.devices.csr import * class TestCsrDevice(unittest.TestCase): def setUp(self): self.obj = CsrDeviceHandler({'name': 'csr'}) def test_add_additional_ssh_connect_params(self): expected = dict() expected["unknown_host_cb"] = csr_unknown_host_cb actual = dict() self.obj.add_additional_ssh_connect_params(actual) self.assertDictEqual(expected, actual) def test_csr_unknown_host_cb(self): self.assertTrue(csr_unknown_host_cb('host', 'fingerprint')) ncclient-0.6.15/test/unit/devices/test_default.py000066400000000000000000000047521451325530500220320ustar00rootroot00000000000000import 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: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)) def test_is_rpc_error_exempt_1(self): self.assertFalse(self.obj.is_rpc_error_exempt(None)) def test_is_rpc_error_exempt_2(self): self.obj._exempt_errors_exact_match = ["test_exempt"] self.assertTrue(self.obj.is_rpc_error_exempt(" Test_Exempt")) def test_is_rpc_error_exempt_3(self): self.obj._exempt_errors_startwith_wildcard_match = ["test_exempt"] self.assertTrue(self.obj.is_rpc_error_exempt("*Test_Exempt")) def test_is_rpc_error_exempt_4(self): self.obj._exempt_errors_endwith_wildcard_match = ["test_exempt"] self.assertTrue(self.obj.is_rpc_error_exempt("Test_Exempt*")) def test_is_rpc_error_exempt_5(self): self.obj._exempt_errors_full_wildcard_match = ["test_exempt"] self.assertTrue(self.obj.is_rpc_error_exempt("*Test_Exempt*")) suite = unittest.TestSuite() unittest.TextTestRunner().run(suite) ncclient-0.6.15/test/unit/devices/test_ericsson.py000066400000000000000000000061771451325530500222360ustar00rootroot00000000000000import unittest from ncclient import manager from ncclient.devices.ericsson import * from ncclient.operations.rpc import * from ncclient.capabilities import Capabilities import ncclient.transport class TestEricssonDevice(unittest.TestCase): def setUp(self): self.device_handler = manager.make_device_handler({'name': 'ericsson'}) def test_rpc_default(self): # It is a switch for user to turn on/off "nc" prefix, the "nc" prefix is disable by default session = ncclient.transport.SSHSession(self.device_handler) obj = RPC(session, self.device_handler, raise_mode=RaiseMode.ALL, timeout=0) expected = """""" % obj.id node = new_ele("get-config") child = sub_ele(node, "source") sub_ele(child, "running") rpc_node = obj._wrap(node) self.assertEqual(rpc_node, expected) def test_rpc_disable_nc_prefix(self): # It is a switch for user to turn on/off "nc" prefix self.device_handler = manager.make_device_handler({'name': 'ericsson', 'with_ns': False}) session = ncclient.transport.SSHSession(self.device_handler) obj = RPC(session, self.device_handler, raise_mode=RaiseMode.ALL, timeout=0) expected = """""" % obj.id node = new_ele("get-config") child = sub_ele(node, "source") sub_ele(child, "running") # It is a switch for user to turn on/off "nc" prefix rpc_node = obj._wrap(node) self.assertEqual(rpc_node, expected) def test_rpc_enable_nc_prefix(self): # It is a switch for user to turn on/off "nc" prefix self.device_handler = manager.make_device_handler({'name': 'ericsson', 'with_ns': True}) session = ncclient.transport.SSHSession(self.device_handler) obj = RPC(session, self.device_handler, raise_mode=RaiseMode.ALL, timeout=0) expected = """""" % obj.id node = new_ele("get-config") child = sub_ele(node, "source") sub_ele(child, "running") rpc_node = obj._wrap(node) self.assertEqual(rpc_node, expected) def test_rpc_enable_nc_prefix_exception(self): # invalid value in "with_ns" self.device_handler = manager.make_device_handler({'name': 'ericsson', 'with_ns': "Invalid_value"}) session = ncclient.transport.SSHSession(self.device_handler) obj = RPC(session, self.device_handler, raise_mode=RaiseMode.ALL, timeout=0) node = new_ele("get-config") child = sub_ele(node, "source") sub_ele(child, "running") self.assertRaises(OperationError, obj._wrap, node) suite = unittest.TestSuite() unittest.TextTestRunner().run(suite) ncclient-0.6.15/test/unit/devices/test_get_supported_devices.py000066400000000000000000000040101451325530500247570ustar00rootroot00000000000000import unittest from ncclient import devices class TestGetSupportedDevices(unittest.TestCase): def test_get_supported_devices(self): supported_devices = devices.get_supported_devices() self.assertEqual(sorted(supported_devices), sorted(('junos', 'csr', 'nexus', 'iosxr', 'iosxe', 'huawei', 'huaweiyang', 'alu', 'h3c', 'hpcomware', 'sros', 'default'))) def test_get_supported_device_labels(self): supported_device_labels = devices.get_supported_device_labels() self.assertEqual(supported_device_labels, {'junos':'Juniper', 'csr':'Cisco CSR1000v', 'nexus':'Cisco Nexus', 'iosxr':'Cisco IOS XR', 'iosxe':'Cisco IOS XE', 'huawei':'Huawei', 'huaweiyang':'Huawei', 'alu':'Alcatel Lucent', 'h3c':'H3C', 'hpcomware':'HP Comware', 'sros':'Nokia SR OS', 'default':'Server or anything not in above'}) ncclient-0.6.15/test/unit/devices/test_h3c.py000066400000000000000000000035731451325530500210630ustar00rootroot00000000000000import unittest from ncclient.devices.h3c import * 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:ietf:params:netconf:capability:interleave:1.0', 'urn:ietf:params:netconf:capability:with-defaults:1.0'] class TestH3cDevice(unittest.TestCase): def setUp(self): self.obj = H3cDeviceHandler({'name': 'h3c'}) def test_add_additional_operations(self): expected = dict() expected['get_bulk'] = GetBulk expected['get_bulk_config'] = GetBulkConfig expected['cli'] = CLI expected['action'] = Action expected['save'] = Save expected['load'] = Load expected['rollback'] = Rollback self.assertDictEqual(expected, self.obj.add_additional_operations()) def test_get_capabilities(self): self.assertListEqual(capabilities, self.obj.get_capabilities()) def test_get_xml_extra_prefix_kwargs(self): expected = dict() expected['nsmap'] = self.obj.get_xml_base_namespace_dict() self.assertDictEqual(expected, self.obj.get_xml_extra_prefix_kwargs()) def test_perform_qualify_check(self): self.assertFalse(self.obj.perform_qualify_check()) ncclient-0.6.15/test/unit/devices/test_hpcomware.py000066400000000000000000000021211451325530500223570ustar00rootroot00000000000000import unittest from ncclient.devices.hpcomware import * from ncclient.xml_ import * class TestHpcomwareDevice(unittest.TestCase): def setUp(self): self.obj = HpcomwareDeviceHandler({'name': 'hpcomware'}) def test_get_xml_base_namespace_dict(self): expected = {None: BASE_NS_1_0} self.assertDictEqual(expected, self.obj.get_xml_base_namespace_dict()) def test_get_xml_extra_prefix_kwargs(self): expected = {'nsmap': {'data': 'http://www.hp.com/netconf/data:1.0', 'config': 'http://www.hp.com/netconf/config:1.0', None: 'urn:ietf:params:xml:ns:netconf:base:1.0'}} self.assertDictEqual(expected, self.obj.get_xml_extra_prefix_kwargs()) def test_add_additional_operations(self): expected = dict() expected['cli_display'] = DisplayCommand expected['cli_config'] = ConfigCommand expected['action'] = Action expected['save'] = Save expected['rollback'] = Rollback self.assertDictEqual(expected, self.obj.add_additional_operations()) ncclient-0.6.15/test/unit/devices/test_huawei.py000066400000000000000000000045531451325530500216670ustar00rootroot00000000000000import unittest from ncclient.devices.huawei import * 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:ietf:params:netconf:capability:interleave:1.0', 'urn:ietf:params:netconf:capability:with-defaults:1.0', 'http://www.huawei.com/netconf/capability/execute-cli/1.0', 'http://www.huawei.com/netconf/capability/action/1.0', 'http://www.huawei.com/netconf/capability/active/1.0', 'http://www.huawei.com/netconf/capability/discard-commit/1.0', 'http://www.huawei.com/netconf/capability/exchange/1.0'] class TestHuaweiDevice(unittest.TestCase): def setUp(self): self.obj = HuaweiDeviceHandler({'name': 'huawei'}) def test_add_additional_operations(self): expected = dict() expected['cli'] = CLI expected['action'] = Action self.assertDictEqual(expected, self.obj.add_additional_operations()) def test_handle_raw_dispatch(self): expected = 'hello' self.assertEqual(expected, self.obj.handle_raw_dispatch('hello')) def test_get_capabilities(self): self.assertListEqual(capabilities, self.obj.get_capabilities()) def test_get_xml_base_namespace_dict(self): expected = {None: BASE_NS_1_0} self.assertDictEqual(expected, self.obj.get_xml_base_namespace_dict()) def test_get_xml_extra_prefix_kwargs(self): expected = dict() expected['nsmap'] = self.obj.get_xml_base_namespace_dict() self.assertDictEqual(expected, self.obj.get_xml_extra_prefix_kwargs()) def test_perform_qualify_check(self): self.assertFalse(self.obj.perform_qualify_check()) ncclient-0.6.15/test/unit/devices/test_huaweiyang.py000066400000000000000000000014161451325530500225410ustar00rootroot00000000000000import unittest from ncclient.devices.huaweiyang import * class TestHuaweiyangDevice(unittest.TestCase): def setUp(self): self.obj = HuaweiyangDeviceHandler({'name': 'huaweiyang'}) def test_get_capabilities(self): expected = ['urn:ietf:params:netconf:base:1.0', 'urn:ietf:params:netconf:base:1.1'] self.assertListEqual(expected, self.obj.get_capabilities()) def test_get_xml_base_namespace_dict(self): expected = {None: BASE_NS_1_0} self.assertDictEqual(expected, self.obj.get_xml_base_namespace_dict()) def test_get_xml_extra_prefix_kwargs(self): expected = dict() expected['nsmap'] = self.obj.get_xml_base_namespace_dict() self.assertDictEqual(expected, self.obj.get_xml_extra_prefix_kwargs()) ncclient-0.6.15/test/unit/devices/test_iosxe.py000066400000000000000000000024511451325530500215270ustar00rootroot00000000000000import unittest from ncclient.devices.iosxe import * from ncclient.xml_ import new_ele from ncclient.xml_ import qualify from ncclient.xml_ import validated_element CFG_BROKEN = """ tl-einarnn-c8kv """ class TestIosxeDevice(unittest.TestCase): def setUp(self): self.obj = IosxeDeviceHandler({'name': 'iosxe'}) def test_add_additional_operations(self): expected = {'save_config': SaveConfig} self.assertDictEqual(expected, self.obj.add_additional_operations()) def test_add_additional_ssh_connect_params(self): expected = dict() expected["unknown_host_cb"] = iosxe_unknown_host_cb actual = dict() self.obj.add_additional_ssh_connect_params(actual) self.assertDictEqual(expected, actual) def test_csr_unknown_host_cb(self): self.assertTrue(iosxe_unknown_host_cb('host', 'fingerprint')) def test_iosxe_transform_edit_config(self): node = new_ele("edit-config") node.append(validated_element(CFG_BROKEN, ("config", qualify("config")))) node = self.obj.transform_edit_config(node) config_nodes = node.findall('./config') self.assertTrue(len(config_nodes) == 0) ncclient-0.6.15/test/unit/devices/test_iosxr.py000066400000000000000000000012301451325530500215360ustar00rootroot00000000000000import unittest from ncclient.devices.iosxr import * class TestIosxrDevice(unittest.TestCase): def setUp(self): self.obj = IosxrDeviceHandler({'name': 'iosxe'}) def test_add_additional_ssh_connect_params(self): expected = dict() expected["unknown_host_cb"] = iosxr_unknown_host_cb actual = dict() self.obj.add_additional_ssh_connect_params(actual) self.assertDictEqual(expected, actual) def test_perform_qualify_check(self): self.assertFalse(self.obj.perform_qualify_check()) def test_csr_unknown_host_cb(self): self.assertTrue(iosxr_unknown_host_cb('host', 'fingerprint')) ncclient-0.6.15/test/unit/devices/test_junos.py000066400000000000000000000066771451325530500215540ustar00rootroot00000000000000import unittest from ncclient.devices.junos import * import ncclient.transport try: from unittest.mock import patch # Python 3.4 and later except ImportError: from mock import patch import paramiko import sys xml = ''' ''' xml2 = """ reX """ xml3 = """ reX """ xml4 = """ commit reX commit failure greeting!""" 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()) def test_handle_raw_dispatch(self): self.assertFalse(self.obj.handle_raw_dispatch(xml)) expected = re.sub(r'', '\n', xml3) self.assertEqual(expected, self.obj.handle_raw_dispatch(xml3)) expected = 'undefined: not an error message in the reply. Enable debug' self.assertEqual(expected, str(self.obj.handle_raw_dispatch(xml4))) ncclient-0.6.15/test/unit/devices/test_nexus.py000066400000000000000000000045531451325530500215470ustar00rootroot00000000000000import unittest from ncclient.devices.nexus import * capabilities = ['urn:ietf:params:xml:ns: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:ietf:params:netconf:capability:interleave:1.0', 'urn:ietf:params:netconf:capability:with-defaults:1.0'] class TestNexusDevice(unittest.TestCase): def setUp(self): self.obj = NexusDeviceHandler({'name': 'nexus'}) def test_add_additional_operations(self): expected = {'exec_command': ExecCommand} self.assertDictEqual(expected, self.obj.add_additional_operations()) def test_get_capabilities(self): self.assertListEqual(capabilities, self.obj.get_capabilities()) def test_get_xml_base_namespace_dict(self): expected = {None: BASE_NS_1_0} self.assertDictEqual(expected, self.obj.get_xml_base_namespace_dict()) def test_get_xml_extra_prefix_kwargs(self): temp = { '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' } temp.update(self.obj.get_xml_base_namespace_dict()) expected = dict() expected['nsmap'] = temp self.assertDictEqual(expected, self.obj.get_xml_extra_prefix_kwargs()) def test_get_ssh_subsystem_names(self): expected = ['netconf', 'xmlagent'] self.assertListEqual(expected, self.obj.get_ssh_subsystem_names()) expected.insert(0, 'ncclient') self.obj = NexusDeviceHandler({'ssh_subsystem_name': 'ncclient'}) self.assertListEqual(expected, self.obj.get_ssh_subsystem_names()) ncclient-0.6.15/test/unit/devices/test_sros.py000066400000000000000000000056211451325530500213700ustar00rootroot00000000000000import unittest from ncclient.devices.sros import * from ncclient.xml_ import * 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:ietf:params:netconf:capability:interleave:1.0', 'urn:ietf:params:netconf:capability:with-defaults:1.0', 'urn:ietf:params:xml:ns:netconf:base:1.0', 'urn:ietf:params:xml:ns:yang:1', 'urn:ietf:params:netconf:capability:confirmed-commit:1.1', 'urn:ietf:params:netconf:capability:validate:1.1'] xml = """ TiMOS-B-20.10.B1-5 both/x86_64 Nokia 7750 SR Copyright (c) 2000-2020 Nokia. All rights reserved. All use subject to applicable license agreements. Built on Fri Oct 2 18:11:20 PDT 2020 by builder in /builds/c/2010B/B1-5/panos/main/sros """ class TestSrosDevice(unittest.TestCase): def setUp(self): self.obj = SrosDeviceHandler({'name': 'sros'}) def test_add_additional_operations(self): expected = { 'md_cli_raw_command': MdCliRawCommand, 'commit': Commit, } self.assertDictEqual(expected, self.obj.add_additional_operations()) def test_transform_reply(self): expected = xml actual = self.obj.transform_reply() ele = to_ele(xml) self.assertEqual(expected, to_xml(actual(ele))) def test_get_capabilities(self): self.assertListEqual(capabilities, self.obj.get_capabilities()) def test_get_xml_base_namespace_dict(self): expected = {None: BASE_NS_1_0} self.assertDictEqual(expected, self.obj.get_xml_base_namespace_dict()) def test_get_xml_extra_prefix_kwargs(self): expected = dict() expected['nsmap'] = self.obj.get_xml_base_namespace_dict() self.assertDictEqual(expected, self.obj.get_xml_extra_prefix_kwargs()) def test_perform_qualify_check(self): self.assertFalse(self.obj.perform_qualify_check()) ncclient-0.6.15/test/unit/operations/000077500000000000000000000000001451325530500175265ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/__init__.py000066400000000000000000000000001451325530500216250ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/test_edit.py000066400000000000000000000302771451325530500220750ustar00rootroot00000000000000from ncclient.operations.edit import * import unittest try: from unittest.mock import patch, MagicMock # Python 3.4 and later except ImportError: 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="merge", test_option="test-then-set") node = new_ele("edit-config") node.append(util.datastore_or_url("target", "running")) sub_ele(node, "default-operation").text = "merge" sub_ele(node, "test-option").text = "test-then-set" sub_ele(node, "error-option").text = "rollback-on-error" 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.edit.RPC._request') @patch('ncclient.operations.edit.RPC._assert') def test_edit_config_valid_url(self, mock_assert, mock_request): session = ncclient.transport.SSHSession(self.device_handler) obj = EditConfig( session, self.device_handler, raise_mode=RaiseMode.ALL) config = "https://www.xxx.org/index.html" obj.request(config, format='url') node = new_ele("edit-config") node.append(util.datastore_or_url("target", "candidate")) sub_ele(node, "url").text = 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') @patch('ncclient.operations.edit.RPC._assert') def test_edit_config_invalid_url(self, mock_assert, mock_request): session = ncclient.transport.SSHSession(self.device_handler) obj = EditConfig( session, self.device_handler, raise_mode=RaiseMode.ALL) config = "Invalid URL" self.assertRaises(OperationError, obj.request, config, format='url') def test_edit_config_invalid_arguments_exception(self): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [":rollback-on-error", ":validate", ":validate:1.1"] obj = EditConfig( session, self.device_handler, raise_mode=RaiseMode.ALL) root = """ system { host-name foo-bar; } """ #invalid argument "default_operation" self.assertRaises(OperationError, obj.request, copy.deepcopy(root), format="xml", target="running", error_option="stop-on-error", default_operation="create", test_option="test-then-set") #invalid argument "error_option" self.assertRaises(OperationError, obj.request, copy.deepcopy(root), format="xml", target="running", error_option="commit-on-error", default_operation="merge", test_option="test-then-set") #invalid argument "test_option" self.assertRaises(OperationError, obj.request, copy.deepcopy(root), format="xml", target="running", error_option="stop-on-error", default_operation="merge", test_option="test") def test_edit_config_validate_capability_exception(self): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = [":validate"] obj = EditConfig( session, self.device_handler, raise_mode=RaiseMode.ALL) root = """ system { host-name foo-bar; } """ self.assertRaises(MissingCapabilityError, obj.request, copy.deepcopy(root), format="xml", target="running", error_option="stop-on-error", default_operation="merge", test_option="test-only") @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.15/test/unit/operations/test_lock.py000066400000000000000000000076551451325530500221040ustar00rootroot00000000000000from ncclient.operations.lock import * import unittest try: from unittest.mock import patch # Python 3.4 and later except ImportError: 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.15/test/unit/operations/test_retrieve.py000066400000000000000000000256321451325530500227740ustar00rootroot00000000000000from ncclient.operations.retrieve import * import unittest try: from unittest.mock import patch # Python 3.4 and later except ImportError: from mock import patch from ncclient import manager import ncclient.manager import ncclient.transport from ncclient.capabilities import Capabilities 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 import six 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_basic_mode(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = Capabilities([ "urn:ietf:params:netconf:capability:with-defaults:1.0" "?basic-mode=explicit" ]) obj = Get(session, self.device_handler, raise_mode=RaiseMode.ALL) obj.request(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_with_defaults_also_supported(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = Capabilities([ "urn:ietf:params:netconf:capability:with-defaults:1.0" "?basic-mode=explicit" "&also-supported=report-all,trim" ]) 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 = Capabilities([ "urn:ietf:params:netconf:capability:with-defaults:1.0" "?basic-mode=explicit" "&also-supported=report-all,trim" ]) obj = Get(session, self.device_handler, raise_mode=RaiseMode.ALL) expected_error = ( "Invalid 'with-defaults' mode 'report-all-tagged'; the server " "only supports the following: explicit, report-all, trim" ) six.assertRaisesRegex( self, WithDefaultsError, expected_error, obj.request, with_defaults='report-all-tagged' ) @patch('ncclient.operations.retrieve.RPC._request') def test_get_with_defaults_missing_capability(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = 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_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 = Capabilities([ "urn:ietf:params:netconf:capability:with-defaults:1.0" "?basic-mode=explicit" ]) 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_missing_capability(self, mock_request): session = ncclient.transport.SSHSession(self.device_handler) session._server_capabilities = 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) @patch('ncclient.operations.retrieve.RPC._request') def test_get_with_multi_subtree_filters(self, mock_request): result = ''' test_mod1_001 this is a test-one example test_mod2_002 this is a test-two example a list of mod2 ''' mock_request.return_value = result session = ncclient.transport.SSHSession(self.device_handler) obj = Get(session, self.device_handler, raise_mode=RaiseMode.ALL) multi_subtree_filters = [ ' \ \ \ ', '' ] ret = obj.request(copy.deepcopy(multi_subtree_filters)) node = new_ele("get") node.append(util.build_filter(multi_subtree_filters)) xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) self.assertEqual(ret, result) ncclient-0.6.15/test/unit/operations/test_rpc.py000066400000000000000000000303241451325530500217250ustar00rootroot00000000000000from ncclient.operations.rpc import * import unittest try: from unittest.mock import patch # Python 3.4 and later except ImportError: 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 from xml.sax.saxutils import escape import sys if sys.version >= '3': patch_str = 'ncclient.operations.rpc.Event.is_set' else: patch_str = 'threading._Event.is_set' 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 """ # Huge dummy text configuration to trigger "xml.etree.XMLSyntaxError: xmlSAX2Characters: huge text node" # - The maximum size of a single text node is `#define XML_MAX_TEXT_LENGTH 10000000` # (https://gitlab.gnome.org/GNOME/libxml2/blob/master/include/libxml/parserInternals.h) # - The text should contain at least one special character that should be escaped (e.g. '>') huge_configuration_text = "-->\n" + "Huge configuration. " * 500000 xml5_huge = """ %s """ % escape(huge_configuration_text) xml6 = """ application invalid-value error path/to/node system1 app-tag1 syntax error """ xml7 = """ protocol missing-element error path/to/different/node system2 app-tag2 missing element error """ 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) def test_rpc_reply_huge_text_node_exception(self): obj = RPCReply(xml5_huge) self.assertRaises(etree.XMLSyntaxError, obj.parse) def test_rpc_reply_huge_text_node_workaround(self): obj = RPCReply(xml5_huge, huge_tree=True) obj.parse() self.assertTrue(obj.ok) self.assertFalse(obj.error) self.assertEqual(xml5_huge, 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, session = self._mock_device_handler_and_session() 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_generic_rpc_send(self, mock_thread, mock_send): device_handler, session = self._mock_device_handler_and_session() obj = GenericRPC(session, device_handler, raise_mode=RaiseMode.ALL, timeout=0) reply = RPCReply(xml1) obj._reply = reply rpc_command = 'edit-config' filters = ('subtree', '') result = obj.request(rpc_command, source='running', target='candidate', filter=filters) ele = new_ele("rpc", {"message-id": obj._id}, **device_handler.get_xml_extra_prefix_kwargs()) child = new_ele(rpc_command) child.append(util.datastore_or_url('target', 'candidate')) child.append(util.datastore_or_url('source', 'running')) child.append(util.build_filter(filters)) ele.append(child) 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, session = self._mock_device_handler_and_session() 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, session = self._mock_device_handler_and_session() 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, session = self._mock_device_handler_and_session() 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) def test_rpc_rpcerror_tag_to_attr(self): '''All elements in extracted.''' err = RPCError(to_ele(xml6)) self.assertEqual(None, err.errlist) self.assertEqual("application", err.type) self.assertEqual("invalid-value", err.tag) self.assertEqual("error", err.severity) self.assertEqual("path/to/node", err.path) self.assertEqual("app-tag1", err.app_tag) self.assertEqual("syntax error", err.message) self.assertIn("system1", err.info) def test_rpc_rpcerror_multiple_errors(self): '''Multiple errors in extracted correctly''' errlist = [RPCError(to_ele(xml6)), RPCError(to_ele(xml7))] multiple_xml = ( '' + xml6 + xml7 + "") multiple_err = RPCError(to_ele(multiple_xml), errs=errlist) errs = multiple_err.errlist self.assertEqual(2, len(errs)) self.assertEqual("application", errs[0].type) self.assertEqual("invalid-value", errs[0].tag) self.assertEqual("error", errs[0].severity) self.assertEqual("path/to/node", errs[0].path) self.assertEqual("app-tag1", errs[0].app_tag) self.assertEqual("syntax error", errs[0].message) self.assertIn("system1", errs[0].info) self.assertEqual("protocol", errs[1].type) self.assertEqual("missing-element", errs[1].tag) self.assertEqual("error", errs[1].severity) self.assertEqual("path/to/different/node", errs[1].path) self.assertEqual("app-tag2", errs[1].app_tag) self.assertEqual("missing element error", errs[1].message) self.assertIn("system2", errs[1].info) @patch('ncclient.transport.Session.send') @patch(patch_str) def test_rpc_capability_error(self, mock_thread, mock_send): device_handler, session = self._mock_device_handler_and_session() session._server_capabilities = [':running'] obj = RPC(session, device_handler, raise_mode=RaiseMode.ALL, timeout=0) obj._assert(':running') self.assertRaises(MissingCapabilityError, obj._assert, ':candidate') @patch('ncclient.transport.Session.send') def test_rpc_huge_text_node_exception(self, mock_send): device_handler, session = self._mock_device_handler_and_session() obj = RPC(session, device_handler, raise_mode=RaiseMode.ALL, timeout=0) obj.deliver_reply(xml5_huge) node = new_ele("get-configuration", {'format': 'text'}) self.assertRaises(etree.XMLSyntaxError, obj._request, node) @patch('ncclient.transport.Session.send') def test_rpc_huge_text_node_workaround(self, mock_send): device_handler, session = self._mock_device_handler_and_session() obj = RPC(session, device_handler, raise_mode=RaiseMode.ALL, timeout=0, huge_tree=True) self.assertTrue(obj.huge_tree) obj.deliver_reply(xml5_huge) node = new_ele("get-configuration", {'format': 'text'}) result = obj._request(node) self.assertEqual(result.find('configuration-text').text, huge_configuration_text) obj.huge_tree = False self.assertFalse(obj.huge_tree) def _mock_device_handler_and_session(self): device_handler = manager.make_device_handler({'name': 'junos'}) capabilities = Capabilities(device_handler.get_capabilities()) session = ncclient.transport.Session(capabilities) return device_handler, session ncclient-0.6.15/test/unit/operations/test_session.py000066400000000000000000000032371451325530500226270ustar00rootroot00000000000000from ncclient.operations.session import * import unittest try: from unittest.mock import patch # Python 3.4 and later except ImportError: 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.15/test/unit/operations/test_subscribe.py000066400000000000000000000055341451325530500231270ustar00rootroot00000000000000from ncclient.operations.edit import * from ncclient.operations.subscribe import * import unittest try: from unittest.mock import patch, MagicMock # Python 3.4 and later except ImportError: 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_ns(node, "stream", NETCONF_NOTIFICATION_NS).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_ns(node, "startTime", NETCONF_NOTIFICATION_NS).text = start_time sub_ele_ns(node, "stopTime", NETCONF_NOTIFICATION_NS).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.15/test/unit/operations/test_utils.py000066400000000000000000000064051451325530500223040ustar00rootroot00000000000000import unittest from xml.etree import ElementTree from ncclient.operations.util import * try: from unittest.mock import MagicMock # Python 3.4 and later except ImportError: 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_xpath(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_xpath_ns(self): select = "configuration/system" ns = {"ns0": "http://www.xxx.org"} criteria = (ns, select) filter = ("xpath", criteria) reply = build_filter(filter) call = ElementTree.tostring(reply) node = new_ele_nsmap("filter", ns, type="xpath") node.attrib["select"] = select self.assertEqual(call, ElementTree.tostring(node)) def test_build_filter_subtree(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_other(self): criteria = """ """ filter = ("text", criteria) self.assertRaises(OperationError, build_filter, filter, capcheck="cap") ncclient-0.6.15/test/unit/operations/third_party/000077500000000000000000000000001451325530500220575ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/__init__.py000066400000000000000000000000001451325530500241560ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/alu/000077500000000000000000000000001451325530500226405ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/alu/__init__.py000066400000000000000000000000001451325530500247370ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/alu/test_rpc.py000066400000000000000000000057021451325530500250410ustar00rootroot00000000000000import unittest try: from unittest.mock import patch # Python 3.4 and later except ImportError: from mock import patch from ncclient import manager import ncclient.transport from ncclient.operations.third_party.alu.rpc import * class TestRPC(unittest.TestCase): def setUp(self): self.device_handler = manager.make_device_handler({'name': 'alu'}) @patch('ncclient.operations.third_party.alu.rpc.RPC._request') def test_showCLI(self, mock_request): mock_request.return_value = 'alu' expected = 'alu' session = ncclient.transport.SSHSession(self.device_handler) obj = ShowCLI(session, self.device_handler) command = 'show system users' actual = obj.request(command=command) self.assertEqual(expected, actual) @patch('ncclient.operations.third_party.alu.rpc.RPC._request') def test_getConfiguration(self, mock_request): mock_request.return_value = 'alu' expected = 'alu' session = ncclient.transport.SSHSession(self.device_handler) obj = GetConfiguration(session, self.device_handler) content = 'xml' actual = obj.request(content=content) self.assertEqual(expected, actual) filter = 'device-name' actual = obj.request(content=content, filter=filter) self.assertEqual(expected, actual) content = 'cli' actual = obj.request(content=content, filter=filter) self.assertEqual(expected, actual) detail = True actual = obj.request(content=content, filter=filter, detail=detail) self.assertEqual(expected, actual) content = '' actual = obj.request(content=content, filter=filter, detail=detail) self.assertEqual(expected, actual) @patch('ncclient.operations.third_party.alu.rpc.RPC._request') def test_loadConfiguration(self, mock_request): mock_request.return_value = 'alu' expected = 'alu' session = ncclient.transport.SSHSession(self.device_handler) obj = LoadConfiguration(session, self.device_handler) default_operation = '' format = 'xml' actual = obj.request(format=format, default_operation=default_operation) self.assertEqual(expected, actual) default_operation = 'get' actual=obj.request(format=format, default_operation=default_operation) self.assertEqual(expected, actual) config = new_ele('device-name') actual=obj.request(format=format, default_operation=default_operation, config=config) self.assertEqual(expected, actual) config = 'device-name' format = 'cli' actual=obj.request(format=format, default_operation=default_operation, config=config) self.assertEqual(expected, actual) default_operation = '' actual=obj.request(format=format, default_operation=default_operation, config=config) self.assertEqual(expected, actual) ncclient-0.6.15/test/unit/operations/third_party/h3c/000077500000000000000000000000001451325530500225345ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/h3c/__init__.py000066400000000000000000000000001451325530500246330ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/h3c/test_rpc.py000066400000000000000000000074061451325530500247400ustar00rootroot00000000000000import unittest try: from unittest.mock import patch # Python 3.4 and later except ImportError: from mock import patch from ncclient import manager import ncclient.transport from ncclient.operations.third_party.h3c.rpc import * class TestRPC(unittest.TestCase): def setUp(self): self.device_handler = manager.make_device_handler({'name': 'h3c'}) @patch('ncclient.operations.third_party.h3c.rpc.RPC._request') def test_getBulk(self, mock_request): mock_request.return_value = 'h3c' expected = 'h3c' session = ncclient.transport.SSHSession(self.device_handler) obj = GetBulk(session, self.device_handler) actual = obj.request() self.assertEqual(expected, actual) filter = '' actual = obj.request(filter=filter) self.assertEqual(expected, actual) @patch('ncclient.operations.third_party.h3c.rpc.RPC._request') def test_getBulkConfig(self, mock_request): mock_request.return_value = 'h3c' expected = 'h3c' session = ncclient.transport.SSHSession(self.device_handler) obj = GetBulkConfig(session, self.device_handler) source = 'devices-name' actual = obj.request(source=source) self.assertEqual(expected, actual) filter='' actual = obj.request(source=source, filter=filter) self.assertEqual(expected, actual) @patch('ncclient.operations.third_party.h3c.rpc.RPC._request') def test_CLI(self, mock_request): mock_request.return_value = 'h3c' expected = 'h3c' session = ncclient.transport.SSHSession(self.device_handler) obj = CLI(session, self.device_handler) command = 'devices-name' actual = obj.request(command=command) self.assertEqual(expected, actual) @patch('ncclient.operations.third_party.h3c.rpc.RPC._request') def test_action(self, mock_request): mock_request.return_value = 'h3c' expected = 'h3c' session = ncclient.transport.SSHSession(self.device_handler) obj = Action(session, self.device_handler) action = 'devices-name' actual = obj.request(action=action) self.assertEqual(expected, actual) @patch('ncclient.operations.third_party.h3c.rpc.RPC._request') def test_save(self, mock_request): mock_request.return_value = 'h3c' expected = 'h3c' session = ncclient.transport.SSHSession(self.device_handler) obj = Save(session, self.device_handler) actual = obj.request() self.assertEqual(expected, actual) file = 'devices-name' actual = obj.request(file=file) self.assertEqual(expected, actual) @patch('ncclient.operations.third_party.h3c.rpc.RPC._request') def test_load(self, mock_request): mock_request.return_value = 'h3c' expected = 'h3c' session = ncclient.transport.SSHSession(self.device_handler) obj = Load(session, self.device_handler) actual = obj.request() self.assertEqual(expected, actual) file = 'devices-name' actual = obj.request(file=file) self.assertEqual(expected, actual) @patch('ncclient.operations.third_party.h3c.rpc.RPC._request') def test_rollback(self, mock_request): mock_request.return_value = 'h3c' expected = 'h3c' session = ncclient.transport.SSHSession(self.device_handler) obj = Rollback(session, self.device_handler) actual = obj.request() self.assertEqual(expected, actual) file = 'devices-name' actual = obj.request(file=file) self.assertEqual(expected, actual) ncclient-0.6.15/test/unit/operations/third_party/hpcomware/000077500000000000000000000000001451325530500240445ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/hpcomware/__init__.py000066400000000000000000000000001451325530500261430ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/hpcomware/test_rpc.py000066400000000000000000000057011451325530500262440ustar00rootroot00000000000000import unittest try: from unittest.mock import patch # Python 3.4 and later except ImportError: from mock import patch from ncclient import manager import ncclient.transport from ncclient.operations.third_party.hpcomware.rpc import * class TestRPC(unittest.TestCase): def setUp(self): self.device_handler = manager.make_device_handler({'name': 'hpcomware'}) @patch('ncclient.operations.third_party.hpcomware.rpc.RPC._request') def test_displayCommand(self, mock_request): mock_request.return_value = 'hpcomware' expected = 'hpcomware' session = ncclient.transport.SSHSession(self.device_handler) obj = DisplayCommand(session, self.device_handler) cmds = 'show devices-name' actual = obj.request(cmds=cmds) self.assertEqual(expected, actual) commands = [cmd for cmd in cmds] actual = obj.request(cmds=commands) self.assertEqual(expected, actual) @patch('ncclient.operations.third_party.hpcomware.rpc.RPC._request') def test_configCommand(self, mock_request): mock_request.return_value = 'hpcomware' expected = 'hpcomware' session = ncclient.transport.SSHSession(self.device_handler) obj = ConfigCommand(session, self.device_handler) cmds = 'show devices-name' actual = obj.request(cmds=cmds) self.assertEqual(expected, actual) commands = [cmd for cmd in cmds] actual = obj.request(cmds=commands) self.assertEqual(expected, actual) @patch('ncclient.operations.third_party.hpcomware.rpc.RPC._request') def test_action(self, mock_request): mock_request.return_value = 'hpcomware' expected = 'hpcomware' session = ncclient.transport.SSHSession(self.device_handler) obj = Action(session, self.device_handler) action = 'devices-name' actual = obj.request(action=action) self.assertEqual(expected, actual) @patch('ncclient.operations.third_party.hpcomware.rpc.RPC._request') def test_save(self, mock_request): mock_request.return_value = 'hpcomware' expected = 'hpcomware' session = ncclient.transport.SSHSession(self.device_handler) obj = Save(session, self.device_handler) actual = obj.request() self.assertEqual(expected, actual) filename = 'devices-name' actual = obj.request(filename=filename) self.assertEqual(expected, actual) @patch('ncclient.operations.third_party.hpcomware.rpc.RPC._request') def test_rollback(self, mock_request): mock_request.return_value = 'hpcomware' expected = 'hpcomware' session = ncclient.transport.SSHSession(self.device_handler) obj = Rollback(session, self.device_handler) actual = obj.request() self.assertEqual(expected, actual) filename = 'devices-name' actual = obj.request(filename=filename) self.assertEqual(expected, actual) ncclient-0.6.15/test/unit/operations/third_party/huawei/000077500000000000000000000000001451325530500233415ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/huawei/__init__.py000066400000000000000000000000001451325530500254400ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/huawei/test_rpc.py000066400000000000000000000023401451325530500255350ustar00rootroot00000000000000import unittest try: from unittest.mock import patch # Python 3.4 and later except ImportError: from mock import patch from ncclient import manager import ncclient.transport from ncclient.operations.third_party.huawei.rpc import * class TestRPC(unittest.TestCase): def setUp(self): self.device_handler = manager.make_device_handler({'name': 'huawei'}) @patch('ncclient.operations.third_party.huawei.rpc.RPC._request') def test_CLI(self, mock_request): mock_request.return_value = 'huawei' expected = 'huawei' session = ncclient.transport.SSHSession(self.device_handler) obj = CLI(session, self.device_handler) command = 'devices-name' actual = obj.request(command=command) self.assertEqual(expected, actual) @patch('ncclient.operations.third_party.huawei.rpc.RPC._request') def test_action(self, mock_request): mock_request.return_value = 'huawei' expected = 'huawei' session = ncclient.transport.SSHSession(self.device_handler) obj = Action(session, self.device_handler) action = 'devices-name' actual = obj.request(action=action) self.assertEqual(expected, actual) ncclient-0.6.15/test/unit/operations/third_party/iosxe/000077500000000000000000000000001451325530500232065ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/iosxe/__init__.py000066400000000000000000000000001451325530500253050ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/iosxe/test_rpc.py000066400000000000000000000013711451325530500254050ustar00rootroot00000000000000import unittest try: from unittest.mock import patch # Python 3.4 and later except ImportError: from mock import patch from ncclient import manager import ncclient.transport from ncclient.operations.third_party.iosxe.rpc import * class TestRPC(unittest.TestCase): def setUp(self): self.device_handler = manager.make_device_handler({'name': 'iosxe'}) @patch('ncclient.operations.third_party.iosxe.rpc.RPC._request') def test_saveConfig(self, mock_request): mock_request.return_value = 'iosxe' expected = 'iosxe' session = ncclient.transport.SSHSession(self.device_handler) obj = SaveConfig(session, self.device_handler) actual = obj.request() self.assertEqual(expected, actual) ncclient-0.6.15/test/unit/operations/third_party/juniper/000077500000000000000000000000001451325530500235335ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/juniper/__init__.py000066400000000000000000000000001451325530500256320ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/juniper/test_rpc.py000066400000000000000000000336321451325530500257370ustar00rootroot00000000000000from ncclient.operations.third_party.juniper.rpc import * import json import unittest try: from unittest.mock import patch # Python 3.4 and later except ImportError: 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_getconf_text_sets_huge_tree(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) obj.huge_tree = False obj.request(format='text') self.assertTrue(obj.huge_tree) @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), 'format': 'text'}) 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) obj.request(check=True) 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) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.juniper.rpc.RPC._request') @patch('ncclient.operations.third_party.juniper.rpc.RPC._assert') def test_rollback(self, mock_assert, mock_request, mock_session): mock_request.return_value = 'junos' expected = 'junos' # mock_session.server_capabilities.return_value = [':candidate'] device_handler = manager.make_device_handler({'name': 'junos'}) session = ncclient.transport.SSHSession(device_handler) obj = Rollback(session, device_handler, raise_mode=RaiseMode.ALL) actual = obj.request() self.assertEqual(expected, actual) actual = obj.request(rollback=1) self.assertEqual(expected, actual) ncclient-0.6.15/test/unit/operations/third_party/nexus/000077500000000000000000000000001451325530500232215ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/nexus/test_rpc.py000066400000000000000000000016501451325530500254200ustar00rootroot00000000000000import unittest try: from unittest.mock import patch # Python 3.4 and later except ImportError: from mock import patch from ncclient import manager import ncclient.transport from ncclient.operations.third_party.nexus.rpc import * class TestRPC(unittest.TestCase): def setUp(self): self.device_handler = manager.make_device_handler({'name': 'nexus'}) @patch('ncclient.operations.third_party.nexus.rpc.RPC._request') def test_execCommand(self, mock_request): mock_request.return_value = 'nexus' expected = 'nexus' session = ncclient.transport.SSHSession(self.device_handler) obj = ExecCommand(session, self.device_handler) cmds = 'show devices-name' actual = obj.request(cmds=cmds) self.assertEqual(expected, actual) commands = [cmd for cmd in cmds] actual = obj.request(cmds=commands) self.assertEqual(expected, actual) ncclient-0.6.15/test/unit/operations/third_party/sros/000077500000000000000000000000001451325530500230455ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/sros/__init__.py000066400000000000000000000000001451325530500251440ustar00rootroot00000000000000ncclient-0.6.15/test/unit/operations/third_party/sros/test_rpc.py000066400000000000000000000035111451325530500252420ustar00rootroot00000000000000import unittest from xml.etree import ElementTree try: from unittest.mock import patch # Python 3.4 and later except ImportError: from mock import patch from ncclient import manager import ncclient.transport from ncclient.operations.third_party.sros.rpc import * from ncclient.operations import RaiseMode class TestRPC(unittest.TestCase): def setUp(self): self.device_handler = manager.make_device_handler({'name': 'sros'}) @patch('ncclient.operations.third_party.sros.rpc.RPC._request') def test_MdCliRawCommand(self, mock_request): mock_request.return_value = 'sros' expected = 'sros' session = ncclient.transport.SSHSession(self.device_handler) obj = MdCliRawCommand(session, self.device_handler) command = 'show version' actual = obj.request(command=command) self.assertEqual(expected, actual) @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.third_party.sros.rpc.RPC._request') @patch('ncclient.operations.third_party.sros.rpc.RPC._assert') def test_commit(self, mock_assert, mock_request, mock_session): device_handler = manager.make_device_handler({'name': 'sros'}) session = ncclient.transport.SSHSession(device_handler) obj = Commit(session, device_handler, raise_mode=RaiseMode.ALL) obj.request(confirmed=True, comment="This is a comment", timeout="50") node = new_ele("commit") sub_ele(node, "comment", attrs={'xmlns': "urn:nokia.com:sros:ns:yang:sr:ietf-netconf-augments"}).text = "This is a comment" sub_ele(node, "confirmed") sub_ele(node, "confirm-timeout").text = "50" xml = ElementTree.tostring(node) call = mock_request.call_args_list[0][0][0] call = ElementTree.tostring(call) self.assertEqual(call, xml) ncclient-0.6.15/test/unit/reply1000066400000000000000000000007751451325530500165130ustar00rootroot00000000000000 R1 firefly-perimeter firefly-perimeter junos JUNOS Software Release [12.1X46-D10.2] ncclient-0.6.15/test/unit/ssh_config000066400000000000000000000001261451325530500174070ustar00rootroot00000000000000Host fake_host hostname 10.0.0.1 proxycommand ssh -W %h:%p jumphost.domain.comncclient-0.6.15/test/unit/test_manager.py000066400000000000000000000402531451325530500203720ustar00rootroot00000000000000import unittest try: from unittest.mock import patch, MagicMock # Python 3.4 and later getattr(MagicMock, 'assert_called_once') # Python 3.6 and later except (ImportError, AttributeError): from mock import patch, MagicMock from ncclient import manager from ncclient.devices.junos import JunosDeviceHandler import logging 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() m.connect.assert_called_once_with(host='10.10.10.10', port=22, username='user', password='password', hostkey_verify=False, allow_agent=False, timeout=3) 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.transport.SSHSession.load_known_hosts') @patch('ncclient.transport.SSHSession.connect') def test_connect_ssh1(self, mock_ssh, mock_load_known_hosts): manager.connect(host='host') mock_ssh.assert_called_once_with(host='host') mock_load_known_hosts.assert_not_called() @patch('socket.socket') @patch('paramiko.Transport') @patch('ncclient.transport.ssh.hexlify') @patch('ncclient.transport.ssh.Session._post_connect') def test_connect_ssh2(self, mock_session, mock_hex, mock_trans, mock_socket): conn = manager.connect_ssh(host='10.10.10.10', port=22, username='user', password='password', timeout=3, hostkey_verify=False, allow_agent=False, keepalive=10) self.assertEqual(mock_trans.called, 1) @patch('ncclient.transport.SSHSession.connect') @patch('ncclient.transport.SSHSession.transport') @patch('ncclient.transport.SSHSession.close') def test_connect_exception(self, mock_close, mock_transport, mock_ssh): mock_ssh.side_effect = Exception try: manager.connect(host='host') except Exception: Exception("connect occured exception") mock_ssh.assert_called_once_with(host='host') @patch('ncclient.transport.SSHSession.connect') @patch('ncclient.transport.SSHSession.take_notification') def test_manager_take_notification(self, mock_take_notification, mock_ssh): mock_take_notification.return_value = "test_take_notification" conn = self._mock_manager() ret = conn.take_notification() mock_take_notification.assert_called_once_with(True, None) self.assertEqual(ret, "test_take_notification") @patch('ncclient.transport.SSHSession.connect') @patch('ncclient.operations.retrieve.GetConfig._request') def test_manager_getattr(self, mock_request, mock_ssh): conn = self._mock_manager() conn.get_config("running") mock_ssh.assert_called_once_with(host='10.10.10.10', port=22, username='user', password='password', timeout=3, hostkey_verify=False, allow_agent=False) @patch('ncclient.transport.SSHSession.connect') @patch('ncclient.transport.Session.send') @patch('ncclient.operations.rpc.RPC._request') def test_manager_getattr2(self, mock_request, mock_send, mock_ssh): conn = self._mock_manager() conn.get_edit('config') mock_ssh.assert_called_once_with(host='10.10.10.10', port=22, username='user', password='password', timeout=3, hostkey_verify=False, allow_agent=False) @patch('ncclient.manager.connect_ssh') def test_connect_ssh_with_hostkey_ed25519(self, mock_ssh): hostkey = 'AAAAC3NzaC1lZDI1NTE5AAAAIIiHpGSf8fla6tCwLpwshvMGmUK+B/0v5CsRu+5v4uT7' manager.connect(host='host', hostkey=hostkey) mock_ssh.assert_called_once_with(host='host', hostkey=hostkey) @patch('ncclient.manager.connect_ssh') def test_connect_ssh_with_hostkey_ecdsa(self, mock_ssh): hostkey = 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFJV9xLkuntH3Ry0GmK4FjYlW+01Ik4j/gbW+i3yIx+YEkF0B3iM7kiyDPqvmOPuVGfW+gq5oQzzdvHKspNkw70=' manager.connect(host='host', hostkey=hostkey) mock_ssh.assert_called_once_with(host='host', hostkey=hostkey) @patch('ncclient.manager.connect_ssh') def test_connect_ssh_with_hostkey_rsa(self, mock_ssh): hostkey = 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDfEAdDrz3l8+PF510ivzWyX/pjpn3Cp6UgjJOinXz82e1LTURZhKwm8blcP8aWe8Uri65Roe6Q/H1WMaR3jFJj4UW2EZY5N+M4esPhoP/APOnDu2XNKy9AK9yD/Bu64TYgkIPQ/6FHdotcQdYTAJ+ac+YfJMp5mhVPnRIh4rlF08a0/tDHzLJVMEoXzp5nfVHcA4W3+5RRhklbct10U0jxHmG8Db9XbKiEbhWs/UMy59UpJ+zr7zLUYPRntgqqkpCyyfeHFNK1P6m3FmyT06QekOioCFmY05y65dkjAwBlaO1RKj1X1lgCirRWu4vxYBo9ewIGPZtuzeyp7jnl7kGV' manager.connect(host='host', hostkey=hostkey) mock_ssh.assert_called_once_with(host='host', hostkey=hostkey) @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('paramiko.proxy.ProxyCommand') @patch('paramiko.Transport') @patch('ncclient.transport.ssh.hexlify') @patch('ncclient.transport.ssh.Session._post_connect') def test_connect_with_ssh_config(self, mock_session, mock_hex, mock_trans, mock_proxy): log = logging.getLogger('TestManager.test_connect_with_ssh_config') ssh_config_path = 'test/unit/ssh_config' conn = manager.connect(host='fake_host', port=830, username='user', password='password', hostkey_verify=False, allow_agent=False, ssh_config=ssh_config_path) log.debug(mock_proxy.call_args[0][0]) self.assertEqual(mock_proxy.called, 1) mock_proxy.assert_called_with('ssh -W 10.0.0.1:830 jumphost.domain.com') @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=3, hostkey_verify=False, device_params={'local': True, 'name': 'junos'}, manager_params={'timeout': 10}) 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_custom_client_capability( self, mock_session, mock_hex, mock_trans, mock_socket): custom_capabilities = [ 'urn:custom:capability:1.0', 'urn:custom:capability:2.0' ] conn = self._mock_manager(nc_params={'capabilities': custom_capabilities}) 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) @patch('ncclient.manager.Manager.HUGE_TREE_DEFAULT') @patch('ncclient.transport.SSHSession') @patch('ncclient.operations.rpc.RPC') def test_manager_huge_node(self, mock_rpc, mock_session, default_value): # Set default value to True only in this test through the default_value mock default_value = True # true should propagate all the way to the RPC conn = self._mock_manager() self.assertTrue(conn.huge_tree) conn.execute(mock_rpc) mock_rpc.assert_called_once() self.assertTrue(mock_rpc.call_args[1]['huge_tree']) # false should propagate all the way to the RPC conn.huge_tree = False self.assertFalse(conn.huge_tree) mock_rpc.reset_mock() conn.execute(mock_rpc) mock_rpc.assert_called_once() self.assertFalse(mock_rpc.call_args[1]['huge_tree']) def _mock_manager(self, nc_params={}): conn = manager.connect(host='10.10.10.10', port=22, username='user', password='password', timeout=3, hostkey_verify=False, allow_agent=False, device_params={'name': 'junos'}, manager_params={'timeout': 10}, nc_params=nc_params) return conn @patch('socket.socket') @patch('paramiko.Transport.start_client') @patch('paramiko.Transport.get_remote_server_key') @patch('ncclient.transport.ssh.hexlify') @patch('ncclient.transport.SSHSession._auth') @patch('paramiko.Transport.open_session') @patch('ncclient.transport.ssh.Session._post_connect') def test_manager_environment( self, mock_session_post_connect, mock_transport_open_session, mock_ssh_session_auth, mock_hex, mock_transport_get_remote_server_key, mock_transport_start_client, mock_socket): m = MagicMock() mock_transport_open_session.return_value = m env={"VAR1":"VALUE1"} conn = manager.connect(host='10.10.10.10', hostkey_verify=False, allow_agent=False, environment=env) self.assertEqual(conn.connected, True) m.update_environment.assert_called_once_with(env) @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', device_params={'name': 'junos'}, hostkey_verify=False, allow_agent=False) return conn @patch('socket.socket') @patch('ncclient.manager.connect_ssh') def test_call_home(self, mock_ssh, mock_socket_open): mock_connected_socket = MagicMock() mock_server_socket = MagicMock() mock_socket_open.return_value = mock_server_socket mock_server_socket.accept.return_value = (mock_connected_socket, 'remote.host') with manager.call_home(host='0.0.0.0', port=1234) as chm: mock_ssh.assert_called_once_with(host='0.0.0.0', port=1234, sock=mock_connected_socket) if __name__ == "__main__": suite = unittest.TestLoader().loadTestsFromTestCase(TestManager) unittest.TextTestRunner(verbosity=2).run(suite) ncclient-0.6.15/test/unit/test_xml_.py000066400000000000000000000205731451325530500177220ustar00rootroot00000000000000from ncclient import manager from ncclient.xml_ import * import unittest import os import sys file_path = os.path.join(os.getcwd(), "test", "unit", "reply1") class Test_NCElement(unittest.TestCase): 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') self.assertEqual(str(result), result_str) #data_xml != tostring self.assertNotEqual(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 self.assertEqual(result.xpath("//host-name")[0].text, "R1") self.assertEqual( result.xpath("/rpc-reply/software-information/host-name")[0].text, "R1") self.assertEqual( 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 self.assertEqual(result.findtext(".//host-name"), "R1") self.assertEqual(result.find(".//host-name").tag, "host-name") self.assertEqual(result.find(".//host-name"), result.findall(".//host-name")[0]) self.assertEqual(result.findall(".//host-name")[0].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_findall(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.findall(".//name")[0].tag, "name") self.assertEqual(result.findall(".//name")[0].text, "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"]) def test_sub_ele_inherit_parent_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) ele = new_ele_ns(result.find("./cli").tag, "http://www.xxx.org") child = sub_ele(ele, "child") sibling = sub_ele(ele, "sibling") grandchild = sub_ele(child, "grandchild") self.assertEqual(child.tag, "{http://www.xxx.org}child") self.assertEqual(sibling.tag, "{http://www.xxx.org}sibling") self.assertEqual(grandchild.tag, "{http://www.xxx.org}grandchild") ncclient-0.6.15/test/unit/transport/000077500000000000000000000000001451325530500173775ustar00rootroot00000000000000ncclient-0.6.15/test/unit/transport/__init__.py000066400000000000000000000000001451325530500214760ustar00rootroot00000000000000ncclient-0.6.15/test/unit/transport/certs/000077500000000000000000000000001451325530500205175ustar00rootroot00000000000000ncclient-0.6.15/test/unit/transport/certs/test.crt000066400000000000000000000026641451325530500222200ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIECTCCAvGgAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCQ1ox FjAUBgNVBAgMDVNvdXRoIE1vcmF2aWExDTALBgNVBAcMBEJybm8xDzANBgNVBAoM BkNFU05FVDEMMAoGA1UECwwDVE1DMRMwEQYDVQQDDApleGFtcGxlIENBMSIwIAYJ KoZIhvcNAQkBFhNleGFtcGxlY2FAbG9jYWxob3N0MB4XDTE1MDczMDA3MjcxOFoX DTM1MDcyNTA3MjcxOFowgYUxCzAJBgNVBAYTAkNaMRYwFAYDVQQIDA1Tb3V0aCBN b3JhdmlhMQ8wDQYDVQQKDAZDRVNORVQxDDAKBgNVBAsMA1RNQzEXMBUGA1UEAwwO ZXhhbXBsZSBjbGllbnQxJjAkBgkqhkiG9w0BCQEWF2V4YW1wbGVjbGllbnRAbG9j YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAueCQaNQWoNmF K6LKu1p8U8ZWdWg/PvDdLsJyzfzl/Qw4UA68SfFNaY06zZl8QB9W02nr5kWeeMY0 VA3adrPgOlvfx3oWlFbkETnMaN4OT3WTQ0Wt6jAWZDzVfopwpJPAzRPxACDftIqF GagYcF32hZlVNqqnVdbXh0S0EViweqp/dbG4VDUHSNVbglc+u4UbEzNIFXMdEFsJ ZpkynOmSiTsIATqIhb+2srkVgLwhfkC2qkuHQwAHdubuB07ObM2z01UhyEdDvEYG HwtYAGDBL2TAcsI0oGeVkRyuOkV0QY0UN7UEFI1yTYw+xZ42HgFx3uGwApCImxhb j69GBYWFqwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVu U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUXGpLeLnh2cSDARAV A7KrBxGYpo8wHwYDVR0jBBgwFoAUc1YQIqjZsHVwlea0AB4N+ilNI2gwDQYJKoZI hvcNAQELBQADggEBAJPV3RTXFRtNyOU4rjPpYeBAIAFp2aqGc4t2J1c7oPp/1n+l ZvjnwtlJpZHxMM783e2ryDQ6dkvXDf8kpwKlg3U3mkJ3xKkDdWrM4QwghXdCN519 aa9qmu0zdFL+jUAaWlQ5tsceOrvbusCcbMqiFGk/QfpHqPv52SVWbYyUx7IX7DE+ UjgsLHycfV/tlcx4ZE6soTzl9VdgSL/zmzG3rjsr58J80rXckLgBhvijgBlIAJvW fC7D0vaouvBInSFXymdPVoUDZ30cdGLf+hI/i/TfsEMOinLrXVdkSGNo6FXAHKSv XeB9oFKSzhQ7OPyRyqvEPycUSw/qD6FVr80oDDc= -----END CERTIFICATE----- ncclient-0.6.15/test/unit/transport/certs/test.key000066400000000000000000000032171451325530500222130ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAueCQaNQWoNmFK6LKu1p8U8ZWdWg/PvDdLsJyzfzl/Qw4UA68 SfFNaY06zZl8QB9W02nr5kWeeMY0VA3adrPgOlvfx3oWlFbkETnMaN4OT3WTQ0Wt 6jAWZDzVfopwpJPAzRPxACDftIqFGagYcF32hZlVNqqnVdbXh0S0EViweqp/dbG4 VDUHSNVbglc+u4UbEzNIFXMdEFsJZpkynOmSiTsIATqIhb+2srkVgLwhfkC2qkuH QwAHdubuB07ObM2z01UhyEdDvEYGHwtYAGDBL2TAcsI0oGeVkRyuOkV0QY0UN7UE FI1yTYw+xZ42HgFx3uGwApCImxhbj69GBYWFqwIDAQABAoIBAQCZN9kR8DGu6V7y t0Ax68asL8O5B/OKaHWKQ9LqpVrXmikZJOxkbzoGldow/CIFoU+q+Zbwu9aDa65a 0wiP7Hoa4Py3q5XNNUrOQDyU/OYC7cI0I83WS0lJ2zOJGYj8wKae5Z81IeQFKGHK 4lsy1OGPAvPRGh7RjUUgRavA2MCwe07rWRuDb/OJFe4Oh56UMEjwMiNBtMNtncog j1vr/qgRJdf9tf0zlJmLvUJ9+HSFFV9I/97LJyFhb95gAfHkjdVroLVgT3Cho+4P WtZaKCIGD0OwfOG2nLV4leXvRUk62/LMlB8NI9+JF7Xm+HCKbaWHNWC7mvWSLV58 Zl4AbUWRAoGBANyJ6SFHFRHSPDY026SsdMzXR0eUxBAK7G70oSBKKhY+O1j0ocLE jI2krHJBhHbLlnvJVyMUaCUOTS5m0uDw9hgSsAqeSL3hL38kxVZw+KNG9Ouno1Fl KnE/xXHlPQyeGs/P8nAMzHZxQtEsQdQayJEhK2XXHTsy7Q3MxDisfVJ1AoGBANfD 34gB+OMx6pwj7zk3qWbYXSX8xjCZMR0ciko+h4xeMP2N8B0oyoqC+v1ABMAtJ3wG sGZd0hV9gwM7OUM3SEwkn6oeg1GemWLcn4rlSmTnZc4aeVwrEWlnSNFX3s4g9l4u k8Ugu4MVJYqH8HuDQ5Ggl6/QAwPzMSEdCW0O+jOfAoGAIBRbegC5+t6m7Yegz4Ja dxV1g98K6f58x+MDsQu4tYWV4mmrQgaPH2dtwizvlMwmdpkh+LNWNtWuumowkJHc akIFo3XExQIFg6wYnGtQb4e5xrGa2xMpKlIJaXjb+YLiCYqJDG2ALFZrTrvuU2kV 9a5qfqTc1qigvNolTM0iaaUCgYApmrZWhnLUdEKV2wP813PNxfioI4afxlpHD8LG sCn48gymR6E+Lihn7vuwq5B+8fYEH1ISWxLwW+RQUjIneNhy/jjfV8TgjyFqg7or 0Sy4KjpiNI6kLBXOakELRNNMkeSPopGR2E7v5rr3bGD9oAD+aqX1G7oJH/KgPPYd Vl7+ZwKBgQDcHyWYrimjyUgKaQD2GmoO9wdcJYQ59ke9K+OuGlp4ti5arsi7N1tP B4f09aeELM2ASIuk8Q/Mx0jQFnm8lzRFXdewgvdPoZW/7VufM9O7dGPOc41cm2Dh yrTcXx/VmUBb+/fnXVEgCv7gylp/wtdTGHQBQJHR81jFBz0lnLj+gg== -----END RSA PRIVATE KEY----- ncclient-0.6.15/test/unit/transport/certs/test.pem000066400000000000000000000061031451325530500222010ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIECTCCAvGgAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCQ1ox FjAUBgNVBAgMDVNvdXRoIE1vcmF2aWExDTALBgNVBAcMBEJybm8xDzANBgNVBAoM BkNFU05FVDEMMAoGA1UECwwDVE1DMRMwEQYDVQQDDApleGFtcGxlIENBMSIwIAYJ KoZIhvcNAQkBFhNleGFtcGxlY2FAbG9jYWxob3N0MB4XDTE1MDczMDA3MjcxOFoX DTM1MDcyNTA3MjcxOFowgYUxCzAJBgNVBAYTAkNaMRYwFAYDVQQIDA1Tb3V0aCBN b3JhdmlhMQ8wDQYDVQQKDAZDRVNORVQxDDAKBgNVBAsMA1RNQzEXMBUGA1UEAwwO ZXhhbXBsZSBjbGllbnQxJjAkBgkqhkiG9w0BCQEWF2V4YW1wbGVjbGllbnRAbG9j YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAueCQaNQWoNmF K6LKu1p8U8ZWdWg/PvDdLsJyzfzl/Qw4UA68SfFNaY06zZl8QB9W02nr5kWeeMY0 VA3adrPgOlvfx3oWlFbkETnMaN4OT3WTQ0Wt6jAWZDzVfopwpJPAzRPxACDftIqF GagYcF32hZlVNqqnVdbXh0S0EViweqp/dbG4VDUHSNVbglc+u4UbEzNIFXMdEFsJ ZpkynOmSiTsIATqIhb+2srkVgLwhfkC2qkuHQwAHdubuB07ObM2z01UhyEdDvEYG HwtYAGDBL2TAcsI0oGeVkRyuOkV0QY0UN7UEFI1yTYw+xZ42HgFx3uGwApCImxhb j69GBYWFqwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVu U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUXGpLeLnh2cSDARAV A7KrBxGYpo8wHwYDVR0jBBgwFoAUc1YQIqjZsHVwlea0AB4N+ilNI2gwDQYJKoZI hvcNAQELBQADggEBAJPV3RTXFRtNyOU4rjPpYeBAIAFp2aqGc4t2J1c7oPp/1n+l ZvjnwtlJpZHxMM783e2ryDQ6dkvXDf8kpwKlg3U3mkJ3xKkDdWrM4QwghXdCN519 aa9qmu0zdFL+jUAaWlQ5tsceOrvbusCcbMqiFGk/QfpHqPv52SVWbYyUx7IX7DE+ UjgsLHycfV/tlcx4ZE6soTzl9VdgSL/zmzG3rjsr58J80rXckLgBhvijgBlIAJvW fC7D0vaouvBInSFXymdPVoUDZ30cdGLf+hI/i/TfsEMOinLrXVdkSGNo6FXAHKSv XeB9oFKSzhQ7OPyRyqvEPycUSw/qD6FVr80oDDc= -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAueCQaNQWoNmFK6LKu1p8U8ZWdWg/PvDdLsJyzfzl/Qw4UA68 SfFNaY06zZl8QB9W02nr5kWeeMY0VA3adrPgOlvfx3oWlFbkETnMaN4OT3WTQ0Wt 6jAWZDzVfopwpJPAzRPxACDftIqFGagYcF32hZlVNqqnVdbXh0S0EViweqp/dbG4 VDUHSNVbglc+u4UbEzNIFXMdEFsJZpkynOmSiTsIATqIhb+2srkVgLwhfkC2qkuH QwAHdubuB07ObM2z01UhyEdDvEYGHwtYAGDBL2TAcsI0oGeVkRyuOkV0QY0UN7UE FI1yTYw+xZ42HgFx3uGwApCImxhbj69GBYWFqwIDAQABAoIBAQCZN9kR8DGu6V7y t0Ax68asL8O5B/OKaHWKQ9LqpVrXmikZJOxkbzoGldow/CIFoU+q+Zbwu9aDa65a 0wiP7Hoa4Py3q5XNNUrOQDyU/OYC7cI0I83WS0lJ2zOJGYj8wKae5Z81IeQFKGHK 4lsy1OGPAvPRGh7RjUUgRavA2MCwe07rWRuDb/OJFe4Oh56UMEjwMiNBtMNtncog j1vr/qgRJdf9tf0zlJmLvUJ9+HSFFV9I/97LJyFhb95gAfHkjdVroLVgT3Cho+4P WtZaKCIGD0OwfOG2nLV4leXvRUk62/LMlB8NI9+JF7Xm+HCKbaWHNWC7mvWSLV58 Zl4AbUWRAoGBANyJ6SFHFRHSPDY026SsdMzXR0eUxBAK7G70oSBKKhY+O1j0ocLE jI2krHJBhHbLlnvJVyMUaCUOTS5m0uDw9hgSsAqeSL3hL38kxVZw+KNG9Ouno1Fl KnE/xXHlPQyeGs/P8nAMzHZxQtEsQdQayJEhK2XXHTsy7Q3MxDisfVJ1AoGBANfD 34gB+OMx6pwj7zk3qWbYXSX8xjCZMR0ciko+h4xeMP2N8B0oyoqC+v1ABMAtJ3wG sGZd0hV9gwM7OUM3SEwkn6oeg1GemWLcn4rlSmTnZc4aeVwrEWlnSNFX3s4g9l4u k8Ugu4MVJYqH8HuDQ5Ggl6/QAwPzMSEdCW0O+jOfAoGAIBRbegC5+t6m7Yegz4Ja dxV1g98K6f58x+MDsQu4tYWV4mmrQgaPH2dtwizvlMwmdpkh+LNWNtWuumowkJHc akIFo3XExQIFg6wYnGtQb4e5xrGa2xMpKlIJaXjb+YLiCYqJDG2ALFZrTrvuU2kV 9a5qfqTc1qigvNolTM0iaaUCgYApmrZWhnLUdEKV2wP813PNxfioI4afxlpHD8LG sCn48gymR6E+Lihn7vuwq5B+8fYEH1ISWxLwW+RQUjIneNhy/jjfV8TgjyFqg7or 0Sy4KjpiNI6kLBXOakELRNNMkeSPopGR2E7v5rr3bGD9oAD+aqX1G7oJH/KgPPYd Vl7+ZwKBgQDcHyWYrimjyUgKaQD2GmoO9wdcJYQ59ke9K+OuGlp4ti5arsi7N1tP B4f09aeELM2ASIuk8Q/Mx0jQFnm8lzRFXdewgvdPoZW/7VufM9O7dGPOc41cm2Dh yrTcXx/VmUBb+/fnXVEgCv7gylp/wtdTGHQBQJHR81jFBz0lnLj+gg== -----END RSA PRIVATE KEY----- ncclient-0.6.15/test/unit/transport/rpc-reply/000077500000000000000000000000001451325530500213145ustar00rootroot00000000000000ncclient-0.6.15/test/unit/transport/rpc-reply/get-software-information-rfc.xml000066400000000000000000000273321451325530500275470ustar00rootroot00000000000000 tests wf-kiwi mx960 mx960 junos JUNOS Base OS boot [12.3R6.6] jbase JUNOS Base OS Software Suite [12.3R6.6] jkernel JUNOS Kernel Software Suite [12.3R6.6] jcrypto JUNOS Crypto Software Suite [12.3R6.6] jpfe-common JUNOS Packet Forwarding Engine Support (M/T/EX Common) [12.3R6.6] jpfe JUNOS Packet Forwarding Engine Support (MX Common) [12.3R6.6] jdocs JUNOS Online Documentation [12.3R6.6] jservices-aacl JUNOS Services AACL Container package [12.3R6.6] jservices-alg JUNOS Services Application Level Gateways [12.3R6.6] jservices-appid JUNOS AppId Services [12.3R6.6] jservices-bgf JUNOS Border Gateway Function package [12.3R6.6] jservices-cpcd JUNOS Services Captive Portal and Content Delivery Container package [12.3R6.6] jservices-hcm JUNOS Services HTTP Content Management package [12.3R6.6] jservices-idp JUNOS IDP Services [12.3R6.6] jservices-llpdf JUNOS Services LL-PDF Container package [12.3R6.6] jservices-nat JUNOS Services NAT [12.3R6.6] jservices-ptsp JUNOS Services PTSP Container package [12.3R6.6] jservices-rpm JUNOS Services RPM [12.3R6.6] jservices-sfw JUNOS Services Stateful Firewall [12.3R6.6] jservices-voice JUNOS Voice Services Container package [12.3R6.6] jservices-example JUNOS Services Example Container package [12.3R6.6] jservices-crypto-base JUNOS Services Crypto [12.3R6.6] jservices-ssl JUNOS Services SSL [12.3R6.6] jservices-ipsec JUNOS Services IPSec [12.3R6.6] jruntime JUNOS Runtime Software Suite [12.3R6.6] jplatform JUNOS platform Software Suite [12.3R6.6] jroute JUNOS Routing Software Suite [12.3R6.6] re1 wf-kiwi mx960 mx960 junos JUNOS Base OS boot [12.3R6.6] jbase JUNOS Base OS Software Suite [12.3R6.6] jkernel JUNOS Kernel Software Suite [12.3R6.6] jcrypto JUNOS Crypto Software Suite [12.3R6.6] jpfe-common JUNOS Packet Forwarding Engine Support (M/T/EX Common) [12.3R6.6] jpfe JUNOS Packet Forwarding Engine Support (MX Common) [12.3R6.6] jdocs JUNOS Online Documentation [12.3R6.6] jservices-aacl JUNOS Services AACL Container package [12.3R6.6] jservices-alg JUNOS Services Application Level Gateways [12.3R6.6] jservices-appid JUNOS AppId Services [12.3R6.6] jservices-bgf JUNOS Border Gateway Function package [12.3R6.6] jservices-cpcd JUNOS Services Captive Portal and Content Delivery Container package [12.3R6.6] jservices-hcm JUNOS Services HTTP Content Management package [12.3R6.6] jservices-idp JUNOS IDP Services [12.3R6.6] jservices-llpdf JUNOS Services LL-PDF Container package [12.3R6.6] jservices-nat JUNOS Services NAT [12.3R6.6] jservices-ptsp JUNOS Services PTSP Container package [12.3R6.6] jservices-rpm JUNOS Services RPM [12.3R6.6] jservices-sfw JUNOS Services Stateful Firewall [12.3R6.6] jservices-voice JUNOS Voice Services Container package [12.3R6.6] jservices-example JUNOS Services Example Container package [12.3R6.6] jservices-crypto-base JUNOS Services Crypto [12.3R6.6] jservices-ssl JUNOS Services SSL [12.3R6.6] jservices-ipsec JUNOS Services IPSec [12.3R6.6] jruntime JUNOS Runtime Software Suite [12.3R6.6] jplatform JUNOS platform Software Suite [12.3R6.6] jroute JUNOS Routing Software Suite [12.3R6.6] ]]>]]>ncclient-0.6.15/test/unit/transport/rpc-reply/get-software-information.xml000066400000000000000000000273241451325530500270000ustar00rootroot00000000000000 tests wf-kiwi mx960 mx960 junos JUNOS Base OS boot [12.3R6.6] jbase JUNOS Base OS Software Suite [12.3R6.6] jkernel JUNOS Kernel Software Suite [12.3R6.6] jcrypto JUNOS Crypto Software Suite [12.3R6.6] jpfe-common JUNOS Packet Forwarding Engine Support (M/T/EX Common) [12.3R6.6] jpfe JUNOS Packet Forwarding Engine Support (MX Common) [12.3R6.6] jdocs JUNOS Online Documentation [12.3R6.6] jservices-aacl JUNOS Services AACL Container package [12.3R6.6] jservices-alg JUNOS Services Application Level Gateways [12.3R6.6] jservices-appid JUNOS AppId Services [12.3R6.6] jservices-bgf JUNOS Border Gateway Function package [12.3R6.6] jservices-cpcd JUNOS Services Captive Portal and Content Delivery Container package [12.3R6.6] jservices-hcm JUNOS Services HTTP Content Management package [12.3R6.6] jservices-idp JUNOS IDP Services [12.3R6.6] jservices-llpdf JUNOS Services LL-PDF Container package [12.3R6.6] jservices-nat JUNOS Services NAT [12.3R6.6] jservices-ptsp JUNOS Services PTSP Container package [12.3R6.6] jservices-rpm JUNOS Services RPM [12.3R6.6] jservices-sfw JUNOS Services Stateful Firewall [12.3R6.6] jservices-voice JUNOS Voice Services Container package [12.3R6.6] jservices-example JUNOS Services Example Container package [12.3R6.6] jservices-crypto-base JUNOS Services Crypto [12.3R6.6] jservices-ssl JUNOS Services SSL [12.3R6.6] jservices-ipsec JUNOS Services IPSec [12.3R6.6] jruntime JUNOS Runtime Software Suite [12.3R6.6] jplatform JUNOS platform Software Suite [12.3R6.6] jroute JUNOS Routing Software Suite [12.3R6.6] re1 wf-kiwi mx960 mx960 junos JUNOS Base OS boot [12.3R6.6] jbase JUNOS Base OS Software Suite [12.3R6.6] jkernel JUNOS Kernel Software Suite [12.3R6.6] jcrypto JUNOS Crypto Software Suite [12.3R6.6] jpfe-common JUNOS Packet Forwarding Engine Support (M/T/EX Common) [12.3R6.6] jpfe JUNOS Packet Forwarding Engine Support (MX Common) [12.3R6.6] jdocs JUNOS Online Documentation [12.3R6.6] jservices-aacl JUNOS Services AACL Container package [12.3R6.6] jservices-alg JUNOS Services Application Level Gateways [12.3R6.6] jservices-appid JUNOS AppId Services [12.3R6.6] jservices-bgf JUNOS Border Gateway Function package [12.3R6.6] jservices-cpcd JUNOS Services Captive Portal and Content Delivery Container package [12.3R6.6] jservices-hcm JUNOS Services HTTP Content Management package [12.3R6.6] jservices-idp JUNOS IDP Services [12.3R6.6] jservices-llpdf JUNOS Services LL-PDF Container package [12.3R6.6] jservices-nat JUNOS Services NAT [12.3R6.6] jservices-ptsp JUNOS Services PTSP Container package [12.3R6.6] jservices-rpm JUNOS Services RPM [12.3R6.6] jservices-sfw JUNOS Services Stateful Firewall [12.3R6.6] jservices-voice JUNOS Voice Services Container package [12.3R6.6] jservices-example JUNOS Services Example Container package [12.3R6.6] jservices-crypto-base JUNOS Services Crypto [12.3R6.6] jservices-ssl JUNOS Services SSL [12.3R6.6] jservices-ipsec JUNOS Services IPSec [12.3R6.6] jruntime JUNOS Runtime Software Suite [12.3R6.6] jplatform JUNOS platform Software Suite [12.3R6.6] jroute JUNOS Routing Software Suite [12.3R6.6] ]]>]]>ncclient-0.6.15/test/unit/transport/test_parser.py000066400000000000000000000453731451325530500223200ustar00rootroot00000000000000import os import unittest try: from unittest.mock import patch # Python 3.4 and later except ImportError: from mock import patch import paramiko from ncclient import manager from ncclient.transport.ssh import SSHSession from ncclient.operations.third_party.juniper.rpc import * from ncclient.operations import RaiseMode from ncclient.transport.parser import DefaultXMLParser try: import selectors except ImportError: import selectors2 as selectors class TestSession(unittest.TestCase): @unittest.skipIf(sys.version_info.major == 2, "test not supported < Python3") @patch('ncclient.transport.SSHSession.connected') @patch('paramiko.channel.Channel.send_ready') @patch('paramiko.channel.Channel.send') @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.recv') @patch('ncclient.transport.SSHSession') @patch('selectors.DefaultSelector.select') @patch('ncclient.operations.rpc.uuid4') def test_filter_xml_sax_on(self, mock_uuid4, mock_select, mock_session, mock_recv, mock_close, mock_send, mock_send_ready, mock_connected): mock_send.return_value = True mock_send_ready.return_value = -1 mock_uuid4.return_value = type('dummy', (), {'urn': "urn:uuid:e0a7abe3-fffa-11e5-b78e-b8e85604f858"}) device_handler = manager.make_device_handler({'name': 'junos', 'use_filter': True}) rpc = '' mock_recv.side_effect = self._read_file('get-software-information.xml') session = SSHSession(device_handler) session._connected = True session._channel = paramiko.Channel("c100") session.parser = session._device_handler.get_xml_parser(session) obj = ExecuteRpc(session, device_handler, raise_mode=RaiseMode.ALL) obj._filter_xml = '' session.run() resp = obj.request(rpc)._NCElement__doc[0] self.assertEqual(len(resp.xpath('multi-routing-engine-item/re-name')), 2) # as filter_xml is not having software-information, response wont contain it self.assertEqual(len(resp.xpath('multi-routing-engine-item/software-information')), 0) @unittest.skipIf(sys.version_info.major == 2, "test not supported < Python3") @patch('ncclient.transport.SSHSession.connected') @patch('paramiko.channel.Channel.send_ready') @patch('paramiko.channel.Channel.send') @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.recv') @patch('ncclient.transport.SSHSession') @patch('selectors.DefaultSelector.select') @patch('ncclient.operations.rpc.uuid4') def test_filter_xml_sax_on_junos_rfc_compliant(self, mock_uuid4, mock_select, mock_session, mock_recv, mock_close, mock_send, mock_send_ready, mock_connected): mock_send.return_value = True mock_send_ready.return_value = -1 mock_uuid4.return_value = type('dummy', (), {'urn': "urn:uuid:e0a7abe3-fffa-11e5-b78e-b8e85604f858"}) device_handler = manager.make_device_handler({'name': 'junos', 'use_filter': True}) rpc = '' mock_recv.side_effect = self._read_file('get-software-information.xml') session = SSHSession(device_handler) session._connected = True session._channel = paramiko.Channel("c100") session.parser = session._device_handler.get_xml_parser(session) obj = ExecuteRpc(session, device_handler, raise_mode=RaiseMode.ALL) obj._filter_xml = '' session.run() resp = obj.request(rpc)._NCElement__doc[0] from lxml import etree print(resp) self.assertEqual(len(resp.xpath('multi-routing-engine-item/re-name')), 2) # as filter_xml is not having software-information, response wont contain it self.assertEqual(len(resp.xpath('multi-routing-engine-item/software-information')), 0) @unittest.skipIf(sys.version_info.major == 2, "test not supported < Python3") @patch('ncclient.transport.SSHSession.connected') @patch('paramiko.channel.Channel.send_ready') @patch('paramiko.channel.Channel.send') @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.recv') @patch('ncclient.transport.SSHSession') @patch('selectors.DefaultSelector.select') @patch('ncclient.operations.rpc.uuid4') def test_filter_xml_delimiter_rpc_reply(self, mock_uuid4, mock_select, mock_session, mock_recv, mock_close, mock_send, mock_send_ready, mock_connected): mock_send.return_value = True mock_send_ready.return_value = -1 mock_uuid4.return_value = type('dummy', (), {'urn': "urn:uuid:e0a7abe3-fffa-11e5-b78e-b8e85604f858"}) device_handler = manager.make_device_handler({'name': 'junos', 'use_filter': True}) rpc = '' mock_recv.side_effect = self._read_file('get-software-information.xml')[:-1] + [b"]]>", b"]]>"] session = SSHSession(device_handler) session._connected = True session._channel = paramiko.Channel("c100") session.parser = session._device_handler.get_xml_parser(session) obj = ExecuteRpc(session, device_handler, raise_mode=RaiseMode.ALL) obj._filter_xml = '' session.run() resp = obj.request(rpc)._NCElement__doc[0] self.assertEqual(len(resp.xpath('multi-routing-engine-item/re-name')), 2) self.assertEqual(len(resp.xpath('multi-routing-engine-item/software-information')), 0) @unittest.skipIf(sys.version_info.major == 2, "test not supported < Python3") @patch('ncclient.transport.SSHSession.connected') @patch('paramiko.channel.Channel.send_ready') @patch('paramiko.channel.Channel.send') @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.recv') @patch('ncclient.transport.SSHSession') @patch('selectors.DefaultSelector.select') @patch('ncclient.operations.rpc.uuid4') def test_filter_xml_delimiter_multiple_rpc_reply(self, mock_uuid4, mock_select, mock_session, mock_recv, mock_close, mock_send, mock_send_ready, mock_connected): mock_send.return_value = True mock_send_ready.return_value = -1 mock_uuid4.return_value = type('dummy', (), {'urn': "urn:uuid:e0a7abe3-fffa-11e5-b78e-b8e85604f858"}) device_handler = manager.make_device_handler({'name': 'junos', 'use_filter': True}) rpc = '' mock_recv.side_effect = self._read_file('get-software-information.xml')[:-1] + [b"]]>", b"]]>"] + \ self._read_file('get-software-information.xml')[1:] session = SSHSession(device_handler) session._connected = True session._channel = paramiko.Channel("c100") session.parser = session._device_handler.get_xml_parser(session) obj = ExecuteRpc(session, device_handler, raise_mode=RaiseMode.ALL) obj._filter_xml = '' session.run() resp = obj.request(rpc)._NCElement__doc[0] self.assertEqual(len(resp.xpath('multi-routing-engine-item/re-name')), 2) self.assertEqual(len(resp.xpath('multi-routing-engine-item/software-information')), 0) @unittest.skipIf(sys.version_info.major == 2, "test not supported < Python3") @patch('ncclient.transport.SSHSession.connected') @patch('paramiko.channel.Channel.send_ready') @patch('paramiko.channel.Channel.send') @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.recv') @patch('ncclient.transport.SSHSession') @patch('selectors.DefaultSelector.select') @patch('ncclient.operations.rpc.uuid4') def test_filter_xml_delimiter_multiple_rpc_in_parallel(self, mock_uuid4, mock_select, mock_session, mock_recv, mock_close, mock_send, mock_send_ready, mock_connected): mock_send.return_value = True mock_send_ready.return_value = -1 mock_uuid4.side_effect = [type('xyz', (), {'urn': "urn:uuid:ddef40cb-5745-481d-974d-7188f9f2bb33"}), type('pqr', (), {'urn': "urn:uuid:549ef9d1-024a-4fd0-88bf-047d25f0870d"})] device_handler = manager.make_device_handler({'name': 'junos', 'use_filter': True}) rpc = '' mock_recv.side_effect = [b""" 13.1.1.2 ge-0/0/0.1 Exchange 2.2.2.2 128 36 0.0.0.0 0x52 13.1.1.1 13.1.1.2 04:56:52 04:56:52 slave 0x204b6fd 3 0 Link state retransmission list: Type LSA ID Adv rtr Seq Router 1.1.1.1 1.1.1.1 0x80000019 OpaqArea 1.0.0.1 1.1.1.1 0x80000011 Router 3.3.3.3 3.3.3.3 0x80000004 Network 23.1.1.2 3.3.3.3 0x80000001 OpaqArea 1.0.0.1 2.2.2.2 0x80000002 OpaqArea 1.0.0.1 3.3.3.3 0x80000002 OpaqArea 1.0.0.3 1.1.1.1 0x80000002 OpaqArea 1.0.0.3 3.3.3.3 0x80000001 OpaqArea 1.0.0.4 2.2.2.2 0x80000001 default 0 Forward Only ]]>]]> 22450 0 31992 0 0 0 0 0 ]]>]]>"""] session = SSHSession(device_handler) session._connected = True session._channel = paramiko.Channel("c100") session.parser = session._device_handler.get_xml_parser(session) obj = ExecuteRpc(session, device_handler, raise_mode=RaiseMode.ALL) obj = ExecuteRpc(session, device_handler, raise_mode=RaiseMode.ALL) obj._filter_xml = '' session.run() resp = obj.request(rpc)._NCElement__doc[0] self.assertEqual(len(resp.xpath('pfe-traffic-statistics')), 1) @unittest.skipIf(sys.version_info.major == 2, "test not supported < Python3") @patch('ncclient.transport.SSHSession.connected') @patch('paramiko.channel.Channel.send_ready') @patch('paramiko.channel.Channel.send') @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.recv') @patch('ncclient.transport.SSHSession') @patch('selectors.DefaultSelector.select') @patch('ncclient.operations.rpc.uuid4') def test_filter_xml_delimiter_splited_rpc_reply(self, mock_uuid4, mock_select, mock_session, mock_recv, mock_close, mock_send, mock_send_ready, mock_connected): mock_send.return_value = True mock_send_ready.return_value = -1 mock_uuid4.return_value = type('dummy', (), {'urn': "urn:uuid:e0a7abe3-fffa-11e5-b78e-b8e85604f858"}) device_handler = manager.make_device_handler({'name': 'junos', 'use_filter': True}) rpc = '' mock_recv.side_effect = self._read_file('get-software-information.xml')[:-1] + [b"]]>", b"]]>"] + \ self._read_file('get-software-information.xml')[1:] session = SSHSession(device_handler) session._connected = True session._channel = paramiko.Channel("c100") session.parser = session._device_handler.get_xml_parser(session) obj = ExecuteRpc(session, device_handler, raise_mode=RaiseMode.ALL) obj._filter_xml = '' session.run() resp = obj.request(rpc)._NCElement__doc[0] self.assertEqual(len(resp.xpath('multi-routing-engine-item/re-name')), 2) self.assertEqual(len(resp.xpath('multi-routing-engine-item/software-information')), 0) @unittest.skipIf(sys.version_info.major == 2, "test not supported < Python3") @patch('ncclient.transport.SSHSession.connected') @patch('paramiko.channel.Channel.send_ready') @patch('paramiko.channel.Channel.send') @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.recv') @patch('ncclient.transport.SSHSession') @patch('selectors.DefaultSelector.select') @patch('ncclient.operations.rpc.uuid4') def test_use_filter_xml_without_sax_input(self, mock_uuid4, mock_select, mock_session, mock_recv, mock_close, mock_send, mock_send_ready, mock_connected): mock_send.return_value = True mock_send_ready.return_value = -1 mock_uuid4.return_value = type('dummy', (), {'urn': "urn:uuid:e0a7abe3-fffa-11e5-b78e-b8e85604f858"}) device_handler = manager.make_device_handler({'name': 'junos', 'use_filter': True}) rpc = '' mock_recv.side_effect = self._read_file('get-software-information.xml') session = SSHSession(device_handler) session._connected = True session._channel = paramiko.Channel("c100") session.parser = session._device_handler.get_xml_parser(session) obj = ExecuteRpc(session, device_handler, raise_mode=RaiseMode.ALL) obj._filter_xml = None session.run() resp = obj.request(rpc)._NCElement__doc[0] self.assertEqual(len(resp.xpath('multi-routing-engine-item/re-name')), 2) self.assertEqual(len(resp.xpath('multi-routing-engine-item/software-information')), 2) @unittest.skipIf(sys.version_info.major == 2, "test not supported < Python3") @patch('ncclient.transport.SSHSession.connected') @patch('paramiko.channel.Channel.send_ready') @patch('paramiko.channel.Channel.send') @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.recv') @patch('ncclient.transport.SSHSession') @patch('selectors.DefaultSelector.select') @patch('ncclient.operations.rpc.uuid4') def test_use_filter_False(self, mock_uuid4, mock_select, mock_session, mock_recv, mock_close, mock_send, mock_send_ready, mock_connected): mock_send.return_value = True mock_send_ready.return_value = -1 mock_uuid4.return_value = type('dummy', (), {'urn': "urn:uuid:e0a7abe3-fffa-11e5-b78e-b8e85604f858"}) device_handler = manager.make_device_handler({'name': 'junos', 'use_filter': False}) rpc = '' mock_recv.side_effect = self._read_file('get-software-information.xml') session = SSHSession(device_handler) session._connected = True session._channel = paramiko.Channel("c100") session.parser = session._device_handler.get_xml_parser(session) obj = ExecuteRpc(session, device_handler, raise_mode=RaiseMode.ALL) obj._filter_xml = '' session.run() resp = obj.request(rpc)._NCElement__doc[0] self.assertEqual(len(resp.xpath('multi-routing-engine-item/re-name')), 2) # as filter_xml is not having software-information, response wont contain it self.assertEqual(len(resp.xpath('multi-routing-engine-item/software-information')), 2) self.assertIsInstance(session.parser, DefaultXMLParser) def _read_file(self, fname): fpath = os.path.join(os.path.dirname(__file__), 'rpc-reply', fname) lines = [] with open(fpath, "rb") as fp: lines = fp.readlines() return lines ncclient-0.6.15/test/unit/transport/test_session.py000066400000000000000000000245471451325530500225070ustar00rootroot00000000000000import unittest try: from unittest.mock import patch # Python 3.4 and later except ImportError: 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('ncclient.logging_.SessionLoggerAdapter.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.parse_root') @patch('ncclient.devices.junos.JunosDeviceHandler.handle_raw_dispatch') @patch('ncclient.transport.session.HelloHandler.errback') @patch('ncclient.logging_.SessionLoggerAdapter.debug') def test_dispatch_message_error2(self, mock_log, mock_errback, mock_handle_raw_dispatch, mock_parse_root): mock_parse_root.side_effect = Exception mock_handle_raw_dispatch.return_value = Exception() mock_errback.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) mock_handle_raw_dispatch.assert_called_once_with(rpc_reply) self.assertEqual( mock_log.call_args_list[0][0][0].find("error dispatching to"), -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('ncclient.logging_.SessionLoggerAdapter.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_args = mock_log.call_args_list[0][0] self.assertNotEqual(log_args[0].find("initialized"), -1) self.assertNotEqual(log_args[0].find("session-id="), -1) self.assertEqual(log_args[1], 100) self.assertNotEqual( log_args[0].find("server_capabilities="), -1) self.assertEqual(log_args[2], [':candidate']) @patch('ncclient.logging_.SessionLoggerAdapter.info') @patch('ncclient.transport.session.Thread.start') @patch('ncclient.transport.session.Event') def test_post_connect2(self, mock_lock, mock_handler, mock_log): cap = ['urn:ietf:params:netconf:base:1.1'] 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_args = mock_log.call_args_list[0][0] self.assertNotEqual(log_args[0].find("initialized"), -1) self.assertNotEqual(log_args[0].find("session-id="), -1) self.assertEqual(log_args[1], 100) self.assertNotEqual( log_args[0].find("server_capabilities="), -1) self.assertEqual(log_args[2], ['urn:ietf:params:netconf:base:1.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.assertEqual(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.15/test/unit/transport/test_ssh.py000066400000000000000000000417731451325530500216210ustar00rootroot00000000000000# -*- coding: utf-8 -*- import unittest try: from unittest.mock import MagicMock, patch, call # Python 3.4 and later except ImportError: from mock import MagicMock, patch, call from ncclient.transport.ssh import SSHSession from ncclient.transport import AuthenticationError, SessionCloseError, NetconfBase import paramiko from ncclient.devices.junos import JunosDeviceHandler import sys try: import selectors except ImportError: import selectors2 as selectors 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) # einarnn: this test message had to be reduced in size as the improved # 1.1 parsing finds a whole fragment in it, so needed to have less # data in it than the terminating '>' reply_ok_partial_chunk = "\n#%d\n%s" % (len(reply_ok), reply_ok[:-1]) # 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): device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) if sys.version >= "3.0": obj._buffer.write(bytes(rpc_reply11, "utf-8")) remainder = bytes(reply_ok_partial_chunk, "utf-8") else: obj._buffer.write(rpc_reply11) remainder = reply_ok_partial_chunk obj.parser._parse11() expected_messages = [reply_data, reply_ok] 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_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('os.path.expanduser') @patch('paramiko.hostkeys.HostKeys.load') def test_load_host_key_IOError(self, mock_load, mock_os): mock_os.return_value = "file_name" mock_load.side_effect = IOError device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj.load_known_hosts() mock_load.assert_called_with("file_name") @patch('ncclient.transport.ssh.hexlify') @patch('os.path.expanduser') @patch('paramiko.HostKeys') @patch('paramiko.Transport') @patch('ncclient.transport.ssh.SSHSession._post_connect') @patch('ncclient.transport.ssh.SSHSession._auth') def test_ssh_known_hosts(self, mock_auth, mock_pc, mock_transport, mock_hk, mock_os, mock_hex): mock_os.return_value = "file_name" hk_inst = MagicMock(check=MagicMock(return_value=True)) mock_hk.return_value = hk_inst device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj.connect(host='h', sock=MagicMock()) hk_inst.load.assert_called_once_with('file_name') mock_os.assert_called_once_with('~/.ssh/known_hosts') @patch('ncclient.transport.ssh.hexlify') @patch('ncclient.transport.ssh.open') @patch('os.path.expanduser') @patch('paramiko.HostKeys') @patch('paramiko.Transport') @patch('paramiko.SSHConfig') @patch('ncclient.transport.ssh.SSHSession._post_connect') @patch('ncclient.transport.ssh.SSHSession._auth') def test_ssh_known_hosts_2(self, mock_auth, mock_pc, mock_sshc, mock_transport, mock_hk, mock_os, mock_open, mock_hex): mock_os.return_value = "file_name" hk_inst = MagicMock(check=MagicMock(return_value=True)) mock_hk.return_value = hk_inst config = {'userknownhostsfile': 'known_hosts_file'} mock_sshc.return_value = MagicMock(lookup=lambda _h: config) device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj.connect(host='h', sock=MagicMock(), ssh_config=True) hk_inst.load.assert_called_once_with('file_name') mock_os.mock_calls == [call('~/.ssh/config'), call('known_hosts_file')] @unittest.skipIf(sys.version_info.major == 2, "test not supported < Python3") @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.recv') @patch('selectors.DefaultSelector.select') @patch('ncclient.transport.ssh.Session._dispatch_error') def test_run_receive_py3(self, mock_error, mock_selector, mock_recv, mock_close): mock_selector.return_value = True 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)) @unittest.skipIf(sys.version_info.major == 2, "test not supported < Python3") def test_run_send_py3_10(self): self._test_run_send_py3(NetconfBase.BASE_10, lambda msg: msg.encode() + b"]]>]]>") @unittest.skipIf(sys.version_info.major == 2, "test not supported < Python3") def test_run_send_py3_11(self): def chunker(msg): encmsg = msg.encode() chunks = b"\n#%i\n%b\n##\n" % (len(encmsg), encmsg) return chunks self._test_run_send_py3(NetconfBase.BASE_11, chunker) @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.send_ready') @patch('paramiko.channel.Channel.send') @patch('selectors.DefaultSelector.select') @patch('ncclient.transport.ssh.Session._dispatch_error') def _test_run_send_py3(self, base, chunker, mock_error, mock_selector, mock_send, mock_ready, mock_close): mock_selector.return_value = False mock_ready.return_value = True mock_send.return_value = -1 device_handler = JunosDeviceHandler({'name': 'junos'}) obj = SSHSession(device_handler) obj._channel = paramiko.Channel("c100") msg = "naïve garçon" obj._q.put(msg) obj._base = base obj.run() self.assertEqual(mock_send.call_args_list[0][0][0], chunker(msg)) self.assertTrue( isinstance( mock_error.call_args_list[0][0][0], SessionCloseError)) @unittest.skipIf(sys.version_info.major >= 3, "test not supported >= Python3") @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.recv') @patch('selectors2.DefaultSelector') @patch('ncclient.transport.ssh.Session._dispatch_error') def test_run_receive_py2(self, mock_error, mock_selector, mock_recv, mock_close): mock_selector.select.return_value = True 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)) @unittest.skip("test currently non-functional") @patch('ncclient.transport.ssh.SSHSession.close') @patch('paramiko.channel.Channel.send_ready') @patch('paramiko.channel.Channel.send') @patch('selectors2.DefaultSelector') @patch('ncclient.transport.ssh.Session._dispatch_error') def test_run_send_py2(self, mock_error, mock_selector, mock_send, mock_ready, mock_close): mock_selector.select.return_value = False 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)) ncclient-0.6.15/test/unit/transport/test_tls.py000066400000000000000000000114551451325530500216200ustar00rootroot00000000000000# Copyright (c) Siemens AG, 2022 # # 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 ssl import sys import unittest import socket try: from unittest.mock import MagicMock, patch, call except ImportError: from mock import MagicMock, patch, call try: from ssl import PROTOCOL_TLS_CLIENT tls_proto = PROTOCOL_TLS_CLIENT except ImportError: from ssl import PROTOCOL_TLS tls_proto = PROTOCOL_TLS from ncclient.transport.errors import TLSError from ncclient.transport.tls import TLSSession, DEFAULT_TLS_NETCONF_PORT HOST = '10.10.10.10' PORT = DEFAULT_TLS_NETCONF_PORT KEYFILE = 'test/unit/transport/certs/test.key' CERTFILE_WO_KEY = 'test/unit/transport/certs/test.crt' CERTFILE_WITH_KEY = 'test/unit/transport/certs/test.pem' class TestTLS(unittest.TestCase): @patch('ssl.SSLContext.wrap_socket') @patch('ncclient.transport.Session._post_connect') def test_connect_tls_certfile_with_key(self, mock_post_connect, mock_wrap_socket_fn): session = TLSSession(MagicMock()) mock_wrap_socket = MagicMock() mock_wrap_socket_fn.return_value = mock_wrap_socket session.connect(host=HOST, certfile=CERTFILE_WITH_KEY, protocol=tls_proto) mock_wrap_socket.connect.assert_called_once_with( (HOST, DEFAULT_TLS_NETCONF_PORT)) self.assertTrue(session.connected) @patch('ssl.SSLContext.wrap_socket') @patch('ncclient.transport.Session._post_connect') def test_connect_tls_certfile_without_key(self, mock_post_connect, mock_wrap_socket_fn): session = TLSSession(MagicMock()) mock_wrap_socket = MagicMock() mock_wrap_socket_fn.return_value = mock_wrap_socket session.connect(host=HOST, certfile=CERTFILE_WO_KEY, keyfile=KEYFILE, protocol=tls_proto) mock_wrap_socket.connect.assert_called_once_with( (HOST, DEFAULT_TLS_NETCONF_PORT)) self.assertTrue(session.connected) @unittest.skipIf(sys.version_info < (3, 6), "test not supported < Python3.6") @patch('ssl.SSLContext.wrap_socket') @patch('ncclient.transport.Session._post_connect') def test_tls_handshake(self, mock_post_connect, mock_wrap_socket_fn): session = TLSSession(MagicMock()) mock_wrap_socket = MagicMock() mock_wrap_socket_fn.return_value = mock_wrap_socket session.connect(host=HOST, certfile=CERTFILE_WITH_KEY, protocol=tls_proto) mock_wrap_socket.do_handshake.assert_called_once() @patch('ssl.SSLContext.wrap_socket') def test_tls_unsuccessul_handshake(self, mock_wrap_socket_fn): session = TLSSession(MagicMock()) mock_wrap_socket = MagicMock() mock_wrap_socket.do_handshake = MagicMock(side_effect=ssl.SSLError) mock_wrap_socket_fn.return_value = mock_wrap_socket with self.assertRaises(TLSError): session.connect(host=HOST, certfile=CERTFILE_WITH_KEY, protocol=tls_proto) def test_connect_tls_missing_hostname(self): session = TLSSession(MagicMock()) with self.assertRaises(TLSError): session.connect(host=None, certfile=CERTFILE_WITH_KEY, protocol=tls_proto) def test_connect_tls_missing_certfile(self): session = TLSSession(MagicMock()) with self.assertRaises(TLSError): session.connect(host=HOST, certfile=None, protocol=tls_proto) def test_connect_tls_missing_private_key(self): session = TLSSession(MagicMock()) with self.assertRaises(TLSError): session.connect(host=HOST, certfile=CERTFILE_WO_KEY, protocol=tls_proto) def test_connect_tls_missing_ca_file(self): session = TLSSession(MagicMock()) with self.assertRaises(TLSError): # Use arbitrary string to point to non-existing key location. session.connect(host=HOST, certfile=CERTFILE_WITH_KEY, ca_certs='-', protocol=tls_proto) @patch('socket.socket.close') def test_close_tls(self, mock_sock_close_fn): session = TLSSession(MagicMock()) session._socket = socket.socket() session._connected = True session.close() mock_sock_close_fn.assert_called_once_with() self.assertFalse(session._connected) ncclient-0.6.15/tox.ini000066400000000000000000000011671451325530500147250ustar00rootroot00000000000000[tox] minversion = 1.6 skipdist = True envlist = py27,py36,py37,py38,py39,py310,pep8,cover,docs [testenv] setenv = VIRTUAL_ENV={envdir} usedevelop = True install_command = pip install {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = pytest {posargs} [testenv:cover] commands = pytest --cov=ncclient [testenv:docs] deps = -r{toxinidir}/test-requirements.txt commands = sphinx-build -b html docs/source docs/html [testenv:pep8] commands = flake8 {posargs} ncclient test [flake8] show-source = True ignore = E713 exclude = .venv,.git,.tox,dist,doc,.ropeproject ncclient-0.6.15/versioneer.py000066400000000000000000002432271451325530500161520ustar00rootroot00000000000000 # Version: 0.28 """The Versioneer - like a rocketeer, but for versions. The Versioneer ============== * like a rocketeer, but for versions! * https://github.com/python-versioneer/python-versioneer * Brian Warner * License: Public Domain (Unlicense) * Compatible with: Python 3.7, 3.8, 3.9, 3.10 and pypy3 * [![Latest Version][pypi-image]][pypi-url] * [![Build Status][travis-image]][travis-url] This is a tool for managing a recorded version number in setuptools-based python projects. The goal is to remove the tedious and error-prone "update the embedded version string" step from your release process. Making a new release should be as easy as recording a new tag in your version-control system, and maybe making new tarballs. ## Quick Install Versioneer provides two installation modes. The "classic" vendored mode installs a copy of versioneer into your repository. The experimental build-time dependency mode is intended to allow you to skip this step and simplify the process of upgrading. ### Vendored mode * `pip install versioneer` to somewhere in your $PATH * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is available, so you can also use `conda install -c conda-forge versioneer` * add a `[tool.versioneer]` section to your `pyproject.toml` or a `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md)) * Note that you will need to add `tomli; python_version < "3.11"` to your build-time dependencies if you use `pyproject.toml` * run `versioneer install --vendor` in your source tree, commit the results * verify version information with `python setup.py version` ### Build-time dependency mode * `pip install versioneer` to somewhere in your $PATH * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is available, so you can also use `conda install -c conda-forge versioneer` * add a `[tool.versioneer]` section to your `pyproject.toml` or a `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md)) * add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`) to the `requires` key of the `build-system` table in `pyproject.toml`: ```toml [build-system] requires = ["setuptools", "versioneer[toml]"] build-backend = "setuptools.build_meta" ``` * run `versioneer install --no-vendor` in your source tree, commit the results * verify version information with `python setup.py version` ## Version Identifiers Source trees come from a variety of places: * a version-control system checkout (mostly used by developers) * a nightly tarball, produced by build automation * a snapshot tarball, produced by a web-based VCS browser, like github's "tarball from tag" feature * a release tarball, produced by "setup.py sdist", distributed through PyPI Within each source tree, the version identifier (either a string or a number, this tool is format-agnostic) can come from a variety of places: * ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows about recent "tags" and an absolute revision-id * the name of the directory into which the tarball was unpacked * an expanded VCS keyword ($Id$, etc) * a `_version.py` created by some earlier build step For released software, the version identifier is closely related to a VCS tag. Some projects use tag names that include more than just the version string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool needs to strip the tag prefix to extract the version identifier. For unreleased software (between tags), the version identifier should provide enough information to help developers recreate the same tree, while also giving them an idea of roughly how old the tree is (after version 1.2, before version 1.3). Many VCS systems can report a description that captures this, for example `git describe --tags --dirty --always` reports things like "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has uncommitted changes). The version identifier is used for multiple purposes: * to allow the module to self-identify its version: `myproject.__version__` * to choose a name and prefix for a 'setup.py sdist' tarball ## Theory of Operation Versioneer works by adding a special `_version.py` file into your source tree, where your `__init__.py` can import it. This `_version.py` knows how to dynamically ask the VCS tool for version information at import time. `_version.py` also contains `$Revision$` markers, and the installation process marks `_version.py` to have this marker rewritten with a tag name during the `git archive` command. As a result, generated tarballs will contain enough information to get the proper version. To allow `setup.py` to compute a version too, a `versioneer.py` is added to the top level of your source tree, next to `setup.py` and the `setup.cfg` that configures it. This overrides several distutils/setuptools commands to compute the version when invoked, and changes `setup.py build` and `setup.py sdist` to replace `_version.py` with a small static file that contains just the generated version data. ## Installation See [INSTALL.md](./INSTALL.md) for detailed installation instructions. ## Version-String Flavors Code which uses Versioneer can learn about its version string at runtime by importing `_version` from your main `__init__.py` file and running the `get_versions()` function. From the "outside" (e.g. in `setup.py`), you can import the top-level `versioneer.py` and run `get_versions()`. Both functions return a dictionary with different flavors of version information: * `['version']`: A condensed version string, rendered using the selected style. This is the most commonly used value for the project's version string. The default "pep440" style yields strings like `0.11`, `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section below for alternative styles. * `['full-revisionid']`: detailed revision identifier. For Git, this is the full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". * `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the commit date in ISO 8601 format. This will be None if the date is not available. * `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that this is only accurate if run in a VCS checkout, otherwise it is likely to be False or None * `['error']`: if the version string could not be computed, this will be set to a string describing the problem, otherwise it will be None. It may be useful to throw an exception in setup.py if this is set, to avoid e.g. creating tarballs with a version string of "unknown". Some variants are more useful than others. Including `full-revisionid` in a bug report should allow developers to reconstruct the exact code being tested (or indicate the presence of local changes that should be shared with the developers). `version` is suitable for display in an "about" box or a CLI `--version` output: it can be easily compared against release notes and lists of bugs fixed in various releases. The installer adds the following text to your `__init__.py` to place a basic version in `YOURPROJECT.__version__`: from ._version import get_versions __version__ = get_versions()['version'] del get_versions ## Styles The setup.cfg `style=` configuration controls how the VCS information is rendered into a version string. The default style, "pep440", produces a PEP440-compliant string, equal to the un-prefixed tag name for actual releases, and containing an additional "local version" section with more detail for in-between builds. For Git, this is TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags --dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and that this commit is two revisions ("+2") beyond the "0.11" tag. For released software (exactly equal to a known tag), the identifier will only contain the stripped tag, e.g. "0.11". Other styles are available. See [details.md](details.md) in the Versioneer source tree for descriptions. ## Debugging Versioneer tries to avoid fatal errors: if something goes wrong, it will tend to return a version of "0+unknown". To investigate the problem, run `setup.py version`, which will run the version-lookup code in a verbose mode, and will display the full contents of `get_versions()` (including the `error` string, which may help identify what went wrong). ## Known Limitations Some situations are known to cause problems for Versioneer. This details the most significant ones. More can be found on Github [issues page](https://github.com/python-versioneer/python-versioneer/issues). ### Subprojects Versioneer has limited support for source trees in which `setup.py` is not in the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are two common reasons why `setup.py` might not be in the root: * Source trees which contain multiple subprojects, such as [Buildbot](https://github.com/buildbot/buildbot), which contains both "master" and "slave" subprojects, each with their own `setup.py`, `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI distributions (and upload multiple independently-installable tarballs). * Source trees whose main purpose is to contain a C library, but which also provide bindings to Python (and perhaps other languages) in subdirectories. Versioneer will look for `.git` in parent directories, and most operations should get the right version string. However `pip` and `setuptools` have bugs and implementation details which frequently cause `pip install .` from a subproject directory to fail to find a correct version string (so it usually defaults to `0+unknown`). `pip install --editable .` should work correctly. `setup.py install` might work too. Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in some later version. [Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking this issue. The discussion in [PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the issue from the Versioneer side in more detail. [pip PR#3176](https://github.com/pypa/pip/pull/3176) and [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve pip to let Versioneer work correctly. Versioneer-0.16 and earlier only looked for a `.git` directory next to the `setup.cfg`, so subprojects were completely unsupported with those releases. ### Editable installs with setuptools <= 18.5 `setup.py develop` and `pip install --editable .` allow you to install a project into a virtualenv once, then continue editing the source code (and test) without re-installing after every change. "Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a convenient way to specify executable scripts that should be installed along with the python package. These both work as expected when using modern setuptools. When using setuptools-18.5 or earlier, however, certain operations will cause `pkg_resources.DistributionNotFound` errors when running the entrypoint script, which must be resolved by re-installing the package. This happens when the install happens with one version, then the egg_info data is regenerated while a different version is checked out. Many setup.py commands cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into a different virtualenv), so this can be surprising. [Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes this one, but upgrading to a newer version of setuptools should probably resolve it. ## Updating Versioneer To upgrade your project to a new release of Versioneer, do the following: * install the new Versioneer (`pip install -U versioneer` or equivalent) * edit `setup.cfg` and `pyproject.toml`, if necessary, to include any new configuration settings indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. * re-run `versioneer install --[no-]vendor` in your source tree, to replace `SRC/_version.py` * commit any changed files ## Future Directions This tool is designed to make it easily extended to other version-control systems: all VCS-specific components are in separate directories like src/git/ . The top-level `versioneer.py` script is assembled from these components by running make-versioneer.py . In the future, make-versioneer.py will take a VCS name as an argument, and will construct a version of `versioneer.py` that is specific to the given VCS. It might also take the configuration arguments that are currently provided manually during installation by editing setup.py . Alternatively, it might go the other direction and include code from all supported VCS systems, reducing the number of intermediate scripts. ## Similar projects * [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time dependency * [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of versioneer * [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools plugin ## License To make Versioneer easier to embed, all its code is dedicated to the public domain. The `_version.py` that it creates is also in the public domain. Specifically, both are released under the "Unlicense", as described in https://unlicense.org/. [pypi-image]: https://img.shields.io/pypi/v/versioneer.svg [pypi-url]: https://pypi.python.org/pypi/versioneer/ [travis-image]: https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg [travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer """ # pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring # pylint:disable=missing-class-docstring,too-many-branches,too-many-statements # pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error # pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with # pylint:disable=attribute-defined-outside-init,too-many-arguments import configparser import errno import json import os import re import subprocess import sys from pathlib import Path from typing import Callable, Dict import functools have_tomllib = True if sys.version_info >= (3, 11): import tomllib else: try: import tomli as tomllib except ImportError: have_tomllib = False class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_root(): """Get the project root directory. We require that all commands are run from the project root, i.e. the directory that contains setup.py, setup.cfg, and versioneer.py . """ root = os.path.realpath(os.path.abspath(os.getcwd())) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): # allow 'python path/to/setup.py COMMAND' root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): err = ("Versioneer was unable to run the project root directory. " "Versioneer requires setup.py to be executed from " "its immediate directory (like 'python setup.py COMMAND'), " "or in a way that lets it use sys.argv[0] to find the root " "(like 'python path/to/setup.py COMMAND').") raise VersioneerBadRootError(err) try: # Certain runtime workflows (setup.py install/develop in a setuptools # tree) execute all dependencies in a single python process, so # "versioneer" may be imported multiple times, and python's shared # module-import table will cache the first one. So we can't use # os.path.dirname(__file__), as that will find whichever # versioneer.py was first imported, even in later projects. my_path = os.path.realpath(os.path.abspath(__file__)) me_dir = os.path.normcase(os.path.splitext(my_path)[0]) vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals(): print("Warning: build in %s is using versioneer.py from %s" % (os.path.dirname(my_path), versioneer_py)) except NameError: pass return root def get_config_from_root(root): """Read the project setup.cfg file to determine Versioneer config.""" # This might raise OSError (if setup.cfg is missing), or # configparser.NoSectionError (if it lacks a [versioneer] section), or # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . root = Path(root) pyproject_toml = root / "pyproject.toml" setup_cfg = root / "setup.cfg" section = None if pyproject_toml.exists() and have_tomllib: try: with open(pyproject_toml, 'rb') as fobj: pp = tomllib.load(fobj) section = pp['tool']['versioneer'] except (tomllib.TOMLDecodeError, KeyError): pass if not section: parser = configparser.ConfigParser() with open(setup_cfg) as cfg_file: parser.read_file(cfg_file) parser.get("versioneer", "VCS") # raise error if missing section = parser["versioneer"] cfg = VersioneerConfig() cfg.VCS = section['VCS'] cfg.style = section.get("style", "") cfg.versionfile_source = section.get("versionfile_source") cfg.versionfile_build = section.get("versionfile_build") cfg.tag_prefix = section.get("tag_prefix") if cfg.tag_prefix in ("''", '""', None): cfg.tag_prefix = "" cfg.parentdir_prefix = section.get("parentdir_prefix") cfg.verbose = section.get("verbose") return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" # these dictionaries contain VCS-specific tools LONG_VERSION_PY: Dict[str, str] = {} HANDLERS: Dict[str, Dict[str, Callable]] = {} def register_vcs_handler(vcs, method): # decorator """Create decorator to mark a method as the handler of a VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" HANDLERS.setdefault(vcs, {})[method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) process = None popen_kwargs = {} if sys.platform == "win32": # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW popen_kwargs["startupinfo"] = startupinfo for command in commands: try: dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git process = subprocess.Popen([command] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None), **popen_kwargs) break except OSError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None, None stdout = process.communicate()[0].strip().decode() if process.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) return None, process.returncode return stdout, process.returncode LONG_VERSION_PY['git'] = r''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. # Generated by versioneer-0.28 # https://github.com/python-versioneer/python-versioneer """Git implementation of _version.py.""" import errno import os import re import subprocess import sys from typing import Callable, Dict import functools def get_keywords(): """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_config(): """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "%(STYLE)s" cfg.tag_prefix = "%(TAG_PREFIX)s" cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY: Dict[str, str] = {} HANDLERS: Dict[str, Dict[str, Callable]] = {} def register_vcs_handler(vcs, method): # decorator """Create decorator to mark a method as the handler of a VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) process = None popen_kwargs = {} if sys.platform == "win32": # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW popen_kwargs["startupinfo"] = startupinfo for command in commands: try: dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git process = subprocess.Popen([command] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None), **popen_kwargs) break except OSError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %%s" %% dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %%s" %% (commands,)) return None, None stdout = process.communicate()[0].strip().decode() if process.returncode != 0: if verbose: print("unable to run %%s (error)" %% dispcmd) print("stdout was %%s" %% stdout) return None, process.returncode return stdout, process.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %%s but none started with prefix %%s" %% (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: with open(versionfile_abs, "r") as fobj: for line in fobj: if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) except OSError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if "refnames" not in keywords: raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %%d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%%s', no digits" %% ",".join(refs - tags)) if verbose: print("likely tags: %%s" %% ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] # Filter out refs that exactly match prefix or that don't start # with a number once the prefix is stripped (mostly a concern # when prefix is '') if not re.match(r'\d', r): continue if verbose: print("picking %%s" %% r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] # GIT_DIR can interfere with correct operation of Versioneer. # It may be intended to be passed to the Versioneer-versioned project, # but that should not change where we get our version from. env = os.environ.copy() env.pop("GIT_DIR", None) runner = functools.partial(runner, env=env) _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %%s not under git control" %% root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = runner(GITS, [ "describe", "--tags", "--dirty", "--always", "--long", "--match", f"{tag_prefix}[[:digit:]]*" ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root) # --abbrev-ref was added in git-1.6.3 if rc != 0 or branch_name is None: raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") branch_name = branch_name.strip() if branch_name == "HEAD": # If we aren't exactly on a branch, pick a branch which represents # the current commit. If all else fails, we are on a branchless # commit. branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) # --contains was added in git-1.5.4 if rc != 0 or branches is None: raise NotThisMethod("'git branch --contains' returned error") branches = branches.split("\n") # Remove the first line if we're running detached if "(" in branches[0]: branches.pop(0) # Strip off the leading "* " from the list of branches. branches = [branch[2:] for branch in branches] if "master" in branches: branch_name = "master" elif not branches: branch_name = None else: # Pick the first branch that is returned. Good or bad. branch_name = branches[0] pieces["branch"] = branch_name # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%%s'" %% describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%%s' doesn't start with prefix '%%s'" print(fmt %% (full_tag, tag_prefix)) pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" %% (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip() # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_branch(pieces): """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . The ".dev0" means not master branch. Note that .dev0 sorts backwards (a feature branch will appear "older" than the master branch). Exceptions: 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0" if pieces["branch"] != "master": rendered += ".dev0" rendered += "+untagged.%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def pep440_split_post(ver): """Split pep440 version string at the post-release segment. Returns the release segments before the post-release and the post-release version number (or -1 if no post-release segment is present). """ vc = str.split(ver, ".post") return vc[0], int(vc[1] or 0) if len(vc) == 2 else None def render_pep440_pre(pieces): """TAG[.postN.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post0.devDISTANCE """ if pieces["closest-tag"]: if pieces["distance"]: # update the post release segment tag_version, post_version = pep440_split_post(pieces["closest-tag"]) rendered = tag_version if post_version is not None: rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"]) else: rendered += ".post0.dev%%d" %% (pieces["distance"]) else: # no commits, use the tag as the version rendered = pieces["closest-tag"] else: # exception #1 rendered = "0.post0.dev%%d" %% pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%%s" %% pieces["short"] else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%%s" %% pieces["short"] return rendered def render_pep440_post_branch(pieces): """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . The ".dev0" means not master branch. Exceptions: 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%%s" %% pieces["short"] if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += "+g%%s" %% pieces["short"] if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-branch": rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-post-branch": rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%%s'" %% style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} def get_versions(): """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. for _ in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to find root of source tree", "date": None} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} ''' @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: with open(versionfile_abs, "r") as fobj: for line in fobj: if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) except OSError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if "refnames" not in keywords: raise NotThisMethod("Short version file found") date = keywords.get("date") if date is not None: # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] # Filter out refs that exactly match prefix or that don't start # with a number once the prefix is stripped (mostly a concern # when prefix is '') if not re.match(r'\d', r): continue if verbose: print("picking %s" % r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] # GIT_DIR can interfere with correct operation of Versioneer. # It may be intended to be passed to the Versioneer-versioned project, # but that should not change where we get our version from. env = os.environ.copy() env.pop("GIT_DIR", None) runner = functools.partial(runner, env=env) _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %s not under git control" % root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = runner(GITS, [ "describe", "--tags", "--dirty", "--always", "--long", "--match", f"{tag_prefix}[[:digit:]]*" ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root) # --abbrev-ref was added in git-1.6.3 if rc != 0 or branch_name is None: raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") branch_name = branch_name.strip() if branch_name == "HEAD": # If we aren't exactly on a branch, pick a branch which represents # the current commit. If all else fails, we are on a branchless # commit. branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) # --contains was added in git-1.5.4 if rc != 0 or branches is None: raise NotThisMethod("'git branch --contains' returned error") branches = branches.split("\n") # Remove the first line if we're running detached if "(" in branches[0]: branches.pop(0) # Strip off the leading "* " from the list of branches. branches = [branch[2:] for branch in branches] if "master" in branches: branch_name = "master" elif not branches: branch_name = None else: # Pick the first branch that is returned. Good or bad. branch_name = branches[0] pieces["branch"] = branch_name # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparsable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def do_vcs_install(versionfile_source, ipy): """Git-specific installation logic for Versioneer. For Git, this means creating/changing .gitattributes to mark _version.py for export-subst keyword substitution. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] files = [versionfile_source] if ipy: files.append(ipy) if "VERSIONEER_PEP518" not in globals(): try: my_path = __file__ if my_path.endswith((".pyc", ".pyo")): my_path = os.path.splitext(my_path)[0] + ".py" versioneer_file = os.path.relpath(my_path) except NameError: versioneer_file = "versioneer.py" files.append(versioneer_file) present = False try: with open(".gitattributes", "r") as fobj: for line in fobj: if line.strip().startswith(versionfile_source): if "export-subst" in line.strip().split()[1:]: present = True break except OSError: pass if not present: with open(".gitattributes", "a+") as fobj: fobj.write(f"{versionfile_source} export-subst\n") files.append(".gitattributes") run_command(GITS, ["add", "--"] + files) def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") SHORT_VERSION_PY = """ # This file was generated by 'versioneer.py' (0.28) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. import json version_json = ''' %s ''' # END VERSION_JSON def get_versions(): return json.loads(version_json) """ def versions_from_file(filename): """Try to determine the version from _version.py if present.""" try: with open(filename) as f: contents = f.read() except OSError: raise NotThisMethod("unable to read _version.py") mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: raise NotThisMethod("no version_json in _version.py") return json.loads(mo.group(1)) def write_to_version_file(filename, versions): """Write the given version number to the given _version.py file.""" os.unlink(filename) contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) with open(filename, "w") as f: f.write(SHORT_VERSION_PY % contents) print("set %s to '%s'" % (filename, versions["version"])) def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_branch(pieces): """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . The ".dev0" means not master branch. Note that .dev0 sorts backwards (a feature branch will appear "older" than the master branch). Exceptions: 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0" if pieces["branch"] != "master": rendered += ".dev0" rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def pep440_split_post(ver): """Split pep440 version string at the post-release segment. Returns the release segments before the post-release and the post-release version number (or -1 if no post-release segment is present). """ vc = str.split(ver, ".post") return vc[0], int(vc[1] or 0) if len(vc) == 2 else None def render_pep440_pre(pieces): """TAG[.postN.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post0.devDISTANCE """ if pieces["closest-tag"]: if pieces["distance"]: # update the post release segment tag_version, post_version = pep440_split_post(pieces["closest-tag"]) rendered = tag_version if post_version is not None: rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) else: rendered += ".post0.dev%d" % (pieces["distance"]) else: # no commits, use the tag as the version rendered = pieces["closest-tag"] else: # exception #1 rendered = "0.post0.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_post_branch(pieces): """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . The ".dev0" means not master branch. Exceptions: 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" rendered += "+g%s" % pieces["short"] if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-branch": rendered = render_pep440_branch(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-post-branch": rendered = render_pep440_post_branch(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} class VersioneerBadRootError(Exception): """The project root directory is unknown or missing key files.""" def get_versions(verbose=False): """Get the project version from whatever source is available. Returns dict with two keys: 'version' and 'full'. """ if "versioneer" in sys.modules: # see the discussion in cmdclass.py:get_cmdclass() del sys.modules["versioneer"] root = get_root() cfg = get_config_from_root(root) assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" handlers = HANDLERS.get(cfg.VCS) assert handlers, "unrecognized VCS '%s'" % cfg.VCS verbose = verbose or cfg.verbose assert cfg.versionfile_source is not None, \ "please set versioneer.versionfile_source" assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" versionfile_abs = os.path.join(root, cfg.versionfile_source) # extract version from first of: _version.py, VCS command (e.g. 'git # describe'), parentdir. This is meant to work for developers using a # source checkout, for users of a tarball created by 'setup.py sdist', # and for users of a tarball/zipball created by 'git archive' or github's # download-from-tag feature or the equivalent in other VCSes. get_keywords_f = handlers.get("get_keywords") from_keywords_f = handlers.get("keywords") if get_keywords_f and from_keywords_f: try: keywords = get_keywords_f(versionfile_abs) ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) if verbose: print("got version from expanded keyword %s" % ver) return ver except NotThisMethod: pass try: ver = versions_from_file(versionfile_abs) if verbose: print("got version from file %s %s" % (versionfile_abs, ver)) return ver except NotThisMethod: pass from_vcs_f = handlers.get("pieces_from_vcs") if from_vcs_f: try: pieces = from_vcs_f(cfg.tag_prefix, root, verbose) ver = render(pieces, cfg.style) if verbose: print("got version from VCS %s" % ver) return ver except NotThisMethod: pass try: if cfg.parentdir_prefix: ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) if verbose: print("got version from parentdir %s" % ver) return ver except NotThisMethod: pass if verbose: print("unable to compute version") return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} def get_version(): """Get the short version string for this project.""" return get_versions()["version"] def get_cmdclass(cmdclass=None): """Get the custom setuptools subclasses used by Versioneer. If the package uses a different cmdclass (e.g. one from numpy), it should be provide as an argument. """ if "versioneer" in sys.modules: del sys.modules["versioneer"] # this fixes the "python setup.py develop" case (also 'install' and # 'easy_install .'), in which subdependencies of the main project are # built (using setup.py bdist_egg) in the same python process. Assume # a main project A and a dependency B, which use different versions # of Versioneer. A's setup.py imports A's Versioneer, leaving it in # sys.modules by the time B's setup.py is executed, causing B to run # with the wrong versioneer. Setuptools wraps the sub-dep builds in a # sandbox that restores sys.modules to it's pre-build state, so the # parent is protected against the child's "import versioneer". By # removing ourselves from sys.modules here, before the child build # happens, we protect the child from the parent's versioneer too. # Also see https://github.com/python-versioneer/python-versioneer/issues/52 cmds = {} if cmdclass is None else cmdclass.copy() # we add "version" to setuptools from setuptools import Command class cmd_version(Command): description = "report generated version string" user_options = [] boolean_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): vers = get_versions(verbose=True) print("Version: %s" % vers["version"]) print(" full-revisionid: %s" % vers.get("full-revisionid")) print(" dirty: %s" % vers.get("dirty")) print(" date: %s" % vers.get("date")) if vers["error"]: print(" error: %s" % vers["error"]) cmds["version"] = cmd_version # we override "build_py" in setuptools # # most invocation pathways end up running build_py: # distutils/build -> build_py # distutils/install -> distutils/build ->.. # setuptools/bdist_wheel -> distutils/install ->.. # setuptools/bdist_egg -> distutils/install_lib -> build_py # setuptools/install -> bdist_egg ->.. # setuptools/develop -> ? # pip install: # copies source tree to a tempdir before running egg_info/etc # if .git isn't copied too, 'git describe' will fail # then does setup.py bdist_wheel, or sometimes setup.py install # setup.py egg_info -> ? # pip install -e . and setuptool/editable_wheel will invoke build_py # but the build_py command is not expected to copy any files. # we override different "build_py" commands for both environments if 'build_py' in cmds: _build_py = cmds['build_py'] else: from setuptools.command.build_py import build_py as _build_py class cmd_build_py(_build_py): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() _build_py.run(self) if getattr(self, "editable_mode", False): # During editable installs `.py` and data files are # not copied to build_lib return # now locate _version.py in the new build/ directory and replace # it with an updated value if cfg.versionfile_build: target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) cmds["build_py"] = cmd_build_py if 'build_ext' in cmds: _build_ext = cmds['build_ext'] else: from setuptools.command.build_ext import build_ext as _build_ext class cmd_build_ext(_build_ext): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() _build_ext.run(self) if self.inplace: # build_ext --inplace will only build extensions in # build/lib<..> dir with no _version.py to write to. # As in place builds will already have a _version.py # in the module dir, we do not need to write one. return # now locate _version.py in the new build/ directory and replace # it with an updated value if not cfg.versionfile_build: return target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) if not os.path.exists(target_versionfile): print(f"Warning: {target_versionfile} does not exist, skipping " "version update. This can happen if you are running build_ext " "without first running build_py.") return print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) cmds["build_ext"] = cmd_build_ext if "cx_Freeze" in sys.modules: # cx_freeze enabled? from cx_Freeze.dist import build_exe as _build_exe # nczeczulin reports that py2exe won't like the pep440-style string # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. # setup(console=[{ # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION # "product_version": versioneer.get_version(), # ... class cmd_build_exe(_build_exe): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) _build_exe.run(self) os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) cmds["build_exe"] = cmd_build_exe del cmds["build_py"] if 'py2exe' in sys.modules: # py2exe enabled? try: from py2exe.setuptools_buildexe import py2exe as _py2exe except ImportError: from py2exe.distutils_buildexe import py2exe as _py2exe class cmd_py2exe(_py2exe): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) _py2exe.run(self) os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) cmds["py2exe"] = cmd_py2exe # sdist farms its file list building out to egg_info if 'egg_info' in cmds: _egg_info = cmds['egg_info'] else: from setuptools.command.egg_info import egg_info as _egg_info class cmd_egg_info(_egg_info): def find_sources(self): # egg_info.find_sources builds the manifest list and writes it # in one shot super().find_sources() # Modify the filelist and normalize it root = get_root() cfg = get_config_from_root(root) self.filelist.append('versioneer.py') if cfg.versionfile_source: # There are rare cases where versionfile_source might not be # included by default, so we must be explicit self.filelist.append(cfg.versionfile_source) self.filelist.sort() self.filelist.remove_duplicates() # The write method is hidden in the manifest_maker instance that # generated the filelist and was thrown away # We will instead replicate their final normalization (to unicode, # and POSIX-style paths) from setuptools import unicode_utils normalized = [unicode_utils.filesys_decode(f).replace(os.sep, '/') for f in self.filelist.files] manifest_filename = os.path.join(self.egg_info, 'SOURCES.txt') with open(manifest_filename, 'w') as fobj: fobj.write('\n'.join(normalized)) cmds['egg_info'] = cmd_egg_info # we override different "sdist" commands for both environments if 'sdist' in cmds: _sdist = cmds['sdist'] else: from setuptools.command.sdist import sdist as _sdist class cmd_sdist(_sdist): def run(self): versions = get_versions() self._versioneer_generated_versions = versions # unless we update this, the command will keep using the old # version self.distribution.metadata.version = versions["version"] return _sdist.run(self) def make_release_tree(self, base_dir, files): root = get_root() cfg = get_config_from_root(root) _sdist.make_release_tree(self, base_dir, files) # now locate _version.py in the new base_dir directory # (remembering that it may be a hardlink) and replace it with an # updated value target_versionfile = os.path.join(base_dir, cfg.versionfile_source) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, self._versioneer_generated_versions) cmds["sdist"] = cmd_sdist return cmds CONFIG_ERROR = """ setup.cfg is missing the necessary Versioneer configuration. You need a section like: [versioneer] VCS = git style = pep440 versionfile_source = src/myproject/_version.py versionfile_build = myproject/_version.py tag_prefix = parentdir_prefix = myproject- You will also need to edit your setup.py to use the results: import versioneer setup(version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), ...) Please read the docstring in ./versioneer.py for configuration instructions, edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. """ SAMPLE_CONFIG = """ # See the docstring in versioneer.py for instructions. Note that you must # re-run 'versioneer.py setup' after changing this section, and commit the # resulting files. [versioneer] #VCS = git #style = pep440 #versionfile_source = #versionfile_build = #tag_prefix = #parentdir_prefix = """ OLD_SNIPPET = """ from ._version import get_versions __version__ = get_versions()['version'] del get_versions """ INIT_PY_SNIPPET = """ from . import {0} __version__ = {0}.get_versions()['version'] """ def do_setup(): """Do main VCS-independent setup function for installing Versioneer.""" root = get_root() try: cfg = get_config_from_root(root) except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e: if isinstance(e, (OSError, configparser.NoSectionError)): print("Adding sample versioneer config to setup.cfg", file=sys.stderr) with open(os.path.join(root, "setup.cfg"), "a") as f: f.write(SAMPLE_CONFIG) print(CONFIG_ERROR, file=sys.stderr) return 1 print(" creating %s" % cfg.versionfile_source) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py") if os.path.exists(ipy): try: with open(ipy, "r") as f: old = f.read() except OSError: old = "" module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0] snippet = INIT_PY_SNIPPET.format(module) if OLD_SNIPPET in old: print(" replacing boilerplate in %s" % ipy) with open(ipy, "w") as f: f.write(old.replace(OLD_SNIPPET, snippet)) elif snippet not in old: print(" appending to %s" % ipy) with open(ipy, "a") as f: f.write(snippet) else: print(" %s unmodified" % ipy) else: print(" %s doesn't exist, ok" % ipy) ipy = None # Make VCS-specific changes. For git, this means creating/changing # .gitattributes to mark _version.py for export-subst keyword # substitution. do_vcs_install(cfg.versionfile_source, ipy) return 0 def scan_setup_py(): """Validate the contents of setup.py against Versioneer's expectations.""" found = set() setters = False errors = 0 with open("setup.py", "r") as f: for line in f.readlines(): if "import versioneer" in line: found.add("import") if "versioneer.get_cmdclass()" in line: found.add("cmdclass") if "versioneer.get_version()" in line: found.add("get_version") if "versioneer.VCS" in line: setters = True if "versioneer.versionfile_source" in line: setters = True if len(found) != 3: print("") print("Your setup.py appears to be missing some important items") print("(but I might be wrong). Please make sure it has something") print("roughly like the following:") print("") print(" import versioneer") print(" setup( version=versioneer.get_version(),") print(" cmdclass=versioneer.get_cmdclass(), ...)") print("") errors += 1 if setters: print("You should remove lines like 'versioneer.VCS = ' and") print("'versioneer.versionfile_source = ' . This configuration") print("now lives in setup.cfg, and should be removed from setup.py") print("") errors += 1 return errors def setup_command(): """Set up Versioneer and exit with appropriate error code.""" errors = do_setup() errors += scan_setup_py() sys.exit(1 if errors else 0) if __name__ == "__main__": cmd = sys.argv[1] if cmd == "setup": setup_command()