diff --git a/ansible/roles/test/tasks/acl/acltb/acltb_config.yml b/ansible/roles/test/tasks/acl/acltb/acltb_config.yml deleted file mode 100644 index 3d60bde049c..00000000000 --- a/ansible/roles/test/tasks/acl/acltb/acltb_config.yml +++ /dev/null @@ -1,80 +0,0 @@ -- name: Init variables - set_fact: - tor_ports: [] - tor_ports_id: [] - spine_ports: [] - spine_ports_id: [] - portchannel_ports: [] - all_ports: "{{ minigraph_ports }}" - router_mac: "{{ ansible_interface_facts['Ethernet0']['macaddress'] }}" - dst_ip_addr_spine: 192.168.0.0 - dst_ip_addr_spine_forwarded: 192.168.0.16 - dst_ip_addr_spine_blocked: 192.168.0.17 - dst_ip_addr_tor: 172.16.1.0 - dst_ip_addr_tor_forwarded: 172.16.2.0 - dst_ip_addr_tor_blocked: 172.16.3.0 - -- name: Get the list of TOR ports - set_fact: - tor_ports: "{% for neigh in minigraph_neighbors.items() %}{% if 'T0' in neigh[1].name %}{{ neigh[0] }},{% endif %}{% endfor %}" -- set_fact: - tor_ports: "{{ tor_ports.split(',')[:-1]}}" - -- name: Get tor ports id - set_fact: - tor_ports_id: "{% for port_name in tor_ports %}{{ minigraph_port_indices[port_name] }},{% endfor %}" -- set_fact: - tor_ports_id: "{{ tor_ports_id.split(',')[:-1]}}" - -- name: Get the list of SPINE ports - set_fact: - spine_ports: "{% for neigh in minigraph_neighbors.items() %}{% if 'T2' in neigh[1].name %}{{ neigh[0] }},{% endif %}{% endfor %}" -- set_fact: - spine_ports: "{{ spine_ports.split(',')[:-1]}}" - -- name: Get spine ports id - set_fact: - spine_ports_id: "{% for port_name in spine_ports %}{{ minigraph_port_indices[port_name] }},{% endfor %}" -- set_fact: - spine_ports_id: "{{ spine_ports_id.split(',')[:-1]}}" - -- name: Get the list of portchannel ports - set_fact: - portchannel_ports: "{% for portchannel in minigraph_portchannels.items() %}{{ portchannel[0] }},{% endfor %}" -- set_fact: - portchannel_ports: "{{ portchannel_ports.split(',')[:-1]}}" - -- name: Get the list of ports to be combined to ACL tables - set_fact: - acl_table_ports: "{{ (testbed_type in ['t1-lag', 't1-64-lag']) | ternary(portchannel_ports + tor_ports, spine_ports + tor_ports) }}" - -- name: Initialize data of ACL tables - set_fact: - acl_tables: "{{ [{'name': 'DATAINGRESS', 'ports': acl_table_ports, 'stage': 'ingress', 'type': 'L3'}] }}" - -- name: Initialize data of ACL tables, egress - set_fact: - acl_tables: "{{ acl_tables + [{'name': 'DATAEGRESS', 'ports': acl_table_ports, 'stage': 'egress', 'type': 'L3'}] }}" - when: run_egress is defined and run_egress|bool == true - -- name: Create dut_tmp_dir on DUT - file: - path: "{{ dut_tmp_dir }}" - state: directory - -- name: Generate config files for acl tables and rules - include: "roles/test/tasks/acl/acltb/acltb_config_generate.yml" - with_items: acl_tables - -- name: copy acsbase files - copy: src=roles/test/files/acstests - dest=/root - delegate_to: "{{ ptf_host }}" - -- name: copy ptftests - copy: src=roles/test/files/ptftests - dest=/root - delegate_to: "{{ ptf_host }}" - -- name: Copy empty ACL config file to the DUT - copy: src="roles/test/tasks/acl/acltb_test_rules-del.json" dest="{{ dut_tmp_dir }}" diff --git a/ansible/roles/test/tasks/acl/acltb/acltb_config_generate.yml b/ansible/roles/test/tasks/acl/acltb/acltb_config_generate.yml deleted file mode 100644 index c93fe3eb18e..00000000000 --- a/ansible/roles/test/tasks/acl/acltb/acltb_config_generate.yml +++ /dev/null @@ -1,26 +0,0 @@ -- name: Init variables - set_fact: - acl_table_name: "{{ item['name'] }}" - acl_table_ports: "{{ item['ports'] }}" - acl_table_stage: "{{ item['stage'] }}" - acl_table_type: "{{ item['type'] }}" - -- name: Generate config for ACL table - template: - src: "roles/test/templates/acltb_table.j2" - dest: "{{ dut_tmp_dir }}/acl_table_{{ acl_table_name }}.json" - -- name: Generate config for ACL rule - template: - src: "roles/test/templates/acltb_test_rules.j2" - dest: "{{ dut_tmp_dir }}/acl_rules_{{ acl_table_name }}.json" - -- name: Generate config for ACL rule part1 - template: - src: "roles/test/templates/acltb_test_rules_part_1.j2" - dest: "{{ dut_tmp_dir }}/acl_rules_{{ acl_table_name }}_part_1.json" - -- name: Generate config for ACL rule part2 - template: - src: "roles/test/templates/acltb_test_rules_part_2.j2" - dest: "{{ dut_tmp_dir }}/acl_rules_{{ acl_table_name }}_part_2.json" diff --git a/ansible/roles/test/tasks/acl/acltb/acltb_run_test.yml b/ansible/roles/test/tasks/acl/acltb/acltb_run_test.yml deleted file mode 100644 index 49b5975cb87..00000000000 --- a/ansible/roles/test/tasks/acl/acltb/acltb_run_test.yml +++ /dev/null @@ -1,28 +0,0 @@ - -- set_fact: - test_basic: true - when: test_basic is not defined - -- set_fact: - acl_table_name: DATAINGRESS - when: acl_stage == 'ingress' - -- set_fact: - acl_table_name: DATAEGRESS - when: acl_stage == 'egress' - -- set_fact: - acl_rules_skip_counters_check: ['RULE_8', 'RULE_21', 'RULE_27', 'RULE_28', 'DEFAULT_RULE'] - -- name: Run sonic-cfggen to add ACL table - command: sonic-cfggen -j {{ dut_tmp_dir }}/acl_table_{{ acl_table_name }}.json --write-to-db - become: yes - -- include: "roles/test/tasks/acl/acltb/acltb_test_basic.yml" - when: test_basic is defined and test_basic|bool == true - -- include: "roles/test/tasks/acl/acltb/acltb_test_port_toggle.yml" - when: test_port_toggle is defined and test_port_toggle|bool == true - -- include: "roles/test/tasks/acl/acltb/acltb_test_reboot.yml" - when: test_reboot is defined and test_reboot|bool == true diff --git a/ansible/roles/test/tasks/acl/acltb/acltb_test_basic.yml b/ansible/roles/test/tasks/acl/acltb/acltb_test_basic.yml deleted file mode 100644 index d8cb2d7b7be..00000000000 --- a/ansible/roles/test/tasks/acl/acltb/acltb_test_basic.yml +++ /dev/null @@ -1,127 +0,0 @@ -- include: roles/test/files/tools/loganalyzer/loganalyzer_init.yml - -- name: Run config acl to add ACL rules - command: config acl update full {{ dut_tmp_dir }}/acl_rules_{{ acl_table_name }}.json - become: yes - -- name: Pause a moment for settings to take effect - pause: seconds=10 - -- name: Gather acl_facts - acl_facts: - -- name: Store ACL facts before test - set_fact: - acl_rules_facts_before: "{{ ansible_acl_facts[acl_table_name]['rules'] }}" - -- name: Run the test - include: roles/test/tasks/ptf_runner.yml - vars: - ptf_test_name: ACL Test - ptf_test_dir: acstests - ptf_test_path: acltb_test.AclTest - ptf_platform_dir: ptftests - ptf_platform: remote - ptf_test_params: - - router_mac='{{ router_mac }}' - - testbed_type='{{ testbed_type }}' - - tor_ports='{{ tor_ports_id | join(',') }}' - - spine_ports='{{ spine_ports_id | join(',') }}' - - dst_ip_tor='{{ dst_ip_addr_tor }}' - - dst_ip_tor_forwarded='{{ dst_ip_addr_tor_forwarded }}' - - dst_ip_tor_blocked='{{ dst_ip_addr_tor_blocked }}' - - dst_ip_spine='{{ dst_ip_addr_spine }}' - - dst_ip_spine_forwarded='{{ dst_ip_addr_spine_forwarded }}' - - dst_ip_spine_blocked='{{ dst_ip_addr_spine_blocked }}' - ptf_extra_options: "--log-file /tmp/acltb_test.AclTest.{{ acl_stage }}.{{lookup('pipe','date +%Y-%m-%d-%H:%M:%S')}}.log" - -- name: Pause a moment for counters to refersh - pause: seconds=10 - -- name: Gather acl_facts - acl_facts: - -- name: Store ACL rules facts after test - set_fact: - acl_rules_facts_after: "{{ ansible_acl_facts[acl_table_name]['rules'] }}" - -- name: Check ACL rule counters - assert: - that: - - "{{ acl_rules_facts_after[item.key]['packets_count'] }} > {{ acl_rules_facts_before[item.key]['packets_count'] }}" - - "{{ acl_rules_facts_after[item.key]['bytes_count'] }} > {{ acl_rules_facts_before[item.key]['bytes_count'] }}" - with_dict: acl_rules_facts_after - when: item.key not in acl_rules_skip_counters_check - -- include: roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml -- include: roles/test/files/tools/loganalyzer/loganalyzer_end.yml - -- include: roles/test/files/tools/loganalyzer/loganalyzer_init.yml - -- name: Remove acl rules - command: config acl update full {{ dut_tmp_dir }}/acltb_test_rules-del.json - become: yes - -- name: Run 'config acl update incremental' to add ACL rules for testing, part_1 - command: config acl update incremental {{ dut_tmp_dir }}/acl_rules_{{ acl_table_name }}_part_1.json - become: yes - -- name: Run 'config acl update incremental' to add ACL rules for testing, part_2 - command: config acl update incremental {{ dut_tmp_dir }}/acl_rules_{{ acl_table_name }}_part_2.json - become: yes - -- name: Pause a moment for settings to take effect - pause: seconds=10 - -- name: Gather acl_facts - acl_facts: - -- name: Store ACL facts before test - set_fact: - acl_rules_facts_before: "{{ ansible_acl_facts[acl_table_name]['rules'] }}" - -- name: Run the test - include: roles/test/tasks/ptf_runner.yml - vars: - ptf_test_name: ACL Test - ptf_test_dir: acstests - ptf_test_path: acltb_test.AclTest - ptf_platform_dir: ptftests - ptf_platform: remote - ptf_test_params: - - router_mac='{{ router_mac }}' - - testbed_type='{{ testbed_type }}' - - tor_ports='{{ tor_ports_id | join(',') }}' - - spine_ports='{{ spine_ports_id | join(',') }}' - - dst_ip_tor='{{ dst_ip_addr_tor }}' - - dst_ip_tor_forwarded='{{ dst_ip_addr_tor_forwarded }}' - - dst_ip_tor_blocked='{{ dst_ip_addr_tor_blocked }}' - - dst_ip_spine='{{ dst_ip_addr_spine }}' - - dst_ip_spine_forwarded='{{ dst_ip_addr_spine_forwarded }}' - - dst_ip_spine_blocked='{{ dst_ip_addr_spine_blocked }}' - ptf_extra_options: "--log-file /tmp/acltb_test.AclTest.{{ acl_stage }}.{{lookup('pipe','date +%Y-%m-%d-%H:%M:%S')}}.log" - -- name: Pause a moment for counters to refresh - pause: seconds=10 - -- name: Gather acl_facts - acl_facts: - -- name: Store ACL rules facts after test - set_fact: - acl_rules_facts_after: "{{ ansible_acl_facts[acl_table_name]['rules'] }}" - -- name: Check ACL rule counters - assert: - that: - - "{{ acl_rules_facts_after[item.key]['packets_count'] }} > {{ acl_rules_facts_before[item.key]['packets_count'] }}" - - "{{ acl_rules_facts_after[item.key]['bytes_count'] }} > {{ acl_rules_facts_before[item.key]['bytes_count'] }}" - with_dict: acl_rules_facts_after - when: item.key not in acl_rules_skip_counters_check - -- name: Remove ACL rules from config - command: config acl update full {{ dut_tmp_dir }}/acltb_test_rules-del.json - become: yes - -- include: roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml -- include: roles/test/files/tools/loganalyzer/loganalyzer_end.yml diff --git a/ansible/roles/test/tasks/acl/acltb/acltb_test_port_toggle.yml b/ansible/roles/test/tasks/acl/acltb/acltb_test_port_toggle.yml deleted file mode 100644 index 4f27a788a94..00000000000 --- a/ansible/roles/test/tasks/acl/acltb/acltb_test_port_toggle.yml +++ /dev/null @@ -1,69 +0,0 @@ -- name: Remove acl rules - command: config acl update full {{ dut_tmp_dir }}/acltb_test_rules-del.json - become: yes - -- name: Run config acl to add ACL rules - command: config acl update full {{ dut_tmp_dir }}/acl_rules_{{ acl_table_name }}.json - become: yes - -# Toggle all interfaces and test again -- name: Toggle interfaces up/down - include: roles/test/tasks/port_toggle.yml - -- name: Pause a moment for interfaces to be stable - pause: seconds=120 - -- include: roles/test/files/tools/loganalyzer/loganalyzer_init.yml - -- name: Gather acl_facts - acl_facts: - -- name: Store ACL facts before test - set_fact: - acl_rules_facts_before: "{{ ansible_acl_facts[acl_table_name]['rules'] }}" - -- name: Run the test - include: roles/test/tasks/ptf_runner.yml - vars: - ptf_test_name: ACL Test - ptf_test_dir: acstests - ptf_test_path: acltb_test.AclTest - ptf_platform_dir: ptftests - ptf_platform: remote - ptf_test_params: - - router_mac='{{ router_mac }}' - - testbed_type='{{ testbed_type }}' - - tor_ports='{{ tor_ports_id | join(',') }}' - - spine_ports='{{ spine_ports_id | join(',') }}' - - dst_ip_tor='{{ dst_ip_addr_tor }}' - - dst_ip_tor_forwarded='{{ dst_ip_addr_tor_forwarded }}' - - dst_ip_tor_blocked='{{ dst_ip_addr_tor_blocked }}' - - dst_ip_spine='{{ dst_ip_addr_spine }}' - - dst_ip_spine_forwarded='{{ dst_ip_addr_spine_forwarded }}' - - dst_ip_spine_blocked='{{ dst_ip_addr_spine_blocked }}' - ptf_extra_options: "--log-file /tmp/acltb_test.AclTest.{{ acl_stage }}.{{lookup('pipe','date +%Y-%m-%d-%H:%M:%S')}}.log" - -- name: Pause a moment for counters to refresh - pause: seconds=10 - -- name: Gather acl_facts - acl_facts: - -- name: Store ACL rules facts after test - set_fact: - acl_rules_facts_after: "{{ ansible_acl_facts[acl_table_name]['rules'] }}" - -- name: Check ACL rule counters - assert: - that: - - "{{ acl_rules_facts_after[item.key]['packets_count'] }} > {{ acl_rules_facts_before[item.key]['packets_count'] }}" - - "{{ acl_rules_facts_after[item.key]['bytes_count'] }} > {{ acl_rules_facts_before[item.key]['bytes_count'] }}" - with_dict: acl_rules_facts_after - when: item.key not in acl_rules_skip_counters_check - -- name: Remove ACL rules from config - command: config acl update full {{ dut_tmp_dir }}/acltb_test_rules-del.json - become: yes - -- include: roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml -- include: roles/test/files/tools/loganalyzer/loganalyzer_end.yml diff --git a/ansible/roles/test/tasks/acl/acltb/acltb_test_reboot.yml b/ansible/roles/test/tasks/acl/acltb/acltb_test_reboot.yml deleted file mode 100644 index 5dd1e789a9a..00000000000 --- a/ansible/roles/test/tasks/acl/acltb/acltb_test_reboot.yml +++ /dev/null @@ -1,73 +0,0 @@ -- name: Remove acl rules - command: config acl update full {{ dut_tmp_dir }}/acltb_test_rules-del.json - become: yes - -- name: Run config acl to add ACL rules - command: config acl update full {{ dut_tmp_dir }}/acl_rules_{{ acl_table_name }}.json - become: yes - -# Verify that the ACL configurations persist after reboot -- name: Save the applied ACL in ConfigDB - command: config save -y - become: true - -- name: Reboot the switch - include: roles/test/tasks/common_tasks/reboot_sonic.yml - -- name: Wait 3 minutes for everything to be ready - pause: seconds=180 - -- include: roles/test/files/tools/loganalyzer/loganalyzer_init.yml - -- name: Gather acl_facts - acl_facts: - -- name: Store ACL facts before test - set_fact: - acl_rules_facts_before: "{{ ansible_acl_facts[acl_table_name]['rules'] }}" - -- name: Run the test - include: roles/test/tasks/ptf_runner.yml - vars: - ptf_test_name: ACL Test - ptf_test_dir: acstests - ptf_test_path: acltb_test.AclTest - ptf_platform_dir: ptftests - ptf_platform: remote - ptf_test_params: - - router_mac='{{ router_mac }}' - - testbed_type='{{ testbed_type }}' - - tor_ports='{{ tor_ports_id | join(',') }}' - - spine_ports='{{ spine_ports_id | join(',') }}' - - dst_ip_tor='{{ dst_ip_addr_tor }}' - - dst_ip_tor_forwarded='{{ dst_ip_addr_tor_forwarded }}' - - dst_ip_tor_blocked='{{ dst_ip_addr_tor_blocked }}' - - dst_ip_spine='{{ dst_ip_addr_spine }}' - - dst_ip_spine_forwarded='{{ dst_ip_addr_spine_forwarded }}' - - dst_ip_spine_blocked='{{ dst_ip_addr_spine_blocked }}' - ptf_extra_options: "--log-file /tmp/acltb_test.AclTest.{{ acl_stage }}.{{lookup('pipe','date +%Y-%m-%d-%H:%M:%S')}}.log" - -- name: Pause a moment for counters to refresh - pause: seconds=10 - -- name: Gather acl_facts - acl_facts: - -- name: Store ACL rules facts after test - set_fact: - acl_rules_facts_after: "{{ ansible_acl_facts[acl_table_name]['rules'] }}" - -- name: Check ACL rule counters - assert: - that: - - "{{ acl_rules_facts_after[item.key]['packets_count'] }} > {{ acl_rules_facts_before[item.key]['packets_count'] }}" - - "{{ acl_rules_facts_after[item.key]['bytes_count'] }} > {{ acl_rules_facts_before[item.key]['bytes_count'] }}" - with_dict: acl_rules_facts_after - when: item.key not in acl_rules_skip_counters_check - -- name: Remove ACL rules from config - command: config acl update full {{ dut_tmp_dir }}/acltb_test_rules-del.json - become: yes - -- include: roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml -- include: roles/test/files/tools/loganalyzer/loganalyzer_end.yml \ No newline at end of file diff --git a/ansible/roles/test/tasks/acltb.yml b/ansible/roles/test/tasks/acltb.yml index b8f8bb77364..c33f8534dc8 100644 --- a/ansible/roles/test/tasks/acltb.yml +++ b/ansible/roles/test/tasks/acltb.yml @@ -39,73 +39,62 @@ # -e testcase_name=acl -e run_egress=yes -e test_port_toggle=yes -e test_reboot=yes -vvvv # This command will run everything, including ingress&egress, and all the 3 scenarios. -# Set facts for the loganalizer -- set_fact: - out_dir: /tmp/loganalizer - testname: acl - run_dir: /tmp - tests_location: "roles/test/tasks" - test_match_file: "acltb_match_messages.txt" - test_ignore_file: "acltb_ignore_messages.txt" - test_expect_file: "acltb_expect_messages.txt" +- name: set ingress test flag + set_fact: + run_ingress: yes + when: run_ingress is not defined -- name: Check supported topology - fail: msg="Invalid testbed_type value '{{testbed_type}}'" - when: testbed_type not in ['t1', 't1-lag', 't1-64-lag'] +- name: set egress test flag + set_fact: + run_egress: no + when: run_egress is not defined -- block: - - name: Prepare variables for ACL configuration - set_fact: - dut_tmp_dir: "/home/admin/acl" - config_db_backup_filename: /etc/sonic/config_db.json.bak.{{ lookup('pipe','date +%Y%m%d-%H%M%S') }} +- name: set basic test flag + set_fact: + test_basic: yes + when: test_basic is not defined - - name: When run_ingress is not specified, set default true - set_fact: - run_ingress: true - when: run_ingress is not defined +- name: set reboot test flag + set_fact: + test_reboot: no + when: test_reboot is not defined - - name: Backup config_db.json - command: cp /etc/sonic/config_db.json {{ config_db_backup_filename }} - become: yes +- name: set port toggle test flag + set_fact: + test_port_toggle: no + when: test_port_toggle is not defined - - include: "roles/test/tasks/acl/acltb/acltb_config.yml" +- name: set test filter expression + set_fact: + filter_expr: 'acl' - - name: Test ingress ACL - include: "roles/test/tasks/acl/acltb/acltb_run_test.yml" - vars: - acl_stage: ingress - when: run_ingress is defined and run_ingress|bool == true +- name: append filter on ingress + set_fact: + filter_expr: '{{ filter_expr }} and not ingress' + when: not run_ingress - - name: Test egress ACL - include: "roles/test/tasks/acl/acltb/acltb_run_test.yml" - vars: - acl_stage: egress - when: run_egress is defined and run_egress|bool == true +- name: append filter on egress + set_fact: + filter_expr: '{{ filter_expr }} and not egress' + when: not run_egress - always: +- name: append filter on basic + set_fact: + filter_expr: '{{ filter_expr }} and not TestBasicAcl and not TestIncrementalAcl' + when: not test_basic - - name: Check existence of config_db.json backup file - stat: - path: "{{ config_db_backup_filename }}" - register: config_db_backup_file +- name: append filter on reboot + set_fact: + filter_expr: '{{ filter_expr }} and not reboot' + when: not test_reboot - - name: Recover config_db.json from backup - command: mv {{ config_db_backup_filename }} /etc/sonic/config_db.json - become: yes - when: config_db_backup_file.stat.exists +- name: append filter on port toggle + set_fact: + filter_expr: '{{ filter_expr }} and not port_toggle' + when: not test_port_toggle - - name: Reload config to cleanup - command: config reload -y - become: yes - - - name: wait 60 seconds for ports to be up - pause: seconds=60 - - - name: Wait for ports to be up - interface_facts: up_ports={{ all_ports }} - until: ansible_interface_link_down_ports | length == 0 - retries: 10 - delay: 20 - - - name: wait 60 seconds for processes and interfaces to be stable - pause: seconds=60 +- name: run test + include: roles/test/tasks/pytest_runner.yml + vars: + test_node: acl + test_filter: '{{ filter_expr }}' diff --git a/ansible/roles/test/tasks/pytest_runner.yml b/ansible/roles/test/tasks/pytest_runner.yml index 01060bbc339..188422bb8cc 100644 --- a/ansible/roles/test/tasks/pytest_runner.yml +++ b/ansible/roles/test/tasks/pytest_runner.yml @@ -59,5 +59,10 @@ environment: ANSIBLE_LIBRARY: ../ansible/library/ register: out + ignore_errors: True - debug: msg='{{ out.stdout_lines }}' + +- fail: + msg: 'py.test run failed' + when: out.rc != 0 diff --git a/tests/acl/conftest.py b/tests/acl/conftest.py new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/acl/conftest.py @@ -0,0 +1 @@ + diff --git a/tests/acl/files/acl_rules_del.json b/tests/acl/files/acl_rules_del.json new file mode 100644 index 00000000000..71ba44005e9 --- /dev/null +++ b/tests/acl/files/acl_rules_del.json @@ -0,0 +1,9 @@ +{ + "acl": { + "acl-sets": { + "acl-set": { + } + } + } +} + diff --git a/tests/acl/templates/acltb_table.j2 b/tests/acl/templates/acltb_table.j2 new file mode 100644 index 00000000000..1215e10bf37 --- /dev/null +++ b/tests/acl/templates/acltb_table.j2 @@ -0,0 +1,15 @@ +{ + "ACL_TABLE": { + "{{ acl_table_name }}": { + "policy_desc": "{{ acl_table_name }}, {{ acl_table_stage }}", + "ports": [ +{% for port in acl_table_ports %} + "{{ port }}"{% if not loop.last %}, +{% endif %}{% endfor %}], +{% if acl_table_stage == "egress" %} + "stage": "egress", +{% endif %} + "type": "{{ acl_table_type }}" + } + } +} diff --git a/tests/acl/templates/acltb_test_rules.j2 b/tests/acl/templates/acltb_test_rules.j2 new file mode 100644 index 00000000000..bddddd82ada --- /dev/null +++ b/tests/acl/templates/acltb_test_rules.j2 @@ -0,0 +1,438 @@ +{ + "acl": { + "acl-sets": { + "acl-set": { + "{{ acl_table_name }}": { + "acl-entries": { + "acl-entry": { + "1": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 1 + }, + "ip": { + "config": { + "source-ip-address": "20.0.0.2/32" + } + } + }, + "2": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 2 + }, + "ip": { + "config": { + "destination-ip-address": "192.168.0.16/32" + } + } + }, + "3": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 3 + }, + "ip": { + "config": { + "destination-ip-address": "172.16.2.0/32" + } + } + }, + "4": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 4 + }, + "transport": { + "config": { + "source-port": "4621" + } + } + }, + "5": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 5 + }, + "ip": { + "config": { + "protocol": 126 + } + } + }, + "6": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 6 + }, + "transport": { + "config": { + "tcp-flags": ["TCP_ACK", "TCP_PSH", "TCP_FIN", "TCP_SYN"] + } + } + }, + "7": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 7 + }, + "ip": { + "config": { + "source-ip-address": "20.0.0.3/32" + } + } + }, + "8": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 8 + }, + "ip": { + "config": { + "source-ip-address": "20.0.0.3/32" + } + } + }, + "9": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 9 + }, + "transport": { + "config": { + "destination-port": "4631" + } + } + }, + "10": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 10 + }, + "transport": { + "config": { + "source-port": "4656..4671" + } + } + }, + "11": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 11 + }, + "transport": { + "config": { + "destination-port": "4640..4687" + } + } + }, + "12": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 12 + }, + "ip": { + "config": { + "protocol":1, + "source-ip-address": "20.0.0.4/32" + } + } + }, + "13": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 13 + }, + "ip": { + "config": { + "protocol":17, + "source-ip-address": "20.0.0.4/32" + } + } + }, + "14": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 14 + }, + "ip": { + "config": { + "source-ip-address": "20.0.0.6/32" + } + } + }, + "15": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 15 + }, + "ip": { + "config": { + "destination-ip-address": "192.168.0.17/32" + } + } + }, + "16": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 16 + }, + "ip": { + "config": { + "destination-ip-address": "172.16.3.0/32" + } + } + }, + "17": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 17 + }, + "transport": { + "config": { + "source-port": "4721" + } + } + }, + "18": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 18 + }, + "ip": { + "config": { + "protocol": 127 + } + } + }, + "19": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 19 + }, + "transport": { + "config": { + "tcp-flags": ["TCP_RST", "TCP_URG"] + } + } + }, + "20": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 20 + }, + "ip": { + "config": { + "source-ip-address": "20.0.0.7/32" + } + } + }, + "21": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 21 + }, + "ip": { + "config": { + "source-ip-address": "20.0.0.7/32" + } + } + }, + "22": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 22 + }, + "transport": { + "config": { + "destination-port": "4731" + } + } + }, + "23": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 23 + }, + "transport": { + "config": { + "source-port": "4756..4771" + } + } + }, + "24": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 24 + }, + "transport": { + "config": { + "destination-port": "4740..4787" + } + } + }, + "25": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 25 + }, + "ip": { + "config": { + "protocol":1, + "source-ip-address": "20.0.0.8/32" + } + } + }, + "26": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 26 + }, + "ip": { + "config": { + "protocol":17, + "source-ip-address": "20.0.0.8/32" + } + } + }, + "27": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 27 + }, + "transport": { + "config": { + "source-port": "179" + } + } + }, + "28": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 28 + }, + "transport": { + "config": { + "destination-port": "179" + } + } + } + } + } + } + } + } + } +} diff --git a/tests/acl/templates/acltb_test_rules_part_1.j2 b/tests/acl/templates/acltb_test_rules_part_1.j2 new file mode 100644 index 00000000000..4583a14977c --- /dev/null +++ b/tests/acl/templates/acltb_test_rules_part_1.j2 @@ -0,0 +1,135 @@ +{ + "acl": { + "acl-sets": { + "acl-set": { + "{{ acl_table_name }}": { + "acl-entries": { + "acl-entry": { + "1": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 1 + }, + "ip": { + "config": { + "source-ip-address": "30.0.0.2/32" + } + } + }, + "2": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 2 + }, + "ip": { + "config": { + "destination-ip-address": "193.168.0.16/32" + } + } + }, + "3": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 3 + }, + "ip": { + "config": { + "destination-ip-address": "173.16.2.0/32" + } + } + }, + "4": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 4 + }, + "transport": { + "config": { + "source-port": "5621" + } + } + }, + "13": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 13 + }, + "ip": { + "config": { + "protocol":17, + "source-ip-address": "30.0.0.4/32" + } + } + }, + "14": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 14 + }, + "ip": { + "config": { + "source-ip-address": "30.0.0.6/32" + } + } + }, + "27": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 27 + }, + "transport": { + "config": { + "source-port": "179" + } + } + }, + "28": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 28 + }, + "transport": { + "config": { + "destination-port": "179" + } + } + } + } + } + } + } + } + } +} diff --git a/tests/acl/templates/acltb_test_rules_part_2.j2 b/tests/acl/templates/acltb_test_rules_part_2.j2 new file mode 100644 index 00000000000..bddddd82ada --- /dev/null +++ b/tests/acl/templates/acltb_test_rules_part_2.j2 @@ -0,0 +1,438 @@ +{ + "acl": { + "acl-sets": { + "acl-set": { + "{{ acl_table_name }}": { + "acl-entries": { + "acl-entry": { + "1": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 1 + }, + "ip": { + "config": { + "source-ip-address": "20.0.0.2/32" + } + } + }, + "2": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 2 + }, + "ip": { + "config": { + "destination-ip-address": "192.168.0.16/32" + } + } + }, + "3": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 3 + }, + "ip": { + "config": { + "destination-ip-address": "172.16.2.0/32" + } + } + }, + "4": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 4 + }, + "transport": { + "config": { + "source-port": "4621" + } + } + }, + "5": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 5 + }, + "ip": { + "config": { + "protocol": 126 + } + } + }, + "6": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 6 + }, + "transport": { + "config": { + "tcp-flags": ["TCP_ACK", "TCP_PSH", "TCP_FIN", "TCP_SYN"] + } + } + }, + "7": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 7 + }, + "ip": { + "config": { + "source-ip-address": "20.0.0.3/32" + } + } + }, + "8": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 8 + }, + "ip": { + "config": { + "source-ip-address": "20.0.0.3/32" + } + } + }, + "9": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 9 + }, + "transport": { + "config": { + "destination-port": "4631" + } + } + }, + "10": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 10 + }, + "transport": { + "config": { + "source-port": "4656..4671" + } + } + }, + "11": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 11 + }, + "transport": { + "config": { + "destination-port": "4640..4687" + } + } + }, + "12": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 12 + }, + "ip": { + "config": { + "protocol":1, + "source-ip-address": "20.0.0.4/32" + } + } + }, + "13": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 13 + }, + "ip": { + "config": { + "protocol":17, + "source-ip-address": "20.0.0.4/32" + } + } + }, + "14": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 14 + }, + "ip": { + "config": { + "source-ip-address": "20.0.0.6/32" + } + } + }, + "15": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 15 + }, + "ip": { + "config": { + "destination-ip-address": "192.168.0.17/32" + } + } + }, + "16": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 16 + }, + "ip": { + "config": { + "destination-ip-address": "172.16.3.0/32" + } + } + }, + "17": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 17 + }, + "transport": { + "config": { + "source-port": "4721" + } + } + }, + "18": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 18 + }, + "ip": { + "config": { + "protocol": 127 + } + } + }, + "19": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 19 + }, + "transport": { + "config": { + "tcp-flags": ["TCP_RST", "TCP_URG"] + } + } + }, + "20": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 20 + }, + "ip": { + "config": { + "source-ip-address": "20.0.0.7/32" + } + } + }, + "21": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 21 + }, + "ip": { + "config": { + "source-ip-address": "20.0.0.7/32" + } + } + }, + "22": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 22 + }, + "transport": { + "config": { + "destination-port": "4731" + } + } + }, + "23": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 23 + }, + "transport": { + "config": { + "source-port": "4756..4771" + } + } + }, + "24": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 24 + }, + "transport": { + "config": { + "destination-port": "4740..4787" + } + } + }, + "25": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 25 + }, + "ip": { + "config": { + "protocol":1, + "source-ip-address": "20.0.0.8/32" + } + } + }, + "26": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 26 + }, + "ip": { + "config": { + "protocol":17, + "source-ip-address": "20.0.0.8/32" + } + } + }, + "27": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 27 + }, + "transport": { + "config": { + "source-port": "179" + } + } + }, + "28": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 28 + }, + "transport": { + "config": { + "destination-port": "179" + } + } + } + } + } + } + } + } + } +} diff --git a/tests/acl/test_acl.py b/tests/acl/test_acl.py new file mode 100644 index 00000000000..88a59dc5fea --- /dev/null +++ b/tests/acl/test_acl.py @@ -0,0 +1,758 @@ +import os +import time +import random +import logging +import pprint + +from abc import ABCMeta, abstractmethod + +import pytest + +import ptf.testutils as testutils +import ptf.mask as mask +import ptf.packet as packet + +from common import reboot, port_toggle +from loganalyzer import LogAnalyzer, LogAnalyzerError + +logger = logging.getLogger(__name__) + +pytestmark = [ + pytest.mark.acl, + pytest.mark.disable_loganalyzer # disable automatic loganalyzer +] + +BASE_DIR = os.path.dirname(os.path.realpath(__file__)) +DUT_TMP_DIR = os.path.join('tmp', os.path.basename(BASE_DIR)) +FILES_DIR = os.path.join(BASE_DIR, 'files') +TEMPLATE_DIR = os.path.join(BASE_DIR, 'templates') + +ACL_TABLE_TEMPLATE = 'acltb_table.j2' +ACL_RULES_FULL_TEMPLATE = 'acltb_test_rules.j2' +ACL_RULES_PART_TEMPLATES = tuple('acltb_test_rules_part_{}.j2'.format(i) for i in xrange(1, 3)) +ACL_REMOVE_RULES_FILE = 'acl_rules_del.json' + +DST_IP_TOR = '172.16.1.0' +DST_IP_TOR_FORWARDED = '172.16.2.0' +DST_IP_TOR_BLOCKED = '172.16.3.0' +DST_IP_SPINE = '192.168.0.0' +DST_IP_SPINE_FORWARDED = '192.168.0.16' +DST_IP_SPINE_BLOCKED = '192.168.0.17' + +LOG_EXPECT_ACL_TABLE_CREATE_RE = '.*Created ACL table.*' +LOG_EXPECT_ACL_TABLE_REMOVE_RE = '.*Successfully deleted ACL table.*' +LOG_EXPECT_ACL_RULE_CREATE_RE = '.*Successfully created ACL rule.*' +LOG_EXPECT_ACL_RULE_REMOVE_RE = '.*Successfully deleted ACL rule.*' + + +@pytest.fixture(scope='module') +def setup(duthost, testbed): + """ + setup fixture gathers all test required information from DUT facts and testbed + :param duthost: DUT host object + :param testbed: Testbed object + :return: dictionary with all test required information + """ + + tor_ports = [] + spine_ports = [] + tor_ports_ids = [] + spine_ports_ids = [] + port_channels = [] + acl_table_ports = [] + + if testbed['topo'] not in ('t1', 't1-lag', 't1-64-lag'): + pytest.skip('Unsupported topology') + + # gather ansible facts + mg_facts = duthost.minigraph_facts(host=duthost.hostname)['ansible_facts'] + + # get the list of TOR/SPINE ports + for dut_port, neigh in mg_facts['minigraph_neighbors'].items(): + port_id = mg_facts['minigraph_port_indices'][dut_port] + if 'T0' in neigh['name']: + tor_ports.append(dut_port) + tor_ports_ids.append(port_id) + elif 'T2' in neigh['name']: + spine_ports.append(dut_port) + spine_ports_ids.append(port_id) + + # get the list of port channels + port_channels = mg_facts['minigraph_portchannels'] + + # get the list of port to be combined to ACL tables + acl_table_ports += tor_ports + if testbed['topo'] in ('t1-lag', 't1-64-lag'): + acl_table_ports += port_channels + else: + acl_table_ports += spine_ports + + logger.info('creating temporary folder for test {}'.format(DUT_TMP_DIR)) + duthost.command("mkdir -p {}".format(DUT_TMP_DIR)) + + host_facts = duthost.setup()['ansible_facts'] + + setup_information = { + 'router_mac': host_facts['ansible_Ethernet0']['macaddress'], + 'dut_tmp_dir': DUT_TMP_DIR, + 'tor_ports': tor_ports, + 'spine_ports': spine_ports, + 'tor_ports_ids': tor_ports_ids, + 'spine_ports_ids': spine_ports_ids, + 'port_channels': port_channels, + 'acl_table_ports': acl_table_ports, + 'dst_ip_tor': DST_IP_TOR, + 'dst_ip_tor_forwarded': DST_IP_TOR_FORWARDED, + 'dst_ip_tor_blocked': DST_IP_TOR_BLOCKED, + 'dst_ip_spine': DST_IP_SPINE, + 'dst_ip_spine_forwarded': DST_IP_SPINE_FORWARDED, + 'dst_ip_spine_blocked': DST_IP_SPINE_BLOCKED, + } + + logger.info('setup variables {}'.format(pprint.pformat(setup_information))) + + yield setup_information + + logger.info('removing {}'.format(DUT_TMP_DIR)) + duthost.command('rm -rf {}'.format(DUT_TMP_DIR)) + + +@pytest.fixture(scope='module', params=['ingress', 'egress']) +def stage(request): + """ + small fixture to parametrize test for ingres/egress stage testing + :param request: pytest request + :return: stage parameter + """ + + return request.param + + +@pytest.fixture(scope='module') +def acl_table_config(duthost, setup, stage): + """ + generate ACL table configuration files and deploy them on DUT; + after test run cleanup artifacts on DUT + :param duthost: DUT host object + :param setup: setup parameters + :param stage: stage + :return: dictionary of table name and matching configuration file + """ + + # Initialize data for ACL tables + tables_map = { + 'ingress': 'DATAINGRESS', + 'egress': 'DATAEGRESS', + } + + acl_table_name = tables_map[stage] + tmp_dir = setup['dut_tmp_dir'] + + acl_table_vars = { + 'acl_table_name': acl_table_name, + 'acl_table_ports': setup['acl_table_ports'], + 'acl_table_stage': stage, + 'acl_table_type': 'L3', + } + + logger.info('extra variables for ACL table:\n{}'.format(pprint.pformat(acl_table_vars))) + duthost.host.options['variable_manager'].extra_vars = acl_table_vars + + logger.info('generate config for ACL table {}'.format(acl_table_name)) + acl_config = 'acl_table_{}.json'.format(acl_table_name) + acl_config_path = os.path.join(tmp_dir, acl_config) + duthost.template(src=os.path.join(TEMPLATE_DIR, ACL_TABLE_TEMPLATE), dest=acl_config_path) + + yield { + 'name': acl_table_name, + 'config_file': acl_config_path, + } + + +@pytest.fixture(scope='module') +def acl_table(duthost, acl_table_config): + """ + fixture to apply ACL table configuration and remove after tests + :param duthost: DUT object + :param acl_table_config: ACL table configuration dictionary + :return: forwards acl_table_config + """ + + name = acl_table_config['name'] + conf = acl_table_config['config_file'] + + loganalyzer = LogAnalyzer(ansible_host=duthost, marker_prefix='acl') + loganalyzer.load_common_config() + + try: + loganalyzer.expect_regex = [LOG_EXPECT_ACL_TABLE_CREATE_RE] + with loganalyzer: + logger.info('creating ACL table: applying {}'.format(conf)) + # TODO: use sonic config CLI + duthost.command('sonic-cfggen -j {} --write-to-db'.format(conf)) + except LogAnalyzerError as err: + # cleanup config DB if create failed + duthost.command('config acl remove table {}'.format(name)) + raise err + + try: + yield acl_table_config + finally: + loganalyzer.expect_regex = [LOG_EXPECT_ACL_TABLE_REMOVE_RE] + with loganalyzer: + logger.info('removing ACL table {}'.format(name)) + duthost.command('config acl remove table {}'.format(name)) + + # save cleaned configuration + duthost.command('config save -y') + + +class BaseAclTest(object): + """ + Base class for ACL rules testing. + Derivatives have to provide @setup_rules method to prepare DUT for ACL traffic test and + optionally override @teardown_rules which base implementation is simply applying empty ACL rules + configuration file + """ + __metaclass__ = ABCMeta + + ACL_COUNTERS_UPDATE_INTERVAL = 10 # seconds + + @abstractmethod + def setup_rules(self, dut, setup, acl_table): + """ + setup rules for test + :param dut: dut host + :param setup: setup information + :param acl_table: acl table creating fixture + :return: + """ + + pass + + def post_setup_hook(self, dut, localhost): + """ + perform actions after rules are applied + :param dut: DUT host object + :param localhost: localhost object + :return: + """ + + pass + + def teardown_rules(self, dut, setup): + """ + teardown ACL rules after test by applying empty configuration + :param dut: DUT host object + :param setup: setup information + :return: + """ + + logger.info('removing all ACL rules') + # copy rules remove configuration + dut.copy(src=os.path.join(FILES_DIR, ACL_REMOVE_RULES_FILE), dest=setup['dut_tmp_dir']) + remove_rules_dut_path = os.path.join(setup['dut_tmp_dir'], ACL_REMOVE_RULES_FILE) + # remove rules + logger.info('applying {}'.format(remove_rules_dut_path)) + dut.command('config acl update full {}'.format(remove_rules_dut_path)) + + @pytest.fixture(scope='class', autouse=True) + def acl_rules(self, duthost, localhost, setup, acl_table): + """ + setup/teardown ACL rules based on test class requirements + :param duthost: DUT host object + :param localhost: localhost object + :param setup: setup information + :param acl_table: table creating fixture + :return: + """ + + loganalyzer = LogAnalyzer(ansible_host=duthost, marker_prefix='acl_rules') + loganalyzer.load_common_config() + + try: + loganalyzer.expect_regex = [LOG_EXPECT_ACL_RULE_CREATE_RE] + with loganalyzer: + self.setup_rules(duthost, setup, acl_table) + self.post_setup_hook(duthost, localhost) + except LogAnalyzerError as err: + # cleanup config DB in case of log analysis error + self.teardown_rules(duthost, setup) + raise err + + try: + yield + finally: + loganalyzer.expect_regex = [LOG_EXPECT_ACL_RULE_REMOVE_RE] + with loganalyzer: + self.teardown_rules(duthost, setup) + + @pytest.yield_fixture(scope='class', autouse=True) + def counters_sanity_check(self, duthost, acl_rules, acl_table): + """ + counters sanity check after traffic test cases. + This fixture yields python list of rule IDs which test case should extend if + the RULE is required to check for increased counters. + After test cases passed the fixture will wait for ACL counters to update + and check if counters for each rule in the list of rules were increased. + :param duthost: DUT host object + :param acl_rules: rules creating fixture + :param acl_table: table creating fixture + :return: + """ + + table_name = acl_table['name'] + acl_facts_before_traffic = duthost.acl_facts()['ansible_facts']['ansible_acl_facts'][table_name]['rules'] + rule_list = [] + yield rule_list + + if not rule_list: + return + + # wait for orchagent to update ACL counters + time.sleep(self.ACL_COUNTERS_UPDATE_INTERVAL) + + acl_facts_after_traffic = duthost.acl_facts()['ansible_facts']['ansible_acl_facts'][table_name]['rules'] + + assert len(acl_facts_after_traffic) == len(acl_facts_before_traffic) + + for rule in rule_list: + rule = 'RULE_{}'.format(rule) + counters_after = acl_facts_after_traffic[rule] + counters_before = acl_facts_before_traffic[rule] + logger.info('counters for {} before traffic:\n{}'.format(rule, pprint.pformat(counters_before))) + logger.info('counters for {} after traffic:\n{}'.format(rule, pprint.pformat(counters_after))) + assert counters_after['packets_count'] > counters_before['packets_count'] + assert counters_after['bytes_count'] > counters_before['bytes_count'] + + @pytest.fixture(params=['tor->spine', 'spine->tor']) + def direction(self, request): + """ + used to parametrized test cases on direction + :param request: pytest request object + :return: direction + """ + + return request.param + + def get_src_port(self, setup, direction): + """ return source ports based on test case direction """ + + src_ports = setup['tor_ports_ids'] if direction == 'tor->spine' else setup['spine_ports_ids'] + return random.choice(src_ports) + + def get_dst_ports(self, setup, direction): + """ return destination ports based on test case direction """ + + return setup['spine_ports_ids'] if direction == 'tor->spine' else setup['tor_ports_ids'] + + def get_dst_ip(self, setup, direction): + """ return allowed destination IP based on test case direction """ + + return setup['dst_ip_spine'] if direction == 'tor->spine' else setup['dst_ip_tor'] + + def tcp_packet(self, setup, direction, ptfadapter): + """ create TCP packet for testing """ + + return testutils.simple_tcp_packet( + eth_dst=setup['router_mac'], + eth_src=ptfadapter.dataplane.get_mac(0, 0), + ip_dst=self.get_dst_ip(setup, direction), + ip_src='20.0.0.1', + tcp_sport=0x4321, + tcp_dport=0x51, + ip_ttl=64, + ) + + def udp_packet(self, setup, direction, ptfadapter): + """ create UDP packet for testing """ + + return testutils.simple_udp_packet( + eth_dst=setup['router_mac'], + eth_src=ptfadapter.dataplane.get_mac(0, 0), + ip_dst=self.get_dst_ip(setup, direction), + ip_src='20.0.0.1', + udp_sport=1234, + udp_dport=80, + ip_ttl=64, + ) + + def icmp_packet(self, setup, direction, ptfadapter): + """ create ICMP packet for testing """ + + return testutils.simple_icmp_packet( + eth_dst=setup['router_mac'], + eth_src=ptfadapter.dataplane.get_mac(0, 0), + ip_dst=self.get_dst_ip(setup, direction), + ip_src='20.0.0.1', + icmp_type=8, + icmp_code=0, + ip_ttl=64, + ) + + def expected_mask_routed_packet(self, pkt): + """ return mask for routed packet """ + + exp_pkt = pkt.copy() + exp_pkt['IP'].ttl -= 1 + exp_pkt = mask.Mask(exp_pkt) + exp_pkt.set_do_not_care_scapy(packet.Ether, 'dst') + exp_pkt.set_do_not_care_scapy(packet.Ether, 'src') + exp_pkt.set_do_not_care_scapy(packet.IP, 'chksum') + return exp_pkt + + def test_unmatched_blocked(self, setup, direction, ptfadapter): + """ verify that unmatched packet is dropped """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_no_packet_any(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + def test_source_ip_match_forwarded(self, setup, direction, ptfadapter, counters_sanity_check): + """ test source IP matched packet is forwarded """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['IP'].src = '20.0.0.2' + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(1) + + def test_rules_priority_forwarded(self, setup, direction, ptfadapter, counters_sanity_check): + """ test rules priorities, forward rule case """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['IP'].src = '20.0.0.7' + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(20) + + def test_rules_priority_dropped(self, setup, direction, ptfadapter, counters_sanity_check): + """ test rules priorities, drop rule case """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['IP'].src = '20.0.0.3' + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_no_packet_any(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(7) + + def test_dest_ip_match_forwarded(self, setup, direction, ptfadapter, counters_sanity_check): + """ test destination IP matched packet forwarded """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['IP'].dst = DST_IP_TOR_FORWARDED if direction == 'spine->tor' else DST_IP_SPINE_FORWARDED + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(2 if direction == 'spine->tor' else 3) + + def test_dest_ip_match_dropped(self, setup, direction, ptfadapter, counters_sanity_check): + """ test destination IP matched packet dropped """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['IP'].dst = DST_IP_TOR_BLOCKED if direction == 'spine->tor' else DST_IP_SPINE_BLOCKED + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_no_packet_any(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(15 if direction == 'spine->tor' else 16) + + def test_source_ip_match_dropped(self, setup, direction, ptfadapter, counters_sanity_check): + """ test source IP matched packet dropped """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['IP'].src = '20.0.0.6' + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_no_packet_any(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(14) + + def test_udp_source_ip_match_forwarded(self, setup, direction, ptfadapter, counters_sanity_check): + """ test UDP source IP matched packet forwarded """ + + pkt = self.udp_packet(setup, direction, ptfadapter) + pkt['IP'].src = '20.0.0.4' + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(13) + + def test_udp_source_ip_match_dropped(self, setup, direction, ptfadapter, counters_sanity_check): + """ test UDP destination IP matched packet dropped """ + + pkt = self.udp_packet(setup, direction, ptfadapter) + pkt['IP'].src = '20.0.0.8' + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_no_packet_any(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(26) + + def test_icmp_source_ip_match_dropped(self, setup, direction, ptfadapter, counters_sanity_check): + """ test ICMP source IP matched packet dropped """ + + pkt = self.icmp_packet(setup, direction, ptfadapter) + pkt['IP'].src = '20.0.0.8' + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_no_packet_any(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(25) + + def test_icmp_source_ip_match_forwarded(self, setup, direction, ptfadapter, counters_sanity_check): + """ test ICMP source IP matched packet forwarded """ + + pkt = self.icmp_packet(setup, direction, ptfadapter) + pkt['IP'].src = '20.0.0.4' + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(12) + + def test_l4_dport_match_forwarded(self, setup, direction, ptfadapter, counters_sanity_check): + """ test L4 destination port matched packet forwarded """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['TCP'].dport = 0x1217 + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(5) + + def test_l4_sport_match_forwarded(self, setup, direction, ptfadapter, counters_sanity_check): + """ test L4 source port matched packet forwarded """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['TCP'].sport = 0x120D + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(4) + + def test_l4_dport_range_match_forwarded(self, setup, direction, ptfadapter, counters_sanity_check): + """ test L4 destination port range matched packet forwarded """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['TCP'].dport = 0x123B + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(11) + + def test_l4_sport_range_match_forwarded(self, setup, direction, ptfadapter, counters_sanity_check): + """ test L4 source port range matched packet forwarded """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['TCP'].sport = 0x123A + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(10) + + def test_l4_dport_range_match_dropped(self, setup, direction, ptfadapter, counters_sanity_check): + """ test L4 destination port range matched packet dropped """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['TCP'].dport = 0x127B + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_no_packet_any(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(22) + + def test_l4_sport_range_match_dropped(self, setup, direction, ptfadapter, counters_sanity_check): + """ test L4 source port range matched packet dropped """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['TCP'].sport = 0x1271 + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_no_packet_any(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(17) + + def test_ip_proto_match_forwarded(self, setup, direction, ptfadapter, counters_sanity_check): + """ test IP protocol matched packet forwarded""" + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['IP'].proto = 0x7E + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(5) + + def test_tcp_flags_match_forwarded(self, setup, direction, ptfadapter, counters_sanity_check): + """ test TCP flags matched packet forwarded """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['TCP'].flags = 0x1B + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(6) + + def test_l4_dport_match_dropped(self, setup, direction, ptfadapter, counters_sanity_check): + """ test L4 destination port matched packet dropped """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['TCP'].dport = 0x127B + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_no_packet_any(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(22) + + def test_l4_sport_match_dropped(self, setup, direction, ptfadapter, counters_sanity_check): + """ test L4 source port matched packet dropped """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['TCP'].sport = 0x1271 + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_no_packet_any(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(10) + + def test_ip_proto_match_dropped(self, setup, direction, ptfadapter, counters_sanity_check): + """ test IP protocol matched packet dropped """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['IP'].proto = 0x7F + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_no_packet_any(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(18) + + def test_tcp_flags_match_dropped(self, setup, direction, ptfadapter, counters_sanity_check): + """ test TCP flags matched packet dropped """ + + pkt = self.tcp_packet(setup, direction, ptfadapter) + pkt['TCP'].flags = 0x24 + exp_pkt = self.expected_mask_routed_packet(pkt) + + testutils.send(ptfadapter, self.get_src_port(setup, direction), pkt) + testutils.verify_no_packet_any(ptfadapter, exp_pkt, ports=self.get_dst_ports(setup, direction)) + + counters_sanity_check.append(5) + + +class TestBasicAcl(BaseAclTest): + """ + Basic ACL rules traffic tests. + Setup rules using full update, run traffic tests cases. + """ + + def setup_rules(self, dut, setup, acl_table): + """ + setup rules on DUT + :param dut: dut host + :param setup: setup information + :param acl_table: acl table creating fixture + :return: + """ + name = acl_table['name'] + dut_conf_file_path = os.path.join(setup['dut_tmp_dir'], 'acl_rules_{}.json'.format(name)) + + logger.info('generating config for ACL rules, ACL table {}'.format(name)) + dut.template(src=os.path.join(TEMPLATE_DIR, ACL_RULES_FULL_TEMPLATE), + dest=dut_conf_file_path) + + logger.info('applying {}'.format(dut_conf_file_path)) + dut.command('config acl update full {}'.format(dut_conf_file_path)) + + +class TestIncrementalAcl(BaseAclTest): + """ + Incremental ACL rules configuration traffic tests. + Setup rules using incremental update in two parts, run traffic tests cases. + """ + + def setup_rules(self, dut, setup, acl_table): + """ + setup rules on DUT for incremental test + :param dut: dut host + :param setup: setup information + :param acl_table: acl table creating fixture + :return: + """ + name = acl_table['name'] + logger.info('generate incremental config for ACL rule ACL table {table_name}'.format(table_name=name)) + for i, conf in enumerate(ACL_RULES_PART_TEMPLATES): + dut_conf_file_path = os.path.join(setup['dut_tmp_dir'], 'acl_rules_{}_part_{}.json'.format(name, i)) + dut.template(src=os.path.join(TEMPLATE_DIR, conf), dest=dut_conf_file_path) + logger.info('applying {}'.format(dut_conf_file_path)) + dut.command('config acl update incremental {}'.format(dut_conf_file_path)) + + +@pytest.mark.reboot +class TestAclWithReboot(TestBasicAcl): + """ + Basic ACL rules traffic tests with reboot. + Verify that the ACL configurations persist after reboot + """ + + def post_setup_hook(self, dut, localhost): + """ + save configuration and execute reboot after rules are applied + :param dut: dut host + :param localhost: localhost object + :return: + """ + dut.command('config save -y') + reboot(dut, localhost) + + +@pytest.mark.port_toggle +class TestAclWithPortToggle(TestBasicAcl): + """ + Basic ACL rules traffic tests with port toggle. + Toggles ports before traffic tests. + """ + + def post_setup_hook(self, dut, localhost): + """ + toggle ports after rules are applied + :param dut: dut host + :param localhost: localhost object + :return: + """ + port_toggle(dut) diff --git a/tests/common/__init__.py b/tests/common/__init__.py new file mode 100644 index 00000000000..27125e0a36a --- /dev/null +++ b/tests/common/__init__.py @@ -0,0 +1,5 @@ +from reboot import reboot +from config_reload import config_reload +from port_toggle import port_toggle + +__all__ = ['reboot', 'config_reload', 'port_toggle'] diff --git a/tests/common/config_reload.py b/tests/common/config_reload.py new file mode 100644 index 00000000000..1c62df0e722 --- /dev/null +++ b/tests/common/config_reload.py @@ -0,0 +1,33 @@ +import time +import logging + +logger = logging.getLogger(__name__) + +config_sources = ['config_db', 'minigraph'] + + +def config_reload(duthost, config_source='config_db', wait=120): + """ + reload SONiC configuration + :param duthost: DUT host object + :param config_source: configuration source either 'config_db' or 'minigraph' + :param wait: wait timeout for DUT to initialize after configuration reload + :return: + """ + + if config_source not in config_sources: + raise ValueError('invalid config source passed in "{}", must be {}'.format( + config_source, + ' or '.join(['"{}"'.format(src) for src in config_sources]) + )) + + logger.info('reloading {}'.format(config_source)) + + if config_source == 'minigraph': + duthost.command('config load_minigraph -y') + duthost.command('config save -y') + + if config_source == 'config_db': + duthost.command('config reload -y') + + time.sleep(wait) diff --git a/tests/common/port_toggle.py b/tests/common/port_toggle.py new file mode 100644 index 00000000000..e8bfd2fa2ea --- /dev/null +++ b/tests/common/port_toggle.py @@ -0,0 +1,48 @@ +import time +import logging +import pprint + +logger = logging.getLogger(__name__) + + +def port_toggle(duthost, ports=None, wait=60, wait_after_ports_up=60): + """ + Toggle ports on DUT + :param duthost: DUT host object + :param ports: specify list of ports, None if toggle all ports + :param wait: time to wait for interface to become up + :param wait_after_ports_up: time to wait after interfaces become up + :return: + """ + + if ports is None: + logger.debug('ports is None, toggling all minigraph ports') + mg_facts = duthost.minigraph_facts(host=duthost.hostname)['ansible_facts'] + ports = mg_facts['minigraph_ports'].keys() + + logger.info('toggling ports:\n{}'.format(pprint.pformat(ports))) + + for port in ports: + duthost.command('config interface shutdown {}'.format(port)) + + # verify all interfaces are up + ports_down = duthost.interface_facts(up_ports=ports)['ansible_facts']['ansible_interface_link_down_ports'] + assert len(ports_down) == len(ports) + + for port in ports: + duthost.command('config interface startup {}'.format(port)) + + logger.info('waiting for ports to become up') + + start = time.time() + ports_down = duthost.interface_facts(up_ports=ports)['ansible_facts']['ansible_interface_link_down_ports'] + while time.time() - start < wait: + ports_down = duthost.interface_facts(up_ports=ports)['ansible_facts']['ansible_interface_link_down_ports'] + logger.info('retry, down ports:\n{}'.format(pprint.pformat(ports_down))) + if len(ports_down) == 0: + break + + assert len(ports_down) == 0 + + logger.info('wait {} seconds for system to startup'.format(wait_after_ports_up)) + time.sleep(wait_after_ports_up) diff --git a/tests/common/reboot.py b/tests/common/reboot.py new file mode 100644 index 00000000000..fe0c9ec2b59 --- /dev/null +++ b/tests/common/reboot.py @@ -0,0 +1,99 @@ +import time +import logging +from multiprocessing.pool import ThreadPool, TimeoutError +from ansible_host import AnsibleModuleException + +logger = logging.getLogger(__name__) + +# SSH defines +SONIC_SSH_PORT = 22 +SONIC_SSH_REGEX = 'OpenSSH_[\\w\\.]+ Debian' + +# map reboot type -> reboot command +reboot_commands =\ +{ + 'cold': 'reboot', + 'fast': 'fast-reboot', + 'warm': 'warm-reboot', +} + + +def reboot(duthost, localhost, reboot_type='cold', delay=10, timeout=180, wait=120): + """ + reboots DUT + :param duthost: DUT host object + :param localhost: local host object + :param reboot_type: reboot type (cold, fast, warm) + :param delay: delay between ssh availability checks + :param timeout: timeout for waiting ssh port state change + :param wait: time to wait for DUT to initialize + :return: + """ + + # pool for executing tasks asynchronously + pool = ThreadPool() + + try: + reboot_command = reboot_commands[reboot_type] + except KeyError: + raise ValueError('invalid reboot type: "{}"'.format(reboot_type)) + + def execute_reboot(): + logger.info('rebooting with command "{}"'.format(reboot_command)) + return duthost.command(reboot_command) + + reboot_res = pool.apply_async(execute_reboot) + + logger.info('waiting for ssh to drop') + res = localhost.wait_for(host=duthost.hostname, + port=SONIC_SSH_PORT, + state='absent', + search_regex=SONIC_SSH_REGEX, + delay=delay, + timeout=timeout) + + if 'failed' in res['localhost']: + if reboot_res.ready(): + logger.error('reboot result: {}'.format(reboot_res.get())) + raise Exception('DUT did not shutdown') + + # TODO: add serial output during reboot for better debuggability + # This feature requires serial information to be present in + # testbed information + + logger.info('waiting for ssh to startup') + res = localhost.wait_for(host=duthost.hostname, + port=SONIC_SSH_PORT, + state='started', + search_regex=SONIC_SSH_REGEX, + delay=delay, + timeout=timeout + ) + if 'failed' in res['localhost']: + raise Exception('DUT did not startup') + + logger.info('ssh has started up') + + logger.info('waiting for switch to initialize') + time.sleep(wait) + + if reboot_type == 'warm': + logger.info('waiting for warmboot-finalizer service to finish') + finalizer_state = 'active' + count = 0 + while finalizer_state == 'active': + try: + res = duthost.command('systemctl is-active warmboot-finalizer.service') + except AnsibleModuleException as err: + res = err.module_result + + finalizer_state = res['stdout'].strip() + time.sleep(delay) + if count * delay > timeout: + raise Exception('warmboot-finalizer.service did not finish') + count += 1 + logger.info('warmboot-finalizer service finished') + + logger.info('{} reboot finished'.format(reboot_type)) + + pool.terminate() diff --git a/tests/pytest.ini b/tests/pytest.ini new file mode 100644 index 00000000000..d4f490db751 --- /dev/null +++ b/tests/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +markers: + acl: ACL tests + reboot: tests which perform SONiC reboot + port_toggle: tests which toggle ports + disable_loganalyzer: make to disable automatic loganalyzer