Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
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.")
Beispiel #6
0
def _get_hwsku():
    config_db = ConfigDBConnector()
    config_db.connect()
    metadata = config_db.get_table('DEVICE_METADATA')
    return metadata['localhost']['hwsku']
Beispiel #7
0
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()
Beispiel #8
0
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
Beispiel #9
0
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_2_0_0'

        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)

        self.stateDB = SonicV2Connector(host='127.0.0.1')
        if self.stateDB is not None:
            self.stateDB.connect(self.stateDB.STATE_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:
            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:
                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:
                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 migrate_copp_table(self):
        '''
        Delete the existing COPP table
        '''
        if self.appDB is None:
            return

        keys = self.appDB.keys(self.appDB.APPL_DB, "COPP_TABLE:*")
        if keys is None:
            return
        for copp_key in keys:
            self.appDB.delete(self.appDB.APPL_DB, copp_key)

    def migrate_config_db_buffer_tables_for_dynamic_calculation(
            self, speed_list, cable_len_list, default_dynamic_th,
            default_lossless_profiles, abandon_method, append_item_method):
        '''
        Migrate buffer tables to dynamic calculation mode
        parameters
        @speed_list - list of speed supported
        @cable_len_list - list of cable length supported
        @default_dynamic_th - default dynamic th
        @default_lossless_profiles - default lossless profiles from the previous image
        @abandon_method - a function which is called to abandon the migration and keep the current configuration
                          if the current one doesn't match the default one
        @append_item_method - a function which is called to append an item to the list of pending commit items
                              any update to buffer configuration will be pended and won't be applied until 
                              all configuration is checked and aligns with the default one

        1. Buffer profiles for lossless PGs in BUFFER_PROFILE table will be removed
           if their names have the convention of pg_lossless_<speed>_<cable_length>_profile
           where the speed and cable_length belongs speed_list and cable_len_list respectively
           and the dynamic_th is equal to default_dynamic_th
        2. Insert tables required for dynamic buffer calculation
           - DEFAULT_LOSSLESS_BUFFER_PARAMETER|AZURE: {'default_dynamic_th': default_dynamic_th}
           - LOSSLESS_TRAFFIC_PATTERN|AZURE: {'mtu': '1500', 'small_packet_percentage': '100'}
        3. For lossless dynamic PGs, remove the explicit referencing buffer profiles
           Before: BUFFER_PG|<port>|3-4: {'profile': 'BUFFER_PROFILE|pg_lossless_<speed>_<cable_length>_profile'}
           After:  BUFFER_PG|<port>|3-4: {'profile': 'NULL'}
        '''
        # Migrate BUFFER_PROFILEs, removing dynamically generated profiles
        dynamic_profile = self.configDB.get_table('BUFFER_PROFILE')
        profile_pattern = 'pg_lossless_([1-9][0-9]*000)_([1-9][0-9]*m)_profile'
        for name, info in dynamic_profile.items():
            m = re.search(profile_pattern, name)
            if not m:
                continue
            speed = m.group(1)
            cable_length = m.group(2)
            if speed in speed_list and cable_length in cable_len_list:
                log.log_info("current profile {} {}".format(name, info))
                log.log_info("default profile {} {}".format(
                    name, default_lossless_profiles.get(name)))
                default_profile = default_lossless_profiles.get(name)
                if info.get("xon") == default_profile.get("xon") and info.get(
                        "size") == default_profile.get("size") and info.get(
                            'dynamic_th') == default_dynamic_th:
                    append_item_method(('BUFFER_PROFILE', name, None))
                    log.log_info(
                        "Lossless profile {} has been removed".format(name))
                else:
                    log.log_notice(
                        "Lossless profile {} doesn't match the default configuration, keep using traditional buffer calculation mode"
                    )
                    abandon_method()
                    return True

        # Migrate BUFFER_PGs, removing the explicit designated profiles
        buffer_pgs = self.configDB.get_table('BUFFER_PG')
        ports = self.configDB.get_table('PORT')
        all_cable_lengths = self.configDB.get_table('CABLE_LENGTH')
        if not buffer_pgs or not ports or not all_cable_lengths:
            log.log_notice(
                "At lease one of tables BUFFER_PG, PORT and CABLE_LENGTH hasn't been defined, skip following migration"
            )
            abandon_method()
            return True

        cable_lengths = all_cable_lengths[list(all_cable_lengths.keys())[0]]
        for name, profile in buffer_pgs.items():
            # do the db migration
            port, pg = name
            if pg != '3-4':
                continue
            try:
                profile_name = profile['profile'][1:-1].split('|')[1]
                m = re.search(profile_pattern, profile_name)
            except Exception:
                continue
            if not m:
                continue
            speed = m.group(1)
            cable_length = m.group(2)
            try:
                if speed == ports[port][
                        'speed'] and cable_length == cable_lengths[port]:
                    append_item_method(('BUFFER_PG', name, {
                        'profile': 'NULL'
                    }))
                else:
                    log.log_notice(
                        "Lossless PG profile {} for port {} doesn't match its speed {} or cable length {}, keep using traditional buffer calculation mode"
                        .format(profile_name, port, speed, cable_length))
                    abandon_method()
                    return True
            except Exception:
                continue

        # Insert other tables required for dynamic buffer calculation
        metadata = self.configDB.get_entry('DEVICE_METADATA', 'localhost')
        metadata['buffer_model'] = 'dynamic'
        append_item_method(('DEVICE_METADATA', 'localhost', metadata))
        append_item_method(('DEFAULT_LOSSLESS_BUFFER_PARAMETER', 'AZURE', {
            'default_dynamic_th': default_dynamic_th
        }))
        append_item_method(('LOSSLESS_TRAFFIC_PATTERN', 'AZURE', {
            'mtu': '1500',
            'small_packet_percentage': '100'
        }))

        return True

    def prepare_dynamic_buffer_for_warm_reboot(self,
                                               buffer_pools=None,
                                               buffer_profiles=None,
                                               buffer_pgs=None):
        '''
        This is the very first warm reboot of buffermgrd (dynamic) if the system reboot from old image by warm-reboot
        In this case steps need to be taken to get buffermgrd prepared (for warm reboot)

        During warm reboot, buffer tables should be installed in the first place.
        However, it isn't able to achieve that when system is warm-rebooted from an old image
        without dynamic buffer supported, because the buffer info wasn't in the APPL_DB in the old image.
        The solution is to copy that info from CONFIG_DB into APPL_DB in db_migrator.
        During warm-reboot, db_migrator adjusts buffer info in CONFIG_DB by removing some fields
        according to requirement from dynamic buffer calculation.
        The buffer info before that adjustment needs to be copied to APPL_DB.

        1. set WARM_RESTART_TABLE|buffermgrd as {restore_count: 0}
        2. Copy the following tables from CONFIG_DB into APPL_DB in case of warm reboot
           The separator in fields that reference objects in other table needs to be updated from '|' to ':'
           - BUFFER_POOL
           - BUFFER_PROFILE, separator updated for field 'pool'
           - BUFFER_PG, separator updated for field 'profile'
           - BUFFER_QUEUE, separator updated for field 'profile
           - BUFFER_PORT_INGRESS_PROFILE_LIST, separator updated for field 'profile_list'
           - BUFFER_PORT_EGRESS_PROFILE_LIST, separator updated for field 'profile_list'

        '''
        warmreboot_state = self.stateDB.get(
            self.stateDB.STATE_DB, 'WARM_RESTART_ENABLE_TABLE|system',
            'enable')
        mmu_size = self.stateDB.get(self.stateDB.STATE_DB,
                                    'BUFFER_MAX_PARAM_TABLE|global',
                                    'mmu_size')
        if warmreboot_state == 'true' and not mmu_size:
            log.log_notice(
                "This is the very first run of buffermgrd (dynamic), prepare info required from warm reboot"
            )
        else:
            return True

        buffer_table_list = [
            ('BUFFER_POOL', buffer_pools, None),
            ('BUFFER_PROFILE', buffer_profiles, 'pool'),
            ('BUFFER_PG', buffer_pgs, 'profile'),
            ('BUFFER_QUEUE', None, 'profile'),
            ('BUFFER_PORT_INGRESS_PROFILE_LIST', None, 'profile_list'),
            ('BUFFER_PORT_EGRESS_PROFILE_LIST', None, 'profile_list')
        ]

        for pair in buffer_table_list:
            keys_copied = []
            keys_ignored = []
            table_name, entries, reference_field_name = pair
            app_table_name = table_name + "_TABLE"
            if not entries:
                entries = self.configDB.get_table(table_name)
            for key, items in entries.items():
                # copy items to appl db
                if reference_field_name:
                    confdb_ref = items.get(reference_field_name)
                    if not confdb_ref or confdb_ref == "NULL":
                        keys_ignored.append(key)
                        continue
                    items_referenced = confdb_ref.split(',')
                    appdb_ref = ""
                    first_item = True
                    for item in items_referenced:
                        if first_item:
                            first_item = False
                        else:
                            appdb_ref += ','
                        subitems = item.split('|')
                        first_key = True
                        for subitem in subitems:
                            if first_key:
                                appdb_ref += subitem + '_TABLE'
                                first_key = False
                            else:
                                appdb_ref += ':' + subitem

                    items[reference_field_name] = appdb_ref
                keys_copied.append(key)
                if type(key) is tuple:
                    appl_db_key = app_table_name + ':' + ':'.join(key)
                else:
                    appl_db_key = app_table_name + ':' + key
                for field, data in items.items():
                    self.appDB.set(self.appDB.APPL_DB, appl_db_key, field,
                                   data)

            if keys_copied:
                log.log_info(
                    "The following items in table {} in CONFIG_DB have been copied to APPL_DB: {}"
                    .format(table_name, keys_copied))
            if keys_ignored:
                log.log_info(
                    "The following items in table {} in CONFIG_DB have been ignored: {}"
                    .format(table_name, keys_copied))

        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.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') \
               and self.mellanox_buffer_migrator.mlnx_flush_new_buffer_configuration():
                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') \
               and self.mellanox_buffer_migrator.mlnx_flush_new_buffer_configuration():
                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')

        # Check ASIC type, if Mellanox platform then need DB migration
        if self.asic_type == "mellanox":
            speed_list = self.mellanox_buffer_migrator.default_speed_list
            cable_len_list = self.mellanox_buffer_migrator.default_cable_len_list
            buffer_pools = self.configDB.get_table('BUFFER_POOL')
            buffer_profiles = self.configDB.get_table('BUFFER_PROFILE')
            buffer_pgs = self.configDB.get_table('BUFFER_PG')
            default_lossless_profiles = self.mellanox_buffer_migrator.mlnx_get_default_lossless_profile(
                'version_1_0_4')
            abandon_method = self.mellanox_buffer_migrator.mlnx_abandon_pending_buffer_configuration
            append_method = self.mellanox_buffer_migrator.mlnx_append_item_on_pending_configuration_list

            if self.mellanox_buffer_migrator.mlnx_migrate_buffer_pool_size('version_1_0_4', 'version_2_0_0') \
               and self.mellanox_buffer_migrator.mlnx_migrate_buffer_profile('version_1_0_4', 'version_2_0_0') \
               and self.migrate_config_db_buffer_tables_for_dynamic_calculation(speed_list, cable_len_list, '0', default_lossless_profiles,
                                                                                abandon_method, append_method) \
               and self.mellanox_buffer_migrator.mlnx_flush_new_buffer_configuration() \
               and self.prepare_dynamic_buffer_for_warm_reboot(buffer_pools, buffer_profiles, buffer_pgs):
                metadata = self.configDB.get_entry('DEVICE_METADATA',
                                                   'localhost')
                if not metadata.get('buffer_model'):
                    metadata['buffer_model'] = 'traditional'
                    self.configDB.set_entry('DEVICE_METADATA', 'localhost',
                                            metadata)
                    log.log_notice('Setting buffer_model to traditional')
                else:
                    log.log_notice('Got buffer_model {}'.format(
                        metadata.get('buffer_model')))

                self.set_version('version_2_0_0')
        else:
            self.prepare_dynamic_buffer_for_warm_reboot()

            metadata = self.configDB.get_entry('DEVICE_METADATA', 'localhost')
            metadata['buffer_model'] = 'traditional'
            self.configDB.set_entry('DEVICE_METADATA', 'localhost', metadata)
            log.log_notice('Setting buffer_model to traditional')

            self.set_version('version_2_0_0')

        return 'version_2_0_0'

    def version_2_0_0(self):
        """
        Current latest version. Nothing to do here.
        """
        log.log_info('Handling version_2_0_0')

        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)

        self.migrate_copp_table()

    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()
Beispiel #10
0
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(unicode(
                                mod_key)).exploded == ipaddress.ip_address(
                                    unicode(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"))
import os
import sys

import yaml
from sonic_py_common.logger import Logger
from swsssdk import ConfigDBConnector

db = ConfigDBConnector()
db.connect()

SYSLOG_IDENTIFIER = 'snmp_yml_to_configdb.py'
logger = Logger(SYSLOG_IDENTIFIER)
logger.set_min_log_priority_info()

snmp_comm_config_db = db.get_table('SNMP_COMMUNITY')
snmp_config_db_communities = snmp_comm_config_db.keys()
snmp_general_config_db = db.get_table('SNMP')
snmp_general_keys = snmp_general_config_db.keys()

full_snmp_comm_list = [
    'snmp_rocommunity', 'snmp_rocommunities', 'snmp_rwcommunity',
    'snmp_rwcommunities'
]

if not os.path.exists('/etc/sonic/snmp.yml'):
    logger.log_info('/etc/sonic/snmp.yml does not exist')
    sys.exit(1)

with open('/etc/sonic/snmp.yml', 'r') as yaml_file:
    yaml_snmp_info = yaml.load(yaml_file, Loader=yaml.FullLoader)
Beispiel #12
0
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
            })
Beispiel #13
0
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 _get_hwsku():
    config_db = ConfigDBConnector()
    config_db.connect()
    metadata = config_db.get_table('DEVICE_METADATA')
    return metadata['localhost']['hwsku']
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()
Beispiel #16
0
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.appdb = SonicV2Connector(host="127.0.0.1")
        self.appdb.connect(self.appdb.APPL_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():
            app_db_info = self.appdb.get_all(
                self.appdb.APPL_DB, "{}:{}".format(self.MIRROR_SESSION, key))
            if app_db_info:
                status = app_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=""))
Beispiel #17
0
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
            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
            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()
            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])

        raw_data.sort(key=lambda x: x[0], reverse=True)

        data = []
        for _, d in raw_data:
            data += d

        print(tabulate.tabulate(data, headers=header, tablefmt="simple", missingval=""))
Beispiel #18
0
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 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