diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 000000000..e3aeda6ca --- /dev/null +++ b/.github/ISSUE_TEMPLATE @@ -0,0 +1,18 @@ + + +### Description of Issue/Question diff --git a/.gitignore b/.gitignore index 51cbe8525..5ab03e9f3 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,14 @@ coverage.xml # Sphinx documentation docs/_build/ +.idea +.DS_Store + +env +*.swp + +test/unit/test_devices.py + +.vagrant + +tags diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..40f2ca3f6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: python +python: +- 2.7 +install: + - pip install -r requirements.txt + - pip install -r test/unit/requirements.txt + - pip install . +deploy: + provider: pypi + user: dbarroso + password: + secure: kt2RgomUtrf5zXo3CyF8B7SkolvKgALAO0s72WuMd0wTGmgOvoBlt10Vfc+G+wuVAYvW/JKdsYRceancAFyWLFgjLtNxbV4cJF2RXN956sYFSJ2VrtUiB19WuKZjX6024gMs780hC/3bdK1SDg/NAAHSR7u2cma3QgRcW6O+UG4= + on: + tags: true + branch: master +script: + - nosetests ./test/unit/TestGetNetworkDriver.py diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..7dd938de1 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +David Barroso +Elisa Jasinska diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..5c304d1a4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ +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. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..f9bd1455b --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include requirements.txt diff --git a/README.md b/README.md new file mode 100644 index 000000000..52e6e681b --- /dev/null +++ b/README.md @@ -0,0 +1,167 @@ +[![PyPI](https://img.shields.io/pypi/v/napalm.svg)](https://pypi.python.org/pypi/napalm) +[![PyPI](https://img.shields.io/pypi/dm/napalm.svg)](https://pypi.python.org/pypi/napalm) +[![Build Status](https://travis-ci.org/napalm-automation/napalm.svg?branch=master)](https://travis-ci.org/napalm-automation/napalm) + + +NAPALM +====== +NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support) is a Python library that implements a set of functions to interact with different router vendor devices using a unified API. + +![NAPALM logo](static/logo.png?raw=true "NAPALM logo") + +NAPALM supports several methods to connect to the devices, to manipulate configurations or to retrieve data. + +Supported Network Operating Systems +----------------------------------- + +Please check the following [link](https://napalm.readthedocs.io/en/latest/support/index.html) to see which devices are supported. Make sure you understand the [caveats](https://napalm.readthedocs.io/en/latest/support/index.html#caveats). + +Documentation +============= + +Before using the library, please read the documentation at: [Read the Docs](https://napalm.readthedocs.io) + +You can also watch a [live demo](https://youtu.be/93q-dHC0u0I) of NAPALM to see what it is and what it can do for you. + +Install +======= + +Full installation +----------------- + +If you want to fully install NAPALM you can do it by executing: + +``` +pip install napalm +``` + +That will install all the drivers currently available. + + +Partial Installation +-------------------- + +If you want to install just a subset of the available modules you can just pick them as follows: + +``` +pip install napalm-eos napalm-junos +``` + +That will install only the `eos` and the `junos` drivers. If you want to remove or add a module later on you can just use `pip` to do it: + +``` +pip uninstall napalm-junos +pip install napalm-ios +``` + +Check the ['Supported Network Operating Systems'](#supported-network-operating-systems) section for more information about supported modules. + +Upgrading +========= + +We plan to upgrade napalm as fast as possible. Adding new methods and bugfixes. To upgrade napalm it's a simple as repeating the steps you performed while installing but adding the `-U` flag. For example: + +``` +pip install napalm -U +``` + +or: + +``` +pip install napalm-eos napalm-junos -U +``` + +We will be posting news on our slack channel and on Twitter (more details soon). + +Automation Frameworks +====================== + +Due to its flexibility, NAPALM can be integrated in widely used automation frameworks. + +Ansible +------- + +Please check [napalm-ansible](https://github.com/napalm-automation/napalm-ansible) for existing Ansible modules leveraging the NAPALM API. Make sure you read the documentation and you understand how it works before trying to use it. + +SaltStack +--------- + +Beginning with release code named `Carbon` (2016.11), [NAPALM is fully integrated](https://mirceaulinic.net/2016-11-30-salt-carbon-released/) in SaltStack - no additional modules required. For setup recommendations, please see [napalm-salt](https://github.com/napalm-automation/napalm-salt). For documentation and usage examples, you can check the modules documentation, starting from the [release notes](https://docs.saltstack.com/en/develop/topics/releases/2016.11.0.html#network-automation-napalm) and [this blog post](https://mirceaulinic.net/2016-11-17-network-orchestration-with-salt-and-napalm/). + + +Contact +======= + +Mailing List +------------ + +If you have any questions, join the users' mailing list at [napalm-automation@googlegroups.com](mailto:napalm-automation@googlegroups.com) and if you are developer and want to contribute to NAPALM feel free to join to the developers' mailing list at [napalm-dev@googlegroups.com](mailto:napalm-dev@googlegroups.com) + +Slack +----- + +Slack is probably the easiest way to get help with NAPALM. You can find us in the channel `napalm` on the [network.toCode()](https://networktocode.herokuapp.com/) team. + +FAQ +--- + +If you have any issues using NAPALM or encounter any errors, before submitting any questions (directly by email or on Slack), please go through the following checklist: + +- Double or triple check if you indeed are able to access the device using the credentials provided. +- Does your device meet the minimum [requirements](http://napalm.readthedocs.io/en/latest/support/index.html#general-support-matrix)? +- Some operating systems have some specific [constraints](http://napalm.readthedocs.io/en/latest/support/index.html#caveats). (e.g. have you enabled the XML agent on IOS-XR, or the NXAPI feature on NXOS?) +- Are you able to connect to the device using NAPALM? Check using the CLI test tool: + +```bash +$ cl_napalm_test --vendor VENDOR --user USERNAME --password PASSWORD --optional_args OPTIONAL_ARGS HOSTNAME +``` + +Where vendor, username, password and hostname are mandatory. [Optional arguments](http://napalm.readthedocs.io/en/latest/support/index.html#optional-arguments) are specified as comma separated values. + +Example: + +```bash +$ cl_napalm_test --vendor junos --user napalm --password dbejmujz --optional_args 'port=12202, config_lock=False' edge01.bjm01 +``` + +In case you have any errors, please review the steps above - this looks like a problem with your environment setup. + +In order to get help faster, when submitting a bug/error make sure to include all the details requested. + +News +==== + +Blog Posts +---------- + +* [NAPALM, Ansible, and Cisco IOS](https://pynet.twb-tech.com/blog/automation/napalm-ios.html) by Kirk Byers +* [Adding Cisco IOS support to NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support)](https://projectme10.wordpress.com/2015/12/07/adding-cisco-ios-support-to-napalm-network-automation-and-programmability-abstraction-layer-with-multivendor-support/) by Gabriele Gerbino +* [Network orchestration with Salt and NAPALM](https://mirceaulinic.net/2016-11-17-network-orchestration-with-salt-and-napalm/) by Mircea Ulinic + +Presentations +------------- + +* [NANOG 64 Presentation & Demo](https://youtu.be/93q-dHC0u0I) by David Barroso and Elisa Jasinska +* [Netnod Autumn Meeting 2015 Presentation](https://www.netnod.se/sites/default/files/NAPALM-david_barroso-Netnodautumnmeeting2015.pdf) by David Barroso +* [Automating IXP Device Configurations with Ansible at the Euro-IX Forum](https://www.euro-ix.net/m/uploads/2015/10/26/euroix-berlin-v2.pdf) by Elisa Jasinska +* [Network Automation with Salt and NAPALM at NANOG 68](https://www.nanog.org/sites/default/files/NANOG68%20Network%20Automation%20with%20Salt%20and%20NAPALM%20Mircea%20Ulinic%20Cloudflare%20(1).pdf); [video](https://www.youtube.com/watch?v=gV2918bH5_c); [recorded demo](https://www.youtube.com/watch?v=AqBk5fM7qZ0) by Mircea Ulinic + +Podcasts +-------- + +* [NAPALM: Integrating Ansible with Network Devices on Software Gone Wild](http://blog.ipspace.net/2015/06/napalm-integrating-ansible-with-network.html) with David Barroso and Elisa Jasinska + +Authors +======= + * David Barroso ([dbarrosop@dravetech.com](mailto:dbarroso@dravetech.com)) + * Elisa Jasinska ([elisa@bigwaveit.org](mailto:elisa@bigwaveit.org)) + * Many others, check the [contributors](https://github.com/napalm-automation/napalm/graphs/contributors) page for details. + + +Thanks +====== + +This project was founded by David Barroso as part of [Spotify][spotify] and Elisa Jasinska as part of [BigWave IT][bigwave]. Originally it was hosted by the [Spotify][spotify] organization but due to the many contributions received by third parties we agreed creating a dedicated organization for NAPALM and give a big thanks to [Spotify][spotify] for the support. + +[spotify]: http://www.spotify.com +[bigwave]: http://bigwaveit.org/ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..8d0cc70f4 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +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 " singlehtml to make a single large HTML file" + @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 " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @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." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +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/napalm.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/napalm.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/napalm" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/napalm" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +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." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/base.rst b/docs/base.rst new file mode 100644 index 000000000..bb99c175f --- /dev/null +++ b/docs/base.rst @@ -0,0 +1,7 @@ +NetworkDriver +------------- + +.. autoclass:: napalm_base.base.NetworkDriver + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/cli.rst b/docs/cli.rst new file mode 100644 index 000000000..eb2183bf2 --- /dev/null +++ b/docs/cli.rst @@ -0,0 +1,114 @@ +Command Line Tool +================= + +NAPALM ships with a simple CLI tool to help you deploying configuration to your devices directly from the shell. +It might be convenient for simple bash scripts or provisioning tools that rely on a shell. + +The usage is very simple. For example, let's do a dry run (changes will not be applied) and check the changes between +my current configuration and a new candidate configuration: + +.. code-block:: diff + + # cl_napalm_configure --user vagrant --vendor eos --strategy replace --optional_args 'port=12443' --dry-run localhost new_good.conf + Enter password: + @@ -2,30 +2,38 @@ + ! + ! boot system flash:/vEOS-lab.swi + ! + -event-handler dhclient + - trigger on-boot + - action bash sudo /mnt/flash/initialize_ma1.sh + +transceiver qsfp default-mode 4x10G + ! + -transceiver qsfp default-mode 4x10G + +hostname pyeos-unittest-changed + ! + spanning-tree mode mstp + ! + aaa authorization exec default local + ! + -aaa root secret 5 $1$b4KXboe4$yeTwqHOKscsF07WGoOnZ0. + +no aaa root + ! + -username admin privilege 15 role network-admin secret 5 $1$nT3t1LkI$1f.SG5YaRo6h4LlhIKgTK. + -username vagrant privilege 15 role network-admin secret 5 $1$589CDTZ0$9S4LGAiCpxHCOC17jECxt1 + +username admin privilege 15 role network-admin secret 5 $1$RT/92Zg9$J8wD1qPAdQBcOhv4fefyt. + +username vagrant privilege 15 role network-admin secret 5 $1$Lw2STh4k$bPEDVVTY2e7lf.vNlnNEO0 + ! + interface Ethernet1 + ! + interface Ethernet2 + + description ble + ! + interface Management1 + ip address 10.0.2.15/24 + ! + no ip routing + ! + +router bgp 65000 + + vrf test + + neighbor 1.1.1.2 remote-as 1 + + neighbor 1.1.1.2 maximum-routes 12000 + + ! + + vrf test2 + + neighbor 2.2.2.3 remote-as 2 + + neighbor 2.2.2.3 maximum-routes 12000 + +! + management api http-commands + no shutdown + ! + # + +We got the diff back. Now let's try a partial configuration instead. However, this time we will directly apply the +configuration and we will also be passing the password directly as an argument: + +.. code-block:: diff + + # cl_napalm_configure --user vagrant --password vagrant --vendor eos --strategy merge --optional_args 'port=12443' merge_good.conf localhost + @@ -7,6 +7,8 @@ + action bash sudo /mnt/flash/initialize_ma1.sh + ! + transceiver qsfp default-mode 4x10G + +! + +hostname NEWHOSTNAME + ! + spanning-tree mode mstp + ! + @@ -20,6 +22,7 @@ + interface Ethernet1 + ! + interface Ethernet2 + + description BLALALAL + ! + interface Management1 + ip address 10.0.2.15/24 + # + +We got the diff back in the stdout. If we try to run the command we should get an empty string: + +.. code-block:: diff + + # cl_napalm_configure --user vagrant --password vagrant --vendor eos --strategy merge --optional_args 'port=12443' merge_good.conf localhost + # + +Errors are detected as well:: + + # cl_napalm_configure --user vagrant --password vagrant --vendor eos --strategy merge --optional_args 'port=12443' merge_typo.conf localhost + Traceback (most recent call last): + File "/Users/dbarroso/.virtualenvs/test/bin/cl_napalm_configure", line 9, in + load_entry_point('napalm==0.50.3', 'console_scripts', 'cl_napalm_configure')() + File "/Users/dbarroso/.virtualenvs/test/lib/python2.7/site-packages/napalm-0.50.3-py2.7.egg/napalm/clitools/cl_napalm_configure.py", line 139, in main + args.optional_args, args.config_file, args.dry_run)) + File "/Users/dbarroso/.virtualenvs/test/lib/python2.7/site-packages/napalm-0.50.3-py2.7.egg/napalm/clitools/cl_napalm_configure.py", line 131, in run + return diff + File "/Users/dbarroso/.virtualenvs/test/lib/python2.7/site-packages/napalm-0.50.3-py2.7.egg/napalm/base.py", line 46, in __exit__ + self.__raise_clean_exception(exc_type, exc_value, exc_traceback) + File "/Users/dbarroso/.virtualenvs/test/lib/python2.7/site-packages/napalm-0.50.3-py2.7.egg/napalm/clitools/cl_napalm_configure.py", line 119, in run + strategy_method(filename=config_file) + File "/Users/dbarroso/.virtualenvs/test/lib/python2.7/site-packages/napalm-0.50.3-py2.7.egg/napalm/eos.py", line 95, in load_merge_candidate + self._load_config(filename, config, False) + File "/Users/dbarroso/.virtualenvs/test/lib/python2.7/site-packages/napalm-0.50.3-py2.7.egg/napalm/eos.py", line 89, in _load_config + raise MergeConfigException(e.message) + napalm.exceptions.MergeConfigException: Error [1002]: CLI command 5 of 5 'descriptin BLALALAL' failed: invalid command + +For more information, run ``cl_napalm_configure --help``. diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..2be003faf --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,361 @@ +"""conf.py.""" +# -*- coding: utf-8 -*- +# +# napalm documentation build configuration file, created by +# sphinx-quickstart on Tue Dec 16 13:17:14 2014. +# +# 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. +from collections import defaultdict + +import glob +import json +import os +import re +import subprocess +import sys + + +from napalm_base import NetworkDriver +from jinja2 import Environment, FileSystemLoader + +# 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 ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' + +autoclass_content = 'both' + +# 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.napoleon', +] + +# 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-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'NAPALM' +copyright = u'2016, David Barroso' + +# 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' +# The full version, including alpha/beta/rc tags. +release = '1' + +# 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 patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# 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 = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +else: + 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 = None + +# 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'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# 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_domain_indices = 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, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = 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 = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'napalmdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'napalm.tex', u'NAPALM Documentation', + u'David Barroso', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'napalm', u'NAPALM Documentation', + [u'David Barroso'], 1) +] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'napalm', u'NAPALM Documentation', + u'David Barroso', 'napalm', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + +# Disable pdf and epub generation +enable_pdf_build = False +enable_epub_build = False + +EXCLUDE_METHODS = ('cli', 'close', 'commit_config', 'compare_config', + 'discard_config', 'load_merge_candidate', + 'load_replace_candidate', 'load_template', 'open', + 'rollback', 'compliance_report') + +EXCLUDE_IN_REPORT = ('test_method_signatures') + +METHOD_ALIASES = { + 'get_config_filtered': 'get_config', +} + + +def _merge_results(last, intermediate): + if intermediate == 'failed': + return 'failed' + elif intermediate == 'skipped': + return 'failed' if last == 'failed' else 'skipped' + elif intermediate == 'passed': + return 'ok' if last == 'ok' else last + else: + return last + + +def build_getters_support_matrix(app): + """Build the getters support matrix.""" + status = subprocess.call("./test.sh", stdout=sys.stdout, stderr=sys.stderr) + + if status != 0: + print("Something bad happened when processing the test reports.") + sys.exit(-1) + + drivers = [] + matrix = {m: defaultdict(dict) for m in dir(NetworkDriver) + if not (m.startswith('_') or + m in EXCLUDE_METHODS)} + + regex_name = re.compile(r"::test_(\w+)") + + for filename in glob.iglob('./support/tests/*.json'): + driver = filename.split('/')[-1].split('.')[0] + drivers.append(driver) + with open(filename, 'r') as f: + data = json.loads(f.read()) + for test in data["report"]["tests"]: + match = regex_name.search(test['name']) + if match: + method = match.group(1) + else: + continue + if method in EXCLUDE_IN_REPORT: + continue + result = test['outcome'] + + if method in METHOD_ALIASES.keys(): + method = METHOD_ALIASES[method] + + intermediate_result = matrix[method].get(driver, None) + matrix[method][driver] = _merge_results(result, intermediate_result) + + sorted_methods = sorted(matrix.keys()) + drivers = sorted(drivers) + env = Environment(loader=FileSystemLoader(".")) + template_file = env.get_template("matrix.j2") + rendered_template = template_file.render(matrix=matrix, drivers=drivers, + sorted_methods=sorted_methods) + + with open('support/matrix.rst', 'w') as f: + f.write(rendered_template) + + +def setup(app): + """Map methods to states of the documentation build.""" + app.connect('builder-inited', build_getters_support_matrix) + + +build_getters_support_matrix(None) diff --git a/docs/contributing/index.rst b/docs/contributing/index.rst new file mode 100644 index 000000000..903a923d8 --- /dev/null +++ b/docs/contributing/index.rst @@ -0,0 +1,52 @@ +Contributing +============ + +Contributing is very easy and you can do it many ways; documentation, bugfixes, new features, etc. Any sort of contribution is useful. + +How to Contribute +----------------- + +In order to speed up things we recommend you to follow the following rules when doing certain types of contributions. If something is not clear don't worry, just ask or send your contribution back and we will help you. + +New Feature +----------- + +New features are going to mostly be either a new method that is not yet defined or implementing a method already defined for a particular driver. + +Proposing a new method +______________________ + +The best way to propose a new method is as follows to send a PR with the proposed method. That will probably spark some debate around the format. The PR will not only have to include the proposed method but some testing. + +In addition, before merging we will want an implementation for any driver of your choice. + +For example: + - `get_config proposal `_ - That particular example had an issue that some had raised as a refernce but that's not mandatory. You can create an issue first but that's optional. + - `get_config implementation for EOS `_ - Before the PR was merged an implementation was provided as a proof of concept. This is mandatory. This PRs doesn't have to arrive at the same time as the previous one but it will be required. Note that the rules for "`Implementing an already defined method`_" apply to this PR. + +Implementing an already defined method +______________________________________ + +Adding an already defined method to a driver has three very simple steps: + +1. Implement the code. +2. Add necessary mocked data. +3. Enable the test and ensure it passes. + +Again `get_config implementation for EOS `_ is a good example. + + +Bugfixes +-------- + +If you found a bug and know how to fix just contribute the bugfix. It might be interesting to provide a test to make sure we don't introduce the bug back in the future but this step is optional. + +Documentation +------------- + +Just do it! :) + +Proposing a new driver +---------------------- + +This is a but more complex but completely doable. You can find more information `here `_. diff --git a/docs/development/index.rst b/docs/development/index.rst new file mode 100644 index 000000000..133f6f994 --- /dev/null +++ b/docs/development/index.rst @@ -0,0 +1,10 @@ + +Development +=========== + +Some useful information for development purposes. + +.. toctree:: + :maxdepth: 1 + + testing_framework diff --git a/docs/development/testing_framework.rst b/docs/development/testing_framework.rst new file mode 100644 index 000000000..7a27daad3 --- /dev/null +++ b/docs/development/testing_framework.rst @@ -0,0 +1,103 @@ +Testing Framework +----------------- + +As napalm consists of multiple drivers and all of them have to provide similar functionality, we have developed a testing framework to provide a consistent test suite for all the drivers. + +Features +________ + +The testing framework has the following features: + +1. Same tests across all vendors. Tests defined in ``napalm_base/test/getters.py`` are shared across all drivers. +2. Multiple test cases per test. +3. Target of the test can be configured with enviromental variables. +4. Expected output is compared against the actual output of the test result. +5. NotImplemented methods are skipped automatically. + +Using the testing framework +___________________________ + +To use the testing framework you have to implement two files in addition to the mocked data: + +- ``test/unit/test_getters.py`` - Generic file with the same content as this file `test_getters.py`_ +- ``test/unit/conftest.py`` - Code specific to each driver with instructions on how to fake the driver. For example, `conftest.py`_ + +Multiple test cases +^^^^^^^^^^^^^^^^^^^ + +To create test cases for your driver you have to create a folder named ``test/unit/mocked_data/$name_of_test_function/$name_of_test_case``. For example: + +- ``test/unit/mocked_data/test_get_bgp_neighbors/no_peers/`` +- ``test/unit/mocked_data/test_get_bgp_neighbors/lots_of_peers/`` + +Each folder will have to contain it's own mocked data and expected result. + +Target +^^^^^^ + +By default, the tests are going to be run agains mocked data but you can change that behavior with the following enviromental variables: + +* ``NAPALM_TEST_MOCK`` - 1 (default) for mocked data and 0 for connecting to a device. +* ``NAPALM_HOSTNAME`` +* ``NAPALM_USERNAME`` +* ``NAPALM_PASSWORD`` +* ``NAPALM_OPTIONAL_ARGS`` + +Mocking the ``open`` method +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To mock data needed to connect to the device, ie, needed by the ``open`` method, just put the data in the folder ``test/unit/mocked_data/`` + +Examples +________ + +Multiple test cases:: + + (napalm) ➜ napalm-eos git:(test_framework) ✗ ls test/unit/mocked_data/test_get_bgp_neighbors + lots_of_peers no_peers normal + (napalm) ➜ napalm-eos git:(test_framework) ✗ py.test test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors + ... + test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors[lots_of_peers] <- ../napalm-base/napalm_base/test/getters.py PASSED + test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors[no_peers] <- ../napalm-base/napalm_base/test/getters.py PASSED + test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors[normal] <- ../napalm-base/napalm_base/test/getters.py PASSED + +Missing test cases:: + + (napalm) ➜ napalm-eos git:(test_framework) ✗ ls test/unit/mocked_data/test_get_bgp_neighbors + ls: test/unit/mocked_data/test_get_bgp_neighbors: No such file or directory + (napalm) ➜ napalm-eos git:(test_framework) ✗ py.test test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors + ... + test/unit/test_getters.py::TestGetter::test_get_bgp_neighbors[no_test_case_found] <- ../napalm-base/napalm_base/test/getters.py FAILED + + ========================================================= FAILURES ========================================================== + ___________________________________ TestGetter.test_get_bgp_neighbors[no_test_case_found] ___________________________________ + + cls = , test_case = 'no_test_case_found' + + @functools.wraps(func) + def wrapper(cls, test_case): + cls.device.device.current_test = func.__name__ + cls.device.device.current_test_case = test_case + + try: + # This is an ugly, ugly, ugly hack because some python objects don't load + # as expected. For example, dicts where integers are strings + result = json.loads(json.dumps(func(cls))) + except IOError: + if test_case == "no_test_case_found": + > pytest.fail("No test case for '{}' found".format(func.__name__)) + E Failed: No test case for 'test_get_bgp_neighbors' found + + ../napalm-base/napalm_base/test/getters.py:64: Failed + ================================================= 1 failed in 0.12 seconds ================================================== + +Method not implemented:: + + (napalm) ➜ napalm-eos git:(test_framework) ✗ py.test test/unit/test_getters.py::TestGetter::test_get_probes_config + ... + test/unit/test_getters.py::TestGetter::test_get_probes_config[no_test_case_found] <- ../napalm-base/napalm_base/test/getters.py SKIPPED + + ================================================= 1 skipped in 0.09 seconds ================================================= + +.. _`test_getters.py`: https://github.com/napalm-automation/napalm-eos/blob/a2fc2cf6a98b0851efe4cba907086191b8f1df02/test/unit/test_getters.py +.. _`conftest.py`: https://github.com/napalm-automation/napalm-eos/blob/a2fc2cf6a98b0851efe4cba907086191b8f1df02/test/unit/conftest.py diff --git a/docs/hackathons/hackathon2016/agenda.rst b/docs/hackathons/hackathon2016/agenda.rst new file mode 100644 index 000000000..31aa58607 --- /dev/null +++ b/docs/hackathons/hackathon2016/agenda.rst @@ -0,0 +1,11 @@ +Agenda +______ + +.. important:: + All dates and times are in UTC + + +.. raw:: html + + + diff --git a/docs/hackathons/hackathon2016/index.rst b/docs/hackathons/hackathon2016/index.rst new file mode 100644 index 000000000..4573e6229 --- /dev/null +++ b/docs/hackathons/hackathon2016/index.rst @@ -0,0 +1,46 @@ +Hackathon 2016 +============== + +Welcome to the very first ``NAPALM`` hackathon ever. What you are about to see is a bunch of people doing the unthinkable; ``writing code to manage the network``!!! + +.. important:: + I love the smell of ``automation`` in the morning. + +Introduction +------------ + +During a weekend we will gather online to hack around napalm, fix existing issues, clean the codebase or just do whatever we want. + +.. _hackathon2016-useful-information: + +Quick Information +----------------- + ++-----------------+-----------------------------------------------+ +| **Date** | 16, 17 and 18th of September | ++-----------------+-----------------------------------------------+ +| **Location** | :doc:`Here for more information ` | ++-----------------+-----------------------------------------------+ +| **Slack** | ``#napalm-hackathon-2016`` in networkToCode_ | ++-----------------+-----------------------------------------------+ +| **Live Feed** | Youtube Live_ | ++-----------------+-----------------------------------------------+ +| **Recordings** | Youtube_ | ++-----------------+-----------------------------------------------+ + +Information +----------- + +.. toctree:: + :maxdepth: 2 + + agenda + locations + participating + mentors + volunteering + presentations + +.. _networkToCode: https://networktocode.herokuapp.com/ +.. _Live: https://www.youtube.com/channel/UCkjR7tuducm2jxFsWv0dQBQ/live +.. _youtube: https://www.youtube.com/playlist?list=PLTUN_ROR5c9jUUHk0Du0Yy6a6lf9PaUet diff --git a/docs/hackathons/hackathon2016/locations.rst b/docs/hackathons/hackathon2016/locations.rst new file mode 100644 index 000000000..a952cd1e7 --- /dev/null +++ b/docs/hackathons/hackathon2016/locations.rst @@ -0,0 +1,36 @@ +Location +________ + +The hackathon will be held online. We will use slack as the main communications channel, github to coordinate the work and hangouts for live presentations, which will be posted online on a youtbe channel within the hour. + +Check this :ref:`link ` for more information regarding the slack channel, hangouts, youtube, etc.. + +IRL Gatherings +^^^^^^^^^^^^^^ + +Apparently there is something called real life and people like to gather in groups, shocking, isn't it? We will gather in some locations just for the sake of feeling the warmth of other human beings and probably have beer after a long day of hacking. This is completely optional and unofficial, although, if you want to host a physical meetup I will be happy to announce it here. + +Known gatherings +^^^^^^^^^^^^^^^^ + +* London, UK:: + + CloudFlare, 25 Lavington St, London SE1 0NZ + Contact: Mircea, @mirceaulinic (slack), tel: +447427735256 + +* New York, NY, United States:: + + Network to Code (WeWork location), 315 W. 36th, NY + Contact: Jason Edelman (jason@networktocode.com) or jedelman8 on Slack and Twitter + +* San Francisco, CA, United states:: + + Contact ktbyers@twb-tech.com for details + +* Krakow, Poland:: + + Contact elisa@bigwaveit.org for details + + + +.. _index: diff --git a/docs/hackathons/hackathon2016/mentors.rst b/docs/hackathons/hackathon2016/mentors.rst new file mode 100644 index 000000000..cd7e1fcb4 --- /dev/null +++ b/docs/hackathons/hackathon2016/mentors.rst @@ -0,0 +1,16 @@ +Mentors +_______ + +.. important:: + All levels of expertise are welcome so if you are new to github, python, napalm or even to networking, don't let that be on the way. We have some :doc:`mentors ` to help you and plenty of tasks to get you started with python, github, and various other tools for testing code, making it look pretty, etc. So if you are looking for some free training this might be a good way to get it ;) + + +The community in networkToCode is pretty much great so if you have any problem just post it on the slack channel. Feel free to also ping any of the mentors if you didn't get any satisfactory answer. + +Mentors' slack handles are: + + * ``dbarroso`` + * ``mirceaulinic`` + * ``ggabriele`` + +.. _networkToCode: https://networktocode.herokuapp.com/ diff --git a/docs/hackathons/hackathon2016/participating.rst b/docs/hackathons/hackathon2016/participating.rst new file mode 100644 index 000000000..d8c0e48da --- /dev/null +++ b/docs/hackathons/hackathon2016/participating.rst @@ -0,0 +1,21 @@ +Participating +_____________ + + +Before the event +^^^^^^^^^^^^^^^^ + +Feel free to navigate all the repos on the `napalm automation `_ organization and find issues you might want to work on. + + #. If you find any feel free to comment on the issue to let the organizers know. + #. If you don't and you know what you would like to work on, please, create an issue with the description of what you want to achieve. + #. If you want to participate and you don't know what to do, feel free to ask on the slack channel. It is already available so go there and ask. + +During the event +^^^^^^^^^^^^^^^^ + + #. Make sure you are on slack. We will use that as our main communications channel. We will make all the announcements there and we will notify you there if we plan to make some announcement on the hangout. + #. If you can't attend the live videos don't sweat. They will be published on YouTube as soon as they are done so you don't miss anything. + #. If you are in New Zealand and think you live on a strange timezone and, thus, you can't participate, you are wrong. GitHub is great for asynchronous communications and I am sure there will always be someone around on slack to help you through any problem you might encounter, to help someone yourself or simply to wind up for a second and just talk about any random topic you want. + +.. napalm-automation: https://github.com/napalm-automation diff --git a/docs/hackathons/hackathon2016/presentations.rst b/docs/hackathons/hackathon2016/presentations.rst new file mode 100644 index 000000000..4bbaa076a --- /dev/null +++ b/docs/hackathons/hackathon2016/presentations.rst @@ -0,0 +1,9 @@ +Presentations +_____________ + +* `NAPALM Introduction (Slides)`_ +* `NAPALM Kickoff (Slides)`_ + + +.. _`NAPALM Introduction (Slides)`: https://www.dravetech.com/presos/napalm_hackathon_intro.html +.. _`NAPALM Kickoff (Slides)`: https://www.dravetech.com/presos/napalm_hackathon_kickoff.html diff --git a/docs/hackathons/hackathon2016/volunteering.rst b/docs/hackathons/hackathon2016/volunteering.rst new file mode 100644 index 000000000..29b05d1e0 --- /dev/null +++ b/docs/hackathons/hackathon2016/volunteering.rst @@ -0,0 +1,4 @@ +Volunteering +____________ + +Do you want to help out? Please, do it. We need mentors. If you know how to work with git, python, napalm, how to record a google hangout, you want to organize a physical gathering or just correct my spleling, please, ping ``dbarroso`` on slack. diff --git a/docs/hackathons/index.rst b/docs/hackathons/index.rst new file mode 100644 index 000000000..423226384 --- /dev/null +++ b/docs/hackathons/index.rst @@ -0,0 +1,7 @@ +Hackathons +========== + +.. toctree:: + :maxdepth: 1 + + hackathon2016/index diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000..0324a6088 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,67 @@ +.. napalm documentation master file, created by + sphinx-quickstart on Tue March 26 12:11:44 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to NAPALM's documentation! +================================== + +NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support) is a Python library that implements a set of functions to interact with different network device Operating Systems using a unified API. + +NAPALM supports several methods to connect to the devices, to manipulate configurations or to retrieve data. + +Supported Network Operating Systems: +------------------------------------ + +* Arista EOS +* Cisco IOS +* Cisco IOS-XR +* Cisco NX-OS +* Fortinet Fortios +* IBM +* Juniper JunOS +* Mikrotik RouterOS +* Palo Alto NOS +* Pluribus +* Vyos + +You can select the driver you need by doing the following: + +.. code-block:: python + + >>> from napalm_base import get_network_driver + >>> get_network_driver('eos') + + >>> get_network_driver('iosxr') + + >>> get_network_driver('junos') + + >>> get_network_driver('fortios') + + >>> get_network_driver('ibm') + + >>> get_network_driver('nxos') + + >>> get_network_driver('ios') + + >>> get_network_driver('pluribus') + + + +Check the tutorials to see how to use the library in more detail, Supported Devices will provide you with detailed support information and caveats and the NetworkDriver section explains which methods are available for you to use. + +Documentation +============= + +.. toctree:: + :maxdepth: 2 + + installation + tutorials/index + validate/index + support/index + cli + base + contributing/index + development/index + hackathons/index diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 000000000..73e725219 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,71 @@ +Installation +============ + + +Full installation +----------------- + +If you want to fully install NAPALM you can do it by executing: + +``` +pip install napalm +``` + +That will install all the drivers currently available. + + +Partial Installation +-------------------- + +If you want to install just a subset of the available modules you can just pick them as follows: + +``` +pip install napalm-eos napalm-junos +``` + +That will install only the `eos` and the `junos` drivers. If you want to remove or add a module later on you can just use `pip` to do it: + +``` +pip uninstall napalm-junos +pip install napalm-ios +``` + +Check the ['Supported Network Operating Systems'](http://napalm.readthedocs.io/en/latest/support/index.html) section for more information about supported modules. + + +Upgrading +--------- + +We plan to upgrade napalm as fast as possible. Adding new methods and bugfixes. To upgrade napalm it's a simple as repeating the steps you performed while installing but adding the `-U` flag. For example: + +``` +pip install napalm -U +``` + +or: + +``` +pip install napalm-eos napalm-junos -U +``` + +Dependencies +------------ + +Dependencies are supposed to be solved by `pip` and in most cases it works out of the box. However, on some systems some dependencies have to be installed using system tools. For example, the ``cryptography`` package that ``napalm-ios`` uses. + +* `napalm_ios` + +To to ensure all dependencies are met for these drivers, use the following commands: + +**Debian/Ubuntu**: + +``` +sudo apt-get install build-essential libssl-dev libffi-dev python-dev +``` + +**Fedora and RHEL-derivatives**: + +``` +sudo yum install gcc libffi-devel python-devel openssl-devel +``` + diff --git a/docs/matrix.j2 b/docs/matrix.j2 new file mode 100644 index 000000000..64d50d7cd --- /dev/null +++ b/docs/matrix.j2 @@ -0,0 +1,22 @@ +{%- macro parse(text) -%} +{%- if text == "passed" -%}✅ +{%- elif text == "failed" -%}☠ +{%- elif text == "skipped" -%}❌ +{%- else -%}{{ text }} +{%- endif -%} +{%- endmacro -%} + +.. raw:: html + + + {% for driver in drivers %}{% endfor %} + {% for method in sorted_methods %} + + {% for driver in drivers %}{% endfor %} + {% endfor %} +
{{driver|upper}}
{{ method }}{{ parse(matrix[method][driver]) }}
+
    +
  • {{ parse("passed") }} - supported
  • +
  • {{ parse("skipped") }} - not supported
  • +
  • {{ parse("failed") }} - broken
  • +
diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..3aaa88de2 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,5 @@ +sphinx +sphinx-rtd-theme +sphinxcontrib-napoleon +invoke +napalm diff --git a/docs/support/.gitignore b/docs/support/.gitignore new file mode 100644 index 000000000..227e39d81 --- /dev/null +++ b/docs/support/.gitignore @@ -0,0 +1,2 @@ +tests/ +matrix.rst diff --git a/docs/support/eos.rst b/docs/support/eos.rst new file mode 100644 index 000000000..e3a2eef30 --- /dev/null +++ b/docs/support/eos.rst @@ -0,0 +1,21 @@ +EOS +--- + +Minimum Version +~~~~~~~~~~~~~~~ + + +To be able to support the ``compare_config`` method you will require to run at least EOS version `4.15.0F`. + +Rollback +~~~~~~~~ + +The rollback feature is supported only when committing from the API. In reality, what the API does during the commit operation is as follows:: + + copy startup-config flash:rollback-0 + +And the rollback does:: + + configure replace flash:rollback-0 + +This means that the rollback will be fine as long as you only use this library. If you are going to do changes outside this API don't forget to mark your last rollback point manually. diff --git a/docs/support/fortios.rst b/docs/support/fortios.rst new file mode 100644 index 000000000..622801efa --- /dev/null +++ b/docs/support/fortios.rst @@ -0,0 +1,19 @@ +FortiOS +------- + +Rollback +~~~~~~~~ + +To make sure the rollback feature works either use only this API to do changes or remember to save your rollback points on the CLI with the command:: + + execute backup config flash your_message + +Atomic Changes +~~~~~~~~~~~~~~ + +FortiOS' plugin will use the "batch" feature. All commands will not go at the same time but all of them will be processed. The sad true is that FortiOS doesn't have any proper tool to apply large chunks of configuration. + +Known Issues +~~~~~~~~~~~~ + +Beginning in FortiOS version 5.2, a Fortigate bug was introduced that generates an ``EOFError`` in ``paramiko/transport.py`` during the SSH key exchange. Full details of the ``paramiko`` issue documented `here `_. Current workaround is to edit the `preferred key exchange algorithms `_ in ``paramiko/transport.py``. Either move ``diffie-hellman-group1-sha1`` below ``diffie-hellman-group-exchange-sha1`` or delete ``diffie-hellman-group1-sha1``. diff --git a/docs/support/ibm.rst b/docs/support/ibm.rst new file mode 100644 index 000000000..b813167ee --- /dev/null +++ b/docs/support/ibm.rst @@ -0,0 +1,13 @@ +IBM Networking Operating System +------------------------------- + +Rollback +~~~~~~~~ + +Rollback is simply implemented by reading current running configuration before any load actions. Rollback function executes load replace and commit. + + +Atomic Changes +~~~~~~~~~~~~~~ + +IBM plugin uses netconf to load configuration on to device. It seems that configuration is executed line by line but we can be sure that all lines will be executed. There are three options for error handling: stop-on-error, continue-on-error and rollback-on-error. Plugin uses rollback-on-error option in case of merge operation. However replace operation uses continue-on-error option. In case of typo in configuration, device will inform plugin about error but execute all the rest lines. Plugin will revert configuration using rollback function from the plugin. I do not use rollback-on-error for replace operation because in case of error device is left without any configuration. It seems like a bug. It will be investigated further. Moreover it seems that replace option wipe out whole configuration on device at the first step, so this option is good for provisioning of new device and it is not recommended for device in production. diff --git a/docs/support/index.rst b/docs/support/index.rst new file mode 100644 index 000000000..2ae65a0fd --- /dev/null +++ b/docs/support/index.rst @@ -0,0 +1,140 @@ +Supported Devices +================= + +General support matrix +---------------------- + + + + ===================== ========== ============= ============ ============== ============ ============ =============== ========================= ============== ============== + _ EOS JunOS IOS-XR FortiOS NXOS IOS Pluribus PANOS MikroTik VyOS + ===================== ========== ============= ============ ============== ============ ============ =============== ========================= ============== ============== + **Module Name** napalm-eos napalm-junos napalm-iosxr napalm-fortios napalm-nxos napalm-ios napalm-pluribus napalm-panos napalm-ros napalm-vyos + **Driver Name** eos junos iosxr fortios nxos ios pluribus panos ros vyos + **Structured data** Yes Yes No No Yes No No Yes Yes Yes + **Minimum version** 4.15.0F 12.1 5.1.0 5.2.0 6.1 [#g1]_ 12.4(20)T N/A 7.0 3.30 1.1.6 + **Backend library** `pyeapi`_ `junos-eznc`_ `pyIOSXR`_ `pyFG`_ `pycsco`_ `netmiko`_ `pyPluribus`_ `netmiko`_, `pan-python`_ `librouteros`_ `netmiko`_ + **Caveats** :doc:`eos` :doc:`fortios` :doc:`nxos` :doc:`ios` :doc:`panos` :doc:`vyos` + ===================== ========== ============= ============ ============== ============ ============ =============== ========================= ============== ============== + +.. _pyeapi: https://github.com/arista-eosplus/pyeapi +.. _junos-eznc: https://github.com/Juniper/py-junos-eznc +.. _pyIOSXR: https://github.com/fooelisa/pyiosxr +.. _pyFG: https://github.com/spotify/pyfg +.. _pycsco: https://github.com/jedelman8/pycsco +.. _netmiko: https://github.com/ktbyers/netmiko +.. _pyPluribus: https://github.com/mirceaulinic/pypluribus +.. _pan-python: https://github.com/kevinsteves/pan-python +.. _librouteros: https://github.com/luqasz/librouteros + +.. [#g1] NX-API support on the Nexus 5k, 6k and 7k families was introduced in version 7.2 + +.. warning:: Please, make sure you understand the caveats for your particular platforms before using the library. + + +Configuration support matrix +---------------------------- + +===================== ========== ===== ========== ============== ============== ============== ============== ============== ======== ======== +_ EOS JunOS IOS-XR FortiOS NXOS IOS Pluribus PANOS MikroTik VyOS +===================== ========== ===== ========== ============== ============== ============== ============== ============== ======== ======== +**Config. replace** Yes Yes Yes Yes Yes Yes No Yes No Yes +**Config. merge** Yes Yes Yes Yes Yes Yes No Yes No Yes +**Compare config** Yes Yes Yes [#c1]_ Yes [#c1]_ Yes [#c4]_ Yes No Yes No Yes +**Atomic Changes** Yes Yes Yes No [#c2]_ Yes/No [#c5]_ Yes Yes Yes/No [#c5]_ No Yes +**Rollback** Yes [#c2]_ Yes Yes Yes Yes/No [#c5]_ Yes No Yes No Yes +===================== ========== ===== ========== ============== ============== ============== ============== ============== ======== ======== + +.. [#c1] Hand-crafted by the API as the device doesn't support the feature. +.. [#c2] Not supported but emulated. Check caveats. +.. [#c3] Check the caveats, this is a dangerous operation in this device. +.. [#c4] For merges, the diff is simply the merge config itself. See caveats. +.. [#c5] No for merges. See caveats. + +.. warning:: Before building a workflow to deploy configuration it is important you understand what the table above means; + what are atomic changes and which devices support it, what does replacing or merging configuration mean, etc. + The key to success is to test your workflow and to try to break things on a lab first. + +Getters support matrix +---------------------- + +.. note:: The following table is built automatically. Everytime there is a release of a supported driver a built is triggered. The result of the tests are aggreggated on the following table. + +.. include:: matrix.rst + + +Other methods +------------- + +.. |yes| unicode:: U+02705 .. Yes +.. |no| unicode:: U+0274C .. No + +============================== ===== ===== ====== ======= ====== ===== ========= ========= ======== ======== +_ EOS JunOS IOS-XR FortiOS NXOS IOS Pluribus PANOS MikroTik VyOS +============================== ===== ===== ====== ======= ====== ===== ========= ========= ======== ======== +**load_template** |yes| |yes| |yes| |yes| |yes| |yes| |yes| |yes| |no| |yes| +**ping** |no| |no| |no| |no| |no| |yes| |no| |no| |yes| |yes| +**traceroute** |yes| |yes| |yes| |no| |yes| |yes| |yes| |no| |no| |no| +**commit_confirm** |no| |yes| |no| |no| |no| |no| |no| |no| |no| |no| +============================== ===== ===== ====== ======= ====== ===== ========= ========= ======== ======== + +Available configuration templates +--------------------------------- + +* :code:`set_hostname` (JunOS, IOS-XR, IOS, PANOS) - Configures the hostname of the device. +* :code:`set_ntp_peers` (JunOS, IOS-XR, EOS, NXOS, IOS) - Configures NTP peers of the device. +* :code:`delete_ntp_peers` (JunOS, IOS-XR, EOS, NXOS, IOS): Removes NTP peers form device's configuration. +* :code:`set_probes` (JunOS, IOS-XR): Configures RPM/SLA probes. +* :code:`schedule_probes` (IOS-XR): On Cisco devices, after defining the SLA probes, it is mandatory to schedule them. Defined also for JunOS as empty template, for consistency reasons. +* :code:`delete_probes` (JunOS, IOS-XR): Removes RPM/SLA probes. + +Caveats +------- + +.. toctree:: + :maxdepth: 1 + + eos + fortios + ibm + nxos + ios + panos + vyos + +Optional arguments +------------------ + +NAPALM supports passing certain optional arguments to some drivers. To do that you have to pass a dictionary via the +:code:`optional_args` parameter when creating the object:: + + >>> from napalm import get_network_driver + >>> driver = get_network_driver('eos') + >>> optional_args = {'my_optional_arg1': 'my_value1', 'my_optional_arg2': 'my_value2'} + >>> device = driver('192.168.76.10', 'dbarroso', 'this_is_not_a_secure_password', optional_args=optional_args) + >>> device.open() + +List of supported optional arguments +____________________________________ + +* :code:`fortios_vdom` (fortios) - VDOM to connect to. +* :code:`port` (eos, iosxr, junos, ios, ros, vyos) - Allows you to specify a port other than the default. +* :code:`config_lock` (iosxr, junos) - Lock the config during open() (default: True). +* :code:`dest_file_system` (ios) - Destination file system for SCP transfers (default: 'flash:'). +* :code:`auto_rollback_on_error` (ios) - Disable automatic rollback (certain versions of IOS support configure replace, but not rollback on error) (default: True). +* :code:`global_delay_factor` (ios) - Allow for additional delay in command execution (default: 1). +* :code:`nxos_protocol` (nxos) - Protocol to connect with. Only 'https' and 'http' allowed (default: 'http'). +* :code:`enable_password` (eos) - Password required to enter privileged exec (enable) (default: ''). +* :code:`allow_agent` (ios, panos) - Paramiko argument, enable connecting to the SSH agent (default: 'False'). +* :code:`use_keys` (ios, panos) - Paramiko argument, enable searching for discoverable private key files in ~/.ssh/ (default: 'False'). +* :code:`key_file` (vyos) - Netmiko/Paramiko argument, path to a private key file (default: 'False'). +* :code:`api_key` (panos) - Allow to specify the API key instead of username/password (default: ''). + + +Adding optional arguments to NAPALM drivers +___________________________________________ + +If you are a developer and want to add an optional argument to a driver, please, follow this pattern when naming the +argument; :code:`$driver_name-$usage` if the argument applies only to a particular driver. For example, the optional +argument :code:`fortios_vdom` is used only by the FortiOS driver to select a particular vdom. Otherwise, just name it +:code:`$driver_name-$usage`. For example the :code:`port` optional argument. diff --git a/docs/support/ios.rst b/docs/support/ios.rst new file mode 100644 index 000000000..e88ebadac --- /dev/null +++ b/docs/support/ios.rst @@ -0,0 +1,63 @@ +IOS +--- + + +Prerequisites +_____________ + +IOS has no native API to play with, that's the reason why we used the Netmiko library to interact with it. +Having Netmiko installed in your working box is a prerequisite. + +netmiko >= 1.0.0 + +Full ios driver support requires configuration rollback on error:: + + Cisco IOS requirements for 'Configuration Rollback Confirmed Change' feature. + 12.2(33)SRC + 12.2(33)SB + 12.4(20)T + 12.2(33)SXI + + +Downgraded ios driver support (i.e. no auto rollback on configuration error for replace operation):: + + Cisco IOS requirements for 'Configuration Replace and Configuration Rollback' feature. + 12.3(7)T + 12.2(25)S + 12.3(14)T + 12.2(27)SBC + 12.2(31)SB2 + 12.2(33)SRA + 12.2(33)SXH + 12.2(33)SB + + +Note, to disable auto rollback you must add the `auto_rollback_on_error=False` optional argument. + + + +Archive +_______ + +IOSDriver requires that the `archive` functionality be enabled to perform auto-rollback on error. Make sure it's enabled and set to local on device flash/hdd:: + + archive + path bootflash:archive + write-memory + + +Configuration file +------------------ + +IOS requires config file to begin with a `version` eg. `15.0` and `end` marker at the end of the file. Otherwise IOS will reject `configure replace` operation. + + +Notes +_______ + +* Will automatically enable secure copy ('ip scp server enable') on the network device. This is a configuration change. + +* During various operations, NAPALM ios driver will turn off the prompting for confirmations (`file prompt quiet`). It should re-enable prompting before exiting the device (`no file prompt quiet`). + +* The NAPALM-ios driver supports all Netmiko arguments as either standard arguments (hostname, username, password, timeout) or as optional_args (everything else). + diff --git a/docs/support/nxos.rst b/docs/support/nxos.rst new file mode 100644 index 000000000..0674a276e --- /dev/null +++ b/docs/support/nxos.rst @@ -0,0 +1,63 @@ +NXOS +---- + +Notes on configuration replacement +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + +Config files aren't aren't normal config files but special "checkpoint" files. +That's because on NXOS the only way to replace a config without reboot is to rollback to a checkpoint (which could be a file). +These files explicitly list a lot of normally implicit config lines, some of them starting with ``!#``. +The ``!#`` part isn't necessary for the rollback to work, but leaving these lines out can cause erratic behavior. +See the "Known gotchas" section below. + +Prerequisites +_____________ + +Your device must be running NXOS 6.1. The features ``nxapi`` server ``scp-server`` must be enabled. +On the device and any checkpoint file you push, you must have the lines:: + + feature scp-server + feature nxapi + + +Getting a base checkpoint file +______________________________ + +An example of a checkpoint file can be seen in ``test/unit/nxos/new_good.conf``. +You can get a checkpoint file representing your device's current config by running the ``get_checkpoint_file()`` +function in the ``napalm.nxos`` driver. + +Known gotchas +_____________ + +- Leaving out a ``shutdown`` or ``no shutdown`` line will cause the switch to toggle the up/down state of an interface, depending on it's current state. + +- ``!#switchport trunk allowed vlan 1-4094`` is required even if the switchport is in ``switchport mode access``. However if ``!#switchport trunk allowed vlan 1-4094`` is included with ``no switchport``, the configuration replacement will fail. + +- Vlans are listed vertically. For example ``vlan 1, 10, 20, 30`` will fail. To succeed, you need: + :: + + vlan 1 + vlan 10 + vlan 20 + vlan 30 + +Diffs +_____ + +Diffs for config replacement are a list of commands that would be needed to take the device from it's current state +to the desired config state. See ``test/unit/nxos/new_good.diff`` as an example. + +Notes on configuration merging +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Merges are currently implemented by simply applying the the merge config line by line. +This doesn't use the checkpoint/rollback functionality. +As a result, merges are **not atomic**. + +Diffs +_____ + +Diffs for merges are simply the lines in the merge candidate config. diff --git a/docs/support/panos.rst b/docs/support/panos.rst new file mode 100644 index 000000000..e6fb3b172 --- /dev/null +++ b/docs/support/panos.rst @@ -0,0 +1,32 @@ +PANOS +----- + + +Prerequisites +_____________ + +PANOS has a Python interface called `pan-python`. Anyway, for some feature like sending multiple commands, `Netmiko` would be a best choice. +That's why having Netmiko installed in your working box is a prerequisite. + +netmiko >= 0.5.0 + + +Replacing Configuration +________________________ + +Only configuration files are supported with `load_replace_candidate`. It must be a full XML file. +Due to the OS nature, at this time we don't support a replace using a configuration string. + + +Merging Configuration +________________________ + +Only configuration strings/lists are supported with `load_merge_candidate`. It can be a string or a list of strings in set-format. +Due to the OS nature and the NAPALM structure, at this time we don't support a merge using a configuration file because it'd need additional parameters. This may be supported in the future. + + +Atomic Changes +______________ + +Changes are atomic only when performing a configuration replace, since it's done with a single command. + diff --git a/docs/support/tests/.placeholder b/docs/support/tests/.placeholder new file mode 100644 index 000000000..e69de29bb diff --git a/docs/support/vyos.rst b/docs/support/vyos.rst new file mode 100644 index 000000000..d559270d5 --- /dev/null +++ b/docs/support/vyos.rst @@ -0,0 +1,42 @@ +VYOS +==== + +Prerequisites +------------- + +` +VyOS has no native HTTP API or NETCONF capability. +We are using Netmiko for ssh connections and scp file transfers. +Having Netmiko installed in your working box is a prerequisite. + +netmiko >= 1.0.0 +vyattaconfparser + + + +Configuration file +------------------ + +Currently VyOS driver supports two different configuration formats: +* load_replace_candidate - Full config file (with brackets) like in /config/config.boot +Due to the OS nature, we do not support a replace using +a set-style configuration format. +* load_merge_candidate - Currently driver supports set-style configuration format. +Example + +`set system login banner pre-login "test"` + +Vyos require configuration file (load_replace) to contain comment like following + +`/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "cluster@1:config-management@1:conntrack-sync@1:conntrack@1:cron@1:dhcp-relay@1:dhcp-server@4:firewall@5:ipsec@4:nat@4:qos@1:quagga@2:system@6:vrrp@1:wanloadbalance@3:webgui@1:webproxy@1:zone-policy@1" === */ +/* Release version: VyOS 1.1.7 */` + +Otherwise VyOS reject `load` operation + +Notes +------------------ +* The NAPALM-vyos driver supports all Netmiko arguments as either standard arguments (hostname, username, password, timeout) or as optional_args (everything else). + +* The NAPALM-vyos driver supports authentication with ssh key. Please specify path to a key in optional_args +`'optional_args': {'key_file': '/home/user/ssh_private_key'}` diff --git a/docs/test.sh b/docs/test.sh new file mode 100755 index 000000000..e91e7c04f --- /dev/null +++ b/docs/test.sh @@ -0,0 +1,31 @@ +#!/bin/bash +CWD=`pwd` +BUILDPATH=../ +TEST_RESULTS_PATH="$CWD/support/tests" +DRIVER=`/bin/cat ../requirements.txt | grep napalm | grep -v base | awk -F\- '{print $2}'` + +set -e +pip install -U napalm-base + +function process_driver { + echo PROCESSING $1 + git clone https://github.com/napalm-automation/napalm-$1.git ../$1 + cd ../$1 + git checkout master + pip install -r requirements-dev.txt + + set +e + py.test -c /dev/null --cov=./ -vs --json=report.json test/unit/test_getters.py + set -e + + cp report.json $TEST_RESULTS_PATH/$1.json + cd $CWD +} + +mkdir -p $TEST_RESULTS_PATH + +for driver in $DRIVER; do + if [ ! -d ../$driver ]; then + process_driver $driver + fi; +done diff --git a/docs/tutorials/Vagrantfile b/docs/tutorials/Vagrantfile new file mode 100644 index 000000000..d49c494d6 --- /dev/null +++ b/docs/tutorials/Vagrantfile @@ -0,0 +1,30 @@ +# Vagrantfile for the quickstart tutorial + +# Script configuration: +# +# Arista vEOS box. +# Please change this to match your installed version +# (use `vagrant box list` to see what you have installed). +VEOS_BOX = "vEOS-lab-4.15.5M-virtualbox" + +Vagrant.configure(2) do |config| + + config.vm.define "base" do |base| + # This box will be downloaded and added automatically if you don't + # have it already. + base.vm.box = "hashicorp/precise64" + base.vm.network :forwarded_port, guest: 22, host: 12200, id: 'ssh' + base.vm.network "private_network", virtualbox__intnet: "link_1", ip: "10.0.1.100" + base.vm.network "private_network", virtualbox__intnet: "link_2", ip: "10.0.2.100" + base.vm.provision "shell", inline: "apt-get update; apt-get install lldpd -y" + end + + config.vm.define "eos" do |eos| + eos.vm.box = VEOS_BOX + eos.vm.network :forwarded_port, guest: 22, host: 12201, id: 'ssh' + eos.vm.network :forwarded_port, guest: 443, host: 12443, id: 'https' + eos.vm.network "private_network", virtualbox__intnet: "link_1", ip: "169.254.1.11", auto_config: false + eos.vm.network "private_network", virtualbox__intnet: "link_2", ip: "169.254.1.11", auto_config: false + end + +end diff --git a/docs/tutorials/context_manager.rst b/docs/tutorials/context_manager.rst new file mode 100644 index 000000000..015f7eabf --- /dev/null +++ b/docs/tutorials/context_manager.rst @@ -0,0 +1,18 @@ +Context Manager +=============== + +In the previous tutorial we used the methods ``open()`` to connect to the device and ``close()`` to disconnect. +Using those methods are useful if you want to do complex or asynchronous code. However, for most situations you should +try to stick with the context manager. It handles opening and closing the session automatically and it's the +pythonic way: + +.. code-block:: python + +>>> from napalm import get_network_driver +>>> driver = get_network_driver('eos') +>>> with driver('localhost', 'vagrant', 'vagrant', optional_args={'port': 12443}) as device: +... print device.get_facts() +... print device.get_interfaces_counters() +... +{'os_version': u'4.15.2.1F-2759627.41521F', 'uptime': 2010, 'interface_list': [u'Ethernet1', u'Ethernet2', u'Management1'], 'vendor': u'Arista', 'serial_number': u'', 'model': u'vEOS', 'hostname': u'NEWHOSTNAME', 'fqdn': u'NEWHOSTNAME'} +{u'Ethernet2': {'tx_multicast_packets': 1028, 'tx_discards': 0, 'tx_octets': 130744, 'tx_errors': 0, 'rx_octets': 0, 'tx_unicast_packets': 0, 'rx_errors': 0, 'tx_broadcast_packets': 0, 'rx_multicast_packets': 0, 'rx_broadcast_packets': 0, 'rx_discards': 0, 'rx_unicast_packets': 0}, u'Management1': {'tx_multicast_packets': 0, 'tx_discards': 0, 'tx_octets': 99664, 'tx_errors': 0, 'rx_octets': 105000, 'tx_unicast_packets': 773, 'rx_errors': 0, 'tx_broadcast_packets': 0, 'rx_multicast_packets': 0, 'rx_broadcast_packets': 0, 'rx_discards': 0, 'rx_unicast_packets': 0}, u'Ethernet1': {'tx_multicast_packets': 1027, 'tx_discards': 0, 'tx_octets': 130077, 'tx_errors': 0, 'rx_octets': 0, 'tx_unicast_packets': 0, 'rx_errors': 0, 'tx_broadcast_packets': 0, 'rx_multicast_packets': 0, 'rx_broadcast_packets': 0, 'rx_discards': 0, 'rx_unicast_packets': 0}} diff --git a/docs/tutorials/first_steps_config.rst b/docs/tutorials/first_steps_config.rst new file mode 100644 index 000000000..b1862c923 --- /dev/null +++ b/docs/tutorials/first_steps_config.rst @@ -0,0 +1,97 @@ +First Steps Manipulating Config +=============================== + +NAPALM tries to provide a common interface and mechanisms to push configuration and retrieve state data from network devices. This method is very useful in combination with tools like `Ansible `_, which in turn allows you to manage a set of devices independent of their network OS. + +Connecting to the Device +------------------------ + +Use the appropriate network driver to connect to the device:: + + >>> from napalm import get_network_driver + >>> driver = get_network_driver('eos') + >>> device = driver('192.168.76.10', 'dbarroso', 'this_is_not_a_secure_password') + >>> device.open() + +Configurations can be replaced entirely or merged into the existing device config. +You can load configuration either from a string or from a file. + +Replacing the Configuration +--------------------------- + +To replace the configuration do the following:: + + >>> device.load_replace_candidate(filename='test/unit/eos/new_good.conf') + +Note that the changes have not been applied yet. Before applying the configuration you can check the changes:: + + >>> print device.compare_config() + + hostname pyeos-unittest-changed + - hostname pyeos-unittest + router bgp 65000 + vrf test + + neighbor 1.1.1.2 maximum-routes 12000 + + neighbor 1.1.1.2 remote-as 1 + - neighbor 1.1.1.1 remote-as 1 + - neighbor 1.1.1.1 maximum-routes 12000 + vrf test2 + + neighbor 2.2.2.3 remote-as 2 + + neighbor 2.2.2.3 maximum-routes 12000 + - neighbor 2.2.2.2 remote-as 2 + - neighbor 2.2.2.2 maximum-routes 12000 + interface Ethernet2 + + description ble + - description bla + +If you are happy with the changes you can commit them:: + + >>> device.commit_config() + +On the contrary, if you don't want the changes you can discard them:: + + >>> device.discard_config() + +Merging Configuration +--------------------- + +Merging configuration is similar, but you need to load the configuration with the merge method:: + + >>> device.load_merge_candidate(config='hostname test\ninterface Ethernet2\ndescription bla') + >>> print device.compare_config() + configure + hostname test + interface Ethernet2 + description bla + end + +If you are happy with the changes you can commit them:: + + >>> device.commit_config() + +On the contrary, if you don't want the changes you can discard them:: + + >>> device.discard_config() + +Rollback Changes +---------------- + +If for some reason you committed the changes and you want to rollback:: + + >>> device.rollback() + +Commit confirmed (auto rollback) +-------------------------------- + +If you are operating on remote devices without out of band network access and/or there is a risk that you could lose access to your device after commit, you can use commit confirmed functionality. You have to provide an additional argument (confirmed) to commit_config() method. It defines how long (in minutes) device will wait for your confirmation. You can confirm the change by executing commit_confirm(). A device will rollback changes by itself if confirmation is not sent or confirmation message can not reach the device. For example, device will wait 5 minutes for confirmation, after that changes will be reverted:: + + >>> device.commit_config(confirmed=5) + >>> device.commit_confirm() + +.. note:: Not all devices support commit confirmed functionality. It may be dependent on device capabilities and/or support for commit confirmed inside particular napalm driver. + +Disconnecting +------------- + +To close the session with the device just do:: + + >>> device.close() diff --git a/docs/tutorials/index.rst b/docs/tutorials/index.rst new file mode 100644 index 000000000..6e0b0ad1c --- /dev/null +++ b/docs/tutorials/index.rst @@ -0,0 +1,14 @@ +Tutorials +========= + +.. toctree:: + :maxdepth: 1 + + outline + installation + lab + samples + ../cli.rst + first_steps_config + context_manager + wrapup \ No newline at end of file diff --git a/docs/tutorials/installation.rst b/docs/tutorials/installation.rst new file mode 100644 index 000000000..731da3e9c --- /dev/null +++ b/docs/tutorials/installation.rst @@ -0,0 +1,24 @@ +Installation +============ + +Tools +----- + +You'll need a few tools: + +* Python +* `pip `_: The PyPA recommended tool for installing Python packages +* `VirtualBox `_: a software virtualization tool +* `Vagrant `_: a command line utility for managing the lifecycle of virtual machines + +As the focus of this tutorial is NAPALM, we don't even scratch the surface of these tools. If you're not familiar with them, please do some research [#f1]_ as they will be an important part of your development/ops toolkit. + +Install +------- + +Install NAPALM with pip:: + + pip install napalm + + +.. [#f1] Vagrant's `getting started guide `_ is worth reading and working through. diff --git a/docs/tutorials/lab.rst b/docs/tutorials/lab.rst new file mode 100644 index 000000000..7863e9e8e --- /dev/null +++ b/docs/tutorials/lab.rst @@ -0,0 +1,69 @@ +Setting up the lab +================== + +We'll set up a lab using VirtualBox and Vagrant, with a virtual Arista device, and get some sample files for the following steps. + +Working directory +----------------- + +Create a directory for your files anywhere on your machine. + +Arista vEOS +----------- + +The Arista EOS image can be downloaded for free from the Arista site. + +Create an account at https://www.arista.com/en/user-registration, and go to https://www.arista.com/en/support/software-download. + +Download the latest "vEOS-lab--virtualbox.box" listed in the vEOS folder at the bottom of the page. + +Add it to your vagrant box list, changing the ``:: + + $ vagrant box add --name vEOS-lab--virtualbox ~/Downloads/vEOS-lab--virtualbox.box + $ vagrant box list + vEOS-lab-quickstart (virtualbox, 0) + +You can delete the downloaded .box file once you have added it, as ``vagrant box add`` copies downloaded file to a designated directory (e.g., for Mac OS X and Linux: ``~/.vagrant.d/boxes``, Windows: ``C:/Users/USERNAME/.vagrant.d/boxes``). + +Starting Vagrant +---------------- + +Create a file named ``Vagrantfile`` (no file extension) in your working directory with the following content (replace VEOS_BOX by your downloaded EOS version): + +.. literalinclude:: Vagrantfile + :language: ruby + +The above content is also available on `GitHub `_. + +This Vagrantfile creates a base box and a vEOS box when you call "vagrant up":: + + $ vagrant up + ... [output omitted] ... + + $ vagrant status + Current machine states: + base running (virtualbox) + eos running (virtualbox) + +You may see some errors when the eos box is getting created [#f1]_. + +Troubleshooting +^^^^^^^^^^^^^^^ + +* After running ``vagrant up``, ensure that you can ssh to the box with ``vagrant ssh eos``. +* If you receive the warning "eos: Warning: Remote connection disconnect. Retrying...", see `this StackOverflow post `_. + +Sample files +------------ + +There are some sample Arista vEOS configuration files on `GitHub `_. You can download them to your machine by copying them from GitHub, or using the commands below:: + + $ for f in new_good.conf merge_good.conf merge_typo.conf; do + $ wget https://raw.githubusercontent.com/napalm-automation/napalm/master/docs/tutorials/sample_configs/$f + $ done + +(Note: please open a GitHub issue if these URLs are invalid.) + + +.. [#f1] Currently, ``vagrant up`` with the eos box prints some warnings: "No guest additions were detected on the base box for this VM! Guest additions are required for forwarded ports, shared folders, host only networking, and more. If SSH fails on this machine, please install the guest additions and repackage the box to continue. This is not an error message; everything may continue to work properly, in which case you may ignore this message." This is not a reassuring message, but everything still seems to work correctly. + diff --git a/docs/tutorials/outline.rst b/docs/tutorials/outline.rst new file mode 100644 index 000000000..947cda3e8 --- /dev/null +++ b/docs/tutorials/outline.rst @@ -0,0 +1,11 @@ +Outline +======= + +This tutorial gets you up-and-running quickly with NAPALM in a local virtual environment so you can see it in action in under an hour. We'll cover the following: + +#. Installing the required tools +#. Creating a virtual lab with an Arista device +#. Manually applying configuration to the device using NAPALM +#. Driving NAPALM through Python code + +.. note:: This tutorial does not cover fully automated configuration management (e.g., using NAPALM in conjunction with Ansible, Chef, Salt, etc.). We hope that tutorials for these tools will be contributed soon so that you can evaluate the options for your particular environment. diff --git a/docs/tutorials/sample_configs/merge_good.conf b/docs/tutorials/sample_configs/merge_good.conf new file mode 100644 index 000000000..0a785354f --- /dev/null +++ b/docs/tutorials/sample_configs/merge_good.conf @@ -0,0 +1,4 @@ +hostname NEWHOSTNAME + +interface Ethernet2 + description BLALALAL diff --git a/docs/tutorials/sample_configs/merge_typo.conf b/docs/tutorials/sample_configs/merge_typo.conf new file mode 100644 index 000000000..0b8fa6eb9 --- /dev/null +++ b/docs/tutorials/sample_configs/merge_typo.conf @@ -0,0 +1,4 @@ +hostname NEWHOSTNAME + +interface Ethernet2 + descriptin BLALALAL diff --git a/docs/tutorials/sample_configs/new_good.conf b/docs/tutorials/sample_configs/new_good.conf new file mode 100644 index 000000000..f5ad266bf --- /dev/null +++ b/docs/tutorials/sample_configs/new_good.conf @@ -0,0 +1,37 @@ +transceiver qsfp default-mode 4x10G + +hostname pyeos-unittest-changed + +spanning-tree mode mstp + +aaa authorization exec default local + +no aaa root + +username admin privilege 15 role network-admin secret 5 $1$RT/92Zg9$J8wD1qPAdQBcOhv4fefyt. +username vagrant privilege 15 role network-admin secret 5 $1$Lw2STh4k$bPEDVVTY2e7lf.vNlnNEO0 + +interface Ethernet1 + +interface Ethernet2 + description ble + +interface Management1 + ip address 10.0.2.15/24 + +no ip routing + +router bgp 65000 + vrf test + neighbor 1.1.1.2 remote-as 1 + neighbor 1.1.1.2 maximum-routes 12000 + + vrf test2 + neighbor 2.2.2.3 remote-as 2 + neighbor 2.2.2.3 maximum-routes 12000 + +management api http-commands + no shutdown + + +end diff --git a/docs/tutorials/sample_scripts/load_replace.py b/docs/tutorials/sample_scripts/load_replace.py new file mode 100644 index 000000000..5b39e356c --- /dev/null +++ b/docs/tutorials/sample_scripts/load_replace.py @@ -0,0 +1,60 @@ +# Sample script to demonstrate loading a config for a device. +# +# Note: this script is as simple as possible: it assumes that you have +# followed the lab setup in the quickstart tutorial, and so hardcodes +# the device IP and password. You should also have the +# 'new_good.conf' configuration saved to disk. + + +import napalm +import sys +import os + +def main(config_file): + """Load a config for the device.""" + + if not (os.path.exists(config_file) and os.path.isfile(config_file)): + msg = 'Missing or invalid config file {0}'.format(config_file) + raise ValueError(msg) + + print 'Loading config file {0}.'.format(config_file) + + # Use the appropriate network driver to connect to the device: + driver = napalm.get_network_driver('eos') + + # Connect: + device = driver(hostname='127.0.0.1', username='vagrant', + password='vagrant', optional_args={'port': 12443}) + + print 'Opening ...' + device.open() + + print 'Loading replacement candidate ...' + device.load_replace_candidate(filename=config_file) + + # Note that the changes have not been applied yet. Before applying + # the configuration you can check the changes: + print '\nDiff:' + print device.compare_config() + + # You can commit or discard the candidate changes. + choice = raw_input("\nWould you like to commit these changes? [yN]: ") + if choice == 'y': + print 'Committing ...' + device.commit_config() + else: + print 'Discarding ...' + device.discard_config() + + # close the session with the device. + device.close() + + print 'Done.' + +if __name__ == '__main__': + if len(sys.argv) < 2: + print 'Please supply the full path to "new_good.conf"' + sys.exit(1) + config_file = sys.argv[1] + + main(config_file) diff --git a/docs/tutorials/samples.rst b/docs/tutorials/samples.rst new file mode 100644 index 000000000..fabff12ce --- /dev/null +++ b/docs/tutorials/samples.rst @@ -0,0 +1,25 @@ +Programming samples +=================== + +NAPALM tries to provide a common interface and mechanisms to push configuration and retrieve state data from network devices. This method is very useful in combination with tools like `Ansible `_, which in turn allows you to manage a set of devices independent of their network OS. + +.. note:: These samples assume you have set up your virtual lab (see :doc:`./lab`), and that the 'eos' box is accessible via point 12443 on your machine. You should also have the sample configuration files saved locally. + +Now that you have installed NAPALM (see :doc:`./installation`) and set up your virtual lab, you can try running some sample scripts to demonstrate NAPALM in action. You can run each of the scripts below by either pulling the files from the GitHub repository, or you can copy the content to a local script (e.g., ``sample_napalm_script.py``) and run it. + +For people new to Python: + +* the script name should not conflict with any existing module or package. For example, don't call the script ``napalm.py``. +* run a Python script with ``$ python your_script_name.py``. + +Load/Replace configuration +-------------------------- + +Create a file called ``load_replace.py`` in a folder with the following content: + +.. literalinclude:: sample_scripts/load_replace.py + :language: python + +Run the script, passing the path to the ``new_good.conf`` file as an argument:: + + python load_replace.py ../sample_configs/new_good.conf diff --git a/docs/tutorials/wrapup.rst b/docs/tutorials/wrapup.rst new file mode 100644 index 000000000..757265bd3 --- /dev/null +++ b/docs/tutorials/wrapup.rst @@ -0,0 +1,25 @@ +Wrapping up +=========== + +You've now tried the main pieces of NAPALM: + +* using NAPALM to get, set, and diff the configuration of a device manually +* driving NAPALM using Python + +Shutting down +------------- + +Shut down the Vagrant virtual boxes. You can recreate them later using ``vagrant up`` if needed. + + $ vagrant destroy -f + + +Next Steps +---------- + +There are many possible steps you could take next: + +* create Vagrant boxes for other devices +* explore using configuration management tools (Ansible, Chef, Salt, etc.) + +Thanks for trying NAPALM! Please contribute to this documentation and help grow the NAPALM community! diff --git a/docs/validate/index.rst b/docs/validate/index.rst new file mode 100644 index 000000000..cea310a20 --- /dev/null +++ b/docs/validate/index.rst @@ -0,0 +1,268 @@ +Validating deployments +====================== + +Let's say you just deployed a few devices and you want to validate your deployment. To do that, you +can write a YAML file describing the state you expect your devices to be in and tell napalm to +retrieve the state of the device and build a compliance report for you. + +As always, with napalm, doing this is very easy even across multiple vendors : ) + +.. note:: Note that this is meant to validate **state**, meaning live data from the device, not + the configuration. Because that something is configured doesn't mean it looks as you want. + + +Documentation +------------- + +Writing validators files that can be interpreted by napalm is very easy. You have to start by +telling napalm how to retrieve that piece of information by using as key the name of the getter and +then write the desired state using the same format the getter would retrieve it. For example:: + + --- + get_facts: + os_version: 7.0(3)I2(2d) + interface_list: + _mode: strict + list: + - Vlan5 + - Vlan100 + hostname: n9k2 + + get_bgp_neighbors: + default: + router_id: 192.0.2.2 + peers: + _mode: strict + 192.0.2.2: + is_enabled: true + address_family: + ipv4: + sent_prefixes: 5 + ipv6: + sent_prefixes: 2 + + get_interfaces_ip: + Ethernet2/1: + ipv4: + 192.0.2.1: + prefix_length: 30 + + ping: + _name: ping_google + _kwargs: + destination: 8.8.8.8 + source: 192.168.1.1 + success: + packet_loss: 0 + _mode: strict + + ping: + _name: something_else + _kwargs: + destination: 10.8.2.8 + source: 192.168.1.1 + success: + packet_loss: 0 + _mode: strict + + +A few notes: + + * You don't have to validate the entire state of the device, you might want to validate certain + information only. For example, with the getter ``get_interfaces_ip`` we are only validating + that the interface ``Ethernet2/1`` has the IP address ``192.0.2.1/30``. If there are other + interfaces or if that same interface has more IP's, it's ok. + * You can also have a more strict validation. For example, if we go to ``get_bgp_neighbors``, + we want to validate there that the ``default`` vrf has *only* the BGP neighbor ``192.0.2.2``. + We do that by specifying at that level ``_mode: strict``. Note that the strict mode is + specific to a level (you can add it to as many levels as you want). So, going back the the + example, we are validating that only that BGP neighbor is present on that vrf but we are not + validating that other vrfs don't exist. We are not validating all the data inside the BGP + neighbor either, we are only validating the ones we specified. + * Lists of objects to be validated require an extra key ``list``. You can see an example with + the ``get_facts`` getter. Lists can be strict as well. In this case, we want to make sure the + device has only those two interfaces. + * Some methods require extra arguments, for example ``ping``. You can pass arguments to those + methods using the magic keyword ``_kwargs``. In addition, an optional keyword ``_name`` can + be specified to override the name in the report. Useful for having a more descriptive report + or for getters than can be run multiple times + +Example +------- + +Let's say we have two devices, one running ``eos`` and another one running ``junos``. A typical +script could start like this:: + + from napalm_base import get_network_driver + import pprint + + eos_driver = get_network_driver("eos") + eos_config = { + "hostname": "localhost", + "username": "vagrant", + "password": "vagrant", + "optional_args": {"port": 12443}, + } + + junos_driver = get_network_driver("junos") + junos_config = { + "hostname": "localhost", + "username": "vagrant", + "password": "", + "optional_args": {"port": 12203}, + } + +Now, let's validate that the devices are running a specific version and that the management IP is +the one we expect. Let's start by writing the validator files. + + * ``validate-eos.yml``:: + + --- + get_facts: + os_version: 4.17 + + get_interfaces_ip: + Management1: + ipv4: + 10.0.2.14: + prefix_length: 24 + _mode: strict + + * ``validate-junos.yml``:: + + --- + get_facts: + os_version: 12.1X47 + + get_interfaces_ip: + ge-0/0/0.0: + ipv4: + 10.0.2.15: + prefix_length: 24 + _mode: strict + +.. note:: You can use regular expressions to validate values. + +As you can see we are validating that the OS running is the one we want and that the management +interfaces have only the IP we expect it to have. Now we can validate the devices like this:: + + >>> with eos_driver(**eos_config) as eos: + ... pprint.pprint(eos.compliance_report("validate-eos.yml")) + ... + {u'complies': False, + u'skipped': [], + 'get_facts': {u'complies': False, + u'extra': [], + u'missing': [], + u'present': {'os_version': {u'actual_value': u'4.15.2.1F-2759627.41521F', + u'complies': False, + u'nested': False}}}, + 'get_interfaces_ip': {u'complies': True, + u'extra': [], + u'missing': [], + u'present': {'Management1': {u'complies': True, + u'nested': True}}}} + +Let's take a look first to the report. The first thing we have to note is the first key +``complies`` which is telling us that overall, the device is not compliant. Now we can dig in on +the rest of the report. The ``get_interfaces_ip`` part seems to be complying just fine, however, +the ``get_facts`` is complaining about something. If we keep digging we will see that the +``os_version`` key we were looking for is present but it's not complying as its actual value +is not the one we specified; it is ``4.15.2.1F-2759627.41521F``. + +Now let's do the same for junos:: + + >>> with junos_driver(**junos_config) as junos: + ... pprint.pprint(junos.compliance_report("validate-junos.yml")) + ... + {u'complies': True, + u'skipped': [], + 'get_facts': {u'complies': True, + u'extra': [], + u'missing': [], + u'present': {'os_version': {u'complies': True, + u'nested': False}}}, + 'get_interfaces_ip': {u'complies': True, + u'extra': [], + u'missing': [], + u'present': {'ge-0/0/0.0': {u'complies': True, + u'nested': True}}}} + +This is great, this device is fully compliant. We can check the outer ``complies`` key is set to +``True``. However, let's see what happens if someone adds and extra IP to ``ge-0/0/0.0``:: + + >>> with junos_driver(**junos_config) as junos: + ... pprint.pprint(junos.compliance_report("validate-junos.yml")) + ... + {u'complies': False, + u'skipped': [], + 'get_facts': {u'complies': True, + u'extra': [], + u'missing': [], + u'present': {'os_version': {u'complies': True, + u'nested': False}}}, + 'get_interfaces_ip': {u'complies': False, + u'extra': [], + u'missing': [], + u'present': {'ge-0/0/0.0': {u'complies': False, + u'diff': {u'complies': False, + u'extra': [], + u'missing': [], + u'present': {'ipv4': {u'complies': False, + u'diff': {u'complies': False, + u'extra': [u'172.20.0.1'], + u'missing': [], + u'present': {'10.0.2.15': {u'complies': True, + u'nested': True}}}, + u'nested': True}}}, + u'nested': True}}}} + +After adding the extra IP it seems the device is not compliant anymore. Let's see what happened: + +* Outer ``complies`` key is telling us something is wrong. +* ``get_facts`` is fine. +* ``get_interfaces_ip`` is telling us something interesting. Note that is saying that + ``ge-0/0/0.0`` has indeed the IPv4 address ``10.0.2.15`` as noted by being ``present`` and with + the inner ``complies`` set to ``True``. However, it's telling us that there is an ``extra`` IP + ``172.20.0.1``. + +The output might be a bit complex for humans but it's predictable and very easy to parse so it's +great if you want to integrate it with your documentation/reports by using simple ``jinja2`` +templates. + +Skipped tasks +_____________ + +In cases where a method is not implemented, the validation will be skipped and the result will not count towards the result. The report will let you know a method wasn't executed in the following manner:: + + ... + "skipped": [ "method_not_implemented", ], + "method_not_implemented": { + "reason": "NotImplemented", + "skipped": True, + } + ... + +``skipped`` will report the list of methods that were skipped. For details about the reason you can dig into the method's report. + +CLI & Ansible +------------- + +If you prefer, you can also make use of the validate functionality via the CLI with the command ``cl_napalm_validate`` or with ansible plugin. You can find more information about them here: + +* CLI - https://github.com/napalm-automation/napalm-base/pull/168 +* Ansible - https://github.com/napalm-automation/napalm-ansible/blob/master/library/napalm_validate.py + + +Why this and what's next +------------------------ + +As mentioned in the introduction, this is interesting to validate state. You could, for example, +very easily check that your BGP neigbors are configured and that the state is up. It becomes even more +interesting if you can build the validator file from data from your inventory. That way you could +deploy your network and verify it matches your expectations all the time without human intervention. + +Something else you could do is write the validation file manually prior to a maintenance based on +some gathered data from the network and on your expectations. You could, then, perform your changs +and use this tool to verify the state of the network is exactly the one you wanted. No more +forgetting things or writing one-offs scripts to validate deployments. diff --git a/napalm/__init__.py b/napalm/__init__.py new file mode 100644 index 000000000..d5e74e941 --- /dev/null +++ b/napalm/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2015 Spotify AB. All rights reserved. +# +# The contents of this file are 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 napalm_base import get_network_driver diff --git a/pylama.ini b/pylama.ini new file mode 100644 index 000000000..3146a20f9 --- /dev/null +++ b/pylama.ini @@ -0,0 +1,6 @@ +[pylama] +linters = mccabe,pep257,pep8,pyflakes +ignore = D203, + +[pylama:pep8] +max_line_length = 120 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..8bb5dec15 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +napalm-base +napalm-eos +napalm-fortios +napalm-ios +napalm-iosxr +napalm-junos +napalm-nxos +napalm-pluribus +napalm-panos +napalm-ros +napalm-vyos diff --git a/setup.py b/setup.py new file mode 100644 index 000000000..7f79cc25e --- /dev/null +++ b/setup.py @@ -0,0 +1,29 @@ +import uuid + +__author__ = 'David Barroso ' +from setuptools import setup, find_packages +from pip.req import parse_requirements + + +install_reqs = parse_requirements('requirements.txt', session=uuid.uuid1()) +reqs = [str(ir.req) for ir in install_reqs] + +setup( + name="napalm", + version="1.2.0", + packages=find_packages(), + author="David Barroso", + author_email="dbarrosop@dravetech.com", + description="Network Automation and Programmability Abstraction Layer with Multivendor support", + classifiers=[ + 'Topic :: Utilities', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Operating System :: POSIX :: Linux', + 'Operating System :: MacOS', + ], + url="https://github.com/napalm-automation/napalm", + include_package_data=True, + install_requires=reqs +) diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 000000000..61836c2ce Binary files /dev/null and b/static/logo.png differ diff --git a/test/unit/TestGetNetworkDriver.py b/test/unit/TestGetNetworkDriver.py new file mode 100644 index 000000000..17cbe4ad5 --- /dev/null +++ b/test/unit/TestGetNetworkDriver.py @@ -0,0 +1,17 @@ +"""Test the method get_network_driver.""" + + +import unittest +from ddt import ddt, data, unpack + +from napalm_base import get_network_driver + + +@ddt +class TestGetNetworkDriver(unittest.TestCase): + """Test the method get_network_driver.""" + + @data('eos', 'fortios', 'ios', 'iosxr', 'junos', 'nxos', 'pluribus') + def test_get_network_driver(self, driver): + """Check that we can get the desired driver.""" + self.assertTrue(get_network_driver(driver)) diff --git a/test/unit/requirements.txt b/test/unit/requirements.txt new file mode 100644 index 000000000..96b1ffd69 --- /dev/null +++ b/test/unit/requirements.txt @@ -0,0 +1 @@ +ddt