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/README.md b/README.md index 23c22cebc..5debad23d 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,26 @@ -Usage -===== - -Connecting ----------- - -``` -from drivers.eos import EOSDriver - -arista = EOSDriver('10.48.71.3', 'admin', 'sp0tify') -arista.open() -``` - -Getting facts -------------- - -``` -facts = arista.get_facts() - ->>> facts.vendor -'Arista' ->>> facts.hostname -u'config01' ->>> facts.fqdn -u'config01.lab.spotify.net' ->>> facts.hardware_model -u'DCS-7150S-64-CL-F' ->>> facts.serial_number -u'JPE14023449' ->>> facts.os_version -u'4.14.5F' ->>> facts.interfaces -[u'Ethernet8', u'Ethernet9', u'Ethernet2', u'Ethernet3', u'Ethernet1', u'Ethernet6', u'Ethernet7', u'Ethernet4', u'Ethernet5', u'Ethernet52/1', u'Ethernet52/3', u'Ethernet52/2', u'Ethernet52/4', u'Ethernet34', u'Ethernet22', u'Ethernet50/4', u'Ethernet50/3', u'Ethernet50/2', u'Ethernet50/1', u'Ethernet51/4', u'Ethernet51/2', u'Ethernet51/3', u'Ethernet51/1', u'Ethernet38', u'Ethernet39', u'Ethernet18', u'Ethernet19', u'Ethernet32', u'Ethernet15', u'Ethernet16', u'Ethernet31', u'Ethernet49/1', u'Ethernet37', u'Ethernet49/3', u'Ethernet35', u'Ethernet10', u'Ethernet14', u'Ethernet49/2', u'Ethernet33', u'Ethernet49/4', u'Ethernet30', u'Management1', u'Ethernet17', u'Ethernet48', u'Ethernet47', u'Ethernet36', u'Ethernet45', u'Ethernet44', u'Ethernet43', u'Ethernet42', u'Ethernet41', u'Ethernet40', u'Ethernet29', u'Ethernet28', u'Ethernet11', u'Ethernet12', u'Ethernet46', u'Ethernet21', u'Ethernet20', u'Ethernet23', u'Ethernet13', u'Ethernet25', u'Ethernet24', u'Ethernet27', u'Ethernet26'] -``` - -Getting BGP information ------------------------ - -``` ->>> for instance in arista.get_bgp_neighbors(): -... print instance -... for neigh in instance.bgp_neighbors: -... print ' ', neigh -... -BGP Instance: vrf=default, asn=123, router_id=10.48.71.3 - BGP Neighbor: ip=2.1.3.5, asn=345, state=Idle - BGP Neighbor: ip=1.1.1.1, asn=1, state=Idle - BGP Neighbor: ip=2.1.3.4, asn=345, state=Idle -BGP Instance: vrf=test, asn=23, router_id=192.12.4.1 - BGP Neighbor: ip=192.12.4.6, asn=94, state=Connect -``` +NAPALM +====== +NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support) is python library that implements a set of functions to interact with different vendors using a unified API. + +NAPALM supports several methods to connect to the devices, to manipulate configuration and to retrieve data. + +Supported Network Operating Systems +----------------------------------- + + * eos - Using [pyEOS](https://github.com/spotify/pyeos). You need version 4.14.6M or superior. + * junos - Using [junos-eznc](https://github.com/Juniper/py-junos-eznc) + * iosxr - Using [pyIOSXR](https://github.com/fooelisa/pyiosxr) + +Documentation +============= + +See the [Read the Docs](http://napalm.readthedocs.org) + +Install +======= + +To install, execute: + +`` + pip install napalm +`` diff --git a/ansible/napalm_install_config b/ansible/napalm_install_config index 30aa487a9..f33489414 100644 --- a/ansible/napalm_install_config +++ b/ansible/napalm_install_config @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright 2014 Spotify AB. All rights reserved. +# 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 diff --git a/docs/Makefile b/docs/Makefile index 58a0aef70..8d0cc70f4 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -85,17 +85,17 @@ qthelp: @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyEOS.qhcp" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/napalm.qhcp" @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyEOS.qhc" + @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/pyEOS" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyEOS" + @echo "# mkdir -p $$HOME/.local/share/devhelp/napalm" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/napalm" @echo "# devhelp" epub: diff --git a/docs/base.rst b/docs/base.rst index 69951e693..ea727b55b 100644 --- a/docs/base.rst +++ b/docs/base.rst @@ -1,7 +1,7 @@ -EOS ---- +NetworkDriver +------------- -.. autoclass:: pyEOS.eos.EOS +.. autoclass:: napalm.base.NetworkDriver :members: :undoc-members: :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py index e67dcecd1..173301fb0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# pyEOS documentation build configuration file, created by +# 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 @@ -47,8 +47,8 @@ master_doc = 'index' # General information about the project. -project = u'pyEOS' -copyright = u'2014, David Barroso' +project = u'NAPALM' +copyright = u'2015, 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 @@ -187,7 +187,7 @@ #html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'pyEOSdoc' +htmlhelp_basename = 'napalmdoc' # -- Options for LaTeX output --------------------------------------------- @@ -207,7 +207,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'pyEOS.tex', u'pyEOS Documentation', + ('index', 'napalm.tex', u'NAPALM Documentation', u'David Barroso', 'manual'), ] @@ -237,7 +237,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'pyeos', u'pyEOS Documentation', + ('index', 'napalm', u'NAPALM Documentation', [u'David Barroso'], 1) ] @@ -251,8 +251,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'pyEOS', u'pyEOS Documentation', - u'David Barroso', 'pyEOS', 'One line description of project.', + ('index', 'napalm', u'NAPALM Documentation', + u'David Barroso', 'napalm', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docs/eos.rst b/docs/eos.rst new file mode 100644 index 000000000..8a2d457dd --- /dev/null +++ b/docs/eos.rst @@ -0,0 +1,12 @@ +EOS +---- + +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/first_steps.rst b/docs/first_steps.rst deleted file mode 100644 index 9ec7ed4e3..000000000 --- a/docs/first_steps.rst +++ /dev/null @@ -1,370 +0,0 @@ -First Steps -=========== - -Connecting to a device ----------------------- - -First you have to connect to a device:: - - >>> from pyEOS import EOS - >>> device = EOS(hostname='10.48.71.3', username='admin', password='pa55w0rd', use_ssl=False) - >>> device.open() - -Running show commands ---------------------- - -Once you are connected you can execute any show command you want. All show commands are supported by default and will return the same output as the eAPI without modifying anything. For example:: - - >>> lldp = device.show_lldp_neighbors() - >>> print lldp - {u'tablesDrops': 0, u'_meta': {u'execStartTime': 1418311629.19, u'execDuration': 0.0096}, u'tablesAgeOuts': 0, u'tablesDeletes': 0, u'tablesInserts': 1, u'lldpNeighbors': [{u'neighborDevice': u'lom-bjg61-r1-1', u'neighborPort': u'6', u'port': u'Management1', u'ttl': 120}], u'tablesLastChangeTime': 1409237643.93} - -Let's explain what you are seeing there. You got a dictionary with different keys, the interesting ones are **_meta** and **lldpNeighbors**. The key *_meta* gives you some useful information to find out if your query is hogging the device and the key **lldpNeighbors** is a list of dictionaries giving you information about your LLDP neighbors. We can easily walk through them:: - - >>> for neighbor in lldp['lldpNeighbors']: - ... print "%s is connected on port %s. Remote port is %s" % (neighbor['neighborDevice'], neighbor['port'], neighbor['neighborPort']) - ... - lom-bjg61-r1-1 is connected on port Management1. Remote port is 6 - -As I explained before, any show command is supported out of the box (with all of its suboptions). Let's try to check the status of the interfaces with **show interfaces description**:: - - >>> interfaces = device.show_interfaces_description() - >>> for interface, status in interfaces['interfaceDescriptions'].iteritems(): - ... print "%s is %s" % (interface, status['interfaceStatus']) - ... - Ethernet8 is down - Ethernet9 is down - Ethernet2 is down - Ethernet3 is down - Ethernet1 is down - Ethernet6 is down - Ethernet7 is down - Ethernet4 is down - Ethernet5 is down - Ethernet52/1 is down - Ethernet52/3 is down - Ethernet52/2 is down - Ethernet52/4 is down - Ethernet34 is down - Ethernet22 is down - Ethernet50/4 is down - Ethernet50/3 is down - Ethernet50/2 is down - Ethernet50/1 is down - Ethernet51/4 is down - Ethernet51/2 is down - Ethernet51/3 is down - Ethernet51/1 is down - Ethernet38 is down - Ethernet39 is down - Ethernet18 is down - Ethernet19 is down - Ethernet32 is down - Ethernet15 is down - Ethernet16 is down - Ethernet31 is down - Ethernet49/1 is down - Ethernet37 is down - Ethernet49/3 is down - Ethernet35 is down - Ethernet10 is down - Ethernet14 is down - Ethernet49/2 is down - Ethernet33 is down - Ethernet49/4 is down - Ethernet30 is down - Management1 is up - Ethernet17 is down - Ethernet48 is down - Ethernet47 is down - Ethernet36 is down - Ethernet45 is down - Ethernet44 is down - Ethernet43 is down - Ethernet42 is down - Ethernet41 is down - Ethernet40 is down - Ethernet29 is down - Ethernet28 is down - Ethernet11 is down - Ethernet12 is down - Ethernet46 is down - Ethernet21 is down - Ethernet20 is down - Ethernet23 is down - Ethernet13 is down - Ethernet25 is down - Ethernet24 is down - Ethernet27 is down - Ethernet26 is down - -Now let's try with the command **show ip route**:: - - >>> routes = device.show_ip_route() - Traceback (most recent call last): - File "", line 1, in - File "pyEOS/eos.py", line 19, in wrapper - return self.run_commands(cmd, **kwargs)[1] - File "pyEOS/eos.py", line 63, in run_commands - raise exceptions.CommandUnconverted(error) - pyEOS.exceptions.CommandUnconverted: CLI command 2 of 2 'show ip route' failed: unconverted command - -Something happened here. That command has not been converted to JSON yet so the command failed as the API will always to fetch JSON by default. You can tell the API to detect this problem automatically for you and fix it:: - - >>> routes = device.show_ip_route(auto_format=True) - >>> print routes['output'] - Codes: C - connected, S - static, K - kernel, - O - OSPF, IA - OSPF inter area, E1 - OSPF external type 1, - E2 - OSPF external type 2, N1 - OSPF NSSA external type 1, - N2 - OSPF NSSA external type2, B I - iBGP, B E - eBGP, - R - RIP, I - ISIS, A B - BGP Aggregate, A O - OSPF Summary, - NG - Nexthop Group Static Route - - Gateway of last resort: - S 0.0.0.0/0 [1/0] via 10.48.68.1, Management1 - - C 10.48.68.0/22 is directly connected, Management1 - -Or you can also explictly ask for text output:: - - >>> lldp = device.show_lldp_neighbors(format='text') - >>> print lldp['output'] - Last table change time : 105 days, 1:02:36 ago - Number of table inserts : 1 - Number of table deletes : 0 - Number of table drops : 0 - Number of table age-outs : 0 - - Port Neighbor Device ID Neighbor Port ID TTL - Ma1 lom-bjg61-r1-1 6 120 - -Running arbitrary commands --------------------------- - -You can also run a list of commands. They can be any command you want:: - - >>> cmds = ['ping 8.8.8.8', 'traceroute 8.8.8.8'] - >>> output = device.run_commands(cmds) - >>> print output[1]['messages'] - [u'PING 8.8.8.8 (8.8.8.8) 72(100) bytes of data.\n80 bytes from 8.8.8.8: icmp_req=1 ttl=60 time=1.31 ms\n80 bytes from 8.8.8.8: icmp_req=2 ttl=60 time=1.08 ms\n80 bytes from 8.8.8.8: icmp_req=3 ttl=60 time=0.783 ms\n80 bytes from 8.8.8.8: icmp_req=4 ttl=60 time=0.722 ms\n80 bytes from 8.8.8.8: icmp_req=5 ttl=60 time=0.724 ms\n\n--- 8.8.8.8 ping statistics ---\n5 packets transmitted, 5 received, 0% packet loss, time 4ms\nrtt min/avg/max/mdev = 0.722/0.925/1.313/0.237 ms, ipg/ewma 1.163/1.105 ms\n'] - >>> print output[1]['messages'][0] - PING 8.8.8.8 (8.8.8.8) 72(100) bytes of data. - 80 bytes from 8.8.8.8: icmp_req=1 ttl=60 time=1.31 ms - 80 bytes from 8.8.8.8: icmp_req=2 ttl=60 time=1.08 ms - 80 bytes from 8.8.8.8: icmp_req=3 ttl=60 time=0.783 ms - 80 bytes from 8.8.8.8: icmp_req=4 ttl=60 time=0.722 ms - 80 bytes from 8.8.8.8: icmp_req=5 ttl=60 time=0.724 ms - - --- 8.8.8.8 ping statistics --- - 5 packets transmitted, 5 received, 0% packet loss, time 4ms - rtt min/avg/max/mdev = 0.722/0.925/1.313/0.237 ms, ipg/ewma 1.163/1.105 ms - - >>> print output[2]['messages'][0] - traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets - 1 10.48.68.1 (10.48.68.1) 0.377 ms 0.351 ms 0.334 ms - 2 80.239.169.193 (80.239.169.193) 1.206 ms 1.181 ms 1.167 ms - 3 62.115.45.14 (62.115.45.14) 48.143 ms 48.117 ms 48.042 ms - 4 72.14.239.241 (72.14.239.241) 1.596 ms 64.233.175.10 (64.233.175.10) 1.303 ms 72.14.239.239 (72.14.239.239) 1.277 ms - 5 8.8.8.8 (8.8.8.8) 1.551 ms 1.538 ms 0.954 ms - -On the previous example *output[0]* will contain the result of executing the command **enable**, *output[1]* the result of the **ping** command and finally *output[2]*, the result of the **traceroute**. *Note: If you don't specify the command enable as the first command on the list it is added automatically by this API.* - -Here is another example with only one command:: - - >>> output = device.run_commands(['dir /all']) - >>> print output[1]['messages'][0] - Directory of flash:/ - - -r-x 358580934 Aug 12 12:23 .boot-image.swi - drwx 4096 Feb 5 02:31 .extensions - -rwx 306459060 Feb 5 02:29 EOS-4.11.6.swi - -rwx 358580934 Aug 12 12:15 EOS-4.14.1F.swi - -rwx 27 Aug 12 12:21 boot-config - drwx 4096 Aug 12 12:25 debug - drwx 4096 Dec 11 13:39 persist - drwx 4096 Feb 5 02:36 schedule - -rwx 2199 Dec 11 13:39 startup-config - -rwx 0 Aug 4 12:31 zerotouch-config - - 1691504640 bytes total (664113152 bytes free) - -It can be any command supported on the CLI. You could even reload the device, install extensions or upgrade firmware:: - - >>> output = device.run_commands(['reload now']) - >>> print output[1] - {u'_meta': {u'execStartTime': 1418314151.77, u'execDuration': 1.2613}} - -Reloading the device did not give that much feedback but this is what I got on an SSH session I had open:: - - arista-7150s-64-2# - Broadcast message from root@arista-7150s-64-2 - (unknown) at 16:09 ... - - The system is going down for reboot NOW! - Connection to 10.48.71.3 closed by remote host. - Connection to 10.48.71.3 closed. - -Managing Configuration ----------------------- - -You can easily get the configuration and print it:: - - >>> device.load_running_config() - >>> print device.running_config.to_string() - transceiver qsfp default-mode 4x10G - queue-monitor length update-interval 5000000 - hostname eapi-lab - spanning-tree mode mstp - aaa authorization exec default local - no aaa root - username admin privilege 15 role network-admin secret 5 $1$7uXjRZfX$pOFGCCKivNwqIDYFIYbze0 - vrf definition mgmtVRF - rd 65000:65000 - interface Ethernet1 - interface Ethernet2 - description "whatever" - shutdown - no switchport - ip address 10.0.0.1/31 - interface Ethernet3 - description "whatever" - interface Ethernet4 - interface Ethernet5 - interface Ethernet6 - interface Ethernet7 - interface Ethernet8 - interface Ethernet9 - interface Ethernet10 - interface Ethernet11 - interface Ethernet12 - interface Ethernet13 - interface Ethernet14 - interface Ethernet15 - interface Ethernet16 - interface Ethernet17 - interface Ethernet18 - interface Ethernet19 - interface Ethernet20 - interface Ethernet21 - interface Ethernet22 - interface Ethernet23 - interface Ethernet24 - interface Ethernet25 - interface Ethernet26 - interface Ethernet27 - interface Ethernet28 - interface Ethernet29 - interface Ethernet30 - interface Ethernet31 - interface Ethernet32 - interface Ethernet33 - interface Ethernet34 - interface Ethernet35 - interface Ethernet36 - interface Ethernet37 - interface Ethernet38 - interface Ethernet39 - interface Ethernet40 - interface Ethernet41 - interface Ethernet42 - interface Ethernet43 - interface Ethernet44 - interface Ethernet45 - interface Ethernet46 - interface Ethernet47 - interface Ethernet48 - interface Ethernet49/1 - interface Ethernet49/2 - interface Ethernet49/3 - interface Ethernet49/4 - interface Ethernet50/1 - interface Ethernet50/2 - interface Ethernet50/3 - interface Ethernet50/4 - interface Ethernet51/1 - interface Ethernet51/2 - interface Ethernet51/3 - interface Ethernet51/4 - interface Ethernet52/1 - interface Ethernet52/2 - interface Ethernet52/3 - interface Ethernet52/4 - interface Management1 - ip address 10.48.71.3/22 - ip route 0.0.0.0/0 10.48.68.1 - ip route vrf mgmtVRF 0.0.0.0/0 10.48.68.1 - no ip routing - no ip routing vrf mgmtVRF - management api http-commands - no protocol https - protocol http - no shutdown - end - -Or just check an interface configuration:: - - >>> print device.running_config['interface Ethernet2'] - [u'description "whatever"', u'shutdown', u'no switchport', u'ip address 10.0.0.1/31'] - -You can also read configuration from a file, compare the running config with the candidate config:: - - >>> device.load_candidate_config('tests/config.txt') - >>> print device.compare_config() - + hostname NEWHOSTNAME - - hostname eapi-lab - interface Ethernet1 - + description "whatever" - interface Ethernet2 - - shutdown - -You can commit the configuration if you are happy:: - - >>> device.commit() - [{u'_meta': {u'execStartTime': 1418660581.91, u'execDuration': 0.00144815444946}}, {u'messages': [u"enter input line by line; when done enter one or more control-d\n! Preserving static routes. Use 'no ip routing delete-static-routes' to clear them.\n! Preserving static routes. Use 'no ip routing delete-static-routes' to clear them.\n"], u'_meta': {u'execStartTime': 1418660581.91, u'execDuration': 9.50796413422}}] - >>> print device.compare_config() - - >>> - -And even rollback if you regret it:: - - >>> device.rollback() - [{u'_meta': {u'execStartTime': 1418660622.75, u'execDuration': 0.00146913528442}}, {u'messages': [u"enter input line by line; when done enter one or more control-d\n! Preserving static routes. Use 'no ip routing delete-static-routes' to clear them.\n! Preserving static routes. Use 'no ip routing delete-static-routes' to clear them.\n"], u'_meta': {u'execStartTime': 1418660622.75, u'execDuration': 9.6508910656}}] - >>> print device.compare_config() - + hostname NEWHOSTNAME - - hostname eapi-lab - interface Ethernet1 - + description "whatever" - interface Ethernet2 - - shutdown - -Facts ------ - -The API also supports gathering some facts:: - - device.get_facts() - >>> print facts['serial_number'] - JPE14023449 - >>> print facts['system_mac_address'] - 00:1c:73:42:86:b7 - >>> print facts['uptime'] - 1418646951.73 - >>> print facts['model_name'] - DCS-7150S-64-CL-F - >>> print facts['version'] - 4.14.5F - -Facts also include interface details:: - - >>> print facts['interfaces'].keys() - [u'Ethernet8', u'Ethernet9', u'Ethernet2', u'Ethernet3', u'Ethernet1', u'Ethernet6', u'Ethernet7', u'Ethernet4', u'Ethernet5', u'Ethernet52/1', u'Ethernet52/3', u'Ethernet52/2', u'Ethernet52/4', u'Ethernet34', u'Ethernet22', u'Ethernet50/4', u'Ethernet50/3', u'Ethernet50/2', u'Ethernet50/1', u'Ethernet51/4', u'Ethernet51/2', u'Ethernet51/3', u'Ethernet51/1', u'Ethernet38', u'Ethernet39', u'Ethernet18', u'Ethernet19', u'Ethernet32', u'Ethernet15', u'Ethernet16', u'Ethernet31', u'Ethernet49/1', u'Ethernet37', u'Ethernet49/3', u'Ethernet35', u'Ethernet10', u'Ethernet14', u'Ethernet49/2', u'Ethernet33', u'Ethernet49/4', u'Ethernet30', u'Management1', u'Ethernet17', u'Ethernet48', u'Ethernet47', u'Ethernet36', u'Ethernet45', u'Ethernet44', u'Ethernet43', u'Ethernet42', u'Ethernet41', u'Ethernet40', u'Ethernet29', u'Ethernet28', u'Ethernet11', u'Ethernet12', u'Ethernet46', u'Ethernet21', u'Ethernet20', u'Ethernet23', u'Ethernet13', u'Ethernet25', u'Ethernet24', u'Ethernet27', u'Ethernet26'] - >>> print facts['interfaces']['Ethernet2'] - {u'interfaceStatistics': {u'inBitsRate': 0.0, u'updateInterval': 300.0, u'outBitsRate': 0.0, u'outPktsRate': 0.0, u'inPktsRate': 0.0}, u'name': u'Ethernet2', u'duplex': u'duplexFull', u'autoNegotiate': u'off', u'mtu': 1500, u'hardware': u'ethernet', u'interfaceStatus': u'disabled', u'bandwidth': 10000000000, u'forwardingModel': u'routed', u'lineProtocolStatus': u'notPresent', u'interfaceCounters': {u'outBroadcastPkts': 0, u'linkStatusChanges': 0, u'totalOutErrors': 0, u'inMulticastPkts': 0, u'counterRefreshTime': 1418722111.41, u'inBroadcastPkts': 0, u'outputErrorsDetail': {u'collisions': 0, u'lateCollisions': 0, u'txPause': 0, u'deferredTransmissions': 0}, u'outOctets': 0, u'outDiscards': 0, u'inOctets': 0, u'inUcastPkts': 0, u'inputErrorsDetail': {u'runtFrames': 0, u'fcsErrors': 0, u'alignmentErrors': 0, u'rxPause': 0, u'symbolErrors': 0, u'giantFrames': 0}, u'outUcastPkts': 0, u'outMulticastPkts': 0, u'totalInErrors': 0, u'inDiscards': 0}, u'interfaceAddress': [{u'secondaryIpsOrderedList': [], u'secondaryIps': {}, u'broadcastAddress': u'255.255.255.255', u'primaryIp': {u'maskLen': 31, u'address': u'10.0.0.1'}, u'virtualIp': {u'maskLen': 0, u'address': u'0.0.0.0'}}], u'physicalAddress': u'00:1c:73:42:86:b7', u'description': u'"whatever"'} - >>> print facts['interfaces']['Ethernet2']['description'] - "whatever" - >>> print facts['interfaces']['Ethernet2']['forwardingModel'] - routed - >>> print facts['interfaces']['Ethernet2']['interfaceAddress'][0]['primaryIp']['address'] - 10.0.0.1 - >>> print facts['interfaces']['Ethernet2']['interfaceAddress'][0]['primaryIp']['maskLen'] - 31 diff --git a/docs/first_steps_config.rst b/docs/first_steps_config.rst new file mode 100644 index 000000000..73b196156 --- /dev/null +++ b/docs/first_steps_config.rst @@ -0,0 +1,76 @@ +First Steps Manipulating Config +=============================== + +NAPALM does not try to hide any configuration details. It only tries to provide a common interface and mechanisms to push configuration to the device so you can build your way around it. This method is very useful in combination with tools like `Ansible`_. + +Connecting to the device +------------------------ + +Now you can use that driver you got 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() + +Replacing the configuration +--------------------------- + +You can load configuration either from a string or from a file. You can also either merge configuration or replace it entirely. + +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 actually 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() + +Rollback changes +---------------- + +If for some reason you committed the changes and you want to rollback:: + + >>> device.rollback() + +Merging configuration +--------------------- + +Merging configuration is equally easy but you need to load the configuration with another method:: + + >>> device.load_merge_candidate(config='hostname test\ninterface Ethernet2\ndescription bla') + >>> print device.compare_config() + configure + hostname test + interface Ethernet2 + description bla + end + +We can commit and rollback the changes in the same way as before: + + >>> device.commit_config() + >>> device.rollback() \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index e42172d2a..3058bda16 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,10 +1,36 @@ -.. pyEOS documentation master file, created by - sphinx-quickstart on Tue Dec 16 13:17:14 2014. +.. 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 pyEOS's documentation! -================================= +Welcome to NAPALM's documentation! +================================== + +NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support) is python library that implements a common set of functions to interact with different network Operating Systems using a unified API. + +NAPALM supports several methods to connect to the devices, to manipulate configuration or to retrieve data. + +Supported Network Operating System: + + * eos + * junos + * iosxr + +You can get the driver you need by doing the following:: + + >>> from napalm import get_network_driver + >>> get_network_driver('eos') + + >>> get_network_driver('iosxr') + + >>> get_network_driver('junos') + + +Check the tutorials to see how to use the library and the driver section to check which methods are available and some notes regarding each driver. + +Notes on Network Operating Systems +================================== + Tutorials ========= @@ -12,13 +38,13 @@ Tutorials .. toctree:: :maxdepth: 1 - first_steps + first_steps_config -Classes +Drivers ======= .. toctree:: :maxdepth: 2 - eos - eosconf \ No newline at end of file + base + eos \ No newline at end of file diff --git a/napalm/base.py b/napalm/base.py index 87fbfb332..282966157 100644 --- a/napalm/base.py +++ b/napalm/base.py @@ -93,3 +93,17 @@ def rollback(self): If changes were made, revert changes to the original state. """ raise NotImplementedError + + ''' + def get_facts(self): + """ + Returns a dictionary containing the following information: + * uptime - Uptime of the device in seconds. + * vendor - Manufacturer of the device. + * model - Device model. + * os_version - String with the OS version running on the device. + * serial_number - Serial number of the device + * interface_list - List of the interfaces of the device + """ + raise NotImplementedError + ''' \ No newline at end of file diff --git a/napalm/eos.py b/napalm/eos.py index 9738287f4..d79e3b212 100644 --- a/napalm/eos.py +++ b/napalm/eos.py @@ -19,7 +19,8 @@ from exceptions import MergeConfigException, ReplaceConfigException -import datetime +from datetime import datetime +#import time class EOSDriver(NetworkDriver): @@ -61,7 +62,7 @@ def _load_and_test_config(self, filename, config, overwrite): test_config.insert(0, 'configure session test') test_config.append('abort') else: - self.config_session = 'napalm_commit_%s' % datetime.datetime.now().strftime('%Y%m%d-%H%m%s') + self.config_session = 'napalm_commit_%s' % datetime.now().strftime('%Y%m%d-%H%m%s') test_config.insert(0, 'configure session %s' % self.config_session) test_config.append('end') @@ -119,3 +120,20 @@ def rollback(self): self.device.run_commands(['configure replace flash:rollback-0']) self.device.run_commands(['write memory']) self.device.load_candidate_config(config=self.device.get_config(format='text')) + + ''' + def get_facts(self): + output = self.device.show_version() + uptime = time.time() - output['bootupTimestamp'] + + interfaces = self.device.show_interfaces_status()['interfaceStatuses'].keys() + + return { + 'vendor': u'Arista', + 'model': output['modelName'], + 'serial_number': output['serialNumber'], + 'os_version': output['internalVersion'], + 'uptime': uptime, + 'interface_list': interfaces + } + ''' \ No newline at end of file diff --git a/test/unit/base.py b/test/unit/base.py index af2986590..b2ac0da96 100644 --- a/test/unit/base.py +++ b/test/unit/base.py @@ -101,3 +101,21 @@ def test_merge_configuration_typo_and_rollback(self): result = self.device.compare_config() == '' self.assertTrue(result) + + ''' + def test_get_facts(self): + intended_facts = { + 'vendor': unicode, + 'model': unicode, + 'os_version': unicode, + 'serial_number': unicode, + 'uptime': float, + 'interface_list': list + } + facts_dictionary = dict() + + for fact, value in self.device.get_facts().iteritems(): + facts_dictionary[fact] = value.__class__ + + self.assertEqual(facts_dictionary, intended_facts) + ''' \ No newline at end of file