Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions ansible/roles/test/files/ptftests/copp_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def __init__(self):
self.default_server_send_rate_limit_pps = test_params.get('send_rate_limit', 2000)

self.needPreSend = None
self.has_trap = test_params.get('has_trap', True)

def log(self, message, debug=False):
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
Expand Down Expand Up @@ -227,8 +228,12 @@ def check_constraints(self, send_count, recv_count, time_delta_ms, rx_pps):
(int(recv_count), int(pkt_rx_limit), str(recv_count > pkt_rx_limit))
)

assert(rx_pps > self.NO_POLICER_LIMIT)
assert(recv_count > pkt_rx_limit)
if self.has_trap:
assert (rx_pps > self.NO_POLICER_LIMIT)
assert (recv_count > pkt_rx_limit)
else:
assert (rx_pps < self.NO_POLICER_LIMIT)
assert (recv_count < pkt_rx_limit)


class PolicyTest(ControlPlaneBaseTest):
Expand Down
7 changes: 7 additions & 0 deletions tests/copp/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@ def pytest_addoption(parser):
default=2000,
help="Set custom server send rate limit",
)
parser.addoption(
"--copp_reboot_type",
action="store",
type=str,
default="cold",
help="reboot type such as cold, fast, warm, soft"
)
132 changes: 131 additions & 1 deletion tests/copp/copp_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"""
import re
import logging
import json

from tests.common.config_reload import config_reload

DEFAULT_NN_TARGET_PORT = 3

Expand All @@ -22,7 +25,7 @@
_COPP_TEMPLATE_PATH = "/usr/share/sonic/templates/copp.json.j2"
_SWSS_COPP_TEMPLATE = ":" + _COPP_TEMPLATE_PATH
_DEFAULT_COPP_TEMPLATE = "/usr/share/sonic/templates/copp_cfg.j2"
_BASE_COPP_TEMPLATE = "/tmp/copp_cfg_base.j2"
_BASE_COPP_TEMPLATE = "/home/admin/copp_cfg_base.j2"

_PTF_NN_TEMPLATE = "templates/ptf_nn_agent.conf.ptf.j2"
_PTF_NN_DEST = "/etc/supervisor/conf.d/ptf_nn_agent.conf"
Expand All @@ -31,6 +34,10 @@
_SYNCD_NN_DEST = "/tmp/ptf_nn_agent.conf"
_SYNCD_NN_FILE = "ptf_nn_agent.conf"

_CONFIG_DB = "/etc/sonic/config_db.json"
_TEMP_CONFIG_DB = "/home/admin/config_db_copp_backup.json"


def limit_policer(dut, pps_limit, nn_target_namespace):
"""
Updates the COPP configuration in the SWSS container to respect a given rate limit.
Expand Down Expand Up @@ -236,3 +243,126 @@ def _get_http_and_https_proxy_ip(creds):

return (re.findall(r'[0-9]+(?:\.[0-9]+){3}', creds.get('proxy_env', {}).get('http_proxy', ''))[0],
re.findall(r'[0-9]+(?:\.[0-9]+){3}', creds.get('proxy_env', {}).get('https_proxy', ''))[0])


def configure_always_enabled_for_trap(dut, trap_id, always_enabled):
"""
Configure the always_enabled to true or false for the specified trap id.
Args:
dut (SonicHost): The target device
trap_id (str): The trap id (e.g. bgp)
always_enabled (str): true or false
"""
copp_trap_config_json = "/tmp/copp_{}.json".format(trap_id)
cmd_copp_trap_always_enabled_config = """
cat << EOF > %s
{
"COPP_TRAP": {
"%s": {
"always_enabled": "%s"
}
}
}
EOF
""" % (copp_trap_config_json, trap_id, always_enabled)

dut.shell(cmd_copp_trap_always_enabled_config)
dut.command("sudo config load {} -y".format(copp_trap_config_json))


def get_config_db_json_obj(dut):
"""
Get config_db content from dut
Args:
dut (SonicHost): The target device
"""
config_db_json = dut.shell("sudo sonic-cfggen -d --print-data")["stdout"]
return json.loads(config_db_json)


def remove_feature_entry(dut, feature_name):
"""
Remove feature entry from dut
Args:
dut (SonicHost): The target device
feature_name (str): feature name (e.g bgp)
"""
dut.command('redis-cli -n 4 del "FEATURE|{}"'.format(feature_name))


def disable_feature_entry(dut, feature_name):
"""
Disable feature entry on dut
Args:
dut (SonicHost): The target device
feature_name (str): feature name (e.g bgp)
"""
dut.command(' sudo config feature state {} disabled'.format(feature_name))


def enable_feature_entry(dut, feature_name):
"""
Enabled feature entry dut
Args:
dut (SonicHost): The target device
feature_name (str): feature name (e.g bgp)
"""
dut.command(' sudo config feature state {} enabled'.format(feature_name))


def backup_config_db(dut):
"""
Backup config db to /home/admin/
Args:
dut (SonicHost): The target device
"""
dut.command("sudo cp {} {}".format(_CONFIG_DB, _TEMP_CONFIG_DB))


def restore_config_db(dut):
"""
Restore config db
Args:
dut (SonicHost): The target device
"""
dut.command("sudo cp {} {}".format(_TEMP_CONFIG_DB, _CONFIG_DB))
dut.command("sudo rm -f {}".format(_TEMP_CONFIG_DB))
config_reload(dut)


def uninstall_trap(dut, feature_name, trap_id):
"""
Uninstall trap by disabling feature and set always_enable to false

Args:
dut (SonicHost): The target device
feature_name (str): feature name corresponding to the trap
trap_id (str): trap id
"""
disable_feature_entry(dut, feature_name)
configure_always_enabled_for_trap(dut, trap_id, "false")


def verify_always_enable_value(dut, trap_id, always_enable_value):
"""
Verify the value of always_enable for the specified trap is expected one

Args:
dut (SonicHost): The target device
trap_id (str): trap id
always_enable_value (str): true or false
"""
config_db_json = get_config_db_json_obj(dut)
assert config_db_json["COPP_TRAP"][trap_id]["always_enabled"] == always_enable_value, \
"The value of always_enable not match. The expected value is:{}, the actual value is :{}".format(
always_enable_value, config_db_json["COPP_TRAP"][trap_id]["always_enabled"])


def install_trap(dut, feature_name):
"""
Install trap by enabling feature status
Args:
dut (SonicHost): The target device
feature_name (str): feature name
"""
enable_feature_entry(dut, feature_name)
137 changes: 135 additions & 2 deletions tests/copp/test_copp.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@
import logging
import pytest
import json
import random
from collections import namedtuple

from tests.copp import copp_utils
from tests.ptf_runner import ptf_runner
from tests.common import config_reload, constants
from tests.common.system_utils import docker
from tests.common.reboot import reboot
from tests.common.utilities import skip_release
from tests.common.utilities import wait_until
from tests.common.helpers.assertions import pytest_assert

# Module-level fixtures
from tests.common.fixtures.ptfhost_utils import copy_ptftests_directory # lgtm[py/unused-import]
Expand All @@ -55,11 +60,15 @@
_TOR_ONLY_PROTOCOL = ["DHCP"]
_TEST_RATE_LIMIT = 600

logger = logging.getLogger(__name__)


class TestCOPP(object):
"""
Tests basic COPP functionality in SONiC.
"""
trap_id = "bgp"
feature_name = "bgp"

@pytest.mark.parametrize("protocol", ["ARP",
"IP2ME",
Expand Down Expand Up @@ -98,6 +107,93 @@ def test_no_policer(self, protocol, duthosts, enum_rand_one_per_hwsku_frontend_h
copp_testbed,
dut_type)

def test_add_new_trap(self, duthosts, enum_rand_one_per_hwsku_frontend_hostname, ptfhost, check_image_version, copp_testbed, dut_type, backup_restore_config_db):
"""
Validates that one new trap(bgp) can be installed

1. The trap(bgp) should be uninstalled
2. Set always_enabled of bgp to true
3. Verify the trap status is installed by sending traffic
"""
duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname]

logger.info("Uninstall trap {}".format(self.trap_id))
copp_utils.uninstall_trap(duthost, self.feature_name, self.trap_id)

logger.info("Verify {} trap status is uninstalled by sending traffic".format(self.trap_id))
_copp_runner(duthost,
ptfhost,
self.trap_id.upper(),
copp_testbed,
dut_type,
has_trap=False)

logger.info("Set always_enabled of {} to true".format(self.trap_id))
copp_utils.configure_always_enabled_for_trap(duthost, self.trap_id, "true")

logger.info("Verify {} trap status is installed by sending traffic".format(self.trap_id))
pytest_assert(
wait_until(60, 20, 0, _copp_runner, duthost, ptfhost, self.trap_id.upper(), copp_testbed, dut_type),
"Installing {} trap fail".format(self.trap_id))

@pytest.mark.parametrize("remove_trap_type", ["delete_feature_entry",
"disable_feature_status"])
def test_remove_trap(self, duthosts, enum_rand_one_per_hwsku_frontend_hostname, ptfhost, check_image_version, copp_testbed, dut_type, backup_restore_config_db, remove_trap_type):
"""
Validates that The trap(bgp) can be uninstalled after deleting the corresponding entry from the feature table

1. Pre condition: make the tested trap installed and always_enable is false
2. Remove trap according to remove trap type
4. Verify the trap status is uninstalled by sending traffic
"""
duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname]

logger.info("Pre condition: make trap {} is installed".format(self.feature_name))
pre_condition_install_trap(ptfhost, duthost, copp_testbed, self.trap_id, self.feature_name)

if remove_trap_type == "delete_feature_entry":
logger.info("Remove feature entry: {}".format(self.feature_name))
copp_utils.remove_feature_entry(duthost, self.feature_name)
else:
logger.info("Disable {} in feature table".format(self.feature_name))
copp_utils.disable_feature_entry(duthost, self.feature_name)

logger.info("Verify {} trap status is uninstalled by sending traffic".format(self.trap_id))
pytest_assert(
wait_until(100, 20, 0, _copp_runner, duthost, ptfhost, self.trap_id.upper(), copp_testbed, dut_type, has_trap=False),
"uninstalling {} trap fail".format(self.trap_id))

def test_trap_config_save_after_reboot(self, duthosts, localhost, enum_rand_one_per_hwsku_frontend_hostname, ptfhost,check_image_version, copp_testbed, dut_type, backup_restore_config_db, request):
"""
Validates that the trap configuration is saved or not after reboot(reboot, fast-reboot, warm-reboot)

1. Set always_enabled of a trap(e.g. bgp) to true
2. Config save -y
3. Do reboot according to the specified parameter of copp_reboot_type (reboot/warm-reboot/fast-reboot/soft-reboot)
4. Verify configuration are saved successfully
5. Verify the trap status is installed by sending traffic
"""

duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname]

logger.info("Set always_enabled of {} to true".format(self.trap_id))
copp_utils.configure_always_enabled_for_trap(duthost, self.trap_id, "true")

logger.info("Config save")
duthost.command("sudo config save -y")

reboot_type = request.config.getoption("--copp_reboot_type")
logger.info("Do {}".format(reboot_type))
reboot(duthost, localhost, reboot_type=reboot_type, reboot_helper=None, reboot_kwargs=None)

logger.info("Verify always_enable of {} == {} in config_db".format(self.trap_id, "true"))
copp_utils.verify_always_enable_value(duthost, self.trap_id, "true")
logger.info("Verify {} trap status is installed by sending traffic".format(self.trap_id))
pytest_assert(
wait_until(100, 20, 0, _copp_runner, duthost, ptfhost, self.trap_id.upper(), copp_testbed, dut_type),
"Installing {} trap fail".format(self.trap_id))


@pytest.fixture(scope="class")
def dut_type(duthosts, enum_rand_one_per_hwsku_frontend_hostname):
duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname]
Expand Down Expand Up @@ -157,7 +253,8 @@ def ignore_expected_loganalyzer_exceptions(enum_rand_one_per_hwsku_frontend_host
if loganalyzer: # Skip if loganalyzer is disabled
loganalyzer[enum_rand_one_per_hwsku_frontend_hostname].ignore_regex.extend(ignoreRegex)

def _copp_runner(dut, ptf, protocol, test_params, dut_type):

def _copp_runner(dut, ptf, protocol, test_params, dut_type, has_trap=True):
"""
Configures and runs the PTF test cases.
"""
Expand All @@ -166,7 +263,8 @@ def _copp_runner(dut, ptf, protocol, test_params, dut_type):
"target_port": test_params.nn_target_port,
"myip": test_params.myip,
"peerip": test_params.peerip,
"send_rate_limit": test_params.send_rate_limit}
"send_rate_limit": test_params.send_rate_limit,
"has_trap": has_trap}

dut_ip = dut.mgmt_ip
device_sockets = ["0-{}@tcp://127.0.0.1:10900".format(test_params.nn_target_port),
Expand All @@ -186,6 +284,8 @@ def _copp_runner(dut, ptf, protocol, test_params, dut_type):
relax=None,
debug_level=None,
device_sockets=device_sockets)
return True


def _gather_test_params(tbinfo, duthost, request):
"""
Expand Down Expand Up @@ -331,3 +431,36 @@ def _teardown_multi_asic_proxy(dut, creds, test_params, tbinfo):
ns_ip = dut.shell("sudo ip -n {} -4 -o addr show eth0".format(test_params.nn_target_namespace) + " | awk '{print $4}' | cut -d'/' -f1")["stdout"]
dut.command("sudo iptables -t nat -D PREROUTING -p tcp --dport 10900 -j DNAT --to-destination {}".format(ns_ip))
dut.command("sudo ip -n {} rule delete from {} to {} pref 3 lookup default".format(test_params.nn_target_namespace, ns_ip, tbinfo["ptf_ip"]))


@pytest.fixture(scope="function", autouse=False)
def backup_restore_config_db(duthosts, enum_rand_one_per_hwsku_frontend_hostname):
duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname]
copp_utils.backup_config_db(duthost)

yield
copp_utils.restore_config_db(duthost)


def pre_condition_install_trap(ptfhost, duthost, copp_testbed, trap_id, feature_name):
copp_utils.install_trap(duthost, feature_name)
logger.info("Set always_enabled of {} to false".format(trap_id))
copp_utils.configure_always_enabled_for_trap(duthost, trap_id, "false")

logger.info("Verify {} trap status is installed by sending traffic in pre_condition".format(trap_id))
pytest_assert(
wait_until(100, 20, 0, _copp_runner, duthost, ptfhost, trap_id.upper(), copp_testbed, dut_type),
"Installing {} trap fail".format(trap_id))


@pytest.fixture(autouse=False, scope="class")
def check_image_version(duthosts, enum_rand_one_per_hwsku_frontend_hostname):
"""Skips this test because new copp management logic works on 202012 branch and above

Args:
duthost: Hostname of DUT.

Returns:
None.
"""
skip_release(duthosts[enum_rand_one_per_hwsku_frontend_hostname], ["201911"])