Skip to content

Commit ed9c8fb

Browse files
authored
add sonic config render engine (#178)
add sonic config render engine
1 parent 43b0e50 commit ed9c8fb

File tree

4 files changed

+463
-0
lines changed

4 files changed

+463
-0
lines changed

rules/sonic-config.mk

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# sonic-config-engine package
2+
3+
SONIC_CONFIG_ENGINE = sonic-config-engine_1.0-1_all.deb
4+
$(SONIC_CONFIG_ENGINE)_SRC_PATH = $(SRC_PATH)/sonic-config-engine
5+
SONIC_PYTHON_STDEB_DEBS += $(SONIC_CONFIG_ENGINE)

src/sonic-config-engine/minigraph.py

+381
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
#!/usr/bin/env python
2+
import calendar
3+
import os
4+
import sys
5+
import socket
6+
import struct
7+
import json
8+
import copy
9+
import ipaddr as ipaddress
10+
from collections import defaultdict
11+
12+
from lxml import etree as ET
13+
from lxml.etree import QName
14+
15+
DOCUMENTATION = '''
16+
---
17+
module: minigraph_facts
18+
version_added: "1.9"
19+
author: Guohan Lu ([email protected])
20+
short_description: Retrive minigraph facts for a device.
21+
description:
22+
- Retrieve minigraph facts for a device, the facts will be
23+
inserted to the ansible_facts key.
24+
options:
25+
host:
26+
description:
27+
- Set to target snmp server (normally {{inventory_hostname}})
28+
required: true
29+
'''
30+
31+
EXAMPLES = '''
32+
# Gather minigraph facts
33+
- name: Gathering minigraph facts about the device
34+
minigraph_facts: host={{ hostname }}
35+
'''
36+
37+
ns = "Microsoft.Search.Autopilot.Evolution"
38+
ns1 = "http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution"
39+
ns2 = "Microsoft.Search.Autopilot.NetMux"
40+
ns3 = "http://www.w3.org/2001/XMLSchema-instance"
41+
42+
class minigraph_encoder(json.JSONEncoder):
43+
def default(self, obj):
44+
if isinstance(obj, (ipaddress.IPv4Network, ipaddress.IPv6Network, ipaddress.IPv4Address, ipaddress.IPv6Address)):
45+
return str(obj)
46+
return json.JSONEncoder.default(self, obj)
47+
48+
def parse_png(png, hname):
49+
neighbors = {}
50+
devices = {}
51+
console_dev = ''
52+
console_port = ''
53+
mgmt_dev = ''
54+
mgmt_port = ''
55+
for child in png:
56+
if child.tag == str(QName(ns, "DeviceInterfaceLinks")):
57+
for link in child.findall(str(QName(ns, "DeviceLinkBase"))):
58+
linktype = link.find(str(QName(ns, "ElementType"))).text
59+
if linktype != "DeviceInterfaceLink" and linktype != "UnderlayInterfaceLink":
60+
continue
61+
62+
enddevice = link.find(str(QName(ns, "EndDevice"))).text
63+
endport = link.find(str(QName(ns, "EndPort"))).text
64+
startdevice = link.find(str(QName(ns, "StartDevice"))).text
65+
startport = link.find(str(QName(ns, "StartPort"))).text
66+
67+
if enddevice == hname:
68+
neighbors[endport] = {'name': startdevice, 'port': startport}
69+
else:
70+
neighbors[startport] = {'name': enddevice, 'port': endport}
71+
if child.tag == str(QName(ns, "Devices")):
72+
for device in child.findall(str(QName(ns, "Device"))):
73+
lo_addr = None
74+
# don't shadow type()
75+
d_type = None
76+
mgmt_addr = None
77+
hwsku = None
78+
if str(QName(ns3, "type")) in device.attrib:
79+
d_type = device.attrib[str(QName(ns3, "type"))]
80+
81+
for node in device:
82+
if node.tag == str(QName(ns, "Address")):
83+
lo_addr = node.find(str(QName(ns2, "IPPrefix"))).text.split('/')[0]
84+
elif node.tag == str(QName(ns, "ManagementAddress")):
85+
mgmt_addr = node.find(str(QName(ns2, "IPPrefix"))).text.split('/')[0]
86+
elif node.tag == str(QName(ns, "Hostname")):
87+
name = node.text
88+
elif node.tag == str(QName(ns, "HwSku")):
89+
hwsku = node.text
90+
91+
devices[name] = {'lo_addr': lo_addr, 'type': d_type, 'mgmt_addr': mgmt_addr, 'hwsku': hwsku}
92+
93+
if child.tag == str(QName(ns, "DeviceInterfaceLinks")):
94+
for if_link in child.findall(str(QName(ns, 'DeviceLinkBase'))):
95+
if str(QName(ns3, "type")) in if_link.attrib:
96+
link_type = if_link.attrib[str(QName(ns3, "type"))]
97+
if link_type == 'DeviceSerialLink':
98+
for node in if_link:
99+
if node.tag == str(QName(ns, "EndPort")):
100+
console_port = node.text.split()[-1]
101+
elif node.tag == str(QName(ns, "EndDevice")):
102+
console_dev = node.text
103+
elif link_type == 'DeviceMgmtLink':
104+
for node in if_link:
105+
if node.tag == str(QName(ns, "EndPort")):
106+
mgmt_port = node.text.split()[-1]
107+
elif node.tag == str(QName(ns, "EndDevice")):
108+
mgmt_dev = node.text
109+
110+
111+
return (neighbors, devices, console_dev, console_port, mgmt_dev, mgmt_port)
112+
113+
114+
def parse_dpg(dpg, hname):
115+
for child in dpg:
116+
hostname = child.find(str(QName(ns, "Hostname")))
117+
if hostname.text != hname:
118+
continue
119+
120+
ipintfs = child.find(str(QName(ns, "IPInterfaces")))
121+
intfs = []
122+
vlan_map = {}
123+
for ipintf in ipintfs.findall(str(QName(ns, "IPInterface"))):
124+
intfname = ipintf.find(str(QName(ns, "AttachTo"))).text
125+
ipprefix = ipintf.find(str(QName(ns, "Prefix"))).text
126+
ipn = ipaddress.IPNetwork(ipprefix)
127+
ipaddr = ipn.ip
128+
prefix_len = ipn.prefixlen
129+
addr_bits = ipn.max_prefixlen
130+
subnet = ipaddress.IPNetwork(str(ipn.network) + '/' + str(prefix_len))
131+
ipmask = ipn.netmask
132+
133+
intf = {'addr': ipaddr, 'subnet': subnet}
134+
if isinstance(ipn, ipaddress.IPv4Network):
135+
intf['mask'] = ipmask
136+
else:
137+
intf['mask'] = str(prefix_len)
138+
139+
if intfname[0:4] == "Vlan":
140+
if intfname in vlan_map:
141+
vlan_map[intfname].append(intf)
142+
143+
else:
144+
vlan_map[intfname] = [intf]
145+
else:
146+
intf.update({'name': intfname, 'prefixlen': int(prefix_len)})
147+
148+
if port_alias_map.has_key(intfname):
149+
intf['alias'] = port_alias_map[intfname]
150+
else:
151+
intf['alias'] = intfname
152+
153+
# TODO: remove peer_addr after dependency removed
154+
ipaddr_val = int(ipn.ip)
155+
peer_addr_val = None
156+
if int(prefix_len) == addr_bits - 2:
157+
if ipaddr_val & 0x3 == 1:
158+
peer_addr_val = ipaddr_val + 1
159+
else:
160+
peer_addr_val = ipaddr_val - 1
161+
elif int(prefix_len) == addr_bits - 1:
162+
if ipaddr_val & 0x1 == 0:
163+
peer_addr_val = ipaddr_val + 1
164+
else:
165+
peer_addr_val = ipaddr_val - 1
166+
167+
if peer_addr_val is not None:
168+
intf['peer_addr'] = ipaddress.IPAddress(peer_addr_val)
169+
intfs.append(intf)
170+
171+
pcintfs = child.find(str(QName(ns, "PortChannelInterfaces")))
172+
pc_intfs = []
173+
for pcintf in pcintfs.findall(str(QName(ns, "PortChannel"))):
174+
pcintfname = pcintf.find(str(QName(ns, "Name"))).text
175+
pcintfmbr = pcintf.find(str(QName(ns, "AttachTo"))).text
176+
pcmbr_list = pcintfmbr.split(';', 1)
177+
pc_intfs.append({'name': pcintfname, 'members': pcmbr_list})
178+
179+
lointfs = child.find(str(QName(ns, "LoopbackIPInterfaces")))
180+
lo_intfs = []
181+
for lointf in lointfs.findall(str(QName(ns1, "LoopbackIPInterface"))):
182+
intfname = lointf.find(str(QName(ns, "AttachTo"))).text
183+
ipprefix = lointf.find(str(QName(ns1, "PrefixStr"))).text
184+
ipn = ipaddress.IPNetwork(ipprefix)
185+
ipaddr = ipn.ip
186+
prefix_len = ipn.prefixlen
187+
ipmask = ipn.netmask
188+
lo_intf = {'name': intfname, 'addr': ipaddr, 'prefixlen': prefix_len}
189+
if isinstance(ipn, ipaddress.IPv4Network):
190+
lo_intf['mask'] = ipmask
191+
else:
192+
lo_intf['mask'] = str(prefix_len)
193+
lo_intfs.append(lo_intf)
194+
195+
mgmtintfs = child.find(str(QName(ns, "ManagementIPInterfaces")))
196+
mgmt_intf = None
197+
for mgmtintf in mgmtintfs.findall(str(QName(ns1, "ManagementIPInterface"))):
198+
ipprefix = mgmtintf.find(str(QName(ns1, "PrefixStr"))).text
199+
mgmtipn = ipaddress.IPNetwork(ipprefix)
200+
ipaddr = mgmtipn.ip
201+
prefix_len = str(mgmtipn.prefixlen)
202+
ipmask = mgmtipn.netmask
203+
gwaddr = ipaddress.IPAddress(int(mgmtipn.network) + 1)
204+
mgmt_intf = {'addr': ipaddr, 'prefixlen': prefix_len, 'mask': ipmask, 'gwaddr': gwaddr}
205+
206+
vlanintfs = child.find(str(QName(ns, "VlanInterfaces")))
207+
vlan_intfs = []
208+
for vintf in vlanintfs.findall(str(QName(ns, "VlanInterface"))):
209+
vintfname = vintf.find(str(QName(ns, "Name"))).text
210+
vlanid = vintf.find(str(QName(ns, "VlanID"))).text
211+
vintfmbr = vintf.find(str(QName(ns, "AttachTo"))).text
212+
vmbr_list = vintfmbr.split(';'))
213+
vlan_attributes = {'name': vintfname, 'members': vmbr_list, 'vlanid': vlanid}
214+
for addrtuple in vlan_map.get(vintfname, []):
215+
vlan_attributes.update(addrtuple)
216+
vlan_intfs.append(copy.deepcopy(vlan_attributes))
217+
218+
return intfs, lo_intfs, mgmt_intf, vlan_intfs, pc_intfs
219+
return None, None, None, None, None
220+
221+
def parse_cpg(cpg, hname):
222+
bgp_sessions = []
223+
myasn = None
224+
for child in cpg:
225+
tag = child.tag
226+
if tag == str(QName(ns, "PeeringSessions")):
227+
for session in child.findall(str(QName(ns, "BGPSession"))):
228+
start_router = session.find(str(QName(ns, "StartRouter"))).text
229+
start_peer = session.find(str(QName(ns, "StartPeer"))).text
230+
end_router = session.find(str(QName(ns, "EndRouter"))).text
231+
end_peer = session.find(str(QName(ns, "EndPeer"))).text
232+
if end_router == hname:
233+
bgp_sessions.append({
234+
'name': start_router,
235+
'addr': start_peer,
236+
'peer_addr': end_peer
237+
})
238+
else:
239+
bgp_sessions.append({
240+
'name': end_router,
241+
'addr': end_peer,
242+
'peer_addr': start_peer
243+
})
244+
elif child.tag == str(QName(ns, "Routers")):
245+
for router in child.findall(str(QName(ns1, "BGPRouterDeclaration"))):
246+
asn = router.find(str(QName(ns1, "ASN"))).text
247+
hostname = router.find(str(QName(ns1, "Hostname"))).text
248+
if hostname == hname:
249+
myasn = int(asn)
250+
else:
251+
for bgp_session in bgp_sessions:
252+
if hostname == bgp_session['name']:
253+
bgp_session['asn'] = int(asn)
254+
255+
return bgp_sessions, myasn
256+
257+
258+
def get_console_info(devices, dev, port):
259+
for k, v in devices.items():
260+
if k == dev:
261+
break
262+
else:
263+
return {}
264+
265+
ret_val = v
266+
ret_val.update({
267+
'ts_port': port,
268+
'ts_dev': dev
269+
})
270+
271+
return ret_val
272+
273+
def get_mgmt_info(devices, dev, port):
274+
for k, v in devices.items():
275+
if k == dev:
276+
break
277+
else:
278+
return {}
279+
280+
ret_val = v
281+
ret_val.update({
282+
'mgmt_port': port,
283+
'mgmt_dev': dev
284+
})
285+
286+
return ret_val
287+
288+
def parse_xml(filename):
289+
root = ET.parse(filename).getroot()
290+
mini_graph_path = filename
291+
292+
u_neighbors = None
293+
u_devices = None
294+
hwsku = None
295+
bgp_sessions = None
296+
bgp_asn = None
297+
intfs = None
298+
vlan_intfs = None
299+
pc_intfs = None
300+
mgmt_intf = None
301+
lo_intf = None
302+
neighbors = None
303+
devices = None
304+
hostname = None
305+
306+
hwsku_qn = QName(ns, "HwSku")
307+
hostname_qn = QName(ns, "Hostname")
308+
for child in root:
309+
if child.tag == str(hwsku_qn):
310+
hwsku = child.text
311+
if child.tag == str(hostname_qn):
312+
hostname = child.text
313+
314+
# port_alias_map maps ngs port name to sonic port name
315+
if hwsku == "Force10-S6000":
316+
for i in range(0, 128, 4):
317+
port_alias_map["fortyGigE0/%d" % i] = "Ethernet%d" % i
318+
elif hwsku == "Arista-7050-QX32":
319+
for i in range(1, 25):
320+
port_alias_map["Ethernet%d/1" % i] = "Ethernet%d" % ((i - 1) * 4)
321+
for i in range(25, 33):
322+
port_alias_map["Ethernet%d" % i] = "Ethernet%d" % ((i - 1) * 4)
323+
324+
for child in root:
325+
if child.tag == str(QName(ns, "DpgDec")):
326+
(intfs, lo_intfs, mgmt_intf, vlan_intfs, pc_intfs) = parse_dpg(child, hostname)
327+
elif child.tag == str(QName(ns, "CpgDec")):
328+
(bgp_sessions, bgp_asn) = parse_cpg(child, hostname)
329+
elif child.tag == str(QName(ns, "PngDec")):
330+
(neighbors, devices, console_dev, console_port, mgmt_dev, mgmt_port) = parse_png(child, hostname)
331+
elif child.tag == str(QName(ns, "UngDec")):
332+
(u_neighbors, u_devices, _, _, _, _) = parse_png(child, hostname)
333+
334+
# Replace port with alias in Vlan interfaces members
335+
for vlan in vlan_intfs:
336+
for i,member in enumerate(vlan['members']):
337+
vlan['members'][i] = port_alias_map[member]
338+
339+
# Convert vlan members into a space-delimited string
340+
vlan['members'] = " ".join(vlan['members'])
341+
342+
# Replace port with alias in port channel interfaces members
343+
for pc in pc_intfs:
344+
for i,member in enumerate(pc['members']):
345+
pc['members'][i] = port_alias_map[member]
346+
347+
Tree = lambda: defaultdict(Tree)
348+
349+
results = Tree()
350+
results['minigraph_hwsku'] = hwsku
351+
# sorting by lambdas are not easily done without custom filters.
352+
# TODO: add jinja2 filter to accept a lambda to sort a list of dictionaries by attribute.
353+
# TODO: alternatively (preferred), implement class containers for multiple-attribute entries, enabling sort by attr
354+
results['minigraph_bgp'] = sorted(bgp_sessions, key=lambda x: x['addr'])
355+
results['minigraph_bgp_asn'] = bgp_asn
356+
# TODO: sort does not work properly on all interfaces of varying lengths. Need to sort by integer group(s).
357+
results['minigraph_interfaces'] = sorted(intfs, key=lambda x: x['name'])
358+
results['minigraph_vlan_interfaces'] = vlan_intfs
359+
results['minigraph_portchannel_interfaces'] = pc_intfs
360+
results['minigraph_mgmt_interface'] = mgmt_intf
361+
results['minigraph_lo_interfaces'] = lo_intfs
362+
results['minigraph_neighbors'] = neighbors
363+
results['minigraph_devices'] = devices
364+
results['minigraph_underlay_neighbors'] = u_neighbors
365+
results['minigraph_underlay_devices'] = u_devices
366+
results['minigraph_as_xml'] = mini_graph_path
367+
results['minigraph_console'] = get_console_info(devices, console_dev, console_port)
368+
results['minigraph_mgmt'] = get_mgmt_info(devices, mgmt_dev, mgmt_port)
369+
results['inventory_hostname'] = hostname
370+
371+
return results
372+
373+
374+
port_alias_map = {}
375+
376+
377+
def print_parse_xml(filename):
378+
results = parse_xml(filename)
379+
print(json.dumps(results, indent=3, cls=minigraph_encoder))
380+
381+

0 commit comments

Comments
 (0)