def load_minigraph(): """Reconfigure based on minigraph.""" log_info("'load_minigraph' executing...") #Stop services before config push _stop_services() config_db = ConfigDBConnector() config_db.connect() client = config_db.redis_clients[config_db.CONFIG_DB] client.flushdb() if os.path.isfile('/etc/sonic/init_cfg.json'): command = "{} -H -m -j /etc/sonic/init_cfg.json --write-to-db".format(SONIC_CFGGEN_PATH) else: command = "{} -H -m --write-to-db".format(SONIC_CFGGEN_PATH) run_command(command, display_cmd=True) client.set(config_db.INIT_INDICATOR, 1) run_command('pfcwd start_default', display_cmd=True) if os.path.isfile('/etc/sonic/acl.json'): run_command("acl-loader update full /etc/sonic/acl.json", display_cmd=True) run_command("config qos reload", display_cmd=True) # Write latest db version string into db db_migrator='/usr/bin/db_migrator.py' if os.path.isfile(db_migrator) and os.access(db_migrator, os.X_OK): run_command(db_migrator + ' -o set_version') #FIXME: After config DB daemon is implemented, we'll no longer need to restart every service. _restart_services() click.echo("Please note setting loaded from minigraph will be lost after system reboot. To preserve setting, run `config save`.")
def _is_neighbor_ipaddress(ipaddress): """Returns True if a neighbor has the IP address <ipaddress>, False if not """ config_db = ConfigDBConnector() config_db.connect() entry = config_db.get_entry('BGP_NEIGHBOR', ipaddress) return True if entry else False
def remove(session_name): """ Delete mirror session """ config_db = ConfigDBConnector() config_db.connect() config_db.set_entry("MIRROR_SESSION", session_name, None)
def _change_bgp_session_status_by_addr(ipaddress, status, verbose): """Start up or shut down BGP session by IP address """ verb = 'Starting' if status == 'up' else 'Shutting' click.echo("{} {} BGP session with neighbor {}...".format(verb, status, ipaddress)) config_db = ConfigDBConnector() config_db.connect() config_db.mod_entry('bgp_neighbor', ipaddress, {'admin_status': status})
def vlan(ctx, redis_unix_socket_path): """VLAN-related configuration tasks""" kwargs = {} if redis_unix_socket_path: kwargs['unix_socket_path'] = redis_unix_socket_path config_db = ConfigDBConnector(**kwargs) config_db.connect(wait_for_init=False) ctx.obj = {'db': config_db} pass
def delete(address): """Delete a TACACS+ server""" if not is_ipaddress(address): click.echo('Invalid ip address') return config_db = ConfigDBConnector() config_db.connect() config_db.set_entry('TACPLUS_SERVER', address, None)
def get_route_entries(): db = ConfigDBConnector() db.db_connect('ASIC_DB') print_message(MODE_DEBUG, "ASIC DB connected") keys = db.get_keys('ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY', False) print_message(MODE_DEBUG, json.dumps({"ASIC_ROUTE_ENTRY": keys}, indent=4)) rt = [] for k in keys: rt.append(k.split("\"", -1)[3]) return sorted(rt)
def _get_neighbor_ipaddress_list_by_hostname(hostname): """Returns list of strings, each containing an IP address of neighbor with hostname <hostname>. Returns empty list if <hostname> not a neighbor """ addrs = [] config_db = ConfigDBConnector() config_db.connect() bgp_sessions = config_db.get_table('BGP_NEIGHBOR') for addr, session in bgp_sessions.iteritems(): if session.has_key('name') and session['name'] == hostname: addrs.append(addr) return addrs
def warm_restart(ctx, redis_unix_socket_path): """warm_restart-related configuration tasks""" kwargs = {} if redis_unix_socket_path: kwargs['unix_socket_path'] = redis_unix_socket_path config_db = ConfigDBConnector(**kwargs) config_db.connect(wait_for_init=False) # warm restart enable/disable config is put in stateDB, not persistent across cold reboot, not saved to config_DB.json file state_db = SonicV2Connector(host='127.0.0.1') state_db.connect(state_db.STATE_DB, False) TABLE_NAME_SEPARATOR = '|' prefix = 'WARM_RESTART_ENABLE_TABLE' + TABLE_NAME_SEPARATOR ctx.obj = {'db': config_db, 'state_db': state_db, 'prefix': prefix} pass
def interface_name_to_alias(interface_name): """Return alias interface name if default name is given as argument """ config_db = ConfigDBConnector() config_db.connect() port_dict = config_db.get_table('PORT') if interface_name is not None: if not port_dict: click.echo("port_dict is None!") raise click.Abort() for port_name in port_dict.keys(): if interface_name == port_name: return port_dict[port_name]['alias'] return None
def get_routes(): db = ConfigDBConnector() db.db_connect('APPL_DB') print_message(MODE_DEBUG, "APPL DB connected for routes") keys = db.get_keys('ROUTE_TABLE') print_message(MODE_DEBUG, json.dumps({"ROUTE_TABLE": keys}, indent=4)) valid_rt = [] skip_rt = [] for k in keys: if db.get_entry('ROUTE_TABLE', k)['nexthop'] != '': valid_rt.append(add_prefix_ifnot(k)) else: skip_rt.append(k) print_message(MODE_INFO, json.dumps({"skipped_routes" : skip_rt}, indent=4)) return sorted(valid_rt)
def interface_alias_to_name(interface_alias): """Return default interface name if alias name is given as argument """ config_db = ConfigDBConnector() config_db.connect() port_dict = config_db.get_table('PORT') if interface_alias is not None: if not port_dict: click.echo("port_dict is None!") raise click.Abort() for port_name in port_dict.keys(): if interface_alias == port_dict[port_name]['alias']: return port_name # Interface alias not in port_dict, just return interface_alias return interface_alias
def set_interface_naming_mode(mode): """Modify SONIC_CLI_IFACE_MODE env variable in user .bashrc """ user = os.getenv('SUDO_USER') bashrc_ifacemode_line = "export SONIC_CLI_IFACE_MODE={}".format(mode) # Ensure all interfaces have an 'alias' key in PORT dict config_db = ConfigDBConnector() config_db.connect() port_dict = config_db.get_table('PORT') if not port_dict: click.echo("port_dict is None!") raise click.Abort() for port_name in port_dict.keys(): try: if port_dict[port_name]['alias']: pass except KeyError: click.echo("Platform does not support alias mapping") raise click.Abort() if not user: user = os.getenv('USER') if user != "root": bashrc = "/home/{}/.bashrc".format(user) else: click.get_current_context().fail("Cannot set interface naming mode for root user!") f = open(bashrc, 'r') filedata = f.read() f.close() if "SONIC_CLI_IFACE_MODE" not in filedata: newdata = filedata + bashrc_ifacemode_line newdata += "\n" else: newdata = re.sub(r"export SONIC_CLI_IFACE_MODE=\w+", bashrc_ifacemode_line, filedata) f = open(bashrc, 'w') f.write(newdata) f.close() click.echo("Please logout and log back in for changes take effect.")
def get_interfaces(): db = ConfigDBConnector() db.db_connect('APPL_DB') print_message(MODE_DEBUG, "APPL DB connected for interfaces") intf = [] keys = db.get_keys('INTF_TABLE') print_message(MODE_DEBUG, json.dumps({"APPL_DB_INTF": keys}, indent=4)) for k in keys: subk = k.split(':', -1) alias = subk[0] ip_prefix = ":".join(subk[1:]) ip = add_prefix(ip_prefix.split("/", -1)[0]) if (subk[0] == "eth0") or (subk[0] == "docker0"): continue if (subk[0] != "lo"): intf.append(ip_subnet(ip_prefix)) intf.append(ip) return sorted(intf)
def _clear_qos(): QOS_TABLE_NAMES = [ 'TC_TO_PRIORITY_GROUP_MAP', 'MAP_PFC_PRIORITY_TO_QUEUE', 'TC_TO_QUEUE_MAP', 'DSCP_TO_TC_MAP', 'SCHEDULER', 'PFC_PRIORITY_TO_PRIORITY_GROUP_MAP', 'PORT_QOS_MAP', 'WRED_PROFILE', 'QUEUE', 'CABLE_LENGTH', 'BUFFER_POOL', 'BUFFER_PROFILE', 'BUFFER_PG', 'BUFFER_QUEUE'] config_db = ConfigDBConnector() config_db.connect() for qos_table in QOS_TABLE_NAMES: config_db.delete_table(qos_table)
def add(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue): """ Add mirror session """ config_db = ConfigDBConnector() config_db.connect() session_info = { "src_ip": src_ip, "dst_ip": dst_ip, "dscp": dscp, "ttl": ttl } if gre_type is not None: session_info['gre_type'] = gre_type if queue is not None: session_info['queue'] = queue config_db.set_entry("MIRROR_SESSION", session_name, session_info)
def add(address, timeout, key, auth_type, port, pri, use_mgmt_vrf): """Specify a TACACS+ server""" if not is_ipaddress(address): click.echo('Invalid ip address') return config_db = ConfigDBConnector() config_db.connect() old_data = config_db.get_entry('TACPLUS_SERVER', address) if old_data != {}: click.echo('server %s already exists' % address) else: data = { 'tcp_port': str(port), 'priority': pri } if auth_type is not None: data['auth_type'] = auth_type if timeout is not None: data['timeout'] = str(timeout) if key is not None: data['passkey'] = key if use_mgmt_vrf : data['vrf'] = "mgmt" config_db.set_entry('TACPLUS_SERVER', address, data)
def del_table_key(table, entry, key): config_db = ConfigDBConnector() config_db.connect() data = config_db.get_entry(table, entry) if data: if key in data: del data[key] config_db.set_entry(table, entry, data)
def interface_name_is_valid(interface_name): """Check if the interface name is valid """ config_db = ConfigDBConnector() config_db.connect() port_dict = config_db.get_table('PORT') port_channel_dict = config_db.get_table('PORTCHANNEL') if get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is not None: if not port_dict: click.echo("port_dict is None!") raise click.Abort() for port_name in port_dict.keys(): if interface_name == port_name: return True if port_channel_dict: for port_channel_name in port_channel_dict.keys(): if interface_name == port_channel_name: return True return False
def reload(filename, yes, load_sysinfo): """Clear current configuration and import a previous saved config DB dump file.""" if not yes: click.confirm('Clear current config and reload config from the file %s?' % filename, abort=True) log_info("'reload' executing...") if load_sysinfo: command = "{} -j {} -v DEVICE_METADATA.localhost.hwsku".format(SONIC_CFGGEN_PATH, filename) proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) cfg_hwsku, err = proc.communicate() if err: click.echo("Could not get the HWSKU from config file, exiting") sys.exit(1) else: cfg_hwsku = cfg_hwsku.strip() #Stop services before config push _stop_services() config_db = ConfigDBConnector() config_db.connect() client = config_db.redis_clients[config_db.CONFIG_DB] client.flushdb() if load_sysinfo: command = "{} -H -k {} --write-to-db".format(SONIC_CFGGEN_PATH, cfg_hwsku) run_command(command, display_cmd=True) command = "{} -j {} --write-to-db".format(SONIC_CFGGEN_PATH, filename) run_command(command, display_cmd=True) client.set(config_db.INIT_INDICATOR, 1) # Migrate DB contents to latest version db_migrator='/usr/bin/db_migrator.py' if os.path.isfile(db_migrator) and os.access(db_migrator, os.X_OK): run_command(db_migrator + ' -o migrate') _restart_services()
def __init__(self): """ Version string format: version_<major>_<minor>_<build> major: starting from 1, sequentially incrementing in master branch. minor: in github branches, minor version stays in 0. This minor version creates space for private branches derived from github public branches. These private branches shall use none-zero values. build: sequentially increase within a minor version domain. """ self.CURRENT_VERSION = 'version_1_0_1' self.TABLE_NAME = 'VERSIONS' self.TABLE_KEY = 'DATABASE' self.TABLE_FIELD = 'VERSION' self.configDB = ConfigDBConnector() self.configDB.db_connect('CONFIG_DB')
def add_table_kv(table, entry, key, val): config_db = ConfigDBConnector() config_db.connect() config_db.mod_entry(table, entry, {key: val})
def setUp(self): self.runner = CliRunner() self.config_db = ConfigDBConnector() self.config_db.connect() self.obj = {'db': self.config_db}
class DBMigrator(): def __init__(self, namespace, socket=None): """ Version string format: version_<major>_<minor>_<build> major: starting from 1, sequentially incrementing in master branch. minor: in github branches, minor version stays in 0. This minor version creates space for private branches derived from github public branches. These private branches shall use none-zero values. build: sequentially increase within a minor version domain. """ self.CURRENT_VERSION = 'version_1_0_3' self.TABLE_NAME = 'VERSIONS' self.TABLE_KEY = 'DATABASE' self.TABLE_FIELD = 'VERSION' db_kwargs = {} if socket: db_kwargs['unix_socket_path'] = socket if namespace is None: self.configDB = ConfigDBConnector(**db_kwargs) else: self.configDB = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace, **db_kwargs) self.configDB.db_connect('CONFIG_DB') self.appDB = SonicV2Connector(host='127.0.0.1') if self.appDB is not None: self.appDB.connect(self.appDB.APPL_DB) def migrate_pfc_wd_table(self): ''' Migrate all data entries from table PFC_WD_TABLE to PFC_WD ''' data = self.configDB.get_table('PFC_WD_TABLE') for key in data.keys(): self.configDB.set_entry('PFC_WD', key, data[key]) self.configDB.delete_table('PFC_WD_TABLE') def is_ip_prefix_in_key(self, key): ''' Function to check if IP address is present in the key. If it is present, then the key would be a tuple or else, it shall be be string ''' return (isinstance(key, tuple)) def migrate_interface_table(self): ''' Migrate all data from existing INTERFACE table with IP Prefix to have an additional ONE entry without IP Prefix. For. e.g, for an entry "Vlan1000|192.168.0.1/21": {}", this function shall add an entry without IP prefix as ""Vlan1000": {}". This is for VRF compatibility. ''' if_db = [] if_tables = { 'INTERFACE', 'PORTCHANNEL_INTERFACE', 'VLAN_INTERFACE', 'LOOPBACK_INTERFACE' } for table in if_tables: data = self.configDB.get_table(table) for key in data.keys(): if not self.is_ip_prefix_in_key(key): if_db.append(key) continue for table in if_tables: data = self.configDB.get_table(table) for key in data.keys(): if not self.is_ip_prefix_in_key(key) or key[0] in if_db: continue log_info('Migrating interface table for ' + key[0]) self.configDB.set_entry(table, key[0], data[key]) if_db.append(key[0]) def migrate_intf_table(self): ''' Migrate all data from existing INTF table in APP DB during warmboot with IP Prefix to have an additional ONE entry without IP Prefix. For. e.g, for an entry "Vlan1000:192.168.0.1/21": {}", this function shall add an entry without IP prefix as ""Vlan1000": {}". This also migrates 'lo' to 'Loopback0' interface ''' if self.appDB is None: return data = self.appDB.keys(self.appDB.APPL_DB, "INTF_TABLE:*") if data is None: return if_db = [] for key in data: if_name = key.split(":")[1] if if_name == "lo": self.appDB.delete(self.appDB.APPL_DB, key) key = key.replace(if_name, "Loopback0") log_info('Migrating lo entry to ' + key) self.appDB.set(self.appDB.APPL_DB, key, 'NULL', 'NULL') if '/' not in key: if_db.append(key.split(":")[1]) continue data = self.appDB.keys(self.appDB.APPL_DB, "INTF_TABLE:*") for key in data: if_name = key.split(":")[1] if if_name in if_db: continue log_info('Migrating intf table for ' + if_name) table = "INTF_TABLE:" + if_name self.appDB.set(self.appDB.APPL_DB, table, 'NULL', 'NULL') if_db.append(if_name) def mlnx_migrate_buffer_pool_size(self): """ On Mellanox platform the buffer pool size changed since version with new SDK 4.3.3052, SONiC to SONiC update from version with old SDK will be broken without migration. This migration is specifically for Mellanox platform. """ # Buffer pools defined in version 1_0_2 buffer_pools = ['ingress_lossless_pool', 'egress_lossless_pool', 'ingress_lossy_pool', 'egress_lossy_pool'] # Old default buffer pool values on Mellanox platform spc1_t0_default_value = [{'ingress_lossless_pool': '4194304'}, {'egress_lossless_pool': '16777152'}, {'ingress_lossy_pool': '7340032'}, {'egress_lossy_pool': '7340032'}] spc1_t1_default_value = [{'ingress_lossless_pool': '2097152'}, {'egress_lossless_pool': '16777152'}, {'ingress_lossy_pool': '5242880'}, {'egress_lossy_pool': '5242880'}] spc2_t0_default_value = [{'ingress_lossless_pool': '8224768'}, {'egress_lossless_pool': '35966016'}, {'ingress_lossy_pool': '8224768'}, {'egress_lossy_pool': '8224768'}] spc2_t1_default_value = [{'ingress_lossless_pool': '12042240'}, {'egress_lossless_pool': '35966016'}, {'ingress_lossy_pool': '12042240'}, {'egress_lossy_pool': '12042240'}] # New default buffer pool configuration on Mellanox platform spc1_t0_default_config = {"ingress_lossless_pool": { "size": "5029836", "type": "ingress", "mode": "dynamic" }, "ingress_lossy_pool": { "size": "5029836", "type": "ingress", "mode": "dynamic" }, "egress_lossless_pool": { "size": "14024599", "type": "egress", "mode": "dynamic" }, "egress_lossy_pool": {"size": "5029836", "type": "egress", "mode": "dynamic" } } spc1_t1_default_config = {"ingress_lossless_pool": { "size": "2097100", "type": "ingress", "mode": "dynamic" }, "ingress_lossy_pool": { "size": "2097100", "type": "ingress", "mode": "dynamic" }, "egress_lossless_pool": { "size": "14024599", "type": "egress", "mode": "dynamic" }, "egress_lossy_pool": {"size": "2097100", "type": "egress", "mode": "dynamic" } } spc2_t0_default_config = {"ingress_lossless_pool": { "size": "14983147", "type": "ingress", "mode": "dynamic" }, "ingress_lossy_pool": { "size": "14983147", "type": "ingress", "mode": "dynamic" }, "egress_lossless_pool": { "size": "34340822", "type": "egress", "mode": "dynamic" }, "egress_lossy_pool": {"size": "14983147", "type": "egress", "mode": "dynamic" } } spc2_t1_default_config = {"ingress_lossless_pool": { "size": "9158635", "type": "ingress", "mode": "dynamic" }, "ingress_lossy_pool": { "size": "9158635", "type": "ingress", "mode": "dynamic" }, "egress_lossless_pool": { "size": "34340822", "type": "egress", "mode": "dynamic" }, "egress_lossy_pool": {"size": "9158635", "type": "egress", "mode": "dynamic" } } # 3800 platform has gearbox installed so the buffer pool size is different with other Spectrum2 platform spc2_3800_t0_default_config = {"ingress_lossless_pool": { "size": "28196784", "type": "ingress", "mode": "dynamic" }, "ingress_lossy_pool": { "size": "28196784", "type": "ingress", "mode": "dynamic" }, "egress_lossless_pool": { "size": "34340832", "type": "egress", "mode": "dynamic" }, "egress_lossy_pool": {"size": "28196784", "type": "egress", "mode": "dynamic" } } spc2_3800_t1_default_config = {"ingress_lossless_pool": { "size": "17891280", "type": "ingress", "mode": "dynamic" }, "ingress_lossy_pool": { "size": "17891280", "type": "ingress", "mode": "dynamic" }, "egress_lossless_pool": { "size": "34340832", "type": "egress", "mode": "dynamic" }, "egress_lossy_pool": {"size": "17891280", "type": "egress", "mode": "dynamic" } } # Try to get related info from DB buffer_pool_conf = {} device_data = self.configDB.get_table('DEVICE_METADATA') if 'localhost' in device_data.keys(): hwsku = device_data['localhost']['hwsku'] platform = device_data['localhost']['platform'] else: log_error("Trying to get DEVICE_METADATA from DB but doesn't exist, skip migration") return False buffer_pool_conf = self.configDB.get_table('BUFFER_POOL') # Get current buffer pool configuration, only migrate configuration which # with default values, if it's not default, leave it as is. pool_size_in_db_list = [] pools_in_db = buffer_pool_conf.keys() # Buffer pool numbers is different with default, don't need migrate if len(pools_in_db) != len(buffer_pools): return True # If some buffer pool is not default ones, don't need migrate for buffer_pool in buffer_pools: if buffer_pool not in pools_in_db: return True pool_size_in_db_list.append({buffer_pool: buffer_pool_conf[buffer_pool]['size']}) # To check if the buffer pool size is equal to old default values new_buffer_pool_conf = None if pool_size_in_db_list == spc1_t0_default_value: new_buffer_pool_conf = spc1_t0_default_config elif pool_size_in_db_list == spc1_t1_default_value: new_buffer_pool_conf = spc1_t1_default_config elif pool_size_in_db_list == spc2_t0_default_value: if platform == 'x86_64-mlnx_msn3800-r0': new_buffer_pool_conf = spc2_3800_t0_default_config else: new_buffer_pool_conf = spc2_t0_default_config elif pool_size_in_db_list == spc2_t1_default_value: if platform == 'x86_64-mlnx_msn3800-r0': new_buffer_pool_conf = spc2_3800_t1_default_config else: new_buffer_pool_conf = spc2_t1_default_config else: # It's not using default buffer pool configuration, no migration needed. log_info("buffer pool size is not old default value, no need to migrate") return True # Migrate old buffer conf to latest. for pool in buffer_pools: self.configDB.set_entry('BUFFER_POOL', pool, new_buffer_pool_conf[pool]) log_info("Successfully migrate mlnx buffer pool size to the latest.") return True def version_unknown(self): """ version_unknown tracks all SONiC versions that doesn't have a version string defined in config_DB. Nothing can be assumped when migrating from this version to the next version. Any migration operation needs to test if the DB is in expected format before migrating date to the next version. """ log_info('Handling version_unknown') # NOTE: Uncomment next 3 lines of code when the migration code is in # place. Note that returning specific string is intentional, # here we only intended to migrade to DB version 1.0.1. # If new DB version is added in the future, the incremental # upgrade will take care of the subsequent migrations. self.migrate_pfc_wd_table() self.migrate_interface_table() self.migrate_intf_table() self.set_version('version_1_0_2') return 'version_1_0_2' def version_1_0_1(self): """ Version 1_0_1. """ log_info('Handling version_1_0_1') self.migrate_interface_table() self.migrate_intf_table() self.set_version('version_1_0_2') return 'version_1_0_2' def version_1_0_2(self): """ Version 1_0_2. """ log_info('Handling version_1_0_2') # Check ASIC type, if Mellanox platform then need DB migration version_info = sonic_device_util.get_sonic_version_info() if version_info['asic_type'] == "mellanox": if self.mlnx_migrate_buffer_pool_size(): self.set_version('version_1_0_3') else: self.set_version('version_1_0_3') return None def version_1_0_3(self): """ Current latest version. Nothing to do here. """ log_info('Handling version_1_0_3') return None def get_version(self): version = self.configDB.get_entry(self.TABLE_NAME, self.TABLE_KEY) if version and version[self.TABLE_FIELD]: return version[self.TABLE_FIELD] return 'version_unknown' def set_version(self, version=None): if not version: version = self.CURRENT_VERSION log_info('Setting version to ' + version) entry = { self.TABLE_FIELD : version } self.configDB.set_entry(self.TABLE_NAME, self.TABLE_KEY, entry) def migrate(self): version = self.get_version() log_info('Upgrading from version ' + version) while version: next_version = getattr(self, version)() if next_version == version: raise Exception('Version migrate from %s stuck in same version' % version) version = next_version
class AclLoader(object): ACL_TABLE = "ACL_TABLE" ACL_RULE = "ACL_RULE" MIRROR_SESSION = "MIRROR_SESSION" SESSION_PREFIX = "everflow" min_priority = 1 max_priority = 10000 ethertype_map = { "ETHERTYPE_LLDP": 0x88CC, "ETHERTYPE_VLAN": 0x8100, "ETHERTYPE_ROCE": 0x8915, "ETHERTYPE_ARP": 0x0806, "ETHERTYPE_IPV4": 0x0800, "ETHERTYPE_IPV6": 0x86DD, "ETHERTYPE_MPLS": 0x8847 } ip_protocol_map = { "IP_TCP": 6, "IP_ICMP": 1, "IP_UDP": 17, "IP_IGMP": 2, "IP_PIM": 103, "IP_RSVP": 46, "IP_GRE": 47, "IP_AUTH": 51, "IP_L2TP": 115 } def __init__(self): self.yang_acl = None self.requested_session = None self.tables_db_info = {} self.rules_db_info = {} self.rules_info = {} self.sessions_db_info = {} self.configdb = ConfigDBConnector() self.configdb.connect() self.read_tables_info() self.read_rules_info() self.read_sessions_info() def read_tables_info(self): """ Read ACL tables information from Config DB :return: """ self.tables_db_info = self.configdb.get_table(self.ACL_TABLE) def get_tables_db_info(self): return self.tables_db_info def read_rules_info(self): """ Read rules information from Config DB :return: """ self.rules_db_info = self.configdb.get_table(self.ACL_RULE) def get_rules_db_info(self): return self.rules_db_info def read_sessions_info(self): """ Read ACL tables information from Config DB :return: """ self.sessions_db_info = self.configdb.get_table(self.MIRROR_SESSION) def get_sessions_db_info(self): """ Read mirror session information from Config DB :return: """ return self.sessions_db_info def get_session_name(self): """ Read mirror session name from Config DB :return: Mirror session name """ if self.requested_session: return self.requested_session for key in self.get_sessions_db_info(): if key.startswith(self.SESSION_PREFIX): return key return None def set_session_name(self, session_name): """ Set session name to se used in ACL rule action. :param session_name: Mirror session name """ if session_name not in self.get_sessions_db_info(): raise AclLoaderException("Session %s does not exist" % session_name) self.requested_session = session_name def set_max_priority(self, priority): """ Set rules max priority :param priority: Rules max priority :return: """ self.max_priority = int(priority) def is_table_valid(self, tname): return self.tables_db_info.get(tname) def is_table_mirror(self, tname): """ Check if ACL table type is MIRROR :param tname: ACL table name :return: True if table type is MIRROR else False """ return self.tables_db_info[tname]['type'].upper() == "MIRROR" def load_rules_from_file(self, filename): """ Load file with ACL rules configuration in openconfig ACL format. Convert rules to Config DB schema. :param filename: File in openconfig ACL format :return: """ self.yang_acl = pybindJSON.load(filename, openconfig_acl, "openconfig_acl") self.convert_rules() def convert_action(self, table_name, rule_idx, rule): rule_props = {} if rule.actions.config.forwarding_action == "ACCEPT": if self.is_table_mirror(table_name): session_name = self.get_session_name() if not session_name: raise AclLoaderException("Mirroring session does not exist") rule_props["MIRROR_ACTION"] = session_name else: rule_props["PACKET_ACTION"] = "FORWARD" elif rule.actions.config.forwarding_action == "DROP": rule_props["PACKET_ACTION"] = "DROP" elif rule.actions.config.forwarding_action == "REJECT": rule_props["PACKET_ACTION"] = "DROP" else: raise AclLoaderException("Unknown rule action %s in table %s, rule %d" % ( rule.actions.config.forwarding_action, table_name, rule_idx)) return rule_props def convert_l2(self, table_name, rule_idx, rule): rule_props = {} if rule.l2.config.ethertype: if rule.l2.config.ethertype in self.ethertype_map: rule_props["ETHER_TYPE"] = self.ethertype_map[rule.l2.config.ethertype] else: try: rule_props["ETHER_TYPE"] = int(rule.l2.config.ethertype) except: raise AclLoaderException("Failed to convert ethertype %s table %s rule %s" % ( rule.l2.config.ethertype, table_name, rule_idx)) return rule_props def convert_ipv4(self, table_name, rule_idx, rule): rule_props = {} if rule.ip.config.protocol: if self.ip_protocol_map.has_key(rule.ip.config.protocol): rule_props["IP_PROTOCOL"] = self.ip_protocol_map[rule.ip.config.protocol] else: try: int(rule.ip.config.protocol) except: raise AclLoaderException("Unknown rule protocol %s in table %s, rule %d!" % ( rule.ip.config.protocol, table_name, rule_idx)) rule_props["IP_PROTOCOL"] = rule.ip.config.protocol if rule.ip.config.source_ip_address: rule_props["SRC_IP"] = rule.ip.config.source_ip_address if rule.ip.config.destination_ip_address: rule_props["DST_IP"] = rule.ip.config.destination_ip_address # NOTE: DSCP is available only for MIRROR table if self.is_table_mirror(table_name): if rule.ip.config.dscp: rule_props["DSCP"] = rule.ip.config.dscp return rule_props def convert_port(self, port): if ".." in port: return port.replace("..", "-"), True else: return port, False def convert_transport(self, table_name, rule_idx, rule): rule_props = {} if rule.transport.config.source_port: port, is_range = self.convert_port(str(rule.transport.config.source_port)) rule_props["L4_SRC_PORT_RANGE" if is_range else "L4_SRC_PORT"] = port if rule.transport.config.destination_port: port, is_range = self.convert_port(str(rule.transport.config.destination_port)) rule_props["L4_DST_PORT_RANGE" if is_range else "L4_DST_PORT"] = port tcp_flags = 0x00 for flag in rule.transport.config.tcp_flags: if flag == "TCP_FIN": tcp_flags = tcp_flags | 0x01 if flag == "TCP_SYN": tcp_flags = tcp_flags | 0x02 if flag == "TCP_RST": tcp_flags = tcp_flags | 0x04 if flag == "TCP_PSH": tcp_flags = tcp_flags | 0x08 if flag == "TCP_ACK": tcp_flags = tcp_flags | 0x10 if flag == "TCP_URG": tcp_flags = tcp_flags | 0x20 if flag == "TCP_ECE": tcp_flags = tcp_flags | 0x40 if flag == "TCP_CWR": tcp_flags = tcp_flags | 0x80 if tcp_flags: rule_props["TCP_FLAGS"] = '0x{:02x}/0x{:02x}'.format(tcp_flags, tcp_flags) return rule_props def convert_rule_to_db_schema(self, table_name, rule): """ Convert rules format from openconfig ACL to Config DB schema :param table_name: ACL table name to which rule belong :param rule: ACL rule in openconfig format :return: dict with Config DB schema """ rule_idx = int(rule.config.sequence_id) rule_props = {} rule_data = {(table_name, "RULE_" + str(rule_idx)): rule_props} rule_props["PRIORITY"] = self.max_priority - rule_idx deep_update(rule_props, self.convert_action(table_name, rule_idx, rule)) deep_update(rule_props, self.convert_l2(table_name, rule_idx, rule)) deep_update(rule_props, self.convert_ipv4(table_name, rule_idx, rule)) deep_update(rule_props, self.convert_transport(table_name, rule_idx, rule)) return rule_data def deny_rule(self, table_name): """ Create default deny rule in Config DB format :param table_name: ACL table name to which rule belong :return: dict with Config DB schema """ rule_props = {} rule_data = {(table_name, "DEFAULT_RULE"): rule_props} rule_props["PRIORITY"] = self.min_priority rule_props["ETHER_TYPE"] = "0x0800" rule_props["PACKET_ACTION"] = "DROP" return rule_data def convert_rules(self): """ Convert rules in openconfig ACL format to Config DB schema :return: """ for acl_set_name in self.yang_acl.acl.acl_sets.acl_set: table_name = acl_set_name.replace(" ", "_").replace("-", "_").upper() acl_set = self.yang_acl.acl.acl_sets.acl_set[acl_set_name] if not self.is_table_valid(table_name): warning("%s table does not exist" % (table_name)) continue for acl_entry_name in acl_set.acl_entries.acl_entry: acl_entry = acl_set.acl_entries.acl_entry[acl_entry_name] rule = self.convert_rule_to_db_schema(table_name, acl_entry) deep_update(self.rules_info, rule) if not self.is_table_mirror(table_name): deep_update(self.rules_info, self.deny_rule(table_name)) def full_update(self): """ Perform full update of ACL rules configuration. All existing rules will be removed. New rules loaded from file will be installed. :return: """ for key in self.rules_db_info.keys(): self.configdb.mod_entry(self.ACL_RULE, key, None) self.configdb.mod_config({self.ACL_RULE: self.rules_info}) def incremental_update(self): """ Perform incremental ACL rules configuration update. Get existing rules from Config DB. Compare with rules specified in file and perform corresponding modifications. :return: """ new_rules = set(self.rules_info.iterkeys()) current_rules = set(self.rules_db_info.iterkeys()) added_rules = new_rules.difference(current_rules) removed_rules = current_rules.difference(new_rules) existing_rules = new_rules.intersection(current_rules) for key in removed_rules: self.configdb.mod_entry(self.ACL_RULE, key, None) for key in added_rules: self.configdb.mod_entry(self.ACL_RULE, key, self.rules_info[key]) for key in existing_rules: if cmp(self.rules_info[key], self.rules_db_info[key]): self.configdb.mod_entry(self.ACL_RULE, key, None) self.configdb.mod_entry(self.ACL_RULE, key, self.rules_info[key]) def show_table(self, table_name): """ Show ACL table configuration. :param table_name: Optional. ACL table name. Filter tables by specified name. :return: """ header = ("Name", "Type", "Ports", "Description") data = [] for key, val in self.get_tables_db_info().iteritems(): if table_name and key != table_name: continue if not val["ports"]: data.append([key, val["type"], "", val["policy_desc"]]) else: ports = natsorted(val["ports"]) data.append([key, val["type"], ports[0], val["policy_desc"]]) if len(ports) > 1: for port in ports[1:]: data.append(["", "", port, ""]) print(tabulate.tabulate(data, headers=header, tablefmt="simple", missingval="")) def show_session(self, session_name): """ Show mirror session configuration. :param session_name: Optional. Mirror session name. Filter sessions by specified name. :return: """ header = ("Name", "SRC IP", "DST IP", "GRE", "DSCP", "TTL", "Queue") data = [] for key, val in self.get_sessions_db_info().iteritems(): if session_name and key != session_name: continue data.append([key, val["src_ip"], val["dst_ip"], val.get("gre_type", ""), val.get("dscp", ""), val.get("ttl", ""), val.get("queue", "")]) print(tabulate.tabulate(data, headers=header, tablefmt="simple", missingval="")) def show_rule(self, table_name, rule_id): """ Show ACL rules configuration. :param table_name: Optional. ACL table name. Filter rules by specified table name. :param rule_id: Optional. ACL rule name. Filter rule by specified rule name. :return: """ header = ("Rule ID", "Table Name", "Priority", "Action", "Match") ignore_list = ["PRIORITY", "PACKET_ACTION", "MIRROR_ACTION"] raw_data = [] for (tname, rid), val in self.get_rules_db_info().iteritems(): if table_name and table_name != tname: continue if rule_id and rule_id != rid: continue priority = val["PRIORITY"] action = "" if "PACKET_ACTION" in val: action = val["PACKET_ACTION"] elif "MIRROR_ACTION" in val: action = "MIRROR: %s" % val["MIRROR_ACTION"] else: continue matches = ["%s: %s" % (k, v) for k, v in val.iteritems() if k not in ignore_list] matches.sort() rule_data = [[tname, rid, priority, action, matches[0]]] if len(matches) > 1: for m in matches[1:]: rule_data.append(["", "", "", "", m]) raw_data.append([priority, rule_data]) def cmp_rules(a, b): return cmp(a[0], b[0]) raw_data.sort(cmp_rules) raw_data.reverse() data = [] for _, d in raw_data: data += d print(tabulate.tabulate(data, headers=header, tablefmt="simple", missingval=""))
class AclLoader(object): ACL_TABLE = "ACL_TABLE" ACL_RULE = "ACL_RULE" ACL_TABLE_TYPE_MIRROR = "MIRROR" ACL_TABLE_TYPE_CTRLPLANE = "CTRLPLANE" MIRROR_SESSION = "MIRROR_SESSION" SESSION_PREFIX = "everflow" min_priority = 1 max_priority = 10000 ethertype_map = { "ETHERTYPE_LLDP": 0x88CC, "ETHERTYPE_VLAN": 0x8100, "ETHERTYPE_ROCE": 0x8915, "ETHERTYPE_ARP": 0x0806, "ETHERTYPE_IPV4": 0x0800, "ETHERTYPE_IPV6": 0x86DD, "ETHERTYPE_MPLS": 0x8847 } ip_protocol_map = { "IP_TCP": 6, "IP_ICMP": 1, "IP_UDP": 17, "IP_IGMP": 2, "IP_PIM": 103, "IP_RSVP": 46, "IP_GRE": 47, "IP_AUTH": 51, "IP_L2TP": 115 } def __init__(self): self.yang_acl = None self.requested_session = None self.current_table = None self.tables_db_info = {} self.rules_db_info = {} self.rules_info = {} self.sessions_db_info = {} self.configdb = ConfigDBConnector() self.configdb.connect() self.statedb = SonicV2Connector(host="127.0.0.1") self.statedb.connect(self.statedb.STATE_DB) self.read_tables_info() self.read_rules_info() self.read_sessions_info() def read_tables_info(self): """ Read ACL tables information from Config DB :return: """ self.tables_db_info = self.configdb.get_table(self.ACL_TABLE) def get_tables_db_info(self): return self.tables_db_info def read_rules_info(self): """ Read rules information from Config DB :return: """ self.rules_db_info = self.configdb.get_table(self.ACL_RULE) def get_rules_db_info(self): return self.rules_db_info def read_sessions_info(self): """ Read ACL tables information from Config DB :return: """ self.sessions_db_info = self.configdb.get_table(self.MIRROR_SESSION) for key in self.sessions_db_info.keys(): state_db_info = self.statedb.get_all( self.statedb.STATE_DB, "{}|{}".format(self.MIRROR_SESSION, key)) if state_db_info: status = state_db_info.get("status", "inactive") else: status = "error" self.sessions_db_info[key]["status"] = status def get_sessions_db_info(self): """ Read mirror session information from Config DB :return: """ return self.sessions_db_info def get_session_name(self): """ Read mirror session name from Config DB :return: Mirror session name """ if self.requested_session: return self.requested_session for key in self.get_sessions_db_info(): if key.startswith(self.SESSION_PREFIX): return key return None def set_table_name(self, table_name): """ Set table name to restrict the table to be modified :param table_name: Table name :return: """ self.current_table = table_name def set_session_name(self, session_name): """ Set session name to be used in ACL rule action :param session_name: Mirror session name :return: """ if session_name not in self.get_sessions_db_info(): raise AclLoaderException("Session %s does not exist" % session_name) self.requested_session = session_name def set_max_priority(self, priority): """ Set rules max priority :param priority: Rules max priority :return: """ self.max_priority = int(priority) def is_table_valid(self, tname): return self.tables_db_info.get(tname) def is_table_mirror(self, tname): """ Check if ACL table type is ACL_TABLE_TYPE_MIRROR :param tname: ACL table name :return: True if table type is ACL_TABLE_TYPE_MIRROR else False """ return self.tables_db_info[tname]['type'].upper( ) == self.ACL_TABLE_TYPE_MIRROR def is_table_control_plane(self, tname): """ Check if ACL table type is ACL_TABLE_TYPE_CTRLPLANE :param tname: ACL table name :return: True if table type is ACL_TABLE_TYPE_CTRLPLANE else False """ return self.tables_db_info[tname]['type'].upper( ) == self.ACL_TABLE_TYPE_CTRLPLANE @staticmethod def parse_acl_json(filename): yang_acl = pybindJSON.load(filename, openconfig_acl, "openconfig_acl") # Check pybindJSON parsing # pybindJSON.load will silently return an empty json object if input invalid with open(filename, 'r') as f: plain_json = json.load(f) if len(plain_json['acl']['acl-sets']['acl-set']) != len( yang_acl.acl.acl_sets.acl_set): raise AclLoaderException("Invalid input file %s" % filename) return yang_acl def load_rules_from_file(self, filename): """ Load file with ACL rules configuration in openconfig ACL format. Convert rules to Config DB schema. :param filename: File in openconfig ACL format :return: """ self.yang_acl = AclLoader.parse_acl_json(filename) self.convert_rules() def convert_action(self, table_name, rule_idx, rule): rule_props = {} if rule.actions.config.forwarding_action == "ACCEPT": if self.is_table_control_plane(table_name): rule_props["PACKET_ACTION"] = "ACCEPT" elif self.is_table_mirror(table_name): session_name = self.get_session_name() if not session_name: raise AclLoaderException( "Mirroring session does not exist") rule_props["MIRROR_ACTION"] = session_name else: rule_props["PACKET_ACTION"] = "FORWARD" elif rule.actions.config.forwarding_action == "DROP": rule_props["PACKET_ACTION"] = "DROP" elif rule.actions.config.forwarding_action == "REJECT": rule_props["PACKET_ACTION"] = "DROP" else: raise AclLoaderException( "Unknown rule action %s in table %s, rule %d" % (rule.actions.config.forwarding_action, table_name, rule_idx)) return rule_props def convert_l2(self, table_name, rule_idx, rule): rule_props = {} if rule.l2.config.ethertype: if rule.l2.config.ethertype in self.ethertype_map: rule_props["ETHER_TYPE"] = self.ethertype_map[ rule.l2.config.ethertype] else: try: rule_props["ETHER_TYPE"] = int(rule.l2.config.ethertype) except: raise AclLoaderException( "Failed to convert ethertype %s table %s rule %s" % (rule.l2.config.ethertype, table_name, rule_idx)) return rule_props def convert_ip(self, table_name, rule_idx, rule): rule_props = {} if rule.ip.config.protocol: if self.ip_protocol_map.has_key(rule.ip.config.protocol): rule_props["IP_PROTOCOL"] = self.ip_protocol_map[ rule.ip.config.protocol] else: try: int(rule.ip.config.protocol) except: raise AclLoaderException( "Unknown rule protocol %s in table %s, rule %d!" % (rule.ip.config.protocol, table_name, rule_idx)) rule_props["IP_PROTOCOL"] = rule.ip.config.protocol if rule.ip.config.source_ip_address: source_ip_address = rule.ip.config.source_ip_address.encode( "ascii") if ipaddr.IPNetwork(source_ip_address).version == 4: rule_props["SRC_IP"] = source_ip_address else: rule_props["SRC_IPV6"] = source_ip_address if rule.ip.config.destination_ip_address: destination_ip_address = rule.ip.config.destination_ip_address.encode( "ascii") if ipaddr.IPNetwork(destination_ip_address).version == 4: rule_props["DST_IP"] = destination_ip_address else: rule_props["DST_IPV6"] = destination_ip_address # NOTE: DSCP is available only for MIRROR table if self.is_table_mirror(table_name): if rule.ip.config.dscp: rule_props["DSCP"] = rule.ip.config.dscp return rule_props def convert_port(self, port): """ Convert port field format from openconfig ACL to Config DB schema :param port: String, ACL port number or range in openconfig format :return: Tuple, first value is converted port string, second value is boolean, True if value is a port range, False if it is a single port value """ # OpenConfig port range is of the format "####..####", whereas # Config DB format is "####-####" if ".." in port: return port.replace("..", "-"), True else: return port, False def convert_transport(self, table_name, rule_idx, rule): rule_props = {} if rule.transport.config.source_port: port, is_range = self.convert_port( str(rule.transport.config.source_port)) rule_props[ "L4_SRC_PORT_RANGE" if is_range else "L4_SRC_PORT"] = port if rule.transport.config.destination_port: port, is_range = self.convert_port( str(rule.transport.config.destination_port)) rule_props[ "L4_DST_PORT_RANGE" if is_range else "L4_DST_PORT"] = port tcp_flags = 0x00 for flag in rule.transport.config.tcp_flags: if flag == "TCP_FIN": tcp_flags |= 0x01 if flag == "TCP_SYN": tcp_flags |= 0x02 if flag == "TCP_RST": tcp_flags |= 0x04 if flag == "TCP_PSH": tcp_flags |= 0x08 if flag == "TCP_ACK": tcp_flags |= 0x10 if flag == "TCP_URG": tcp_flags |= 0x20 if flag == "TCP_ECE": tcp_flags |= 0x40 if flag == "TCP_CWR": tcp_flags |= 0x80 if tcp_flags: rule_props["TCP_FLAGS"] = '0x{:02x}/0x{:02x}'.format( tcp_flags, tcp_flags) return rule_props def convert_input_interface(self, table_name, rule_idx, rule): rule_props = {} if rule.input_interface.interface_ref.config.interface: rule_props[ "IN_PORTS"] = rule.input_interface.interface_ref.config.interface return rule_props def convert_rule_to_db_schema(self, table_name, rule): """ Convert rules format from openconfig ACL to Config DB schema :param table_name: ACL table name to which rule belong :param rule: ACL rule in openconfig format :return: dict with Config DB schema """ rule_idx = int(rule.config.sequence_id) rule_props = {} rule_data = {(table_name, "RULE_" + str(rule_idx)): rule_props} rule_props["PRIORITY"] = str(self.max_priority - rule_idx) deep_update(rule_props, self.convert_action(table_name, rule_idx, rule)) deep_update(rule_props, self.convert_l2(table_name, rule_idx, rule)) deep_update(rule_props, self.convert_ip(table_name, rule_idx, rule)) deep_update(rule_props, self.convert_transport(table_name, rule_idx, rule)) deep_update(rule_props, self.convert_input_interface(table_name, rule_idx, rule)) return rule_data def deny_rule(self, table_name): """ Create default deny rule in Config DB format :param table_name: ACL table name to which rule belong :return: dict with Config DB schema """ rule_props = {} rule_data = {(table_name, "DEFAULT_RULE"): rule_props} rule_props["PRIORITY"] = str(self.min_priority) rule_props["ETHER_TYPE"] = str(self.ethertype_map["ETHERTYPE_IPV4"]) rule_props["PACKET_ACTION"] = "DROP" return rule_data def convert_rules(self): """ Convert rules in openconfig ACL format to Config DB schema :return: """ for acl_set_name in self.yang_acl.acl.acl_sets.acl_set: table_name = acl_set_name.replace(" ", "_").replace( "-", "_").upper().encode('ascii') acl_set = self.yang_acl.acl.acl_sets.acl_set[acl_set_name] if not self.is_table_valid(table_name): warning("%s table does not exist" % (table_name)) continue if self.current_table is not None and self.current_table != table_name: continue for acl_entry_name in acl_set.acl_entries.acl_entry: acl_entry = acl_set.acl_entries.acl_entry[acl_entry_name] try: rule = self.convert_rule_to_db_schema( table_name, acl_entry) deep_update(self.rules_info, rule) except AclLoaderException as ex: error("Error processing rule %s: %s. Skipped." % (acl_entry_name, ex)) if not self.is_table_mirror(table_name): deep_update(self.rules_info, self.deny_rule(table_name)) def full_update(self): """ Perform full update of ACL rules configuration. All existing rules will be removed. New rules loaded from file will be installed. If the current_table is not empty, only rules within that table will be removed and new rules in that table will be installed. :return: """ for key in self.rules_db_info.keys(): if self.current_table is None or self.current_table == key[0]: self.configdb.mod_entry(self.ACL_RULE, key, None) self.configdb.mod_config({self.ACL_RULE: self.rules_info}) def incremental_update(self): """ Perform incremental ACL rules configuration update. Get existing rules from Config DB. Compare with rules specified in file and perform corresponding modifications. :return: """ # TODO: Until we test ASIC behavior, we cannot assume that we can insert # dataplane ACLs and shift existing ACLs. Therefore, we perform a full # update on dataplane ACLs, and only perform an incremental update on # control plane ACLs. new_rules = set(self.rules_info.iterkeys()) new_dataplane_rules = set() new_controlplane_rules = set() current_rules = set(self.rules_db_info.iterkeys()) current_dataplane_rules = set() current_controlplane_rules = set() for key in new_rules: table_name = key[0] if self.tables_db_info[table_name]['type'].upper( ) == self.ACL_TABLE_TYPE_CTRLPLANE: new_controlplane_rules.add(key) else: new_dataplane_rules.add(key) for key in current_rules: table_name = key[0] if self.tables_db_info[table_name]['type'].upper( ) == self.ACL_TABLE_TYPE_CTRLPLANE: current_controlplane_rules.add(key) else: current_dataplane_rules.add(key) # Remove all existing dataplane rules for key in current_dataplane_rules: self.configdb.mod_entry(self.ACL_RULE, key, None) # Add all new dataplane rules for key in new_dataplane_rules: self.configdb.mod_entry(self.ACL_RULE, key, self.rules_info[key]) added_controlplane_rules = new_controlplane_rules.difference( current_controlplane_rules) removed_controlplane_rules = current_controlplane_rules.difference( new_controlplane_rules) existing_controlplane_rules = new_rules.intersection( current_controlplane_rules) for key in added_controlplane_rules: self.configdb.mod_entry(self.ACL_RULE, key, self.rules_info[key]) for key in removed_controlplane_rules: self.configdb.mod_entry(self.ACL_RULE, key, None) for key in existing_controlplane_rules: if cmp(self.rules_info[key], self.rules_db_info[key]) != 0: self.configdb.set_entry(self.ACL_RULE, key, self.rules_info[key]) def delete(self, table=None, rule=None): """ :param table: :param rule: :return: """ for key in self.rules_db_info.iterkeys(): if not table or table == key[0]: if not rule or rule == key[1]: self.configdb.set_entry(self.ACL_RULE, key, None) def show_table(self, table_name): """ Show ACL table configuration. :param table_name: Optional. ACL table name. Filter tables by specified name. :return: """ header = ("Name", "Type", "Binding", "Description") data = [] for key, val in self.get_tables_db_info().iteritems(): if table_name and key != table_name: continue if val["type"] == AclLoader.ACL_TABLE_TYPE_CTRLPLANE: services = natsorted(val["services"]) data.append( [key, val["type"], services[0], val["policy_desc"]]) if len(services) > 1: for service in services[1:]: data.append(["", "", service, ""]) else: if not val["ports"]: data.append([key, val["type"], "", val["policy_desc"]]) else: ports = natsorted(val["ports"]) data.append( [key, val["type"], ports[0], val["policy_desc"]]) if len(ports) > 1: for port in ports[1:]: data.append(["", "", port, ""]) print( tabulate.tabulate(data, headers=header, tablefmt="simple", missingval="")) def show_session(self, session_name): """ Show mirror session configuration. :param session_name: Optional. Mirror session name. Filter sessions by specified name. :return: """ header = ("Name", "Status", "SRC IP", "DST IP", "GRE", "DSCP", "TTL", "Queue") data = [] for key, val in self.get_sessions_db_info().iteritems(): if session_name and key != session_name: continue data.append([ key, val["status"], val["src_ip"], val["dst_ip"], val.get("gre_type", ""), val.get("dscp", ""), val.get("ttl", ""), val.get("queue", "") ]) print( tabulate.tabulate(data, headers=header, tablefmt="simple", missingval="")) def show_rule(self, table_name, rule_id): """ Show ACL rules configuration. :param table_name: Optional. ACL table name. Filter rules by specified table name. :param rule_id: Optional. ACL rule name. Filter rule by specified rule name. :return: """ header = ("Table", "Rule", "Priority", "Action", "Match") ignore_list = ["PRIORITY", "PACKET_ACTION", "MIRROR_ACTION"] raw_data = [] for (tname, rid), val in self.get_rules_db_info().iteritems(): if table_name and table_name != tname: continue if rule_id and rule_id != rid: continue priority = val["PRIORITY"] action = "" if "PACKET_ACTION" in val: action = val["PACKET_ACTION"] elif "MIRROR_ACTION" in val: action = "MIRROR: %s" % val["MIRROR_ACTION"] else: continue matches = [ "%s: %s" % (k, v) for k, v in val.iteritems() if k not in ignore_list ] matches.sort() rule_data = [[tname, rid, priority, action, matches[0]]] if len(matches) > 1: for m in matches[1:]: rule_data.append(["", "", "", "", m]) raw_data.append([priority, rule_data]) def cmp_rules(a, b): return cmp(a[0], b[0]) raw_data.sort(cmp_rules) raw_data.reverse() data = [] for _, d in raw_data: data += d print( tabulate.tabulate(data, headers=header, tablefmt="simple", missingval=""))
def add_binding(ctx, binding_name, pool_name, acl_name, nat_type, twice_nat_id): """Add Binding for Dynamic NAT-related configutation""" entryFound = False table = 'NAT_BINDINGS' key = binding_name dataKey1 = 'access_list' dataKey2 = 'nat_pool' dataKey3 = 'nat_type' dataKey4 = 'twice_nat_id' if acl_name is None: acl_name = "" if len(binding_name) > 32: ctx.fail( "Invalid binding name. Maximum allowed binding name is 32 characters !!" ) config_db = ConfigDBConnector() config_db.connect() data = config_db.get_entry(table, key) if data: if data[dataKey1] == acl_name and data[dataKey2] == pool_name: click.echo("Trying to add binding, which is already present.") entryFound = True binding_dict = config_db.get_table(table) if len(binding_dict) == 16: click.echo( "Failed to add binding, as already reached maximum binding limit 16." ) entryFound = True if nat_type is not None: if nat_type == "dnat": click.echo("Ignored, DNAT is not yet suported for Binding ") entryFound = True else: nat_type = "snat" if twice_nat_id is None: twice_nat_id = "NULL" if entryFound is False: count = 0 if twice_nat_id is not None: count = getTwiceNatIdCountWithStaticEntries( twice_nat_id, 'STATIC_NAT', count) count = getTwiceNatIdCountWithStaticEntries( twice_nat_id, 'STATIC_NAPT', count) count = getTwiceNatIdCountWithDynamicBinding( twice_nat_id, count, key) if count > 1: ctx.fail( "Same Twice nat id is not allowed for more than 2 entries!!" ) config_db.set_entry( table, key, { dataKey1: acl_name, dataKey2: pool_name, dataKey3: nat_type, dataKey4: twice_nat_id })
class AclLoader(object): ACL_TABLE = "ACL_TABLE" ACL_RULE = "ACL_RULE" ACL_TABLE_TYPE_MIRROR = "MIRROR" ACL_TABLE_TYPE_CTRLPLANE = "CTRLPLANE" CFG_MIRROR_SESSION_TABLE = "MIRROR_SESSION" STATE_MIRROR_SESSION_TABLE = "MIRROR_SESSION_TABLE" POLICER = "POLICER" SESSION_PREFIX = "everflow" SWITCH_CAPABILITY_TABLE = "SWITCH_CAPABILITY" ACL_ACTIONS_CAPABILITY_FIELD = "ACL_ACTIONS" ACL_ACTION_CAPABILITY_FIELD = "ACL_ACTION" min_priority = 1 max_priority = 10000 ethertype_map = { "ETHERTYPE_LLDP": 0x88CC, "ETHERTYPE_VLAN": 0x8100, "ETHERTYPE_ROCE": 0x8915, "ETHERTYPE_ARP": 0x0806, "ETHERTYPE_IPV4": 0x0800, "ETHERTYPE_IPV6": 0x86DD, "ETHERTYPE_MPLS": 0x8847 } ip_protocol_map = { "IP_TCP": 6, "IP_ICMP": 1, "IP_UDP": 17, "IP_IGMP": 2, "IP_PIM": 103, "IP_RSVP": 46, "IP_GRE": 47, "IP_AUTH": 51, "IP_L2TP": 115 } def __init__(self): self.yang_acl = None self.requested_session = None self.mirror_stage = None self.current_table = None self.tables_db_info = {} self.rules_db_info = {} self.rules_info = {} # Load global db config. This call is no-op in single npu platforms SonicDBConfig.load_sonic_global_db_config() self.sessions_db_info = {} self.configdb = ConfigDBConnector() self.configdb.connect() self.statedb = SonicV2Connector(host="127.0.0.1") self.statedb.connect(self.statedb.STATE_DB) # For multi-npu architecture we will have both global and per front asic namespace. # Global namespace will be used for Control plane ACL which are via IPTables. # Per ASIC namespace will be used for Data and Everflow ACL's. # Global Configdb will have all ACL information for both Ctrl and Data/Evereflow ACL's # and will be used as souurce of truth for ACL modification to config DB which will be done to both Global DB and # front asic namespace self.per_npu_configdb = {} # State DB are used for to get mirror Session monitor port. # For multi-npu platforms each asic namespace can have different monitor port # dependinding on which route to session destination ip. So for multi-npu # platforms we get state db for all front asic namespace in addition to self.per_npu_statedb = {} # Getting all front asic namespace and correspding config and state DB connector namespaces = device_info.get_all_namespaces() for front_asic_namespaces in namespaces['front_ns']: self.per_npu_configdb[front_asic_namespaces] = ConfigDBConnector( use_unix_socket_path=True, namespace=front_asic_namespaces) self.per_npu_configdb[front_asic_namespaces].connect() self.per_npu_statedb[front_asic_namespaces] = SonicV2Connector( use_unix_socket_path=True, namespace=front_asic_namespaces) self.per_npu_statedb[front_asic_namespaces].connect( self.per_npu_statedb[front_asic_namespaces].STATE_DB) self.read_tables_info() self.read_rules_info() self.read_sessions_info() self.read_policers_info() def read_tables_info(self): """ Read ACL_TABLE table from configuration database :return: """ self.tables_db_info = self.configdb.get_table(self.ACL_TABLE) def get_tables_db_info(self): return self.tables_db_info def read_rules_info(self): """ Read ACL_RULE table from configuration database :return: """ self.rules_db_info = self.configdb.get_table(self.ACL_RULE) def get_rules_db_info(self): return self.rules_db_info def read_policers_info(self): """ Read POLICER table from configuration database :return: """ # For multi-npu platforms we will read from any one of front asic namespace # config db as the information should be same across all config db if self.per_npu_configdb: namespace_configdb = list(self.per_npu_configdb.values())[0] self.policers_db_info = namespace_configdb.get_table(self.POLICER) else: self.policers_db_info = self.configdb.get_table(self.POLICER) def get_policers_db_info(self): return self.policers_db_info def read_sessions_info(self): """ Read MIRROR_SESSION table from configuration database :return: """ # For multi-npu platforms we will read from any one of front asic namespace # config db as the information should be same across all config db if self.per_npu_configdb: namespace_configdb = list(self.per_npu_configdb.values())[0] self.sessions_db_info = namespace_configdb.get_table( self.CFG_MIRROR_SESSION_TABLE) else: self.sessions_db_info = self.configdb.get_table( self.CFG_MIRROR_SESSION_TABLE) for key in self.sessions_db_info: if self.per_npu_statedb: # For multi-npu platforms we will read from all front asic name space # statedb as the monitor port will be differnt for each asic # and it's status also might be different (ideally should not happen) # We will store them as dict of 'asic' : value self.sessions_db_info[key]["status"] = {} self.sessions_db_info[key]["monitor_port"] = {} for namespace_key, namespace_statedb in self.per_npu_statedb.items( ): state_db_info = namespace_statedb.get_all( self.statedb.STATE_DB, "{}|{}".format(self.STATE_MIRROR_SESSION_TABLE, key)) self.sessions_db_info[key]["status"][ namespace_key] = state_db_info.get( "status", "inactive") if state_db_info else "error" self.sessions_db_info[key][ "monitor_port"][namespace_key] = state_db_info.get( "monitor_port", "") if state_db_info else "" else: state_db_info = self.statedb.get_all( self.statedb.STATE_DB, "{}|{}".format(self.STATE_MIRROR_SESSION_TABLE, key)) self.sessions_db_info[key]["status"] = state_db_info.get( "status", "inactive") if state_db_info else "error" self.sessions_db_info[key]["monitor_port"] = state_db_info.get( "monitor_port", "") if state_db_info else "" def get_sessions_db_info(self): return self.sessions_db_info def get_session_name(self): """ Get requested mirror session name or default session :return: Mirror session name """ if self.requested_session: return self.requested_session for key in self.get_sessions_db_info(): if key.startswith(self.SESSION_PREFIX): return key return None def set_table_name(self, table_name): """ Set table name to restrict the table to be modified :param table_name: Table name :return: """ if not self.is_table_valid(table_name): warning("Table \"%s\" not found" % table_name) self.current_table = table_name def set_session_name(self, session_name): """ Set session name to be used in ACL rule action :param session_name: Mirror session name :return: """ if session_name not in self.get_sessions_db_info(): raise AclLoaderException("Session %s does not exist" % session_name) self.requested_session = session_name def set_mirror_stage(self, stage): """ Set mirror stage to be used in ACL mirror rule action :param session_name: stage 'ingress'/'egress' :return: """ self.mirror_stage = stage.upper() def set_max_priority(self, priority): """ Set rules max priority :param priority: Rules max priority :return: """ self.max_priority = int(priority) def is_table_valid(self, tname): return self.tables_db_info.get(tname) def is_table_mirror(self, tname): """ Check if ACL table type is ACL_TABLE_TYPE_MIRROR or ACL_TABLE_TYPE_MIRRORV6 :param tname: ACL table name :return: True if table type is MIRROR or MIRRORV6 else False """ return self.tables_db_info[tname]['type'].upper().startswith( self.ACL_TABLE_TYPE_MIRROR) def is_table_control_plane(self, tname): """ Check if ACL table type is ACL_TABLE_TYPE_CTRLPLANE :param tname: ACL table name :return: True if table type is ACL_TABLE_TYPE_CTRLPLANE else False """ return self.tables_db_info[tname]['type'].upper( ) == self.ACL_TABLE_TYPE_CTRLPLANE @staticmethod def parse_acl_json(filename): yang_acl = pybindJSON.load(filename, openconfig_acl, "openconfig_acl") # Check pybindJSON parsing # pybindJSON.load will silently return an empty json object if input invalid with open(filename, 'r') as f: plain_json = json.load(f) if len(plain_json['acl']['acl-sets']['acl-set']) != len( yang_acl.acl.acl_sets.acl_set): raise AclLoaderException("Invalid input file %s" % filename) return yang_acl def load_rules_from_file(self, filename): """ Load file with ACL rules configuration in openconfig ACL format. Convert rules to Config DB schema. :param filename: File in openconfig ACL format :return: """ self.yang_acl = AclLoader.parse_acl_json(filename) self.convert_rules() def convert_action(self, table_name, rule_idx, rule): rule_props = {} if rule.actions.config.forwarding_action == "ACCEPT": if self.is_table_control_plane(table_name): rule_props[AclAction.PACKET] = PacketAction.ACCEPT elif self.is_table_mirror(table_name): session_name = self.get_session_name() if not session_name: raise AclLoaderException( "Mirroring session does not exist") if self.mirror_stage == Stage.INGRESS: mirror_action = AclAction.MIRROR_INGRESS elif self.mirror_stage == Stage.EGRESS: mirror_action = AclAction.MIRROR_EGRESS else: raise AclLoaderException( "Invalid mirror stage passed {}".format( self.mirror_stage)) rule_props[mirror_action] = session_name else: rule_props[AclAction.PACKET] = PacketAction.FORWARD elif rule.actions.config.forwarding_action == "DROP": rule_props[AclAction.PACKET] = PacketAction.DROP elif rule.actions.config.forwarding_action == "REJECT": rule_props[AclAction.PACKET] = PacketAction.DROP else: raise AclLoaderException( "Unknown rule action {} in table {}, rule {}".format( rule.actions.config.forwarding_action, table_name, rule_idx)) if not self.validate_actions(table_name, rule_props): raise AclLoaderException( "Rule action {} is not supported in table {}, rule {}".format( rule.actions.config.forwarding_action, table_name, rule_idx)) return rule_props def validate_actions(self, table_name, action_props): if self.is_table_control_plane(table_name): return True action_count = len(action_props) if table_name not in self.tables_db_info: raise AclLoaderException( "Table {} does not exist".format(table_name)) stage = self.tables_db_info[table_name].get("stage", Stage.INGRESS) # check if per npu state db is there then read using first state db # else read from global statedb if self.per_npu_statedb: # For multi-npu we will read using anyone statedb connector for front asic namespace. # Same information should be there in all state DB's # as it is static information about switch capability namespace_statedb = list(self.per_npu_statedb.values())[0] capability = namespace_statedb.get_all( self.statedb.STATE_DB, "{}|switch".format(self.SWITCH_CAPABILITY_TABLE)) else: capability = self.statedb.get_all( self.statedb.STATE_DB, "{}|switch".format(self.SWITCH_CAPABILITY_TABLE)) for action_key in dict(action_props): key = "{}|{}".format(self.ACL_ACTIONS_CAPABILITY_FIELD, stage.upper()) if key not in capability: del action_props[action_key] continue values = capability[key].split(",") if action_key.upper() not in values: del action_props[action_key] continue if action_key == AclAction.PACKET: # Check if action_value is supported action_value = action_props[action_key] key = "{}|{}".format(self.ACL_ACTION_CAPABILITY_FIELD, action_key.upper()) if key not in capability: del action_props[action_key] continue if action_value not in capability[key]: del action_props[action_key] continue return action_count == len(action_props) def convert_l2(self, table_name, rule_idx, rule): rule_props = {} if rule.l2.config.ethertype: if rule.l2.config.ethertype in self.ethertype_map: rule_props["ETHER_TYPE"] = self.ethertype_map[ rule.l2.config.ethertype] else: try: rule_props["ETHER_TYPE"] = int(rule.l2.config.ethertype) except: raise AclLoaderException( "Failed to convert ethertype %s table %s rule %s" % (rule.l2.config.ethertype, table_name, rule_idx)) return rule_props def convert_ip(self, table_name, rule_idx, rule): rule_props = {} # FIXME: 0 is a valid protocol number, but openconfig seems to use it as a default value, # so there isn't currently a good way to check if the user defined proto=0 or not. if rule.ip.config.protocol: if rule.ip.config.protocol in self.ip_protocol_map: rule_props["IP_PROTOCOL"] = self.ip_protocol_map[ rule.ip.config.protocol] else: try: int(rule.ip.config.protocol) except: raise AclLoaderException( "Unknown rule protocol %s in table %s, rule %d!" % (rule.ip.config.protocol, table_name, rule_idx)) rule_props["IP_PROTOCOL"] = rule.ip.config.protocol if rule.ip.config.source_ip_address: source_ip_address = rule.ip.config.source_ip_address.encode( "ascii") if ipaddress.ip_network(source_ip_address).version == 4: rule_props["SRC_IP"] = source_ip_address else: rule_props["SRC_IPV6"] = source_ip_address if rule.ip.config.destination_ip_address: destination_ip_address = rule.ip.config.destination_ip_address.encode( "ascii") if ipaddress.ip_network(destination_ip_address).version == 4: rule_props["DST_IP"] = destination_ip_address else: rule_props["DST_IPV6"] = destination_ip_address # NOTE: DSCP is available only for MIRROR table if self.is_table_mirror(table_name): if rule.ip.config.dscp: rule_props["DSCP"] = rule.ip.config.dscp return rule_props def convert_port(self, port): """ Convert port field format from openconfig ACL to Config DB schema :param port: String, ACL port number or range in openconfig format :return: Tuple, first value is converted port string, second value is boolean, True if value is a port range, False if it is a single port value """ # OpenConfig port range is of the format "####..####", whereas # Config DB format is "####-####" if ".." in port: return port.replace("..", "-"), True else: return port, False def convert_transport(self, table_name, rule_idx, rule): rule_props = {} if rule.transport.config.source_port: port, is_range = self.convert_port( str(rule.transport.config.source_port)) rule_props[ "L4_SRC_PORT_RANGE" if is_range else "L4_SRC_PORT"] = port if rule.transport.config.destination_port: port, is_range = self.convert_port( str(rule.transport.config.destination_port)) rule_props[ "L4_DST_PORT_RANGE" if is_range else "L4_DST_PORT"] = port tcp_flags = 0x00 for flag in rule.transport.config.tcp_flags: if flag == "TCP_FIN": tcp_flags |= 0x01 if flag == "TCP_SYN": tcp_flags |= 0x02 if flag == "TCP_RST": tcp_flags |= 0x04 if flag == "TCP_PSH": tcp_flags |= 0x08 if flag == "TCP_ACK": tcp_flags |= 0x10 if flag == "TCP_URG": tcp_flags |= 0x20 if flag == "TCP_ECE": tcp_flags |= 0x40 if flag == "TCP_CWR": tcp_flags |= 0x80 if tcp_flags: rule_props["TCP_FLAGS"] = '0x{:02x}/0x{:02x}'.format( tcp_flags, tcp_flags) return rule_props def convert_input_interface(self, table_name, rule_idx, rule): rule_props = {} if rule.input_interface.interface_ref.config.interface: rule_props[ "IN_PORTS"] = rule.input_interface.interface_ref.config.interface return rule_props def convert_rule_to_db_schema(self, table_name, rule): """ Convert rules format from openconfig ACL to Config DB schema :param table_name: ACL table name to which rule belong :param rule: ACL rule in openconfig format :return: dict with Config DB schema """ rule_idx = int(rule.config.sequence_id) rule_props = {} rule_data = {(table_name, "RULE_" + str(rule_idx)): rule_props} rule_props["PRIORITY"] = str(self.max_priority - rule_idx) deep_update(rule_props, self.convert_action(table_name, rule_idx, rule)) deep_update(rule_props, self.convert_l2(table_name, rule_idx, rule)) deep_update(rule_props, self.convert_ip(table_name, rule_idx, rule)) deep_update(rule_props, self.convert_transport(table_name, rule_idx, rule)) deep_update(rule_props, self.convert_input_interface(table_name, rule_idx, rule)) return rule_data def deny_rule(self, table_name): """ Create default deny rule in Config DB format :param table_name: ACL table name to which rule belong :return: dict with Config DB schema """ rule_props = {} rule_data = {(table_name, "DEFAULT_RULE"): rule_props} rule_props["PRIORITY"] = str(self.min_priority) rule_props["PACKET_ACTION"] = "DROP" if 'v6' in table_name.lower(): rule_props["ETHER_TYPE"] = str( self.ethertype_map["ETHERTYPE_IPV6"]) else: rule_props["ETHER_TYPE"] = str( self.ethertype_map["ETHERTYPE_IPV4"]) return rule_data def convert_rules(self): """ Convert rules in openconfig ACL format to Config DB schema :return: """ for acl_set_name in self.yang_acl.acl.acl_sets.acl_set: table_name = acl_set_name.replace(" ", "_").replace( "-", "_").upper().encode('ascii') acl_set = self.yang_acl.acl.acl_sets.acl_set[acl_set_name] if not self.is_table_valid(table_name): warning("%s table does not exist" % (table_name)) continue if self.current_table is not None and self.current_table != table_name: continue for acl_entry_name in acl_set.acl_entries.acl_entry: acl_entry = acl_set.acl_entries.acl_entry[acl_entry_name] try: rule = self.convert_rule_to_db_schema( table_name, acl_entry) deep_update(self.rules_info, rule) except AclLoaderException as ex: error("Error processing rule %s: %s. Skipped." % (acl_entry_name, ex)) if not self.is_table_mirror(table_name): deep_update(self.rules_info, self.deny_rule(table_name)) def full_update(self): """ Perform full update of ACL rules configuration. All existing rules will be removed. New rules loaded from file will be installed. If the current_table is not empty, only rules within that table will be removed and new rules in that table will be installed. :return: """ for key in self.rules_db_info: if self.current_table is None or self.current_table == key[0]: self.configdb.mod_entry(self.ACL_RULE, key, None) # Program for per front asic namespace also if present for namespace_configdb in self.per_npu_configdb.values(): namespace_configdb.mod_entry(self.ACL_RULE, key, None) self.configdb.mod_config({self.ACL_RULE: self.rules_info}) # Program for per front asic namespace also if present for namespace_configdb in self.per_npu_configdb.values(): namespace_configdb.mod_config({self.ACL_RULE: self.rules_info}) def incremental_update(self): """ Perform incremental ACL rules configuration update. Get existing rules from Config DB. Compare with rules specified in file and perform corresponding modifications. :return: """ # TODO: Until we test ASIC behavior, we cannot assume that we can insert # dataplane ACLs and shift existing ACLs. Therefore, we perform a full # update on dataplane ACLs, and only perform an incremental update on # control plane ACLs. new_rules = set(self.rules_info.keys()) new_dataplane_rules = set() new_controlplane_rules = set() current_rules = set(self.rules_db_info.keys()) current_dataplane_rules = set() current_controlplane_rules = set() for key in new_rules: table_name = key[0] if self.tables_db_info[table_name]['type'].upper( ) == self.ACL_TABLE_TYPE_CTRLPLANE: new_controlplane_rules.add(key) else: new_dataplane_rules.add(key) for key in current_rules: table_name = key[0] if self.tables_db_info[table_name]['type'].upper( ) == self.ACL_TABLE_TYPE_CTRLPLANE: current_controlplane_rules.add(key) else: current_dataplane_rules.add(key) # Remove all existing dataplane rules for key in current_dataplane_rules: self.configdb.mod_entry(self.ACL_RULE, key, None) # Program for per-asic namespace also if present for namespace_configdb in self.per_npu_configdb.values(): namespace_configdb.mod_entry(self.ACL_RULE, key, None) # Add all new dataplane rules for key in new_dataplane_rules: self.configdb.mod_entry(self.ACL_RULE, key, self.rules_info[key]) # Program for per-asic namespace corresponding to front asic also if present. for namespace_configdb in self.per_npu_configdb.values(): namespace_configdb.mod_entry(self.ACL_RULE, key, self.rules_info[key]) added_controlplane_rules = new_controlplane_rules.difference( current_controlplane_rules) removed_controlplane_rules = current_controlplane_rules.difference( new_controlplane_rules) existing_controlplane_rules = new_rules.intersection( current_controlplane_rules) for key in added_controlplane_rules: self.configdb.mod_entry(self.ACL_RULE, key, self.rules_info[key]) # Program for per-asic namespace corresponding to front asic also if present. # For control plane ACL it's not needed but to keep all db in sync program everywhere for namespace_configdb in self.per_npu_configdb.values(): namespace_configdb.mod_entry(self.ACL_RULE, key, self.rules_info[key]) for key in removed_controlplane_rules: self.configdb.mod_entry(self.ACL_RULE, key, None) # Program for per-asic namespace corresponding to front asic also if present. # For control plane ACL it's not needed but to keep all db in sync program everywhere for namespace_configdb in self.per_npu_configdb.values(): namespace_configdb.mod_entry(self.ACL_RULE, key, None) for key in existing_controlplane_rules: if cmp(self.rules_info[key], self.rules_db_info[key]) != 0: self.configdb.set_entry(self.ACL_RULE, key, self.rules_info[key]) # Program for per-asic namespace corresponding to front asic also if present. # For control plane ACL it's not needed but to keep all db in sync program everywhere for namespace_configdb in self.per_npu_configdb.values(): namespace_configdb.set_entry(self.ACL_RULE, key, self.rules_info[key]) def delete(self, table=None, rule=None): """ :param table: :param rule: :return: """ for key in self.rules_db_info: if not table or table == key[0]: if not rule or rule == key[1]: self.configdb.set_entry(self.ACL_RULE, key, None) # Program for per-asic namespace corresponding to front asic also if present. for namespace_configdb in self.per_npu_configdb.values(): namespace_configdb.set_entry(self.ACL_RULE, key, None) def show_table(self, table_name): """ Show ACL table configuration. :param table_name: Optional. ACL table name. Filter tables by specified name. :return: """ header = ("Name", "Type", "Binding", "Description", "Stage") data = [] for key, val in self.get_tables_db_info().items(): if table_name and key != table_name: continue stage = val.get("stage", Stage.INGRESS).lower() if val["type"] == AclLoader.ACL_TABLE_TYPE_CTRLPLANE: services = natsorted(val["services"]) data.append( [key, val["type"], services[0], val["policy_desc"], stage]) if len(services) > 1: for service in services[1:]: data.append(["", "", service, "", ""]) else: if not val["ports"]: data.append( [key, val["type"], "", val["policy_desc"], stage]) else: ports = natsorted(val["ports"]) data.append([ key, val["type"], ports[0], val["policy_desc"], stage ]) if len(ports) > 1: for port in ports[1:]: data.append(["", "", port, "", ""]) print( tabulate.tabulate(data, headers=header, tablefmt="simple", missingval="")) def show_session(self, session_name): """ Show mirror session configuration. :param session_name: Optional. Mirror session name. Filter sessions by specified name. :return: """ erspan_header = ("Name", "Status", "SRC IP", "DST IP", "GRE", "DSCP", "TTL", "Queue", "Policer", "Monitor Port", "SRC Port", "Direction") span_header = ("Name", "Status", "DST Port", "SRC Port", "Direction", "Queue", "Policer") erspan_data = [] span_data = [] for key, val in self.get_sessions_db_info().items(): if session_name and key != session_name: continue if val.get("type") == "SPAN": span_data.append([ key, val.get("status", ""), val.get("dst_port", ""), val.get("src_port", ""), val.get("direction", "").lower(), val.get("queue", ""), val.get("policer", "") ]) else: erspan_data.append([ key, val.get("status", ""), val.get("src_ip", ""), val.get("dst_ip", ""), val.get("gre_type", ""), val.get("dscp", ""), val.get("ttl", ""), val.get("queue", ""), val.get("policer", ""), val.get("monitor_port", ""), val.get("src_port", ""), val.get("direction", "").lower() ]) print("ERSPAN Sessions") print( tabulate.tabulate(erspan_data, headers=erspan_header, tablefmt="simple", missingval="")) print("\nSPAN Sessions") print( tabulate.tabulate(span_data, headers=span_header, tablefmt="simple", missingval="")) def show_policer(self, policer_name): """ Show policer configuration. :param policer_name: Optional. Policer name. Filter policers by specified name. :return: """ header = ("Name", "Type", "Mode", "CIR", "CBS") data = [] for key, val in self.get_policers_db_info().items(): if policer_name and key != policer_name: continue data.append([ key, val["meter_type"], val["mode"], val.get("cir", ""), val.get("cbs", "") ]) print( tabulate.tabulate(data, headers=header, tablefmt="simple", missingval="")) def show_rule(self, table_name, rule_id): """ Show ACL rules configuration. :param table_name: Optional. ACL table name. Filter rules by specified table name. :param rule_id: Optional. ACL rule name. Filter rule by specified rule name. :return: """ header = ("Table", "Rule", "Priority", "Action", "Match") def pop_priority(val): priority = "N/A" for key in dict(val): if (key.upper() == "PRIORITY"): priority = val.pop(key) return priority return priority def pop_action(val): action = "" for key in dict(val): key = key.upper() if key == AclAction.PACKET: action = val.pop(key) elif key == AclAction.REDIRECT: action = "REDIRECT: {}".format(val.pop(key)) elif key in (AclAction.MIRROR, AclAction.MIRROR_INGRESS): action = "MIRROR INGRESS: {}".format(val.pop(key)) elif key == AclAction.MIRROR_EGRESS: action = "MIRROR EGRESS: {}".format(val.pop(key)) else: continue return action def pop_matches(val): matches = list(sorted(["%s: %s" % (k, val[k]) for k in val])) if len(matches) == 0: matches.append("N/A") return matches raw_data = [] for (tname, rid), val in self.get_rules_db_info().items(): if table_name and table_name != tname: continue if rule_id and rule_id != rid: continue priority = pop_priority(val) action = pop_action(val) matches = pop_matches(val) rule_data = [[tname, rid, priority, action, matches[0]]] if len(matches) > 1: for m in matches[1:]: rule_data.append(["", "", "", "", m]) raw_data.append([priority, rule_data]) def cmp_rules(a, b): return cmp(a[0], b[0]) raw_data.sort(cmp_rules) raw_data.reverse() data = [] for _, d in raw_data: data += d print( tabulate.tabulate(data, headers=header, tablefmt="simple", missingval=""))
def interface(ctx): """Interface-related configuration tasks""" config_db = ConfigDBConnector() config_db.connect() ctx.obj = {} ctx.obj['config_db'] = config_db
def portchannel(ctx): config_db = ConfigDBConnector() config_db.connect() ctx.obj = {'db': config_db} pass
class TestBuffer(object): @classmethod def setup_class(cls): os.environ["PATH"] += os.pathsep + scripts_path os.environ['UTILITIES_UNIT_TESTING'] = "2" print("SETUP") def setUp(self): self.runner = CliRunner() self.config_db = ConfigDBConnector() self.config_db.connect() self.obj = {'db': self.config_db} def test_config_buffer_profile_headroom(self): runner = CliRunner() db = Db() result = runner.invoke(config.config.commands["buffer"].commands["profile"].commands["add"], ["testprofile", "--dynamic_th", "3", "--xon", "18432", "--xoff", "32768"], obj=db) print(result.exit_code) print(result.output) assert result.exit_code == 0 profile = db.cfgdb.get_entry('BUFFER_PROFILE', 'testprofile') assert profile == {'dynamic_th': '3', 'pool': '[BUFFER_POOL|ingress_lossless_pool]', 'xon': '18432', 'xoff': '32768', 'size': '51200'} def test_config_buffer_profile_dynamic_th(self): runner = CliRunner() db = Db() result = runner.invoke(config.config.commands["buffer"].commands["profile"].commands["add"], ["testprofile", "--dynamic_th", "3"], obj=db) print(result.exit_code) print(result.output) assert result.exit_code == 0 profile = db.cfgdb.get_entry('BUFFER_PROFILE', 'testprofile') assert profile == {'dynamic_th': '3', 'pool': '[BUFFER_POOL|ingress_lossless_pool]', 'headroom_type': 'dynamic'} def test_config_buffer_profile_add_existing(self): runner = CliRunner() result = runner.invoke(config.config.commands["buffer"].commands["profile"].commands["add"], ["headroom_profile", "--dynamic_th", "3"]) print(result.exit_code) print(result.output) assert result.exit_code != 0 assert "Profile headroom_profile already exist" in result.output def test_config_buffer_profile_set_non_existing(self): runner = CliRunner() result = runner.invoke(config.config.commands["buffer"].commands["profile"].commands["set"], ["non_existing_profile", "--dynamic_th", "3"]) print(result.exit_code) print(result.output) assert result.exit_code != 0 assert "Profile non_existing_profile doesn't exist" in result.output def test_config_buffer_profile_add_headroom_to_dynamic_profile(self): runner = CliRunner() result = runner.invoke(config.config.commands["buffer"].commands["profile"].commands["set"], ["alpha_profile", "--dynamic_th", "3", "--xon", "18432", "--xoff", "32768"]) print(result.exit_code) print(result.output) assert result.exit_code != 0 assert "Can't change profile alpha_profile from dynamically calculating headroom to non-dynamically one" in result.output def test_show_buffer_configuration(self): self.executor(testData['show_buffer_configuration']) def test_show_buffer_information(self): self.executor(testData['show_buffer_information']) def executor(self, testcase): runner = CliRunner() for input in testcase: exec_cmd = show.cli.commands[input['cmd'][0]].commands[input['cmd'][1]] result = runner.invoke(exec_cmd, []) print(result.exit_code) print(result.output) assert result.exit_code == 0 assert result.output == input['rc_output'] @classmethod def teardown_class(cls): os.environ["PATH"] = os.pathsep.join(os.environ["PATH"].split(os.pathsep)[:-1]) os.environ['UTILITIES_UNIT_TESTING'] = "0" print("TEARDOWN")
class DBMigrator(): def __init__(self, socket=None): """ Version string format: version_<major>_<minor>_<build> major: starting from 1, sequentially incrementing in master branch. minor: in github branches, minor version stays in 0. This minor version creates space for private branches derived from github public branches. These private branches shall use none-zero values. build: sequentially increase within a minor version domain. """ self.CURRENT_VERSION = 'version_1_0_1' self.TABLE_NAME = 'VERSIONS' self.TABLE_KEY = 'DATABASE' self.TABLE_FIELD = 'VERSION' db_kwargs = {} if socket: db_kwargs['unix_socket_path'] = socket self.configDB = ConfigDBConnector(**db_kwargs) self.configDB.db_connect('CONFIG_DB') def migrate_pfc_wd_table(self): ''' Migrate all data entries from table PFC_WD_TABLE to PFC_WD ''' data = self.configDB.get_table('PFC_WD_TABLE') for key in data.keys(): self.configDB.set_entry('PFC_WD', key, data[key]) self.configDB.delete_table('PFC_WD_TABLE') def is_ip_prefix_in_key(self, key): ''' Function to check if IP address is present in the key. If it is present, then the key would be a tuple or else, it shall be be string ''' return (isinstance(key, tuple)) def migrate_interface_table(self): ''' Migrate all data from existing INTERFACE table with IP Prefix to have an additional ONE entry without IP Prefix. For. e.g, for an entry "Vlan1000|192.168.0.1/21": {}", this function shall add an entry without IP prefix as ""Vlan1000": {}". This is for VRF compatibility. ''' if_db = [] if_tables = {'INTERFACE', 'PORTCHANNEL_INTERFACE', 'VLAN_INTERFACE'} for table in if_tables: data = self.configDB.get_table(table) for key in data.keys(): if not self.is_ip_prefix_in_key(key): if_db.append(key) continue for table in if_tables: data = self.configDB.get_table(table) for key in data.keys(): if not self.is_ip_prefix_in_key(key) or key[0] in if_db: continue log_info('Migrating interface table for ' + key[0]) self.configDB.set_entry(table, key[0], data[key]) if_db.append(key[0]) def version_unknown(self): """ version_unknown tracks all SONiC versions that doesn't have a version string defined in config_DB. Nothing can be assumped when migrating from this version to the next version. Any migration operation needs to test if the DB is in expected format before migrating date to the next version. """ log_info('Handling version_unknown') # NOTE: Uncomment next 3 lines of code when the migration code is in # place. Note that returning specific string is intentional, # here we only intended to migrade to DB version 1.0.1. # If new DB version is added in the future, the incremental # upgrade will take care of the subsequent migrations. self.migrate_pfc_wd_table() self.migrate_interface_table() self.set_version('version_1_0_1') return 'version_1_0_1' def version_1_0_1(self): """ Current latest version. Nothing to do here. """ log_info('Handling version_1_0_1') return None def get_version(self): version = self.configDB.get_entry(self.TABLE_NAME, self.TABLE_KEY) if version and version[self.TABLE_FIELD]: return version[self.TABLE_FIELD] return 'version_unknown' def set_version(self, version=None): if not version: version = self.CURRENT_VERSION log_info('Setting version to ' + version) entry = {self.TABLE_FIELD: version} self.configDB.set_entry(self.TABLE_NAME, self.TABLE_KEY, entry) def migrate(self): version = self.get_version() log_info('Upgrading from version ' + version) while version: next_version = getattr(self, version)() if next_version == version: raise Exception( 'Version migrate from %s stuck in same version' % version) version = next_version
class DropMon(object): def __init__(self): # connect CONFIG DB self.config_db = ConfigDBConnector() self.config_db.connect() # connect COUNTERS_DB self.counters_db = ConfigDBConnector() self.counters_db.db_connect('COUNTERS_DB') # connect APPL DB self.app_db = ConfigDBConnector() self.app_db.db_connect('APPL_DB') def config_drop_mon(self, args): self.config_db.mod_entry( TAM_DROP_MONITOR_FLOW_TABLE, args.flowname, { 'acl-table': args.acl_table, 'acl-rule': args.acl_rule, 'collector': args.dropcollector, 'sample': args.dropsample }) return def config_drop_mon_aging(self, args): self.config_db.mod_entry(TAM_DROP_MONITOR_AGING_INTERVAL_TABLE, "aging", {'aging-interval': args.aginginterval}) return def config_drop_mon_sample(self, args): self.config_db.mod_entry(SAMPLE_RATE_TABLE, args.samplename, {'sampling-rate': args.rate}) return def clear_single_drop_mon_flow(self, key): entry = self.config_db.get_entry(TAM_DROP_MONITOR_FLOW_TABLE, key) if entry: self.config_db.set_entry(TAM_DROP_MONITOR_FLOW_TABLE, key, None) else: return False return def clear_drop_mon_flow(self, args): key = args.flowname if key == "all": # Get all the flow keys table_data = self.config_db.get_keys(TAM_DROP_MONITOR_FLOW_TABLE) if not table_data: return True # Clear each flow key for key in table_data: self.clear_single_drop_mon_flow(key) else: # Clear the specified flow entry self.clear_single_drop_mon_flow(key) return def clear_drop_mon_sample(self, args): key = args.samplename entry = self.config_db.get_entry(SAMPLE_RATE_TABLE, key) if entry: self.config_db.set_entry(SAMPLE_RATE_TABLE, key, None) else: print "Entry Not Found" return False return def clear_drop_mon_aging_int(self, args): key = "aging" entry = self.config_db.get_entry(TAM_DROP_MONITOR_AGING_INTERVAL_TABLE, key) if entry: self.config_db.set_entry(TAM_DROP_MONITOR_AGING_INTERVAL_TABLE, key, None) else: return False return def show_flow(self, args): self.get_print_all_dropmon_flows(args.flowname) return def get_dropmon_flow_stat(self, flowname): api_response_stat = {} api_response, entryfound = self.get_dropmon_flow_info(flowname) api_response_stat['flow-name'] = flowname if entryfound is not None: for k in api_response: if k == "ietf-ts:each-flow-data": acl_rule = api_response['ietf-ts:each-flow-data'][ 'acl-rule'] acl_table = api_response['ietf-ts:each-flow-data'][ 'acl-table'] api_response_stat['rule-name'] = acl_rule api_response_stat['table-name'] = acl_table acl_rule_keys = self.config_db.get_keys(ACL_RULE_TABLE_PREFIX) for acl_rule_key in acl_rule_keys: if acl_rule_key[1] == acl_rule: acl_counter_key = 'COUNTERS:' + acl_rule_key[ 0] + ':' + acl_rule_key[1] raw_dropmon_stats = self.counters_db.get_all( self.counters_db.COUNTERS_DB, acl_counter_key) api_response_stat['ietf-ts:dropmon-stats'] = raw_ifa_stats return api_response_stat, entryfound def get_print_all_dropmon_stats(self, name): stat_dict = {} stat_list = [] if name != 'all': api_response, entryfound = self.get_dropmon_flow_stat(name) if entryfound is not None: stat_list.append(api_response) else: table_data = self.config_db.get_keys(TAM_DROP_MONITOR_FLOW_TABLE) # Get data for all keys for k in table_data: api_each_stat_response, entryfound = self.get_dropmon_flow_stat( k) if entryfound is not None: stat_list.append(api_each_stat_response) stat_dict['stat-list'] = stat_list show_cli_output("show_statistics_flow.j2", stat_dict) return def show_statistics(self, args): self.get_print_all_dropmon_stats(args.flowname) return def show_aging_interval(self, args): key = "aging" entry = self.config_db.get_entry(TAM_DROP_MONITOR_AGING_INTERVAL_TABLE, key) if entry: print "Aging interval : {}".format(entry['aging-interval']) return def show_sample(self, args): self.get_print_all_sample(args.samplename) return def get_dropmon_flow_info(self, k): flow_data = {} flow_data['acl-table-name'] = '' flow_data['sampling-rate'] = '' flow_data['collector'] = '' api_response = {} key = TAM_DROP_MONITOR_FLOW_TABLE + '|' + k raw_flow_data = self.config_db.get_all(self.config_db.CONFIG_DB, key) if raw_flow_data: sample = raw_flow_data['sample'] rate = self.config_db.get_entry(SAMPLE_RATE_TABLE, sample) raw_flow_data['sample'] = rate['sampling-rate'] api_response['ietf-ts:flow-key'] = k api_response['ietf-ts:each-flow-data'] = raw_flow_data return api_response, raw_flow_data def get_print_all_dropmon_flows(self, name): flow_dict = {} flow_list = [] if name != 'all': api_response, entryfound = self.get_dropmon_flow_info(name) if entryfound is not None: flow_list.append(api_response) else: table_data = self.config_db.get_keys(TAM_DROP_MONITOR_FLOW_TABLE) # Get data for all keys for k in table_data: api_each_flow_response, entryfound = self.get_dropmon_flow_info( k) if entryfound is not None: flow_list.append(api_each_flow_response) flow_dict['flow-list'] = flow_list show_cli_output("show_drop_monitor_flow.j2", flow_dict) return def get_sample_info(self, k): sample_data = {} sample_data['sampling-rate'] = '' api_response = {} key = SAMPLE_RATE_TABLE + '|' + k raw_sample_data = self.config_db.get_all(self.config_db.CONFIG_DB, key) api_response['ietf-ts:sample-key'] = k api_response['ietf-ts:each-sample-data'] = raw_sample_data return api_response, raw_sample_data def get_print_all_sample(self, name): sample_dict = {} sample_list = [] if name != 'all': api_response, entryfound = self.get_sample_info(name) if entryfound is not None: sample_list.append(api_response) else: table_data = self.config_db.get_keys(SAMPLE_RATE_TABLE) # Get data for all keys for k in table_data: api_each_flow_response, entryfound = self.get_sample_info(k) if entryfound is not None: sample_list.append(api_each_flow_response) sample_dict['sample-list'] = sample_list show_cli_output("show_sample.j2", sample_dict) return
class TunnelPacketHandler(object): """ This class handles unroutable tunnel packets that are trapped to the CPU from the ASIC. """ def __init__(self): self.config_db = ConfigDBConnector() self.config_db.connect() self.state_db = SonicV2Connector() self.state_db.connect(STATE_DB) self._portchannel_intfs = None self.up_portchannels = None self.netlink_api = IPRoute() @property def portchannel_intfs(self): """ Gets all portchannel interfaces and IPv4 addresses in config DB Returns: (list) Tuples of a portchannel interface name (str) and associated IPv4 address (str) """ if self._portchannel_intfs is None: intf_keys = self.config_db.get_keys(PORTCHANNEL_INTERFACE_TABLE) portchannel_intfs = [] for key in intf_keys: if isinstance(key, tuple) and len(key) > 1: if ip_interface(key[1]).version == 4: portchannel_intfs.append(key) self._portchannel_intfs = portchannel_intfs return self._portchannel_intfs def get_intf_name(self, msg): """ Gets the interface name for a netlink msg Returns: (str) The interface name, or the empty string if no interface name was found """ attr_list = msg.get('attrs', list()) for attribute, val in attr_list: if attribute == 'IFLA_IFNAME': return val return '' def netlink_msg_is_for_portchannel(self, msg): """ Determines if a netlink message is about a PortChannel interface Returns: (list) integers representing kernel indices """ ifname = self.get_intf_name(msg) return ifname in [name for name, _ in self.portchannel_intfs] def get_up_portchannels(self): """ Returns the portchannels which are operationally up Returns: (list) of interface names which are up, as strings """ portchannel_intf_names = [name for name, _ in self.portchannel_intfs] link_statuses = [] for intf in portchannel_intf_names: status = self.netlink_api.link("get", ifname=intf) link_statuses.append(status[0]) up_portchannels = [] for status in link_statuses: if status['state'] == 'up': up_portchannels.append(self.get_intf_name(status)) return up_portchannels def all_portchannels_established(self): """ Checks if the portchannel interfaces are established Note that this status does not indicate operational state Returns: (bool) True, if all interfaces are established False, otherwise """ intfs = self.portchannel_intfs for intf in intfs: intf_table_name = INTF_TABLE_TEMPLATE.format(intf[0], intf[1]) intf_state = self.state_db.get( STATE_DB, intf_table_name, STATE_KEY ) if intf_state and intf_state.lower() != 'ok': return False return True def wait_for_portchannels(self, interval=5, timeout=60): """ Continuosly checks if all portchannel host interfaces are established Args: interval: the interval (in seconds) at which to perform the check timeout: maximum allowed duration (in seconds) to wait for interfaces to come up Raises: RuntimeError if the timeout duration is reached and interfaces are still not up """ start = datetime.now() while (datetime.now() - start).seconds < timeout: if self.all_portchannels_established(): logger.log_info("All portchannel intfs are established") return None logger.log_info("Not all portchannel intfs are established") time.sleep(interval) raise RuntimeError('Portchannel intfs were not established ' 'within {}'.format(timeout)) def get_ipinip_tunnel_addrs(self): """ Get the IP addresses used for the IPinIP tunnel These should be the Loopback0 addresses for this device and the peer device Returns: ((str) self_loopback_ip, (str) peer_loopback_ip) or (None, None) If the tunnel type is not IPinIP or if an error is encountered. This most likely means the host device is not a dual ToR device """ try: peer_switch = self.config_db.get_keys(PEER_SWITCH_TABLE)[0] tunnel = self.config_db.get_keys(TUNNEL_TABLE)[0] except IndexError: logger.log_warning('PEER_SWITCH or TUNNEL table' 'not found in config DB') return None, None try: tunnel_table = self.config_db.get_entry(TUNNEL_TABLE, tunnel) tunnel_type = tunnel_table[TUNNEL_TYPE_KEY].lower() self_loopback_ip = tunnel_table[DST_IP_KEY] peer_loopback_ip = self.config_db.get_entry( PEER_SWITCH_TABLE, peer_switch )[ADDRESS_IPV4_KEY] except KeyError as error: logger.log_warning( 'PEER_SWITCH or TUNNEL table missing data, ' 'could not find key {}' .format(error) ) return None, None if tunnel_type == IPINIP_TUNNEL: return self_loopback_ip, peer_loopback_ip return None, None def get_inner_pkt_type(self, packet): """ Get the type of an inner encapsulated packet Returns: (str) 'v4' if the inner packet is IPv4 (str) 'v6' if the inner packet is IPv6 (bool) False if `packet` is not an IPinIP packet """ if packet.haslayer(IP): # Determine inner packet type based on IP protocol number # The outer packet type should always be IPv4 if packet[IP].proto == 4: return IP elif packet[IP].proto == 41: return IPv6 return False def wait_for_netlink_msgs(self): """ Gathers any RTM_NEWLINK messages Returns: (list) containing any received messages """ msgs = [] with IPRoute() as ipr: ipr.bind() for msg in ipr.get(): if msg['event'] == RTM_NEWLINK: msgs.append(msg) return msgs def sniffer_restart_required(self, messages): """ Determines if the packet sniffer needs to be restarted A restart is required if all of the following conditions are met: 1. A netlink message of type RTM_NEWLINK is received (this is checked by `wait_for_netlink_msgs`) 2. The interface index of the message corresponds to a portchannel interface 3. The state of the interface in the message is 'up' Here, we do not care about an interface going down since the sniffer is able to continue sniffing on the other interfaces. However, if an interface has gone down and come back up, we need to restart the sniffer to be able to sniff traffic on the interface that has come back up. """ for msg in messages: if self.netlink_msg_is_for_portchannel(msg): if msg['state'] == 'up': logger.log_info('{} came back up, sniffer restart required' .format(self.get_intf_name(msg))) return True return False def listen_for_tunnel_pkts(self): """ Listens for tunnel packets that are trapped to CPU These packets may be trapped if there is no neighbor info for the inner packet destination IP in the hardware. """ def _ping_inner_dst(packet): """ Pings the inner destination IP for an encapsulated packet Args: packet: The encapsulated packet received """ inner_packet_type = self.get_inner_pkt_type(packet) if inner_packet_type and packet[IP].dst == self_ip: cmds = ['timeout', '0.2', 'ping', '-c1', '-W1', '-i0', '-n', '-q'] if inner_packet_type == IPv6: cmds.append('-6') dst_ip = packet[IP].payload[inner_packet_type].dst cmds.append(dst_ip) logger.log_info("Running command '{}'".format(' '.join(cmds))) subprocess.run(cmds, stdout=subprocess.DEVNULL) self_ip, peer_ip = self.get_ipinip_tunnel_addrs() if self_ip is None or peer_ip is None: logger.log_notice('Could not get tunnel addresses from ' 'config DB, exiting...') return None packet_filter = 'host {} and host {}'.format(self_ip, peer_ip) logger.log_notice('Starting tunnel packet handler for {}' .format(packet_filter)) sniff_intfs = self.get_up_portchannels() logger.log_info("Listening on interfaces {}".format(sniff_intfs)) sniffer = AsyncSniffer( iface=sniff_intfs, filter=packet_filter, prn=_ping_inner_dst, store=0 ) sniffer.start() while True: msgs = self.wait_for_netlink_msgs() if self.sniffer_restart_required(msgs): sniffer.stop() sniff_intfs = self.get_up_portchannels() logger.log_notice('Restarting tunnel packet handler on ' 'interfaces {}'.format(sniff_intfs)) sniffer = AsyncSniffer( iface=sniff_intfs, filter=packet_filter, prn=_ping_inner_dst, store=0 ) sniffer.start() def run(self): """ Entry point for the TunnelPacketHandler class """ self.wait_for_portchannels() self.listen_for_tunnel_pkts()
class DBMigrator(): def __init__(self, namespace, socket=None): """ Version string format: version_<major>_<minor>_<build> major: starting from 1, sequentially incrementing in master branch. minor: in github branches, minor version stays in 0. This minor version creates space for private branches derived from github public branches. These private branches shall use none-zero values. build: sequentially increase within a minor version domain. """ self.CURRENT_VERSION = 'version_1_0_4' self.TABLE_NAME = 'VERSIONS' self.TABLE_KEY = 'DATABASE' self.TABLE_FIELD = 'VERSION' db_kwargs = {} if socket: db_kwargs['unix_socket_path'] = socket if namespace is None: self.configDB = ConfigDBConnector(**db_kwargs) else: self.configDB = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace, **db_kwargs) self.configDB.db_connect('CONFIG_DB') self.appDB = SonicV2Connector(host='127.0.0.1') if self.appDB is not None: self.appDB.connect(self.appDB.APPL_DB) version_info = device_info.get_sonic_version_info() asic_type = version_info.get('asic_type') self.asic_type = asic_type if asic_type == "mellanox": from mellanox_buffer_migrator import MellanoxBufferMigrator self.mellanox_buffer_migrator = MellanoxBufferMigrator(self.configDB) def migrate_pfc_wd_table(self): ''' Migrate all data entries from table PFC_WD_TABLE to PFC_WD ''' data = self.configDB.get_table('PFC_WD_TABLE') for key in data.keys(): self.configDB.set_entry('PFC_WD', key, data[key]) self.configDB.delete_table('PFC_WD_TABLE') def is_ip_prefix_in_key(self, key): ''' Function to check if IP address is present in the key. If it is present, then the key would be a tuple or else, it shall be be string ''' return (isinstance(key, tuple)) def migrate_interface_table(self): ''' Migrate all data from existing INTERFACE table with IP Prefix to have an additional ONE entry without IP Prefix. For. e.g, for an entry "Vlan1000|192.168.0.1/21": {}", this function shall add an entry without IP prefix as ""Vlan1000": {}". This is for VRF compatibility. ''' if_db = [] if_tables = { 'INTERFACE', 'PORTCHANNEL_INTERFACE', 'VLAN_INTERFACE', 'LOOPBACK_INTERFACE' } for table in if_tables: data = self.configDB.get_table(table) for key in data.keys(): if not self.is_ip_prefix_in_key(key): if_db.append(key) continue for table in if_tables: data = self.configDB.get_table(table) for key in data.keys(): if not self.is_ip_prefix_in_key(key) or key[0] in if_db: continue log.log_info('Migrating interface table for ' + key[0]) self.configDB.set_entry(table, key[0], data[key]) if_db.append(key[0]) def migrate_intf_table(self): ''' Migrate all data from existing INTF table in APP DB during warmboot with IP Prefix to have an additional ONE entry without IP Prefix. For. e.g, for an entry "Vlan1000:192.168.0.1/21": {}", this function shall add an entry without IP prefix as ""Vlan1000": {}". This also migrates 'lo' to 'Loopback0' interface ''' if self.appDB is None: return data = self.appDB.keys(self.appDB.APPL_DB, "INTF_TABLE:*") if data is None: return if_db = [] for key in data: if_name = key.split(":")[1] if if_name == "lo": self.appDB.delete(self.appDB.APPL_DB, key) key = key.replace(if_name, "Loopback0") log.log_info('Migrating lo entry to ' + key) self.appDB.set(self.appDB.APPL_DB, key, 'NULL', 'NULL') if '/' not in key: if_db.append(key.split(":")[1]) continue data = self.appDB.keys(self.appDB.APPL_DB, "INTF_TABLE:*") for key in data: if_name = key.split(":")[1] if if_name in if_db: continue log.log_info('Migrating intf table for ' + if_name) table = "INTF_TABLE:" + if_name self.appDB.set(self.appDB.APPL_DB, table, 'NULL', 'NULL') if_db.append(if_name) def version_unknown(self): """ version_unknown tracks all SONiC versions that doesn't have a version string defined in config_DB. Nothing can be assumped when migrating from this version to the next version. Any migration operation needs to test if the DB is in expected format before migrating date to the next version. """ log.log_info('Handling version_unknown') # NOTE: Uncomment next 3 lines of code when the migration code is in # place. Note that returning specific string is intentional, # here we only intended to migrade to DB version 1.0.1. # If new DB version is added in the future, the incremental # upgrade will take care of the subsequent migrations. self.migrate_pfc_wd_table() self.migrate_interface_table() self.migrate_intf_table() self.set_version('version_1_0_2') return 'version_1_0_2' def version_1_0_1(self): """ Version 1_0_1. """ log.log_info('Handling version_1_0_1') self.migrate_interface_table() self.migrate_intf_table() self.set_version('version_1_0_2') return 'version_1_0_2' def version_1_0_2(self): """ Version 1_0_2. """ log.log_info('Handling version_1_0_2') # Check ASIC type, if Mellanox platform then need DB migration if self.asic_type == "mellanox": if self.mellanox_buffer_migrator.mlnx_migrate_buffer_pool_size('version_1_0_2', 'version_1_0_3'): self.set_version('version_1_0_3') else: self.set_version('version_1_0_3') return 'version_1_0_3' def version_1_0_3(self): """ Version 1_0_3. """ log.log_info('Handling version_1_0_3') # Check ASIC type, if Mellanox platform then need DB migration if self.asic_type == "mellanox": if self.mellanox_buffer_migrator.mlnx_migrate_buffer_pool_size('version_1_0_3', 'version_1_0_4') and self.mellanox_buffer_migrator.mlnx_migrate_buffer_profile('version_1_0_3', 'version_1_0_4'): self.set_version('version_1_0_4') else: self.set_version('version_1_0_4') return 'version_1_0_4' def version_1_0_4(self): """ Current latest version. Nothing to do here. """ log.log_info('Handling version_1_0_4') return None def get_version(self): version = self.configDB.get_entry(self.TABLE_NAME, self.TABLE_KEY) if version and version[self.TABLE_FIELD]: return version[self.TABLE_FIELD] return 'version_unknown' def set_version(self, version=None): if not version: version = self.CURRENT_VERSION log.log_info('Setting version to ' + version) entry = { self.TABLE_FIELD : version } self.configDB.set_entry(self.TABLE_NAME, self.TABLE_KEY, entry) def common_migration_ops(self): try: with open(INIT_CFG_FILE) as f: init_db = json.load(f) except Exception as e: raise Exception(str(e)) for init_cfg_table, table_val in init_db.items(): data = self.configDB.get_table(init_cfg_table) if data: # Ignore overriding the values that pre-exist in configDB continue log.log_info("Migrating table {} from INIT_CFG to config_db".format(init_cfg_table)) # Update all tables that do not exist in configDB but are present in INIT_CFG for init_table_key, init_table_val in table_val.items(): self.configDB.set_entry(init_cfg_table, init_table_key, init_table_val) def migrate(self): version = self.get_version() log.log_info('Upgrading from version ' + version) while version: next_version = getattr(self, version)() if next_version == version: raise Exception('Version migrate from %s stuck in same version' % version) version = next_version # Perform common migration ops self.common_migration_ops()
def config(port, json_output): """Show muxcable config information""" port_mux_tbl_keys = {} asic_start_idx = None per_npu_configdb = {} mux_tbl_cfg_db = {} peer_switch_tbl_cfg_db = {} # Getting all front asic namespace and correspding config and state DB connector namespaces = multi_asic.get_front_end_namespaces() for namespace in namespaces: asic_id = multi_asic.get_asic_index_from_namespace(namespace) if asic_start_idx is None: asic_start_idx = asic_id # TO-DO replace the macros with correct swsscommon names #config_db[asic_id] = swsscommon.DBConnector("CONFIG_DB", REDIS_TIMEOUT_MSECS, True, namespace) #mux_tbl_cfg_db[asic_id] = swsscommon.Table(config_db[asic_id], swsscommon.CFG_MUX_CABLE_TABLE_NAME) per_npu_configdb[asic_id] = ConfigDBConnector( use_unix_socket_path=True, namespace=namespace) per_npu_configdb[asic_id].connect() mux_tbl_cfg_db[asic_id] = per_npu_configdb[asic_id].get_table( "MUX_CABLE") peer_switch_tbl_cfg_db[asic_id] = per_npu_configdb[asic_id].get_table( "PEER_SWITCH") #peer_switch_tbl_cfg_db[asic_id] = swsscommon.Table(config_db[asic_id], swsscommon.CFG_PEER_SWITCH_TABLE_NAME) port_mux_tbl_keys[asic_id] = mux_tbl_cfg_db[asic_id].keys() if port is not None: asic_index = None if platform_sfputil is not None: asic_index = platform_sfputil.get_asic_id_for_logical_port(port) if asic_index is None: # TODO this import is only for unit test purposes, and should be removed once sonic_platform_base # is fully mocked import sonic_platform_base.sonic_sfp.sfputilhelper asic_index = sonic_platform_base.sonic_sfp.sfputilhelper.SfpUtilHelper( ).get_asic_id_for_logical_port(port) if asic_index is None: click.echo( "Got invalid asic index for port {}, cant retreive mux status" .format(port)) sys.exit(CONFIG_FAIL) port_status_dict = {} port_status_dict["MUX_CABLE"] = {} port_status_dict["MUX_CABLE"]["PEER_TOR"] = {} peer_switch_value = None switch_name = get_switch_name(per_npu_configdb[asic_start_idx]) if asic_start_idx is not None: peer_switch_value = get_value_for_key_in_config_tbl( per_npu_configdb[asic_start_idx], switch_name, "address_ipv4", "PEER_SWITCH") port_status_dict["MUX_CABLE"]["PEER_TOR"] = peer_switch_value if port_mux_tbl_keys[asic_id] is not None: if port in port_mux_tbl_keys[asic_id]: if json_output: port_status_dict["MUX_CABLE"] = {} port_status_dict["MUX_CABLE"]["PORTS"] = {} create_json_dump_per_port_config(port_status_dict, per_npu_configdb, asic_id, port) click.echo("{}".format( json.dumps(port_status_dict, indent=4))) sys.exit(CONFIG_SUCCESSFUL) else: print_data = [] print_peer_tor = [] create_table_dump_per_port_config(print_data, per_npu_configdb, asic_id, port) headers = ['SWITCH_NAME', 'PEER_TOR'] peer_tor_data = [] peer_tor_data.append(switch_name) peer_tor_data.append(peer_switch_value) print_peer_tor.append(peer_tor_data) click.echo(tabulate(print_peer_tor, headers=headers)) headers = ['port', 'state', 'ipv4', 'ipv6'] click.echo(tabulate(print_data, headers=headers)) sys.exit(CONFIG_SUCCESSFUL) else: click.echo( "this is not a valid port present on mux_cable".format( port)) sys.exit(CONFIG_FAIL) else: click.echo( "there is not a valid asic table for this asic_index".format( asic_index)) sys.exit(CONFIG_FAIL) else: port_status_dict = {} port_status_dict["MUX_CABLE"] = {} port_status_dict["MUX_CABLE"]["PEER_TOR"] = {} peer_switch_value = None switch_name = get_switch_name(per_npu_configdb[asic_start_idx]) if asic_start_idx is not None: peer_switch_value = get_value_for_key_in_config_tbl( per_npu_configdb[asic_start_idx], switch_name, "address_ipv4", "PEER_SWITCH") port_status_dict["MUX_CABLE"]["PEER_TOR"] = peer_switch_value if json_output: port_status_dict["MUX_CABLE"]["PORTS"] = {} for namespace in namespaces: asic_id = multi_asic.get_asic_index_from_namespace(namespace) for port in port_mux_tbl_keys[asic_id]: create_json_dump_per_port_config(port_status_dict, per_npu_configdb, asic_id, port) click.echo("{}".format(json.dumps(port_status_dict, indent=4))) else: print_data = [] print_peer_tor = [] for namespace in namespaces: asic_id = multi_asic.get_asic_index_from_namespace(namespace) for port in port_mux_tbl_keys[asic_id]: create_table_dump_per_port_config(print_data, per_npu_configdb, asic_id, port) headers = ['SWITCH_NAME', 'PEER_TOR'] peer_tor_data = [] peer_tor_data.append(switch_name) peer_tor_data.append(peer_switch_value) print_peer_tor.append(peer_tor_data) click.echo(tabulate(print_peer_tor, headers=headers)) headers = ['port', 'state', 'ipv4', 'ipv6'] click.echo(tabulate(print_data, headers=headers)) sys.exit(CONFIG_SUCCESSFUL)
def _get_all_neighbor_ipaddresses(): """Returns list of strings containing IP addresses of all BGP neighbors """ config_db = ConfigDBConnector() config_db.connect() return config_db.get_table('BGP_NEIGHBOR').keys()
from __future__ import print_function import sys import os.path import argparse import json import time from collections import OrderedDict from natsort import natsorted from swsssdk import ConfigDBConnector test_only = False connected = False db = ConfigDBConnector() def init(): global connected if connected == False: db.connect(False) connected = True def db_update(t, k, lst): init() db.mod_entry(t, k, lst)
class DBSyncDaemon(SonicSyncDaemon): """ A Thread that listens to changes in CONFIG DB, and contains handlers to configure lldpd accordingly. """ def __init__(self): super(DBSyncDaemon, self).__init__() self.config_db = ConfigDBConnector() self.config_db.connect() logger.info("[lldp dbsyncd] Connected to configdb") self.port_table = {} def run_command(self, command): p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) stdout = p.communicate()[0] p.wait() if p.returncode != 0: logger.error("[lldp dbsyncd] command execution returned {}. " "Command: '{}', stdout: '{}'".format( p.returncode, command, stdout)) def port_handler(self, key, data): """ Handle updates in 'PORT' table. """ # we're interested only in description for now if self.port_table[key].get("description") != data.get("description"): new_descr = data.get("description", " ") logger.info( "[lldp dbsyncd] Port {} description changed to {}.".format( key, new_descr)) self.run_command( "lldpcli configure lldp portidsubtype local {} description '{}'" .format(key, new_descr)) # update local cache self.port_table[key] = data def mgmt_addr_init(self): man_table = self.config_db.get_table(MGMT_INTERFACE_TABLE_NAME) # example table: # {('eth0', 'FC00:2::32/64'): {'forced_mgmt_routes': ['10.0.0.100/31'], 'gwaddr': 'fc00:2::fe'}, # ('eth0', '10.224.23.69/24'): {'gwaddr': '10.224.23.254'}} mgmt_ips = [i[1].split('/')[0] for i in man_table.keys()] ipv4_mgmt_ips = [i for i in mgmt_ips if re.match(IPV4_PATTERN, i)] try: self.run_command( "lldpcli configure system ip management pattern {}".format( ipv4_mgmt_ips[0])) logger.debug("Configured lldpd with {} local management ip".format( ipv4_mgmt_ips[0])) except IndexError: logger.error("No IPv4 management interface found") def port_table_init(self): self.port_table = self.config_db.get_table(PORT_TABLE_NAME) # supply LLDP_LOC_ENTRY_TABLE and lldpd with correct values on start for port_name, attributes in self.port_table.items(): self.run_command( "lldpcli configure lldp portidsubtype local {} description '{}'" .format(port_name, attributes.get("description", " "))) def run(self): self.port_table_init() self.mgmt_addr_init() # subscribe for further changes self.config_db.subscribe( PORT_TABLE_NAME, lambda table, key, data: self.port_handler(key, data)) logger.info("[lldp dbsyncd] Subscribed to configdb PORT table") self.config_db.listen()
def add_table_kv(table, entry, key, val): config_db = ConfigDBConnector() config_db.connect() config_db.mod_entry(table, entry, {key:val})
def disable(ctx): """Disable the NAT feature """ config_db = ConfigDBConnector() config_db.connect() config_db.mod_entry("NAT_GLOBAL", "Values", {"admin_mode": "disabled"})
def add_udp(ctx, global_ip, global_port, local_ip, local_port, nat_type, twice_nat_id): """Add Static UDP Protocol NAPT-related configutation""" # Verify the ip address format if is_valid_ipv4_address(local_ip) is False: ctx.fail( "Given local ip address {} is invalid. Please enter a valid local ip address !!" .format(local_ip)) if is_valid_ipv4_address(global_ip) is False: ctx.fail( "Given global ip address {} is invalid. Please enter a valid global ip address !!" .format(global_ip)) config_db = ConfigDBConnector() config_db.connect() entryFound = False table = "STATIC_NAPT" key = "{}|UDP|{}".format(global_ip, global_port) dataKey1 = 'local_ip' dataKey2 = 'local_port' dataKey3 = 'nat_type' dataKey4 = 'twice_nat_id' data = config_db.get_entry(table, key) if data: if data[dataKey1] == local_ip and data[dataKey2] == str(local_port): click.echo( "Trying to add static napt entry, which is already present.") entryFound = True if nat_type == 'snat': ipAddress = local_ip else: ipAddress = global_ip if isIpOverlappingWithAnyStaticEntry(ipAddress, 'STATIC_NAT') is True: ctx.fail("Given entry is overlapping with existing NAT entry !!") if entryFound is False: counters_db = SonicV2Connector(host="127.0.0.1") counters_db.connect(counters_db.COUNTERS_DB) snat_entries = 0 max_entries = 0 exists = counters_db.exists(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') if exists: counter_entry = counters_db.get_all(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') if 'SNAT_ENTRIES' in counter_entry: snat_entries = counter_entry['SNAT_ENTRIES'] if 'MAX_NAT_ENTRIES' in counter_entry: max_entries = counter_entry['MAX_NAT_ENTRIES'] if int(snat_entries) >= int(max_entries): click.echo( "Max limit is reached for NAT entries, skipping adding the entry." ) entryFound = True if entryFound is False: count = 0 if twice_nat_id is not None: count = getTwiceNatIdCountWithStaticEntries( twice_nat_id, table, count) count = getTwiceNatIdCountWithDynamicBinding( twice_nat_id, count, None) if count > 1: ctx.fail( "Same Twice nat id is not allowed for more than 2 entries!!" ) if nat_type is not None and twice_nat_id is not None: config_db.set_entry( table, key, { dataKey1: local_ip, dataKey2: local_port, dataKey3: nat_type, dataKey4: twice_nat_id }) elif nat_type is not None: config_db.set_entry(table, key, { dataKey1: local_ip, dataKey2: local_port, dataKey3: nat_type }) elif twice_nat_id is not None: config_db.set_entry(table, key, { dataKey1: local_ip, dataKey2: local_port, dataKey4: twice_nat_id }) else: config_db.set_entry(table, key, { dataKey1: local_ip, dataKey2: local_port })
def hash_view(nhg): config_db = ConfigDBConnector() config_db.connect() fg_nhg_prefix_table = {} fg_nhg_alias = {} fg_nhg_prefix_table = config_db.get_table('FG_NHG_PREFIX') for key, value in fg_nhg_prefix_table.items(): fg_nhg_alias[key] = value['FG_NHG'] state_db = SonicV2Connector(host='127.0.0.1') state_db.connect(state_db.STATE_DB, False) # Make one attempt only STATE_DB TABLE_NAME_SEPARATOR = '|' prefix = 'FG_ROUTE_TABLE' + TABLE_NAME_SEPARATOR _hash = '{}{}'.format(prefix, '*') table_keys = [] table_keys = state_db.keys(state_db.STATE_DB, _hash) t_dict = {} table = [] output_dict = {} bank_dict = {} if nhg is None: for nhg_prefix in table_keys: bank_dict = {} t_dict = state_db.get_all(state_db.STATE_DB, nhg_prefix) vals = sorted(set([val for val in t_dict.values()])) for nh_ip in vals: bank_ids = sorted( [int(k) for k, v in t_dict.items() if v == nh_ip]) bank_ids = [str(x) for x in bank_ids] if nhg_prefix in output_dict: output_dict[nhg_prefix].append(nh_ip.split("@")[0]) else: output_dict[nhg_prefix] = [nh_ip.split("@")[0]] bank_dict[nh_ip.split("@")[0]] = bank_ids bank_dict = OrderedDict(sorted(bank_dict.items())) nhg_prefix_report = (nhg_prefix.split("|")[1]) header = ["FG_NHG_PREFIX", "Next Hop", "Hash buckets"] for nhip, val in bank_dict.items(): formatted_banks = ','.replace(',', '\n').join(bank_dict[nhip]) table.append([nhg_prefix_report, nhip, formatted_banks]) click.echo(tabulate(table, header, tablefmt="grid")) else: for nhg_prefix, alias in fg_nhg_alias.items(): if nhg == alias: if ":" in nhg_prefix: for key in table_keys: mod_key = key.split("|")[1].split("/")[0] mod_nhg_prefix = nhg_prefix.split("/")[0] if ipaddress.ip_address( mod_key).exploded == ipaddress.ip_address( mod_nhg_prefix).exploded: t_dict = state_db.get_all(state_db.STATE_DB, key) nhg_prefix = "FG_ROUTE_TABLE|" + nhg_prefix else: nhg_prefix = "FG_ROUTE_TABLE|" + nhg_prefix t_dict = state_db.get_all(state_db.STATE_DB, nhg_prefix) vals = sorted(set([val for val in t_dict.values()])) for nh_ip in vals: bank_ids = sorted( [int(k) for k, v in t_dict.items() if v == nh_ip]) bank_ids = [str(x) for x in bank_ids] if nhg_prefix in output_dict: output_dict[nhg_prefix].append(nh_ip.split("@")[0]) else: output_dict[nhg_prefix] = [nh_ip.split("@")[0]] bank_dict[nh_ip.split("@")[0]] = bank_ids nhg_prefix_report = (nhg_prefix.split("|")[1]) bank_dict = OrderedDict(sorted(bank_dict.items())) header = ["FG_NHG_PREFIX", "Next Hop", "Hash buckets"] for nhip, val in bank_dict.items(): formatted_banks = ','.replace(',', '\n').join(bank_dict[nhip]) table.append([nhg_prefix_report, nhip, formatted_banks]) click.echo(tabulate(table, header, tablefmt="grid"))
def __init__(self): super(DBSyncDaemon, self).__init__() self.config_db = ConfigDBConnector() self.config_db.connect() logger.info("[lldp dbsyncd] Connected to configdb") self.port_table = {}
def udp_timeout(ctx, seconds): """Set NAT UDP timeout configuration""" config_db = ConfigDBConnector() config_db.connect() config_db.mod_entry("NAT_GLOBAL", "Values", {"nat_udp_timeout": seconds})
class DBMigrator(): def __init__(self): """ Version string format: version_<major>_<minor>_<build> major: starting from 1, sequentially incrementing in master branch. minor: in github branches, minor version stays in 0. This minor version creates space for private branches derived from github public branches. These private branches shall use none-zero values. build: sequentially increase within a minor version domain. """ self.CURRENT_VERSION = 'version_1_0_1' self.TABLE_NAME = 'VERSIONS' self.TABLE_KEY = 'DATABASE' self.TABLE_FIELD = 'VERSION' self.configDB = ConfigDBConnector() self.configDB.db_connect('CONFIG_DB') def migrate_pfc_wd_table(self): # Migrate all data entries from table PFC_WD_TABLE to PFC_WD data = self.configDB.get_table('PFC_WD_TABLE') for key in data.keys(): self.configDB.set_entry('PFC_WD', key, data[key]) self.configDB.delete_table('PFC_WD_TABLE') def version_unknown(self): """ version_unknown tracks all SONiC versions that doesn't have a version string defined in config_DB. Nothing can be assumped when migrating from this version to the next version. Any migration operation needs to test if the DB is in expected format before migrating date to the next version. """ log_info('Handling version_unknown') # NOTE: Uncomment next 3 lines of code when the migration code is in # place. Note that returning specific string is intentional, # here we only intended to migrade to DB version 1.0.1. # If new DB version is added in the future, the incremental # upgrade will take care of the subsequent migrations. # self.migrate_pfc_wd_table() # self.set_version('version_1_0_1') # return 'version_1_0_1' def version_1_0_1(self): """ Current latest version. Nothing to do here. """ log_info('Handling version_1_0_1') return None def get_version(self): version = self.configDB.get_entry(self.TABLE_NAME, self.TABLE_KEY) if version and version[self.TABLE_FIELD]: return version[self.TABLE_FIELD] return 'version_unknown' def set_version(self, version=None): if not version: version = self.CURRENT_VERSION log_info('Setting version to ' + version) entry = { self.TABLE_FIELD : version } self.configDB.set_entry(self.TABLE_NAME, self.TABLE_KEY, entry) def migrate(self): version = self.get_version() log_info('Upgrading from version ' + version) while version: next_version = getattr(self, version)() if next_version == version: raise Exception('Version migrate from %s stuck in same version' % version) version = next_version
def add_pool(ctx, pool_name, global_ip_range, global_port_range): """Add Pool for Dynamic NAT-related configutation""" if len(pool_name) > 32: ctx.fail( "Invalid pool name. Maximum allowed pool name is 32 characters !!") # Verify the ip address range and format ip_address = global_ip_range.split("-") if len(ip_address) > 2: ctx.fail( "Given ip address range {} is invalid. Please enter a valid ip address range !!" .format(global_ip_range)) elif len(ip_address) == 2: if is_valid_ipv4_address(ip_address[0]) is False: ctx.fail( "Given ip address {} is not valid global address. Please enter a valid ip address !!" .format(ip_address[0])) if is_valid_ipv4_address(ip_address[1]) is False: ctx.fail( "Given ip address {} is not valid global address. Please enter a valid ip address !!" .format(ip_address[1])) ipLowLimit = int(ipaddress.IPv4Address(ip_address[0])) ipHighLimit = int(ipaddress.IPv4Address(ip_address[1])) if ipLowLimit >= ipHighLimit: ctx.fail( "Given ip address range {} is invalid. Please enter a valid ip address range !!" .format(global_ip_range)) else: if is_valid_ipv4_address(ip_address[0]) is False: ctx.fail( "Given ip address {} is not valid global address. Please enter a valid ip address !!" .format(ip_address[0])) ipLowLimit = int(ipaddress.IPv4Address(ip_address[0])) ipHighLimit = int(ipaddress.IPv4Address(ip_address[0])) # Verify the port address range and format if global_port_range is not None: port_address = global_port_range.split("-") if len(port_address) > 2: ctx.fail( "Given port address range {} is invalid. Please enter a valid port address range !!" .format(global_port_range)) elif len(port_address) == 2: if is_valid_port_address(port_address[0]) is False: ctx.fail( "Given port value {} is invalid. Please enter a valid port value !!" .format(port_address[0])) if is_valid_port_address(port_address[1]) is False: ctx.fail( "Given port value {} is invalid. Please enter a valid port value !!" .format(port_address[1])) portLowLimit = int(port_address[0]) portHighLimit = int(port_address[1]) if portLowLimit >= portHighLimit: ctx.fail( "Given port address range {} is invalid. Please enter a valid port address range !!" .format(global_port_range)) else: if is_valid_port_address(port_address[0]) is False: ctx.fail( "Given port value {} is invalid. Please enter a valid port value !!" .format(port_address[0])) portLowLimit = int(port_address[0]) portHighLimit = int(port_address[0]) else: global_port_range = "NULL" config_db = ConfigDBConnector() config_db.connect() entryFound = False table = "NAT_POOL" key = pool_name dataKey1 = 'nat_ip' dataKey2 = 'nat_port' data = config_db.get_entry(table, key) if data: if data[dataKey1] == global_ip_range and data[ dataKey2] == global_port_range: click.echo("Trying to add pool, which is already present.") entryFound = True pool_dict = config_db.get_table(table) if len(pool_dict) == 16: click.echo( "Failed to add pool, as already reached maximum pool limit 16.") entryFound = True # Verify the Ip address is overlapping with any Static NAT entry if entryFound == False: static_dict = config_db.get_table('STATIC_NAT') if static_dict: for staticKey, staticValues in static_dict.items(): global_ip = "---" local_ip = "---" nat_type = "dnat" if isinstance(staticKey, unicode) is True: global_ip = staticKey else: continue local_ip = staticValues["local_ip"] if "nat_type" in staticValues: nat_type = staticValues["nat_type"] if nat_type == "snat": global_ip = local_ip ipAddress = int(ipaddress.IPv4Address(unicode(global_ip))) if (ipAddress >= ipLowLimit and ipAddress <= ipHighLimit): ctx.fail( "Given Ip address entry is overlapping with existing Static NAT entry !!" ) if entryFound == False: config_db.set_entry(table, key, { dataKey1: global_ip_range, dataKey2: global_port_range })
def config(redis_unix_socket_path): """Show warm restart config""" kwargs = {} if redis_unix_socket_path: kwargs['unix_socket_path'] = redis_unix_socket_path config_db = ConfigDBConnector(**kwargs) config_db.connect(wait_for_init=False) data = config_db.get_table('WARM_RESTART') # Python dictionary keys() Method keys = list(data.keys()) state_db = SonicV2Connector(host='127.0.0.1') state_db.connect(state_db.STATE_DB, False) # Make one attempt only TABLE_NAME_SEPARATOR = '|' prefix = 'WARM_RESTART_ENABLE_TABLE' + TABLE_NAME_SEPARATOR _hash = '{}{}'.format(prefix, '*') # DBInterface keys() method enable_table_keys = state_db.keys(state_db.STATE_DB, _hash) def tablelize(keys, data, enable_table_keys, prefix): table = [] if enable_table_keys is not None: for k in enable_table_keys: k = k.replace(prefix, "") if k not in keys: keys.append(k) for k in keys: r = [] r.append(k) enable_k = prefix + k if enable_table_keys is None or enable_k not in enable_table_keys: r.append("false") else: r.append(state_db.get(state_db.STATE_DB, enable_k, "enable")) if k not in data: r.append("NULL") r.append("NULL") r.append("NULL") elif 'neighsyncd_timer' in data[k]: r.append("neighsyncd_timer") r.append(data[k]['neighsyncd_timer']) r.append("NULL") elif 'bgp_timer' in data[k] or 'bgp_eoiu' in data[k]: if 'bgp_timer' in data[k]: r.append("bgp_timer") r.append(data[k]['bgp_timer']) else: r.append("NULL") r.append("NULL") if 'bgp_eoiu' in data[k]: r.append(data[k]['bgp_eoiu']) else: r.append("NULL") elif 'teamsyncd_timer' in data[k]: r.append("teamsyncd_timer") r.append(data[k]['teamsyncd_timer']) r.append("NULL") else: r.append("NULL") r.append("NULL") r.append("NULL") table.append(r) return table header = ['name', 'enable', 'timer_name', 'timer_duration', 'eoiu_enable'] click.echo( tabulate(tablelize(keys, data, enable_table_keys, prefix), header)) state_db.close(state_db.STATE_DB)
def mode(state, port, json_output): """Show muxcable summary information""" port_table_keys = {} y_cable_asic_table_keys = {} per_npu_configdb = {} per_npu_statedb = {} mux_tbl_cfg_db = {} # Getting all front asic namespace and correspding config and state DB connector namespaces = multi_asic.get_front_end_namespaces() for namespace in namespaces: asic_id = multi_asic.get_asic_index_from_namespace(namespace) # replace these with correct macros per_npu_configdb[asic_id] = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace) per_npu_configdb[asic_id].connect() per_npu_statedb[asic_id] = swsscommon.SonicV2Connector(use_unix_socket_path=True, namespace=namespace) per_npu_statedb[asic_id].connect(per_npu_statedb[asic_id].STATE_DB) mux_tbl_cfg_db[asic_id] = per_npu_configdb[asic_id].get_table("MUX_CABLE") port_table_keys[asic_id] = per_npu_statedb[asic_id].keys( per_npu_statedb[asic_id].STATE_DB, 'MUX_CABLE_TABLE|*') if port is not None and port != "all": asic_index = None if platform_sfputil is not None: asic_index = platform_sfputil.get_asic_id_for_logical_port(port) if asic_index is None: # TODO this import is only for unit test purposes, and should be removed once sonic_platform_base # is fully mocked import sonic_platform_base.sonic_sfp.sfputilhelper asic_index = sonic_platform_base.sonic_sfp.sfputilhelper.SfpUtilHelper().get_asic_id_for_logical_port(port) if asic_index is None: click.echo("Got invalid asic index for port {}, cant retreive mux status".format(port)) sys.exit(CONFIG_FAIL) if per_npu_statedb[asic_index] is not None: y_cable_asic_table_keys = port_table_keys[asic_index] logical_key = "MUX_CABLE_TABLE"+"|"+port if logical_key in y_cable_asic_table_keys: port_status_dict = {} lookup_statedb_and_update_configdb( per_npu_statedb[asic_index], per_npu_configdb[asic_index], port, state, port_status_dict) if json_output: click.echo("{}".format(json.dumps(port_status_dict, indent=4))) else: headers = ['port', 'state'] data = sorted([(k, v) for k, v in port_status_dict.items()]) click.echo(tabulate(data, headers=headers)) sys.exit(CONFIG_SUCCESSFUL) else: click.echo("this is not a valid port present on mux_cable".format(port)) sys.exit(CONFIG_FAIL) else: click.echo("there is not a valid asic table for this asic_index".format(asic_index)) sys.exit(CONFIG_FAIL) elif port == "all" and port is not None: port_status_dict = {} for namespace in namespaces: asic_id = multi_asic.get_asic_index_from_namespace(namespace) for key in port_table_keys[asic_id]: logical_port = key.split("|")[1] lookup_statedb_and_update_configdb( per_npu_statedb[asic_id], per_npu_configdb[asic_id], logical_port, state, port_status_dict) if json_output: click.echo("{}".format(json.dumps(port_status_dict, indent=4))) else: data = sorted([(k, v) for k, v in port_status_dict.items()]) headers = ['port', 'state'] click.echo(tabulate(data, headers=headers)) sys.exit(CONFIG_SUCCESSFUL)
def _get_hwsku(): config_db = ConfigDBConnector() config_db.connect() metadata = config_db.get_table('DEVICE_METADATA') return metadata['localhost']['hwsku']
#!/usr/bin/python from sonic_py_common import device_info, logger from swsssdk import ConfigDBConnector import time configdb = ConfigDBConnector(**{}) configdb.db_connect('CONFIG_DB') def copy_profile_with_pool_replaced(profile, new_name, new_pool): profile['pool'] = '[BUFFER_POOL|{}]'.format(new_pool) configdb.set_entry('BUFFER_PROFILE', new_name, profile) def copy_profile_list_with_profile_replaced(table, pl, port, profile_list): pl['profile_list'] = profile_list configdb.set_entry(table, port, pl) # step 1: Create a new buffer pools for lossy and lossless: ingress_lossless_pool_new. # It can be copied from ingress_lossless_pool with size updated properly. ingress_pool = {'type': 'ingress', 'mode': 'dynamic', 'size': '7719936'} egress_lossy_pool = {'type': 'egress', 'mode': 'dynamic', 'size': '7719936'} configdb.set_entry('BUFFER_POOL', 'ingress_pool', ingress_pool) configdb.set_entry('BUFFER_POOL', 'egress_lossy_pool_new', egress_lossy_pool) # step 2: Create the following new buffer profiles based on the new ingress pool profiles = configdb.get_table('BUFFER_PROFILE') for name, profile in profiles.iteritems(): if name[0:12] == 'pg_lossless_' or name[0:12] == 'ingress_loss':
#!/usr/bin/python from sonic_py_common import device_info, logger from swsssdk import ConfigDBConnector import time import json import subprocess configdb = ConfigDBConnector(**{}) configdb.db_connect('CONFIG_DB') def copy_profile_with_pool_replaced(profile, new_name, new_pool): profile['pool'] = '[BUFFER_POOL|{}]'.format(new_pool) configdb.set_entry('BUFFER_PROFILE', new_name, profile) def copy_profile_list_with_profile_replaced(table, pl, port, profile_list): pl['profile_list'] = profile_list configdb.set_entry(table, port, pl) def update_buffer_pool_size(poolname, default_config): pool = configdb.get_entry('BUFFER_POOL', poolname) pool['size'] = buffers['BUFFER_POOL'][poolname]['size'] configdb.set_entry('BUFFER_POOL', poolname, pool) # step 0: preparation: get all the necessary info # fetch the meta data metadata = configdb.get_entry('DEVICE_METADATA', 'localhost')