sonic-buildimage/src/sonic-config-engine/sonic-cfggen

239 lines
8.4 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env python
"""sonic-cfggen
A tool to read SONiC config data from one or more of the following sources:
minigraph file, config DB, json file(s), yaml files(s), command line input,
and write the data into DB, print as json, or render a jinja2 config template.
Examples:
Render template with minigraph:
sonic-cfggen -m -t /usr/share/template/bgpd.conf.j2
Dump config DB content into json file:
sonic-cfggen -d --print-data > db_dump.json
Load content of json file into config DB:
sonic-cfggen -j db_dump.json --write-to-db
See usage string for detail description for arguments.
"""
import sys
import os.path
import argparse
import yaml
import jinja2
import netaddr
import json
from functools import partial
from minigraph import minigraph_encoder
from minigraph import parse_xml
from minigraph import parse_device_desc_xml
from sonic_platform import get_machine_info
from sonic_platform import get_platform_info
from sonic_platform import get_system_mac
from swsssdk import ConfigDBConnector
from collections import OrderedDict
from natsort import natsorted
def is_ipv4(value):
if not value:
return False
if isinstance(value, netaddr.IPNetwork):
addr = value
else:
try:
addr = netaddr.IPNetwork(str(value))
except:
return False
return addr.version == 4
def is_ipv6(value):
if not value:
return False
if isinstance(value, netaddr.IPNetwork):
addr = value
else:
try:
addr = netaddr.IPNetwork(str(value))
except:
return False
return addr.version == 6
def prefix_attr(attr, value):
if not value:
return None
else:
try:
prefix = netaddr.IPNetwork(str(value))
except:
return None
return str(getattr(prefix, attr))
def unique_name(l):
name_list = []
new_list = []
for item in l:
if item['name'] not in name_list:
name_list.append(item['name'])
new_list.append(item)
return new_list
class FormatConverter:
"""Convert config DB based schema to legacy minigraph based schema for backward capability.
We will move to DB schema and remove this class when the config templates are modified.
TODO(taoyl): Current version of config db only supports BGP admin states.
All other configuration are still loaded from minigraph. Plan to remove
minigraph and move everything into config db in a later commit.
"""
@staticmethod
def db_to_output(db_data):
return db_data
@staticmethod
def output_to_db(output_data):
db_data = {}
for table_name in output_data:
if table_name[0].isupper():
db_data[table_name] = output_data[table_name]
return db_data
@staticmethod
def to_serialized(data):
for table in data:
if type(data[table]) is dict:
data[table] = OrderedDict(natsorted(data[table].items()))
for key in data[table].keys():
new_key = ConfigDBConnector.serialize_key(key)
if new_key != key:
data[table][new_key] = data[table].pop(key)
return data
@staticmethod
def to_deserialized(data):
for table in data:
if type(data[table]) is dict:
for key in data[table].keys():
new_key = ConfigDBConnector.deserialize_key(key)
if new_key != key:
data[table][new_key] = data[table].pop(key)
return data
def deep_update(dst, src):
for key, value in src.iteritems():
if isinstance(value, dict):
node = dst.setdefault(key, {})
deep_update(node, value)
else:
dst[key] = value
return dst
[sonic-cfggen]: Sorting the information generated by sonic-cfggen for all j2 templates. (#1616) With this patch all the content of the files generated by "sonic-cfggen -d -t" instructions will now be sorted, which is very useful for operational and troubleshooting purposes. See below a few examples of this behavior. Also, a few sonic-cfggen UT's have been modified to accomodate the new sorted configurations. admin@lnos-x1-a-asw02:~$ sonic-cfggen -d -t /usr/share/sonic/templates/interfaces.j2 | more … auto lo iface lo inet loopback iface lo inet static address 10.10.1.2 netmask 255.255.255.255 auto eth0 iface eth0 inet dhcp allow-hotplug Ethernet112 iface Ethernet112 inet static mtu 9100 address 10.1.2.2 netmask 255.255.255.0 allow-hotplug Ethernet112 iface Ethernet112 inet6 static mtu 9100 address fc00:1:2::2 netmask 64 allow-hotplug Ethernet116 iface Ethernet116 inet static mtu 9100 address 10.2.2.2 netmask 255.255.255.0 allow-hotplug Ethernet116 iface Ethernet116 inet6 static mtu 9100 address fc00:2:2::2 netmask 64 allow-hotplug Ethernet120 iface Ethernet120 inet static mtu 9100 address 10.3.2.2 netmask 255.255.255.0 root@lnos-x1-a-csw01:/# sonic-cfggen -d -y /etc/sonic/deployment_id_asn_map.yml -t /usr/share/sonic/templates/bgpd.conf.j2 … router bgp 65100 … network 10.10.2.1/32 neighbor 10.0.0.1 remote-as 65200 neighbor 10.0.0.1 description ARISTA01T2 address-family ipv4 neighbor 10.0.0.1 activate maximum-paths 64 exit-address-family neighbor 10.0.0.11 remote-as 65200 neighbor 10.0.0.11 description ARISTA06T2 address-family ipv4 neighbor 10.0.0.11 activate maximum-paths 64 exit-address-family neighbor 10.0.0.13 remote-as 65200 neighbor 10.0.0.13 description ARISTA07T2 address-family ipv4 neighbor 10.0.0.13 activate maximum-paths 64 exit-address-family neighbor 10.0.0.15 remote-as 65200 neighbor 10.0.0.15 description ARISTA08T2 address-family ipv4 neighbor 10.0.0.15 activate maximum-paths 64 exit-address-family root@lnos-x1-a-asw02:/# sonic-cfggen -d -t /usr/share/sonic/templates/lldpd.conf.j2 … configure ports Ethernet4 lldp portidsubtype local Eth2/1 description ixia-card2-port8:Eth1/2/8 configure ports Ethernet112 lldp portidsubtype local Eth29/1 description lnos-x1-a-csw01:Eth29/1 configure ports Ethernet116 lldp portidsubtype local Eth30/1 description lnos-x1-a-csw02:Eth30/1 configure ports Ethernet120 lldp portidsubtype local Eth31/1 description lnos-x1-a-csw03:Eth31/1 configure ports Ethernet124 lldp portidsubtype local Eth32/1 description lnos-x1-a-csw04:Eth32/1 root@lnos-x1-a-asw02:/# sonic-cfggen -d -t /usr/share/sonic/templates/ports.json.j2 | more [ { "PORT_TABLE:Ethernet0": { "speed": "10000", "description": "" }, "OP": "SET" }, { "PORT_TABLE:Ethernet1": { "speed": "10000", "description": "" }, "OP": "SET" }, { "PORT_TABLE:Ethernet2": { "speed": "10000", "description": "" }, "OP": "SET" }, ]
2018-04-19 03:43:00 -05:00
def sort_data(data):
for table in data:
if type(data[table]) is dict:
data[table] = OrderedDict(natsorted(data[table].items()))
return data
def main():
parser=argparse.ArgumentParser(description="Render configuration file from minigraph data and jinja2 template.")
group = parser.add_mutually_exclusive_group()
group.add_argument("-m", "--minigraph", help="minigraph xml file", nargs='?', const='/etc/sonic/minigraph.xml')
group.add_argument("-M", "--device-description", help="device description xml file")
parser.add_argument("-p", "--port-config", help="port config file, used with -m")
parser.add_argument("-y", "--yaml", help="yaml file that contains additional variables", action='append', default=[])
parser.add_argument("-j", "--json", help="json file that contains additional variables", action='append', default=[])
parser.add_argument("-a", "--additional-data", help="addition data, in json string")
parser.add_argument("-d", "--from-db", help="read config from configdb", action='store_true')
parser.add_argument("-H", "--platform-info", help="read platform and hardware info", action='store_true')
parser.add_argument("-s", "--redis-unix-sock-file", help="unix sock file for redis connection")
group = parser.add_mutually_exclusive_group()
group.add_argument("-t", "--template", help="render the data with the template file")
group.add_argument("-v", "--var", help="print the value of a variable, support jinja2 expression")
group.add_argument("--var-json", help="print the value of a variable, in json format")
group.add_argument("--write-to-db", help="write config into configdb", action='store_true')
group.add_argument("--print-data", help="print all data", action='store_true')
args = parser.parse_args()
platform = get_platform_info(get_machine_info())
db_kwargs = {}
if args.redis_unix_sock_file != None:
db_kwargs['unix_socket_path'] = args.redis_unix_sock_file
data = {}
if args.minigraph != None:
minigraph = args.minigraph
if platform:
if args.port_config != None:
deep_update(data, parse_xml(minigraph, platform, args.port_config))
else:
deep_update(data, parse_xml(minigraph, platform))
else:
if args.port_config != None:
deep_update(data, parse_xml(minigraph, port_config_file=args.port_config))
else:
deep_update(data, parse_xml(minigraph))
if args.device_description != None:
deep_update(data, parse_device_desc_xml(args.device_description))
for yaml_file in args.yaml:
with open(yaml_file, 'r') as stream:
additional_data = yaml.load(stream)
deep_update(data, FormatConverter.to_deserialized(additional_data))
for json_file in args.json:
with open(json_file, 'r') as stream:
deep_update(data, FormatConverter.to_deserialized(json.load(stream)))
if args.additional_data != None:
deep_update(data, json.loads(args.additional_data))
if args.from_db:
configdb = ConfigDBConnector(**db_kwargs)
configdb.connect()
deep_update(data, FormatConverter.db_to_output(configdb.get_config()))
if args.platform_info:
hardware_data = {'DEVICE_METADATA': {'localhost': {
'platform': platform,
'mac': get_system_mac()
}}}
deep_update(data, hardware_data)
if args.template != None:
template_file = os.path.abspath(args.template)
paths = ['/', '/usr/share/sonic/templates', os.path.dirname(template_file)]
loader = jinja2.FileSystemLoader(paths)
env = jinja2.Environment(loader=loader, trim_blocks=True)
env.filters['ipv4'] = is_ipv4
env.filters['ipv6'] = is_ipv6
env.filters['unique_name'] = unique_name
for attr in ['ip', 'network', 'prefixlen', 'netmask']:
env.filters[attr] = partial(prefix_attr, attr)
template = env.get_template(template_file)
[sonic-cfggen]: Sorting the information generated by sonic-cfggen for all j2 templates. (#1616) With this patch all the content of the files generated by "sonic-cfggen -d -t" instructions will now be sorted, which is very useful for operational and troubleshooting purposes. See below a few examples of this behavior. Also, a few sonic-cfggen UT's have been modified to accomodate the new sorted configurations. admin@lnos-x1-a-asw02:~$ sonic-cfggen -d -t /usr/share/sonic/templates/interfaces.j2 | more … auto lo iface lo inet loopback iface lo inet static address 10.10.1.2 netmask 255.255.255.255 auto eth0 iface eth0 inet dhcp allow-hotplug Ethernet112 iface Ethernet112 inet static mtu 9100 address 10.1.2.2 netmask 255.255.255.0 allow-hotplug Ethernet112 iface Ethernet112 inet6 static mtu 9100 address fc00:1:2::2 netmask 64 allow-hotplug Ethernet116 iface Ethernet116 inet static mtu 9100 address 10.2.2.2 netmask 255.255.255.0 allow-hotplug Ethernet116 iface Ethernet116 inet6 static mtu 9100 address fc00:2:2::2 netmask 64 allow-hotplug Ethernet120 iface Ethernet120 inet static mtu 9100 address 10.3.2.2 netmask 255.255.255.0 root@lnos-x1-a-csw01:/# sonic-cfggen -d -y /etc/sonic/deployment_id_asn_map.yml -t /usr/share/sonic/templates/bgpd.conf.j2 … router bgp 65100 … network 10.10.2.1/32 neighbor 10.0.0.1 remote-as 65200 neighbor 10.0.0.1 description ARISTA01T2 address-family ipv4 neighbor 10.0.0.1 activate maximum-paths 64 exit-address-family neighbor 10.0.0.11 remote-as 65200 neighbor 10.0.0.11 description ARISTA06T2 address-family ipv4 neighbor 10.0.0.11 activate maximum-paths 64 exit-address-family neighbor 10.0.0.13 remote-as 65200 neighbor 10.0.0.13 description ARISTA07T2 address-family ipv4 neighbor 10.0.0.13 activate maximum-paths 64 exit-address-family neighbor 10.0.0.15 remote-as 65200 neighbor 10.0.0.15 description ARISTA08T2 address-family ipv4 neighbor 10.0.0.15 activate maximum-paths 64 exit-address-family root@lnos-x1-a-asw02:/# sonic-cfggen -d -t /usr/share/sonic/templates/lldpd.conf.j2 … configure ports Ethernet4 lldp portidsubtype local Eth2/1 description ixia-card2-port8:Eth1/2/8 configure ports Ethernet112 lldp portidsubtype local Eth29/1 description lnos-x1-a-csw01:Eth29/1 configure ports Ethernet116 lldp portidsubtype local Eth30/1 description lnos-x1-a-csw02:Eth30/1 configure ports Ethernet120 lldp portidsubtype local Eth31/1 description lnos-x1-a-csw03:Eth31/1 configure ports Ethernet124 lldp portidsubtype local Eth32/1 description lnos-x1-a-csw04:Eth32/1 root@lnos-x1-a-asw02:/# sonic-cfggen -d -t /usr/share/sonic/templates/ports.json.j2 | more [ { "PORT_TABLE:Ethernet0": { "speed": "10000", "description": "" }, "OP": "SET" }, { "PORT_TABLE:Ethernet1": { "speed": "10000", "description": "" }, "OP": "SET" }, { "PORT_TABLE:Ethernet2": { "speed": "10000", "description": "" }, "OP": "SET" }, ]
2018-04-19 03:43:00 -05:00
print template.render(sort_data(data))
if args.var != None:
template = jinja2.Template('{{' + args.var + '}}')
print template.render(data)
if args.var_json != None:
print json.dumps(FormatConverter.to_serialized(data[args.var_json]), indent=4, cls=minigraph_encoder)
if args.write_to_db:
configdb = ConfigDBConnector(**db_kwargs)
configdb.connect(False)
configdb.mod_config(FormatConverter.output_to_db(data))
if args.print_data:
print json.dumps(FormatConverter.to_serialized(data), indent=4, cls=minigraph_encoder)
if __name__ == "__main__":
main()