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

221 lines
7.9 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 swsssdk import ConfigDBConnector
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:
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
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("-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()
data = {}
machine_info = get_machine_info()
if machine_info != None:
deep_update(data, machine_info)
platform_info = get_platform_info(machine_info)
if platform_info != None:
data['platform'] = platform_info
db_kwargs = {}
if args.redis_unix_sock_file != None:
db_kwargs['unix_socket_path'] = args.redis_unix_sock_file
if args.minigraph != None:
minigraph = args.minigraph
if data.has_key('platform'):
if args.port_config != None:
deep_update(data, parse_xml(minigraph, data['platform'], args.port_config))
else:
deep_update(data, parse_xml(minigraph, data['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.template != None:
template_file = os.path.abspath(args.template)
env = jinja2.Environment(loader=jinja2.FileSystemLoader('/'), 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)
print template.render(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()