diff --git a/do.py b/do.py index 0dda01b34..d0b20f0ef 100644 --- a/do.py +++ b/do.py @@ -60,7 +60,7 @@ def test(): "--ext=ixnetwork", "--speed=speed_100_gbps", "tests", - '-m "not e2e and not l1_manual"', + '-m "not e2e and not l1_manual and not uhd"', "--cov=./snappi_ixnetwork --cov-report term" " --cov-report html:cov_report", ] diff --git a/snappi_ixnetwork/snappi_api.py b/snappi_ixnetwork/snappi_api.py index 5ff5eabdb..eebe9e444 100644 --- a/snappi_ixnetwork/snappi_api.py +++ b/snappi_ixnetwork/snappi_api.py @@ -642,6 +642,20 @@ def _validate_instance(self, config): self._initial_flows_config = self.config().flows.deserialize( config.flows.serialize(config.flows.DICT) ) + + if "UHD" in self._ixnetwork.Globals.ProductVersion: + chassis_info = "localuhd" + for port in config.ports: + if port.location is None: + continue + if ";" in port.location: + (_, card, port_info) = port.location.split(";") + port_info = "{}.{}".format(card, port_info) + elif "/" in port.location: + (_, port_info) = port.location.split("/") + else: + raise SnappiIxnException(400, "port location is not valid") + port.location = chassis_info + "/" + port_info return config def _apply_change(self): diff --git a/snappi_ixnetwork/trafficitem.py b/snappi_ixnetwork/trafficitem.py index 45139e838..0b08725e6 100644 --- a/snappi_ixnetwork/trafficitem.py +++ b/snappi_ixnetwork/trafficitem.py @@ -56,13 +56,14 @@ class TrafficItem(CustomField): "gTPuOptionalFields": "gtpv1option", "custom": "custom", "vxlan": "vxlan", - "ethernetARP": "arp" + "ethernetARP": "arp", } _HEADER_TO_TYPE = { "ethernet": "ethernet", "pfcpause": "pfcPause", "ethernetpause": "ethernet", + "ethernetpauseUHD": "globalPause", "vlan": "vlan", "ipv4": "ipv4", "ipv6": "ipv6", @@ -72,7 +73,17 @@ class TrafficItem(CustomField): "gtpv1option": "gTPuOptionalFields", "custom": "custom", "vxlan": "vxlan", - "arp": "ethernetARP" + "arp": "ethernetARP", + } + + _ETHERNETPAUSEUHD = { + "dst": "globalPause.header.header.dstAddress", + "src": "globalPause.header.header.srcAddress", + "ether_type": "globalPause.header.header.ethertype", + "control_op_code": "globalPause.header.macControl.controlOpcode", + "time": "globalPause.header.macControl.pfcQueue0", + "order": ["dst", "src", "ether_type", "control_op_code", "time"], + "convert_int_to_hex": ["ether_type", "time"], } _BIT_RATE_UNITS_TYPE = { @@ -167,7 +178,7 @@ class TrafficItem(CustomField): "sender_hardware_addr", "sender_protocol_addr", "target_hardware_addr", - "target_protocol_addr" + "target_protocol_addr", ], "convert_int_to_hex": [ "hardware_type", @@ -622,7 +633,9 @@ def config_raw_stack(self, xpath, packet): ce_path = "%s/configElement[1]" % xpath config_elem = {"xpath": ce_path, "stack": []} for i, header in enumerate(packet): - stack_name = self._HEADER_TO_TYPE.get(header.parent.choice) + stack_name = self._HEADER_TO_TYPE.get( + self._getUhdHeader(header.parent.choice) + ) header_xpath = "%s/stack[@alias = '%s-%d']" % ( ce_path, stack_name, @@ -800,6 +813,8 @@ def _configure_tracking(self, tr_item_json): return {"tracking": tracking} def _configure_options(self): + if self.isUhd is True: + return enable_min_frame_size = False for flow in self._config.flows: if ( @@ -835,7 +850,9 @@ def _configure_packet(self, ixn_stack, snappi_packet): else: stack_names.append(choice) for index, stack in enumerate(stack_names): - ixn_header_name = self._HEADER_TO_TYPE.get(stack) + ixn_header_name = self._HEADER_TO_TYPE.get( + self._getUhdHeader(stack) + ) if ixn_header_name is None: msg = "%s ixia header is not mapped" % ixn_header_name raise SnappiIxnException("400", msg) @@ -859,8 +876,13 @@ def _append_header( header_index=None, is_raw_traffic=False, ): - field_map = getattr(self, "_%s" % snappi_header.parent.choice.upper()) - stack_name = self._HEADER_TO_TYPE.get(snappi_header.parent.choice) + field_map = getattr( + self, + "_%s" % (self._getUhdHeader(snappi_header.parent.choice).upper()), + ) + stack_name = self._HEADER_TO_TYPE.get( + self._getUhdHeader(snappi_header.parent.choice) + ) if stack_name is None: raise NotImplementedError( "%s stack is not implemented" % snappi_header.parent.choice @@ -889,6 +911,15 @@ def _generate_fields(self, field_map, xpath): fields.append({"xpath": "%s/field[@alias = '%s']" % (xpath, fmap)}) return fields + @property + def isUhd(self): + return "UHD" in self._api._ixnetwork.Globals.ProductVersion + + def _getUhdHeader(self, header=None): + if self.isUhd is True and header == "ethernetpause": + return header + "UHD" + return header + def _configure_stack_fields( self, ixn_fields, snappi_header, stacks, is_raw_traffic=False ): @@ -897,7 +928,10 @@ def _configure_stack_fields( f["xpath"].split(" = ")[-1].strip("']").split("-")[0] for f in ixn_fields ] - field_map = getattr(self, "_%s" % snappi_header.parent.choice.upper()) + field_map = getattr( + self, + "_%s" % (self._getUhdHeader(snappi_header.parent.choice).upper()), + ) for field in snappi_header._TYPES: format_type = None try: diff --git a/tests/pytest.ini b/tests/pytest.ini index e54017772..a6f945bf0 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -3,6 +3,7 @@ addopts = -s -v markers = e2e: marks tests as e2e since they're slower (deselect with '-m "not e2e"') l1_manual: marks test as layer1 manual which needs to be run separately for specific Novus card + uhd: runs only uhd tests serial filterwarnings = ignore::urllib3.exceptions.InsecureRequestWarning diff --git a/tests/uhd/test_uhd_global_pause.py b/tests/uhd/test_uhd_global_pause.py new file mode 100644 index 000000000..5f3793268 --- /dev/null +++ b/tests/uhd/test_uhd_global_pause.py @@ -0,0 +1,48 @@ +import pytest + + +@pytest.mark.uhd +def test_uhd_global_pause(api, b2b_raw_config_vports, utils): + """ + Configure Ethernet Pause flow and validate the snappi + config is applied in UHD + """ + # fixed + flow1 = b2b_raw_config_vports.flows[0] + eth = flow1.packet.ethernetpause()[-1] + eth.dst.value = "01:80:c2:00:00:01" + eth.src.value = "00:AB:BC:AB:BC:AB" + eth.control_op_code.value = 1 + eth.time.value = 65535 + api.set_config(b2b_raw_config_vports) + + attrs = { + "Destination address": "01:80:c2:00:00:01", + "Source address": "00:ab:bc:ab:bc:ab", + "Ethertype": "8808", + "Control opcode": "1", + "PAUSE Quanta": "ffff", + } + utils.validate_config(api, "f1", 0, **attrs) + + eth.dst.increment.start = "01:80:c2:00:00:01" + eth.dst.increment.step = "00:00:00:01:00:00" + eth.dst.increment.count = 10 + eth.src.increment.start = "00:AB:BC:AB:BC:AB" + eth.src.increment.step = "00:00:00:01:00:00" + eth.src.increment.count = 10 + + api.set_config(b2b_raw_config_vports) + + attrs = { + "Destination address": ( + "01:80:c2:00:00:01", + "00:00:00:01:00:00", + "10", + ), + "Source address": ("00:ab:bc:ab:bc:ab", "00:00:00:01:00:00", "10"), + "Ethertype": "8808", + "Control opcode": "1", + "PAUSE Quanta": "ffff", + } + utils.validate_config(api, "f1", 0, **attrs) diff --git a/tests/uhd/test_uhd_mock.py b/tests/uhd/test_uhd_mock.py new file mode 100644 index 000000000..b4008e0c9 --- /dev/null +++ b/tests/uhd/test_uhd_mock.py @@ -0,0 +1,146 @@ +import snappi +from mock import MagicMock +from snappi_ixnetwork.snappi_api import Api as ixn_api +from snappi_ixnetwork.trafficitem import TrafficItem + + +def test_uhd_port_locations(): + api = ixn_api() + api._ixnetwork = lambda x: None + api._ixnetwork.Globals = lambda y: None + api._ixnetwork.Globals.ProductVersion = "UHD" + api._traffic = lambda z: None + api._traffic.State = "Stopped" + config = snappi.Api().config() + api._config = config + port = config.ports.port(location="a;b;c", name="p1").port( + location="localuhd/a", name="p2" + ) + api._validate_instance(config) + assert port[0].location == "localuhd/b.c" + assert port[1].location == "localuhd/a" + + +expected_global = { + "xpath": "/traffic", + "trafficItem": [ + { + "xpath": "/traffic/trafficItem[1]", + "name": "f1", + "srcDestMesh": "oneToOne", + "endpointSet": [ + { + "xpath": "/traffic/trafficItem[1]/endpointSet[1]", + "sources": ["/vport[1]/protocols"], + "destinations": ["/vport[2]/protocols"], + } + ], + "trafficType": "raw", + "configElement": [ + { + "xpath": "/traffic/trafficItem[1]/configElement[1]", + "stack": [ + { + "xpath": "/traffic/trafficItem[1]/configElement[1]" + "/stack[@alias = 'globalPause-1']", + "field": [ + { + "xpath": "/traffic/trafficItem[1]/config" + "Element[1]/stack[@alias = 'globalPause-1'" + "]/field[@alias = 'globalPause.header." + "header.dstAddress-1']", + "valueType": "singleValue", + "singleValue": "01:80:c2:00:00:01", + "activeFieldChoice": False, + "auto": False, + }, + { + "xpath": "/traffic/trafficItem[1]/config" + "Element[1]/stack[@alias = 'globalPause-1']" + "/field[@alias = 'globalPause.header." + "header.srcAddress-2']", + "valueType": "singleValue", + "singleValue": "00:00:00:00:00:00", + "activeFieldChoice": False, + "auto": False, + }, + { + "xpath": "/traffic/trafficItem[1]/config" + "Element[1]/stack[@alias = 'globalPause-1'" + "]/field[@alias = 'globalPause.header." + "header.ethertype-3']", + "valueType": "singleValue", + "singleValue": "8808", + "activeFieldChoice": False, + "auto": False, + }, + { + "xpath": "/traffic/trafficItem[1]/config" + "Element[1]/stack[@alias = 'globalPause-1" + "']/field[@alias = 'globalPause.header." + "macControl.controlOpcode-4']", + "valueType": "singleValue", + "singleValue": 1, + "activeFieldChoice": False, + "auto": False, + }, + { + "xpath": "/traffic/trafficItem[1]/config" + "Element[1]/stack[@alias = 'globalPause-1'" + "]/field[@alias = 'globalPause.header." + "macControl.pfcQueue0-5']", + "valueType": "singleValue", + "singleValue": "0", + "activeFieldChoice": False, + "auto": False, + }, + ], + } + ], + } + ], + } + ], +} + + +def test_global_pause_header(): + config = snappi.Api().config() + api = ixn_api() + api._ixnetwork = lambda x: None + api._ixnetwork.Globals = lambda y: None + api._ixnetwork.Globals.ProductVersion = "UHD" + tr_obj = TrafficItem(api) + ports = {"p1": "/vport[1]", "p2": "/vport[2]"} + tr_obj.get_ports_encap = MagicMock(return_value=ports) + tr_obj.get_device_encap = MagicMock(return_value={}) + f1 = config.flows.flow(name="f1")[-1] + f1.tx_rx.port.tx_name = "p1" + f1.tx_rx.port.rx_name = "p2" + f1.packet.ethernetpause() + tr_obj.copy_flow_packet(config) + tr_raw = tr_obj.create_traffic(config) + assert tr_raw == expected_global + + +def test_enable_min_frame_size(): + config = snappi.Api().config() + api = ixn_api() + tr_obj = TrafficItem(api) + api._ixnetwork = lambda x: None + api._ixnetwork.Globals = lambda y: None + api._ixnetwork.Globals.ProductVersion = "ABC" + api._traffic = lambda x: None + api._traffic.EnableMinFrameSize = False + f1 = config.flows.flow()[-1] + f1.name = "flow1" + f1.packet.pfcpause() + tr_obj._config = config + + tr_obj._configure_options() + assert api._traffic.EnableMinFrameSize is True + api._ixnetwork.Globals.ProductVersion = "UHD" + + api._traffic.EnableMinFrameSize = False + tr_obj._configure_options() + assert api._traffic.EnableMinFrameSize is False diff --git a/tests/uhd/test_uhd_pfc_pause.py b/tests/uhd/test_uhd_pfc_pause.py new file mode 100644 index 000000000..73033be85 --- /dev/null +++ b/tests/uhd/test_uhd_pfc_pause.py @@ -0,0 +1,56 @@ +import pytest + + +@pytest.mark.uhd +def test_fixed_pfc_pause(api, b2b_raw_config, utils): + """ + Configure a pfc pause header fields, + - with fixed pattern + + Validate, + - Fetch the pfc pause header config via restpy and validate + against expected + """ + f = b2b_raw_config.flows[0] + f.name = "pfcpause" + f.size.fixed = 100 + f.packet.pfcpause() + pfc = f.packet[0] + pfc.src.value = "00:AB:BC:AB:BC:AB" + pfc.dst.value = "00:AB:BC:AB:BC:AB" + pfc.ether_type.value = 3000 + pfc.class_enable_vector.value = 255 + pfc.control_op_code.value = 257 + pfc.pause_class_0.value = 65535 + pfc.pause_class_1.value = 65535 + pfc.pause_class_2.value = 65535 + pfc.pause_class_3.value = 65535 + pfc.pause_class_4.value = 65535 + pfc.pause_class_5.value = 65535 + pfc.pause_class_6.value = 65535 + pfc.pause_class_7.value = 65535 + api.set_config(b2b_raw_config) + attrs = { + "Destination address": "00:ab:bc:ab:bc:ab", + "Source address": "00:ab:bc:ab:bc:ab", + "Ethertype": "bb8", + "Control opcode": "101", + "priority_enable_vector": "ff", + "PFC Queue 0": "ffff", + "PFC Queue 1": "ffff", + "PFC Queue 2": "ffff", + "PFC Queue 3": "ffff", + "PFC Queue 4": "ffff", + "PFC Queue 5": "ffff", + "PFC Queue 6": "ffff", + "PFC Queue 7": "ffff", + } + utils.validate_config(api, "pfcpause", 0, **attrs) + + pfc.pause_class_7.value = 3333 + + api.set_config(b2b_raw_config) + + attrs["PFC Queue 7"] = "d05" + + utils.validate_config(api, "pfcpause", 0, **attrs)