├── requirements.txt
├── .gitignore
├── __pycache__
└── netmiko.cpython-36.pyc
├── iosxeapi
├── __init__.py
└── iosxerestapi.py
├── routers.json
├── CONTRIBUTING.md
├── LICENSE
├── get_device.py
├── get_bgp.py
├── get_interfaces.py
├── router_info.py
├── topology.virl
└── README.md
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests==2.20.0
2 | click==6.7
3 | tabulate==0.8.2
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | *.py[cod]
3 |
4 | # virtualenv
5 | *venv*/
6 |
--------------------------------------------------------------------------------
/__pycache__/netmiko.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigevilbeard/challenges_with-network_automation/HEAD/__pycache__/netmiko.cpython-36.pyc
--------------------------------------------------------------------------------
/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 | ]
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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_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_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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------