Fun with Python and ODL part 2
Last time around I had collected the following string after an API request:
odb = {"nodes":{"node":[{"id":"openflow:8398923990168109056","node-connector":[{"id":"openflow:8398923990168109056:1","flow-node-inventory:supported":"one-gb-hd forty-gb-fd","flow-node-inventory:peer-features":"one-gb-hd forty-gb-fd","flow-node-inventory:advertised-features":"","flow-node-inventory:port-number":"1","flow-node-inventory:hardware-address":"74:8e:f8:de:a1:7a","flow-node-inventory:current-speed":1000000,"flow-node-inventory:current-feature":"one-gb-hd forty-gb-fd","flow-node-inventory:maximum-speed":1000000,"flow-node-inventory:name":"eth1/1/1","flow-node-inventory:state":{"link-down":true,"blocked":false,"live":false},"flow-node-inventory:configuration":"","opendaylight-port-statistics:flow-capable-node-connector-statistics":{"collision-count":0,"transmit-drops":0,"receive-frame-error":0,"transmit-errors":0,"bytes":{"received":0,"transmitted":0},"receive-crc-error":0,"duration":{"second":0,"nanosecond":0},"receive-errors":0,"receive-drops":0,"receive-over-run-error":0,"packets":{"received":0,"transmitted":0}}},{"id":"openflow:8398923990168109056:31","flow-node-inventory:supported":"one-gb-hd forty-gb-fd","flow-node-inventory:peer-features":"one-gb-hd forty-gb-fd","flow-node-inventory:advertised-features":"","flow-node-inventory:port-number":"31","flow-node-inventory:hardware-address":"74:8e:f8:de:a1:98","flow-node-inventory:current-speed":1000000,"flow-node-inventory:current-feature":"one-gb-hd forty-gb-fd","flow-node-inventory:maximum-speed":1000000,"flow-node-inventory:name":"eth1/1/31","flow-node-inventory:state":{"link-down":true,"blocked":false,"live":false},"flow-node-inventory:configuration":"","opendaylight-port-statistics:flow-capable-node-connector-statistics":{"collision-count":0,"transmit-drops":0,"receive-frame-error":0,"transmit-errors":0,"bytes":{"received":0,"transmitted":0},"receive-crc-error":0,"duration":{"second":0,"nanosecond":0},"receive-errors":0,"receive-drops":0,"receive-over-run-error":0,"packets":{"received":0,"transmitted":0}}},{"id":"openflow:8398923990168109056:32","flow-node-inventory:supported":"one-gb-hd forty-gb-fd","flow-node-inventory:peer-features":"one-gb-hd forty-gb-fd","flow-node-inventory:advertised-features":"","flow-node-inventory:port-number":"32","flow-node-inventory:hardware-address":"74:8e:f8:de:a1:99","flow-node-inventory:current-speed":1000000,"flow-node-inventory:current-feature":"one-gb-hd forty-gb-fd","flow-node-inventory:maximum-speed":1000000,"flow-node-inventory:name":"eth1/1/32","flow-node-inventory:state":{"link-down":true,"blocked":false,"live":false},"flow-node-inventory:configuration":"","opendaylight-port-statistics:flow-capable-node-connector-statistics":{"collision-count":0,"transmit-drops":0,"receive-frame-error":0,"transmit-errors":0,"bytes":{"received":0,"transmitted":0},"receive-crc-error":0,"duration":{"second":0,"nanosecond":0},"receive-errors":0,"receive-drops":0,"receive-over-run-error":0,"packets":{"received":0,"transmitted":0}}},{"id":"openflow:8398923990168109056:16","flow-node-inventory:supported":"one-gb-hd forty-gb-fd","flow-node-inventory:peer-features":"one-gb-hd forty-gb-fd","flow-node-inventory:advertised-features":"","flow-node-inventory:port-number":"16","flow-node-inventory:hardware-address":"74:8e:f8:de:a1:89","flow-node-inventory:current-speed":1000000,"flow-node-inventory:current-feature":"one-gb-hd forty-gb-fd","flow-node-inventory:maximum-speed":1000000,"flow-node-inventory:name":"eth1/1/16","flow-node-inventory:state":{"link-down":true,"blocked":false,"live":false},"flow-node-inventory:configuration":"","opendaylight-port-statistics:flow-capable-node-connector-statistics":{"collision-count":0,"transmit-drops":0,"receive-frame-error":0,"transmit-errors":0,"bytes":{"received":0,"transmitted":0},"receive-crc-error":0,"duration":{"second":0,"nanosecond":0},"receive-errors":0,"receive-drops":0,"receive-over-run-error":0,"packets":{"received":0,"transmitted":0}}},{"id":"openflow:8398923990168109056:2","flow-node-inventory:supported":"one-gb-hd forty-gb-fd","flow-node-inventory:peer-features":"one-gb-hd forty-gb-fd","flow-node-inventory:advertised-features":"","flow-node-inventory:port-number":"2","flow-node-inventory:hardware-address":"74:8e:f8:de:a1:7b","flow-node-inventory:current-speed":1000000,"flow-node-inventory:current-feature":"one-gb-hd forty-gb-fd","flow-node-inventory:maximum-speed":1000000,"flow-node-inventory:name":"eth1/1/2","flow-node-inventory:state":{"link-down":true,"blocked":false,"live":false},"flow-node-inventory:configuration":"","opendaylight-port-statistics:flow-capable-node-connector-statistics":{"collision-count":0,"transmit-drops":0,"receive-frame-error":0,"transmit-errors":0,"bytes":{"received":0,"transmitted":0},"receive-crc-error":0,"duration":{"second":0,"nanosecond":0},"receive-errors":0,"receive-drops":0,"receive-over-run-error":0,"packets":{"received":0,"transmitted":0}}}],"flow-node-inventory:snapshot-gathering-status-start":{"begin":"2016-11-11T08:21:01.567-08:00"},"flow-node-inventory:snapshot-gathering-status-end":{"end":"2016-11-11T08:21:02.068-08:00","succeeded":true},"flow-node-inventory:table":[{"id":0,"flow":[{"id":"L2switch-0","opendaylight-flow-statistics:flow-statistics":{"packet-count":0,"duration":{"nanosecond":711000000,"second":607124},"byte-count":0},"priority":100,"table_id":0,"hard-timeout":0,"match":{"ethernet-match":{"ethernet-type":{"type":35020}}},"cookie":3098476543630901248,"flags":"","instructions":{"instruction":[{"order":0,"apply-actions":{"action":[{"order":0,"output-action":{"max-length":65535,"output-node-connector":"CONTROLLER"}}]}}]},"idle-timeout":0}]}],"flow-node-inventory:hardware":"FastIron","flow-node-inventory:description":"None","flow-node-inventory:switch-features":{"max_tables":1,"max_buffers":0,"capabilities":["flow-node-inventory:flow-feature-capability-flow-stats","flow-node-inventory:flow-feature-capability-group-stats","flow-node-inventory:flow-feature-capability-queue-stats","flow-node-inventory:flow-feature-capability-port-stats"]},"flow-node-inventory:manufacturer":"Brocade Communications, Inc","flow-node-inventory:serial-number":"None","flow-node-inventory:software":"FI 8.0.30","flow-node-inventory:ip-address":"10.10.10.2","opendaylight-meter-statistics:meter-features":{"meter-capabilities-supported":["opendaylight-meter-types:meter-kbps","opendaylight-meter-types:meter-stats","opendaylight-meter-types:meter-burst"],"max_bands":2,"meter-band-supported":["opendaylight-meter-types:meter-band-drop","opendaylight-meter-types:meter-band-dscp-remark"],"max_meter":1024,"max_color":3},"opendaylight-group-statistics:group-features":{"group-capabilities-supported":["opendaylight-group-types:select-liveness"],"actions":[1],"group-types-supported":["opendaylight-group-types:group-ff","opendaylight-group-types:group-all","opendaylight-group-types:group-select","opendaylight-group-types:group-indirect"],"max-groups":[512,0]}}]}}
Now I will use the JSON and JMESPATH modules to parse it!
The first step is to convert the string I am returned into objects that I can work with.
The JSON module does this by “loading” the string:
odb_json = json.loads(odb)
Examining the resulting object in the interpreter, I can see that it is a series of dictionaries and lists:
>>> type((json.loads(operational_db)).get('nodes'))
<class 'dict'>
>>> (json.loads(operational_db)).get('nodes').keys()
dict_keys(['node'])
>>> type((json.loads(operational_db)).get('nodes').get('node'))
<class 'list'>
Eventually I arrive at the following:
odb_json = json.loads(operational_db
#Top level is nodes
nodes = odb_json['nodes']
#Next level is node
node = nodes.get('node')
#At the node level there are multiple items, we will print the first one
node1 = node[0]
for k, v in node1.items():
if (k == 'id' or
k == 'flow-node-inventory:ip-address' or
k == 'flow-node-inventory:manufacturer' or
k == 'flow-node-inventory:hardware'):
print(v)
Which returns us:
$ ./odl_json_parse.py
10.10.10.2
FastIron
openflow:8398923990168109056
Brocade Communications, Inc
I’ve got what I wanted, but there has to be an easier way.
I will use the jmespath module to work with the object using a syntax language. It treats the JSON object as a tree that you can navigate through. Play around with it at the jmes examples page to get an idea of how it works.
So, with some experimentation I am able to pull the keys for the ODL node.
>>> (jmespath.search('nodes.node[0]',odb_json)).keys()
dict_keys(['flow-node-inventory:description', 'flow-node-inventory:switch-features', 'opendaylight-group-statistics:group-features', 'node-connector', 'flow-node-inventory:software', 'flow-node-inventory:serial-number', 'opendaylight-meter-statistics:meter-features', 'flow-node-inventory:table', 'flow-node-inventory:snapshot-gathering-status-end', 'id', 'flow-node-inventory:manufacturer', 'flow-node-inventory:ip-address', 'flow-node-inventory:snapshot-gathering-status-start', 'flow-node-inventory:hardware'])
Passing the keys as a parameter to the search function is alittle trickier, the hyphen is a special character. To parse the key as a literal value we wrap the expression in single quotes and wrap the key in double quotes.
>>> jmespath.search('nodes.node[0].["flow-node-inventory:software", "id"]',odb_json)
['FI 8.0.30', 'openflow:8398923990168109056']
Turning that into a program I’m able to reduce this to a two-liner with the same result.
!/usr/local/bin/python3
import json
import jmespath
operational_db = {"nodes":{"node":[{"id":"openflow:8398923990168109056","node-connector":[{"id":"openflow:8398923990168109056:1","flow-node-inventory:supported":"one-gb-hd forty-gb-fd","flow-node-inventory:peer-features":"one-gb-hd forty-gb-fd","flow-node-inventory:advertised-features":"","flow-node-inventory:port-number":"1","flow-node-inventory:hardware-address":"74:8e:f8:de:a1:7a","flow-node-inventory:current-speed":1000000,"flow-node-inventory:current-feature":"one-gb-hd forty-gb-fd","flow-node-inventory:maximum-speed":1000000,"flow-node-inventory:name":"eth1/1/1","flow-node-inventory:state":{"link-down":true,"blocked":false,"live":false},"flow-node-inventory:configuration":"","opendaylight-port-statistics:flow-capable-node-connector-statistics":{"collision-count":0,"transmit-drops":0,"receive-frame-error":0,"transmit-errors":0,"bytes":{"received":0,"transmitted":0},"receive-crc-error":0,"duration":{"second":0,"nanosecond":0},"receive-errors":0,"receive-drops":0,"receive-over-run-error":0,"packets":{"received":0,"transmitted":0}}},{"id":"openflow:8398923990168109056:31","flow-node-inventory:supported":"one-gb-hd forty-gb-fd","flow-node-inventory:peer-features":"one-gb-hd forty-gb-fd","flow-node-inventory:advertised-features":"","flow-node-inventory:port-number":"31","flow-node-inventory:hardware-address":"74:8e:f8:de:a1:98","flow-node-inventory:current-speed":1000000,"flow-node-inventory:current-feature":"one-gb-hd forty-gb-fd","flow-node-inventory:maximum-speed":1000000,"flow-node-inventory:name":"eth1/1/31","flow-node-inventory:state":{"link-down":true,"blocked":false,"live":false},"flow-node-inventory:configuration":"","opendaylight-port-statistics:flow-capable-node-connector-statistics":{"collision-count":0,"transmit-drops":0,"receive-frame-error":0,"transmit-errors":0,"bytes":{"received":0,"transmitted":0},"receive-crc-error":0,"duration":{"second":0,"nanosecond":0},"receive-errors":0,"receive-drops":0,"receive-over-run-error":0,"packets":{"received":0,"transmitted":0}}},{"id":"openflow:8398923990168109056:32","flow-node-inventory:supported":"one-gb-hd forty-gb-fd","flow-node-inventory:peer-features":"one-gb-hd forty-gb-fd","flow-node-inventory:advertised-features":"","flow-node-inventory:port-number":"32","flow-node-inventory:hardware-address":"74:8e:f8:de:a1:99","flow-node-inventory:current-speed":1000000,"flow-node-inventory:current-feature":"one-gb-hd forty-gb-fd","flow-node-inventory:maximum-speed":1000000,"flow-node-inventory:name":"eth1/1/32","flow-node-inventory:state":{"link-down":true,"blocked":false,"live":false},"flow-node-inventory:configuration":"","opendaylight-port-statistics:flow-capable-node-connector-statistics":{"collision-count":0,"transmit-drops":0,"receive-frame-error":0,"transmit-errors":0,"bytes":{"received":0,"transmitted":0},"receive-crc-error":0,"duration":{"second":0,"nanosecond":0},"receive-errors":0,"receive-drops":0,"receive-over-run-error":0,"packets":{"received":0,"transmitted":0}}},{"id":"openflow:8398923990168109056:16","flow-node-inventory:supported":"one-gb-hd forty-gb-fd","flow-node-inventory:peer-features":"one-gb-hd forty-gb-fd","flow-node-inventory:advertised-features":"","flow-node-inventory:port-number":"16","flow-node-inventory:hardware-address":"74:8e:f8:de:a1:89","flow-node-inventory:current-speed":1000000,"flow-node-inventory:current-feature":"one-gb-hd forty-gb-fd","flow-node-inventory:maximum-speed":1000000,"flow-node-inventory:name":"eth1/1/16","flow-node-inventory:state":{"link-down":true,"blocked":false,"live":false},"flow-node-inventory:configuration":"","opendaylight-port-statistics:flow-capable-node-connector-statistics":{"collision-count":0,"transmit-drops":0,"receive-frame-error":0,"transmit-errors":0,"bytes":{"received":0,"transmitted":0},"receive-crc-error":0,"duration":{"second":0,"nanosecond":0},"receive-errors":0,"receive-drops":0,"receive-over-run-error":0,"packets":{"received":0,"transmitted":0}}},{"id":"openflow:8398923990168109056:2","flow-node-inventory:supported":"one-gb-hd forty-gb-fd","flow-node-inventory:peer-features":"one-gb-hd forty-gb-fd","flow-node-inventory:advertised-features":"","flow-node-inventory:port-number":"2","flow-node-inventory:hardware-address":"74:8e:f8:de:a1:7b","flow-node-inventory:current-speed":1000000,"flow-node-inventory:current-feature":"one-gb-hd forty-gb-fd","flow-node-inventory:maximum-speed":1000000,"flow-node-inventory:name":"eth1/1/2","flow-node-inventory:state":{"link-down":true,"blocked":false,"live":false},"flow-node-inventory:configuration":"","opendaylight-port-statistics:flow-capable-node-connector-statistics":{"collision-count":0,"transmit-drops":0,"receive-frame-error":0,"transmit-errors":0,"bytes":{"received":0,"transmitted":0},"receive-crc-error":0,"duration":{"second":0,"nanosecond":0},"receive-errors":0,"receive-drops":0,"receive-over-run-error":0,"packets":{"received":0,"transmitted":0}}}],"flow-node-inventory:snapshot-gathering-status-start":{"begin":"2016-11-11T08:21:01.567-08:00"},"flow-node-inventory:snapshot-gathering-status-end":{"end":"2016-11-11T08:21:02.068-08:00","succeeded":true},"flow-node-inventory:table":[{"id":0,"flow":[{"id":"L2switch-0","opendaylight-flow-statistics:flow-statistics":{"packet-count":0,"duration":{"nanosecond":711000000,"second":607124},"byte-count":0},"priority":100,"table_id":0,"hard-timeout":0,"match":{"ethernet-match":{"ethernet-type":{"type":35020}}},"cookie":3098476543630901248,"flags":"","instructions":{"instruction":[{"order":0,"apply-actions":{"action":[{"order":0,"output-action":{"max-length":65535,"output-node-connector":"CONTROLLER"}}]}}]},"idle-timeout":0}]}],"flow-node-inventory:hardware":"FastIron","flow-node-inventory:description":"None","flow-node-inventory:switch-features":{"max_tables":1,"max_buffers":0,"capabilities":["flow-node-inventory:flow-feature-capability-flow-stats","flow-node-inventory:flow-feature-capability-group-stats","flow-node-inventory:flow-feature-capability-queue-stats","flow-node-inventory:flow-feature-capability-port-stats"]},"flow-node-inventory:manufacturer":"Brocade Communications, Inc","flow-node-inventory:serial-number":"None","flow-node-inventory:software":"FI 8.0.30","flow-node-inventory:ip-address":"10.10.10.2","opendaylight-meter-statistics:meter-features":{"meter-capabilities-supported":["opendaylight-meter-types:meter-kbps","opendaylight-meter-types:meter-stats","opendaylight-meter-types:meter-burst"],"max_bands":2,"meter-band-supported":["opendaylight-meter-types:meter-band-drop","opendaylight-meter-types:meter-band-dscp-remark"],"max_meter":1024,"max_color":3},"opendaylight-group-statistics:group-features":{"group-capabilities-supported":["opendaylight-group-types:select-liveness"],"actions":[1],"group-types-supported":["opendaylight-group-types:group-ff","opendaylight-group-types:group-all","opendaylight-group-types:group-select","opendaylight-group-types:group-indirect"],"max-groups":[512,0]}}]}}
#Use the json library to convert the string into a dictionary
odb_json = json.loads(operational_db)
print(jmespath.search('nodes.node[0].["id", "flow-node-inventory:software", "flow-node-inventory:hardware", "flow-node-inventory:manufacturer"]', odb_json))