diff --git a/ansible/README.test.md b/ansible/README.test.md index 5c2cae13e09..f5be517337b 100644 --- a/ansible/README.test.md +++ b/ansible/README.test.md @@ -85,3 +85,8 @@ ansible-playbook test_sonic.yml -i inventory --limit {DUT_NAME} --become --tags ansible-playbook test_sonic.yml -i inventory --limit {DUT_NAME} --become --tags syslog ``` +### BGP GR helper mode test +``` +ansible-playbook test_sonic.yml -i inventory --limit {DUT_NAME} --become --tags bgp_gr_helper --extra-vars "testbed_type={TESTBED_TYPE}" +``` + diff --git a/ansible/group_vars/eos/eos.yml b/ansible/group_vars/eos/eos.yml index 220e0a77901..9095de7c2c3 100644 --- a/ansible/group_vars/eos/eos.yml +++ b/ansible/group_vars/eos/eos.yml @@ -1,4 +1,5 @@ # snmp variables snmp_rocommunity: strcommunity snmp_location: str - +# Default value. Should be adjusted based on PTF host capabilities +bgp_gr_timer: 300 diff --git a/ansible/library/bgp_facts.py b/ansible/library/bgp_facts.py index bf144923f0e..e72e49d5256 100644 --- a/ansible/library/bgp_facts.py +++ b/ansible/library/bgp_facts.py @@ -104,6 +104,11 @@ def parse_neighbors(self): regex_routerid = re.compile(r'.*remote router ID (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})') regex_peer_group = re.compile(r'.*Member of peer-group (.*) for session parameters') regex_subnet = re.compile(r'.*subnet range group: (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})') + regex_cap_gr = re.compile(r'.*Graceful Restart Capabilty: (\w+)') + regex_cap_gr_peer_restart_time = re.compile(r'.*Remote Restart timer is (\d+)') + regex_cap_gr_peer_af_ip4 = re.compile(r'.*IPv4 Unicast\((.*)\)') + regex_cap_gr_peer_af_ip6 = re.compile(r'.*IPv6 Unicast\((.*)\)') + neighbors = {} try: @@ -114,6 +119,7 @@ def parse_neighbors(self): # ignore empty rows if 'BGP' in n: neighbor = {} + capabilities = {} message_stats = {} n = "BGP neighbor is" + n lines = n.splitlines() @@ -132,6 +138,11 @@ def parse_neighbors(self): if regex_peer_group.match(line): neighbor['peer group'] = regex_peer_group.match(line).group(1) if regex_subnet.match(line): neighbor['subnet'] = regex_subnet.match(line).group(1) + if regex_cap_gr.match(line): capabilities['graceful restart'] = regex_cap_gr.match(line).group(1).lower() + if regex_cap_gr_peer_restart_time.match(line): capabilities['peer restart timer'] = int(regex_cap_gr_peer_restart_time.match(line).group(1)) + if regex_cap_gr_peer_af_ip4.match(line): capabilities['peer af ipv4 unicast'] = regex_cap_gr_peer_af_ip4.match(line).group(1).lower() + if regex_cap_gr_peer_af_ip6.match(line): capabilities['peer af ipv6 unicast'] = regex_cap_gr_peer_af_ip6.match(line).group(1).lower() + if regex_stats.match(line): key, values = line.split(':') key = key.lstrip() @@ -141,6 +152,9 @@ def parse_neighbors(self): value_dict['rcvd'] = int(rcvd) message_stats[key] = value_dict + if capabilities: + neighbor['capabilities'] = capabilities + if message_stats: neighbor['message statistics'] = message_stats diff --git a/ansible/roles/eos/templates/t1-lag-spine.j2 b/ansible/roles/eos/templates/t1-lag-spine.j2 index 8fa2aa90c2b..669c2d038ec 100644 --- a/ansible/roles/eos/templates/t1-lag-spine.j2 +++ b/ansible/roles/eos/templates/t1-lag-spine.j2 @@ -90,6 +90,9 @@ route-map PREPENDAS permit {{ 2 * (podset * props.tor_number + tor + 1) + 1 }} router bgp {{ host['bgp']['asn'] }} router-id {{ host['interfaces']['Loopback0']['ipv4'] | ipaddr('address') }} ! + graceful-restart restart-time {{ bgp_gr_timer }} + graceful-restart + ! {% for asn, remote_ips in host['bgp']['peers'].items() %} {% for remote_ip in remote_ips %} neighbor {{ remote_ip }} remote-as {{ asn }} diff --git a/ansible/roles/eos/templates/t1-lag-tor.j2 b/ansible/roles/eos/templates/t1-lag-tor.j2 index 91b1f49594b..43008df4048 100644 --- a/ansible/roles/eos/templates/t1-lag-tor.j2 +++ b/ansible/roles/eos/templates/t1-lag-tor.j2 @@ -52,6 +52,9 @@ interface {{ name }} router bgp {{ host['bgp']['asn'] }} router-id {{ host['interfaces']['Loopback0']['ipv4'] | ipaddr('address') }} ! + graceful-restart restart-time {{ bgp_gr_timer }} + graceful-restart + ! {% for asn, remote_ips in host['bgp']['peers'].items() %} {% for remote_ip in remote_ips %} neighbor {{ remote_ip }} remote-as {{ asn }} diff --git a/ansible/roles/eos/templates/t1-spine.j2 b/ansible/roles/eos/templates/t1-spine.j2 index 6f308f2e018..dd6f5bb67fc 100644 --- a/ansible/roles/eos/templates/t1-spine.j2 +++ b/ansible/roles/eos/templates/t1-spine.j2 @@ -83,6 +83,9 @@ route-map PREPENDAS permit {{ 2 * (podset * props.tor_number + tor + 1) + 1 }} router bgp {{ host['bgp']['asn'] }} router-id {{ host['interfaces']['Loopback0']['ipv4'] | ipaddr('address') }} ! + graceful-restart restart-time {{ bgp_gr_timer }} + graceful-restart + ! {% for asn, remote_ips in host['bgp']['peers'].items() %} {% for remote_ip in remote_ips %} neighbor {{ remote_ip }} remote-as {{ asn }} diff --git a/ansible/roles/eos/templates/t1-tor.j2 b/ansible/roles/eos/templates/t1-tor.j2 index 91b1f49594b..43008df4048 100644 --- a/ansible/roles/eos/templates/t1-tor.j2 +++ b/ansible/roles/eos/templates/t1-tor.j2 @@ -52,6 +52,9 @@ interface {{ name }} router bgp {{ host['bgp']['asn'] }} router-id {{ host['interfaces']['Loopback0']['ipv4'] | ipaddr('address') }} ! + graceful-restart restart-time {{ bgp_gr_timer }} + graceful-restart + ! {% for asn, remote_ips in host['bgp']['peers'].items() %} {% for remote_ip in remote_ips %} neighbor {{ remote_ip }} remote-as {{ asn }} diff --git a/ansible/roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml b/ansible/roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml index bb2dae21996..5f78ef2c265 100644 --- a/ansible/roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml +++ b/ansible/roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml @@ -4,7 +4,28 @@ - debug: msg="starting loganalyzer analysis phase" -- set_fact: cmd="python {{ run_dir }}/loganalyzer.py --action analyze --run_id {{ testname_unique }} --out_dir {{ test_out_dir }} -m {{ match_file }} -i {{ ignore_file }} -e {{ test_expect_file }} -v" +- name: Init variables + set_fact: + match_file_option: "-m {{ match_file }}" + ignore_file_option: "-i {{ ignore_file }}" + expect_file_option: "-e {{ expect_file }}" + +- name: Add test specific match file + set_fact: + match_file_option: "{{ match_file_option }},{{ test_match_file }} " + when: test_match_file is defined + +- name: Add test specific ignore file + set_fact: + ignore_file_option: "{{ ignore_file_option }},{{ test_ignore_file }}" + when: test_ignore_file is defined + +- name: Use test specific expect file + set_fact: + expect_file_option: "-e {{ test_expect_file }}" + when: test_expect_file is defined + +- set_fact: cmd="python {{ run_dir }}/loganalyzer.py --action analyze --run_id {{ testname_unique }} --out_dir {{ test_out_dir }} {{ match_file_option }} {{ ignore_file_option }} {{ expect_file_option }} -v" - debug: msg={{cmd}} diff --git a/ansible/roles/test/files/tools/loganalyzer/loganalyzer_common_expect.txt b/ansible/roles/test/files/tools/loganalyzer/loganalyzer_common_expect.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ansible/roles/test/files/tools/loganalyzer/loganalyzer_init.yml b/ansible/roles/test/files/tools/loganalyzer/loganalyzer_init.yml index 4ace815cefe..8ab6011e27e 100644 --- a/ansible/roles/test/files/tools/loganalyzer/loganalyzer_init.yml +++ b/ansible/roles/test/files/tools/loganalyzer/loganalyzer_init.yml @@ -5,16 +5,44 @@ - set_fact: loganalyzer_location: "{{ 'roles/test/files/tools/loganalyzer' }}" +- set_fact: + match_file: loganalyzer_common_match.txt + when: match_file is not defined + +- set_fact: + ignore_file: loganalyzer_common_ignore.txt + when: ignore_file is not defined + +- set_fact: + expect_file: "loganalyzer_common_expect.txt" + when: expect_file is not defined + +- set_fact: + testname_unique: "{{ testname }}.{{ ansible_date_time.date }}.{{ ansible_date_time.time }}" + when: testname_unique is not defined + +- set_fact: + test_out_dir: "{{ out_dir }}/{{ testname_unique }}" + when: test_out_dir is not defined + - name: Copy loganalyzer common match and ignore files to switch copy: src="{{ loganalyzer_location }}/{{ item }}" dest="{{ run_dir }}/{{ item }}" with_items: - "{{ match_file }}" - "{{ ignore_file }}" + - "{{ expect_file }}" + +- name: Copy test specific file match-files to switch + copy: src="{{ tests_location }}/{{ testname }}/{{ test_match_file }}" dest="{{ run_dir }}/{{ test_match_file }}" + when: test_match_file is defined + +- name: Copy test specific ignore-files to switch + copy: src="{{ tests_location }}/{{ testname }}/{{ test_ignore_file }}" dest="{{ run_dir }}/{{ test_ignore_file }}" + when: test_ignore_file is defined - name: Copy test specific expect-files to switch - copy: src="{{ tests_location }}/{{ testname }}/{{ item }}" dest="{{ run_dir }}/{{ item }}" - with_items: - - "{{ test_expect_file }}" + copy: src="{{ tests_location }}/{{ testname }}/{{ test_expect_file }}" dest="{{ run_dir }}/{{ test_expect_file }}" + when: test_expect_file is defined - name: Copy loganalyzer.py to run directory copy: src="{{ loganalyzer_location }}/loganalyzer.py" dest="{{ run_dir }}" diff --git a/ansible/roles/test/tasks/bgp_gr_helper.yml b/ansible/roles/test/tasks/bgp_gr_helper.yml new file mode 100644 index 00000000000..777e75cb89a --- /dev/null +++ b/ansible/roles/test/tasks/bgp_gr_helper.yml @@ -0,0 +1,102 @@ +#----------------------------------------- +# Run BGP GR helper mode test and Perform log analysis. +#----------------------------------------- + +- fail: msg="testbed_type is not defined." + when: testbed_type is not defined + +- fail: msg="testbed_type {{testbed_type}} is invalid." + when: testbed_type not in ['t1-lag', 't1'] + +- name: Get VM info. + include: "roles/test/tasks/bgp_gr_helper/get_vm_info.yml" + +- name: Gather facts from bgp container. + bgp_facts: + +- name: Get VM GR timer. + set_fact: + bgp_gr_timer: "{{ bgp_neighbors[peer_ip]['capabilities']['peer restart timer'] }}" + +- name: Set default value for GR simulation time in seconds. + set_fact: + bgp_gr_simulation_timer: 100 + +- set_fact: + testname: "bgp_gr_helper" + run_dir: /tmp + out_dir: /tmp/ansible-loganalyzer-results + tests_location: "{{ 'roles/test/tasks' }}" + +- include: roles/test/files/tools/loganalyzer/loganalyzer_init.yml + vars: + test_match_file: routes_update_match.txt + +- name: Set log level to INFO bo be able to catch route update messages from orchagent + command: "swssloglevel -l INFO -c orchagent" + +- name: Define stalepath time. Should be at least 30 seconds grater than graceful restart time. + set_fact: + bgp_gr_stalepath_timer: "{{ (bgp_gr_timer|int + 30) }}" + +- name: Set bgp stalepath timer. + command: vtysh -c "conf t" -c "router bgp {{ minigraph_bgp_asn }}" -c "bgp graceful-restart stalepath-time {{ bgp_gr_stalepath_timer }}" + +# When RIBD up and send bgp open message it will set F bit to 1. Which means that during restart +# all routes were preserved in FIB. When DUT receives open message with F bit set to 1 it also +# should preserve all routes (no route update should happens). +- name: Force stop RIBD to simulate GR. + shell: "killall -9 ribd; sleep 0.5; ifconfig et1 down" + delegate_to: "{{ vm_ip }}" + +- name: Simulate GR. + pause: + seconds: "{{ bgp_gr_simulation_timer if (bgp_gr_timer|int - 30) > bgp_gr_simulation_timer else (bgp_gr_timer|int - 30) }}" + +- name: Up interface to allow RIBD to send open message. End of GR. + command: ifconfig et1 up + delegate_to: "{{ vm_ip }}" + +- name: Wait for BGP session state update. + pause: + seconds: 2 + +- name: Gather facts from bgp container. + bgp_facts: + +- name: Verify bgp session is established + assert: { that: "'{{ bgp_neighbors[peer_ip]['state'] }}' == 'established'" } + +- name: Verify that IPv4 unicast routes were preserved during GR. + assert: { that: "'{{ bgp_neighbors[peer_ip]['capabilities']['peer af ipv4 unicast'] }}' == 'preserved'" } + +- include: roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml + vars: + test_match_file: routes_update_match.txt + +- include: roles/test/files/tools/loganalyzer/loganalyzer_init.yml + vars: + test_expect_file: routes_update_expect.txt + +- name: Restart the VM + shell: killall -9 ribd ; reboot + delegate_to: "{{ vm_ip }}" + +- local_action: wait_for port=22 host="{{ vm_ip }}" delay=20 timeout="{{ bgp_gr_timer }}" state=started + +- name: Wait for BGP session state update. + pause: + seconds: 2 + +- name: Gather facts from bgp container. + bgp_facts: + +- name: Verify bgp session is established + assert: { that: "'{{ bgp_neighbors[peer_ip]['state'] }}' == 'established'" } + +- name: Verify that IPv4 unicast routes were not preserved during GR. FIB should be updated. + assert: { that: "'{{ bgp_neighbors[peer_ip]['capabilities']['peer af ipv4 unicast'] }}' == 'not preserved'" } + +- include: roles/test/files/tools/loganalyzer/loganalyzer_analyze.yml + vars: + test_expect_file: routes_update_expect.txt diff --git a/ansible/roles/test/tasks/bgp_gr_helper/get_vm_info.yml b/ansible/roles/test/tasks/bgp_gr_helper/get_vm_info.yml new file mode 100644 index 00000000000..188d31b83d3 --- /dev/null +++ b/ansible/roles/test/tasks/bgp_gr_helper/get_vm_info.yml @@ -0,0 +1,39 @@ +- name: Gathering lab graph facts about the device + conn_graph_facts: host={{ ansible_host }} + connection: local + tags: always + +- name: Init variables. + set_fact: + vm_name: "" + vm_intf: "" + vm_ip: "" + peer_ip: "" + +- name: Get neighbor VM info. + set_fact: + vm_name: "{{ item.value.name }}" + vm_intf: "{{ item.key }}" + with_dict: "{{ minigraph_neighbors }}" + when: "testbed_type in ['t1-lag', 't1'] and 'T0' in item.value.name and not vm_name" + +- name: Get neighbor IP address. + set_fact: + peer_ip: "{{ item.addr }}" + with_items: "{{ minigraph_bgp }}" + when: "item.name == vm_name and item.addr|ipv4" + +- name: Gather information from LLDP + include: "roles/test/tasks/lldp.yml" + +- name: Get VM IP address. + set_fact: + vm_ip: "{{ lldp[vm_intf]['chassis']['mgmt-ip'] }}" + +- debug: + var: vm_name +- debug: + var: vm_intf +- debug: + var: vm_ip + diff --git a/ansible/roles/test/tasks/bgp_gr_helper/routes_update_expect.txt b/ansible/roles/test/tasks/bgp_gr_helper/routes_update_expect.txt new file mode 100644 index 00000000000..a7d15390b5f --- /dev/null +++ b/ansible/roles/test/tasks/bgp_gr_helper/routes_update_expect.txt @@ -0,0 +1 @@ +r, ".*Set Route next hop.*" diff --git a/ansible/roles/test/tasks/bgp_gr_helper/routes_update_match.txt b/ansible/roles/test/tasks/bgp_gr_helper/routes_update_match.txt new file mode 100644 index 00000000000..a36e7b421af --- /dev/null +++ b/ansible/roles/test/tasks/bgp_gr_helper/routes_update_match.txt @@ -0,0 +1,3 @@ +r, ".*Remove route.*" +r, ".*Create route.*" +r, ".*Set route.*" diff --git a/ansible/roles/test/tasks/sonic.yml b/ansible/roles/test/tasks/sonic.yml index f0b670738a4..8268d3b2c63 100644 --- a/ansible/roles/test/tasks/sonic.yml +++ b/ansible/roles/test/tasks/sonic.yml @@ -155,3 +155,7 @@ - name: ACL test include: acltb.yml tags: acl + +- name: BGP GR helper test + include: bgp_gr_helper.yml + tags: bgp_gr_helper