From 3f84ea9ffc717acf27c5d1ef96daeff7b60f889f Mon Sep 17 00:00:00 2001 From: Yutong Zhang Date: Tue, 22 Aug 2023 16:18:25 +0800 Subject: [PATCH] cherry pick #9523 and #9312 --- tests/cacl/test_cacl_function.py | 147 +++++++++--------- tests/common/templates/default_acl_rules.json | 38 +++++ tests/common/utilities.py | 39 +++++ tests/crm/conftest.py | 6 +- tests/crm/test_crm.py | 86 +++++----- 5 files changed, 203 insertions(+), 113 deletions(-) create mode 100644 tests/common/templates/default_acl_rules.json diff --git a/tests/cacl/test_cacl_function.py b/tests/cacl/test_cacl_function.py index d03e111ee7c..cff82543c08 100644 --- a/tests/cacl/test_cacl_function.py +++ b/tests/cacl/test_cacl_function.py @@ -2,6 +2,7 @@ import logging from tests.common.helpers.assertions import pytest_assert from tests.common.helpers.snmp_helpers import get_snmp_facts +from tests.common.utilities import get_data_acl, recover_acl_rule try: import ntplib @@ -25,6 +26,7 @@ def test_cacl_function(duthosts, rand_one_dut_hostname, localhost, creds): """Test control plane ACL functionality on a SONiC device""" duthost = duthosts[rand_one_dut_hostname] + data_acl = get_data_acl(duthost) dut_mgmt_ip = duthost.mgmt_ip # Start an NTP client @@ -48,76 +50,79 @@ def test_cacl_function(duthosts, rand_one_dut_hostname, localhost, creds): ntp_client.request(dut_mgmt_ip) except ntplib.NTPException: pytest.fail("NTP did timed out when expected to succeed!") - - # Copy config_service_acls.sh to the DuT (this also implicitly verifies we can successfully SSH to the DuT) - duthost.copy(src="scripts/config_service_acls.sh", dest="/tmp/config_service_acls.sh", mode="0755") - - # We run the config_service_acls.sh script in the background because it - # will install ACL rules which will only allow control plane traffic - # to an unused IP range. Thus, if it works properly, it will sever our - # SSH session, but we don't want the script itself to get killed, - # because it is also responsible for resetting the control plane ACLs - # back to their previous, working state - duthost.shell("nohup /tmp/config_service_acls.sh < /dev/null > /dev/null 2>&1 &") - - # Wait until we are unable to SSH into the DuT - res = localhost.wait_for(host=dut_mgmt_ip, - port=SONIC_SSH_PORT, - state='stopped', - search_regex=SONIC_SSH_REGEX, - delay=30, - timeout=40, - module_ignore_errors=True) - - pytest_assert(not res.is_failed, "SSH port did not stop. {}".format(res.get('msg', ''))) - - # Try to SSH back into the DuT, it should time out - res = localhost.wait_for(host=dut_mgmt_ip, - port=SONIC_SSH_PORT, - state='started', - search_regex=SONIC_SSH_REGEX, - delay=0, - timeout=10, - module_ignore_errors=True) - - pytest_assert(res.is_failed, "SSH did not timeout when expected. {}".format(res.get('msg', ''))) - - # Ensure we CANNOT gather basic SNMP facts from the device - res = get_snmp_facts(localhost, host=dut_mgmt_ip, version='v2c', community=creds['snmp_rocommunity'], - module_ignore_errors=True) - - pytest_assert('ansible_facts' not in res and "No SNMP response received before timeout" in res.get('msg', '')) - - # Ensure we cannot send an NTP request to the DUT - if NTPLIB_INSTALLED: - try: - ntp_client.request(dut_mgmt_ip) - pytest.fail("NTP did not time out when expected") - except ntplib.NTPException: - pass - - # Wait until the original service ACLs are reinstated and the SSH port on the - # DUT is open to us once again. Note that the timeout here should be set sufficiently - # long enough to allow config_service_acls.sh to reset the ACLs to their original - # configuration. - res = localhost.wait_for(host=dut_mgmt_ip, - port=SONIC_SSH_PORT, - state='started', - search_regex=SONIC_SSH_REGEX, - delay=0, - timeout=90, + try: + # Copy config_service_acls.sh to the DuT (this also implicitly verifies we can successfully SSH to the DuT) + duthost.copy(src="scripts/config_service_acls.sh", dest="/tmp/config_service_acls.sh", mode="0755") + + # We run the config_service_acls.sh script in the background because it + # will install ACL rules which will only allow control plane traffic + # to an unused IP range. Thus, if it works properly, it will sever our + # SSH session, but we don't want the script itself to get killed, + # because it is also responsible for resetting the control plane ACLs + # back to their previous, working state + duthost.shell("nohup /tmp/config_service_acls.sh < /dev/null > /dev/null 2>&1 &") + + # Wait until we are unable to SSH into the DuT + res = localhost.wait_for(host=dut_mgmt_ip, + port=SONIC_SSH_PORT, + state='stopped', + search_regex=SONIC_SSH_REGEX, + delay=30, + timeout=40, + module_ignore_errors=True) + + pytest_assert(not res.is_failed, "SSH port did not stop. {}".format(res.get('msg', ''))) + + # Try to SSH back into the DuT, it should time out + res = localhost.wait_for(host=dut_mgmt_ip, + port=SONIC_SSH_PORT, + state='started', + search_regex=SONIC_SSH_REGEX, + delay=0, + timeout=10, + module_ignore_errors=True) + + pytest_assert(res.is_failed, "SSH did not timeout when expected. {}".format(res.get('msg', ''))) + + # Ensure we CANNOT gather basic SNMP facts from the device + res = get_snmp_facts(localhost, host=dut_mgmt_ip, version='v2c', community=creds['snmp_rocommunity'], module_ignore_errors=True) - pytest_assert(not res.is_failed, "SSH did not start working when expected. {}".format(res.get('msg', ''))) - - # Delete config_service_acls.sh from the DuT - duthost.file(path="/tmp/config_service_acls.sh", state="absent") - - # Ensure we can gather basic SNMP facts from the device once again. Should fail on timeout - get_snmp_facts(localhost, - host=dut_mgmt_ip, - version="v2c", - community=creds['snmp_rocommunity'], - wait=True, - timeout = 20, - interval=20) + pytest_assert('ansible_facts' not in res and "No SNMP response received before timeout" in res.get('msg', '')) + + # Ensure we cannot send an NTP request to the DUT + if NTPLIB_INSTALLED: + try: + ntp_client.request(dut_mgmt_ip) + pytest.fail("NTP did not time out when expected") + except ntplib.NTPException: + pass + + # Wait until the original service ACLs are reinstated and the SSH port on the + # DUT is open to us once again. Note that the timeout here should be set sufficiently + # long enough to allow config_service_acls.sh to reset the ACLs to their original + # configuration. + res = localhost.wait_for(host=dut_mgmt_ip, + port=SONIC_SSH_PORT, + state='started', + search_regex=SONIC_SSH_REGEX, + delay=0, + timeout=90, + module_ignore_errors=True) + + pytest_assert(not res.is_failed, "SSH did not start working when expected. {}".format(res.get('msg', ''))) + + # Delete config_service_acls.sh from the DuT + duthost.file(path="/tmp/config_service_acls.sh", state="absent") + + # Ensure we can gather basic SNMP facts from the device once again. Should fail on timeout + get_snmp_facts(localhost, + host=dut_mgmt_ip, + version="v2c", + community=creds['snmp_rocommunity'], + wait=True, + timeout = 20, + interval=20) + finally: + if data_acl: + recover_acl_rule(duthost, data_acl) diff --git a/tests/common/templates/default_acl_rules.json b/tests/common/templates/default_acl_rules.json new file mode 100644 index 00000000000..e3de18c870c --- /dev/null +++ b/tests/common/templates/default_acl_rules.json @@ -0,0 +1,38 @@ +{ + "acl": { + "acl-sets": { + "acl-set": { + "dataacl": { + "acl-entries": { + "acl-entry": { + "1": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 1 + }, + "l2": { + "config": { + "ethertype": "2048", + "vlan_id": "1000" + } + }, + "input_interface": { + "interface_ref": + { + "config": { + "interface": "Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet4,Ethernet40,Ethernet44,Ethernet48,Ethernet52,Ethernet56,Ethernet60,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet8" + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/common/utilities.py b/tests/common/utilities.py index ca11a2328ec..e1be8f929ce 100644 --- a/tests/common/utilities.py +++ b/tests/common/utilities.py @@ -12,6 +12,9 @@ import time import traceback import json +import copy +import tempfile +import os from io import BytesIO import pytest @@ -638,3 +641,39 @@ def delete_running_config(config_entry, duthost, is_json=True): duthost.copy(src=config_entry, dest="/tmp/del_config_entry.json") duthost.shell("configlet -d -j {}".format("/tmp/del_config_entry.json")) duthost.shell("rm -f {}".format("/tmp/del_config_entry.json")) + +def get_data_acl(duthost): + acl_facts = duthost.acl_facts()["ansible_facts"]["ansible_acl_facts"] + pre_acl_rules = acl_facts.get("DATAACL", {}).get("rules", None) + return pre_acl_rules + + +def recover_acl_rule(duthost, data_acl): + base_dir = os.path.dirname(os.path.realpath(__file__)) + template_dir = os.path.join(base_dir, "templates") + acl_rules_template = "default_acl_rules.json" + dut_tmp_dir = "/tmp" + dut_conf_file_path = os.path.join(dut_tmp_dir, acl_rules_template) + + for key, value in data_acl.items(): + if key != "DEFAULT_RULE": + seq_id = key.split('_')[1] + acl_config = json.loads(open(os.path.join(template_dir, acl_rules_template)).read()) + acl_entry_template = \ + acl_config["acl"]["acl-sets"]["acl-set"]["dataacl"]["acl-entries"]["acl-entry"]["1"] + acl_entry_config = acl_config["acl"]["acl-sets"]["acl-set"]["dataacl"]["acl-entries"]["acl-entry"] + + acl_entry_config[seq_id] = copy.deepcopy(acl_entry_template) + acl_entry_config[seq_id]["config"]["sequence-id"] = seq_id + acl_entry_config[seq_id]["l2"]["config"]["ethertype"] = value["ETHER_TYPE"] + acl_entry_config[seq_id]["l2"]["config"]["vlan_id"] = value["VLAN_ID"] + acl_entry_config[seq_id]["input_interface"]["interface_ref"]["config"]["interface"] = value["IN_PORTS"] + + with tempfile.NamedTemporaryFile(suffix=".json", prefix="acl_config", mode="w") as fp: + json.dump(acl_config, fp) + fp.flush() + logger.info("Generating config for ACL rule, ACL table - DATAACL") + duthost.template(src=fp.name, dest=dut_conf_file_path, force=True) + + logger.info("Applying {}".format(dut_conf_file_path)) + duthost.command("acl-loader update full {}".format(dut_conf_file_path)) diff --git a/tests/crm/conftest.py b/tests/crm/conftest.py index 83014a63d33..b8ae8545f7a 100755 --- a/tests/crm/conftest.py +++ b/tests/crm/conftest.py @@ -6,6 +6,7 @@ from test_crm import RESTORE_CMDS, CRM_POLLING_INTERVAL from tests.common.errors import RunAnsibleModuleFail +from tests.common.utilities import recover_acl_rule logger = logging.getLogger(__name__) @@ -52,7 +53,10 @@ def pytest_runtest_teardown(item, nextitem): for cmd in RESTORE_CMDS[test_name]: logger.info(cmd) try: - dut.shell(cmd) + if isinstance(cmd, dict): + recover_acl_rule(dut, cmd["data_acl"]) + else: + dut.shell(cmd) except RunAnsibleModuleFail as err: failures.append("Failure during command execution '{command}':\n{error}".format(command=cmd, error=str(err))) diff --git a/tests/crm/test_crm.py b/tests/crm/test_crm.py index b39d99f9e56..7a0d409a5f0 100755 --- a/tests/crm/test_crm.py +++ b/tests/crm/test_crm.py @@ -16,6 +16,7 @@ from tests.common.fixtures.duthost_utils import disable_route_checker from tests.common.fixtures.duthost_utils import disable_fdb_aging from tests.common.utilities import wait_until +from tests.common.utilities import wait_until, get_data_acl pytestmark = [ @@ -851,62 +852,65 @@ def recover_acl_rule(duthosts, enum_rand_one_per_hwsku_frontend_hostname, enum_f duthost.command("acl-loader update full {}".format(dut_conf_file_path)) -def test_acl_entry(duthosts, enum_rand_one_per_hwsku_frontend_hostname, enum_frontend_asic_index, collector, recover_acl_rule): +def test_acl_entry(duthosts, enum_rand_one_per_hwsku_frontend_hostname, enum_frontend_asic_index, collector): duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname] + data_acl = get_data_acl(duthost) asichost = duthost.asic_instance(enum_frontend_asic_index) asic_collector = collector[asichost.asic_index] + try: + apply_acl_config(duthost, asichost, "test_acl_entry", asic_collector) + acl_tbl_key = asic_collector["acl_tbl_key"] + get_acl_entry_stats = "{db_cli} COUNTERS_DB HMGET {acl_tbl_key} \ + crm_stats_acl_entry_used \ + crm_stats_acl_entry_available"\ + .format(db_cli=asichost.sonic_db_cli, + acl_tbl_key=acl_tbl_key) - apply_acl_config(duthost, asichost, "test_acl_entry", asic_collector) - acl_tbl_key = asic_collector["acl_tbl_key"] - get_acl_entry_stats = "{db_cli} COUNTERS_DB HMGET {acl_tbl_key} \ - crm_stats_acl_entry_used \ - crm_stats_acl_entry_available"\ - .format(db_cli=asichost.sonic_db_cli, - acl_tbl_key=acl_tbl_key) + base_dir = os.path.dirname(os.path.realpath(__file__)) + template_dir = os.path.join(base_dir, "templates") + acl_rules_template = "acl.json" + dut_tmp_dir = "/tmp" - base_dir = os.path.dirname(os.path.realpath(__file__)) - template_dir = os.path.join(base_dir, "templates") - acl_rules_template = "acl.json" - dut_tmp_dir = "/tmp" + RESTORE_CMDS["crm_threshold_name"] = "acl_entry" - RESTORE_CMDS["crm_threshold_name"] = "acl_entry" + crm_stats_acl_entry_used = 0 + crm_stats_acl_entry_available = 0 - crm_stats_acl_entry_used = 0 - crm_stats_acl_entry_available = 0 + # Get new "crm_stats_acl_entry" used and available counter value + new_crm_stats_acl_entry_used, new_crm_stats_acl_entry_available = get_crm_stats(get_acl_entry_stats, duthost) - # Get new "crm_stats_acl_entry" used and available counter value - new_crm_stats_acl_entry_used, new_crm_stats_acl_entry_available = get_crm_stats(get_acl_entry_stats, duthost) + # Verify "crm_stats_acl_entry_used" counter was incremented + pytest_assert(new_crm_stats_acl_entry_used - crm_stats_acl_entry_used == 2, \ + "\"crm_stats_acl_entry_used\" counter was not incremented") - # Verify "crm_stats_acl_entry_used" counter was incremented - pytest_assert(new_crm_stats_acl_entry_used - crm_stats_acl_entry_used == 2, \ - "\"crm_stats_acl_entry_used\" counter was not incremented") + crm_stats_acl_entry_available = new_crm_stats_acl_entry_available + new_crm_stats_acl_entry_used - crm_stats_acl_entry_available = new_crm_stats_acl_entry_available + new_crm_stats_acl_entry_used + used_percent = get_used_percent(new_crm_stats_acl_entry_used, new_crm_stats_acl_entry_available) + if used_percent < 1: + # Preconfiguration needed for used percentage verification + nexthop_group_num = get_entries_num(new_crm_stats_acl_entry_used, new_crm_stats_acl_entry_available) - used_percent = get_used_percent(new_crm_stats_acl_entry_used, new_crm_stats_acl_entry_available) - if used_percent < 1: - # Preconfiguration needed for used percentage verification - nexthop_group_num = get_entries_num(new_crm_stats_acl_entry_used, new_crm_stats_acl_entry_available) + apply_acl_config(duthost, asichost, "test_acl_entry", asic_collector, nexthop_group_num) - apply_acl_config(duthost, asichost, "test_acl_entry", asic_collector, nexthop_group_num) + logger.info("Waiting {} seconds for SONiC to update resources...".format(SONIC_RES_UPDATE_TIME)) + # Make sure SONIC configure expected entries + time.sleep(SONIC_RES_UPDATE_TIME) - logger.info("Waiting {} seconds for SONiC to update resources...".format(SONIC_RES_UPDATE_TIME)) - # Make sure SONIC configure expected entries - time.sleep(SONIC_RES_UPDATE_TIME) - - # Verify thresholds for "ACL entry" CRM resource - verify_thresholds(duthost, asichost, crm_cli_res="acl group entry", crm_cmd=get_acl_entry_stats) + # Verify thresholds for "ACL entry" CRM resource + verify_thresholds(duthost, asichost, crm_cli_res="acl group entry", crm_cmd=get_acl_entry_stats) - # Remove ACL - duthost.command("acl-loader delete") - - crm_stats_checker = wait_until(30, 5, 0, check_crm_stats, get_acl_entry_stats, duthost, - crm_stats_acl_entry_used, - crm_stats_acl_entry_available) - pytest_assert(crm_stats_checker, - "\"crm_stats_acl_entry_used\" counter was not decremented or " - "\"crm_stats_acl_entry_available\" counter was not incremented") + # Remove ACL + duthost.command("acl-loader delete") + crm_stats_checker = wait_until(30, 5, 0, check_crm_stats, get_acl_entry_stats, duthost, + crm_stats_acl_entry_used, + crm_stats_acl_entry_available) + pytest_assert(crm_stats_checker, + "\"crm_stats_acl_entry_used\" counter was not decremented or " + "\"crm_stats_acl_entry_available\" counter was not incremented") + finally: + if data_acl: + RESTORE_CMDS["test_acl_entry"].append({"data_acl": data_acl}) def test_acl_counter(duthosts, enum_rand_one_per_hwsku_frontend_hostname,enum_frontend_asic_index, collector): duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname]