mirror of
				https://github.com/Telecominfraproject/ols-nos.git
				synced 2025-11-04 03:57:57 +00:00 
			
		
		
		
	[dhcp-relay] make DHCP relay an extension (#6531)
- Why I did it Make DHCP relay docker an extension. DHCP relay now carries dhcp relay commands CLI plugin and has a complete manifest. It is installed as extension if INCLUDE_DHCP_REALY is set to y. DEPENDS on #5939 - How I did it Modify DHCP relay docker makefile and dockerfile. Make changes to sonic_debian_extension.j2 to install sonic packages. I moved DHCP related CLI tests from sonic-utilities to DHCP relay docker. This PR introduces a way to write a plugin as part of docker image and run the tests from cli-plugin-tests directory under docker directory. The test result is available in target/docker-dhcp-relay.gz.log: [ REASON ] : target/docker-dhcp-relay.gz does not exist NON-EXISTENT PREREQUISITES: docker-start target/docker-config-engine-buster.gz-load target/python-wheels/sonic_utilities-1.2-py3-none-any.whl-in stall target/debs/buster/python3-swsscommon_1.0.0_amd64.deb-install [ FLAGS FILE ] : [] [ FLAGS DEPENDS ] : [] [ FLAGS DIFF ] : [] ============================= test session starts ============================== platform linux -- Python 3.7.3, pytest-3.10.1, py-1.7.0, pluggy-0.8.0 -- /usr/bin/python3 cachedir: .pytest_cache rootdir: /sonic/dockers/docker-dhcp-relay/cli-plugin-tests, inifile: plugins: cov-2.6.0 collecting ... collected 10 items test_config_dhcp_relay.py::TestConfigVlanDhcpRelay::test_plugin_registration PASSED [ 10%] test_config_dhcp_relay.py::TestConfigVlanDhcpRelay::test_config_vlan_add_dhcp_relay_with_nonexist_vlanid PASSED [ 20%] test_config_dhcp_relay.py::TestConfigVlanDhcpRelay::test_config_vlan_add_dhcp_relay_with_invalid_vlanid PASSED [ 30%] test_config_dhcp_relay.py::TestConfigVlanDhcpRelay::test_config_vlan_add_dhcp_relay_with_invalid_ip PASSED [ 40%] test_config_dhcp_relay.py::TestConfigVlanDhcpRelay::test_config_vlan_add_dhcp_relay_with_exist_ip PASSED [ 50%] test_config_dhcp_relay.py::TestConfigVlanDhcpRelay::test_config_vlan_add_del_dhcp_relay_dest PASSED [ 60%] test_config_dhcp_relay.py::TestConfigVlanDhcpRelay::test_config_vlan_remove_nonexist_dhcp_relay_dest PASSED [ 70%] test_config_dhcp_relay.py::TestConfigVlanDhcpRelay::test_config_vlan_remove_dhcp_relay_dest_with_nonexist_vlanid PASSED [ 80%] test_show_dhcp_relay.py::TestVlanDhcpRelay::test_plugin_registration PASSED [ 90%] test_show_dhcp_relay.py::TestVlanDhcpRelay::test_dhcp_relay_column_output PASSED [100%] =============================== warnings summary =============================== /usr/local/lib/python3.7/dist-packages/tabulate.py:7 /usr/local/lib/python3.7/dist-packages/tabulate.py:7: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working from collections import namedtuple, Iterable -- Docs: https://docs.pytest.org/en/latest/warnings.html ==================== 10 passed, 1 warnings in 0.35 seconds =====================
This commit is contained in:
		
							
								
								
									
										154
									
								
								dockers/docker-dhcp-relay/cli-plugin-tests/mock_tables.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								dockers/docker-dhcp-relay/cli-plugin-tests/mock_tables.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,154 @@
 | 
			
		||||
# MONKEY PATCH!!!
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
from unittest import mock
 | 
			
		||||
 | 
			
		||||
import mockredis
 | 
			
		||||
import redis
 | 
			
		||||
import swsssdk
 | 
			
		||||
from sonic_py_common import multi_asic
 | 
			
		||||
from swsssdk import SonicDBConfig, SonicV2Connector, ConfigDBConnector, ConfigDBPipeConnector
 | 
			
		||||
from swsscommon import swsscommon
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
topo = None
 | 
			
		||||
dedicated_dbs = {}
 | 
			
		||||
 | 
			
		||||
def clean_up_config():
 | 
			
		||||
    # Set SonicDBConfig variables to initial state
 | 
			
		||||
    # so that it can be loaded with single or multiple
 | 
			
		||||
    # namespaces before the test begins.
 | 
			
		||||
    SonicDBConfig._sonic_db_config = {}
 | 
			
		||||
    SonicDBConfig._sonic_db_global_config_init = False
 | 
			
		||||
    SonicDBConfig._sonic_db_config_init = False
 | 
			
		||||
 | 
			
		||||
def load_namespace_config():
 | 
			
		||||
    # To support multi asic testing
 | 
			
		||||
    # SonicDBConfig load_sonic_global_db_config
 | 
			
		||||
    # is invoked to load multiple namespaces
 | 
			
		||||
    clean_up_config()
 | 
			
		||||
    SonicDBConfig.load_sonic_global_db_config(
 | 
			
		||||
        global_db_file_path=os.path.join(
 | 
			
		||||
            os.path.dirname(os.path.abspath(__file__)), 'database_global.json'))
 | 
			
		||||
 | 
			
		||||
def load_database_config():
 | 
			
		||||
    # Load local database_config.json for single namespace test scenario
 | 
			
		||||
    clean_up_config()
 | 
			
		||||
    SonicDBConfig.load_sonic_db_config(
 | 
			
		||||
        sonic_db_file_path=os.path.join(
 | 
			
		||||
            os.path.dirname(os.path.abspath(__file__)), 'database_config.json'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_old_connect_SonicV2Connector = SonicV2Connector.connect
 | 
			
		||||
 | 
			
		||||
def connect_SonicV2Connector(self, db_name, retry_on=True):
 | 
			
		||||
    # add topo to kwargs for testing different topology
 | 
			
		||||
    self.dbintf.redis_kwargs['topo'] = topo
 | 
			
		||||
    # add the namespace to kwargs for testing multi asic
 | 
			
		||||
    self.dbintf.redis_kwargs['namespace'] = self.namespace
 | 
			
		||||
    # Mock DB filename for unit-test
 | 
			
		||||
    global dedicated_dbs
 | 
			
		||||
    if dedicated_dbs and dedicated_dbs.get(db_name):
 | 
			
		||||
        self.dbintf.redis_kwargs['db_name'] = dedicated_dbs[db_name]
 | 
			
		||||
    else:
 | 
			
		||||
        self.dbintf.redis_kwargs['db_name'] = db_name
 | 
			
		||||
    self.dbintf.redis_kwargs['decode_responses'] = True
 | 
			
		||||
    _old_connect_SonicV2Connector(self, db_name, retry_on)
 | 
			
		||||
 | 
			
		||||
def _subscribe_keyspace_notification(self, db_name, client):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def config_set(self, *args):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MockPubSub:
 | 
			
		||||
    def get_message(self):
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def psubscribe(self, *args, **kwargs):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def __call__(self, *args, **kwargs):
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def listen(self):
 | 
			
		||||
        return []
 | 
			
		||||
 | 
			
		||||
    def punsubscribe(self, *args, **kwargs):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def clear(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
INPUT_DIR = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SwssSyncClient(mockredis.MockRedis):
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super(SwssSyncClient, self).__init__(strict=True, *args, **kwargs)
 | 
			
		||||
        # Namespace is added in kwargs specifically for unit-test
 | 
			
		||||
        # to identify the file path to load the db json files.
 | 
			
		||||
        topo = kwargs.pop('topo')
 | 
			
		||||
        namespace = kwargs.pop('namespace')
 | 
			
		||||
        db_name = kwargs.pop('db_name')
 | 
			
		||||
        self.decode_responses = kwargs.pop('decode_responses', False) == True
 | 
			
		||||
        fname = db_name.lower() + ".json"
 | 
			
		||||
        self.pubsub = MockPubSub()
 | 
			
		||||
 | 
			
		||||
        if namespace is not None and namespace is not multi_asic.DEFAULT_NAMESPACE:
 | 
			
		||||
            fname = os.path.join(INPUT_DIR, namespace, fname)
 | 
			
		||||
        elif topo is not None:
 | 
			
		||||
            fname = os.path.join(INPUT_DIR, topo, fname)
 | 
			
		||||
        else:
 | 
			
		||||
            fname = os.path.join(INPUT_DIR, fname)
 | 
			
		||||
 | 
			
		||||
        if os.path.exists(fname):
 | 
			
		||||
            with open(fname) as f:
 | 
			
		||||
                js = json.load(f)
 | 
			
		||||
                for k, v in js.items():
 | 
			
		||||
                    if 'expireat' in v and 'ttl' in v and 'type' in v and 'value' in v:
 | 
			
		||||
                        # database is in redis-dump format
 | 
			
		||||
                        if v['type'] == 'hash':
 | 
			
		||||
                            # ignore other types for now since sonic has hset keys only in the db
 | 
			
		||||
                            for attr, value in v['value'].items():
 | 
			
		||||
                                self.hset(k, attr, value)
 | 
			
		||||
                    else:
 | 
			
		||||
                        for attr, value in v.items():
 | 
			
		||||
                            self.hset(k, attr, value)
 | 
			
		||||
 | 
			
		||||
    # Patch mockredis/mockredis/client.py
 | 
			
		||||
    # The offical implementation assume decode_responses=False
 | 
			
		||||
    # Here we detect the option and decode after doing encode
 | 
			
		||||
    def _encode(self, value):
 | 
			
		||||
        "Return a bytestring representation of the value. Taken from redis-py connection.py"
 | 
			
		||||
 | 
			
		||||
        value = super(SwssSyncClient, self)._encode(value)
 | 
			
		||||
 | 
			
		||||
        if self.decode_responses:
 | 
			
		||||
           return value.decode('utf-8')
 | 
			
		||||
 | 
			
		||||
    # Patch mockredis/mockredis/client.py
 | 
			
		||||
    # The official implementation will filter out keys with a slash '/'
 | 
			
		||||
    # ref: https://github.com/locationlabs/mockredis/blob/master/mockredis/client.py
 | 
			
		||||
    def keys(self, pattern='*'):
 | 
			
		||||
        """Emulate keys."""
 | 
			
		||||
        import fnmatch
 | 
			
		||||
        import re
 | 
			
		||||
 | 
			
		||||
        # Make regex out of glob styled pattern.
 | 
			
		||||
        regex = fnmatch.translate(pattern)
 | 
			
		||||
        regex = re.compile(regex)
 | 
			
		||||
 | 
			
		||||
        # Find every key that matches the pattern
 | 
			
		||||
        return [key for key in self.redis if regex.match(key)]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
swsssdk.interface.DBInterface._subscribe_keyspace_notification = _subscribe_keyspace_notification
 | 
			
		||||
mockredis.MockRedis.config_set = config_set
 | 
			
		||||
redis.StrictRedis = SwssSyncClient
 | 
			
		||||
SonicV2Connector.connect = connect_SonicV2Connector
 | 
			
		||||
swsscommon.SonicV2Connector = SonicV2Connector
 | 
			
		||||
swsscommon.ConfigDBConnector = ConfigDBConnector
 | 
			
		||||
swsscommon.ConfigDBPipeConnector = ConfigDBPipeConnector
 | 
			
		||||
		Reference in New Issue
	
	Block a user