Repository: sonic-net/sonic-mgmt
Pull request #17215: Add parsing logic for transceiver inventory file
Author: login: mihirpat1
Labels: name: Request for msft-202412 Branch
name: Approved for msft-202412 Branch
name: Included in msft-202412 Branch
Created: 2025-02-27T07:40:47Z | Merged: 2025-03-13T00:47:11Z

<!--
Please make sure you've read and understood our contributing guidelines;
https://github.com/sonic-net/SONiC/blob/gh-pages/CONTRIBUTING.md

Please provide following information to help code review process a bit easier:
-->
### Description of PR
<!--
- Please include a summary of the change and which issue is fixed.
- Please also include relevant motivation and context. Where should reviewer start? background context?
- List any dependencies that are required for this change.
-->
We need to add a parsing logic per the transceiver onboarding plan (section "Pre-requisites for the Below Tests") so that the transceiver specific data can be used to execute transceiver specific testcases.

[Transceiver Onboarding Test Plan by mihirpat1 · Pull Request #10130 · sonic-net/sonic-mgmt](https://github.com/sonic-net/sonic-mgmt/pull/10130)

Summary:


### Type of change

<!--
- Fill x for your type of change.
- e.g.
- [x] Bug fix
-->

- [ ] Bug fix
- [x] Testbed and Framework(new/improvement)
- [x] New Test case
    - [ ] Skipped for non-supported platforms
- [ ] Test case improvement


### Back port request
- [ ] 202012
- [ ] 202205
- [ ] 202305
- [ ] 202311
- [ ] 202405
- [ ] 202411

### Approach
#### What is the motivation for this PR?
Add parsing logic for transceiver inventory file. For the details of the parsing logic and the inventory file, please refer to section "Pre-requisites for the Below Tests" in [Transceiver Onboarding Test Plan by mihirpat1 · Pull Request #10130 · sonic-net/sonic-mgmt](https://github.com/sonic-net/sonic-mgmt/pull/10130)

#### How did you do it?
1. Added `transceiver_inventory` function with module scope to parse transceiver inventory file.
2. Created test_check_show_int_transceiver_eeprom function to test the parsing code and verify that the transceiver EEPROM of DUT is inline with the inventory data defined in `transceiver_common_attributes.csv` and `transceiver_dut_info.csv` files. The `test_ptp_show_intf_transceiver.py` is planned to be modified in future to have more testcases related to `show interfaces transceiver` related CLIs.
3. The current tests in `test_ptp_show_intf_transceiver.py` are applicable only to ptp-256 topology since the transceiver onboarding suite of tests is specific to ptp based topology.


Future enhancements
1. We need to create an optimized way to find the logical port to physical port dictionary. Currently, this information is retrieved from the device via `get_physical_port_indices` and is time consuming since this function invokes the redis-db CLI equivalent to the number of logical ports on the DUT.
2. Additional logic to parse raw firmware binary and its metadata.

#### How did you verify/test it?
Test passed for `ptp-256` topology
```
patelmi@temp2:/var/src/mgmt_int/tests$ ./run_tests.sh -c $test_path -n $tb_name -i ../ansible/$tb_str,../ansible/veos -u -e "--skip_sanity --disable_loganalyzer" -t 'ptp-256'
.
======================================================================= warnings summary ========================================================================
../../../../usr/local/lib/python3.8/dist-packages/paramiko/transport.py:236
  /usr/local/lib/python3.8/dist-packages/paramiko/transport.py:236: CryptographyDeprecationWarning: Blowfish has been deprecated
    "class": algorithms.Blowfish,

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
---------------------------------------------------- generated xml file: /var/src/mgmt_int/tests/logs/tr.xml ----------------------------------------------------
=========================================================== 1 passed, 1 warning in 244.13s (0:04:04) ============================================================
DEBUG:tests.conftest:[log_custom_msg] item: <Function test_check_show_int_transceiver_eeprom[str3-7060x6-64pe-4-None]>
INFO:root:Can not get Allure report URL. Please check logs
patelmi@temp2:/var/src/mgmt_int/tests$ 
```

Test skipped for other topology
```
patelmi@temp2:/var/src/mgmt_int/tests$ ./run_tests.sh -c $test_path -n $tb_name -i ../ansible/$tb_str,../ansible/veos -u -e "--skip_sanity --disable_loganalyzer" -t 'ptp'
=== Running tests in groups ===
.
transceiver/eeprom_tests/test_transceiver_eeprom.py::test_check_show_int_transceiver_eeprom[str3-7060x6-64pe-4-None] SKIPPED (test requires topology ...) [100%]

======================================================================= warnings summary ========================================================================
../../../../usr/local/lib/python3.8/dist-packages/paramiko/transport.py:236
  /usr/local/lib/python3.8/dist-packages/paramiko/transport.py:236: CryptographyDeprecationWarning: Blowfish has been deprecated
    "class": algorithms.Blowfish,

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
---------------------------------------------------- generated xml file: /var/src/mgmt_int/tests/logs/tr.xml ----------------------------------------------------
==================================================================== short test summary info ====================================================================
SKIPPED [1] transceiver/eeprom_tests/test_transceiver_eeprom.py: test requires topology in Mark(name='topology', args=('ptp-256',), kwargs={})
================================================================ 1 skipped, 1 warning in 32.67s =================================================================
DEBUG:tests.conftest:[log_custom_msg] item: <Function test_check_show_int_transceiver_eeprom[str3-7060x6-64pe-4-None]>
INFO:root:Can not get Allure report URL. Please check logs
patelmi@temp2:/var/src/mgmt_int/tests$ 
```
#### Any platform specific information?

#### Supported testbed topology if it's a new test case?
`ptp-256`

### Documentation
<!--
(If it's a new feature, new test case)
Did you update documentation/Wiki relevant to your implementation?
Link to the wiki page?
-->
https://github.com/sonic-net/sonic-mgmt/pull/10130/files?short_path=1707bc9#diff-1707bc9546f32667883a248c43fb8433faabd02ed0e09fe6bf430540ee4bea31

MSFT ADO - 31582995


Diff:
diff --git a/tests/common/platform/interface_utils.py b/tests/common/platform/interface_utils.py
index 35f633c6ec4..dffaff294dc 100644
--- a/tests/common/platform/interface_utils.py
+++ b/tests/common/platform/interface_utils.py
@@ -181,6 +181,21 @@ def get_port_map(dut, asic_index=None):
     return port_mapping
 
 
+def get_dev_conn(duthost, conn_graph_facts, asic_index):
+    dev_conn = conn_graph_facts.get("device_conn", {}).get(duthost.hostname, {})
+
+    # Get the interface pertaining to that asic
+    portmap = get_port_map(duthost, asic_index)
+    logging.info("Got portmap {}".format(portmap))
+
+    if asic_index is not None:
+        # Check if the interfaces of this ASIC is present in conn_graph_facts
+        dev_conn = {k: v for k, v in list(portmap.items()) if k in conn_graph_facts["device_conn"][duthost.hostname]}
+        logging.info("ASIC {} interface_list {}".format(asic_index, dev_conn))
+
+    return portmap, dev_conn
+
+
 def get_physical_port_indices(duthost, logical_intfs=None):
     """
     @summary: Returns dictionary map of logical ports to corresponding physical port indices
diff --git a/tests/transceiver/__init__.py b/tests/transceiver/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/transceiver/cli/__init__.py b/tests/transceiver/cli/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/transceiver/cli/sfputil/test_sfputil.py b/tests/transceiver/cli/sfputil/test_sfputil.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/transceiver/cli/show/test_transceiver_info_cli.py b/tests/transceiver/cli/show/test_transceiver_info_cli.py
new file mode 100644
index 00000000000..01a59ee1b50
--- /dev/null
+++ b/tests/transceiver/cli/show/test_transceiver_info_cli.py
@@ -0,0 +1,72 @@
+"""
+Basic test for show int transceiver info CLI.
+This file is created to verify the parsing logic of transceiver inventory in conftest.py.
+"""
+import logging
+import pytest
+
+from tests.transceiver.transceiver_test_base import TransceiverTestBase
+from tests.transceiver.utils.cli_parser_helper import parse_eeprom
+
+pytestmark = [
+    pytest.mark.topology('ptp-256')
+]
+
+CMD_SFP_EEPROM = "show interfaces transceiver info"
+ERROR_CHASSIS_LOAD = 2
+
+logger = logging.getLogger(__name__)
+
+
+class TestTransceiverInfoValidator(TransceiverTestBase):
+    """
+    @summary: Test class to validate
+    transceiver inventory against the parsed EEPROM data.
+    """
+
+    EEPROM_EXPECTED_CLI_KEY_TO_TRANSCEIVER_INV_KEY_MAPPING = {
+        "Vendor Date Code(YYYY-MM-DD Lot)": "vendor_date",
+        "Vendor OUI": "vendor_oui",
+        "Vendor Rev": "vendor_rev",
+        "Vendor SN": "vendor_sn",
+        "Vendor PN": "vendor_pn",
+        "Active Firmware": "active_firmware",
+        "Inactive Firmware": "inactive_firmware",
+        "CMIS Rev": "cmis_rev",
+        "Vendor Name": "vendor_name",
+    }
+
+    def validate_parsed_eeprom(self, parsed_eeprom):
+        for intf in self.dev_conn:
+            port_parsed_eeprom = parsed_eeprom[intf]
+            port_transceiver_details = self.dev_transceiver_details.get(self.lport_to_pport_mapping[intf], {})
+            for cli_key, transceiver_inv_key in self.EEPROM_EXPECTED_CLI_KEY_TO_TRANSCEIVER_INV_KEY_MAPPING.items():
+                assert cli_key in port_parsed_eeprom, "{}: {} not present in parsed_eeprom".format(intf, cli_key)
+                assert transceiver_inv_key in port_transceiver_details, (
+                    "{}: {} not present in transceiver_inventory".format(intf, transceiver_inv_key)
+                )
+                assert port_parsed_eeprom[cli_key] == port_transceiver_details[transceiver_inv_key], (
+                    "{}: {} mismatch for {}: expected {}, got {}".format(
+                        intf, cli_key, self.lport_to_pport_mapping[intf],
+                        port_transceiver_details[transceiver_inv_key],
+                        port_parsed_eeprom[cli_key]
+                    )
+                )
+        logger.info("All transceiver EEPROM contents matched successfully.")
+
+    def test_check_show_int_transceiver_info(self):
+        """
+        @summary: Check SFP EEPROM using 'show interfaces transceiver eeprom'
+        """
+        logger.info("Check output of '{}'".format(CMD_SFP_EEPROM))
+        sfp_eeprom = self.duthost.command(CMD_SFP_EEPROM, module_ignore_errors=True)
+
+        # For vs testbed, we will get expected Error code `ERROR_CHASSIS_LOAD = 2` here.
+        if self.duthost.facts["asic_type"] == "vs" and sfp_eeprom['rc'] == ERROR_CHASSIS_LOAD:
+            return
+        assert sfp_eeprom['rc'] == 0, "Run command '{}' failed".format(CMD_SFP_EEPROM)
+
+        parsed_eeprom = parse_eeprom(sfp_eeprom["stdout_lines"])
+
+        # Validate the parsed_eeprom against the transceiver inventory
+        self.validate_parsed_eeprom(parsed_eeprom)
diff --git a/tests/transceiver/conftest.py b/tests/transceiver/conftest.py
new file mode 100644
index 00000000000..8f3fc2b7c18
--- /dev/null
+++ b/tests/transceiver/conftest.py
@@ -0,0 +1,28 @@
+import os
+import pytest
+import logging
+from .inventory.parser import TransceiverInventory
+
+from tests.common.platform.interface_utils import get_physical_port_indices
+
+
+@pytest.fixture(scope="module")
+def transceiver_inventory():
+    """
+    Fixture to provide transceiver inventory information.
+    """
+    base_path = os.path.dirname(os.path.realpath(__file__))
+    inventory = TransceiverInventory(base_path)
+    return inventory.get_transceiver_info()
+
+
+@pytest.fixture(scope="module")
+def get_lport_to_pport_mapping(duthosts, enum_rand_one_per_hwsku_frontend_hostname, tbinfo):
+    """
+    Fixture to get the mapping of logical ports to physical ports.
+    """
+    duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname]
+    lport_to_pport_mapping = get_physical_port_indices(duthost)
+
+    logging.info("Logical to Physical Port Mapping: {}".format(lport_to_pport_mapping))
+    return lport_to_pport_mapping
diff --git a/tests/transceiver/inventory/parser.py b/tests/transceiver/inventory/parser.py
new file mode 100644
index 00000000000..d9d8886879c
--- /dev/null
+++ b/tests/transceiver/inventory/parser.py
@@ -0,0 +1,70 @@
+import csv
+import os
+import logging
+
+
+class TransceiverInventory:
+    def __init__(self, base_path):
+        self.base_path = base_path
+        self.transceiver_inventory_path = os.path.join(
+            self.base_path, "../../ansible/files/transceiver_inventory/"
+        )
+        self.common_attributes_file = os.path.join(self.transceiver_inventory_path, "transceiver_common_attributes.csv")
+        self.dut_info_file = os.path.join(self.transceiver_inventory_path, "transceiver_dut_info.csv")
+        self.common_attributes = self.parse_common_attributes()
+        self.dut_info = self.parse_dut_info()
+
+    def parse_common_attributes(self):
+        """
+        Parses the transceiver_common_attributes.csv file and stores the data in a dictionary.
+        The vendor_pn is used as the key for the outer dictionary, and the remaining row data is stored as the value.
+        """
+        common_attributes = {}
+        with open(self.common_attributes_file, mode='r') as file:
+            reader = csv.DictReader(file)
+            for row in reader:
+                vendor_pn = row['vendor_pn']
+                common_attributes[vendor_pn] = self._convert_row_types(row)
+        logging.debug("Common Attributes: {}".format(common_attributes))
+        return common_attributes
+
+    def parse_dut_info(self):
+        """
+        Parses the transceiver_dut_info.csv file and stores the data in a nested dictionary.
+        The outer dictionary is keyed by dut_name, and the inner dictionary is keyed by physical_port.
+        The values are dictionaries containing the remaining row data.
+        Common attributes are merged into the inner dictionary based on vendor_pn.
+        """
+        dut_info = {}
+        with open(self.dut_info_file, mode='r') as file:
+            reader = csv.DictReader(file)
+            for row in reader:
+                dut_name = row.pop('dut_name')
+                port = int(row.pop('physical_port'))
+                vendor_pn = row.pop('vendor_pn')
+                if dut_name not in dut_info:
+                    dut_info[dut_name] = {}
+                dut_info[dut_name][port] = self._convert_row_types(row)
+                if vendor_pn in self.common_attributes:
+                    dut_info[dut_name][port].update(self.common_attributes[vendor_pn])
+        logging.debug("DUT Info: {}".format(dut_info))
+        return dut_info
+
+    def _convert_row_types(self, row):
+        """
+        Converts the values in a row to their appropriate types (e.g., int, float, bool).
+        This is required since default csv.DictReader returns all values as strings, we need to
+        convert them to their appropriate types.
+        """
+        special_handling = {
+            'vdm_supported': lambda x: x == 'True',
+            'cdb_backgroundmode_supported': lambda x: x == 'True',
+            'dual_bank_supported': lambda x: x == 'True',
+        }
+        return {key: special_handling.get(key, lambda x: x)(value) for key, value in row.items()}
+
+    def get_transceiver_info(self):
+        """
+        Returns the parsed transceiver information.
+        """
+        return self.dut_info
diff --git a/tests/transceiver/transceiver_test_base.py b/tests/transceiver/transceiver_test_base.py
new file mode 100644
index 00000000000..a2a6cc2ec64
--- /dev/null
+++ b/tests/transceiver/transceiver_test_base.py
@@ -0,0 +1,38 @@
+"""
+Transceiver Test Base Class
+=================================
+This module contains a base class for transceiver tests.
+It sets up the test environment by initializing the necessary components such as
+the DUT host and the device connection.
+It also provides a fixture to retrieve transceiver details and logical to physical port mapping.
+The test class is designed to be inherited by specific test cases that require
+transceiver information.
+"""
+import pytest
+import logging
+
+from tests.common.platform.interface_utils import get_dev_conn
+from tests.transceiver.utils.inventory import get_dev_transceiver_details
+
+logger = logging.getLogger(__name__)
+
+
+@pytest.mark.topology('ptp-256')
+@pytest.mark.usefixtures("setup")
+class TransceiverTestBase:
+    @pytest.fixture(scope="class", autouse=True)
+    def setup(self, request, duthosts, enum_rand_one_per_hwsku_frontend_hostname,
+              enum_frontend_asic_index, conn_graph_facts, transceiver_inventory, get_lport_to_pport_mapping):
+        """
+        Fixture to set up the test environment.
+        """
+        duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname]
+        _, dev_conn = get_dev_conn(duthost, conn_graph_facts, enum_frontend_asic_index)
+        dev_transceiver_details = get_dev_transceiver_details(duthost, transceiver_inventory)
+        logger.info("Dev transceiver details: {}".format(dev_transceiver_details))
+        if request.cls is not None:
+            request.cls.duthost = duthost
+            request.cls.enum_frontend_asic_index = enum_frontend_asic_index
+            request.cls.dev_conn = dev_conn
+            request.cls.dev_transceiver_details = dev_transceiver_details
+            request.cls.lport_to_pport_mapping = get_lport_to_pport_mapping
diff --git a/tests/transceiver/utils/cli_parser_helper.py b/tests/transceiver/utils/cli_parser_helper.py
new file mode 100644
index 00000000000..3f6b5b98a82
--- /dev/null
+++ b/tests/transceiver/utils/cli_parser_helper.py
@@ -0,0 +1,30 @@
+"""
+CLI Parser Helper for various transceiver related commands
+"""
+import re
+
+
+def parse_eeprom(output_lines):
+    """
+    @summary: Parse the SFP eeprom information from command output
+    @param output_lines: Command output lines
+    @return: Returns result in a dictionary
+    """
+    res = {}
+    current_interface = None
+
+    for line in output_lines:
+        line = line.strip()
+        # Check if the line indicates a new interface
+        if re.match(r"^Ethernet\d+: .*", line):
+            fields = line.split(":", 1)
+            current_interface = fields[0]
+            res[current_interface] = {"status": fields[1].strip()}
+        elif current_interface:
+            # Parse key-value pairs for the current interface
+            key_value = line.split(": ", 1)
+            if len(key_value) == 2:
+                key, value = key_value
+                res[current_interface][key] = value.strip()
+
+    return res
diff --git a/tests/transceiver/utils/inventory.py b/tests/transceiver/utils/inventory.py
new file mode 100644
index 00000000000..ce3ac21e9e2
--- /dev/null
+++ b/tests/transceiver/utils/inventory.py
@@ -0,0 +1,22 @@
+"""
+Contains utility functions for extracting transceiver information
+from the already parsed transceiver inventory.
+"""
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+def get_dev_transceiver_details(duthost, transceiver_inventory):
+    """
+    Get transceiver details from transceiver_inventory for the given DUT.
+
+    @param duthost: DUT host
+    @param transceiver_inventory: Transceiver inventory
+    @return: Returns transceiver details in a dictionary for the given DUT with port as key
+    """
+    hostname = duthost.hostname
+    details = transceiver_inventory.get(hostname, {})
+    if not details:
+        logger.error(f"No transceiver details found for host: {hostname}")
+    return details


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## PR Overview

This PR adds parsing logic for the transceiver inventory file and integrates it with new test cases and fixtures to validate the transceiver EEPROM information against inventory data. Key changes include:
- Implementation of the CLI parser helper (parse_eeprom) to parse SFP EEPROM output.
- Addition of the TransceiverInventory class (with CSV parsing for common and DUT-specific attributes).
- New test case (TestTransceiverInfoValidator) and updated fixtures to support transceiver tests on the ptp-256 topology.

### Reviewed Changes

| File                                                                     | Description                                                                                     |
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| tests/transceiver/utils/cli_parser_helper.py                             | Introduces a function to parse SFP EEPROM CLI output.                                          |
| tests/transceiver/transceiver_test_base.py                               | New base class for transceiver tests using inventory and port mapping fixtures.                 |
| tests/transceiver/inventory/parser.py                                      | Adds CSV parsing logic to extract transceiver inventory details.                                |
| tests/transceiver/conftest.py                                             | Creates fixtures for transceiver inventory information and logical-to-physical port mapping.      |
| tests/transceiver/utils/transceiver_inv_utils.py                          | Provides a utility to retrieve DUT-specific transceiver details from the inventory.             |
| tests/transceiver/show_intf_transceiver_cli_tests/test_transceiver_info_cli.py | Implements a test to validate that parsed EEPROM data matches inventory details.                |
| tests/common/platform/interface_utils.py                                  | Updates the interface utilities to retrieve device connection and port mapping details.         |

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.



state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T23:56:00Z
user: login: prgeor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T04:50:22Z
user: login: prgeor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T04:51:32Z
user: login: prgeor
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T06:15:10Z
user: login: mihirpat1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T17:06:24Z
user: login: mihirpat1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T17:06:32Z
user: login: prgeor
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T17:09:07Z

Review comments:
diff_hunk: @@ -181,6 +181,21 @@ def get_port_map(dut, asic_index=None):
     return port_mapping
 
 
+def get_dev_conn(duthost, conn_graph_facts, asic_index):
+    dev_conn = conn_graph_facts.get("device_conn", {}).get(duthost.hostname, {})
+
+    # Get the interface pertaining to that asic
+    portmap = get_port_map(duthost, asic_index)
+    logging.info("Got portmap {}".format(portmap))
+
+    if asic_index is not None:
+        # Check if the interfaces of this AISC is present in conn_graph_facts
path: tests/common/platform/interface_utils.py
user: login: Copilot
body: Typo in the comment: 'AISC' should be 'ASIC'.
```suggestion
        # Check if the interfaces of this ASIC is present in conn_graph_facts
```
created_at: 2025-03-05T23:56:00Z
author_association: CONTRIBUTOR
path: tests/transceiver/show_intf_transceiver_cli_tests/test_transceiver_info_cli.py
user: login: prgeor
body: tests/transceiver/cli/show/test_transceiver_info_cli.py

tests/transceiver/cli/sfputil/test_sfputil.py


created_at: 2025-03-06T04:50:22Z
author_association: CONTRIBUTOR
path: tests/transceiver/utils/transceiver_inv_utils.py
user: login: prgeor
body: nit tranceiver_inv_utils.py -> inventory.py
created_at: 2025-03-06T04:51:32Z
author_association: CONTRIBUTOR
path: tests/transceiver/show_intf_transceiver_cli_tests/test_transceiver_info_cli.py
user: login: mihirpat1
body: @prgeor I have addressed this now.
created_at: 2025-03-06T17:06:23Z
author_association: CONTRIBUTOR
path: tests/transceiver/utils/transceiver_inv_utils.py
user: login: mihirpat1
body: @prgeor I have addressed this now.
created_at: 2025-03-06T17:06:31Z
author_association: CONTRIBUTOR

PR comments:
user: login: mssonicbld
created_at: 2025-02-27T07:40:50Z
body: /azp run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-02-27T07:41:02Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: mssonicbld
created_at: 2025-02-27T08:08:49Z
body: /azp run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-02-27T08:09:01Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: mssonicbld
created_at: 2025-02-27T18:03:09Z
body: /azp run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-02-27T18:03:21Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: mssonicbld
created_at: 2025-02-27T21:23:11Z
body: /azp run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-02-27T21:23:22Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: mssonicbld
created_at: 2025-02-27T22:03:22Z
body: /azp run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-02-27T22:03:34Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: mssonicbld
created_at: 2025-03-05T23:55:13Z
body: /azp run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-03-05T23:55:19Z
body: <samp>
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.<br>

</samp>
author_association: NONE
user: login: mssonicbld
created_at: 2025-03-05T23:57:46Z
body: /azp run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-03-05T23:57:56Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: mssonicbld
created_at: 2025-03-06T16:59:39Z
body: /azp run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-03-06T16:59:51Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: mssonicbld
created_at: 2025-03-07T02:59:50Z
body: /azp run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-03-07T03:00:03Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: prgeor
created_at: 2025-03-09T22:58:49Z
body: @mihirpat1 can you check the build failures?
author_association: CONTRIBUTOR
user: login: mihirpat1
created_at: 2025-03-11T16:49:22Z
body: > @mihirpat1 can you check the build failures?

@prgeor The current failures are due to known baseline failures. I have retriggered the test now.
author_association: CONTRIBUTOR
user: login: mssonicbld
created_at: 2025-03-11T17:07:46Z
body: /azp run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-03-11T17:07:52Z
body: <samp>
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.<br>

</samp>
author_association: NONE
user: login: mihirpat1
created_at: 2025-03-11T17:18:13Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-03-11T17:18:19Z
body: <samp>
Commenter does not have sufficient privileges for PR 17215 in repo sonic-net/sonic-mgmt<br>

</samp>
author_association: NONE
user: login: mihirpat1
created_at: 2025-03-11T17:20:47Z
body: /azpw run
author_association: CONTRIBUTOR
user: login: mssonicbld
created_at: 2025-03-11T17:20:50Z
body: /AzurePipelines run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-03-11T17:21:03Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: mihirpat1
created_at: 2025-03-12T04:38:13Z
body: /azpw run
author_association: CONTRIBUTOR
user: login: mssonicbld
created_at: 2025-03-12T04:38:16Z
body: /AzurePipelines run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-03-12T04:38:28Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: mssonicbld
created_at: 2025-09-02T10:27:52Z
body: Cherry-pick PR to msft-202412: https://github.com/Azure/sonic-mgmt.msft/pull/683
author_association: COLLABORATOR