2017-01-06 20:19:42 -06:00
|
|
|
#!/usr/bin/env python
|
2017-08-01 21:02:00 -05:00
|
|
|
"""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.
|
|
|
|
"""
|
2017-01-06 20:19:42 -06:00
|
|
|
|
|
|
|
import sys
|
|
|
|
import os.path
|
|
|
|
import argparse
|
|
|
|
import yaml
|
|
|
|
import jinja2
|
|
|
|
import netaddr
|
2017-01-19 22:56:26 -06:00
|
|
|
import json
|
2017-02-28 12:52:56 -06:00
|
|
|
from minigraph import minigraph_encoder
|
2017-04-11 15:04:21 -05:00
|
|
|
from minigraph import parse_xml
|
2017-07-06 17:28:23 -05:00
|
|
|
from minigraph import parse_device_desc_xml
|
2017-04-11 15:04:21 -05:00
|
|
|
from sonic_platform import get_machine_info
|
|
|
|
from sonic_platform import get_platform_info
|
2017-08-01 21:02:00 -05:00
|
|
|
from swsssdk import ConfigDBConnector
|
2017-01-06 20:19:42 -06:00
|
|
|
|
|
|
|
def is_ipv4(value):
|
|
|
|
if not value:
|
|
|
|
return False
|
|
|
|
if isinstance(value, netaddr.IPAddress):
|
|
|
|
addr = value
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
addr = netaddr.IPAddress(str(value))
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
return addr.version == 4
|
|
|
|
|
|
|
|
def is_ipv6(value):
|
|
|
|
if not value:
|
|
|
|
return False
|
|
|
|
if isinstance(value, netaddr.IPAddress):
|
|
|
|
addr = value
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
addr = netaddr.IPAddress(str(value))
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
return addr.version == 6
|
|
|
|
|
2017-03-16 13:22:40 -05:00
|
|
|
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
|
|
|
|
|
2017-08-01 21:02:00 -05:00
|
|
|
|
|
|
|
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):
|
|
|
|
data_bgp_admin = {}
|
|
|
|
for table_name, content in db_data.iteritems():
|
|
|
|
if table_name == 'BGP_NEIGHBOR':
|
|
|
|
for key, value in content.iteritems():
|
|
|
|
if value.has_key('admin_status'):
|
|
|
|
data_bgp_admin[key] = (value['admin_status'] == 'up')
|
|
|
|
elif table_name == 'DEVICE_METADATA':
|
|
|
|
if content['localhost'].has_key('bgp_default_status'):
|
|
|
|
data_bgp_admin['all'] = (content['localhost']['bgp_default_status'] == 'up')
|
|
|
|
|
|
|
|
output_data = {'bgp_admin_state': data_bgp_admin} if data_bgp_admin else {}
|
|
|
|
return output_data
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def output_to_db(output_data):
|
|
|
|
db_data = {}
|
|
|
|
for key, value in output_data.iteritems():
|
|
|
|
if key == 'bgp_admin_state':
|
|
|
|
for neighbor, state in value.iteritems():
|
|
|
|
if neighbor == 'all':
|
|
|
|
if not db_data.has_key('DEVICE_METADATA'):
|
|
|
|
db_data['DEVICE_METADATA'] = {'localhost': {}}
|
|
|
|
db_data['DEVICE_METADATA']['localhost']['bgp_default_status'] = 'up' if state else 'down'
|
|
|
|
else:
|
|
|
|
if not db_data.has_key('BGP_NEIGHBOR'):
|
|
|
|
db_data['BGP_NEIGHBOR'] = {}
|
|
|
|
if not db_data['BGP_NEIGHBOR'].has_key(neighbor):
|
|
|
|
db_data['BGP_NEIGHBOR'][neighbor] = {}
|
|
|
|
db_data['BGP_NEIGHBOR'][neighbor]['admin_status'] = 'up' if state else 'down'
|
|
|
|
return db_data
|
|
|
|
|
|
|
|
|
2017-01-06 20:19:42 -06:00
|
|
|
def main():
|
|
|
|
parser=argparse.ArgumentParser(description="Render configuration file from minigraph data and jinja2 template.")
|
2017-07-06 17:28:23 -05:00
|
|
|
group = parser.add_mutually_exclusive_group()
|
2017-08-01 21:02:00 -05:00
|
|
|
group.add_argument("-m", "--minigraph", help="minigraph xml file", nargs='?', const='/etc/sonic/minigraph.xml')
|
2017-07-06 17:28:23 -05:00
|
|
|
group.add_argument("-M", "--device-description", help="device description xml file")
|
2017-03-17 23:38:20 -05:00
|
|
|
parser.add_argument("-p", "--port-config", help="port config file, used with -m")
|
2017-08-01 21:02:00 -05:00
|
|
|
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=[])
|
2017-01-19 22:56:26 -06:00
|
|
|
parser.add_argument("-a", "--additional-data", help="addition data, in json string")
|
2017-08-01 21:02:00 -05:00
|
|
|
parser.add_argument("-d", "--from-db", help="read config from configdb", action='store_true')
|
2017-01-19 22:56:26 -06:00
|
|
|
group = parser.add_mutually_exclusive_group()
|
|
|
|
group.add_argument("-t", "--template", help="render the data with the template file")
|
2017-02-28 12:52:56 -06:00
|
|
|
group.add_argument("-v", "--var", help="print the value of a variable, support jinja2 expression")
|
2017-01-19 22:56:26 -06:00
|
|
|
group.add_argument("--var-json", help="print the value of a variable, in json format")
|
2017-08-01 21:02:00 -05:00
|
|
|
group.add_argument("--write-to-db", help="write config into configdb", action='store_true')
|
2017-01-19 22:56:26 -06:00
|
|
|
group.add_argument("--print-data", help="print all data", action='store_true')
|
2017-01-06 20:19:42 -06:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
2017-01-19 22:56:26 -06:00
|
|
|
data = {}
|
2017-02-17 15:03:42 -06:00
|
|
|
machine_info = get_machine_info()
|
2017-02-17 21:47:50 -06:00
|
|
|
if machine_info != None:
|
|
|
|
data.update(machine_info)
|
2017-04-11 15:04:21 -05:00
|
|
|
platform_info = get_platform_info(machine_info)
|
|
|
|
if platform_info != None:
|
|
|
|
data['platform'] = platform_info
|
2017-01-19 22:56:26 -06:00
|
|
|
|
|
|
|
if args.minigraph != None:
|
|
|
|
minigraph = args.minigraph
|
2017-02-17 15:03:42 -06:00
|
|
|
if data.has_key('platform'):
|
2017-03-17 23:38:20 -05:00
|
|
|
if args.port_config != None:
|
|
|
|
data.update(parse_xml(minigraph, data['platform'], args.port_config))
|
|
|
|
else:
|
|
|
|
data.update(parse_xml(minigraph, data['platform']))
|
2017-02-17 15:03:42 -06:00
|
|
|
else:
|
2017-03-17 23:38:20 -05:00
|
|
|
if args.port_config != None:
|
|
|
|
data.update(parse_xml(minigraph, port_config_file=args.port_config))
|
|
|
|
else:
|
|
|
|
data.update(parse_xml(minigraph))
|
2017-01-06 20:19:42 -06:00
|
|
|
|
2017-07-06 17:28:23 -05:00
|
|
|
if args.device_description != None:
|
|
|
|
data.update(parse_device_desc_xml(args.device_description))
|
|
|
|
|
2017-06-22 01:49:15 -05:00
|
|
|
for yaml_file in args.yaml:
|
|
|
|
with open(yaml_file, 'r') as stream:
|
2017-01-19 22:56:26 -06:00
|
|
|
additional_data = yaml.load(stream)
|
2017-01-06 20:19:42 -06:00
|
|
|
data.update(additional_data)
|
|
|
|
|
2017-08-01 21:02:00 -05:00
|
|
|
for json_file in args.json:
|
|
|
|
with open(json_file, 'r') as stream:
|
|
|
|
data.update(json.load(stream))
|
|
|
|
|
2017-01-19 22:56:26 -06:00
|
|
|
if args.additional_data != None:
|
|
|
|
data.update(json.loads(args.additional_data))
|
2017-08-01 21:02:00 -05:00
|
|
|
|
|
|
|
if args.from_db:
|
|
|
|
configdb = ConfigDBConnector()
|
|
|
|
configdb.connect()
|
|
|
|
data.update(FormatConverter.db_to_output(configdb.get_config()))
|
2017-01-19 22:56:26 -06:00
|
|
|
|
|
|
|
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
|
2017-03-16 13:22:40 -05:00
|
|
|
env.filters['unique_name'] = unique_name
|
2017-01-19 22:56:26 -06:00
|
|
|
template = env.get_template(template_file)
|
|
|
|
print template.render(data)
|
|
|
|
|
|
|
|
if args.var != None:
|
2017-02-28 12:52:56 -06:00
|
|
|
template = jinja2.Template('{{' + args.var + '}}')
|
|
|
|
print template.render(data)
|
2017-01-19 22:56:26 -06:00
|
|
|
|
|
|
|
if args.var_json != None:
|
2017-02-28 12:52:56 -06:00
|
|
|
print json.dumps(data[args.var_json], indent=4, cls=minigraph_encoder)
|
2017-01-19 22:56:26 -06:00
|
|
|
|
2017-08-01 21:02:00 -05:00
|
|
|
if args.write_to_db:
|
|
|
|
configdb = ConfigDBConnector()
|
|
|
|
configdb.connect(False)
|
|
|
|
configdb.set_config(FormatConverter.output_to_db(data))
|
|
|
|
|
2017-01-19 22:56:26 -06:00
|
|
|
if args.print_data:
|
2017-02-28 12:52:56 -06:00
|
|
|
print json.dumps(data, indent=4, cls=minigraph_encoder)
|
2017-01-06 20:19:42 -06:00
|
|
|
|
2017-08-01 21:02:00 -05:00
|
|
|
|
2017-01-06 20:19:42 -06:00
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|