├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── __pycache__ └── netmiko.cpython-36.pyc ├── get_bgp.py ├── get_device.py ├── get_interfaces.py ├── iosxeapi ├── __init__.py └── iosxerestapi.py ├── requirements.txt ├── router_info.py ├── routers.json └── topology.virl /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | *.py[cod] 3 | 4 | # virtualenv 5 | *venv*/ 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | The following is a set of guidelines for contributing, I want to make contributing to this project as easy and transparent as possible. Use your best judgment, and feel free to propose changes to this document in a pull request. 4 | 5 | 6 | ## Pull Requests 7 | 8 | 1. Fork the repo and create your branch from `master`. 9 | 2. If you've added/changes to the code, please test this thoroughly. 10 | 11 | 12 | Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should look like this please: 13 | 14 | ``` 15 | $ git commit -m "A brief summary of the commit 16 | > 17 | > A paragraph describing what changed." 18 | ``` 19 | 20 | 21 | Thank you. 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Challenges With Network Automation 2 | 3 | ## DevNet Sandbox 4 | All code has been tested on the Cisco DevNet Multi-IOS Cisco Test Network Sandbox [HERE](https://devnetsandbox.cisco.com/RM/Diagram/Index/6b023525-4e7f-4755-81ae-05ac500d464a?diagramType=Topology). 5 | 6 | Please see the sandbox pages for credentials and reservations, virl default passwords are used on all routers (cisco/cisco). This demo example is based on Python 3.6 and was tested successfully under that version. 7 | 8 | 9 | ## Code 10 | 11 | All of the code and examples for this are located in this directory. Clone and access it with the following commands: 12 | 13 | ``` 14 | git clone https://github.com/bigevilbeard/challenges_with-network_automation.git 15 | cd challenges_with-network_automation 16 | ``` 17 | 18 | ## Python Environment Setup 19 | It is recommended that this demo be completed using Python 3.6. 20 | 21 | It is highly recommended to leverage Python Virtual Environments for completing exercises in this course. 22 | 23 | Follow these steps to create and activate a venv. 24 | ``` 25 | # OS X or Linux 26 | virtualenv venv --python=python3.6 27 | source venv/bin/activate 28 | ``` 29 | ## Install the code requirements 30 | ``` 31 | pip install -r requirements.txt 32 | ``` 33 | 34 | ## Reservation Setup 35 | This lesson leverages a specific [VIRL](https://github.com/bigevilbeard/automated_bgp_iox/blob/master/topology.virl) topology, as such virl default passwords are used on all routers (cisco/cisco). Before beginning this lesson run the following command to reconfigure the Sandbox with the proper topology. 36 | 37 | From the `challenges_with-network_automation` directory 38 | ``` 39 | # Get a list of currently running simulations 40 | virl ls --all 41 | 42 | # Stop any running simulations. 43 | virl down --sim-name 44 | 45 | # Start the VIRL Simulation for demo 46 | virl up 47 | 48 | # Monitor status of simulation 49 | virl nodes # Node startup 50 | ``` 51 | Once the VIRL simulation is built, the following will be seen. 52 | ``` 53 | (venv) [developer@devbox challenges_with-network_automation]$virl ls 54 | Running Simulations 55 | ╒═══════════════════════════════════════════════════╤══════════╤════════════════════════════╤═══════════╕ 56 | │ Simulation │ Status │ Launched │ Expires │ 57 | ╞═══════════════════════════════════════════════════╪══════════╪════════════════════════════╪═══════════╡ 58 | │ challenges_with-network_automation_default_yNccAp │ ACTIVE │ 2019-03-04T16:19:46.778031 │ │ 59 | ╘═══════════════════════════════════════════════════╧══════════╧════════════════════════════╧═══════════╛ 60 | ``` 61 | 62 | NOTE: IP addresses will differ in your own simulation 63 | 64 | ``` 65 | (venv) [developer@devbox challenges_with-network_automation]$virl nodes 66 | Here is a list of all the running nodes 67 | ╒═══════════╤══════════╤═════════╤═════════════╤════════════╤══════════════════════╤════════════════════╕ 68 | │ Node │ Type │ State │ Reachable │ Protocol │ Management Address │ External Address │ 69 | ╞═══════════╪══════════╪═════════╪═════════════╪════════════╪══════════════════════╪════════════════════╡ 70 | │ PE-2 │ CSR1000v │ ACTIVE │ REACHABLE │ telnet │ 172.16.30.91 │ N/A │ 71 | ├───────────┼──────────┼─────────┼─────────────┼────────────┼──────────────────────┼────────────────────┤ 72 | │ PE-1 │ CSR1000v │ ACTIVE │ REACHABLE │ telnet │ 172.16.30.90 │ N/A │ 73 | ├───────────┼──────────┼─────────┼─────────────┼────────────┼──────────────────────┼────────────────────┤ 74 | │ CE-1 │ CSR1000v │ ACTIVE │ REACHABLE │ telnet │ 172.16.30.89 │ N/A │ 75 | ├───────────┼──────────┼─────────┼─────────────┼────────────┼──────────────────────┼────────────────────┤ 76 | │ ~mgmt-lxc │ mgmt-lxc │ ACTIVE │ REACHABLE │ ssh │ 172.16.30.87 │ 172.16.30.88 │ 77 | ╘═══════════╧══════════╧═════════╧═════════════╧════════════╧══════════════════════╧════════════════════╛ 78 | ``` 79 | 80 | ## Running the code examples 81 | 82 | Configuration is done using the Representational State Transfer Configuration Protocol (RESTCONF). RESTCONF is an HTTP based protocol. It provides a programmatic interface based on standard mechanisms for accessing configuration data, state data, data-model-specific Remote Procedure Call (RPC) operations and events defined in a YANG model. This code is using native YANG models for IOS-XE - models that are specific to IOS-XE platforms. 83 | 84 | - `get_bgp.py` - Passes static configuration IP Address/Port/User/Password and will get all device BGP information. Results are printed using [Tabulate](https://pypi.org/project/tabulate/) 85 | - `get_interfaces.py` - Passes static configuration IP Address/Port/User/Password and will get all device interface information. Results are printed using [Tabulate](https://pypi.org/project/tabulate/) 86 | - `get_device.py` - Passes static configuration IP Address/Port/User/Password and will get device hostname and version information. Results are printed using [Tabulate](https://pypi.org/project/tabulate/) 87 | 88 | - `router_info.py` - This code uses Object-Oriented Programming (OOP). This is a programming paradigm where different components of a computer program are modeled after real-world objects. An object is anything that has some characteristics and can perform a function. All args used in the running of the code are handled using [CLICK](https://click.palletsprojects.com/en/7.x/). Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. 89 | 90 | In this code, we can show the router BGP and interface information (shown in `json` format). We can also add an access list to an interface with the `patch` and `delete`. As with REST, with RESTCONF we can use Methods. Methods are `HTTPS` operations _`(GET/PATCH/POST/DELETE/OPTIONS/PUT)`_ performed on a target resource. Use either a single IP or update the `JSON` file with devices IP addresses. 91 | 92 | Use the `--help` to see the Options and Commands 93 | 94 | ``` 95 | (venv) STUACLAR-M-R6EU:challenges_with-network_automation stuaclar$ python router_info.py --help 96 | Usage: router_info.py [OPTIONS] COMMAND [ARGS]... 97 | 98 | Gather and Add IOS XE device information using restconf 99 | 100 | Options: 101 | --ip TEXT ip or dns address of device 102 | --file TEXT file ip addresses of devices 103 | --port INTEGER device port, default = 443 104 | --username TEXT device username (default lab username = cisco) 105 | --password TEXT device password (default lab password = cisco) 106 | --help Show this message and exit. 107 | 108 | Commands: 109 | add_drop Add ACL to Interface 110 | delete_drop Remove ACL from Interface 111 | get_bgp Gather BGP information 112 | get_device Gather Device information 113 | get_interfaces Gather Interface information 114 | ``` 115 | 116 | ## Example Use Commands 117 | 118 | - `python router_info.py --ip 172.16.30.62 get_interfaces` 119 | `python router_info.py --file routers.json get_device` 120 | 121 | ``` 122 | (venv) STUACLAR-M-R6EU:challenges_with-network_automation stuaclar$ python router_info.py --ip 172.16.30.89 get_device 123 | Username: cisco 124 | Password: 125 | Working.... 126 | { 127 | "Cisco-IOS-XE-native:native": { 128 | "device": { 129 | "hostname": "csr1000v", 130 | "version": "16.8" 131 | } 132 | } 133 | } 134 | Task completed 135 | ``` 136 | 137 | ## About me 138 | 139 | Network Automation Developer Advocate for Cisco DevNet. 140 | I'm like Hugh Hefner... minus the mansion, the exotic cars, the girls, the magazine and the money. So basically, I have a robe. 141 | 142 | Find me here: [LinkedIn](https://www.linkedin.com/in/stuarteclark/) / [Twitter](https://twitter.com/bigevilbeard) 143 | -------------------------------------------------------------------------------- /__pycache__/netmiko.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigevilbeard/challenges_with-network_automation/38de42f4bbc0e86d386b7f5e8b75d6da2e371ce0/__pycache__/netmiko.cpython-36.pyc -------------------------------------------------------------------------------- /get_bgp.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import urllib3 3 | import sys 4 | from tabulate import tabulate 5 | 6 | 7 | # HOST = '172.16.30.68' 8 | # PORT = '443' 9 | # USER = 'cisco' 10 | # PASS = 'cisco' 11 | 12 | # HOST = 'ios-xe-mgmt.cisco.com' 13 | # PORT = '9443' 14 | # USER = 'root' 15 | # PASS = 'D_Vay!_10&' 16 | 17 | 18 | 19 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 20 | 21 | def get_bgp(): 22 | url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-bgp-oper:bgp-state-data".format(h=HOST, p=PORT) 23 | headers = {'Content-Type': 'application/yang-data+json', 24 | 'Accept': 'application/yang-data+json'} 25 | 26 | response = requests.get(url, auth=(USER, PASS), headers=headers, verify=False) 27 | return response.json() 28 | # print(response) 29 | 30 | def main(): 31 | neighbors = get_bgp() 32 | # print(neighbors) 33 | 34 | headers = ["Neighbor", 35 | "LINK", 36 | "UP-TIME", 37 | "STATE", 38 | "PfxRcd" ] 39 | table = list() 40 | 41 | for item in neighbors['Cisco-IOS-XE-bgp-oper:bgp-state-data']['neighbors']['neighbor']: 42 | tr = [item['neighbor-id'], 43 | item['link'], 44 | item['up-time'], 45 | item['connection']['state'], 46 | item['prefix-activity']['received']['total-prefixes']] 47 | table.append(tr) 48 | # print(tr) 49 | 50 | print(tabulate(table, headers, tablefmt="fancy_grid")) 51 | 52 | if __name__ == '__main__': 53 | sys.exit(main()) 54 | -------------------------------------------------------------------------------- /get_device.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import urllib3 3 | import sys 4 | from tabulate import tabulate 5 | 6 | 7 | # HOST = '172.16.30.69' 8 | # PORT = '443' 9 | # USER = 'cisco' 10 | # PASS = 'cisco' 11 | 12 | # HOST = 'ios-xe-mgmt.cisco.com' 13 | # PORT = '9443' 14 | # USER = 'root' 15 | # PASS = 'D_Vay!_10&' 16 | 17 | 18 | 19 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 20 | 21 | def get_info(): 22 | url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native".format(h=HOST, p=PORT) 23 | headers = {'Content-Type': 'application/yang-data+json', 24 | 'Accept': 'application/yang-data+json'} 25 | 26 | response = requests.get(url, auth=(USER, PASS), headers=headers, verify=False) 27 | return response.json() 28 | # print(response) 29 | 30 | def main(): 31 | system = get_info().get("Cisco-IOS-XE-native:native") 32 | # print(system) 33 | 34 | headers = ["Hostname", 35 | "Version"] 36 | table = list() 37 | 38 | hostname = system.get('hostname') 39 | version = system.get('version') 40 | table.append((hostname, version)) 41 | 42 | print(tabulate(table, headers, tablefmt="fancy_grid")) 43 | 44 | if __name__ == '__main__': 45 | sys.exit(main()) 46 | -------------------------------------------------------------------------------- /get_interfaces.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import urllib3 3 | import sys 4 | from tabulate import tabulate 5 | 6 | 7 | # HOST = '172.16.30.68' 8 | # PORT = '443' 9 | # USER = 'cisco' 10 | # PASS = 'cisco' 11 | 12 | # HOST = 'ios-xe-mgmt.cisco.com' 13 | # PORT = '9443' 14 | # USER = 'root' 15 | # PASS = 'D_Vay!_10&' 16 | 17 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 18 | 19 | def get_interfaces(): 20 | url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-interfaces-oper:interfaces".format(h=HOST, p=PORT) 21 | headers = {'Content-Type': 'application/yang-data+json', 22 | 'Accept': 'application/yang-data+json'} 23 | 24 | response = requests.get(url, auth=(USER, PASS), headers=headers, verify=False) 25 | return response.json() 26 | 27 | def main(): 28 | interfaces = get_interfaces() 29 | # print(interfaces) 30 | 31 | headers = ["Name", 32 | "Description", 33 | "VRF", 34 | "Status", 35 | "IN-DIS", 36 | "IN-ERR", 37 | "OUT-ERR", 38 | "IN-DIS", 39 | "IP Address", 40 | "IN-PKTS", 41 | "OUT-PKTS"] 42 | table = list() 43 | 44 | for item in interfaces['Cisco-IOS-XE-interfaces-oper:interfaces']['interface']: 45 | tr =[item['name'], 46 | item['description'], 47 | # item['ipv4'], 48 | item['vrf'], 49 | item['admin-status'], 50 | item['statistics']['in-discards'], 51 | item['statistics']['in-errors'], 52 | item['statistics']['out-discards'], 53 | item['statistics']['out-errors'], 54 | item['v4-protocol-stats']['in-pkts'], 55 | item['v4-protocol-stats']['out-pkts']] 56 | table.append(tr) 57 | # print(tr) 58 | 59 | print(tabulate(table, headers, tablefmt="fancy_grid")) 60 | 61 | if __name__ == '__main__': 62 | sys.exit(main()) 63 | -------------------------------------------------------------------------------- /iosxeapi/__init__.py: -------------------------------------------------------------------------------- 1 | from . import iosxerestapi 2 | import sys 3 | 4 | sys.path.insert(0, '../iosxeapi/') 5 | __all__ = [ 6 | 'iosxerestapi', # export the function 7 | ] -------------------------------------------------------------------------------- /iosxeapi/iosxerestapi.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import urllib3 3 | import logging.config 4 | import json 5 | import re 6 | 7 | HTTP_SUCCESS_CODES = { 8 | 200: 'OK', 9 | 201: 'Created', 10 | 202: 'Accepted', 11 | 204: 'Accepted but with no JSON body', 12 | } 13 | 14 | HTTP_ERROR_CODES = { 15 | 400: 'Bad Request', 16 | 401: 'Unauthorized', 17 | 404: 'Not Found', 18 | 405: 'Method not Allowed', 19 | } 20 | 21 | HTTP_SERVER_ERRORS = { 22 | 500: 'Internal Server Error', 23 | 503: 'Service Unavailable', 24 | } 25 | 26 | config = { 27 | 'disable_existing_loggers': False, 28 | 'version': 1, 29 | 'formatters': { 30 | 'short': { 31 | 'format': '%(asctime)s %(levelname)s %(name)s: %(message)s' 32 | }, 33 | }, 34 | 'handlers': { 35 | 'console': { 36 | 'level': 'ERROR', 37 | 'formatter': 'short', 38 | 'class': 'logging.StreamHandler', 39 | }, 40 | }, 41 | 'loggers': { 42 | '': { 43 | 'handlers': ['console'], 44 | 'level': 'DEBUG', 45 | }, 46 | 'plugins': { 47 | 'handlers': ['console'], 48 | 'level': 'INFO', 49 | 'propagate': False 50 | } 51 | }, 52 | } 53 | 54 | logging.config.dictConfig(config) 55 | 56 | class Result(object): 57 | def __init__(self, 58 | ok=False, response=None, status_code=None, 59 | error=None, message=None, json=None): 60 | self.ok = ok 61 | self.response = response 62 | self.status_code = status_code 63 | self.error = error 64 | self.message = message 65 | self.json = json 66 | 67 | class DictQuery(dict): 68 | def get(self, path, default = None): 69 | keys = path.split("/") 70 | val = None 71 | 72 | for key in keys: 73 | if val: 74 | if isinstance(val, list): 75 | val = [ v.get(key, default) if v else None for v in val] 76 | else: 77 | val = val.get(key, default) 78 | else: 79 | val = dict.get(self, key, default) 80 | 81 | if not val: 82 | break; 83 | 84 | return val 85 | 86 | class iosxerestapi(object): 87 | def __init__(self, host=None, username=None, password=None, port=443): 88 | self.host = host 89 | self.username = username 90 | self.password = password 91 | self.port = port 92 | self.logger = logging.getLogger('iosxerestapi') 93 | 94 | def __repr__(self): 95 | return '%s(%r)' % (self.__class__.__name__, self.host) 96 | 97 | def _execute_call(self, url, method='get', data=None): 98 | try: 99 | self.logger.info('Calling {}'.format(url)) 100 | requests.packages.urllib3.disable_warnings() 101 | url_base = 'https://{0}:{1}/restconf/data/'.format(self.host, self.port) 102 | headers = { 103 | 'Accept': 'application/yang-data+json', 104 | 'content-type': 'application/yang-data+json' 105 | } 106 | if method == 'get': 107 | response = requests.get(url_base+url, auth=(self.username, self.password), headers=headers, verify=False) 108 | elif method == 'patch': 109 | response = requests.patch(url_base+url, auth=(self.username, self.password), headers=headers, verify=False, data=json.dumps(data, ensure_ascii=False)) 110 | elif method == 'delete': 111 | response = requests.delete(url_base+url, auth=(self.username, self.password), headers=headers, verify=False, data=json.dumps(data, ensure_ascii=False)) 112 | 113 | result = Result(response=response) 114 | result.status_code = response.status_code 115 | 116 | if response.status_code in HTTP_ERROR_CODES: 117 | result.ok = False 118 | result.error = HTTP_ERROR_CODES[response.status_code] 119 | 120 | elif response.status_code in HTTP_SERVER_ERRORS: 121 | result.ok = False 122 | result.error = HTTP_SERVER_ERRORS[response.status_code] 123 | 124 | elif response.status_code in HTTP_SUCCESS_CODES: 125 | result.ok = True 126 | result.message = HTTP_SUCCESS_CODES[response.status_code] 127 | 128 | if not response.status_code == 204: 129 | result.json = response.json() 130 | 131 | return result 132 | 133 | #response = requests.get(url, auth=(USER, PASS), headers=headers, verify=False) 134 | except Exception as e: 135 | self.logger.error(e) 136 | 137 | def get_bgp(self): 138 | """Function to get BGP information on IOS XE""" 139 | neighbors_list = dict() 140 | neighbors_list['Cisco-IOS-XE-bgp-oper:bgp-state-data'] = {'neighbors':[]} 141 | api_data = self._execute_call('Cisco-IOS-XE-bgp-oper:bgp-state-data') 142 | neighbors = DictQuery(api_data.json).get( 143 | 'Cisco-IOS-XE-bgp-oper:bgp-state-data/neighbors/neighbor') 144 | 145 | for neighbor in neighbors: 146 | dict_temp = {} 147 | dict_temp['neighbor-id'] = neighbor.get('neighbor-id',None) 148 | dict_temp['link'] = neighbor.get('link',None) 149 | dict_temp['up-time'] = neighbor.get('up-time',None) 150 | dict_temp['state'] = DictQuery(neighbor.get('connection')).get('state') 151 | dict_temp['total-prefixes'] = DictQuery(neighbor.get('prefix-activity')).get('received/total-prefixes') 152 | neighbors_list['Cisco-IOS-XE-bgp-oper:bgp-state-data']['neighbors'].append(dict_temp) 153 | 154 | return json.dumps(neighbors_list, sort_keys=False, indent=4) 155 | 156 | def get_device(self): 157 | """Function to get Device information on IOS XE""" 158 | device_list = dict() 159 | # device_list['Cisco-IOS-XE-native:native'] = {'device':[]} 160 | api_data = self._execute_call('Cisco-IOS-XE-native:native') 161 | device = DictQuery(api_data.json).get( 162 | 'Cisco-IOS-XE-native:native') 163 | 164 | # print(system) 165 | 166 | hostname = device.get('hostname') 167 | version = device.get('version') 168 | 169 | dict_temp = dict() 170 | dict_temp['hostname'] = hostname 171 | dict_temp['version'] = version 172 | device_list['Cisco-IOS-XE-native:native'] = dict() 173 | device_list['Cisco-IOS-XE-native:native']['device'] = dict_temp 174 | 175 | 176 | return json.dumps(device_list, sort_keys=False, indent=4) 177 | 178 | 179 | def get_interfaces_oper(self): 180 | """Function to get interface information on IOS XE""" 181 | # return self._execute_call('Cisco-IOS-XE-interfaces-oper:interfaces') 182 | interfaces_list = dict() 183 | interfaces_list['Cisco-IOS-XE-interfaces-oper:interfaces'] = {'interface':[]} 184 | api_data = self._execute_call('Cisco-IOS-XE-interfaces-oper:interfaces') 185 | interfaces = DictQuery(api_data.json).get('Cisco-IOS-XE-interfaces-oper:interfaces/interface') 186 | 187 | for interface in interfaces: 188 | dict_temp = {} 189 | dict_temp['name'] = interface.get('name') 190 | dict_temp['description'] = interface.get('description') 191 | dict_temp['ipv4'] = interface.get('ipv4') 192 | dict_temp['vrf'] = interface.get('vrf') 193 | dict_temp['admin-status'] = interface.get('admin-status') 194 | dict_temp['input-security-acl'] = interface.get('input-security-acl') 195 | dict_temp['output-security-acl'] = interface.get('output-security-acl') 196 | dict_temp['in-discards'] = interface.get('in-discards') 197 | dict_temp['in-errors'] = interface.get('in-errors') 198 | dict_temp['out-discards'] = interface.get('out-discards') 199 | dict_temp['out-errors'] = interface.get('out-errors') 200 | dict_temp['in-pkts'] = interface.get('in-pkts') 201 | dict_temp['out-pkts'] = interface.get('out-pkts') 202 | 203 | interfaces_list['Cisco-IOS-XE-interfaces-oper:interfaces']['interface'].append(dict_temp) 204 | 205 | return json.dumps(interfaces_list, sort_keys=False, indent=4) 206 | 207 | def get_interfaces_list(self): 208 | """Function to get interface information on IOS XE""" 209 | interfaces_list = str() 210 | api_data = self._execute_call('Cisco-IOS-XE-interfaces-oper:interfaces') 211 | interfaces = DictQuery(api_data.json).get('Cisco-IOS-XE-interfaces-oper:interfaces/interface') 212 | 213 | for interface in interfaces: 214 | interfaces_list = interfaces_list + interface.get('name') + '\n' 215 | 216 | return interfaces_list 217 | 218 | def add_access_group(self, interface): 219 | """Function to create a IP accessgroup on IOS XE""" 220 | parsed_interface =re.search(r"(?P[A-Za-z]+)(?P\d((/\d+)+(\.\d+)?)|\d)",interface).groupdict() 221 | interface_name = parsed_interface.get('intrfname') 222 | interface_number = parsed_interface.get('intf_num') 223 | api_interface = '{0}={1}'.format(interface_name, interface_number) 224 | url = 'https://{0}:{1}/data/Cisco-IOS-XE-native:native/interface/{2}'.format(self.host, self.port, api_interface) 225 | headers = { 226 | 'Accept': 'application/yang-data+json', 227 | 'content-type': 'application/yang-data+json' 228 | } 229 | 230 | data = { 231 | "Cisco-IOS-XE-native:GigabitEthernet":[ 232 | { 233 | "name":interface_number, 234 | "ip":{ 235 | "access-group":{ 236 | "in":{ 237 | "acl":{ 238 | "acl-name":"DROP", 239 | "in":[None] 240 | } 241 | } 242 | } 243 | } 244 | } 245 | ] 246 | } 247 | 248 | response = self._execute_call('Cisco-IOS-XE-native:native/interface/{0}'.format(api_interface), method='patch', data=data) 249 | return response 250 | 251 | def delete_access_group(self, interface): 252 | """Function to delete a IP accessgroup on IOS XE""" 253 | parsed_interface =re.search(r"(?P[A-Za-z]+)(?P\d((/\d+)+(\.\d+)?)|\d)",interface).groupdict() 254 | interface_name = parsed_interface.get('intrfname') 255 | interface_number = parsed_interface.get('intf_num') 256 | api_interface = '{0}={1}'.format(interface_name, interface_number) 257 | url = 'https://{0}:{1}/data/Cisco-IOS-XE-native:native/interface/{2}/ip/access-group/in/acl'.format(self.host, self.port, api_interface) 258 | headers = { 259 | 'Accept': 'application/yang-data+json', 260 | 'content-type': 'application/yang-data+json' 261 | } 262 | 263 | data = {} 264 | response = self._execute_call('Cisco-IOS-XE-native:native/interface/{0}/ip/access-group/in/acl'.format(api_interface), method='delete', data=json.dumps(data)) 265 | return response 266 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.20.0 2 | click==6.7 3 | tabulate==0.8.2 4 | -------------------------------------------------------------------------------- /router_info.py: -------------------------------------------------------------------------------- 1 | from iosxeapi.iosxerestapi import iosxerestapi 2 | import click 3 | import json 4 | 5 | 6 | class Device(object): 7 | def __init__(self, ip=None, port=None, username=None, password=None): 8 | self.ip = ip 9 | self.port = port 10 | self.username = username 11 | self.password = password 12 | def set_up(self): 13 | return iosxerestapi(host=self.ip, username=self.username, password=self.password, port=self.port) 14 | 15 | @click.group() 16 | # ip addresses or dns of devices 17 | @click.option("--ip",help="ip or dns address of device") 18 | # file of ip addresseses or dns of devices 19 | @click.option("--file",help="file ip addresses of devices") 20 | # option for custom port or uses restconf port 443 21 | @click.option("--port", default=443, help="device port, default = 443" ) 22 | # prompts user for name/password of device(s) 23 | @click.option("--username",help="device username (default lab username = cisco)", prompt=True, hide_input=False) 24 | @click.option("--password",help="device password (default lab password = cisco)", prompt=True, hide_input=True) 25 | @click.pass_context 26 | 27 | def main(ctx, ip, file, port, username, password): 28 | """Gather and Add IOS XE device information using restconf""" 29 | devices = [] 30 | if ip: 31 | device = Device(ip, port, username, password) 32 | devices.append(device) 33 | click.secho("Working....") 34 | else: 35 | try: 36 | with open(file) as f: 37 | device_data = json.load(f) 38 | except (ValueError, IOError, OSError) as err: 39 | print("Could not read the 'devices' file:", err) 40 | 41 | for device_info in device_data.values(): 42 | ip = device_info['IP'] 43 | device = Device(device_info['IP'], port, username, password) 44 | devices.append(device) 45 | click.secho("Working....{}".format(ip)) 46 | ctx.obj = devices 47 | 48 | 49 | @main.command('get_device') 50 | @click.pass_obj 51 | def get_device(devices): 52 | """Gather Device information""" 53 | for device in devices: 54 | api = device.set_up() 55 | result = api.get_device() 56 | print(result) 57 | click.secho("Task completed") 58 | 59 | 60 | @main.command('get_bgp') 61 | @click.pass_obj 62 | def get_bgp(devices): 63 | """Gather BGP information""" 64 | for device in devices: 65 | api = device.set_up() 66 | result = api.get_bgp() 67 | print(result) 68 | click.secho("Task completed") 69 | 70 | 71 | @main.command('get_interfaces') 72 | @click.pass_obj 73 | def get_interfaces(devices): 74 | """Gather Interface information""" 75 | for device in devices: 76 | api = device.set_up() 77 | result = api.get_interfaces_oper() 78 | print(result) 79 | click.secho("Task completed") 80 | 81 | @main.command('add_drop') 82 | @click.pass_obj 83 | def add_drop(devices): 84 | """Add ACL to Interface """ 85 | for device in devices: 86 | click.secho("Select Interface!") 87 | router_object = device.set_up() 88 | list_interfaces = router_object.get_interfaces_list() 89 | user_interface = click.prompt('Available Interfaces Are:\n' + list_interfaces) 90 | access = router_object.add_access_group(user_interface) 91 | print(access.message) 92 | click.secho("Task completed") 93 | 94 | @main.command('delete_drop') 95 | @click.pass_obj 96 | def delete_drop(devices): 97 | """Remove ACL from Interface """ 98 | for device in devices: 99 | click.secho("Select Interface!") 100 | router_object = device.set_up() 101 | list_interfaces = router_object.get_interfaces_list() 102 | user_interface = click.prompt('Available Interfaces Are:\n' + list_interfaces) 103 | delete = router_object.delete_access_group(user_interface) 104 | print(delete.message) 105 | click.secho("Task completed") 106 | 107 | main() 108 | -------------------------------------------------------------------------------- /routers.json: -------------------------------------------------------------------------------- 1 | { 2 | "router:1": { 3 | "IP": "172.16.30.68" 4 | }, 5 | "router:2": { 6 | "IP": "172.16.30.69" 7 | }, 8 | "router:3": { 9 | "IP": "172.16.30.70" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /topology.virl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | flat 5 | 6 | 7 | 8 | 1 9 | ! 10 | hostname host-CE-1 11 | boot-start-marker 12 | boot-end-marker 13 | ! 14 | vrf definition Mgmt-intf 15 | ! 16 | address-family ipv4 17 | exit-address-family 18 | ! 19 | address-family ipv6 20 | exit-address-family 21 | ! 22 | ! 23 | ! 24 | license accept end user agreement 25 | license boot level premium 26 | ! 27 | ! 28 | no aaa new-model 29 | ! 30 | ! 31 | ipv6 unicast-routing 32 | ! 33 | ! 34 | service timestamps debug datetime msec 35 | service timestamps log datetime msec 36 | no service password-encryption 37 | no service config 38 | enable password cisco 39 | enable secret 4 tnhtc92DXBhelxjYk8LWJrPV36S2i4ntXrpb4RFmfqY 40 | ip classless 41 | ip subnet-zero 42 | no ip domain lookup 43 | crypto key generate rsa modulus 1024 44 | ip ssh server algorithm authentication password 45 | username cisco privilege 15 secret cisco 46 | line vty 0 4 47 | transport input ssh telnet 48 | exec-timeout 720 0 49 | password cisco 50 | login local 51 | line con 0 52 | password cisco 53 | ! 54 | restconf 55 | ! 56 | interface VirtualPortGroup0 57 | ip address 10.1.1.1 255.255.255.0 58 | no mop enabled 59 | no mop sysid 60 | ! 61 | interface GigabitEthernet1 62 | description OOB Management 63 | vrf forwarding Mgmt-intf 64 | ! Configured on launch 65 | no ip address 66 | cdp enable 67 | no shutdown 68 | ! 69 | interface GigabitEthernet2 70 | description to pe-1 71 | ip address 10.1.2.1 255.255.255.0 72 | no mop enabled 73 | no mop sysid 74 | ! 75 | interface GigabitEthernet3 76 | description to pe-2 77 | ip address 10.1.3.1 255.255.255.0 78 | no mop enabled 79 | no mop sysid 80 | ! 81 | router bgp 100 82 | bgp log-neighbor-changes 83 | neighbor 10.1.2.2 remote-as 200 84 | neighbor 10.1.3.2 remote-as 300 85 | ! 86 | track 2 ip sla 2 reachability 87 | ! 88 | ip sla 2 89 | icmp-echo 10.1.2.2 90 | frequency 5 91 | ip sla schedule 2 life forever start-time now 92 | ip sla 3 93 | icmp-echo 10.1.3.3 94 | frequency 5 95 | ip sla schedule 3 life forever start-time now 96 | ! 97 | ip route vrf Mgmt-intf 0.0.0.0 0.0.0.0 {{ gateway }} 98 | ! 99 | event manager applet bgp-check 100 | event syslog pattern "TRACK-6-STATE" 101 | action 1.0 cli command "enable" 102 | action 2.0 cli command "guestshell run python /home/guestshell/bgp_down.py --syslog" 103 | action 3.0 regexp "ERROR" "$_cli_result" 104 | action 4.0 if $_regexp_result eq "1" 105 | action 5.0 syslog msg "$_cli_result" 106 | action 6.0 end 107 | ! 108 | app-hosting appid guestshell 109 | app-vnic gateway0 virtualportgroup 0 guest-interface 0 110 | guest-ipaddress 10.1.1.2 netmask 255.255.255.0 111 | ! 112 | end 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 1 121 | ! 122 | hostname host-PE-1 123 | boot-start-marker 124 | boot-end-marker 125 | ! 126 | vrf definition Mgmt-intf 127 | ! 128 | address-family ipv4 129 | exit-address-family 130 | ! 131 | address-family ipv6 132 | exit-address-family 133 | ! 134 | ! 135 | ! 136 | license accept end user agreement 137 | license boot level premium 138 | ! 139 | ! 140 | no aaa new-model 141 | ! 142 | ! 143 | ipv6 unicast-routing 144 | ! 145 | ! 146 | service timestamps debug datetime msec 147 | service timestamps log datetime msec 148 | no service password-encryption 149 | no service config 150 | enable password cisco 151 | enable secret 4 tnhtc92DXBhelxjYk8LWJrPV36S2i4ntXrpb4RFmfqY 152 | ip classless 153 | ip subnet-zero 154 | no ip domain lookup 155 | crypto key generate rsa modulus 1024 156 | ip ssh server algorithm authentication password 157 | ip scp server enable 158 | username cisco privilege 15 secret cisco 159 | line vty 0 4 160 | transport input ssh telnet 161 | exec-timeout 720 0 162 | password cisco 163 | login local 164 | line con 0 165 | password cisco 166 | ! 167 | restconf 168 | ! 169 | ip access-list extended DROP 170 | deny icmp any any 171 | permit tcp any any 172 | ! 173 | interface Loopback1 174 | ip address 172.168.1.1 255.255.255.255 175 | ! 176 | interface GigabitEthernet1 177 | description OOB Management 178 | vrf forwarding Mgmt-intf 179 | ! Configured on launch 180 | no ip address 181 | cdp enable 182 | no shutdown 183 | ! 184 | interface GigabitEthernet2 185 | description to ce-1 186 | ip address 10.1.2.2 255.255.255.0 187 | no mop enabled 188 | no mop sysid 189 | ! 190 | interface GigabitEthernet3 191 | negotiation auto 192 | no mop enabled 193 | no mop sysid 194 | ! 195 | router bgp 200 196 | bgp log-neighbor-changes 197 | redistribute connected 198 | neighbor 10.1.2.1 remote-as 100 199 | ! 200 | ip route vrf Mgmt-intf 0.0.0.0 0.0.0.0 {{ gateway }} 201 | ! 202 | end 203 | 204 | 205 | 206 | 207 | 208 | 209 | 1 210 | ! 211 | hostname host-PE-2 212 | boot-start-marker 213 | boot-end-marker 214 | ! 215 | vrf definition Mgmt-intf 216 | ! 217 | address-family ipv4 218 | exit-address-family 219 | ! 220 | address-family ipv6 221 | exit-address-family 222 | ! 223 | ! 224 | ! 225 | license accept end user agreement 226 | license boot level premium 227 | ! 228 | ! 229 | no aaa new-model 230 | ! 231 | ! 232 | ipv6 unicast-routing 233 | ! 234 | ! 235 | service timestamps debug datetime msec 236 | service timestamps log datetime msec 237 | no service password-encryption 238 | no service config 239 | enable password cisco 240 | enable secret 4 tnhtc92DXBhelxjYk8LWJrPV36S2i4ntXrpb4RFmfqY 241 | ip classless 242 | ip subnet-zero 243 | no ip domain lookup 244 | crypto key generate rsa modulus 1024 245 | ip ssh server algorithm authentication password 246 | username cisco privilege 15 secret cisco 247 | line vty 0 4 248 | transport input ssh telnet 249 | exec-timeout 720 0 250 | password cisco 251 | login local 252 | line con 0 253 | password cisco 254 | ! 255 | restconf 256 | ! 257 | interface Loopback1 258 | ip address 172.168.1.1 255.255.255.0 259 | ! 260 | interface GigabitEthernet1 261 | description OOB Management 262 | vrf forwarding Mgmt-intf 263 | ! Configured on launch 264 | no ip address 265 | cdp enable 266 | no shutdown 267 | ! 268 | interface GigabitEthernet2 269 | description to ce-1 270 | ip address 10.1.3.2 255.255.255.0 271 | no mop enabled 272 | no mop sysid 273 | ! 274 | interface GigabitEthernet3 275 | negotiation auto 276 | no mop enabled 277 | no mop sysid 278 | ! 279 | router bgp 300 280 | bgp log-neighbor-changes 281 | redistribute connected 282 | neighbor 10.1.3.1 remote-as 100 283 | ! 284 | ip route vrf Mgmt-intf 0.0.0.0 0.0.0.0 {{ gateway }} 285 | ! 286 | end 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | --------------------------------------------------------------------------------