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

323 lines
12 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.
"""
from __future__ import print_function
# monkey patch re.compile to do lazy regular expression compilation.
# This is done to improve import time of jinja2, yaml, natsort modules, because they
# do many regexp compilation at import time, so it will speed up sonic-cfggen invocations
# that do not require template generation or yaml loading. sonic-cfggen is used in so many places
# during system boot up that importing jinja2, yaml, natsort every time
# without lazy regular expression compilation affect boot up time.
# FIXME: remove this once sonic-cfggen and templates dependencies are replaced with a faster approach
import lazy_re
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 portconfig import get_port_config
from sonic_device_util import get_machine_info
from sonic_device_util import get_platform_info
from sonic_device_util import get_system_mac
from config_samples import generate_sample_config
from config_samples import get_available_config
from swsssdk import SonicV2Connector, ConfigDBConnector
from redis_bcc import RedisBytecodeCache
from collections import OrderedDict
from natsort import natsorted
[QoS]: Unify qos json by using qos_config.j2 template (#2023) * Unify qos config with qos_config.j2 template Signed-off-by: Wenda <wenni@microsoft.com> * Change 7050 to use qos config template Signed-off-by: Wenda <wenni@microsoft.com> modified: device/arista/x86_64-arista_7050_qx32/Arista-7050-QX32/qos.json.j2 modified: device/arista/x86_64-arista_7050_qx32s/Arista-7050-QX-32S/qos.json.j2 * Change a7060, a7260, s6000, s6100, z9100 to use qos config template Signed-off-by: Wenda <wenni@microsoft.com> * Change mlnx devices to use qos config template Signed-off-by: Wenda <wenni@microsoft.com> modified: ../../../mellanox/x86_64-mlnx_msn2100-r0/ACS-MSN2100/qos.json.j2 modified: ../../../mellanox/x86_64-mlnx_msn2410-r0/ACS-MSN2410/qos.json.j2 modified: ../../../mellanox/x86_64-mlnx_msn2700-r0/ACS-MSN2700/qos.json.j2 modified: ../../../mellanox/x86_64-mlnx_msn2700-r0/Mellanox-SN2700-D48C8/qos.json.j2 * Change barefoot devices to use qos config template Signed-off-by: Wenda <wenni@microsoft.com> modified: barefoot/x86_64-accton_wedge100bf_32x-r0/montara/qos.json.j2 modified: barefoot/x86_64-accton_wedge100bf_65x-r0/mavericks/qos.json.j2 * Change accton as7212 to use qos config template Signed-off-by: Wenda <wenni@microsoft.com> modified: accton/x86_64-accton_as7212_54x-r0/AS7212-54x/qos.json.j2 * Apply PORT_QOS_MAP to active ports only Signed-off-by: Wenda <wenni@microsoft.com> * Update qos config test with qos_config.j2 template Signed-off-by: Wenda <wenni@microsoft.com> * Update sample output of qos-dell6100.json Signed-off-by: Wenda <wenni@microsoft.com> * Remove generating the default port name and index list, i.e., remove the generate_port_lists macro, because PORT is always defined Signed-off-by: Wenda <wenni@microsoft.com> * Include pfc_to_pg_map according to platform asic type obtained from /etc/sonic/sonic_version.yml rather than specifying per hwsku Signed-off-by: Wenda Ni <wenni@microsoft.com> * Customize TC_TO_PRIORITY_GROUP_MAP and PFC_PRIORITY_TO_PRIORITY_GROUP_MAP for barefoot Signed-off-by: Wenda <wenni@microsoft.com> * Unify PFC_PRIORITY_TO_PRIORITY_GROUP_MAP: remove "0":"0", "1":"1" as these two pgs do not generate PFC frames. Signed-off-by: Wenda <wenni@microsoft.com>
2018-10-17 16:10:34 -05:00
def sort_by_port_index(value):
if not value:
return
if isinstance(value, list):
value.sort(key = lambda k: int(k[8:]))
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
def pfx_filter(value):
"""INTERFACE Table can have keys in one of the two formats:
string or tuple - This filter skips the string keys and only
take into account the tuple.
For eg - VLAN_INTERFACE|Vlan1000 vs VLAN_INTERFACE|Vlan1000|192.168.0.1/21
"""
table = OrderedDict()
if not value:
return table
for key,val in value.items():
if not isinstance(key, tuple):
continue
table[key] = val
return table
def ip_network(value):
""" Extract network for network prefix """
try:
r_v = netaddr.IPNetwork(value)
except:
return "Invalid ip address %s" % value
return r_v.network
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, lookup_key = None):
if type(data) is dict:
data = OrderedDict(natsorted(data.items()))
if lookup_key != None:
newData = {}
for key in data.keys():
if ((type(key) is unicode and lookup_key == key) or (type(key) is tuple and lookup_key in key)):
newData[ConfigDBConnector.serialize_key(key)] = data.pop(key)
break
return newData
for key in data.keys():
new_key = ConfigDBConnector.serialize_key(key)
if new_key != key:
data[new_key] = data.pop(key)
data[new_key] = FormatConverter.to_serialized(data[new_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")
group.add_argument("-k", "--hwsku", help="HwSKU")
parser.add_argument("-p", "--port-config", help="port config file, used with -m or -k", nargs='?', const=None)
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")
parser.add_argument("-T", "--template_dir", help="search base for the template files", action='store')
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("-w", "--write-to-db", help="write config into configdb", action='store_true')
group.add_argument("--print-data", help="print all data", action='store_true')
group.add_argument("--preset", help="generate sample configuration from a preset template", choices=get_available_config())
group = parser.add_mutually_exclusive_group()
group.add_argument("-K", "--key", help="Lookup for a specific key")
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 = {}
hwsku = args.hwsku
if hwsku is not None:
hardware_data = {'DEVICE_METADATA': {'localhost': {
'hwsku': hwsku
}}}
deep_update(data, hardware_data)
(ports, _) = get_port_config(hwsku, platform, args.port_config)
if not ports:
print('Failed to get port config', file=sys.stderr)
sys.exit(1)
deep_update(data, {'PORT': ports})
for json_file in args.json:
with open(json_file, 'r') as stream:
deep_update(data, FormatConverter.to_deserialized(json.load(stream)))
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:
deep_update(data, parse_xml(minigraph, port_config_file=args.port_config))
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:
if yaml.__version__ >= "5.1":
additional_data = yaml.full_load(stream)
else:
additional_data = yaml.load(stream)
deep_update(data, FormatConverter.to_deserialized(additional_data))
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 is not None:
template_file = os.path.abspath(args.template)
paths = ['/', '/usr/share/sonic/templates', os.path.dirname(template_file)]
if args.template_dir is not None:
template_dir = os.path.abspath(args.template_dir)
paths.append(template_dir)
loader = jinja2.FileSystemLoader(paths)
redis_bcc = RedisBytecodeCache(SonicV2Connector(host='127.0.0.1'))
env = jinja2.Environment(loader=loader, trim_blocks=True, bytecode_cache=redis_bcc)
[QoS]: Unify qos json by using qos_config.j2 template (#2023) * Unify qos config with qos_config.j2 template Signed-off-by: Wenda <wenni@microsoft.com> * Change 7050 to use qos config template Signed-off-by: Wenda <wenni@microsoft.com> modified: device/arista/x86_64-arista_7050_qx32/Arista-7050-QX32/qos.json.j2 modified: device/arista/x86_64-arista_7050_qx32s/Arista-7050-QX-32S/qos.json.j2 * Change a7060, a7260, s6000, s6100, z9100 to use qos config template Signed-off-by: Wenda <wenni@microsoft.com> * Change mlnx devices to use qos config template Signed-off-by: Wenda <wenni@microsoft.com> modified: ../../../mellanox/x86_64-mlnx_msn2100-r0/ACS-MSN2100/qos.json.j2 modified: ../../../mellanox/x86_64-mlnx_msn2410-r0/ACS-MSN2410/qos.json.j2 modified: ../../../mellanox/x86_64-mlnx_msn2700-r0/ACS-MSN2700/qos.json.j2 modified: ../../../mellanox/x86_64-mlnx_msn2700-r0/Mellanox-SN2700-D48C8/qos.json.j2 * Change barefoot devices to use qos config template Signed-off-by: Wenda <wenni@microsoft.com> modified: barefoot/x86_64-accton_wedge100bf_32x-r0/montara/qos.json.j2 modified: barefoot/x86_64-accton_wedge100bf_65x-r0/mavericks/qos.json.j2 * Change accton as7212 to use qos config template Signed-off-by: Wenda <wenni@microsoft.com> modified: accton/x86_64-accton_as7212_54x-r0/AS7212-54x/qos.json.j2 * Apply PORT_QOS_MAP to active ports only Signed-off-by: Wenda <wenni@microsoft.com> * Update qos config test with qos_config.j2 template Signed-off-by: Wenda <wenni@microsoft.com> * Update sample output of qos-dell6100.json Signed-off-by: Wenda <wenni@microsoft.com> * Remove generating the default port name and index list, i.e., remove the generate_port_lists macro, because PORT is always defined Signed-off-by: Wenda <wenni@microsoft.com> * Include pfc_to_pg_map according to platform asic type obtained from /etc/sonic/sonic_version.yml rather than specifying per hwsku Signed-off-by: Wenda Ni <wenni@microsoft.com> * Customize TC_TO_PRIORITY_GROUP_MAP and PFC_PRIORITY_TO_PRIORITY_GROUP_MAP for barefoot Signed-off-by: Wenda <wenni@microsoft.com> * Unify PFC_PRIORITY_TO_PRIORITY_GROUP_MAP: remove "0":"0", "1":"1" as these two pgs do not generate PFC frames. Signed-off-by: Wenda <wenni@microsoft.com>
2018-10-17 16:10:34 -05:00
env.filters['sort_by_port_index'] = sort_by_port_index
env.filters['ipv4'] = is_ipv4
env.filters['ipv6'] = is_ipv6
env.filters['unique_name'] = unique_name
env.filters['pfx_filter'] = pfx_filter
env.filters['ip_network'] = ip_network
for attr in ['ip', 'network', 'prefixlen', 'netmask', 'broadcast']:
env.filters[attr] = partial(prefix_attr, attr)
template = env.get_template(template_file)
print(template.render(sort_data(data)))
[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
if args.var != None:
template = jinja2.Template('{{' + args.var + '}}')
print(template.render(data))
if args.var_json != None and args.var_json in data:
if args.key != None:
print(json.dumps(FormatConverter.to_serialized(data[args.var_json], args.key), indent=4, cls=minigraph_encoder))
else:
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 args.preset != None:
data = generate_sample_config(data, args.preset)
print(json.dumps(FormatConverter.to_serialized(data), indent=4, cls=minigraph_encoder))
if __name__ == "__main__":
main()