Skip to content

Commit 88a8457

Browse files
committed
coding 102 changes
1 parent fc407c1 commit 88a8457

File tree

9 files changed

+223
-40
lines changed

9 files changed

+223
-40
lines changed

labs/coding-102-rest-python-ga/1.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ Completion Time: 35 minutes
3030
* **If you are working on a DevNet Learning Lab PC at a DevNet event**, the Requests Library is already installed.
3131
* See **How to Set up Your Own Computer** section above for how to install the Requests Library on your own machine.
3232

33+
**Python Flask Library**
34+
* Step 6 uses use the Python Flask Library to interface with NeXt UI.
35+
* **If you are working on a DevNet Learning Lab PC at a DevNet event**, the Flask Library is already installed.
36+
* See **How to Set up Your Own Computer** section above for how to install the Flask Library on your own machine.
37+
3338
**Clone Git Repo**
3439
* **If you are working on a DevNet Learning Lab PC at a DevNet event**,
3540
* Open the Git Command window by clicking on the *Git CMD* icon on the Task Bar or click on the Start button, then in the Run bar type: **git cmd** and press the **Enter** key.

labs/coding-102-rest-python-ga/3.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ These files are located on your workstation in the directory: /DevNetCode/<yo
66
* **create-ticket.py** - Example to create a service ticket. Used in Step 2.
77
* **get-network-hosts.py** – First application to parse the service ticket response and show list of hosts by doing a pretty print of the JSON data
88
* **get-network-devices.py** – Retrieves network device list and parses JSON to display networkDeviceId values
9-
* **build-topology.py** – Shows how to retrieve devices and interfaces, and to build a spreadsheet-like topology
9+
* **build-topology.py** – Shows how to retrieve devices and interfaces, and to build and display spreadsheet-like text topology
10+
* **build-topology-web-server.py** – Shows how to retrieve devices and interfaces, and to build a graphical topology
1011

1112

1213
#### get-network-hosts.py

labs/coding-102-rest-python-ga/5.md

Lines changed: 48 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ import json
1717
# Disable warnings
1818
requests.packages.urllib3.disable_warnings()
1919

20+
#controller='sandboxapic.cisco.com'
2021
controller='devnetapi.cisco.com/sandbox/apic_em'
2122

23+
2224
def getTicket():
2325
# put the ip address or dns of your apic-em controller in this url
2426
url = "https://" + controller + "/api/v1/ticket"
@@ -63,43 +65,48 @@ def getTopology(ticket):
6365
r_json=response.json()
6466

6567
#Iterate through network device data and list the nodes, their interfaces, status and to what they connect
66-
#Iterate through network device data and list the nodes, their interfaces, status and to what they connect
67-
for n in r_json["response"]["nodes"]:
68+
for n in r_json["response"]["nodes"]:
69+
print()
70+
print()
71+
print('{:30}'.format("Node") + '{:25}'.format("Family") + '{:20}'.format("Label")+ "Management IP")
6872
if "platformId" in n:
69-
print()
70-
print()
71-
print('{:30}'.format("Node") + '{:25}'.format("Family") + '{:20}'.format("Label")+ "Management IP")
72-
print('{:30}'.format(n["platformId"]) + '{:25}'.format(n["family"]) + '{:20}'.format(n["label"]) + n["ip"])
73+
print('{:30}'.format(n["platformId"]) + '{:25}'.format(n["family"]) + '{:20.14}'.format(n["label"]) + n["ip"])
74+
else:
75+
print('{:30}'.format(n["role"]) + '{:25}'.format(n["family"]) + '{:20.14}'.format(n["label"]) + n["ip"])
7376
found=0 #print header flag
7477
printed=0 #formatting flag
7578
for i in r_json["response"]["links"]:
76-
if "startPortName" in i:
77-
#check that the source device id for the interface matches the node id. Means interface originated from this device.
78-
if i["source"] == n["id"]:
79-
if found==0:
80-
print('{:>20}'.format("Source Interface") + '{:>15}'.format("Target") +'{:>28}'.format("Target Interface") + '{:>15}'.format("Status") )
81-
found=1
82-
printed=1
83-
for n1 in r_json["response"]["nodes"]:
84-
#find name of node to which this one connects
85-
if i["target"] == n1["id"]:
86-
print(" " + '{:<25}'.format(i["startPortName"]) + '{:<18}'.format(n1["platformId"]) + '{:<25}'.format(i["endPortName"]) + '{:<9}'.format(i["linkStatus"]) )
87-
break;
88-
found=0
79+
#check that the source device id for the interface matches the node id. Means interface originated from this device.
80+
if i["source"] == n["id"]:
81+
if found==0:
82+
print('{:>20}'.format("Source Interface") + '{:>15}'.format("Target") +'{:>28}'.format("Target Interface") + '{:>15}'.format("Status") )
83+
found=1
84+
printed=1
85+
for n1 in r_json["response"]["nodes"]:
86+
#find name of node to which this one connects
87+
if i["target"] == n1["id"]:
88+
if "startPortName" in i:
89+
print(" " + '{:<25}'.format(i["startPortName"]) + '{:<18.14}'.format(n1["label"]) + '{:<25}'.format(i["endPortName"]) + '{:<9}'.format(i["linkStatus"]) )
90+
else:
91+
print(" " + '{:<25}'.format("unknown") + '{:<18.14}'.format(n1["label"]) + '{:<25}'.format("unknown") + '{:<9}'.format(i["linkStatus"]) )
92+
break;
93+
found=0
8994
for i in r_json["response"]["links"]:
90-
if "startPortName" in i:
91-
#Find interfaces that link to this one which means this node is the target.
92-
if i["target"] == n["id"]:
93-
if found==0:
94-
if printed==1:
95-
print()
96-
print('{:>10}'.format("Source") + '{:>30}'.format("Source Interface") + '{:>25}'.format("Target Interface") + '{:>13}'.format("Status"))
97-
found=1
98-
for n1 in r_json["response"]["nodes"]:
99-
#find name of node to that connects to this one
100-
if i["source"] == n1["id"]:
101-
print(" " + '{:<20}'.format(n1["platformId"]) + '{:<25}'.format(i["startPortName"]) + '{:<23}'.format(i["endPortName"]) + '{:<8}'.format(i["linkStatus"]))
102-
break;
95+
#Find interfaces that link to this one which means this node is the target.
96+
if i["target"] == n["id"]:
97+
if found==0:
98+
if printed==1:
99+
print()
100+
print('{:>10}'.format("Source") + '{:>30}'.format("Source Interface") + '{:>25}'.format("Target Interface") + '{:>13}'.format("Status"))
101+
found=1
102+
for n1 in r_json["response"]["nodes"]:
103+
#find name of node to that connects to this one
104+
if i["source"] == n1["id"]:
105+
if "startPortName" in i:
106+
print(" " + '{:<20}'.format(n1["label"]) + '{:<25}'.format(i["startPortName"]) + '{:<23}'.format(i["endPortName"]) + '{:<8}'.format(i["linkStatus"]))
107+
else:
108+
print(" " + '{:<20}'.format(n1["label"]) + '{:<25}'.format("unknown") + '{:<23}'.format("unknown") + '{:<8}'.format(i["linkStatus"]))
109+
break;
103110

104111
theTicket=getTicket()
105112
getTopology(theTicket)
@@ -111,7 +118,9 @@ Let's look at what the code is doing. We'll focus on the key code changes.
111118
* *for n in r_json["response"]["nodes"]:*
112119
* We read the dictionary data for each node into n from which we will parse the data.
113120
* *if "platformId" in n:*
114-
* Checking that the node in n contains a 'platformId' key. Some records don't have this key which is the reason for the check.
121+
* Checking that the node data in n contains a 'platformId' key. Some records don't have this key so different field data would need to be accessed.
122+
* *if "startPortName" in i:*
123+
* Checking that the interface data in i contains a 'startPortName' key. Some records don't have this key so different field data would need to be accessed.
115124

116125

117126
To run this code sample:
@@ -133,7 +142,9 @@ To run this code sample:
133142

134143

135144
## Things to Try
136-
* After running the script, review the node data printed. Replace the label attribute that is printed with another attribute such as role or nodeType.
137-
* You may have noticed that no host devices are printed. Review the JSON data and the source code, and determine why hosts aren't printed along with how you would print this data. Hint: you can easily identify the host by its attribute nodeType or role.
138-
* Modify the source code so that host devices are printed as well.
139-
* Modify the source code so that interface connections to the hosts are printed as is currently done with the other network devices.
145+
* After running the script review the node data printed. Modify the source code by replacing the label key with another key such as role or nodeType. Run the script again and determine the change of the data displayed.
146+
* Review the printed topology data and determine how many host devices exist and to which devices they connect.
147+
* Starting from the cloud node, use the topology data provided to draw a picture of the first three layers of the topology. Hint: look for matching devices specified by the Label attribute. For example, the cloud node connects to four devices with one device being Branch-Router1. Determine which devices Branch-Router1 connects to by finding it in the Label field of the topology data. Then do the same steps for the other three devices.
148+
149+
150+
In the next section, we will learn how to use the NeXt UI toolkit and Flask to build a network topology and display it graphically

labs/coding-102-rest-python-ga/6.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Step 6. Build Network Topology and Graphically Display it
2+
3+
In this step we're going to get the network topology data and have a separate graphical tool Cisco [NeXt UI](https://developer.cisco.com/site/neXt/) parse the data and graphically render the topology. We'll also use a web server tool called [Flask](http://flask.pocoo.org/) which well use to interface with NeXt UI. Flask is a Python module
4+
5+
Let's take a high level view of how this process works in a step-by-step manner then we'll look at more detail in the source code. Please see the flow chart below as well.
6+
1. When you start the python script build-topology-web-server.py it starts Flask which is a web server.
7+
2. Flask will call the index() function located in the build-topology-web-server.py which sets off a chain of events.
8+
3. The index() function loads the file topology.html which has javascript in it that uses the NeXt UI toolkit libraries.
9+
4. NeXt calls the topology() function in file build-topology-web-server.py to get the topology data in JSON format
10+
5. NeXt then parses and renders the network topology from the data.
11+
12+
![](/posts/files/coding-102-rest-python-ga/assets/images/web-flow.png)
13+
14+
15+
Now let's dig into the source code. We'll focus just on the python script.
16+
#### build-topology-web-server.py
17+
This sample code starts the Flask web application and provides functions that are called to retrieve a list of devices called nodes and links which are the interfaces that connect them. This script along with Flask and NeXt UI read the network topology and graphically display it as a web page.
18+
19+
20+
```python
21+
# import requests library
22+
import requests
23+
24+
# import json library
25+
import json
26+
27+
# import flask web framework
28+
from flask import Flask
29+
30+
# from flask import render_template function
31+
from flask import render_template, jsonify
32+
33+
controller = 'devnetapi.cisco.com/sandbox/apic_em'
34+
35+
36+
def getTicket():
37+
# put the ip address or dns of your apic-em controller in this url
38+
url = "https://" + controller + "/api/v1/ticket"
39+
40+
# the username and password to access the APIC-EM Controller
41+
payload = {"username": "devnetuser", "password": "Cisco123!"}
42+
43+
# Content type must be included in the header
44+
header = {"content-type": "application/json"}
45+
46+
# Performs a POST on the specified url to get the service ticket
47+
response = requests.post(url, data=json.dumps(payload), headers=header, verify=False)
48+
49+
print(response)
50+
51+
# convert response to json format
52+
r_json = response.json()
53+
54+
# parse the json to get the service ticket
55+
ticket = r_json["response"]["serviceTicket"]
56+
57+
return ticket
58+
59+
60+
def getTopology(ticket):
61+
# URL for network-device REST API call to get list of exisiting devices on the network.
62+
url = "https://" + controller + "/api/v1/topology/physical-topology"
63+
64+
# Content type as well as the ticket must be included in the header
65+
header = {"content-type": "application/json", "X-Auth-Token": ticket}
66+
67+
# this statement performs a GET on the specified network device url
68+
response = requests.get(url, headers=header, verify=False)
69+
70+
# convert data to json format.
71+
r_json = response.json()
72+
73+
# return json object
74+
return r_json["response"]
75+
76+
77+
# intialize a web app
78+
app = Flask(__name__)
79+
80+
81+
# define index route to return topology.html
82+
@app.route("/")
83+
def index():
84+
# when called '/' which is the default index page, render the template 'topology.html'
85+
return render_template("topology.html")
86+
87+
88+
# define an reset api to get topology data
89+
@app.route("/api/topology")
90+
def topology():
91+
# get ticket
92+
theTicket = getTicket()
93+
94+
# get topology data and return `jsonify` string to request
95+
return jsonify(getTopology(theTicket))
96+
97+
98+
if __name__ == "__main__":
99+
app.run()
100+
```
101+
102+
Let's look at what the code is doing. We'll focus on the key code changes.
103+
* *from flask import Flask*
104+
* From the flask python module imports the Flask object
105+
* *from flask import render_template, jsonify*
106+
* From the flask python module imports a couple of functions which are render_template and jsonify.
107+
* *app = Flask(__name__)*
108+
* Instanitates the Flask web application.
109+
* *@app.route("/")*
110+
* Tags the function below it "def index()" and specifies that it is called as the default web page for the Flask web application variable 'app'.
111+
* *def index():*
112+
* The index function that calls the Flask function render_template that loads the topology.html web page.
113+
* *@app.route("/api/topology")*
114+
* Tags the function below it "def topology()" and is called by NeXt UI. Identifies as web page for the Flask web application variable 'app'
115+
* *def topology():*
116+
* Returns the network topology data in JSON format.
117+
* *if __name__ == "__main__":*
118+
* Optional code that specifies that if this module is run, rather than just importing it and calling it's functions, that below this point is where the script should begin running. Python typically knows already has this information, but this prevents any ambiguity.
119+
* *app.run()*
120+
* Starts the Flask web application.
121+
122+
123+
To run this code sample:
124+
1. Go to directory **coding102-REST-python-ga**. In the terminal type:
125+
**cd \DevNetCode\&lt;your-name&gt;\coding-skills-sample-code\coding102-REST-python-ga**
126+
2. Assign the APIC-EM Controller IP address or DNS to the **controller** variable.
127+
* Open the file **build-topology-web-server.py**. For example, in Windows type: **notepad build-topology-web-server.py**
128+
* *If you are not using your own APIC-EM Controller*, use the [DevNet Sandbox](https://developer.cisco.com/site/devnet/sandbox/) Always-On APIC-EM Lab: https[]()://devnetapi.cisco.com/sandbox/apic_em
129+
* controller='devnetapi.cisco.com/sandbox/apic_em'
130+
3. Save the file. If encoding type is an option select **UTF-8**.
131+
4. Type the python command and then the filename at the command prompt, and press the return key.
132+
* *On Windows type*: **py -3 build-topology-web-server.py**. Or type: **python build-topology-web-server.py**
133+
* *On Mac OS or Linux type*: **python3 build-topology-web-server.py**
134+
5. The program will start listening at IP 128.0.0.1 port 5000. If the error: *"ImportError: No module named 'flask'"* is given click on "How to Set up Your Computer" on the top of this web page.
135+
![](/posts/files/coding-102-rest-python-ga/assets/images/webapp-start.png)
136+
6. Open a web browser such as Chrome or Safari and in the URL field enter the URL **http://127.0.0.1:5000** . You should see a result like the following below.
137+
![](/posts/files/coding-102-rest-python-ga/assets/images/topology-graph.png)
138+
139+
140+
## Things to Try
141+
* Compare the first three layers of the topology to what you drew in step 5. Do they match?
142+
* Go to the templates directory and open the file topology.html. In the javascript block identified within block *<script type="text/javascript">* change the width and height from 800 to 400. Rerun the program. What's changed?
143+
144+
145+
##Congratulations! You've completed Coding 102!
Loading
Loading
Loading

labs/coding-102-rest-python-ga/byod.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,25 @@ <h3>Install the Requests Library</h3>
3737
</li>
3838
</ul>
3939
</li>
40+
<h3>Install the Flask Library</h3>
41+
<ul>
42+
<li>You can read more about the <a href="http://flask.pocoo.org/" target="_blank">Flask Library here </a></li>
43+
<li>To install Flask for Python 3 on Mac OS, use this command line:
44+
<ul>
45+
<li><pre>
46+
pip3 install flask
47+
</pre></li>
48+
</ul>
49+
</li>
50+
<li>To install Requests on Windows:
51+
<ul>
52+
<li>If pip is in the Python34 path, the execute
53+
<pre>
54+
pip install flask
55+
</pre>
56+
</li>
57+
</ul>
58+
</li>
4059
<li>
4160
You will also need access to an APIC-EM controller.
4261
<ul>

labs/coding-102-rest-python-ga/coding-102-rest-python-ga.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@
99
{"title": "2.md"},
1010
{"title": "3.md"},
1111
{"title": "4.md"},
12-
{"title": "5.md"}
12+
{"title": "5.md"},
13+
{"title": "6.md"}
1314
],
1415
"tags": [
1516
{"title": "Coding"},
1617
{"title": "Python"},
1718
{"title": "REST"},
1819
{"title": "APIC-EM"},
19-
{"title": "JSON"}
20+
{"title": "JSON"},
21+
{"title": "NEXT UI"}
2022
],
2123
"related": [
2224
{

0 commit comments

Comments
 (0)