Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GCU portchannel interface test #4875

Merged
merged 3 commits into from
Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/generic_config_updater/test_cacl.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
def get_cacl_tables(duthost):
"""Get acl control palne tables
"""
cmds = "show acl table | grep -w CTRLPLANE | awk {'print $1'}"
cmds = "show acl table | grep -w CTRLPLANE | awk '{print $1}'"
output = duthost.shell(cmds)
pytest_assert(not output['rc'],
"'{}' failed with rc={}".format(cmds, output['rc'])
Expand Down
359 changes: 359 additions & 0 deletions tests/generic_config_updater/test_portchannel_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,359 @@
import logging
import pytest

from tests.common.helpers.assertions import pytest_assert
from tests.generic_config_updater.gu_utils import apply_patch, expect_op_success, expect_op_failure
from tests.generic_config_updater.gu_utils import generate_tmpfile, delete_tmpfile
from tests.generic_config_updater.gu_utils import create_checkpoint, delete_checkpoint, rollback_or_reload
from tests.generic_config_updater.gu_utils import create_path, check_show_ip_intf

# Test on t0 topo to verify functionality and to choose predefined variable
# "PORTCHANNEL_INTERFACE": {
# "PortChannel0001": {},
# "PortChannel0001|10.0.0.56/31": {},
# "PortChannel0001|FC00::71/126": {},
# "PortChannel0002": {},
# "PortChannel0002|10.0.0.58/31": {},
# "PortChannel0002|FC00::75/126": {},
# "PortChannel0003": {},
# "PortChannel0003|10.0.0.60/31": {},
# "PortChannel0003|FC00::79/126": {},
# "PortChannel0004": {},
# "PortChannel0004|10.0.0.62/31": {},
# "PortChannel0004|FC00::7D/126": {}
# }

pytestmark = [
pytest.mark.topology('t0'),
Copy link
Contributor

@qiluo-msft qiluo-msft Dec 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

t0

also test on t1-lag? If you hardcoded some logic based on t0, is it possible to adapt to any topo? If not, we need to consider add test case sepately for t1-lag. #Pending

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you find me a t1-lag topo? I can check if the test can be adopted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add t1-lag in another PR.
In this testcase, we are actually modifying the interface, like ip and attributes(mtu, min-link). Topo should not make any difference right?

]

logger = logging.getLogger(__name__)

T0_PORTCHANNEL_TABLE = {
"PortChannel0001": {
"ip": "10.0.0.56/31",
"ipv6": "fc00::71/126"
},
"PortChannel0002": {
"ip": "10.0.0.58/31",
"ipv6": "fc00::75/126"
},
"PortChannel0003": {
"ip": "10.0.0.60/31",
"ipv6": "fc00::79/126"
},
"PortChannel0004": {
"ip": "10.0.0.62/31",
"ipv6": "fc00::7d/126"
}
}

def check_portchannel_table(duthost):
"""This is to check if portchannel interfaces are the same as t0 initial setup
"""
for portchannel_name, ips in T0_PORTCHANNEL_TABLE.items():
check_show_ip_intf(duthost, portchannel_name, [ips['ip']], [], is_ipv4=True)
check_show_ip_intf(duthost, portchannel_name, [ips['ipv6']], [], is_ipv4=False)

@pytest.fixture(autouse=True)
def setup_env(duthosts, rand_one_dut_hostname):
"""
Setup/teardown fixture for portchannel interface config
Args:
duthosts: list of DUTs.
rand_selected_dut: The fixture returns a randomly selected DuT.
"""
duthost = duthosts[rand_one_dut_hostname]
create_checkpoint(duthost)

yield

try:
logger.info("Rolled back to original checkpoint")
rollback_or_reload(duthost)
check_portchannel_table(duthost)
finally:
delete_checkpoint(duthost)

def test_portchannel_interface_tc1_add_new_portchannel(duthost):
""" Clean up original portchannel intf and apply-patch to default config

Expected output
admin@vlab-01:~$ show ip interfaces
Interface Master IPv4 address/mask Admin/Oper BGP Neighbor Neighbor IP
--------------- -------- ------------------- ------------ -------------- -------------
...
PortChannel0005 10.0.0.64/31 up/down N/A N/A
admin@vlab-01:~$ show ipv6 interfaces
Interface Master IPv4 address/mask Admin/Oper BGP Neighbor Neighbor IP
--------------- --------------- ------------------------------------------ ------------ -------------- -------------
...
PortChannel0005 fc00::81/126 up/down N/A N/A
"""

json_patch = [
{
"op": "add",
"path": "/PORTCHANNEL/PortChannel0005",
"value": {
"admin_status": "up"
}
},
{
"op": "add",
"path": "/PORTCHANNEL_INTERFACE/PortChannel0005",
"value": {}
},
{
"op": "add",
"path": create_path(["PORTCHANNEL_INTERFACE", "PortChannel0005|10.0.0.64/31"]),
"value": {}
},
{
"op": "add",
"path": create_path(["PORTCHANNEL_INTERFACE", "PortChannel0005|FC00::81/126"]),
"value": {}
}
]

logger.info("json patch {}".format(json_patch))

tmpfile = generate_tmpfile(duthost)
logger.info("tmpfile {}".format(tmpfile))

try:
output = apply_patch(duthost, json_data=json_patch, dest_file=tmpfile)
expect_op_success(duthost, output)

check_show_ip_intf(duthost, "PortChannel0005", ["10.0.0.64/31"], [], is_ipv4=True)
check_show_ip_intf(duthost, "PortChannel0005", ["fc00::81/126"], [], is_ipv4=False)
finally:
delete_tmpfile(duthost, tmpfile)

def test_portchannel_interface_tc2_add_duplicate(duthost):
""" Test adding duplicate portchannel interface
"""
json_patch = [
{
"op": "add",
"path": create_path(["PORTCHANNEL_INTERFACE", "PortChannel0001|10.0.0.56/31"]),
"value": {}
},
{
"op": "add",
"path": create_path(["PORTCHANNEL_INTERFACE", "PortChannel0001|FC00::71/126"]),
"value": {}
}
]

logger.info("json patch {}".format(json_patch))

tmpfile = generate_tmpfile(duthost)
logger.info("tmpfile {}".format(tmpfile))

try:
output = apply_patch(duthost, json_data=json_patch, dest_file=tmpfile)
expect_op_success(duthost, output)

check_show_ip_intf(duthost, "PortChannel0001", ["10.0.0.56/31"], [], is_ipv4=True)
check_show_ip_intf(duthost, "PortChannel0001", ["fc00::71/126"], [], is_ipv4=False)
finally:
delete_tmpfile(duthost, tmpfile)

@pytest.mark.parametrize("op, name, dummy_portchannel_interface_v4, dummy_portchannel_interface_v6", [
("add", "PortChannel0001", "10.0.0.256/31", "FC00::71/126"),
("add", "PortChannel0001", "10.0.0.56/31", "FC00::xyz/126"),
("remove", "PortChannel0001", "10.0.0.57/31", "FC00::71/126"),
("remove", "PortChannel0001", "10.0.0.56/31", "FC00::72/126")
])
def test_portchannel_interface_tc2_xfail(duthost, op, name,
dummy_portchannel_interface_v4, dummy_portchannel_interface_v6):
""" Test invalid ip address and remove unexited interface

("add", "PortChannel0001", "10.0.0.256/31", "FC00::71/126"), ADD Invalid IPv4 address
("add", "PortChannel0001", "10.0.0.56/31", "FC00::xyz/126"), ADD Invalid IPv6 address
("remove", "PortChannel0001", "10.0.0.57/31", "FC00::71/126"), REMOVE Unexist IPv4 address
("remove", "PortChannel0001", "10.0.0.56/31", "FC00::72/126"), REMOVE Unexist IPv6 address
"""

dummy_portchannel_interface_v4 = name + "|" + dummy_portchannel_interface_v4
dummy_portchannel_interface_v6 = name + "|" + dummy_portchannel_interface_v6
json_patch = [
{
"op": "{}".format(op),
"path": create_path(["PORTCHANNEL_INTERFACE", dummy_portchannel_interface_v4]),
"value": {}
},
{
"op": "{}".format(op),
"path": create_path(["PORTCHANNEL_INTERFACE", dummy_portchannel_interface_v6]),
"value": {}
}
]

tmpfile = generate_tmpfile(duthost)
logger.info("tmpfile {}".format(tmpfile))

try:
output = apply_patch(duthost, json_data=json_patch, dest_file=tmpfile)
expect_op_failure(output)
finally:
delete_tmpfile(duthost, tmpfile)

def test_portchannel_interface_tc3_replace(duthost):
""" Test portchannel interface replace ip address
"""
json_patch = [
{
"op": "remove",
"path": create_path(["PORTCHANNEL_INTERFACE", "PortChannel0001|FC00::71/126"]),
},
{
"op": "remove",
"path": create_path(["PORTCHANNEL_INTERFACE", "PortChannel0001|10.0.0.56/31"]),
},
{
"op": "add",
"path": create_path(["PORTCHANNEL_INTERFACE", "PortChannel0001|10.0.0.156/31"]),
"value": {}
},
{
"op": "add",
"path": create_path(["PORTCHANNEL_INTERFACE", "PortChannel0001|FC00::171/126"]),
"value": {}
}
]

logger.info("json patch {}".format(json_patch))

tmpfile = generate_tmpfile(duthost)
logger.info("tmpfile {}".format(tmpfile))

try:
output = apply_patch(duthost, json_data=json_patch, dest_file=tmpfile)
expect_op_success(duthost, output)

check_show_ip_intf(duthost, "PortChannel0001", ["10.0.0.156/31"], ["10.0.0.56/31"], is_ipv4=True)
check_show_ip_intf(duthost, "PortChannel0001", ["fc00::171/126"], ["fc00::71/126"], is_ipv4=False)
finally:
delete_tmpfile(duthost, tmpfile)

def test_portchannel_interface_tc4_remove(duthost):
""" Test remove all portchannel intf
"""
json_patch = [
{
"op": "remove",
"path": "/PORTCHANNEL_INTERFACE"
}
]

tmpfile = generate_tmpfile(duthost)
logger.info("tmpfile {}".format(tmpfile))

try:
output = apply_patch(duthost, json_data=json_patch, dest_file=tmpfile)
expect_op_success(duthost, output)

for portchannel_name, ips in T0_PORTCHANNEL_TABLE.items():
check_show_ip_intf(duthost, portchannel_name, [], [ips['ip']], is_ipv4=True)
check_show_ip_intf(duthost, portchannel_name, [], [ips['ipv6']], is_ipv4=False)
finally:
delete_tmpfile(duthost, tmpfile)

def verify_po_running(duthost):
for portchannel_name in T0_PORTCHANNEL_TABLE:
cmds = 'teamdctl {} state dump | python -c "import sys, json; print(json.load(sys.stdin)[\'runner\'][\'active\'])"'.format(portchannel_name)
output = duthost.shell(cmds, module_ignore_errors=True)

pytest_assert(
not output['rc'] or output['stdout'] != 'True',
"{} is not running correctly."
)

def verify_attr_change(duthost, name, attr, value):
"""
attr:
mtu: check if "mtu 3324" exists
admin@vlab-01:~$ show interfaces status | grep -w ^PortChannel0001
PortChannel0001 N/A 40G 3324 N/A N/A routed up up N/A N/A
min_links:
TODO: further check
admin_status: check if 3rd column start with "down"
admin@vlab-01:~/lag$ show ip interfaces
Interface Master IPv4 address/mask Admin/Oper BGP Neighbor Neighbor IP
--------------- -------- ------------------- ------------ -------------- -------------
...
PortChannel0001 10.0.0.56/31 up/up ARISTA01T1 10.0.0.57
...
"""
if attr == "mtu":
output = duthost.shell("show interfaces status | grep -w '^{}' | awk '{{print $4}}'".format(name))

pytest_assert(output['stdout'] == value,
"{} attribute {} failed to change to {}".format(name, attr, value)
)
elif attr == "min_links":
pass
elif attr == "admin_status":
output = duthost.shell("show ip interfaces | grep -w '{}' | awk '{{print $3}}'".format(name))

pytest_assert(output['stdout'].startswith(value),
"{} {} change failed".format(name, attr)
)

@pytest.mark.parametrize("op, name, attr, value", [
("replace", "PortChannel0001", "mtu", "3324"),
("replace", "PortChannel0001", "min_links", "2"),
("replace", "PortChannel0001", "admin_status", "down")
])
def test_portchannel_interface_tc5_modify_attribute(duthost, op, name, attr, value):
"""Test PortChannelXXXX attribute change

("replace", "PortChannel0001", "mtu", "3324"), mtu change
("replace", "PortChannel0001", "min_links", "2"), min_link change
("replace", "PortChannel0001", "admin_status", "down"), admin_status change
"""
json_patch = [
{
"op": "{}".format(op),
"path": "/PORTCHANNEL/{}/{}".format(name, attr),
"value": "{}".format(value)
}
]

logger.info("json patch {}".format(json_patch))

tmpfile = generate_tmpfile(duthost)
logger.info("tmpfile {}".format(tmpfile))

try:
output = apply_patch(duthost, json_data=json_patch, dest_file=tmpfile)
expect_op_success(duthost, output)

verify_po_running(duthost)
verify_attr_change(duthost, name, attr, value)
Copy link
Contributor

@qiluo-msft qiluo-msft Dec 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify_attr_change

For end-to-end test, we could also verify that teamd processes (per port channel) are running without crash. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about checking portchannel pid?
docker exec -it teamd test -f /var/run/teamd/PortChannel0001.pid

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking process such as command ps is better. There is risk that the process crashed but the pid file is still there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to state dump verification

finally:
delete_tmpfile(duthost, tmpfile)

def test_portchannel_interface_tc6_incremental_change(duthost):
"""Test PortChannelXXXX incremental change
"""
json_patch = [
{
"op": "add",
"path": "/PORTCHANNEL/PortChannel0001/description",
"value": "Description for PortChannel0001"
}
]

logger.info("json patch {}".format(json_patch))

tmpfile = generate_tmpfile(duthost)
logger.info("tmpfile {}".format(tmpfile))

try:
output = apply_patch(duthost, json_data=json_patch, dest_file=tmpfile)
expect_op_success(duthost, output)
finally:
delete_tmpfile(duthost, tmpfile)