From f35685e37e05fd8584431e191188341614e17aa8 Mon Sep 17 00:00:00 2001 From: Sonic Build Admin Date: Thu, 14 May 2026 22:41:02 +0000 Subject: [PATCH] [generic_hwsku] add platform.json support to port_config_gen module ### Description of PR Summary: Add `platform.json` support to the `port_config_gen` Ansible module (fanout port config generator). When `hwsku.json` exists and `platform.json` contains a non-empty `interfaces` section, use them for port config lookup. Fall back to `port_config.ini` otherwise. ### Type of change - [ ] Bug fix - [x] Testbed and Framework(new/improvement) - [ ] New Test case - [ ] Skipped for non-supported platforms - [ ] Test case improvement ### Back port request - [ ] 202205 - [ ] 202305 - [ ] 202311 - [ ] 202405 - [ ] 202411 - [ ] 202505 - [x] 202511 ### Approach #### What is the motivation for this PR? This is part of the larger effort to support generic HWSKUs. One of the steps toward that goal is removing the dependency on `port_config.ini` ([sonic-net/sonic-buildimage#24494](https://github.com/sonic-net/sonic-buildimage/issues/24494)). The `port_config_gen` Ansible module currently relies solely on port_config.ini for port configuration, so it needs to be updated to also support `platform.json`. The changes are in line with the existing port config resolution logic in [device_info.py](https://github.com/sonic-net/sonic-buildimage/blob/master/src/sonic-py-common/sonic_py_common/device_info.py#L443). #### How did you do it? - Add JSON-based port config lookup: use `platform.json` when `hwsku.json` exists and `interfaces` section in `platform.json` is populated. Use `port_config.ini` otherwise - Define `_read_from_json()` to parse `platform.json` into the same alias-keyed dict format as `_read_from_port_config()` #### How did you verify/test it? Tested on T1 and LT2 platforms: ran `port_config_gen.py` via an Ansible stub and verified the new `platform.json` + `hwsku.json` path generates the same port config as the legacy `port_config.ini` path. #### Any platform specific information? #### Supported testbed topology if it's a new test case? ### Documentation Signed-off-by: Sonic Build Admin --- .../roles/fanout/library/port_config_gen.py | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/ansible/roles/fanout/library/port_config_gen.py b/ansible/roles/fanout/library/port_config_gen.py index cff44ceaff..a085ee73b2 100644 --- a/ansible/roles/fanout/library/port_config_gen.py +++ b/ansible/roles/fanout/library/port_config_gen.py @@ -1,5 +1,6 @@ #!/usr/bin/python """Generate the port config for the fanout device.""" +import json import os import re import shutil @@ -13,6 +14,11 @@ from natsort import natsorted from ansible.module_utils.basic import AnsibleModule +try: + from portconfig import parse_platform_json_file +except ImportError: + parse_platform_json_file = None + DOCUMENTATION = """ --- @@ -69,6 +75,9 @@ class PortConfigGenerator(object): SONIC_VERSION_FILE = "/etc/sonic/sonic_version.yml" HWSKU_DIR_PREFIX = "/usr/share/sonic/device/" PORT_CONF_FILENAME = "port_config.ini" + PLATFORM_JSON = "platform.json" + HWSKU_JSON = "hwsku.json" + INTF_KEY = "interfaces" PORT_ALIAS_PATTERNS = ( re.compile(r"^etp(?P\d+)(?P[a-d]?)"), re.compile(r"^Ethernet(?P\d+)(/)?(?(2)(?P[1-4]+))") @@ -169,6 +178,19 @@ def _read_from_port_config(filepath): port_config[values[alias_index]] = dict(zip(keys, values)) return port_config + @staticmethod + def _read_from_json(platform_json_path, hwsku_json_path): + """Parse platform.json + hwsku.json and return the same alias-keyed dict + format as _read_from_port_config().""" + (ports, _, _) = parse_platform_json_file(hwsku_json_path, platform_json_path) + port_config = {} + for name, data in ports.items(): + alias = data.get('alias', name) + entry = dict(data) + entry['name'] = name + port_config[alias] = entry + return port_config + @staticmethod def _prettify(xml_elem): """Output a xml element with indentation.""" @@ -176,6 +198,29 @@ def _prettify(xml_elem): reparsed_output = minidom.parseString(xml_output) return reparsed_output.toprettyxml(indent=" ") + def _read_hwsku_port_config(self, hwsku_name): + """Read port config for a given hwsku, preferring platform.json + hwsku.json + over port_config.ini when both JSON files are present and valid.""" + hwsku_dir = os.path.join(self.HWSKU_DIR_PREFIX, hwsku_name) + hwsku_json_path = os.path.join(hwsku_dir, self.HWSKU_JSON) + + if parse_platform_json_file is not None and os.path.isfile(hwsku_json_path): + platform_json_path = os.path.join(self.HWSKU_DIR_PREFIX, self.PLATFORM_JSON) + if os.path.isfile(platform_json_path): + try: + with open(platform_json_path) as f: + platform_data = json.load(f) + interfaces = platform_data.get(self.INTF_KEY, None) + if interfaces is not None and len(interfaces) > 0: + return self._read_from_json(platform_json_path, hwsku_json_path) + except (json.JSONDecodeError, ValueError, KeyError): + # platform.json is unreadable or malformed; + # fall through to port_config.ini. + pass + + ini_path = os.path.join(hwsku_dir, self.PORT_CONF_FILENAME) + return self._read_from_port_config(ini_path) + def init_platform(self): self.fanout_asic_type = self._get_asic_type() self.fanout_platform = self._get_platform() @@ -204,8 +249,7 @@ def init_port_config(self): elif self.fanout_hwsku_type == "dynamic": # fill missing ports in device_conn from the default port config file - default_hwsku_port_config = self._read_from_port_config(os.path.join( - self.HWSKU_DIR_PREFIX, self.platform_default_hwsku, self.PORT_CONF_FILENAME)) + default_hwsku_port_config = self._read_hwsku_port_config(self.platform_default_hwsku) default_hwsku_port_index_to_port_config = { self._parse_interface_alias(port_alias)[0]: port_config for port_alias, port_config in default_hwsku_port_config.items() @@ -253,8 +297,7 @@ def init_port_config(self): if "broadcom" in self.fanout_asic_type: self._use_flex_bcm_config() - hwsku_port_config = self._read_from_port_config(os.path.join( - self.HWSKU_DIR_PREFIX, self.fanout_hwsku, self.PORT_CONF_FILENAME)) + hwsku_port_config = self._read_hwsku_port_config(self.fanout_hwsku) self.fanout_port_config = self.fanout_connections.copy() fanout_port_name_to_alias_map = dict(