Skip to content

Commit aa4b2d5

Browse files
authored
[pytest][qos][config] Added pytests for "config qos reload" commands" (sonic-net#1346)
Added pytest to verify QoS config generation on multi ASIC platform.
1 parent be7cac5 commit aa4b2d5

28 files changed

+2124
-17
lines changed

config/main.py

+36-9
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,21 @@
3737
from . import vxlan
3838
from .config_mgmt import ConfigMgmtDPB
3939

40+
# mock masic APIs for unit test
41+
try:
42+
if os.environ["UTILITIES_UNIT_TESTING"] == "1" or os.environ["UTILITIES_UNIT_TESTING"] == "2":
43+
modules_path = os.path.join(os.path.dirname(__file__), "..")
44+
tests_path = os.path.join(modules_path, "tests")
45+
sys.path.insert(0, modules_path)
46+
sys.path.insert(0, tests_path)
47+
import mock_tables.dbconnector
48+
if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic":
49+
import mock_tables.mock_multi_asic
50+
mock_tables.dbconnector.load_namespace_config()
51+
except KeyError:
52+
pass
53+
54+
4055
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?'])
4156

4257
SONIC_GENERATED_SERVICE_PATH = '/etc/sonic/generated_services.conf'
@@ -1756,12 +1771,24 @@ def _update_buffer_calculation_model(config_db, model):
17561771
@qos.command('reload')
17571772
@click.pass_context
17581773
@click.option('--no-dynamic-buffer', is_flag=True, help="Disable dynamic buffer calculation")
1759-
def reload(ctx, no_dynamic_buffer):
1774+
@click.option(
1775+
'--json-data', type=click.STRING,
1776+
help="json string with additional data, valid with --dry-run option"
1777+
)
1778+
@click.option(
1779+
'--dry_run', type=click.STRING,
1780+
help="Dry run, writes config to the given file"
1781+
)
1782+
def reload(ctx, no_dynamic_buffer, dry_run, json_data):
17601783
"""Reload QoS configuration"""
17611784
log.log_info("'qos reload' executing...")
17621785
_clear_qos()
17631786

17641787
_, hwsku_path = device_info.get_paths_to_platform_and_hwsku_dirs()
1788+
sonic_version_file = device_info.get_sonic_version_file()
1789+
from_db = "-d --write-to-db"
1790+
if dry_run:
1791+
from_db = "--additional-data \'{}\'".format(json_data) if json_data else ""
17651792

17661793
namespace_list = [DEFAULT_NAMESPACE]
17671794
if multi_asic.get_num_asics() > 1:
@@ -1798,17 +1825,17 @@ def reload(ctx, no_dynamic_buffer):
17981825
buffer_template_file = os.path.join(hwsku_path, asic_id_suffix, "buffers.json.j2")
17991826
if asic_type in vendors_supporting_dynamic_buffer:
18001827
buffer_model_updated |= _update_buffer_calculation_model(config_db, "traditional")
1828+
18011829
if os.path.isfile(buffer_template_file):
1802-
qos_template_file = os.path.join(hwsku_path, asic_id_suffix, "qos.json.j2")
1830+
qos_template_file = os.path.join(
1831+
hwsku_path, asic_id_suffix, "qos.json.j2"
1832+
)
18031833
if os.path.isfile(qos_template_file):
18041834
cmd_ns = "" if ns is DEFAULT_NAMESPACE else "-n {}".format(ns)
1805-
sonic_version_file = os.path.join('/', "etc", "sonic", "sonic_version.yml")
1806-
command = "{} {} -d -t {},config-db -t {},config-db -y {} --write-to-db".format(
1807-
SONIC_CFGGEN_PATH,
1808-
cmd_ns,
1809-
buffer_template_file,
1810-
qos_template_file,
1811-
sonic_version_file
1835+
fname = "{}{}".format(dry_run, asic_id_suffix) if dry_run else "config-db"
1836+
command = "{} {} {} -t {},{} -t {},{} -y {}".format(
1837+
SONIC_CFGGEN_PATH, cmd_ns, from_db, buffer_template_file,
1838+
fname, qos_template_file, fname, sonic_version_file
18121839
)
18131840
# Apply the configurations only when both buffer and qos
18141841
# configuration files are present

pfcwd/main.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import imp
23
import sys
34

45
import click
@@ -21,6 +22,7 @@
2122
import mock_tables.dbconnector
2223
if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic":
2324
import mock_tables.mock_multi_asic
25+
imp.reload(mock_tables.mock_multi_asic)
2426
mock_tables.dbconnector.load_namespace_config()
2527

2628
except KeyError:

scripts/intfutil

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ try:
2222
if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic":
2323
import mock_tables.mock_multi_asic
2424
mock_tables.dbconnector.load_namespace_config()
25-
25+
2626
except KeyError:
2727
pass
2828

@@ -461,7 +461,7 @@ class IntfDescription(object):
461461
self.intf_name = intf_name
462462

463463
def display_intf_description(self):
464-
464+
465465
self.get_intf_description()
466466

467467
# Sorting and tabulating the result table.

tests/config_test.py

+103-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import filecmp
2+
import imp
13
import os
24
import traceback
5+
import json
36
from unittest import mock
47

58
import click
69
from click.testing import CliRunner
710

11+
from sonic_py_common import device_info
812
from utilities_common.db import Db
913

1014
load_minigraph_command_output="""\
@@ -61,8 +65,10 @@ class TestLoadMinigraph(object):
6165
def setup_class(cls):
6266
os.environ['UTILITIES_UNIT_TESTING'] = "1"
6367
print("SETUP")
68+
import config.main
69+
imp.reload(config.main)
6470

65-
def test_load_minigraph(self, get_cmd_module, setup_single_broacom_asic):
71+
def test_load_minigraph(self, get_cmd_module, setup_single_broadcom_asic):
6672
with mock.patch("utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command:
6773
(config, show) = get_cmd_module
6874
runner = CliRunner()
@@ -74,7 +80,7 @@ def test_load_minigraph(self, get_cmd_module, setup_single_broacom_asic):
7480
assert "\n".join([l.rstrip() for l in result.output.split('\n')]) == load_minigraph_command_output
7581
assert mock_run_command.call_count == 38
7682

77-
def test_load_minigraph_with_disabled_telemetry(self, get_cmd_module, setup_single_broacom_asic):
83+
def test_load_minigraph_with_disabled_telemetry(self, get_cmd_module, setup_single_broadcom_asic):
7884
with mock.patch("utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command:
7985
(config, show) = get_cmd_module
8086
db = Db()
@@ -95,3 +101,98 @@ def test_load_minigraph_with_disabled_telemetry(self, get_cmd_module, setup_sing
95101
def teardown_class(cls):
96102
os.environ['UTILITIES_UNIT_TESTING'] = "0"
97103
print("TEARDOWN")
104+
105+
106+
class TestConfigQos(object):
107+
@classmethod
108+
def setup_class(cls):
109+
print("SETUP")
110+
os.environ['UTILITIES_UNIT_TESTING'] = "2"
111+
import config.main
112+
imp.reload(config.main)
113+
114+
def test_qos_reload_single(
115+
self, get_cmd_module, setup_qos_mock_apis,
116+
setup_single_broadcom_asic
117+
):
118+
(config, show) = get_cmd_module
119+
runner = CliRunner()
120+
output_file = os.path.join(os.sep, "tmp", "qos_config_output.json")
121+
print("Saving output in {}".format(output_file))
122+
try:
123+
os.remove(output_file)
124+
except OSError:
125+
pass
126+
json_data = '{"DEVICE_METADATA": {"localhost": {}}}'
127+
result = runner.invoke(
128+
config.config.commands["qos"],
129+
["reload", "--dry_run", output_file, "--json-data", json_data]
130+
)
131+
print(result.exit_code)
132+
print(result.output)
133+
assert result.exit_code == 0
134+
135+
cwd = os.path.dirname(os.path.realpath(__file__))
136+
expected_result = os.path.join(
137+
cwd, "qos_config_input", "config_qos.json"
138+
)
139+
assert filecmp.cmp(output_file, expected_result, shallow=False)
140+
141+
@classmethod
142+
def teardown_class(cls):
143+
print("TEARDOWN")
144+
os.environ['UTILITIES_UNIT_TESTING'] = "0"
145+
146+
147+
class TestConfigQosMasic(object):
148+
@classmethod
149+
def setup_class(cls):
150+
print("SETUP")
151+
os.environ['UTILITIES_UNIT_TESTING'] = "2"
152+
os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic"
153+
import config.main
154+
imp.reload(config.main)
155+
156+
def test_qos_reload_masic(
157+
self, get_cmd_module, setup_qos_mock_apis,
158+
setup_multi_broadcom_masic
159+
):
160+
(config, show) = get_cmd_module
161+
runner = CliRunner()
162+
output_file = os.path.join(os.sep, "tmp", "qos_config_output.json")
163+
print("Saving output in {}<0,1,2..>".format(output_file))
164+
num_asic = device_info.get_num_npus()
165+
for asic in range(num_asic):
166+
try:
167+
file = "{}{}".format(output_file, asic)
168+
os.remove(file)
169+
except OSError:
170+
pass
171+
json_data = '{"DEVICE_METADATA": {"localhost": {}}}'
172+
result = runner.invoke(
173+
config.config.commands["qos"],
174+
["reload", "--dry_run", output_file, "--json-data", json_data]
175+
)
176+
print(result.exit_code)
177+
print(result.output)
178+
assert result.exit_code == 0
179+
180+
cwd = os.path.dirname(os.path.realpath(__file__))
181+
182+
for asic in range(num_asic):
183+
expected_result = os.path.join(
184+
cwd, "qos_config_input", str(asic), "config_qos.json"
185+
)
186+
file = "{}{}".format(output_file, asic)
187+
assert filecmp.cmp(file, expected_result, shallow=False)
188+
189+
@classmethod
190+
def teardown_class(cls):
191+
print("TEARDOWN")
192+
os.environ['UTILITIES_UNIT_TESTING'] = "0"
193+
os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = ""
194+
# change back to single asic config
195+
from .mock_tables import dbconnector
196+
from .mock_tables import mock_single_asic
197+
imp.reload(mock_single_asic)
198+
dbconnector.load_namespace_config()

tests/conftest.py

+32-3
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,47 @@ def get_cmd_module():
5656

5757
return (config, show)
5858

59+
def set_mock_apis():
60+
import config.main as config
61+
cwd = os.path.dirname(os.path.realpath(__file__))
62+
config.asic_type = mock.MagicMock(return_value="broadcom")
63+
config._get_device_type = mock.MagicMock(return_value="ToRRouter")
64+
65+
@pytest.fixture
66+
def setup_qos_mock_apis():
67+
cwd = os.path.dirname(os.path.realpath(__file__))
68+
device_info.get_paths_to_platform_and_hwsku_dirs = mock.MagicMock(
69+
return_value=(
70+
os.path.join(cwd, "."), os.path.join(cwd, "qos_config_input")
71+
)
72+
)
73+
device_info.get_sonic_version_file = mock.MagicMock(
74+
return_value=os.path.join(cwd, "qos_config_input/sonic_version.yml")
75+
)
5976

6077
@pytest.fixture
61-
def setup_single_broacom_asic():
78+
def setup_single_broadcom_asic():
6279
import config.main as config
6380
import show.main as show
6481

82+
set_mock_apis()
6583
device_info.get_num_npus = mock.MagicMock(return_value=1)
6684
config._get_sonic_generated_services = \
6785
mock.MagicMock(return_value=(generated_services_list, []))
6886

69-
config.asic_type = mock.MagicMock(return_value="broadcom")
70-
config._get_device_type = mock.MagicMock(return_value="ToRRouter")
87+
88+
@pytest.fixture
89+
def setup_multi_broadcom_masic():
90+
import config.main as config
91+
import show.main as show
92+
93+
set_mock_apis()
94+
device_info.get_num_npus = mock.MagicMock(return_value=2)
95+
96+
yield
97+
98+
device_info.get_num_npus = mock.MagicMock(return_value=1)
99+
71100

72101
@pytest.fixture
73102
def setup_t1_topo():

tests/crm_test.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import imp
12
import os
23
import sys
34
from importlib import reload
@@ -1574,5 +1575,7 @@ def teardown_class(cls):
15741575
print("TEARDOWN")
15751576
os.environ["UTILITIES_UNIT_TESTING"] = "0"
15761577
os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = ""
1578+
from .mock_tables import dbconnector
15771579
from .mock_tables import mock_single_asic
1578-
reload(mock_single_asic)
1580+
imp.reload(mock_single_asic)
1581+
dbconnector.load_namespace_config()

tests/mock_tables/mock_multi_asic.py

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ def mock_is_multi_asic():
1414
def mock_get_namespace_list(namespace=None):
1515
return ['asic0', 'asic1']
1616

17+
1718
multi_asic.get_num_asics = mock_get_num_asics
1819
multi_asic.is_multi_asic = mock_is_multi_asic
1920
multi_asic.get_namespace_list = mock_get_namespace_list
21+
multi_asic.get_namespaces_from_linux = mock_get_namespace_list
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{%- set default_topo = 't1' %}
2+
{%- include 'buffers_config.j2' %}
3+

0 commit comments

Comments
 (0)