Skip to content

Commit

Permalink
[Multi asic]:parameterize asic_index and dut_index (#2245)
Browse files Browse the repository at this point in the history
In case of SONiC on multi asic platforms, each asic is bound to a namespace. The dataplane and control plane containers are replicated per namespace. The tests which are testing functionality within a namespace, can be repeated on all namespaces by passing asic_index as parameter to the test. This way the testing logic remains. The test will be repeated N times, where the N is the num_asics.

Recently sonic-mgmt introduced the support for multiple duts in the topology. We are proposing to pass dut_index as parameter to the testcase. So, the test will be repeated on each dut in the topology.

If asic_index and dut_index is used in the test, the test will be repeated on each asic of each dut in the topology.

Signed-off-by: Arvindsrinivasan Lakshminarasimhan <arlakshm@microsoft.com>
  • Loading branch information
arlakshm authored Oct 27, 2020
1 parent 6ae107b commit 4703756
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 18 deletions.
13 changes: 8 additions & 5 deletions ansible/library/config_facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ def create_maps(config):
}


def get_running_config(module):

rt, out, err = module.run_command("sonic-cfggen -d --print-data")
def get_running_config(module, namespace):
cmd = "sonic-cfggen -d --print-data"
if namespace:
cmd += " -n {}".format(namespace)
rt, out, err = module.run_command(cmd)
if rt != 0:
module.fail_json(msg="Failed to dump running config! {}".format(err))
json_info = json.loads(out)
Expand All @@ -111,14 +113,15 @@ def main():
host=dict(required=True),
source=dict(required=True, choices=["running", "persistent"]),
filename=dict(),
namespace=dict(default=None),
),
supports_check_mode=True
)

m_args = module.params
try:
config = {}

namespace = m_args['namespace']
if m_args["source"] == "persistent":
if 'filename' in m_args and m_args['filename'] is not None:
cfg_file_path = "%s" % m_args['filename']
Expand All @@ -127,7 +130,7 @@ def main():
with open(cfg_file_path, "r") as f:
config = json.load(f)
elif m_args["source"] == "running":
config = get_running_config(module)
config = get_running_config(module, namespace)
results = get_facts(config)
module.exit_json(ansible_facts=results)
except Exception as e:
Expand Down
24 changes: 12 additions & 12 deletions tests/bgp/test_bgp_fact.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@
pytest.mark.device_type('vs')
]

def test_bgp_facts(duthost):
def test_bgp_facts(duthosts, dut_index, asic_index):
"""compare the bgp facts between observed states and target state"""

bgp_facts = duthost.bgp_facts()['ansible_facts']
mg_facts = duthost.minigraph_facts(host=duthost.hostname)['ansible_facts']
duthost = duthosts[dut_index]
bgp_facts =duthost.bgp_facts(instance_id=asic_index)['ansible_facts']
namespace = duthost.get_namespace_from_asic_id(asic_index)
config_facts = duthost.config_facts(host=duthost.hostname, source="running",namespace=namespace)['ansible_facts']

for k, v in bgp_facts['bgp_neighbors'].items():
# Verify bgp sessions are established
assert v['state'] == 'established'
# Verify locat ASNs in bgp sessions
assert v['local AS'] == mg_facts['minigraph_bgp_asn']
# Check bgpmon functionality by validate STATE DB contains this neighbor as well
state_fact = duthost.shell('sonic-db-cli STATE_DB HGET "NEIGH_STATE_TABLE|{}" "state"'.format(k), module_ignore_errors=False)['stdout_lines']
assert state_fact[0] == "Established"
assert v['local AS'] == int(config_facts['DEVICE_METADATA']['localhost']['bgp_asn'].decode("utf-8"))

for k, v in config_facts['BGP_NEIGHBOR'].items():
# Compare the bgp neighbors name with config db bgp neigbhors name
assert v['name'] == bgp_facts['bgp_neighbors'][k]['description']
# Compare the bgp neighbors ASN with config db
assert int(v['asn'].decode("utf-8")) == bgp_facts['bgp_neighbors'][k]['remote AS']

for v in mg_facts['minigraph_bgp']:
# Compare the bgp neighbors name with minigraph bgp neigbhors name
assert v['name'] == bgp_facts['bgp_neighbors'][v['addr'].lower()]['description']
# Compare the bgp neighbors ASN with minigraph
assert v['asn'] == bgp_facts['bgp_neighbors'][v['addr'].lower()]['remote AS']
7 changes: 6 additions & 1 deletion tests/common/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from errors import RunAnsibleModuleFail
from errors import UnsupportedAnsibleModule

from tests.common.helpers.constants import DEFAULT_ASIC_ID, DEFAULT_NAMESPACE, NAMESPACE_PREFIX

# HACK: This is a hack for issue https://github.com/Azure/sonic-mgmt/issues/1941 and issue
# https://github.com/ansible/pytest-ansible/issues/47
Expand Down Expand Up @@ -1038,6 +1038,11 @@ def show_and_parse(self, show_cmd, **kwargs):
"""
output = self.shell(show_cmd, **kwargs)["stdout_lines"]
return self._parse_show(output)

def get_namespace_from_asic_id(self, asic_id):
if asic_id is DEFAULT_ASIC_ID:
return DEFAULT_NAMESPACE
return "{}{}".format(NAMESPACE_PREFIX, asic_id)


class EosHost(AnsibleHostBase):
Expand Down
6 changes: 6 additions & 0 deletions tests/common/helpers/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

DEFAULT_ASIC_ID = None
DEFAULT_NAMESPACE = None
NAMESPACE_PREFIX = 'asic'
ASIC_PARAM_TYPE_ALL = 'num_asics'
ASIC_PARAM_TYPE_FRONTEND = 'frontend_asics'
62 changes: 62 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@
import yaml
import jinja2
import ipaddr as ipaddress
from ansible.parsing.dataloader import DataLoader
from ansible.inventory.manager import InventoryManager

from collections import defaultdict
from datetime import datetime
from tests.common.fixtures.conn_graph_facts import conn_graph_facts
from tests.common.devices import SonicHost, Localhost
from tests.common.devices import PTFHost, EosHost, FanoutHost
from tests.common.helpers.constants import ASIC_PARAM_TYPE_ALL, ASIC_PARAM_TYPE_FRONTEND, DEFAULT_ASIC_ID

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -430,4 +433,63 @@ def tag_test_report(request, pytestconfig, tbinfo, duthost, record_testsuite_pro
record_testsuite_property("hwsku", duthost.facts["hwsku"])
record_testsuite_property("os_version", duthost.os_version)

def get_host_data(request, dut):
'''
This function parses multple inventory files and returns the dut information present in the inventory
'''
inv_data = None
inv_files = [inv_file.strip() for inv_file in request.config.getoption("ansible_inventory").split(",")]
for inv_file in inv_files:
inv_mgr = InventoryManager(loader=DataLoader(), sources=inv_file)
if dut in inv_mgr.hosts:
return inv_mgr.get_host(dut).get_vars()

return inv_data

def generate_param_asic_index(request, dut_indices, param_type):
logging.info("generating {} asic indicies for DUT [{}] in ".format(param_type, dut_indices))

tbname = request.config.getoption("--testbed")
tbfile = request.config.getoption("--testbed_file")
if tbname is None or tbfile is None:
raise ValueError("testbed and testbed_file are required!")


tbinfo = TestbedInfo(tbfile)

#if the params are not present treat the device as a single asic device
asic_index_params = [DEFAULT_ASIC_ID]

for dut_id in dut_indices:
dut = tbinfo.testbed_topo[tbname]["duts"][dut_id]
inv_data = get_host_data(request, dut)
if inv_data is not None:
if param_type == ASIC_PARAM_TYPE_ALL and ASIC_PARAM_TYPE_ALL in inv_data:
asic_index_params = range(int(inv_data[ASIC_PARAM_TYPE_ALL]))
elif param_type == ASIC_PARAM_TYPE_FRONTEND and ASIC_PARAM_TYPE_FRONTEND in inv_data:
asic_index_params = inv_data[ASIC_PARAM_TYPE_FRONTEND]
logging.info("dut_index {} dut name {} asics params = {}".format(
dut_id, dut, asic_index_params))
return asic_index_params

def generate_params_dut_index(request):
tbname = request.config.getoption("--testbed")
tbfile = request.config.getoption("--testbed_file")
if tbname is None or tbfile is None:
raise ValueError("testbed and testbed_file are required!")
tbinfo = TestbedInfo(tbfile)
num_duts = len(tbinfo.testbed_topo[tbname]["duts"])
logging.info("Num of duts in testbed topology {}".format(num_duts))
return range(num_duts)

def pytest_generate_tests(metafunc):
# The topology always has atleast 1 dut
dut_indices = [0]
if "dut_index" in metafunc.fixturenames:
dut_indices = generate_params_dut_index(metafunc)
metafunc.parametrize("dut_index",dut_indices)
if "asic_index" in metafunc.fixturenames:
metafunc.parametrize("asic_index",generate_param_asic_index(metafunc, dut_indices, ASIC_PARAM_TYPE_ALL))
if "frontend_asic_index" in metafunc.fixturenames:
metafunc.parametrize("frontend_asic_index",generate_param_asic_index(metafunc, dut_indices, ASIC_PARAM_TYPE_FRONTEND))

0 comments on commit 4703756

Please sign in to comment.