Repository: sonic-net/sonic-mgmt

Issue:
Need new test: System and Management - Access-control lists (ACL) on NTP protocol
<!--
If you are reporting a new issue, make sure that we do not have any duplicates
already open. You can ensure this by searching the issue list for this
repository. If there is a duplicate, please close your issue and add a comment
to the existing issue instead.

If you suspect your issue is a bug, please edit your issue description to
include the BUG REPORT INFORMATION shown below. If you fail to provide this
information within 7 days, we cannot debug your issue and will close it. We
will, however, reopen it if you later provide the information.

For more information about reporting issues, see
https://github.com/Azure/SONiC/wiki#report-issues

---------------------------------------------------
GENERAL SUPPORT INFORMATION
---------------------------------------------------

The GitHub issue tracker is for bug reports and feature requests.
General support can be found at the following locations:

- SONiC Support Forums - https://groups.google.com/forum/#!forum/sonicproject

---------------------------------------------------
BUG REPORT INFORMATION
---------------------------------------------------
Use the commands below to provide key information from your environment:
You do NOT have to include this information if this is a FEATURE REQUEST
-->

This is a test gap:

Need to 1. generate NTP acl table in minigraph. 2. generate acl.json
<!--
software/ASIC/Hardware Flatform version and info

    **Output of `show version`:**

    ```
    (paste your output here)
    ```

    **Attach debug file `sudo generate_dump`:**

    ```
    (paste your output here)
    ```
-->


Discussion:

Resolving patch:
diff --git a/ansible/library/snmp_facts.py b/ansible/library/snmp_facts.py
--- a/ansible/library/snmp_facts.py
+++ b/ansible/library/snmp_facts.py
@@ -298,6 +298,7 @@ def main():
     module = AnsibleModule(
         argument_spec=dict(
             host=dict(required=True),
+            timeout=dict(reqired=False, type='int', default=5),
             version=dict(required=True, choices=['v2', 'v2c', 'v3']),
             community=dict(required=False, default=False),
             username=dict(required=False),
@@ -367,7 +368,7 @@ def main():
     # (e.g. S6000) when cpu utilization is high, increse timeout to tolerate the delay.
     errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd(
         snmp_auth,
-        cmdgen.UdpTransportTarget((m_args['host'], 161), timeout=5.0),
+        cmdgen.UdpTransportTarget((m_args['host'], 161), timeout=m_args['timeout']),
         cmdgen.MibVariable(p.sysDescr,),
     )
 


Test patch:
diff --git a/ansible/roles/test/files/helpers/config_service_acls.sh b/ansible/roles/test/files/helpers/config_service_acls.sh
--- a/ansible/roles/test/files/helpers/config_service_acls.sh
+++ b/ansible/roles/test/files/helpers/config_service_acls.sh
@@ -65,6 +65,31 @@ cat << EOF > /tmp/testacl.json
                     "config": {
                         "name": "ssh-only"
                     }
+                },
+                "ntp-acl": {
+                    "acl-entries": {
+                        "acl-entry": {
+                            "1": {
+                                "actions": {
+                                    "config": {
+                                        "forwarding-action": "ACCEPT"
+                                    }
+                                },
+                                "config": {
+                                    "sequence-id": 1
+                                },
+                                "ip": {
+                                    "config": {
+                                        "protocol": "IP_TCP",
+                                        "source-ip-address": "1.1.1.1/32"
+                                    }
+                                }
+                            }
+                        }
+                    },
+                    "config": {
+                        "name": "ntp-acl"
+                    }
                 }
             }
         }
@@ -80,7 +105,7 @@ logger -t cacltest "added cacl test rules"
 iptables -nL | logger -t cacltest
 
 # Sleep to allow Ansible playbook ample time to attempt to connect and timeout
-sleep 60
+sleep 120
 
 # Delete the test ACL config file
 rm -rf /tmp/testacl.json
diff --git a/tests/cacl/test_cacl_function.py b/tests/cacl/test_cacl_function.py
--- a/tests/cacl/test_cacl_function.py
+++ b/tests/cacl/test_cacl_function.py
@@ -1,4 +1,13 @@
 import pytest
+import logging
+from tests.common.helpers.assertions import pytest_assert
+from tests.common.helpers.snmp_helpers import get_snmp_facts
+
+try:
+    import ntplib
+    NTPLIB_INSTALLED = True
+except ImportError:
+    NTPLIB_INSTALLED = False
 
 from tests.common.helpers.snmp_helpers import get_snmp_facts
 
@@ -13,16 +22,32 @@
 
 
 def test_cacl_function(duthosts, rand_one_dut_hostname, localhost, creds):
-    """Test control plane ACL functionality on a SONiC device
-    """
+    """Test control plane ACL functionality on a SONiC device"""
+
     duthost = duthosts[rand_one_dut_hostname]
     dut_mgmt_ip = duthost.mgmt_ip
 
-    # Ensure we can gather basic SNMP facts from the device
-    res = get_snmp_facts(localhost, host=dut_mgmt_ip, version='v2c', community=creds['snmp_rocommunity'])
-
-    if 'ansible_facts' not in res:
-        pytest.fail("Failed to retrieve SNMP facts from DuT!")
+    # Start an NTP client
+    if NTPLIB_INSTALLED:
+        ntp_client = ntplib.NTPClient()
+    else:
+        logging.warning("Will not check NTP connection. ntplib is not installed.")
+
+    # Ensure we can gather basic SNMP facts from the device. Should fail on timeout
+    get_snmp_facts(localhost, 
+                   host=dut_mgmt_ip, 
+                   version="v2c", 
+                   community=creds['snmp_rocommunity'], 
+                   wait=True, 
+                   timeout = 20, 
+                   interval=20)
+
+    # Ensure we can send an NTP request
+    if NTPLIB_INSTALLED:
+        try:
+            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")
@@ -40,12 +65,11 @@ def test_cacl_function(duthosts, rand_one_dut_hostname, localhost, creds):
                              port=SONIC_SSH_PORT,
                              state='stopped',
                              search_regex=SONIC_SSH_REGEX,
-                             delay=0,
-                             timeout=20,
+                             delay=30,
+                             timeout=40,
                              module_ignore_errors=True)
 
-    if res.is_failed:
-        pytest.fail("SSH port did not stop. {}".format(res.get('msg', '')))
+    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,
@@ -56,16 +80,21 @@ def test_cacl_function(duthosts, rand_one_dut_hostname, localhost, creds):
                              timeout=10,
                              module_ignore_errors=True)
 
-    if not res.is_failed:
-        pytest.fail("SSH did not timeout when expected. {}".format(res.get('msg', '')))
+    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)
 
-    if 'ansible_facts' in res or "No SNMP response received before timeout" not in res.get('msg', ''):
-        pytest.fail("SNMP did not time out when expected")
+    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
@@ -79,15 +108,16 @@ def test_cacl_function(duthosts, rand_one_dut_hostname, localhost, creds):
                              timeout=90,
                              module_ignore_errors=True)
 
-    if res.is_failed:
-        pytest.fail("SSH did not start working when expected. {}".format(res.get('msg', '')))
+    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
-    res = get_snmp_facts(localhost, host=dut_mgmt_ip, version='v2c', community=creds['snmp_rocommunity'],
-                         module_ignore_errors=True)
-
-    if 'ansible_facts' not in res:
-        pytest.fail("Failed to retrieve SNMP facts from DuT!")
+    # 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)
diff --git a/tests/common/helpers/snmp_helpers.py b/tests/common/helpers/snmp_helpers.py
--- a/tests/common/helpers/snmp_helpers.py
+++ b/tests/common/helpers/snmp_helpers.py
@@ -29,13 +29,13 @@ def _update_snmp_facts(localhost, host, version, community, is_dell):
 
 
 def get_snmp_facts(localhost, host, version, community, is_dell=False, module_ignore_errors=False,
-                   wait=False, timeouot=DEF_WAIT_TIMEOUT, interval=DEF_CHECK_INTERVAL):
+                   wait=False, timeout=DEF_WAIT_TIMEOUT, interval=DEF_CHECK_INTERVAL):
     if not wait:
         return _get_snmp_facts(localhost, host, version, community, is_dell, module_ignore_errors)
 
     global global_snmp_facts
 
-    pytest_assert(wait_until(timeouot, interval, _update_snmp_facts, localhost, host, version,
+    pytest_assert(wait_until(timeout, interval, _update_snmp_facts, localhost, host, version,
                              community, is_dell), "Timeout waiting for SNMP facts")
     return global_snmp_facts