diff --git a/ansible/README.test.md b/ansible/README.test.md index 7774b4e5b49..4a781021a9b 100644 --- a/ansible/README.test.md +++ b/ansible/README.test.md @@ -160,6 +160,13 @@ ansible-playbook test_sonic.yml -i {INVENTORY} --limit {DUT_NAME} -e testcase_na ``` - Requires switch connected to a PTF testbed +##### Incremental Config Test +``` +ansible-playbook test_sonic.yml -i {INVENTORY} --limit {DUT_NAME} -e testcase_name=incremental_config -e testbed_name={TESTBED_NAME} +``` +- Requires 2 active Ethernet interfaces on the DUT + + ##### LAG test ``` ansible-playbook test_sonic.yml -i {INVENTORY} --limit {DUT_NAME} -e testcase_name=lag_2 -e testbed_name={TESTBED_NAME} diff --git a/ansible/roles/test/tasks/incremental_config.yml b/ansible/roles/test/tasks/incremental_config.yml new file mode 100644 index 00000000000..831982fc0ff --- /dev/null +++ b/ansible/roles/test/tasks/incremental_config.yml @@ -0,0 +1,60 @@ +- debug: msg="Tests for the incremental configuration features of the cli" + +- name: gather interface facts + interface_facts: + +- name: set initial variables + set_fact: + test_ipv4_address: 10.200.4.5/24 + test_ipv6_address: fc00::aa07/126 + test_port_channel_name: PortChannel0999 + test_vlan: 999 + +- name: Get a list of active Ethernet interface names + set_fact: + test_interfaces: "{{ test_interfaces|default([]) }} + ['{{ item.device }}']" + when: item.active == True and "Ethernet" in item.device + with_items: "{{ ansible_interface_facts.values() }}" + +- name: verify that there are at least 2 interfaces that we can test with + assert: + that: (test_interfaces | length) >= 2 + msg: This test requires at least two active Ethernet interfaces + +- name: Add and remove an IPv4 address from an interface + include: incremental_config/incremental_config_ip.yml + vars: + ip_type: 'ipv4' + address: "{{ test_ipv4_address }}" + interface: "{{ test_interfaces[0] }}" + +- name: Add and remove an IPv6 address from an interface + include: incremental_config/incremental_config_ip.yml + vars: + ip_type: 'ipv6' + address: "{{ test_ipv6_address }}" + interface: "{{ test_interfaces[0] }}" + +- name: Add and remove VLAN membership from an interface + include: incremental_config/incremental_config_vlan.yml + vars: + interface: "{{ test_interfaces[0] }}" + +- name: Add and remove a portchannel interface + include: incremental_config/incremental_config_portchannel.yml + vars: + name: "{{ test_port_channel_name }}" + members: "{{ test_interfaces[:2] }}" + +- name: Add and remove some acl rules via the incremental updated command + include: incremental_config/incremental_config_acl_incremental_update.yml + +- name: Do basic sanity check and allow recovery + include: base_sanity.yml + vars: + recover: "{{ allow_recover }}" + +- name: Validate all interfaces are up and allow recovery + include: interface.yml + vars: + recover: "{{ allow_recover }}" diff --git a/ansible/roles/test/tasks/incremental_config/ic_test_rules.json b/ansible/roles/test/tasks/incremental_config/ic_test_rules.json new file mode 100644 index 00000000000..4106b702e88 --- /dev/null +++ b/ansible/roles/test/tasks/incremental_config/ic_test_rules.json @@ -0,0 +1,241 @@ +{ + "acl": { + "acl-sets": { + "acl-set": { + "dataacl": { + "acl-entries": { + "acl-entry": { + "1": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 1 + }, + "ip": { + "config": { + "source-ip-address": "10.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": "4661" + } + } + }, + "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_SYN"] + } + } + }, + "7": { + "actions": { + "config": { + "forwarding-action": "DROP" + } + }, + "config": { + "sequence-id": 7 + }, + "ip": { + "config": { + "source-ip-address": "10.0.0.3/32" + } + } + }, + "8": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 8 + }, + "ip": { + "config": { + "source-ip-address": "10.0.0.3/32" + } + } + }, + "9": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 9 + }, + "transport": { + "config": { + "destination-port": "4661" + } + } + }, + "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": "10.0.0.2/32" + } + } + }, + "13": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 13 + }, + "ip": { + "config": { + "protocol":17, + "source-ip-address": "10.0.0.2/32" + } + } + }, + "14": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 27 + }, + "transport": { + "config": { + "source-port": "179" + } + } + }, + "15": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 28 + }, + "transport": { + "config": { + "destination-port": "179" + } + } + } + } + } + } + } + } + } +} diff --git a/ansible/roles/test/tasks/incremental_config/ic_test_rules_allow_all.json b/ansible/roles/test/tasks/incremental_config/ic_test_rules_allow_all.json new file mode 100644 index 00000000000..072653ba557 --- /dev/null +++ b/ansible/roles/test/tasks/incremental_config/ic_test_rules_allow_all.json @@ -0,0 +1,29 @@ +{ + "acl": { + "acl-sets": { + "acl-set": { + "dataacl": { + "acl-entries": { + "acl-entry": { + "1": { + "actions": { + "config": { + "forwarding-action": "ACCEPT" + } + }, + "config": { + "sequence-id": 1 + }, + "l2": { + "config": { + "ethertype": "2048" + } + } + } + } + } + } + } + } + } +} diff --git a/ansible/roles/test/tasks/incremental_config/ic_test_rules_del.json b/ansible/roles/test/tasks/incremental_config/ic_test_rules_del.json new file mode 100644 index 00000000000..71ba44005e9 --- /dev/null +++ b/ansible/roles/test/tasks/incremental_config/ic_test_rules_del.json @@ -0,0 +1,9 @@ +{ + "acl": { + "acl-sets": { + "acl-set": { + } + } + } +} + diff --git a/ansible/roles/test/tasks/incremental_config/incremental_config_acl_incremental_update.yml b/ansible/roles/test/tasks/incremental_config/incremental_config_acl_incremental_update.yml new file mode 100644 index 00000000000..a30a2bfabbe --- /dev/null +++ b/ansible/roles/test/tasks/incremental_config/incremental_config_acl_incremental_update.yml @@ -0,0 +1,65 @@ +- block: + - name: create destination directory + file: + path: ~/incremental_config + state: directory + + - name: copy test files + copy: + src: "{{ item }}" + dest: ~/incremental_config/ + with_fileglob: + - roles/test/tasks/incremental_config/*.json + + - name: get the current state of the roles + shell: show acl rule + register: out + + - name: verify no entries + assert: + that: out.stdout_lines | length == 2 + + - name: add initial role to the device + shell: config acl update incremental incremental_config/ic_test_rules.json + become: yes + + - name: get the current state of the roles + shell: show acl rule + register: out + + - name: verify the entry was added as expected + assert: + that: + - "'DATAACL' in out.stdout" + - "'DEFAULT_RULE' in out.stdout" + - out.stdout_lines | length == 20 + + - name: update the role to allow everything + shell: config acl update incremental incremental_config/ic_test_rules_allow_all.json + become: yes + + - name: get the current state of the roles + shell: show acl rule + register: out + + - name: verify the entry was added as expected + assert: + that: + - "'DATAACL' in out.stdout" + - "'DEFAULT_RULE' in out.stdout" + - out.stdout_lines | length == 4 + + rescue: + - name: A failure occured + debug: + msg: A failure occured while trying to run the commands, examine the debug logs + + always: + - name: delete the acl + shell: config acl update incremental incremental_config/ic_test_rules_del.json + become: yes + + - name: Delete the files + file: + state: absent + path: ~/incremental_config diff --git a/ansible/roles/test/tasks/incremental_config/incremental_config_ip.yml b/ansible/roles/test/tasks/incremental_config/incremental_config_ip.yml new file mode 100644 index 00000000000..582f06a61a9 --- /dev/null +++ b/ansible/roles/test/tasks/incremental_config/incremental_config_ip.yml @@ -0,0 +1,47 @@ +- debug: msg="Starting ip incremental config test" + +- name: fail if environment is not set + fail: msg="the ip_type variables is not set" + when: ip_type | default('') not in ['ipv4', 'ipv6'] + +- name: set show command for ipv4 + set_fact: + show_command: "ip" + when: ip_type == "ipv4" + +- name: set show command for ipv6 + set_fact: + show_command: "ipv6" + when: ip_type == "ipv6" + + +# Add and remove an ip address from an interface +- block: + - name: add IP to interface + shell: config interface ip add {{ interface }} {{ address }} + become: yes + + - name: confirm IP was added to the interface + shell: show {{ show_command }} interfaces | grep {{ address }} + register: out + + - name: verify that the IP shows up in the output + assert: + that: out.stdout | search(address) + + - name: remove IP from the interface + shell: config interface ip remove {{ interface }} {{ address }} + become: yes + + - name: confirm IP was removed from the interface + shell: show {{ show_command }} interfaces | grep {{ address }} || true + register: out + + - name: verify that the IP does not show up in the output + assert: + that: not out.stdout | search(address) + + rescue: + - name: remove IP from the interface on failure + shell: config interface ip remove {{ interface }} {{ address }} + become: yes diff --git a/ansible/roles/test/tasks/incremental_config/incremental_config_portchannel.yml b/ansible/roles/test/tasks/incremental_config/incremental_config_portchannel.yml new file mode 100644 index 00000000000..c756fc92a14 --- /dev/null +++ b/ansible/roles/test/tasks/incremental_config/incremental_config_portchannel.yml @@ -0,0 +1,87 @@ +- debug: msg="Starting port channel incremental config test" + +- name: set initial variables for portchannel test + set_fact: + ethernet_portchannel_interfaces: [] + + +# Add a portchannel +- block: + - name: add a port channel + shell: config portchannel add {{ name }} + become: yes + + - name: pull current portchannel status + shell: show interfaces portchannel + register: out + + - name: verify that the portchannel shows up in the output + assert: + that: out.stdout | search(name) + + - name: add members to the portchannel + shell: config portchannel member add {{ name }} {{ item }} + become: yes + with_items: "{{ members }}" + + - name: pull current portchannel status + shell: show interfaces portchannel + register: out + + - name: verify that the members show up in the output + assert: + that: out.stdout | search(item) + with_items: members + + - name: pull out lines from output for the portchannel ports + set_fact: + ethernet_portchannel_interfaces: "{{ ethernet_portchannel_interfaces }} + ['{{ item }}']" + when: "{{ item | search(name) }}" + with_items: "{{ out.stdout_lines }}" + + - name: check the condensed list and confirm each port channel member is in the list + assert: + that: ethernet_portchannel_interfaces | select('match', item) + with_items: "{{ members }}" + + - name: remove the members from the port channel + shell: config portchannel member del {{ name }} {{ item }} + become: yes + with_items: "{{ members }}" + + - name: verify the ports have been removed + shell: show interfaces portchannel + register: out + + - name: verify the portchannel name no longer shows up in the output + assert: + that: item not in out.stdout + with_items: members + + - name: remove the portchannel + shell: config portchannel del {{ name }} + become: yes + + - name: verify the portchannel has been removed + shell: show interfaces portchannel + register: out + + - name: verify that the portchannel is not configured + assert: + that: not out.stdout | search( name ) + + rescue: + - name: pull output from switch + shell: show interface portchannel + register: out + + - name: remove portchannel members on failure + shell: config portchannel member del {{ name }} {{ item }} + become: yes + with_items: "{{ members }}" + when: item in out.stdout + + - name: remove the portchannel on failure + shell: config portchannel del {{ name }} + become: yes + when: name in out.stdout diff --git a/ansible/roles/test/tasks/incremental_config/incremental_config_vlan.yml b/ansible/roles/test/tasks/incremental_config/incremental_config_vlan.yml new file mode 100644 index 00000000000..e964710ba53 --- /dev/null +++ b/ansible/roles/test/tasks/incremental_config/incremental_config_vlan.yml @@ -0,0 +1,44 @@ +# Test the CLI commands to add/remove VLAN membership from an interface +- debug: msg="Starting vlan incremental config test" + +- block: + - name: add vlan + shell: config vlan add {{ test_vlan }} + become: yes + + - name: confirm vlan was added + shell: show vlan brief + register: out + failed_when: not out.stdout | search(test_vlan | string) + + - name: Make the test interface a member of the VLAN + shell: config vlan member add {{ test_vlan }} {{ interface }} + become: yes + + - name: Verify that the test interface is a member of the VLAN + shell: show vlan config | grep Vlan{{ test_vlan }} + register: out + failed_when: not out.stdout | search(interface) + + - name: Remove the VLAN membership from the test interface + shell: config vlan member del {{ test_vlan }} {{ interface }} + become: yes + + - name: Verify that the test interface is not a member of the VLAN + shell: show vlan config | grep Vlan{{ test_vlan }} + register: out + failed_when: out.stdout | search(interface) + + - name: Remove the VLAN + shell: config vlan del {{ test_vlan }} + become: yes + + - name: Confirm that the VLAN was deleted + shell: show vlan brief + register: out + failed_when: out.stdout | search(test_vlan | string) + + rescue: + - name: Remove the VLAN + shell: config vlan del {{ test_vlan }} + become: yes diff --git a/ansible/roles/test/vars/testcases.yml b/ansible/roles/test/vars/testcases.yml index ee80eb25424..ab709568c9c 100644 --- a/ansible/roles/test/vars/testcases.yml +++ b/ansible/roles/test/vars/testcases.yml @@ -80,6 +80,10 @@ testcases: ptf_host: vm_hosts: + incremental_config: + filename: incremental_config.yml + topologies: [t0, t0-16, t0-56, t0-64, t0-116, t1] + warm-reboot: filename: warm-reboot.yml topologies: [t0, t0-64, t0-64-32, t0-116]