├── .gitignore ├── EEM-interface-move-routes ├── EEM-interface-move-routes.py └── README.md ├── LICENSE ├── NC-edit-config ├── NC-edit-config-hostname.py └── README.md ├── NC-get-config-xpath ├── NC-get-config-xpath.py └── README.md ├── NC-get-config ├── NC-get-config.py └── README.md ├── PortFlap_email_alert ├── EEM_configuration.txt ├── README.md ├── port_flap_email_alert.py └── sample_log_file.txt ├── Py-sho-ver-onbox ├── README.md └── sho-ver.py ├── RC-get-config ├── RC-get-config.py └── README.md ├── RC-get-serial-numbers ├── RC-get-sns.py └── README.md ├── README.md ├── RESTCONF ├── README.md ├── delete-Ip-address.py ├── get-interface-config.py ├── patch-Ip-address-config.py ├── post-ipdomain.py └── put-hostname-config.py ├── acitoolkit_show_tenants ├── README.md └── aci-show-tenants.py ├── apic-em_get_hosts ├── README.md ├── gethosts.py └── requirements.txt ├── apic-em_get_inventory_stats ├── README.md └── apic-em_get_inventory_stats.py ├── eem_configdiff_to_spark ├── README.md ├── sl_config_diff_to_spark.py └── spark_notice1.png ├── model-based-aaa ├── .DS_Store ├── README.md ├── delete-config_user.py ├── edit-config-permit-native.py ├── edit-config-permit-netconf-native.py ├── edit-config-permit-netconf.py ├── edit-config-user-groups.py ├── get-config-nacm.py └── update-config-hostname.py ├── netconf_entity ├── README.md └── nc_entity.py ├── netmiko-interface-example ├── README.md ├── device_info.py ├── netmiko-create-interface.py ├── netmiko-delete-interface.py ├── netmiko-get-interface.py └── requirements.txt ├── restconf_update_ipaddress ├── README.md ├── requirements.txt └── updateip.py ├── snmp_entity ├── README.md └── snmp_entity.py ├── spark_checkin ├── README.md ├── Spark_Message.png ├── requirements.txt └── spark_checkin.py └── tdr-test ├── README.md └── tdr-test.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea 3 | -------------------------------------------------------------------------------- /EEM-interface-move-routes/EEM-interface-move-routes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Jason Frazier 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves entire configuration from a network element via NETCONF 28 | # prints it out in a "pretty" XML tree. 29 | 30 | # importing the cli module is necessary to run 31 | # config or exec commands on the host. 32 | import cli 33 | # importing the time module works well in 34 | # this script, as we are pacing some of the execution. 35 | 36 | import time 37 | # importing the time module works well in this script, as we are pacing 38 | # some of the execution. 39 | 40 | cli.configure('no ip route 10.10.1.2 255.255.255.255 10.90.1.1'); 41 | # CLI route to negate when original event occurs. Idea being, this is the next hop 42 | # on the other end of the interface that went down. 43 | time.sleep(2) 44 | # Sleep, just because. We are doing CLI after all. 45 | cli.configure('ip route 10.10.1.2 255.255.255.255 10.90.1.65'); 46 | # CLI route to add when the original event occurs. Next hop of a backup interface. 47 | -------------------------------------------------------------------------------- /EEM-interface-move-routes/README.md: -------------------------------------------------------------------------------- 1 | # EEM interface-move-routes.py 2 | 3 | This is an example Python script utilizing EEM integration. 4 | 5 | The example EEM is below: 6 | 7 | ``` 8 | event manager applet INTERFACE-DOWN 9 | event syslog pattern "LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet0/0, changed state to down" 10 | action 0.0 cli command "en" 11 | action 1.0 cli command "guestshell run python EEM-interface-routes.py" 12 | ``` 13 | 14 | As you can see, this EEM policy named "INTERFACE-DOWN" looks for a syslog pattern. In this example case, it is a syslog pattern for a specifc interface going down. When EEM sees this, it triggers an exec CLI that executes the onbox Python script named "EEM-interface-routes.py" 15 | 16 | No verification has been built into this, but the script could be extednded as well. 17 | 18 | # requirements 19 | -- IOS-XE running >/= 16.5.1 also enabled for GuestShell 20 | 21 | # running 22 | -- onbox 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019, Cisco Systems, Inc. and/or its affiliates 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NC-edit-config/NC-edit-config-hostname.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Jason Frazier 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves entire configuration from a network element via NETCONF 28 | # prints it out in a "pretty" XML tree. 29 | 30 | import sys 31 | from argparse import ArgumentParser 32 | from ncclient import manager 33 | import xml.dom.minidom 34 | 35 | data = ''' 36 | 37 | 38 | NC-WAS-HERE 39 | 40 | 41 | ''' 42 | 43 | if __name__ == '__main__': 44 | parser = ArgumentParser(description='Select options.') 45 | # Input parameters 46 | parser.add_argument('--host', type=str, required=True, 47 | help="The device IP or DN") 48 | parser.add_argument('-u', '--username', type=str, default='cisco', 49 | help="Go on, guess!") 50 | parser.add_argument('-p', '--password', type=str, default='cisco', 51 | help="Yep, this one too! ;-)") 52 | parser.add_argument('--port', type=int, default=830, 53 | help="Specify this if you want a non-default port") 54 | args = parser.parse_args() 55 | m = manager.connect(host=args.host, 56 | port=args.port, 57 | username=args.username, 58 | password=args.password, 59 | device_params={'name':"csr"}) 60 | # Pretty print the XML reply 61 | xmlDom = xml.dom.minidom.parseString( str( m.edit_config(data, target='running') ) ) 62 | print xmlDom.toprettyxml( indent = " " ) 63 | -------------------------------------------------------------------------------- /NC-edit-config/README.md: -------------------------------------------------------------------------------- 1 | # NETCONF edit-config 2 | 3 | This is an example Python script utilizes edit-config. 4 | 5 | The script calls the edit-config verb in NETCONF, and sets the data in the hostname container to what is specified. 6 | 7 | For fun, you'll realize the script reconfigures the hostname of your network element to "NETCONF-WAS-HERE" 8 | 9 | # requirements 10 | -- ncclient 11 | 12 | -- IOS-XE running >/= 16.5(1) also enabled for NETCONF 13 | 14 | # running 15 | -- Can run on-box or off-box. 16 | -------------------------------------------------------------------------------- /NC-get-config-xpath/NC-get-config-xpath.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Jason Frazier 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves entire configuration from a network element via NETCONF 28 | # prints it out in a "pretty" XML tree. 29 | 30 | import sys 31 | from argparse import ArgumentParser 32 | from ncclient import manager 33 | import xml.dom.minidom 34 | 35 | if __name__ == '__main__': 36 | parser = ArgumentParser(description='Select options.') 37 | # Input parameters 38 | parser.add_argument('--host', type=str, required=True, 39 | help="The device IP or DN") 40 | parser.add_argument('-u', '--username', type=str, default='cisco', 41 | help="Go on, guess!") 42 | parser.add_argument('-p', '--password', type=str, default='cisco', 43 | help="Yep, this one too! ;-)") 44 | parser.add_argument('--port', type=int, default=830, 45 | help="Specify this if you want a non-default port") 46 | args = parser.parse_args() 47 | 48 | m = manager.connect(host=args.host, 49 | port=args.port, 50 | username=args.username, 51 | password=args.password, 52 | device_params={'name':"csr"}) 53 | # Pretty print the XML reply 54 | xmlDom = xml.dom.minidom.parseString( str( m.get_config( source='running', filter=('xpath', '/native/hostname')))) 55 | print xmlDom.toprettyxml( indent = " " ) 56 | 57 | 58 | -------------------------------------------------------------------------------- /NC-get-config-xpath/README.md: -------------------------------------------------------------------------------- 1 | # NETCONF get-config with XPATH 2 | 3 | This is an example Python script that utilizes get-config. It also takes advantage of XPATH filtering for NETCONF available in IOS-XE. 4 | 5 | The script calls the get-config verb in NETCONF, and then filters the request to the native model, and also asks for just the data in the hostname container. The data you get back should be the hostname (or more specifically the configured hostname). 6 | 7 | For a human, this is roughly equivalent to 'show running-config | include hostname' 8 | 9 | # requirements 10 | -- ncclient 11 | 12 | -- IOS-XE running >/= 16.3.1 also enabled for NETCONF 13 | 14 | # running 15 | -- Can run on-box or off-box. 16 | -------------------------------------------------------------------------------- /NC-get-config/NC-get-config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Jason Frazier 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves entire configuration from a network element via NETCONF 28 | # prints it out in a "pretty" XML tree. 29 | 30 | import sys 31 | from argparse import ArgumentParser 32 | from ncclient import manager 33 | import xml.dom.minidom 34 | 35 | if __name__ == '__main__': 36 | parser = ArgumentParser(description='Select options.') 37 | # Input parameters 38 | parser.add_argument('--host', type=str, required=True, 39 | help="The device IP or DN") 40 | parser.add_argument('-u', '--username', type=str, default='cisco', 41 | help="Go on, guess!") 42 | parser.add_argument('-p', '--password', type=str, default='cisco', 43 | help="Yep, this one too! ;-)") 44 | parser.add_argument('--port', type=int, default=830, 45 | help="Specify this if you want a non-default port") 46 | args = parser.parse_args() 47 | 48 | m = manager.connect(host=args.host, 49 | port=args.port, 50 | username=args.username, 51 | password=args.password, 52 | device_params={'name':"csr"}) 53 | 54 | hostname_filter = ''' 55 | 56 | 57 | 58 | 59 | ''' 60 | 61 | # Pretty print the XML reply 62 | xmlDom = xml.dom.minidom.parseString( str( m.get_config('running', hostname_filter))) 63 | print(xmlDom.toprettyxml( indent = " " )) 64 | -------------------------------------------------------------------------------- /NC-get-config/README.md: -------------------------------------------------------------------------------- 1 | # NETCONF get-config 2 | 3 | This is an example Python script that literally just grabs the entire config of a network element. 4 | 5 | It's not just what you would see from the CLI exec command "show running-config". 6 | You'll get everything. From all known open-models, and the native-model (which is the translation of the running config a human is used to). 7 | 8 | # requirements 9 | -- ncclient 10 | 11 | -- IOS-XE running >/= 16.3.1 also enabled for NETCONF 12 | 13 | # running 14 | -- Can run on-box or off-box. 15 | -------------------------------------------------------------------------------- /PortFlap_email_alert/EEM_configuration.txt: -------------------------------------------------------------------------------- 1 | Catalyst_Switch#sh run | sec event manager 2 | event manager applet config_change 3 | event syslog pattern "GigabitEthernet1/0/5, changed state to down" 4 | action 0 cli command "enable" 5 | action 1 cli command "guestshell run python port_flap_email_alert.py" 6 | -------------------------------------------------------------------------------- /PortFlap_email_alert/README.md: -------------------------------------------------------------------------------- 1 | # port_flap_email_alert script will send an email alert if an interface is flapping 2 | 3 | If an interface has flapped 5 times in last 5 minutes this script will shutdown that interface. You can change the flapping count to whatever you want. Then the script will wait for 5 mins and enable the shutdown interface. If that interface still flapping at least once script will shutdown the interface and will send an email alert. 4 | 5 | ## requirements 6 | 7 | -- IOS-XE running >/= 16.5.1 8 | 9 | ## Setup 10 | 11 | * This script requires the IOS-XE guestshell feature. To enable guestshell, configure 12 | 13 | ``` 14 | iox 15 | ``` 16 | 17 | * Then type the following in EXEC mode: 18 | 19 | ``` 20 | guestshell enable 21 | ``` 22 | 23 | * Save the script port_flap_email_alert.py in guestshell. 24 | 25 | * Next, configure the EEM environment using EEM_configuration file. Embedded Event Manager (EEM) is a distributed and customized approach to event detection and recovery. EEM offers the ability to monitor events and take informational, corrective, or any desired EEM action when the monitored events occur or when a threshold is reached. 26 | -------------------------------------------------------------------------------- /PortFlap_email_alert/port_flap_email_alert.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2018 Krishna Kotha 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions 7 | # are met: 8 | # 1. Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # 2. Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | # SUCH DAMAGE. 25 | # 26 | # PURPOSE of this SCRIPT 27 | # The purpose of this script is if a interface flapped 5 times in last 5 minutes it will shutdown that interface. You can change that flapping count to what ever you want. 28 | # Then it will wait for 5 mins and enable that interface. 29 | # If still flapping atleast once it will shutdown that interface and will send an email alert. 30 | # 31 | # 32 | # This script monitors the interface GigabitEthernet1/0/5, change the interface ID based on your requirement. 33 | # 34 | # 35 | # This script requires the following variables to be defined: 36 | # FROM_ADDR 37 | # TO_ADDR 38 | # name-server ip address 39 | # http_proxy and https_proxy commands 40 | # 41 | 42 | # importing necessary modules 43 | import os 44 | import sys 45 | import cli 46 | import time 47 | import difflib 48 | from datetime import datetime 49 | from time import strptime 50 | from time import mktime 51 | import smtplib 52 | import shutil 53 | from email.MIMEMultipart import MIMEMultipart 54 | from email.MIMEText import MIMEText 55 | import subprocess 56 | 57 | 58 | def checkPortFlap(port_flaps_cnt,interface): 59 | 60 | # calculate current time using time module 61 | currTime = time.time() 62 | print("curr time is: %s" % (currTime)) 63 | 64 | # retrieve the show log output 65 | arr_output = cli.cli("show log").split("\n") 66 | 67 | for line in arr_output: 68 | 69 | # In this example I am monitoring the Gi 1/0/5 interface, based on your requirement change the interface ID. 70 | if(line.find("GigabitEthernet1/0/5, changed state to down") != -1): 71 | print("The line %s indicates a port flap action" % (line)) 72 | line_arr = line.split("%") 73 | 74 | # seperate the date from the log lines and convert the matching line to list objects and strings 75 | date_in_log = line_arr[0] 76 | print("the entry date is: %s" % (date_in_log)) 77 | line_arr = line.split(",") 78 | line_arr_1 = line_arr[0].split(" ") 79 | str_line_arr_0=''.join(line_arr_1[0]) 80 | 81 | # remove * and : from the strings 82 | str_line_arr_0= str_line_arr_0[1:] 83 | str_line_arr_1=''.join(line_arr_1[3]) 84 | str_line_arr_1=str_line_arr_1[:-1] 85 | 86 | # parse a string to represent a time according to a format 87 | log_time = time.strptime(str_line_arr_0+" "+line_arr_1[1]+" "+line_arr_1[2]+" "+str_line_arr_1, "%b %d %Y %H:%M:%S") 88 | 89 | # calculate the log time in seconds 90 | log_time_secs = mktime(log_time) 91 | 92 | # calculate the difference from current time to log time 93 | delta=currTime-log_time_secs 94 | interface = line_arr_1[len(line_arr_1) - 1] 95 | print("the interface is: %s" % (interface)) 96 | 97 | # if delta is less than 5 minutes that log will be counted. 98 | if delta <= 300: 99 | port_flaps_cnt = port_flaps_cnt + 1 100 | return port_flaps_cnt, interface 101 | 102 | 103 | 104 | def send_e_mail(subject): 105 | """ 106 | send an e mail from the Cisco network to anyone - 107 | Note: This example uses from address as a Cisco server, Change the domain to your SMTP server to send email from your domain. 108 | """ 109 | 110 | # retrieve the hostname using in-built cli module 111 | host_name = cli.cli("show running-config | include hostname") 112 | FROM_ADDR = 'xxxx@cisco.com' 113 | TO_ADDR = 'xxxx@gmail.com' 114 | 115 | # create the message 116 | msg = MIMEMultipart() 117 | msg['From'] = FROM_ADDR 118 | msg['To'] = TO_ADDR 119 | msg['Subject'] = "%s - %s" % (subject, host_name) 120 | text = "This is an automated e mail message:\n===========================\nAn on-Box Python script running on a Cisco Polaris guestshell device and detected that the %s %s \n===========================\n\n\n===========================\n\n" % (subject, host_name) 121 | msg.attach(MIMEText(text, 'plain')) 122 | 123 | # connect to server and send 124 | server = smtplib.SMTP('outbound.your_company_name.com', 25) 125 | server.sendmail(FROM_ADDR, TO_ADDR, msg.as_string()) 126 | server.quit() 127 | 128 | 129 | def set_device_to_role_as_cisco_mail_server(): 130 | """ 131 | Manipulates the /etc/resolv.conf and set it to a Cisco mail server. The commands are known to the average network Linux admin... 132 | """ 133 | commands_to_run_on_gs = [ 134 | "sudo rm /etc/resolv.conf", 135 | "sudo touch /etc/resolv.conf", 136 | "echo \"nameserver x.x.x.x\" | sudo tee -a /etc/resolv.conf", 137 | "echo \"domain x.x.x.x.com\" | sudo tee -a /etc/resolv.conf", 138 | "echo \"export http_proxy=http://x.x.x.x:80/\" | sudo tee -a /etc/bashrc", 139 | "echo \"export https_proxy=http://x.x.x.x:80/\" | sudo tee -a /etc/bashrc", 140 | "echo \"export ftp_proxy=http://x.x.x.x:80/\" | sudo tee -a /etc/bashrc", 141 | "echo \"export no_proxy=.x.x.x.x.com\" | sudo tee -a /etc/bashrc", 142 | "echo \"export HTTP_PROXY=http://x.x.x.x:80/\" | sudo tee -a /etc/bashrc", 143 | "echo \"export HTTPS_PROXY=http://x.x.x.x:80/\" | sudo tee -a /etc/bashrc", 144 | "echo \"export FTP_PROXY=http://x.x.x.x:80/\" | sudo tee -a /etc/bashrc" 145 | ] 146 | for command in commands_to_run_on_gs: 147 | p = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True) 148 | (output, err) = p.communicate() 149 | print "Linux command output is", output 150 | 151 | 152 | # main code: 153 | if __name__ == '__main__': 154 | 155 | # these commands are need to remove msec and add year in the show log command 156 | cli.configurep("service timestamps log datetime") 157 | cli.configurep("service timestamps log datetime year") 158 | 159 | 160 | port_flaps_cnt = 0 161 | interface = None 162 | port_flaps = checkPortFlap(port_flaps_cnt,interface) 163 | 164 | if(port_flaps[0] == 10): 165 | if(port_flaps[1] is not None): 166 | print("shutting down interface %s" % (port_flaps[1])) 167 | 168 | # shut down the interface using cli module 169 | #cli(["interface %s" % (port_flaps[1]), "shut", "end"]) 170 | cli.configurep(["interface %s" % (port_flaps[1]), "shut", "end"]) 171 | 172 | # wait for 5 mins to monitor the interface flap again 173 | time.sleep(300) 174 | print("Waited for 5 mins Enabling the interface %s" % (port_flaps[1])) 175 | 176 | # enable the interface 177 | #cli("cont f", "interfact %s" % (port_flaps[1]), "no shut", "end") 178 | cli.configurep(["interface %s" % (port_flaps[1]), "no shut", "end"]) 179 | port_flaps_cnt = 0 180 | interface = None 181 | 182 | # re check the interface is flapping or not 183 | port_flaps = checkPortFlap(port_flaps_cnt,interface) 184 | 185 | # if still flapping shut down the interface and send email 186 | if(port_flaps[0] >= 2): 187 | if(port_flaps[1] is not None): 188 | print("shutting down interface %s" % (port_flaps[1])) 189 | #cli("cont f", "interfact %s" % (port_flaps[1]), "shut", "end") 190 | cli.configurep(["interface %s" % (port_flaps[1]), "shut", "end"]) 191 | set_device_to_role_as_cisco_mail_server() 192 | send_e_mail("interface %s is flapping and did admin shutdown" % (port_flaps[1])) 193 | -------------------------------------------------------------------------------- /PortFlap_email_alert/sample_log_file.txt: -------------------------------------------------------------------------------- 1 | Catalyst_Switch#sh log 2 | Syslog logging: enabled (0 messages dropped, 11 messages rate-limited, 0 flushes, 0 overruns, xml disabled, filtering disabled) 3 | 4 | No Active Message Discriminator. 5 | 6 | 7 | 8 | No Inactive Message Discriminator. 9 | 10 | 11 | Console logging: level debugging, 169945 messages logged, xml disabled, 12 | filtering disabled 13 | Monitor logging: level debugging, 39 messages logged, xml disabled, 14 | filtering disabled 15 | Buffer logging: level debugging, 169955 messages logged, xml disabled, 16 | filtering disabled 17 | Exception Logging: size (4096 bytes) 18 | Count and timestamp logging messages: disabled 19 | File logging: disabled 20 | Persistent logging: disabled 21 | 22 | No active filter modules. 23 | 24 | Trap logging: level informational, 169933 message lines logged 25 | Logging Source-Interface: VRF Name: 26 | 27 | Log Buffer (4096 bytes): 28 | 29 | *Apr 19 2018 18:00:06: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet1/0/5, changed state to down 30 | *Apr 19 2018 18:00:07: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/5, changed state to down 31 | *Apr 19 2018 18:00:14: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/5, changed state to up 32 | *Apr 19 2018 18:00:15: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet1/0/5, changed state to up 33 | *Apr 19 2018 18:00:19: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet1/0/5, changed state to down 34 | *Apr 19 2018 18:00:20: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/5, changed state to down 35 | *Apr 19 2018 18:00:29: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/5, changed state to up 36 | *Apr 19 2018 18:00:30: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet1/0/5, changed state to up 37 | *Apr 19 2018 18:00:53: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet1/0/5, changed state to down 38 | *Apr 19 2018 18:00:54: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/5, changed state to down 39 | *Apr 19 2018 18:01:01: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/5, changed state to up 40 | *Apr 19 2018 18:01:02: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet1/0/5, changed state to up 41 | *Apr 19 2018 18:01:07: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet1/0/5, changed state to down 42 | *Apr 19 2018 18:01:08: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/5, changed state to down 43 | *Apr 19 2018 18:01:27: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/5, changed state to up 44 | *Apr 19 2018 18:01:29: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/5, changed state to down 45 | *Apr 19 2018 18:01:50: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/5, changed state to up 46 | *Apr 19 2018 18:01:51: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet1/0/5, changed state to up 47 | *Apr 19 2018 18:02:07: %UTIL-6-RANDOM: A pseudo-random number was generated twice in succession 48 | *Apr 19 2018 18:02:08: %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet1/0/5, changed state to down 49 | *Apr 19 2018 18:02:09: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/5, changed state to down 50 | *Apr 19 2018 18:02:16: %LINK-5-CHANGED: Interface GigabitEthernet1/0/5, changed state to administratively down <<<<<<<<<< Flapped 5 times in last 5 mins EEM script ran and did admin shutdown on the gi 1/0/5 interface. 51 | Catalyst_Switch# 52 | -------------------------------------------------------------------------------- /Py-sho-ver-onbox/README.md: -------------------------------------------------------------------------------- 1 | # Python sho-ver 2 | 3 | This is an example Python script that literally just does "show version" on a network element. 4 | 5 | # requirements 6 | 7 | -- IOS-XE running >/= 16.5 with guestshell enabled. 8 | 9 | # running 10 | -- on-box. 11 | -------------------------------------------------------------------------------- /Py-sho-ver-onbox/sho-ver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Jason Frazier 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script is designed to run on the network element itself. It runs a 28 | # single exec command .. "show version" 29 | 30 | #!/usr/bin/python 31 | 32 | import cli 33 | import sys 34 | 35 | try: 36 | print(cli.execute('show version')) 37 | except Exception as err: 38 | sys.stderr.write("CLI error: {0}\n".format(err)) 39 | sys.exit(1) 40 | -------------------------------------------------------------------------------- /RC-get-config/RC-get-config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Jason Frazier 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves entire configuration from a network element via RESTCONF 28 | # and prints it out in a "pretty" JSON tree. 29 | 30 | from argparse import ArgumentParser 31 | import requests 32 | import urllib3 33 | import json 34 | import sys 35 | import os 36 | from getpass import getpass 37 | from pprint import pprint 38 | 39 | if __name__ == '__main__': 40 | 41 | # Disable SSL Warnings 42 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 43 | 44 | parser = ArgumentParser(description='Select options.') 45 | 46 | # Input parameters 47 | parser.add_argument('-host', '--host', type=str, required=True, 48 | help="The device IP or DN") 49 | parser.add_argument('-user', '--username', type=str, default='cisco', 50 | help="User credentials for the request") 51 | parser.add_argument('-port', '--port', type=int, default=443, 52 | help="Specify this if you want a non-default port") 53 | 54 | args = parser.parse_args() 55 | 56 | username = args.username 57 | password = os.getenv('DEVNET_RESTCONF_PASSWORD') 58 | if password is None: 59 | password = getpass() 60 | host = args.host 61 | port = str(args.port) 62 | 63 | url = "https://" + host + ":" + port + "/restconf/data/Cisco-IOS-XE-native:native" 64 | 65 | headers = { 66 | "Content-Type": "application/yang-data+json", 67 | "Accept": "application/yang-data+json", 68 | } 69 | 70 | try: 71 | response = requests.request("GET", url, headers=headers, auth=(username,password), verify=False) 72 | response.raise_for_status() 73 | except Exception as e: 74 | print(e, file=sys.stderr) 75 | sys.exit(1) 76 | 77 | pprint(response.json()) 78 | -------------------------------------------------------------------------------- /RC-get-config/README.md: -------------------------------------------------------------------------------- 1 | # RESTCONF get-config 2 | 3 | This is an example Python script that literally just grabs the entiere config of a network element. 4 | 5 | It's not just what you would see from the CLI exec command "show running-config". 6 | You'll get everything. From all known open-models, and the native-model (which is the translation of the running config a human is used to). 7 | 8 | # requirements 9 | -- IOS-XE running >/= 16.3.1 also enabled for RESTCONF 10 | 11 | # running 12 | -- Can run on-box or off-box. -------------------------------------------------------------------------------- /RC-get-serial-numbers/RC-get-sns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2019 Joe Clarke 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves inventory from devices with RESTCONF and prints all serial 28 | # numbers per device. 29 | # 30 | 31 | import requests 32 | from argparse import ArgumentParser 33 | 34 | 35 | def main(): 36 | 37 | parser = ArgumentParser(description='Select options.') 38 | 39 | # Input parameters 40 | parser.add_argument('-hosts', '--hosts', type=str, required=True, 41 | help="Comma-separated list of devices") 42 | parser.add_argument('-user', '--username', type=str, default='cisco', 43 | help="User credentials for the request") 44 | parser.add_argument('-passwd', '--password', type=str, default='cisco', 45 | help="It's the password") 46 | 47 | args = parser.parse_args() 48 | url = 'https://{}/restconf/data/Cisco-IOS-XE-device-hardware-oper:device-hardware-data/device-hardware' 49 | inv_cache = {} 50 | 51 | hosts = args.hosts.split(',') 52 | 53 | for host in hosts: 54 | 55 | u = url.format(host) 56 | 57 | headers = { 58 | 'Accept': "application/yang-data+json", 59 | } 60 | 61 | response = None 62 | 63 | try: 64 | response = requests.request('GET', u, auth=( 65 | args.username, args.password), headers=headers, verify=False) 66 | response.raise_for_status() 67 | except Exception as e: 68 | print('Failed to get inventory from device: {}'.format(e)) 69 | continue 70 | 71 | inv = response.json() 72 | 73 | for asset in inv['Cisco-IOS-XE-device-hardware-oper:device-hardware']['device-inventory']: 74 | if host not in inv_cache: 75 | inv_cache[host] = [] 76 | 77 | if asset['serial-number'] == '': 78 | continue 79 | 80 | inv_cache[host].append( 81 | {'sn': asset['serial-number'], 'pn': asset['part-number']}) 82 | 83 | for host, comps in inv_cache.items(): 84 | print('Host {} serial numbers:'.format(host)) 85 | for comp in comps: 86 | print('\t{}'.format(comp['sn'])) 87 | 88 | 89 | if __name__ == '__main__': 90 | main() 91 | -------------------------------------------------------------------------------- /RC-get-serial-numbers/README.md: -------------------------------------------------------------------------------- 1 | # RESTCONF get-serial-numbers 2 | 3 | This is an example Python script that retrieves the serial numbers of IOS-XE devices and prints 4 | each device with its collection of serial numbers. 5 | 6 | # requirements 7 | -- IOS-XE running >/= 16.3.1 also enabled for RESTCONF 8 | 9 | # running 10 | -- Can run on-box or off-box. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Network Automation with Python Code Samples 2 | 3 | A collection of Python Code Samples for Network Management. Includes samples that leverage on-box libraries, as well as samples that use exposed external APIs (NETCONF/RESTCONF, SNMP, SSH, REST, etc). Some examples make use of available SDKs. 4 | 5 | ## On-Box Examples 6 | 7 | Many Cisco switches and routers provide an on-box Python Interpreter that can be leveraged to execute scripts and programs directly on end devices. In addition to the interpreter, Python libraries are included that provide direct access to the underlying devices operations to execute CLI commands, or monitor for events. 8 | 9 | ### Sample Code 10 | 11 | | Code Sample | Description | 12 | | --- | --- | 13 | | [Execute CLI via Python](/Py-sho-ver-onbox) | This example is about as simple as it gets. By leveraging the CLI library, we execute the “show version” command on the box. | 14 | | [TDR Test Every Interface](/tdr-test) | This example once again leverages the CLI library, but to do something a bit more interesting. A TDR test is run on every interface in “up” status. | 15 | | [EEM Config Changes to Spark](/eem_configdiff_to_spark) | In this example, the EEM library is used to monitor for configuration changes. When one occurs a message is sent to a Cisco Spark Room. | 16 | | [Python with Eventing Example](/EEM-interface-move-routes) | Use the EEM and Python together to script based on local events. | 17 | | [EEM + Python + Spark ChatOps](/spark_checkin) | Use the EEM to monitor for config changes and send a Spark Message | 18 | | [EEM + Python + Email alert](/PortFlap_email_alert) | This example leverages the CLI library and using the EEM feature to monitor for interface flapping and send an email alert | 19 | 20 | 21 | ## Off-Box Examples 22 | 23 | Here are few Python scripts that can interact with network elements using one of the many exposed interfaces (NETCONF, RESTCONF, SNMP, SSH, etc). Many of these scripts could also be run on-box, however they don’t leverage any of the unique libraries available on device. 24 | 25 | | Code Sample | Description | 26 | | --- | --- | 27 | | [Netmiko and CLI Example for Interface Management](/netmiko-interface-example) | These are a series of python scripts for retrieving, creating, deleting a Loopback Interface with Python. | 28 | | [MIB Walk with Python](/snmp_entity) | In this example, we perform a MIB walk against a device leveraging the “netsnmp” library for Python. | 29 | | [NETCONF Connection with Python](/netconf_entity) | This example shows the basics of connecting to a device with NETCONF using the “ncclient” library for Python. | 30 | | [Configure Interface IP Address with RESTCONF](/restconf_update_ipaddress) | In this example the newly ratified RESTCONF standard is used to configure the IP Address on an interface. | 31 | | [Get Inventory from APIC-EM](/apic-em_get_inventory_stats) | APIC-EM maintains an inventory database of the entire network. In this example Python is used to retrieve that information using the REST API. | 32 | | [Get Host List from APIC-EM](/apic-em_get_hosts) | APIC-EM maintains a list of all clients connected to the network devices discovered by APIC-EM. This example queries the APIC-EM for the list, and display’s it in a simple table. | 33 | | [Retrieve Tenants from ACI APIC](/acitoolkit_show_tenants) | This example leverages the ACI Toolkit to connect to an APIC controller and retrieve the list of Tenants configured. | 34 | | [Basic NETCONF Get](/NC-get-config) | A basic ncclient example to `` NETCONF Data | 35 | | [Basic NETCONF Edit](/NC-edit-config) | A basic ncclient example to `` NETCONF Data | 36 | | [NETCONF XPATH Example](/NC-get-config-xpath) | Use the XPATH feature when making a NETCONF Requests | 37 | | [Model Based AAA](/model-based-aaa) | These example scripts are for Model Based AAA to get, edit and delete the rule-lists for privilege level users and Groups by using ietf-netconf-acm.yang data model | 38 | | [RESTCONF](/RESTCONF) | These example scripts are for RESTCONF to retrieve and configure the switch using different operations such as Get, Delete, Put, Post and Patch. | 39 | -------------------------------------------------------------------------------- /RESTCONF/README.md: -------------------------------------------------------------------------------- 1 | # RESTCONF 2 | 3 | The RESTCONF is a standard protocol uses YANG data models for managing network devices. It is a protocol based on HTTP to access the configuration data or state data of a networking device. RESTCONF supports operations such as Get, Put, Post, Patch, and Delete. One of the major advantages RESTCONF has over NETCONF is its ability to leverage JSON as a data format. To make the RESTCONF calls, you can use any client application that supports any REST call. 4 | 5 | These are examples scripts for RESTCONF to retrieve and configure the switch using different operations such as Get, Delete, Post, Put and Patch. 6 | 7 | ## requirements 8 | 9 | -- IOS-XE running >/= 16.8 also enabled for RESTCONF 10 | 11 | -------------------------------------------------------------------------------- /RESTCONF/delete-Ip-address.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright (c) 2018 Krishna Kotha 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script deletes the Ip address of a interface using DELETE operation via RESTCONF 28 | 29 | # import the requests library 30 | import requests 31 | import sys 32 | 33 | # disable warnings from SSL/TLS certificates 34 | requests.packages.urllib3.disable_warnings() 35 | 36 | # use the IP address or hostname of your Cat9300 37 | HOST = '172.26.198.63' 38 | 39 | # use your user credentials to access the Cat9300 40 | USER = 'cisco' 41 | PASS = 'cisco' 42 | 43 | 44 | # create a main() method 45 | def main(): 46 | """Main method that configures the Ip address for a interface via RESTCONF.""" 47 | 48 | # url string to issue GET request 49 | url = "https://172.26.198.63/restconf/data/Cisco-IOS-XE-native:native/interface/TenGigabitEthernet=1%2F0%2F10/ip/address/primary" 50 | 51 | # RESTCONF media types for REST API headers 52 | headers = {'Content-Type': 'application/yang-data+json', 53 | 'Accept': 'application/yang-data+json'} 54 | # this statement performs a DELETE on the specified url 55 | response = requests.request("DELETE",url, auth=(USER, PASS), 56 | headers=headers, verify=False) 57 | # print the json that is returned 58 | print(response.text) 59 | 60 | if __name__ == '__main__': 61 | sys.exit(main()) 62 | -------------------------------------------------------------------------------- /RESTCONF/get-interface-config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright (c) 2018 Krishna Kotha 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves entire interface configuration from a network element via RESTCONF 28 | 29 | # import the requests library 30 | import requests 31 | import sys 32 | 33 | # disable warnings from SSL/TLS certificates 34 | requests.packages.urllib3.disable_warnings() 35 | 36 | # use the IP address or hostname of your Cat9300 37 | HOST = '172.26.198.63' 38 | 39 | # use your user credentials to access the Cat9300 40 | USER = 'cisco' 41 | PASS = 'cisco' 42 | 43 | 44 | # create a main() method 45 | def main(): 46 | """Main method that retrieves the Interface details from Cat9300 via RESTCONF.""" 47 | 48 | # url string to issue GET request 49 | url = "https://{h}/restconf/data/ietf-interfaces:interfaces".format(h=HOST) 50 | 51 | # RESTCONF media types for REST API headers 52 | headers = {'Content-Type': 'application/yang-data+json', 53 | 'Accept': 'application/yang-data+json'} 54 | # this statement performs a GET on the specified url 55 | response = requests.get(url, auth=(USER, PASS), 56 | headers=headers, verify=False) 57 | 58 | # print the json that is returned 59 | print(response.text) 60 | 61 | if __name__ == '__main__': 62 | sys.exit(main()) 63 | -------------------------------------------------------------------------------- /RESTCONF/patch-Ip-address-config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright (c) 2018 Krishna Kotha 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script configures Ip address of a interface using PATCH operation via RESTCONF 28 | 29 | # import the requests library 30 | import requests 31 | import sys 32 | 33 | # disable warnings from SSL/TLS certificates 34 | requests.packages.urllib3.disable_warnings() 35 | 36 | # use the IP address or hostname of your Cat9300 37 | HOST = '172.26.198.63' 38 | 39 | # use your user credentials to access the Cat9300 40 | USER = 'cisco' 41 | PASS = 'cisco' 42 | 43 | 44 | # create a main() method 45 | def main(): 46 | """Main method that configures the Ip address for a interface via RESTCONF.""" 47 | 48 | # url string to issue GET request 49 | url = "https://{h}/restconf/data/Cisco-IOS-XE-native:native/interface/TenGigabitEthernet=1%2F0%2F10/ip/address/primary".format(h=HOST) 50 | payload = "{\"primary\": {\"address\": \"100.100.100.1\", \"mask\": \"255.255.255.0\"}}" 51 | 52 | # RESTCONF media types for REST API headers 53 | headers = {'Content-Type': 'application/yang-data+json', 54 | 'Accept': 'application/yang-data+json'} 55 | # this statement performs a PATCH on the specified url 56 | response = requests.request("PATCH",url, auth=(USER, PASS), 57 | data=payload, headers=headers, verify=False) 58 | 59 | # print the json that is returned 60 | print(response.text) 61 | 62 | if __name__ == '__main__': 63 | sys.exit(main()) 64 | -------------------------------------------------------------------------------- /RESTCONF/post-ipdomain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright (c) 2018 Krishna Kotha 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script configures IP domain cisco.com using POST operation via RESTCONF 28 | 29 | # import the requests library 30 | import requests 31 | import sys 32 | 33 | # disable warnings from SSL/TLS certificates 34 | requests.packages.urllib3.disable_warnings() 35 | 36 | # use the IP address or hostname of your Cat9300 37 | HOST = '172.26.198.63' 38 | 39 | # use your user credentials to access the Cat9300 40 | USER = 'cisco' 41 | PASS = 'cisco' 42 | 43 | 44 | # create a main() method 45 | def main(): 46 | """Main method that configures the Ip address for a interface via RESTCONF.""" 47 | 48 | # url string to issue GET request 49 | url = "https://{h}/restconf/data/Cisco-IOS-XE-native:native/ip/domain".format(h=HOST) 50 | payload = "{\"name\": \"cisco.com\"}" 51 | 52 | # RESTCONF media types for REST API headers 53 | headers = {'Content-Type': 'application/yang-data+json', 54 | 'Accept': 'application/yang-data+json'} 55 | # this statement performs a POST on the specified url 56 | response = requests.request("POST",url, auth=(USER, PASS), 57 | data=payload, headers=headers, verify=False) 58 | 59 | # print the json that is returned 60 | print(response.text) 61 | 62 | if __name__ == '__main__': 63 | sys.exit(main()) 64 | -------------------------------------------------------------------------------- /RESTCONF/put-hostname-config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright (c) 2018 Krishna Kotha 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script configures hostname of a network element using PUT operation via RESTCONF 28 | 29 | # import the requests library 30 | import requests 31 | import sys 32 | 33 | # disable warnings from SSL/TLS certificates 34 | requests.packages.urllib3.disable_warnings() 35 | 36 | # use the IP address or hostname of your Cat9300 37 | HOST = '172.26.198.63' 38 | 39 | # use your user credentials to access the Cat9300 40 | USER = 'cisco' 41 | PASS = 'cisco' 42 | 43 | 44 | # create a main() method 45 | def main(): 46 | """Main method that configures the Ip address for a interface via RESTCONF.""" 47 | 48 | # url string to issue GET request 49 | url = "https://{h}/restconf/data/Cisco-IOS-XE-native:native/hostname".format(h=HOST) 50 | payload = "{\"hostname\": \"CATALYST9300\"}" 51 | 52 | # RESTCONF media types for REST API headers 53 | headers = {'Content-Type': 'application/yang-data+json', 54 | 'Accept': 'application/yang-data+json'} 55 | # this statement performs a PUT on the specified url 56 | response = requests.request("PUT",url, auth=(USER, PASS), 57 | data=payload, headers=headers, verify=False) 58 | 59 | # print the json that is returned 60 | print(response.text) 61 | 62 | if __name__ == '__main__': 63 | sys.exit(main()) 64 | -------------------------------------------------------------------------------- /acitoolkit_show_tenants/README.md: -------------------------------------------------------------------------------- 1 | # Using the ACI Toolkit in Python 2 | 3 | This example script shows how you can leverage the [ACI Toolkit](https://github.com/datacenter/acitoolkit) as part of a Python script connecting to an APIC Controller. 4 | 5 | This basic script simply connects to the APIC and displays the list of tenants and is based on the sample [aci-show-tenants.py](https://github.com/datacenter/acitoolkit/blob/master/samples/aci-show-tenants.py) script from the toolkit. 6 | 7 | ## ACI Toolkit Installation 8 | 9 | To run this script, you'll need to have installed the ACI Toolkit in your working Python environment. See [github.com/datacenter/acitoolkit](https://github.com/datacenter/acitoolkit) for installation instructions. 10 | 11 | ## DevNet Sandbox 12 | 13 | This script targets the Always On ACI Simulator DevNet Sandbox. 14 | 15 | Find details on the Sandbox [here](https://developer.cisco.com/docs/sandbox/#!data-center). 16 | 17 | To execute this script against a different device, update the variables that list the URL, User and Password for the APIC. 18 | 19 | ## Requirements 20 | 21 | * Python 2.7 22 | * acitoolkit 23 | 24 | # Getting Started 25 | 26 | * Clone the Python Examples and change into the directory. 27 | 28 | ```bash 29 | git clone https://github.com/CiscoDevNet/python_code_samples_network 30 | cd acitoolkit_show_tenants 31 | ``` 32 | 33 | * Run the script 34 | 35 | ```bash 36 | $ python aci-show-tenants.py 37 | 38 | TENANT 39 | ------ 40 | Tenant_Max 41 | common 42 | infra 43 | SB-T01 44 | mgmt 45 | ``` 46 | -------------------------------------------------------------------------------- /acitoolkit_show_tenants/aci-show-tenants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Simple application that logs on to the APIC and displays all 4 | of the Tenants. 5 | 6 | Leverages the DevNet Sandbox - APIC Simulator Always On 7 | Information at https://developer.cisco.com/docs/sandbox/#!data-center 8 | 9 | Code sample based off the ACI-Toolkit Code sample 10 | 11 | https://github.com/datacenter/acitoolkit/blob/master/samples/aci-show-tenants.py 12 | """ 13 | 14 | 15 | import sys 16 | import acitoolkit.acitoolkit as ACI 17 | 18 | # Credentials and information for the DevNet ACI Simulator Always-On Sandbox 19 | APIC_URL = "https://sandboxapicdc.cisco.com/" 20 | APIC_USER = "admin" 21 | APIC_PASSWORD = "C1sco12345" 22 | 23 | def main(): 24 | """ 25 | Main execution routine 26 | :return: None 27 | """ 28 | 29 | # Login to APIC 30 | session = ACI.Session(APIC_URL, APIC_USER, APIC_PASSWORD) 31 | resp = session.login() 32 | if not resp.ok: 33 | print('%% Could not login to APIC') 34 | sys.exit(0) 35 | 36 | # Download all of the tenants 37 | print("TENANT") 38 | print("------") 39 | tenants = ACI.Tenant.get(session) 40 | for tenant in tenants: 41 | print(tenant.name) 42 | 43 | if __name__ == '__main__': 44 | main() 45 | -------------------------------------------------------------------------------- /apic-em_get_hosts/README.md: -------------------------------------------------------------------------------- 1 | # Get Hosts List from APIC-EM 2 | 3 | This example script uses the requests Python library to interact with the APIC-EM and retrieve the list of clients connected. 4 | 5 | This script has been tested with Python 3.5, however may work with other versions. 6 | 7 | ## DevNet Sandbox 8 | 9 | This script targets the Always On APIC-EM DevNet Sandbox. 10 | 11 | Find details on the Sandbox [here](https://developer.cisco.com/docs/sandbox/#!networking). 12 | 13 | To execute this script against a different device, update the variables that list the APIC-EM IP, User and Password. 14 | 15 | ## Requirements 16 | 17 | Python 18 | 19 | - requests 20 | 21 | # Getting Started 22 | 23 | * Clone the Python Examples and change into the directory. 24 | 25 | ```bash 26 | git clone https://github.com/CiscoDevNet/python_code_samples_network 27 | cd apic-em_get_hosts 28 | ``` 29 | 30 | * Create and activate a virtualenv 31 | 32 | ```bash 33 | virtualenv venv --python=python3.5 34 | source venv/bin/activate 35 | ``` 36 | 37 | * Install the requirements 38 | 39 | ```bash 40 | pip install -r requirements.txt 41 | ``` 42 | 43 | * Run the script 44 | 45 | ``` 46 | $ python gethosts.py 47 | Client List from APIC-EM 48 | IP Address MAC Address Type 49 | 10.1.15.117 00:24:d7:43:59:d8 wireless 50 | 10.2.1.22 5c:f9:dd:52:07:78 wired 51 | 10.1.12.20 e8:9a:8f:7a:22:99 wired 52 | ``` 53 | 54 | -------------------------------------------------------------------------------- /apic-em_get_hosts/gethosts.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Simple application that logs on to the APIC-EM and displays all 4 | of the clients connected. 5 | 6 | Leverages the DevNet Sandbox - APIC-EM Always On 7 | Information at https://developer.cisco.com/docs/sandbox/#!networking 8 | 9 | """ 10 | 11 | 12 | # Import necessary modules 13 | import requests 14 | 15 | # Disable warnings 16 | requests.packages.urllib3.disable_warnings() 17 | 18 | # Variables 19 | apic_em_ip = "https://sandboxapic.cisco.com/api/v1" 20 | apic_em_user = "devnetuser" 21 | apic_em_password = "Cisco123!" 22 | 23 | def get_token(url): 24 | 25 | # Define API Call 26 | api_call = "/ticket" 27 | 28 | # Payload contains authentication information 29 | payload = {"username": apic_em_user, "password": apic_em_password} 30 | 31 | # Header information 32 | headers = {"content-type": "application/json"} 33 | 34 | # Combine URL, API call and parameters variables 35 | url += api_call 36 | 37 | response = requests.post(url, json=payload, headers=headers, verify=False).json() 38 | 39 | # Return authentication token from respond body 40 | return response["response"]["serviceTicket"] 41 | 42 | def get_hosts(token, url): 43 | # Define API Call 44 | api_call = "/host" 45 | 46 | # Header information 47 | headers = {"X-AUTH-TOKEN": token} 48 | 49 | # Combine URL, API call and parameters variables 50 | url += api_call 51 | 52 | response = requests.get(url, headers=headers, verify=False).json() 53 | 54 | # Get hosts list from response and return 55 | hosts = response["response"] 56 | return hosts 57 | 58 | 59 | # Assign obtained authentication token to a variable. Provide APIC-EM's 60 | # URL address 61 | auth_token = get_token(apic_em_ip) 62 | 63 | # Get list of hosts 64 | hosts = get_hosts(auth_token, apic_em_ip) 65 | 66 | # Display in table 67 | print("Client List from APIC-EM") 68 | print("{ip:20} {mac:20} {type:10}".format(ip="IP Address", 69 | mac="MAC Address", 70 | type="Type")) 71 | 72 | for host in hosts: 73 | print("{ip:20} {mac:20} {type:10}".format(ip=host["hostIp"], 74 | mac=host["hostMac"], 75 | type=host["hostType"])) 76 | -------------------------------------------------------------------------------- /apic-em_get_hosts/requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.0 2 | packaging==16.8 3 | pyparsing==2.1.10 4 | requests>=2.20.0 5 | six==1.10.0 6 | -------------------------------------------------------------------------------- /apic-em_get_inventory_stats/README.md: -------------------------------------------------------------------------------- 1 | # APIC-EM: Print some inventory statistics from APIC-EM 2 | 3 | This script uses the APIC-EM REST API to grab the list of devices, and then prints 4 | out the list of device families, total counts of devices per family, model names 5 | per family, and software versions per model. 6 | 7 | For example: 8 | 9 | ``` 10 | $ ./apic-em_get_inventory_stats.py --hostname 10.1.1.2 --username admin --password Cisco123 11 | Routers (total: 3) 12 | CISCO891W-AGN-A-K9 (total: 1) 13 | 15.4(2)T1 (total: 1) 14 | CSR1000V (total: 1) 15 | 16.4.1 (total: 1) 16 | CISCO1941/K9 (total: 1) 17 | 15.6(2)T2 (total: 1) 18 | 19 | Switches and Hubs (total: 1) 20 | WS-C3560V2-24TS-E (total: 1) 21 | 15.0(2)SE10 (total: 1) 22 | ... 23 | ``` 24 | 25 | ## Setup 26 | 27 | This script has been tested with Python 2.7, and requires the following non-default module(s): 28 | 29 | * ncclient (pip install ncclient) 30 | -------------------------------------------------------------------------------- /apic-em_get_inventory_stats/apic-em_get_inventory_stats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Joe Clarke 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script prints inventory stats from APIC-EM including device families, 28 | # total devices in each family, device models, and number of different 29 | # software revisions per model. 30 | # 31 | 32 | import requests 33 | from requests import Request, Session 34 | import argparse 35 | import re 36 | import sys 37 | import time 38 | import json 39 | import operator 40 | 41 | DEBUG = False 42 | REST_TIMEOUT = 300 43 | REST_RETRY_INTERVAL = 1 44 | REST_RETRIES = 3 45 | REST_PAGE_LIMIT = 500 46 | 47 | 48 | class RestError: 49 | code = -1 50 | msg = "" 51 | 52 | def set_code(self, code): 53 | self.code = code 54 | 55 | def set_msg(self, msg): 56 | self.msg = msg 57 | 58 | def get_code(self): 59 | return self.code 60 | 61 | def get_msg(self): 62 | return self.msg 63 | 64 | 65 | def fetch_url(url, error, p=False, timeout=REST_TIMEOUT): 66 | global DEBUG 67 | 68 | s = Session() 69 | requests.packages.urllib3.disable_warnings() 70 | req = 'GET' 71 | data = {} 72 | headers = {} 73 | params = {} 74 | 75 | if 'get' in p: 76 | params = p['get'] 77 | if 'post' in p: 78 | req = 'POST' 79 | data = p['post'] 80 | if 'put' in p: 81 | req = 'PUT' 82 | if 'header' not in p: 83 | p['header'] = {} 84 | p['header']['Content-Length'] = len(p['put']) 85 | data = p['put'] 86 | if 'header' in p: 87 | headers = p['header'] 88 | 89 | req = Request(req, url, data=data, params=params, headers=headers) 90 | ph = req.prepare() 91 | 92 | if DEBUG: 93 | print('DEBUG: Requested URL {}'.format(url)) 94 | print('DEBUG: Headers = ') 95 | for h in ph.headers: 96 | print(' {} : {}'.format(h, ph.headers[h])) 97 | print('DEBUG: Parameters = ') 98 | for par, val in params.items(): 99 | print(' {} : {}'.format(par, val)) 100 | print('DEBUG: Body = {}'.format(ph.body)) 101 | 102 | res = s.send(ph, verify=False, timeout=timeout) 103 | 104 | if res.status_code > 299: 105 | if DEBUG: 106 | print('DEBUG: The server returned code: {} response: {}'.format( 107 | str(res.status_code), res.text)) 108 | print('DEBUG: Response Headers: ') 109 | for h in res.headers: 110 | print(' {} : {}'.format(h, res.headers[h])) 111 | error.set_code(res.status_code) 112 | j = res.json() 113 | error.set_msg(j['response']['message']) 114 | return None 115 | else: 116 | return res.text 117 | 118 | 119 | def get_device_count(host, port, ticket, error): 120 | global REST_RETRIES, REST_RETRY_INTERVAL 121 | 122 | url = 'https://{}:{}/api/v1/network-device/count'.format(host, port) 123 | p = {} 124 | p['header'] = {"X-Auth-Token": ticket} 125 | 126 | i = 0 127 | res = None 128 | while i < REST_RETRIES: 129 | res = fetch_url(url, error, p) 130 | if res is not None: 131 | break 132 | time.sleep(REST_RETRY_INTERVAL) 133 | i = i + 1 134 | if res is not None: 135 | j = json.loads(res) 136 | return j['response'] 137 | 138 | return None 139 | 140 | 141 | def get_ticket(host, port, user, password, error): 142 | global REST_RETRIES, REST_RETRY_INTERVAL 143 | 144 | url = 'https://{}:{}/api/v1/ticket'.format(host, port) 145 | p = {} 146 | 147 | p['post'] = json.dumps({"username": user, "password": password}) 148 | p['header'] = {"Content-Type": "application/json"} 149 | 150 | i = 0 151 | res = None 152 | while i < REST_RETRIES: 153 | res = fetch_url(url, error, p) 154 | if res is not None: 155 | break 156 | time.sleep(REST_RETRY_INTERVAL) 157 | i = i + 1 158 | if res is not None: 159 | j = json.loads(res) 160 | return j['response']['serviceTicket'] 161 | 162 | return None 163 | 164 | 165 | def get_inventory(host, port, ticket, error): 166 | global REST_RETRIES, REST_RETRY_INTERVAL, REST_PAGE_LIMIT 167 | 168 | url = 'https://{}:{}/api/v1/network-device'.format(host, port) 169 | p = {} 170 | 171 | p['header'] = {"X-Auth-Token": ticket} 172 | 173 | count = get_device_count(host, port, ticket, error) 174 | if count is None: 175 | return None 176 | 177 | offset = 1 178 | limit = REST_PAGE_LIMIT 179 | p['get'] = {} 180 | devs = [] 181 | while True: 182 | p['get']['offset'] = offset 183 | p['get']['limit'] = limit 184 | 185 | i = 0 186 | res = None 187 | while i < REST_RETRIES: 188 | res = fetch_url(url, error, p) 189 | if res is not None: 190 | break 191 | time.sleep(REST_RETRY_INTERVAL) 192 | i = i + 1 193 | if res is not None: 194 | j = json.loads(res) 195 | devs += j['response'] 196 | 197 | offset += limit 198 | if offset >= count: 199 | break 200 | 201 | return devs 202 | 203 | 204 | if __name__ == '__main__': 205 | error = RestError() 206 | families = {} 207 | models = {} 208 | 209 | parser = argparse.ArgumentParser( 210 | prog=sys.argv[0], description='Print inventory stats from APIC-EM') 211 | parser.add_argument('--hostname', '-a', type=str, 212 | help='APIC-EM hostname or IP address', required=True) 213 | parser.add_argument('--username', '-u', type=str, 214 | help='APIC-EM API username', required=True) 215 | parser.add_argument('--password', '-p', type=str, 216 | help='APIC-EM API password', required=True) 217 | parser.add_argument('--port', '-P', type=int, 218 | help='APIC-EM web port (default: 443)') 219 | parser.add_argument('--debug', '-d', action='store_true', 220 | help='Enable debugging (default: False)') 221 | parser.set_defaults(debug=False, port=443) 222 | args = parser.parse_args() 223 | 224 | if args.debug: 225 | DEBUG = args.debug 226 | 227 | ticket = get_ticket(args.hostname, args.port, 228 | args.username, args.password, error) 229 | if ticket is None: 230 | print('Error obtaining RBAC ticket from APIC-EM: {}'.format(error.get_msg())) 231 | sys.exit(1) 232 | 233 | inventory = get_inventory(args.hostname, args.port, ticket, error) 234 | if inventory is None: 235 | print('Error obtaining inventory from APIC-EM: {}'.format(error.get_msg())) 236 | sys.exit(1) 237 | 238 | for dev in inventory: 239 | if dev['family'] not in families: 240 | families[dev['family']] = {} 241 | 242 | if dev['platformId'] not in families[dev['family']]: 243 | families[dev['family']][dev['platformId']] = {} 244 | 245 | if dev['softwareVersion'] not in families[dev['family']][dev['platformId']]: 246 | families[dev['family']][dev['platformId']][ 247 | dev['softwareVersion']] = 1 248 | else: 249 | families[dev['family']][dev['platformId']][ 250 | dev['softwareVersion']] += 1 251 | 252 | fam_sort = sorted(families.items(), key=operator.itemgetter(0)) 253 | for fams in fam_sort: 254 | family = fams[0] 255 | model = fams[1] 256 | total = 0 257 | mod_sort = sorted(model.items(), key=lambda x: sum(x[1].values())) 258 | for m in model.values(): 259 | total += sum(m.values()) 260 | print('{} (total: {})'.format(family, total)) 261 | for mods in mod_sort: 262 | m = mods[0] 263 | c = mods[1] 264 | print(' {} (total: {})'.format(m, sum(c.values()))) 265 | 266 | c_sort = sorted( 267 | c.items(), key=operator.itemgetter(1), reverse=True) 268 | for revs in c_sort: 269 | code = revs[0] 270 | count = revs[1] 271 | print(' {} (total: {})'.format(code, count)) 272 | print('') 273 | -------------------------------------------------------------------------------- /eem_configdiff_to_spark/README.md: -------------------------------------------------------------------------------- 1 | # EEM: Config Diff to Cisco Spark 2 | 3 | An EEM+Python policy that pushes config change diffs to Spark. 4 | 5 | ![](spark_notice1.png) 6 | 7 | ## Setup 8 | 9 | This script requires the IOS-XE _guestshell_ feature. To enable guestshell, configure: 10 | 11 | ``` 12 | iox 13 | ``` 14 | 15 | Then type the following in `EXEC` mode: 16 | 17 | ``` 18 | guestshell enable 19 | ``` 20 | 21 | Guestshell may not include the needed `requests` module. Enter guestshell with the following command: 22 | 23 | ``` 24 | guestshell 25 | ``` 26 | 27 | From the guestshell prompt, run the following command: 28 | 29 | ``` 30 | sudo pip install requests 31 | ``` 32 | 33 | If this returns an error about not being able to establish a connection to download the module, you 34 | may need to update your DNS settings within guestshell. If you know your DNS server, you can use 35 | that address. If you don't, just use 8.8.8.8. Run the command: 36 | 37 | ``` 38 | sudo echo 'nameserver DNSSERVER' > /etc/resolv.conf 39 | ``` 40 | 41 | Where DNSSERVER is the IP address of your DNS server (or 8.8.8.8). After doing that, repeat 42 | the `pip` command, and it should install successfully. If `pip` tells you `requests` is already 43 | installed, then your guestshell environment is good. You can type `exit` to return to IOS-XE. 44 | 45 | **NOTE** The guestshell environment will persist across reboots. It will not revert to the default 46 | state unless you do a `guestshell destory` followed by another `guestshell enable`. 47 | 48 | Next, define the following EEM environment. Be sure **NOT** to put quotes around the variable 49 | values: 50 | 51 | * `spark_token` : Bearer token for your Spark user/bot 52 | * `spark_room` : Spark room name to which messages will be sent 53 | 54 | ``` 55 | event manager environment spark_token Bearer 1234abd... 56 | event manager environment spark_room Network Operators 57 | ``` 58 | Once the environment variables have been defined, copy the script to the EEM user policy 59 | directory. If you have not defined an EEM user policy directory yet, a good choice is 60 | to create a directory called `flash:/policies` in which to store EEM policies. Once 61 | the directory has been created, configure: 62 | 63 | ``` 64 | event manager directory user policy flash:policies 65 | ``` 66 | 67 | Once your policy has been copied into that directory, register it with the following 68 | command: 69 | 70 | ``` 71 | event manager policy sl_config_diff_to_spark.py 72 | ``` 73 | 74 | Once configuration changes start to happen, check your specified Spark room for updates. 75 | -------------------------------------------------------------------------------- /eem_configdiff_to_spark/sl_config_diff_to_spark.py: -------------------------------------------------------------------------------- 1 | ::cisco::eem::event_register_syslog pattern "CONFIG_I" maxrun 60 2 | # 3 | # Copyright (c) 2017 Joe Clarke 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script requires the following EEM environment variables to be defined: 28 | # 29 | # spark_token : Bearer token for your Spark user/bot 30 | # spark_room : Spark room name to which messages will be sent 31 | # device_name : Device name from which the messages will be sent 32 | # 33 | # E.g.: 34 | # 35 | # event manager environment spark_token Bearer 1234abd... 36 | # event manager environment spark_room Network Operators 37 | # event manager environment device_name C3850 38 | # 39 | 40 | import eem 41 | import sys 42 | import os 43 | import requests 44 | import re 45 | 46 | CFG_BAK_PY = '/flash/running-config.bak' 47 | CFG_BAK_IOS = 'flash:/running-config.bak' 48 | SPARK_API = 'https://api.ciscospark.com/v1/' 49 | 50 | # Get the CLI event variables for this specific event. 51 | arr_einfo = eem.event_reqinfo() 52 | # Get the environment variables 53 | arr_envinfo = eem.env_reqinfo() 54 | 55 | if 'spark_token' not in arr_envinfo: 56 | eem.action_syslog( 57 | 'Environment variable "spark_token" must be set', priority='3') 58 | sys.exit(1) 59 | if 'spark_room' not in arr_envinfo: 60 | eem.action_syslog( 61 | 'Environment variable "spark_room" must be set', priority='3') 62 | sys.exit(1) 63 | if 'device_name' not in arr_envinfo: 64 | eem.action_syslog( 65 | 'Environment variable "device_name" must be set', priority='3') 66 | sys.exit(1) 67 | 68 | # Get a CLI handle 69 | cli = eem.cli_open() 70 | eem.cli_exec(cli, 'enable') 71 | 72 | if not os.path.isfile(CFG_BAK_PY): 73 | try: 74 | eem.cli_write(cli, 'copy runn {}'.format(CFG_BAK_IOS)) 75 | prom = eem.cli_read_pattern(cli, '(filename|#)') 76 | if re.search(r'filename', prom): 77 | eem.cli_exec(cli, '\r') 78 | except Exception as e: 79 | eem.action_syslog('Failed to backup configuration to {}: {}'.format( 80 | CFG_BAK_IOS, e), priority='3') 81 | sys.exit(1) 82 | # First time through, only save the current config 83 | eem.cli_close(cli) 84 | sys.exit(0) 85 | 86 | res = None 87 | try: 88 | res = eem.cli_exec( 89 | cli, 'show archive config diff {} system:running-config'.format(CFG_BAK_IOS)) 90 | os.remove(CFG_BAK_PY) 91 | eem.cli_write(cli, 'copy runn {}'.format(CFG_BAK_IOS)) 92 | prom = eem.cli_read_pattern(cli, 'filename') 93 | if re.search(r'filename', prom): 94 | eem.cli_exec(cli, '\r') 95 | except Exception as e: 96 | eem.action_syslog( 97 | 'Failed to get config differences: {}'.format(e), priority='3') 98 | sys.exit(1) 99 | 100 | eem.cli_close(cli) 101 | 102 | diff_lines = re.split(r'\r?\n', res) 103 | if re.search('No changes were found', res): 104 | # No differences found 105 | sys.exit(0) 106 | 107 | device_name = arr_envinfo['device_name'] 108 | msg = '### Alert: Config changed on ' + device_name + '\n' 109 | msg += 'Configuration differences between the running config and last backup:\n' 110 | msg += '```{}```'.format('\n'.join(diff_lines[:-1])) 111 | 112 | headers = { 113 | 'authorization': arr_envinfo['spark_token'], 114 | 'content-type': 'application/json' 115 | } 116 | 117 | # Get the Spark room ID 118 | url = SPARK_API + 'rooms' 119 | 120 | r = None 121 | try: 122 | r = requests.request('GET', url, headers=headers) 123 | r.raise_for_status() 124 | except Exception as e: 125 | eem.action_syslog( 126 | 'Failed to get list of Spark rooms: {}'.format(e), priority='3') 127 | sys.exit(1) 128 | 129 | room_id = None 130 | for room in r.json()['items']: 131 | if room['title'] == arr_envinfo['spark_room']: 132 | room_id = room['id'] 133 | break 134 | 135 | if room_id is None: 136 | eem.action_syslog('Failed to find room ID for {}'.format( 137 | arr_envinfo['spark_room'])) 138 | sys.exit(1) 139 | 140 | # Post the message to Spark 141 | url = SPARK_API + 'messages' 142 | 143 | payload = {'roomId': room_id, 'markdown': msg} 144 | 145 | try: 146 | r = requests.request( 147 | 'POST', url, json=payload, headers=headers) 148 | r.raise_for_status() 149 | except Exception as e: 150 | eem.action_syslog( 151 | 'Error posting message to Spark: {}'.format(e), priority='3') 152 | sys.exit(1) 153 | -------------------------------------------------------------------------------- /eem_configdiff_to_spark/spark_notice1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CiscoDevNet/python_code_samples_network/a9f38e4e9cb291a629fc484e34c096fb6b91e627/eem_configdiff_to_spark/spark_notice1.png -------------------------------------------------------------------------------- /model-based-aaa/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CiscoDevNet/python_code_samples_network/a9f38e4e9cb291a629fc484e34c096fb6b91e627/model-based-aaa/.DS_Store -------------------------------------------------------------------------------- /model-based-aaa/README.md: -------------------------------------------------------------------------------- 1 | # Model Based AAA 2 | 3 | The NETCONF and RESTCONF are industry standard protocols uses YANG data models for managing network devices. These protocols do not provide any mechanism for authorizing a user with different privilege levels. Every NETCONF or RESTCONF user is a super user with privilege level 15. 4 | 5 | NETCONF Access Control Model is a form of role-based access control (RBAC) specified in RFC 6536 can provide rules for privilege levels. A user can be authorized with aaa new-model and the privilege level is determined for that user, in the absence of aaa new-model configuration the locally configured privilege level is used. Using NACM you can set rules to that privilege level to control what to access for that user. It is a group-based authorization scheme for data and operations modeled in YANG. 6 | 7 | These are examples scripts for the Model Based AAA to retrieve, edit and delete the rules for a privilege level by using ietf-netconf-acm.yang data model. There are also examples for configuring and deleting users in a group. 8 | 9 | ## requirements 10 | 11 | -- ncclient 12 | -- IOS-XE running >/= 16.8 also enabled for NETCONF 13 | 14 | -------------------------------------------------------------------------------- /model-based-aaa/delete-config_user.py: -------------------------------------------------------------------------------- 1 | """ 2 | #!/usr/bin/env python 3 | # 4 | # Copyright (c) 2017 Krishna Kotha 5 | # All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions 9 | # are met: 10 | # 1. Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # 2. Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | # SUCH DAMAGE. 27 | # 28 | # This script retrieves entire configuration from a network element via NETCONF 29 | # prints it out in a "pretty" XML tree.) 30 | # 31 | # Installing python dependencies: 32 | # > pip install lxml ncclient 33 | # 34 | # Running script: (save as example.py) 35 | # > python example.py -a 172.26.198.63 -u cisco -p cisco --port 830 36 | """ 37 | 38 | import lxml.etree as ET 39 | from argparse import ArgumentParser 40 | from ncclient import manager 41 | from ncclient.operations import RPCError 42 | 43 | payload = """ 44 | 45 | 46 | 47 | priv04-group 48 | 49 | 50 | 51 | """ 52 | 53 | if __name__ == '__main__': 54 | 55 | parser = ArgumentParser(description='Usage:') 56 | 57 | # script arguments 58 | parser.add_argument('-a', '--host', type=str, required=True, 59 | help="Device IP address or Hostname") 60 | parser.add_argument('-u', '--username', type=str, required=True, 61 | help="Device Username (netconf agent username)") 62 | parser.add_argument('-p', '--password', type=str, required=True, 63 | help="Device Password (netconf agent password)") 64 | parser.add_argument('--port', type=int, default=830, 65 | help="Netconf agent port") 66 | args = parser.parse_args() 67 | 68 | # connect to netconf agent 69 | with manager.connect(host=args.host, 70 | port=args.port, 71 | username=args.username, 72 | password=args.password, 73 | timeout=90, 74 | hostkey_verify=False, 75 | device_params={'name': 'csr'}) as m: 76 | 77 | # execute netconf operation 78 | try: 79 | response = m.edit_config(target='running', config=payload).xml 80 | data = ET.fromstring(response) 81 | except RPCError as e: 82 | data = e._raw 83 | 84 | # beautify output 85 | print(ET.tostring(data, pretty_print=True)) -------------------------------------------------------------------------------- /model-based-aaa/edit-config-permit-native.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Krishna Kotha 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves entire configuration from a network element via NETCONF 28 | # prints it out in a "pretty" XML tree.) 29 | # 30 | # Installing python dependencies: 31 | # > pip install lxml ncclient 32 | # 33 | # Running script: (save as example.py) 34 | # > python example.py --host 172.26.198.63 -u cisco -p cisco 35 | 36 | import sys 37 | from argparse import ArgumentParser 38 | from ncclient import manager 39 | import xml.dom.minidom 40 | 41 | data = ''' 42 | 43 | 44 | 45 | priv04-group 46 | PRIV04 47 | 48 | permit-read-native 49 | Cisco-IOS-XE-native 50 | read 51 | permit 52 | 53 | 54 | 55 | 56 | ''' 57 | 58 | if __name__ == '__main__': 59 | parser = ArgumentParser(description='Select options.') 60 | # Input parameters 61 | parser.add_argument('--host', type=str, required=True, 62 | help="The device IP or DN") 63 | parser.add_argument('-u', '--username', type=str, default='cisco', 64 | help="Go on, guess!") 65 | parser.add_argument('-p', '--password', type=str, default='cisco', 66 | help="Yep, this one too! ;-)") 67 | parser.add_argument('--port', type=int, default=830, 68 | help="Specify this if you want a non-default port") 69 | args = parser.parse_args() 70 | m = manager.connect(host=args.host, 71 | port=args.port, 72 | username=args.username, 73 | password=args.password, 74 | device_params={'name':"csr"}) 75 | # Pretty print the XML reply 76 | xmlDom = xml.dom.minidom.parseString( str( m.edit_config(data, target='running') ) ) 77 | print xmlDom.toprettyxml( indent = " " ) -------------------------------------------------------------------------------- /model-based-aaa/edit-config-permit-netconf-native.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Krishna Kotha 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves entire configuration from a network element via NETCONF 28 | # prints it out in a "pretty" XML tree.) 29 | # 30 | # Installing python dependencies: 31 | # > pip install lxml ncclient 32 | # 33 | # Running script: (save as example.py) 34 | # > python example.py --host 172.26.198.63 -u cisco -p cisco 35 | 36 | import sys 37 | from argparse import ArgumentParser 38 | from ncclient import manager 39 | import xml.dom.minidom 40 | 41 | data = ''' 42 | 43 | 44 | 45 | priv04-group 46 | PRIV04 47 | 48 | permit-netconf-rpc 49 | ietf-netcon 50 | exec 51 | permit 52 | 53 | 54 | permit-read-native 55 | Cisco-IOS-XE-native 56 | read 57 | permit 58 | 59 | 60 | 61 | 62 | ''' 63 | 64 | if __name__ == '__main__': 65 | parser = ArgumentParser(description='Select options.') 66 | # Input parameters 67 | parser.add_argument('--host', type=str, required=True, 68 | help="The device IP or DN") 69 | parser.add_argument('-u', '--username', type=str, default='cisco', 70 | help="Go on, guess!") 71 | parser.add_argument('-p', '--password', type=str, default='cisco', 72 | help="Yep, this one too! ;-)") 73 | parser.add_argument('--port', type=int, default=830, 74 | help="Specify this if you want a non-default port") 75 | args = parser.parse_args() 76 | m = manager.connect(host=args.host, 77 | port=args.port, 78 | username=args.username, 79 | password=args.password, 80 | device_params={'name':"csr"}) 81 | # Pretty print the XML reply 82 | xmlDom = xml.dom.minidom.parseString( str( m.edit_config(data, target='running') ) ) 83 | print xmlDom.toprettyxml( indent = " " ) -------------------------------------------------------------------------------- /model-based-aaa/edit-config-permit-netconf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Krishna Kotha 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves entire configuration from a network element via NETCONF 28 | # prints it out in a "pretty" XML tree.) 29 | # 30 | # Installing python dependencies: 31 | # > pip install lxml ncclient 32 | # 33 | # Running script: (save as example.py) 34 | # > python example.py --host 172.26.198.63 -u cisco -p cisco 35 | 36 | import sys 37 | from argparse import ArgumentParser 38 | from ncclient import manager 39 | import xml.dom.minidom 40 | 41 | data = ''' 42 | 43 | 44 | 45 | priv04-group 46 | PRIV04 47 | 48 | permit-netconf-rpc 49 | ietf-netconf 50 | exec 51 | permit 52 | 53 | 54 | 55 | 56 | ''' 57 | 58 | if __name__ == '__main__': 59 | parser = ArgumentParser(description='Select options.') 60 | # Input parameters 61 | parser.add_argument('--host', type=str, required=True, 62 | help="The device IP or DN") 63 | parser.add_argument('-u', '--username', type=str, default='cisco', 64 | help="Go on, guess!") 65 | parser.add_argument('-p', '--password', type=str, default='cisco', 66 | help="Yep, this one too! ;-)") 67 | parser.add_argument('--port', type=int, default=830, 68 | help="Specify this if you want a non-default port") 69 | args = parser.parse_args() 70 | m = manager.connect(host=args.host, 71 | port=args.port, 72 | username=args.username, 73 | password=args.password, 74 | device_params={'name':"csr"}) 75 | # Pretty print the XML reply 76 | xmlDom = xml.dom.minidom.parseString( str( m.edit_config(data, target='running') ) ) 77 | print xmlDom.toprettyxml( indent = " " ) 78 | -------------------------------------------------------------------------------- /model-based-aaa/edit-config-user-groups.py: -------------------------------------------------------------------------------- 1 | """ 2 | #!/usr/bin/env python 3 | # 4 | # Copyright (c) 2017 Krishna Kotha 5 | # All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions 9 | # are met: 10 | # 1. Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # 2. Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | # SUCH DAMAGE. 27 | # 28 | # This script retrieves entire configuration from a network element via NETCONF 29 | # prints it out in a "pretty" XML tree.) 30 | # 31 | # Installing python dependencies: 32 | # > pip install lxml ncclient 33 | # 34 | # Running script: (save as example.py) 35 | # > python example.py -a 172.26.198.63 -u cisco -p cisco --port 830 36 | """ 37 | 38 | import lxml.etree as ET 39 | from argparse import ArgumentParser 40 | from ncclient import manager 41 | from ncclient.operations import RPCError 42 | 43 | payload = """ 44 | 45 | 46 | 47 | 48 | users 49 | user1 50 | user2 51 | user3 52 | 53 | 54 | 55 | 56 | """ 57 | 58 | if __name__ == '__main__': 59 | 60 | parser = ArgumentParser(description='Usage:') 61 | 62 | # script arguments 63 | parser.add_argument('-a', '--host', type=str, required=True, 64 | help="Device IP address or Hostname") 65 | parser.add_argument('-u', '--username', type=str, required=True, 66 | help="Device Username (netconf agent username)") 67 | parser.add_argument('-p', '--password', type=str, required=True, 68 | help="Device Password (netconf agent password)") 69 | parser.add_argument('--port', type=int, default=830, 70 | help="Netconf agent port") 71 | args = parser.parse_args() 72 | 73 | # connect to netconf agent 74 | with manager.connect(host=args.host, 75 | port=args.port, 76 | username=args.username, 77 | password=args.password, 78 | timeout=90, 79 | hostkey_verify=False, 80 | device_params={'name': 'csr'}) as m: 81 | 82 | # execute netconf operation 83 | try: 84 | response = m.edit_config(target='running', config=payload).xml 85 | data = ET.fromstring(response) 86 | except RPCError as e: 87 | data = e._raw 88 | 89 | # beautify output 90 | print(ET.tostring(data, pretty_print=True)) -------------------------------------------------------------------------------- /model-based-aaa/get-config-nacm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Krishna Kotha 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves entire configuration from a network element via NETCONF 28 | # prints it out in a "pretty" XML tree.) 29 | # 30 | # Installing python dependencies: 31 | # > pip install lxml ncclient 32 | # 33 | # Running script: (save as example.py) 34 | # > python example.py --host 172.26.198.63 -u cisco -p cisco 35 | 36 | import sys 37 | from argparse import ArgumentParser 38 | from ncclient import manager 39 | import xml.dom.minidom 40 | 41 | if __name__ == '__main__': 42 | parser = ArgumentParser(description='Select options.') 43 | # Input parameters 44 | parser.add_argument('--host', type=str, required=True, 45 | help="The device IP or DN") 46 | parser.add_argument('-u', '--username', type=str, default='cisco', 47 | help="Go on, guess!") 48 | parser.add_argument('-p', '--password', type=str, default='cisco', 49 | help="Yep, this one too! ;-)") 50 | parser.add_argument('--port', type=int, default=830, 51 | help="Specify this if you want a non-default port") 52 | args = parser.parse_args() 53 | 54 | m = manager.connect(host=args.host, 55 | port=args.port, 56 | username=args.username, 57 | password=args.password, 58 | device_params={'name':"csr"}) 59 | 60 | hostname_filter = ''' 61 | 62 | 63 | 64 | ''' 65 | 66 | # Pretty print the XML reply 67 | xmlDom = xml.dom.minidom.parseString( str( m.get_config('running', hostname_filter))) 68 | print(xmlDom.toprettyxml( indent = " " )) 69 | -------------------------------------------------------------------------------- /model-based-aaa/update-config-hostname.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Krishna Kotha 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves entire configuration from a network element via NETCONF 28 | # prints it out in a "pretty" XML tree.) 29 | # 30 | # Installing python dependencies: 31 | # > pip install lxml ncclient 32 | # 33 | # Running script: (save as example.py) 34 | # > python example.py --host 172.26.198.63 -u cisco -p cisco 35 | 36 | 37 | import sys 38 | from argparse import ArgumentParser 39 | from ncclient import manager 40 | import xml.dom.minidom 41 | 42 | data = ''' 43 | 44 | 45 | 46 | priv04-group 47 | PRIV04 48 | 49 | permit-hostname 50 | /native/hostname 51 | update 52 | permit 53 | 54 | 55 | 56 | 57 | """ 58 | ''' 59 | 60 | if __name__ == '__main__': 61 | parser = ArgumentParser(description='Select options.') 62 | # Input parameters 63 | parser.add_argument('--host', type=str, required=True, 64 | help="The device IP or DN") 65 | parser.add_argument('-u', '--username', type=str, default='cisco', 66 | help="Go on, guess!") 67 | parser.add_argument('-p', '--password', type=str, default='cisco', 68 | help="Yep, this one too! ;-)") 69 | parser.add_argument('--port', type=int, default=830, 70 | help="Specify this if you want a non-default port") 71 | args = parser.parse_args() 72 | m = manager.connect(host=args.host, 73 | port=args.port, 74 | username=args.username, 75 | password=args.password, 76 | device_params={'name':"csr"}) 77 | # Pretty print the XML reply 78 | xmlDom = xml.dom.minidom.parseString( str( m.edit_config(data, target='running') ) ) 79 | print xmlDom.toprettyxml( indent = " " ) -------------------------------------------------------------------------------- /netconf_entity/README.md: -------------------------------------------------------------------------------- 1 | # NETCONF: Print the ENTITY-MIB tree from a device 2 | 3 | This is an off-box Python script that uses ncclient to get the ENTITY-MIB 4 | tree from a device and print it as "pretty" XML. 5 | 6 | For example: 7 | 8 | ``` 9 | $ nc_entity.py -a 10.1.1.1 -u admin -p admin --port 830 10 | 11 | 12 | 13 | 14 | 114619 15 | 16 | 17 | 18 | 1 19 | Cisco CSR1000V Chassis 20 | 1.3.6.1.4.1.9.12.3.1.3.1165 21 | 0 22 | ... 23 | ``` 24 | 25 | ## Setup 26 | 27 | This script has been tested with Python 2.7, and requires the following non-default module(s): 28 | 29 | * ncclient (pip install ncclient) 30 | 31 | This script also requires SNMP to be enabled on the target device. Use the configuration command 32 | `netconf-yang cisco-ia snmp-community-string COMMUNITY` to set the NETCONF community string to 33 | be the same as the read-only string you use on your device. Note: SNMPv3 is not supported. 34 | -------------------------------------------------------------------------------- /netconf_entity/nc_entity.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Joe Clarke 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves the ENTITY-MIB tree from a device via NETCONF and 28 | # prints it out in a "pretty" XML tree. 29 | # 30 | 31 | from ncclient import manager 32 | import xml.dom.minidom 33 | import logging 34 | import argparse 35 | import sys 36 | 37 | if __name__ == '__main__': 38 | parser = argparse.ArgumentParser( 39 | prog=sys.argv[0], description='Print ENTITY-MIB data via NETCONF from a device') 40 | 41 | parser.add_argument('-a', '--host', type=str, required=True, 42 | help="Device IP address or Hostname") 43 | parser.add_argument('-u', '--username', type=str, required=True, 44 | help="Device Username (NETCONF server username)") 45 | parser.add_argument('-p', '--password', type=str, required=True, 46 | help="Device Password (NETCONF server password)") 47 | parser.add_argument('--port', type=int, default=830, 48 | help="Netconf agent port") 49 | parser.add_argument('-d', '--debug', action='store_true', 50 | help="Enable ncclient debugging") 51 | 52 | parser.set_defaults(debug=False) 53 | args = parser.parse_args() 54 | 55 | if args.debug: 56 | logging.basicConfig(level=logging.DEBUG) 57 | 58 | with manager.connect_ssh(host=args.host, port=args.port, username=args.username, hostkey_verify=False, password=args.password) as m: 59 | entity_filter = ''' 60 | 61 | 62 | 63 | ''' 64 | 65 | try: 66 | c = m.get(entity_filter).data_xml 67 | print(xml.dom.minidom.parseString(c).toprettyxml()) 68 | except Exception as e: 69 | print('Failed to execute RPC: {}'.format(e)) 70 | -------------------------------------------------------------------------------- /netmiko-interface-example/README.md: -------------------------------------------------------------------------------- 1 | # Using Netmiko to work with Interfaces 2 | 3 | These example scripts use the Netmiko Python library to interact with a device through CLI 4 | 5 | These Python scripts leverages Netmiko to: 6 | - Create Loopback 103 7 | - Retrieve details about Loopback 103 8 | - Delete Loopback 103 9 | 10 | This script has been tested with Python 3.6, however may work with other versions. 11 | 12 | ## DevNet Sandbox 13 | 14 | This script targets the [IOS XE DevNet Always On Sandbox](https://devnetsandbox.cisco.com/RM/Diagram/Index/27d9747a-db48-4565-8d44-df318fce37ad?diagramType=Topology) that leverages a CSR1000v as a target. 15 | 16 | To execute this script against a different device, create a new device dictionary in `device_info.py` for your device. Then import this new dictionary instead of `ios_xe1` in the scripts. 17 | 18 | ## Requirements 19 | 20 | Python 21 | 22 | - netmiko 23 | 24 | # Getting Started 25 | 26 | * Clone the Python Examples and change into the directory. 27 | 28 | ```bash 29 | git clone https://github.com/CiscoDevNet/python_code_samples_network.git 30 | cd python_code_samples_network 31 | cd netmiko-interface-example 32 | ``` 33 | 34 | * Create and activate a virtualenv 35 | 36 | ```bash 37 | Windows - recommendation to use git-bash terminal 38 | py -3 -m venv venv 39 | source venv/Scripts/activate 40 | 41 | MacOS or Linux 42 | python3.6 -m venv venv 43 | source venv/bin/activate 44 | 45 | ``` 46 | 47 | * Install the requirements 48 | 49 | ```bash 50 | pip install -r requirements.txt 51 | ``` 52 | 53 | * Run the scripts 54 | 55 | ``` 56 | # Run the get script to retrieve interface 57 | $ python netmiko-get-interface.py 58 | 59 | # Output - Interface not there yet... ^ 60 | % Invalid input detected at '^' marker. 61 | 62 | There was an error, Loopback103 might not exist. 63 | 64 | # Run the create script to create interface 65 | $ python netmiko-create-interface.py 66 | 67 | # Output - what was configured 68 | The following configuration was sent: 69 | config term 70 | Enter configuration commands, one per line. End with CNTL/Z. 71 | csr1000v(config)#interface Loopback103 72 | csr1000v(config-if)#description Demo interface by CLI and netmiko 73 | csr1000v(config-if)#ip address 192.168.103.1 255.255.255.0 74 | csr1000v(config-if)#no shut 75 | csr1000v(config-if)#end 76 | csr1000v# 77 | 78 | # Run the get script again 79 | $ python netmiko-get-interface.py 80 | 81 | # Output - there it is! 82 | Building configuration... 83 | 84 | Current configuration : 116 bytes 85 | ! 86 | interface Loopback103 87 | description Demo interface by CLI and netmiko 88 | ip address 192.168.103.1 255.255.255.0 89 | end 90 | 91 | The interface Loopback103 has ip address 192.168.103.1/255.255.255.0 92 | 93 | # Run the delete script to remove the interface 94 | $ python netmiko-delete-interface.py 95 | 96 | # Output - what was sent to remove the interface 97 | The following configuration was sent: 98 | config term 99 | Enter configuration commands, one per line. End with CNTL/Z. 100 | csr1000v(config)#no interface Loopback103 101 | csr1000v(config)#end 102 | csr1000v# 103 | ``` 104 | -------------------------------------------------------------------------------- /netmiko-interface-example/device_info.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """Device Details for DevNet Sandboxes 3 | 4 | This script is imported into other code. 5 | 6 | Copyright (c) 2018 Cisco and/or its affiliates. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | """ 26 | 27 | __author__ = "Hank Preston" 28 | __author_email__ = "hapresto@cisco.com" 29 | __copyright__ = "Copyright (c) 2016 Cisco Systems, Inc." 30 | __license__ = "MIT" 31 | 32 | # DevNet Always-On NETCONF/YANG & RESTCONF Sandbox Device 33 | # https://devnetsandbox.cisco.com/RM/Diagram/Index/27d9747a-db48-4565-8d44-df318fce37ad?diagramType=Topology 34 | ios_xe1 = { 35 | "address": "ios-xe-mgmt.cisco.com", 36 | "netconf_port": 10000, 37 | "restconf_port": 9443, 38 | "ssh_port": 8181, 39 | "username": "root", 40 | "password": "D_Vay!_10&", 41 | "device_type": "cisco_ios" 42 | } 43 | # DevNet Always-On NETCONF/YANG & RESTCONF Sandbox Device 44 | # https://devnetsandbox.cisco.com/RM/Diagram/Index/7b4d4209-a17c-4bc3-9b38-f15184e53a94?diagramType=Topology 45 | # try this one if you can't access the previous one 46 | ios_xe_latest = { 47 | "address": "sandbox-iosxe-latest-1.cisco.com", 48 | "netconf_port": 830, 49 | "restconf_port": 443, 50 | "ssh_port": 22, 51 | "username": "admin", 52 | "password": "C1sco12345", 53 | "device_type": "cisco_ios" 54 | } 55 | # DevNet Always-On Sandbox NX-OS 56 | # 57 | nxos1 = { 58 | "address": "sbx-nxos-mgmt.cisco.com", 59 | "netconf_port": 10000, 60 | "restconf_port": 443, 61 | "ssh_port": 818122, 62 | "username": "admin", 63 | "password": "Admin_1234!", 64 | "device_type": "cisco_nxos" 65 | } 66 | 67 | # Sample GitHub Editor Comment. 68 | -------------------------------------------------------------------------------- /netmiko-interface-example/netmiko-create-interface.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """Sample use of the netmiko library for CLI interfacing 3 | 4 | This script will create new configuration on a device. 5 | 6 | Copyright (c) 2018 Cisco and/or its affiliates. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | """ 26 | 27 | # Import libraries 28 | from netmiko import ConnectHandler 29 | 30 | from device_info import ios_xe1 as device # noqa 31 | 32 | # New Loopback Details 33 | loopback = {"int_name": "Loopback103", 34 | "description": "Demo interface by CLI and netmiko", 35 | "ip": "192.168.103.1", 36 | "netmask": "255.255.255.0"} 37 | 38 | # Create a CLI configuration 39 | interface_config = [ 40 | "interface {}".format(loopback["int_name"]), 41 | "description {}".format(loopback["description"]), 42 | "ip address {} {}".format(loopback["ip"], loopback["netmask"]), 43 | "no shut" 44 | ] 45 | 46 | # Open CLI connection to device 47 | with ConnectHandler(ip = device["address"], 48 | port = device["ssh_port"], 49 | username = device["username"], 50 | password = device["password"], 51 | device_type = device["device_type"]) as ch: 52 | 53 | # Send configuration to device 54 | output = ch.send_config_set(interface_config) 55 | 56 | # Print the raw command output to the screen 57 | print("The following configuration was sent: ") 58 | print(output) 59 | -------------------------------------------------------------------------------- /netmiko-interface-example/netmiko-delete-interface.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """Sample use of the netmiko library for CLI interfacing 3 | 4 | This script will delete configuration on a device. 5 | 6 | Copyright (c) 2018 Cisco and/or its affiliates. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | """ 26 | 27 | # Import libraries 28 | from netmiko import ConnectHandler 29 | 30 | from device_info import ios_xe1 as device # noqa 31 | 32 | # New Loopback Details 33 | loopback = {"int_name": "Loopback103"} 34 | 35 | # Create a CLI configuration 36 | interface_config = [ 37 | "no interface {}".format(loopback["int_name"]) 38 | ] 39 | 40 | # Open CLI connection to device 41 | with ConnectHandler(ip = device["address"], 42 | port = device["ssh_port"], 43 | username = device["username"], 44 | password = device["password"], 45 | device_type = device["device_type"]) as ch: 46 | 47 | # Send configuration to device 48 | output = ch.send_config_set(interface_config) 49 | 50 | # Print the raw command output to the screen 51 | print("The following configuration was sent: ") 52 | print(output) 53 | -------------------------------------------------------------------------------- /netmiko-interface-example/netmiko-get-interface.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | """Sample use of the netmiko library for CLI interfacing 3 | 4 | This script will retrieve information from a device. 5 | 6 | Copyright (c) 2018 Cisco and/or its affiliates. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | """ 26 | 27 | # Import libraries 28 | from netmiko import ConnectHandler 29 | import re 30 | 31 | from device_info import ios_xe1 as device # noqa 32 | 33 | # Create a CLI command template 34 | show_interface_config_temp = "show running-config interface {}" 35 | 36 | # Open CLI connection to device 37 | with ConnectHandler(ip = device["address"], 38 | port = device["ssh_port"], 39 | username = device["username"], 40 | password = device["password"], 41 | device_type = device["device_type"]) as ch: 42 | 43 | # Create desired CLI command and send to device 44 | command = show_interface_config_temp.format("Loopback103") 45 | interface = ch.send_command(command) 46 | 47 | # Print the raw command output to the screen 48 | print(interface) 49 | 50 | try: 51 | # Use regular expressions to parse the output for desired data 52 | name = re.search(r'interface (.*)', interface).group(1) 53 | description = re.search(r'description (.*)', interface).group(1) 54 | ip_info = re.search(r'ip address (.*) (.*)', interface) 55 | ip = ip_info.group(1) 56 | netmask = ip_info.group(2) 57 | 58 | # Print the info to the screen 59 | print("The interface {name} has ip address {ip}/{mask}".format( 60 | name = name, 61 | ip = ip, 62 | mask = netmask, 63 | ) 64 | ) 65 | except Exception: 66 | print("There was an error, Loopback103 might not exist.") 67 | -------------------------------------------------------------------------------- /netmiko-interface-example/requirements.txt: -------------------------------------------------------------------------------- 1 | netmiko 2 | -------------------------------------------------------------------------------- /restconf_update_ipaddress/README.md: -------------------------------------------------------------------------------- 1 | # Calling RESTCONF with Python 2 | 3 | This example script uses the requests Python library to interact with a RESTCONF Agent. 4 | 5 | This Python script leverages RESTCONF to: 6 | - retrieve a list of interfaces on a device 7 | - ask the user for the interface to configure 8 | - displays the interface IP information 9 | - asks user for new IP information 10 | - updates the IP address on the interface 11 | - displays the final IP information on the interface 12 | 13 | This script has been tested with Python 3.7, however may work with other versions. 14 | 15 | ## DevNet Sandbox 16 | 17 | This script targets the RESTCONF DevNet Sandbox that leverages a CSR1000v as a target. 18 | 19 | Find details on the Sandbox [here](https://developer.cisco.com/docs/sandbox/#!networking). 20 | 21 | To execute this script against a different device, update the variables that list the connectivity, management interface, and url_base for RESTCONF. 22 | 23 | ## Requirements 24 | 25 | Python 26 | 27 | - requests 28 | 29 | # Getting Started 30 | 31 | * Clone the Python Examples and change into the directory. 32 | 33 | ```bash 34 | git clone https://github.com/CiscoDevNet/python_code_samples_network 35 | cd restconf_update_ipaddress 36 | ``` 37 | 38 | * Create and activate a virtualenv 39 | 40 | ```bash 41 | virtualenv venv --python=python3.7 42 | source venv/bin/activate 43 | ``` 44 | 45 | * Install the requirements 46 | 47 | ```bash 48 | pip install -r requirements.txt 49 | ``` 50 | 51 | * Run the script 52 | 53 | ``` 54 | $ python updateip.py 55 | 56 | # Output 57 | The router has the following interfaces: 58 | 59 | * GigabitEthernet1 60 | * GigabitEthernet2 61 | * GigabitEthernet3 62 | 63 | Which Interface do you want to configure? GigabitEthernet2 64 | GigabitEthernet2 65 | Starting Interface Configuration 66 | Name: GigabitEthernet2 67 | IP Address: 101.101.10.10 / 255.255.255.0 68 | 69 | What IP address do you want to set? 9.9.9.9 70 | What Subnet Mask do you want to set? 255.255.255.0 71 | 72 | Ending Interface Configuration 73 | Name: GigabitEthernet2 74 | IP Address: 9.9.9.9 / 255.255.255.0 75 | ``` 76 | 77 | -------------------------------------------------------------------------------- /restconf_update_ipaddress/requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.0 2 | packaging==16.8 3 | pyparsing==2.1.10 4 | requests>=2.20.0 5 | six==1.10.0 6 | urllib3>=1.23 7 | -------------------------------------------------------------------------------- /restconf_update_ipaddress/updateip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | This Python script leverages RESTCONF to: 4 | - retrieve a list of interfaces on a device 5 | - ask the user for the interface to configure 6 | - displays the interface IP information 7 | - asks user for new IP information 8 | - updates the IP address on the interface 9 | - displays the final IP information on the interface 10 | 11 | This script has been tested with Python 3.7, however may work with other versions. 12 | 13 | This script targets the RESTCONF DevNet Sandbox that leverages a CSR1000v as 14 | a target. To execute this script against a different device, update the 15 | variables and command-line arguments that list the connectivity, management 16 | interface, and url_base for RESTCONF. 17 | 18 | Requirements: 19 | Python 20 | - requests 21 | 22 | 23 | """ 24 | 25 | import json 26 | import requests 27 | import sys 28 | import os 29 | import ipaddress 30 | from argparse import ArgumentParser 31 | from collections import OrderedDict 32 | from getpass import getpass 33 | import urllib3 34 | 35 | # Disable SSL Warnings 36 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 37 | 38 | # Identify yang+json as the data formats 39 | headers = {'Content-Type': 'application/yang-data+json', 40 | 'Accept': 'application/yang-data+json'} 41 | 42 | 43 | # Function to retrieve the list of interfaces on a device 44 | def get_configured_interfaces(url_base, username, password): 45 | # this statement performs a GET on the specified url 46 | try: 47 | response = requests.get(url_base, 48 | auth=(username, password), 49 | headers=headers, 50 | verify=False 51 | ) 52 | response.raise_for_status() 53 | except Exception as e: 54 | print(e, file=sys.stderr) 55 | sys.exit(1) 56 | 57 | # return the json as text 58 | return response.json()["ietf-interfaces:interfaces"]["interface"] 59 | 60 | 61 | # Used to configure the IP address on an interface 62 | def configure_ip_address(url_base, interface, ip, username, password): 63 | # RESTCONF URL for specific interface 64 | url = url_base + "/interface={i}".format(i=interface) 65 | 66 | # Create the data payload to reconfigure IP address 67 | # Need to use OrderedDicts to maintain the order of elements 68 | data = OrderedDict([('ietf-interfaces:interface', 69 | OrderedDict([ 70 | ('name', interface), 71 | ('type', 'iana-if-type:ethernetCsmacd'), 72 | ('ietf-ip:ipv4', 73 | OrderedDict([ 74 | ('address', [OrderedDict([ 75 | ('ip', ip["address"]), 76 | ('netmask', ip["mask"]) 77 | ])] 78 | ) 79 | ]) 80 | ), 81 | ]) 82 | )]) 83 | 84 | # Use PUT request to update data 85 | try: 86 | response = requests.put(url, 87 | auth=(username, password), 88 | headers=headers, 89 | verify=False, 90 | json=data 91 | ) 92 | response.raise_for_status() 93 | except Exception as e: 94 | print(e, file=sys.stderr) 95 | sys.exit(1) 96 | 97 | print(response.text) 98 | 99 | 100 | # Retrieve and print the current configuration of an interface 101 | def print_interface_details(url_base, interface, username, password, cidr): 102 | url = url_base + "/interface={i}".format(i=interface) 103 | 104 | # this statement performs a GET on the specified url 105 | try: 106 | response = requests.get(url, 107 | auth=(username, password), 108 | headers=headers, 109 | verify=False 110 | ) 111 | response.raise_for_status() 112 | except Exception as e: 113 | print(e, file=sys.stderr) 114 | sys.exit(1) 115 | 116 | intf = response.json()["ietf-interfaces:interface"] 117 | # return the json as text 118 | print("Name: ", intf[0]["name"]) 119 | try: 120 | netmask = intf[0]["ietf-ip:ipv4"]["address"][0]["netmask"] 121 | if cidr: 122 | nma = ipaddress.ip_address(netmask) 123 | netmask = str("{0:b}".format(int(nma)).count('1')) 124 | print("IP Address: ", intf[0]["ietf-ip:ipv4"]["address"][0]["ip"], "/", 125 | netmask) 126 | except KeyError: 127 | print("IP Address: UNCONFIGURED") 128 | except Exception as e: 129 | print(e, file=sys.stderr) 130 | sys.exit(1) 131 | print() 132 | 133 | return(intf) 134 | 135 | 136 | # Ask the user to select an interface to configure. Ensures input is valid and 137 | # NOT the management interface 138 | def interface_selection(interfaces, mgmt_if): 139 | # Ask User which interface to configure 140 | sel = input("Which Interface do you want to configure? ") 141 | 142 | # Validate interface input 143 | # Must be an interface on the device AND NOT be the Management Interface 144 | while sel == mgmt_if or not sel in [intf["name"] for intf in interfaces]: 145 | print("INVALID: Select an available interface.") 146 | print(" " + mgmt_if + " is used for management.") 147 | print(" Choose another Interface") 148 | sel = input("Which Interface do you want to configure? ") 149 | 150 | return(sel) 151 | 152 | 153 | # Asks the user to provide an IP address and Mask. 154 | def get_ip_info(cidr): 155 | # Ask User for IP and Mask 156 | ip = {} 157 | try: 158 | if cidr: 159 | ipa_t = input("What IP address/prefixlen do you want to set? ") 160 | ipi = ipaddress.ip_interface(ipa_t) 161 | ip["address"] = ipi.ip.compressed 162 | ip["mask"] = ipi.netmask.compressed 163 | else: 164 | ipa_t = input("What IP address do you want to set? ") 165 | ipi = ipaddress.ip_interface(ipa_t) 166 | ip["address"] = ipi.ip.compressed 167 | ipm_t = input("What Subnet Mask do you want to set? ") 168 | ipm = ipaddress.ip_address(ipm_t) 169 | ip["mask"] = ipm.compressed 170 | except Exception as e: 171 | print(e, file=sys.stderr) 172 | sys.exit(1) 173 | return(ip) 174 | 175 | 176 | def main(): 177 | """ 178 | Simple main method calling our function. 179 | """ 180 | 181 | parser = ArgumentParser( 182 | prog=sys.argv[0], description='RESTCONF interface management tool') 183 | parser.add_argument('--hostname', '-a', type=str, 184 | help='sandbox hostname or IP address', 185 | default='ios-xe-mgmt.cisco.com') 186 | parser.add_argument('--username', '-u', type=str, 187 | help='sandbox username', default='developer') 188 | # Identifies the interface on the device used for management access 189 | # Used to ensure the script isn't used to update the IP leveraged to manage 190 | # device 191 | parser.add_argument('--management_if', '-m', type=str, 192 | help='management interface', default='GigabitEthernet1') 193 | parser.add_argument('--port', '-P', type=int, 194 | help='sandbox web port', default=443) 195 | parser.add_argument('--cidr', help='use CIDR format for interface IP', 196 | action='store_true') 197 | args = parser.parse_args() 198 | 199 | password = os.getenv('DEVNET_RESTCONF_PASSWORD') 200 | if password is None: 201 | password = getpass() 202 | 203 | # Create the base URL for RESTCONF calls 204 | 205 | url_base = "https://{h}:{p}/restconf/data/ietf-interfaces:interfaces".format(h=args.hostname, p=args.port) 206 | 207 | # Get a List of Interfaces 208 | interfaces = get_configured_interfaces(url_base, args.username, password) 209 | 210 | print("The router has the following interfaces: \n") 211 | for interface in interfaces: 212 | print(" * {name:25}".format(name=interface["name"])) 213 | 214 | print("") 215 | 216 | # Ask User which interface to configure 217 | selected_interface = interface_selection(interfaces, args.management_if) 218 | print(selected_interface) 219 | 220 | # Print Starting Interface Details 221 | print("Starting Interface Configuration") 222 | print_interface_details(url_base, selected_interface, args.username, 223 | password, args.cidr) 224 | 225 | # As User for IP Address to set 226 | ip = get_ip_info(args.cidr) 227 | 228 | # Configure interface 229 | configure_ip_address(url_base, selected_interface, ip, args.username, password) 230 | 231 | # Print Ending Interface Details 232 | print("Ending Interface Configuration") 233 | print_interface_details(url_base, selected_interface, args.username, 234 | password, args.cidr) 235 | 236 | if __name__ == '__main__': 237 | sys.exit(main()) 238 | -------------------------------------------------------------------------------- /snmp_entity/README.md: -------------------------------------------------------------------------------- 1 | # SNMP: Print the results of an snmpwalk of the ENTITY-MIB from a device 2 | 3 | This is an off-box Python script that uses net-snmp's Python bindings 4 | to print the results of an snmpwalk of the ENTITY-MIB from a device. 5 | 6 | The script uses either SNMPv1 or SNMPv2c. 7 | 8 | For example: 9 | 10 | ``` 11 | $ snmp_entity.py -a 10.1.1.1 -c public -v 2 12 | entPhysicalDescr.1 : CISCO1941/K9 chassis, Hw Serial#: FTX180381QX, Hw Revision: 1.0 13 | entPhysicalDescr.2 : C1941 Chassis Slot 14 | entPhysicalDescr.3 : C1941 Mother board 2GE, integrated VPN and 2W 15 | entPhysicalDescr.4 : C1941 DaughterCard Slot 16 | entPhysicalDescr.5 : C1941 DaughterCard Slot 17 | entPhysicalDescr.6 : 8 Port GE POE EHWIC Switch 18 | entPhysicalDescr.7 : EHWIC-8 Gigabit Ethernet 19 | ... 20 | ``` 21 | 22 | ## Setup 23 | 24 | This script has been tested with Python 2.7, and requires the following non-default module(s): 25 | 26 | * net-snmp's netsnmp module (see http://net-snmp.sourceforge.net/wiki/index.php/Python_Bindings) 27 | 28 | Ubuntu or Debian: 29 | 30 | ``` 31 | $ sudo apt-get install python-netsnmp 32 | ``` 33 | 34 | CentOS: 35 | 36 | ``` 37 | $ yum install net-snmp-python 38 | ``` 39 | 40 | FreeBSD: 41 | 42 | Install `/usr/ports/net-mgmt/net-snmp` and make sure the PYTHON option is enabled. 43 | 44 | Additionally, the ENTITY-MIB needs to be loaded into net-snmp. By default, the MIBs directory 45 | is `/usr/share/snmp/mibs` on Linux and `/usr/local/share/snmp/mibs` on FreeBSD. Copy 46 | [https://cisco.github.io/cisco-mibs/v2/ENTITY-MIB.my](https://cisco.github.io/cisco-mibs/v2/ENTITY-MIB.my) 47 | to that location before running this script. 48 | -------------------------------------------------------------------------------- /snmp_entity/snmp_entity.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2017 Joe Clarke 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | # 27 | # This script retrieves the ENTITY-MIB tree from a device via NETCONF and 28 | # prints it out in a "pretty" XML tree. 29 | # 30 | 31 | import netsnmp 32 | import argparse 33 | import sys 34 | 35 | if __name__ == '__main__': 36 | parser = argparse.ArgumentParser( 37 | prog=sys.argv[0], description='Print the snmpwalk of the ENTITY-MIB from a device') 38 | 39 | parser.add_argument('-a', '--host', type=str, required=True, 40 | help="Device IP address or Hostname") 41 | parser.add_argument('-c', '--community', type=str, required=True, 42 | help="SNMP community string") 43 | parser.add_argument('-v', '--version', type=int, required=True, 44 | help="SNMP version (1 or 2 [for v2c])") 45 | 46 | args = parser.parse_args() 47 | 48 | if args.version != 1 and args.version != 2: 49 | parser.error('SNMP version must be either 1 or 2') 50 | 51 | vars = netsnmp.VarList(netsnmp.Varbind('entityMIB')) 52 | netsnmp.snmpwalk(vars, 53 | Version = args.version, 54 | DestHost = args.host, 55 | Community = args.community) 56 | 57 | for var in vars: 58 | print('{}.{} : {}'.format(var.tag, var.iid, var.val)) 59 | -------------------------------------------------------------------------------- /spark_checkin/README.md: -------------------------------------------------------------------------------- 1 | # EEM Spark-CheckIn 2 | 3 | This example script sends a notification message to a Cisco Spark user of a Configuration Change. It was created to be used along with an EEM Applet that monitors for configuration changes, and then executes the script leveraging Python withing Guest Shell. 4 | 5 | This example was tested using IOS-XE 16.5.1b on an ISR 4K router, but should work on other platforms supporting Guest Shell. 6 | 7 | This example script uses the CiscoSparkApi Python library to send the Spark Message. 8 | 9 | You will need to provide the Spark Authorization Token and recipient email address within the EEM Configuration. It is recommended that you create a [Bot Account](https://developer.ciscospark.com/bots.html) for this use case. 10 | 11 | This script has been tested with Python 2.7, however may work with other versions. 12 | 13 | 14 | # Requirements and Setup 15 | 16 | ## Host Device and Guest Shell 17 | 18 | IOS-XE or NX-OS device supporting Guest Shell and EEM. 19 | 20 | Example Steps to configure Guest Shell on IOS-XE are: 21 | 22 | ``` 23 | conf t 24 | iox 25 | exit 26 | guestshell enable 27 | 28 | ! Enter Guest Shell with 29 | guestshell run bash 30 | ``` 31 | 32 | ### Python Library 33 | 34 | - ciscosparkapi Python Library 35 | 36 | ``` 37 | # From within Guest Shell 38 | sudo -E pip install ciscosparkapi 39 | ``` 40 | 41 | ## Create folder for Scripts and Add Script 42 | 43 | ``` 44 | # From within Guest Shell 45 | mkdir /bootflash/scripts 46 | cd /bootflash/scripts 47 | wget https://raw.githubusercontent.com/CiscoDevNet/python_code_samples_network/master/spark_checkin/spark_checkin.py 48 | 49 | ``` 50 | 51 | ## Test Script 52 | 53 | Test the script by running in manually from within guestshell. 54 | 55 | ``` 56 | python spark_checkin.py -t SPARK_AUTH_TOKEN -e DESTINATION_EMAIL 57 | ``` 58 | 59 | You should recieve a message similar to this in Spark. 60 | 61 | ![](Spark_Message.png) 62 | 63 | ## Setup EEM Applet 64 | 65 | Back within IOS, configure the EEM Applet to monitor for Configuration Changes. 66 | 67 | ``` 68 | event manager applet GUESTSHELL-CONFIG-CHANGE-TO-SPARK 69 | event syslog pattern "%SYS-5-CONFIG_I: Configured from" 70 | action 0.0 cli command "en" 71 | action 1.0 cli command "python spark_checkin.py -t SPARK_AUTH_TOKEN -e DESTINATION_EMAIL" 72 | ``` 73 | -------------------------------------------------------------------------------- /spark_checkin/Spark_Message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CiscoDevNet/python_code_samples_network/a9f38e4e9cb291a629fc484e34c096fb6b91e627/spark_checkin/Spark_Message.png -------------------------------------------------------------------------------- /spark_checkin/requirements.txt: -------------------------------------------------------------------------------- 1 | ciscosparkapi 2 | -------------------------------------------------------------------------------- /spark_checkin/spark_checkin.py: -------------------------------------------------------------------------------- 1 | from ciscosparkapi import CiscoSparkAPI 2 | 3 | if __name__=='__main__': 4 | # Use ArgParse to retrieve command line parameters. 5 | from argparse import ArgumentParser 6 | parser = ArgumentParser("Spark Check In") 7 | 8 | # Retrieve the Spark Token and Destination Email 9 | parser.add_argument( 10 | "-t", "--token", help="Spark Authentication Token", required=True 11 | ) 12 | # Retrieve the Spark Token and Destination Email 13 | parser.add_argument( 14 | "-e", "--email", help="Email to Send to", required=True 15 | ) 16 | 17 | args = parser.parse_args() 18 | 19 | token = args.token 20 | email = args.email 21 | message = "**Alert:** Config Changed" 22 | 23 | api = CiscoSparkAPI(access_token=token) 24 | api.messages.create(toPersonEmail=email, markdown=message) 25 | 26 | -------------------------------------------------------------------------------- /tdr-test/README.md: -------------------------------------------------------------------------------- 1 | # tdr-test 2 | 3 | This is an example Python script that runs TDR test for every interface in an up status. 4 | 5 | The output is thrown to screen, plus written to a file in bootflash. 6 | 7 | # requirements 8 | -- IOS-XE running >/= 16.3.1 also enabled for GuestShell 9 | -- 3850 Switches (TDR typically only works on Cat switches). 10 | 11 | # running 12 | -- Run onbox. 13 | -------------------------------------------------------------------------------- /tdr-test/tdr-test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # importing the cli module is necessary to run 4 | # config or exec commands on the host. 5 | import cli 6 | # importing the time module works well in 7 | # this script, as we are pacing some of the execution. 8 | import time 9 | # importing the time module works well in this script, as we are pacing 10 | # some of the execution. 11 | 12 | o1 = cli.execute('show ip int brief | include Ethernet1/0/.*up'); 13 | # Create this thing called o1. Make it be equal to the command above. 14 | # This is also proper use of the cli.execute API. As you can see, 15 | # this is CLI, taking advantage of regexp # matching. Essentially, 16 | # we are running this command to get a list of all the interfaces 17 | # that are UP on the host! 18 | 19 | # Create this thing called intfs. Make it a Python dictionary. 20 | intfs = dict() 21 | 22 | # Create a function to grab the list of interfaces from passed in data. 23 | def grab_intf(i): 24 | # use the global intfs variable defined outside of the function. 25 | global intfs 26 | # If the passed in data is empty exit the function. 27 | if i == "": 28 | return 29 | # Take the first column of space separated data 30 | j = (i.split(' '))[0] 31 | # Assign the interface name as a key value in the intfs dictionary. 32 | intfs[j] = "" 33 | 34 | # Take the o1 data, split it on each line, 35 | for i in o1.split('\n'): 36 | # and call the grab_intf function on each line. 37 | grab_intf(i) 38 | 39 | # Run through our interface list. 40 | for i in intfs: 41 | # While we run through our interface list, each time we iterate, create 42 | # this thing called cmd, and set it equal to the output of the command. 43 | cmd = "test cable-diagnostics tdr interface " + i 44 | # And each time we do, create this thing called o2, and set it 45 | # equal to the execution of each command for each interface on the host. 46 | o2 = cli.execute(cmd); 47 | 48 | # Create this thing called done and set it to a value of False. Essentially, 49 | # we're not done! 50 | done = False 51 | 52 | # Create a loop condition for when we're not done. 53 | while done == False: 54 | 55 | # Remember our list of interfaces? Run through them again. 56 | for i in intfs: 57 | # If we already have data for an interface, then skip 58 | if intfs[i] != "": 59 | continue 60 | 61 | # Try to get data for the interface we are working on. 62 | # Once again, create this thing called cmd, and set it equal to the 63 | # iterative exec command for all of our interfaces. 64 | cmd = "show cable-diagnostics tdr interface " + i 65 | # And create this thing called o2, and execute each CLI. 66 | o2 = cli.execute(cmd); 67 | # Parse the data we get. We are making sure the TDR test runs, and 68 | # also completes. 69 | if "Not Completed" in o2: 70 | continue 71 | else : 72 | # We got valid data, set it into the dictionary value for the 73 | # specific interface we are working on. 74 | intfs[i] = o2 75 | 76 | time.sleep(2) 77 | 78 | # now loop again looking to see if we are all done 79 | found_one = False 80 | for i in intfs: 81 | if intfs[i] == "": 82 | found_one = True; 83 | if found_one == False: 84 | done = True 85 | 86 | # We are done gathering now just print output 87 | target = open('/bootflash/myoutputfile', 'w') 88 | target.truncate() 89 | 90 | for i in intfs: 91 | title = "Interfaces: " + i 92 | print(title) 93 | target.write(title) 94 | print(intfs[i]) 95 | target.write(intfs[i]) 96 | print("\n\n") 97 | target.write("\n\n") 98 | 99 | target.close() 100 | --------------------------------------------------------------------------------